From d0084345afeb6ff15cd447ad5706d01e5a70779c Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Mon, 27 Oct 2025 19:07:20 +0000 Subject: [PATCH 01/63] Implemented class to handle access permissions for options --- CMakeLists.txt | 2 + include/permissions.hxx | 150 ++++++++++++++++++++++ src/permissions.cxx | 98 ++++++++++++++ tests/unit/test_permissions.cxx | 219 ++++++++++++++++++++++++++++++++ 4 files changed, 469 insertions(+) create mode 100644 include/permissions.hxx create mode 100644 src/permissions.cxx create mode 100644 tests/unit/test_permissions.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 02dee5699..de8a856bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,6 +93,7 @@ set(HERMES_SOURCES src/noflow_boundary.cxx src/neutral_parallel_diffusion.cxx src/neutral_boundary.cxx + src/permissions.cxx src/polarisation_drift.cxx src/solkit_neutral_parallel_diffusion.cxx src/hydrogen_charge_exchange.cxx @@ -144,6 +145,7 @@ set(HERMES_SOURCES include/neutral_parallel_diffusion.hxx include/solkit_neutral_parallel_diffusion.hxx include/noflow_boundary.hxx + include/permissions.hxx include/polarisation_drift.hxx include/quasineutral.hxx include/reaction.hxx diff --git a/include/permissions.hxx b/include/permissions.hxx new file mode 100644 index 000000000..ce6368c76 --- /dev/null +++ b/include/permissions.hxx @@ -0,0 +1,150 @@ +#pragma once +#include +#include +#include +#include +#include + +/// Class to store information on whether particular variables an be +/// read from and/or written to. These permissions can apply on +/// particular regions of the domain. +class Permissions { +public: + /// Ways in which someone is allowed to access the variable, with + /// increasing levels of rights. "Final" refers to the last time + /// anyone is allowed to write to the variable. + enum PermissionTypes { None=-1, Read, Write, Final, PERMISSION_TYPES_END }; + + /// The regions of the domain to which a particular permission + /// apply. These are designed to be used as bit-flags. + enum Regions { Nowhere = 0, Interior = 1 << 0, Boundaries = 1 << 1, AllRegions = Interior | Boundaries }; + + /// Data type for storing the regions of a variable which have a + /// particular level of permission. Some examples can be seen below: + /// + /// AccessRights read_only = { AllRegions, Nowhere, Nowhere } + /// write_boundaries = { Nowhere, Boundaries, Nowhere } + /// read_and_write_everywhere = { AllReginos, AllRegions, Nowhere } + /// final_write_boundaries_read_interior = { Interior, Nowhere, Boundaries}; + /// + using AccessRights = std::array; + + Permissions() = default; + Permissions(Permissions& other) = default; + Permissions(Permissions&& other) = default; + + /// Create permission from an initialiser list. Each item in the + /// initialiser list should be a pair made up of the name of a + /// variable stored in an Options object (with a colon separating + /// section names) and an array describing which regions of the + /// domain each level of permission is applied to. The order of the + /// permissions in the array is: read, write, final write. Note that + /// higher permissions are taken to imply all lower permissions. See + /// the examples below. + /// + /// Permissions example({ + /// // Read permission for atomic mass + /// {"species:he:AA", {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, + /// // Read permissions for density + /// {"species:he:density", {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, + /// // Read and write permissions for pressure in the interior region + /// {"species:he:pressure", {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, + /// // Set the final value for collision frequency + /// {"species:he:collision_frequency", {Permissions::Nowhere, Permissions::Nowhere, Permissions::AllRegions}} + /// }); + /// + /// If a variable is not included in the initialiser list then it is + /// assumed there are no access rights. If a section name appears in + /// the list then those permissions apply to all children of that + /// section. The section name must end in a colon (e.g., + /// "species:he:"). If an additional entry is present for somethign + /// located in that section, then the more specific entry takes + /// precidences. + /// + /// Placeholders can be used in variable names by surrounding a + /// label with curly braces. Multiple values can then be substituted + /// for this placeholder using the Permissions::substitute + /// method. For example, if you wanted to read the collision + /// frequency for every species you would write: + /// + /// Permissions example2({ + /// {"species:{name}:collision_frequency", {Permissions::AllRegions, + /// Permissions::Nowhere, Permissions::Nowhere}} + /// }); + /// example2.substitute("name", {"he+", "d+", "e", "d", "he"}); + /// + Permissions(std::initializer_list> data); + + /// Set the level of access for the various regions of the + /// variable. This uses the same logic as the constructor. For + /// example, to indicate that the density of helium is readable + /// everywhere but only writeable in the interior, you would use + /// + /// permissions.setAccess("species:he:density", + /// {Permissions::AllRegions, Permissions::Interior, Permissions::Nowhere}) + /// + /// or, equivalently, + /// + /// permissions.setAccess("species:he:density", + /// {Permissions::Boundary, Permissions::Interior, Permissions::Nowhere}); + /// + /// As in the constructor, if the variable name is just a section in + /// an Options object then the permissions apply to all children of + /// that section. Placeholder names can also be used. + void setAccess(const std::string& variable, const AccessRights& rights); + + /// Replace a placeholder in the names of variables stored in this + /// object. This is useful if you need to access the same variable + /// for multiple species. For example, the following code gives + /// permission to read the density and write the collision frequency + /// for every species. + /// + /// Permissions example({ + /// {"species:{name}:density", {Permissions::AllRegions, Permissions::Nowhere, + /// Permissions::Nowhere}}, + /// {"species:{name}:collision_frequency", {Permissions::Nowhere, + /// Permissions::AllRegions, Permissions::Nowhere}}, + /// }); + /// example.substitute("name", {"d", "d+", "t", "t+", "he", "he+", "c", "c+", "e"}); + /// + void substitute(const std::string& label, const std::vector& substitutions); + + /// Check whether users are allowed to access this variable to the + /// given permission level, in the given region. + bool canAccess(const std::string& variable, PermissionTypes permission = Read, Regions region = AllRegions) const; + + /// Get the highest permission level with which the given variable + /// can be accessed in the given region. + PermissionTypes getHighestPermission(const std::string& variable, Regions region = AllRegions) const; + + /// Get a list of variables and regions for which there is the + /// specified level of permission to access. If ``highestOnly`` is + /// true then it will only include variables/regions for which this + /// is the highest permission. + /// + /// Permissions example({"test", {Permissions::AllRegions, Permissions::AllRegions, Permissions:Nowhere}}); + /// // Print variables which can be read + /// for (const auto [varname, region] : example.getVariablesWithPermission(Permissions::Read, false)) + /// std::cout << "Variable name: " << varname << ", Region ID: " << region << "\n"; + /// // Print variables which can only be read (not written) + /// for (const auto [varname, region] : example.getVariablesWithPermission(Permissions::Read, true)) + /// std::cout << "Variable name: " << varname << ", Region ID: " << region << "\n"; + /// + /// The above code would write a line of output from the first + /// for-loop, but not the second. + std::vector> getVariablesWithPermission(PermissionTypes permission, bool highestOnly = true) const; + +private: + /// Returns the access rights for the most specific entry in this + /// object which matches the variable name. If there are no matching + /// entries then the result will indicate no access rights. + AccessRights bestMatchRights(const std::string& variable) const; + + /// Return a set of access rights where the lower permissions have + /// been updated so that they reflect higher permissions (e.g., read + /// permission will be set in all cases where write permission was + /// set). + static AccessRights applyLowerPermissions(const AccessRights & rights); + + std::map variable_permissions; +}; diff --git a/src/permissions.cxx b/src/permissions.cxx new file mode 100644 index 000000000..172098ca3 --- /dev/null +++ b/src/permissions.cxx @@ -0,0 +1,98 @@ +#include "../include/permissions.hxx" + +Permissions::Permissions(std::initializer_list> data) : + variable_permissions() { + for (const auto& [varname, access] : data) { + setAccess(varname, access); + } +} + +void Permissions::setAccess(const std::string& variable, const AccessRights& rights) { + + variable_permissions[variable] = applyLowerPermissions(rights); +} + +std::string replaceAll(const std::string& str, const std::string& from, const std::string& to) { + std::string result = str; + if(from.empty()) return result; + size_t start_pos = 0; + while((start_pos = result.find(from, start_pos)) != std::string::npos) { + result.replace(start_pos, from.length(), to); + start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx' + } + return result; +} + +void Permissions::substitute(const std::string& label, + const std::vector& substitutions) { + for (const auto [varname, access] : variable_permissions) { + const std::string pattern = "{" + label + "}"; + if (varname.find(pattern) == std::string::npos) continue; + variable_permissions.erase(varname); + for (const std::string& val : substitutions) { + variable_permissions[replaceAll(varname, pattern, val)] = access; + } + } +} + +Permissions::AccessRights Permissions::bestMatchRights(const std::string& variable) const { + Permissions::AccessRights best_candidate = {Permissions::Nowhere, Permissions::Nowhere, Permissions::Nowhere}; + int max_len = 0; + for (const auto& [varname, rights] : variable_permissions) { + if (varname == variable) { + return rights; + } + if (varname.back() == ':' and varname.size() > max_len + and variable.find(varname) == 0) { + max_len = varname.size(); + best_candidate = rights; + } + } + return best_candidate; +} + +bool Permissions::canAccess(const std::string& variable, + PermissionTypes permission, Regions region) const { + return (bestMatchRights(variable)[permission] & region) == region; +} + +Permissions::PermissionTypes Permissions::getHighestPermission(const std::string & variable, Permissions::Regions region) const { + if (region == Nowhere) return None; + AccessRights rights = bestMatchRights(variable); + int i = Read; + while (i < PERMISSION_TYPES_END and (rights[i] & region) == region) { + i++; + } + return static_cast(i - 1); +} + +std::vector> +Permissions::getVariablesWithPermission(PermissionTypes permission, + bool highestOnly) const { + std::vector> result; + if (highestOnly and permission < PERMISSION_TYPES_END - 1) { + for (const auto& [varname, rights] : variable_permissions) { + auto regions = rights[permission]; + auto perm_in_regions = static_cast(rights[permission] & ~rights[permission+1]); + if (perm_in_regions != Nowhere) result.emplace_back(varname, perm_in_regions); + } + } else { + for (const auto& [varname, rights] : variable_permissions) { + auto regions = rights[permission]; + if (regions != Nowhere) result.emplace_back(varname, regions); + } + } + return result; +} + +Permissions::AccessRights Permissions::applyLowerPermissions(const AccessRights& rights) { + AccessRights result(rights); + for (int i = Read; i < PERMISSION_TYPES_END; i++) { + result[i] = rights[i]; + // Higher permissions imply lower permissions + for (int j = Read; j < i; j++) { + result[j] = static_cast(result[j] | rights[i]); + } + } + return result; +} diff --git a/tests/unit/test_permissions.cxx b/tests/unit/test_permissions.cxx new file mode 100644 index 000000000..1c68c3c00 --- /dev/null +++ b/tests/unit/test_permissions.cxx @@ -0,0 +1,219 @@ +#include "gtest/gtest.h" + +#include "../include/permissions.hxx" + +TEST(PermissionsTests, TestCanAccess) { + Permissions example( + {{"species:he:density", + {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, + // Read and write permissions for pressure in the interior region + {"species:he:pressure", + {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, + // Set the final value for collision frequency + {"species:he:collision_frequency", + {Permissions::Nowhere, Permissions::Nowhere, Permissions::AllRegions}}, + // Only allow reading of boundary velocity + {"species:he:velocity", + {Permissions::Boundaries, Permissions::Nowhere, Permissions::Nowhere}}, + {"species:d:", {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, + {"species:d:pressure", {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, + {"species:d:collision_frequencies:", {Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere}}, + }); + + // Check whether we have read permission for the variables across the entire domain + EXPECT_TRUE(example.canAccess("species:he:density")); + EXPECT_FALSE(example.canAccess("species:he:pressure")); + EXPECT_TRUE(example.canAccess("species:he:collision_frequency")); + EXPECT_FALSE(example.canAccess("species:he:velocity")); + EXPECT_FALSE(example.canAccess("unset")); + + // Check whether we have write permission for the variables across the entire domain + EXPECT_FALSE(example.canAccess("species:he:density", Permissions::Write)); + EXPECT_FALSE(example.canAccess("species:he:pressure", Permissions::Write)); + EXPECT_TRUE(example.canAccess("species:he:collision_frequency", Permissions::Write)); + EXPECT_FALSE(example.canAccess("species:he:velocity", Permissions::Write)); + EXPECT_FALSE(example.canAccess("unset", Permissions::Write)); + + // Check whether we have read permission at the boundaries + EXPECT_TRUE(example.canAccess("species:he:density", Permissions::Read, Permissions::Boundaries)); + EXPECT_FALSE(example.canAccess("species:he:pressure", Permissions::Read, Permissions::Boundaries)); + EXPECT_TRUE(example.canAccess("species:he:collision_frequency", Permissions::Read, Permissions::Boundaries)); + EXPECT_TRUE(example.canAccess("species:he:velocity", Permissions::Read, Permissions::Boundaries)); + EXPECT_FALSE(example.canAccess("unset", Permissions::Read, Permissions::Boundaries)); + + // Check permissions set for whole sections + EXPECT_FALSE(example.canAccess("species:d:pressure")); + EXPECT_FALSE(example.canAccess("species:d:pressure", Permissions::Write)); + EXPECT_TRUE(example.canAccess("species:d:pressure", Permissions::Write, Permissions::Interior)); + EXPECT_TRUE(example.canAccess("species:d:velocity")); + EXPECT_FALSE(example.canAccess("species:d:velocity", Permissions::Write)); + EXPECT_TRUE(example.canAccess("species:d:velocity", Permissions::Read, Permissions::Boundaries)); + EXPECT_FALSE(example.canAccess("species:d:collision_frequencies:d_d_coll")); + EXPECT_FALSE(example.canAccess("species:d:collision_frequencies:d_d_coll", Permissions::Write)); + EXPECT_TRUE(example.canAccess("species:d:collision_frequencies:d_d_coll", Permissions::Write, Permissions::Boundaries)); +} + + +TEST(PermissionsTests, TestGetHighestPermission) { + Permissions example( + {{"species:he:density", + {Permissions::AllRegions, Permissions::Nowhere, Permissions::Boundaries}}, + // Read and write permissions for pressure in the interior region + {"species:he:pressure", + {Permissions::Boundaries, Permissions::Interior, Permissions::Nowhere}}, + // Set the final value for collision frequency + {"species:he:collision_frequency", + {Permissions::Interior, Permissions::Nowhere, Permissions::AllRegions}}, + // Only allow reading of boundary velocity + {"species:he:velocity", + {Permissions::Boundaries, Permissions::Nowhere, Permissions::Nowhere}}, + {"species:d:", {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, + {"species:d:pressure", {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, + {"species:d:collision_frequencies:", {Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere}}, + }); + + // Get the highest permission that covers the entire domain + EXPECT_EQ(example.getHighestPermission("species:he:density"), Permissions::Read); + EXPECT_EQ(example.getHighestPermission("species:he:pressure"), Permissions::Read); + EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency"), Permissions::Final); + EXPECT_EQ(example.getHighestPermission("species:he:velocity"), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:d:pressure"), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:d:velocity"), Permissions::Read); + EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll"), Permissions::None); + EXPECT_EQ(example.getHighestPermission("unset"), Permissions::None); + + // Get the highest permission on the boundaries + EXPECT_EQ(example.getHighestPermission("species:he:density", Permissions::Boundaries), Permissions::Final); + EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Boundaries), Permissions::Read); + EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency", Permissions::Boundaries), Permissions::Final); + EXPECT_EQ(example.getHighestPermission("species:he:velocity", Permissions::Boundaries), Permissions::Read); + EXPECT_EQ(example.getHighestPermission("species:d:pressure", Permissions::Boundaries), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:d:velocity", Permissions::Boundaries), Permissions::Read); + EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll", Permissions::Boundaries), Permissions::Write); + EXPECT_EQ(example.getHighestPermission("unset", Permissions::Boundaries), Permissions::None); + + // Get the highest permission on the interior + EXPECT_EQ(example.getHighestPermission("species:he:density", Permissions::Interior), Permissions::Read); + EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Interior), Permissions::Write); + EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency", Permissions::Interior), Permissions::Final); + EXPECT_EQ(example.getHighestPermission("species:he:velocity", Permissions::Interior), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:d:pressure", Permissions::Interior), Permissions::Write); + EXPECT_EQ(example.getHighestPermission("species:d:velocity", Permissions::Interior), Permissions::Read); + EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll", Permissions::Interior), Permissions::None); + EXPECT_EQ(example.getHighestPermission("unset", Permissions::Interior), Permissions::None); + + // Check the permission for the "Nowhere" region is always "None" + EXPECT_EQ(example.getHighestPermission("species:he:density", Permissions::Nowhere), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Nowhere), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency", Permissions::Nowhere), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:he:velocity", Permissions::Nowhere), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:d:pressure", Permissions::Nowhere), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:d:velocity", Permissions::Nowhere), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll", Permissions::Nowhere), Permissions::None); + EXPECT_EQ(example.getHighestPermission("unset", Permissions::Nowhere), Permissions::None); +} + +TEST(PermissionsTests, TestSetAccess) { + Permissions example({ + {"species:he:density", + {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, + // Read and write permissions for pressure in the interior region + {"species:he:pressure", + {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, + }); + + EXPECT_EQ(example.getHighestPermission("species:he:density"), Permissions::Read); + example.setAccess("species:he:density", { + Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere}); + EXPECT_EQ(example.getHighestPermission("species:he:density"), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:he:density", Permissions::Boundaries), Permissions::Write); + + EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Interior), Permissions::Write); + EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::AllRegions), Permissions::None); + example.setAccess("species:he:pressure", {Permissions::AllRegions, Permissions::Nowhere, + Permissions::Nowhere}); + EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Interior), Permissions::Read); + EXPECT_EQ(example.getHighestPermission("species:he:pressure"), Permissions::Read); + + EXPECT_EQ(example.getHighestPermission("unset", Permissions::Interior), Permissions::None); + EXPECT_EQ(example.getHighestPermission("unset", Permissions::Boundaries), Permissions::None); + example.setAccess("unset", {Permissions::Interior, Permissions::Nowhere, Permissions::Boundaries}); + EXPECT_EQ(example.getHighestPermission("unset", Permissions::AllRegions), Permissions::Read); + EXPECT_EQ(example.getHighestPermission("unset", Permissions::Boundaries), Permissions::Final); +} + +auto make_permissions = std::make_pair; + +std::set> +make_set(const std::vector> & vec) { + return std::set>(vec.begin(), vec.end()); +} + +TEST(PermissionsTests, TestGetVariablesWithPermissions) { + Permissions example( + {{"species:he:density", + {Permissions::AllRegions, Permissions::Nowhere, Permissions::Boundaries}}, + // Read and write permissions for pressure in the interior region + {"species:he:pressure", + {Permissions::Boundaries, Permissions::Interior, Permissions::Nowhere}}, + // Set the final value for collision frequency + {"species:he:collision_frequency", + {Permissions::Interior, Permissions::Nowhere, Permissions::AllRegions}}, + // Only allow reading of boundary velocity + {"species:he:velocity", + {Permissions::Boundaries, Permissions::Nowhere, Permissions::Nowhere}}}); + + auto read_only = make_set(example.getVariablesWithPermission(Permissions::Read)); + EXPECT_EQ(read_only.size(), 3); + EXPECT_EQ(read_only.count(make_permissions("species:he:density", Permissions::Interior)), 1); + EXPECT_EQ(read_only.count(make_permissions("species:he:pressure", Permissions::Boundaries)), 1); + EXPECT_EQ(read_only.count(make_permissions("species:he:velocity", Permissions::Boundaries)), 1); + + auto readable = make_set(example.getVariablesWithPermission(Permissions::Read, false)); + EXPECT_EQ(readable.size(), 4); + EXPECT_EQ(readable.count(make_permissions("species:he:density", Permissions::AllRegions)), 1); + EXPECT_EQ(readable.count(make_permissions("species:he:pressure", Permissions::AllRegions)), 1); + EXPECT_EQ(readable.count(make_permissions("species:he:collision_frequency", Permissions::AllRegions)), 1); + EXPECT_EQ(readable.count(make_permissions("species:he:velocity", Permissions::Boundaries)), 1); + + auto write_nonfinal = make_set(example.getVariablesWithPermission(Permissions::Write, true)); + EXPECT_EQ(write_nonfinal.size(), 1); + EXPECT_EQ(write_nonfinal.count(make_permissions("species:he:pressure", Permissions::Interior)), 1); + + auto writable = make_set(example.getVariablesWithPermission(Permissions::Write, false)); + EXPECT_EQ(writable.size(), 3); + EXPECT_EQ(writable.count(make_permissions("species:he:density", Permissions::Boundaries)), 1); + EXPECT_EQ(writable.count(make_permissions("species:he:pressure", Permissions::Interior)), 1); + EXPECT_EQ(writable.count(make_permissions("species:he:collision_frequency", Permissions::AllRegions)), 1); + + auto final_write = make_set(example.getVariablesWithPermission(Permissions::Final)); + EXPECT_EQ(final_write.size(), 2); + EXPECT_EQ(final_write.count(make_permissions("species:he:density", Permissions::Boundaries)), 1); + EXPECT_EQ(final_write.count(make_permissions("species:he:collision_frequency", Permissions::AllRegions)), 1); +} + +TEST(PermissionsTests, TestSubstitute) { + Permissions example({{"species:{s1}:collision_frequencies:{s1}_{s2}_coll", + {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}}); + + example.setAccess("{var}", {Permissions::Nowhere, Permissions::Interior}); + + example.substitute("s1", {"e", "d+"}); + example.substitute("s2", {"e", "d+"}); + + auto readable = make_set(example.getVariablesWithPermission(Permissions::Read, false)); + EXPECT_EQ(readable.size(), 5); + EXPECT_EQ(readable.count(make_permissions("{var}", Permissions::Interior)), 1); + EXPECT_EQ(readable.count(make_permissions("species:e:collision_frequencies:e_e_coll", Permissions::AllRegions)), 1); + EXPECT_EQ(readable.count(make_permissions("species:e:collision_frequencies:e_d+_coll", Permissions::AllRegions)), 1); + EXPECT_EQ(readable.count(make_permissions("species:d+:collision_frequencies:d+_e_coll", Permissions::AllRegions)), 1); + EXPECT_EQ(readable.count(make_permissions("species:d+:collision_frequencies:d+_d+_coll", Permissions::AllRegions)), 1); + + example.substitute("var", {"a", "b", "c"}); + + auto writable = make_set(example.getVariablesWithPermission(Permissions::Write)); + EXPECT_EQ(writable.size(), 3); + EXPECT_EQ(writable.count(make_permissions("a", Permissions::Interior)), 1); + EXPECT_EQ(writable.count(make_permissions("b", Permissions::Interior)), 1); + EXPECT_EQ(writable.count(make_permissions("c", Permissions::Interior)), 1); +} From 03087f18b5efe7a6ec59dad48749c5f38fb779b5 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Tue, 28 Oct 2025 18:50:38 +0000 Subject: [PATCH 02/63] Add wrapper class to control access to Options --- CMakeLists.txt | 2 + include/guarded_options.hxx | 64 ++++++++++ include/permissions.hxx | 18 ++- src/guarded_options.cxx | 62 +++++++++ src/permissions.cxx | 30 +++-- tests/unit/test_guarded_options.cxx | 190 ++++++++++++++++++++++++++++ tests/unit/test_permissions.cxx | 134 +++++++++++--------- 7 files changed, 421 insertions(+), 79 deletions(-) create mode 100644 include/guarded_options.hxx create mode 100644 src/guarded_options.cxx create mode 100644 tests/unit/test_guarded_options.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index de8a856bb..42d4d70c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,6 +73,7 @@ set(HERMES_SOURCES src/evolve_energy.cxx src/evolve_pressure.cxx src/evolve_momentum.cxx + src/guarded_options.cxx src/isothermal.cxx src/quasineutral.cxx src/diamagnetic_drift.cxx @@ -134,6 +135,7 @@ set(HERMES_SOURCES include/fixed_density.hxx include/fixed_fraction_ions.hxx include/fixed_velocity.hxx + include/guarded_options.hxx include/neutral_full_velocity.hxx include/hermes_utils.hxx include/hydrogen_charge_exchange.hxx diff --git a/include/guarded_options.hxx b/include/guarded_options.hxx new file mode 100644 index 000000000..37825fafa --- /dev/null +++ b/include/guarded_options.hxx @@ -0,0 +1,64 @@ +#pragma once + +#include + +#include + +#include "permissions.hxx" + +/// A wrapper class around BOUT++ Options objects. It uses access +/// right data, stored using a Permissions object, to control reading +/// from and writing to the underlying data. +class GuardedOptions { +public: + GuardedOptions() = delete; + /// Create a guarded options object which applies the specified + /// permissions to the underlying options object. Note that the + /// variable names used in the Permissions object must always be the + /// full names, relative to the highest-level of the Options + /// hierarchy. + GuardedOptions(Options* options, Permissions* permissions) + : options(options), permissions(permissions), + unread_variables(std::make_shared>()), + unwritten_variables( + std::make_shared>()) { + if (permissions != nullptr) { + *unread_variables = permissions->getVariablesWithPermission(Permissions::Read); + *unwritten_variables = + permissions->getVariablesWithPermission(Permissions::Write, false); + } + } + + /// Get a subsection or value. The result will also be wrapped in a + /// GuardedOptions object, with the same permissions as this one. + GuardedOptions operator[](const std::string& name); + GuardedOptions operator[](const char* name) { return (*this)[std::string(name)]; } + + /// Get read-only access to the underlying Options object. Throws + /// BoutException if there is not read-permission for this object. + const Options& get(Permissions::Regions region = Permissions::AllRegions); + /// Get read-write access to the underlying Options object. Throws + /// BoutException if there is not write-permission for this object. + Options& getWritable(Permissions::Regions region = Permissions::AllRegions); + + /// Returns a list of variables with read-only permission but which + /// have not been accessed using the `get()` method. + std::map unreadItems() const; + + /// Returns a list of variables with read-write permission but which + /// have not been accessed using the `getWritable()` method. + std::map unwrittenItems() const; + +private: + Options* options; + Permissions* permissions; + std::shared_ptr> unread_variables, + unwritten_variables; + + GuardedOptions( + Options* options, Permissions* permissions, + std::shared_ptr> unread_vars, + std::shared_ptr> unwritten_vars) + : options(options), permissions(permissions), unread_variables(unread_vars), + unwritten_variables(unwritten_vars) {} +}; diff --git a/include/permissions.hxx b/include/permissions.hxx index ce6368c76..3906e381f 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -110,14 +110,17 @@ public: void substitute(const std::string& label, const std::vector& substitutions); /// Check whether users are allowed to access this variable to the - /// given permission level, in the given region. - bool canAccess(const std::string& variable, PermissionTypes permission = Read, Regions region = AllRegions) const; + /// given permission level, in the given region. The secon item + /// returned indicates the name of the variable or section from + /// which the access rights are derived. If there is no matching + /// section then it will be an empty string. + std::pair canAccess(const std::string& variable, PermissionTypes permission = Read, Regions region = AllRegions) const; /// Get the highest permission level with which the given variable /// can be accessed in the given region. PermissionTypes getHighestPermission(const std::string& variable, Regions region = AllRegions) const; - /// Get a list of variables and regions for which there is the + /// Get a set of variables and regions for which there is the /// specified level of permission to access. If ``highestOnly`` is /// true then it will only include variables/regions for which this /// is the highest permission. @@ -132,13 +135,16 @@ public: /// /// The above code would write a line of output from the first /// for-loop, but not the second. - std::vector> getVariablesWithPermission(PermissionTypes permission, bool highestOnly = true) const; + std::map getVariablesWithPermission(PermissionTypes permission, bool highestOnly = true) const; private: /// Returns the access rights for the most specific entry in this /// object which matches the variable name. If there are no matching - /// entries then the result will indicate no access rights. - AccessRights bestMatchRights(const std::string& variable) const; + /// entries then the result will indicate no access rights. The + /// string indicates the name of the variable from which the access + /// rights were derived. It will be empty if there are no matching + /// entries. + std::pair bestMatchRights(const std::string& variable) const; /// Return a set of access rights where the lower permissions have /// been updated so that they reflect higher permissions (e.g., read diff --git a/src/guarded_options.cxx b/src/guarded_options.cxx new file mode 100644 index 000000000..9b9cf34de --- /dev/null +++ b/src/guarded_options.cxx @@ -0,0 +1,62 @@ +#include + +#include "../include/guarded_options.hxx" + +GuardedOptions GuardedOptions::operator[](const std::string& name) { + if (options == nullptr) + throw BoutException( + "Trying to access GuardedOptions when underlying options are nullptr."); + return GuardedOptions(&(*options)[name], permissions, unread_variables, + unwritten_variables); +} + +void updateAccessRecords(std::map& records, + const std::string& name, Permissions::Regions region) { + if (records.count(name) > 0) { + Permissions::Regions new_region = + static_cast(records[name] & ~region); + if (new_region == Permissions::Nowhere) { + records.erase(name); + } else { + records[name] = new_region; + } + } +} + +const Options& GuardedOptions::get(Permissions::Regions region) { + if (options == nullptr) + throw BoutException( + "Trying to access GuardedOptions when underlying options are nullptr."); + std::string name = options->str(); + if (permissions != nullptr) { + auto [access, varname] = permissions->canAccess(name, Permissions::Read, region); + if (access) { + updateAccessRecords(*unread_variables, varname, region); + return *options; + } + } + throw BoutException(fmt::format("Do not have read permission for {}.", name)); +} + +Options& GuardedOptions::getWritable(Permissions::Regions region) { + if (options == nullptr) + throw BoutException( + "Trying to access GuardedOptions when underlying options are nullptr."); + std::string name = options->str(); + if (permissions != nullptr) { + auto [access, varname] = permissions->canAccess(name, Permissions::Write, region); + if (access) { + updateAccessRecords(*unwritten_variables, varname, region); + return *options; + } + } + throw BoutException(fmt::format("Do not have read permission for {}.", options->str())); +} + +std::map GuardedOptions::unreadItems() const { + return *unread_variables; +} + +std::map GuardedOptions::unwrittenItems() const { + return *unwritten_variables; +} diff --git a/src/permissions.cxx b/src/permissions.cxx index 172098ca3..1eff811d2 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -35,30 +35,36 @@ void Permissions::substitute(const std::string& label, } } -Permissions::AccessRights Permissions::bestMatchRights(const std::string& variable) const { +std::pair Permissions::bestMatchRights(const std::string& variable) const { Permissions::AccessRights best_candidate = {Permissions::Nowhere, Permissions::Nowhere, Permissions::Nowhere}; + std::string best_candidate_name = ""; int max_len = 0; for (const auto& [varname, rights] : variable_permissions) { if (varname == variable) { - return rights; + return {varname, rights}; } - if (varname.back() == ':' and varname.size() > max_len - and variable.find(varname) == 0) { + if (varname.size() > max_len and variable.find(varname + ":") == 0) { max_len = varname.size(); best_candidate = rights; + best_candidate_name = varname; } } - return best_candidate; + return {best_candidate_name, best_candidate}; } -bool Permissions::canAccess(const std::string& variable, +std::pair Permissions::canAccess(const std::string& variable, PermissionTypes permission, Regions region) const { - return (bestMatchRights(variable)[permission] & region) == region; + auto [match_name, match_rights] = bestMatchRights(variable); + if ((match_rights[permission] & region) == region) { + return {true, match_name}; + } else { + return {false, ""}; + } } Permissions::PermissionTypes Permissions::getHighestPermission(const std::string & variable, Permissions::Regions region) const { if (region == Nowhere) return None; - AccessRights rights = bestMatchRights(variable); + AccessRights rights = std::get<1>(bestMatchRights(variable)); int i = Read; while (i < PERMISSION_TYPES_END and (rights[i] & region) == region) { i++; @@ -66,20 +72,20 @@ Permissions::PermissionTypes Permissions::getHighestPermission(const std::string return static_cast(i - 1); } -std::vector> +std::map Permissions::getVariablesWithPermission(PermissionTypes permission, bool highestOnly) const { - std::vector> result; + std::map result; if (highestOnly and permission < PERMISSION_TYPES_END - 1) { for (const auto& [varname, rights] : variable_permissions) { auto regions = rights[permission]; auto perm_in_regions = static_cast(rights[permission] & ~rights[permission+1]); - if (perm_in_regions != Nowhere) result.emplace_back(varname, perm_in_regions); + if (perm_in_regions != Nowhere) result.emplace(varname, perm_in_regions); } } else { for (const auto& [varname, rights] : variable_permissions) { auto regions = rights[permission]; - if (regions != Nowhere) result.emplace_back(varname, regions); + if (regions != Nowhere) result.emplace(varname, regions); } } return result; diff --git a/tests/unit/test_guarded_options.cxx b/tests/unit/test_guarded_options.cxx new file mode 100644 index 000000000..75b737f5d --- /dev/null +++ b/tests/unit/test_guarded_options.cxx @@ -0,0 +1,190 @@ +#include "../include/guarded_options.hxx" +#include "gtest/gtest.h" + +class GuardedOptionsTests : public testing::Test { +protected: + GuardedOptionsTests() + : permissions( + {{"species:he:density", + {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, + {"species:he:pressure", + {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, + {"species:he:collision_frequency", + {Permissions::Nowhere, Permissions::Nowhere, Permissions::AllRegions}}, + {"species:he:velocity", + {Permissions::Boundaries, Permissions::Nowhere, Permissions::Nowhere}}, + {"species:d", + {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, + {"species:d:pressure", + {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, + {"species:d:collision_frequencies", + {Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere}}}), + opts({{"species", + {{"he", + {{"temperature", 0}, {"density", 1}, {"pressure", 2}, {"velocity", 4}}}, + {"d", + {{"pressure", 5}, + {"velocity", 6}, + {"collision_frequencies", {{"d_d_coll", 7}, {"d_he_coll", 8}}}}}}}}), + guarded_opts(&opts, &permissions){}; + + Permissions permissions; + Options opts; + GuardedOptions guarded_opts; +}; + +TEST_F(GuardedOptionsTests, TestGet) { + EXPECT_EQ(guarded_opts["species:he:density"].get(), 1); + EXPECT_EQ(guarded_opts["species:he:density"].get(Permissions::Boundaries), 1); + EXPECT_EQ(guarded_opts["species:he:pressure"].get(Permissions::Interior), 2); + EXPECT_FALSE(guarded_opts["species:he:collision_frequency"].get().isSet()); + EXPECT_EQ(guarded_opts["species"]["he"]["velocity"].get(Permissions::Boundaries), 4); + EXPECT_EQ(guarded_opts["species"]["d"]["pressure"].get(Permissions::Interior), 5); + EXPECT_EQ(guarded_opts["species"]["d"]["velocity"].get(Permissions::AllRegions), 6); + EXPECT_EQ(guarded_opts["species:d:collision_frequencies"]["d_d_coll"].get( + Permissions::Boundaries), + 7); + EXPECT_EQ(guarded_opts["species:d:collision_frequencies"].get( + Permissions::Boundaries)["d_he_coll"], + 8); + EXPECT_FALSE(guarded_opts["species:d:collision_frequencies:d_t+_cx"] + .get(Permissions::Boundaries) + .isSet()); +} + +TEST_F(GuardedOptionsTests, TestGetException) { + EXPECT_THROW(guarded_opts["species"]["he"]["temperature"].get(), BoutException); + EXPECT_THROW(guarded_opts["species:he:pressure"].get(), BoutException); + EXPECT_THROW(guarded_opts["species"]["he"]["velocity"].get(Permissions::Interior), + BoutException); + EXPECT_THROW(guarded_opts["species:d:collision_frequencies"].get(Permissions::Interior), + BoutException); + EXPECT_THROW(guarded_opts["species:d:pressure"].get(Permissions::Boundaries), + BoutException); + EXPECT_THROW(guarded_opts["no_permission"].get(), BoutException); + EXPECT_THROW(guarded_opts["species:d+:velocity"].get(), BoutException); +} + +TEST_F(GuardedOptionsTests, TestGetWritable) { + auto& he_pressure = + guarded_opts["species:he:pressure"].getWritable(Permissions::Interior); + EXPECT_EQ(he_pressure, 2); + EXPECT_EQ(opts["species"]["he"]["pressure"], 2); + he_pressure.force(10); + EXPECT_EQ(he_pressure, 10); + EXPECT_EQ(opts["species"]["he"]["pressure"], 10); + + auto& he_freq = guarded_opts["species"]["he"]["collision_frequency"].getWritable(); + EXPECT_FALSE(he_freq.isSet()); + he_freq = 11; + EXPECT_EQ(opts["species"]["he"]["collision_frequency"], 11); + + auto& d_pressure = + guarded_opts["species"]["d"]["pressure"].getWritable(Permissions::Interior); + EXPECT_EQ(opts["species"]["d"]["pressure"], 5); + d_pressure.force(12); + EXPECT_EQ(opts["species"]["d"]["pressure"], 12); + + auto& d_d_coll = guarded_opts["species:d:collision_frequencies:d_d_coll"].getWritable( + Permissions::Boundaries); + EXPECT_EQ(d_d_coll, 7); + d_d_coll.force(13); + EXPECT_EQ(opts["species:d:collision_frequencies:d_d_coll"], 13); + + auto& d_tp_coll = guarded_opts["species:d:collision_frequencies:d_t+_coll"].getWritable( + Permissions::Boundaries); + EXPECT_FALSE(d_tp_coll.isSet()); + d_tp_coll = 14; + EXPECT_EQ(opts["species:d:collision_frequencies:d_t+_coll"], 14); +} + +TEST_F(GuardedOptionsTests, TestGetWritableException) { + EXPECT_THROW(guarded_opts["species"]["he"]["temperature"].getWritable(), BoutException); + EXPECT_THROW(guarded_opts["unset"].getWritable(), BoutException); + EXPECT_THROW(guarded_opts["species:he:density"].getWritable(), BoutException); + EXPECT_THROW(guarded_opts["species:he:density"].getWritable(Permissions::Interior), + BoutException); + EXPECT_THROW(guarded_opts["species:he:density"].getWritable(Permissions::Boundaries), + BoutException); + EXPECT_THROW(guarded_opts["species:he:pressure"].getWritable(), BoutException); + EXPECT_THROW(guarded_opts["species:he:pressure"].getWritable(Permissions::Boundaries), + BoutException); + EXPECT_THROW(guarded_opts["species"]["d"]["velocity"].getWritable(), BoutException); + EXPECT_THROW( + guarded_opts["species"]["d"]["pressure"].getWritable(Permissions::Boundaries), + BoutException); + EXPECT_THROW(guarded_opts["species:d:collision_frequencies:unset"].getWritable(), + BoutException); + EXPECT_THROW(guarded_opts["species:d:collision_frequencies:unset"].getWritable( + Permissions::Interior), + BoutException); + EXPECT_THROW( + guarded_opts["species"]["d"]["pressure_suffix"].getWritable(Permissions::Interior), + BoutException); +} + +TEST_F(GuardedOptionsTests, TestUnreadItems) { + std::map + expected1 = {{"species:he:density", Permissions::AllRegions}, + {"species:he:velocity", Permissions::Boundaries}, + {"species:d", Permissions::AllRegions}}, + expected2 = {{"species:he:density", Permissions::Boundaries}, + {"species:he:velocity", Permissions::Boundaries}, + {"species:d", Permissions::AllRegions}}, + expected3 = {{"species:d", Permissions::AllRegions}}, expected4; + + EXPECT_EQ(guarded_opts.unreadItems(), expected1); + + guarded_opts["species:he:density"].get(Permissions::Interior); + guarded_opts["species:d:pressure"].get(Permissions::Interior); + guarded_opts["species:d:collision_frequencies:d_d_coll"].getWritable( + Permissions::Boundaries); + EXPECT_EQ(guarded_opts.unreadItems(), expected2); + + guarded_opts["species"]["he"]["density"].get(); + guarded_opts["species:he:velocity"].get(Permissions::Boundaries); + EXPECT_EQ(guarded_opts.unreadItems(), expected3); + + guarded_opts["species:d:velocity"].get(); + EXPECT_EQ(guarded_opts.unreadItems(), expected4); +} + +TEST_F(GuardedOptionsTests, TestUnwrittenItems) { + std::map + expected1 = {{"species:he:pressure", Permissions::Interior}, + {"species:he:collision_frequency", Permissions::AllRegions}, + {"species:d:pressure", Permissions::Interior}, + {"species:d:collision_frequencies", Permissions::Boundaries}}, + expected2 = {{"species:he:collision_frequency", Permissions::AllRegions}, + {"species:d:pressure", Permissions::Interior}}, + expected3; + + EXPECT_EQ(guarded_opts.unwrittenItems(), expected1); + + guarded_opts["species:he:pressure"].get(Permissions::Interior); + guarded_opts["species:he:collision_frequency"].get(); + EXPECT_EQ(guarded_opts.unwrittenItems(), expected1); + + guarded_opts["species"]["he"]["pressure"].getWritable(Permissions::Interior); + guarded_opts["species:d:collision_frequencies:d_d_coll"].getWritable( + Permissions::Boundaries); + EXPECT_EQ(guarded_opts.unwrittenItems(), expected2); + + guarded_opts["species:he:collision_frequency"].getWritable(); + guarded_opts["species:d:pressure"].getWritable(Permissions::Interior); + EXPECT_EQ(guarded_opts.unwrittenItems(), expected3); +} + +TEST_F(GuardedOptionsTests, TestNullOptions) { + GuardedOptions null_opts(nullptr, &permissions); + EXPECT_THROW(null_opts["species:he:collision_frequency"], BoutException); + EXPECT_THROW(null_opts.get(), BoutException); + EXPECT_THROW(null_opts.getWritable(), BoutException); +} + +TEST_F(GuardedOptionsTests, TestNullPermissions) { + GuardedOptions null_perms(&opts, nullptr); + GuardedOptions sub_opt = null_perms["species:he:pressure"]; + EXPECT_THROW(sub_opt.get(Permissions::Interior), BoutException); + EXPECT_THROW(sub_opt.getWritable(Permissions::Interior), BoutException); +} diff --git a/tests/unit/test_permissions.cxx b/tests/unit/test_permissions.cxx index 1c68c3c00..e5b50cc12 100644 --- a/tests/unit/test_permissions.cxx +++ b/tests/unit/test_permissions.cxx @@ -2,6 +2,8 @@ #include "../include/permissions.hxx" +auto make_access = std::make_pair; + TEST(PermissionsTests, TestCanAccess) { Permissions example( {{"species:he:density", @@ -15,42 +17,51 @@ TEST(PermissionsTests, TestCanAccess) { // Only allow reading of boundary velocity {"species:he:velocity", {Permissions::Boundaries, Permissions::Nowhere, Permissions::Nowhere}}, - {"species:d:", {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, + {"species:d", {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, {"species:d:pressure", {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, - {"species:d:collision_frequencies:", {Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere}}, + {"species:d:collision_frequencies", {Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere}}, }); + auto no_access = make_access(false, ""); + // Check whether we have read permission for the variables across the entire domain - EXPECT_TRUE(example.canAccess("species:he:density")); - EXPECT_FALSE(example.canAccess("species:he:pressure")); - EXPECT_TRUE(example.canAccess("species:he:collision_frequency")); - EXPECT_FALSE(example.canAccess("species:he:velocity")); - EXPECT_FALSE(example.canAccess("unset")); + EXPECT_EQ(example.canAccess("species:he:density"), make_access(true, "species:he:density")); + EXPECT_EQ(example.canAccess("species:he:pressure"), no_access); + EXPECT_EQ(example.canAccess("species:he:collision_frequency"), make_access(true, "species:he:collision_frequency")); + EXPECT_EQ(example.canAccess("species:he:velocity"), no_access); + EXPECT_EQ(example.canAccess("unset"), no_access); // Check whether we have write permission for the variables across the entire domain - EXPECT_FALSE(example.canAccess("species:he:density", Permissions::Write)); - EXPECT_FALSE(example.canAccess("species:he:pressure", Permissions::Write)); - EXPECT_TRUE(example.canAccess("species:he:collision_frequency", Permissions::Write)); - EXPECT_FALSE(example.canAccess("species:he:velocity", Permissions::Write)); - EXPECT_FALSE(example.canAccess("unset", Permissions::Write)); + EXPECT_EQ(example.canAccess("species:he:density", Permissions::Write), no_access); + EXPECT_EQ(example.canAccess("species:he:pressure", Permissions::Write), no_access); + EXPECT_EQ(example.canAccess("species:he:collision_frequency", Permissions::Write), make_access(true, "species:he:collision_frequency")); + EXPECT_EQ(example.canAccess("species:he:velocity", Permissions::Write), no_access); + EXPECT_EQ(example.canAccess("unset", Permissions::Write), no_access); // Check whether we have read permission at the boundaries - EXPECT_TRUE(example.canAccess("species:he:density", Permissions::Read, Permissions::Boundaries)); - EXPECT_FALSE(example.canAccess("species:he:pressure", Permissions::Read, Permissions::Boundaries)); - EXPECT_TRUE(example.canAccess("species:he:collision_frequency", Permissions::Read, Permissions::Boundaries)); - EXPECT_TRUE(example.canAccess("species:he:velocity", Permissions::Read, Permissions::Boundaries)); - EXPECT_FALSE(example.canAccess("unset", Permissions::Read, Permissions::Boundaries)); + EXPECT_EQ(example.canAccess("species:he:density", Permissions::Read, Permissions::Boundaries), make_access(true, "species:he:density")); + EXPECT_EQ(example.canAccess("species:he:pressure", Permissions::Read, Permissions::Boundaries), no_access); + EXPECT_EQ(example.canAccess("species:he:collision_frequency", Permissions::Read, Permissions::Boundaries), make_access(true, "species:he:collision_frequency")); + EXPECT_EQ(example.canAccess("species:he:velocity", Permissions::Read, Permissions::Boundaries), make_access(true, "species:he:velocity")); + EXPECT_EQ(example.canAccess("unset", Permissions::Read, Permissions::Boundaries), no_access); // Check permissions set for whole sections - EXPECT_FALSE(example.canAccess("species:d:pressure")); - EXPECT_FALSE(example.canAccess("species:d:pressure", Permissions::Write)); - EXPECT_TRUE(example.canAccess("species:d:pressure", Permissions::Write, Permissions::Interior)); - EXPECT_TRUE(example.canAccess("species:d:velocity")); - EXPECT_FALSE(example.canAccess("species:d:velocity", Permissions::Write)); - EXPECT_TRUE(example.canAccess("species:d:velocity", Permissions::Read, Permissions::Boundaries)); - EXPECT_FALSE(example.canAccess("species:d:collision_frequencies:d_d_coll")); - EXPECT_FALSE(example.canAccess("species:d:collision_frequencies:d_d_coll", Permissions::Write)); - EXPECT_TRUE(example.canAccess("species:d:collision_frequencies:d_d_coll", Permissions::Write, Permissions::Boundaries)); + EXPECT_EQ(example.canAccess("species:d:pressure"), no_access); + EXPECT_EQ(example.canAccess("species:d:pressure", Permissions::Write), no_access); + EXPECT_EQ(example.canAccess("species:d:pressure", Permissions::Write, Permissions::Interior), make_access(true, "species:d:pressure")); + EXPECT_EQ(example.canAccess("species:d:velocity"), make_access(true, "species:d")); + EXPECT_EQ(example.canAccess("species:d:velocity", Permissions::Write), no_access); + EXPECT_EQ(example.canAccess("species:d:velocity", Permissions::Read, Permissions::Boundaries), make_access(true, "species:d")); + EXPECT_EQ(example.canAccess("species:d:collision_frequencies:d_d_coll"), no_access); + EXPECT_EQ(example.canAccess("species:d:collision_frequencies:d_d_coll", Permissions::Write), no_access); + EXPECT_EQ(example.canAccess("species:d:collision_frequencies:d_d_coll", Permissions::Write, Permissions::Boundaries), make_access(true, "species:d:collision_frequencies")); + + // Check permissions for a species that might be mistaken for one of + // the sections we've given permissions for + EXPECT_EQ(example.canAccess("species:d+"), no_access); + EXPECT_EQ(example.canAccess("species:d+", Permissions::Write), no_access); + EXPECT_EQ(example.canAccess("species:d+", Permissions::Read, Permissions::Interior), no_access); + EXPECT_EQ(example.canAccess("species:d+", Permissions::Read, Permissions::Boundaries), no_access); } @@ -67,9 +78,9 @@ TEST(PermissionsTests, TestGetHighestPermission) { // Only allow reading of boundary velocity {"species:he:velocity", {Permissions::Boundaries, Permissions::Nowhere, Permissions::Nowhere}}, - {"species:d:", {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, + {"species:d", {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, {"species:d:pressure", {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, - {"species:d:collision_frequencies:", {Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere}}, + {"species:d:collision_frequencies", {Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere}}, }); // Get the highest permission that covers the entire domain @@ -111,6 +122,12 @@ TEST(PermissionsTests, TestGetHighestPermission) { EXPECT_EQ(example.getHighestPermission("species:d:velocity", Permissions::Nowhere), Permissions::None); EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll", Permissions::Nowhere), Permissions::None); EXPECT_EQ(example.getHighestPermission("unset", Permissions::Nowhere), Permissions::None); + + // Check permissions for a species that might be mistaken for one of + // the sections we've given permissions for + EXPECT_EQ(example.getHighestPermission("species:d+"), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:d+", Permissions::Interior), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:d+", Permissions::Boundaries), Permissions::None); } TEST(PermissionsTests, TestSetAccess) { @@ -144,11 +161,6 @@ TEST(PermissionsTests, TestSetAccess) { auto make_permissions = std::make_pair; -std::set> -make_set(const std::vector> & vec) { - return std::set>(vec.begin(), vec.end()); -} - TEST(PermissionsTests, TestGetVariablesWithPermissions) { Permissions example( {{"species:he:density", @@ -163,33 +175,33 @@ TEST(PermissionsTests, TestGetVariablesWithPermissions) { {"species:he:velocity", {Permissions::Boundaries, Permissions::Nowhere, Permissions::Nowhere}}}); - auto read_only = make_set(example.getVariablesWithPermission(Permissions::Read)); + auto read_only = example.getVariablesWithPermission(Permissions::Read); EXPECT_EQ(read_only.size(), 3); - EXPECT_EQ(read_only.count(make_permissions("species:he:density", Permissions::Interior)), 1); - EXPECT_EQ(read_only.count(make_permissions("species:he:pressure", Permissions::Boundaries)), 1); - EXPECT_EQ(read_only.count(make_permissions("species:he:velocity", Permissions::Boundaries)), 1); + EXPECT_EQ(read_only["species:he:density"], Permissions::Interior); + EXPECT_EQ(read_only["species:he:pressure"], Permissions::Boundaries); + EXPECT_EQ(read_only["species:he:velocity"], Permissions::Boundaries); - auto readable = make_set(example.getVariablesWithPermission(Permissions::Read, false)); + auto readable = example.getVariablesWithPermission(Permissions::Read, false); EXPECT_EQ(readable.size(), 4); - EXPECT_EQ(readable.count(make_permissions("species:he:density", Permissions::AllRegions)), 1); - EXPECT_EQ(readable.count(make_permissions("species:he:pressure", Permissions::AllRegions)), 1); - EXPECT_EQ(readable.count(make_permissions("species:he:collision_frequency", Permissions::AllRegions)), 1); - EXPECT_EQ(readable.count(make_permissions("species:he:velocity", Permissions::Boundaries)), 1); + EXPECT_EQ(readable["species:he:density"], Permissions::AllRegions); + EXPECT_EQ(readable["species:he:pressure"], Permissions::AllRegions); + EXPECT_EQ(readable["species:he:collision_frequency"], Permissions::AllRegions); + EXPECT_EQ(readable["species:he:velocity"], Permissions::Boundaries); - auto write_nonfinal = make_set(example.getVariablesWithPermission(Permissions::Write, true)); + auto write_nonfinal = example.getVariablesWithPermission(Permissions::Write, true); EXPECT_EQ(write_nonfinal.size(), 1); - EXPECT_EQ(write_nonfinal.count(make_permissions("species:he:pressure", Permissions::Interior)), 1); + EXPECT_EQ(write_nonfinal["species:he:pressure"], Permissions::Interior); - auto writable = make_set(example.getVariablesWithPermission(Permissions::Write, false)); + auto writable = example.getVariablesWithPermission(Permissions::Write, false); EXPECT_EQ(writable.size(), 3); - EXPECT_EQ(writable.count(make_permissions("species:he:density", Permissions::Boundaries)), 1); - EXPECT_EQ(writable.count(make_permissions("species:he:pressure", Permissions::Interior)), 1); - EXPECT_EQ(writable.count(make_permissions("species:he:collision_frequency", Permissions::AllRegions)), 1); + EXPECT_EQ(writable["species:he:density"], Permissions::Boundaries); + EXPECT_EQ(writable["species:he:pressure"], Permissions::Interior); + EXPECT_EQ(writable["species:he:collision_frequency"], Permissions::AllRegions); - auto final_write = make_set(example.getVariablesWithPermission(Permissions::Final)); + auto final_write = example.getVariablesWithPermission(Permissions::Final); EXPECT_EQ(final_write.size(), 2); - EXPECT_EQ(final_write.count(make_permissions("species:he:density", Permissions::Boundaries)), 1); - EXPECT_EQ(final_write.count(make_permissions("species:he:collision_frequency", Permissions::AllRegions)), 1); + EXPECT_EQ(final_write["species:he:density"], Permissions::Boundaries); + EXPECT_EQ(final_write["species:he:collision_frequency"], Permissions::AllRegions); } TEST(PermissionsTests, TestSubstitute) { @@ -201,19 +213,19 @@ TEST(PermissionsTests, TestSubstitute) { example.substitute("s1", {"e", "d+"}); example.substitute("s2", {"e", "d+"}); - auto readable = make_set(example.getVariablesWithPermission(Permissions::Read, false)); + auto readable = example.getVariablesWithPermission(Permissions::Read, false); EXPECT_EQ(readable.size(), 5); - EXPECT_EQ(readable.count(make_permissions("{var}", Permissions::Interior)), 1); - EXPECT_EQ(readable.count(make_permissions("species:e:collision_frequencies:e_e_coll", Permissions::AllRegions)), 1); - EXPECT_EQ(readable.count(make_permissions("species:e:collision_frequencies:e_d+_coll", Permissions::AllRegions)), 1); - EXPECT_EQ(readable.count(make_permissions("species:d+:collision_frequencies:d+_e_coll", Permissions::AllRegions)), 1); - EXPECT_EQ(readable.count(make_permissions("species:d+:collision_frequencies:d+_d+_coll", Permissions::AllRegions)), 1); + EXPECT_EQ(readable["{var}"], Permissions::Interior); + EXPECT_EQ(readable["species:e:collision_frequencies:e_e_coll"], Permissions::AllRegions); + EXPECT_EQ(readable["species:e:collision_frequencies:e_d+_coll"], Permissions::AllRegions); + EXPECT_EQ(readable["species:d+:collision_frequencies:d+_e_coll"], Permissions::AllRegions); + EXPECT_EQ(readable["species:d+:collision_frequencies:d+_d+_coll"], Permissions::AllRegions); example.substitute("var", {"a", "b", "c"}); - auto writable = make_set(example.getVariablesWithPermission(Permissions::Write)); + auto writable = example.getVariablesWithPermission(Permissions::Write); EXPECT_EQ(writable.size(), 3); - EXPECT_EQ(writable.count(make_permissions("a", Permissions::Interior)), 1); - EXPECT_EQ(writable.count(make_permissions("b", Permissions::Interior)), 1); - EXPECT_EQ(writable.count(make_permissions("c", Permissions::Interior)), 1); + EXPECT_EQ(writable["a"], Permissions::Interior); + EXPECT_EQ(writable["b"], Permissions::Interior); + EXPECT_EQ(writable["c"], Permissions::Interior); } From d54c5d0d9cd2b265726c3774d06ab86021f7dd41 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Tue, 28 Oct 2025 18:53:36 +0000 Subject: [PATCH 03/63] Apply proper formatting --- include/permissions.hxx | 70 +++++--- src/permissions.cxx | 47 ++++-- tests/unit/test_permissions.cxx | 291 ++++++++++++++++++++------------ 3 files changed, 259 insertions(+), 149 deletions(-) diff --git a/include/permissions.hxx b/include/permissions.hxx index 3906e381f..70645d705 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -1,8 +1,8 @@ #pragma once #include #include -#include #include +#include #include /// Class to store information on whether particular variables an be @@ -13,19 +13,25 @@ public: /// Ways in which someone is allowed to access the variable, with /// increasing levels of rights. "Final" refers to the last time /// anyone is allowed to write to the variable. - enum PermissionTypes { None=-1, Read, Write, Final, PERMISSION_TYPES_END }; + enum PermissionTypes { None = -1, Read, Write, Final, PERMISSION_TYPES_END }; /// The regions of the domain to which a particular permission /// apply. These are designed to be used as bit-flags. - enum Regions { Nowhere = 0, Interior = 1 << 0, Boundaries = 1 << 1, AllRegions = Interior | Boundaries }; + enum Regions { + Nowhere = 0, + Interior = 1 << 0, + Boundaries = 1 << 1, + AllRegions = Interior | Boundaries + }; /// Data type for storing the regions of a variable which have a /// particular level of permission. Some examples can be seen below: /// - /// AccessRights read_only = { AllRegions, Nowhere, Nowhere } - /// write_boundaries = { Nowhere, Boundaries, Nowhere } - /// read_and_write_everywhere = { AllReginos, AllRegions, Nowhere } - /// final_write_boundaries_read_interior = { Interior, Nowhere, Boundaries}; + /// AccessRights read_only = { AllRegions, Nowhere, Nowhere }, + /// write_boundaries = { Nowhere, Boundaries, Nowhere }, + /// read_and_write_everywhere = { AllReginos, AllRegions, Nowhere }, + /// final_write_boundaries_read_interior = { Interior, Nowhere, + /// Boundaries }; /// using AccessRights = std::array; @@ -44,13 +50,17 @@ public: /// /// Permissions example({ /// // Read permission for atomic mass - /// {"species:he:AA", {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, + /// {"species:he:AA", {Permissions::AllRegions, Permissions::Nowhere, + /// Permissions::Nowhere}}, /// // Read permissions for density - /// {"species:he:density", {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, + /// {"species:he:density", {Permissions::AllRegions, Permissions::Nowhere, + /// Permissions::Nowhere}}, /// // Read and write permissions for pressure in the interior region - /// {"species:he:pressure", {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, + /// {"species:he:pressure", {Permissions::Nowhere, Permissions::Interior, + /// Permissions::Nowhere}}, /// // Set the final value for collision frequency - /// {"species:he:collision_frequency", {Permissions::Nowhere, Permissions::Nowhere, Permissions::AllRegions}} + /// {"species:he:collision_frequency", {Permissions::Nowhere, + /// Permissions::Nowhere, Permissions::AllRegions}} /// }); /// /// If a variable is not included in the initialiser list then it is @@ -81,12 +91,14 @@ public: /// everywhere but only writeable in the interior, you would use /// /// permissions.setAccess("species:he:density", - /// {Permissions::AllRegions, Permissions::Interior, Permissions::Nowhere}) + /// {Permissions::AllRegions, Permissions::Interior, + /// Permissions::Nowhere}) /// /// or, equivalently, /// /// permissions.setAccess("species:he:density", - /// {Permissions::Boundary, Permissions::Interior, Permissions::Nowhere}); + /// {Permissions::Boundary, Permissions::Interior, + /// Permissions::Nowhere}); /// /// As in the constructor, if the variable name is just a section in /// an Options object then the permissions apply to all children of @@ -107,35 +119,45 @@ public: /// }); /// example.substitute("name", {"d", "d+", "t", "t+", "he", "he+", "c", "c+", "e"}); /// - void substitute(const std::string& label, const std::vector& substitutions); + void substitute(const std::string& label, + const std::vector& substitutions); /// Check whether users are allowed to access this variable to the /// given permission level, in the given region. The secon item /// returned indicates the name of the variable or section from /// which the access rights are derived. If there is no matching /// section then it will be an empty string. - std::pair canAccess(const std::string& variable, PermissionTypes permission = Read, Regions region = AllRegions) const; + std::pair canAccess(const std::string& variable, + PermissionTypes permission = Read, + Regions region = AllRegions) const; /// Get the highest permission level with which the given variable /// can be accessed in the given region. - PermissionTypes getHighestPermission(const std::string& variable, Regions region = AllRegions) const; + PermissionTypes getHighestPermission(const std::string& variable, + Regions region = AllRegions) const; /// Get a set of variables and regions for which there is the /// specified level of permission to access. If ``highestOnly`` is /// true then it will only include variables/regions for which this /// is the highest permission. /// - /// Permissions example({"test", {Permissions::AllRegions, Permissions::AllRegions, Permissions:Nowhere}}); + /// Permissions example({"test", {Permissions::AllRegions, Permissions::AllRegions, + /// Permissions:Nowhere}}); /// // Print variables which can be read - /// for (const auto [varname, region] : example.getVariablesWithPermission(Permissions::Read, false)) - /// std::cout << "Variable name: " << varname << ", Region ID: " << region << "\n"; + /// for (const auto [varname, region] : + /// example.getVariablesWithPermission(Permissions::Read, false)) + /// std::cout << "Variable name: " << varname << ", Region ID: " << region << + /// "\n"; /// // Print variables which can only be read (not written) - /// for (const auto [varname, region] : example.getVariablesWithPermission(Permissions::Read, true)) - /// std::cout << "Variable name: " << varname << ", Region ID: " << region << "\n"; - /// + /// for (const auto [varname, region] : + /// example.getVariablesWithPermission(Permissions::Read, true)) + /// std::cout << "Variable name: " << varname << ", Region ID: " << region << + /// "\n"; + /// /// The above code would write a line of output from the first /// for-loop, but not the second. - std::map getVariablesWithPermission(PermissionTypes permission, bool highestOnly = true) const; + std::map + getVariablesWithPermission(PermissionTypes permission, bool highestOnly = true) const; private: /// Returns the access rights for the most specific entry in this @@ -150,7 +172,7 @@ private: /// been updated so that they reflect higher permissions (e.g., read /// permission will be set in all cases where write permission was /// set). - static AccessRights applyLowerPermissions(const AccessRights & rights); + static AccessRights applyLowerPermissions(const AccessRights& rights); std::map variable_permissions; }; diff --git a/src/permissions.cxx b/src/permissions.cxx index 1eff811d2..6c6a5a891 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -1,33 +1,37 @@ #include "../include/permissions.hxx" -Permissions::Permissions(std::initializer_list> data) : - variable_permissions() { +Permissions::Permissions(std::initializer_list> data) + : variable_permissions() { for (const auto& [varname, access] : data) { setAccess(varname, access); } } void Permissions::setAccess(const std::string& variable, const AccessRights& rights) { - + variable_permissions[variable] = applyLowerPermissions(rights); } -std::string replaceAll(const std::string& str, const std::string& from, const std::string& to) { +std::string replaceAll(const std::string& str, const std::string& from, + const std::string& to) { std::string result = str; - if(from.empty()) return result; + if (from.empty()) + return result; size_t start_pos = 0; - while((start_pos = result.find(from, start_pos)) != std::string::npos) { + while ((start_pos = result.find(from, start_pos)) != std::string::npos) { result.replace(start_pos, from.length(), to); - start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx' + start_pos += + to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx' } - return result; + return result; } void Permissions::substitute(const std::string& label, const std::vector& substitutions) { for (const auto [varname, access] : variable_permissions) { const std::string pattern = "{" + label + "}"; - if (varname.find(pattern) == std::string::npos) continue; + if (varname.find(pattern) == std::string::npos) + continue; variable_permissions.erase(varname); for (const std::string& val : substitutions) { variable_permissions[replaceAll(varname, pattern, val)] = access; @@ -35,8 +39,10 @@ void Permissions::substitute(const std::string& label, } } -std::pair Permissions::bestMatchRights(const std::string& variable) const { - Permissions::AccessRights best_candidate = {Permissions::Nowhere, Permissions::Nowhere, Permissions::Nowhere}; +std::pair +Permissions::bestMatchRights(const std::string& variable) const { + Permissions::AccessRights best_candidate = {Permissions::Nowhere, Permissions::Nowhere, + Permissions::Nowhere}; std::string best_candidate_name = ""; int max_len = 0; for (const auto& [varname, rights] : variable_permissions) { @@ -53,7 +59,8 @@ std::pair Permissions::bestMatchRights(c } std::pair Permissions::canAccess(const std::string& variable, - PermissionTypes permission, Regions region) const { + PermissionTypes permission, + Regions region) const { auto [match_name, match_rights] = bestMatchRights(variable); if ((match_rights[permission] & region) == region) { return {true, match_name}; @@ -62,8 +69,11 @@ std::pair Permissions::canAccess(const std::string& variable, } } -Permissions::PermissionTypes Permissions::getHighestPermission(const std::string & variable, Permissions::Regions region) const { - if (region == Nowhere) return None; +Permissions::PermissionTypes +Permissions::getHighestPermission(const std::string& variable, + Permissions::Regions region) const { + if (region == Nowhere) + return None; AccessRights rights = std::get<1>(bestMatchRights(variable)); int i = Read; while (i < PERMISSION_TYPES_END and (rights[i] & region) == region) { @@ -79,13 +89,16 @@ Permissions::getVariablesWithPermission(PermissionTypes permission, if (highestOnly and permission < PERMISSION_TYPES_END - 1) { for (const auto& [varname, rights] : variable_permissions) { auto regions = rights[permission]; - auto perm_in_regions = static_cast(rights[permission] & ~rights[permission+1]); - if (perm_in_regions != Nowhere) result.emplace(varname, perm_in_regions); + auto perm_in_regions = + static_cast(rights[permission] & ~rights[permission + 1]); + if (perm_in_regions != Nowhere) + result.emplace(varname, perm_in_regions); } } else { for (const auto& [varname, rights] : variable_permissions) { auto regions = rights[permission]; - if (regions != Nowhere) result.emplace(varname, regions); + if (regions != Nowhere) + result.emplace(varname, regions); } } return result; diff --git a/tests/unit/test_permissions.cxx b/tests/unit/test_permissions.cxx index e5b50cc12..0b6112138 100644 --- a/tests/unit/test_permissions.cxx +++ b/tests/unit/test_permissions.cxx @@ -5,158 +5,228 @@ auto make_access = std::make_pair; TEST(PermissionsTests, TestCanAccess) { - Permissions example( - {{"species:he:density", - {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, - // Read and write permissions for pressure in the interior region - {"species:he:pressure", - {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, - // Set the final value for collision frequency - {"species:he:collision_frequency", - {Permissions::Nowhere, Permissions::Nowhere, Permissions::AllRegions}}, - // Only allow reading of boundary velocity - {"species:he:velocity", - {Permissions::Boundaries, Permissions::Nowhere, Permissions::Nowhere}}, - {"species:d", {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, - {"species:d:pressure", {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, - {"species:d:collision_frequencies", {Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere}}, - }); + Permissions example({ + {"species:he:density", + {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, + // Read and write permissions for pressure in the interior region + {"species:he:pressure", + {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, + // Set the final value for collision frequency + {"species:he:collision_frequency", + {Permissions::Nowhere, Permissions::Nowhere, Permissions::AllRegions}}, + // Only allow reading of boundary velocity + {"species:he:velocity", + {Permissions::Boundaries, Permissions::Nowhere, Permissions::Nowhere}}, + {"species:d", + {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, + {"species:d:pressure", + {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, + {"species:d:collision_frequencies", + {Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere}}, + }); auto no_access = make_access(false, ""); - + // Check whether we have read permission for the variables across the entire domain - EXPECT_EQ(example.canAccess("species:he:density"), make_access(true, "species:he:density")); + EXPECT_EQ(example.canAccess("species:he:density"), + make_access(true, "species:he:density")); EXPECT_EQ(example.canAccess("species:he:pressure"), no_access); - EXPECT_EQ(example.canAccess("species:he:collision_frequency"), make_access(true, "species:he:collision_frequency")); + EXPECT_EQ(example.canAccess("species:he:collision_frequency"), + make_access(true, "species:he:collision_frequency")); EXPECT_EQ(example.canAccess("species:he:velocity"), no_access); EXPECT_EQ(example.canAccess("unset"), no_access); // Check whether we have write permission for the variables across the entire domain EXPECT_EQ(example.canAccess("species:he:density", Permissions::Write), no_access); EXPECT_EQ(example.canAccess("species:he:pressure", Permissions::Write), no_access); - EXPECT_EQ(example.canAccess("species:he:collision_frequency", Permissions::Write), make_access(true, "species:he:collision_frequency")); + EXPECT_EQ(example.canAccess("species:he:collision_frequency", Permissions::Write), + make_access(true, "species:he:collision_frequency")); EXPECT_EQ(example.canAccess("species:he:velocity", Permissions::Write), no_access); EXPECT_EQ(example.canAccess("unset", Permissions::Write), no_access); - + // Check whether we have read permission at the boundaries - EXPECT_EQ(example.canAccess("species:he:density", Permissions::Read, Permissions::Boundaries), make_access(true, "species:he:density")); - EXPECT_EQ(example.canAccess("species:he:pressure", Permissions::Read, Permissions::Boundaries), no_access); - EXPECT_EQ(example.canAccess("species:he:collision_frequency", Permissions::Read, Permissions::Boundaries), make_access(true, "species:he:collision_frequency")); - EXPECT_EQ(example.canAccess("species:he:velocity", Permissions::Read, Permissions::Boundaries), make_access(true, "species:he:velocity")); - EXPECT_EQ(example.canAccess("unset", Permissions::Read, Permissions::Boundaries), no_access); + EXPECT_EQ( + example.canAccess("species:he:density", Permissions::Read, Permissions::Boundaries), + make_access(true, "species:he:density")); + EXPECT_EQ(example.canAccess("species:he:pressure", Permissions::Read, + Permissions::Boundaries), + no_access); + EXPECT_EQ(example.canAccess("species:he:collision_frequency", Permissions::Read, + Permissions::Boundaries), + make_access(true, "species:he:collision_frequency")); + EXPECT_EQ(example.canAccess("species:he:velocity", Permissions::Read, + Permissions::Boundaries), + make_access(true, "species:he:velocity")); + EXPECT_EQ(example.canAccess("unset", Permissions::Read, Permissions::Boundaries), + no_access); // Check permissions set for whole sections EXPECT_EQ(example.canAccess("species:d:pressure"), no_access); EXPECT_EQ(example.canAccess("species:d:pressure", Permissions::Write), no_access); - EXPECT_EQ(example.canAccess("species:d:pressure", Permissions::Write, Permissions::Interior), make_access(true, "species:d:pressure")); + EXPECT_EQ( + example.canAccess("species:d:pressure", Permissions::Write, Permissions::Interior), + make_access(true, "species:d:pressure")); EXPECT_EQ(example.canAccess("species:d:velocity"), make_access(true, "species:d")); EXPECT_EQ(example.canAccess("species:d:velocity", Permissions::Write), no_access); - EXPECT_EQ(example.canAccess("species:d:velocity", Permissions::Read, Permissions::Boundaries), make_access(true, "species:d")); + EXPECT_EQ( + example.canAccess("species:d:velocity", Permissions::Read, Permissions::Boundaries), + make_access(true, "species:d")); EXPECT_EQ(example.canAccess("species:d:collision_frequencies:d_d_coll"), no_access); - EXPECT_EQ(example.canAccess("species:d:collision_frequencies:d_d_coll", Permissions::Write), no_access); - EXPECT_EQ(example.canAccess("species:d:collision_frequencies:d_d_coll", Permissions::Write, Permissions::Boundaries), make_access(true, "species:d:collision_frequencies")); + EXPECT_EQ( + example.canAccess("species:d:collision_frequencies:d_d_coll", Permissions::Write), + no_access); + EXPECT_EQ(example.canAccess("species:d:collision_frequencies:d_d_coll", + Permissions::Write, Permissions::Boundaries), + make_access(true, "species:d:collision_frequencies")); // Check permissions for a species that might be mistaken for one of // the sections we've given permissions for EXPECT_EQ(example.canAccess("species:d+"), no_access); EXPECT_EQ(example.canAccess("species:d+", Permissions::Write), no_access); - EXPECT_EQ(example.canAccess("species:d+", Permissions::Read, Permissions::Interior), no_access); - EXPECT_EQ(example.canAccess("species:d+", Permissions::Read, Permissions::Boundaries), no_access); + EXPECT_EQ(example.canAccess("species:d+", Permissions::Read, Permissions::Interior), + no_access); + EXPECT_EQ(example.canAccess("species:d+", Permissions::Read, Permissions::Boundaries), + no_access); } - TEST(PermissionsTests, TestGetHighestPermission) { - Permissions example( - {{"species:he:density", - {Permissions::AllRegions, Permissions::Nowhere, Permissions::Boundaries}}, - // Read and write permissions for pressure in the interior region - {"species:he:pressure", - {Permissions::Boundaries, Permissions::Interior, Permissions::Nowhere}}, - // Set the final value for collision frequency - {"species:he:collision_frequency", - {Permissions::Interior, Permissions::Nowhere, Permissions::AllRegions}}, - // Only allow reading of boundary velocity - {"species:he:velocity", - {Permissions::Boundaries, Permissions::Nowhere, Permissions::Nowhere}}, - {"species:d", {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, - {"species:d:pressure", {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, - {"species:d:collision_frequencies", {Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere}}, - }); + Permissions example({ + {"species:he:density", + {Permissions::AllRegions, Permissions::Nowhere, Permissions::Boundaries}}, + // Read and write permissions for pressure in the interior region + {"species:he:pressure", + {Permissions::Boundaries, Permissions::Interior, Permissions::Nowhere}}, + // Set the final value for collision frequency + {"species:he:collision_frequency", + {Permissions::Interior, Permissions::Nowhere, Permissions::AllRegions}}, + // Only allow reading of boundary velocity + {"species:he:velocity", + {Permissions::Boundaries, Permissions::Nowhere, Permissions::Nowhere}}, + {"species:d", + {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, + {"species:d:pressure", + {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, + {"species:d:collision_frequencies", + {Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere}}, + }); // Get the highest permission that covers the entire domain EXPECT_EQ(example.getHighestPermission("species:he:density"), Permissions::Read); EXPECT_EQ(example.getHighestPermission("species:he:pressure"), Permissions::Read); - EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency"), Permissions::Final); + EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency"), + Permissions::Final); EXPECT_EQ(example.getHighestPermission("species:he:velocity"), Permissions::None); EXPECT_EQ(example.getHighestPermission("species:d:pressure"), Permissions::None); EXPECT_EQ(example.getHighestPermission("species:d:velocity"), Permissions::Read); - EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll"), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll"), + Permissions::None); EXPECT_EQ(example.getHighestPermission("unset"), Permissions::None); // Get the highest permission on the boundaries - EXPECT_EQ(example.getHighestPermission("species:he:density", Permissions::Boundaries), Permissions::Final); - EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Boundaries), Permissions::Read); - EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency", Permissions::Boundaries), Permissions::Final); - EXPECT_EQ(example.getHighestPermission("species:he:velocity", Permissions::Boundaries), Permissions::Read); - EXPECT_EQ(example.getHighestPermission("species:d:pressure", Permissions::Boundaries), Permissions::None); - EXPECT_EQ(example.getHighestPermission("species:d:velocity", Permissions::Boundaries), Permissions::Read); - EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll", Permissions::Boundaries), Permissions::Write); - EXPECT_EQ(example.getHighestPermission("unset", Permissions::Boundaries), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:he:density", Permissions::Boundaries), + Permissions::Final); + EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Boundaries), + Permissions::Read); + EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency", + Permissions::Boundaries), + Permissions::Final); + EXPECT_EQ(example.getHighestPermission("species:he:velocity", Permissions::Boundaries), + Permissions::Read); + EXPECT_EQ(example.getHighestPermission("species:d:pressure", Permissions::Boundaries), + Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:d:velocity", Permissions::Boundaries), + Permissions::Read); + EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll", + Permissions::Boundaries), + Permissions::Write); + EXPECT_EQ(example.getHighestPermission("unset", Permissions::Boundaries), + Permissions::None); // Get the highest permission on the interior - EXPECT_EQ(example.getHighestPermission("species:he:density", Permissions::Interior), Permissions::Read); - EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Interior), Permissions::Write); - EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency", Permissions::Interior), Permissions::Final); - EXPECT_EQ(example.getHighestPermission("species:he:velocity", Permissions::Interior), Permissions::None); - EXPECT_EQ(example.getHighestPermission("species:d:pressure", Permissions::Interior), Permissions::Write); - EXPECT_EQ(example.getHighestPermission("species:d:velocity", Permissions::Interior), Permissions::Read); - EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll", Permissions::Interior), Permissions::None); - EXPECT_EQ(example.getHighestPermission("unset", Permissions::Interior), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:he:density", Permissions::Interior), + Permissions::Read); + EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Interior), + Permissions::Write); + EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency", + Permissions::Interior), + Permissions::Final); + EXPECT_EQ(example.getHighestPermission("species:he:velocity", Permissions::Interior), + Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:d:pressure", Permissions::Interior), + Permissions::Write); + EXPECT_EQ(example.getHighestPermission("species:d:velocity", Permissions::Interior), + Permissions::Read); + EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll", + Permissions::Interior), + Permissions::None); + EXPECT_EQ(example.getHighestPermission("unset", Permissions::Interior), + Permissions::None); // Check the permission for the "Nowhere" region is always "None" - EXPECT_EQ(example.getHighestPermission("species:he:density", Permissions::Nowhere), Permissions::None); - EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Nowhere), Permissions::None); - EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency", Permissions::Nowhere), Permissions::None); - EXPECT_EQ(example.getHighestPermission("species:he:velocity", Permissions::Nowhere), Permissions::None); - EXPECT_EQ(example.getHighestPermission("species:d:pressure", Permissions::Nowhere), Permissions::None); - EXPECT_EQ(example.getHighestPermission("species:d:velocity", Permissions::Nowhere), Permissions::None); - EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll", Permissions::Nowhere), Permissions::None); - EXPECT_EQ(example.getHighestPermission("unset", Permissions::Nowhere), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:he:density", Permissions::Nowhere), + Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Nowhere), + Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency", + Permissions::Nowhere), + Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:he:velocity", Permissions::Nowhere), + Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:d:pressure", Permissions::Nowhere), + Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:d:velocity", Permissions::Nowhere), + Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll", + Permissions::Nowhere), + Permissions::None); + EXPECT_EQ(example.getHighestPermission("unset", Permissions::Nowhere), + Permissions::None); // Check permissions for a species that might be mistaken for one of // the sections we've given permissions for EXPECT_EQ(example.getHighestPermission("species:d+"), Permissions::None); - EXPECT_EQ(example.getHighestPermission("species:d+", Permissions::Interior), Permissions::None); - EXPECT_EQ(example.getHighestPermission("species:d+", Permissions::Boundaries), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:d+", Permissions::Interior), + Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:d+", Permissions::Boundaries), + Permissions::None); } TEST(PermissionsTests, TestSetAccess) { Permissions example({ - {"species:he:density", - {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, - // Read and write permissions for pressure in the interior region - {"species:he:pressure", - {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, - }); - - EXPECT_EQ(example.getHighestPermission("species:he:density"), Permissions::Read); - example.setAccess("species:he:density", { - Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere}); - EXPECT_EQ(example.getHighestPermission("species:he:density"), Permissions::None); - EXPECT_EQ(example.getHighestPermission("species:he:density", Permissions::Boundaries), Permissions::Write); - - EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Interior), Permissions::Write); - EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::AllRegions), Permissions::None); - example.setAccess("species:he:pressure", {Permissions::AllRegions, Permissions::Nowhere, + {"species:he:density", + {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, + // Read and write permissions for pressure in the interior region + {"species:he:pressure", + {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, + }); + + EXPECT_EQ(example.getHighestPermission("species:he:density"), Permissions::Read); + example.setAccess("species:he:density", {Permissions::Nowhere, Permissions::Boundaries, + Permissions::Nowhere}); + EXPECT_EQ(example.getHighestPermission("species:he:density"), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:he:density", Permissions::Boundaries), + Permissions::Write); + + EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Interior), + Permissions::Write); + EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::AllRegions), + Permissions::None); + example.setAccess("species:he:pressure", {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}); - EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Interior), Permissions::Read); - EXPECT_EQ(example.getHighestPermission("species:he:pressure"), Permissions::Read); - - EXPECT_EQ(example.getHighestPermission("unset", Permissions::Interior), Permissions::None); - EXPECT_EQ(example.getHighestPermission("unset", Permissions::Boundaries), Permissions::None); - example.setAccess("unset", {Permissions::Interior, Permissions::Nowhere, Permissions::Boundaries}); - EXPECT_EQ(example.getHighestPermission("unset", Permissions::AllRegions), Permissions::Read); - EXPECT_EQ(example.getHighestPermission("unset", Permissions::Boundaries), Permissions::Final); + EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Interior), + Permissions::Read); + EXPECT_EQ(example.getHighestPermission("species:he:pressure"), Permissions::Read); + + EXPECT_EQ(example.getHighestPermission("unset", Permissions::Interior), + Permissions::None); + EXPECT_EQ(example.getHighestPermission("unset", Permissions::Boundaries), + Permissions::None); + example.setAccess( + "unset", {Permissions::Interior, Permissions::Nowhere, Permissions::Boundaries}); + EXPECT_EQ(example.getHighestPermission("unset", Permissions::AllRegions), + Permissions::Read); + EXPECT_EQ(example.getHighestPermission("unset", Permissions::Boundaries), + Permissions::Final); } auto make_permissions = std::make_pair; @@ -205,8 +275,9 @@ TEST(PermissionsTests, TestGetVariablesWithPermissions) { } TEST(PermissionsTests, TestSubstitute) { - Permissions example({{"species:{s1}:collision_frequencies:{s1}_{s2}_coll", - {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}}); + Permissions example( + {{"species:{s1}:collision_frequencies:{s1}_{s2}_coll", + {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}}); example.setAccess("{var}", {Permissions::Nowhere, Permissions::Interior}); @@ -216,13 +287,17 @@ TEST(PermissionsTests, TestSubstitute) { auto readable = example.getVariablesWithPermission(Permissions::Read, false); EXPECT_EQ(readable.size(), 5); EXPECT_EQ(readable["{var}"], Permissions::Interior); - EXPECT_EQ(readable["species:e:collision_frequencies:e_e_coll"], Permissions::AllRegions); - EXPECT_EQ(readable["species:e:collision_frequencies:e_d+_coll"], Permissions::AllRegions); - EXPECT_EQ(readable["species:d+:collision_frequencies:d+_e_coll"], Permissions::AllRegions); - EXPECT_EQ(readable["species:d+:collision_frequencies:d+_d+_coll"], Permissions::AllRegions); + EXPECT_EQ(readable["species:e:collision_frequencies:e_e_coll"], + Permissions::AllRegions); + EXPECT_EQ(readable["species:e:collision_frequencies:e_d+_coll"], + Permissions::AllRegions); + EXPECT_EQ(readable["species:d+:collision_frequencies:d+_e_coll"], + Permissions::AllRegions); + EXPECT_EQ(readable["species:d+:collision_frequencies:d+_d+_coll"], + Permissions::AllRegions); example.substitute("var", {"a", "b", "c"}); - + auto writable = example.getVariablesWithPermission(Permissions::Write); EXPECT_EQ(writable.size(), 3); EXPECT_EQ(writable["a"], Permissions::Interior); From 9b776c16e849dc9e8131f14dd9e8f1b1d4ca0561 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Fri, 31 Oct 2025 16:44:47 +0000 Subject: [PATCH 04/63] Switch transform method to using GuardedOptions Note that components still aren't setting up any permissions, so there would be runtime errors when executing the transform methods. Furthermore, there are some missing methods I realise I need for GuardedOptions. The unit tests are also failing to compile, although I don't understand what the problem is for them, yet. --- include/adas_carbon.hxx | 11 +++- include/adas_lithium.hxx | 9 ++- include/adas_neon.hxx | 9 ++- include/amjuel_reaction.hxx | 2 +- include/anomalous_diffusion.hxx | 27 ++++---- include/binormal_stpm.hxx | 16 ++--- include/braginskii_collisions.hxx | 8 +-- include/braginskii_electron_viscosity.hxx | 16 ++--- include/braginskii_ion_viscosity.hxx | 28 ++++---- include/braginskii_thermal_force.hxx | 17 +++-- include/classical_diffusion.hxx | 4 +- include/component.hxx | 63 +++++++++++++++++- include/detachment_controller.hxx | 4 +- include/diamagnetic_drift.hxx | 12 ++-- include/electromagnetic.hxx | 36 +++++----- include/electron_force_balance.hxx | 16 ++--- include/evolve_density.hxx | 16 ++--- include/evolve_energy.hxx | 28 ++++---- include/evolve_momentum.hxx | 14 ++-- include/evolve_pressure.hxx | 26 ++++---- include/fixed_density.hxx | 34 +++++----- include/fixed_fraction_ions.hxx | 8 +-- include/fixed_fraction_radiation.hxx | 54 +++++++-------- include/fixed_temperature.hxx | 55 ++++++++-------- include/fixed_velocity.hxx | 38 +++++------ include/guarded_options.hxx | 13 +++- include/hydrogen_charge_exchange.hxx | 66 +++++++++---------- include/ionisation.hxx | 3 +- include/isothermal.hxx | 20 +++--- include/neutral_boundary.hxx | 25 +++---- include/neutral_full_velocity.hxx | 6 +- include/neutral_mixed.hxx | 6 +- include/neutral_parallel_diffusion.hxx | 40 +++++------ include/noflow_boundary.hxx | 12 ++-- include/permissions.hxx | 9 ++- include/polarisation_drift.hxx | 36 +++++----- include/quasineutral.hxx | 18 ++--- include/rate_helper.hxx | 4 +- include/reaction.hxx | 14 ++-- include/recycling.hxx | 28 ++++---- include/relax_potential.hxx | 40 +++++------ include/scale_timederivs.hxx | 12 ++-- include/set_temperature.hxx | 50 +++++++------- include/sheath_boundary.hxx | 27 ++++---- include/sheath_boundary_insulating.hxx | 19 +++--- include/sheath_boundary_simple.hxx | 58 ++++++++-------- include/sheath_closure.hxx | 23 +++---- include/simple_conduction.hxx | 22 +++---- include/simple_pump.hxx | 19 +++--- include/snb_conduction.hxx | 18 ++--- include/solkit_hydrogen_charge_exchange.hxx | 5 +- include/solkit_neutral_parallel_diffusion.hxx | 10 +-- include/sound_speed.hxx | 16 ++--- include/temperature_feedback.hxx | 22 +++---- include/transform.hxx | 4 +- include/upstream_density_feedback.hxx | 22 +++---- include/vorticity.hxx | 38 +++++------ include/zero_current.hxx | 28 ++++---- src/amjuel_reaction.cxx | 14 ++-- src/anomalous_diffusion.cxx | 8 +-- src/binormal_stpm.cxx | 12 ++-- src/braginskii_collisions.cxx | 59 +++++++++-------- src/braginskii_electron_viscosity.cxx | 4 +- src/braginskii_ion_viscosity.cxx | 22 +++---- src/braginskii_thermal_force.cxx | 20 +++--- src/classical_diffusion.cxx | 18 ++--- src/component.cxx | 22 +++++++ src/detachment_controller.cxx | 2 +- src/diamagnetic_drift.cxx | 8 +-- src/electromagnetic.cxx | 12 ++-- src/electron_force_balance.cxx | 8 +-- src/evolve_density.cxx | 4 +- src/evolve_energy.cxx | 4 +- src/evolve_momentum.cxx | 4 +- src/evolve_pressure.cxx | 4 +- src/fixed_fraction_ions.cxx | 2 +- src/guarded_options.cxx | 2 +- src/hydrogen_charge_exchange.cxx | 8 +-- src/ionisation.cxx | 8 +-- src/isothermal.cxx | 4 +- src/neutral_boundary.cxx | 6 +- src/neutral_full_velocity.cxx | 4 +- src/neutral_mixed.cxx | 4 +- src/neutral_parallel_diffusion.cxx | 26 ++++---- src/noflow_boundary.cxx | 8 +-- src/permissions.cxx | 14 ++++ src/polarisation_drift.cxx | 22 +++---- src/quasineutral.cxx | 14 ++-- src/reaction.cxx | 8 +-- src/recycling.cxx | 16 ++--- src/relax_potential.cxx | 10 +-- src/sheath_boundary.cxx | 24 +++---- src/sheath_boundary_insulating.cxx | 22 +++---- src/sheath_boundary_simple.cxx | 36 +++++----- src/sheath_closure.cxx | 12 ++-- src/snb_conduction.cxx | 6 +- src/solkit_hydrogen_charge_exchange.cxx | 2 +- src/solkit_neutral_parallel_diffusion.cxx | 14 ++-- src/sound_speed.cxx | 12 ++-- src/temperature_feedback.cxx | 4 +- src/transform.cxx | 4 +- src/upstream_density_feedback.cxx | 4 +- src/vorticity.cxx | 24 +++---- src/zero_current.cxx | 12 ++-- tests/unit/test_component.cxx | 3 +- tests/unit/test_component_scheduler.cxx | 8 ++- 106 files changed, 994 insertions(+), 858 deletions(-) diff --git a/include/adas_carbon.hxx b/include/adas_carbon.hxx index ed82c1b3f..47a614be7 100644 --- a/include/adas_carbon.hxx +++ b/include/adas_carbon.hxx @@ -40,7 +40,8 @@ struct ADASCarbonIonisation : public OpenADAS { : OpenADAS(alloptions["units"], "scd96_c.json", "plt96_c.json", level, -carbon_ionisation_energy[level]) {} - void transform(Options& state) override { +private: + void transform(GuardedOptions& state) override { calculate_rates( state["species"]["e"], // Electrons state["species"][carbon_species_name], // From this ionisation state @@ -61,7 +62,9 @@ struct ADASCarbonRecombination : public OpenADAS { : OpenADAS(alloptions["units"], "acd96_c.json", "prb96_c.json", level, carbon_ionisation_energy[level]) {} - void transform(Options& state) override { + +private: + void transform(GuardedOptions& state) override { calculate_rates( state["species"]["e"], // Electrons state["species"][carbon_species_name], // From this ionisation state @@ -78,7 +81,9 @@ struct ADASCarbonCX : public OpenADASChargeExchange { ADASCarbonCX(std::string, Options& alloptions, Solver*) : OpenADASChargeExchange(alloptions["units"], "ccd96_c.json", level) {} - void transform(Options& state) override { + +private: + void transform(GuardedOptions& state) override { Options& species = state["species"]; calculate_rates( species["e"], // Electrons diff --git a/include/adas_lithium.hxx b/include/adas_lithium.hxx index 20cc6f349..11afb911d 100644 --- a/include/adas_lithium.hxx +++ b/include/adas_lithium.hxx @@ -43,7 +43,8 @@ struct ADASLithiumIonisation : public OpenADAS { : OpenADAS(alloptions["units"], "scd96_li.json", "plt96_li.json", level, -lithium_ionisation_energy[level]) {} - void transform(Options& state) override { +private: + void transform(GuardedOptions& state) override { calculate_rates( state["species"]["e"], // Electrons state["species"][lithium_species_name], // From this ionisation state @@ -64,7 +65,8 @@ struct ADASLithiumRecombination : public OpenADAS { : OpenADAS(alloptions["units"], "acd96_li.json", "prb96_li.json", level, lithium_ionisation_energy[level]) {} - void transform(Options& state) override { +private: + void transform(GuardedOptions& state) override { calculate_rates( state["species"]["e"], // Electrons state["species"][lithium_species_name], // From this ionisation state @@ -81,7 +83,8 @@ struct ADASLithiumCX : public OpenADASChargeExchange { ADASLithiumCX(std::string, Options& alloptions, Solver*) : OpenADASChargeExchange(alloptions["units"], "ccd89_li.json", level) {} - void transform(Options& state) override { +private: + void transform(GuardedOptions& state) override { Options& species = state["species"]; calculate_rates( species["e"], // Electrons diff --git a/include/adas_neon.hxx b/include/adas_neon.hxx index d055c403f..8efbb3fad 100644 --- a/include/adas_neon.hxx +++ b/include/adas_neon.hxx @@ -43,7 +43,8 @@ struct ADASNeonIonisation : public OpenADAS { : OpenADAS(alloptions["units"], "scd96_ne.json", "plt96_ne.json", level, -neon_ionisation_energy[level]) {} - void transform(Options& state) override { +private: + void transform(GuardedOptions& state) override { calculate_rates( state["species"]["e"], // Electrons state["species"][neon_species_name], // From this ionisation state @@ -64,7 +65,8 @@ struct ADASNeonRecombination : public OpenADAS { : OpenADAS(alloptions["units"], "acd96_ne.json", "prb96_ne.json", level, neon_ionisation_energy[level]) {} - void transform(Options& state) override { +private: + void transform(GuardedOptions& state) override { calculate_rates( state["species"]["e"], // Electrons state["species"][neon_species_name], // From this ionisation state @@ -81,7 +83,8 @@ struct ADASNeonCX : public OpenADASChargeExchange { ADASNeonCX(std::string, Options& alloptions, Solver*) : OpenADASChargeExchange(alloptions["units"], "ccd89_ne.json", level) {} - void transform(Options& state) override { +private: + void transform(GuardedOptions& state) override { Options& species = state["species"]; calculate_rates( species["e"], // Electrons diff --git a/include/amjuel_reaction.hxx b/include/amjuel_reaction.hxx index a6fdef54c..e57c0d6cc 100644 --- a/include/amjuel_reaction.hxx +++ b/include/amjuel_reaction.hxx @@ -56,7 +56,7 @@ protected: virtual BoutReal eval_sigma_v_E(BoutReal T, BoutReal n) override final; virtual BoutReal eval_sigma_v(BoutReal T, BoutReal n) override final; - virtual void transform_additional(Options& state, + virtual void transform_additional(GuardedOptions& state, Field3D& reaction_rate) override final; private: diff --git a/include/anomalous_diffusion.hxx b/include/anomalous_diffusion.hxx index e8c833592..d512f788b 100644 --- a/include/anomalous_diffusion.hxx +++ b/include/anomalous_diffusion.hxx @@ -24,6 +24,19 @@ struct AnomalousDiffusion : public Component { // Default false. AnomalousDiffusion(std::string name, Options &alloptions, Solver *); + void outputVars(Options &state) override; + +private: + std::string name; ///< Species name + + bool diagnose; ///< Outputting diagnostics? + bool include_D, include_chi, include_nu; ///< Which terms should be included? + Field2D anomalous_D; ///< Anomalous density diffusion coefficient + Field2D anomalous_chi; ///< Anomalous thermal diffusion coefficient + Field2D anomalous_nu; ///< Anomalous momentum diffusion coefficient + + bool anomalous_sheath_flux; ///< Allow anomalous diffusion into sheath? + /// Inputs /// - species /// - @@ -39,19 +52,7 @@ struct AnomalousDiffusion : public Component { /// - momentum_source /// - energy_source /// - void transform(Options &state) override; - void outputVars(Options &state) override; - -private: - std::string name; ///< Species name - - bool diagnose; ///< Outputting diagnostics? - bool include_D, include_chi, include_nu; ///< Which terms should be included? - Field2D anomalous_D; ///< Anomalous density diffusion coefficient - Field2D anomalous_chi; ///< Anomalous thermal diffusion coefficient - Field2D anomalous_nu; ///< Anomalous momentum diffusion coefficient - - bool anomalous_sheath_flux; ///< Allow anomalous diffusion into sheath? + void transform(GuardedOptions &state) override; }; diff --git a/include/binormal_stpm.hxx b/include/binormal_stpm.hxx index 8652c4fdf..d806df0bb 100644 --- a/include/binormal_stpm.hxx +++ b/include/binormal_stpm.hxx @@ -23,14 +23,6 @@ struct BinormalSTPM : public Component { /// BinormalSTPM(std::string name, Options& options, Solver* solver); - /// Sets - /// - species - /// - - /// - pressure correction - /// - momentum correction - /// - density correction - /// - void transform(Options& state) override; void outputVars(Options &state) override; @@ -41,6 +33,14 @@ private: Field3D nu_Theta, chi_Theta, D_Theta; ///< nu/Theta, chi/Theta, D/Theta, precalculated Field3D Theta_inv; ///< Precalculate 1/Theta + /// Sets + /// - species + /// - + /// - pressure correction + /// - momentum correction + /// - density correction + /// + void transform(GuardedOptions& state) override; }; namespace { diff --git a/include/braginskii_collisions.hxx b/include/braginskii_collisions.hxx index 84a34a815..14401e83a 100644 --- a/include/braginskii_collisions.hxx +++ b/include/braginskii_collisions.hxx @@ -43,8 +43,6 @@ struct BraginskiiCollisions : public Component { /// BraginskiiCollisions(const std::string& name, Options& alloptions, Solver*); - void transform(Options& state) override; - /// Add extra fields for output, or set attributes e.g docstrings void outputVars(Options& state) override; @@ -67,9 +65,11 @@ private: /// Save more diagnostics? bool diagnose; - /// Update collision frequencies + void transform(GuardedOptions &state) override; + + /// Update collision frequencies, momentum and energy exchange /// nu_12 normalised frequency - void collide(Options& species1, Options& species2, const Field3D& nu_12); + void collide(Options& species1, Options& species2, const Field3D& nu_12, BoutReal momentum_coefficient); }; namespace { diff --git a/include/braginskii_electron_viscosity.hxx b/include/braginskii_electron_viscosity.hxx index 68e09169e..d81af13f7 100644 --- a/include/braginskii_electron_viscosity.hxx +++ b/include/braginskii_electron_viscosity.hxx @@ -30,6 +30,13 @@ struct BraginskiiElectronViscosity : public Component { /// Flux limiter coefficient. < 0 means no limiter BraginskiiElectronViscosity(const std::string& name, Options& alloptions, Solver*); + void outputVars(Options &state) override; + +private: + BoutReal eta_limit_alpha; ///< Flux limit coefficient + bool diagnose; ///< Output viscosity diagnostic? + Field3D viscosity; ///< The viscosity momentum source + /// Inputs /// - species /// - e @@ -42,14 +49,7 @@ struct BraginskiiElectronViscosity : public Component { /// - e /// - momentum_source /// - void transform(Options& state) override; - - void outputVars(Options& state) override; - -private: - BoutReal eta_limit_alpha; ///< Flux limit coefficient - bool diagnose; ///< Output viscosity diagnostic? - Field3D viscosity; ///< The viscosity momentum source + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/braginskii_ion_viscosity.hxx b/include/braginskii_ion_viscosity.hxx index 6cd7bcc22..30b4b8ef6 100644 --- a/include/braginskii_ion_viscosity.hxx +++ b/include/braginskii_ion_viscosity.hxx @@ -42,20 +42,6 @@ struct BraginskiiIonViscosity : public Component { /// BraginskiiIonViscosity(const std::string& name, Options& alloptions, Solver*); - /// Inputs - /// - species - /// - (skips "e") - /// - pressure (skips if not present) - /// - velocity (skips if not present) - /// - collision_frequency - /// - /// Sets in the state - /// - species - /// - - /// - momentum_source - /// - void transform(Options& state) override; - /// Save variables to the output void outputVars(Options& state) override; @@ -86,6 +72,20 @@ private: /// Store diagnostics for each species std::map diagnostics; + + /// Inputs + /// - species + /// - (skips "e") + /// - pressure (skips if not present) + /// - velocity (skips if not present) + /// - collision_frequency + /// + /// Sets in the state + /// - species + /// - + /// - momentum_source + /// + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/braginskii_thermal_force.hxx b/include/braginskii_thermal_force.hxx index be63056a0..6a8202dc1 100644 --- a/include/braginskii_thermal_force.hxx +++ b/include/braginskii_thermal_force.hxx @@ -44,6 +44,13 @@ struct BraginskiiThermalForce : public Component { .withDefault(false); } +private: + bool electron_ion; ///< Include electron-ion collisions? + bool ion_ion; ///< Include ion-ion elastic collisions? + bool override_ion_mass_restrictions; ///< Ignore default mass restrictions when + ///< calculating thermal force between ions? + bool first_time{true}; ///< True the first time transform() is called + /// Inputs /// - species /// - e [ if electron_ion true ] @@ -62,15 +69,7 @@ struct BraginskiiThermalForce : public Component { /// - [ if AA < 4 ("light") or AA > 10 ("heavy") ] /// - momentum_source /// - void transform(Options& state) override; - -private: - bool electron_ion; ///< Include electron-ion collisions? - bool ion_ion; ///< Include ion-ion elastic collisions? - bool override_ion_mass_restrictions; ///< Ignore default mass restrictions when - ///< calculating thermal force between ions? - - bool first_time{true}; ///< True the first time transform() is called + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/classical_diffusion.hxx b/include/classical_diffusion.hxx index dfa71eb70..067cd8f43 100644 --- a/include/classical_diffusion.hxx +++ b/include/classical_diffusion.hxx @@ -7,8 +7,6 @@ struct ClassicalDiffusion : public Component { ClassicalDiffusion(std::string name, Options& alloptions, Solver*); - void transform(Options &state) override; - void outputVars(Options &state) override; private: Field2D Bsq; // Magnetic field squared @@ -16,6 +14,8 @@ private: bool diagnose; ///< Output additional diagnostics? Field3D Dn; ///< Particle diffusion coefficient BoutReal custom_D; ///< User-set particle diffusion coefficient override + + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/component.hxx b/include/component.hxx index 3650e0afd..c05d06876 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -10,6 +10,9 @@ #include #include +#include "guarded_options.hxx" +#include "permissions.hxx" + class Solver; // Time integrator /// Interface for a component of a simulation model @@ -20,9 +23,10 @@ class Solver; // Time integrator struct Component { virtual ~Component() {} - /// Modify the given simulation state - /// All components must implement this function - virtual void transform(Options &state) = 0; + /// Modify the given simulation state. This method will wrap the + /// state in a GuardedOptions object and pass that to the private + /// implementation of transform provided by each component. + void transform(Options &state); /// Use the final simulation state to update internal state /// (e.g. time derivatives) @@ -47,6 +51,17 @@ struct Component { const std::string &name, // The species/name for this instance Options &options, // Component settings: options[name] are specific to this component Solver *solver); // Time integration solver + +protected: + /// Information on which state variables the transform method will read and write + Permissions state_variable_access; + +private: + /// Modify the given simulation state. All components must + /// implement this function. It will only allow the reading + /// from/writing to state variables with the appropriate permissiosn + /// in `state_variable_access`. + virtual void transform(GuardedOptions &state) = 0; }; /////////////////////////////////////////////////////////////////// @@ -96,6 +111,10 @@ T getNonFinal(const Options& option) { option.str(), typeid(T).name()); } } +template +T getNonFinal(const GuardedOptions option) { + return getNonFinal(option.get()); +} #define TOSTRING_(x) #x #define TOSTRING(x) TOSTRING_(x) @@ -120,11 +139,16 @@ T get(const Options& option, [[maybe_unused]] const std::string& location = "") #endif return getNonFinal(option); } +template +T get(const GuardedOptions option, const std::string& location = "") { + return get(option.get(), location); +} /// Check if an option can be fetched /// Sets the final flag so setting the value /// afterwards will lead to an error bool isSetFinal(const Options& option, const std::string& location = ""); +bool isSetFinal(const GuardedOptions option, const std::string& location = ""); #if CHECKLEVEL >= 1 /// A wrapper around isSetFinal() which captures debugging information @@ -142,6 +166,7 @@ bool isSetFinal(const Options& option, const std::string& location = ""); /// Sets the final flag so setting the value in the domain /// afterwards will lead to an error bool isSetFinalNoBoundary(const Options& option, const std::string& location = ""); +bool isSetFinalNoBoundary(const GuardedOptions option, const std::string& location = ""); #if CHECKLEVEL >= 1 /// A wrapper around isSetFinalNoBoundary() which captures debugging information @@ -187,6 +212,10 @@ T getNoBoundary(const Options& option, [[maybe_unused]] const std::string& locat #endif return getNonFinal(option); } +template +T getNoBoundary(const GuardedOptions option, const std::string& location = "") { + return getNoBoundary(option.get(Permissions::Interior), location); +} #if CHECKLEVEL >= 1 /// A wrapper around get<>() which captures debugging information @@ -260,6 +289,11 @@ Options& set(Options& option, T value) { option.force(std::move(value)); return option; } +template +GuardedOptions set(GuardedOptions option, T value) { + set(option.getWritable(), value); + return option; +} /// Set values in an option. This could be optimised, but /// currently the is_value private variable would need to be modified. @@ -281,6 +315,11 @@ Options& setBoundary(Options& option, T value) { option.force(std::move(value)); return option; } +template +GuardedOptions setBoundary(GuardedOptions option, T value) { + setBoundary(option.getWritable(Permissions::Boundaries), value); + return option; +} /// Add value to a given option. If not already set, treats /// as zero and sets the option to the value. @@ -304,6 +343,11 @@ Options& add(Options& option, T value) { } } } +template +GuardedOptions add(GuardedOptions option, T value) { + add(option.getWritable(), value); + return option; +} /// Add value to a given option. If not already set, treats /// as zero and sets the option to the value. @@ -324,12 +368,21 @@ Options& subtract(Options& option, T value) { } } } +template +GuardedOptions subtract(GuardedOptions option, T value) { + subtract(option.getWritable(), value); + return option; +} template void set_with_attrs(Options& option, T value, std::initializer_list> attrs) { option.force(value); option.setAttributes(attrs); } +template +void set_with_attrs(GuardedOptions option, T value, std::initializer_list> attrs) { + set_with_attrs(option.getWritable(), value, attrs); +} #if CHECKLEVEL >= 1 template<> @@ -340,6 +393,10 @@ inline void set_with_attrs(Options& option, Field3D value, std::initializer_list option.force(value); option.setAttributes(attrs); } +template<> +inline void set_with_attrs(GuardedOptions option, Field3D value, std::initializer_list> attrs) { + set_with_attrs(option.getWritable(), value, attrs); +} #endif #endif // HERMES_COMPONENT_H diff --git a/include/detachment_controller.hxx b/include/detachment_controller.hxx index 77c39465b..8f33af5c8 100644 --- a/include/detachment_controller.hxx +++ b/include/detachment_controller.hxx @@ -156,8 +156,6 @@ ASSERT0(BoutComm::size() == 1); // Only works on one processor }; - void transform(Options& state) override; - void outputVars(Options& state) override { AUTO_TRACE(); if (diagnose) { @@ -316,6 +314,8 @@ ASSERT0(BoutComm::size() == 1); // Only works on one processor std::vector time_buffer; std::vector error_buffer; + void transform(GuardedOptions& state) override; + }; namespace { diff --git a/include/diamagnetic_drift.hxx b/include/diamagnetic_drift.hxx index 2a4bbde82..b4c9e0ca9 100644 --- a/include/diamagnetic_drift.hxx +++ b/include/diamagnetic_drift.hxx @@ -9,6 +9,11 @@ struct DiamagneticDrift : public Component { DiamagneticDrift(std::string name, Options &options, Solver *UNUSED(solver)); +private: + Vector2D Curlb_B; + bool bndry_flux; + Field2D diamag_form; + /// For every species, if it has: /// - temperature /// - charge @@ -17,12 +22,7 @@ struct DiamagneticDrift : public Component { /// - density_source /// - energy_source /// - momentum_source - void transform(Options &state) override; - -private: - Vector2D Curlb_B; - bool bndry_flux; - Field2D diamag_form; + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/electromagnetic.hxx b/include/electromagnetic.hxx index 4296a7a5e..bfca17679 100644 --- a/include/electromagnetic.hxx +++ b/include/electromagnetic.hxx @@ -37,24 +37,6 @@ struct Electromagnetic : public Component { /// Electromagnetic(std::string name, Options &options, Solver *solver); - /// Inputs - /// - species - /// - <..> All species with charge and parallel momentum - /// - charge - /// - momentum - /// - density - /// - AA - /// - /// Sets - /// - species - /// - <..> All species with charge and parallel momentum - /// - momentum (modifies) to m n v|| - /// - velocity (modifies) to v|| - /// - fields - /// - Apar Electromagnetic potential - /// - void transform(Options &state) override; - // Save and restore Apar from restart files void restartVars(Options& state) override; @@ -75,6 +57,24 @@ private: Field3D Apar_flutter; bool diagnose; ///< Output additional diagnostics? + + /// Inputs + /// - species + /// - <..> All species with charge and parallel momentum + /// - charge + /// - momentum + /// - density + /// - AA + /// + /// Sets + /// - species + /// - <..> All species with charge and parallel momentum + /// - momentum (modifies) to m n v|| + /// - velocity (modifies) to v|| + /// - fields + /// - Apar Electromagnetic potential + /// + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/electron_force_balance.hxx b/include/electron_force_balance.hxx index 6fc771881..08feabb5b 100644 --- a/include/electron_force_balance.hxx +++ b/include/electron_force_balance.hxx @@ -27,6 +27,13 @@ struct ElectronForceBalance : public Component { .withDefault(false); } + /// Save output diagnostics + void outputVars(Options& state) override; +private: + bool diagnose; ///< Output additional fields + + Field3D Epar; ///< Parallel electric field + /// Required inputs /// - species /// - e @@ -40,14 +47,7 @@ struct ElectronForceBalance : public Component { /// - if both density and charge are set /// - momentum_source /// - void transform(Options &state) override; - - /// Save output diagnostics - void outputVars(Options& state) override; -private: - bool diagnose; ///< Output additional fields - - Field3D Epar; ///< Parallel electric field + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/evolve_density.hxx b/include/evolve_density.hxx index c2a7c15cc..f14b397b1 100644 --- a/include/evolve_density.hxx +++ b/include/evolve_density.hxx @@ -32,14 +32,6 @@ struct EvolveDensity : public Component { /// EvolveDensity(std::string name, Options &options, Solver *solver); - /// This sets in the state - /// - species - /// - - /// - AA - /// - charge - /// - density - void transform(Options &state) override; - /// Calculate ddt(N). /// /// Requires state components @@ -93,6 +85,14 @@ private: bool diagnose; ///< Output additional diagnostics? Field3D flow_xlow, flow_ylow; ///< Particle flow diagnostics + + /// This sets in the state + /// - species + /// - + /// - AA + /// - charge + /// - density + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/evolve_energy.hxx b/include/evolve_energy.hxx index 440a23ef0..cc7dc496b 100644 --- a/include/evolve_energy.hxx +++ b/include/evolve_energy.hxx @@ -36,20 +36,6 @@ struct EvolveEnergy : public Component { /// EvolveEnergy(std::string name, Options& options, Solver* solver); - /// Inputs - /// - species - /// - - /// - density - /// - velocity - /// - /// Sets - /// - species - /// - - /// - pressure - /// - temperature - /// - void transform(Options& state) override; - /// /// Optional inputs /// @@ -97,6 +83,20 @@ private: bool diagnose; ///< Output additional diagnostics? bool enable_precon; ///< Enable preconditioner? Field3D flow_xlow, flow_ylow; ///< Energy flow diagnostics + + /// Inputs + /// - species + /// - + /// - density + /// - velocity + /// + /// Sets + /// - species + /// - + /// - pressure + /// - temperature + /// + void transform(GuardedOptions& state) override; }; namespace { diff --git a/include/evolve_momentum.hxx b/include/evolve_momentum.hxx index c13f91571..b0e44e1b9 100644 --- a/include/evolve_momentum.hxx +++ b/include/evolve_momentum.hxx @@ -7,13 +7,6 @@ /// Evolve parallel momentum struct EvolveMomentum : public Component { EvolveMomentum(std::string name, Options &options, Solver *solver); - - /// This sets in the state - /// - species - /// - - /// - momentum - /// - velocity if density is defined - void transform(Options &state) override; /// Calculate ddt(NV). /// @@ -54,6 +47,13 @@ private: bool diagnose; ///< Output additional diagnostics? bool fix_momentum_boundary_flux; ///< Fix momentum flux to boundary condition? Field3D flow_xlow, flow_ylow; ///< Momentum flow diagnostics + + /// This sets in the state + /// - species + /// - + /// - momentum + /// - velocity if density is defined + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/evolve_pressure.hxx b/include/evolve_pressure.hxx index 04fbe719d..f58f6ff7f 100644 --- a/include/evolve_pressure.hxx +++ b/include/evolve_pressure.hxx @@ -38,19 +38,6 @@ struct EvolvePressure : public Component { /// EvolvePressure(std::string name, Options& options, Solver* solver); - /// Inputs - /// - species - /// - - /// - density - /// - /// Sets - /// - species - /// - - /// - pressure - /// - temperature Requires density - /// - void transform(Options& state) override; - /// /// Optional inputs /// @@ -113,6 +100,19 @@ private: bool fix_momentum_boundary_flux; ///< Fix momentum flux to boundary condition? Field3D Sp_nvh; ///< Pressure source due to artificial viscosity Field3D E_PdivV, E_VgradP; ///< Diagnostic energy source terms for p*Div(V) and V*Grad(P) + + /// Inputs + /// - species + /// - + /// - density + /// + /// Sets + /// - species + /// - + /// - pressure + /// - temperature Requires density + /// + void transform(GuardedOptions& state) override; }; namespace { diff --git a/include/fixed_density.hxx b/include/fixed_density.hxx index 0ee9080d3..cdfddd97f 100644 --- a/include/fixed_density.hxx +++ b/include/fixed_density.hxx @@ -29,23 +29,6 @@ struct FixedDensity : public Component { N = options["density"].as() / Nnorm; } - /// Sets in the state the density, mass and charge of the species - /// - /// - species - /// - - /// - AA - /// - charge - /// - density - void transform(Options& state) override { - AUTO_TRACE(); - auto& species = state["species"][name]; - if (charge != 0.0) { // Don't set charge for neutral species - set(species["charge"], charge); - } - set(species["AA"], AA); // Atomic mass - set(species["density"], N); - } - void outputVars(Options& state) override { AUTO_TRACE(); auto Nnorm = get(state["Nnorm"]); @@ -66,6 +49,23 @@ private: BoutReal AA; ///< Atomic mass e.g. proton = 1 Field3D N; ///< Species density (normalised) + + /// Sets in the state the density, mass and charge of the species + /// + /// - species + /// - + /// - AA + /// - charge + /// - density + void transform(GuardedOptions& state) override { + AUTO_TRACE(); + auto& species = state["species"][name]; + if (charge != 0.0) { // Don't set charge for neutral species + set(species["charge"], charge); + } + set(species["AA"], AA); // Atomic mass + set(species["density"], N); + } }; namespace { diff --git a/include/fixed_fraction_ions.hxx b/include/fixed_fraction_ions.hxx index 5ea0b5f1b..6fa8a4e36 100644 --- a/include/fixed_fraction_ions.hxx +++ b/include/fixed_fraction_ions.hxx @@ -13,6 +13,9 @@ struct FixedFractionIons : public Component { /// e.g. 'd+ @ 0.5, t+ @ 0.5' FixedFractionIons(std::string name, Options &options, Solver *UNUSED(solver)); + private: + std::vector> fractions; + /// Required inputs /// /// - species @@ -25,10 +28,7 @@ struct FixedFractionIons : public Component { /// - /// - density = * electron density /// - ... - void transform(Options &state) override; - - private: - std::vector> fractions; + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/fixed_fraction_radiation.hxx b/include/fixed_fraction_radiation.hxx index bba24d01c..b09d7697a 100644 --- a/include/fixed_fraction_radiation.hxx +++ b/include/fixed_fraction_radiation.hxx @@ -371,6 +371,32 @@ struct FixedFractionRadiation : public Component { FreqNorm = 1. / get(units["seconds"]); } + void outputVars(Options& state) override { + AUTO_TRACE(); + + if (diagnose) { + set_with_attrs(state[std::string("R") + name], -radiation, + {{"time_dimension", "t"}, + {"units", "W / m^3"}, + {"conversion", SI::qe * Tnorm * Nnorm * FreqNorm}, + {"long_name", std::string("Radiation cooling ") + name}, + {"source", "fixed_fraction_radiation"}}); + } + } + private: + std::string name; + + CoolingCurve cooling; ///< The cooling curve L(T) -> Wm^3 + BoutReal fraction; ///< Fixed fraction + + bool diagnose; ///< Output radiation diagnostic? + bool no_core_radiation; ///< Set radiation to zero in core? + BoutReal radiation_multiplier; ///< Scale the radiation rate by this factor + Field3D radiation; ///< For output diagnostic + + // Normalisations + BoutReal Tnorm, Nnorm, FreqNorm; + /// Required inputs /// /// - species @@ -384,7 +410,7 @@ struct FixedFractionRadiation : public Component { /// - e /// - energy_source /// - void transform(Options &state) override { + void transform(GuardedOptions &state) override { auto& electrons = state["species"]["e"]; // Don't need boundary cells const Field3D Ne = GET_NOBOUNDARY(Field3D, electrons["density"]); @@ -421,32 +447,6 @@ struct FixedFractionRadiation : public Component { // Remove radiation from the electron energy source subtract(electrons["energy_source"], radiation); } - - void outputVars(Options& state) override { - AUTO_TRACE(); - - if (diagnose) { - set_with_attrs(state[std::string("R") + name], -radiation, - {{"time_dimension", "t"}, - {"units", "W / m^3"}, - {"conversion", SI::qe * Tnorm * Nnorm * FreqNorm}, - {"long_name", std::string("Radiation cooling ") + name}, - {"source", "fixed_fraction_radiation"}}); - } - } - private: - std::string name; - - CoolingCurve cooling; ///< The cooling curve L(T) -> Wm^3 - BoutReal fraction; ///< Fixed fraction - - bool diagnose; ///< Output radiation diagnostic? - bool no_core_radiation; ///< Set radiation to zero in core? - BoutReal radiation_multiplier; ///< Scale the radiation rate by this factor - Field3D radiation; ///< For output diagnostic - - // Normalisations - BoutReal Tnorm, Nnorm, FreqNorm; }; namespace { diff --git a/include/fixed_temperature.hxx b/include/fixed_temperature.hxx index 19c8ec6cb..ea439dd57 100644 --- a/include/fixed_temperature.hxx +++ b/include/fixed_temperature.hxx @@ -28,33 +28,6 @@ struct FixedTemperature : public Component { .withDefault(false); } - /// Sets in the state the temperature and pressure of the species - /// - /// Inputs - /// - species - /// - - /// - density (optional) - /// - /// Sets in the state - /// - /// - species - /// - - /// - temperature - /// - pressure (if density is set) - void transform(Options& state) override { - AUTO_TRACE(); - auto& species = state["species"][name]; - - set(species["temperature"], T); - - // If density is set, also set pressure - if (isSetFinalNoBoundary(species["density"])) { - // Note: The boundary of N may not be set yet - auto N = GET_NOBOUNDARY(Field3D, species["density"]); - P = N * T; - set(species["pressure"], P); - } - } void outputVars(Options& state) override { AUTO_TRACE(); @@ -92,6 +65,34 @@ private: Field3D P; ///< Species pressure (normalised) bool diagnose; ///< Output additional fields + + /// Sets in the state the temperature and pressure of the species + /// + /// Inputs + /// - species + /// - + /// - density (optional) + /// + /// Sets in the state + /// + /// - species + /// - + /// - temperature + /// - pressure (if density is set) + void transform(GuardedOptions& state) override { + AUTO_TRACE(); + auto& species = state["species"][name]; + + set(species["temperature"], T); + + // If density is set, also set pressure + if (isSetFinalNoBoundary(species["density"])) { + // Note: The boundary of N may not be set yet + auto N = GET_NOBOUNDARY(Field3D, species["density"]); + P = N * T; + set(species["pressure"], P); + } + } }; namespace { diff --git a/include/fixed_velocity.hxx b/include/fixed_velocity.hxx index cb9613ce7..9825a5fb5 100644 --- a/include/fixed_velocity.hxx +++ b/include/fixed_velocity.hxx @@ -30,25 +30,6 @@ struct FixedVelocity : public Component { V = options["velocity"].withDefault(V) / Cs0; } - /// This sets in the state - /// - species - /// - - /// - velocity - /// - momentum - void transform(Options& state) override { - AUTO_TRACE(); - auto& species = state["species"][name]; - set(species["velocity"], V); - - // If density is set, also set momentum - if (isSetFinalNoBoundary(species["density"])) { - const Field3D N = getNoBoundary(species["density"]); - const BoutReal AA = get(species["AA"]); // Atomic mass - - set(species["momentum"], AA * N * V); - } - } - void outputVars(Options& state) override { AUTO_TRACE(); auto Cs0 = get(state["Cs0"]); @@ -67,6 +48,25 @@ private: std::string name; ///< Short name of species e.g "e" Field3D V; ///< Species velocity (normalised) + + /// This sets in the state + /// - species + /// - + /// - velocity + /// - momentum + void transform(GuardedOptions& state) override { + AUTO_TRACE(); + auto& species = state["species"][name]; + set(species["velocity"], V); + + // If density is set, also set momentum + if (isSetFinalNoBoundary(species["density"])) { + const Field3D N = getNoBoundary(species["density"]); + const BoutReal AA = get(species["AA"]); // Atomic mass + + set(species["momentum"], AA * N * V); + } + } }; namespace { diff --git a/include/guarded_options.hxx b/include/guarded_options.hxx index 37825fafa..6fe62889f 100644 --- a/include/guarded_options.hxx +++ b/include/guarded_options.hxx @@ -11,7 +11,6 @@ /// from and writing to the underlying data. class GuardedOptions { public: - GuardedOptions() = delete; /// Create a guarded options object which applies the specified /// permissions to the underlying options object. Note that the /// variable names used in the Permissions object must always be the @@ -34,9 +33,17 @@ public: GuardedOptions operator[](const std::string& name); GuardedOptions operator[](const char* name) { return (*this)[std::string(name)]; } + const GuardedOptions operator[](const std::string& name) const; + const GuardedOptions operator[](const char* name) const { return (*this)[std::string(name)]; } + + std::map getChildren(); + bool isSection(const std::string& name) const; + bool isSection(const char* name) const { return (*this).isSection(std::string(name)); + } + /// Get read-only access to the underlying Options object. Throws /// BoutException if there is not read-permission for this object. - const Options& get(Permissions::Regions region = Permissions::AllRegions); + const Options& get(Permissions::Regions region = Permissions::AllRegions) const; /// Get read-write access to the underlying Options object. Throws /// BoutException if there is not write-permission for this object. Options& getWritable(Permissions::Regions region = Permissions::AllRegions); @@ -52,7 +59,7 @@ public: private: Options* options; Permissions* permissions; - std::shared_ptr> unread_variables, + mutable std::shared_ptr> unread_variables, unwritten_variables; GuardedOptions( diff --git a/include/hydrogen_charge_exchange.hxx b/include/hydrogen_charge_exchange.hxx index 0cf1ed390..47e6c20fc 100644 --- a/include/hydrogen_charge_exchange.hxx +++ b/include/hydrogen_charge_exchange.hxx @@ -69,7 +69,7 @@ protected: /// atom_energy Energy removed from atom1, added to ion2 /// ion_energy Energy removed from ion1, added to atom2 /// - void calculate_rates(Options& atom1, Options& ion1, Options& atom2, Options& ion2, + void calculate_rates(GuardedOptions atom1, GuardedOptions ion1, GuardedOptions atom2, GuardedOptions ion2, Field3D& R, Field3D& atom_mom, Field3D& ion_mom, Field3D& atom_energy, Field3D& ion_energy, Field3D& atom_rate, Field3D& ion_rate, BoutReal& rate_multiplier, @@ -140,38 +140,6 @@ struct HydrogenIsotopeChargeExchange : public HydrogenChargeExchange { .withDefault(1.0); } - void transform(Options& state) override { - Field3D R, atom_mom, ion_mom, atom_energy, ion_energy; - - calculate_rates(state["species"][{Isotope1}], // e.g. "h" - state["species"][{Isotope2, '+'}], // e.g. "d+" - state["species"][{Isotope2}], // e.g. "d" - state["species"][{Isotope1, '+'}], // e.g. "h+" - R, atom_mom, ion_mom, atom_energy, ion_energy, // Transfer channels - atom_rate, ion_rate, // Collision rates in s^-1 - rate_multiplier, // Arbitrary user set multiplier - no_neutral_cx_mom_gain); // Make CX behave as in diffusive neutrals? - - if (diagnose) { - // Calculate diagnostics to be written to dump file - if (Isotope1 == Isotope2) { - // Simpler case of same isotopes - // - No net particle source/sink - // - atoms lose atom_mom, gain ion_mom - // - F = ion_mom - atom_mom; // Momentum transferred to atoms due to CX with ions - E = ion_energy - atom_energy; // Energy transferred to atoms - } else { - // Different isotopes - S = -R; // Source of Isotope1 atoms - F = -atom_mom; // Source of momentum for Isotope1 atoms - F2 = -ion_mom; // Source of momentum for Isotope2 ions - E = -atom_energy; // Source of energy for Isotope1 atoms - E2 = -ion_energy; // Source of energy for Isotope2 ions - } - } - } - void outputVars(Options& state) override { AUTO_TRACE(); // Normalisations @@ -267,6 +235,38 @@ private: Field3D E, E2; ///< Energy exchange Field3D atom_rate, ion_rate; ///< Collision rates in s^-1 bool no_neutral_cx_mom_gain; ///< Make CX behave as in diffusive neutrals? + + void transform(GuardedOptions& state) override { + Field3D R, atom_mom, ion_mom, atom_energy, ion_energy; + + calculate_rates(state["species"][{Isotope1}], // e.g. "h" + state["species"][{Isotope2, '+'}], // e.g. "d+" + state["species"][{Isotope2}], // e.g. "d" + state["species"][{Isotope1, '+'}], // e.g. "h+" + R, atom_mom, ion_mom, atom_energy, ion_energy, // Transfer channels + atom_rate, ion_rate, // Collision rates in s^-1 + rate_multiplier, // Arbitrary user set multiplier + no_neutral_cx_mom_gain); // Make CX behave as in diffusive neutrals? + + if (diagnose) { + // Calculate diagnostics to be written to dump file + if (Isotope1 == Isotope2) { + // Simpler case of same isotopes + // - No net particle source/sink + // - atoms lose atom_mom, gain ion_mom + // + F = ion_mom - atom_mom; // Momentum transferred to atoms due to CX with ions + E = ion_energy - atom_energy; // Energy transferred to atoms + } else { + // Different isotopes + S = -R; // Source of Isotope1 atoms + F = -atom_mom; // Source of momentum for Isotope1 atoms + F2 = -ion_mom; // Source of momentum for Isotope2 ions + E = -atom_energy; // Source of energy for Isotope1 atoms + E2 = -ion_energy; // Source of energy for Isotope2 ions + } + } + } }; namespace { diff --git a/include/ionisation.hxx b/include/ionisation.hxx index d3a0e0dfd..325cc90f9 100644 --- a/include/ionisation.hxx +++ b/include/ionisation.hxx @@ -8,12 +8,13 @@ class Ionisation : public Component { public: Ionisation(std::string name, Options &options, Solver *); - void transform(Options &state) override; private: BoutReal Eionize; // Energy loss per ionisation [eV] BoutReal Tnorm, Nnorm, FreqNorm; // Normalisations + + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/isothermal.hxx b/include/isothermal.hxx index f80ee2656..f807a9157 100644 --- a/include/isothermal.hxx +++ b/include/isothermal.hxx @@ -9,6 +9,15 @@ struct Isothermal : public Component { Isothermal(std::string name, Options &options, Solver *); + void outputVars(Options &state) override; +private: + std::string name; // Species name + + BoutReal T; ///< The normalised temperature + Field3D P; ///< The normalised pressure + + bool diagnose; ///< Output additional diagnostics? + /// Inputs /// - species /// - @@ -21,16 +30,7 @@ struct Isothermal : public Component { /// - temperature /// - pressure (if density is set) /// - void transform(Options &state) override; - - void outputVars(Options &state) override; -private: - std::string name; // Species name - - BoutReal T; ///< The normalised temperature - Field3D P; ///< The normalised pressure - - bool diagnose; ///< Output additional diagnostics? + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/neutral_boundary.hxx b/include/neutral_boundary.hxx index f0f1f0d8a..4954243d8 100644 --- a/include/neutral_boundary.hxx +++ b/include/neutral_boundary.hxx @@ -23,18 +23,6 @@ struct NeutralBoundary : public Component { NeutralBoundary(std::string name, Options& options, Solver*); - /// - /// state - /// - species - /// - - /// - density Free boundary - /// - temperature Free boundary - /// - pressure Free boundary - /// - velocity [if set] Zero boundary - /// - momentum [if set] Zero boundary - /// - energy_source Adds wall losses - /// - void transform(Options& state) override; void outputVars(Options &state) override; private: @@ -53,6 +41,19 @@ private: bool upper_y; ///< Boundary condition at upper y? bool sol; ///< Boundary condition at sol? bool pfr; ///< Boundary condition at pfr? + + /// + /// state + /// - species + /// - + /// - density Free boundary + /// - temperature Free boundary + /// - pressure Free boundary + /// - velocity [if set] Zero boundary + /// - momentum [if set] Zero boundary + /// - energy_source Adds wall losses + /// + void transform(GuardedOptions& state) override; }; namespace { diff --git a/include/neutral_full_velocity.hxx b/include/neutral_full_velocity.hxx index e60757bf3..e6c3ac4bb 100644 --- a/include/neutral_full_velocity.hxx +++ b/include/neutral_full_velocity.hxx @@ -15,9 +15,6 @@ struct NeutralFullVelocity : public Component { NeutralFullVelocity(const std::string& name, Options& options, Solver* solver); - /// Modify the given simulation state - void transform(Options& state) override; - /// Use the final simulation state to update internal state /// (e.g. time derivatives) void finally(const Options& state) override; @@ -65,6 +62,9 @@ private: bool diagnose; ///< Output additional diagnostics? Field2D Vnpar; ///< Parallel flow velocity diagnostic + + /// Modify the given simulation state + void transform(GuardedOptions& state) override; }; namespace { diff --git a/include/neutral_mixed.hxx b/include/neutral_mixed.hxx index 0a80aaad6..82fde5273 100644 --- a/include/neutral_mixed.hxx +++ b/include/neutral_mixed.hxx @@ -19,9 +19,6 @@ struct NeutralMixed : public Component { /// @param solver Time-integration solver to be used NeutralMixed(const std::string& name, Options& options, Solver *solver); - /// Modify the given simulation state - void transform(Options &state) override; - /// Use the final simulation state to update internal state /// (e.g. time derivatives) void finally(const Options &state) override; @@ -82,6 +79,9 @@ private: Field3D mf_visc_perp_xlow, mf_visc_perp_ylow, mf_visc_par_ylow; Field3D ef_adv_perp_xlow, ef_adv_perp_ylow, ef_adv_par_ylow; Field3D ef_cond_perp_xlow, ef_cond_perp_ylow, ef_cond_par_ylow; + + /// Modify the given simulation state + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/neutral_parallel_diffusion.hxx b/include/neutral_parallel_diffusion.hxx index ebabb3ade..f5514b299 100644 --- a/include/neutral_parallel_diffusion.hxx +++ b/include/neutral_parallel_diffusion.hxx @@ -50,26 +50,6 @@ struct NeutralParallelDiffusion : public Component { .withDefault(true); } - /// - /// Inputs - /// - species - /// - # Applies to all neutral species - /// - AA - /// - collision_frequency - /// - density - /// - temperature - /// - pressure [optional, or density * temperature] - /// - velocity [optional] - /// - momentum [if velocity set] - /// - /// Sets - /// - species - /// - - /// - density_source - /// - energy_source - /// - momentum_source [if velocity set] - void transform(Options &state) override; - /// Save variables to the output void outputVars(Options &state) override; private: @@ -93,6 +73,26 @@ private: /// Store diagnostics for each species std::map diagnostics; + + /// + /// Inputs + /// - species + /// - # Applies to all neutral species + /// - AA + /// - collision_frequency + /// - density + /// - temperature + /// - pressure [optional, or density * temperature] + /// - velocity [optional] + /// - momentum [if velocity set] + /// + /// Sets + /// - species + /// - + /// - density_source + /// - energy_source + /// - momentum_source [if velocity set] + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/noflow_boundary.hxx b/include/noflow_boundary.hxx index 68d7e91b0..844def7db 100644 --- a/include/noflow_boundary.hxx +++ b/include/noflow_boundary.hxx @@ -17,6 +17,11 @@ struct NoFlowBoundary : public Component { .withDefault(true); } +private: + std::string name; ///< + bool noflow_lower_y; ///< No-flow boundary on lower y? + bool noflow_upper_y; ///< No-flow boundary on upper y? + /// Inputs /// - species /// - @@ -25,12 +30,7 @@ struct NoFlowBoundary : public Component { /// - pressure [Optional] /// - velocity [Optional] /// - momentum [Optional] - void transform(Options& state) override; - -private: - std::string name; ///< - bool noflow_lower_y; ///< No-flow boundary on lower y? - bool noflow_upper_y; ///< No-flow boundary on upper y? + void transform(GuardedOptions& state) override; }; namespace { diff --git a/include/permissions.hxx b/include/permissions.hxx index 70645d705..f62f52fbc 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -24,13 +24,15 @@ public: AllRegions = Interior | Boundaries }; + const static std::map fundamental_regions; + /// Data type for storing the regions of a variable which have a /// particular level of permission. Some examples can be seen below: /// /// AccessRights read_only = { AllRegions, Nowhere, Nowhere }, /// write_boundaries = { Nowhere, Boundaries, Nowhere }, - /// read_and_write_everywhere = { AllReginos, AllRegions, Nowhere }, - /// final_write_boundaries_read_interior = { Interior, Nowhere, + /// read_and_write_everywhere = { AllReginos, AllRegions, Nowhere + /// }, final_write_boundaries_read_interior = { Interior, Nowhere, /// Boundaries }; /// using AccessRights = std::array; @@ -159,6 +161,9 @@ public: std::map getVariablesWithPermission(PermissionTypes permission, bool highestOnly = true) const; + /// Return a string version of the region names + static std::string regionNames(const Regions regions); + private: /// Returns the access rights for the most specific entry in this /// object which matches the variable name. If there are no matching diff --git a/include/polarisation_drift.hxx b/include/polarisation_drift.hxx index 0663ad9fa..79071efa6 100644 --- a/include/polarisation_drift.hxx +++ b/include/polarisation_drift.hxx @@ -32,6 +32,23 @@ struct PolarisationDrift : public Component { // PolarisationDrift(std::string name, Options &options, Solver *UNUSED(solver)); + void outputVars(Options &state) override; +private: + std::unique_ptr phiSolver; // Laplacian solver in X-Z + + Field2D Bsq; // Cached SQ(coord->Bxy) + + // Diagnostic outputs + bool diagnose; ///< Save diagnostic outputs? + Field3D DivJ; //< Divergence of all other currents + Field3D phi_pol; //< Polarisation drift potential + + bool boussinesq; // If true, assume a constant mass density in Jpol + BoutReal average_atomic_mass; // If boussinesq=true, mass density to use + BoutReal density_floor; // Minimum mass density if boussinesq=false + bool advection; // Advect fluids by an approximate polarisation velocity? + bool diamagnetic_polarisation; // Calculate compression terms? + /// Inputs /// /// - species @@ -54,24 +71,7 @@ struct PolarisationDrift : public Component { /// - energy_source (if pressure set) /// - momentum_source (if momentum set) /// - void transform(Options &state) override; - - void outputVars(Options &state) override; -private: - std::unique_ptr phiSolver; // Laplacian solver in X-Z - - Field2D Bsq; // Cached SQ(coord->Bxy) - - // Diagnostic outputs - bool diagnose; ///< Save diagnostic outputs? - Field3D DivJ; //< Divergence of all other currents - Field3D phi_pol; //< Polarisation drift potential - - bool boussinesq; // If true, assume a constant mass density in Jpol - BoutReal average_atomic_mass; // If boussinesq=true, mass density to use - BoutReal density_floor; // Minimum mass density if boussinesq=false - bool advection; // Advect fluids by an approximate polarisation velocity? - bool diamagnetic_polarisation; // Calculate compression terms? + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/quasineutral.hxx b/include/quasineutral.hxx index 63340faf0..21f074937 100644 --- a/include/quasineutral.hxx +++ b/include/quasineutral.hxx @@ -25,15 +25,6 @@ struct Quasineutral : public Component { /// Quasineutral(std::string name, Options &alloptions, Solver *UNUSED(solver)); - /// - /// Sets in state - /// - species - /// - - /// - density - /// - charge - /// - AA - void transform(Options &state) override; - /// Get the final density for output /// including any boundary conditions applied void finally(const Options &state) override; @@ -45,6 +36,15 @@ private: BoutReal AA; ///< Atomic mass Field3D density; ///< The density (for writing to output) + + /// + /// Sets in state + /// - species + /// - + /// - density + /// - charge + /// - AA + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/rate_helper.hxx b/include/rate_helper.hxx index a2ff8720e..6da39348d 100644 --- a/include/rate_helper.hxx +++ b/include/rate_helper.hxx @@ -26,12 +26,12 @@ struct RateHelper { * factor, n_e and T_e * @param region the region in which to calculate the rate */ - RateHelper(const Options& state, const std::vector& reactant_names, + RateHelper(const GuardedOptions state, const std::vector& reactant_names, RateFunctionType rate_calc_func, const Region region) : region(region), rate_calc_func(rate_calc_func) { // Extract electron properties from state - const Options& electron = state["species"]["e"]; + const GuardedOptions electron = state["species"]["e"]; n_e = get(electron["density"]); T_e = get(electron["temperature"]); diff --git a/include/reaction.hxx b/include/reaction.hxx index 21e34681b..033634708 100644 --- a/include/reaction.hxx +++ b/include/reaction.hxx @@ -7,7 +7,7 @@ #include "reaction_diagnostic.hxx" #include "reaction_parser.hxx" -typedef Options& (*OPTYPE)(Options&, Field3D); +typedef GuardedOptions (*OPTYPE)(GuardedOptions, Field3D); /** * @brief Temporary struct to use as a base class for all reactions components. Ensures @@ -32,8 +32,6 @@ protected: struct Reaction : public ReactionBase { Reaction(std::string name, Options& alloptions); - void transform(Options& state) override final; - void outputVars(Options& state) override final; protected: @@ -85,7 +83,7 @@ protected: * * @param state Current sim state */ - void calc_weightsums(Options& state); + void calc_weightsums(GuardedOptions state); /** * @brief Evaluate at a particular density and temperature @@ -125,7 +123,7 @@ protected: * @param state * @param reaction_rate */ - virtual void transform_additional([[maybe_unused]] Options& state, + virtual void transform_additional([[maybe_unused]] GuardedOptions& state, [[maybe_unused]] Field3D& reaction_rate) {} /** @@ -140,7 +138,7 @@ protected: * @param fld the field used in the update */ template - void update_source(Options& state, const std::string& sp_name, + void update_source(GuardedOptions& state, const std::string& sp_name, ReactionDiagnosticType type, Field3D& fld) { // Update species data operation(state["species"][sp_name][state_labels.at(type)], fld); @@ -171,6 +169,8 @@ private: /// Participation factors of all species std::map pfactors; - void zero_diagnostics(Options& state); + void zero_diagnostics(GuardedOptions state); + + void transform(GuardedOptions& state) override final; }; #endif diff --git a/include/recycling.hxx b/include/recycling.hxx index 995effcf5..54da4e69a 100644 --- a/include/recycling.hxx +++ b/include/recycling.hxx @@ -22,20 +22,6 @@ struct Recycling : public Component { /// Recycling(std::string name, Options &alloptions, Solver *); - /// Inputs - /// - /// - species - /// - - /// - density - /// - velocity - /// - /// Outputs - /// - /// - species - /// - - /// - density_source - /// - void transform(Options &state) override; void outputVars(Options &state) override; private: @@ -79,6 +65,20 @@ private: Field3D particle_flow_xlow; ///< Radial wall particle fluxes for recycling calc. No need to get poloidal from here, it's calculated from sheath velocity Field2D is_pump; ///< 1 = pump, 0 = no pump. Works only in SOL/PFR. Provided by user in grid file. + /// Inputs + /// + /// - species + /// - + /// - density + /// - velocity + /// + /// Outputs + /// + /// - species + /// - + /// - density_source + /// + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/relax_potential.hxx b/include/relax_potential.hxx index bb6668d54..47a13f95b 100644 --- a/include/relax_potential.hxx +++ b/include/relax_potential.hxx @@ -27,26 +27,6 @@ struct RelaxPotential : public Component { /// RelaxPotential(std::string name, Options& options, Solver* solver); - /// Optional inputs - /// - /// - species - /// - pressure and charge => Calculates diamagnetic terms [if diamagnetic=true] - /// - pressure, charge and mass => Calculates polarisation current terms - /// [if diamagnetic_polarisation=true] - /// - /// Sets in the state - /// - species - /// - [if has pressure and charge] - /// - energy_source - /// - fields - /// - vorticity - /// - phi Electrostatic potential - /// - DivJdia Divergence of diamagnetic current [if diamagnetic=true] - /// - /// Note: Diamagnetic current calculated here, but could be moved - /// to a component with the diamagnetic drift advection terms - void transform(Options& state) override; - /// Optional inputs /// - fields /// - DivJextra Divergence of current, including parallel current @@ -76,6 +56,26 @@ private: Vector2D Curlb_B; ///< Curvature vector Curl(b/B) BoutReal lambda_1, lambda_2; ///< Relaxation parameters + + /// Optional inputs + /// + /// - species + /// - pressure and charge => Calculates diamagnetic terms [if diamagnetic=true] + /// - pressure, charge and mass => Calculates polarisation current terms + /// [if diamagnetic_polarisation=true] + /// + /// Sets in the state + /// - species + /// - [if has pressure and charge] + /// - energy_source + /// - fields + /// - vorticity + /// - phi Electrostatic potential + /// - DivJdia Divergence of diamagnetic current [if diamagnetic=true] + /// + /// Note: Diamagnetic current calculated here, but could be moved + /// to a component with the diamagnetic drift advection terms + void transform(GuardedOptions& state) override; }; namespace { diff --git a/include/scale_timederivs.hxx b/include/scale_timederivs.hxx index 4471188d6..2603fa117 100644 --- a/include/scale_timederivs.hxx +++ b/include/scale_timederivs.hxx @@ -13,12 +13,6 @@ struct ScaleTimeDerivs : public Component { ScaleTimeDerivs(std::string, Options&, Solver*) {} - /// Sets in the state - /// - /// - scale_timederivs - /// - void transform(Options &state) override { - auto* coord = bout::globals::mesh->getCoordinates(); Field2D dl2 = coord->g_22 * SQ(coord->dy); @@ -39,6 +33,12 @@ struct ScaleTimeDerivs : public Component { } private: Field3D scaling; // The scaling factor applied to each cell + + /// Sets in the state + /// + /// - scale_timederivs + /// + void transform(GuardedOptions &state) override { }; namespace { diff --git a/include/set_temperature.hxx b/include/set_temperature.hxx index 2bb51c7e1..4825e2667 100644 --- a/include/set_temperature.hxx +++ b/include/set_temperature.hxx @@ -37,6 +37,30 @@ struct SetTemperature : public Component { .withDefault(false); } + void outputVars(Options& state) override { + AUTO_TRACE(); + + if (diagnose) { + auto Tnorm = get(state["Tnorm"]); + + // Save temperature to output files + set_with_attrs(state[std::string("T") + name], T, + {{"time_dimension", "t"}, + {"units", "eV"}, + {"conversion", Tnorm}, + {"standard_name", "temperature"}, + {"long_name", name + " temperature set from " + temperature_from}, + {"species", name}, + {"source", "set_temperature"}}); + } + } + +private: + std::string name; ///< Short name of species e.g "e" + std::string temperature_from; ///< The species that the temperature is taken from + Field3D T; ///< The temperature + bool diagnose; ///< Output diagnostics? + /// /// Inputs /// - species @@ -49,7 +73,7 @@ struct SetTemperature : public Component { /// - temperature /// - pressure (if density is set) /// - void transform(Options& state) override { + void transform(GuardedOptions& state) override { AUTO_TRACE(); // Get the temperature @@ -65,30 +89,6 @@ struct SetTemperature : public Component { set(species["pressure"], N * T); } } - - void outputVars(Options& state) override { - AUTO_TRACE(); - - if (diagnose) { - auto Tnorm = get(state["Tnorm"]); - - // Save temperature to output files - set_with_attrs(state[std::string("T") + name], T, - {{"time_dimension", "t"}, - {"units", "eV"}, - {"conversion", Tnorm}, - {"standard_name", "temperature"}, - {"long_name", name + " temperature set from " + temperature_from}, - {"species", name}, - {"source", "set_temperature"}}); - } - } - -private: - std::string name; ///< Short name of species e.g "e" - std::string temperature_from; ///< The species that the temperature is taken from - Field3D T; ///< The temperature - bool diagnose; ///< Output diagnostics? }; namespace { diff --git a/include/sheath_boundary.hxx b/include/sheath_boundary.hxx index c3e2532f5..87face383 100644 --- a/include/sheath_boundary.hxx +++ b/include/sheath_boundary.hxx @@ -31,6 +31,19 @@ struct SheathBoundary : public Component { /// - always_set_phi Always set phi field? Default is to only modify if already set SheathBoundary(std::string name, Options &options, Solver *); +private: + BoutReal Ge; // Secondary electron emission coefficient + Field3D sin_alpha; // sin of angle between magnetic field and wall. + + bool lower_y; // Boundary on lower y? + bool upper_y; // Boundary on upper y? + + bool always_set_phi; ///< Set phi field? + + Field3D wall_potential; ///< Voltage at the wall. Normalised units. + + bool floor_potential; ///< Apply floor to sheath potential? + /// /// # Inputs /// - species @@ -74,19 +87,7 @@ struct SheathBoundary : public Component { /// Note that phi in the domain will not be set, so will be invalid data. /// /// - void transform(Options &state) override; -private: - BoutReal Ge; // Secondary electron emission coefficient - Field3D sin_alpha; // sin of angle between magnetic field and wall. - - bool lower_y; // Boundary on lower y? - bool upper_y; // Boundary on upper y? - - bool always_set_phi; ///< Set phi field? - - Field3D wall_potential; ///< Voltage at the wall. Normalised units. - - bool floor_potential; ///< Apply floor to sheath potential? + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/sheath_boundary_insulating.hxx b/include/sheath_boundary_insulating.hxx index ecc0fc966..c50b5faae 100644 --- a/include/sheath_boundary_insulating.hxx +++ b/include/sheath_boundary_insulating.hxx @@ -13,6 +13,15 @@ struct SheathBoundaryInsulating : public Component { SheathBoundaryInsulating(std::string name, Options &options, Solver *); +private: + BoutReal Ge; // Secondary electron emission coefficient + BoutReal sin_alpha; // sin of angle between magnetic field and wall. + + bool lower_y; // Boundary on lower y? + bool upper_y; // Boundary on upper y? + + BoutReal gamma_e; ///< Electron sheath heat transmission + /// /// Inputs /// - species @@ -56,15 +65,7 @@ struct SheathBoundaryInsulating : public Component { /// Note that phi in the domain will not be set, so will be invalid data. /// /// - void transform(Options &state) override; -private: - BoutReal Ge; // Secondary electron emission coefficient - BoutReal sin_alpha; // sin of angle between magnetic field and wall. - - bool lower_y; // Boundary on lower y? - bool upper_y; // Boundary on upper y? - - BoutReal gamma_e; ///< Electron sheath heat transmission + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/sheath_boundary_simple.hxx b/include/sheath_boundary_simple.hxx index 6f4cf975c..7dadb7ec9 100644 --- a/include/sheath_boundary_simple.hxx +++ b/include/sheath_boundary_simple.hxx @@ -29,6 +29,34 @@ struct SheathBoundarySimple : public Component { /// - always_set_phi Always set phi field? Default is to only modify if already set SheathBoundarySimple(std::string name, Options &options, Solver *); + void outputVars(Options &state) override; + +private: + BoutReal Ge; // Secondary electron emission coefficient + BoutReal sin_alpha; // sin of angle between magnetic field and wall. + + BoutReal gamma_e; ///< Electron sheath heat transmission + BoutReal gamma_i; ///< Ion sheath heat transmission + BoutReal sheath_ion_polytropic; ///< Polytropic coefficient in sheat velocity + + bool lower_y; // Boundary on lower y? + bool upper_y; // Boundary on upper y? + + bool always_set_phi; ///< Set phi field? + + Field3D wall_potential; ///< Voltage of the wall. Normalised units. + + Field3D hflux_e; // Electron heat flux through sheath + Field3D phi; // Phi at sheath + Field3D ion_sum; // Sum of ion current at sheath + + bool diagnose; // Save diagnostic variables? + Options diagnostics; // Options object to store diagnostic fields like a dict + + bool no_flow; ///< No flow speed, only remove energy + + BoutReal density_boundary_mode, pressure_boundary_mode, temperature_boundary_mode; ///< BC mode: 0=LimitFree, 1=ExponentialFree, 2=LinearFree + /// /// # Inputs /// - species @@ -72,33 +100,7 @@ struct SheathBoundarySimple : public Component { /// Note that phi in the domain will not be set, so will be invalid data. /// /// - void transform(Options &state) override; - void outputVars(Options &state) override; -private: - BoutReal Ge; // Secondary electron emission coefficient - BoutReal sin_alpha; // sin of angle between magnetic field and wall. - - BoutReal gamma_e; ///< Electron sheath heat transmission - BoutReal gamma_i; ///< Ion sheath heat transmission - BoutReal sheath_ion_polytropic; ///< Polytropic coefficient in sheat velocity - - bool lower_y; // Boundary on lower y? - bool upper_y; // Boundary on upper y? - - bool always_set_phi; ///< Set phi field? - - Field3D wall_potential; ///< Voltage of the wall. Normalised units. - - Field3D hflux_e; // Electron heat flux through sheath - Field3D phi; // Phi at sheath - Field3D ion_sum; // Sum of ion current at sheath - - bool diagnose; // Save diagnostic variables? - Options diagnostics; // Options object to store diagnostic fields like a dict - - bool no_flow; ///< No flow speed, only remove energy - - BoutReal density_boundary_mode, pressure_boundary_mode, temperature_boundary_mode; ///< BC mode: 0=LimitFree, 1=ExponentialFree, 2=LinearFree + void transform(GuardedOptions &state) override; }; namespace { @@ -106,4 +108,4 @@ RegisterComponent registercomponentsheathboundarysimple("sheath_boundary_simple"); } -#endif // SHEATH_BOUNDARY_SIMPLE_H \ No newline at end of file +#endif // SHEATH_BOUNDARY_SIMPLE_H diff --git a/include/sheath_closure.hxx b/include/sheath_closure.hxx index 07e60b4c0..e4246db2f 100644 --- a/include/sheath_closure.hxx +++ b/include/sheath_closure.hxx @@ -17,6 +17,17 @@ struct SheathClosure : public Component { /// SheathClosure(std::string name, Options &options, Solver *); +private: + BoutReal L_par; // Normalised connection length + + BoutReal sheath_gamma; // Sheath heat transmission coefficient + + BoutReal sheath_gamma_ions; // Sheath heat transmission coefficient for ions + + BoutReal offset; // Potential at which the sheath current is zero + + bool sinks; // Include sinks of density and energy? + /// Inputs /// - fields /// - phi Electrostatic potential @@ -34,17 +45,7 @@ struct SheathClosure : public Component { /// - fields /// - DivJdia Divergence of current /// - void transform(Options &state) override; -private: - BoutReal L_par; // Normalised connection length - - BoutReal sheath_gamma; // Sheath heat transmission coefficient - - BoutReal sheath_gamma_ions; // Sheath heat transmission coefficient for ions - - BoutReal offset; // Potential at which the sheath current is zero - - bool sinks; // Include sinks of density and energy? + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/simple_conduction.hxx b/include/simple_conduction.hxx index 238ad0704..ae9b3fc1e 100644 --- a/include/simple_conduction.hxx +++ b/include/simple_conduction.hxx @@ -56,7 +56,17 @@ struct SimpleConduction : public Component { .withDefault(false); } - void transform(Options& state) override { +private: + std::string name; ///< Name of the species e.g. "e" + BoutReal kappa0; ///< Pre-calculated constant in heat conduction coefficient + BoutReal Nnorm, Tnorm; ///< Normalisation coefficients + + BoutReal temperature; ///< Fix temperature if > 0 + BoutReal density; ///< Fix density if > 0 + + bool boundary_flux; ///< Allow flux through sheath boundaries? + + void transform(GuardedOptions& state) override { auto& species = state["species"][name]; // Species time-evolving temperature @@ -85,16 +95,6 @@ struct SimpleConduction : public Component { add(species["energy_source"], DivQ); } - -private: - std::string name; ///< Name of the species e.g. "e" - BoutReal kappa0; ///< Pre-calculated constant in heat conduction coefficient - BoutReal Nnorm, Tnorm; ///< Normalisation coefficients - - BoutReal temperature; ///< Fix temperature if > 0 - BoutReal density; ///< Fix density if > 0 - - bool boundary_flux; ///< Allow flux through sheath boundaries? }; namespace { diff --git a/include/simple_pump.hxx b/include/simple_pump.hxx index dd0f85457..bb6fc3287 100644 --- a/include/simple_pump.hxx +++ b/include/simple_pump.hxx @@ -33,16 +33,6 @@ struct SimplePump : public Component { }; - void transform(Options& state) override { - - Field3D species_density = getNoBoundary(state["species"][name]["density"]); - - pumping_sink = (sink_shape * species_density) * (-1.0 / residence_time); - - add(state["species"][name]["density_source"], pumping_sink); - - }; - void outputVars(Options& state) override { AUTO_TRACE(); if (diagnose) { @@ -69,6 +59,15 @@ struct SimplePump : public Component { BoutReal residence_time; bool diagnose; + void transform(GuardedOptions& state) override { + + Field3D species_density = getNoBoundary(state["species"][name]["density"]); + + pumping_sink = (sink_shape * species_density) * (-1.0 / residence_time); + + add(state["species"][name]["density_source"], pumping_sink); + + }; }; namespace { diff --git a/include/snb_conduction.hxx b/include/snb_conduction.hxx index bc609ce10..0760991a3 100644 --- a/include/snb_conduction.hxx +++ b/include/snb_conduction.hxx @@ -55,6 +55,14 @@ struct SNBConduction : public Component { .withDefault(false); } + void outputVars(Options& state) override; +private: + bout::HeatFluxSNB snb; + + Field3D Div_Q_SH, Div_Q_SNB; ///< Divergence of heat fluxes + + bool diagnose; ///< Output additional diagnostics? + /// Inputs /// - species /// - e @@ -65,15 +73,7 @@ struct SNBConduction : public Component { /// - species /// - e /// - energy_source - void transform(Options& state) override; - - void outputVars(Options& state) override; -private: - bout::HeatFluxSNB snb; - - Field3D Div_Q_SH, Div_Q_SNB; ///< Divergence of heat fluxes - - bool diagnose; ///< Output additional diagnostics? + void transform(GuardedOptions& state) override; }; namespace { diff --git a/include/solkit_hydrogen_charge_exchange.hxx b/include/solkit_hydrogen_charge_exchange.hxx index 0a186dc9d..ead37d901 100644 --- a/include/solkit_hydrogen_charge_exchange.hxx +++ b/include/solkit_hydrogen_charge_exchange.hxx @@ -31,7 +31,7 @@ struct SOLKITHydrogenChargeExchange : public ReactionBase { /// Sets in all species: /// - momentum_source /// - void calculate_rates(Options& atom, Options& ion); + void calculate_rates(GuardedOptions atom, GuardedOptions ion); protected: BoutReal Nnorm, rho_s0; ///< Normalisations @@ -46,7 +46,8 @@ struct SOLKITHydrogenChargeExchangeIsotope : public SOLKITHydrogenChargeExchange SOLKITHydrogenChargeExchangeIsotope(std::string name, Options& alloptions, Solver* solver) : SOLKITHydrogenChargeExchange(name, alloptions, solver) {} - void transform(Options& state) override { +private: + void transform(GuardedOptions& state) override { calculate_rates(state["species"][{Isotope}], // e.g. "h" state["species"][{Isotope, '+'}]); // e.g. "d+" } diff --git a/include/solkit_neutral_parallel_diffusion.hxx b/include/solkit_neutral_parallel_diffusion.hxx index 8f1242144..b80299256 100644 --- a/include/solkit_neutral_parallel_diffusion.hxx +++ b/include/solkit_neutral_parallel_diffusion.hxx @@ -32,6 +32,10 @@ struct SOLKITNeutralParallelDiffusion : public Component { area_norm = 1. / (Nnorm * rho_s0); } +private: + BoutReal neutral_temperature; ///< Fixed neutral t + BoutReal area_norm; ///< Area normalisation [m^2] + /// /// Inputs /// - species @@ -43,11 +47,7 @@ struct SOLKITNeutralParallelDiffusion : public Component { /// - species /// - /// - density_source - void transform(Options &state) override; - -private: - BoutReal neutral_temperature; ///< Fixed neutral t - BoutReal area_norm; ///< Area normalisation [m^2] + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/sound_speed.hxx b/include/sound_speed.hxx index 69be20c35..bf99331a0 100644 --- a/include/sound_speed.hxx +++ b/include/sound_speed.hxx @@ -41,6 +41,13 @@ struct SoundSpeed : public Component { temperature_floor /= get(alloptions["units"]["eV"]); } } + +private: + bool electron_dynamics; ///< Include electron sound speed? + bool alfven_wave; ///< Include Alfven wave speed? + BoutReal beta_norm{0.0}; ///< Normalisation factor for Alfven speed + BoutReal temperature_floor; ///< Minimum temperature when calculating speed + BoutReal fastest_wave_factor; ///< Multiply the fastest wave by this factor /// This sets in the state /// - sound_speed The collective sound speed, based on total pressure and total mass density @@ -53,14 +60,7 @@ struct SoundSpeed : public Component { /// - AA // Atomic mass /// - pressure /// - void transform(Options &state) override; - -private: - bool electron_dynamics; ///< Include electron sound speed? - bool alfven_wave; ///< Include Alfven wave speed? - BoutReal beta_norm{0.0}; ///< Normalisation factor for Alfven speed - BoutReal temperature_floor; ///< Minimum temperature when calculating speed - BoutReal fastest_wave_factor; ///< Multiply the fastest wave by this factor + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/temperature_feedback.hxx b/include/temperature_feedback.hxx index b4975aed2..e9811190e 100644 --- a/include/temperature_feedback.hxx +++ b/include/temperature_feedback.hxx @@ -84,17 +84,6 @@ struct TemperatureFeedback : public Component { .withDefault(false); } - /// Inputs - /// - - /// - temperature - /// - /// Outputs - /// - /// - - /// - temperature_source - /// - void transform(Options& state) override; - void outputVars(Options& state) override { AUTO_TRACE(); if (diagnose) { @@ -191,6 +180,17 @@ private: BoutReal proportional_term, integral_term; ///< Components of resulting source for diagnostics bool diagnose; ///< Output diagnostic information? + + /// Inputs + /// - + /// - temperature + /// + /// Outputs + /// + /// - + /// - temperature_source + /// + void transform(GuardedOptions& state) override; }; namespace { diff --git a/include/transform.hxx b/include/transform.hxx index 8387196ab..a586d5e98 100644 --- a/include/transform.hxx +++ b/include/transform.hxx @@ -9,10 +9,10 @@ struct Transform : public Component { Transform(std::string name, Options& options, Solver*); - void transform(Options& state) override; - private: std::map transforms; + + void transform(GuardedOptions& state) override; }; namespace { diff --git a/include/upstream_density_feedback.hxx b/include/upstream_density_feedback.hxx index 2a3a11fce..c7519099a 100644 --- a/include/upstream_density_feedback.hxx +++ b/include/upstream_density_feedback.hxx @@ -61,17 +61,6 @@ struct UpstreamDensityFeedback : public Component { .withDefault(false); } - /// Inputs - /// - - /// - density - /// - /// Outputs - /// - /// - - /// - density_source - /// - void transform(Options& state) override; - void outputVars(Options& state) override { AUTO_TRACE(); if (diagnose) { @@ -161,6 +150,17 @@ private: BoutReal proportional_term, integral_term; ///< Components of resulting source for diagnostics bool diagnose; ///< Output diagnostic information? + + /// Inputs + /// - + /// - density + /// + /// Outputs + /// + /// - + /// - density_source + /// + void transform(GuardedOptions& state) override; }; namespace { diff --git a/include/vorticity.hxx b/include/vorticity.hxx index 42218caab..0a60053a2 100644 --- a/include/vorticity.hxx +++ b/include/vorticity.hxx @@ -57,25 +57,6 @@ struct Vorticity : public Component { /// Vorticity(std::string name, Options &options, Solver *solver); - /// Optional inputs - /// - /// - species - /// - pressure and charge => Calculates diamagnetic terms [if diamagnetic=true] - /// - pressure, charge and mass => Calculates polarisation current terms [if diamagnetic_polarisation=true] - /// - /// Sets in the state - /// - species - /// - [if has pressure and charge] - /// - energy_source - /// - fields - /// - vorticity - /// - phi Electrostatic potential - /// - DivJdia Divergence of diamagnetic current [if diamagnetic=true] - /// - /// Note: Diamagnetic current calculated here, but could be moved - /// to a component with the diamagnetic drift advection terms - void transform(Options &state) override; - /// Optional inputs /// - fields /// - DivJextra Divergence of current, including parallel current @@ -145,6 +126,25 @@ private: Field3D DivJdia, DivJcol; // Divergence of diamagnetic and collisional current bool diagnose; ///< Output additional diagnostics? + + /// Optional inputs + /// + /// - species + /// - pressure and charge => Calculates diamagnetic terms [if diamagnetic=true] + /// - pressure, charge and mass => Calculates polarisation current terms [if diamagnetic_polarisation=true] + /// + /// Sets in the state + /// - species + /// - [if has pressure and charge] + /// - energy_source + /// - fields + /// - vorticity + /// - phi Electrostatic potential + /// - DivJdia Divergence of diamagnetic current [if diamagnetic=true] + /// + /// Note: Diamagnetic current calculated here, but could be moved + /// to a component with the diamagnetic drift advection terms + void transform(GuardedOptions &state) override; }; namespace { diff --git a/include/zero_current.hxx b/include/zero_current.hxx index 27a55f9cf..68234aca5 100644 --- a/include/zero_current.hxx +++ b/include/zero_current.hxx @@ -19,6 +19,19 @@ struct ZeroCurrent : public Component { /// - /// - charge (must not be zero) ZeroCurrent(std::string name, Options& alloptions, Solver*); + + void finally(const Options &state) override { + // Get the velocity with boundary condition applied. + // This is for output only + velocity = get(state["species"][name]["velocity"]); + } + + void outputVars(Options &state) override; +private: + std::string name; ///< Name of this species + BoutReal charge; ///< The charge of this species + + Field3D velocity; ///< Species velocity (for writing to output) /// Required inputs /// - species @@ -35,20 +48,7 @@ struct ZeroCurrent : public Component { /// - /// - velocity /// - void transform(Options &state) override; - - void finally(const Options &state) override { - // Get the velocity with boundary condition applied. - // This is for output only - velocity = get(state["species"][name]["velocity"]); - } - - void outputVars(Options &state) override; -private: - std::string name; ///< Name of this species - BoutReal charge; ///< The charge of this species - - Field3D velocity; ///< Species velocity (for writing to output) + void transform(GuardedOptions &state) override; }; namespace { diff --git a/src/amjuel_reaction.cxx b/src/amjuel_reaction.cxx index c3cd94d83..f0fe74b98 100644 --- a/src/amjuel_reaction.cxx +++ b/src/amjuel_reaction.cxx @@ -57,7 +57,7 @@ BoutReal AmjuelReaction::eval_sigma_v(BoutReal T, BoutReal n) { return eval_amjuel_fit(T, n, amjuel_data.sigma_v_coeffs); } -void AmjuelReaction::transform_additional(Options& state, Field3D& reaction_rate) { +void AmjuelReaction::transform_additional(GuardedOptions& state, Field3D& reaction_rate) { // Amjuel-based reactions are assumed to have exactly 2 reactants, for now. std::vector reactant_species = @@ -69,7 +69,7 @@ void AmjuelReaction::transform_additional(Options& state, Field3D& reaction_rate parser->get_species(reactant_species, species_filter::heavy); // Amjuel-based reactions are assumed to have exactly 1 heavy reactant, for now. ASSERT1(heavy_reactant_species.size() == 1); - Options& rh = state["species"][heavy_reactant_species[0]]; + GuardedOptions rh = state["species"][heavy_reactant_species[0]]; BoutReal AA_rh = get(rh["AA"]); Field3D n_rh = get(rh["density"]); Field3D v_rh = get(rh["velocity"]); @@ -79,7 +79,7 @@ void AmjuelReaction::transform_additional(Options& state, Field3D& reaction_rate parser->get_species(species_filter::heavy, species_filter::products); // Amjuel-based reactions are assumed to have exactly 1 heavy product, for now. ASSERT1(heavy_product_species.size() == 1); - Options& ph = state["species"][heavy_product_species[0]]; + GuardedOptions ph = state["species"][heavy_product_species[0]]; Field3D v_ph = get(ph["velocity"]); // Kinetic energy transfer to thermal energy @@ -116,11 +116,11 @@ void AmjuelReaction::transform_additional(Options& state, Field3D& reaction_rate add(ph["energy_source"], 0.5 * AA_rh * reaction_rate * SQ(v_rh - v_ph)); // Energy source for electrons due to pop change - Options& electron = state["species"]["e"]; + GuardedOptions electron = state["species"]["e"]; Field3D T_e = get(electron["temperature"]); const int e_pop_change = this->parser->get_stoich().at("e"); if (e_pop_change != 0) { - if (electron.isSet("velocity")) { + if (IS_SET(electron["velocity"])) { // Transfer of electron kinetic to thermal energy due to density source // For ionisation: // Electrons with zero average velocity are created, diluting the kinetic energy. @@ -176,6 +176,6 @@ void AmjuelReaction::transform_additional(Options& state, Field3D& reaction_rate std::vector neutral_species = parser->get_species(species_filter::neutral); ASSERT1(neutral_species.size() == 1); set(state["species"][neutral_species[0]]["collision_frequencies"] - [rh.name() + "_" + ph.name() + "_" + this->short_reaction_type], + [heavy_reactant_species[0] + "_" + heavy_product_species[0] + "_" + this->short_reaction_type], heavy_particle_frequency); -} \ No newline at end of file +} diff --git a/src/anomalous_diffusion.cxx b/src/anomalous_diffusion.cxx index cc585efaa..29fbe1686 100644 --- a/src/anomalous_diffusion.cxx +++ b/src/anomalous_diffusion.cxx @@ -52,10 +52,10 @@ AnomalousDiffusion::AnomalousDiffusion(std::string name, Options& alloptions, So .withDefault(false); } -void AnomalousDiffusion::transform(Options& state) { +void AnomalousDiffusion::transform(GuardedOptions& state) { AUTO_TRACE(); - Options& species = state["species"][name]; + GuardedOptions species = state["species"][name]; // Diffusion operates on 2D (axisymmetric) profiles // Note: Includes diffusion in Y, so set boundary fluxes @@ -63,13 +63,13 @@ void AnomalousDiffusion::transform(Options& state) { const Field3D N = GET_NOBOUNDARY(Field3D, species["density"]); Field2D N2D = DC(N); - const Field3D T = species.isSet("temperature") + const Field3D T = IS_SET(species["temperature"]) ? GET_NOBOUNDARY(Field3D, species["temperature"]) : 0.0; Field2D T2D = DC(T); const Field3D V = - species.isSet("velocity") ? GET_NOBOUNDARY(Field3D, species["velocity"]) : 0.0; + IS_SET(species["velocity"]) ? GET_NOBOUNDARY(Field3D, species["velocity"]) : 0.0; Field2D V2D = DC(V); if (!anomalous_sheath_flux) { diff --git a/src/binormal_stpm.cxx b/src/binormal_stpm.cxx index c855e776f..e29bbe93b 100644 --- a/src/binormal_stpm.cxx +++ b/src/binormal_stpm.cxx @@ -47,23 +47,23 @@ BinormalSTPM::BinormalSTPM(std::string name, Options& alloptions, .withDefault(false); } -void BinormalSTPM::transform(Options& state) { +void BinormalSTPM::transform(GuardedOptions& state) { AUTO_TRACE(); - Options& allspecies = state["species"]; + GuardedOptions allspecies = state["species"]; // Loop through all species for (auto& kv : allspecies.getChildren()) { const auto& species_name = kv.first; - Options& species = allspecies[species_name]; + GuardedOptions species = allspecies[species_name]; auto AA = get(species["AA"]); - const Field3D N = species.isSet("density") + const Field3D N = IS_SET(species["density"]) ? GET_NOBOUNDARY(Field3D, species["density"]) : 0.0; - const Field3D T = species.isSet("temperature") + const Field3D T = IS_SET(species["temperature"]) ? GET_NOBOUNDARY(Field3D, species["temperature"]) : 0.0; - const Field3D NV = species.isSet("momentum") + const Field3D NV = IS_SET(species["momentum"]) ? GET_NOBOUNDARY(Field3D, species["momentum"]) : 0.0; diff --git a/src/braginskii_collisions.cxx b/src/braginskii_collisions.cxx index 0d0a952a2..35b09a709 100644 --- a/src/braginskii_collisions.cxx +++ b/src/braginskii_collisions.cxx @@ -69,8 +69,7 @@ BraginskiiCollisions::BraginskiiCollisions(const std::string& name, Options& all /// /// Note: A* variables are used for atomic mass numbers; /// mass* variables are species masses in kg -void BraginskiiCollisions::collide(Options& species1, Options& species2, - const Field3D& nu_12) { +void BraginskiiCollisions::collide(Options & species1, Options & species2, const Field3D& nu_12, BoutReal momentum_coefficient) { AUTO_TRACE(); add(species1["collision_frequency"], nu_12); // Total collision frequency @@ -105,16 +104,16 @@ void BraginskiiCollisions::collide(Options& species1, Options& species2, } } -void BraginskiiCollisions::transform(Options& state) { +void BraginskiiCollisions::transform(GuardedOptions& state) { AUTO_TRACE(); - Options& allspecies = state["species"]; + GuardedOptions allspecies = state["species"]; // Treat electron collisions specially // electron-ion and electron-neutral collisions if (allspecies.isSection("e")) { - Options& electrons = allspecies["e"]; + GuardedOptions electrons = allspecies["e"]; const Field3D Te = GET_NOBOUNDARY(Field3D, electrons["temperature"]) * Tnorm; // eV const Field3D Ne = GET_NOBOUNDARY(Field3D, electrons["density"]) * Nnorm; // In m^-3 @@ -148,13 +147,13 @@ void BraginskiiCollisions::transform(Options& state) { return nu; }); - collide(electrons, electrons, nu_ee / Omega_ci); + collide(electrons.getWritable(), electrons.getWritable(), nu_ee / Omega_ci, 1.0); continue; } - Options& species = allspecies[kv.first]; // Note: Need non-const + GuardedOptions species = allspecies[kv.first]; // Note: Need non-const - if (species.isSet("charge") and (get(species["charge"]) > 0.0)) { + if (IS_SET(species["charge"]) and (get(species["charge"]) > 0.0)) { //////////////////////////////////// // electron-positive ion collisions @@ -201,9 +200,17 @@ void BraginskiiCollisions::transform(Options& state) { return nu; }); - collide(electrons, species, nu_ei / Omega_ci); + // Coefficient in front of parallel momentum exchange + // This table is from Braginskii 1965 + BoutReal mom_coeff = + Zi == 1 ? 0.51 : + Zi == 2 ? 0.44 : + Zi == 3 ? 0.40 : + 0.38; // Note: 0.38 is for Zi=4; tends to 0.29 for Zi->infty - } else if (species.isSet("charge") and (get(species["charge"]) < 0.0)) { + collide(electrons.getWritable(), species.getWritable(), nu_ei / Omega_ci, mom_coeff); + + } else if (IS_SET(species["charge"]) and (get(species["charge"]) < 0.0)) { //////////////////////////////////// // electron-negative ion collisions @@ -233,7 +240,7 @@ void BraginskiiCollisions::transform(Options& state) { return vth_e * Nnorm * Nn[i] * a0 * rho_s0; }); - collide(electrons, species, nu_en); + collide(electrons.getWritable(), species.getWritable(), nu_en, 1.0); } } } @@ -250,17 +257,17 @@ void BraginskiiCollisions::transform(Options& state) { // || species2 X X // \/ species3 X // - const std::map& children = allspecies.getChildren(); + const std::map children = allspecies.getChildren(); for (auto kv1 = std::begin(children); kv1 != std::end(children); ++kv1) { if (kv1->first == "e" or kv1->first == "ebeam") { continue; // Skip electrons } - Options& species1 = allspecies[kv1->first]; + GuardedOptions species1 = allspecies[kv1->first]; // If temperature isn't set, assume zero. in eV const Field3D temperature1 = - species1.isSet("temperature") + IS_SET(species1["temperature"]) ? GET_NOBOUNDARY(Field3D, species1["temperature"]) * Tnorm : 0.0; @@ -269,26 +276,26 @@ void BraginskiiCollisions::transform(Options& state) { const BoutReal AA1 = get(species1["AA"]); const BoutReal mass1 = AA1 * SI::Mp; // in Kg - if (species1.isSet("charge") and (get(species1["charge"]) != 0.0)) { + if (IS_SET(species1["charge"]) and (get(species1["charge"]) != 0.0)) { // Charged species const BoutReal Z1 = get(species1["charge"]); const BoutReal charge1 = Z1 * SI::qe; // in Coulombs // Copy the iterator, so we don't iterate over the // lower half of the matrix, but start at the diagonal - for (std::map::const_iterator kv2 = kv1; + for (std::map::const_iterator kv2 = kv1; kv2 != std::end(children); ++kv2) { if (kv2->first == "e" or kv2->first == "ebeam") { continue; // Skip electrons } - Options& species2 = allspecies[kv2->first]; + GuardedOptions species2 = allspecies[kv2->first]; // Note: Here species1 could be equal to species2 // If temperature isn't set, assume zero. in eV const Field3D temperature2 = - species2.isSet("temperature") + IS_SET(species2["temperature"]) ? GET_NOBOUNDARY(Field3D, species2["temperature"]) * Tnorm : 0.0; @@ -297,7 +304,7 @@ void BraginskiiCollisions::transform(Options& state) { const BoutReal AA2 = get(species2["AA"]); const BoutReal mass2 = AA2 * SI::Mp; // in Kg - if (species2.isSet("charge") and (get(species2["charge"]) != 0.0)) { + if (IS_SET(species2["charge"]) and (get(species2["charge"]) != 0.0)) { ////////////////////////////// // Both charged species @@ -335,7 +342,7 @@ void BraginskiiCollisions::transform(Options& state) { }); // Update the species collision rates, momentum & energy exchange - collide(species1, species2, nu_12 / Omega_ci); + collide(species1.getWritable(), species2.getWritable(), nu_12 / Omega_ci, 1.0); } else { // species1 charged, species2 neutral @@ -356,7 +363,7 @@ void BraginskiiCollisions::transform(Options& state) { return vrel * density2[i] * a0 * rho_s0; }); - collide(species1, species2, nu_12); + collide(species1.getWritable(), species2.getWritable(), nu_12, 1.0); } } } else { @@ -364,19 +371,19 @@ void BraginskiiCollisions::transform(Options& state) { // Copy the iterator, so we don't iterate over the // lower half of the matrix, but start at the diagonal - for (std::map::const_iterator kv2 = kv1; + for (std::map::const_iterator kv2 = kv1; kv2 != std::end(children); ++kv2) { if (kv2->first == "e") { continue; // Skip electrons } - Options& species2 = allspecies[kv2->first]; + GuardedOptions species2 = allspecies[kv2->first]; // Note: Here species1 could be equal to species2 // If temperature isn't set, assume zero const Field3D temperature2 = - species2.isSet("temperature") + IS_SET(species2["temperature"]) ? GET_NOBOUNDARY(Field3D, species2["temperature"]) * Tnorm : 0.0; const BoutReal AA2 = get(species2["AA"]); @@ -404,7 +411,7 @@ void BraginskiiCollisions::transform(Options& state) { return vrel * density2[i] * a0 * rho_s0; }); - collide(species1, species2, nu_12); + collide(species1.getWritable(), species2.getWritable(), nu_12, 1.0); } else { // Both species neutral @@ -433,7 +440,7 @@ void BraginskiiCollisions::transform(Options& state) { return vrel * density2[i] * a0 * rho_s0; }); - collide(species1, species2, nu_12); + collide(species1.getWritable(), species2.getWritable(), nu_12, 1.0); } } } diff --git a/src/braginskii_electron_viscosity.cxx b/src/braginskii_electron_viscosity.cxx index 4e3971514..f52863914 100644 --- a/src/braginskii_electron_viscosity.cxx +++ b/src/braginskii_electron_viscosity.cxx @@ -29,10 +29,10 @@ BraginskiiElectronViscosity::BraginskiiElectronViscosity(const std::string& name diagnose = options["diagnose"].doc("Output diagnostics?").withDefault(false); } -void BraginskiiElectronViscosity::transform(Options& state) { +void BraginskiiElectronViscosity::transform(GuardedOptions& state) { AUTO_TRACE(); - Options& species = state["species"]["e"]; + GuardedOptions species = state["species"]["e"]; if (!isSetFinal(species["pressure"], "electron_viscosity")) { throw BoutException("No electron pressure => Can't calculate electron viscosity"); diff --git a/src/braginskii_ion_viscosity.cxx b/src/braginskii_ion_viscosity.cxx index 780df2018..83e6223c1 100644 --- a/src/braginskii_ion_viscosity.cxx +++ b/src/braginskii_ion_viscosity.cxx @@ -109,10 +109,10 @@ BraginskiiIonViscosity::BraginskiiIonViscosity(const std::string& name, } } -void BraginskiiIonViscosity::transform(Options& state) { +void BraginskiiIonViscosity::transform(GuardedOptions &state) { AUTO_TRACE(); - Options& allspecies = state["species"]; + GuardedOptions allspecies = state["species"]; auto coord = mesh->getCoordinates(); const Field2D Bxy = coord->Bxy; @@ -126,7 +126,7 @@ void BraginskiiIonViscosity::transform(Options& state) { } const auto& species_name = kv.first; - Options& species = allspecies[species_name]; + GuardedOptions species = allspecies[species_name]; if (!(isSetFinal(species["pressure"], "ion_viscosity") and isSetFinal(species["velocity"], "ion_viscosity") @@ -150,10 +150,10 @@ void BraginskiiIonViscosity::transform(Options& state) { if (viscosity_collisions_mode == "braginskii") { for (const auto& collision : species["collision_frequencies"].getChildren()) { - std::string const collision_name = collision.second.name(); + const std::string collision_name = collision.first; if ( // Self-collisions - (collisionSpeciesMatch(collision_name, species.name(), species.name(), + (collisionSpeciesMatch(collision_name, species_name, species_name, "coll", "exact"))) { collision_names[species_name].push_back(collision_name); } @@ -162,13 +162,13 @@ void BraginskiiIonViscosity::transform(Options& state) { } else if (viscosity_collisions_mode == "multispecies") { for (const auto& collision : species["collision_frequencies"].getChildren()) { - std::string const collision_name = collision.second.name(); + const std::string collision_name = collision.first; if ( // Charge exchange - (collisionSpeciesMatch(collision_name, species.name(), "", "cx", "partial")) + (collisionSpeciesMatch(collision_name, species_name, "", "cx", "partial")) or // Any collision (en, in, ee, ii, nn) - (collisionSpeciesMatch(collision_name, species.name(), "", "coll", + (collisionSpeciesMatch(collision_name, species_name, "", "coll", "partial"))) { collision_names[species_name].push_back(collision_name); @@ -177,18 +177,18 @@ void BraginskiiIonViscosity::transform(Options& state) { } else { throw BoutException("\tviscosity_collisions_mode for {:s} must be either " "multispecies or braginskii", - species.name()); + species_name); } if (collision_names[species_name].empty()) { throw BoutException("\tNo collisions found for {:s} in ion_viscosity for " "selected collisions mode", - species.name()); + species_name); } // Write chosen collisions to log file output_info.write("\t{:s} viscosity collisionality mode: '{:s}' using ", - species.name(), viscosity_collisions_mode); + species_name, viscosity_collisions_mode); for (const auto& collision : collision_names[species_name]) { output_info.write("{:s} ", collision); } diff --git a/src/braginskii_thermal_force.cxx b/src/braginskii_thermal_force.cxx index 2786e5f61..5c6a49a75 100644 --- a/src/braginskii_thermal_force.cxx +++ b/src/braginskii_thermal_force.cxx @@ -14,15 +14,15 @@ #include "../include/braginskii_thermal_force.hxx" #include "../include/component.hxx" -void BraginskiiThermalForce::transform(Options& state) { +void BraginskiiThermalForce::transform(GuardedOptions& state) { AUTO_TRACE(); - Options& allspecies = state["species"]; + GuardedOptions allspecies = state["species"]; if (electron_ion && allspecies.isSection("e")) { // Electron-ion collisions - Options& electrons = allspecies["e"]; + GuardedOptions electrons = allspecies["e"]; // Need Te boundary to take gradient const Field3D Te = GET_VALUE(Field3D, electrons["temperature"]); const Field3D Grad_Te = Grad_par(Te); @@ -31,7 +31,7 @@ void BraginskiiThermalForce::transform(Options& state) { if (kv.first == "e") { continue; // Omit electron-electron } - Options& species = allspecies[kv.first]; + GuardedOptions species = allspecies[kv.first]; if (!species.isSet("charge") or get(species["charge"]) == 0) { continue; // Only considering charged particle interactions @@ -61,9 +61,9 @@ void BraginskiiThermalForce::transform(Options& state) { // || species2 X // \/ species3 // - const std::map& children = allspecies.getChildren(); + const std::map children = allspecies.getChildren(); for (auto kv1 = std::begin(children); kv1 != std::end(children); ++kv1) { - Options& species1 = allspecies[kv1->first]; + GuardedOptions species1 = allspecies[kv1->first]; if (kv1->first == "e" or !species1.isSet("charge") or get(species1["charge"]) == 0) { @@ -72,9 +72,9 @@ void BraginskiiThermalForce::transform(Options& state) { // Copy the iterator, so we don't iterate over the // lower half of the matrix or the diagonal but start the diagonal - for (std::map::const_iterator kv2 = std::next(kv1); + for (std::map::const_iterator kv2 = std::next(kv1); kv2 != std::end(children); ++kv2) { - Options& species2 = allspecies[kv2->first]; + GuardedOptions species2 = allspecies[kv2->first]; if (kv2->first == "e" or !species2.isSet("charge") or get(species2["charge"]) == 0) { @@ -84,8 +84,8 @@ void BraginskiiThermalForce::transform(Options& state) { // Now have two different ion species, species1 and species2 // Only including one majority light species, and one trace heavy species - Options* light; - Options* heavy; + GuardedOptions* light; + GuardedOptions* heavy; if ((get(species1["AA"]) < 4) and (get(species2["AA"]) > 10)) { // species1 light, species2 heavy diff --git a/src/classical_diffusion.cxx b/src/classical_diffusion.cxx index eb95780e3..2244516b6 100644 --- a/src/classical_diffusion.cxx +++ b/src/classical_diffusion.cxx @@ -12,18 +12,20 @@ ClassicalDiffusion::ClassicalDiffusion(std::string name, Options& alloptions, So custom_D = options["custom_D"].doc("Custom diffusion coefficient override. -1: Off, calculate D normally").withDefault(-1); } -void ClassicalDiffusion::transform(Options &state) { +void ClassicalDiffusion::transform(GuardedOptions &state) { AUTO_TRACE(); - Options& allspecies = state["species"]; + GuardedOptions allspecies = state["species"]; // Particle diffusion coefficient // The only term here comes from the resistive drift Field3D Ptotal = 0.0; for (auto& kv : allspecies.getChildren()) { - const auto& species = kv.second; + const auto species = kv.second; - if (!(species.isSet("charge") and IS_SET(species["pressure"]))) { + GuardedOptions x = species["charge"]; + + if (!(IS_SET(species["charge"]) and IS_SET(species["pressure"]))) { continue; // Skip, go to next species } auto q = get(species["charge"]); @@ -33,7 +35,7 @@ void ClassicalDiffusion::transform(Options &state) { Ptotal += GET_VALUE(Field3D, species["pressure"]); } - auto& electrons = allspecies["e"]; + auto electrons = allspecies["e"]; const auto me = get(electrons["AA"]); const Field3D Ne = GET_VALUE(Field3D, electrons["density"]); @@ -52,10 +54,10 @@ void ClassicalDiffusion::transform(Options &state) { Dn[i] = 0.0; } - for (auto& kv : allspecies.getChildren()) { - Options& species = allspecies[kv.first]; // Note: Need non-const + for (auto kv : allspecies.getChildren()) { + GuardedOptions species = allspecies[kv.first]; // Note: Need non-const - if (!(species.isSet("charge") and IS_SET(species["density"]))) { + if (!(IS_SET(species["charge"]) and IS_SET(species["density"]))) { continue; // Skip, go to next species } auto q = get(species["charge"]); diff --git a/src/component.cxx b/src/component.cxx index 07d8a6182..37b6a7764 100644 --- a/src/component.cxx +++ b/src/component.cxx @@ -9,6 +9,19 @@ std::unique_ptr Component::create(const std::string &type, return ComponentFactory::getInstance().create(type, name, alloptions, solver); } +void Component::transform(Options& state) { + GuardedOptions guarded(&state, &state_variable_access); + transform(guarded); + for (auto& [varname, region] : guarded.unreadItems()) { + output_warn.write("Did not read from state variable {} in region(s) {}", + varname, Permissions::regionNames(region)); + } + for (auto& [varname, region] : guarded.unwrittenItems()) { + output_warn.write("Did not write to state variable {} in region(s) {}", + varname, Permissions::regionNames(region)); + } +} + constexpr decltype(ComponentFactory::type_name) ComponentFactory::type_name; constexpr decltype(ComponentFactory::section_name) ComponentFactory::section_name; constexpr decltype(ComponentFactory::option_name) ComponentFactory::option_name; @@ -23,6 +36,11 @@ bool isSetFinal(const Options& option, [[maybe_unused]] const std::string& locat return option.isSet(); } +bool isSetFinal(const GuardedOptions option, const std::string& location) { + return isSetFinal(option.get(), location); +} + + bool isSetFinalNoBoundary(const Options& option, [[maybe_unused]] const std::string& location) { #if CHECKLEVEL >= 1 // Mark option as final inside the domain, but not in the boundary @@ -30,3 +48,7 @@ bool isSetFinalNoBoundary(const Options& option, [[maybe_unused]] const std::str #endif return option.isSet(); } + +bool isSetFinalNoBoundary(const GuardedOptions option, const std::string& location) { + return isSetFinalNoBoundary(option.get(Permissions::Interior), location); +} diff --git a/src/detachment_controller.cxx b/src/detachment_controller.cxx index d2d6004a0..04b607497 100644 --- a/src/detachment_controller.cxx +++ b/src/detachment_controller.cxx @@ -31,7 +31,7 @@ BoutReal calculateGradient(const std::vector& x, const std::vector(species["charge"]); @@ -184,9 +184,9 @@ void Electromagnetic::transform(Options &state) { // Update momentum for (auto& kv : allspecies.getChildren()) { - Options& species = allspecies[kv.first]; // Note: need non-const + GuardedOptions species = allspecies[kv.first]; // Note: need non-const - if (!species.isSet("charge") or !species.isSet("momentum")) { + if (!IS_SET(species["charge"]) or !IS_SET(species["momentum"])) { continue; // Not charged, or no parallel flow } const BoutReal Z = get(species["charge"]); diff --git a/src/electron_force_balance.cxx b/src/electron_force_balance.cxx index a097fa70b..696957709 100644 --- a/src/electron_force_balance.cxx +++ b/src/electron_force_balance.cxx @@ -5,7 +5,7 @@ using bout::globals::mesh; -void ElectronForceBalance::transform(Options &state) { +void ElectronForceBalance::transform(GuardedOptions &state) { AUTO_TRACE(); if (IS_SET(state["fields"]["phi"])) { @@ -15,7 +15,7 @@ void ElectronForceBalance::transform(Options &state) { } // Get the electron pressure, with boundary condition applied - Options& electrons = state["species"]["e"]; + GuardedOptions electrons = state["species"]["e"]; Field3D Pe = GET_VALUE(Field3D, electrons["pressure"]); Field3D Ne = GET_NOBOUNDARY(Field3D, electrons["density"]); @@ -34,12 +34,12 @@ void ElectronForceBalance::transform(Options &state) { Epar = force_density / floor(Ne, 1e-5); // Now calculate forces on other species - Options& allspecies = state["species"]; + GuardedOptions allspecies = state["species"]; for (auto& kv : allspecies.getChildren()) { if (kv.first == "e") { continue; // Skip electrons } - Options& species = allspecies[kv.first]; // Note: Need non-const + GuardedOptions species = allspecies[kv.first]; // Note: Need non-const if (!(IS_SET(species["density"]) and IS_SET(species["charge"]))) { continue; // Needs both density and charge to experience a force diff --git a/src/evolve_density.cxx b/src/evolve_density.cxx index 672d1ea6b..bbdf098d5 100644 --- a/src/evolve_density.cxx +++ b/src/evolve_density.cxx @@ -132,7 +132,7 @@ EvolveDensity::EvolveDensity(std::string name, Options& alloptions, Solver* solv .withDefault(false); } -void EvolveDensity::transform(Options& state) { +void EvolveDensity::transform(GuardedOptions& state) { AUTO_TRACE(); if (evolve_log) { @@ -176,7 +176,7 @@ void EvolveDensity::transform(Options& state) { } } - auto& species = state["species"][name]; + auto species = state["species"][name]; set(species["density"], floor(N, 0.0)); // Density in state always >= 0 set(species["AA"], AA); // Atomic mass if (charge != 0.0) { // Don't set charge for neutral species diff --git a/src/evolve_energy.cxx b/src/evolve_energy.cxx index 6b55067b0..68afaa25a 100644 --- a/src/evolve_energy.cxx +++ b/src/evolve_energy.cxx @@ -113,7 +113,7 @@ EvolveEnergy::EvolveEnergy(std::string name, Options& alloptions, Solver* solver .withDefault(true); } -void EvolveEnergy::transform(Options& state) { +void EvolveEnergy::transform(GuardedOptions& state) { AUTO_TRACE(); if (evolve_log) { @@ -123,7 +123,7 @@ void EvolveEnergy::transform(Options& state) { mesh->communicate(E); - auto& species = state["species"][name]; + auto species = state["species"][name]; N = getNoBoundary(species["density"]); const Field3D V = getNoBoundary(species["velocity"]); const BoutReal AA = get(species["AA"]); diff --git a/src/evolve_momentum.cxx b/src/evolve_momentum.cxx index 3ccadf51a..2cfb1b332 100644 --- a/src/evolve_momentum.cxx +++ b/src/evolve_momentum.cxx @@ -60,11 +60,11 @@ EvolveMomentum::EvolveMomentum(std::string name, Options &alloptions, Solver *so NV_err = 0.0; } -void EvolveMomentum::transform(Options &state) { +void EvolveMomentum::transform(GuardedOptions &state) { AUTO_TRACE(); mesh->communicate(NV); - auto& species = state["species"][name]; + auto species = state["species"][name]; // Not using density boundary condition auto N = getNoBoundary(species["density"]); diff --git a/src/evolve_pressure.cxx b/src/evolve_pressure.cxx index 9b2ee1833..ecfb165da 100644 --- a/src/evolve_pressure.cxx +++ b/src/evolve_pressure.cxx @@ -164,7 +164,7 @@ EvolvePressure::EvolvePressure(std::string name, Options& alloptions, Solver* so .withDefault(true); } -void EvolvePressure::transform(Options& state) { +void EvolvePressure::transform(GuardedOptions& state) { AUTO_TRACE(); if (evolve_log) { @@ -208,7 +208,7 @@ void EvolvePressure::transform(Options& state) { } } - auto& species = state["species"][name]; + auto species = state["species"][name]; // Calculate temperature // Not using density boundary condition diff --git a/src/fixed_fraction_ions.cxx b/src/fixed_fraction_ions.cxx index bc89357b1..4a572e24d 100644 --- a/src/fixed_fraction_ions.cxx +++ b/src/fixed_fraction_ions.cxx @@ -28,7 +28,7 @@ FixedFractionIons::FixedFractionIons(std::string name, Options &alloptions, } } -void FixedFractionIons::transform(Options &state) { +void FixedFractionIons::transform(GuardedOptions &state) { AUTO_TRACE(); // Electron density diff --git a/src/guarded_options.cxx b/src/guarded_options.cxx index 9b9cf34de..2ef202404 100644 --- a/src/guarded_options.cxx +++ b/src/guarded_options.cxx @@ -23,7 +23,7 @@ void updateAccessRecords(std::map& records, } } -const Options& GuardedOptions::get(Permissions::Regions region) { +const Options& GuardedOptions::get(Permissions::Regions region) const { if (options == nullptr) throw BoutException( "Trying to access GuardedOptions when underlying options are nullptr."); diff --git a/src/hydrogen_charge_exchange.cxx b/src/hydrogen_charge_exchange.cxx index cc9051558..b0047fd78 100644 --- a/src/hydrogen_charge_exchange.cxx +++ b/src/hydrogen_charge_exchange.cxx @@ -1,7 +1,7 @@ #include "../include/hydrogen_charge_exchange.hxx" -void HydrogenChargeExchange::calculate_rates(Options& atom1, Options& ion1, - Options& atom2, Options& ion2, +void HydrogenChargeExchange::calculate_rates(GuardedOptions atom1, GuardedOptions ion1, + GuardedOptions atom2, GuardedOptions ion2, Field3D &R, Field3D &atom_mom, Field3D &ion_mom, Field3D &atom_energy, Field3D &ion_energy, @@ -103,6 +103,6 @@ void HydrogenChargeExchange::calculate_rates(Options& atom1, Options& ion1, add(ion1["collision_frequency"], ion_rate); // Set individual collision frequencies - set(atom1["collision_frequencies"][atom1.name() + std::string("_") + ion1.name() + std::string("_cx")], atom_rate); - set(ion1["collision_frequencies"][ion1.name() + std::string("_") + atom1.name() + std::string("_cx")], ion_rate); + set(atom1["collision_frequencies"][atom1.get().name() + std::string("_") + ion1.get().name() + std::string("_cx")], atom_rate); + set(ion1["collision_frequencies"][ion1.get().name() + std::string("_") + atom1.get().name() + std::string("_cx")], ion_rate); } diff --git a/src/ionisation.cxx b/src/ionisation.cxx index 1c779ae2b..6d722efdd 100644 --- a/src/ionisation.cxx +++ b/src/ionisation.cxx @@ -38,19 +38,19 @@ Ionisation::Ionisation(std::string name, Options &alloptions, Solver *) { Eionize /= Tnorm; } -void Ionisation::transform(Options &state) { +void Ionisation::transform(GuardedOptions &state) { // Get neutral atom properties - Options& hydrogen = state["species"]["h"]; + GuardedOptions hydrogen = state["species"]["h"]; Field3D Nn = get(hydrogen["density"]); Field3D Tn = get(hydrogen["temperature"]); Field3D Vn = get(hydrogen["velocity"]); auto AA = get(hydrogen["AA"]); - Options& electron = state["species"]["e"]; + GuardedOptions electron = state["species"]["e"]; Field3D Ne = get(electron["density"]); Field3D Te = get(electron["temperature"]); - Options& ion = state["species"]["h+"]; + GuardedOptions ion = state["species"]["h+"]; ASSERT1(AA == get(ion["AA"])); Field3D reaction_rate = cellAverage( diff --git a/src/isothermal.cxx b/src/isothermal.cxx index eaca1b1ee..10e9b6ba7 100644 --- a/src/isothermal.cxx +++ b/src/isothermal.cxx @@ -18,10 +18,10 @@ Isothermal::Isothermal(std::string name, Options &alloptions, .withDefault(false); } -void Isothermal::transform(Options &state) { +void Isothermal::transform(GuardedOptions &state) { AUTO_TRACE(); - Options& species = state["species"][name]; + GuardedOptions species = state["species"][name]; set(species["temperature"], T); diff --git a/src/neutral_boundary.cxx b/src/neutral_boundary.cxx index 4879eef99..a50a81428 100644 --- a/src/neutral_boundary.cxx +++ b/src/neutral_boundary.cxx @@ -51,9 +51,9 @@ NeutralBoundary::NeutralBoundary(std::string name, Options& alloptions, .withDefault(0.8); } -void NeutralBoundary::transform(Options& state) { +void NeutralBoundary::transform(GuardedOptions& state) { AUTO_TRACE(); - auto& species = state["species"][name]; + auto species = state["species"][name]; const BoutReal AA = get(species["AA"]); Field3D Nn = toFieldAligned(GET_NOBOUNDARY(Field3D, species["density"])); @@ -70,7 +70,7 @@ void NeutralBoundary::transform(Options& state) { // Get the energy source, or create if not set Field3D energy_source = - species.isSet("energy_source") + IS_SET(species["energy_source"]) ? toFieldAligned(getNonFinal(species["energy_source"])) : zeroFrom(Nn); diff --git a/src/neutral_full_velocity.cxx b/src/neutral_full_velocity.cxx index 0adbf9387..72ad2a78d 100644 --- a/src/neutral_full_velocity.cxx +++ b/src/neutral_full_velocity.cxx @@ -191,7 +191,7 @@ NeutralFullVelocity::NeutralFullVelocity(const std::string& name, Options& allop } /// Modify the given simulation state -void NeutralFullVelocity::transform(Options& state) { +void NeutralFullVelocity::transform(GuardedOptions& state) { AUTO_TRACE(); mesh->communicate(Nn2D, Vn2D, Pn2D); @@ -254,7 +254,7 @@ void NeutralFullVelocity::transform(Options& state) { Vnpar = Vn2D.y / (coord->J * coord->Bxy); // Set values in the state - auto& localstate = state["species"][name]; + auto localstate = state["species"][name]; set(localstate["density"], Nn2D); set(localstate["AA"], AA); // Atomic mass set(localstate["pressure"], Pn2D); diff --git a/src/neutral_mixed.cxx b/src/neutral_mixed.cxx index 5aa0c6525..b6ac57000 100644 --- a/src/neutral_mixed.cxx +++ b/src/neutral_mixed.cxx @@ -171,7 +171,7 @@ NeutralMixed::NeutralMixed(const std::string& name, Options& alloptions, Solver* DnnNVn.setBoundary(std::string("Dnn") + name); } -void NeutralMixed::transform(Options& state) { +void NeutralMixed::transform(GuardedOptions& state) { AUTO_TRACE(); mesh->communicate(Nn, Pn, NVn); @@ -254,7 +254,7 @@ void NeutralMixed::transform(Options& state) { } // Set values in the state - auto& localstate = state["species"][name]; + auto localstate = state["species"][name]; set(localstate["density"], Nn); set(localstate["AA"], AA); // Atomic mass set(localstate["pressure"], Pn); diff --git a/src/neutral_parallel_diffusion.cxx b/src/neutral_parallel_diffusion.cxx index d8ff767f3..dbebd4323 100644 --- a/src/neutral_parallel_diffusion.cxx +++ b/src/neutral_parallel_diffusion.cxx @@ -6,16 +6,16 @@ using bout::globals::mesh; -void NeutralParallelDiffusion::transform(Options& state) { +void NeutralParallelDiffusion::transform(GuardedOptions& state) { AUTO_TRACE(); - Options& allspecies = state["species"]; + GuardedOptions allspecies = state["species"]; for (auto& kv : allspecies.getChildren()) { const auto& species_name = kv.first; // Get non-const reference - auto& species = allspecies[species_name]; + auto species = allspecies[species_name]; - if (species.isSet("charge") and (get(species["charge"]) != 0.0)) { + if (IS_SET(species["charge"]) and (get(species["charge"]) != 0.0)) { // Skip charged species continue; } @@ -38,14 +38,14 @@ void NeutralParallelDiffusion::transform(Options& state) { if (diffusion_collisions_mode == "afn") { for (const auto& collision : species["collision_frequencies"].getChildren()) { - std::string collision_name = collision.second.name(); + std::string collision_name = collision.first; if (// Charge exchange (collisionSpeciesMatch( - collision_name, species.name(), "+", "cx", "partial")) or + collision_name, species_name, "+", "cx", "partial")) or // Ionisation (collisionSpeciesMatch( - collision_name, species.name(), "+", "iz", "partial"))) { + collision_name, species_name, "+", "iz", "partial"))) { collision_names[species_name].push_back(collision_name); } @@ -54,30 +54,30 @@ void NeutralParallelDiffusion::transform(Options& state) { } else if (diffusion_collisions_mode == "multispecies") { for (const auto& collision : species["collision_frequencies"].getChildren()) { - std::string collision_name = collision.second.name(); + std::string collision_name = collision.first; if (// Charge exchange (collisionSpeciesMatch( - collision_name, species.name(), "", "cx", "partial")) or + collision_name, species_name, "", "cx", "partial")) or // Any collision (ne, ni, nn) (collisionSpeciesMatch( - collision_name, species.name(), "", "coll", "partial"))) { + collision_name, species_name, "", "coll", "partial"))) { collision_names[species_name].push_back(collision_name); } } } else { - throw BoutException("\tdiffusion_collisions_mode for {:s} must be either multispecies or afn", species.name()); + throw BoutException("\tdiffusion_collisions_mode for {:s} must be either multispecies or afn", species_name); } if (collision_names[species_name].empty()) { - throw BoutException("\tNo collisions found for {:s} in neutral_parallel_diffusion for selected collisions mode", species.name()); + throw BoutException("\tNo collisions found for {:s} in neutral_parallel_diffusion for selected collisions mode", species_name); } // Write chosen collisions to log file output_info.write("\t{:s} neutral diffusion collisionality mode: '{:s}' using ", - species.name(), diffusion_collisions_mode); + species_name, diffusion_collisions_mode); for (const auto& collision : collision_names[species_name]) { output_info.write("{:s} ", collision); } diff --git a/src/noflow_boundary.cxx b/src/noflow_boundary.cxx index bc9bf252a..a668c8185 100644 --- a/src/noflow_boundary.cxx +++ b/src/noflow_boundary.cxx @@ -11,17 +11,17 @@ Ind3D indexAt(const Field3D& f, int x, int y, int z) { } } -void NoFlowBoundary::transform(Options& state) { +void NoFlowBoundary::transform(GuardedOptions& state) { AUTO_TRACE(); // Make sure that the state has been set for this species ASSERT1(state["species"].isSection(name)); - Options& species = state["species"][name]; + GuardedOptions species = state["species"][name]; // Apply zero-gradient boundary conditions to state variables for (const std::string field : {"density", "temperature", "pressure"}) { - if (!species.isSet(field)) { + if (!IS_SET(species[field])) { continue; // Skip variables which are not set } @@ -57,7 +57,7 @@ void NoFlowBoundary::transform(Options& state) { // Apply zero-value boundary conditions to flows for (const std::string field : {"velocity", "momentum"}) { - if (!species.isSet(field)) { + if (!IS_SET(species[field])) { continue; // Skip variables which are not set } diff --git a/src/permissions.cxx b/src/permissions.cxx index 6c6a5a891..a85f82617 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -1,5 +1,8 @@ #include "../include/permissions.hxx" +const std::map Permissions::fundamental_regions = { + {Permissions::Interior, "Interior"}, {Permissions::Boundaries, "Boundaries"}}; + Permissions::Permissions(std::initializer_list> data) : variable_permissions() { for (const auto& [varname, access] : data) { @@ -115,3 +118,14 @@ Permissions::AccessRights Permissions::applyLowerPermissions(const AccessRights& } return result; } + +std::string Permissions::regionNames(const Regions regions) { + std::string result; + for (auto & [region, name] : fundamental_regions) { + if ((regions & region) == region) { + if (result.size() > 0) result += ", "; + result += name; + } + } + return result; +} diff --git a/src/polarisation_drift.cxx b/src/polarisation_drift.cxx index 5c49b4509..c4629aa19 100644 --- a/src/polarisation_drift.cxx +++ b/src/polarisation_drift.cxx @@ -59,11 +59,11 @@ PolarisationDrift::PolarisationDrift(std::string name, .withDefault(false); } -void PolarisationDrift::transform(Options &state) { +void PolarisationDrift::transform(GuardedOptions &state) { AUTO_TRACE(); // Iterate through all subsections - Options& allspecies = state["species"]; + GuardedOptions allspecies = state["species"]; // Calculate divergence of all currents except the polarisation current @@ -75,9 +75,9 @@ void PolarisationDrift::transform(Options &state) { // Parallel current due to species parallel flow for (auto& kv : allspecies.getChildren()) { - const Options& species = kv.second; + const GuardedOptions species = kv.second; - if (!species.isSet("charge") or !species.isSet("momentum")) { + if (!IS_SET(species["charge"]) or !IS_SET(species["momentum"])) { continue; // Not charged, or no parallel flow } const BoutReal Z = get(species["charge"]); @@ -103,10 +103,10 @@ void PolarisationDrift::transform(Options &state) { // Calculate energy exchange term nonlinear in pressure // (3 / 2) ddt(Pi) += (Pi / n0) * Div((Pe + Pi) * Curlb_B + Jpar); for (auto& kv : allspecies.getChildren()) { - Options& species = allspecies[kv.first]; // Note: need non-const + GuardedOptions species = allspecies[kv.first]; // Note: need non-const - if (!(IS_SET_NOBOUNDARY(species["pressure"]) and species.isSet("charge") - and species.isSet("AA"))) { + if (!(IS_SET_NOBOUNDARY(species["pressure"]) and IS_SET(species["charge"]) + and IS_SET(species["AA"]))) { // No pressure, charge or mass -> no polarisation current due to // diamagnetic flow continue; @@ -139,9 +139,9 @@ void PolarisationDrift::transform(Options &state) { } else { mass_density = 0.0; for (auto& kv : allspecies.getChildren()) { - const Options& species = kv.second; + const GuardedOptions species = kv.second; - if (!(species.isSet("charge") and species.isSet("AA"))) { + if (!(IS_SET(species["charge"]) and IS_SET(species["AA"]))) { continue; // No charge or mass -> no current } if (fabs(get(species["charge"])) < 1e-5) { @@ -185,9 +185,9 @@ void PolarisationDrift::transform(Options &state) { // v_p = - (m_i / (Z_i * B^2)) * Grad(phi_pol) // for (auto& kv : allspecies.getChildren()) { - Options& species = allspecies[kv.first]; // Note: need non-const + GuardedOptions species = allspecies[kv.first]; // Note: need non-const - if (!(species.isSet("charge") and species.isSet("AA"))) { + if (!(IS_SET(species["charge"]) and IS_SET(species["AA"]))) { continue; // No charge or mass -> no current } const BoutReal Z = get(species["charge"]); diff --git a/src/quasineutral.cxx b/src/quasineutral.cxx index 77edd6850..5cf121b29 100644 --- a/src/quasineutral.cxx +++ b/src/quasineutral.cxx @@ -15,10 +15,10 @@ Quasineutral::Quasineutral(std::string name, Options &alloptions, ASSERT0(charge != 0.0); } -void Quasineutral::transform(Options &state) { +void Quasineutral::transform(GuardedOptions &state) { AUTO_TRACE(); // Iterate through all subsections - Options &allspecies = state["species"]; + GuardedOptions allspecies = state["species"]; // Add charge density of other species const Field3D rho = std::accumulate( @@ -27,11 +27,11 @@ void Quasineutral::transform(Options &state) { // Start with no charge Field3D(0.0), [this](Field3D value, - const std::map::value_type &name_species) { - const Options &species = name_species.second; + const std::map::value_type &name_species) { + const GuardedOptions species = name_species.second; // Add other species which have density and charge - if (name_species.first != name and species.isSet("charge") and - species.isSet("density")) { + if (name_species.first != name and IS_SET(species["charge"]) and + IS_SET(species["density"])) { // Note: Not assuming that the boundary has been set return value + getNoBoundary(species["density"]) * get(species["charge"]); @@ -40,7 +40,7 @@ void Quasineutral::transform(Options &state) { }); // Set quantites for this species - Options &species = allspecies[name]; + GuardedOptions species = allspecies[name]; // Calculate density required. Floor so that density is >= 0 density = floor(rho / (-charge), 0.0); diff --git a/src/reaction.cxx b/src/reaction.cxx index 595b120e3..3418ed2a4 100644 --- a/src/reaction.cxx +++ b/src/reaction.cxx @@ -85,7 +85,7 @@ void Reaction::add_diagnostic(const std::string& sp_name, const std::string& dia * * @param state current simulation state */ -void Reaction::calc_weightsums(Options& state) { +void Reaction::calc_weightsums(GuardedOptions state) { if (this->energy_weightsum < 0 || this->momentum_weightsum < 0) { this->momentum_weightsum = 0; this->energy_weightsum = 0; @@ -122,7 +122,7 @@ void Reaction::outputVars(Options& state) { * * @param state */ -void Reaction::transform(Options& state) { +void Reaction::transform(GuardedOptions& state) { Field3D momentum_exchange, energy_exchange, energy_loss; zero_diagnostics(state); @@ -131,7 +131,7 @@ void Reaction::transform(Options& state) { parser->get_species(species_filter::reactants); // Extract electron properties - Options& electron = state["species"]["e"]; + GuardedOptions electron = state["species"]["e"]; Field3D n_e = get(electron["density"]); Field3D T_e = get(electron["temperature"]); @@ -228,7 +228,7 @@ void Reaction::transform(Options& state) { * * @param state */ -void Reaction::zero_diagnostics(Options& state) { +void Reaction::zero_diagnostics(GuardedOptions state) { if (this->diagnose) { for (auto& [key, diag] : diagnostics) { set(state[diag.name], 0.0); diff --git a/src/recycling.cxx b/src/recycling.cxx index 93688ec5b..c3acebdba 100644 --- a/src/recycling.cxx +++ b/src/recycling.cxx @@ -148,7 +148,7 @@ Recycling::Recycling(std::string name, Options& alloptions, Solver*) { } } -void Recycling::transform(Options& state) { +void Recycling::transform(GuardedOptions& state) { AUTO_TRACE(); // Get metric tensor components @@ -160,13 +160,13 @@ void Recycling::transform(Options& state) { const Field2D& g_22 = coord->g_22; for (auto& channel : channels) { - const Options& species_from = state["species"][channel.from]; + const GuardedOptions species_from = state["species"][channel.from]; const Field3D N = get(species_from["density"]); const Field3D V = get(species_from["velocity"]); // Parallel flow velocity const Field3D T = get(species_from["temperature"]); // Ion temperature - Options& species_to = state["species"][channel.to]; + GuardedOptions species_to = state["species"][channel.to]; const Field3D Nn = get(species_to["density"]); const Field3D Pn = get(species_to["pressure"]); const Field3D Tn = get(species_to["temperature"]); @@ -178,10 +178,10 @@ void Recycling::transform(Options& state) { // Recycling particle and energy sources will be added to these global sources // which are then passed to the density and pressure equations - density_source = species_to.isSet("density_source") + density_source = IS_SET(species_to["density_source"]) ? getNonFinal(species_to["density_source"]) : 0.0; - energy_source = species_to.isSet("energy_source") + energy_source = IS_SET(species_to["energy_source"]) ? getNonFinal(species_to["energy_source"]) : 0.0; @@ -189,7 +189,7 @@ void Recycling::transform(Options& state) { if (target_recycle) { // Fast recycling needs to know how much energy the "from" species is losing to the boundary - if (species_from.isSet("energy_flow_ylow")) { + if (IS_SET(species_from["energy_flow_ylow"])) { energy_flow_ylow = get(species_from["energy_flow_ylow"]); } else { energy_flow_ylow = 0; @@ -291,13 +291,13 @@ void Recycling::transform(Options& state) { channel.pump_density_source = 0; channel.pump_energy_source = 0; - if (species_from.isSet("energy_flow_xlow")) { + if (IS_SET(species_from["energy_flow_xlow"])) { energy_flow_xlow = get(species_from["energy_flow_xlow"]); } else if ((channel.sol_fast_recycle_fraction > 0) or (channel.pfr_fast_recycle_fraction > 0)) { throw BoutException("SOL/PFR fast recycle enabled but no cell edge heat flow available, check your wall BC choice"); }; - if (species_from.isSet("particle_flow_xlow")) { + if (IS_SET(species_from["particle_flow_xlow"])) { particle_flow_xlow = get(species_from["particle_flow_xlow"]); } else if ((channel.sol_fast_recycle_fraction > 0) or (channel.pfr_fast_recycle_fraction > 0)) { throw BoutException("SOL/PFR fast recycle enabled but no cell edge particle flow available, check your wall BC choice"); diff --git a/src/relax_potential.cxx b/src/relax_potential.cxx index aa7bbb061..ceec752a7 100644 --- a/src/relax_potential.cxx +++ b/src/relax_potential.cxx @@ -78,7 +78,7 @@ RelaxPotential::RelaxPotential(std::string name, Options& alloptions, Solver* so Bsq = SQ(coord->Bxy); } -void RelaxPotential::transform(Options& state) { +void RelaxPotential::transform(GuardedOptions& state) { AUTO_TRACE(); // Scale potential @@ -86,7 +86,7 @@ void RelaxPotential::transform(Options& state) { phi.applyBoundary("neumann"); Vort.applyBoundary("neumann"); - auto& fields = state["fields"]; + auto fields = state["fields"]; ddt(Vort) = 0.0; @@ -100,13 +100,13 @@ void RelaxPotential::transform(Options& state) { Jdia.z = 0.0; Jdia.covariant = Curlb_B.covariant; - Options& allspecies = state["species"]; + GuardedOptions allspecies = state["species"]; // Pre-calculate this rather than calculate for each species Vector3D Grad_phi = Grad(phi); for (auto& kv : allspecies.getChildren()) { - Options& species = allspecies[kv.first]; // Note: need non-const + GuardedOptions species = allspecies[kv.first]; // Note: need non-const if (!(IS_SET_NOBOUNDARY(species["pressure"]) and IS_SET(species["charge"]) and (get(species["charge"]) != 0.0))) { @@ -135,7 +135,7 @@ void RelaxPotential::transform(Options& state) { // Calculate energy exchange term nonlinear in pressure // ddt(Pi) += Pi * Div((Pe + Pi) * Curlb_B); for (auto& kv : allspecies.getChildren()) { - Options& species = allspecies[kv.first]; // Note: need non-const + GuardedOptions species = allspecies[kv.first]; // Note: need non-const if (!(IS_SET_NOBOUNDARY(species["pressure"]) and IS_SET(species["charge"]) and IS_SET(species["AA"]))) { diff --git a/src/sheath_boundary.cxx b/src/sheath_boundary.cxx index b574f44cc..e0eb84d02 100644 --- a/src/sheath_boundary.cxx +++ b/src/sheath_boundary.cxx @@ -96,11 +96,11 @@ SheathBoundary::SheathBoundary(std::string name, Options& alloptions, Solver*) { .withDefault(true); } -void SheathBoundary::transform(Options& state) { +void SheathBoundary::transform(GuardedOptions& state) { AUTO_TRACE(); - Options& allspecies = state["species"]; - Options& electrons = allspecies["e"]; + GuardedOptions allspecies = state["species"]; + GuardedOptions electrons = allspecies["e"]; // Need electron properties // Not const because boundary conditions will be set @@ -149,7 +149,7 @@ void SheathBoundary::transform(Options& state) { // Iterate through charged ion species for (auto& kv : allspecies.getChildren()) { - Options& species = allspecies[kv.first]; + GuardedOptions species = allspecies[kv.first]; if ((kv.first == "e") or !IS_SET(species["charge"]) or (get(species["charge"]) == 0.0)) { @@ -291,7 +291,7 @@ void SheathBoundary::transform(Options& state) { // Electrons Field3D electron_energy_source = - electrons.isSet("energy_source") + IS_SET(electrons["energy_source"]) ? toFieldAligned(getNonFinal(electrons["energy_source"])) : zeroFrom(Ne); @@ -464,7 +464,7 @@ void SheathBoundary::transform(Options& state) { continue; // Skip electrons } - Options& species = allspecies[kv.first]; // Note: Need non-const + GuardedOptions species = allspecies[kv.first]; // Note: Need non-const // Ion charge const BoutReal Zi = @@ -484,23 +484,23 @@ void SheathBoundary::transform(Options& state) { // Density and temperature boundary conditions will be imposed (free) Field3D Ni = toFieldAligned(floor(getNoBoundary(species["density"]), 0.0)); Field3D Ti = toFieldAligned(getNoBoundary(species["temperature"])); - Field3D Pi = species.isSet("pressure") + Field3D Pi = IS_SET(species["pressure"]) ? toFieldAligned(getNoBoundary(species["pressure"])) : Ni * Ti; // Get the velocity and momentum // These will be modified at the boundaries // and then put back into the state - Field3D Vi = species.isSet("velocity") + Field3D Vi = IS_SET(species["velocity"]) ? toFieldAligned(getNoBoundary(species["velocity"])) : zeroFrom(Ni); - Field3D NVi = species.isSet("momentum") + Field3D NVi = IS_SET(species["momentum"]) ? toFieldAligned(getNoBoundary(species["momentum"])) : Mi * Ni * Vi; // Energy source will be modified in the domain Field3D energy_source = - species.isSet("energy_source") + IS_SET(species["energy_source"]) ? toFieldAligned(getNonFinal(species["energy_source"])) : zeroFrom(Ni); @@ -667,12 +667,12 @@ void SheathBoundary::transform(Options& state) { setBoundary(species["temperature"], fromFieldAligned(Ti)); setBoundary(species["pressure"], fromFieldAligned(Pi)); - if (species.isSet("velocity")) { + if (IS_SET(species["velocity"])) { Vi.clearParallelSlices(); setBoundary(species["velocity"], fromFieldAligned(Vi)); } - if (species.isSet("momentum")) { + if (IS_SET(species["momentum"])) { NVi.clearParallelSlices(); setBoundary(species["momentum"], fromFieldAligned(NVi)); } diff --git a/src/sheath_boundary_insulating.cxx b/src/sheath_boundary_insulating.cxx index a501639f6..26f5c4bd8 100644 --- a/src/sheath_boundary_insulating.cxx +++ b/src/sheath_boundary_insulating.cxx @@ -79,11 +79,11 @@ SheathBoundaryInsulating::SheathBoundaryInsulating(std::string name, Options& al .withDefault(3.5); } -void SheathBoundaryInsulating::transform(Options& state) { +void SheathBoundaryInsulating::transform(GuardedOptions& state) { AUTO_TRACE(); - Options& allspecies = state["species"]; - Options& electrons = allspecies["e"]; + GuardedOptions allspecies = state["species"]; + GuardedOptions electrons = allspecies["e"]; // Need electron properties // Not const because boundary conditions will be set @@ -203,7 +203,7 @@ void SheathBoundaryInsulating::transform(Options& state) { continue; // Skip electrons } - Options& species = allspecies[kv.first]; // Note: Need non-const + GuardedOptions species = allspecies[kv.first]; // Note: Need non-const // Ion charge const BoutReal Zi = @@ -223,23 +223,23 @@ void SheathBoundaryInsulating::transform(Options& state) { // Density and temperature boundary conditions will be imposed (free) Field3D Ni = toFieldAligned(floor(getNoBoundary(species["density"]), 0.0)); Field3D Ti = toFieldAligned(getNoBoundary(species["temperature"])); - Field3D Pi = species.isSet("pressure") + Field3D Pi = IS_SET(species["pressure"]) ? toFieldAligned(getNoBoundary(species["pressure"])) : Ni * Ti; // Get the velocity and momentum // These will be modified at the boundaries // and then put back into the state - Field3D Vi = species.isSet("velocity") + Field3D Vi = IS_SET(species["velocity"]) ? toFieldAligned(getNoBoundary(species["velocity"])) : zeroFrom(Ni); - Field3D NVi = species.isSet("momentum") + Field3D NVi = IS_SET(species["momentum"]) ? toFieldAligned(getNoBoundary(species["momentum"])) : Mi * Ni * Vi; // Energy source will be modified in the domain Field3D energy_source = - species.isSet("energy_source") + IS_SET(species["energy_source"]) ? toFieldAligned(getNonFinal(species["energy_source"])) : zeroFrom(Ni); @@ -410,11 +410,11 @@ void SheathBoundaryInsulating::transform(Options& state) { setBoundary(species["temperature"], fromFieldAligned(Ti)); setBoundary(species["pressure"], fromFieldAligned(Pi)); - if (species.isSet("velocity")) { + if (IS_SET(species["velocity"])) { setBoundary(species["velocity"], fromFieldAligned(Vi)); } - if (species.isSet("momentum")) { + if (IS_SET(species["momentum"])) { setBoundary(species["momentum"], fromFieldAligned(NVi)); } @@ -429,7 +429,7 @@ void SheathBoundaryInsulating::transform(Options& state) { // Field3D electron_energy_source = - electrons.isSet("energy_source") + IS_SET(electrons["energy_source"]) ? toFieldAligned(getNonFinal(electrons["energy_source"])) : zeroFrom(Ne); diff --git a/src/sheath_boundary_simple.cxx b/src/sheath_boundary_simple.cxx index 53da53230..36ed369b4 100644 --- a/src/sheath_boundary_simple.cxx +++ b/src/sheath_boundary_simple.cxx @@ -133,11 +133,11 @@ SheathBoundarySimple::SheathBoundarySimple(std::string name, Options& alloptions } -void SheathBoundarySimple::transform(Options& state) { +void SheathBoundarySimple::transform(GuardedOptions& state) { AUTO_TRACE(); - Options& allspecies = state["species"]; - Options& electrons = allspecies["e"]; + GuardedOptions allspecies = state["species"]; + GuardedOptions electrons = allspecies["e"]; // Need electron properties // Not const because boundary conditions will be set @@ -180,9 +180,9 @@ void SheathBoundarySimple::transform(Options& state) { // Iterate through charged ion species for (auto& kv : allspecies.getChildren()) { - const Options& species = kv.second; + const GuardedOptions species = kv.second; - if ((kv.first == "e") or !species.isSet("charge") + if ((kv.first == "e") or !IS_SET(species["charge"]) or (get(species["charge"]) == 0.0)) { continue; // Skip electrons and non-charged ions } @@ -191,7 +191,7 @@ void SheathBoundarySimple::transform(Options& state) { const Field3D Ti = getNoBoundary(species["temperature"]); const BoutReal Mi = getNoBoundary(species["AA"]); const BoutReal Zi = getNoBoundary(species["charge"]); - Field3D Vi = species.isSet("velocity") + Field3D Vi = IS_SET(species["velocity"]) ? toFieldAligned(getNoBoundary(species["velocity"])) : zeroFrom(Ni); @@ -318,7 +318,7 @@ void SheathBoundarySimple::transform(Options& state) { ////////////////////////////////////////////////////////////////// // Electrons - Field3D electron_energy_source = electrons.isSet("energy_source") + Field3D electron_energy_source = IS_SET(electrons["energy_source"]) ? toFieldAligned(getNonFinal(electrons["energy_source"])) : zeroFrom(Ne); @@ -477,7 +477,7 @@ void SheathBoundarySimple::transform(Options& state) { setBoundary(electrons["momentum"], fromFieldAligned(NVe)); } - if (always_set_phi or (state.isSection("fields") and state["fields"].isSet("phi"))) { + if (always_set_phi or (state.isSection("fields") and IS_SET(state["fields"]["phi"]))) { // Set the potential, including boundary conditions phi.clearParallelSlices(); setBoundary(state["fields"]["phi"], fromFieldAligned(phi)); @@ -492,10 +492,10 @@ void SheathBoundarySimple::transform(Options& state) { continue; // Skip electrons } - Options& species = allspecies[kv.first]; // Note: Need non-const + GuardedOptions species = allspecies[kv.first]; // Note: Need non-const // Ion charge - const BoutReal Zi = species.isSet("charge") ? get(species["charge"]) : 0.0; + const BoutReal Zi = IS_SET(species["charge"]) ? get(species["charge"]) : 0.0; if (Zi == 0.0) { continue; // Neutral -> skip @@ -507,22 +507,22 @@ void SheathBoundarySimple::transform(Options& state) { // Density and temperature boundary conditions will be imposed (free) Field3D Ni = toFieldAligned(floor(getNoBoundary(species["density"]), 0.0)); Field3D Ti = toFieldAligned(getNoBoundary(species["temperature"])); - Field3D Pi = species.isSet("pressure") + Field3D Pi = IS_SET(species["pressure"]) ? toFieldAligned(getNoBoundary(species["pressure"])) : Ni * Ti; // Get the velocity and momentum // These will be modified at the boundaries // and then put back into the state - Field3D Vi = species.isSet("velocity") + Field3D Vi = IS_SET(species["velocity"]) ? toFieldAligned(getNoBoundary(species["velocity"])) : zeroFrom(Ni); - Field3D NVi = species.isSet("momentum") + Field3D NVi = IS_SET(species["momentum"]) ? toFieldAligned(getNoBoundary(species["momentum"])) : Mi * Ni * Vi; // Energy source will be modified in the domain - Field3D energy_source = species.isSet("energy_source") + Field3D energy_source = IS_SET(species["energy_source"]) ? toFieldAligned(getNonFinal(species["energy_source"])) : zeroFrom(Ni); @@ -673,12 +673,12 @@ void SheathBoundarySimple::transform(Options& state) { setBoundary(species["temperature"], fromFieldAligned(Ti)); setBoundary(species["pressure"], fromFieldAligned(Pi)); - if (species.isSet("velocity")) { + if (IS_SET(species["velocity"])) { Vi.clearParallelSlices(); setBoundary(species["velocity"], fromFieldAligned(Vi)); } - if (species.isSet("momentum")) { + if (IS_SET(species["momentum"])) { NVi.clearParallelSlices(); setBoundary(species["momentum"], fromFieldAligned(NVi)); } @@ -690,8 +690,8 @@ void SheathBoundarySimple::transform(Options& state) { // Add the total sheath power flux to the tracker of y power flows add(species["energy_flow_ylow"], fromFieldAligned(ion_sheath_power_ylow)); - set(diagnostics[species.name()]["energy_source"], hflux_i); - set(diagnostics[species.name()]["particle_source"], particle_source); + set(diagnostics[kv.first]["energy_source"], hflux_i); + set(diagnostics[kv.first]["particle_source"], particle_source); } } diff --git a/src/sheath_closure.cxx b/src/sheath_closure.cxx index 585caec28..8f212aa75 100644 --- a/src/sheath_closure.cxx +++ b/src/sheath_closure.cxx @@ -31,13 +31,13 @@ SheathClosure::SheathClosure(std::string name, Options &alloptions, Solver *) { output.write("\tL_par = {:e} (normalised)\n", L_par); } -void SheathClosure::transform(Options &state) { +void SheathClosure::transform(GuardedOptions &state) { AUTO_TRACE(); // Get electrostatic potential auto phi = get(state["fields"]["phi"]); - auto& electrons = state["species"]["e"]; + auto electrons = state["species"]["e"]; // Electron density auto n = get(electrons["density"]); @@ -51,7 +51,7 @@ void SheathClosure::transform(Options &state) { add(electrons["density_source"], DivJsh); // Electron heat conduction - if (electrons.isSet("temperature")) { + if (IS_SET(electrons["temperature"])) { // Assume attached, sheath-limited regime // Sheath heat transmission gamma * n * T * cs @@ -70,9 +70,9 @@ void SheathClosure::transform(Options &state) { // standard Bohm boundary conditions for a pure, hydrogenic plasma.] Field3D P_total = 0.0; Field3D rho_total = 0.0; // mass density - Options& allspecies = state["species"]; + GuardedOptions allspecies = state["species"]; for (auto& kv : allspecies.getChildren()) { - Options& species = allspecies[kv.first]; + GuardedOptions species = allspecies[kv.first]; const BoutReal A = get(species["AA"]); Field3D Ns = get(species["density"]); @@ -85,7 +85,7 @@ void SheathClosure::transform(Options &state) { Field3D c_s = sqrt(P_total / rho_total); for (auto& kv : allspecies.getChildren()) { - Options& species = allspecies[kv.first]; + GuardedOptions species = allspecies[kv.first]; Field3D Ns = get(species["density"]); Field3D sheath_flux = floor(Ns * c_s, 0.0); diff --git a/src/snb_conduction.cxx b/src/snb_conduction.cxx index 36521355f..ff143b387 100644 --- a/src/snb_conduction.cxx +++ b/src/snb_conduction.cxx @@ -4,14 +4,14 @@ #include using bout::globals::mesh; -void SNBConduction::transform(Options& state) { - auto& units = state["units"]; +void SNBConduction::transform(GuardedOptions& state) { + auto units = state["units"]; const auto rho_s0 = get(units["meters"]); const auto Tnorm = get(units["eV"]); const auto Nnorm = get(units["inv_meters_cubed"]); const auto Omega_ci = 1. / get(units["seconds"]); - Options& electrons = state["species"]["e"]; + GuardedOptions electrons = state["species"]["e"]; // Note: Needs boundary conditions on temperature const Field3D Te = GET_VALUE(Field3D, electrons["temperature"]) * Tnorm; // eV const Field3D Ne = GET_VALUE(Field3D, electrons["density"]) * Nnorm; // In m^-3 diff --git a/src/solkit_hydrogen_charge_exchange.cxx b/src/solkit_hydrogen_charge_exchange.cxx index 6c16f269c..949d86bf2 100644 --- a/src/solkit_hydrogen_charge_exchange.cxx +++ b/src/solkit_hydrogen_charge_exchange.cxx @@ -2,7 +2,7 @@ #include "../include/integrate.hxx" // for cellAverage -void SOLKITHydrogenChargeExchange::calculate_rates(Options& atom, Options& ion) { +void SOLKITHydrogenChargeExchange::calculate_rates(GuardedOptions atom, GuardedOptions ion) { const auto AA = get(ion["AA"]); // Check that mass is consistent ASSERT1(get(atom["AA"]) == AA); diff --git a/src/solkit_neutral_parallel_diffusion.cxx b/src/solkit_neutral_parallel_diffusion.cxx index 0555d9a97..5beac62fa 100644 --- a/src/solkit_neutral_parallel_diffusion.cxx +++ b/src/solkit_neutral_parallel_diffusion.cxx @@ -6,14 +6,14 @@ using bout::globals::mesh; -void SOLKITNeutralParallelDiffusion::transform(Options& state) { +void SOLKITNeutralParallelDiffusion::transform(GuardedOptions& state) { AUTO_TRACE(); - Options& allspecies = state["species"]; + GuardedOptions allspecies = state["species"]; for (auto& kv : allspecies.getChildren()) { // Get non-const reference - auto& species = allspecies[kv.first]; + auto species = allspecies[kv.first]; - if (species.isSet("charge") and (get(species["charge"]) != 0.0)) { + if (IS_SET(species["charge"]) and (get(species["charge"]) != 0.0)) { // Skip charged species continue; } @@ -25,13 +25,13 @@ void SOLKITNeutralParallelDiffusion::transform(Options& state) { // Start with no collisions Field3D(0.0), [this](Field3D value, - const std::map::value_type &name_species) { - const Options &species = name_species.second; + const std::map::value_type &name_species) { + const GuardedOptions species = name_species.second; if (name_species.first == "e") { // Electrons const Field3D Ne = GET_VALUE(Field3D, species["density"]); return value + (8.8e-21 / area_norm) * Ne; - } else if (species.isSet("charge") and + } else if (IS_SET(species["charge"]) and (get(species["charge"]) != 0.0)) { // Charged ion species const Field3D Ni = GET_VALUE(Field3D, species["density"]); diff --git a/src/sound_speed.cxx b/src/sound_speed.cxx index eaa7d9335..6bf13d3de 100644 --- a/src/sound_speed.cxx +++ b/src/sound_speed.cxx @@ -3,15 +3,15 @@ #include "../include/hermes_utils.hxx" #include -void SoundSpeed::transform(Options &state) { +void SoundSpeed::transform(GuardedOptions &state) { Field3D total_pressure = 0.0; Field3D total_density = 0.0; Field3D fastest_wave = 0.0; for (auto& kv : state["species"].getChildren()) { - const Options& species = kv.second; + const GuardedOptions species = kv.second; - if (species.isSet("pressure")) { + if (IS_SET(species["pressure"])) { total_pressure += GET_NOBOUNDARY(Field3D, species["pressure"]); } @@ -21,14 +21,14 @@ void SoundSpeed::transform(Options &state) { continue; } - if (species.isSet("AA")) { + if (IS_SET(species["AA"])) { auto AA = get(species["AA"]); // Atomic mass number - if (species.isSet("density")) { + if (IS_SET(species["density"])) { total_density += GET_NOBOUNDARY(Field3D, species["density"]) * get(species["AA"]); } - if (species.isSet("temperature")) { + if (IS_SET(species["temperature"])) { auto T = GET_NOBOUNDARY(Field3D, species["temperature"]); for (auto& i : fastest_wave.getRegion("RGN_NOBNDRY")) { BoutReal sound_speed = sqrt(softFloor(T[i], temperature_floor) / AA); diff --git a/src/temperature_feedback.cxx b/src/temperature_feedback.cxx index bf226e37c..b642509fa 100644 --- a/src/temperature_feedback.cxx +++ b/src/temperature_feedback.cxx @@ -3,8 +3,8 @@ #include using bout::globals::mesh; -void TemperatureFeedback::transform(Options& state) { - Options& species = state["species"][name]; +void TemperatureFeedback::transform(GuardedOptions& state) { + GuardedOptions species = state["species"][name]; // Doesn't need all boundaries to be set Field3D T = getNoBoundary(species["temperature"]); diff --git a/src/transform.cxx b/src/transform.cxx index 77842118a..b560f96b3 100644 --- a/src/transform.cxx +++ b/src/transform.cxx @@ -25,8 +25,8 @@ Transform::Transform(std::string name, Options& alloptions, Solver* UNUSED(solve } } -void Transform::transform(Options& state) { +void Transform::transform(GuardedOptions& state) { for (const auto& lr : transforms) { - state[lr.first] = state[lr.second].copy(); + state[lr.first].getWritable() = state[lr.second].get().copy(); } } diff --git a/src/upstream_density_feedback.cxx b/src/upstream_density_feedback.cxx index f3acc0626..b44b1cd09 100644 --- a/src/upstream_density_feedback.cxx +++ b/src/upstream_density_feedback.cxx @@ -3,8 +3,8 @@ #include using bout::globals::mesh; -void UpstreamDensityFeedback::transform(Options& state) { - Options& species = state["species"][name]; +void UpstreamDensityFeedback::transform(GuardedOptions& state) { + GuardedOptions species = state["species"][name]; // Doesn't need all boundaries to be set Field3D N = getNoBoundary(species["density"]); diff --git a/src/vorticity.cxx b/src/vorticity.cxx index e663d73e7..cb4ff0f31 100644 --- a/src/vorticity.cxx +++ b/src/vorticity.cxx @@ -207,11 +207,11 @@ Vorticity::Vorticity(std::string name, Options& alloptions, Solver* solver) { .withDefault(false); } -void Vorticity::transform(Options& state) { +void Vorticity::transform(GuardedOptions& state) { AUTO_TRACE(); phi.name = "phi"; - auto& fields = state["fields"]; + auto fields = state["fields"]; // Set the boundary of phi. Both 2D and 3D fields are kept, though the 3D field // is constant in Z. This is for efficiency, to reduce the number of conversions. @@ -221,12 +221,12 @@ void Vorticity::transform(Options& state) { if (diamagnetic_polarisation) { // Diamagnetic term in vorticity. Note this is weighted by the mass // This includes all species, including electrons - Options& allspecies = state["species"]; + GuardedOptions allspecies = state["species"]; for (auto& kv : allspecies.getChildren()) { - Options& species = allspecies[kv.first]; // Note: need non-const + GuardedOptions species = allspecies[kv.first]; // Note: need non-const - if (!(IS_SET_NOBOUNDARY(species["pressure"]) and species.isSet("charge") - and species.isSet("AA"))) { + if (!(IS_SET_NOBOUNDARY(species["pressure"]) and IS_SET(species["charge"]) + and IS_SET(species["AA"]))) { continue; // No pressure, charge or mass -> no polarisation current } @@ -349,7 +349,7 @@ void Vorticity::transform(Options& state) { } Field3D Te; // Electron temperature, use for outer boundary conditions - if (state["species"]["e"].isSet("temperature")) { + if (IS_SET(state["species"]["e"]["temperature"])) { // Electron temperature set Te = GET_NOBOUNDARY(Field3D, state["species"]["e"]["temperature"]); } else { @@ -482,10 +482,10 @@ void Vorticity::transform(Options& state) { Jdia.z = 0.0; Jdia.covariant = Curlb_B.covariant; - Options& allspecies = state["species"]; + GuardedOptions allspecies = state["species"]; for (auto& kv : allspecies.getChildren()) { - Options& species = allspecies[kv.first]; // Note: need non-const + GuardedOptions species = allspecies[kv.first]; // Note: need non-const if (!(IS_SET_NOBOUNDARY(species["pressure"]) and IS_SET(species["charge"]))) { continue; // No pressure or charge -> no diamagnetic current @@ -587,11 +587,11 @@ void Vorticity::transform(Options& state) { zeroFrom(Vort); // Sum of atomic mass * collision frequency * density Field3D sum_A_n = zeroFrom(Vort); // Sum of atomic mass * density - const Options& allspecies = state["species"]; + GuardedOptions allspecies = state["species"]; for (const auto& kv : allspecies.getChildren()) { - const Options& species = kv.second; + const GuardedOptions species = kv.second; - if (!(species.isSet("charge") and species.isSet("AA"))) { + if (!(IS_SET(species["charge"]) and IS_SET(species["AA"]))) { continue; // No charge or mass -> no current } if (fabs(get(species["charge"])) < 1e-5) { diff --git a/src/zero_current.cxx b/src/zero_current.cxx index 37757b116..14005d208 100644 --- a/src/zero_current.cxx +++ b/src/zero_current.cxx @@ -13,21 +13,21 @@ ZeroCurrent::ZeroCurrent(std::string name, Options& alloptions, Solver*) ASSERT0(charge != 0.0); } -void ZeroCurrent::transform(Options &state) { +void ZeroCurrent::transform(GuardedOptions &state) { AUTO_TRACE(); // Current due to other species Field3D current; // Now calculate forces on other species - Options& allspecies = state["species"]; + GuardedOptions allspecies = state["species"]; for (auto& kv : allspecies.getChildren()) { if (kv.first == name) { continue; // Skip self } - Options& species = allspecies[kv.first]; // Note: Need non-const + GuardedOptions species = allspecies[kv.first]; // Note: Need non-const - if (!(species.isSet("density") and species.isSet("charge"))) { + if (!(IS_SET(species["density"]) and IS_SET(species["charge"]))) { continue; // Needs both density and charge to contribute } @@ -55,8 +55,8 @@ void ZeroCurrent::transform(Options &state) { } // Get the species density - Options& species = state["species"][name]; - if (species["velocity"].isSet()) { + GuardedOptions species = state["species"][name]; + if (IS_SET(species["velocity"])) { throw BoutException("Cannot use zero_current in species {} if velocity already set\n", name); } Field3D N = getNoBoundary(species["density"]); diff --git a/tests/unit/test_component.cxx b/tests/unit/test_component.cxx index 0a62fa64c..36520c005 100644 --- a/tests/unit/test_component.cxx +++ b/tests/unit/test_component.cxx @@ -8,7 +8,8 @@ namespace { struct TestComponent : public Component { TestComponent(const std::string&, Options&, Solver *) {} - void transform(Options &state) override { state["answer"] = 42; } +private: + void transform(GuardedOptions &state) override { state["answer"].getWritable() = 42; } }; RegisterComponent registertestcomponent("testcomponent"); diff --git a/tests/unit/test_component_scheduler.cxx b/tests/unit/test_component_scheduler.cxx index f3f05bd3d..11865d758 100644 --- a/tests/unit/test_component_scheduler.cxx +++ b/tests/unit/test_component_scheduler.cxx @@ -5,14 +5,16 @@ namespace { struct TestComponent : public Component { TestComponent(const std::string&, Options&, Solver *) {} - void transform(Options &state) override { state["answer"] = 42; } +private: + void transform(GuardedOptions &state) override { state["answer"].getWritable() = 42; } }; struct TestMultiply : public Component { TestMultiply(const std::string&, Options&, Solver *) {} - - void transform(Options &state) override { + +private: + void transform(GuardedOptions &state) override { // Note: Using set<>() and get<>() for quicker access, avoiding printing // getNonFinal needs to be used because we set the value afterwards set(state["answer"], From 69515b9538ebff07426a4e732891f602895c9865 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Wed, 5 Nov 2025 14:18:40 +0000 Subject: [PATCH 05/63] Got everything compiling and linking. Tests still fail. Also need to revert a bunch of changes from Options::isSet(std::string) to IS_SET, to avoid creating objects we don't need. --- include/adas_carbon.hxx | 8 ++-- include/adas_lithium.hxx | 8 ++-- include/adas_neon.hxx | 8 ++-- include/adas_reaction.hxx | 6 +-- include/anomalous_diffusion.hxx | 2 +- include/binormal_stpm.hxx | 2 +- include/braginskii_collisions.hxx | 2 +- include/braginskii_electron_viscosity.hxx | 2 +- include/braginskii_ion_viscosity.hxx | 2 +- include/braginskii_thermal_force.hxx | 2 +- include/classical_diffusion.hxx | 2 +- include/component.hxx | 2 +- include/detachment_controller.hxx | 2 +- include/diamagnetic_drift.hxx | 2 +- include/electromagnetic.hxx | 2 +- include/electron_force_balance.hxx | 2 +- include/evolve_density.hxx | 2 +- include/evolve_energy.hxx | 2 +- include/evolve_momentum.hxx | 2 +- include/evolve_pressure.hxx | 2 +- include/fixed_density.hxx | 4 +- include/fixed_fraction_ions.hxx | 2 +- include/fixed_fraction_radiation.hxx | 4 +- include/fixed_temperature.hxx | 4 +- include/fixed_velocity.hxx | 4 +- include/guarded_options.hxx | 17 +++++--- include/hydrogen_charge_exchange.hxx | 2 +- include/ionisation.hxx | 2 +- include/isothermal.hxx | 2 +- include/neutral_boundary.hxx | 2 +- include/neutral_full_velocity.hxx | 2 +- include/neutral_mixed.hxx | 2 +- include/neutral_parallel_diffusion.hxx | 2 +- include/noflow_boundary.hxx | 2 +- include/polarisation_drift.hxx | 2 +- include/quasineutral.hxx | 2 +- include/reaction.hxx | 2 +- include/recycling.hxx | 2 +- include/relax_potential.hxx | 2 +- include/scale_timederivs.hxx | 26 +++++------ include/set_temperature.hxx | 4 +- include/sheath_boundary.hxx | 2 +- include/sheath_boundary_insulating.hxx | 2 +- include/sheath_boundary_simple.hxx | 2 +- include/sheath_closure.hxx | 2 +- include/simple_conduction.hxx | 4 +- include/simple_pump.hxx | 2 +- include/snb_conduction.hxx | 2 +- include/solkit_hydrogen_charge_exchange.hxx | 2 +- include/solkit_neutral_parallel_diffusion.hxx | 2 +- include/sound_speed.hxx | 2 +- include/temperature_feedback.hxx | 2 +- include/transform.hxx | 2 +- include/upstream_density_feedback.hxx | 2 +- include/vorticity.hxx | 2 +- include/zero_current.hxx | 2 +- src/adas_reaction.cxx | 8 ++-- src/anomalous_diffusion.cxx | 2 +- src/binormal_stpm.cxx | 2 +- src/braginskii_collisions.cxx | 2 +- src/braginskii_electron_viscosity.cxx | 2 +- src/braginskii_ion_viscosity.cxx | 2 +- src/braginskii_thermal_force.cxx | 2 +- src/classical_diffusion.cxx | 2 +- src/component.cxx | 2 +- src/detachment_controller.cxx | 2 +- src/diamagnetic_drift.cxx | 2 +- src/electromagnetic.cxx | 2 +- src/electron_force_balance.cxx | 2 +- src/evolve_density.cxx | 2 +- src/evolve_energy.cxx | 2 +- src/evolve_momentum.cxx | 2 +- src/evolve_pressure.cxx | 2 +- src/fixed_fraction_ions.cxx | 2 +- src/guarded_options.cxx | 16 +++++++ src/ionisation.cxx | 2 +- src/isothermal.cxx | 2 +- src/neutral_boundary.cxx | 2 +- src/neutral_full_velocity.cxx | 2 +- src/neutral_mixed.cxx | 2 +- src/neutral_parallel_diffusion.cxx | 2 +- src/noflow_boundary.cxx | 2 +- src/polarisation_drift.cxx | 2 +- src/quasineutral.cxx | 2 +- src/reaction.cxx | 2 +- src/recycling.cxx | 2 +- src/relax_potential.cxx | 2 +- src/sheath_boundary.cxx | 2 +- src/sheath_boundary_insulating.cxx | 2 +- src/sheath_boundary_simple.cxx | 2 +- src/sheath_closure.cxx | 2 +- src/snb_conduction.cxx | 2 +- src/solkit_neutral_parallel_diffusion.cxx | 2 +- src/sound_speed.cxx | 2 +- src/temperature_feedback.cxx | 2 +- src/transform.cxx | 2 +- src/upstream_density_feedback.cxx | 2 +- src/vorticity.cxx | 2 +- src/zero_current.cxx | 2 +- tests/unit/test_component.cxx | 2 +- tests/unit/test_component_scheduler.cxx | 4 +- tests/unit/test_guarded_options.cxx | 43 +++++++++++++++++++ 102 files changed, 203 insertions(+), 137 deletions(-) diff --git a/include/adas_carbon.hxx b/include/adas_carbon.hxx index 47a614be7..2bf0338bf 100644 --- a/include/adas_carbon.hxx +++ b/include/adas_carbon.hxx @@ -41,7 +41,7 @@ struct ADASCarbonIonisation : public OpenADAS { -carbon_ionisation_energy[level]) {} private: - void transform(GuardedOptions& state) override { + void transform_impl(GuardedOptions& state) override { calculate_rates( state["species"]["e"], // Electrons state["species"][carbon_species_name], // From this ionisation state @@ -64,7 +64,7 @@ struct ADASCarbonRecombination : public OpenADAS { private: - void transform(GuardedOptions& state) override { + void transform_impl(GuardedOptions& state) override { calculate_rates( state["species"]["e"], // Electrons state["species"][carbon_species_name], // From this ionisation state @@ -83,8 +83,8 @@ struct ADASCarbonCX : public OpenADASChargeExchange { private: - void transform(GuardedOptions& state) override { - Options& species = state["species"]; + void transform_impl(GuardedOptions& state) override { + GuardedOptions species = state["species"]; calculate_rates( species["e"], // Electrons species[carbon_species_name], // From this ionisation state diff --git a/include/adas_lithium.hxx b/include/adas_lithium.hxx index 11afb911d..1e56c9a01 100644 --- a/include/adas_lithium.hxx +++ b/include/adas_lithium.hxx @@ -44,7 +44,7 @@ struct ADASLithiumIonisation : public OpenADAS { -lithium_ionisation_energy[level]) {} private: - void transform(GuardedOptions& state) override { + void transform_impl(GuardedOptions& state) override { calculate_rates( state["species"]["e"], // Electrons state["species"][lithium_species_name], // From this ionisation state @@ -66,7 +66,7 @@ struct ADASLithiumRecombination : public OpenADAS { lithium_ionisation_energy[level]) {} private: - void transform(GuardedOptions& state) override { + void transform_impl(GuardedOptions& state) override { calculate_rates( state["species"]["e"], // Electrons state["species"][lithium_species_name], // From this ionisation state @@ -84,8 +84,8 @@ struct ADASLithiumCX : public OpenADASChargeExchange { : OpenADASChargeExchange(alloptions["units"], "ccd89_li.json", level) {} private: - void transform(GuardedOptions& state) override { - Options& species = state["species"]; + void transform_impl(GuardedOptions& state) override { + GuardedOptions species = state["species"]; calculate_rates( species["e"], // Electrons species[lithium_species_name], // From this ionisation state diff --git a/include/adas_neon.hxx b/include/adas_neon.hxx index 8efbb3fad..bf14503b4 100644 --- a/include/adas_neon.hxx +++ b/include/adas_neon.hxx @@ -44,7 +44,7 @@ struct ADASNeonIonisation : public OpenADAS { -neon_ionisation_energy[level]) {} private: - void transform(GuardedOptions& state) override { + void transform_impl(GuardedOptions& state) override { calculate_rates( state["species"]["e"], // Electrons state["species"][neon_species_name], // From this ionisation state @@ -66,7 +66,7 @@ struct ADASNeonRecombination : public OpenADAS { neon_ionisation_energy[level]) {} private: - void transform(GuardedOptions& state) override { + void transform_impl(GuardedOptions& state) override { calculate_rates( state["species"]["e"], // Electrons state["species"][neon_species_name], // From this ionisation state @@ -84,8 +84,8 @@ struct ADASNeonCX : public OpenADASChargeExchange { : OpenADASChargeExchange(alloptions["units"], "ccd89_ne.json", level) {} private: - void transform(GuardedOptions& state) override { - Options& species = state["species"]; + void transform_impl(GuardedOptions& state) override { + GuardedOptions species = state["species"]; calculate_rates( species["e"], // Electrons species[neon_species_name], // From this ionisation state diff --git a/include/adas_reaction.hxx b/include/adas_reaction.hxx index 0c584949b..9d944bbfa 100644 --- a/include/adas_reaction.hxx +++ b/include/adas_reaction.hxx @@ -70,7 +70,7 @@ struct OpenADAS : public ReactionBase { /// @param electron The electron species e.g. state["species"]["e"] /// @param from_ion The ion on the left of the reaction /// @param to_ion The ion on the right of the reaction - void calculate_rates(Options& electron, Options& from_ion, Options& to_ion); + void calculate_rates(GuardedOptions electron, GuardedOptions from_ion, GuardedOptions to_ion); private: OpenADASRateCoefficient rate_coef; ///< Reaction rate coefficient OpenADASRateCoefficient radiation_coef; ///< Energy loss (radiation) coefficient @@ -95,8 +95,8 @@ struct OpenADASChargeExchange : public ReactionBase { /// from_A and to_A must have the same atomic mass /// from_B and to_B must have the same atomic mass /// The charge of from_A + from_B must equal the charge of to_A + to_B - void calculate_rates(Options& electron, Options& from_A, Options& from_B, Options& to_A, - Options& to_B); + void calculate_rates(GuardedOptions electron, GuardedOptions from_A, GuardedOptions from_B, GuardedOptions to_A, + GuardedOptions to_B); private: OpenADASRateCoefficient rate_coef; ///< Reaction rate coefficient diff --git a/include/anomalous_diffusion.hxx b/include/anomalous_diffusion.hxx index d512f788b..8c223c16f 100644 --- a/include/anomalous_diffusion.hxx +++ b/include/anomalous_diffusion.hxx @@ -52,7 +52,7 @@ private: /// - momentum_source /// - energy_source /// - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; diff --git a/include/binormal_stpm.hxx b/include/binormal_stpm.hxx index d806df0bb..c077303bc 100644 --- a/include/binormal_stpm.hxx +++ b/include/binormal_stpm.hxx @@ -40,7 +40,7 @@ private: /// - momentum correction /// - density correction /// - void transform(GuardedOptions& state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/braginskii_collisions.hxx b/include/braginskii_collisions.hxx index 14401e83a..bff9d013a 100644 --- a/include/braginskii_collisions.hxx +++ b/include/braginskii_collisions.hxx @@ -65,7 +65,7 @@ private: /// Save more diagnostics? bool diagnose; - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; /// Update collision frequencies, momentum and energy exchange /// nu_12 normalised frequency diff --git a/include/braginskii_electron_viscosity.hxx b/include/braginskii_electron_viscosity.hxx index d81af13f7..ccb57feaa 100644 --- a/include/braginskii_electron_viscosity.hxx +++ b/include/braginskii_electron_viscosity.hxx @@ -49,7 +49,7 @@ private: /// - e /// - momentum_source /// - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/braginskii_ion_viscosity.hxx b/include/braginskii_ion_viscosity.hxx index 30b4b8ef6..739f91bfc 100644 --- a/include/braginskii_ion_viscosity.hxx +++ b/include/braginskii_ion_viscosity.hxx @@ -85,7 +85,7 @@ private: /// - /// - momentum_source /// - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/braginskii_thermal_force.hxx b/include/braginskii_thermal_force.hxx index 6a8202dc1..55a11354c 100644 --- a/include/braginskii_thermal_force.hxx +++ b/include/braginskii_thermal_force.hxx @@ -69,7 +69,7 @@ private: /// - [ if AA < 4 ("light") or AA > 10 ("heavy") ] /// - momentum_source /// - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/classical_diffusion.hxx b/include/classical_diffusion.hxx index 067cd8f43..cb738cc90 100644 --- a/include/classical_diffusion.hxx +++ b/include/classical_diffusion.hxx @@ -15,7 +15,7 @@ private: Field3D Dn; ///< Particle diffusion coefficient BoutReal custom_D; ///< User-set particle diffusion coefficient override - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/component.hxx b/include/component.hxx index c05d06876..4fb89283d 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -61,7 +61,7 @@ private: /// implement this function. It will only allow the reading /// from/writing to state variables with the appropriate permissiosn /// in `state_variable_access`. - virtual void transform(GuardedOptions &state) = 0; + virtual void transform_impl(GuardedOptions &state) = 0; }; /////////////////////////////////////////////////////////////////// diff --git a/include/detachment_controller.hxx b/include/detachment_controller.hxx index 8f33af5c8..16831347c 100644 --- a/include/detachment_controller.hxx +++ b/include/detachment_controller.hxx @@ -314,7 +314,7 @@ ASSERT0(BoutComm::size() == 1); // Only works on one processor std::vector time_buffer; std::vector error_buffer; - void transform(GuardedOptions& state) override; + void transform_impl(GuardedOptions& state) override; }; diff --git a/include/diamagnetic_drift.hxx b/include/diamagnetic_drift.hxx index b4c9e0ca9..0ca0d8481 100644 --- a/include/diamagnetic_drift.hxx +++ b/include/diamagnetic_drift.hxx @@ -22,7 +22,7 @@ private: /// - density_source /// - energy_source /// - momentum_source - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/electromagnetic.hxx b/include/electromagnetic.hxx index bfca17679..aacd11beb 100644 --- a/include/electromagnetic.hxx +++ b/include/electromagnetic.hxx @@ -74,7 +74,7 @@ private: /// - fields /// - Apar Electromagnetic potential /// - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/electron_force_balance.hxx b/include/electron_force_balance.hxx index 08feabb5b..646926508 100644 --- a/include/electron_force_balance.hxx +++ b/include/electron_force_balance.hxx @@ -47,7 +47,7 @@ private: /// - if both density and charge are set /// - momentum_source /// - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/evolve_density.hxx b/include/evolve_density.hxx index f14b397b1..9c22536e4 100644 --- a/include/evolve_density.hxx +++ b/include/evolve_density.hxx @@ -92,7 +92,7 @@ private: /// - AA /// - charge /// - density - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/evolve_energy.hxx b/include/evolve_energy.hxx index cc7dc496b..658f8e6a8 100644 --- a/include/evolve_energy.hxx +++ b/include/evolve_energy.hxx @@ -96,7 +96,7 @@ private: /// - pressure /// - temperature /// - void transform(GuardedOptions& state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/evolve_momentum.hxx b/include/evolve_momentum.hxx index b0e44e1b9..17f716e62 100644 --- a/include/evolve_momentum.hxx +++ b/include/evolve_momentum.hxx @@ -53,7 +53,7 @@ private: /// - /// - momentum /// - velocity if density is defined - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/evolve_pressure.hxx b/include/evolve_pressure.hxx index f58f6ff7f..32b2ef9ba 100644 --- a/include/evolve_pressure.hxx +++ b/include/evolve_pressure.hxx @@ -112,7 +112,7 @@ private: /// - pressure /// - temperature Requires density /// - void transform(GuardedOptions& state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/fixed_density.hxx b/include/fixed_density.hxx index cdfddd97f..820dffe1d 100644 --- a/include/fixed_density.hxx +++ b/include/fixed_density.hxx @@ -57,9 +57,9 @@ private: /// - AA /// - charge /// - density - void transform(GuardedOptions& state) override { + void transform_impl(GuardedOptions& state) override { AUTO_TRACE(); - auto& species = state["species"][name]; + auto species = state["species"][name]; if (charge != 0.0) { // Don't set charge for neutral species set(species["charge"], charge); } diff --git a/include/fixed_fraction_ions.hxx b/include/fixed_fraction_ions.hxx index 6fa8a4e36..b43291922 100644 --- a/include/fixed_fraction_ions.hxx +++ b/include/fixed_fraction_ions.hxx @@ -28,7 +28,7 @@ struct FixedFractionIons : public Component { /// - /// - density = * electron density /// - ... - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/fixed_fraction_radiation.hxx b/include/fixed_fraction_radiation.hxx index b09d7697a..8ded88490 100644 --- a/include/fixed_fraction_radiation.hxx +++ b/include/fixed_fraction_radiation.hxx @@ -410,8 +410,8 @@ struct FixedFractionRadiation : public Component { /// - e /// - energy_source /// - void transform(GuardedOptions &state) override { - auto& electrons = state["species"]["e"]; + void transform_impl(GuardedOptions& state) override { + auto electrons = state["species"]["e"]; // Don't need boundary cells const Field3D Ne = GET_NOBOUNDARY(Field3D, electrons["density"]); const Field3D Te = GET_NOBOUNDARY(Field3D, electrons["temperature"]); diff --git a/include/fixed_temperature.hxx b/include/fixed_temperature.hxx index ea439dd57..0ac523dd4 100644 --- a/include/fixed_temperature.hxx +++ b/include/fixed_temperature.hxx @@ -79,9 +79,9 @@ private: /// - /// - temperature /// - pressure (if density is set) - void transform(GuardedOptions& state) override { + void transform_impl(GuardedOptions& state) override { AUTO_TRACE(); - auto& species = state["species"][name]; + auto species = state["species"][name]; set(species["temperature"], T); diff --git a/include/fixed_velocity.hxx b/include/fixed_velocity.hxx index 9825a5fb5..48a295575 100644 --- a/include/fixed_velocity.hxx +++ b/include/fixed_velocity.hxx @@ -54,9 +54,9 @@ private: /// - /// - velocity /// - momentum - void transform(GuardedOptions& state) override { + void transform_impl(GuardedOptions& state) override { AUTO_TRACE(); - auto& species = state["species"][name]; + auto species = state["species"][name]; set(species["velocity"], V); // If density is set, also set momentum diff --git a/include/guarded_options.hxx b/include/guarded_options.hxx index 6fe62889f..b4ed9825e 100644 --- a/include/guarded_options.hxx +++ b/include/guarded_options.hxx @@ -11,6 +11,10 @@ /// from and writing to the underlying data. class GuardedOptions { public: + GuardedOptions() = default; + // GuardedOptions(GuardedOptions &other) = default; + // GuardedOptions(GuardedOptions &&other) = default; + /// Create a guarded options object which applies the specified /// permissions to the underlying options object. Note that the /// variable names used in the Permissions object must always be the @@ -37,9 +41,12 @@ public: const GuardedOptions operator[](const char* name) const { return (*this)[std::string(name)]; } std::map getChildren(); - bool isSection(const std::string& name) const; - bool isSection(const char* name) const { return (*this).isSection(std::string(name)); - } + bool isSection(const std::string& name) const { return options->isSection(name); } + bool isSection(const char* name) const { return (*this).isSection(std::string(name)); } + bool isSection() const { return options->isSection(); } + bool isSet(const std::string& name) const { return options->isSet(name); } + bool isSet(const char* name) const { return (*this).isSet((std::string(name))); } + bool isSet() const { return options->isSet(); } /// Get read-only access to the underlying Options object. Throws /// BoutException if there is not read-permission for this object. @@ -57,8 +64,8 @@ public: std::map unwrittenItems() const; private: - Options* options; - Permissions* permissions; + Options* options{nullptr}; + Permissions* permissions{nullptr}; mutable std::shared_ptr> unread_variables, unwritten_variables; diff --git a/include/hydrogen_charge_exchange.hxx b/include/hydrogen_charge_exchange.hxx index 47e6c20fc..b65619190 100644 --- a/include/hydrogen_charge_exchange.hxx +++ b/include/hydrogen_charge_exchange.hxx @@ -236,7 +236,7 @@ private: Field3D atom_rate, ion_rate; ///< Collision rates in s^-1 bool no_neutral_cx_mom_gain; ///< Make CX behave as in diffusive neutrals? - void transform(GuardedOptions& state) override { + void transform_impl(GuardedOptions& state) override { Field3D R, atom_mom, ion_mom, atom_energy, ion_energy; calculate_rates(state["species"][{Isotope1}], // e.g. "h" diff --git a/include/ionisation.hxx b/include/ionisation.hxx index 325cc90f9..6c464d251 100644 --- a/include/ionisation.hxx +++ b/include/ionisation.hxx @@ -14,7 +14,7 @@ private: BoutReal Tnorm, Nnorm, FreqNorm; // Normalisations - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/isothermal.hxx b/include/isothermal.hxx index f807a9157..1b385c5bd 100644 --- a/include/isothermal.hxx +++ b/include/isothermal.hxx @@ -30,7 +30,7 @@ private: /// - temperature /// - pressure (if density is set) /// - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/neutral_boundary.hxx b/include/neutral_boundary.hxx index 4954243d8..fda96dff2 100644 --- a/include/neutral_boundary.hxx +++ b/include/neutral_boundary.hxx @@ -53,7 +53,7 @@ private: /// - momentum [if set] Zero boundary /// - energy_source Adds wall losses /// - void transform(GuardedOptions& state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/neutral_full_velocity.hxx b/include/neutral_full_velocity.hxx index e6c3ac4bb..e5d1c4527 100644 --- a/include/neutral_full_velocity.hxx +++ b/include/neutral_full_velocity.hxx @@ -64,7 +64,7 @@ private: Field2D Vnpar; ///< Parallel flow velocity diagnostic /// Modify the given simulation state - void transform(GuardedOptions& state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/neutral_mixed.hxx b/include/neutral_mixed.hxx index 82fde5273..dd67eb0d5 100644 --- a/include/neutral_mixed.hxx +++ b/include/neutral_mixed.hxx @@ -81,7 +81,7 @@ private: Field3D ef_cond_perp_xlow, ef_cond_perp_ylow, ef_cond_par_ylow; /// Modify the given simulation state - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/neutral_parallel_diffusion.hxx b/include/neutral_parallel_diffusion.hxx index f5514b299..e796bbcf3 100644 --- a/include/neutral_parallel_diffusion.hxx +++ b/include/neutral_parallel_diffusion.hxx @@ -92,7 +92,7 @@ private: /// - density_source /// - energy_source /// - momentum_source [if velocity set] - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/noflow_boundary.hxx b/include/noflow_boundary.hxx index 844def7db..0f313c384 100644 --- a/include/noflow_boundary.hxx +++ b/include/noflow_boundary.hxx @@ -30,7 +30,7 @@ private: /// - pressure [Optional] /// - velocity [Optional] /// - momentum [Optional] - void transform(GuardedOptions& state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/polarisation_drift.hxx b/include/polarisation_drift.hxx index 79071efa6..638bf602b 100644 --- a/include/polarisation_drift.hxx +++ b/include/polarisation_drift.hxx @@ -71,7 +71,7 @@ private: /// - energy_source (if pressure set) /// - momentum_source (if momentum set) /// - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/quasineutral.hxx b/include/quasineutral.hxx index 21f074937..c5b15d6e0 100644 --- a/include/quasineutral.hxx +++ b/include/quasineutral.hxx @@ -44,7 +44,7 @@ private: /// - density /// - charge /// - AA - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/reaction.hxx b/include/reaction.hxx index 033634708..71ae91846 100644 --- a/include/reaction.hxx +++ b/include/reaction.hxx @@ -171,6 +171,6 @@ private: void zero_diagnostics(GuardedOptions state); - void transform(GuardedOptions& state) override final; + void transform_impl(GuardedOptions& state) override final; }; #endif diff --git a/include/recycling.hxx b/include/recycling.hxx index 54da4e69a..6dadbb376 100644 --- a/include/recycling.hxx +++ b/include/recycling.hxx @@ -78,7 +78,7 @@ private: /// - /// - density_source /// - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/relax_potential.hxx b/include/relax_potential.hxx index 47a13f95b..c24afbfaa 100644 --- a/include/relax_potential.hxx +++ b/include/relax_potential.hxx @@ -75,7 +75,7 @@ private: /// /// Note: Diamagnetic current calculated here, but could be moved /// to a component with the diamagnetic drift advection terms - void transform(GuardedOptions& state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/scale_timederivs.hxx b/include/scale_timederivs.hxx index 2603fa117..34855b567 100644 --- a/include/scale_timederivs.hxx +++ b/include/scale_timederivs.hxx @@ -13,17 +13,6 @@ struct ScaleTimeDerivs : public Component { ScaleTimeDerivs(std::string, Options&, Solver*) {} - auto* coord = bout::globals::mesh->getCoordinates(); - Field2D dl2 = coord->g_22 * SQ(coord->dy); - - // Scale by parallel heat conduction CFL timescale - auto Te = get(state["species"]["e"]["temperature"]); - Field3D dt = dl2 / pow(floor(Te, 1e-5), 5./2); - scaling = dt / max(dt, true); // Saved for output - - state["scale_timederivs"] = scaling; - } - void outputVars(Options& state) override { set_with_attrs( state["scale_timederivs"], scaling, @@ -38,11 +27,22 @@ private: /// /// - scale_timederivs /// - void transform(GuardedOptions &state) override { + void transform_impl(GuardedOptions& state) override { + + auto* coord = bout::globals::mesh->getCoordinates(); + Field2D dl2 = coord->g_22 * SQ(coord->dy); + + // Scale by parallel heat conduction CFL timescale + auto Te = get(state["species"]["e"]["temperature"]); + Field3D dt = dl2 / pow(floor(Te, 1e-5), 5. / 2); + scaling = dt / max(dt, true); // Saved for output + + state["scale_timederivs"].getWritable() = scaling; + } }; namespace { -RegisterComponent registercomponentscaletimederivs("scale_timederivs"); + RegisterComponent registercomponentscaletimederivs("scale_timederivs"); } #endif // SCALE_TIMEDERIVS_H diff --git a/include/set_temperature.hxx b/include/set_temperature.hxx index 4825e2667..c593ea695 100644 --- a/include/set_temperature.hxx +++ b/include/set_temperature.hxx @@ -73,14 +73,14 @@ private: /// - temperature /// - pressure (if density is set) /// - void transform(GuardedOptions& state) override { + void transform_impl(GuardedOptions& state) override { AUTO_TRACE(); // Get the temperature T = GET_NOBOUNDARY(Field3D, state["species"][temperature_from]["temperature"]); // Set temperature - auto& species = state["species"][name]; + auto species = state["species"][name]; set(species["temperature"], T); if (isSetFinalNoBoundary(species["density"])) { diff --git a/include/sheath_boundary.hxx b/include/sheath_boundary.hxx index 87face383..6cde28edb 100644 --- a/include/sheath_boundary.hxx +++ b/include/sheath_boundary.hxx @@ -87,7 +87,7 @@ private: /// Note that phi in the domain will not be set, so will be invalid data. /// /// - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/sheath_boundary_insulating.hxx b/include/sheath_boundary_insulating.hxx index c50b5faae..a29afa24c 100644 --- a/include/sheath_boundary_insulating.hxx +++ b/include/sheath_boundary_insulating.hxx @@ -65,7 +65,7 @@ private: /// Note that phi in the domain will not be set, so will be invalid data. /// /// - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/sheath_boundary_simple.hxx b/include/sheath_boundary_simple.hxx index 7dadb7ec9..fa28f610e 100644 --- a/include/sheath_boundary_simple.hxx +++ b/include/sheath_boundary_simple.hxx @@ -100,7 +100,7 @@ private: /// Note that phi in the domain will not be set, so will be invalid data. /// /// - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/sheath_closure.hxx b/include/sheath_closure.hxx index e4246db2f..059dbf5b1 100644 --- a/include/sheath_closure.hxx +++ b/include/sheath_closure.hxx @@ -45,7 +45,7 @@ private: /// - fields /// - DivJdia Divergence of current /// - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/simple_conduction.hxx b/include/simple_conduction.hxx index ae9b3fc1e..92d311508 100644 --- a/include/simple_conduction.hxx +++ b/include/simple_conduction.hxx @@ -66,8 +66,8 @@ private: bool boundary_flux; ///< Allow flux through sheath boundaries? - void transform(GuardedOptions& state) override { - auto& species = state["species"][name]; + void transform_impl(GuardedOptions& state) override { + auto species = state["species"][name]; // Species time-evolving temperature Field3D T = GET_NOBOUNDARY(Field3D, species["temperature"]); diff --git a/include/simple_pump.hxx b/include/simple_pump.hxx index bb6fc3287..5946ea8d9 100644 --- a/include/simple_pump.hxx +++ b/include/simple_pump.hxx @@ -59,7 +59,7 @@ struct SimplePump : public Component { BoutReal residence_time; bool diagnose; - void transform(GuardedOptions& state) override { + void transform_impl(GuardedOptions& state) override { Field3D species_density = getNoBoundary(state["species"][name]["density"]); diff --git a/include/snb_conduction.hxx b/include/snb_conduction.hxx index 0760991a3..8126e28e1 100644 --- a/include/snb_conduction.hxx +++ b/include/snb_conduction.hxx @@ -73,7 +73,7 @@ private: /// - species /// - e /// - energy_source - void transform(GuardedOptions& state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/solkit_hydrogen_charge_exchange.hxx b/include/solkit_hydrogen_charge_exchange.hxx index ead37d901..bdbcc89bc 100644 --- a/include/solkit_hydrogen_charge_exchange.hxx +++ b/include/solkit_hydrogen_charge_exchange.hxx @@ -47,7 +47,7 @@ struct SOLKITHydrogenChargeExchangeIsotope : public SOLKITHydrogenChargeExchange : SOLKITHydrogenChargeExchange(name, alloptions, solver) {} private: - void transform(GuardedOptions& state) override { + void transform_impl(GuardedOptions& state) override { calculate_rates(state["species"][{Isotope}], // e.g. "h" state["species"][{Isotope, '+'}]); // e.g. "d+" } diff --git a/include/solkit_neutral_parallel_diffusion.hxx b/include/solkit_neutral_parallel_diffusion.hxx index b80299256..a2979fb62 100644 --- a/include/solkit_neutral_parallel_diffusion.hxx +++ b/include/solkit_neutral_parallel_diffusion.hxx @@ -47,7 +47,7 @@ private: /// - species /// - /// - density_source - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/sound_speed.hxx b/include/sound_speed.hxx index bf99331a0..a126153ce 100644 --- a/include/sound_speed.hxx +++ b/include/sound_speed.hxx @@ -60,7 +60,7 @@ private: /// - AA // Atomic mass /// - pressure /// - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/temperature_feedback.hxx b/include/temperature_feedback.hxx index e9811190e..0bd8764eb 100644 --- a/include/temperature_feedback.hxx +++ b/include/temperature_feedback.hxx @@ -190,7 +190,7 @@ private: /// - /// - temperature_source /// - void transform(GuardedOptions& state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/transform.hxx b/include/transform.hxx index a586d5e98..ff302f864 100644 --- a/include/transform.hxx +++ b/include/transform.hxx @@ -12,7 +12,7 @@ struct Transform : public Component { private: std::map transforms; - void transform(GuardedOptions& state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/upstream_density_feedback.hxx b/include/upstream_density_feedback.hxx index c7519099a..d73326d01 100644 --- a/include/upstream_density_feedback.hxx +++ b/include/upstream_density_feedback.hxx @@ -160,7 +160,7 @@ private: /// - /// - density_source /// - void transform(GuardedOptions& state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/vorticity.hxx b/include/vorticity.hxx index 0a60053a2..da591d2a2 100644 --- a/include/vorticity.hxx +++ b/include/vorticity.hxx @@ -144,7 +144,7 @@ private: /// /// Note: Diamagnetic current calculated here, but could be moved /// to a component with the diamagnetic drift advection terms - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/zero_current.hxx b/include/zero_current.hxx index 68234aca5..5a00eb758 100644 --- a/include/zero_current.hxx +++ b/include/zero_current.hxx @@ -48,7 +48,7 @@ private: /// - /// - velocity /// - void transform(GuardedOptions &state) override; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/src/adas_reaction.cxx b/src/adas_reaction.cxx index 1af741dac..dbfbb9c4b 100644 --- a/src/adas_reaction.cxx +++ b/src/adas_reaction.cxx @@ -112,7 +112,7 @@ BoutReal OpenADASRateCoefficient::evaluate(BoutReal T, BoutReal n) { return pow(10., eval_log_coef); } -void OpenADAS::calculate_rates(Options& electron, Options& from_ion, Options& to_ion) { +void OpenADAS::calculate_rates(GuardedOptions electron, GuardedOptions from_ion, GuardedOptions to_ion) { AUTO_TRACE(); Field3D Ne = GET_VALUE(Field3D, electron["density"]); @@ -173,9 +173,9 @@ void OpenADAS::calculate_rates(Options& electron, Options& from_ion, Options& to subtract(electron["energy_source"], energy_loss); } -void OpenADASChargeExchange::calculate_rates(Options& electron, Options& from_A, - Options& from_B, Options& to_A, - Options& to_B) { +void OpenADASChargeExchange::calculate_rates(GuardedOptions electron, GuardedOptions from_A, + GuardedOptions from_B, GuardedOptions to_A, + GuardedOptions to_B) { AUTO_TRACE(); // Check that the reaction conserves mass and charge diff --git a/src/anomalous_diffusion.cxx b/src/anomalous_diffusion.cxx index 29fbe1686..28b6cf75a 100644 --- a/src/anomalous_diffusion.cxx +++ b/src/anomalous_diffusion.cxx @@ -52,7 +52,7 @@ AnomalousDiffusion::AnomalousDiffusion(std::string name, Options& alloptions, So .withDefault(false); } -void AnomalousDiffusion::transform(GuardedOptions& state) { +void AnomalousDiffusion::transform_impl(GuardedOptions& state) { AUTO_TRACE(); GuardedOptions species = state["species"][name]; diff --git a/src/binormal_stpm.cxx b/src/binormal_stpm.cxx index e29bbe93b..620bd5f7e 100644 --- a/src/binormal_stpm.cxx +++ b/src/binormal_stpm.cxx @@ -47,7 +47,7 @@ BinormalSTPM::BinormalSTPM(std::string name, Options& alloptions, .withDefault(false); } -void BinormalSTPM::transform(GuardedOptions& state) { +void BinormalSTPM::transform_impl(GuardedOptions& state) { AUTO_TRACE(); GuardedOptions allspecies = state["species"]; // Loop through all species diff --git a/src/braginskii_collisions.cxx b/src/braginskii_collisions.cxx index 35b09a709..794c9f002 100644 --- a/src/braginskii_collisions.cxx +++ b/src/braginskii_collisions.cxx @@ -104,7 +104,7 @@ void BraginskiiCollisions::collide(Options & species1, Options & species2, const } } -void BraginskiiCollisions::transform(GuardedOptions& state) { +void BraginskiiCollisions::transform_impl(GuardedOptions& state) { AUTO_TRACE(); GuardedOptions allspecies = state["species"]; diff --git a/src/braginskii_electron_viscosity.cxx b/src/braginskii_electron_viscosity.cxx index f52863914..9a5f584d7 100644 --- a/src/braginskii_electron_viscosity.cxx +++ b/src/braginskii_electron_viscosity.cxx @@ -29,7 +29,7 @@ BraginskiiElectronViscosity::BraginskiiElectronViscosity(const std::string& name diagnose = options["diagnose"].doc("Output diagnostics?").withDefault(false); } -void BraginskiiElectronViscosity::transform(GuardedOptions& state) { +void BraginskiiElectronViscosity::transform_impl(GuardedOptions& state) { AUTO_TRACE(); GuardedOptions species = state["species"]["e"]; diff --git a/src/braginskii_ion_viscosity.cxx b/src/braginskii_ion_viscosity.cxx index 83e6223c1..672704198 100644 --- a/src/braginskii_ion_viscosity.cxx +++ b/src/braginskii_ion_viscosity.cxx @@ -109,7 +109,7 @@ BraginskiiIonViscosity::BraginskiiIonViscosity(const std::string& name, } } -void BraginskiiIonViscosity::transform(GuardedOptions &state) { +void BraginskiiIonViscosity::transform_impl(GuardedOptions& state) { AUTO_TRACE(); GuardedOptions allspecies = state["species"]; diff --git a/src/braginskii_thermal_force.cxx b/src/braginskii_thermal_force.cxx index 5c6a49a75..62108dd98 100644 --- a/src/braginskii_thermal_force.cxx +++ b/src/braginskii_thermal_force.cxx @@ -14,7 +14,7 @@ #include "../include/braginskii_thermal_force.hxx" #include "../include/component.hxx" -void BraginskiiThermalForce::transform(GuardedOptions& state) { +void BraginskiiThermalForce::transform_impl(GuardedOptions& state) { AUTO_TRACE(); GuardedOptions allspecies = state["species"]; diff --git a/src/classical_diffusion.cxx b/src/classical_diffusion.cxx index 2244516b6..a3aacbf62 100644 --- a/src/classical_diffusion.cxx +++ b/src/classical_diffusion.cxx @@ -12,7 +12,7 @@ ClassicalDiffusion::ClassicalDiffusion(std::string name, Options& alloptions, So custom_D = options["custom_D"].doc("Custom diffusion coefficient override. -1: Off, calculate D normally").withDefault(-1); } -void ClassicalDiffusion::transform(GuardedOptions &state) { +void ClassicalDiffusion::transform_impl(GuardedOptions& state) { AUTO_TRACE(); GuardedOptions allspecies = state["species"]; diff --git a/src/component.cxx b/src/component.cxx index 37b6a7764..d2668e2c0 100644 --- a/src/component.cxx +++ b/src/component.cxx @@ -11,7 +11,7 @@ std::unique_ptr Component::create(const std::string &type, void Component::transform(Options& state) { GuardedOptions guarded(&state, &state_variable_access); - transform(guarded); + transform_impl(guarded); for (auto& [varname, region] : guarded.unreadItems()) { output_warn.write("Did not read from state variable {} in region(s) {}", varname, Permissions::regionNames(region)); diff --git a/src/detachment_controller.cxx b/src/detachment_controller.cxx index 04b607497..a68c927ff 100644 --- a/src/detachment_controller.cxx +++ b/src/detachment_controller.cxx @@ -31,7 +31,7 @@ BoutReal calculateGradient(const std::vector& x, const std::vector(false); } -void EvolveDensity::transform(GuardedOptions& state) { +void EvolveDensity::transform_impl(GuardedOptions& state) { AUTO_TRACE(); if (evolve_log) { diff --git a/src/evolve_energy.cxx b/src/evolve_energy.cxx index 68afaa25a..e380c9557 100644 --- a/src/evolve_energy.cxx +++ b/src/evolve_energy.cxx @@ -113,7 +113,7 @@ EvolveEnergy::EvolveEnergy(std::string name, Options& alloptions, Solver* solver .withDefault(true); } -void EvolveEnergy::transform(GuardedOptions& state) { +void EvolveEnergy::transform_impl(GuardedOptions& state) { AUTO_TRACE(); if (evolve_log) { diff --git a/src/evolve_momentum.cxx b/src/evolve_momentum.cxx index 2cfb1b332..44db2dd6d 100644 --- a/src/evolve_momentum.cxx +++ b/src/evolve_momentum.cxx @@ -60,7 +60,7 @@ EvolveMomentum::EvolveMomentum(std::string name, Options &alloptions, Solver *so NV_err = 0.0; } -void EvolveMomentum::transform(GuardedOptions &state) { +void EvolveMomentum::transform_impl(GuardedOptions& state) { AUTO_TRACE(); mesh->communicate(NV); diff --git a/src/evolve_pressure.cxx b/src/evolve_pressure.cxx index ecfb165da..40573fe6f 100644 --- a/src/evolve_pressure.cxx +++ b/src/evolve_pressure.cxx @@ -164,7 +164,7 @@ EvolvePressure::EvolvePressure(std::string name, Options& alloptions, Solver* so .withDefault(true); } -void EvolvePressure::transform(GuardedOptions& state) { +void EvolvePressure::transform_impl(GuardedOptions& state) { AUTO_TRACE(); if (evolve_log) { diff --git a/src/fixed_fraction_ions.cxx b/src/fixed_fraction_ions.cxx index 4a572e24d..e1c2bebf7 100644 --- a/src/fixed_fraction_ions.cxx +++ b/src/fixed_fraction_ions.cxx @@ -28,7 +28,7 @@ FixedFractionIons::FixedFractionIons(std::string name, Options &alloptions, } } -void FixedFractionIons::transform(GuardedOptions &state) { +void FixedFractionIons::transform_impl(GuardedOptions& state) { AUTO_TRACE(); // Electron density diff --git a/src/guarded_options.cxx b/src/guarded_options.cxx index 2ef202404..2b74c1f51 100644 --- a/src/guarded_options.cxx +++ b/src/guarded_options.cxx @@ -10,6 +10,22 @@ GuardedOptions GuardedOptions::operator[](const std::string& name) { unwritten_variables); } +const GuardedOptions GuardedOptions::operator[](const std::string& name) const { + if (options == nullptr) + throw BoutException( + "Trying to access GuardedOptions when underlying options are nullptr."); + return GuardedOptions(&(*options)[name], permissions, unread_variables, + unwritten_variables); +} + +std::map GuardedOptions::getChildren() { + std::map result; + for (const auto& [varname, _] : options->getChildren()) { + result.insert({varname, (*this)[varname]}); + } + return result; +} + void updateAccessRecords(std::map& records, const std::string& name, Permissions::Regions region) { if (records.count(name) > 0) { diff --git a/src/ionisation.cxx b/src/ionisation.cxx index 6d722efdd..7997b12e6 100644 --- a/src/ionisation.cxx +++ b/src/ionisation.cxx @@ -38,7 +38,7 @@ Ionisation::Ionisation(std::string name, Options &alloptions, Solver *) { Eionize /= Tnorm; } -void Ionisation::transform(GuardedOptions &state) { +void Ionisation::transform_impl(GuardedOptions& state) { // Get neutral atom properties GuardedOptions hydrogen = state["species"]["h"]; Field3D Nn = get(hydrogen["density"]); diff --git a/src/isothermal.cxx b/src/isothermal.cxx index 10e9b6ba7..0d68dac77 100644 --- a/src/isothermal.cxx +++ b/src/isothermal.cxx @@ -18,7 +18,7 @@ Isothermal::Isothermal(std::string name, Options &alloptions, .withDefault(false); } -void Isothermal::transform(GuardedOptions &state) { +void Isothermal::transform_impl(GuardedOptions& state) { AUTO_TRACE(); GuardedOptions species = state["species"][name]; diff --git a/src/neutral_boundary.cxx b/src/neutral_boundary.cxx index a50a81428..e9bac2f2e 100644 --- a/src/neutral_boundary.cxx +++ b/src/neutral_boundary.cxx @@ -51,7 +51,7 @@ NeutralBoundary::NeutralBoundary(std::string name, Options& alloptions, .withDefault(0.8); } -void NeutralBoundary::transform(GuardedOptions& state) { +void NeutralBoundary::transform_impl(GuardedOptions& state) { AUTO_TRACE(); auto species = state["species"][name]; const BoutReal AA = get(species["AA"]); diff --git a/src/neutral_full_velocity.cxx b/src/neutral_full_velocity.cxx index 72ad2a78d..4d079b944 100644 --- a/src/neutral_full_velocity.cxx +++ b/src/neutral_full_velocity.cxx @@ -191,7 +191,7 @@ NeutralFullVelocity::NeutralFullVelocity(const std::string& name, Options& allop } /// Modify the given simulation state -void NeutralFullVelocity::transform(GuardedOptions& state) { +void NeutralFullVelocity::transform_impl(GuardedOptions& state) { AUTO_TRACE(); mesh->communicate(Nn2D, Vn2D, Pn2D); diff --git a/src/neutral_mixed.cxx b/src/neutral_mixed.cxx index b6ac57000..c9d54c87d 100644 --- a/src/neutral_mixed.cxx +++ b/src/neutral_mixed.cxx @@ -171,7 +171,7 @@ NeutralMixed::NeutralMixed(const std::string& name, Options& alloptions, Solver* DnnNVn.setBoundary(std::string("Dnn") + name); } -void NeutralMixed::transform(GuardedOptions& state) { +void NeutralMixed::transform_impl(GuardedOptions& state) { AUTO_TRACE(); mesh->communicate(Nn, Pn, NVn); diff --git a/src/neutral_parallel_diffusion.cxx b/src/neutral_parallel_diffusion.cxx index dbebd4323..950da6559 100644 --- a/src/neutral_parallel_diffusion.cxx +++ b/src/neutral_parallel_diffusion.cxx @@ -6,7 +6,7 @@ using bout::globals::mesh; -void NeutralParallelDiffusion::transform(GuardedOptions& state) { +void NeutralParallelDiffusion::transform_impl(GuardedOptions& state) { AUTO_TRACE(); GuardedOptions allspecies = state["species"]; for (auto& kv : allspecies.getChildren()) { diff --git a/src/noflow_boundary.cxx b/src/noflow_boundary.cxx index a668c8185..39be328a4 100644 --- a/src/noflow_boundary.cxx +++ b/src/noflow_boundary.cxx @@ -11,7 +11,7 @@ Ind3D indexAt(const Field3D& f, int x, int y, int z) { } } -void NoFlowBoundary::transform(GuardedOptions& state) { +void NoFlowBoundary::transform_impl(GuardedOptions& state) { AUTO_TRACE(); // Make sure that the state has been set for this species diff --git a/src/polarisation_drift.cxx b/src/polarisation_drift.cxx index c4629aa19..fcebdcfde 100644 --- a/src/polarisation_drift.cxx +++ b/src/polarisation_drift.cxx @@ -59,7 +59,7 @@ PolarisationDrift::PolarisationDrift(std::string name, .withDefault(false); } -void PolarisationDrift::transform(GuardedOptions &state) { +void PolarisationDrift::transform_impl(GuardedOptions& state) { AUTO_TRACE(); // Iterate through all subsections diff --git a/src/quasineutral.cxx b/src/quasineutral.cxx index 5cf121b29..cb8034e8a 100644 --- a/src/quasineutral.cxx +++ b/src/quasineutral.cxx @@ -15,7 +15,7 @@ Quasineutral::Quasineutral(std::string name, Options &alloptions, ASSERT0(charge != 0.0); } -void Quasineutral::transform(GuardedOptions &state) { +void Quasineutral::transform_impl(GuardedOptions& state) { AUTO_TRACE(); // Iterate through all subsections GuardedOptions allspecies = state["species"]; diff --git a/src/reaction.cxx b/src/reaction.cxx index 3418ed2a4..d25363e41 100644 --- a/src/reaction.cxx +++ b/src/reaction.cxx @@ -122,7 +122,7 @@ void Reaction::outputVars(Options& state) { * * @param state */ -void Reaction::transform(GuardedOptions& state) { +void Reaction::transform_impl(GuardedOptions& state) { Field3D momentum_exchange, energy_exchange, energy_loss; zero_diagnostics(state); diff --git a/src/recycling.cxx b/src/recycling.cxx index c3acebdba..ddcc1a25f 100644 --- a/src/recycling.cxx +++ b/src/recycling.cxx @@ -148,7 +148,7 @@ Recycling::Recycling(std::string name, Options& alloptions, Solver*) { } } -void Recycling::transform(GuardedOptions& state) { +void Recycling::transform_impl(GuardedOptions& state) { AUTO_TRACE(); // Get metric tensor components diff --git a/src/relax_potential.cxx b/src/relax_potential.cxx index ceec752a7..f66e99beb 100644 --- a/src/relax_potential.cxx +++ b/src/relax_potential.cxx @@ -78,7 +78,7 @@ RelaxPotential::RelaxPotential(std::string name, Options& alloptions, Solver* so Bsq = SQ(coord->Bxy); } -void RelaxPotential::transform(GuardedOptions& state) { +void RelaxPotential::transform_impl(GuardedOptions& state) { AUTO_TRACE(); // Scale potential diff --git a/src/sheath_boundary.cxx b/src/sheath_boundary.cxx index e0eb84d02..2069e1867 100644 --- a/src/sheath_boundary.cxx +++ b/src/sheath_boundary.cxx @@ -96,7 +96,7 @@ SheathBoundary::SheathBoundary(std::string name, Options& alloptions, Solver*) { .withDefault(true); } -void SheathBoundary::transform(GuardedOptions& state) { +void SheathBoundary::transform_impl(GuardedOptions& state) { AUTO_TRACE(); GuardedOptions allspecies = state["species"]; diff --git a/src/sheath_boundary_insulating.cxx b/src/sheath_boundary_insulating.cxx index 26f5c4bd8..10e16efc6 100644 --- a/src/sheath_boundary_insulating.cxx +++ b/src/sheath_boundary_insulating.cxx @@ -79,7 +79,7 @@ SheathBoundaryInsulating::SheathBoundaryInsulating(std::string name, Options& al .withDefault(3.5); } -void SheathBoundaryInsulating::transform(GuardedOptions& state) { +void SheathBoundaryInsulating::transform_impl(GuardedOptions& state) { AUTO_TRACE(); GuardedOptions allspecies = state["species"]; diff --git a/src/sheath_boundary_simple.cxx b/src/sheath_boundary_simple.cxx index 36ed369b4..3fcc7f340 100644 --- a/src/sheath_boundary_simple.cxx +++ b/src/sheath_boundary_simple.cxx @@ -133,7 +133,7 @@ SheathBoundarySimple::SheathBoundarySimple(std::string name, Options& alloptions } -void SheathBoundarySimple::transform(GuardedOptions& state) { +void SheathBoundarySimple::transform_impl(GuardedOptions& state) { AUTO_TRACE(); GuardedOptions allspecies = state["species"]; diff --git a/src/sheath_closure.cxx b/src/sheath_closure.cxx index 8f212aa75..712e9d120 100644 --- a/src/sheath_closure.cxx +++ b/src/sheath_closure.cxx @@ -31,7 +31,7 @@ SheathClosure::SheathClosure(std::string name, Options &alloptions, Solver *) { output.write("\tL_par = {:e} (normalised)\n", L_par); } -void SheathClosure::transform(GuardedOptions &state) { +void SheathClosure::transform_impl(GuardedOptions& state) { AUTO_TRACE(); // Get electrostatic potential diff --git a/src/snb_conduction.cxx b/src/snb_conduction.cxx index ff143b387..2820aca3d 100644 --- a/src/snb_conduction.cxx +++ b/src/snb_conduction.cxx @@ -4,7 +4,7 @@ #include using bout::globals::mesh; -void SNBConduction::transform(GuardedOptions& state) { +void SNBConduction::transform_impl(GuardedOptions& state) { auto units = state["units"]; const auto rho_s0 = get(units["meters"]); const auto Tnorm = get(units["eV"]); diff --git a/src/solkit_neutral_parallel_diffusion.cxx b/src/solkit_neutral_parallel_diffusion.cxx index 5beac62fa..14e322112 100644 --- a/src/solkit_neutral_parallel_diffusion.cxx +++ b/src/solkit_neutral_parallel_diffusion.cxx @@ -6,7 +6,7 @@ using bout::globals::mesh; -void SOLKITNeutralParallelDiffusion::transform(GuardedOptions& state) { +void SOLKITNeutralParallelDiffusion::transform_impl(GuardedOptions& state) { AUTO_TRACE(); GuardedOptions allspecies = state["species"]; for (auto& kv : allspecies.getChildren()) { diff --git a/src/sound_speed.cxx b/src/sound_speed.cxx index 6bf13d3de..ed9bbb6b2 100644 --- a/src/sound_speed.cxx +++ b/src/sound_speed.cxx @@ -3,7 +3,7 @@ #include "../include/hermes_utils.hxx" #include -void SoundSpeed::transform(GuardedOptions &state) { +void SoundSpeed::transform_impl(GuardedOptions& state) { Field3D total_pressure = 0.0; Field3D total_density = 0.0; diff --git a/src/temperature_feedback.cxx b/src/temperature_feedback.cxx index b642509fa..a868e231e 100644 --- a/src/temperature_feedback.cxx +++ b/src/temperature_feedback.cxx @@ -3,7 +3,7 @@ #include using bout::globals::mesh; -void TemperatureFeedback::transform(GuardedOptions& state) { +void TemperatureFeedback::transform_impl(GuardedOptions& state) { GuardedOptions species = state["species"][name]; // Doesn't need all boundaries to be set diff --git a/src/transform.cxx b/src/transform.cxx index b560f96b3..1bc2849e0 100644 --- a/src/transform.cxx +++ b/src/transform.cxx @@ -25,7 +25,7 @@ Transform::Transform(std::string name, Options& alloptions, Solver* UNUSED(solve } } -void Transform::transform(GuardedOptions& state) { +void Transform::transform_impl(GuardedOptions& state) { for (const auto& lr : transforms) { state[lr.first].getWritable() = state[lr.second].get().copy(); } diff --git a/src/upstream_density_feedback.cxx b/src/upstream_density_feedback.cxx index b44b1cd09..d5ff3c809 100644 --- a/src/upstream_density_feedback.cxx +++ b/src/upstream_density_feedback.cxx @@ -3,7 +3,7 @@ #include using bout::globals::mesh; -void UpstreamDensityFeedback::transform(GuardedOptions& state) { +void UpstreamDensityFeedback::transform_impl(GuardedOptions& state) { GuardedOptions species = state["species"][name]; // Doesn't need all boundaries to be set diff --git a/src/vorticity.cxx b/src/vorticity.cxx index cb4ff0f31..9e8f3e922 100644 --- a/src/vorticity.cxx +++ b/src/vorticity.cxx @@ -207,7 +207,7 @@ Vorticity::Vorticity(std::string name, Options& alloptions, Solver* solver) { .withDefault(false); } -void Vorticity::transform(GuardedOptions& state) { +void Vorticity::transform_impl(GuardedOptions& state) { AUTO_TRACE(); phi.name = "phi"; diff --git a/src/zero_current.cxx b/src/zero_current.cxx index 14005d208..15f09347d 100644 --- a/src/zero_current.cxx +++ b/src/zero_current.cxx @@ -13,7 +13,7 @@ ZeroCurrent::ZeroCurrent(std::string name, Options& alloptions, Solver*) ASSERT0(charge != 0.0); } -void ZeroCurrent::transform(GuardedOptions &state) { +void ZeroCurrent::transform_impl(GuardedOptions& state) { AUTO_TRACE(); // Current due to other species diff --git a/tests/unit/test_component.cxx b/tests/unit/test_component.cxx index 36520c005..fce7b6c27 100644 --- a/tests/unit/test_component.cxx +++ b/tests/unit/test_component.cxx @@ -9,7 +9,7 @@ namespace { struct TestComponent : public Component { TestComponent(const std::string&, Options&, Solver *) {} private: - void transform(GuardedOptions &state) override { state["answer"].getWritable() = 42; } + void transform_impl(GuardedOptions& state) override { state["answer"].getWritable() = 42; } }; RegisterComponent registertestcomponent("testcomponent"); diff --git a/tests/unit/test_component_scheduler.cxx b/tests/unit/test_component_scheduler.cxx index 11865d758..af1c93879 100644 --- a/tests/unit/test_component_scheduler.cxx +++ b/tests/unit/test_component_scheduler.cxx @@ -6,7 +6,7 @@ namespace { struct TestComponent : public Component { TestComponent(const std::string&, Options&, Solver *) {} private: - void transform(GuardedOptions &state) override { state["answer"].getWritable() = 42; } + void transform_impl(GuardedOptions& state) override { state["answer"].getWritable() = 42; } }; @@ -14,7 +14,7 @@ struct TestMultiply : public Component { TestMultiply(const std::string&, Options&, Solver *) {} private: - void transform(GuardedOptions &state) override { + void transform_impl(GuardedOptions& state) override { // Note: Using set<>() and get<>() for quicker access, avoiding printing // getNonFinal needs to be used because we set the value afterwards set(state["answer"], diff --git a/tests/unit/test_guarded_options.cxx b/tests/unit/test_guarded_options.cxx index 75b737f5d..ffe54b13c 100644 --- a/tests/unit/test_guarded_options.cxx +++ b/tests/unit/test_guarded_options.cxx @@ -188,3 +188,46 @@ TEST_F(GuardedOptionsTests, TestNullPermissions) { EXPECT_THROW(sub_opt.get(Permissions::Interior), BoutException); EXPECT_THROW(sub_opt.getWritable(Permissions::Interior), BoutException); } + +TEST_F(GuardedOptionsTests, TestGetChildren) { + std::map guarded_children = guarded_opts["species"].getChildren(); + EXPECT_EQ(guarded_children.size(), 2); + EXPECT_EQ(guarded_children.count("he"), 1); + EXPECT_EQ(guarded_children.count("d"), 1); + EXPECT_EQ(&(guarded_children["d"].get()), &(opts["species"]["d"])); + // We do not have access to the whole "he" section + EXPECT_THROW(guarded_children["he"].get(), BoutException); +} + +TEST_F(GuardedOptionsTests, TestIsThisSection) { + EXPECT_TRUE(guarded_opts.isSection()); + EXPECT_TRUE(guarded_opts["species:d:collision_frequencies"].isSection()); + EXPECT_FALSE(guarded_opts["species:he:temperature"].isSection()); + EXPECT_TRUE(guarded_opts["species"]["he"]["collision_frequency"].isSection()); +} + +TEST_F(GuardedOptionsTests, TestIsChildSection) { + EXPECT_TRUE(guarded_opts.isSection("")); + // Unforunately Operator::isSection does not support full paths + EXPECT_FALSE(guarded_opts.isSection("species:he")); + EXPECT_FALSE(guarded_opts["species"]["he"].isSection("temperature")); + EXPECT_FALSE(guarded_opts["species:he"].isSection("pressure")); + EXPECT_TRUE(guarded_opts["species:d"].isSection("collision_frequencies")); +} + +TEST_F(GuardedOptionsTests, TestIsThisSet) { + EXPECT_TRUE(guarded_opts["species"]["he"]["temperature"].isSet()); + EXPECT_TRUE(guarded_opts["species:he:pressure"].isSet()); + EXPECT_TRUE(guarded_opts["species"]["he"]["velocity"].isSet()); + EXPECT_FALSE(guarded_opts["species"]["he"]["collision_frequency"].isSet()); + EXPECT_FALSE(guarded_opts["unset"].isSet()); +} + +TEST_F(GuardedOptionsTests, TestIsChildSet) { + EXPECT_FALSE(guarded_opts["species:he:pressure"].isSet("test")); + EXPECT_FALSE(guarded_opts["species:he"].isSet("collision_frequency")); + EXPECT_TRUE(guarded_opts["species"]["he"].isSet("temperature")); + // Unforunately Operator::isSet does not support full paths + EXPECT_FALSE(guarded_opts.isSet("species:he:temperature")); + EXPECT_TRUE(guarded_opts["species"]["d"]["collision_frequencies"].isSet("d_d_coll")); +} From b0b444e48f73d10e08063a1956eefccd59b53c35 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Wed, 5 Nov 2025 14:19:35 +0000 Subject: [PATCH 06/63] Reverted a bunch of uses of IS_SET() --- src/amjuel_reaction.cxx | 2 +- src/anomalous_diffusion.cxx | 4 ++-- src/binormal_stpm.cxx | 6 +++--- src/braginskii_collisions.cxx | 14 +++++++------- src/classical_diffusion.cxx | 6 ++---- src/diamagnetic_drift.cxx | 2 +- src/electromagnetic.cxx | 4 ++-- src/neutral_boundary.cxx | 2 +- src/neutral_parallel_diffusion.cxx | 2 +- src/noflow_boundary.cxx | 4 ++-- src/polarisation_drift.cxx | 10 +++++----- src/quasineutral.cxx | 4 ++-- src/recycling.cxx | 10 +++++----- src/sheath_boundary.cxx | 14 +++++++------- src/sheath_boundary_insulating.cxx | 14 +++++++------- src/sheath_boundary_simple.cxx | 22 +++++++++++----------- src/sheath_closure.cxx | 2 +- src/solkit_neutral_parallel_diffusion.cxx | 4 ++-- src/sound_speed.cxx | 8 ++++---- src/vorticity.cxx | 8 ++++---- src/zero_current.cxx | 4 ++-- 21 files changed, 72 insertions(+), 74 deletions(-) diff --git a/src/amjuel_reaction.cxx b/src/amjuel_reaction.cxx index f0fe74b98..bbb9bff9e 100644 --- a/src/amjuel_reaction.cxx +++ b/src/amjuel_reaction.cxx @@ -120,7 +120,7 @@ void AmjuelReaction::transform_additional(GuardedOptions& state, Field3D& reacti Field3D T_e = get(electron["temperature"]); const int e_pop_change = this->parser->get_stoich().at("e"); if (e_pop_change != 0) { - if (IS_SET(electron["velocity"])) { + if (electron.isSet("velocity")) { // Transfer of electron kinetic to thermal energy due to density source // For ionisation: // Electrons with zero average velocity are created, diluting the kinetic energy. diff --git a/src/anomalous_diffusion.cxx b/src/anomalous_diffusion.cxx index 28b6cf75a..c01233629 100644 --- a/src/anomalous_diffusion.cxx +++ b/src/anomalous_diffusion.cxx @@ -63,13 +63,13 @@ void AnomalousDiffusion::transform_impl(GuardedOptions& state) { const Field3D N = GET_NOBOUNDARY(Field3D, species["density"]); Field2D N2D = DC(N); - const Field3D T = IS_SET(species["temperature"]) + const Field3D T = species.isSet("temperature") ? GET_NOBOUNDARY(Field3D, species["temperature"]) : 0.0; Field2D T2D = DC(T); const Field3D V = - IS_SET(species["velocity"]) ? GET_NOBOUNDARY(Field3D, species["velocity"]) : 0.0; + species.isSet("velocity") ? GET_NOBOUNDARY(Field3D, species["velocity"]) : 0.0; Field2D V2D = DC(V); if (!anomalous_sheath_flux) { diff --git a/src/binormal_stpm.cxx b/src/binormal_stpm.cxx index 620bd5f7e..f3f67463f 100644 --- a/src/binormal_stpm.cxx +++ b/src/binormal_stpm.cxx @@ -57,13 +57,13 @@ void BinormalSTPM::transform_impl(GuardedOptions& state) { GuardedOptions species = allspecies[species_name]; auto AA = get(species["AA"]); - const Field3D N = IS_SET(species["density"]) + const Field3D N = species.isSet("density") ? GET_NOBOUNDARY(Field3D, species["density"]) : 0.0; - const Field3D T = IS_SET(species["temperature"]) + const Field3D T = species.isSet("temperature") ? GET_NOBOUNDARY(Field3D, species["temperature"]) : 0.0; - const Field3D NV = IS_SET(species["momentum"]) + const Field3D NV = species.isSet("momentum") ? GET_NOBOUNDARY(Field3D, species["momentum"]) : 0.0; diff --git a/src/braginskii_collisions.cxx b/src/braginskii_collisions.cxx index 794c9f002..1d68bbbf1 100644 --- a/src/braginskii_collisions.cxx +++ b/src/braginskii_collisions.cxx @@ -153,7 +153,7 @@ void BraginskiiCollisions::transform_impl(GuardedOptions& state) { GuardedOptions species = allspecies[kv.first]; // Note: Need non-const - if (IS_SET(species["charge"]) and (get(species["charge"]) > 0.0)) { + if (species.isSet("charge") and (get(species["charge"]) > 0.0)) { //////////////////////////////////// // electron-positive ion collisions @@ -210,7 +210,7 @@ void BraginskiiCollisions::transform_impl(GuardedOptions& state) { collide(electrons.getWritable(), species.getWritable(), nu_ei / Omega_ci, mom_coeff); - } else if (IS_SET(species["charge"]) and (get(species["charge"]) < 0.0)) { + } else if (species.isSet("charge") and (get(species["charge"]) < 0.0)) { //////////////////////////////////// // electron-negative ion collisions @@ -267,7 +267,7 @@ void BraginskiiCollisions::transform_impl(GuardedOptions& state) { // If temperature isn't set, assume zero. in eV const Field3D temperature1 = - IS_SET(species1["temperature"]) + species1.isSet("temperature") ? GET_NOBOUNDARY(Field3D, species1["temperature"]) * Tnorm : 0.0; @@ -276,7 +276,7 @@ void BraginskiiCollisions::transform_impl(GuardedOptions& state) { const BoutReal AA1 = get(species1["AA"]); const BoutReal mass1 = AA1 * SI::Mp; // in Kg - if (IS_SET(species1["charge"]) and (get(species1["charge"]) != 0.0)) { + if (species1.isSet("charge") and (get(species1["charge"]) != 0.0)) { // Charged species const BoutReal Z1 = get(species1["charge"]); const BoutReal charge1 = Z1 * SI::qe; // in Coulombs @@ -295,7 +295,7 @@ void BraginskiiCollisions::transform_impl(GuardedOptions& state) { // If temperature isn't set, assume zero. in eV const Field3D temperature2 = - IS_SET(species2["temperature"]) + species2.isSet("temperature") ? GET_NOBOUNDARY(Field3D, species2["temperature"]) * Tnorm : 0.0; @@ -304,7 +304,7 @@ void BraginskiiCollisions::transform_impl(GuardedOptions& state) { const BoutReal AA2 = get(species2["AA"]); const BoutReal mass2 = AA2 * SI::Mp; // in Kg - if (IS_SET(species2["charge"]) and (get(species2["charge"]) != 0.0)) { + if (species2.isSet("charge") and (get(species2["charge"]) != 0.0)) { ////////////////////////////// // Both charged species @@ -383,7 +383,7 @@ void BraginskiiCollisions::transform_impl(GuardedOptions& state) { // If temperature isn't set, assume zero const Field3D temperature2 = - IS_SET(species2["temperature"]) + species2.isSet("temperature") ? GET_NOBOUNDARY(Field3D, species2["temperature"]) * Tnorm : 0.0; const BoutReal AA2 = get(species2["AA"]); diff --git a/src/classical_diffusion.cxx b/src/classical_diffusion.cxx index a3aacbf62..1ff09af9c 100644 --- a/src/classical_diffusion.cxx +++ b/src/classical_diffusion.cxx @@ -23,9 +23,7 @@ void ClassicalDiffusion::transform_impl(GuardedOptions& state) { for (auto& kv : allspecies.getChildren()) { const auto species = kv.second; - GuardedOptions x = species["charge"]; - - if (!(IS_SET(species["charge"]) and IS_SET(species["pressure"]))) { + if (!(species.isSet("charge") and species.isSet("pressure"))) { continue; // Skip, go to next species } auto q = get(species["charge"]); @@ -57,7 +55,7 @@ void ClassicalDiffusion::transform_impl(GuardedOptions& state) { for (auto kv : allspecies.getChildren()) { GuardedOptions species = allspecies[kv.first]; // Note: Need non-const - if (!(IS_SET(species["charge"]) and IS_SET(species["density"]))) { + if (!(species.isSet("charge") and species.isSet("density"))) { continue; // Skip, go to next species } auto q = get(species["charge"]); diff --git a/src/diamagnetic_drift.cxx b/src/diamagnetic_drift.cxx index e0c2b28b4..c282507db 100644 --- a/src/diamagnetic_drift.cxx +++ b/src/diamagnetic_drift.cxx @@ -64,7 +64,7 @@ void DiamagneticDrift::transform_impl(GuardedOptions& state) { for (auto& kv : allspecies.getChildren()) { GuardedOptions species = allspecies[kv.first]; // Note: Need non-const - if (!(IS_SET(species["charge"]) and IS_SET(species["temperature"]))) + if (!(species.isSet("charge") and species.isSet("temperature"))) continue; // Skip, go to next species // Calculate diamagnetic drift velocity for this species diff --git a/src/electromagnetic.cxx b/src/electromagnetic.cxx index b73404950..258e4ab9d 100644 --- a/src/electromagnetic.cxx +++ b/src/electromagnetic.cxx @@ -115,7 +115,7 @@ void Electromagnetic::transform_impl(GuardedOptions& state) { for (auto& kv : allspecies.getChildren()) { const GuardedOptions species = kv.second; - if (!IS_SET(species["charge"]) or !IS_SET(species["momentum"])) { + if (!species.isSet("charge") or !species.isSet("momentum")) { continue; // Not charged, or no parallel flow } const BoutReal Z = get(species["charge"]); @@ -186,7 +186,7 @@ void Electromagnetic::transform_impl(GuardedOptions& state) { for (auto& kv : allspecies.getChildren()) { GuardedOptions species = allspecies[kv.first]; // Note: need non-const - if (!IS_SET(species["charge"]) or !IS_SET(species["momentum"])) { + if (!species.isSet("charge") or !species.isSet("momentum")) { continue; // Not charged, or no parallel flow } const BoutReal Z = get(species["charge"]); diff --git a/src/neutral_boundary.cxx b/src/neutral_boundary.cxx index e9bac2f2e..d8460cb4c 100644 --- a/src/neutral_boundary.cxx +++ b/src/neutral_boundary.cxx @@ -70,7 +70,7 @@ void NeutralBoundary::transform_impl(GuardedOptions& state) { // Get the energy source, or create if not set Field3D energy_source = - IS_SET(species["energy_source"]) + species.isSet("energy_source") ? toFieldAligned(getNonFinal(species["energy_source"])) : zeroFrom(Nn); diff --git a/src/neutral_parallel_diffusion.cxx b/src/neutral_parallel_diffusion.cxx index 950da6559..b66178b93 100644 --- a/src/neutral_parallel_diffusion.cxx +++ b/src/neutral_parallel_diffusion.cxx @@ -15,7 +15,7 @@ void NeutralParallelDiffusion::transform_impl(GuardedOptions& state) { // Get non-const reference auto species = allspecies[species_name]; - if (IS_SET(species["charge"]) and (get(species["charge"]) != 0.0)) { + if (species.isSet("charge") and (get(species["charge"]) != 0.0)) { // Skip charged species continue; } diff --git a/src/noflow_boundary.cxx b/src/noflow_boundary.cxx index 39be328a4..d4bf09ead 100644 --- a/src/noflow_boundary.cxx +++ b/src/noflow_boundary.cxx @@ -21,7 +21,7 @@ void NoFlowBoundary::transform_impl(GuardedOptions& state) { // Apply zero-gradient boundary conditions to state variables for (const std::string field : {"density", "temperature", "pressure"}) { - if (!IS_SET(species[field])) { + if (!species.isSet(field)) { continue; // Skip variables which are not set } @@ -57,7 +57,7 @@ void NoFlowBoundary::transform_impl(GuardedOptions& state) { // Apply zero-value boundary conditions to flows for (const std::string field : {"velocity", "momentum"}) { - if (!IS_SET(species[field])) { + if (!species.isSet(field)) { continue; // Skip variables which are not set } diff --git a/src/polarisation_drift.cxx b/src/polarisation_drift.cxx index fcebdcfde..ff45695d7 100644 --- a/src/polarisation_drift.cxx +++ b/src/polarisation_drift.cxx @@ -77,7 +77,7 @@ void PolarisationDrift::transform_impl(GuardedOptions& state) { for (auto& kv : allspecies.getChildren()) { const GuardedOptions species = kv.second; - if (!IS_SET(species["charge"]) or !IS_SET(species["momentum"])) { + if (!species.isSet("charge") or !species.isSet("momentum")) { continue; // Not charged, or no parallel flow } const BoutReal Z = get(species["charge"]); @@ -105,8 +105,8 @@ void PolarisationDrift::transform_impl(GuardedOptions& state) { for (auto& kv : allspecies.getChildren()) { GuardedOptions species = allspecies[kv.first]; // Note: need non-const - if (!(IS_SET_NOBOUNDARY(species["pressure"]) and IS_SET(species["charge"]) - and IS_SET(species["AA"]))) { + if (!(IS_SET_NOBOUNDARY(species["pressure"]) and species.isSet("charge") + and species.isSet("AA"))) { // No pressure, charge or mass -> no polarisation current due to // diamagnetic flow continue; @@ -141,7 +141,7 @@ void PolarisationDrift::transform_impl(GuardedOptions& state) { for (auto& kv : allspecies.getChildren()) { const GuardedOptions species = kv.second; - if (!(IS_SET(species["charge"]) and IS_SET(species["AA"]))) { + if (!(species.isSet("charge") and species.isSet("AA"))) { continue; // No charge or mass -> no current } if (fabs(get(species["charge"])) < 1e-5) { @@ -187,7 +187,7 @@ void PolarisationDrift::transform_impl(GuardedOptions& state) { for (auto& kv : allspecies.getChildren()) { GuardedOptions species = allspecies[kv.first]; // Note: need non-const - if (!(IS_SET(species["charge"]) and IS_SET(species["AA"]))) { + if (!(species.isSet("charge") and species.isSet("AA"))) { continue; // No charge or mass -> no current } const BoutReal Z = get(species["charge"]); diff --git a/src/quasineutral.cxx b/src/quasineutral.cxx index cb8034e8a..7efc46813 100644 --- a/src/quasineutral.cxx +++ b/src/quasineutral.cxx @@ -30,8 +30,8 @@ void Quasineutral::transform_impl(GuardedOptions& state) { const std::map::value_type &name_species) { const GuardedOptions species = name_species.second; // Add other species which have density and charge - if (name_species.first != name and IS_SET(species["charge"]) and - IS_SET(species["density"])) { + if (name_species.first != name and species.isSet("charge") and + species.isSet("density")) { // Note: Not assuming that the boundary has been set return value + getNoBoundary(species["density"]) * get(species["charge"]); diff --git a/src/recycling.cxx b/src/recycling.cxx index ddcc1a25f..875de3745 100644 --- a/src/recycling.cxx +++ b/src/recycling.cxx @@ -178,10 +178,10 @@ void Recycling::transform_impl(GuardedOptions& state) { // Recycling particle and energy sources will be added to these global sources // which are then passed to the density and pressure equations - density_source = IS_SET(species_to["density_source"]) + density_source = species_to.isSet("density_source") ? getNonFinal(species_to["density_source"]) : 0.0; - energy_source = IS_SET(species_to["energy_source"]) + energy_source = species_to.isSet("energy_source") ? getNonFinal(species_to["energy_source"]) : 0.0; @@ -189,7 +189,7 @@ void Recycling::transform_impl(GuardedOptions& state) { if (target_recycle) { // Fast recycling needs to know how much energy the "from" species is losing to the boundary - if (IS_SET(species_from["energy_flow_ylow"])) { + if (species_from.isSet("energy_flow_ylow")) { energy_flow_ylow = get(species_from["energy_flow_ylow"]); } else { energy_flow_ylow = 0; @@ -291,13 +291,13 @@ void Recycling::transform_impl(GuardedOptions& state) { channel.pump_density_source = 0; channel.pump_energy_source = 0; - if (IS_SET(species_from["energy_flow_xlow"])) { + if (species_from.isSet("energy_flow_xlow")) { energy_flow_xlow = get(species_from["energy_flow_xlow"]); } else if ((channel.sol_fast_recycle_fraction > 0) or (channel.pfr_fast_recycle_fraction > 0)) { throw BoutException("SOL/PFR fast recycle enabled but no cell edge heat flow available, check your wall BC choice"); }; - if (IS_SET(species_from["particle_flow_xlow"])) { + if (species_from.isSet("particle_flow_xlow")) { particle_flow_xlow = get(species_from["particle_flow_xlow"]); } else if ((channel.sol_fast_recycle_fraction > 0) or (channel.pfr_fast_recycle_fraction > 0)) { throw BoutException("SOL/PFR fast recycle enabled but no cell edge particle flow available, check your wall BC choice"); diff --git a/src/sheath_boundary.cxx b/src/sheath_boundary.cxx index 2069e1867..7b0d2208f 100644 --- a/src/sheath_boundary.cxx +++ b/src/sheath_boundary.cxx @@ -291,7 +291,7 @@ void SheathBoundary::transform_impl(GuardedOptions& state) { // Electrons Field3D electron_energy_source = - IS_SET(electrons["energy_source"]) + electrons.isSet("energy_source") ? toFieldAligned(getNonFinal(electrons["energy_source"])) : zeroFrom(Ne); @@ -484,23 +484,23 @@ void SheathBoundary::transform_impl(GuardedOptions& state) { // Density and temperature boundary conditions will be imposed (free) Field3D Ni = toFieldAligned(floor(getNoBoundary(species["density"]), 0.0)); Field3D Ti = toFieldAligned(getNoBoundary(species["temperature"])); - Field3D Pi = IS_SET(species["pressure"]) + Field3D Pi = species.isSet("pressure") ? toFieldAligned(getNoBoundary(species["pressure"])) : Ni * Ti; // Get the velocity and momentum // These will be modified at the boundaries // and then put back into the state - Field3D Vi = IS_SET(species["velocity"]) + Field3D Vi = species.isSet("velocity") ? toFieldAligned(getNoBoundary(species["velocity"])) : zeroFrom(Ni); - Field3D NVi = IS_SET(species["momentum"]) + Field3D NVi = species.isSet("momentum") ? toFieldAligned(getNoBoundary(species["momentum"])) : Mi * Ni * Vi; // Energy source will be modified in the domain Field3D energy_source = - IS_SET(species["energy_source"]) + species.isSet("energy_source") ? toFieldAligned(getNonFinal(species["energy_source"])) : zeroFrom(Ni); @@ -667,12 +667,12 @@ void SheathBoundary::transform_impl(GuardedOptions& state) { setBoundary(species["temperature"], fromFieldAligned(Ti)); setBoundary(species["pressure"], fromFieldAligned(Pi)); - if (IS_SET(species["velocity"])) { + if (species.isSet("velocity")) { Vi.clearParallelSlices(); setBoundary(species["velocity"], fromFieldAligned(Vi)); } - if (IS_SET(species["momentum"])) { + if (species.isSet("momentum")) { NVi.clearParallelSlices(); setBoundary(species["momentum"], fromFieldAligned(NVi)); } diff --git a/src/sheath_boundary_insulating.cxx b/src/sheath_boundary_insulating.cxx index 10e16efc6..a4fef6e8f 100644 --- a/src/sheath_boundary_insulating.cxx +++ b/src/sheath_boundary_insulating.cxx @@ -223,23 +223,23 @@ void SheathBoundaryInsulating::transform_impl(GuardedOptions& state) { // Density and temperature boundary conditions will be imposed (free) Field3D Ni = toFieldAligned(floor(getNoBoundary(species["density"]), 0.0)); Field3D Ti = toFieldAligned(getNoBoundary(species["temperature"])); - Field3D Pi = IS_SET(species["pressure"]) + Field3D Pi = species.isSet("pressure") ? toFieldAligned(getNoBoundary(species["pressure"])) : Ni * Ti; // Get the velocity and momentum // These will be modified at the boundaries // and then put back into the state - Field3D Vi = IS_SET(species["velocity"]) + Field3D Vi = species.isSet("velocity") ? toFieldAligned(getNoBoundary(species["velocity"])) : zeroFrom(Ni); - Field3D NVi = IS_SET(species["momentum"]) + Field3D NVi = species.isSet("momentum") ? toFieldAligned(getNoBoundary(species["momentum"])) : Mi * Ni * Vi; // Energy source will be modified in the domain Field3D energy_source = - IS_SET(species["energy_source"]) + species.isSet("energy_source") ? toFieldAligned(getNonFinal(species["energy_source"])) : zeroFrom(Ni); @@ -410,11 +410,11 @@ void SheathBoundaryInsulating::transform_impl(GuardedOptions& state) { setBoundary(species["temperature"], fromFieldAligned(Ti)); setBoundary(species["pressure"], fromFieldAligned(Pi)); - if (IS_SET(species["velocity"])) { + if (species.isSet("velocity")) { setBoundary(species["velocity"], fromFieldAligned(Vi)); } - if (IS_SET(species["momentum"])) { + if (species.isSet("momentum")) { setBoundary(species["momentum"], fromFieldAligned(NVi)); } @@ -429,7 +429,7 @@ void SheathBoundaryInsulating::transform_impl(GuardedOptions& state) { // Field3D electron_energy_source = - IS_SET(electrons["energy_source"]) + electrons.isSet("energy_source") ? toFieldAligned(getNonFinal(electrons["energy_source"])) : zeroFrom(Ne); diff --git a/src/sheath_boundary_simple.cxx b/src/sheath_boundary_simple.cxx index 3fcc7f340..554033f72 100644 --- a/src/sheath_boundary_simple.cxx +++ b/src/sheath_boundary_simple.cxx @@ -182,7 +182,7 @@ void SheathBoundarySimple::transform_impl(GuardedOptions& state) { for (auto& kv : allspecies.getChildren()) { const GuardedOptions species = kv.second; - if ((kv.first == "e") or !IS_SET(species["charge"]) + if ((kv.first == "e") or !species.isSet("charge") or (get(species["charge"]) == 0.0)) { continue; // Skip electrons and non-charged ions } @@ -191,7 +191,7 @@ void SheathBoundarySimple::transform_impl(GuardedOptions& state) { const Field3D Ti = getNoBoundary(species["temperature"]); const BoutReal Mi = getNoBoundary(species["AA"]); const BoutReal Zi = getNoBoundary(species["charge"]); - Field3D Vi = IS_SET(species["velocity"]) + Field3D Vi = species.isSet("velocity") ? toFieldAligned(getNoBoundary(species["velocity"])) : zeroFrom(Ni); @@ -318,7 +318,7 @@ void SheathBoundarySimple::transform_impl(GuardedOptions& state) { ////////////////////////////////////////////////////////////////// // Electrons - Field3D electron_energy_source = IS_SET(electrons["energy_source"]) + Field3D electron_energy_source = electrons.isSet("energy_source") ? toFieldAligned(getNonFinal(electrons["energy_source"])) : zeroFrom(Ne); @@ -477,7 +477,7 @@ void SheathBoundarySimple::transform_impl(GuardedOptions& state) { setBoundary(electrons["momentum"], fromFieldAligned(NVe)); } - if (always_set_phi or (state.isSection("fields") and IS_SET(state["fields"]["phi"]))) { + if (always_set_phi or (state.isSection("fields") and state["fields"].isSet("phi"))) { // Set the potential, including boundary conditions phi.clearParallelSlices(); setBoundary(state["fields"]["phi"], fromFieldAligned(phi)); @@ -495,7 +495,7 @@ void SheathBoundarySimple::transform_impl(GuardedOptions& state) { GuardedOptions species = allspecies[kv.first]; // Note: Need non-const // Ion charge - const BoutReal Zi = IS_SET(species["charge"]) ? get(species["charge"]) : 0.0; + const BoutReal Zi = species.isSet("charge") ? get(species["charge"]) : 0.0; if (Zi == 0.0) { continue; // Neutral -> skip @@ -507,22 +507,22 @@ void SheathBoundarySimple::transform_impl(GuardedOptions& state) { // Density and temperature boundary conditions will be imposed (free) Field3D Ni = toFieldAligned(floor(getNoBoundary(species["density"]), 0.0)); Field3D Ti = toFieldAligned(getNoBoundary(species["temperature"])); - Field3D Pi = IS_SET(species["pressure"]) + Field3D Pi = species.isSet("pressure") ? toFieldAligned(getNoBoundary(species["pressure"])) : Ni * Ti; // Get the velocity and momentum // These will be modified at the boundaries // and then put back into the state - Field3D Vi = IS_SET(species["velocity"]) + Field3D Vi = species.isSet("velocity") ? toFieldAligned(getNoBoundary(species["velocity"])) : zeroFrom(Ni); - Field3D NVi = IS_SET(species["momentum"]) + Field3D NVi = species.isSet("momentum") ? toFieldAligned(getNoBoundary(species["momentum"])) : Mi * Ni * Vi; // Energy source will be modified in the domain - Field3D energy_source = IS_SET(species["energy_source"]) + Field3D energy_source = species.isSet("energy_source") ? toFieldAligned(getNonFinal(species["energy_source"])) : zeroFrom(Ni); @@ -673,12 +673,12 @@ void SheathBoundarySimple::transform_impl(GuardedOptions& state) { setBoundary(species["temperature"], fromFieldAligned(Ti)); setBoundary(species["pressure"], fromFieldAligned(Pi)); - if (IS_SET(species["velocity"])) { + if (species.isSet("velocity")) { Vi.clearParallelSlices(); setBoundary(species["velocity"], fromFieldAligned(Vi)); } - if (IS_SET(species["momentum"])) { + if (species.isSet("momentum")) { NVi.clearParallelSlices(); setBoundary(species["momentum"], fromFieldAligned(NVi)); } diff --git a/src/sheath_closure.cxx b/src/sheath_closure.cxx index 712e9d120..9b7cbfae0 100644 --- a/src/sheath_closure.cxx +++ b/src/sheath_closure.cxx @@ -51,7 +51,7 @@ void SheathClosure::transform_impl(GuardedOptions& state) { add(electrons["density_source"], DivJsh); // Electron heat conduction - if (IS_SET(electrons["temperature"])) { + if (electrons.isSet("temperature")) { // Assume attached, sheath-limited regime // Sheath heat transmission gamma * n * T * cs diff --git a/src/solkit_neutral_parallel_diffusion.cxx b/src/solkit_neutral_parallel_diffusion.cxx index 14e322112..76676859a 100644 --- a/src/solkit_neutral_parallel_diffusion.cxx +++ b/src/solkit_neutral_parallel_diffusion.cxx @@ -13,7 +13,7 @@ void SOLKITNeutralParallelDiffusion::transform_impl(GuardedOptions& state) { // Get non-const reference auto species = allspecies[kv.first]; - if (IS_SET(species["charge"]) and (get(species["charge"]) != 0.0)) { + if (species.isSet("charge") and (get(species["charge"]) != 0.0)) { // Skip charged species continue; } @@ -31,7 +31,7 @@ void SOLKITNeutralParallelDiffusion::transform_impl(GuardedOptions& state) { // Electrons const Field3D Ne = GET_VALUE(Field3D, species["density"]); return value + (8.8e-21 / area_norm) * Ne; - } else if (IS_SET(species["charge"]) and + } else if (species.isSet("charge") and (get(species["charge"]) != 0.0)) { // Charged ion species const Field3D Ni = GET_VALUE(Field3D, species["density"]); diff --git a/src/sound_speed.cxx b/src/sound_speed.cxx index ed9bbb6b2..c4ce23fc9 100644 --- a/src/sound_speed.cxx +++ b/src/sound_speed.cxx @@ -11,7 +11,7 @@ void SoundSpeed::transform_impl(GuardedOptions& state) { for (auto& kv : state["species"].getChildren()) { const GuardedOptions species = kv.second; - if (IS_SET(species["pressure"])) { + if (species.isSet("pressure")) { total_pressure += GET_NOBOUNDARY(Field3D, species["pressure"]); } @@ -21,14 +21,14 @@ void SoundSpeed::transform_impl(GuardedOptions& state) { continue; } - if (IS_SET(species["AA"])) { + if (species.isSet("AA")) { auto AA = get(species["AA"]); // Atomic mass number - if (IS_SET(species["density"])) { + if (species.isSet("density")) { total_density += GET_NOBOUNDARY(Field3D, species["density"]) * get(species["AA"]); } - if (IS_SET(species["temperature"])) { + if (species.isSet("temperature")) { auto T = GET_NOBOUNDARY(Field3D, species["temperature"]); for (auto& i : fastest_wave.getRegion("RGN_NOBNDRY")) { BoutReal sound_speed = sqrt(softFloor(T[i], temperature_floor) / AA); diff --git a/src/vorticity.cxx b/src/vorticity.cxx index 9e8f3e922..640b03a90 100644 --- a/src/vorticity.cxx +++ b/src/vorticity.cxx @@ -225,8 +225,8 @@ void Vorticity::transform_impl(GuardedOptions& state) { for (auto& kv : allspecies.getChildren()) { GuardedOptions species = allspecies[kv.first]; // Note: need non-const - if (!(IS_SET_NOBOUNDARY(species["pressure"]) and IS_SET(species["charge"]) - and IS_SET(species["AA"]))) { + if (!(IS_SET_NOBOUNDARY(species["pressure"]) and species.isSet("charge") + and species.isSet("AA"))) { continue; // No pressure, charge or mass -> no polarisation current } @@ -349,7 +349,7 @@ void Vorticity::transform_impl(GuardedOptions& state) { } Field3D Te; // Electron temperature, use for outer boundary conditions - if (IS_SET(state["species"]["e"]["temperature"])) { + if (state["species"]["e"].isSet("temperature")) { // Electron temperature set Te = GET_NOBOUNDARY(Field3D, state["species"]["e"]["temperature"]); } else { @@ -591,7 +591,7 @@ void Vorticity::transform_impl(GuardedOptions& state) { for (const auto& kv : allspecies.getChildren()) { const GuardedOptions species = kv.second; - if (!(IS_SET(species["charge"]) and IS_SET(species["AA"]))) { + if (!(species.isSet("charge") and species.isSet("AA"))) { continue; // No charge or mass -> no current } if (fabs(get(species["charge"])) < 1e-5) { diff --git a/src/zero_current.cxx b/src/zero_current.cxx index 15f09347d..e543df423 100644 --- a/src/zero_current.cxx +++ b/src/zero_current.cxx @@ -27,7 +27,7 @@ void ZeroCurrent::transform_impl(GuardedOptions& state) { } GuardedOptions species = allspecies[kv.first]; // Note: Need non-const - if (!(IS_SET(species["density"]) and IS_SET(species["charge"]))) { + if (!(species.isSet("density") and species.isSet("charge"))) { continue; // Needs both density and charge to contribute } @@ -56,7 +56,7 @@ void ZeroCurrent::transform_impl(GuardedOptions& state) { // Get the species density GuardedOptions species = state["species"][name]; - if (IS_SET(species["velocity"])) { + if (species.isSet("velocity")) { throw BoutException("Cannot use zero_current in species {} if velocity already set\n", name); } Field3D N = getNoBoundary(species["density"]); From eab3fb3306a1176d43d63a90b9b588e2ac264de9 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Fri, 31 Oct 2025 18:39:00 +0000 Subject: [PATCH 07/63] Added support for a permission to only read variable if already set --- include/guarded_options.hxx | 16 +- include/permissions.hxx | 69 +++++---- src/guarded_options.cxx | 43 +++++- src/permissions.cxx | 14 +- tests/unit/test_guarded_options.cxx | 57 ++++--- tests/unit/test_permissions.cxx | 228 +++++++++++++++++----------- 6 files changed, 267 insertions(+), 160 deletions(-) diff --git a/include/guarded_options.hxx b/include/guarded_options.hxx index b4ed9825e..49cbc6662 100644 --- a/include/guarded_options.hxx +++ b/include/guarded_options.hxx @@ -12,25 +12,13 @@ class GuardedOptions { public: GuardedOptions() = default; - // GuardedOptions(GuardedOptions &other) = default; - // GuardedOptions(GuardedOptions &&other) = default; - + /// Create a guarded options object which applies the specified /// permissions to the underlying options object. Note that the /// variable names used in the Permissions object must always be the /// full names, relative to the highest-level of the Options /// hierarchy. - GuardedOptions(Options* options, Permissions* permissions) - : options(options), permissions(permissions), - unread_variables(std::make_shared>()), - unwritten_variables( - std::make_shared>()) { - if (permissions != nullptr) { - *unread_variables = permissions->getVariablesWithPermission(Permissions::Read); - *unwritten_variables = - permissions->getVariablesWithPermission(Permissions::Write, false); - } - } + GuardedOptions(Options* options, Permissions* permissions); /// Get a subsection or value. The result will also be wrapped in a /// GuardedOptions object, with the same permissions as this one. diff --git a/include/permissions.hxx b/include/permissions.hxx index f62f52fbc..c5c7434bc 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -11,9 +11,12 @@ class Permissions { public: /// Ways in which someone is allowed to access the variable, with - /// increasing levels of rights. "Final" refers to the last time - /// anyone is allowed to write to the variable. - enum PermissionTypes { None = -1, Read, Write, Final, PERMISSION_TYPES_END }; + /// increasing levels of rights. "ReadIfSet" indicates that + /// variables should only be read if already set. "Final" refers to + /// the last time anyone is allowed to write to the variable. These + /// two concepts need to be captured to decide the order in which to + /// execute components. + enum PermissionTypes { None = -1, ReadIfSet, Read, Write, Final, PERMISSION_TYPES_END }; /// The regions of the domain to which a particular permission /// apply. These are designed to be used as bit-flags. @@ -29,9 +32,11 @@ public: /// Data type for storing the regions of a variable which have a /// particular level of permission. Some examples can be seen below: /// - /// AccessRights read_only = { AllRegions, Nowhere, Nowhere }, - /// write_boundaries = { Nowhere, Boundaries, Nowhere }, - /// read_and_write_everywhere = { AllReginos, AllRegions, Nowhere + /// AccessRights only_read_if_set = { AllRegions, Nowhere, Nowhere, Nowhere }, + /// read_only = { Nowhere, AllRegions, Nowhere, Nowhere }, + /// write_boundaries = { Nowhere, Nowhere, Boundaries, Nowhere }, + /// read_and_write_everywhere = { Nowhere, AllReginos, AllRegions, + /// Nowhere /// }, final_write_boundaries_read_interior = { Interior, Nowhere, /// Boundaries }; /// @@ -51,18 +56,21 @@ public: /// the examples below. /// /// Permissions example({ + /// // Permission to read charge only if it has been set + /// {"species:he:charge", {Permissions::AllRegions, Permissions::Nowhere, + /// Permissions::Nowhere,, Permissions::Nowhere}}, /// // Read permission for atomic mass - /// {"species:he:AA", {Permissions::AllRegions, Permissions::Nowhere, - /// Permissions::Nowhere}}, + /// {"species:he:AA", {Permissions::Nowhere, Permissions::AllRegions, + /// Permissions::Nowhere, Permissions::Nowhere}}, /// // Read permissions for density - /// {"species:he:density", {Permissions::AllRegions, Permissions::Nowhere, - /// Permissions::Nowhere}}, + /// {"species:he:density", {Permissions::Nowhere, Permissions::AllRegions, + /// Permissions::Nowhere, Permissions::Nowhere}}, /// // Read and write permissions for pressure in the interior region - /// {"species:he:pressure", {Permissions::Nowhere, Permissions::Interior, - /// Permissions::Nowhere}}, + /// {"species:he:pressure", {Permissions::Nowhere, Permissions::Nowhere, + /// Permissions::Interior, Permissions::Nowhere}}, /// // Set the final value for collision frequency /// {"species:he:collision_frequency", {Permissions::Nowhere, - /// Permissions::Nowhere, Permissions::AllRegions}} + /// Permissions::Nowhere, Permissions::Nowhere, Permissions::AllRegions}} /// }); /// /// If a variable is not included in the initialiser list then it is @@ -80,8 +88,8 @@ public: /// frequency for every species you would write: /// /// Permissions example2({ - /// {"species:{name}:collision_frequency", {Permissions::AllRegions, - /// Permissions::Nowhere, Permissions::Nowhere}} + /// {"species:{name}:collision_frequency", {Permissions::Nowhere, + /// Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}} /// }); /// example2.substitute("name", {"he+", "d+", "e", "d", "he"}); /// @@ -93,14 +101,14 @@ public: /// everywhere but only writeable in the interior, you would use /// /// permissions.setAccess("species:he:density", - /// {Permissions::AllRegions, Permissions::Interior, - /// Permissions::Nowhere}) - /// + /// {Permissions::Nowhere, Permissions::AllRegions, + /// Permissions::Interior, Permissions::Nowhere}) + /// 0 /// or, equivalently, /// /// permissions.setAccess("species:he:density", - /// {Permissions::Boundary, Permissions::Interior, - /// Permissions::Nowhere}); + /// {Permissions::Nowhere, Permissions::Boundary, + /// Permissions::Interior, Permissions::Nowhere}); /// /// As in the constructor, if the variable name is just a section in /// an Options object then the permissions apply to all children of @@ -114,10 +122,10 @@ public: /// for every species. /// /// Permissions example({ - /// {"species:{name}:density", {Permissions::AllRegions, Permissions::Nowhere, - /// Permissions::Nowhere}}, + /// {"species:{name}:density", {Permissions::Nowhere, Permissions::AllRegions, + /// Permissions::Nowhere, Permissions::Nowhere}}, /// {"species:{name}:collision_frequency", {Permissions::Nowhere, - /// Permissions::AllRegions, Permissions::Nowhere}}, + /// Permissions::Nowhere, Permissions::AllRegions, Permissions::Nowhere}}, /// }); /// example.substitute("name", {"d", "d+", "t", "t+", "he", "he+", "c", "c+", "e"}); /// @@ -125,7 +133,7 @@ public: const std::vector& substitutions); /// Check whether users are allowed to access this variable to the - /// given permission level, in the given region. The secon item + /// given permission level, in the given region. The second item /// returned indicates the name of the variable or section from /// which the access rights are derived. If there is no matching /// section then it will be an empty string. @@ -134,17 +142,20 @@ public: Regions region = AllRegions) const; /// Get the highest permission level with which the given variable - /// can be accessed in the given region. - PermissionTypes getHighestPermission(const std::string& variable, - Regions region = AllRegions) const; + /// can be accessed in the given region. The second item + /// returned indicates the name of the variable or section from + /// which the access rights are derived. If there is no matching + /// section then it will be an empty string. + std::pair + getHighestPermission(const std::string& variable, Regions region = AllRegions) const; /// Get a set of variables and regions for which there is the /// specified level of permission to access. If ``highestOnly`` is /// true then it will only include variables/regions for which this /// is the highest permission. /// - /// Permissions example({"test", {Permissions::AllRegions, Permissions::AllRegions, - /// Permissions:Nowhere}}); + /// Permissions example({"test", {Permissions::Nowhere, Permissions::AllRegions, + /// Permissions::AllRegions, Permissions:Nowhere}}); /// // Print variables which can be read /// for (const auto [varname, region] : /// example.getVariablesWithPermission(Permissions::Read, false)) diff --git a/src/guarded_options.cxx b/src/guarded_options.cxx index 2b74c1f51..2c82ea95e 100644 --- a/src/guarded_options.cxx +++ b/src/guarded_options.cxx @@ -2,6 +2,36 @@ #include "../include/guarded_options.hxx" +GuardedOptions::GuardedOptions(Options* options, Permissions* permissions) + : options(options), permissions(permissions), + unread_variables(std::make_shared>()), + unwritten_variables( + std::make_shared>()) { + if (permissions != nullptr) { + *unread_variables = permissions->getVariablesWithPermission(Permissions::Read); + // Only add variables with permission ReadIfSet to + // unread_variables if they are already present in the options + // object + if (options != nullptr) { + for (auto& [varname, region] : + permissions->getVariablesWithPermission(Permissions::ReadIfSet)) { + // find the last component of the full varname path + size_t last_colon = varname.find_last_of(":"); + Options& parent = (last_colon != std::string::npos) + ? (*options)[varname.substr(0, last_colon)] + : *options; + if (parent.isSet((last_colon != std::string::npos) + ? varname.substr(last_colon + 1) + : varname)) { + unread_variables->insert({varname, region}); + } + } + } + *unwritten_variables = + permissions->getVariablesWithPermission(Permissions::Write, false); + } +} + GuardedOptions GuardedOptions::operator[](const std::string& name) { if (options == nullptr) throw BoutException( @@ -45,13 +75,18 @@ const Options& GuardedOptions::get(Permissions::Regions region) const { "Trying to access GuardedOptions when underlying options are nullptr."); std::string name = options->str(); if (permissions != nullptr) { - auto [access, varname] = permissions->canAccess(name, Permissions::Read, region); - if (access) { + auto [permission, varname] = permissions->getHighestPermission(name, region); + if (permission >= Permissions::ReadIfSet) { + if (permission == Permissions::ReadIfSet && !options->isSet()) { + throw BoutException( + "Only have permission to read {} if it is already set, which it is not.", + name); + } updateAccessRecords(*unread_variables, varname, region); return *options; } } - throw BoutException(fmt::format("Do not have read permission for {}.", name)); + throw BoutException("Do not have read permission for {}.", name); } Options& GuardedOptions::getWritable(Permissions::Regions region) { @@ -66,7 +101,7 @@ Options& GuardedOptions::getWritable(Permissions::Regions region) { return *options; } } - throw BoutException(fmt::format("Do not have read permission for {}.", options->str())); + throw BoutException("Do not have read permission for {}.", options->str()); } std::map GuardedOptions::unreadItems() const { diff --git a/src/permissions.cxx b/src/permissions.cxx index a85f82617..ec3b765c7 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -72,17 +72,17 @@ std::pair Permissions::canAccess(const std::string& variable, } } -Permissions::PermissionTypes +std::pair Permissions::getHighestPermission(const std::string& variable, Permissions::Regions region) const { if (region == Nowhere) - return None; - AccessRights rights = std::get<1>(bestMatchRights(variable)); - int i = Read; + return {None, ""}; + auto [varname, rights] = bestMatchRights(variable); + int i = ReadIfSet; while (i < PERMISSION_TYPES_END and (rights[i] & region) == region) { i++; } - return static_cast(i - 1); + return {static_cast(i - 1), varname}; } std::map @@ -109,10 +109,10 @@ Permissions::getVariablesWithPermission(PermissionTypes permission, Permissions::AccessRights Permissions::applyLowerPermissions(const AccessRights& rights) { AccessRights result(rights); - for (int i = Read; i < PERMISSION_TYPES_END; i++) { + for (int i = ReadIfSet; i < PERMISSION_TYPES_END; i++) { result[i] = rights[i]; // Higher permissions imply lower permissions - for (int j = Read; j < i; j++) { + for (int j = ReadIfSet; j < i; j++) { result[j] = static_cast(result[j] | rights[i]); } } diff --git a/tests/unit/test_guarded_options.cxx b/tests/unit/test_guarded_options.cxx index ffe54b13c..65dff00da 100644 --- a/tests/unit/test_guarded_options.cxx +++ b/tests/unit/test_guarded_options.cxx @@ -4,24 +4,40 @@ class GuardedOptionsTests : public testing::Test { protected: GuardedOptionsTests() - : permissions( - {{"species:he:density", - {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, - {"species:he:pressure", - {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, - {"species:he:collision_frequency", - {Permissions::Nowhere, Permissions::Nowhere, Permissions::AllRegions}}, - {"species:he:velocity", - {Permissions::Boundaries, Permissions::Nowhere, Permissions::Nowhere}}, - {"species:d", - {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, - {"species:d:pressure", - {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, - {"species:d:collision_frequencies", - {Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere}}}), + : permissions({{"species:he:AA", + {Permissions::AllRegions, Permissions::Nowhere, + Permissions::Nowhere, Permissions::Nowhere}}, + {"species:he:charge", + {Permissions::AllRegions, Permissions::Nowhere, + Permissions::Nowhere, Permissions::Nowhere}}, + {"species:he:density", + {Permissions::Nowhere, Permissions::AllRegions, + Permissions::Nowhere, Permissions::Nowhere}}, + {"species:he:pressure", + {Permissions::Nowhere, Permissions::Nowhere, Permissions::Interior, + Permissions::Nowhere}}, + {"species:he:collision_frequency", + {Permissions::Nowhere, Permissions::Nowhere, Permissions::Nowhere, + Permissions::AllRegions}}, + {"species:he:velocity", + {Permissions::Nowhere, Permissions::Boundaries, + Permissions::Nowhere, Permissions::Nowhere}}, + {"species:d", + {Permissions::Nowhere, Permissions::AllRegions, + Permissions::Nowhere, Permissions::Nowhere}}, + {"species:d:pressure", + {Permissions::Nowhere, Permissions::Nowhere, Permissions::Interior, + Permissions::Nowhere}}, + {"species:d:collision_frequencies", + {Permissions::Nowhere, Permissions::Nowhere, + Permissions::Boundaries, Permissions::Nowhere}}}), opts({{"species", {{"he", - {{"temperature", 0}, {"density", 1}, {"pressure", 2}, {"velocity", 4}}}, + {{"charge", 0}, + {"temperature", 0}, + {"density", 1}, + {"pressure", 2}, + {"velocity", 4}}}, {"d", {{"pressure", 5}, {"velocity", 6}, @@ -34,6 +50,7 @@ class GuardedOptionsTests : public testing::Test { }; TEST_F(GuardedOptionsTests, TestGet) { + EXPECT_EQ(guarded_opts["species:he:charge"].get(), 0); EXPECT_EQ(guarded_opts["species:he:density"].get(), 1); EXPECT_EQ(guarded_opts["species:he:density"].get(Permissions::Boundaries), 1); EXPECT_EQ(guarded_opts["species:he:pressure"].get(Permissions::Interior), 2); @@ -53,6 +70,7 @@ TEST_F(GuardedOptionsTests, TestGet) { } TEST_F(GuardedOptionsTests, TestGetException) { + EXPECT_THROW(guarded_opts["species:he:AA"].get(), BoutException); EXPECT_THROW(guarded_opts["species"]["he"]["temperature"].get(), BoutException); EXPECT_THROW(guarded_opts["species:he:pressure"].get(), BoutException); EXPECT_THROW(guarded_opts["species"]["he"]["velocity"].get(Permissions::Interior), @@ -125,10 +143,12 @@ TEST_F(GuardedOptionsTests, TestGetWritableException) { TEST_F(GuardedOptionsTests, TestUnreadItems) { std::map - expected1 = {{"species:he:density", Permissions::AllRegions}, + expected1 = {{"species:he:charge", Permissions::AllRegions}, + {"species:he:density", Permissions::AllRegions}, {"species:he:velocity", Permissions::Boundaries}, {"species:d", Permissions::AllRegions}}, - expected2 = {{"species:he:density", Permissions::Boundaries}, + expected2 = {{"species:he:charge", Permissions::AllRegions}, + {"species:he:density", Permissions::Boundaries}, {"species:he:velocity", Permissions::Boundaries}, {"species:d", Permissions::AllRegions}}, expected3 = {{"species:d", Permissions::AllRegions}}, expected4; @@ -141,6 +161,7 @@ TEST_F(GuardedOptionsTests, TestUnreadItems) { Permissions::Boundaries); EXPECT_EQ(guarded_opts.unreadItems(), expected2); + guarded_opts["species"]["he"]["charge"].get(); guarded_opts["species"]["he"]["density"].get(); guarded_opts["species:he:velocity"].get(Permissions::Boundaries); EXPECT_EQ(guarded_opts.unreadItems(), expected3); diff --git a/tests/unit/test_permissions.cxx b/tests/unit/test_permissions.cxx index 0b6112138..00f6f1d34 100644 --- a/tests/unit/test_permissions.cxx +++ b/tests/unit/test_permissions.cxx @@ -3,31 +3,43 @@ #include "../include/permissions.hxx" auto make_access = std::make_pair; +auto make_permission = std::make_pair; TEST(PermissionsTests, TestCanAccess) { Permissions example({ + {"species:he:charge", + {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere, + Permissions::Nowhere}}, {"species:he:density", - {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, + {Permissions::Nowhere, Permissions::AllRegions, Permissions::Nowhere, + Permissions::Nowhere}}, // Read and write permissions for pressure in the interior region {"species:he:pressure", - {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, + {Permissions::Nowhere, Permissions::Nowhere, Permissions::Interior, + Permissions::Nowhere}}, // Set the final value for collision frequency {"species:he:collision_frequency", - {Permissions::Nowhere, Permissions::Nowhere, Permissions::AllRegions}}, + {Permissions::Nowhere, Permissions::Nowhere, Permissions::Nowhere, + Permissions::AllRegions}}, // Only allow reading of boundary velocity {"species:he:velocity", - {Permissions::Boundaries, Permissions::Nowhere, Permissions::Nowhere}}, + {Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere, + Permissions::Nowhere}}, {"species:d", - {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, + {Permissions::Nowhere, Permissions::AllRegions, Permissions::Nowhere, + Permissions::Nowhere}}, {"species:d:pressure", - {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, + {Permissions::Nowhere, Permissions::Nowhere, Permissions::Interior, + Permissions::Nowhere}}, {"species:d:collision_frequencies", - {Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere}}, + {Permissions::Nowhere, Permissions::Nowhere, Permissions::Boundaries, + Permissions::Nowhere}}, }); auto no_access = make_access(false, ""); // Check whether we have read permission for the variables across the entire domain + EXPECT_EQ(example.canAccess("species:he:charge"), no_access); EXPECT_EQ(example.canAccess("species:he:density"), make_access(true, "species:he:density")); EXPECT_EQ(example.canAccess("species:he:pressure"), no_access); @@ -36,7 +48,15 @@ TEST(PermissionsTests, TestCanAccess) { EXPECT_EQ(example.canAccess("species:he:velocity"), no_access); EXPECT_EQ(example.canAccess("unset"), no_access); + // Check whether we have ReadIfSet permissions + EXPECT_EQ(example.canAccess("species:he:charge", Permissions::ReadIfSet), + make_access(true, "species:he:charge")); + EXPECT_EQ(example.canAccess("species:he:density", Permissions::ReadIfSet), + make_access(true, "species:he:density")); + EXPECT_EQ(example.canAccess("unset", Permissions::ReadIfSet), no_access); + // Check whether we have write permission for the variables across the entire domain + EXPECT_EQ(example.canAccess("species:he:charge", Permissions::Write), no_access); EXPECT_EQ(example.canAccess("species:he:density", Permissions::Write), no_access); EXPECT_EQ(example.canAccess("species:he:pressure", Permissions::Write), no_access); EXPECT_EQ(example.canAccess("species:he:collision_frequency", Permissions::Write), @@ -45,6 +65,9 @@ TEST(PermissionsTests, TestCanAccess) { EXPECT_EQ(example.canAccess("unset", Permissions::Write), no_access); // Check whether we have read permission at the boundaries + EXPECT_EQ( + example.canAccess("species:he:charge", Permissions::Read, Permissions::Boundaries), + no_access); EXPECT_EQ( example.canAccess("species:he:density", Permissions::Read, Permissions::Boundaries), make_access(true, "species:he:density")); @@ -91,159 +114,187 @@ TEST(PermissionsTests, TestCanAccess) { TEST(PermissionsTests, TestGetHighestPermission) { Permissions example({ + {"species:he:charge", + {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere, + Permissions::Nowhere}}, {"species:he:density", - {Permissions::AllRegions, Permissions::Nowhere, Permissions::Boundaries}}, + {Permissions::Nowhere, Permissions::AllRegions, Permissions::Nowhere, + Permissions::Boundaries}}, // Read and write permissions for pressure in the interior region {"species:he:pressure", - {Permissions::Boundaries, Permissions::Interior, Permissions::Nowhere}}, + {Permissions::Nowhere, Permissions::Boundaries, Permissions::Interior, + Permissions::Nowhere}}, // Set the final value for collision frequency {"species:he:collision_frequency", - {Permissions::Interior, Permissions::Nowhere, Permissions::AllRegions}}, + {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere, + Permissions::AllRegions}}, // Only allow reading of boundary velocity {"species:he:velocity", - {Permissions::Boundaries, Permissions::Nowhere, Permissions::Nowhere}}, + {Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere, + Permissions::Nowhere}}, {"species:d", - {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, + {Permissions::Nowhere, Permissions::AllRegions, Permissions::Nowhere, + Permissions::Nowhere}}, {"species:d:pressure", - {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, + {Permissions::Nowhere, Permissions::Nowhere, Permissions::Interior, + Permissions::Nowhere}}, {"species:d:collision_frequencies", - {Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere}}, + {Permissions::Nowhere, Permissions::Nowhere, Permissions::Boundaries, + Permissions::Nowhere}}, }); + auto no_permission = make_permission(Permissions::None, ""); + // Get the highest permission that covers the entire domain - EXPECT_EQ(example.getHighestPermission("species:he:density"), Permissions::Read); - EXPECT_EQ(example.getHighestPermission("species:he:pressure"), Permissions::Read); + EXPECT_EQ(example.getHighestPermission("species:he:charge"), + make_permission(Permissions::ReadIfSet, "species:he:charge")); + EXPECT_EQ(example.getHighestPermission("species:he:density"), + make_permission(Permissions::Read, "species:he:density")); + EXPECT_EQ(example.getHighestPermission("species:he:pressure"), + make_permission(Permissions::Read, "species:he:pressure")); EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency"), - Permissions::Final); - EXPECT_EQ(example.getHighestPermission("species:he:velocity"), Permissions::None); - EXPECT_EQ(example.getHighestPermission("species:d:pressure"), Permissions::None); - EXPECT_EQ(example.getHighestPermission("species:d:velocity"), Permissions::Read); + make_permission(Permissions::Final, "species:he:collision_frequency")); + EXPECT_EQ(example.getHighestPermission("species:he:velocity"), no_permission); + EXPECT_EQ(example.getHighestPermission("species:d:pressure"), no_permission); + EXPECT_EQ(example.getHighestPermission("species:d:velocity"), + make_permission(Permissions::Read, "species:d")); EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll"), - Permissions::None); - EXPECT_EQ(example.getHighestPermission("unset"), Permissions::None); + no_permission); + EXPECT_EQ(example.getHighestPermission("unset"), no_permission); // Get the highest permission on the boundaries + EXPECT_EQ(example.getHighestPermission("species:he:charge", Permissions::Boundaries), + make_permission(Permissions::ReadIfSet, "species:he:charge")); EXPECT_EQ(example.getHighestPermission("species:he:density", Permissions::Boundaries), - Permissions::Final); + make_permission(Permissions::Final, "species:he:density")); EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Boundaries), - Permissions::Read); + make_permission(Permissions::Read, "species:he:pressure")); EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency", Permissions::Boundaries), - Permissions::Final); + make_permission(Permissions::Final, "species:he:collision_frequency")); EXPECT_EQ(example.getHighestPermission("species:he:velocity", Permissions::Boundaries), - Permissions::Read); + make_permission(Permissions::Read, "species:he:velocity")); EXPECT_EQ(example.getHighestPermission("species:d:pressure", Permissions::Boundaries), - Permissions::None); + no_permission); EXPECT_EQ(example.getHighestPermission("species:d:velocity", Permissions::Boundaries), - Permissions::Read); + no_permission); EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll", Permissions::Boundaries), - Permissions::Write); + make_permission(Permissions::Write, "species:d:collision_frequencies")); EXPECT_EQ(example.getHighestPermission("unset", Permissions::Boundaries), - Permissions::None); + no_permission); // Get the highest permission on the interior + EXPECT_EQ(example.getHighestPermission("species:he:charge", Permissions::Interior), + make_permission(Permissions::ReadIfSet, "species:he:charge")); EXPECT_EQ(example.getHighestPermission("species:he:density", Permissions::Interior), - Permissions::Read); + make_permission(Permissions::Read, "species:he:density")); EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Interior), - Permissions::Write); + make_permission(Permissions::Write, "species:he:pressure")); EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency", Permissions::Interior), - Permissions::Final); + make_permission(Permissions::Final, "species:he:collision_frequency")); EXPECT_EQ(example.getHighestPermission("species:he:velocity", Permissions::Interior), - Permissions::None); + no_permission); EXPECT_EQ(example.getHighestPermission("species:d:pressure", Permissions::Interior), - Permissions::Write); + make_permission(Permissions::Write, "species:d:pressure")); EXPECT_EQ(example.getHighestPermission("species:d:velocity", Permissions::Interior), - Permissions::Read); + make_permission(Permissions::Read, "species:d")); EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll", Permissions::Interior), - Permissions::None); - EXPECT_EQ(example.getHighestPermission("unset", Permissions::Interior), - Permissions::None); + no_permission); + EXPECT_EQ(example.getHighestPermission("unset", Permissions::Interior), no_permission); // Check the permission for the "Nowhere" region is always "None" + EXPECT_EQ(example.getHighestPermission("species:he:charge", Permissions::Nowhere), + no_permission); EXPECT_EQ(example.getHighestPermission("species:he:density", Permissions::Nowhere), - Permissions::None); + no_permission); EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Nowhere), - Permissions::None); + no_permission); EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency", Permissions::Nowhere), - Permissions::None); + no_permission); EXPECT_EQ(example.getHighestPermission("species:he:velocity", Permissions::Nowhere), - Permissions::None); + no_permission); EXPECT_EQ(example.getHighestPermission("species:d:pressure", Permissions::Nowhere), - Permissions::None); + no_permission); EXPECT_EQ(example.getHighestPermission("species:d:velocity", Permissions::Nowhere), - Permissions::None); + no_permission); EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll", Permissions::Nowhere), - Permissions::None); - EXPECT_EQ(example.getHighestPermission("unset", Permissions::Nowhere), - Permissions::None); + no_permission); + EXPECT_EQ(example.getHighestPermission("unset", Permissions::Nowhere), no_permission); // Check permissions for a species that might be mistaken for one of // the sections we've given permissions for - EXPECT_EQ(example.getHighestPermission("species:d+"), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:d+"), no_permission); EXPECT_EQ(example.getHighestPermission("species:d+", Permissions::Interior), - Permissions::None); + no_permission); EXPECT_EQ(example.getHighestPermission("species:d+", Permissions::Boundaries), - Permissions::None); + no_permission); } TEST(PermissionsTests, TestSetAccess) { Permissions example({ {"species:he:density", - {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}, + {Permissions::Nowhere, Permissions::AllRegions, Permissions::Nowhere, + Permissions::Nowhere}}, // Read and write permissions for pressure in the interior region {"species:he:pressure", - {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, + {Permissions::Nowhere, Permissions::Nowhere, Permissions::Interior, + Permissions::Nowhere}}, }); - EXPECT_EQ(example.getHighestPermission("species:he:density"), Permissions::Read); - example.setAccess("species:he:density", {Permissions::Nowhere, Permissions::Boundaries, - Permissions::Nowhere}); - EXPECT_EQ(example.getHighestPermission("species:he:density"), Permissions::None); + EXPECT_EQ(example.getHighestPermission("species:he:density"), + make_permission(Permissions::Read, "species:he:density")); + example.setAccess("species:he:density", + {Permissions::Nowhere, Permissions::Nowhere, Permissions::Boundaries, + Permissions::Nowhere}); + EXPECT_EQ(example.getHighestPermission("species:he:density"), + make_permission(Permissions::None, "species:he:density")); EXPECT_EQ(example.getHighestPermission("species:he:density", Permissions::Boundaries), - Permissions::Write); + make_permission(Permissions::Write, "species:he:density")); EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Interior), - Permissions::Write); + make_permission(Permissions::Write, "species:he:pressure")); EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::AllRegions), - Permissions::None); - example.setAccess("species:he:pressure", {Permissions::AllRegions, Permissions::Nowhere, - Permissions::Nowhere}); + make_permission(Permissions::None, "species:he:pressure")); + example.setAccess("species:he:pressure", {Permissions::Nowhere, Permissions::AllRegions, + Permissions::Nowhere, Permissions::Nowhere}); EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Interior), - Permissions::Read); - EXPECT_EQ(example.getHighestPermission("species:he:pressure"), Permissions::Read); + make_permission(Permissions::Read, "species:he:pressure")); + EXPECT_EQ(example.getHighestPermission("species:he:pressure"), + make_permission(Permissions::Read, "species:he:pressure")); EXPECT_EQ(example.getHighestPermission("unset", Permissions::Interior), - Permissions::None); + make_permission(Permissions::None, "")); EXPECT_EQ(example.getHighestPermission("unset", Permissions::Boundaries), - Permissions::None); - example.setAccess( - "unset", {Permissions::Interior, Permissions::Nowhere, Permissions::Boundaries}); + make_permission(Permissions::None, "")); + example.setAccess("unset", {Permissions::Nowhere, Permissions::Interior, + Permissions::Nowhere, Permissions::Boundaries}); EXPECT_EQ(example.getHighestPermission("unset", Permissions::AllRegions), - Permissions::Read); + make_permission(Permissions::Read, "unset")); EXPECT_EQ(example.getHighestPermission("unset", Permissions::Boundaries), - Permissions::Final); + make_permission(Permissions::Final, "unset")); } -auto make_permissions = std::make_pair; - TEST(PermissionsTests, TestGetVariablesWithPermissions) { - Permissions example( - {{"species:he:density", - {Permissions::AllRegions, Permissions::Nowhere, Permissions::Boundaries}}, - // Read and write permissions for pressure in the interior region - {"species:he:pressure", - {Permissions::Boundaries, Permissions::Interior, Permissions::Nowhere}}, - // Set the final value for collision frequency - {"species:he:collision_frequency", - {Permissions::Interior, Permissions::Nowhere, Permissions::AllRegions}}, - // Only allow reading of boundary velocity - {"species:he:velocity", - {Permissions::Boundaries, Permissions::Nowhere, Permissions::Nowhere}}}); + Permissions example({{"species:he:density", + {Permissions::Nowhere, Permissions::AllRegions, + Permissions::Nowhere, Permissions::Boundaries}}, + // Read and write permissions for pressure in the interior region + {"species:he:pressure", + {Permissions::Nowhere, Permissions::Boundaries, + Permissions::Interior, Permissions::Nowhere}}, + // Set the final value for collision frequency + {"species:he:collision_frequency", + {Permissions::Nowhere, Permissions::Interior, + Permissions::Nowhere, Permissions::AllRegions}}, + // Only allow reading of boundary velocity + {"species:he:velocity", + {Permissions::Nowhere, Permissions::Boundaries, + Permissions::Nowhere, Permissions::Nowhere}}}); auto read_only = example.getVariablesWithPermission(Permissions::Read); EXPECT_EQ(read_only.size(), 3); @@ -275,11 +326,12 @@ TEST(PermissionsTests, TestGetVariablesWithPermissions) { } TEST(PermissionsTests, TestSubstitute) { - Permissions example( - {{"species:{s1}:collision_frequencies:{s1}_{s2}_coll", - {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}}}); + Permissions example({{"species:{s1}:collision_frequencies:{s1}_{s2}_coll", + {Permissions::Nowhere, Permissions::AllRegions, + Permissions::Nowhere, Permissions::Nowhere}}}); - example.setAccess("{var}", {Permissions::Nowhere, Permissions::Interior}); + example.setAccess("{var}", {Permissions::Nowhere, Permissions::Nowhere, + Permissions::Interior, Permissions::Nowhere}); example.substitute("s1", {"e", "d+"}); example.substitute("s2", {"e", "d+"}); From c9d08b57062cab8b782b3219f21431244cf14587 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Fri, 31 Oct 2025 19:12:12 +0000 Subject: [PATCH 08/63] Add convenience functions for setting permissions on components --- include/permissions.hxx | 22 +++++++++++++++++++++ src/permissions.cxx | 30 +++++++++++++++++++++++++++++ tests/unit/test_guarded_options.cxx | 20 +++++-------------- tests/unit/test_permissions.cxx | 16 ++++----------- 4 files changed, 61 insertions(+), 27 deletions(-) diff --git a/include/permissions.hxx b/include/permissions.hxx index c5c7434bc..5d853d008 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -192,3 +192,25 @@ private: std::map variable_permissions; }; + +/// Convenience function to return an object expressing that the +/// variable should have ReadIfSet permissions on all regions. +std::pair readIfSet(std::string varname); + +/// Convenience function to return an object expressing that the +/// variable should have Read permissions on all regions. +std::pair readOnly(std::string varname); + +/// Convenience function to return an object expressing that the +/// variable should have Write permissions on all regions. +std::pair readWrite(std::string varname); + +/// Convenience function to return an object expressing that the +/// variable should have Final permissions on all regions. +std::pair writeFinal(std::string varname); + +/// Convenience function to return an object expressing that the +/// variable should have Write permissions on the boundaries. It will +/// have Read permissions in the interior, as this is normally +/// required to set the boundaries correctly. +std::pair writeBoundary(std::string varname); diff --git a/src/permissions.cxx b/src/permissions.cxx index ec3b765c7..233d33069 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -129,3 +129,33 @@ std::string Permissions::regionNames(const Regions regions) { } return result; } + +std::pair readIfSet(std::string varname) { + return {varname, + {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere, + Permissions::Nowhere}}; +} + +std::pair readOnly(std::string varname) { + return {varname, + {Permissions::Nowhere, Permissions::AllRegions, Permissions::Nowhere, + Permissions::Nowhere}}; +} + +std::pair readWrite(std::string varname) { + return {varname, + {Permissions::Nowhere, Permissions::Nowhere, Permissions::AllRegions, + Permissions::Nowhere}}; +} + +std::pair writeFinal(std::string varname) { + return {varname, + {Permissions::Nowhere, Permissions::Nowhere, Permissions::Nowhere, + Permissions::AllRegions}}; +} + +std::pair writeBoundary(std::string varname) { + return {varname, + {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere, + Permissions::Boundaries}}; +} diff --git a/tests/unit/test_guarded_options.cxx b/tests/unit/test_guarded_options.cxx index 65dff00da..00a92e7ad 100644 --- a/tests/unit/test_guarded_options.cxx +++ b/tests/unit/test_guarded_options.cxx @@ -4,27 +4,17 @@ class GuardedOptionsTests : public testing::Test { protected: GuardedOptionsTests() - : permissions({{"species:he:AA", - {Permissions::AllRegions, Permissions::Nowhere, - Permissions::Nowhere, Permissions::Nowhere}}, - {"species:he:charge", - {Permissions::AllRegions, Permissions::Nowhere, - Permissions::Nowhere, Permissions::Nowhere}}, - {"species:he:density", - {Permissions::Nowhere, Permissions::AllRegions, - Permissions::Nowhere, Permissions::Nowhere}}, + : permissions({readIfSet("species:he:AA"), + readIfSet("species:he:charge"), + readOnly("species:he:density"), {"species:he:pressure", {Permissions::Nowhere, Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, - {"species:he:collision_frequency", - {Permissions::Nowhere, Permissions::Nowhere, Permissions::Nowhere, - Permissions::AllRegions}}, + writeFinal("species:he:collision_frequency"), {"species:he:velocity", {Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere, Permissions::Nowhere}}, - {"species:d", - {Permissions::Nowhere, Permissions::AllRegions, - Permissions::Nowhere, Permissions::Nowhere}}, + readOnly("species:d"), {"species:d:pressure", {Permissions::Nowhere, Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, diff --git a/tests/unit/test_permissions.cxx b/tests/unit/test_permissions.cxx index 00f6f1d34..d5f1e2619 100644 --- a/tests/unit/test_permissions.cxx +++ b/tests/unit/test_permissions.cxx @@ -7,27 +7,19 @@ auto make_permission = std::make_pair TEST(PermissionsTests, TestCanAccess) { Permissions example({ - {"species:he:charge", - {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere, - Permissions::Nowhere}}, - {"species:he:density", - {Permissions::Nowhere, Permissions::AllRegions, Permissions::Nowhere, - Permissions::Nowhere}}, + readIfSet("species:he:charge"), + readOnly("species:he:density"), // Read and write permissions for pressure in the interior region {"species:he:pressure", {Permissions::Nowhere, Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, // Set the final value for collision frequency - {"species:he:collision_frequency", - {Permissions::Nowhere, Permissions::Nowhere, Permissions::Nowhere, - Permissions::AllRegions}}, + writeFinal("species:he:collision_frequency"), // Only allow reading of boundary velocity {"species:he:velocity", {Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere, Permissions::Nowhere}}, - {"species:d", - {Permissions::Nowhere, Permissions::AllRegions, Permissions::Nowhere, - Permissions::Nowhere}}, + readOnly("species:d"), {"species:d:pressure", {Permissions::Nowhere, Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}}, From 97468915d1753910facecebbd794a7f35fd42a50 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Fri, 31 Oct 2025 19:12:46 +0000 Subject: [PATCH 09/63] Fixed broken tests --- tests/unit/test_permissions.cxx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/unit/test_permissions.cxx b/tests/unit/test_permissions.cxx index d5f1e2619..cd7a295ba 100644 --- a/tests/unit/test_permissions.cxx +++ b/tests/unit/test_permissions.cxx @@ -146,12 +146,14 @@ TEST(PermissionsTests, TestGetHighestPermission) { make_permission(Permissions::Read, "species:he:pressure")); EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency"), make_permission(Permissions::Final, "species:he:collision_frequency")); - EXPECT_EQ(example.getHighestPermission("species:he:velocity"), no_permission); - EXPECT_EQ(example.getHighestPermission("species:d:pressure"), no_permission); + EXPECT_EQ(example.getHighestPermission("species:he:velocity"), + make_permission(Permissions::None, "species:he:velocity")); + EXPECT_EQ(example.getHighestPermission("species:d:pressure"), + make_permission(Permissions::None, "species:d:pressure")); EXPECT_EQ(example.getHighestPermission("species:d:velocity"), make_permission(Permissions::Read, "species:d")); EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll"), - no_permission); + make_permission(Permissions::None, "species:d:collision_frequencies")); EXPECT_EQ(example.getHighestPermission("unset"), no_permission); // Get the highest permission on the boundaries @@ -167,9 +169,9 @@ TEST(PermissionsTests, TestGetHighestPermission) { EXPECT_EQ(example.getHighestPermission("species:he:velocity", Permissions::Boundaries), make_permission(Permissions::Read, "species:he:velocity")); EXPECT_EQ(example.getHighestPermission("species:d:pressure", Permissions::Boundaries), - no_permission); + make_permission(Permissions::None, "species:d:pressure")); EXPECT_EQ(example.getHighestPermission("species:d:velocity", Permissions::Boundaries), - no_permission); + make_permission(Permissions::Read, "species:d")); EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll", Permissions::Boundaries), make_permission(Permissions::Write, "species:d:collision_frequencies")); @@ -187,14 +189,14 @@ TEST(PermissionsTests, TestGetHighestPermission) { Permissions::Interior), make_permission(Permissions::Final, "species:he:collision_frequency")); EXPECT_EQ(example.getHighestPermission("species:he:velocity", Permissions::Interior), - no_permission); + make_permission(Permissions::None, "species:he:velocity")); EXPECT_EQ(example.getHighestPermission("species:d:pressure", Permissions::Interior), make_permission(Permissions::Write, "species:d:pressure")); EXPECT_EQ(example.getHighestPermission("species:d:velocity", Permissions::Interior), make_permission(Permissions::Read, "species:d")); EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll", Permissions::Interior), - no_permission); + make_permission(Permissions::None, "species:d:collision_frequencies")); EXPECT_EQ(example.getHighestPermission("unset", Permissions::Interior), no_permission); // Check the permission for the "Nowhere" region is always "None" From 8cd32fb88feb01b0b511c14757ce31565656553f Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Tue, 4 Nov 2025 15:48:54 +0000 Subject: [PATCH 10/63] Specify which variables are read/written for reaction components --- include/adas_carbon.hxx | 12 ++++--- include/adas_lithium.hxx | 10 ++++-- include/adas_neon.hxx | 10 ++++-- include/adas_reaction.hxx | 36 ++++++++++++++++++--- include/amjuel_reaction.hxx | 17 ++++++++++ include/component.hxx | 7 ++++ include/guarded_options.hxx | 4 +++ include/hydrogen_charge_exchange.hxx | 32 ++++++++++++++---- include/permissions.hxx | 4 +++ include/reaction.hxx | 3 +- include/solkit_hydrogen_charge_exchange.hxx | 13 ++++++-- src/guarded_options.cxx | 12 ++++++- src/hydrogen_charge_exchange.cxx | 13 ++++++-- src/ionisation.cxx | 10 +++++- src/permissions.cxx | 9 ++++-- src/reaction.cxx | 14 ++++++-- 16 files changed, 169 insertions(+), 37 deletions(-) diff --git a/include/adas_carbon.hxx b/include/adas_carbon.hxx index 2bf0338bf..730ac416a 100644 --- a/include/adas_carbon.hxx +++ b/include/adas_carbon.hxx @@ -37,7 +37,8 @@ constexpr std::initializer_list carbon_species_name<0>{'c'}; template struct ADASCarbonIonisation : public OpenADAS { ADASCarbonIonisation(std::string, Options& alloptions, Solver*) - : OpenADAS(alloptions["units"], "scd96_c.json", "plt96_c.json", level, + : OpenADAS(alloptions["units"], "scd96_c.json", "plt96_c.json", + carbon_species_name, carbon_species_name, level, -carbon_ionisation_energy[level]) {} private: @@ -59,10 +60,10 @@ template struct ADASCarbonRecombination : public OpenADAS { /// @param alloptions The top-level options. Only uses the ["units"] subsection. ADASCarbonRecombination(std::string, Options& alloptions, Solver*) - : OpenADAS(alloptions["units"], "acd96_c.json", "prb96_c.json", level, + : OpenADAS(alloptions["units"], "acd96_c.json", "prb96_c.json", + carbon_species_name, carbon_species_name, level, carbon_ionisation_energy[level]) {} - private: void transform_impl(GuardedOptions& state) override { calculate_rates( @@ -79,8 +80,9 @@ template struct ADASCarbonCX : public OpenADASChargeExchange { /// @param alloptions The top-level options. Only uses the ["units"] subsection. ADASCarbonCX(std::string, Options& alloptions, Solver*) - : OpenADASChargeExchange(alloptions["units"], "ccd96_c.json", level) {} - + : OpenADASChargeExchange(alloptions["units"], "ccd96_c.json", + carbon_species_name, {Hisotope}, + carbon_species_name, {Hisotope, '+'}, level) {} private: void transform_impl(GuardedOptions& state) override { diff --git a/include/adas_lithium.hxx b/include/adas_lithium.hxx index 1e56c9a01..476e62333 100644 --- a/include/adas_lithium.hxx +++ b/include/adas_lithium.hxx @@ -40,7 +40,8 @@ constexpr std::initializer_list lithium_species_name<0>{'l', 'i'}; template struct ADASLithiumIonisation : public OpenADAS { ADASLithiumIonisation(std::string, Options& alloptions, Solver*) - : OpenADAS(alloptions["units"], "scd96_li.json", "plt96_li.json", level, + : OpenADAS(alloptions["units"], "scd96_li.json", "plt96_li.json", + lithium_species_name, lithium_species_name, level, -lithium_ionisation_energy[level]) {} private: @@ -62,7 +63,8 @@ template struct ADASLithiumRecombination : public OpenADAS { /// @param alloptions The top-level options. Only uses the ["units"] subsection. ADASLithiumRecombination(std::string, Options& alloptions, Solver*) - : OpenADAS(alloptions["units"], "acd96_li.json", "prb96_li.json", level, + : OpenADAS(alloptions["units"], "acd96_li.json", "prb96_li.json", + lithium_species_name, lithium_species_name, level, lithium_ionisation_energy[level]) {} private: @@ -81,7 +83,9 @@ template struct ADASLithiumCX : public OpenADASChargeExchange { /// @param alloptions The top-level options. Only uses the ["units"] subsection. ADASLithiumCX(std::string, Options& alloptions, Solver*) - : OpenADASChargeExchange(alloptions["units"], "ccd89_li.json", level) {} + : OpenADASChargeExchange(alloptions["units"], "ccd89_li.json", + lithium_species_name, {Hisotope}, + lithium_species_name, {Hisotope, '+'}, level) {} private: void transform_impl(GuardedOptions& state) override { diff --git a/include/adas_neon.hxx b/include/adas_neon.hxx index bf14503b4..f2030868f 100644 --- a/include/adas_neon.hxx +++ b/include/adas_neon.hxx @@ -40,7 +40,8 @@ constexpr std::initializer_list neon_species_name<0>{'n', 'e'}; template struct ADASNeonIonisation : public OpenADAS { ADASNeonIonisation(std::string, Options& alloptions, Solver*) - : OpenADAS(alloptions["units"], "scd96_ne.json", "plt96_ne.json", level, + : OpenADAS(alloptions["units"], "scd96_ne.json", "plt96_ne.json", + neon_species_name, neon_species_name, level, -neon_ionisation_energy[level]) {} private: @@ -62,7 +63,8 @@ template struct ADASNeonRecombination : public OpenADAS { /// @param alloptions The top-level options. Only uses the ["units"] subsection. ADASNeonRecombination(std::string, Options& alloptions, Solver*) - : OpenADAS(alloptions["units"], "acd96_ne.json", "prb96_ne.json", level, + : OpenADAS(alloptions["units"], "acd96_ne.json", "prb96_ne.json", + neon_species_name, neon_species_name, level, neon_ionisation_energy[level]) {} private: @@ -81,7 +83,9 @@ template struct ADASNeonCX : public OpenADASChargeExchange { /// @param alloptions The top-level options. Only uses the ["units"] subsection. ADASNeonCX(std::string, Options& alloptions, Solver*) - : OpenADASChargeExchange(alloptions["units"], "ccd89_ne.json", level) {} + : OpenADASChargeExchange(alloptions["units"], "ccd89_ne.json", + neon_species_name, {Hisotope}, + neon_species_name, {Hisotope, '+'}, level) {} private: void transform_impl(GuardedOptions& state) override { diff --git a/include/adas_reaction.hxx b/include/adas_reaction.hxx index 9d944bbfa..a64de0584 100644 --- a/include/adas_reaction.hxx +++ b/include/adas_reaction.hxx @@ -53,16 +53,31 @@ struct OpenADAS : public ReactionBase { /// /// Notes /// - The rate and radiation file names have "json_database/" prepended - /// + /// OpenADAS(const Options& units, const std::string& rate_file, - const std::string& radiation_file, std::size_t level, BoutReal electron_heating) - : rate_coef(std::string("json_database/") + rate_file, level), + const std::string& radiation_file, std::string from_ion, std::string to_ion, + std::size_t level, BoutReal electron_heating) + : ReactionBase({readIfSet("species:{sp}:charge"), readOnly("species:{sp}:AA"), + readOnly("species:{from_ion}:{val}"), readOnly("species:e:{e_val}"), + readWrite("species:{sp}:{w_val}"), + readWrite("species:e:{ew_val}")}), + rate_coef(std::string("json_database/") + rate_file, level), radiation_coef(std::string("json_database/") + radiation_file, level), electron_heating(electron_heating) { // Get the units Tnorm = get(units["eV"]); Nnorm = get(units["inv_meters_cubed"]); FreqNorm = 1. / get(units["seconds"]); + state_variable_access.substitute("val", {"density", "temperature", "velocity"}); + state_variable_access.substitute("e_val", {"density", "temperature"}); + state_variable_access.substitute( + "w_val", {"density_source", "momentum_source", "energy_source"}); + // FIXME: There are hypothetically circumstances in which species:e:density_source + // will not be written + state_variable_access.substitute( + "ew_val", {"density_source", "momentum_source", "energy_source"}); + state_variable_access.substitute("sp", {from_ion, to_ion}); + state_variable_access.substitute("from_ion", {from_ion}); } /// Perform the calculation of rates, and transfer of particles/momentum/energy @@ -81,12 +96,23 @@ private: }; struct OpenADASChargeExchange : public ReactionBase { - OpenADASChargeExchange(const Options& units, const std::string& rate_file, std::size_t level) - : rate_coef(std::string("json_database/") + rate_file, level) { + OpenADASChargeExchange(const Options& units, const std::string& rate_file, + std::string from_A, std::string from_B, std::string to_A, + std::string to_B, std::size_t level) + : ReactionBase({readIfSet("species:{sp}:charge"), readOnly("species:{sp}:AA"), + readOnly("species:{from_ion}:{val}"), readOnly("species:e:{e_val}"), + readWrite("species:{sp}:{w_val}")}), + rate_coef(std::string("json_database/") + rate_file, level) { // Get the units Tnorm = get(units["eV"]); Nnorm = get(units["inv_meters_cubed"]); FreqNorm = 1. / get(units["seconds"]); + state_variable_access.substitute("val", {"density", "temperature", "velocity"}); + state_variable_access.substitute("e_val", {"density", "temperature"}); + state_variable_access.substitute( + "w_val", {"density_source", "momentum_source", "energy_source"}); + state_variable_access.substitute("sp", {from_A, from_B, to_A, to_B}); + state_variable_access.substitute("from_ion", {from_A, from_B}); } /// Perform charge exchange /// diff --git a/include/amjuel_reaction.hxx b/include/amjuel_reaction.hxx index e57c0d6cc..d7bd10cd2 100644 --- a/include/amjuel_reaction.hxx +++ b/include/amjuel_reaction.hxx @@ -41,6 +41,23 @@ struct AmjuelReaction : public Reaction { amjuel_data(get_json_db_dir(alloptions), short_reaction_type, amjuel_lbl) { this->includes_sigma_v_e = amjuel_data.includes_sigma_v_e; + // Most of the access information we need is inherited from the parent Reaction class. + // The electron velocity will be read if it is set + state_variable_access.setAccess("species:e:velocity", + {Permissions::AllRegions, Permissions::Nowhere, + Permissions::Nowhere, Permissions::Nowhere}); + // The energy source is set for electrons + state_variable_access.setAccess("species:e:energy_source", + {Permissions::Nowhere, Permissions::Nowhere, + Permissions::AllRegions, Permissions::Nowhere}); + std::string heavy_reactant = this->parser->get_species(species_filter::reactants, + species_filter::heavy)[0], + heavy_product = this->parser->get_species(species_filter::products, + species_filter::heavy)[0], + neutral = this->parser->get_species(species_filter::neutral)[0]; + state_variable_access.setAccess( + readWrite(fmt::format("species:{}:collision_frequencies:{}_{}_{}", neutral, + heavy_reactant, heavy_product, short_reaction_type))); } protected: diff --git a/include/component.hxx b/include/component.hxx index 4fb89283d..dd7c2c8a4 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -21,6 +21,13 @@ class Solver; // Time integrator /// (std::string name, Options &options, Solver *solver) /// struct Component { + Component() = default; + Component(Component& other) = default; + Component(Component&& other) = default; + + Component(Permissions&& access_permissions) + : state_variable_access(access_permissions) {} + virtual ~Component() {} /// Modify the given simulation state. This method will wrap the diff --git a/include/guarded_options.hxx b/include/guarded_options.hxx index 49cbc6662..e1d07e753 100644 --- a/include/guarded_options.hxx +++ b/include/guarded_options.hxx @@ -35,6 +35,7 @@ public: bool isSet(const std::string& name) const { return options->isSet(name); } bool isSet(const char* name) const { return (*this).isSet((std::string(name))); } bool isSet() const { return options->isSet(); } + std::string name() const { return options->name(); } /// Get read-only access to the underlying Options object. Throws /// BoutException if there is not read-permission for this object. @@ -51,6 +52,9 @@ public: /// have not been accessed using the `getWritable()` method. std::map unwrittenItems() const; + bool operator==(const GuardedOptions& other) const; + bool operator!=(const GuardedOptions& other) const; + private: Options* options{nullptr}; Permissions* permissions{nullptr}; diff --git a/include/hydrogen_charge_exchange.hxx b/include/hydrogen_charge_exchange.hxx index b65619190..1768bc8df 100644 --- a/include/hydrogen_charge_exchange.hxx +++ b/include/hydrogen_charge_exchange.hxx @@ -29,8 +29,13 @@ struct HydrogenChargeExchange : public ReactionBase { /// - eV /// - inv_meters_cubed /// - seconds - HydrogenChargeExchange([[maybe_unused]] std::string name, Options& alloptions, - Solver*) { + HydrogenChargeExchange([[maybe_unused]] std::string name, Options& alloptions, Solver*) + : ReactionBase({readOnly("species:{reactant}:{react_vals}"), + readOnly("species:{sp}:{read_vals}"), + readWrite("species:{sp}:{writevals}"), + readWrite("species:{reactant}:collision_frequency"), + readWrite("species:{atom}:collision_frequencies:{atom}_{ion}_cx"), + readWrite("species:{ion}:collision_frequencies:{ion}_{atom}_cx")}) { // Get the units const auto& units = alloptions["units"]; Tnorm = get(units["eV"]); @@ -49,11 +54,11 @@ protected: /// /// atom1 -> ion2, ion1 -> atom2 /// - /// Assumes that both atom1 and ion1 have: - /// - AA - /// - density - /// - velocity - /// - temperature + /// Assumes that species have: + /// - AA (all) + /// - density (atom1, ion1) + /// - velocity (all) + /// - temperature (atom2, ion2) /// /// Sets in all species: /// - density_source [If atom1 != atom2 or ion1 != ion2] @@ -138,6 +143,19 @@ struct HydrogenIsotopeChargeExchange : public HydrogenChargeExchange { rate_multiplier = alloptions[{Isotope1}]["K_cx_multiplier"] .doc("Scale the charge exchange rate by this factor") .withDefault(1.0); + + std::vector writevals = {"momentum_source", "energy_source"}; + if constexpr (Isotope1 != Isotope2) { + writevals.push_back("density_source"); + } + state_variable_access.substitute("reactant", {{Isotope1}, {Isotope2, '+'}}); + state_variable_access.substitute("react_vals", {"density", "temperature"}); + state_variable_access.substitute("read_vals", {"AA", "velocity"}); + state_variable_access.substitute( + "sp", {{Isotope1}, {Isotope2, '+'}, {Isotope1, '+'}, {Isotope2}}); + state_variable_access.substitute("writevals", writevals); + state_variable_access.substitute("atom", {{Isotope1}}); + state_variable_access.substitute("ion", {{Isotope2, '+'}}); } void outputVars(Options& state) override { diff --git a/include/permissions.hxx b/include/permissions.hxx index 5d853d008..829bb4d53 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -115,6 +115,10 @@ public: /// that section. Placeholder names can also be used. void setAccess(const std::string& variable, const AccessRights& rights); + void setAccess(const std::pair& info) { + setAccess(info.first, info.second); + } + /// Replace a placeholder in the names of variables stored in this /// object. This is useful if you need to access the same variable /// for multiple species. For example, the following code gives diff --git a/include/reaction.hxx b/include/reaction.hxx index 71ae91846..39277a208 100644 --- a/include/reaction.hxx +++ b/include/reaction.hxx @@ -15,7 +15,8 @@ typedef GuardedOptions (*OPTYPE)(GuardedOptions, Field3D); * all reaction classes have been refactored to inherit from Reaction. */ struct ReactionBase : public Component { - ReactionBase() : inst_num(get_instance_num() + 1) {} + ReactionBase(Permissions&& permissions) + : Component(std::move(permissions)), inst_num(get_instance_num() + 1) {} static int get_instance_num() { static int instance_num{0}; return instance_num++; diff --git a/include/solkit_hydrogen_charge_exchange.hxx b/include/solkit_hydrogen_charge_exchange.hxx index bdbcc89bc..4e3d958b6 100644 --- a/include/solkit_hydrogen_charge_exchange.hxx +++ b/include/solkit_hydrogen_charge_exchange.hxx @@ -12,7 +12,10 @@ struct SOLKITHydrogenChargeExchange : public ReactionBase { /// - units /// - inv_meters_cubed /// - seconds - SOLKITHydrogenChargeExchange(std::string, Options& alloptions, Solver*) { + SOLKITHydrogenChargeExchange(std::string, Options& alloptions, Solver*) + : ReactionBase({readIfSet("species:{sp}:velocity"), + readOnly("species:{sp}:{readvals}"), + readWrite("species:{sp}:momentum_source")}) { // Get the units const auto& units = alloptions["units"]; Nnorm = get(units["inv_meters_cubed"]); @@ -43,8 +46,12 @@ protected: /// @tparam Isotope The isotope ('h', 'd' or 't') of the atom and ion template struct SOLKITHydrogenChargeExchangeIsotope : public SOLKITHydrogenChargeExchange { - SOLKITHydrogenChargeExchangeIsotope(std::string name, Options& alloptions, Solver* solver) - : SOLKITHydrogenChargeExchange(name, alloptions, solver) {} + SOLKITHydrogenChargeExchangeIsotope(std::string name, Options& alloptions, + Solver* solver) + : SOLKITHydrogenChargeExchange(name, alloptions, solver) { + state_variable_access.substitute("sp", {{Isotope}, {Isotope, '+'}}); + state_variable_access.substitute("readvals", {"AA", "density"}); + } private: void transform_impl(GuardedOptions& state) override { diff --git a/src/guarded_options.cxx b/src/guarded_options.cxx index 2c82ea95e..836563bf4 100644 --- a/src/guarded_options.cxx +++ b/src/guarded_options.cxx @@ -101,7 +101,7 @@ Options& GuardedOptions::getWritable(Permissions::Regions region) { return *options; } } - throw BoutException("Do not have read permission for {}.", options->str()); + throw BoutException("Do not have write permission for {}.", options->str()); } std::map GuardedOptions::unreadItems() const { @@ -111,3 +111,13 @@ std::map GuardedOptions::unreadItems() const std::map GuardedOptions::unwrittenItems() const { return *unwritten_variables; } + +bool GuardedOptions::operator==(const GuardedOptions& other) const { + return std::tie(options, permissions, unread_variables, unwritten_variables) + == std::tie(other.options, other.permissions, other.unread_variables, + other.unwritten_variables); +} + +bool GuardedOptions::operator!=(const GuardedOptions& other) const { + return !(*this == other); +} diff --git a/src/hydrogen_charge_exchange.cxx b/src/hydrogen_charge_exchange.cxx index b0047fd78..8fb61e436 100644 --- a/src/hydrogen_charge_exchange.cxx +++ b/src/hydrogen_charge_exchange.cxx @@ -49,7 +49,7 @@ void HydrogenChargeExchange::calculate_rates(GuardedOptions atom1, GuardedOption R = Natom * Nion * sigmav; // Rate coefficient in [m^-3 s^-1] - if ((&atom1 != &atom2) or (&ion1 != &ion2)) { + if ((atom1 != atom2) or (ion1 != ion2)) { // Transfer particles atom1 -> ion2, ion1 -> atom2 subtract(atom1["density_source"], R); add(ion2["density_source"], R); @@ -102,7 +102,14 @@ void HydrogenChargeExchange::calculate_rates(GuardedOptions atom1, GuardedOption add(atom1["collision_frequency"], atom_rate); add(ion1["collision_frequency"], ion_rate); + std::cout << 1; // Set individual collision frequencies - set(atom1["collision_frequencies"][atom1.get().name() + std::string("_") + ion1.get().name() + std::string("_cx")], atom_rate); - set(ion1["collision_frequencies"][ion1.get().name() + std::string("_") + atom1.get().name() + std::string("_cx")], ion_rate); + set(atom1["collision_frequencies"] + [atom1.name() + std::string("_") + ion1.name() + std::string("_cx")], + atom_rate); + set(ion1["collision_frequencies"] + [ion1.name() + std::string("_") + atom1.name() + std::string("_cx")], + ion_rate); + std::cout << 2; + std::cout << "\n"; } diff --git a/src/ionisation.cxx b/src/ionisation.cxx index 7997b12e6..848ece60e 100644 --- a/src/ionisation.cxx +++ b/src/ionisation.cxx @@ -21,7 +21,15 @@ BoutReal ionisation_rate(BoutReal T) { } } // namespace -Ionisation::Ionisation(std::string name, Options &alloptions, Solver *) { +Ionisation::Ionisation(std::string name, Options& alloptions, Solver*) + : Component( + {readOnly("species:h:density"), readOnly("species:h:temperature"), + readOnly("species:h:velocity"), readOnly("species:h:AA"), + readOnly("species:e:density"), readOnly("species:e:temperature"), + readOnly("species:h+:AA"), readWrite("species:h:density_source"), + readWrite("species:h+:density_source"), readWrite("species:h:momentum_source"), + readWrite("species:h+:momentum_source"), readWrite("species:h:energy_source"), + readWrite("species:h+:energy_source"), readWrite("species:e:energy_source")}) { // Get options for this component auto& options = alloptions[name]; diff --git a/src/permissions.cxx b/src/permissions.cxx index 233d33069..14e2bb3b2 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -31,11 +31,14 @@ std::string replaceAll(const std::string& str, const std::string& from, void Permissions::substitute(const std::string& label, const std::vector& substitutions) { - for (const auto [varname, access] : variable_permissions) { + for (auto it = variable_permissions.begin(); it != variable_permissions.end();) { + const auto [varname, access] = *it; const std::string pattern = "{" + label + "}"; - if (varname.find(pattern) == std::string::npos) + if (varname.find(pattern) == std::string::npos) { + it++; continue; - variable_permissions.erase(varname); + } + it = variable_permissions.erase(it); for (const std::string& val : substitutions) { variable_permissions[replaceAll(varname, pattern, val)] = access; } diff --git a/src/reaction.cxx b/src/reaction.cxx index d25363e41..dcbf00615 100644 --- a/src/reaction.cxx +++ b/src/reaction.cxx @@ -9,7 +9,10 @@ #include "integrate.hxx" -Reaction::Reaction(std::string name, Options& options) : name(name) { +Reaction::Reaction(std::string name, Options& options) + : ReactionBase({readOnly("species:{sp}:{r_val}"), readOnly("species:e:{e_val}"), + readWrite("species:{sp}:{w_val}")}), + name(name) { // Extract some relevant options, units to member vars for readability const auto& units = options["units"]; @@ -38,11 +41,18 @@ Reaction::Reaction(std::string name, Options& options) : name(name) { // Parse the reaction string this->parser = std::make_unique(reaction_str); + std::vector species = this->parser->get_species(); // Participation factors. All set to unity for now; could make configurable in future. - for (const std::string& sp : this->parser->get_species()) { + for (const std::string& sp : species) { this->pfactors[sp] = 1; } + state_variable_access.substitute("sp", species); + state_variable_access.substitute("r_val", {"AA", "density", "velocity", "temperature"}); + state_variable_access.substitute("e_val", {"density", "temperature"}); + state_variable_access.substitute( + "w_val", {"momentum_source", "energy_source", "density_source"}); + // Initialise weight sums with dummy values. Real values are set on first call to // transform(). this->momentum_weightsum = -1; From 93942c323ec67e5ee1fe59cc9a4edd77865d661f Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Tue, 4 Nov 2025 17:35:33 +0000 Subject: [PATCH 11/63] Made convenience functions more flexible --- include/permissions.hxx | 20 ++++++++++++-------- src/permissions.cxx | 24 ++++++++++++------------ 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/include/permissions.hxx b/include/permissions.hxx index 829bb4d53..116b95a55 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -198,20 +198,24 @@ private: }; /// Convenience function to return an object expressing that the -/// variable should have ReadIfSet permissions on all regions. -std::pair readIfSet(std::string varname); +/// variable should have ReadIfSet permissions in the specified regions. +std::pair +readIfSet(std::string varname, Permissions::Regions region = Permissions::AllRegions); /// Convenience function to return an object expressing that the -/// variable should have Read permissions on all regions. -std::pair readOnly(std::string varname); +/// variable should have Read permissions in the specified regions. +std::pair +readOnly(std::string varname, Permissions::Regions region = Permissions::AllRegions); /// Convenience function to return an object expressing that the -/// variable should have Write permissions on all regions. -std::pair readWrite(std::string varname); +/// variable should have Write permissions in the specified regions. +std::pair +readWrite(std::string varname, Permissions::Regions region = Permissions::AllRegions); /// Convenience function to return an object expressing that the -/// variable should have Final permissions on all regions. -std::pair writeFinal(std::string varname); +/// variable should have Final permissions in the specified regions. +std::pair +writeFinal(std::string varname, Permissions::Regions region = Permissions::AllRegions); /// Convenience function to return an object expressing that the /// variable should have Write permissions on the boundaries. It will diff --git a/src/permissions.cxx b/src/permissions.cxx index 14e2bb3b2..4f4194449 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -133,28 +133,28 @@ std::string Permissions::regionNames(const Regions regions) { return result; } -std::pair readIfSet(std::string varname) { +std::pair readIfSet(std::string varname, + Permissions::Regions region) { return {varname, - {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere, - Permissions::Nowhere}}; + {region, Permissions::Nowhere, Permissions::Nowhere, Permissions::Nowhere}}; } -std::pair readOnly(std::string varname) { +std::pair readOnly(std::string varname, + Permissions::Regions region) { return {varname, - {Permissions::Nowhere, Permissions::AllRegions, Permissions::Nowhere, - Permissions::Nowhere}}; + {Permissions::Nowhere, region, Permissions::Nowhere, Permissions::Nowhere}}; } -std::pair readWrite(std::string varname) { +std::pair readWrite(std::string varname, + Permissions::Regions region) { return {varname, - {Permissions::Nowhere, Permissions::Nowhere, Permissions::AllRegions, - Permissions::Nowhere}}; + {Permissions::Nowhere, Permissions::Nowhere, region, Permissions::Nowhere}}; } -std::pair writeFinal(std::string varname) { +std::pair +writeFinal(std::string varname, Permissions::Regions region) { return {varname, - {Permissions::Nowhere, Permissions::Nowhere, Permissions::Nowhere, - Permissions::AllRegions}}; + {Permissions::Nowhere, Permissions::Nowhere, Permissions::Nowhere, region}}; } std::pair writeBoundary(std::string varname) { From 7649fbce3c1d8291b816e5be8f9dbee314864391 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Tue, 4 Nov 2025 17:56:52 +0000 Subject: [PATCH 12/63] Substitute for all_species --- include/component.hxx | 14 +++++++++++++- src/component_scheduler.cxx | 7 +++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/include/component.hxx b/include/component.hxx index dd7c2c8a4..e6fe5d2e2 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -25,6 +25,11 @@ struct Component { Component(Component& other) = default; Component(Component&& other) = default; + /// Initialise the `state_variable_acceess` permissions. Note that + /// `{all_species}` in any variable names will be replaced with the + /// names of all species being simulated (by claling + /// `declareAllSpecies()`, which is done after all components are + /// created by a ComponentSchedular). Component(Permissions&& access_permissions) : state_variable_access(access_permissions) {} @@ -59,8 +64,15 @@ struct Component { Options &options, // Component settings: options[name] are specific to this component Solver *solver); // Time integration solver + /// Tell the component the name of all species in the simulation. It + /// will use this information to substitute these for the + /// "all_species" label in `svate_variable_access`. + void declareAllSpecies(const std::vector& species) { + state_variable_access.substitute("all_species", species); + } + protected: - /// Information on which state variables the transform method will read and write + /// Information on which state variables the transform method will read and write. Permissions state_variable_access; private: diff --git a/src/component_scheduler.cxx b/src/component_scheduler.cxx index 9f3c0c48c..bb9d925cf 100644 --- a/src/component_scheduler.cxx +++ b/src/component_scheduler.cxx @@ -10,6 +10,8 @@ ComponentScheduler::ComponentScheduler(Options &scheduler_options, .doc("Components in order of execution") .as(); + std::vector species; + // For now split on ','. Something like "->" might be better for (const auto &name : strsplit(component_names, ',')) { // Ignore brackets, to allow these to be used to span lines. @@ -20,6 +22,8 @@ ComponentScheduler::ComponentScheduler(Options &scheduler_options, continue; } + if (component_options[name_trimmed].isSet("AA")) species.push_back(name_trimmed); + // For each component e.g. "e", several Component types can be created // but if types are not specified then the component name is used std::string types = component_options[name_trimmed].isSet("type") @@ -38,6 +42,9 @@ ComponentScheduler::ComponentScheduler(Options &scheduler_options, solver)); } } + for (auto& component : components) { + component->declareAllSpecies(species); + } } std::unique_ptr ComponentScheduler::create(Options &scheduler_options, From 06becad2f785fd00479672ca20c78495dc67609a Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Wed, 5 Nov 2025 13:34:51 +0000 Subject: [PATCH 13/63] Fix access permissions in more components and update tests --- include/anomalous_diffusion.hxx | 1 + include/detachment_controller.hxx | 21 ++++++++++++++--- include/electron_force_balance.hxx | 11 ++++++++- src/anomalous_diffusion.cxx | 27 ++++++++++++++++++++-- src/binormal_stpm.cxx | 11 ++++++++- src/classical_diffusion.cxx | 21 ++++++++++++++++- src/diamagnetic_drift.cxx | 15 +++++++++++- src/electromagnetic.cxx | 11 ++++++++- src/electron_force_balance.cxx | 4 ++-- tests/unit/test_component.cxx | 8 +++++-- tests/unit/test_component_scheduler.cxx | 11 ++++++--- tests/unit/test_electron_force_balance.cxx | 3 +++ 12 files changed, 127 insertions(+), 17 deletions(-) diff --git a/include/anomalous_diffusion.hxx b/include/anomalous_diffusion.hxx index 8c223c16f..56a777013 100644 --- a/include/anomalous_diffusion.hxx +++ b/include/anomalous_diffusion.hxx @@ -40,6 +40,7 @@ private: /// Inputs /// - species /// - + /// - AA /// - density /// - temperature (optional) /// - velocity (optional) diff --git a/include/detachment_controller.hxx b/include/detachment_controller.hxx index 16831347c..60c943fdf 100644 --- a/include/detachment_controller.hxx +++ b/include/detachment_controller.hxx @@ -9,8 +9,11 @@ struct DetachmentController : public Component { - DetachmentController(std::string, Options& options, Solver*) { -ASSERT0(BoutComm::size() == 1); // Only works on one processor + DetachmentController(std::string, Options& options, Solver*) + : Component({readOnly("species:{neutral}:density", Permissions::Interior), + readOnly("species:e:density", Permissions::Interior), readOnly("time"), + readWrite("species:{sp}:{output}")}) { + ASSERT0(BoutComm::size() == 1); // Only works on one processor Options& detachment_controller_options = options["detachment_controller"]; const auto& units = options["units"]; @@ -153,7 +156,19 @@ ASSERT0(BoutComm::size() == 1); // Only works on one processor detachment_controller_options["debug"] .doc("Print debugging information to the screen (0 for none, 1 for basic, 2 for extensive).") .withDefault(0); - + + state_variable_access.substitute("neutral", {neutral_species}); + state_variable_access.substitute( + "sp", std::vector(species_list.begin(), species_list.end())); + std::string output; + if (control_mode == control_power) { + output = "energy_source"; + } else if (control_mode == control_particles) { + output = "density_source"; + } else { + ASSERT2(false); + } + state_variable_access.substitute("output", {output}); }; void outputVars(Options& state) override { diff --git a/include/electron_force_balance.hxx b/include/electron_force_balance.hxx index 646926508..a13213952 100644 --- a/include/electron_force_balance.hxx +++ b/include/electron_force_balance.hxx @@ -19,7 +19,16 @@ /// components which impose forces on electrons /// struct ElectronForceBalance : public Component { - ElectronForceBalance(std::string name, Options& alloptions, Solver*) { + ElectronForceBalance(std::string name, Options& alloptions, Solver*) + : Component({readOnly("species:e:pressure"), + // FIXME: This is read unconditionally for electrons, only if set for + // everything else + readIfSet("species:{all_species}:density", Permissions::Interior), + // FIXME: This is read unconditionally for electrons, only if set for + // everything else + readIfSet("species:{all_species}:charge"), + // FIXME: Only written if density and charge have been set. + readWrite("species:{all_species}:momentum_source")}) { AUTO_TRACE(); auto& options = alloptions[name]; diagnose = options["diagnose"] diff --git a/src/anomalous_diffusion.cxx b/src/anomalous_diffusion.cxx index c01233629..939175400 100644 --- a/src/anomalous_diffusion.cxx +++ b/src/anomalous_diffusion.cxx @@ -7,7 +7,10 @@ using bout::globals::mesh; AnomalousDiffusion::AnomalousDiffusion(std::string name, Options& alloptions, Solver*) - : name(name) { + : Component({readOnly("species:{name}:density", Permissions::Interior), + readIfSet("species:{name}:{optional}", Permissions::Interior), + readWrite("species:{name}:{output}")}), + name(name) { // Normalisations const Options& units = alloptions["units"]; const BoutReal rho_s0 = units["meters"]; @@ -50,6 +53,27 @@ AnomalousDiffusion::AnomalousDiffusion(std::string name, Options& alloptions, So diagnose = alloptions[name]["diagnose"] .doc("Output additional diagnostics?") .withDefault(false); + + state_variable_access.substitute("name", {name}); + state_variable_access.substitute("optional", {"temperature", "velocity"}); + std::vector output_vars; + if (include_D) { + output_vars.push_back("density_source"); + output_vars.push_back("particle_flow_xlow"); + output_vars.push_back("particle_flow_ylow"); + } + if (include_D or include_chi) { + output_vars.push_back("energy_source"); + output_vars.push_back("energy_flow_xlow"); + output_vars.push_back("energy_flow_ylow"); + } + if (include_D or include_nu) { + state_variable_access.setAccess(readOnly(fmt::format("species:{}:AA", name))); + output_vars.push_back("momentum_source"); + output_vars.push_back("momentum_flow_xlow"); + output_vars.push_back("momentum_flow_ylow"); + } + state_variable_access.substitute("output", output_vars); } void AnomalousDiffusion::transform_impl(GuardedOptions& state) { @@ -168,4 +192,3 @@ void AnomalousDiffusion::outputVars(Options& state) { {"source", "anomalous_diffusion"}}); } } - diff --git a/src/binormal_stpm.cxx b/src/binormal_stpm.cxx index f3f67463f..843f1aff7 100644 --- a/src/binormal_stpm.cxx +++ b/src/binormal_stpm.cxx @@ -9,7 +9,12 @@ using bout::globals::mesh; BinormalSTPM::BinormalSTPM(std::string name, Options& alloptions, [[maybe_unused]] Solver* solver) - : name(name) { + : Component({ + readIfSet("species:{all_species}:{input}", Permissions::Interior), + readOnly("species:{all_species}:AA"), + readWrite("species:{all_species}:{output}"), + }), + name(name) { AUTO_TRACE(); auto& options = alloptions[name]; const Options& units = alloptions["units"]; @@ -45,6 +50,10 @@ BinormalSTPM::BinormalSTPM(std::string name, Options& alloptions, diagnose = options["diagnose"] .doc("Output diagnostics?") .withDefault(false); + + state_variable_access.substitute("input", {"density", "temperature", "momentum"}); + state_variable_access.substitute( + "output", {"energy_source", "momentum_source", "density_source"}); } void BinormalSTPM::transform_impl(GuardedOptions& state) { diff --git a/src/classical_diffusion.cxx b/src/classical_diffusion.cxx index 1ff09af9c..6a54f6e0f 100644 --- a/src/classical_diffusion.cxx +++ b/src/classical_diffusion.cxx @@ -2,7 +2,10 @@ #include -ClassicalDiffusion::ClassicalDiffusion(std::string name, Options& alloptions, Solver*) { +ClassicalDiffusion::ClassicalDiffusion(std::string name, Options& alloptions, Solver*) + : Component({readIfSet("species:{all_species}:{optional}"), + readOnly("species:e:{e_vals}"), + readWrite("species:{all_species}:{output}")}) { AUTO_TRACE(); Options& options = alloptions[name]; @@ -10,6 +13,22 @@ ClassicalDiffusion::ClassicalDiffusion(std::string name, Options& alloptions, So diagnose = options["diagnose"].doc("Output additional diagnostics?").withDefault(false); custom_D = options["custom_D"].doc("Custom diffusion coefficient override. -1: Off, calculate D normally").withDefault(-1); + + state_variable_access.substitute( + "optional", {"charge", "pressure", "density", "velocity", "temperature"}); + std::vector e_vals = {"AA", "density"}; + if (custom_D <= 0.) + e_vals.push_back("collision_frequency"); + state_variable_access.substitute("e_vals", e_vals); + // FIXME: momentum and energy sources are only set if velocity and + // temperature are defined (respectively). Collision frequency is + // only used if temperature is set. Nothing happens if the charge or + // density are unset. + state_variable_access.substitute( + "output", {"density_source", "momentum_source", "energy_source"}); + if (custom_D < 0.) + state_variable_access.setAccess( + readOnly("species:{all_species}:collision_frequency")); } void ClassicalDiffusion::transform_impl(GuardedOptions& state) { diff --git a/src/diamagnetic_drift.cxx b/src/diamagnetic_drift.cxx index c282507db..2fc080f01 100644 --- a/src/diamagnetic_drift.cxx +++ b/src/diamagnetic_drift.cxx @@ -6,7 +6,9 @@ using bout::globals::mesh; DiamagneticDrift::DiamagneticDrift(std::string name, Options& alloptions, - Solver* UNUSED(solver)) { + Solver* UNUSED(solver)) + : Component({readIfSet("species:{all_species}:{input}"), + readWrite("species:{all_species}:{output}")}) { // Get options for this component auto& options = alloptions[name]; @@ -55,6 +57,17 @@ DiamagneticDrift::DiamagneticDrift(std::string name, Options& alloptions, for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { Curlb_B.y(r.ind, mesh->yend + 1) = -Curlb_B.y(r.ind, mesh->yend); } + + // FIXME: density, pressure, and momentum will not be read even if + // they are defined if charge and temperature were not defined for + // that species. + state_variable_access.substitute( + "input", {"charge", "temperature", "density", "pressure", "momentum"}); + // FIXME: These will actually only be written if density, pressure, + // and momentum are set, respectively. They also require charge and + // temperature to have been set. + state_variable_access.substitute( + "output", {"density_source", "energy_source", "momentum_source"}); } void DiamagneticDrift::transform_impl(GuardedOptions& state) { diff --git a/src/electromagnetic.cxx b/src/electromagnetic.cxx index 258e4ab9d..2d5cb74cc 100644 --- a/src/electromagnetic.cxx +++ b/src/electromagnetic.cxx @@ -13,7 +13,13 @@ BOUT_OVERRIDE_DEFAULT_OPTION("electromagnetic:laplacian:rtol_accept", 1e-2); BOUT_OVERRIDE_DEFAULT_OPTION("electromagnetic:laplacian:atol_accept", 1e-6); BOUT_OVERRIDE_DEFAULT_OPTION("electromagnetic:laplacian:maxits", 1000); -Electromagnetic::Electromagnetic(std::string name, Options &alloptions, Solver* solver) { +Electromagnetic::Electromagnetic(std::string name, Options& alloptions, Solver* solver) + : Component({readIfSet("species:{all_species}:charge"), + writeFinal("species:{all_species}:momentum"), + writeFinal("species:{all_species}:velocity"), readOnly("time"), + readOnly("species:{all_species}:AA"), + readOnly("species:{all_species}:density", Permissions::Interior), + readWrite("fields:Apar")}) { AUTO_TRACE(); Options& units = alloptions["units"]; @@ -79,6 +85,9 @@ Electromagnetic::Electromagnetic(std::string name, Options &alloptions, Solver* magnetic_flutter = options["magnetic_flutter"] .doc("Set magnetic flutter terms (Apar_flutter)?") .withDefault(false); + + if (magnetic_flutter) + state_variable_access.setAccess(readWrite("fields:Apar_flutter")); } void Electromagnetic::restartVars(Options& state) { diff --git a/src/electron_force_balance.cxx b/src/electron_force_balance.cxx index 649111992..a2e32087b 100644 --- a/src/electron_force_balance.cxx +++ b/src/electron_force_balance.cxx @@ -8,7 +8,7 @@ using bout::globals::mesh; void ElectronForceBalance::transform_impl(GuardedOptions& state) { AUTO_TRACE(); - if (IS_SET(state["fields"]["phi"])) { + if (state["fields"].isSet("phi")) { // Here we use electron force balance to calculate the parallel electric field // rather than the electrostatic potential throw BoutException("Cannot calculate potential and use electron force balance\n"); @@ -41,7 +41,7 @@ void ElectronForceBalance::transform_impl(GuardedOptions& state) { } GuardedOptions species = allspecies[kv.first]; // Note: Need non-const - if (!(IS_SET(species["density"]) and IS_SET(species["charge"]))) { + if (!(species.isSet("density") and species.isSet("charge"))) { continue; // Needs both density and charge to experience a force } diff --git a/tests/unit/test_component.cxx b/tests/unit/test_component.cxx index fce7b6c27..f8ec11144 100644 --- a/tests/unit/test_component.cxx +++ b/tests/unit/test_component.cxx @@ -7,9 +7,13 @@ namespace { struct TestComponent : public Component { - TestComponent(const std::string&, Options&, Solver *) {} + TestComponent(const std::string&, Options&, Solver*) + : Component({readWrite("answer")}) {} + private: - void transform_impl(GuardedOptions& state) override { state["answer"].getWritable() = 42; } + void transform_impl(GuardedOptions& state) override { + state["answer"].getWritable() = 42; + } }; RegisterComponent registertestcomponent("testcomponent"); diff --git a/tests/unit/test_component_scheduler.cxx b/tests/unit/test_component_scheduler.cxx index af1c93879..c4e3c6f03 100644 --- a/tests/unit/test_component_scheduler.cxx +++ b/tests/unit/test_component_scheduler.cxx @@ -4,14 +4,19 @@ namespace { struct TestComponent : public Component { - TestComponent(const std::string&, Options&, Solver *) {} + TestComponent(const std::string&, Options&, Solver*) + : Component({readWrite("answer")}) {} + private: - void transform_impl(GuardedOptions& state) override { state["answer"].getWritable() = 42; } + void transform_impl(GuardedOptions& state) override { + state["answer"].getWritable() = 42; + } }; struct TestMultiply : public Component { - TestMultiply(const std::string&, Options&, Solver *) {} + TestMultiply(const std::string&, Options&, Solver*) + : Component({writeFinal("answer")}) {} private: void transform_impl(GuardedOptions& state) override { diff --git a/tests/unit/test_electron_force_balance.cxx b/tests/unit/test_electron_force_balance.cxx index e194944cf..d2a4b7b62 100644 --- a/tests/unit/test_electron_force_balance.cxx +++ b/tests/unit/test_electron_force_balance.cxx @@ -47,6 +47,7 @@ TEST_F(ElectronForceBalanceTest, ZeroPressureGradient) { options["species"]["h+"]["density"] = 1.0; options["species"]["h+"]["charge"] = 1.0; + component.declareAllSpecies({"e", "h+"}); component.transform(options); // Should have a momentum source, but zero because no pressure gradient @@ -69,6 +70,7 @@ TEST_F(ElectronForceBalanceTest, WithPressureGradient) { options["species"]["h+"]["density"] = 1.0; options["species"]["h+"]["charge"] = 1.0; + component.declareAllSpecies({"e", "h+"}); component.transform(options); // Should have a momentum source @@ -98,6 +100,7 @@ TEST_F(ElectronForceBalanceTest, ForceBalance) { options["species"]["ion"]["density"] = 1.0; options["species"]["ion"]["charge"] = 3.0; + component.declareAllSpecies({"e", "ion"}); component.transform(options); // Should give ion momentum source charge * E = 3 * 0.5 / 2.0 From 4e5a27308e91ec5ad721d8be47b059d04878079a Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Thu, 6 Nov 2025 15:30:26 +0000 Subject: [PATCH 14/63] Add access permissions for Braginskii closure --- include/braginskii_collisions.hxx | 2 +- include/braginskii_conduction.hxx | 40 +++++++++--------- include/braginskii_friction.hxx | 30 ++++++------- include/braginskii_heat_exchange.hxx | 34 +++++++-------- include/braginskii_ion_viscosity.hxx | 6 +++ include/braginskii_thermal_force.hxx | 13 +++++- include/component.hxx | 1 + src/braginskii_collisions.cxx | 44 +++++++++++--------- src/braginskii_conduction.cxx | 20 ++++++--- src/braginskii_electron_viscosity.cxx | 5 ++- src/braginskii_friction.cxx | 27 +++++++++--- src/braginskii_heat_exchange.cxx | 29 +++++++++---- src/braginskii_ion_viscosity.cxx | 16 ++++++- src/guarded_options.cxx | 23 ++++++---- src/permissions.cxx | 2 + tests/unit/test_braginskii_collisions.cxx | 5 ++- tests/unit/test_braginskii_conduction.cxx | 6 +++ tests/unit/test_braginskii_friction.cxx | 6 +++ tests/unit/test_braginskii_heat_exchange.cxx | 4 ++ tests/unit/test_braginskii_ion_viscosity.cxx | 5 +++ tests/unit/test_braginskii_thermal_force.cxx | 10 +++++ tests/unit/test_guarded_options.cxx | 26 ++++++++++-- 22 files changed, 246 insertions(+), 108 deletions(-) diff --git a/include/braginskii_collisions.hxx b/include/braginskii_collisions.hxx index bff9d013a..5303d72c6 100644 --- a/include/braginskii_collisions.hxx +++ b/include/braginskii_collisions.hxx @@ -69,7 +69,7 @@ private: /// Update collision frequencies, momentum and energy exchange /// nu_12 normalised frequency - void collide(Options& species1, Options& species2, const Field3D& nu_12, BoutReal momentum_coefficient); + void collide(GuardedOptions& species1, GuardedOptions& species2, const Field3D& nu_12); }; namespace { diff --git a/include/braginskii_conduction.hxx b/include/braginskii_conduction.hxx index 9a4e859c1..225cedb2d 100644 --- a/include/braginskii_conduction.hxx +++ b/include/braginskii_conduction.hxx @@ -37,26 +37,6 @@ struct BraginskiiConduction : public Component { /// BraginskiiConduction(const std::string& name, Options& alloptions, Solver*); - /// Calculate conduction of energy for each species where this has been turned on. - /// - /// Uses - /// - species - /// - - /// - AA - /// - collision_frequencies - /// - density - /// - temperature - /// - pressure - /// - /// Modifies - /// - species - /// - - /// - energy_source Conduction contribution to energy evolution - /// - kappa_par The parallel heat conduction coefficient - /// - energy_flow_ylow Energy flow diagnostics. - /// - void transform(Options& state) override; - /// Add extra fields for output, or set attributes e.g docstrings void outputVars(Options& state) override; @@ -76,6 +56,26 @@ private: all_flow_ylow_conduction; ///< Conduction energy flow diagnostics /// Save more diagnostics? std::map all_diagnose; + + /// Calculate conduction of energy for each species where this has been turned on. + /// + /// Uses + /// - species + /// - + /// - AA + /// - collision_frequencies + /// - density + /// - temperature + /// - pressure + /// + /// Modifies + /// - species + /// - + /// - energy_source Conduction contribution to energy evolution + /// - kappa_par The parallel heat conduction coefficient + /// - energy_flow_ylow Energy flow diagnostics. + /// + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/braginskii_friction.hxx b/include/braginskii_friction.hxx index e34cb2bc7..274877e72 100644 --- a/include/braginskii_friction.hxx +++ b/include/braginskii_friction.hxx @@ -19,6 +19,20 @@ struct BraginskiiFriction : public Component { /// BraginskiiFriction(const std::string& name, Options& alloptions, Solver*); + /// Add extra fields for output, or set attributes e.g docstrings + void outputVars(Options& state) override; + +private: + /// Include frictional heating term? + bool frictional_heating; + + /// Calculated friction heating and momentum rates saved for post-processing and use by + /// other components Saved in options, the BOUT++ dictionary-like object + Options friction_energy_channels, momentum_channels; + + /// Save more diagnostics? + bool diagnose; + /// Calculate transfer of momentum and energy between species due to /// friction arising from collisions. /// @@ -37,21 +51,7 @@ struct BraginskiiFriction : public Component { /// - momentum_source if species1 or species2 velocity is set /// - energy_source if velocity is set and frictional_heating /// - void transform(Options& state) override; - - /// Add extra fields for output, or set attributes e.g docstrings - void outputVars(Options& state) override; - -private: - /// Include frictional heating term? - bool frictional_heating; - - /// Calculated friction heating and momentum rates saved for post-processing and use by - /// other components Saved in options, the BOUT++ dictionary-like object - Options friction_energy_channels, momentum_channels; - - /// Save more diagnostics? - bool diagnose; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/braginskii_heat_exchange.hxx b/include/braginskii_heat_exchange.hxx index 3135dd76f..5bea5f781 100644 --- a/include/braginskii_heat_exchange.hxx +++ b/include/braginskii_heat_exchange.hxx @@ -16,35 +16,35 @@ struct BraginskiiHeatExchange : public Component { /// BraginskiiHeatExchange(const std::string& name, Options& alloptions, Solver*); + /// Add extra fields for output, or set attributes e.g docstrings + void outputVars(Options& state) override; + +private: + /// Calculated energy transfer for post-processing and use by other components + /// Saved in options, the BOUT++ dictionary-like object + Options energy_channels; + + /// Save more diagnostics? + bool diagnose; + /// Calculate thermal energy exchange between species due to collisions. /// /// Uses /// - species /// - /// - AA - /// - charge - /// - collision_frequencies + /// - charge (if set) + /// - collision_frequencies (if section) /// - density - /// - velocity + /// - temperature (if set) /// /// Modifies /// - species /// - - /// - momentum_source if species1 or species2 velocity is set - /// - energy_source if velocity is set and frictional_heating + /// - momentum_source if species1 or species2 temperature is set + /// - energy_source if temperature is set /// - void transform(Options& state) override; - - /// Add extra fields for output, or set attributes e.g docstrings - void outputVars(Options& state) override; - -private: - /// Calculated energy transfer for post-processing and use by other components - /// Saved in options, the BOUT++ dictionary-like object - Options energy_channels; - - /// Save more diagnostics? - bool diagnose; + void transform_impl(GuardedOptions& state) override; }; namespace { diff --git a/include/braginskii_ion_viscosity.hxx b/include/braginskii_ion_viscosity.hxx index 739f91bfc..9f51b1845 100644 --- a/include/braginskii_ion_viscosity.hxx +++ b/include/braginskii_ion_viscosity.hxx @@ -76,14 +76,20 @@ private: /// Inputs /// - species /// - (skips "e") + /// - charge /// - pressure (skips if not present) /// - velocity (skips if not present) /// - collision_frequency + /// - fields + /// - phi /// /// Sets in the state /// - species /// - /// - momentum_source + /// - energy_source + /// - fields + /// - DivJextra /// void transform_impl(GuardedOptions& state) override; }; diff --git a/include/braginskii_thermal_force.hxx b/include/braginskii_thermal_force.hxx index 55a11354c..a999da8de 100644 --- a/include/braginskii_thermal_force.hxx +++ b/include/braginskii_thermal_force.hxx @@ -27,7 +27,17 @@ /// - ion_ion : bool Include ion-ion elastic collisions? /// struct BraginskiiThermalForce : public Component { - BraginskiiThermalForce(const std::string& name, Options& alloptions, Solver*) { + BraginskiiThermalForce(const std::string& name, Options& alloptions, Solver*) + : Component({// FIXME: Not account for electron_ion or ion_ion settings + // FIXME: don't access charge for electrons + // FIXME: Don't read or write for neutrals + readIfSet("species:{all_species}:charge"), + readOnly("species:{all_species}:density", Permissions::Interior), + // FIXME: Only get temperature for electrons and light ions + readOnly("species:{all_species}:temperature"), + // FIXME: Don't access AA for electrons + readOnly("species:{all_species}:AA"), + readWrite("species:{all_species}:momentum_source")}) { Options& options = alloptions[name]; this->electron_ion = options["electron_ion"] .doc("Include electron-ion collisions?") @@ -60,6 +70,7 @@ private: /// - /// - charge [ Checks, skips species if not set ] /// - AA + /// - density /// - temperature [ If AA < 4 i.e. "light" species ] /// /// Outputs diff --git a/include/component.hxx b/include/component.hxx index e6fe5d2e2..ce0b90f45 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -69,6 +69,7 @@ struct Component { /// "all_species" label in `svate_variable_access`. void declareAllSpecies(const std::vector& species) { state_variable_access.substitute("all_species", species); + state_variable_access.substitute("all_species2", species); } protected: diff --git a/src/braginskii_collisions.cxx b/src/braginskii_collisions.cxx index 1d68bbbf1..29b1f4b22 100644 --- a/src/braginskii_collisions.cxx +++ b/src/braginskii_collisions.cxx @@ -20,8 +20,20 @@ #include "../include/component.hxx" #include "../include/hermes_utils.hxx" -BraginskiiCollisions::BraginskiiCollisions(const std::string& name, Options& alloptions, - Solver*) { +BraginskiiCollisions::BraginskiiCollisions(const std::string& name, Options& alloptions, Solver*) + // FIXME: Not everything is necessarily read/written, depending on which + // collisions are calculated. Ideally I would be able to distinguish between them. + // + // FIXME: temperature is used unconditionally for species that + // collide with electrons, but only if set for other species. + : Component({readOnly("species:{all_species}:temperature", Permissions::Interior), + readOnly("species:{all_species}:density", Permissions::Interior), + readOnly("species:{all_species}:AA"), + // FIXME: This isn't actually used for electrons + readIfSet("species:{all_species}:charge"), + readWrite("species:{all_species}:collision_frequency"), + readWrite("species:{all_species}:collision_frequencies:{all_species}_{" + "all_species2}_coll")}) { AUTO_TRACE(); const Options& units = alloptions["units"]; @@ -69,7 +81,8 @@ BraginskiiCollisions::BraginskiiCollisions(const std::string& name, Options& all /// /// Note: A* variables are used for atomic mass numbers; /// mass* variables are species masses in kg -void BraginskiiCollisions::collide(Options & species1, Options & species2, const Field3D& nu_12, BoutReal momentum_coefficient) { +void BraginskiiCollisions::collide(GuardedOptions& species1, GuardedOptions& species2, + const Field3D& nu_12) { AUTO_TRACE(); add(species1["collision_frequency"], nu_12); // Total collision frequency @@ -80,7 +93,7 @@ void BraginskiiCollisions::collide(Options & species1, Options & species2, const set(collision_rates[species1.name()][species2.name()], nu_12); // Individual collision frequency used for diagnostics - if (&species1 != &species2) { + if (species1 != species2) { // For collisions between different species // m_a n_a \nu_{ab} = m_b n_b \nu_{ba} @@ -147,7 +160,7 @@ void BraginskiiCollisions::transform_impl(GuardedOptions& state) { return nu; }); - collide(electrons.getWritable(), electrons.getWritable(), nu_ee / Omega_ci, 1.0); + collide(electrons, electrons, nu_ee / Omega_ci); continue; } @@ -200,15 +213,7 @@ void BraginskiiCollisions::transform_impl(GuardedOptions& state) { return nu; }); - // Coefficient in front of parallel momentum exchange - // This table is from Braginskii 1965 - BoutReal mom_coeff = - Zi == 1 ? 0.51 : - Zi == 2 ? 0.44 : - Zi == 3 ? 0.40 : - 0.38; // Note: 0.38 is for Zi=4; tends to 0.29 for Zi->infty - - collide(electrons.getWritable(), species.getWritable(), nu_ei / Omega_ci, mom_coeff); + collide(electrons, species, nu_ei / Omega_ci); } else if (species.isSet("charge") and (get(species["charge"]) < 0.0)) { //////////////////////////////////// @@ -240,7 +245,7 @@ void BraginskiiCollisions::transform_impl(GuardedOptions& state) { return vth_e * Nnorm * Nn[i] * a0 * rho_s0; }); - collide(electrons.getWritable(), species.getWritable(), nu_en, 1.0); + collide(electrons, species, nu_en); } } } @@ -270,7 +275,6 @@ void BraginskiiCollisions::transform_impl(GuardedOptions& state) { species1.isSet("temperature") ? GET_NOBOUNDARY(Field3D, species1["temperature"]) * Tnorm : 0.0; - const Field3D density1 = GET_NOBOUNDARY(Field3D, species1["density"]) * Nnorm; const BoutReal AA1 = get(species1["AA"]); @@ -342,7 +346,7 @@ void BraginskiiCollisions::transform_impl(GuardedOptions& state) { }); // Update the species collision rates, momentum & energy exchange - collide(species1.getWritable(), species2.getWritable(), nu_12 / Omega_ci, 1.0); + collide(species1, species2, nu_12 / Omega_ci); } else { // species1 charged, species2 neutral @@ -363,7 +367,7 @@ void BraginskiiCollisions::transform_impl(GuardedOptions& state) { return vrel * density2[i] * a0 * rho_s0; }); - collide(species1.getWritable(), species2.getWritable(), nu_12, 1.0); + collide(species1, species2, nu_12); } } } else { @@ -411,7 +415,7 @@ void BraginskiiCollisions::transform_impl(GuardedOptions& state) { return vrel * density2[i] * a0 * rho_s0; }); - collide(species1.getWritable(), species2.getWritable(), nu_12, 1.0); + collide(species1, species2, nu_12); } else { // Both species neutral @@ -440,7 +444,7 @@ void BraginskiiCollisions::transform_impl(GuardedOptions& state) { return vrel * density2[i] * a0 * rho_s0; }); - collide(species1.getWritable(), species2.getWritable(), nu_12, 1.0); + collide(species1, species2, nu_12); } } } diff --git a/src/braginskii_conduction.cxx b/src/braginskii_conduction.cxx index 4bc604c03..f1ffbf95b 100644 --- a/src/braginskii_conduction.cxx +++ b/src/braginskii_conduction.cxx @@ -26,7 +26,11 @@ using bout::globals::mesh; BraginskiiConduction::BraginskiiConduction(const std::string&, Options& alloptions, - Solver*) { + Solver*) + // FIXME: state variables are only read and written for species that have collisions + : Component({readOnly("species:{all_species}:{input_vars}"), + writeBoundary("species:{all_species}:pressure"), + readWrite("species:{all_species}:{output_vars}")}) { AUTO_TRACE(); // Get settings for each species @@ -84,10 +88,16 @@ BraginskiiConduction::BraginskiiConduction(const std::string&, Options& alloptio .doc("Can be multispecies: all collisions, or " "braginskii: self collisions and ie") .withDefault("multispecies"); + + // FIXME: Should I try specifying exactly which collision frequencies are used? + state_variable_access.substitute( + "input_vars", {"AA", "density", "temperature", "collision_frequencies"}); + state_variable_access.substitute("output_vars", + {"energy_source", "kappa_par", "energy_flow_ylow"}); } } -void BraginskiiConduction::transform(Options& state) { +void BraginskiiConduction::transform_impl(GuardedOptions& state) { AUTO_TRACE(); for (auto& kv : state["species"].getChildren()) { @@ -97,8 +107,8 @@ void BraginskiiConduction::transform(Options& state) { continue; } /// Get the section containing this species - auto& species = state["species"][name]; - std::string const conduction_collisions_mode = all_conduction_collisions_mode[name]; + auto species = state["species"][name]; + const std::string conduction_collisions_mode = all_conduction_collisions_mode[name]; // Braginskii mode: plasma - self collisions and ei, neutrals - CX, IZ if (all_collision_names.count(name) == 0) { /// Calculate only once - at the beginning @@ -205,7 +215,7 @@ void BraginskiiConduction::transform(Options& state) { // FIXME: We end up applying these operations twice: here and in // EvolvePressure::finally - Field3D P = species["pressure"]; + Field3D P = GET_VALUE(Field3D, species["pressure"]); P.clearParallelSlices(); P.setBoundaryTo(get(species["pressure"])); Field3D const Pfloor = floor(P, 0.0); // Restricted to never go below zero diff --git a/src/braginskii_electron_viscosity.cxx b/src/braginskii_electron_viscosity.cxx index 9a5f584d7..beca6078c 100644 --- a/src/braginskii_electron_viscosity.cxx +++ b/src/braginskii_electron_viscosity.cxx @@ -19,7 +19,10 @@ #include "../include/component.hxx" BraginskiiElectronViscosity::BraginskiiElectronViscosity(const std::string& name, - Options& alloptions, Solver*) { + Options& alloptions, Solver*) + : Component({readIfSet("species:e:pressure"), readIfSet("species:e:velocity"), + readOnly("species:e:collision_frequency"), + readWrite("species:e:momentum_source")}) { auto& options = alloptions[name]; eta_limit_alpha = options["eta_limit_alpha"] diff --git a/src/braginskii_friction.cxx b/src/braginskii_friction.cxx index 19245be21..14617354c 100644 --- a/src/braginskii_friction.cxx +++ b/src/braginskii_friction.cxx @@ -13,7 +13,18 @@ #include "../include/component.hxx" BraginskiiFriction::BraginskiiFriction(const std::string& name, Options& alloptions, - Solver*) { + Solver*) + // FIXME: Not all species actually have collisions calculated + : Component({readOnly("species:{all_species}:density"), + readIfSet("species:{all_species}:velocity", Permissions::Interior), + readOnly("species:{all_species}:AA"), + readIfSet("species:{all_species}:charge"), + // FIXME: Need to add setting of all_species_2. Could result + // in doubling-up of settings, although I don't think that + // will actually cause any problems. + readOnly("species:{all_species}:collision_frequencies:{all_species}_{" + "all_species2}_coll"), + readWrite("species:{all_species}:momentum_source")}) { AUTO_TRACE(); Options& options = alloptions[name]; frictional_heating = options["frictional_heating"] @@ -21,6 +32,10 @@ BraginskiiFriction::BraginskiiFriction(const std::string& name, Options& allopti .withDefault(true); diagnose = options["diagnose"].doc("Output additional diagnostics?").withDefault(false); + + if (frictional_heating) { + state_variable_access.setAccess(readWrite("species:{all_species}:energy_source")); + } } BoutReal momentumCoefficient(BoutReal Zi) { @@ -38,10 +53,10 @@ BoutReal momentumCoefficient(const std::string& name1, BoutReal Z1, return 1.; } -void BraginskiiFriction::transform(Options& state) { +void BraginskiiFriction::transform_impl(GuardedOptions& state) { AUTO_TRACE(); - Options& allspecies = state["species"]; + GuardedOptions allspecies = state["species"]; // Iterate through all species // To avoid double counting, this needs to iterate over pairs @@ -55,9 +70,9 @@ void BraginskiiFriction::transform(Options& state) { // || species2 X X // \/ species3 X // - const std::map& children = allspecies.getChildren(); + const std::map children = allspecies.getChildren(); for (auto kv1 = std::begin(children); kv1 != std::end(children); ++kv1) { - Options& species1 = allspecies[kv1->first]; + GuardedOptions species1 = allspecies[kv1->first]; // If collisions were not calculated for this species, skip it. if (not species1.isSection("collision_frequencies")) { continue; @@ -80,7 +95,7 @@ void BraginskiiFriction::transform(Options& state) { continue; } - Options& species2 = allspecies[kv2->first]; + GuardedOptions species2 = allspecies[kv2->first]; // At least one of the species must have a velocity for there to be friction. if (!(isSetFinalNoBoundary(species1["velocity"]) diff --git a/src/braginskii_heat_exchange.cxx b/src/braginskii_heat_exchange.cxx index 14cb211cd..09ae45fc7 100644 --- a/src/braginskii_heat_exchange.cxx +++ b/src/braginskii_heat_exchange.cxx @@ -13,17 +13,29 @@ #include "../include/component.hxx" BraginskiiHeatExchange::BraginskiiHeatExchange(const std::string& name, - Options& alloptions, Solver*) { + Options& alloptions, Solver*) + // FIXME: Not all species are actually read or written; only those with collision + // rates and temperatures + : Component({readOnly("species:{all_species}:{input_vars}"), + readIfSet("species:{all_species}:{optional_vars}"), + readWrite("species:{all_species}:{output_vars}")}) { AUTO_TRACE(); diagnose = alloptions[name]["diagnose"] .doc("Output additional diagnostics?") .withDefault(false); + state_variable_access.substitute("input_vars", {"AA", "density"}); + // FIXME: We don't access the self-collision rate + state_variable_access.substitute( + "optional_vars", + {"charge", "collision_frequencies:{all_species}_{all_species2}_coll", + "temperature"}); + state_variable_access.substitute("output_vars", {"momentum_source", "energy_source"}); } -void BraginskiiHeatExchange::transform(Options& state) { +void BraginskiiHeatExchange::transform_impl(GuardedOptions& state) { AUTO_TRACE(); - Options& allspecies = state["species"]; + GuardedOptions allspecies = state["species"]; // Iterate through all species // To avoid double counting, this needs to iterate over pairs @@ -37,9 +49,9 @@ void BraginskiiHeatExchange::transform(Options& state) { // || species2 X X // \/ species3 X // - const std::map& children = allspecies.getChildren(); + const std::map children = allspecies.getChildren(); for (auto kv1 = std::begin(children); kv1 != std::end(children); ++kv1) { - Options& species1 = allspecies[kv1->first]; + GuardedOptions species1 = allspecies[kv1->first]; // If collisions were not calculated for this species, skip it. if (not species1.isSection("collision_frequencies")) { continue; @@ -54,16 +66,17 @@ void BraginskiiHeatExchange::transform(Options& state) { // Copy the iterator, so we don't iterate over the // lower half of the matrix, but start at the diagonal - for (std::map::const_iterator kv2 = kv1; + for (std::map::const_iterator kv2 = kv1; kv2 != std::end(children); ++kv2) { // Can't have heat exchange with oneself if (kv1->first == kv2->first) { continue; } - Options& species2 = allspecies[kv2->first]; + GuardedOptions species2 = allspecies[kv2->first]; - // At least one of the species must have a velocity for there to be friction. + // At least one of the species must have a temperature for there to be heat + // exchange. if (!(species1.isSet("temperature") or species2.isSet("temperature"))) { continue; } diff --git a/src/braginskii_ion_viscosity.cxx b/src/braginskii_ion_viscosity.cxx index 672704198..71248bdb6 100644 --- a/src/braginskii_ion_viscosity.cxx +++ b/src/braginskii_ion_viscosity.cxx @@ -29,8 +29,20 @@ using bout::globals::mesh; -BraginskiiIonViscosity::BraginskiiIonViscosity(const std::string& name, - Options& alloptions, Solver*) { +BraginskiiIonViscosity::BraginskiiIonViscosity(cons tstd::string& name, + Options& alloptions, Solver*) + // FIXME: does not read or write electron data + : Component({ + readIfSet("species:{all_species}:pressure"), + readIfSet("species:{all_species}:velocity"), + readIfSet("species:{all_species}:charge"), + // FIXME: This specifies more collision frequencies than are actually read + readOnly("species:{all_species}:collision_frequencies"), + readOnly("fields:phi"), + readWrite("species:{all_species}:momentum_source"), + readWrite("species:{all_species}:energy_source"), + readWrite("fields:DivJextra"), + }) { auto& options = alloptions[name]; eta_limit_alpha = options["eta_limit_alpha"] diff --git a/src/guarded_options.cxx b/src/guarded_options.cxx index 836563bf4..a1312372c 100644 --- a/src/guarded_options.cxx +++ b/src/guarded_options.cxx @@ -2,6 +2,20 @@ #include "../include/guarded_options.hxx" +/// Check whether an option is set, without creating any parent +/// Options objects in the process. +bool isSetRecursive(Options& opt, std::string varname) { + size_t colon = varname.find(":"); + if (colon == std::string::npos) { + return opt.isSet(varname); + } + std::string fragment = varname.substr(0, colon); + if (not opt.isSection(fragment)) { + return false; + } + return isSetRecursive(opt[fragment], varname.substr(colon + 1)); +} + GuardedOptions::GuardedOptions(Options* options, Permissions* permissions) : options(options), permissions(permissions), unread_variables(std::make_shared>()), @@ -15,14 +29,7 @@ GuardedOptions::GuardedOptions(Options* options, Permissions* permissions) if (options != nullptr) { for (auto& [varname, region] : permissions->getVariablesWithPermission(Permissions::ReadIfSet)) { - // find the last component of the full varname path - size_t last_colon = varname.find_last_of(":"); - Options& parent = (last_colon != std::string::npos) - ? (*options)[varname.substr(0, last_colon)] - : *options; - if (parent.isSet((last_colon != std::string::npos) - ? varname.substr(last_colon + 1) - : varname)) { + if (isSetRecursive(*options, varname)) { unread_variables->insert({varname, region}); } } diff --git a/src/permissions.cxx b/src/permissions.cxx index 4f4194449..20d0b96e2 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -45,6 +45,8 @@ void Permissions::substitute(const std::string& label, } } +#include + std::pair Permissions::bestMatchRights(const std::string& variable) const { Permissions::AccessRights best_candidate = {Permissions::Nowhere, Permissions::Nowhere, diff --git a/tests/unit/test_braginskii_collisions.cxx b/tests/unit/test_braginskii_collisions.cxx index 56462d4b7..b735ed953 100644 --- a/tests/unit/test_braginskii_collisions.cxx +++ b/tests/unit/test_braginskii_collisions.cxx @@ -46,6 +46,7 @@ TEST_F(BraginskiiCollisionsTest, OnlyElectrons) { state["species"]["e"]["density"] = 1e19; state["species"]["e"]["temperature"] = 10.; + component.declareAllSpecies({"e"}); component.transform(state); ASSERT_TRUE(state["species"]["e"].isSet("collision_frequency")); @@ -67,7 +68,6 @@ TEST_F(BraginskiiCollisionsTest, OneOrTwoSpeciesCharged) { state1["species"]["s1"]["charge"] = 1; state1["species"]["s1"]["AA"] = 2; - // State with two species, both the same but half the density Options state2; state2["species"]["s1"]["density"] = 5e18; // Half density state2["species"]["s1"]["temperature"] = 10; @@ -77,6 +77,7 @@ TEST_F(BraginskiiCollisionsTest, OneOrTwoSpeciesCharged) { state2["species"]["s2"] = state2["species"]["s1"].copy(); // Run calculations + component.declareAllSpecies({"s1", "s2"}); component.transform(state1); component.transform(state2); @@ -115,6 +116,7 @@ TEST_F(BraginskiiCollisionsTest, TnormDependence) { {"d+", {{"density", 2e19}, {"temperature", 20}, {"charge", 1}, {"AA", 2}}}, {"d", {{"density", 1e18}, {"temperature", 3}, {"AA", 2}}}}}}; + component.declareAllSpecies({"e", "d", "d+"}); component.transform(state); ASSERT_TRUE(state["species"]["e"].isSet("collision_frequency")); @@ -154,6 +156,7 @@ TEST_F(BraginskiiCollisionsTest, TnormDependence) { {{"density", 2e19}, {"temperature", 20 / Tnorm}, {"charge", 1}, {"AA", 2}}}, {"d", {{"density", 1e18}, {"temperature", 3 / Tnorm}, {"AA", 2}}}}}}; + component2.declareAllSpecies({"e", "d", "d+"}); component2.transform(state2); // Normalised frequencies should be unchanged diff --git a/tests/unit/test_braginskii_conduction.cxx b/tests/unit/test_braginskii_conduction.cxx index 4caa87e1b..9b110d6f0 100644 --- a/tests/unit/test_braginskii_conduction.cxx +++ b/tests/unit/test_braginskii_conduction.cxx @@ -91,6 +91,7 @@ TEST_F(BraginskiiConductionTest, ConductionGradientScaling) { state1["species"]["test+"]["temperature"] = this->temp1; state2["species"]["test+"]["temperature"] = this->temp2; + component.declareAllSpecies({"test+"}); component.transform(state0); component.transform(state1); component.transform(state2); @@ -116,7 +117,9 @@ TEST_F(BraginskiiConductionTest, ConductionKappaScaling) { state1["species"]["test+"]["temperature"] = this->temp1; Options state2 = state1.copy(); + component1.declareAllSpecies({"test+"}); component1.transform(state1); + component2.declareAllSpecies({"test+"}); component2.transform(state2); Field3D conduction1 = this->getDeriv(state1); @@ -146,6 +149,7 @@ TEST_F(BraginskiiConductionTest, ConductionCollisionScaling) { state2["species"]["test+"]["collision_frequencies"]["test+_test+_coll"] = 2.; state2["species"]["test+"]["collision_frequency"] = 2.; + component.declareAllSpecies({"test+"}); component.transform(state0); component.transform(state1); component.transform(state2); @@ -172,7 +176,9 @@ TEST_F(BraginskiiConductionTest, ConductionCollisionsMode) { state_brag["species"]["test+"]["collision_frequency"] = 1.0; Options state_multi = state_brag.copy(); + component_braginskii.declareAllSpecies({"test+"}); component_braginskii.transform(state_brag); + component_multispecies.declareAllSpecies({"test+"}); component_multispecies.transform(state_multi); Field3D conduction_brag = this->getDeriv(state_brag); diff --git a/tests/unit/test_braginskii_friction.cxx b/tests/unit/test_braginskii_friction.cxx index 6bdac0c0d..798ed460b 100644 --- a/tests/unit/test_braginskii_friction.cxx +++ b/tests/unit/test_braginskii_friction.cxx @@ -36,6 +36,7 @@ TEST_F(BraginskiiFrictionTest, OnlyElectrons) { state["species"]["e"]["AA"] = 1. / 1836; state["species"]["e"]["collision_frequencies"]["e_e_coll"] = 1.; + component.declareAllSpecies({"e"}); component.transform(state); // A species can't exert friction on itself, so momentum and energy transfer won't be @@ -73,6 +74,7 @@ TEST_F(BraginskiiFrictionTest, TwoComovingSpeciesCharged) { state["species"]["s2"]["collision_frequencies"]["s2_s1_coll"] = 0.25; // Run calculations + component.declareAllSpecies({"s1", "s2"}); component.transform(state); ASSERT_TRUE(state["species"]["s1"].isSet("momentum_source")); ASSERT_TRUE(state["species"]["s2"].isSet("momentum_source")); @@ -119,6 +121,7 @@ TEST_F(BraginskiiFrictionTest, TwoSpeciesCharged) { state["species"]["s2"]["collision_frequencies"]["s2_s1_coll"] = 0.25; // Run calculations + component.declareAllSpecies({"s1", "s2"}); component.transform(state); Field3D ms1 = get(state["species"]["s1"]["momentum_source"]); @@ -166,6 +169,7 @@ TEST_F(BraginskiiFrictionTest, DoubleRelativeVelocities) { state2["species"]["s2"]["velocity"] = 3; // Run calculations + component.declareAllSpecies({"s1", "s2"}); component.transform(state1); component.transform(state2); @@ -214,6 +218,7 @@ TEST_F(BraginskiiFrictionTest, TwoSpeciesNoHeating) { state["species"]["s2"]["velocity"] = 2; // Run calculations + component.declareAllSpecies({"s1", "s2"}); component.transform(state); ASSERT_TRUE(state["species"]["s1"]["momentum_source"].isSet()); @@ -251,6 +256,7 @@ TEST_F(BraginskiiFrictionTest, DoubleCollisionRate) { state2["species"]["s2"]["collision_frequencies"]["s2_s1_coll"] = 1.0; // Run calculations + component.declareAllSpecies({"s1", "s2"}); component.transform(state1); component.transform(state2); diff --git a/tests/unit/test_braginskii_heat_exchange.cxx b/tests/unit/test_braginskii_heat_exchange.cxx index e1b5ec388..a749bd987 100644 --- a/tests/unit/test_braginskii_heat_exchange.cxx +++ b/tests/unit/test_braginskii_heat_exchange.cxx @@ -36,6 +36,7 @@ TEST_F(BraginskiiHeatExchangeTest, OnlyElectrons) { state["species"]["e"]["AA"] = 1. / 1836; state["species"]["e"]["collision_frequencies"]["e_e_coll"] = 1.; + component.declareAllSpecies({"e"}); component.transform(state); // A species can't exchange heat with itself @@ -68,6 +69,7 @@ TEST_F(BraginskiiHeatExchangeTest, TwoEqualTempSpeciesCharged) { state["species"]["s2"]["collision_frequencies"]["s2_s1_coll"] = 0.25; // Run calculations + component.declareAllSpecies({"s1", "s2"}); component.transform(state); ASSERT_TRUE(state["species"]["s1"].isSet("energy_source")); ASSERT_TRUE(state["species"]["s2"].isSet("energy_source")); @@ -108,6 +110,7 @@ TEST_F(BraginskiiHeatExchangeTest, TwoSpeciesCharged) { state["species"]["s2"]["temperature"] = 20; // Run calculations + component.declareAllSpecies({"s1", "s2"}); component.transform(state); Field3D es1 = get(state["species"]["s1"]["energy_source"]); @@ -149,6 +152,7 @@ TEST_F(BraginskiiHeatExchangeTest, DoubleCollisionRates) { state2["species"]["s2"]["collision_frequencies"]["s2_s1_coll"] = 1.0; // Run calculations + component.declareAllSpecies({"s1", "s2"}); component.transform(state1); component.transform(state2); diff --git a/tests/unit/test_braginskii_ion_viscosity.cxx b/tests/unit/test_braginskii_ion_viscosity.cxx index c78b9e187..22eba5e96 100644 --- a/tests/unit/test_braginskii_ion_viscosity.cxx +++ b/tests/unit/test_braginskii_ion_viscosity.cxx @@ -68,6 +68,7 @@ TEST_F(BraginskiiIonViscosityTest, ViscosityPressureScaling) { state2["species"]["d+"]["pressure"] = 2 * state1["species"]["d+"]["pressure"].as(); + component.declareAllSpecies({"d+"}); component.transform(state1); component.transform(state2); @@ -98,6 +99,7 @@ TEST_F(BraginskiiIonViscosityTest, ViscosityCollisionScaling) { state2["species"]["d+"]["collision_frequencies"]["d+_he+_coll"] = 2 * state1["species"]["d+"]["collision_frequencies"]["d+_he+_coll"].as(); + component.declareAllSpecies({"d+"}); component.transform(state1); component.transform(state2); @@ -129,6 +131,7 @@ TEST_F(BraginskiiIonViscosityTest, ViscosityVelocityScaling) { state2["species"]["d+"]["velocity"] = 2 * state1["species"]["d+"]["velocity"].as(); + component.declareAllSpecies({"d+"}); component.transform(state0); component.transform(state1); component.transform(state2); @@ -161,7 +164,9 @@ TEST_F(BraginskiiIonViscosityTest, ViscosityCollisionMode) { options2["test2"]["viscosity_collisions_mode"] = "braginskii"; BraginskiiIonViscosity component2("test2", options2, nullptr); + component.declareAllSpecies({"d+"}); component.transform(state1); + component2.declareAllSpecies({"d+"}); component2.transform(state2); Field3D visc1 = state1["species"]["d+"]["momentum_source"]; diff --git a/tests/unit/test_braginskii_thermal_force.cxx b/tests/unit/test_braginskii_thermal_force.cxx index b59aa48ff..a07fbab84 100644 --- a/tests/unit/test_braginskii_thermal_force.cxx +++ b/tests/unit/test_braginskii_thermal_force.cxx @@ -52,6 +52,7 @@ TEST_F(BraginskiiThermalForceTest, OnlyElectrons) { state["species"]["e"]["charge"] = -1; state["species"]["e"]["AA"] = 1. / 1836; + component.declareAllSpecies({"e"}); component.transform(state); EXPECT_FALSE(state["species"]["e"]["momentum_source"].isSet()); } @@ -63,6 +64,7 @@ TEST_F(BraginskiiThermalForceTest, OnlyOneIon) { state["species"]["d+"]["charge"] = 1; state["species"]["d+"]["AA"] = 2.; + component.declareAllSpecies({"d+"}); component.transform(state); EXPECT_FALSE(state["species"]["d+"]["momentum_source"].isSet()); } @@ -78,6 +80,7 @@ TEST_F(BraginskiiThermalForceTest, ElectronIonBalance) { state["species"]["d+"]["charge"] = 1; state["species"]["d+"]["AA"] = 2.; + component.declareAllSpecies({"e", "d+"}); component.transform(state); Field3D mom_e = state["species"]["e"]["momentum_source"]; @@ -99,6 +102,7 @@ TEST_F(BraginskiiThermalForceTest, IonIonBalance) { state["species"]["d+"]["charge"] = 1; state["species"]["d+"]["AA"] = 2.; + component.declareAllSpecies({"c", "d+"}); component.transform(state); Field3D mom_c = state["species"]["c"]["momentum_source"]; @@ -132,6 +136,7 @@ TEST_F(BraginskiiThermalForceTest, NoNetForce) { state["species"]["e"]["charge"] = -1; state["species"]["e"]["AA"] = 1. / 1836; + component.declareAllSpecies({"c", "ar", "d", "d+", "e"}); component.transform(state); Field3D force(0.); for (const auto& [name, species] : state["species"].subsections()) { @@ -159,6 +164,7 @@ TEST_F(BraginskiiThermalForceTest, ElectronForceDensityScaling) { state["species"]["e"]["charge"] = -1; state["species"]["e"]["AA"] = 1. / 1836; + component.declareAllSpecies({"d1+", "d2+", "e"}); component.transform(state); Field3D mom1 = state["species"]["d1+"]["momentum_source"]; Field3D mom2 = state["species"]["d2+"]["momentum_source"]; @@ -184,6 +190,7 @@ TEST_F(BraginskiiThermalForceTest, ElectronForceChargeScaling) { state["species"]["e"]["charge"] = -1; state["species"]["e"]["AA"] = 1. / 1836; + component.declareAllSpecies({"d1+", "d2+", "e"}); component.transform(state); Field3D mom1 = state["species"]["d1+"]["momentum_source"]; Field3D mom2 = state["species"]["d2+"]["momentum_source"]; @@ -218,6 +225,7 @@ TEST_F(BraginskiiThermalForceTest, ElectronForceTemperatureGradScaling) { state1["species"]["d2+"]["charge"] = 1; state1["species"]["d2+"]["AA"] = 2.; + component.declareAllSpecies({"d+", "d2+", "e"}); component.transform(state0); component.transform(state1); component.transform(state2); @@ -255,6 +263,7 @@ TEST_F(BraginskiiThermalForceTest, IonIonForceTemperatureGradScaling) { state1["species"]["d+"]["temperature"] = grad1; state2["species"]["d+"]["temperature"] = grad2; + component.declareAllSpecies({"d+", "c1", "c2"}); component.transform(state1); component.transform(state2); @@ -281,6 +290,7 @@ TEST_P(BraginskiiThermalForceTest_MassRatio, CheckForIonMasses) { {"species", {{"M", {{"density", 1}, {"temperature", grad1}, {"charge", 1}, {"AA", aa1}}}, {"N", {{"density", 1}, {"temperature", grad2}, {"charge", 2}, {"AA", aa2}}}}}}; + component.declareAllSpecies({"M", "N"}); component.transform(state); if (thermal_force_present) { Field3D momentum_source1 = state["species"]["M"]["momentum_source"]; diff --git a/tests/unit/test_guarded_options.cxx b/tests/unit/test_guarded_options.cxx index 00a92e7ad..3c7cc4ee5 100644 --- a/tests/unit/test_guarded_options.cxx +++ b/tests/unit/test_guarded_options.cxx @@ -20,7 +20,9 @@ class GuardedOptionsTests : public testing::Test { Permissions::Nowhere}}, {"species:d:collision_frequencies", {Permissions::Nowhere, Permissions::Nowhere, - Permissions::Boundaries, Permissions::Nowhere}}}), + Permissions::Boundaries, Permissions::Nowhere}}, + readIfSet("fields:phi"), + readOnly("unused:option")}), opts({{"species", {{"he", {{"charge", 0}, @@ -136,11 +138,13 @@ TEST_F(GuardedOptionsTests, TestUnreadItems) { expected1 = {{"species:he:charge", Permissions::AllRegions}, {"species:he:density", Permissions::AllRegions}, {"species:he:velocity", Permissions::Boundaries}, - {"species:d", Permissions::AllRegions}}, + {"species:d", Permissions::AllRegions}, + {"unused:option", Permissions::AllRegions}}, expected2 = {{"species:he:charge", Permissions::AllRegions}, {"species:he:density", Permissions::Boundaries}, {"species:he:velocity", Permissions::Boundaries}, - {"species:d", Permissions::AllRegions}}, + {"species:d", Permissions::AllRegions}, + {"unused:option", Permissions::AllRegions}}, expected3 = {{"species:d", Permissions::AllRegions}}, expected4; EXPECT_EQ(guarded_opts.unreadItems(), expected1); @@ -154,6 +158,7 @@ TEST_F(GuardedOptionsTests, TestUnreadItems) { guarded_opts["species"]["he"]["charge"].get(); guarded_opts["species"]["he"]["density"].get(); guarded_opts["species:he:velocity"].get(Permissions::Boundaries); + EXPECT_FALSE(guarded_opts["unused"]["option"].get().isSet()); EXPECT_EQ(guarded_opts.unreadItems(), expected3); guarded_opts["species:d:velocity"].get(); @@ -242,3 +247,18 @@ TEST_F(GuardedOptionsTests, TestIsChildSet) { EXPECT_FALSE(guarded_opts.isSet("species:he:temperature")); EXPECT_TRUE(guarded_opts["species"]["d"]["collision_frequencies"].isSet("d_d_coll")); } + +TEST_F(GuardedOptionsTests, TestUnsetNotInChildren) { + // This is a test for a bug that was found in the initial implementation + auto children = guarded_opts.getChildren(); + EXPECT_EQ(children.count("fields"), 0); + EXPECT_EQ(children.count("unused"), 0); +} + +TEST_F(GuardedOptionsTests, TestEqual) { + EXPECT_EQ(guarded_opts, guarded_opts); + EXPECT_EQ(guarded_opts["species:he:pressure"], + guarded_opts["species"]["he"]["pressure"]); + EXPECT_NE(guarded_opts["species:he:pressure"], guarded_opts["species:he:velocity"]); + EXPECT_NE(guarded_opts, GuardedOptions(&opts, &permissions)); +} From cfaac45e7b56071b6efd95f73d8c3c5099bad775 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Fri, 7 Nov 2025 17:13:01 +0000 Subject: [PATCH 15/63] Add access permissions for remaining unit-tested components --- include/fixed_density.hxx | 4 +++- include/fixed_velocity.hxx | 8 ++++++- include/guarded_options.hxx | 5 +++++ include/permissions.hxx | 11 +++++++++ include/sheath_boundary.hxx | 6 +++-- include/sheath_closure.hxx | 6 +++-- include/snb_conduction.hxx | 14 ++++++++++-- include/sound_speed.hxx | 17 +++++++++++--- src/component.cxx | 22 ++++++++++++++++-- src/fixed_fraction_ions.cxx | 9 ++++++-- src/isothermal.cxx | 10 ++++++--- src/permissions.cxx | 13 ++++++++++- src/sheath_boundary.cxx | 35 ++++++++++++++++++++++++++++- src/sheath_closure.cxx | 18 ++++++++++++++- src/snb_conduction.cxx | 5 ----- src/zero_current.cxx | 7 +++++- tests/unit/test_permissions.cxx | 8 +++++-- tests/unit/test_sheath_boundary.cxx | 2 ++ tests/unit/test_sheath_closure.cxx | 5 ++++- tests/unit/test_snb_conduction.cxx | 12 +++++++--- tests/unit/test_sound_speed.cxx | 6 +++-- tests/unit/test_zero_current.cxx | 1 + 22 files changed, 189 insertions(+), 35 deletions(-) diff --git a/include/fixed_density.hxx b/include/fixed_density.hxx index 820dffe1d..bb18a060c 100644 --- a/include/fixed_density.hxx +++ b/include/fixed_density.hxx @@ -13,7 +13,7 @@ struct FixedDensity : public Component { /// - charge /// - density value (expression) in units of m^-3 FixedDensity(std::string name, Options& alloptions, Solver* UNUSED(solver)) - : name(name) { + : Component({readWrite("species:{name}:{vars}")}), name(name) { AUTO_TRACE(); auto& options = alloptions[name]; @@ -27,6 +27,8 @@ struct FixedDensity : public Component { // Get the density and normalise N = options["density"].as() / Nnorm; + state_variable_access.substitute("name", {name}); + state_variable_access.substitute("vars", {"AA", "charge", "density"}); } void outputVars(Options& state) override { diff --git a/include/fixed_velocity.hxx b/include/fixed_velocity.hxx index 48a295575..d8a8ed872 100644 --- a/include/fixed_velocity.hxx +++ b/include/fixed_velocity.hxx @@ -10,7 +10,10 @@ struct FixedVelocity : public Component { FixedVelocity(std::string name, Options& alloptions, Solver* UNUSED(solver)) - : name(name) { + : Component({readIfSet("species:{name}:density", Permissions::Interior), + // FIXME: AA is only read if density is set + readOnly("species:{name}:AA"), readWrite("species:{name}:{output}")}), + name(name) { AUTO_TRACE(); auto& options = alloptions[name]; @@ -28,6 +31,9 @@ struct FixedVelocity : public Component { // Option overrides mesh value // so use mesh value (if any) as default value. V = options["velocity"].withDefault(V) / Cs0; + state_variable_access.substitute("name", {name}); + // FIXME: Momentum is only written if density is set + state_variable_access.substitute("output", {"velocity", "momentum"}); } void outputVars(Options& state) override { diff --git a/include/guarded_options.hxx b/include/guarded_options.hxx index e1d07e753..4cfa3fe94 100644 --- a/include/guarded_options.hxx +++ b/include/guarded_options.hxx @@ -55,6 +55,11 @@ public: bool operator==(const GuardedOptions& other) const; bool operator!=(const GuardedOptions& other) const; + Permissions::PermissionTypes + getHighestPermission(Permissions::Regions region = Permissions::AllRegions) const { + return permissions->getHighestPermission(options->str(), region).first; + } + private: Options* options{nullptr}; Permissions* permissions{nullptr}; diff --git a/include/permissions.hxx b/include/permissions.hxx index 116b95a55..d803569c0 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -133,6 +133,9 @@ public: /// }); /// example.substitute("name", {"d", "d+", "t", "t+", "he", "he+", "c", "c+", "e"}); /// + /// Note that variable names which already have a permission set + /// will not be overwritten. + /// void substitute(const std::string& label, const std::vector& substitutions); @@ -222,3 +225,11 @@ writeFinal(std::string varname, Permissions::Regions region = Permissions::AllRe /// have Read permissions in the interior, as this is normally /// required to set the boundaries correctly. std::pair writeBoundary(std::string varname); + +/// Convenience function to return an object expressing that the +/// variable should have Write permissions on the boundaries. It will +/// have Read permissions in the interior if the interior is already set. +std::pair writeBoundaryIfSet(std::string varname); + +// FIXME: Ideally there would be some way to express write permissions only if setaccess +// FIXME: Ideally we could express to write a boundary only if the interior is set diff --git a/include/sheath_boundary.hxx b/include/sheath_boundary.hxx index 6cde28edb..0cd5f83a9 100644 --- a/include/sheath_boundary.hxx +++ b/include/sheath_boundary.hxx @@ -52,11 +52,11 @@ private: /// - temperature /// - pressure Optional /// - velocity Optional - /// - mass Optional + /// - AA Optional /// - adiabatic Optional. Ratio of specific heats, default 5/3. /// - if charge is set (i.e. not neutrals) /// - charge - /// - mass + /// - AA /// - density /// - temperature /// - pressure Optional @@ -71,11 +71,13 @@ private: /// - e /// - density Sets boundary /// - temperature Sets boundary + /// - pressure Sets boundary /// - velocity Sets boundary /// - energy_source /// - /// - density Sets boundary /// - temperature Sets boundary + /// - pressure Sets boundary /// - velocity Sets boundary /// - momentum Sets boundary /// - energy_source diff --git a/include/sheath_closure.hxx b/include/sheath_closure.hxx index 059dbf5b1..bbd0569d0 100644 --- a/include/sheath_closure.hxx +++ b/include/sheath_closure.hxx @@ -34,13 +34,15 @@ private: /// /// Optional inputs /// - species + /// - AA /// - density - /// - pressure + /// - temperature /// /// Modifies /// - species /// - e - /// - density_source (If density present) + /// - density_source + /// - energy_source (if temperature present) /// - density_source and energy_source (If sinks=true) /// - fields /// - DivJdia Divergence of current diff --git a/include/snb_conduction.hxx b/include/snb_conduction.hxx index 8126e28e1..0d8868757 100644 --- a/include/snb_conduction.hxx +++ b/include/snb_conduction.hxx @@ -46,10 +46,19 @@ struct SNBConduction : public Component { /// Inputs /// - /// - diagnose Saves Div_Q_SH and Div_Q_SNB - SNBConduction(std::string name, Options& alloptions, Solver*) : snb(alloptions[name]) { + SNBConduction(std::string name, Options& alloptions, Solver*) + : Component({readOnly("species:e:density"), readOnly("species:e:temperature"), + readWrite("species:e:energy_source")}), + snb(alloptions[name]) { AUTO_TRACE(); auto& options = alloptions[name]; + auto& units = alloptions["units"]; + rho_s0 = get(units["meters"]); + Tnorm = get(units["eV"]); + Nnorm = get(units["inv_meters_cubed"]); + Omega_ci = 1. / get(units["seconds"]); + diagnose = options["diagnose"] .doc("Save additional output diagnostics") .withDefault(false); @@ -59,6 +68,7 @@ struct SNBConduction : public Component { private: bout::HeatFluxSNB snb; + BoutReal rho_s0, Tnorm, Nnorm, Omega_ci; ///< Normalisations for units Field3D Div_Q_SH, Div_Q_SNB; ///< Divergence of heat fluxes bool diagnose; ///< Output additional diagnostics? @@ -67,7 +77,7 @@ private: /// - species /// - e /// - density - /// - collision_frequency + /// - temperature /// /// Sets /// - species diff --git a/include/sound_speed.hxx b/include/sound_speed.hxx index a126153ce..e00f89f70 100644 --- a/include/sound_speed.hxx +++ b/include/sound_speed.hxx @@ -11,7 +11,14 @@ /// This uses the sum of all species pressures and mass densities /// so should run after those have been set. struct SoundSpeed : public Component { - SoundSpeed(std::string name, Options &alloptions, Solver*) { + SoundSpeed(std::string name, Options& alloptions, Solver*) + : Component( + {readOnly("species:{all_species}:pressure", Permissions::Interior), + writeFinal("sound_speed"), writeFinal("fastest_wave"), + // FIXME: These are not read for electrons + readIfSet("species:{all_species}:AA"), + // FIXME: Only read if AA is set + readIfSet("species:{all_species}:{opt_inputs}", Permissions::Interior)}) { Options &options = alloptions[name]; electron_dynamics = options["electron_dynamics"] .doc("Include electron sound speed?") @@ -40,6 +47,8 @@ struct SoundSpeed : public Component { if (temperature_floor > 0.0) { temperature_floor /= get(alloptions["units"]["eV"]); } + + state_variable_access.substitute("opt_inputs", {"density", "temperature"}); } private: @@ -48,9 +57,10 @@ private: BoutReal beta_norm{0.0}; ///< Normalisation factor for Alfven speed BoutReal temperature_floor; ///< Minimum temperature when calculating speed BoutReal fastest_wave_factor; ///< Multiply the fastest wave by this factor - + /// This sets in the state - /// - sound_speed The collective sound speed, based on total pressure and total mass density + /// - sound_speed The collective sound speed, based on total pressure and total mass + /// density /// - fastest_wave The highest species sound speed at each point in the domain /// /// Optional inputs: @@ -59,6 +69,7 @@ private: /// - density /// - AA // Atomic mass /// - pressure + /// - temperature /// void transform_impl(GuardedOptions& state) override; }; diff --git a/src/component.cxx b/src/component.cxx index d2668e2c0..eb527bd51 100644 --- a/src/component.cxx +++ b/src/component.cxx @@ -37,7 +37,16 @@ bool isSetFinal(const Options& option, [[maybe_unused]] const std::string& locat } bool isSetFinal(const GuardedOptions option, const std::string& location) { - return isSetFinal(option.get(), location); + bool set = option.isSet(); +#if CHECKLEVEL >= 1 + Permissions::PermissionTypes perm = option.getHighestPermission(); + if (perm >= Permissions::Read or (perm == Permissions::ReadIfSet and set)) { + const Options& opt = option.get(); + const_cast(opt).attributes["final"] = location; + const_cast(opt).attributes["final-domain"] = location; + } +#endif + return set; } @@ -50,5 +59,14 @@ bool isSetFinalNoBoundary(const Options& option, [[maybe_unused]] const std::str } bool isSetFinalNoBoundary(const GuardedOptions option, const std::string& location) { - return isSetFinalNoBoundary(option.get(Permissions::Interior), location); + bool set = option.isSet(); +#if CHECKLEVEL >= 1 + Permissions::PermissionTypes perm = option.getHighestPermission(Permissions::Interior); + if (perm >= Permissions::Read or (perm == Permissions::ReadIfSet and set)) { + // Mark option as final inside the domain, but not in the boundary + const_cast(option.get(Permissions::Interior)).attributes["final-domain"] = + location; + } +#endif + return set; } diff --git a/src/fixed_fraction_ions.cxx b/src/fixed_fraction_ions.cxx index e1c2bebf7..09fa3d4f9 100644 --- a/src/fixed_fraction_ions.cxx +++ b/src/fixed_fraction_ions.cxx @@ -1,8 +1,9 @@ #include "../include/fixed_fraction_ions.hxx" -FixedFractionIons::FixedFractionIons(std::string name, Options &alloptions, - Solver *UNUSED(solver)) { +FixedFractionIons::FixedFractionIons(std::string name, Options& alloptions, + Solver* UNUSED(solver)) + : Component({readOnly("species:e:density"), readWrite("species:{sp}:density")}) { std::string fractions_str = alloptions[name]["fractions"] @@ -10,6 +11,8 @@ FixedFractionIons::FixedFractionIons(std::string name, Options &alloptions, "'species1@fraction1, species2@fraction2'") .as(); + std::vector specified_species; + for (const auto &pair : strsplit(fractions_str, ',')) { auto species_frac = strsplit(pair, '@'); if (species_frac.size() != 2) { @@ -20,12 +23,14 @@ FixedFractionIons::FixedFractionIons(std::string name, Options &alloptions, BoutReal fraction = stringToReal(trim(species_frac.back())); fractions.emplace_back(std::pair(species, fraction)); + specified_species.push_back(species); } // Check that there are some species if (fractions.size() == 0) { throw BoutException("No ion species specified. Got fractions = '%s'", fractions_str.c_str()); } + state_variable_access.substitute("sp", specified_species); } void FixedFractionIons::transform_impl(GuardedOptions& state) { diff --git a/src/isothermal.cxx b/src/isothermal.cxx index 0d68dac77..6ddffaa97 100644 --- a/src/isothermal.cxx +++ b/src/isothermal.cxx @@ -3,9 +3,13 @@ #include "../include/isothermal.hxx" -Isothermal::Isothermal(std::string name, Options &alloptions, - Solver *UNUSED(solver)) - : name(name) { +Isothermal::Isothermal(std::string name, Options& alloptions, Solver* UNUSED(solver)) + : Component( + {readIfSet(fmt::format("species:{}:density", name), Permissions::Interior), + readWrite(fmt::format("species:{}:temperature", name)), + // FIXME: This is only written if density is set + readWrite(fmt::format("species:{}:pressure", name))}), + name(name) { AUTO_TRACE(); Options& options = alloptions[name]; diff --git a/src/permissions.cxx b/src/permissions.cxx index 20d0b96e2..c40e7f366 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -40,7 +40,11 @@ void Permissions::substitute(const std::string& label, } it = variable_permissions.erase(it); for (const std::string& val : substitutions) { - variable_permissions[replaceAll(varname, pattern, val)] = access; + const std::string newname = replaceAll(varname, pattern, val); + // Do not overwrite permissiosn that are already set + if (variable_permissions.count(newname) == 0) { + variable_permissions[newname] = access; + } } } } @@ -164,3 +168,10 @@ std::pair writeBoundary(std::string varn {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere, Permissions::Boundaries}}; } + +std::pair +writeBoundaryIfSet(std::string varname) { + return {varname, + {Permissions::Interior, Permissions::Nowhere, Permissions::Nowhere, + Permissions::Boundaries}}; +} diff --git a/src/sheath_boundary.cxx b/src/sheath_boundary.cxx index 7b0d2208f..42899bc05 100644 --- a/src/sheath_boundary.cxx +++ b/src/sheath_boundary.cxx @@ -45,7 +45,23 @@ BoutReal limitFree(BoutReal fm, BoutReal fc) { } // namespace -SheathBoundary::SheathBoundary(std::string name, Options& alloptions, Solver*) { +SheathBoundary::SheathBoundary(std::string name, Options& alloptions, Solver*) + // FIXME: writeBoundaryIfSet doesn't really express that boundary + // should only be written if the interior is set. Instead it just + // give the permission readIfSet to the interior and writeFinal to + // the boundary. + : Component({ + readIfSet("species:e:{e_whole_domain}"), + writeBoundary("species:e:{e_boundary}"), + readWrite("species:e:energy_source"), + writeBoundaryIfSet("species:e:{e_optional}"), + // FIXME: These only applies to ions, not to all species + readIfSet("species:{all_species}:{ion_whole_domain}"), + readOnly("species:{all_species}:AA"), + readWrite("species:{all_species}:energy_source"), + writeBoundary("species:{all_species}:{ion_boundary}"), + writeBoundaryIfSet("species:{all_species}:{ion_optional}"), + }) { AUTO_TRACE(); Options& options = alloptions[name]; @@ -94,6 +110,23 @@ SheathBoundary::SheathBoundary(std::string name, Options& alloptions, Solver*) { floor_potential = options["floor_potential"] .doc("Apply a floor to wall potential when calculating Ve?") .withDefault(true); + + state_variable_access.substitute("e_whole_domain", {"AA", "charge", "adiabatic"}); + state_variable_access.substitute("e_boundary", {"density", "temperature"}); + state_variable_access.substitute("e_optional", {"pressure", "velocity"}); + state_variable_access.substitute("ion_whole_domain", {"charge", "adiabatic"}); + state_variable_access.substitute("ion_boundary", {"density", "temperature"}); + // FIXME: velocity and momentum will only be set on boundaries if already set on + // interior + state_variable_access.substitute("ion_optional", {"pressure", "velocity", "momentum"}); + // FIXME: The two results of the ternary are actually the same; need + // to change what writeBoundaryIfSet returns (and how we model + // permissions, for that matter) + state_variable_access.setAccess( + always_set_phi ? std::pair( + "fields:phi", {Permissions::Interior, Permissions::Nowhere, + Permissions::Nowhere, Permissions::Boundaries}) + : writeBoundaryIfSet("fields:phi")); } void SheathBoundary::transform_impl(GuardedOptions& state) { diff --git a/src/sheath_closure.cxx b/src/sheath_closure.cxx index 9b7cbfae0..9b6104207 100644 --- a/src/sheath_closure.cxx +++ b/src/sheath_closure.cxx @@ -1,7 +1,14 @@ #include "../include/sheath_closure.hxx" -SheathClosure::SheathClosure(std::string name, Options &alloptions, Solver *) { +SheathClosure::SheathClosure(std::string name, Options& alloptions, Solver*) + : Component({readOnly("fields:phi"), readOnly("species:e:density"), + // FIXME: If sink is true then electron temperature seems to be used + // unconditionally + readIfSet("species:e:temperature"), + readWrite("species:e:density_source"), + // FIXME: This is only written if temperature is set + readWrite("species:e:energy_source"), readWrite("fields:DivJextra")}) { Options& options = alloptions[name]; BoutReal Lnorm = alloptions["units"]["meters"]; // Length normalisation factor @@ -29,6 +36,14 @@ SheathClosure::SheathClosure(std::string name, Options &alloptions, Solver *) { .withDefault(false); output.write("\tL_par = {:e} (normalised)\n", L_par); + + if (sinks) { + // FIXME: This shouldn't apply to electrons + state_variable_access.setAccess(readOnly("species:{all_species}:{inputs}")); + state_variable_access.setAccess(readWrite("species:{all_species}:{outputs}")); + state_variable_access.substitute("inputs", {"AA", "density", "temperature"}); + state_variable_access.substitute("output", {"density_source", "energy_source"}); + } } void SheathClosure::transform_impl(GuardedOptions& state) { @@ -74,6 +89,7 @@ void SheathClosure::transform_impl(GuardedOptions& state) { for (auto& kv : allspecies.getChildren()) { GuardedOptions species = allspecies[kv.first]; + // FIXME: This includes electrons in the calculation. Is that desired? const BoutReal A = get(species["AA"]); Field3D Ns = get(species["density"]); Field3D Ts = get(species["temperature"]); diff --git a/src/snb_conduction.cxx b/src/snb_conduction.cxx index 2820aca3d..f7c40a3b9 100644 --- a/src/snb_conduction.cxx +++ b/src/snb_conduction.cxx @@ -5,11 +5,6 @@ using bout::globals::mesh; void SNBConduction::transform_impl(GuardedOptions& state) { - auto units = state["units"]; - const auto rho_s0 = get(units["meters"]); - const auto Tnorm = get(units["eV"]); - const auto Nnorm = get(units["inv_meters_cubed"]); - const auto Omega_ci = 1. / get(units["seconds"]); GuardedOptions electrons = state["species"]["e"]; // Note: Needs boundary conditions on temperature diff --git a/src/zero_current.cxx b/src/zero_current.cxx index e543df423..2914b74ae 100644 --- a/src/zero_current.cxx +++ b/src/zero_current.cxx @@ -4,13 +4,18 @@ #include "../include/zero_current.hxx" ZeroCurrent::ZeroCurrent(std::string name, Options& alloptions, Solver*) - : name(name) { + : Component({readIfSet("species:{all_species}:charge"), + readIfSet("species:{all_species}:{inputs}", Permissions::Interior), + readWrite(fmt::format("species:{}:velocity", name))}), + name(name) { AUTO_TRACE(); Options &options = alloptions[name]; charge = options["charge"].doc("Particle charge. electrons = -1"); ASSERT0(charge != 0.0); + + state_variable_access.substitute("inputs", {"density", "velocity"}); } void ZeroCurrent::transform_impl(GuardedOptions& state) { diff --git a/tests/unit/test_permissions.cxx b/tests/unit/test_permissions.cxx index cd7a295ba..89a1fad20 100644 --- a/tests/unit/test_permissions.cxx +++ b/tests/unit/test_permissions.cxx @@ -322,7 +322,8 @@ TEST(PermissionsTests, TestGetVariablesWithPermissions) { TEST(PermissionsTests, TestSubstitute) { Permissions example({{"species:{s1}:collision_frequencies:{s1}_{s2}_coll", {Permissions::Nowhere, Permissions::AllRegions, - Permissions::Nowhere, Permissions::Nowhere}}}); + Permissions::Nowhere, Permissions::Nowhere}}, + readIfSet("d")}); example.setAccess("{var}", {Permissions::Nowhere, Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere}); @@ -342,11 +343,14 @@ TEST(PermissionsTests, TestSubstitute) { EXPECT_EQ(readable["species:d+:collision_frequencies:d+_d+_coll"], Permissions::AllRegions); - example.substitute("var", {"a", "b", "c"}); + example.substitute("var", {"a", "b", "c", "d"}); auto writable = example.getVariablesWithPermission(Permissions::Write); EXPECT_EQ(writable.size(), 3); EXPECT_EQ(writable["a"], Permissions::Interior); EXPECT_EQ(writable["b"], Permissions::Interior); EXPECT_EQ(writable["c"], Permissions::Interior); + + EXPECT_EQ(example.getHighestPermission("d"), + make_permission(Permissions::ReadIfSet, "d")); } diff --git a/tests/unit/test_sheath_boundary.cxx b/tests/unit/test_sheath_boundary.cxx index b6100466d..dc00c0591 100644 --- a/tests/unit/test_sheath_boundary.cxx +++ b/tests/unit/test_sheath_boundary.cxx @@ -53,6 +53,7 @@ TEST_F(SheathBoundaryTest, DontSetPotential) { {"charge", Zi}, {"velocity", 0.0}}}}}}; + component.declareAllSpecies({"e", "h"}); component.transform(state); // Should have calculated, but not set potential @@ -82,6 +83,7 @@ TEST_F(SheathBoundaryTest, CalculatePotential) { {"charge", Zi}, {"velocity", 0.0}}}}}}; + component.declareAllSpecies({"e", "h"}); component.transform(state); // Should have calculated, but not set potential diff --git a/tests/unit/test_sheath_closure.cxx b/tests/unit/test_sheath_closure.cxx index 67fe4bf6d..9093791a0 100644 --- a/tests/unit/test_sheath_closure.cxx +++ b/tests/unit/test_sheath_closure.cxx @@ -38,6 +38,7 @@ TEST_F(SheathClosureTest, NeedsDensity) { state["fields"]["phi"] = Field3D(2.0); // Needs electron density + component.declareAllSpecies({"e"}); ASSERT_THROW(component.transform(state), BoutException); } @@ -51,6 +52,7 @@ TEST_F(SheathClosureTest, PhiAndDensity) { Options state; state["fields"]["phi"] = Field3D(2.0); state["species"]["e"]["density"] = Field3D(1.5); + component.declareAllSpecies({"e"}); component.transform(state); ASSERT_TRUE(state["fields"].isSet("DivJextra")); @@ -68,7 +70,8 @@ TEST_F(SheathClosureTest, Temperature) { state["fields"]["phi"] = Field3D(2.0); state["species"]["e"]["density"] = Field3D(1.5); state["species"]["e"]["temperature"] = Field3D(1.2); - + + component.declareAllSpecies({"e"}); component.transform(state); ASSERT_TRUE(state["fields"].isSet("DivJextra")); diff --git a/tests/unit/test_snb_conduction.cxx b/tests/unit/test_snb_conduction.cxx index d79f7f000..aee2cb155 100644 --- a/tests/unit/test_snb_conduction.cxx +++ b/tests/unit/test_snb_conduction.cxx @@ -21,12 +21,16 @@ using namespace bout::globals; using SNBConductionTest = FakeMeshFixture; TEST_F(SNBConductionTest, CreateComponent) { - Options options; + Options options{ + {"units", + {{"meters", 1.0}, {"eV", 1.0}, {"inv_meters_cubed", 1e19}, {"seconds", 1e-6}}}}; SNBConduction component("test", options, nullptr); } TEST_F(SNBConductionTest, Transform) { - Options options; + Options options{ + {"units", + {{"meters", 1.0}, {"eV", 1.0}, {"inv_meters_cubed", 1e19}, {"seconds", 1e-6}}}}; SNBConduction component("test", options, nullptr); Options state{ @@ -44,7 +48,9 @@ TEST_F(SNBConductionTest, Transform) { } TEST_F(SNBConductionTest, OutputDiagnose) { - Options options; + Options options{ + {"units", + {{"meters", 1.0}, {"eV", 1.0}, {"inv_meters_cubed", 1e19}, {"seconds", 1e-6}}}}; options["test"]["diagnose"] = true; SNBConduction component("test", options, nullptr); diff --git a/tests/unit/test_sound_speed.cxx b/tests/unit/test_sound_speed.cxx index b0819e0b8..45bac22e8 100644 --- a/tests/unit/test_sound_speed.cxx +++ b/tests/unit/test_sound_speed.cxx @@ -32,7 +32,8 @@ TEST_F(SoundSpeedTest, OneSpecies) { options["species"]["e"]["density"] = 2.0; options["species"]["e"]["pressure"] = 1.2; options["species"]["e"]["AA"] = 1.5; - + + component.declareAllSpecies({"e"}); component.transform(options); ASSERT_TRUE(options.isSet("sound_speed")); @@ -51,7 +52,8 @@ TEST_F(SoundSpeedTest, TwoSpecies) { options["species"]["h"]["density"] = 3.0; options["species"]["h"]["pressure"] = 2.5; options["species"]["h"]["AA"] = 0.9; - + + component.declareAllSpecies({"e", "h"}); component.transform(options); ASSERT_TRUE(options.isSet("sound_speed")); diff --git a/tests/unit/test_zero_current.cxx b/tests/unit/test_zero_current.cxx index 4738842db..a4aea7fa0 100644 --- a/tests/unit/test_zero_current.cxx +++ b/tests/unit/test_zero_current.cxx @@ -45,6 +45,7 @@ TEST_F(ZeroCurrentTest, ElectronFlowVelocity) { Field3D Vi = FieldFactory::get()->create3D("y - x", &options, mesh); options["species"]["ion"]["velocity"] = Vi; + component.declareAllSpecies({"e", "ion"}); component.transform(options); // Electron velocity should be equal to ion velocity From ee6d2182a96dca2cc800250b962aebadb403c1a2 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Mon, 10 Nov 2025 19:30:02 +0000 Subject: [PATCH 16/63] Add permission information to components for time-evolution --- include/evolve_density.hxx | 4 +++- include/evolve_energy.hxx | 1 + include/evolve_momentum.hxx | 6 ++++++ src/evolve_density.cxx | 12 +++++++++++- src/evolve_energy.cxx | 8 +++++++- src/evolve_momentum.cxx | 9 ++++++++- src/evolve_pressure.cxx | 8 +++++++- 7 files changed, 43 insertions(+), 5 deletions(-) diff --git a/include/evolve_density.hxx b/include/evolve_density.hxx index 9c22536e4..273d1999d 100644 --- a/include/evolve_density.hxx +++ b/include/evolve_density.hxx @@ -90,8 +90,10 @@ private: /// - species /// - /// - AA - /// - charge + /// - charge (if non-zero) /// - density + /// - density_source + /// - low_n_coeff (if low_n_diffuse) void transform_impl(GuardedOptions& state) override; }; diff --git a/include/evolve_energy.hxx b/include/evolve_energy.hxx index 658f8e6a8..441c0d128 100644 --- a/include/evolve_energy.hxx +++ b/include/evolve_energy.hxx @@ -87,6 +87,7 @@ private: /// Inputs /// - species /// - + /// - AA /// - density /// - velocity /// diff --git a/include/evolve_momentum.hxx b/include/evolve_momentum.hxx index 17f716e62..02711488a 100644 --- a/include/evolve_momentum.hxx +++ b/include/evolve_momentum.hxx @@ -48,6 +48,12 @@ private: bool fix_momentum_boundary_flux; ///< Fix momentum flux to boundary condition? Field3D flow_xlow, flow_ylow; ///< Momentum flow diagnostics + /// This takes as inputs + /// - species + /// - + /// - AA + /// - density + /// /// This sets in the state /// - species /// - diff --git a/src/evolve_density.cxx b/src/evolve_density.cxx index b928210eb..4039799fb 100644 --- a/src/evolve_density.cxx +++ b/src/evolve_density.cxx @@ -15,7 +15,7 @@ using bout::globals::mesh; EvolveDensity::EvolveDensity(std::string name, Options& alloptions, Solver* solver) - : name(name) { + : Component({readWrite("species:{name}:{outputs}")}), name(name) { AUTO_TRACE(); auto& options = alloptions[name]; @@ -130,6 +130,16 @@ EvolveDensity::EvolveDensity(std::string name, Options& alloptions, Solver* solv neumann_boundary_average_z = alloptions[std::string("N") + name]["neumann_boundary_average_z"] .doc("Apply neumann boundary with Z average?") .withDefault(false); + + std::vector outputs = {"AA", "density", "density_source"}; + if (charge != 0.) { + outputs.push_back("charge"); + } + if (low_n_diffuse) { + outputs.push_back("low_n_coeff"); + } + state_variable_access.substitute("name", {name}); + state_variable_access.substitute("outputs", outputs); } void EvolveDensity::transform_impl(GuardedOptions& state) { diff --git a/src/evolve_energy.cxx b/src/evolve_energy.cxx index e380c9557..810524c42 100644 --- a/src/evolve_energy.cxx +++ b/src/evolve_energy.cxx @@ -17,7 +17,9 @@ using bout::globals::mesh; EvolveEnergy::EvolveEnergy(std::string name, Options& alloptions, Solver* solver) - : name(name) { + : Component( + {readOnly("species:{name}:{inputs}"), readWrite("species:{name}:{outputs}")}), + name(name) { AUTO_TRACE(); auto& options = alloptions[name]; @@ -111,6 +113,10 @@ EvolveEnergy::EvolveEnergy(std::string name, Options& alloptions, Solver* solver thermal_conduction = options["thermal_conduction"] .doc("Include parallel heat conduction?") .withDefault(true); + + state_variable_access.substitute("name", {name}); + state_variable_access.substitute("inputs", {"AA", "density", "velocity"}); + state_variable_access.substitute("outputs", {"pressure", "temperature"}); } void EvolveEnergy::transform_impl(GuardedOptions& state) { diff --git a/src/evolve_momentum.cxx b/src/evolve_momentum.cxx index 44db2dd6d..70b6d8537 100644 --- a/src/evolve_momentum.cxx +++ b/src/evolve_momentum.cxx @@ -12,7 +12,11 @@ using bout::globals::mesh; -EvolveMomentum::EvolveMomentum(std::string name, Options &alloptions, Solver *solver) : name(name) { +EvolveMomentum::EvolveMomentum(std::string name, Options& alloptions, Solver* solver) + : Component({readOnly("species:{name}:AA"), + readOnly("species:{name}:density", Permissions::Interior), + readWrite("species:{name}:{outputs}")}), + name(name) { AUTO_TRACE(); // Evolve the momentum in time @@ -58,6 +62,9 @@ EvolveMomentum::EvolveMomentum(std::string name, Options &alloptions, Solver *so // Set to zero so set for output momentum_source = 0.0; NV_err = 0.0; + + state_variable_access.substitute("name", {name}); + state_variable_access.substitute("inputs", {"velocity", "momentum"}); } void EvolveMomentum::transform_impl(GuardedOptions& state) { diff --git a/src/evolve_pressure.cxx b/src/evolve_pressure.cxx index 40573fe6f..6365bc5a3 100644 --- a/src/evolve_pressure.cxx +++ b/src/evolve_pressure.cxx @@ -16,7 +16,9 @@ using bout::globals::mesh; EvolvePressure::EvolvePressure(std::string name, Options& alloptions, Solver* solver) - : name(name) { + : Component( + {readOnly("species:{name}:{inputs}", Permissions::Interior), readWrite("species:{name}:{outputs}")}), + name(name) { AUTO_TRACE(); auto& options = alloptions[name]; @@ -162,6 +164,10 @@ EvolvePressure::EvolvePressure(std::string name, Options& alloptions, Solver* so thermal_conduction = options["thermal_conduction"] .doc("Include parallel heat conduction?") .withDefault(true); + + state_variable_access.substitute("name", {name}); + state_variable_access.substitute("inputs", {"density"}); + state_variable_access.substitute("outputs", {"pressure", "temperature"}); } void EvolvePressure::transform_impl(GuardedOptions& state) { From 735521921e4f309e920cc5188853075e538d3474 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Wed, 12 Nov 2025 14:16:37 +0000 Subject: [PATCH 17/63] Added access permissions for remaining components All unit and integration tests pass, but many components are untested. There may be errors in their permissions. --- include/component.hxx | 6 +--- include/fixed_fraction_radiation.hxx | 6 +++- include/fixed_temperature.hxx | 13 ++++--- include/neutral_full_velocity.hxx | 10 +++++- include/neutral_mixed.hxx | 10 +++++- include/neutral_parallel_diffusion.hxx | 19 ++++++++-- include/noflow_boundary.hxx | 6 +++- include/quasineutral.hxx | 17 ++++++--- include/recycling.hxx | 17 ++++++--- include/scale_timederivs.hxx | 9 ++++- include/set_temperature.hxx | 12 ++++++- include/simple_conduction.hxx | 12 ++++++- include/simple_pump.hxx | 8 +++-- include/solkit_neutral_parallel_diffusion.hxx | 17 ++++++--- include/temperature_feedback.hxx | 11 +++++- include/upstream_density_feedback.hxx | 16 ++++++++- include/vorticity.hxx | 10 ++++-- src/evolve_momentum.cxx | 2 +- src/hydrogen_charge_exchange.cxx | 3 -- src/neutral_boundary.cxx | 9 ++++- src/neutral_full_velocity.cxx | 5 ++- src/neutral_mixed.cxx | 6 +++- src/permissions.cxx | 4 +++ src/polarisation_drift.cxx | 31 ++++++++++++++-- src/quasineutral.cxx | 16 ++++++--- src/reaction.cxx | 1 + src/recycling.cxx | 26 ++++++++++++-- src/relax_potential.cxx | 16 ++++++++- src/sheath_boundary_insulating.cxx | 30 +++++++++++++++- src/sheath_boundary_simple.cxx | 34 ++++++++++++++++-- src/transform.cxx | 10 +++++- src/vorticity.cxx | 36 ++++++++++++++++++- 32 files changed, 364 insertions(+), 64 deletions(-) diff --git a/include/component.hxx b/include/component.hxx index ce0b90f45..1f55a6da4 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -19,12 +19,8 @@ class Solver; // Time integrator /// /// The constructor of derived types should have signature /// (std::string name, Options &options, Solver *solver) -/// +/// struct Component { - Component() = default; - Component(Component& other) = default; - Component(Component&& other) = default; - /// Initialise the `state_variable_acceess` permissions. Note that /// `{all_species}` in any variable names will be replaced with the /// names of all species being simulated (by claling diff --git a/include/fixed_fraction_radiation.hxx b/include/fixed_fraction_radiation.hxx index 8ded88490..9abf9f19c 100644 --- a/include/fixed_fraction_radiation.hxx +++ b/include/fixed_fraction_radiation.hxx @@ -345,7 +345,10 @@ struct FixedFractionRadiation : public Component { /// Inputs /// - /// - fraction - FixedFractionRadiation(std::string name, Options &alloptions, Solver *UNUSED(solver)) : name(name) { + FixedFractionRadiation(std::string name, Options& alloptions, Solver* UNUSED(solver)) + : Component({readOnly("species:e:{inputs}", Permissions::Interior), + readWrite("species:e:energy_source")}), + name(name) { auto& options = alloptions[name]; fraction = options["fraction"] @@ -383,6 +386,7 @@ struct FixedFractionRadiation : public Component { {"source", "fixed_fraction_radiation"}}); } } + private: std::string name; diff --git a/include/fixed_temperature.hxx b/include/fixed_temperature.hxx index 0ac523dd4..cbde4328c 100644 --- a/include/fixed_temperature.hxx +++ b/include/fixed_temperature.hxx @@ -11,7 +11,11 @@ struct FixedTemperature : public Component { /// - /// - temperature value (expression) in units of eV FixedTemperature(std::string name, Options& alloptions, Solver* UNUSED(solver)) - : name(name) { + : Component({readIfSet("species:{name}:density", Permissions::Interior), + readWrite("species:{name}:temperature"), + // FIXME: Only written if density is set + readWrite("species:{name}:pressure")}), + name(name) { AUTO_TRACE(); auto& options = alloptions[name]; @@ -24,10 +28,11 @@ struct FixedTemperature : public Component { / Tnorm; // Normalise diagnose = options["diagnose"] - .doc("Save additional output diagnostics") - .withDefault(false); - } + .doc("Save additional output diagnostics") + .withDefault(false); + state_variable_access.substitute("name", {name}); + } void outputVars(Options& state) override { AUTO_TRACE(); diff --git a/include/neutral_full_velocity.hxx b/include/neutral_full_velocity.hxx index e5d1c4527..f5f20d81b 100644 --- a/include/neutral_full_velocity.hxx +++ b/include/neutral_full_velocity.hxx @@ -63,7 +63,15 @@ private: bool diagnose; ///< Output additional diagnostics? Field2D Vnpar; ///< Parallel flow velocity diagnostic - /// Modify the given simulation state + /// Sets + /// - species + /// - + /// - AA + /// - density + /// - momentum + /// - pressure + /// - temperature + /// - velocity void transform_impl(GuardedOptions& state) override; }; diff --git a/include/neutral_mixed.hxx b/include/neutral_mixed.hxx index dd67eb0d5..37979b9a2 100644 --- a/include/neutral_mixed.hxx +++ b/include/neutral_mixed.hxx @@ -80,7 +80,15 @@ private: Field3D ef_adv_perp_xlow, ef_adv_perp_ylow, ef_adv_par_ylow; Field3D ef_cond_perp_xlow, ef_cond_perp_ylow, ef_cond_par_ylow; - /// Modify the given simulation state + /// Sets + /// - species + /// - + /// - AA + /// - density + /// - momentum + /// - pressure + /// - temperature + /// - velocity void transform_impl(GuardedOptions& state) override; }; diff --git a/include/neutral_parallel_diffusion.hxx b/include/neutral_parallel_diffusion.hxx index e796bbcf3..a3798a8a3 100644 --- a/include/neutral_parallel_diffusion.hxx +++ b/include/neutral_parallel_diffusion.hxx @@ -23,7 +23,12 @@ /// - F_Dpar Momentum source due to diffusion /// struct NeutralParallelDiffusion : public Component { - NeutralParallelDiffusion(std::string name, Options &alloptions, Solver *) { + NeutralParallelDiffusion(std::string name, Options& alloptions, Solver*) + : Component({readIfSet("species:{all_species}:charge"), + // FIXME: These applies only to neutral species. + readIfSet("species:{all_species}:{optional_inputs}"), + readOnly("species:{all_species}:{inputs}"), + readWrite("species:{all_species}:{outputs}")}) { auto& options = alloptions[name]; dneut = options["dneut"] .doc("cross-field diffusion projection (B / Bpol)^2") @@ -48,6 +53,15 @@ struct NeutralParallelDiffusion : public Component { perpendicular_viscosity = options["perpendicular_viscosity"] .doc("Enable parallel projection of perpendicular viscosity?") .withDefault(true); + + // FIXME: strictly speaking, momentum is not optional if velocity has been set + state_variable_access.substitute("optional_inputs", + {"pressure", "velocity", "momentum"}); + state_variable_access.substitute( + "inputs", {"AA", "collision_frequencies", "density", "temperature"}); + // FIXME: momentum_source is only set if velocity was set. + state_variable_access.substitute( + "outputs", {"density_source", "energy_source", "momentum_source"}); } /// Save variables to the output @@ -79,7 +93,8 @@ private: /// - species /// - # Applies to all neutral species /// - AA - /// - collision_frequency + /// - charge [if set] + /// - collision_frequencies /// - density /// - temperature /// - pressure [optional, or density * temperature] diff --git a/include/noflow_boundary.hxx b/include/noflow_boundary.hxx index 0f313c384..b2fee4a3c 100644 --- a/include/noflow_boundary.hxx +++ b/include/noflow_boundary.hxx @@ -5,7 +5,8 @@ #include "component.hxx" struct NoFlowBoundary : public Component { - NoFlowBoundary(std::string name, Options& alloptions, Solver*) : name(name) { + NoFlowBoundary(std::string name, Options& alloptions, Solver*) + : Component({writeBoundaryIfSet("species:{name}:{variables}")}), name(name) { AUTO_TRACE(); Options& options = alloptions[name]; @@ -15,6 +16,9 @@ struct NoFlowBoundary : public Component { noflow_upper_y = options["noflow_upper_y"] .doc("No-flow boundary on upper y?") .withDefault(true); + state_variable_access.substitute("name", {name}); + state_variable_access.substitute( + "variables", {"density", "temperature", "pressure", "velocity", "momentum"}); } private: diff --git a/include/quasineutral.hxx b/include/quasineutral.hxx index c5b15d6e0..f48cfb2f5 100644 --- a/include/quasineutral.hxx +++ b/include/quasineutral.hxx @@ -23,21 +23,28 @@ struct Quasineutral : public Component { /// - charge Required to have a particle charge /// - AA Atomic mass /// - Quasineutral(std::string name, Options &alloptions, Solver *UNUSED(solver)); + Quasineutral(std::string name, Options& alloptions, Solver* UNUSED(solver)); /// Get the final density for output /// including any boundary conditions applied - void finally(const Options &state) override; + void finally(const Options& state) override; + + void outputVars(Options& state) override; - void outputVars(Options &state) override; private: std::string name; ///< Name of this species BoutReal charge; ///< The charge of this species BoutReal AA; ///< Atomic mass - Field3D density; ///< The density (for writing to output) + Field3D density; ///< The density (for writing to output) - /// + /// + /// Reads in state + /// - species + /// - + /// - charge [if density and charge are set] + /// - density [if density and charge are set] + /// /// Sets in state /// - species /// - diff --git a/include/recycling.hxx b/include/recycling.hxx index 6dadbb376..af73b03d7 100644 --- a/include/recycling.hxx +++ b/include/recycling.hxx @@ -68,15 +68,22 @@ private: /// Inputs /// /// - species - /// - - /// - density - /// - velocity + /// - + /// - density + /// - velocity + /// - temperature + /// - + /// - AA + /// - density + /// - pressure + /// - temperature /// /// Outputs /// /// - species - /// - - /// - density_source + /// - + /// - density_source + /// - energy_source /// void transform_impl(GuardedOptions& state) override; }; diff --git a/include/scale_timederivs.hxx b/include/scale_timederivs.hxx index 34855b567..8476e3654 100644 --- a/include/scale_timederivs.hxx +++ b/include/scale_timederivs.hxx @@ -11,7 +11,8 @@ /// where the aim is to reach ddt -> 0 /// struct ScaleTimeDerivs : public Component { - ScaleTimeDerivs(std::string, Options&, Solver*) {} + ScaleTimeDerivs(std::string, Options&, Solver*) + : Component({readOnly("species:e:temperature"), writeFinal("scale_timederivs")}) {} void outputVars(Options& state) override { set_with_attrs( @@ -23,6 +24,12 @@ struct ScaleTimeDerivs : public Component { private: Field3D scaling; // The scaling factor applied to each cell + /// Inputs + /// + /// - species + /// - e + /// - temperature + /// /// Sets in the state /// /// - scale_timederivs diff --git a/include/set_temperature.hxx b/include/set_temperature.hxx index c593ea695..dddcc23ee 100644 --- a/include/set_temperature.hxx +++ b/include/set_temperature.hxx @@ -23,7 +23,12 @@ struct SetTemperature : public Component { /// - /// - temperature_from name of species SetTemperature(std::string name, Options& alloptions, Solver* UNUSED(solver)) - : name(name) { + : Component({readIfSet("species:{name}:density", Permissions::Interior), + readOnly("species:{from}:temperature"), + readWrite("species:{name}:temperature"), + // FIXME: Only written if density set + readWrite("species:{name}:pressure")}), + name(name) { AUTO_TRACE(); auto& options = alloptions[name]; @@ -35,6 +40,9 @@ struct SetTemperature : public Component { diagnose = options["diagnose"] .doc("Save additional output diagnostics") .withDefault(false); + + state_variable_access.substitute("name", {name}); + state_variable_access.substitute("from", {temperature_from}); } void outputVars(Options& state) override { @@ -66,6 +74,8 @@ private: /// - species /// - /// - temperature + /// - + /// - density (if set) /// /// Sets in the state: /// - species diff --git a/include/simple_conduction.hxx b/include/simple_conduction.hxx index 92d311508..2b82bba84 100644 --- a/include/simple_conduction.hxx +++ b/include/simple_conduction.hxx @@ -14,7 +14,11 @@ /// Expressions taken from: /// https://farside.ph.utexas.edu/teaching/plasma/lectures1/node35.html struct SimpleConduction : public Component { - SimpleConduction(std::string name, Options& alloptions, Solver*) : name(name) { + SimpleConduction(std::string name, Options& alloptions, Solver*) + : Component({readOnly("species:{name}:temperature", Permissions::Interior), + readOnly("species:{name}:AA"), + readWrite("species:{name}:energy_source")}), + name(name) { auto& units = alloptions["units"]; Tnorm = units["eV"]; Nnorm = units["inv_meters_cubed"]; @@ -54,6 +58,12 @@ struct SimpleConduction : public Component { boundary_flux = options["conduction_boundary_flux"] .doc("Allow heat conduction through sheath boundaries?") .withDefault(false); + + if (density <= 0.0) { + state_variable_access.setAccess( + readOnly("species:{name}:density", Permissions::Interior)); + } + state_variable_access.substitute("name", {name}); } private: diff --git a/include/simple_pump.hxx b/include/simple_pump.hxx index 5946ea8d9..0bfa248f3 100644 --- a/include/simple_pump.hxx +++ b/include/simple_pump.hxx @@ -9,7 +9,10 @@ struct SimplePump : public Component { - SimplePump(std::string name, Options& alloptions, Solver*) : name(name) { + SimplePump(std::string name, Options& alloptions, Solver*) + : Component({readOnly("species:{name}:density", Permissions::Interior), + readWrite("species:{name}:density_source")}), + name(name) { Options& options = alloptions[name]; @@ -31,7 +34,8 @@ struct SimplePump : public Component { .doc("Output additional diagnostics?") .withDefault(false); - }; + state_variable_access.substitute("name", {name}); + }; void outputVars(Options& state) override { AUTO_TRACE(); diff --git a/include/solkit_neutral_parallel_diffusion.hxx b/include/solkit_neutral_parallel_diffusion.hxx index a2979fb62..6c3ea1d8e 100644 --- a/include/solkit_neutral_parallel_diffusion.hxx +++ b/include/solkit_neutral_parallel_diffusion.hxx @@ -18,8 +18,12 @@ struct SOLKITNeutralParallelDiffusion : public Component { /// - inv_meters_cubed /// - /// - neutral_temperature [eV] - /// - SOLKITNeutralParallelDiffusion(std::string name, Options &alloptions, Solver *) { + /// + SOLKITNeutralParallelDiffusion(std::string name, Options& alloptions, Solver*) + : Component({readOnly("species:{all_species}:{inputs}"), + // FIXME: These only apply to neutral species + readOnly("species:{all_species}:AA"), + readWrite("species:{all_species}:density_source")}) { auto Tnorm = get(alloptions["units"]["eV"]); auto& options = alloptions[name]; neutral_temperature = options["neutral_temperature"] @@ -30,6 +34,8 @@ struct SOLKITNeutralParallelDiffusion : public Component { auto Nnorm = get(alloptions["units"]["inv_meters_cubed"]); auto rho_s0 = get(alloptions["units"]["meters"]); area_norm = 1. / (Nnorm * rho_s0); + + state_variable_access.substitute("inputs", {"charge", "density"}); } private: @@ -39,13 +45,14 @@ private: /// /// Inputs /// - species - /// - # Applies to all neutral species - /// - AA + /// - + /// - AA [neutral species only] + /// - charge /// - density /// /// Sets /// - species - /// - + /// - # Applies to all neutral species /// - density_source void transform_impl(GuardedOptions& state) override; }; diff --git a/include/temperature_feedback.hxx b/include/temperature_feedback.hxx index 0bd8764eb..47364f0ed 100644 --- a/include/temperature_feedback.hxx +++ b/include/temperature_feedback.hxx @@ -22,7 +22,10 @@ struct TemperatureFeedback : public Component { /// - T (e.g. "Td+") /// - source_shape The initial source that is scaled by a time-varying factor /// - TemperatureFeedback(std::string name, Options& alloptions, Solver*) : name(name) { + TemperatureFeedback(std::string name, Options& alloptions, Solver*) + : Component({readOnly("species:{name}:temperature", Permissions::Interior), + readOnly("time"), readWrite("species:{sp}:energy_source")}), + name(name) { Options& options = alloptions[name]; const auto& units = alloptions["units"]; @@ -82,6 +85,12 @@ struct TemperatureFeedback : public Component { diagnose = options["diagnose"] .doc("Output additional diagnostics?") .withDefault(false); + + std::vector species_stripped; + std::transform(species_list.begin(), species_list.end(), species_stripped.begin(), + [](const std::string& val) { return trim(val); }); + state_variable_access.substitute("name", {name}); + state_variable_access.substitute("sp", species_stripped); } void outputVars(Options& state) override { diff --git a/include/upstream_density_feedback.hxx b/include/upstream_density_feedback.hxx index d73326d01..1514a1a47 100644 --- a/include/upstream_density_feedback.hxx +++ b/include/upstream_density_feedback.hxx @@ -20,7 +20,16 @@ struct UpstreamDensityFeedback : public Component { /// - N (e.g. "Nd+") /// - source_shape The initial source that is scaled by a time-varying factor /// - UpstreamDensityFeedback(std::string name, Options& alloptions, Solver*) : name(name) { + UpstreamDensityFeedback(std::string name, Options& alloptions, Solver*) + : Component({readOnly("time"), + readOnly("species:{name}:density", Permissions::Interior), + // FIXME: These are only read if BOTH are set + readIfSet("species:{name}:AA"), + readIfSet("species:{name}:velocity", Permissions::Interior), + readWrite("species:{name}:density_source"), + // FIXME: This is only set if AA and density_source are set + readWrite("species:{name}:energy_source")}), + name(name) { const auto& units = alloptions["units"]; BoutReal Nnorm = get(units["inv_meters_cubed"]); BoutReal FreqNorm = 1. / get(units["seconds"]); @@ -59,6 +68,8 @@ struct UpstreamDensityFeedback : public Component { diagnose = options["diagnose"] .doc("Output additional diagnostics?") .withDefault(false); + + state_variable_access.substitute("name", {name}); } void outputVars(Options& state) override { @@ -154,11 +165,14 @@ private: /// Inputs /// - /// - density + /// - velocity (if set) + /// - AA (if set) /// /// Outputs /// /// - /// - density_source + /// - energy_source (if velocity and AA are set) /// void transform_impl(GuardedOptions& state) override; }; diff --git a/include/vorticity.hxx b/include/vorticity.hxx index da591d2a2..70d0243e7 100644 --- a/include/vorticity.hxx +++ b/include/vorticity.hxx @@ -131,16 +131,20 @@ private: /// /// - species /// - pressure and charge => Calculates diamagnetic terms [if diamagnetic=true] - /// - pressure, charge and mass => Calculates polarisation current terms [if diamagnetic_polarisation=true] - /// + /// - pressure, charge and mass => Calculates polarisation current terms [if + /// diamagnetic_polarisation=true] + /// - density, charge, and collision_frequency => Calculate damping due to friction + /// [if collisional_friction=true] + /// /// Sets in the state /// - species - /// - [if has pressure and charge] + /// - [if has pressure and charge and diamagnetic=true] /// - energy_source /// - fields /// - vorticity /// - phi Electrostatic potential /// - DivJdia Divergence of diamagnetic current [if diamagnetic=true] + /// - DivJcol [if collisional_friction=true] /// /// Note: Diamagnetic current calculated here, but could be moved /// to a component with the diamagnetic drift advection terms diff --git a/src/evolve_momentum.cxx b/src/evolve_momentum.cxx index 70b6d8537..12dc5722f 100644 --- a/src/evolve_momentum.cxx +++ b/src/evolve_momentum.cxx @@ -64,7 +64,7 @@ EvolveMomentum::EvolveMomentum(std::string name, Options& alloptions, Solver* so NV_err = 0.0; state_variable_access.substitute("name", {name}); - state_variable_access.substitute("inputs", {"velocity", "momentum"}); + state_variable_access.substitute("outputs", {"velocity", "momentum"}); } void EvolveMomentum::transform_impl(GuardedOptions& state) { diff --git a/src/hydrogen_charge_exchange.cxx b/src/hydrogen_charge_exchange.cxx index 8fb61e436..3e1a2fafe 100644 --- a/src/hydrogen_charge_exchange.cxx +++ b/src/hydrogen_charge_exchange.cxx @@ -102,7 +102,6 @@ void HydrogenChargeExchange::calculate_rates(GuardedOptions atom1, GuardedOption add(atom1["collision_frequency"], atom_rate); add(ion1["collision_frequency"], ion_rate); - std::cout << 1; // Set individual collision frequencies set(atom1["collision_frequencies"] [atom1.name() + std::string("_") + ion1.name() + std::string("_cx")], @@ -110,6 +109,4 @@ void HydrogenChargeExchange::calculate_rates(GuardedOptions atom1, GuardedOption set(ion1["collision_frequencies"] [ion1.name() + std::string("_") + atom1.name() + std::string("_cx")], ion_rate); - std::cout << 2; - std::cout << "\n"; } diff --git a/src/neutral_boundary.cxx b/src/neutral_boundary.cxx index d8460cb4c..89558c395 100644 --- a/src/neutral_boundary.cxx +++ b/src/neutral_boundary.cxx @@ -7,7 +7,10 @@ using bout::globals::mesh; NeutralBoundary::NeutralBoundary(std::string name, Options& alloptions, [[maybe_unused]] Solver* solver) - : name(name) { + : Component({writeBoundary("species:{name}:{outputs}"), + writeBoundaryIfSet("species:{name}:{conditional_outputs}"), + readWrite("species:{name}:energy_source")}), + name(name) { AUTO_TRACE(); auto& options = alloptions[name]; @@ -49,6 +52,10 @@ NeutralBoundary::NeutralBoundary(std::string name, Options& alloptions, options["pfr_fast_refl_fraction"] .doc("Fraction of neutrals that are undergoing fast reflection at the pfr") .withDefault(0.8); + + state_variable_access.substitute("name", {name}); + state_variable_access.substitute("outputs", {"density", "temperature", "pressure"}); + state_variable_access.substitute("conditional_outputs", {"velocity", "momentum"}); } void NeutralBoundary::transform_impl(GuardedOptions& state) { diff --git a/src/neutral_full_velocity.cxx b/src/neutral_full_velocity.cxx index 4d079b944..287318329 100644 --- a/src/neutral_full_velocity.cxx +++ b/src/neutral_full_velocity.cxx @@ -14,7 +14,7 @@ using bout::globals::mesh; NeutralFullVelocity::NeutralFullVelocity(const std::string& name, Options& alloptions, Solver* solver) - : name(name) { + : Component({readWrite("species:{name}:{outputs}")}), name(name) { AUTO_TRACE(); // This is used in both transform and finally functions @@ -188,6 +188,9 @@ NeutralFullVelocity::NeutralFullVelocity(const std::string& name, Options& allop // Ensure that guard cells are filled and consistent between processors mesh->communicate(Urx, Ury, Uzx, Uzy); mesh->communicate(Txr, Txz, Tyr, Tyz); + state_variable_access.substitute("name", {name}); + state_variable_access.substitute( + "outputs", {"AA", "density", "pressure", "temperature", "momentum", "velocity"}); } /// Modify the given simulation state diff --git a/src/neutral_mixed.cxx b/src/neutral_mixed.cxx index c9d54c87d..fcc392f82 100644 --- a/src/neutral_mixed.cxx +++ b/src/neutral_mixed.cxx @@ -18,7 +18,7 @@ using bout::globals::mesh; using ParLimiter = hermes::Limiter; NeutralMixed::NeutralMixed(const std::string& name, Options& alloptions, Solver* solver) - : name(name) { + : Component({readWrite("species:{name}:{outputs}")}), name(name) { AUTO_TRACE(); // Normalisations @@ -169,6 +169,10 @@ NeutralMixed::NeutralMixed(const std::string& name, Options& alloptions, Solver* DnnNn.setBoundary(std::string("Dnn") + name); DnnPn.setBoundary(std::string("Dnn") + name); DnnNVn.setBoundary(std::string("Dnn") + name); + + state_variable_access.substitute("name", {name}); + state_variable_access.substitute( + "outputs", {"AA", "density", "pressure", "temperature", "momentum", "velocity"}); } void NeutralMixed::transform_impl(GuardedOptions& state) { diff --git a/src/permissions.cxx b/src/permissions.cxx index c40e7f366..9551db6f9 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -53,6 +53,10 @@ void Permissions::substitute(const std::string& label, std::pair Permissions::bestMatchRights(const std::string& variable) const { + auto match = variable_permissions.find(variable); + if (match != variable_permissions.end()) { + return *match; + } Permissions::AccessRights best_candidate = {Permissions::Nowhere, Permissions::Nowhere, Permissions::Nowhere}; std::string best_candidate_name = ""; diff --git a/src/polarisation_drift.cxx b/src/polarisation_drift.cxx index ff45695d7..9978d7303 100644 --- a/src/polarisation_drift.cxx +++ b/src/polarisation_drift.cxx @@ -8,9 +8,16 @@ using bout::globals::mesh; -PolarisationDrift::PolarisationDrift(std::string name, - Options &alloptions, - Solver *UNUSED(solver)) { +PolarisationDrift::PolarisationDrift(std::string name, Options& alloptions, + Solver* UNUSED(solver)) + // FIXME: There is a lot of complicated conditional logic which is not being captured + // here + // FIXME: Only charged species (with mass) are actually read/written (except for + // charge itself) + : Component({readOnly("species:{all_species}:{inputs}"), + readIfSet("species:{all_species}:{optional_inputs}"), + readIfSet("fields:{fields}"), + readWrite("species:{all_species}:{outputs}")}) { AUTO_TRACE(); // Get options for this component @@ -57,6 +64,24 @@ PolarisationDrift::PolarisationDrift(std::string name, diagnose = options["diagnose"] .doc("Output additional diagnostics?") .withDefault(false); + + // Pressure interior only, + std::vector inputs = {"AA"}, fields = {"DivJdia"}, + outputs = {"energy_source"}; + if (advection) { + // Interior only + inputs.push_back("density"); + fields.push_back("DivJextra"); + fields.push_back("DivJdia"); + outputs.push_back("density_source"); + outputs.push_back("momentum_source"); + } + state_variable_access.substitute("inputs", {"AA", "density"}); + state_variable_access.substitute("optional_inputs", {"charge", "momentum, pressure"}); + state_variable_access.substitute("fields", fields); + // FIXME: energy_source and momentum source are only set if pressure + // and momentum were set, respectively + state_variable_access.substitute("outputs", outputs); } void PolarisationDrift::transform_impl(GuardedOptions& state) { diff --git a/src/quasineutral.cxx b/src/quasineutral.cxx index 7efc46813..6b35e1457 100644 --- a/src/quasineutral.cxx +++ b/src/quasineutral.cxx @@ -3,9 +3,12 @@ #include "../include/quasineutral.hxx" -Quasineutral::Quasineutral(std::string name, Options &alloptions, - Solver *UNUSED(solver)) - : name(name) { +Quasineutral::Quasineutral(std::string name, Options& alloptions, Solver* UNUSED(solver)) + : Component({readWrite("species:{name}:{outputs}"), + // FIXME: These are only read if BOTH are set + readIfSet("species:{all_species}:charge"), + readIfSet("species:{all_species}:density", Permissions::Interior)}), + name(name) { Options &options = alloptions[name]; // Need to have a charge and mass @@ -13,21 +16,24 @@ Quasineutral::Quasineutral(std::string name, Options &alloptions, AA = options["AA"].doc("Particle atomic mass. Proton = 1"); ASSERT0(charge != 0.0); + state_variable_access.substitute("name", {name}); + state_variable_access.substitute("outputs", {"AA", "charge", "density"}); } void Quasineutral::transform_impl(GuardedOptions& state) { AUTO_TRACE(); // Iterate through all subsections GuardedOptions allspecies = state["species"]; + std::map children = allspecies.getChildren(); // Add charge density of other species const Field3D rho = std::accumulate( // Iterate through species - begin(allspecies.getChildren()), end(allspecies.getChildren()), + begin(children), end(children), // Start with no charge Field3D(0.0), [this](Field3D value, - const std::map::value_type &name_species) { + const std::map::value_type& name_species) { const GuardedOptions species = name_species.second; // Add other species which have density and charge if (name_species.first != name and species.isSet("charge") and diff --git a/src/reaction.cxx b/src/reaction.cxx index dcbf00615..ae837b12c 100644 --- a/src/reaction.cxx +++ b/src/reaction.cxx @@ -86,6 +86,7 @@ void Reaction::add_diagnostic(const std::string& sp_name, const std::string& dia diag_key, ReactionDiagnostic(diag_name, description, type, data_source, standard_name, transformer))); } + state_variable_access.setAccess(readWrite(diag_name)); } /** diff --git a/src/recycling.cxx b/src/recycling.cxx index 875de3745..c0a56d571 100644 --- a/src/recycling.cxx +++ b/src/recycling.cxx @@ -11,7 +11,10 @@ using bout::globals::mesh; -Recycling::Recycling(std::string name, Options& alloptions, Solver*) { +Recycling::Recycling(std::string name, Options& alloptions, Solver*) + : Component({readOnly("species:{from}:{from_inputs}"), + readOnly("species:{to}:{to_inputs}"), + readWrite("species:{to}:{outputs}")}) { AUTO_TRACE(); const Options& units = alloptions["units"]; @@ -24,7 +27,8 @@ Recycling::Recycling(std::string name, Options& alloptions, Solver*) { .as(), ','); - + std::set from_species, to_species; + // Neutral pump // Mark cells as having a pump by setting the Field2D is_pump to 1 in the grid file // Works only on SOL and PFR edges, where it locally modifies the recycle multiplier to the pump albedo @@ -43,6 +47,9 @@ Recycling::Recycling(std::string name, Options& alloptions, Solver*) { .doc("Name of the species to recycle into") .as(); + from_species.insert(from); + to_species.insert(to); + density_floor = options["density_floor"].doc("Minimum density floor").withDefault(1e-7); pressure_floor = density_floor * (1./get(alloptions["units"]["eV"])); @@ -128,6 +135,8 @@ Recycling::Recycling(std::string name, Options& alloptions, Solver*) { target_recycle_energy, sol_recycle_energy, pfr_recycle_energy, target_fast_recycle_fraction, pfr_fast_recycle_fraction, sol_fast_recycle_fraction, target_fast_recycle_energy_factor, sol_fast_recycle_energy_factor, pfr_fast_recycle_energy_factor}); + // FIXME: These are global settings, but are being overwritten by each particular + // recycling channel // Boolean flags for enabling recycling in different regions target_recycle = from_options["target_recycle"] @@ -146,6 +155,19 @@ Recycling::Recycling(std::string name, Options& alloptions, Solver*) { .doc("Neutral pump enabled? Note, need location in grid file") .withDefault(false); } + + // FIXME: Need to do this for the other regions too + if (target_recycle) { + state_variable_access.setAccess(readIfSet("species:{from}:energy_flow_ylow")); + } + state_variable_access.substitute( + "to", std::vector(to_species.begin(), to_species.end())); + state_variable_access.substitute( + "from", std::vector(from_species.begin(), from_species.end())); + state_variable_access.substitute("to_inputs", + {"AA", "density", "pressure", "temperature"}); + state_variable_access.substitute("from_inputs", {"density", "velocity", "temperature"}); + state_variable_access.substitute("outputs", {"density_source", "energy_source"}); } void Recycling::transform_impl(GuardedOptions& state) { diff --git a/src/relax_potential.cxx b/src/relax_potential.cxx index f66e99beb..7d00a7352 100644 --- a/src/relax_potential.cxx +++ b/src/relax_potential.cxx @@ -6,7 +6,8 @@ using bout::globals::mesh; #include "../include/div_ops.hxx" #include "../include/relax_potential.hxx" -RelaxPotential::RelaxPotential(std::string name, Options& alloptions, Solver* solver) { +RelaxPotential::RelaxPotential(std::string name, Options& alloptions, Solver* solver) + : Component({readWrite("fields:vorticity"), readWrite("fields:phi")}) { AUTO_TRACE(); auto* coord = mesh->getCoordinates(); @@ -44,6 +45,18 @@ RelaxPotential::RelaxPotential(std::string name, Options& alloptions, Solver* so solver->add(phi1, "phi1"); // Evolving scaled potential ϕ_1 = λ_2 ϕ if (diamagnetic) { + // FIXME: These should apply only to charged species + // FIXME: These will only be read if BOTH charge and pressure are set + state_variable_access.setAccess( + readIfSet("species:{all_species}:pressure", Permissions::Interior)); + state_variable_access.setAccess(readIfSet("species:{all_species}:charge")); + // FIXME: The weay transform_impl is currently written, + // energy_source is set for neutral species with an explicit + // charge declared as 0 if diamagnetic_polarisation == true. I + // suspect that's a mistake though. + state_variable_access.setAccess(readWrite("species:{all_species}:energy_source")); + state_variable_access.setAccess(readWrite("fields:DivJdia")); + // Read curvature vector try { Curlb_B.covariant = false; // Contravariant @@ -137,6 +150,7 @@ void RelaxPotential::transform_impl(GuardedOptions& state) { for (auto& kv : allspecies.getChildren()) { GuardedOptions species = allspecies[kv.first]; // Note: need non-const + // FIXME: Should this apply even if charge is 0? if (!(IS_SET_NOBOUNDARY(species["pressure"]) and IS_SET(species["charge"]) and IS_SET(species["AA"]))) { continue; // No pressure, charge or mass -> no polarisation current due to diff --git a/src/sheath_boundary_insulating.cxx b/src/sheath_boundary_insulating.cxx index a4fef6e8f..e63290461 100644 --- a/src/sheath_boundary_insulating.cxx +++ b/src/sheath_boundary_insulating.cxx @@ -49,7 +49,25 @@ BoutReal limitFree(BoutReal fm, BoutReal fc) { } // namespace SheathBoundaryInsulating::SheathBoundaryInsulating(std::string name, Options& alloptions, - Solver*) { + Solver*) + : Component({ + readIfSet("species:e:{e_whole_domain}"), + writeBoundary("species:e:{e_boundary}"), + readWrite("species:e:energy_source"), + writeBoundaryIfSet("species:e:{e_optional}"), + {"species:e:pressure", + {Permissions::Interior, Permissions::Nowhere, Permissions::Boundaries, + Permissions::Nowhere}}, + // FIXME: These only applies to ions, not to all species + readIfSet("species:{all_species}:{ion_whole_domain}"), + readOnly("species:{all_species}:AA"), + readWrite("species:{all_species}:energy_source"), + {"species:{all_species}:pressure", + {Permissions::Interior, Permissions::Nowhere, Permissions::Boundaries, + Permissions::Nowhere}}, + writeBoundary("species:{all_species}:{ion_boundary}"), + writeBoundaryIfSet("species:{all_species}:{ion_optional}"), + }) { AUTO_TRACE(); Options& options = alloptions[name]; @@ -77,6 +95,16 @@ SheathBoundaryInsulating::SheathBoundaryInsulating(std::string name, Options& al gamma_e = options["gamma_e"] .doc("Electron sheath heat transmission coefficient") .withDefault(3.5); + + state_variable_access.substitute("e_whole_domain", {"AA", "charge", "adiabatic"}); + state_variable_access.substitute("e_boundary", {"density", "temperature"}); + state_variable_access.substitute("e_optional", {"velocity", "momentum"}); + state_variable_access.substitute("ion_whole_domain", {"charge", "adiabatic"}); + state_variable_access.substitute("ion_boundary", {"density", "temperature"}); + // FIXME: velocity and momentum will only be set on boundaries if already set on + // interior + state_variable_access.substitute("ion_optional", {"velocity", "momentum"}); + state_variable_access.setAccess(writeBoundaryIfSet("fields:phi")); } void SheathBoundaryInsulating::transform_impl(GuardedOptions& state) { diff --git a/src/sheath_boundary_simple.cxx b/src/sheath_boundary_simple.cxx index 554033f72..442f4603a 100644 --- a/src/sheath_boundary_simple.cxx +++ b/src/sheath_boundary_simple.cxx @@ -57,8 +57,27 @@ BoutReal limitFree(BoutReal fm, BoutReal fc, BoutReal mode) { } // namespace -SheathBoundarySimple::SheathBoundarySimple(std::string name, Options& alloptions, - Solver*) { +SheathBoundarySimple::SheathBoundarySimple(std::string name, Options& alloptions, Solver*) + : Component({ + readIfSet("species:e:{e_whole_domain}"), + writeBoundary("species:e:{e_boundary}"), + readWrite("species:e:energy_source"), + readWrite("species:e:energy_flow_ylow"), + writeBoundaryIfSet("species:e:{e_optional}"), + {"species:e:pressure", + {Permissions::Interior, Permissions::Nowhere, Permissions::Boundaries, + Permissions::Nowhere}}, + // FIXME: These only applies to ions, not to all species + readIfSet("species:{all_species}:{ion_whole_domain}"), + readOnly("species:{all_species}:AA"), + readWrite("species:{all_species}:energy_source"), + readWrite("species:{all_species}:energy_flow_ylow"), + {"species:{all_species}:pressure", + {Permissions::Interior, Permissions::Nowhere, Permissions::Boundaries, + Permissions::Nowhere}}, + writeBoundary("species:{all_species}:{ion_boundary}"), + writeBoundaryIfSet("species:{all_species}:{ion_optional}"), + }) { AUTO_TRACE(); Options& options = alloptions[name]; @@ -130,7 +149,16 @@ SheathBoundarySimple::SheathBoundarySimple(std::string name, Options& alloptions diagnose = options["diagnose"] .doc("Save additional output diagnostics") .withDefault(false); - + + state_variable_access.substitute("e_whole_domain", {"AA", "charge", "adiabatic"}); + state_variable_access.substitute("e_boundary", {"density", "temperature"}); + state_variable_access.substitute("e_optional", {"velocity", "momentum"}); + state_variable_access.substitute("ion_whole_domain", {"charge", "adiabatic"}); + state_variable_access.substitute("ion_boundary", {"density", "temperature"}); + // FIXME: velocity and momentum will only be set on boundaries if already set on + // interior + state_variable_access.substitute("ion_optional", {"velocity", "momentum"}); + state_variable_access.setAccess(writeBoundaryIfSet("fields:phi")); } void SheathBoundarySimple::transform_impl(GuardedOptions& state) { diff --git a/src/transform.cxx b/src/transform.cxx index 1bc2849e0..03dc0cb03 100644 --- a/src/transform.cxx +++ b/src/transform.cxx @@ -3,7 +3,8 @@ #include // for trim, strsplit -Transform::Transform(std::string name, Options& alloptions, Solver* UNUSED(solver)) { +Transform::Transform(std::string name, Options& alloptions, Solver* UNUSED(solver)) + : Component({readOnly("{inputs}"), writeFinal("{outputs}")}) { Options& options = alloptions[name]; @@ -12,6 +13,8 @@ Transform::Transform(std::string name, Options& alloptions, Solver* UNUSED(solve const auto str = trim( options["transforms"].doc("Comma-separated list e.g. a = b, c = d"), trim_chars); + std::vector inputs, outputs; + for (const auto& assign_str : strsplit(str, ',')) { auto assign_lr = strsplit(assign_str, '='); if (assign_lr.size() != 2) { @@ -22,7 +25,12 @@ Transform::Transform(std::string name, Options& alloptions, Solver* UNUSED(solve const auto right = trim(assign_lr.back(), trim_chars); transforms[left] = right; + inputs.push_back(left); + outputs.push_back(right); } + + state_variable_access.substitute("inputs", inputs); + state_variable_access.substitute("outputs", outputs); } void Transform::transform_impl(GuardedOptions& state) { diff --git a/src/vorticity.cxx b/src/vorticity.cxx index 640b03a90..20717020f 100644 --- a/src/vorticity.cxx +++ b/src/vorticity.cxx @@ -40,7 +40,8 @@ BoutReal limitFree(BoutReal fm, BoutReal fc) { } } // namespace -Vorticity::Vorticity(std::string name, Options& alloptions, Solver* solver) { +Vorticity::Vorticity(std::string name, Options& alloptions, Solver* solver) + : Component({readWrite("fields:vorticity"), readWrite("fields:phi")}) { AUTO_TRACE(); solver->add(Vort, "Vort"); @@ -205,6 +206,39 @@ Vorticity::Vorticity(std::string name, Options& alloptions, Solver* solver) { diagnose = options["diagnose"] .doc("Output additional diagnostics?") .withDefault(false); + + if (diamagnetic or diamagnetic_polarisation) { + // FIXME: These should apply only to charged species + // FIXME: These will only be read if BOTH charge and pressure (and possibly AA) are + // set + state_variable_access.setAccess( + readIfSet("species:{all_species}:pressure", Permissions::Interior)); + state_variable_access.setAccess(readIfSet("species:{all_species}:charge")); + } + if (diamagnetic) { + // FIXME: These should only apply to charged species + state_variable_access.setAccess(readWrite("species:{all_species}:energy_source")); + state_variable_access.setAccess(readWrite("fields:DivJdia")); + } + if (diamagnetic_polarisation or collisional_friction) { + // FIXME: Only read for charged species and only if pressure and charge also set + state_variable_access.setAccess(readIfSet("species:{all_species}:AA")); + } + if (phi_boundary_relax) { + state_variable_access.setAccess(readOnly("time")); + } else { + state_variable_access.setAccess(readOnly("species:e:AA")); + state_variable_access.setAccess( + readIfSet("species:e:temperature", Permissions::Interior)); + } + if (collisional_friction) { + state_variable_access.setAccess(readIfSet("species:{all_species}:charge")); + // FIXME: Only applies for ions + state_variable_access.setAccess(readOnly("species:{all_species}:density")); + state_variable_access.setAccess( + readIfSet("species:{all_species}:collision_frequency")); + state_variable_access.setAccess(readWrite("fields:DivJcol")); + } } void Vorticity::transform_impl(GuardedOptions& state) { From e508b4f79b7f4725e2360d99d80a7d0ce2741ae2 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Wed, 12 Nov 2025 14:17:31 +0000 Subject: [PATCH 18/63] Only perform access permission checks if CHECKLEVEL >= 1 --- src/component.cxx | 10 ++++++---- src/guarded_options.cxx | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/component.cxx b/src/component.cxx index eb527bd51..7ef401f9e 100644 --- a/src/component.cxx +++ b/src/component.cxx @@ -12,14 +12,16 @@ std::unique_ptr Component::create(const std::string &type, void Component::transform(Options& state) { GuardedOptions guarded(&state, &state_variable_access); transform_impl(guarded); +#if CHECKLEVEL >= 1 for (auto& [varname, region] : guarded.unreadItems()) { - output_warn.write("Did not read from state variable {} in region(s) {}", - varname, Permissions::regionNames(region)); + output_warn.write("Did not read from state variable {} in region(s) {}\n", varname, + Permissions::regionNames(region)); } for (auto& [varname, region] : guarded.unwrittenItems()) { - output_warn.write("Did not write to state variable {} in region(s) {}", - varname, Permissions::regionNames(region)); + output_warn.write("Did not write to state variable {} in region(s) {}\n", varname, + Permissions::regionNames(region)); } +#endif } constexpr decltype(ComponentFactory::type_name) ComponentFactory::type_name; diff --git a/src/guarded_options.cxx b/src/guarded_options.cxx index a1312372c..2885387f7 100644 --- a/src/guarded_options.cxx +++ b/src/guarded_options.cxx @@ -21,6 +21,7 @@ GuardedOptions::GuardedOptions(Options* options, Permissions* permissions) unread_variables(std::make_shared>()), unwritten_variables( std::make_shared>()) { +#if CHECKLEVEL >= 1 if (permissions != nullptr) { *unread_variables = permissions->getVariablesWithPermission(Permissions::Read); // Only add variables with permission ReadIfSet to @@ -37,6 +38,7 @@ GuardedOptions::GuardedOptions(Options* options, Permissions* permissions) *unwritten_variables = permissions->getVariablesWithPermission(Permissions::Write, false); } +#endif } GuardedOptions GuardedOptions::operator[](const std::string& name) { @@ -80,6 +82,7 @@ const Options& GuardedOptions::get(Permissions::Regions region) const { if (options == nullptr) throw BoutException( "Trying to access GuardedOptions when underlying options are nullptr."); +#if CHECKLEVEL >= 1 std::string name = options->str(); if (permissions != nullptr) { auto [permission, varname] = permissions->getHighestPermission(name, region); @@ -94,12 +97,16 @@ const Options& GuardedOptions::get(Permissions::Regions region) const { } } throw BoutException("Do not have read permission for {}.", name); +#else + return *options; +#endif } Options& GuardedOptions::getWritable(Permissions::Regions region) { if (options == nullptr) throw BoutException( "Trying to access GuardedOptions when underlying options are nullptr."); +#if CHECKLEVEL >= 1 std::string name = options->str(); if (permissions != nullptr) { auto [access, varname] = permissions->canAccess(name, Permissions::Write, region); @@ -109,14 +116,27 @@ Options& GuardedOptions::getWritable(Permissions::Regions region) { } } throw BoutException("Do not have write permission for {}.", options->str()); +#else + return *options; +#endif } std::map GuardedOptions::unreadItems() const { +#if CHECKLEVEL >= 1 return *unread_variables; +#else + throw BoutException( + "Reading of items in GuardedOptions is not tracked when CHECKLEVEL < 1"); +#endif } std::map GuardedOptions::unwrittenItems() const { +#if CHECKLEVEL >= 1 return *unwritten_variables; +#else + throw BoutException( + "Reading of items in GuardedOptions is not tracked when CHECKLEVEL < 1"); +#endif } bool GuardedOptions::operator==(const GuardedOptions& other) const { From 1e584f97f44cf73138c7ff47cde01b21e55702c1 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Wed, 12 Nov 2025 19:18:33 +0000 Subject: [PATCH 19/63] Added finer-grained control for which species are read/written --- include/component.hxx | 51 +++++++++++++++++++++++++++++++------ src/component.cxx | 17 +++++++++++++ src/component_scheduler.cxx | 25 ++++++++++++++++-- 3 files changed, 83 insertions(+), 10 deletions(-) diff --git a/include/component.hxx b/include/component.hxx index 1f55a6da4..accf5b280 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -15,8 +15,33 @@ class Solver; // Time integrator +/// Simple struct to store information on the different types of +/// species present in a simulation +struct SpeciesInformation { + SpeciesInformation(bool has_electrons, bool has_electron_beam, + const std::vector& neutrals, + const std::vector& positive_ions, + const std::vector negative_ions) + : neutrals(neutrals), positive_ions(positive_ions), negative_ions(negative_ions), ions(positive_ions) { + ions.insert(ions.end(), negative_ions.begin(), negative_ions.end()); + charged = ions; + if (has_electrons) { + charged.push_back("e"); + } + if (has_electron_beam) { + charged.push_back("ebeam"); + } + non_electrons = ions; + non_electrons.insert(non_electrons.end(), neutrals.begin(), neutrals.end()); + all_species = charged; + all_species.insert(all_species.end(), neutrals.begin(), neutrals.end()); + } + + std::vector neutrals, positive_ions, negative_ions, ions, charged, non_electrons, all_species; +}; + /// Interface for a component of a simulation model -/// +/// /// The constructor of derived types should have signature /// (std::string name, Options &options, Solver *solver) /// @@ -60,13 +85,23 @@ struct Component { Options &options, // Component settings: options[name] are specific to this component Solver *solver); // Time integration solver - /// Tell the component the name of all species in the simulation. It - /// will use this information to substitute these for the - /// "all_species" label in `svate_variable_access`. - void declareAllSpecies(const std::vector& species) { - state_variable_access.substitute("all_species", species); - state_variable_access.substitute("all_species2", species); - } + /// Tell the component the name of all species in the simulation, by type. It + /// will use this information to substitute the following placeholders in `svate_variable_access`: + /// - neutrals (species with no charge) + /// - neutrals2 (same as above, used for cross-product) + /// - positive_ions (ions with a positive charge) + /// - positive_ions2 (same as above, used for cross-product) + /// - negative_ions (ions with a negative charge) + /// - negative_ions2 (same as above, used for cross-product) + /// - ions (all ions, regardless of sign of charge) + /// - ions2 (same as above, used for cross-product) + /// - charged (ions and electrons) + /// - charged2 (same as above, used for cross-product) + /// - non_electrons (ions and neutrals) + /// - non_electrons2 (same as above, used for cross-product) + /// - all_species (ions, neutrals, and electrons) + /// - all_species2 (same as above, used for cross-product) + void declareAllSpecies(const SpeciesInformation & info); protected: /// Information on which state variables the transform method will read and write. diff --git a/src/component.cxx b/src/component.cxx index 7ef401f9e..075941784 100644 --- a/src/component.cxx +++ b/src/component.cxx @@ -24,6 +24,23 @@ void Component::transform(Options& state) { #endif } +void Component::declareAllSpecies(const SpeciesInformation & info) { + state_variable_access.substitute("neutrals", info.neutrals); + state_variable_access.substitute("neutrals2", info.neutrals); + state_variable_access.substitute("positive_ions", info.positive_ions); + state_variable_access.substitute("positive_ions2", info.positive_ions); + state_variable_access.substitute("negative_ions", info.negative_ions); + state_variable_access.substitute("negative_ions2", info.negative_ions); + state_variable_access.substitute("ions", info.ions); + state_variable_access.substitute("ions2", info.ions); + state_variable_access.substitute("charged", info.charged); + state_variable_access.substitute("charged", info.charged); + state_variable_access.substitute("non_electrons", info.non_electrons); + state_variable_access.substitute("non_electrons2", info.non_electrons); + state_variable_access.substitute("all_species", info.all_species); + state_variable_access.substitute("all_species2", info.all_species); +} + constexpr decltype(ComponentFactory::type_name) ComponentFactory::type_name; constexpr decltype(ComponentFactory::section_name) ComponentFactory::section_name; constexpr decltype(ComponentFactory::option_name) ComponentFactory::option_name; diff --git a/src/component_scheduler.cxx b/src/component_scheduler.cxx index bb9d925cf..4efc91324 100644 --- a/src/component_scheduler.cxx +++ b/src/component_scheduler.cxx @@ -10,7 +10,8 @@ ComponentScheduler::ComponentScheduler(Options &scheduler_options, .doc("Components in order of execution") .as(); - std::vector species; + std::vector neutrals, positive_ions, negative_ions; + bool electrons_present = false, ebeam_present = false; // For now split on ','. Something like "->" might be better for (const auto &name : strsplit(component_names, ',')) { @@ -22,7 +23,24 @@ ComponentScheduler::ComponentScheduler(Options &scheduler_options, continue; } - if (component_options[name_trimmed].isSet("AA")) species.push_back(name_trimmed); + if (component_options[name_trimmed].isSet("AA")) { + if (name_trimmed == "e") { + electrons_present = true; + } else if (name == "ebeam") { + ebeam_present = true; + } else if (component_options[name_trimmed].isSet("charge")) { + BoutReal charge = component_options[name_trimmed]["charge"]; + if (charge > 1e-5) { + positive_ions.push_back(name_trimmed); + } else if (charge < 1e-5) { + negative_ions.push_back(name_trimmed); + } else { + neutrals.push_back(name_trimmed); + } + } else { + neutrals.push_back(name_trimmed); + } + } // For each component e.g. "e", several Component types can be created // but if types are not specified then the component name is used @@ -42,6 +60,9 @@ ComponentScheduler::ComponentScheduler(Options &scheduler_options, solver)); } } + + const SpeciesInformation species(electrons_present, ebeam_present, neutrals, positive_ions, negative_ions); + for (auto& component : components) { component->declareAllSpecies(species); } From 06aa4fbcd9e9170a4b9188ff3a7c97d554458e6d Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Thu, 13 Nov 2025 15:12:56 +0000 Subject: [PATCH 20/63] Substitute for electrons in permission data Also added an additional constructor for SpeciesInfo --- include/component.hxx | 52 ++++++++++++++++++++++++++----------- src/component.cxx | 2 ++ src/component_scheduler.cxx | 18 ++++++------- 3 files changed, 48 insertions(+), 24 deletions(-) diff --git a/include/component.hxx b/include/component.hxx index accf5b280..1a3c78994 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -12,32 +12,52 @@ #include "guarded_options.hxx" #include "permissions.hxx" +#include "hermes_utils.hxx" class Solver; // Time integrator /// Simple struct to store information on the different types of /// species present in a simulation struct SpeciesInformation { - SpeciesInformation(bool has_electrons, bool has_electron_beam, + SpeciesInformation(const std::vector& electrons, const std::vector& neutrals, const std::vector& positive_ions, - const std::vector negative_ions) - : neutrals(neutrals), positive_ions(positive_ions), negative_ions(negative_ions), ions(positive_ions) { - ions.insert(ions.end(), negative_ions.begin(), negative_ions.end()); - charged = ions; - if (has_electrons) { - charged.push_back("e"); - } - if (has_electron_beam) { - charged.push_back("ebeam"); + const std::vector & negative_ions) + : electrons(electrons), neutrals(neutrals), positive_ions(positive_ions), negative_ions(negative_ions), ions(positive_ions) { + finish_construction(); + } + + SpeciesInformation(const std::initializer_list species) { + for (auto& sp : species) { + // FIXME: identifySpecies only identifies positive ions + // FIXME: identifySpecies has no concept of ebeam + SpeciesType type = identifySpeciesType(sp); + if (type == SpeciesType::electron) { + electrons.push_back(sp); + } else if (type == SpeciesType::ion) { + positive_ions.push_back(sp); + } else if (type == SpeciesType::neutral) { + neutrals.push_back(sp); + } else { + throw BoutException("Species {} has unrecognised type {}", sp, toString(type)); + } + finish_construction(); } - non_electrons = ions; - non_electrons.insert(non_electrons.end(), neutrals.begin(), neutrals.end()); - all_species = charged; - all_species.insert(all_species.end(), neutrals.begin(), neutrals.end()); } - std::vector neutrals, positive_ions, negative_ions, ions, charged, non_electrons, all_species; + std::vector electrons, neutrals, positive_ions, negative_ions, ions, charged, non_electrons, all_species; + + private: + void finish_construction() { + ions = positive_ions; + ions.insert(ions.end(), negative_ions.begin(), negative_ions.end()); + charged = ions; + charged.insert(charged.end(), electrons.begin(), electrons.end()); + non_electrons = ions; + non_electrons.insert(non_electrons.end(), neutrals.begin(), neutrals.end()); + all_species = charged; + all_species.insert(all_species.end(), neutrals.begin(), neutrals.end()); + } }; /// Interface for a component of a simulation model @@ -87,6 +107,8 @@ struct Component { /// Tell the component the name of all species in the simulation, by type. It /// will use this information to substitute the following placeholders in `svate_variable_access`: + /// - electrons (any electron species) + /// - electrons2 (same as above, used for cross-product) /// - neutrals (species with no charge) /// - neutrals2 (same as above, used for cross-product) /// - positive_ions (ions with a positive charge) diff --git a/src/component.cxx b/src/component.cxx index 075941784..775ab38ad 100644 --- a/src/component.cxx +++ b/src/component.cxx @@ -25,6 +25,8 @@ void Component::transform(Options& state) { } void Component::declareAllSpecies(const SpeciesInformation & info) { + state_variable_access.substitute("electrons", info.electrons); + state_variable_access.substitute("electrons2", info.electrons); state_variable_access.substitute("neutrals", info.neutrals); state_variable_access.substitute("neutrals2", info.neutrals); state_variable_access.substitute("positive_ions", info.positive_ions); diff --git a/src/component_scheduler.cxx b/src/component_scheduler.cxx index 4efc91324..1fa30f171 100644 --- a/src/component_scheduler.cxx +++ b/src/component_scheduler.cxx @@ -10,8 +10,7 @@ ComponentScheduler::ComponentScheduler(Options &scheduler_options, .doc("Components in order of execution") .as(); - std::vector neutrals, positive_ions, negative_ions; - bool electrons_present = false, ebeam_present = false; + std::vector electrons, neutrals, positive_ions, negative_ions; // For now split on ','. Something like "->" might be better for (const auto &name : strsplit(component_names, ',')) { @@ -23,12 +22,13 @@ ComponentScheduler::ComponentScheduler(Options &scheduler_options, continue; } - if (component_options[name_trimmed].isSet("AA")) { - if (name_trimmed == "e") { - electrons_present = true; - } else if (name == "ebeam") { - ebeam_present = true; - } else if (component_options[name_trimmed].isSet("charge")) { + if (name_trimmed == "e" or name == "ebeam") { + electrons.push_back(name_trimmed); + } + // FIXME: Would there be any spcies without AA? Is there any other + // reliable way to identify what is a species? + else if (component_options[name_trimmed].isSet("AA")) { + if (component_options[name_trimmed].isSet("charge")) { BoutReal charge = component_options[name_trimmed]["charge"]; if (charge > 1e-5) { positive_ions.push_back(name_trimmed); @@ -61,7 +61,7 @@ ComponentScheduler::ComponentScheduler(Options &scheduler_options, } } - const SpeciesInformation species(electrons_present, ebeam_present, neutrals, positive_ions, negative_ions); + const SpeciesInformation species(electrons, neutrals, positive_ions, negative_ions); for (auto& component : components) { component->declareAllSpecies(species); From 2b3c2a3c774745c2a1d7cb44132b31292e0b13ec Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Fri, 14 Nov 2025 12:16:19 +0000 Subject: [PATCH 21/63] Updated all components to use more precise permissions --- include/adas_reaction.hxx | 3 +- include/braginskii_thermal_force.hxx | 36 ++++++--- include/electron_force_balance.hxx | 14 ++-- include/neutral_parallel_diffusion.hxx | 7 +- include/permissions.hxx | 2 +- include/solkit_neutral_parallel_diffusion.hxx | 5 +- include/sound_speed.hxx | 14 ++-- src/braginskii_collisions.cxx | 76 +++++++++++++++---- src/braginskii_conduction.cxx | 36 ++++++--- src/braginskii_friction.cxx | 3 - src/braginskii_ion_viscosity.cxx | 25 +++--- src/component_scheduler.cxx | 2 +- src/permissions.cxx | 5 -- src/polarisation_drift.cxx | 13 ++-- src/recycling.cxx | 5 +- src/relax_potential.cxx | 3 +- src/sheath_boundary.cxx | 11 ++- src/sheath_boundary_insulating.cxx | 13 ++-- src/sheath_closure.cxx | 11 ++- src/vorticity.cxx | 15 ++-- tests/unit/test_braginskii_collisions.cxx | 8 +- tests/unit/test_braginskii_conduction.cxx | 2 +- tests/unit/test_braginskii_ion_viscosity.cxx | 10 +-- tests/unit/test_braginskii_thermal_force.cxx | 70 ++++++++--------- tests/unit/test_sheath_boundary.cxx | 31 ++++---- 25 files changed, 247 insertions(+), 173 deletions(-) diff --git a/include/adas_reaction.hxx b/include/adas_reaction.hxx index a64de0584..74b179fc6 100644 --- a/include/adas_reaction.hxx +++ b/include/adas_reaction.hxx @@ -72,8 +72,7 @@ struct OpenADAS : public ReactionBase { state_variable_access.substitute("e_val", {"density", "temperature"}); state_variable_access.substitute( "w_val", {"density_source", "momentum_source", "energy_source"}); - // FIXME: There are hypothetically circumstances in which species:e:density_source - // will not be written + // FIXME: electron density_source only written if from_ion charge != to_ion charge. state_variable_access.substitute( "ew_val", {"density_source", "momentum_source", "energy_source"}); state_variable_access.substitute("sp", {from_ion, to_ion}); diff --git a/include/braginskii_thermal_force.hxx b/include/braginskii_thermal_force.hxx index a999da8de..09451d4db 100644 --- a/include/braginskii_thermal_force.hxx +++ b/include/braginskii_thermal_force.hxx @@ -28,16 +28,7 @@ /// struct BraginskiiThermalForce : public Component { BraginskiiThermalForce(const std::string& name, Options& alloptions, Solver*) - : Component({// FIXME: Not account for electron_ion or ion_ion settings - // FIXME: don't access charge for electrons - // FIXME: Don't read or write for neutrals - readIfSet("species:{all_species}:charge"), - readOnly("species:{all_species}:density", Permissions::Interior), - // FIXME: Only get temperature for electrons and light ions - readOnly("species:{all_species}:temperature"), - // FIXME: Don't access AA for electrons - readOnly("species:{all_species}:AA"), - readWrite("species:{all_species}:momentum_source")}) { + : Component(Permissions()) { Options& options = alloptions[name]; this->electron_ion = options["electron_ion"] .doc("Include electron-ion collisions?") @@ -52,6 +43,31 @@ struct BraginskiiThermalForce : public Component { .doc("Override the default mass restrictions for ion-ion thermal " "force calculations?") .withDefault(false); + + if (electron_ion or ion_ion) { + state_variable_access.setAccess(readIfSet("species:{all_species}:charge")); + } + if (electron_ion) { + // FIXME: These are only accessed if electrons present + state_variable_access.setAccess(readOnly("species:e:temperature")); + state_variable_access.setAccess( + readOnly("species:{ions}:density", Permissions::Interior)); + state_variable_access.setAccess(readWrite("species:{ions}:momentum_source")); + state_variable_access.setAccess(readWrite("species:e:momentum_source")); + } else if (ion_ion) { + } + if (ion_ion) { + state_variable_access.setAccess(readOnly("species:{ions}:AA")); + // FIXME: This is only accessed for light ions + state_variable_access.setAccess(readOnly("species:{ions}:temperature")); + if (!electron_ion) { + // FIXME: This is only accessed for heavy ions + state_variable_access.setAccess( + readOnly("species:{ions}:density", Permissions::Interior)); + // FIXME: This is only set for heavy and light ions, not intermediate + state_variable_access.setAccess(readWrite("species:{ions}:momentum_source")); + } + } } private: diff --git a/include/electron_force_balance.hxx b/include/electron_force_balance.hxx index a13213952..6ae7de9c5 100644 --- a/include/electron_force_balance.hxx +++ b/include/electron_force_balance.hxx @@ -21,14 +21,14 @@ struct ElectronForceBalance : public Component { ElectronForceBalance(std::string name, Options& alloptions, Solver*) : Component({readOnly("species:e:pressure"), - // FIXME: This is read unconditionally for electrons, only if set for - // everything else - readIfSet("species:{all_species}:density", Permissions::Interior), - // FIXME: This is read unconditionally for electrons, only if set for - // everything else - readIfSet("species:{all_species}:charge"), + readOnly("species:e:density", Permissions::Interior), + readOnly("species:e:charge"), + // FIXME: Only writes if already exists + readWrite("species:e:momentum_source"), + readIfSet("species:{non_electrons}:density", Permissions::Interior), + readIfSet("species:{non_electrons}:charge"), // FIXME: Only written if density and charge have been set. - readWrite("species:{all_species}:momentum_source")}) { + readWrite("species:{non_electrons}:momentum_source")}) { AUTO_TRACE(); auto& options = alloptions[name]; diagnose = options["diagnose"] diff --git a/include/neutral_parallel_diffusion.hxx b/include/neutral_parallel_diffusion.hxx index a3798a8a3..19e265855 100644 --- a/include/neutral_parallel_diffusion.hxx +++ b/include/neutral_parallel_diffusion.hxx @@ -25,10 +25,9 @@ struct NeutralParallelDiffusion : public Component { NeutralParallelDiffusion(std::string name, Options& alloptions, Solver*) : Component({readIfSet("species:{all_species}:charge"), - // FIXME: These applies only to neutral species. - readIfSet("species:{all_species}:{optional_inputs}"), - readOnly("species:{all_species}:{inputs}"), - readWrite("species:{all_species}:{outputs}")}) { + readIfSet("species:{neutrals}:{optional_inputs}"), + readOnly("species:{neutrals}:{inputs}"), + readWrite("species:{neutrals}:{outputs}")}) { auto& options = alloptions[name]; dneut = options["dneut"] .doc("cross-field diffusion projection (B / Bpol)^2") diff --git a/include/permissions.hxx b/include/permissions.hxx index d803569c0..bd08cd3eb 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -231,5 +231,5 @@ std::pair writeBoundary(std::string varn /// have Read permissions in the interior if the interior is already set. std::pair writeBoundaryIfSet(std::string varname); -// FIXME: Ideally there would be some way to express write permissions only if setaccess +// FIXME: Ideally there would be some way to express write permissions only if set // FIXME: Ideally we could express to write a boundary only if the interior is set diff --git a/include/solkit_neutral_parallel_diffusion.hxx b/include/solkit_neutral_parallel_diffusion.hxx index 6c3ea1d8e..262512197 100644 --- a/include/solkit_neutral_parallel_diffusion.hxx +++ b/include/solkit_neutral_parallel_diffusion.hxx @@ -21,9 +21,8 @@ struct SOLKITNeutralParallelDiffusion : public Component { /// SOLKITNeutralParallelDiffusion(std::string name, Options& alloptions, Solver*) : Component({readOnly("species:{all_species}:{inputs}"), - // FIXME: These only apply to neutral species - readOnly("species:{all_species}:AA"), - readWrite("species:{all_species}:density_source")}) { + readOnly("species:{neutrals}:AA"), + readWrite("species:{neutrals}:density_source")}) { auto Tnorm = get(alloptions["units"]["eV"]); auto& options = alloptions[name]; neutral_temperature = options["neutral_temperature"] diff --git a/include/sound_speed.hxx b/include/sound_speed.hxx index e00f89f70..10c5290de 100644 --- a/include/sound_speed.hxx +++ b/include/sound_speed.hxx @@ -12,13 +12,11 @@ /// so should run after those have been set. struct SoundSpeed : public Component { SoundSpeed(std::string name, Options& alloptions, Solver*) - : Component( - {readOnly("species:{all_species}:pressure", Permissions::Interior), - writeFinal("sound_speed"), writeFinal("fastest_wave"), - // FIXME: These are not read for electrons - readIfSet("species:{all_species}:AA"), - // FIXME: Only read if AA is set - readIfSet("species:{all_species}:{opt_inputs}", Permissions::Interior)}) { + : Component({readOnly("species:{all_species}:pressure", Permissions::Interior), + writeFinal("sound_speed"), writeFinal("fastest_wave"), + readIfSet("species:{sp}:AA"), + // FIXME: Only read if AA is set + readIfSet("species:{sp}:{opt_inputs}", Permissions::Interior)}) { Options &options = alloptions[name]; electron_dynamics = options["electron_dynamics"] .doc("Include electron sound speed?") @@ -48,6 +46,8 @@ struct SoundSpeed : public Component { temperature_floor /= get(alloptions["units"]["eV"]); } + state_variable_access.substitute( + "sp", {electron_dynamics ? "{all_species}" : "{non_electrons}"}); state_variable_access.substitute("opt_inputs", {"density", "temperature"}); } diff --git a/src/braginskii_collisions.cxx b/src/braginskii_collisions.cxx index 29b1f4b22..9a351acfb 100644 --- a/src/braginskii_collisions.cxx +++ b/src/braginskii_collisions.cxx @@ -21,19 +21,10 @@ #include "../include/hermes_utils.hxx" BraginskiiCollisions::BraginskiiCollisions(const std::string& name, Options& alloptions, Solver*) - // FIXME: Not everything is necessarily read/written, depending on which - // collisions are calculated. Ideally I would be able to distinguish between them. - // - // FIXME: temperature is used unconditionally for species that - // collide with electrons, but only if set for other species. - : Component({readOnly("species:{all_species}:temperature", Permissions::Interior), - readOnly("species:{all_species}:density", Permissions::Interior), - readOnly("species:{all_species}:AA"), - // FIXME: This isn't actually used for electrons - readIfSet("species:{all_species}:charge"), - readWrite("species:{all_species}:collision_frequency"), - readWrite("species:{all_species}:collision_frequencies:{all_species}_{" - "all_species2}_coll")}) { + : Component({readOnly("species:{non_electrons}:density", Permissions::Interior), + readIfSet("species:{non_electrons}:charge"), + readIfSet("species:{negative_ions}:temperature", Permissions::Interior), + readOnly("species:{all_species}:AA")}) { AUTO_TRACE(); const Options& units = alloptions["units"]; @@ -70,6 +61,64 @@ BraginskiiCollisions::BraginskiiCollisions(const std::string& name, Options& all diagnose = options["diagnose"].doc("Output additional diagnostics?").withDefault(false); + + state_variable_access.setAccess( + readOnly("species:{electrons}:temperature", Permissions::Interior)); + state_variable_access.setAccess( + readOnly("species:{electrons}:density", Permissions::Interior)); + if (electron_electron) { + state_variable_access.setAccess(readWrite( + "species:{electrons}:collision_frequencies:{electrons}_{electrons2}_coll")); + } + if (electron_ion) { + state_variable_access.setAccess( + readOnly("species:{positive_ions}:temperature", Permissions::Interior)); + state_variable_access.setAccess( + readWrite("species:{positive_ions}:collision_frequencies:{positive_ions}_{" + "electrons}_coll")); + state_variable_access.setAccess(readWrite( + "species:{electrons}:collision_frequencies:{electrons}_{positive_ions}_coll")); + } else { + state_variable_access.setAccess( + readIfSet("species:{positive_ions}:temperature", Permissions::Interior)); + } + if (electron_neutral) { + state_variable_access.setAccess( + readOnly("species:{neutrals}:temperature", Permissions::Interior)); + state_variable_access.setAccess(readWrite( + "species:{neutrals}:collision_frequencies:{neutrals}_{electrons}_coll")); + state_variable_access.setAccess(readWrite( + "species:{electrons}:collision_frequencies:{electrons}_{neutrals}_coll")); + } else { + state_variable_access.setAccess( + readIfSet("species:{neutrals}:temperature", Permissions::Interior)); + } + if (ion_ion) { + state_variable_access.setAccess( + readWrite("species:{ions}:collision_frequencies:{ions}_{ions2}_coll")); + } + if (ion_neutral) { + state_variable_access.setAccess( + readWrite("species:{ions}:collision_frequencies:{ions}_{neutrals}_coll")); + state_variable_access.setAccess( + readWrite("species:{neutrals}:collision_frequencies:{neutrals}_{ions}_coll")); + } + if (neutral_neutral) { + state_variable_access.setAccess(readWrite( + "species:{neutrals}:collision_frequencies:{neutrals}_{neutrals2}_coll")); + } + if (electron_electron or electron_ion or electron_neutral) { + state_variable_access.setAccess(readWrite("species:{electrons}:collision_frequency")); + } + if (ion_ion or ion_neutral) { + state_variable_access.setAccess(readWrite("species:{ions}:collision_frequency")); + } else if (electron_ion) { + state_variable_access.setAccess( + readWrite("species:{positive_ions}:collision_frequency")); + } + if (neutral_neutral or electron_neutral or ion_neutral) { + state_variable_access.setAccess(readWrite("species:{neutrals}:collision_frequency")); + } } /// Calculate apply collision data for the two species. @@ -377,6 +426,7 @@ void BraginskiiCollisions::transform_impl(GuardedOptions& state) { // lower half of the matrix, but start at the diagonal for (std::map::const_iterator kv2 = kv1; kv2 != std::end(children); ++kv2) { + // FIXME: Should this include a check for "ebeam" too? if (kv2->first == "e") { continue; // Skip electrons } diff --git a/src/braginskii_conduction.cxx b/src/braginskii_conduction.cxx index f1ffbf95b..50884cdaa 100644 --- a/src/braginskii_conduction.cxx +++ b/src/braginskii_conduction.cxx @@ -27,10 +27,9 @@ using bout::globals::mesh; BraginskiiConduction::BraginskiiConduction(const std::string&, Options& alloptions, Solver*) - // FIXME: state variables are only read and written for species that have collisions - : Component({readOnly("species:{all_species}:{input_vars}"), - writeBoundary("species:{all_species}:pressure"), - readWrite("species:{all_species}:{output_vars}")}) { + : Component({readOnly("species:{sp}:{input_vars}"), + writeBoundary("species:{sp}:pressure"), + readWrite("species:{sp}:{output_vars}")}) { AUTO_TRACE(); // Get settings for each species @@ -88,13 +87,32 @@ BraginskiiConduction::BraginskiiConduction(const std::string&, Options& alloptio .doc("Can be multispecies: all collisions, or " "braginskii: self collisions and ie") .withDefault("multispecies"); + } - // FIXME: Should I try specifying exactly which collision frequencies are used? - state_variable_access.substitute( - "input_vars", {"AA", "density", "temperature", "collision_frequencies"}); - state_variable_access.substitute("output_vars", - {"energy_source", "kappa_par", "energy_flow_ylow"}); + std::vector coll_types; + + state_variable_access.substitute("input_vars", {"AA", "density", "temperature"}); + state_variable_access.substitute("output_vars", + {"energy_source", "kappa_par", "energy_flow_ylow"}); + std::vector species; + for (const auto& [sp, mode] : all_conduction_collisions_mode) { + species.push_back(sp); + if (mode == "braginskii" and identifySpeciesType(sp) != SpeciesType::neutral) { + state_variable_access.setAccess(readIfSet( + fmt::format("species:{}:collision_frequencies:{}_{}_coll", sp, sp, sp))); + } else if (mode == "multispecies") { + state_variable_access.setAccess(readIfSet(fmt::format( + "species:{}:collision_frequencies:{}_{}_coll", sp, sp, "{all_species}"))); + state_variable_access.setAccess(readIfSet(fmt::format( + "species:{}:collision_frequencies:{}_{}_cx", sp, sp, "{all_species}"))); + } else if (mode == "AFN" and identifySpeciesType(sp) == SpeciesType::neutral) { + state_variable_access.setAccess(readIfSet(fmt::format( + "species:{}:collision_frequencies:{}_{}_cx", sp, sp, "{positive_ions}"))); + state_variable_access.setAccess(readIfSet(fmt::format( + "species:{}:collision_frequencies:{}_{}_iz", sp, sp, "{positive_ions}"))); + } } + state_variable_access.substitute("sp", species); } void BraginskiiConduction::transform_impl(GuardedOptions& state) { diff --git a/src/braginskii_friction.cxx b/src/braginskii_friction.cxx index 14617354c..4b0bcfcc5 100644 --- a/src/braginskii_friction.cxx +++ b/src/braginskii_friction.cxx @@ -19,9 +19,6 @@ BraginskiiFriction::BraginskiiFriction(const std::string& name, Options& allopti readIfSet("species:{all_species}:velocity", Permissions::Interior), readOnly("species:{all_species}:AA"), readIfSet("species:{all_species}:charge"), - // FIXME: Need to add setting of all_species_2. Could result - // in doubling-up of settings, although I don't think that - // will actually cause any problems. readOnly("species:{all_species}:collision_frequencies:{all_species}_{" "all_species2}_coll"), readWrite("species:{all_species}:momentum_source")}) { diff --git a/src/braginskii_ion_viscosity.cxx b/src/braginskii_ion_viscosity.cxx index 71248bdb6..905f2a2e5 100644 --- a/src/braginskii_ion_viscosity.cxx +++ b/src/braginskii_ion_viscosity.cxx @@ -29,18 +29,16 @@ using bout::globals::mesh; -BraginskiiIonViscosity::BraginskiiIonViscosity(cons tstd::string& name, +BraginskiiIonViscosity::BraginskiiIonViscosity(const std::string& name, Options& alloptions, Solver*) - // FIXME: does not read or write electron data : Component({ - readIfSet("species:{all_species}:pressure"), - readIfSet("species:{all_species}:velocity"), - readIfSet("species:{all_species}:charge"), - // FIXME: This specifies more collision frequencies than are actually read - readOnly("species:{all_species}:collision_frequencies"), + readIfSet("species:{non_electrons}:pressure"), + readIfSet("species:{non_electrons}:velocity"), + readIfSet("species:{non_electrons}:charge"), + readIfSet("species:{non_electrons}:collision_frequencies:{coll_type}"), readOnly("fields:phi"), - readWrite("species:{all_species}:momentum_source"), - readWrite("species:{all_species}:energy_source"), + readWrite("species:{non_electrons}:momentum_source"), + readWrite("species:{non_electrons}:energy_source"), readWrite("fields:DivJextra"), }) { auto& options = alloptions[name]; @@ -119,6 +117,15 @@ BraginskiiIonViscosity::BraginskiiIonViscosity(cons tstd::string& name, const BoutReal Lnorm = units["meters"]; bounce_frequency_R /= Lnorm; } + + std::vector coll_types; + if (viscosity_collisions_mode == "braginskii") { + coll_types.push_back("{non_electrons}_{non_electrons}_coll"); + } else if (viscosity_collisions_mode == "multispecies") { + coll_types.push_back("{non_electrons}_{all_species}_coll"); + coll_types.push_back("{non_electrons}_{all_species}_cx"); + } + state_variable_access.substitute("coll_type", coll_types); } void BraginskiiIonViscosity::transform_impl(GuardedOptions& state) { diff --git a/src/component_scheduler.cxx b/src/component_scheduler.cxx index 1fa30f171..d0f8b79b2 100644 --- a/src/component_scheduler.cxx +++ b/src/component_scheduler.cxx @@ -32,7 +32,7 @@ ComponentScheduler::ComponentScheduler(Options &scheduler_options, BoutReal charge = component_options[name_trimmed]["charge"]; if (charge > 1e-5) { positive_ions.push_back(name_trimmed); - } else if (charge < 1e-5) { + } else if (charge < -1e-5) { negative_ions.push_back(name_trimmed); } else { neutrals.push_back(name_trimmed); diff --git a/src/permissions.cxx b/src/permissions.cxx index 9551db6f9..4f29049cd 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -49,8 +49,6 @@ void Permissions::substitute(const std::string& label, } } -#include - std::pair Permissions::bestMatchRights(const std::string& variable) const { auto match = variable_permissions.find(variable); @@ -62,9 +60,6 @@ Permissions::bestMatchRights(const std::string& variable) const { std::string best_candidate_name = ""; int max_len = 0; for (const auto& [varname, rights] : variable_permissions) { - if (varname == variable) { - return {varname, rights}; - } if (varname.size() > max_len and variable.find(varname + ":") == 0) { max_len = varname.size(); best_candidate = rights; diff --git a/src/polarisation_drift.cxx b/src/polarisation_drift.cxx index 9978d7303..5b9faf172 100644 --- a/src/polarisation_drift.cxx +++ b/src/polarisation_drift.cxx @@ -11,13 +11,12 @@ using bout::globals::mesh; PolarisationDrift::PolarisationDrift(std::string name, Options& alloptions, Solver* UNUSED(solver)) // FIXME: There is a lot of complicated conditional logic which is not being captured - // here - // FIXME: Only charged species (with mass) are actually read/written (except for - // charge itself) - : Component({readOnly("species:{all_species}:{inputs}"), - readIfSet("species:{all_species}:{optional_inputs}"), + // here. E.g., species without AA or momentum will be skipped. + : Component({readIfSet("species:{all_species}:charge"), + readOnly("species:{charged}:{inputs}"), + readIfSet("species:{charged}:{optional_inputs}"), readIfSet("fields:{fields}"), - readWrite("species:{all_species}:{outputs}")}) { + readWrite("species:{charged}:{outputs}")}) { AUTO_TRACE(); // Get options for this component @@ -77,7 +76,7 @@ PolarisationDrift::PolarisationDrift(std::string name, Options& alloptions, outputs.push_back("momentum_source"); } state_variable_access.substitute("inputs", {"AA", "density"}); - state_variable_access.substitute("optional_inputs", {"charge", "momentum, pressure"}); + state_variable_access.substitute("optional_inputs", {"momentum, pressure"}); state_variable_access.substitute("fields", fields); // FIXME: energy_source and momentum source are only set if pressure // and momentum were set, respectively diff --git a/src/recycling.cxx b/src/recycling.cxx index c0a56d571..a0762394e 100644 --- a/src/recycling.cxx +++ b/src/recycling.cxx @@ -156,10 +156,13 @@ Recycling::Recycling(std::string name, Options& alloptions, Solver*) .withDefault(false); } - // FIXME: Need to do this for the other regions too if (target_recycle) { state_variable_access.setAccess(readIfSet("species:{from}:energy_flow_ylow")); } + if (sol_recycle or pfr_recycle) { + state_variable_access.setAccess(readIfSet("species:{from}:energy_flow_xlow")); + state_variable_access.setAccess(readIfSet("species:{from}:particle_flow_xlow")); + } state_variable_access.substitute( "to", std::vector(to_species.begin(), to_species.end())); state_variable_access.substitute( diff --git a/src/relax_potential.cxx b/src/relax_potential.cxx index 7d00a7352..61a147e25 100644 --- a/src/relax_potential.cxx +++ b/src/relax_potential.cxx @@ -45,10 +45,9 @@ RelaxPotential::RelaxPotential(std::string name, Options& alloptions, Solver* so solver->add(phi1, "phi1"); // Evolving scaled potential ϕ_1 = λ_2 ϕ if (diamagnetic) { - // FIXME: These should apply only to charged species // FIXME: These will only be read if BOTH charge and pressure are set state_variable_access.setAccess( - readIfSet("species:{all_species}:pressure", Permissions::Interior)); + readIfSet("species:{charged}:pressure", Permissions::Interior)); state_variable_access.setAccess(readIfSet("species:{all_species}:charge")); // FIXME: The weay transform_impl is currently written, // energy_source is set for neutral species with an explicit diff --git a/src/sheath_boundary.cxx b/src/sheath_boundary.cxx index 42899bc05..d3c753cf6 100644 --- a/src/sheath_boundary.cxx +++ b/src/sheath_boundary.cxx @@ -55,12 +55,11 @@ SheathBoundary::SheathBoundary(std::string name, Options& alloptions, Solver*) writeBoundary("species:e:{e_boundary}"), readWrite("species:e:energy_source"), writeBoundaryIfSet("species:e:{e_optional}"), - // FIXME: These only applies to ions, not to all species - readIfSet("species:{all_species}:{ion_whole_domain}"), - readOnly("species:{all_species}:AA"), - readWrite("species:{all_species}:energy_source"), - writeBoundary("species:{all_species}:{ion_boundary}"), - writeBoundaryIfSet("species:{all_species}:{ion_optional}"), + readIfSet("species:{ions}:{ion_whole_domain}"), + readOnly("species:{ions}:AA"), + readWrite("species:{ions}:energy_source"), + writeBoundary("species:{ions}:{ion_boundary}"), + writeBoundaryIfSet("species:{ions}:{ion_optional}"), }) { AUTO_TRACE(); diff --git a/src/sheath_boundary_insulating.cxx b/src/sheath_boundary_insulating.cxx index e63290461..4975a9c64 100644 --- a/src/sheath_boundary_insulating.cxx +++ b/src/sheath_boundary_insulating.cxx @@ -58,15 +58,14 @@ SheathBoundaryInsulating::SheathBoundaryInsulating(std::string name, Options& al {"species:e:pressure", {Permissions::Interior, Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere}}, - // FIXME: These only applies to ions, not to all species - readIfSet("species:{all_species}:{ion_whole_domain}"), - readOnly("species:{all_species}:AA"), - readWrite("species:{all_species}:energy_source"), - {"species:{all_species}:pressure", + readIfSet("species:{ions}:{ion_whole_domain}"), + readOnly("species:{ions}:AA"), + readWrite("species:{ions}:energy_source"), + {"species:{ions}:pressure", {Permissions::Interior, Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere}}, - writeBoundary("species:{all_species}:{ion_boundary}"), - writeBoundaryIfSet("species:{all_species}:{ion_optional}"), + writeBoundary("species:{ions}:{ion_boundary}"), + writeBoundaryIfSet("species:{ions}:{ion_optional}"), }) { AUTO_TRACE(); diff --git a/src/sheath_closure.cxx b/src/sheath_closure.cxx index 9b6104207..f9ce9628f 100644 --- a/src/sheath_closure.cxx +++ b/src/sheath_closure.cxx @@ -3,9 +3,6 @@ SheathClosure::SheathClosure(std::string name, Options& alloptions, Solver*) : Component({readOnly("fields:phi"), readOnly("species:e:density"), - // FIXME: If sink is true then electron temperature seems to be used - // unconditionally - readIfSet("species:e:temperature"), readWrite("species:e:density_source"), // FIXME: This is only written if temperature is set readWrite("species:e:energy_source"), readWrite("fields:DivJextra")}) { @@ -38,11 +35,13 @@ SheathClosure::SheathClosure(std::string name, Options& alloptions, Solver*) output.write("\tL_par = {:e} (normalised)\n", L_par); if (sinks) { - // FIXME: This shouldn't apply to electrons - state_variable_access.setAccess(readOnly("species:{all_species}:{inputs}")); - state_variable_access.setAccess(readWrite("species:{all_species}:{outputs}")); + state_variable_access.setAccess(readOnly("species:e:temperature")); + state_variable_access.setAccess(readOnly("species:{non_electrons}:{inputs}")); + state_variable_access.setAccess(readWrite("species:{non_electrons}:{outputs}")); state_variable_access.substitute("inputs", {"AA", "density", "temperature"}); state_variable_access.substitute("output", {"density_source", "energy_source"}); + } else { + state_variable_access.setAccess(readIfSet("species:e:temperature")); } } diff --git a/src/vorticity.cxx b/src/vorticity.cxx index 20717020f..ebd243d0d 100644 --- a/src/vorticity.cxx +++ b/src/vorticity.cxx @@ -208,21 +208,19 @@ Vorticity::Vorticity(std::string name, Options& alloptions, Solver* solver) .withDefault(false); if (diamagnetic or diamagnetic_polarisation) { - // FIXME: These should apply only to charged species // FIXME: These will only be read if BOTH charge and pressure (and possibly AA) are // set state_variable_access.setAccess( - readIfSet("species:{all_species}:pressure", Permissions::Interior)); + readIfSet("species:{charged}:pressure", Permissions::Interior)); state_variable_access.setAccess(readIfSet("species:{all_species}:charge")); } if (diamagnetic) { - // FIXME: These should only apply to charged species - state_variable_access.setAccess(readWrite("species:{all_species}:energy_source")); + state_variable_access.setAccess(readWrite("species:{charged}:energy_source")); state_variable_access.setAccess(readWrite("fields:DivJdia")); } if (diamagnetic_polarisation or collisional_friction) { - // FIXME: Only read for charged species and only if pressure and charge also set - state_variable_access.setAccess(readIfSet("species:{all_species}:AA")); + // FIXME: Only read if pressure also set + state_variable_access.setAccess(readIfSet("species:{charged}:AA")); } if (phi_boundary_relax) { state_variable_access.setAccess(readOnly("time")); @@ -233,10 +231,9 @@ Vorticity::Vorticity(std::string name, Options& alloptions, Solver* solver) } if (collisional_friction) { state_variable_access.setAccess(readIfSet("species:{all_species}:charge")); - // FIXME: Only applies for ions - state_variable_access.setAccess(readOnly("species:{all_species}:density")); + state_variable_access.setAccess(readOnly("species:{positive_ions}:density")); state_variable_access.setAccess( - readIfSet("species:{all_species}:collision_frequency")); + readIfSet("species:{positive_ions}:collision_frequency")); state_variable_access.setAccess(readWrite("fields:DivJcol")); } } diff --git a/tests/unit/test_braginskii_collisions.cxx b/tests/unit/test_braginskii_collisions.cxx index b735ed953..1ad9b0a99 100644 --- a/tests/unit/test_braginskii_collisions.cxx +++ b/tests/unit/test_braginskii_collisions.cxx @@ -46,7 +46,7 @@ TEST_F(BraginskiiCollisionsTest, OnlyElectrons) { state["species"]["e"]["density"] = 1e19; state["species"]["e"]["temperature"] = 10.; - component.declareAllSpecies({"e"}); + component.declareAllSpecies(SpeciesInformation({"e"}, {}, {}, {})); component.transform(state); ASSERT_TRUE(state["species"]["e"].isSet("collision_frequency")); @@ -77,7 +77,7 @@ TEST_F(BraginskiiCollisionsTest, OneOrTwoSpeciesCharged) { state2["species"]["s2"] = state2["species"]["s1"].copy(); // Run calculations - component.declareAllSpecies({"s1", "s2"}); + component.declareAllSpecies(SpeciesInformation({}, {}, {"s1", "s2"}, {})); component.transform(state1); component.transform(state2); @@ -116,7 +116,7 @@ TEST_F(BraginskiiCollisionsTest, TnormDependence) { {"d+", {{"density", 2e19}, {"temperature", 20}, {"charge", 1}, {"AA", 2}}}, {"d", {{"density", 1e18}, {"temperature", 3}, {"AA", 2}}}}}}; - component.declareAllSpecies({"e", "d", "d+"}); + component.declareAllSpecies(SpeciesInformation({"e"}, {"d"}, {"d+"}, {})); component.transform(state); ASSERT_TRUE(state["species"]["e"].isSet("collision_frequency")); @@ -156,7 +156,7 @@ TEST_F(BraginskiiCollisionsTest, TnormDependence) { {{"density", 2e19}, {"temperature", 20 / Tnorm}, {"charge", 1}, {"AA", 2}}}, {"d", {{"density", 1e18}, {"temperature", 3 / Tnorm}, {"AA", 2}}}}}}; - component2.declareAllSpecies({"e", "d", "d+"}); + component2.declareAllSpecies(SpeciesInformation({"e"}, {"d"}, {"d+"}, {})); component2.transform(state2); // Normalised frequencies should be unchanged diff --git a/tests/unit/test_braginskii_conduction.cxx b/tests/unit/test_braginskii_conduction.cxx index 9b110d6f0..5337f679e 100644 --- a/tests/unit/test_braginskii_conduction.cxx +++ b/tests/unit/test_braginskii_conduction.cxx @@ -178,7 +178,7 @@ TEST_F(BraginskiiConductionTest, ConductionCollisionsMode) { component_braginskii.declareAllSpecies({"test+"}); component_braginskii.transform(state_brag); - component_multispecies.declareAllSpecies({"test+"}); + component_multispecies.declareAllSpecies({"test+", "e"}); component_multispecies.transform(state_multi); Field3D conduction_brag = this->getDeriv(state_brag); diff --git a/tests/unit/test_braginskii_ion_viscosity.cxx b/tests/unit/test_braginskii_ion_viscosity.cxx index 22eba5e96..630712e1d 100644 --- a/tests/unit/test_braginskii_ion_viscosity.cxx +++ b/tests/unit/test_braginskii_ion_viscosity.cxx @@ -68,7 +68,7 @@ TEST_F(BraginskiiIonViscosityTest, ViscosityPressureScaling) { state2["species"]["d+"]["pressure"] = 2 * state1["species"]["d+"]["pressure"].as(); - component.declareAllSpecies({"d+"}); + component.declareAllSpecies({"d+", "c+"}); component.transform(state1); component.transform(state2); @@ -99,7 +99,7 @@ TEST_F(BraginskiiIonViscosityTest, ViscosityCollisionScaling) { state2["species"]["d+"]["collision_frequencies"]["d+_he+_coll"] = 2 * state1["species"]["d+"]["collision_frequencies"]["d+_he+_coll"].as(); - component.declareAllSpecies({"d+"}); + component.declareAllSpecies({"d+", "he+"}); component.transform(state1); component.transform(state2); @@ -131,7 +131,7 @@ TEST_F(BraginskiiIonViscosityTest, ViscosityVelocityScaling) { state2["species"]["d+"]["velocity"] = 2 * state1["species"]["d+"]["velocity"].as(); - component.declareAllSpecies({"d+"}); + component.declareAllSpecies({"d+", "c+"}); component.transform(state0); component.transform(state1); component.transform(state2); @@ -164,9 +164,9 @@ TEST_F(BraginskiiIonViscosityTest, ViscosityCollisionMode) { options2["test2"]["viscosity_collisions_mode"] = "braginskii"; BraginskiiIonViscosity component2("test2", options2, nullptr); - component.declareAllSpecies({"d+"}); + component.declareAllSpecies({"d+", "c+"}); component.transform(state1); - component2.declareAllSpecies({"d+"}); + component2.declareAllSpecies({"d+", "c+"}); component2.transform(state2); Field3D visc1 = state1["species"]["d+"]["momentum_source"]; diff --git a/tests/unit/test_braginskii_thermal_force.cxx b/tests/unit/test_braginskii_thermal_force.cxx index a07fbab84..25d3108fb 100644 --- a/tests/unit/test_braginskii_thermal_force.cxx +++ b/tests/unit/test_braginskii_thermal_force.cxx @@ -93,19 +93,19 @@ TEST_F(BraginskiiThermalForceTest, ElectronIonBalance) { TEST_F(BraginskiiThermalForceTest, IonIonBalance) { Options state; - state["species"]["c"]["density"] = 0.01; - state["species"]["c"]["temperature"] = grad1; - state["species"]["c"]["charge"] = 1; - state["species"]["c"]["AA"] = 12.; + state["species"]["c+"]["density"] = 0.01; + state["species"]["c+"]["temperature"] = grad1; + state["species"]["c+"]["charge"] = 1; + state["species"]["c+"]["AA"] = 12.; state["species"]["d+"]["density"] = 1; state["species"]["d+"]["temperature"] = grad1; state["species"]["d+"]["charge"] = 1; state["species"]["d+"]["AA"] = 2.; - component.declareAllSpecies({"c", "d+"}); + component.declareAllSpecies({"c+", "d+"}); component.transform(state); - Field3D mom_c = state["species"]["c"]["momentum_source"]; + Field3D mom_c = state["species"]["c+"]["momentum_source"]; Field3D mom_d = state["species"]["d+"]["momentum_source"]; BOUT_FOR_SERIAL(i, mom_c.getRegion("RGN_NOBNDRY")) { // Forces on the species should be equal and opposite @@ -115,14 +115,14 @@ TEST_F(BraginskiiThermalForceTest, IonIonBalance) { TEST_F(BraginskiiThermalForceTest, NoNetForce) { Options state; - state["species"]["c"]["density"] = 0.01; - state["species"]["c"]["temperature"] = grad1; - state["species"]["c"]["charge"] = 1; - state["species"]["c"]["AA"] = 12.; - state["species"]["ar"]["density"] = 0.01; - state["species"]["ar"]["temperature"] = grad2; - state["species"]["ar"]["charge"] = 1; - state["species"]["ar"]["AA"] = 40.; + state["species"]["c+"]["density"] = 0.01; + state["species"]["c+"]["temperature"] = grad1; + state["species"]["c+"]["charge"] = 1; + state["species"]["c+"]["AA"] = 12.; + state["species"]["ar+"]["density"] = 0.01; + state["species"]["ar+"]["temperature"] = grad2; + state["species"]["ar+"]["charge"] = 1; + state["species"]["ar+"]["AA"] = 40.; state["species"]["d"]["density"] = 1; state["species"]["d"]["temperature"] = grad1; state["species"]["d"]["charge"] = 0; @@ -136,7 +136,7 @@ TEST_F(BraginskiiThermalForceTest, NoNetForce) { state["species"]["e"]["charge"] = -1; state["species"]["e"]["AA"] = 1. / 1836; - component.declareAllSpecies({"c", "ar", "d", "d+", "e"}); + component.declareAllSpecies({"c+", "ar+", "d", "d+", "e"}); component.transform(state); Field3D force(0.); for (const auto& [name, species] : state["species"].subsections()) { @@ -247,14 +247,14 @@ TEST_F(BraginskiiThermalForceTest, ElectronForceTemperatureGradScaling) { TEST_F(BraginskiiThermalForceTest, IonIonForceTemperatureGradScaling) { Options state1; Options state2; - state1["species"]["c1"]["density"] = 0.01; - state1["species"]["c1"]["temperature"] = grad1; - state1["species"]["c1"]["charge"] = 1; - state1["species"]["c1"]["AA"] = 12.; - state1["species"]["c2"]["density"] = 0.01; - state1["species"]["c2"]["temperature"] = grad2; - state1["species"]["c2"]["charge"] = 1; - state1["species"]["c2"]["AA"] = 12.; + state1["species"]["c1+"]["density"] = 0.01; + state1["species"]["c1+"]["temperature"] = grad1; + state1["species"]["c1+"]["charge"] = 1; + state1["species"]["c1+"]["AA"] = 12.; + state1["species"]["c2+"]["density"] = 0.01; + state1["species"]["c2+"]["temperature"] = grad2; + state1["species"]["c2+"]["charge"] = 1; + state1["species"]["c2+"]["AA"] = 12.; state1["species"]["d+"]["density"] = 1; state1["species"]["d+"]["charge"] = 1; state1["species"]["d+"]["AA"] = 2.; @@ -263,14 +263,14 @@ TEST_F(BraginskiiThermalForceTest, IonIonForceTemperatureGradScaling) { state1["species"]["d+"]["temperature"] = grad1; state2["species"]["d+"]["temperature"] = grad2; - component.declareAllSpecies({"d+", "c1", "c2"}); + component.declareAllSpecies({"d+", "c1+", "c2+"}); component.transform(state1); component.transform(state2); - Field3D mom1_1 = state1["species"]["c1"]["momentum_source"]; - Field3D mom1_2 = state1["species"]["c2"]["momentum_source"]; - Field3D mom2_1 = state2["species"]["c1"]["momentum_source"]; - Field3D mom2_2 = state2["species"]["c2"]["momentum_source"]; + Field3D mom1_1 = state1["species"]["c1+"]["momentum_source"]; + Field3D mom1_2 = state1["species"]["c2+"]["momentum_source"]; + Field3D mom2_1 = state2["species"]["c1+"]["momentum_source"]; + Field3D mom2_2 = state2["species"]["c2+"]["momentum_source"]; BOUT_FOR_SERIAL(i, mom1_1.getRegion("RGN_NOBNDRY")) { // Changing temperature gradient of the heavy ion should not change the force EXPECT_DOUBLE_EQ(mom1_1[i], mom1_2[i]); @@ -288,13 +288,13 @@ TEST_P(BraginskiiThermalForceTest_MassRatio, CheckForIonMasses) { auto [aa1, aa2, thermal_force_present] = GetParam(); Options state{ {"species", - {{"M", {{"density", 1}, {"temperature", grad1}, {"charge", 1}, {"AA", aa1}}}, - {"N", {{"density", 1}, {"temperature", grad2}, {"charge", 2}, {"AA", aa2}}}}}}; - component.declareAllSpecies({"M", "N"}); + {{"M+", {{"density", 1}, {"temperature", grad1}, {"charge", 1}, {"AA", aa1}}}, + {"N+", {{"density", 1}, {"temperature", grad2}, {"charge", 2}, {"AA", aa2}}}}}}; + component.declareAllSpecies({"M+", "N+"}); component.transform(state); if (thermal_force_present) { - Field3D momentum_source1 = state["species"]["M"]["momentum_source"]; - Field3D momentum_source2 = state["species"]["N"]["momentum_source"]; + Field3D momentum_source1 = state["species"]["M+"]["momentum_source"]; + Field3D momentum_source2 = state["species"]["N+"]["momentum_source"]; BOUT_FOR_SERIAL(i, momentum_source1.getRegion("RGN_NOBNDRY")) { // The masses of the ions are different enough for thermal force to be present. @@ -302,8 +302,8 @@ TEST_P(BraginskiiThermalForceTest_MassRatio, CheckForIonMasses) { EXPECT_NE(momentum_source2[i], 0.); } } else { - EXPECT_FALSE(state["species"]["M"]["momentum_source"].isSet()); - EXPECT_FALSE(state["species"]["N"]["momentum_source"].isSet()); + EXPECT_FALSE(state["species"]["M+"]["momentum_source"].isSet()); + EXPECT_FALSE(state["species"]["N+"]["momentum_source"].isSet()); } } diff --git a/tests/unit/test_sheath_boundary.cxx b/tests/unit/test_sheath_boundary.cxx index dc00c0591..86ec45560 100644 --- a/tests/unit/test_sheath_boundary.cxx +++ b/tests/unit/test_sheath_boundary.cxx @@ -40,20 +40,19 @@ TEST_F(SheathBoundaryTest, DontSetPotential) { BoutReal Ti = 3.0; BoutReal Zi = 1.1; BoutReal si = 0.5; - - Options state {{"species", - {// Electrons - {"e", {{"density", N}, - {"temperature", Te}, - {"velocity", 0.0}}}, - // Ion species - {"h", {{"density", si*N}, - {"temperature", Ti}, - {"AA", 1.0}, - {"charge", Zi}, - {"velocity", 0.0}}}}}}; - - component.declareAllSpecies({"e", "h"}); + + Options state{{"species", + {// Electrons + {"e", {{"density", N}, {"temperature", Te}, {"velocity", 0.0}}}, + // Ion species + {"h+", + {{"density", si * N}, + {"temperature", Ti}, + {"AA", 1.0}, + {"charge", Zi}, + {"velocity", 0.0}}}}}}; + + component.declareAllSpecies({"e", "h+"}); component.transform(state); // Should have calculated, but not set potential @@ -76,14 +75,14 @@ TEST_F(SheathBoundaryTest, CalculatePotential) { {// Electrons {"e", {{"density", N}, {"temperature", Te}, {"velocity", 0.0}}}, // Ion species - {"h", + {"h+", {{"density", si * N}, {"temperature", Ti}, {"AA", 1.0}, {"charge", Zi}, {"velocity", 0.0}}}}}}; - component.declareAllSpecies({"e", "h"}); + component.declareAllSpecies({"e", "h+"}); component.transform(state); // Should have calculated, but not set potential From 1b11009d52b80937bbfddb76fe58ccf0cab67f4a Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Fri, 14 Nov 2025 22:41:42 +0000 Subject: [PATCH 22/63] Try to pass GuardedOptions by value in more places --- include/adas_reaction.hxx | 6 ++-- include/amjuel_reaction.hxx | 4 +-- include/component.hxx | 54 ++++++++++++++++++++++------ include/hydrogen_charge_exchange.hxx | 2 +- include/reaction.hxx | 6 ++-- src/adas_reaction.cxx | 8 ++--- src/component.cxx | 4 +-- src/hydrogen_charge_exchange.cxx | 4 +-- src/reaction.cxx | 4 +-- 9 files changed, 62 insertions(+), 30 deletions(-) diff --git a/include/adas_reaction.hxx b/include/adas_reaction.hxx index 74b179fc6..1f2b07eb2 100644 --- a/include/adas_reaction.hxx +++ b/include/adas_reaction.hxx @@ -84,7 +84,7 @@ struct OpenADAS : public ReactionBase { /// @param electron The electron species e.g. state["species"]["e"] /// @param from_ion The ion on the left of the reaction /// @param to_ion The ion on the right of the reaction - void calculate_rates(GuardedOptions electron, GuardedOptions from_ion, GuardedOptions to_ion); + void calculate_rates(GuardedOptions && electron, GuardedOptions && from_ion, GuardedOptions && to_ion); private: OpenADASRateCoefficient rate_coef; ///< Reaction rate coefficient OpenADASRateCoefficient radiation_coef; ///< Energy loss (radiation) coefficient @@ -120,8 +120,8 @@ struct OpenADASChargeExchange : public ReactionBase { /// from_A and to_A must have the same atomic mass /// from_B and to_B must have the same atomic mass /// The charge of from_A + from_B must equal the charge of to_A + to_B - void calculate_rates(GuardedOptions electron, GuardedOptions from_A, GuardedOptions from_B, GuardedOptions to_A, - GuardedOptions to_B); + void calculate_rates(GuardedOptions && electron, GuardedOptions && from_A, GuardedOptions && from_B, GuardedOptions && to_A, + GuardedOptions && to_B); private: OpenADASRateCoefficient rate_coef; ///< Reaction rate coefficient diff --git a/include/amjuel_reaction.hxx b/include/amjuel_reaction.hxx index d7bd10cd2..7e134c136 100644 --- a/include/amjuel_reaction.hxx +++ b/include/amjuel_reaction.hxx @@ -44,12 +44,12 @@ struct AmjuelReaction : public Reaction { // Most of the access information we need is inherited from the parent Reaction class. // The electron velocity will be read if it is set state_variable_access.setAccess("species:e:velocity", - {Permissions::AllRegions, Permissions::Nowhere, + {Permissions::All, Permissions::Nowhere, Permissions::Nowhere, Permissions::Nowhere}); // The energy source is set for electrons state_variable_access.setAccess("species:e:energy_source", {Permissions::Nowhere, Permissions::Nowhere, - Permissions::AllRegions, Permissions::Nowhere}); + Permissions::All, Permissions::Nowhere}); std::string heavy_reactant = this->parser->get_species(species_filter::reactants, species_filter::heavy)[0], heavy_product = this->parser->get_species(species_filter::products, diff --git a/include/component.hxx b/include/component.hxx index 1a3c78994..819c679af 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -185,7 +185,7 @@ T getNonFinal(const Options& option) { } } template -T getNonFinal(const GuardedOptions option) { +T getNonFinal(const GuardedOptions & option) { return getNonFinal(option.get()); } @@ -213,7 +213,7 @@ T get(const Options& option, [[maybe_unused]] const std::string& location = "") return getNonFinal(option); } template -T get(const GuardedOptions option, const std::string& location = "") { +T get(const GuardedOptions & option, const std::string& location = "") { return get(option.get(), location); } @@ -221,7 +221,7 @@ T get(const GuardedOptions option, const std::string& location = "") { /// Sets the final flag so setting the value /// afterwards will lead to an error bool isSetFinal(const Options& option, const std::string& location = ""); -bool isSetFinal(const GuardedOptions option, const std::string& location = ""); +bool isSetFinal(const GuardedOptions & option, const std::string& location = ""); #if CHECKLEVEL >= 1 /// A wrapper around isSetFinal() which captures debugging information @@ -239,7 +239,7 @@ bool isSetFinal(const GuardedOptions option, const std::string& location = ""); /// Sets the final flag so setting the value in the domain /// afterwards will lead to an error bool isSetFinalNoBoundary(const Options& option, const std::string& location = ""); -bool isSetFinalNoBoundary(const GuardedOptions option, const std::string& location = ""); +bool isSetFinalNoBoundary(const GuardedOptions & option, const std::string& location = ""); #if CHECKLEVEL >= 1 /// A wrapper around isSetFinalNoBoundary() which captures debugging information @@ -286,7 +286,11 @@ T getNoBoundary(const Options& option, [[maybe_unused]] const std::string& locat return getNonFinal(option); } template -T getNoBoundary(const GuardedOptions option, const std::string& location = "") { +T getNoBoundary(const GuardedOptions & option, const std::string& location = "") { + return getNoBoundary(option.get(Permissions::Interior), location); +} +template +T getNoBoundary(const GuardedOptions && option, const std::string& location = "") { return getNoBoundary(option.get(Permissions::Interior), location); } @@ -363,10 +367,15 @@ Options& set(Options& option, T value) { return option; } template -GuardedOptions set(GuardedOptions option, T value) { +GuardedOptions & set(GuardedOptions & option, T value) { set(option.getWritable(), value); return option; } +template +GuardedOptions && set(GuardedOptions && option, T value) { + set(option.getWritable(), value); + return std::move(option); +} /// Set values in an option. This could be optimised, but /// currently the is_value private variable would need to be modified. @@ -389,10 +398,15 @@ Options& setBoundary(Options& option, T value) { return option; } template -GuardedOptions setBoundary(GuardedOptions option, T value) { +GuardedOptions & setBoundary(GuardedOptions & option, T value) { setBoundary(option.getWritable(Permissions::Boundaries), value); return option; } +template +GuardedOptions && setBoundary(GuardedOptions && option, T value) { + setBoundary(option.getWritable(Permissions::Boundaries), value); + return std::move(option); +} /// Add value to a given option. If not already set, treats /// as zero and sets the option to the value. @@ -417,10 +431,15 @@ Options& add(Options& option, T value) { } } template -GuardedOptions add(GuardedOptions option, T value) { +GuardedOptions & add(GuardedOptions & option, T value) { add(option.getWritable(), value); return option; } +template +GuardedOptions && add(GuardedOptions && option, T value) { + add(option.getWritable(), value); + return std::move(option); +} /// Add value to a given option. If not already set, treats /// as zero and sets the option to the value. @@ -442,10 +461,15 @@ Options& subtract(Options& option, T value) { } } template -GuardedOptions subtract(GuardedOptions option, T value) { +GuardedOptions & subtract(GuardedOptions & option, T value) { subtract(option.getWritable(), value); return option; } +template +GuardedOptions && subtract(GuardedOptions && option, T value) { + subtract(option.getWritable(), value); + return std::move(option); +} template void set_with_attrs(Options& option, T value, std::initializer_list> attrs) { @@ -453,7 +477,11 @@ void set_with_attrs(Options& option, T value, std::initializer_list -void set_with_attrs(GuardedOptions option, T value, std::initializer_list> attrs) { +void set_with_attrs(GuardedOptions & option, T value, std::initializer_list> attrs) { + set_with_attrs(option.getWritable(), value, attrs); +} +template +void set_with_attrs(GuardedOptions && option, T value, std::initializer_list> attrs) { set_with_attrs(option.getWritable(), value, attrs); } @@ -467,7 +495,11 @@ inline void set_with_attrs(Options& option, Field3D value, std::initializer_list option.setAttributes(attrs); } template<> -inline void set_with_attrs(GuardedOptions option, Field3D value, std::initializer_list> attrs) { +inline void set_with_attrs(GuardedOptions & option, Field3D value, std::initializer_list> attrs) { + set_with_attrs(option.getWritable(), value, attrs); +} +template<> +inline void set_with_attrs(GuardedOptions && option, Field3D value, std::initializer_list> attrs) { set_with_attrs(option.getWritable(), value, attrs); } #endif diff --git a/include/hydrogen_charge_exchange.hxx b/include/hydrogen_charge_exchange.hxx index 1768bc8df..6e9a01f75 100644 --- a/include/hydrogen_charge_exchange.hxx +++ b/include/hydrogen_charge_exchange.hxx @@ -74,7 +74,7 @@ protected: /// atom_energy Energy removed from atom1, added to ion2 /// ion_energy Energy removed from ion1, added to atom2 /// - void calculate_rates(GuardedOptions atom1, GuardedOptions ion1, GuardedOptions atom2, GuardedOptions ion2, + void calculate_rates(GuardedOptions&& atom1, GuardedOptions&& ion1, GuardedOptions&& atom2, GuardedOptions&& ion2, Field3D& R, Field3D& atom_mom, Field3D& ion_mom, Field3D& atom_energy, Field3D& ion_energy, Field3D& atom_rate, Field3D& ion_rate, BoutReal& rate_multiplier, diff --git a/include/reaction.hxx b/include/reaction.hxx index 39277a208..96863a692 100644 --- a/include/reaction.hxx +++ b/include/reaction.hxx @@ -7,7 +7,7 @@ #include "reaction_diagnostic.hxx" #include "reaction_parser.hxx" -typedef GuardedOptions (*OPTYPE)(GuardedOptions, Field3D); +typedef GuardedOptions && (*OPTYPE)(GuardedOptions&&, Field3D); /** * @brief Temporary struct to use as a base class for all reactions components. Ensures @@ -84,7 +84,7 @@ protected: * * @param state Current sim state */ - void calc_weightsums(GuardedOptions state); + void calc_weightsums(GuardedOptions & state); /** * @brief Evaluate at a particular density and temperature @@ -170,7 +170,7 @@ private: /// Participation factors of all species std::map pfactors; - void zero_diagnostics(GuardedOptions state); + void zero_diagnostics(GuardedOptions& state); void transform_impl(GuardedOptions& state) override final; }; diff --git a/src/adas_reaction.cxx b/src/adas_reaction.cxx index dbfbb9c4b..741bdf782 100644 --- a/src/adas_reaction.cxx +++ b/src/adas_reaction.cxx @@ -112,7 +112,7 @@ BoutReal OpenADASRateCoefficient::evaluate(BoutReal T, BoutReal n) { return pow(10., eval_log_coef); } -void OpenADAS::calculate_rates(GuardedOptions electron, GuardedOptions from_ion, GuardedOptions to_ion) { +void OpenADAS::calculate_rates(GuardedOptions && electron, GuardedOptions && from_ion, GuardedOptions && to_ion) { AUTO_TRACE(); Field3D Ne = GET_VALUE(Field3D, electron["density"]); @@ -173,9 +173,9 @@ void OpenADAS::calculate_rates(GuardedOptions electron, GuardedOptions from_ion, subtract(electron["energy_source"], energy_loss); } -void OpenADASChargeExchange::calculate_rates(GuardedOptions electron, GuardedOptions from_A, - GuardedOptions from_B, GuardedOptions to_A, - GuardedOptions to_B) { +void OpenADASChargeExchange::calculate_rates(GuardedOptions && electron, GuardedOptions && from_A, + GuardedOptions && from_B, GuardedOptions && to_A, + GuardedOptions && to_B) { AUTO_TRACE(); // Check that the reaction conserves mass and charge diff --git a/src/component.cxx b/src/component.cxx index 775ab38ad..3d6d403f2 100644 --- a/src/component.cxx +++ b/src/component.cxx @@ -57,7 +57,7 @@ bool isSetFinal(const Options& option, [[maybe_unused]] const std::string& locat return option.isSet(); } -bool isSetFinal(const GuardedOptions option, const std::string& location) { +bool isSetFinal(const GuardedOptions & option, const std::string& location) { bool set = option.isSet(); #if CHECKLEVEL >= 1 Permissions::PermissionTypes perm = option.getHighestPermission(); @@ -79,7 +79,7 @@ bool isSetFinalNoBoundary(const Options& option, [[maybe_unused]] const std::str return option.isSet(); } -bool isSetFinalNoBoundary(const GuardedOptions option, const std::string& location) { +bool isSetFinalNoBoundary(const GuardedOptions & option, const std::string& location) { bool set = option.isSet(); #if CHECKLEVEL >= 1 Permissions::PermissionTypes perm = option.getHighestPermission(Permissions::Interior); diff --git a/src/hydrogen_charge_exchange.cxx b/src/hydrogen_charge_exchange.cxx index 3e1a2fafe..44cca5f8d 100644 --- a/src/hydrogen_charge_exchange.cxx +++ b/src/hydrogen_charge_exchange.cxx @@ -1,7 +1,7 @@ #include "../include/hydrogen_charge_exchange.hxx" -void HydrogenChargeExchange::calculate_rates(GuardedOptions atom1, GuardedOptions ion1, - GuardedOptions atom2, GuardedOptions ion2, +void HydrogenChargeExchange::calculate_rates(GuardedOptions&& atom1, GuardedOptions&& ion1, + GuardedOptions&& atom2, GuardedOptions&& ion2, Field3D &R, Field3D &atom_mom, Field3D &ion_mom, Field3D &atom_energy, Field3D &ion_energy, diff --git a/src/reaction.cxx b/src/reaction.cxx index ae837b12c..c1dc5a563 100644 --- a/src/reaction.cxx +++ b/src/reaction.cxx @@ -96,7 +96,7 @@ void Reaction::add_diagnostic(const std::string& sp_name, const std::string& dia * * @param state current simulation state */ -void Reaction::calc_weightsums(GuardedOptions state) { +void Reaction::calc_weightsums(GuardedOptions & state) { if (this->energy_weightsum < 0 || this->momentum_weightsum < 0) { this->momentum_weightsum = 0; this->energy_weightsum = 0; @@ -239,7 +239,7 @@ void Reaction::transform_impl(GuardedOptions& state) { * * @param state */ -void Reaction::zero_diagnostics(GuardedOptions state) { +void Reaction::zero_diagnostics(GuardedOptions& state) { if (this->diagnose) { for (auto& [key, diag] : diagnostics) { set(state[diag.name], 0.0); From aed7fa79ab4e39405a6434d523ece2d7245bef57 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Fri, 14 Nov 2025 23:24:19 +0000 Subject: [PATCH 23/63] Switched to using class enums for permission information --- include/amjuel_reaction.hxx | 12 +- include/braginskii_thermal_force.hxx | 4 +- include/component.hxx | 8 +- include/detachment_controller.hxx | 4 +- include/electron_force_balance.hxx | 4 +- include/fixed_fraction_radiation.hxx | 2 +- include/fixed_temperature.hxx | 2 +- include/fixed_velocity.hxx | 2 +- include/guarded_options.hxx | 20 +- include/permissions.hxx | 123 +++++---- include/set_temperature.hxx | 2 +- include/simple_conduction.hxx | 4 +- include/simple_pump.hxx | 2 +- include/sound_speed.hxx | 4 +- include/temperature_feedback.hxx | 2 +- include/upstream_density_feedback.hxx | 4 +- src/anomalous_diffusion.cxx | 4 +- src/binormal_stpm.cxx | 2 +- src/braginskii_collisions.cxx | 16 +- src/braginskii_friction.cxx | 2 +- src/component.cxx | 12 +- src/electromagnetic.cxx | 2 +- src/evolve_momentum.cxx | 2 +- src/evolve_pressure.cxx | 4 +- src/guarded_options.cxx | 34 ++- src/isothermal.cxx | 9 +- src/permissions.cxx | 112 +++++--- src/quasineutral.cxx | 2 +- src/relax_potential.cxx | 2 +- src/sheath_boundary.cxx | 4 +- src/sheath_boundary_insulating.cxx | 6 +- src/sheath_boundary_simple.cxx | 6 +- src/vorticity.cxx | 4 +- src/zero_current.cxx | 2 +- tests/unit/test_guarded_options.cxx | 144 +++++----- tests/unit/test_permissions.cxx | 379 ++++++++++++-------------- 36 files changed, 478 insertions(+), 469 deletions(-) diff --git a/include/amjuel_reaction.hxx b/include/amjuel_reaction.hxx index 7e134c136..907e4526d 100644 --- a/include/amjuel_reaction.hxx +++ b/include/amjuel_reaction.hxx @@ -43,13 +43,13 @@ struct AmjuelReaction : public Reaction { this->includes_sigma_v_e = amjuel_data.includes_sigma_v_e; // Most of the access information we need is inherited from the parent Reaction class. // The electron velocity will be read if it is set - state_variable_access.setAccess("species:e:velocity", - {Permissions::All, Permissions::Nowhere, - Permissions::Nowhere, Permissions::Nowhere}); + state_variable_access.setAccess( + "species:e:velocity", + {Regions::All, Regions::Nowhere, Regions::Nowhere, Regions::Nowhere}); // The energy source is set for electrons - state_variable_access.setAccess("species:e:energy_source", - {Permissions::Nowhere, Permissions::Nowhere, - Permissions::All, Permissions::Nowhere}); + state_variable_access.setAccess( + "species:e:energy_source", + {Regions::Nowhere, Regions::Nowhere, Regions::All, Regions::Nowhere}); std::string heavy_reactant = this->parser->get_species(species_filter::reactants, species_filter::heavy)[0], heavy_product = this->parser->get_species(species_filter::products, diff --git a/include/braginskii_thermal_force.hxx b/include/braginskii_thermal_force.hxx index 09451d4db..a0c696607 100644 --- a/include/braginskii_thermal_force.hxx +++ b/include/braginskii_thermal_force.hxx @@ -51,7 +51,7 @@ struct BraginskiiThermalForce : public Component { // FIXME: These are only accessed if electrons present state_variable_access.setAccess(readOnly("species:e:temperature")); state_variable_access.setAccess( - readOnly("species:{ions}:density", Permissions::Interior)); + readOnly("species:{ions}:density", Regions::Interior)); state_variable_access.setAccess(readWrite("species:{ions}:momentum_source")); state_variable_access.setAccess(readWrite("species:e:momentum_source")); } else if (ion_ion) { @@ -63,7 +63,7 @@ struct BraginskiiThermalForce : public Component { if (!electron_ion) { // FIXME: This is only accessed for heavy ions state_variable_access.setAccess( - readOnly("species:{ions}:density", Permissions::Interior)); + readOnly("species:{ions}:density", Regions::Interior)); // FIXME: This is only set for heavy and light ions, not intermediate state_variable_access.setAccess(readWrite("species:{ions}:momentum_source")); } diff --git a/include/component.hxx b/include/component.hxx index 819c679af..51e51e89d 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -287,11 +287,11 @@ T getNoBoundary(const Options& option, [[maybe_unused]] const std::string& locat } template T getNoBoundary(const GuardedOptions & option, const std::string& location = "") { - return getNoBoundary(option.get(Permissions::Interior), location); + return getNoBoundary(option.get(Regions::Interior), location); } template T getNoBoundary(const GuardedOptions && option, const std::string& location = "") { - return getNoBoundary(option.get(Permissions::Interior), location); + return getNoBoundary(option.get(Regions::Interior), location); } #if CHECKLEVEL >= 1 @@ -399,12 +399,12 @@ Options& setBoundary(Options& option, T value) { } template GuardedOptions & setBoundary(GuardedOptions & option, T value) { - setBoundary(option.getWritable(Permissions::Boundaries), value); + setBoundary(option.getWritable(Regions::Boundaries), value); return option; } template GuardedOptions && setBoundary(GuardedOptions && option, T value) { - setBoundary(option.getWritable(Permissions::Boundaries), value); + setBoundary(option.getWritable(Regions::Boundaries), value); return std::move(option); } diff --git a/include/detachment_controller.hxx b/include/detachment_controller.hxx index 60c943fdf..fdea2e584 100644 --- a/include/detachment_controller.hxx +++ b/include/detachment_controller.hxx @@ -10,8 +10,8 @@ struct DetachmentController : public Component { DetachmentController(std::string, Options& options, Solver*) - : Component({readOnly("species:{neutral}:density", Permissions::Interior), - readOnly("species:e:density", Permissions::Interior), readOnly("time"), + : Component({readOnly("species:{neutral}:density", Regions::Interior), + readOnly("species:e:density", Regions::Interior), readOnly("time"), readWrite("species:{sp}:{output}")}) { ASSERT0(BoutComm::size() == 1); // Only works on one processor Options& detachment_controller_options = options["detachment_controller"]; diff --git a/include/electron_force_balance.hxx b/include/electron_force_balance.hxx index 6ae7de9c5..ab8c61351 100644 --- a/include/electron_force_balance.hxx +++ b/include/electron_force_balance.hxx @@ -21,11 +21,11 @@ struct ElectronForceBalance : public Component { ElectronForceBalance(std::string name, Options& alloptions, Solver*) : Component({readOnly("species:e:pressure"), - readOnly("species:e:density", Permissions::Interior), + readOnly("species:e:density", Regions::Interior), readOnly("species:e:charge"), // FIXME: Only writes if already exists readWrite("species:e:momentum_source"), - readIfSet("species:{non_electrons}:density", Permissions::Interior), + readIfSet("species:{non_electrons}:density", Regions::Interior), readIfSet("species:{non_electrons}:charge"), // FIXME: Only written if density and charge have been set. readWrite("species:{non_electrons}:momentum_source")}) { diff --git a/include/fixed_fraction_radiation.hxx b/include/fixed_fraction_radiation.hxx index 9abf9f19c..97e2c0409 100644 --- a/include/fixed_fraction_radiation.hxx +++ b/include/fixed_fraction_radiation.hxx @@ -346,7 +346,7 @@ struct FixedFractionRadiation : public Component { /// - /// - fraction FixedFractionRadiation(std::string name, Options& alloptions, Solver* UNUSED(solver)) - : Component({readOnly("species:e:{inputs}", Permissions::Interior), + : Component({readOnly("species:e:{inputs}", Regions::Interior), readWrite("species:e:energy_source")}), name(name) { auto& options = alloptions[name]; diff --git a/include/fixed_temperature.hxx b/include/fixed_temperature.hxx index cbde4328c..69bb46cd3 100644 --- a/include/fixed_temperature.hxx +++ b/include/fixed_temperature.hxx @@ -11,7 +11,7 @@ struct FixedTemperature : public Component { /// - /// - temperature value (expression) in units of eV FixedTemperature(std::string name, Options& alloptions, Solver* UNUSED(solver)) - : Component({readIfSet("species:{name}:density", Permissions::Interior), + : Component({readIfSet("species:{name}:density", Regions::Interior), readWrite("species:{name}:temperature"), // FIXME: Only written if density is set readWrite("species:{name}:pressure")}), diff --git a/include/fixed_velocity.hxx b/include/fixed_velocity.hxx index d8a8ed872..970384cb7 100644 --- a/include/fixed_velocity.hxx +++ b/include/fixed_velocity.hxx @@ -10,7 +10,7 @@ struct FixedVelocity : public Component { FixedVelocity(std::string name, Options& alloptions, Solver* UNUSED(solver)) - : Component({readIfSet("species:{name}:density", Permissions::Interior), + : Component({readIfSet("species:{name}:density", Regions::Interior), // FIXME: AA is only read if density is set readOnly("species:{name}:AA"), readWrite("species:{name}:{output}")}), name(name) { diff --git a/include/guarded_options.hxx b/include/guarded_options.hxx index 4cfa3fe94..5f7343e32 100644 --- a/include/guarded_options.hxx +++ b/include/guarded_options.hxx @@ -39,37 +39,35 @@ public: /// Get read-only access to the underlying Options object. Throws /// BoutException if there is not read-permission for this object. - const Options& get(Permissions::Regions region = Permissions::AllRegions) const; + const Options& get(Regions region = Regions::All) const; /// Get read-write access to the underlying Options object. Throws /// BoutException if there is not write-permission for this object. - Options& getWritable(Permissions::Regions region = Permissions::AllRegions); + Options& getWritable(Regions region = Regions::All); /// Returns a list of variables with read-only permission but which /// have not been accessed using the `get()` method. - std::map unreadItems() const; + std::map unreadItems() const; /// Returns a list of variables with read-write permission but which /// have not been accessed using the `getWritable()` method. - std::map unwrittenItems() const; + std::map unwrittenItems() const; bool operator==(const GuardedOptions& other) const; bool operator!=(const GuardedOptions& other) const; - Permissions::PermissionTypes - getHighestPermission(Permissions::Regions region = Permissions::AllRegions) const { + PermissionTypes getHighestPermission(Regions region = Regions::All) const { return permissions->getHighestPermission(options->str(), region).first; } private: Options* options{nullptr}; Permissions* permissions{nullptr}; - mutable std::shared_ptr> unread_variables, + mutable std::shared_ptr> unread_variables, unwritten_variables; - GuardedOptions( - Options* options, Permissions* permissions, - std::shared_ptr> unread_vars, - std::shared_ptr> unwritten_vars) + GuardedOptions(Options* options, Permissions* permissions, + std::shared_ptr> unread_vars, + std::shared_ptr> unwritten_vars) : options(options), permissions(permissions), unread_variables(unread_vars), unwritten_variables(unwritten_vars) {} }; diff --git a/include/permissions.hxx b/include/permissions.hxx index bd08cd3eb..6689dedd7 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -5,27 +5,43 @@ #include #include +/// Ways in which someone is allowed to access the variable, with +/// increasing levels of rights. "ReadIfSet" indicates that +/// variables should only be read if already set. "Final" refers to +/// the last time anyone is allowed to write to the variable. These +/// two concepts need to be captured to decide the order in which to +/// execute components. +enum class PermissionTypes { None = -1, ReadIfSet, Read, Write, Final, END }; + +/// The regions of the domain to which a particular permission +/// apply. These are designed to be used as bit-flags. +enum class Regions { + Nowhere = 0, + Interior = 1 << 0, + Boundaries = 1 << 1, + All = Interior | Boundaries +}; + +inline Regions operator&(Regions a, Regions b) { + return static_cast(static_cast(a) & static_cast(b)); +} + +inline Regions operator|(Regions a, Regions b) { + return static_cast(static_cast(a) | static_cast(b)); +} + +inline Regions operator~(Regions a) { return static_cast(~static_cast(a)); } + /// Class to store information on whether particular variables an be /// read from and/or written to. These permissions can apply on /// particular regions of the domain. class Permissions { public: - /// Ways in which someone is allowed to access the variable, with - /// increasing levels of rights. "ReadIfSet" indicates that - /// variables should only be read if already set. "Final" refers to - /// the last time anyone is allowed to write to the variable. These - /// two concepts need to be captured to decide the order in which to - /// execute components. - enum PermissionTypes { None = -1, ReadIfSet, Read, Write, Final, PERMISSION_TYPES_END }; - - /// The regions of the domain to which a particular permission - /// apply. These are designed to be used as bit-flags. - enum Regions { - Nowhere = 0, - Interior = 1 << 0, - Boundaries = 1 << 1, - AllRegions = Interior | Boundaries - }; + Permissions() = default; + Permissions(Permissions& x) = default; + Permissions(Permissions&& x) = default; + Permissions& operator=(Permissions& x) = default; + Permissions& operator=(Permissions&& x) = default; const static std::map fundamental_regions; @@ -40,11 +56,7 @@ public: /// }, final_write_boundaries_read_interior = { Interior, Nowhere, /// Boundaries }; /// - using AccessRights = std::array; - - Permissions() = default; - Permissions(Permissions& other) = default; - Permissions(Permissions&& other) = default; + using AccessRights = std::array(PermissionTypes::END)>; /// Create permission from an initialiser list. Each item in the /// initialiser list should be a pair made up of the name of a @@ -57,20 +69,20 @@ public: /// /// Permissions example({ /// // Permission to read charge only if it has been set - /// {"species:he:charge", {Permissions::AllRegions, Permissions::Nowhere, - /// Permissions::Nowhere,, Permissions::Nowhere}}, + /// {"species:he:charge", {Regions::AllRegions, Regions::Nowhere, + /// Regions::Nowhere,, Regions::Nowhere}}, /// // Read permission for atomic mass - /// {"species:he:AA", {Permissions::Nowhere, Permissions::AllRegions, - /// Permissions::Nowhere, Permissions::Nowhere}}, + /// {"species:he:AA", {Regions::Nowhere, Regions::AllRegions, + /// Regions::Nowhere, Regions::Nowhere}}, /// // Read permissions for density - /// {"species:he:density", {Permissions::Nowhere, Permissions::AllRegions, - /// Permissions::Nowhere, Permissions::Nowhere}}, + /// {"species:he:density", {Regions::Nowhere, Regions::AllRegions, + /// Regions::Nowhere, Regions::Nowhere}}, /// // Read and write permissions for pressure in the interior region - /// {"species:he:pressure", {Permissions::Nowhere, Permissions::Nowhere, - /// Permissions::Interior, Permissions::Nowhere}}, + /// {"species:he:pressure", {Regions::Nowhere, Regions::Nowhere, + /// Regions::Interior, Regions::Nowhere}}, /// // Set the final value for collision frequency - /// {"species:he:collision_frequency", {Permissions::Nowhere, - /// Permissions::Nowhere, Permissions::Nowhere, Permissions::AllRegions}} + /// {"species:he:collision_frequency", {Regions::Nowhere, + /// Regions::Nowhere, Regions::Nowhere, Regions::AllRegions}} /// }); /// /// If a variable is not included in the initialiser list then it is @@ -88,8 +100,8 @@ public: /// frequency for every species you would write: /// /// Permissions example2({ - /// {"species:{name}:collision_frequency", {Permissions::Nowhere, - /// Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere}} + /// {"species:{name}:collision_frequency", {Regions::Nowhere, + /// Regions::AllRegions, Regions::Nowhere, Regions::Nowhere}} /// }); /// example2.substitute("name", {"he+", "d+", "e", "d", "he"}); /// @@ -101,14 +113,14 @@ public: /// everywhere but only writeable in the interior, you would use /// /// permissions.setAccess("species:he:density", - /// {Permissions::Nowhere, Permissions::AllRegions, - /// Permissions::Interior, Permissions::Nowhere}) + /// {Regions::Nowhere, Regions::AllRegions, + /// Regions::Interior, Regions::Nowhere}) /// 0 /// or, equivalently, /// /// permissions.setAccess("species:he:density", - /// {Permissions::Nowhere, Permissions::Boundary, - /// Permissions::Interior, Permissions::Nowhere}); + /// {Regions::Nowhere, Permissions::Boundary, + /// Regions::Interior, Regions::Nowhere}); /// /// As in the constructor, if the variable name is just a section in /// an Options object then the permissions apply to all children of @@ -119,6 +131,8 @@ public: setAccess(info.first, info.second); } + void printAllPermissions() const; + /// Replace a placeholder in the names of variables stored in this /// object. This is useful if you need to access the same variable /// for multiple species. For example, the following code gives @@ -126,10 +140,10 @@ public: /// for every species. /// /// Permissions example({ - /// {"species:{name}:density", {Permissions::Nowhere, Permissions::AllRegions, - /// Permissions::Nowhere, Permissions::Nowhere}}, - /// {"species:{name}:collision_frequency", {Permissions::Nowhere, - /// Permissions::Nowhere, Permissions::AllRegions, Permissions::Nowhere}}, + /// {"species:{name}:density", {Regions::Nowhere, Regions::AllRegions, + /// Regions::Nowhere, Regions::Nowhere}}, + /// {"species:{name}:collision_frequency", {Regions::Nowhere, + /// Regions::Nowhere, Regions::AllRegions, Regions::Nowhere}}, /// }); /// example.substitute("name", {"d", "d+", "t", "t+", "he", "he+", "c", "c+", "e"}); /// @@ -144,9 +158,10 @@ public: /// returned indicates the name of the variable or section from /// which the access rights are derived. If there is no matching /// section then it will be an empty string. - std::pair canAccess(const std::string& variable, - PermissionTypes permission = Read, - Regions region = AllRegions) const; + std::pair + canAccess(const std::string& variable, + PermissionTypes permission = PermissionTypes::Read, + Regions region = Regions::All) const; /// Get the highest permission level with which the given variable /// can be accessed in the given region. The second item @@ -154,23 +169,23 @@ public: /// which the access rights are derived. If there is no matching /// section then it will be an empty string. std::pair - getHighestPermission(const std::string& variable, Regions region = AllRegions) const; + getHighestPermission(const std::string& variable, Regions region = Regions::All) const; /// Get a set of variables and regions for which there is the /// specified level of permission to access. If ``highestOnly`` is /// true then it will only include variables/regions for which this /// is the highest permission. /// - /// Permissions example({"test", {Permissions::Nowhere, Permissions::AllRegions, - /// Permissions::AllRegions, Permissions:Nowhere}}); + /// Permissions example({"test", {Regions::Nowhere, Regions::AllRegions, + /// Regions::AllRegions, Permissions:Nowhere}}); /// // Print variables which can be read /// for (const auto [varname, region] : - /// example.getVariablesWithPermission(Permissions::Read, false)) + /// example.getVariablesWithPermission(PermissionTypes::Read), false)) /// std::cout << "Variable name: " << varname << ", Region ID: " << region << /// "\n"; /// // Print variables which can only be read (not written) /// for (const auto [varname, region] : - /// example.getVariablesWithPermission(Permissions::Read, true)) + /// example.getVariablesWithPermission(PermissionTypes::Read), true)) /// std::cout << "Variable name: " << varname << ", Region ID: " << region << /// "\n"; /// @@ -203,22 +218,22 @@ private: /// Convenience function to return an object expressing that the /// variable should have ReadIfSet permissions in the specified regions. std::pair -readIfSet(std::string varname, Permissions::Regions region = Permissions::AllRegions); +readIfSet(std::string varname, Regions region = Regions::All); /// Convenience function to return an object expressing that the /// variable should have Read permissions in the specified regions. -std::pair -readOnly(std::string varname, Permissions::Regions region = Permissions::AllRegions); +std::pair readOnly(std::string varname, + Regions region = Regions::All); /// Convenience function to return an object expressing that the /// variable should have Write permissions in the specified regions. std::pair -readWrite(std::string varname, Permissions::Regions region = Permissions::AllRegions); +readWrite(std::string varname, Regions region = Regions::All); /// Convenience function to return an object expressing that the /// variable should have Final permissions in the specified regions. std::pair -writeFinal(std::string varname, Permissions::Regions region = Permissions::AllRegions); +writeFinal(std::string varname, Regions region = Regions::All); /// Convenience function to return an object expressing that the /// variable should have Write permissions on the boundaries. It will diff --git a/include/set_temperature.hxx b/include/set_temperature.hxx index dddcc23ee..d40e6e785 100644 --- a/include/set_temperature.hxx +++ b/include/set_temperature.hxx @@ -23,7 +23,7 @@ struct SetTemperature : public Component { /// - /// - temperature_from name of species SetTemperature(std::string name, Options& alloptions, Solver* UNUSED(solver)) - : Component({readIfSet("species:{name}:density", Permissions::Interior), + : Component({readIfSet("species:{name}:density", Regions::Interior), readOnly("species:{from}:temperature"), readWrite("species:{name}:temperature"), // FIXME: Only written if density set diff --git a/include/simple_conduction.hxx b/include/simple_conduction.hxx index 2b82bba84..700dd7e45 100644 --- a/include/simple_conduction.hxx +++ b/include/simple_conduction.hxx @@ -15,7 +15,7 @@ /// https://farside.ph.utexas.edu/teaching/plasma/lectures1/node35.html struct SimpleConduction : public Component { SimpleConduction(std::string name, Options& alloptions, Solver*) - : Component({readOnly("species:{name}:temperature", Permissions::Interior), + : Component({readOnly("species:{name}:temperature", Regions::Interior), readOnly("species:{name}:AA"), readWrite("species:{name}:energy_source")}), name(name) { @@ -61,7 +61,7 @@ struct SimpleConduction : public Component { if (density <= 0.0) { state_variable_access.setAccess( - readOnly("species:{name}:density", Permissions::Interior)); + readOnly("species:{name}:density", Regions::Interior)); } state_variable_access.substitute("name", {name}); } diff --git a/include/simple_pump.hxx b/include/simple_pump.hxx index 0bfa248f3..1a664c727 100644 --- a/include/simple_pump.hxx +++ b/include/simple_pump.hxx @@ -10,7 +10,7 @@ struct SimplePump : public Component { SimplePump(std::string name, Options& alloptions, Solver*) - : Component({readOnly("species:{name}:density", Permissions::Interior), + : Component({readOnly("species:{name}:density", Regions::Interior), readWrite("species:{name}:density_source")}), name(name) { diff --git a/include/sound_speed.hxx b/include/sound_speed.hxx index 10c5290de..83c0862ae 100644 --- a/include/sound_speed.hxx +++ b/include/sound_speed.hxx @@ -12,11 +12,11 @@ /// so should run after those have been set. struct SoundSpeed : public Component { SoundSpeed(std::string name, Options& alloptions, Solver*) - : Component({readOnly("species:{all_species}:pressure", Permissions::Interior), + : Component({readOnly("species:{all_species}:pressure", Regions::Interior), writeFinal("sound_speed"), writeFinal("fastest_wave"), readIfSet("species:{sp}:AA"), // FIXME: Only read if AA is set - readIfSet("species:{sp}:{opt_inputs}", Permissions::Interior)}) { + readIfSet("species:{sp}:{opt_inputs}", Regions::Interior)}) { Options &options = alloptions[name]; electron_dynamics = options["electron_dynamics"] .doc("Include electron sound speed?") diff --git a/include/temperature_feedback.hxx b/include/temperature_feedback.hxx index 47364f0ed..476fd862a 100644 --- a/include/temperature_feedback.hxx +++ b/include/temperature_feedback.hxx @@ -23,7 +23,7 @@ struct TemperatureFeedback : public Component { /// - source_shape The initial source that is scaled by a time-varying factor /// TemperatureFeedback(std::string name, Options& alloptions, Solver*) - : Component({readOnly("species:{name}:temperature", Permissions::Interior), + : Component({readOnly("species:{name}:temperature", Regions::Interior), readOnly("time"), readWrite("species:{sp}:energy_source")}), name(name) { diff --git a/include/upstream_density_feedback.hxx b/include/upstream_density_feedback.hxx index 1514a1a47..b9ed47ff8 100644 --- a/include/upstream_density_feedback.hxx +++ b/include/upstream_density_feedback.hxx @@ -22,10 +22,10 @@ struct UpstreamDensityFeedback : public Component { /// UpstreamDensityFeedback(std::string name, Options& alloptions, Solver*) : Component({readOnly("time"), - readOnly("species:{name}:density", Permissions::Interior), + readOnly("species:{name}:density", Regions::Interior), // FIXME: These are only read if BOTH are set readIfSet("species:{name}:AA"), - readIfSet("species:{name}:velocity", Permissions::Interior), + readIfSet("species:{name}:velocity", Regions::Interior), readWrite("species:{name}:density_source"), // FIXME: This is only set if AA and density_source are set readWrite("species:{name}:energy_source")}), diff --git a/src/anomalous_diffusion.cxx b/src/anomalous_diffusion.cxx index 939175400..17481337a 100644 --- a/src/anomalous_diffusion.cxx +++ b/src/anomalous_diffusion.cxx @@ -7,8 +7,8 @@ using bout::globals::mesh; AnomalousDiffusion::AnomalousDiffusion(std::string name, Options& alloptions, Solver*) - : Component({readOnly("species:{name}:density", Permissions::Interior), - readIfSet("species:{name}:{optional}", Permissions::Interior), + : Component({readOnly("species:{name}:density", Regions::Interior), + readIfSet("species:{name}:{optional}", Regions::Interior), readWrite("species:{name}:{output}")}), name(name) { // Normalisations diff --git a/src/binormal_stpm.cxx b/src/binormal_stpm.cxx index 843f1aff7..2b95aeca3 100644 --- a/src/binormal_stpm.cxx +++ b/src/binormal_stpm.cxx @@ -10,7 +10,7 @@ using bout::globals::mesh; BinormalSTPM::BinormalSTPM(std::string name, Options& alloptions, [[maybe_unused]] Solver* solver) : Component({ - readIfSet("species:{all_species}:{input}", Permissions::Interior), + readIfSet("species:{all_species}:{input}", Regions::Interior), readOnly("species:{all_species}:AA"), readWrite("species:{all_species}:{output}"), }), diff --git a/src/braginskii_collisions.cxx b/src/braginskii_collisions.cxx index 9a351acfb..a49d53d3d 100644 --- a/src/braginskii_collisions.cxx +++ b/src/braginskii_collisions.cxx @@ -21,9 +21,9 @@ #include "../include/hermes_utils.hxx" BraginskiiCollisions::BraginskiiCollisions(const std::string& name, Options& alloptions, Solver*) - : Component({readOnly("species:{non_electrons}:density", Permissions::Interior), + : Component({readOnly("species:{non_electrons}:density", Regions::Interior), readIfSet("species:{non_electrons}:charge"), - readIfSet("species:{negative_ions}:temperature", Permissions::Interior), + readIfSet("species:{negative_ions}:temperature", Regions::Interior), readOnly("species:{all_species}:AA")}) { AUTO_TRACE(); const Options& units = alloptions["units"]; @@ -63,16 +63,16 @@ BraginskiiCollisions::BraginskiiCollisions(const std::string& name, Options& all options["diagnose"].doc("Output additional diagnostics?").withDefault(false); state_variable_access.setAccess( - readOnly("species:{electrons}:temperature", Permissions::Interior)); + readOnly("species:{electrons}:temperature", Regions::Interior)); state_variable_access.setAccess( - readOnly("species:{electrons}:density", Permissions::Interior)); + readOnly("species:{electrons}:density", Regions::Interior)); if (electron_electron) { state_variable_access.setAccess(readWrite( "species:{electrons}:collision_frequencies:{electrons}_{electrons2}_coll")); } if (electron_ion) { state_variable_access.setAccess( - readOnly("species:{positive_ions}:temperature", Permissions::Interior)); + readOnly("species:{positive_ions}:temperature", Regions::Interior)); state_variable_access.setAccess( readWrite("species:{positive_ions}:collision_frequencies:{positive_ions}_{" "electrons}_coll")); @@ -80,18 +80,18 @@ BraginskiiCollisions::BraginskiiCollisions(const std::string& name, Options& all "species:{electrons}:collision_frequencies:{electrons}_{positive_ions}_coll")); } else { state_variable_access.setAccess( - readIfSet("species:{positive_ions}:temperature", Permissions::Interior)); + readIfSet("species:{positive_ions}:temperature", Regions::Interior)); } if (electron_neutral) { state_variable_access.setAccess( - readOnly("species:{neutrals}:temperature", Permissions::Interior)); + readOnly("species:{neutrals}:temperature", Regions::Interior)); state_variable_access.setAccess(readWrite( "species:{neutrals}:collision_frequencies:{neutrals}_{electrons}_coll")); state_variable_access.setAccess(readWrite( "species:{electrons}:collision_frequencies:{electrons}_{neutrals}_coll")); } else { state_variable_access.setAccess( - readIfSet("species:{neutrals}:temperature", Permissions::Interior)); + readIfSet("species:{neutrals}:temperature", Regions::Interior)); } if (ion_ion) { state_variable_access.setAccess( diff --git a/src/braginskii_friction.cxx b/src/braginskii_friction.cxx index 4b0bcfcc5..bb7f6cf78 100644 --- a/src/braginskii_friction.cxx +++ b/src/braginskii_friction.cxx @@ -16,7 +16,7 @@ BraginskiiFriction::BraginskiiFriction(const std::string& name, Options& allopti Solver*) // FIXME: Not all species actually have collisions calculated : Component({readOnly("species:{all_species}:density"), - readIfSet("species:{all_species}:velocity", Permissions::Interior), + readIfSet("species:{all_species}:velocity", Regions::Interior), readOnly("species:{all_species}:AA"), readIfSet("species:{all_species}:charge"), readOnly("species:{all_species}:collision_frequencies:{all_species}_{" diff --git a/src/component.cxx b/src/component.cxx index 3d6d403f2..66a93a622 100644 --- a/src/component.cxx +++ b/src/component.cxx @@ -60,8 +60,9 @@ bool isSetFinal(const Options& option, [[maybe_unused]] const std::string& locat bool isSetFinal(const GuardedOptions & option, const std::string& location) { bool set = option.isSet(); #if CHECKLEVEL >= 1 - Permissions::PermissionTypes perm = option.getHighestPermission(); - if (perm >= Permissions::Read or (perm == Permissions::ReadIfSet and set)) { + PermissionTypes perm = option.getHighestPermission(); + if (static_cast(perm) >= static_cast(PermissionTypes::Read) + or (perm == PermissionTypes::ReadIfSet and set)) { const Options& opt = option.get(); const_cast(opt).attributes["final"] = location; const_cast(opt).attributes["final-domain"] = location; @@ -82,10 +83,11 @@ bool isSetFinalNoBoundary(const Options& option, [[maybe_unused]] const std::str bool isSetFinalNoBoundary(const GuardedOptions & option, const std::string& location) { bool set = option.isSet(); #if CHECKLEVEL >= 1 - Permissions::PermissionTypes perm = option.getHighestPermission(Permissions::Interior); - if (perm >= Permissions::Read or (perm == Permissions::ReadIfSet and set)) { + PermissionTypes perm = option.getHighestPermission(Regions::Interior); + if (static_cast(perm) >= static_cast(PermissionTypes::Read) + or (perm == PermissionTypes::ReadIfSet and set)) { // Mark option as final inside the domain, but not in the boundary - const_cast(option.get(Permissions::Interior)).attributes["final-domain"] = + const_cast(option.get(Regions::Interior)).attributes["final-domain"] = location; } #endif diff --git a/src/electromagnetic.cxx b/src/electromagnetic.cxx index 2d5cb74cc..31aa1b39b 100644 --- a/src/electromagnetic.cxx +++ b/src/electromagnetic.cxx @@ -18,7 +18,7 @@ Electromagnetic::Electromagnetic(std::string name, Options& alloptions, Solver* writeFinal("species:{all_species}:momentum"), writeFinal("species:{all_species}:velocity"), readOnly("time"), readOnly("species:{all_species}:AA"), - readOnly("species:{all_species}:density", Permissions::Interior), + readOnly("species:{all_species}:density", Regions::Interior), readWrite("fields:Apar")}) { AUTO_TRACE(); diff --git a/src/evolve_momentum.cxx b/src/evolve_momentum.cxx index 12dc5722f..045eab67b 100644 --- a/src/evolve_momentum.cxx +++ b/src/evolve_momentum.cxx @@ -14,7 +14,7 @@ using bout::globals::mesh; EvolveMomentum::EvolveMomentum(std::string name, Options& alloptions, Solver* solver) : Component({readOnly("species:{name}:AA"), - readOnly("species:{name}:density", Permissions::Interior), + readOnly("species:{name}:density", Regions::Interior), readWrite("species:{name}:{outputs}")}), name(name) { AUTO_TRACE(); diff --git a/src/evolve_pressure.cxx b/src/evolve_pressure.cxx index 6365bc5a3..6d1a123f6 100644 --- a/src/evolve_pressure.cxx +++ b/src/evolve_pressure.cxx @@ -16,8 +16,8 @@ using bout::globals::mesh; EvolvePressure::EvolvePressure(std::string name, Options& alloptions, Solver* solver) - : Component( - {readOnly("species:{name}:{inputs}", Permissions::Interior), readWrite("species:{name}:{outputs}")}), + : Component({readOnly("species:{name}:{inputs}", Regions::Interior), + readWrite("species:{name}:{outputs}")}), name(name) { AUTO_TRACE(); diff --git a/src/guarded_options.cxx b/src/guarded_options.cxx index 2885387f7..0c4e7cc7e 100644 --- a/src/guarded_options.cxx +++ b/src/guarded_options.cxx @@ -18,25 +18,24 @@ bool isSetRecursive(Options& opt, std::string varname) { GuardedOptions::GuardedOptions(Options* options, Permissions* permissions) : options(options), permissions(permissions), - unread_variables(std::make_shared>()), - unwritten_variables( - std::make_shared>()) { + unread_variables(std::make_shared>()), + unwritten_variables(std::make_shared>()) { #if CHECKLEVEL >= 1 if (permissions != nullptr) { - *unread_variables = permissions->getVariablesWithPermission(Permissions::Read); + *unread_variables = permissions->getVariablesWithPermission(PermissionTypes::Read); // Only add variables with permission ReadIfSet to // unread_variables if they are already present in the options // object if (options != nullptr) { for (auto& [varname, region] : - permissions->getVariablesWithPermission(Permissions::ReadIfSet)) { + permissions->getVariablesWithPermission(PermissionTypes::ReadIfSet)) { if (isSetRecursive(*options, varname)) { unread_variables->insert({varname, region}); } } } *unwritten_variables = - permissions->getVariablesWithPermission(Permissions::Write, false); + permissions->getVariablesWithPermission(PermissionTypes::Write, false); } #endif } @@ -65,12 +64,11 @@ std::map GuardedOptions::getChildren() { return result; } -void updateAccessRecords(std::map& records, - const std::string& name, Permissions::Regions region) { +void updateAccessRecords(std::map& records, const std::string& name, + Regions region) { if (records.count(name) > 0) { - Permissions::Regions new_region = - static_cast(records[name] & ~region); - if (new_region == Permissions::Nowhere) { + Regions new_region = static_cast(records[name] & ~region); + if (new_region == Regions::Nowhere) { records.erase(name); } else { records[name] = new_region; @@ -78,7 +76,7 @@ void updateAccessRecords(std::map& records, } } -const Options& GuardedOptions::get(Permissions::Regions region) const { +const Options& GuardedOptions::get(Regions region) const { if (options == nullptr) throw BoutException( "Trying to access GuardedOptions when underlying options are nullptr."); @@ -86,8 +84,8 @@ const Options& GuardedOptions::get(Permissions::Regions region) const { std::string name = options->str(); if (permissions != nullptr) { auto [permission, varname] = permissions->getHighestPermission(name, region); - if (permission >= Permissions::ReadIfSet) { - if (permission == Permissions::ReadIfSet && !options->isSet()) { + if (permission >= PermissionTypes::ReadIfSet) { + if (permission == PermissionTypes::ReadIfSet && !options->isSet()) { throw BoutException( "Only have permission to read {} if it is already set, which it is not.", name); @@ -102,14 +100,14 @@ const Options& GuardedOptions::get(Permissions::Regions region) const { #endif } -Options& GuardedOptions::getWritable(Permissions::Regions region) { +Options& GuardedOptions::getWritable(Regions region) { if (options == nullptr) throw BoutException( "Trying to access GuardedOptions when underlying options are nullptr."); #if CHECKLEVEL >= 1 std::string name = options->str(); if (permissions != nullptr) { - auto [access, varname] = permissions->canAccess(name, Permissions::Write, region); + auto [access, varname] = permissions->canAccess(name, PermissionTypes::Write, region); if (access) { updateAccessRecords(*unwritten_variables, varname, region); return *options; @@ -121,7 +119,7 @@ Options& GuardedOptions::getWritable(Permissions::Regions region) { #endif } -std::map GuardedOptions::unreadItems() const { +std::map GuardedOptions::unreadItems() const { #if CHECKLEVEL >= 1 return *unread_variables; #else @@ -130,7 +128,7 @@ std::map GuardedOptions::unreadItems() const #endif } -std::map GuardedOptions::unwrittenItems() const { +std::map GuardedOptions::unwrittenItems() const { #if CHECKLEVEL >= 1 return *unwritten_variables; #else diff --git a/src/isothermal.cxx b/src/isothermal.cxx index 6ddffaa97..186bbed72 100644 --- a/src/isothermal.cxx +++ b/src/isothermal.cxx @@ -4,11 +4,10 @@ #include "../include/isothermal.hxx" Isothermal::Isothermal(std::string name, Options& alloptions, Solver* UNUSED(solver)) - : Component( - {readIfSet(fmt::format("species:{}:density", name), Permissions::Interior), - readWrite(fmt::format("species:{}:temperature", name)), - // FIXME: This is only written if density is set - readWrite(fmt::format("species:{}:pressure", name))}), + : Component({readIfSet(fmt::format("species:{}:density", name), Regions::Interior), + readWrite(fmt::format("species:{}:temperature", name)), + // FIXME: This is only written if density is set + readWrite(fmt::format("species:{}:pressure", name))}), name(name) { AUTO_TRACE(); Options& options = alloptions[name]; diff --git a/src/permissions.cxx b/src/permissions.cxx index 4f29049cd..a408308a4 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -1,7 +1,30 @@ #include "../include/permissions.hxx" - -const std::map Permissions::fundamental_regions = { - {Permissions::Interior, "Interior"}, {Permissions::Boundaries, "Boundaries"}}; +#include + +// TODO: It might be useful to add an optional condition which must be +// met for conditions to apply. So a variable is only read or written +// if another variable is already set, for example. Could potentially +// have conditions based on values as well. Could put in boolean +// logic. This would allow finer-grained access control in Components, +// but is it worth the hassle? +// +// struct Condition { +// virtual bool eval() const = 0; +// } +// +// struct IsSetCondition : Condition { +// std::string varname +// } +// +// struct TrueCondition; +// struct AndCondition; +// struct OrCondition; +// +// There should be ways to do this with templates to allow greater inlining +// + +const std::map Permissions::fundamental_regions = { + {Regions::Interior, "Interior"}, {Regions::Boundaries, "Boundaries"}}; Permissions::Permissions(std::initializer_list> data) : variable_permissions() { @@ -29,6 +52,16 @@ std::string replaceAll(const std::string& str, const std::string& from, return result; } +void Permissions::printAllPermissions() const { + for (const auto& [varname, perms] : variable_permissions) { + fmt::print("{}", varname); + for (int i = 0; i < static_cast(PermissionTypes::END); i++) { + fmt::print(" {}", static_cast(perms[i])); + } + fmt::print("\n"); + } +} + void Permissions::substitute(const std::string& label, const std::vector& substitutions) { for (auto it = variable_permissions.begin(); it != variable_permissions.end();) { @@ -55,8 +88,8 @@ Permissions::bestMatchRights(const std::string& variable) const { if (match != variable_permissions.end()) { return *match; } - Permissions::AccessRights best_candidate = {Permissions::Nowhere, Permissions::Nowhere, - Permissions::Nowhere}; + Permissions::AccessRights best_candidate = {Regions::Nowhere, Regions::Nowhere, + Regions::Nowhere}; std::string best_candidate_name = ""; int max_len = 0; for (const auto& [varname, rights] : variable_permissions) { @@ -73,42 +106,42 @@ std::pair Permissions::canAccess(const std::string& variable, PermissionTypes permission, Regions region) const { auto [match_name, match_rights] = bestMatchRights(variable); - if ((match_rights[permission] & region) == region) { + if ((match_rights[static_cast(permission)] & region) == region) { return {true, match_name}; } else { return {false, ""}; } } -std::pair -Permissions::getHighestPermission(const std::string& variable, - Permissions::Regions region) const { - if (region == Nowhere) - return {None, ""}; +std::pair +Permissions::getHighestPermission(const std::string& variable, Regions region) const { + if (region == Regions::Nowhere) + return {PermissionTypes::None, ""}; auto [varname, rights] = bestMatchRights(variable); - int i = ReadIfSet; - while (i < PERMISSION_TYPES_END and (rights[i] & region) == region) { + int i = static_cast(PermissionTypes::ReadIfSet); + while (i < static_cast(PermissionTypes::END) and (rights[i] & region) == region) { i++; } return {static_cast(i - 1), varname}; } -std::map +std::map Permissions::getVariablesWithPermission(PermissionTypes permission, bool highestOnly) const { - std::map result; - if (highestOnly and permission < PERMISSION_TYPES_END - 1) { + std::map result; + if (highestOnly + and static_cast(permission) < static_cast(PermissionTypes::END) - 1) { for (const auto& [varname, rights] : variable_permissions) { - auto regions = rights[permission]; - auto perm_in_regions = - static_cast(rights[permission] & ~rights[permission + 1]); - if (perm_in_regions != Nowhere) + auto regions = rights[static_cast(permission)]; + auto perm_in_regions = rights[static_cast(permission)] + & ~rights[static_cast(permission) + 1]; + if (perm_in_regions != Regions::Nowhere) result.emplace(varname, perm_in_regions); } } else { for (const auto& [varname, rights] : variable_permissions) { - auto regions = rights[permission]; - if (regions != Nowhere) + auto regions = rights[static_cast(permission)]; + if (regions != Regions::Nowhere) result.emplace(varname, regions); } } @@ -117,11 +150,12 @@ Permissions::getVariablesWithPermission(PermissionTypes permission, Permissions::AccessRights Permissions::applyLowerPermissions(const AccessRights& rights) { AccessRights result(rights); - for (int i = ReadIfSet; i < PERMISSION_TYPES_END; i++) { + for (int i = static_cast(PermissionTypes::ReadIfSet); + i < static_cast(PermissionTypes::END); i++) { result[i] = rights[i]; // Higher permissions imply lower permissions - for (int j = ReadIfSet; j < i; j++) { - result[j] = static_cast(result[j] | rights[i]); + for (int j = static_cast(PermissionTypes::ReadIfSet); j < i; j++) { + result[j] = result[j] | rights[i]; } } return result; @@ -139,38 +173,32 @@ std::string Permissions::regionNames(const Regions regions) { } std::pair readIfSet(std::string varname, - Permissions::Regions region) { - return {varname, - {region, Permissions::Nowhere, Permissions::Nowhere, Permissions::Nowhere}}; + Regions region) { + return {varname, {region, Regions::Nowhere, Regions::Nowhere, Regions::Nowhere}}; } std::pair readOnly(std::string varname, - Permissions::Regions region) { - return {varname, - {Permissions::Nowhere, region, Permissions::Nowhere, Permissions::Nowhere}}; + Regions region) { + return {varname, {Regions::Nowhere, region, Regions::Nowhere, Regions::Nowhere}}; } std::pair readWrite(std::string varname, - Permissions::Regions region) { - return {varname, - {Permissions::Nowhere, Permissions::Nowhere, region, Permissions::Nowhere}}; + Regions region) { + return {varname, {Regions::Nowhere, Regions::Nowhere, region, Regions::Nowhere}}; } -std::pair -writeFinal(std::string varname, Permissions::Regions region) { - return {varname, - {Permissions::Nowhere, Permissions::Nowhere, Permissions::Nowhere, region}}; +std::pair writeFinal(std::string varname, + Regions region) { + return {varname, {Regions::Nowhere, Regions::Nowhere, Regions::Nowhere, region}}; } std::pair writeBoundary(std::string varname) { return {varname, - {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere, - Permissions::Boundaries}}; + {Regions::Nowhere, Regions::Interior, Regions::Nowhere, Regions::Boundaries}}; } std::pair writeBoundaryIfSet(std::string varname) { return {varname, - {Permissions::Interior, Permissions::Nowhere, Permissions::Nowhere, - Permissions::Boundaries}}; + {Regions::Interior, Regions::Nowhere, Regions::Nowhere, Regions::Boundaries}}; } diff --git a/src/quasineutral.cxx b/src/quasineutral.cxx index 6b35e1457..5ca591ad9 100644 --- a/src/quasineutral.cxx +++ b/src/quasineutral.cxx @@ -7,7 +7,7 @@ Quasineutral::Quasineutral(std::string name, Options& alloptions, Solver* UNUSED : Component({readWrite("species:{name}:{outputs}"), // FIXME: These are only read if BOTH are set readIfSet("species:{all_species}:charge"), - readIfSet("species:{all_species}:density", Permissions::Interior)}), + readIfSet("species:{all_species}:density", Regions::Interior)}), name(name) { Options &options = alloptions[name]; diff --git a/src/relax_potential.cxx b/src/relax_potential.cxx index 61a147e25..9e5c7e670 100644 --- a/src/relax_potential.cxx +++ b/src/relax_potential.cxx @@ -47,7 +47,7 @@ RelaxPotential::RelaxPotential(std::string name, Options& alloptions, Solver* so if (diamagnetic) { // FIXME: These will only be read if BOTH charge and pressure are set state_variable_access.setAccess( - readIfSet("species:{charged}:pressure", Permissions::Interior)); + readIfSet("species:{charged}:pressure", Regions::Interior)); state_variable_access.setAccess(readIfSet("species:{all_species}:charge")); // FIXME: The weay transform_impl is currently written, // energy_source is set for neutral species with an explicit diff --git a/src/sheath_boundary.cxx b/src/sheath_boundary.cxx index d3c753cf6..6fdb0bb7b 100644 --- a/src/sheath_boundary.cxx +++ b/src/sheath_boundary.cxx @@ -123,8 +123,8 @@ SheathBoundary::SheathBoundary(std::string name, Options& alloptions, Solver*) // permissions, for that matter) state_variable_access.setAccess( always_set_phi ? std::pair( - "fields:phi", {Permissions::Interior, Permissions::Nowhere, - Permissions::Nowhere, Permissions::Boundaries}) + "fields:phi", + {Regions::Interior, Regions::Nowhere, Regions::Nowhere, Regions::Boundaries}) : writeBoundaryIfSet("fields:phi")); } diff --git a/src/sheath_boundary_insulating.cxx b/src/sheath_boundary_insulating.cxx index 4975a9c64..a2c98b069 100644 --- a/src/sheath_boundary_insulating.cxx +++ b/src/sheath_boundary_insulating.cxx @@ -56,14 +56,12 @@ SheathBoundaryInsulating::SheathBoundaryInsulating(std::string name, Options& al readWrite("species:e:energy_source"), writeBoundaryIfSet("species:e:{e_optional}"), {"species:e:pressure", - {Permissions::Interior, Permissions::Nowhere, Permissions::Boundaries, - Permissions::Nowhere}}, + {Regions::Interior, Regions::Nowhere, Regions::Boundaries, Regions::Nowhere}}, readIfSet("species:{ions}:{ion_whole_domain}"), readOnly("species:{ions}:AA"), readWrite("species:{ions}:energy_source"), {"species:{ions}:pressure", - {Permissions::Interior, Permissions::Nowhere, Permissions::Boundaries, - Permissions::Nowhere}}, + {Regions::Interior, Regions::Nowhere, Regions::Boundaries, Regions::Nowhere}}, writeBoundary("species:{ions}:{ion_boundary}"), writeBoundaryIfSet("species:{ions}:{ion_optional}"), }) { diff --git a/src/sheath_boundary_simple.cxx b/src/sheath_boundary_simple.cxx index 442f4603a..9c6777695 100644 --- a/src/sheath_boundary_simple.cxx +++ b/src/sheath_boundary_simple.cxx @@ -65,16 +65,14 @@ SheathBoundarySimple::SheathBoundarySimple(std::string name, Options& alloptions readWrite("species:e:energy_flow_ylow"), writeBoundaryIfSet("species:e:{e_optional}"), {"species:e:pressure", - {Permissions::Interior, Permissions::Nowhere, Permissions::Boundaries, - Permissions::Nowhere}}, + {Regions::Interior, Regions::Nowhere, Regions::Boundaries, Regions::Nowhere}}, // FIXME: These only applies to ions, not to all species readIfSet("species:{all_species}:{ion_whole_domain}"), readOnly("species:{all_species}:AA"), readWrite("species:{all_species}:energy_source"), readWrite("species:{all_species}:energy_flow_ylow"), {"species:{all_species}:pressure", - {Permissions::Interior, Permissions::Nowhere, Permissions::Boundaries, - Permissions::Nowhere}}, + {Regions::Interior, Regions::Nowhere, Regions::Boundaries, Regions::Nowhere}}, writeBoundary("species:{all_species}:{ion_boundary}"), writeBoundaryIfSet("species:{all_species}:{ion_optional}"), }) { diff --git a/src/vorticity.cxx b/src/vorticity.cxx index ebd243d0d..17822b2b3 100644 --- a/src/vorticity.cxx +++ b/src/vorticity.cxx @@ -211,7 +211,7 @@ Vorticity::Vorticity(std::string name, Options& alloptions, Solver* solver) // FIXME: These will only be read if BOTH charge and pressure (and possibly AA) are // set state_variable_access.setAccess( - readIfSet("species:{charged}:pressure", Permissions::Interior)); + readIfSet("species:{charged}:pressure", Regions::Interior)); state_variable_access.setAccess(readIfSet("species:{all_species}:charge")); } if (diamagnetic) { @@ -227,7 +227,7 @@ Vorticity::Vorticity(std::string name, Options& alloptions, Solver* solver) } else { state_variable_access.setAccess(readOnly("species:e:AA")); state_variable_access.setAccess( - readIfSet("species:e:temperature", Permissions::Interior)); + readIfSet("species:e:temperature", Regions::Interior)); } if (collisional_friction) { state_variable_access.setAccess(readIfSet("species:{all_species}:charge")); diff --git a/src/zero_current.cxx b/src/zero_current.cxx index 2914b74ae..8883ccf54 100644 --- a/src/zero_current.cxx +++ b/src/zero_current.cxx @@ -5,7 +5,7 @@ ZeroCurrent::ZeroCurrent(std::string name, Options& alloptions, Solver*) : Component({readIfSet("species:{all_species}:charge"), - readIfSet("species:{all_species}:{inputs}", Permissions::Interior), + readIfSet("species:{all_species}:{inputs}", Regions::Interior), readWrite(fmt::format("species:{}:velocity", name))}), name(name) { AUTO_TRACE(); diff --git a/tests/unit/test_guarded_options.cxx b/tests/unit/test_guarded_options.cxx index 3c7cc4ee5..23fa70667 100644 --- a/tests/unit/test_guarded_options.cxx +++ b/tests/unit/test_guarded_options.cxx @@ -4,25 +4,22 @@ class GuardedOptionsTests : public testing::Test { protected: GuardedOptionsTests() - : permissions({readIfSet("species:he:AA"), - readIfSet("species:he:charge"), - readOnly("species:he:density"), - {"species:he:pressure", - {Permissions::Nowhere, Permissions::Nowhere, Permissions::Interior, - Permissions::Nowhere}}, - writeFinal("species:he:collision_frequency"), - {"species:he:velocity", - {Permissions::Nowhere, Permissions::Boundaries, - Permissions::Nowhere, Permissions::Nowhere}}, - readOnly("species:d"), - {"species:d:pressure", - {Permissions::Nowhere, Permissions::Nowhere, Permissions::Interior, - Permissions::Nowhere}}, - {"species:d:collision_frequencies", - {Permissions::Nowhere, Permissions::Nowhere, - Permissions::Boundaries, Permissions::Nowhere}}, - readIfSet("fields:phi"), - readOnly("unused:option")}), + : permissions( + {readIfSet("species:he:AA"), + readIfSet("species:he:charge"), + readOnly("species:he:density"), + {"species:he:pressure", + {Regions::Nowhere, Regions::Nowhere, Regions::Interior, Regions::Nowhere}}, + writeFinal("species:he:collision_frequency"), + {"species:he:velocity", + {Regions::Nowhere, Regions::Boundaries, Regions::Nowhere, Regions::Nowhere}}, + readOnly("species:d"), + {"species:d:pressure", + {Regions::Nowhere, Regions::Nowhere, Regions::Interior, Regions::Nowhere}}, + {"species:d:collision_frequencies", + {Regions::Nowhere, Regions::Nowhere, Regions::Boundaries, Regions::Nowhere}}, + readIfSet("fields:phi"), + readOnly("unused:option")}), opts({{"species", {{"he", {{"charge", 0}, @@ -44,20 +41,20 @@ class GuardedOptionsTests : public testing::Test { TEST_F(GuardedOptionsTests, TestGet) { EXPECT_EQ(guarded_opts["species:he:charge"].get(), 0); EXPECT_EQ(guarded_opts["species:he:density"].get(), 1); - EXPECT_EQ(guarded_opts["species:he:density"].get(Permissions::Boundaries), 1); - EXPECT_EQ(guarded_opts["species:he:pressure"].get(Permissions::Interior), 2); + EXPECT_EQ(guarded_opts["species:he:density"].get(Regions::Boundaries), 1); + EXPECT_EQ(guarded_opts["species:he:pressure"].get(Regions::Interior), 2); EXPECT_FALSE(guarded_opts["species:he:collision_frequency"].get().isSet()); - EXPECT_EQ(guarded_opts["species"]["he"]["velocity"].get(Permissions::Boundaries), 4); - EXPECT_EQ(guarded_opts["species"]["d"]["pressure"].get(Permissions::Interior), 5); - EXPECT_EQ(guarded_opts["species"]["d"]["velocity"].get(Permissions::AllRegions), 6); + EXPECT_EQ(guarded_opts["species"]["he"]["velocity"].get(Regions::Boundaries), 4); + EXPECT_EQ(guarded_opts["species"]["d"]["pressure"].get(Regions::Interior), 5); + EXPECT_EQ(guarded_opts["species"]["d"]["velocity"].get(Regions::All), 6); EXPECT_EQ(guarded_opts["species:d:collision_frequencies"]["d_d_coll"].get( - Permissions::Boundaries), + Regions::Boundaries), 7); EXPECT_EQ(guarded_opts["species:d:collision_frequencies"].get( - Permissions::Boundaries)["d_he_coll"], + Regions::Boundaries)["d_he_coll"], 8); EXPECT_FALSE(guarded_opts["species:d:collision_frequencies:d_t+_cx"] - .get(Permissions::Boundaries) + .get(Regions::Boundaries) .isSet()); } @@ -65,19 +62,18 @@ TEST_F(GuardedOptionsTests, TestGetException) { EXPECT_THROW(guarded_opts["species:he:AA"].get(), BoutException); EXPECT_THROW(guarded_opts["species"]["he"]["temperature"].get(), BoutException); EXPECT_THROW(guarded_opts["species:he:pressure"].get(), BoutException); - EXPECT_THROW(guarded_opts["species"]["he"]["velocity"].get(Permissions::Interior), + EXPECT_THROW(guarded_opts["species"]["he"]["velocity"].get(Regions::Interior), BoutException); - EXPECT_THROW(guarded_opts["species:d:collision_frequencies"].get(Permissions::Interior), + EXPECT_THROW(guarded_opts["species:d:collision_frequencies"].get(Regions::Interior), BoutException); - EXPECT_THROW(guarded_opts["species:d:pressure"].get(Permissions::Boundaries), + EXPECT_THROW(guarded_opts["species:d:pressure"].get(Regions::Boundaries), BoutException); EXPECT_THROW(guarded_opts["no_permission"].get(), BoutException); EXPECT_THROW(guarded_opts["species:d+:velocity"].get(), BoutException); } TEST_F(GuardedOptionsTests, TestGetWritable) { - auto& he_pressure = - guarded_opts["species:he:pressure"].getWritable(Permissions::Interior); + auto& he_pressure = guarded_opts["species:he:pressure"].getWritable(Regions::Interior); EXPECT_EQ(he_pressure, 2); EXPECT_EQ(opts["species"]["he"]["pressure"], 2); he_pressure.force(10); @@ -90,19 +86,19 @@ TEST_F(GuardedOptionsTests, TestGetWritable) { EXPECT_EQ(opts["species"]["he"]["collision_frequency"], 11); auto& d_pressure = - guarded_opts["species"]["d"]["pressure"].getWritable(Permissions::Interior); + guarded_opts["species"]["d"]["pressure"].getWritable(Regions::Interior); EXPECT_EQ(opts["species"]["d"]["pressure"], 5); d_pressure.force(12); EXPECT_EQ(opts["species"]["d"]["pressure"], 12); auto& d_d_coll = guarded_opts["species:d:collision_frequencies:d_d_coll"].getWritable( - Permissions::Boundaries); + Regions::Boundaries); EXPECT_EQ(d_d_coll, 7); d_d_coll.force(13); EXPECT_EQ(opts["species:d:collision_frequencies:d_d_coll"], 13); auto& d_tp_coll = guarded_opts["species:d:collision_frequencies:d_t+_coll"].getWritable( - Permissions::Boundaries); + Regions::Boundaries); EXPECT_FALSE(d_tp_coll.isSet()); d_tp_coll = 14; EXPECT_EQ(opts["species:d:collision_frequencies:d_t+_coll"], 14); @@ -112,52 +108,52 @@ TEST_F(GuardedOptionsTests, TestGetWritableException) { EXPECT_THROW(guarded_opts["species"]["he"]["temperature"].getWritable(), BoutException); EXPECT_THROW(guarded_opts["unset"].getWritable(), BoutException); EXPECT_THROW(guarded_opts["species:he:density"].getWritable(), BoutException); - EXPECT_THROW(guarded_opts["species:he:density"].getWritable(Permissions::Interior), + EXPECT_THROW(guarded_opts["species:he:density"].getWritable(Regions::Interior), BoutException); - EXPECT_THROW(guarded_opts["species:he:density"].getWritable(Permissions::Boundaries), + EXPECT_THROW(guarded_opts["species:he:density"].getWritable(Regions::Boundaries), BoutException); EXPECT_THROW(guarded_opts["species:he:pressure"].getWritable(), BoutException); - EXPECT_THROW(guarded_opts["species:he:pressure"].getWritable(Permissions::Boundaries), + EXPECT_THROW(guarded_opts["species:he:pressure"].getWritable(Regions::Boundaries), BoutException); EXPECT_THROW(guarded_opts["species"]["d"]["velocity"].getWritable(), BoutException); - EXPECT_THROW( - guarded_opts["species"]["d"]["pressure"].getWritable(Permissions::Boundaries), - BoutException); + EXPECT_THROW(guarded_opts["species"]["d"]["pressure"].getWritable(Regions::Boundaries), + BoutException); EXPECT_THROW(guarded_opts["species:d:collision_frequencies:unset"].getWritable(), BoutException); EXPECT_THROW(guarded_opts["species:d:collision_frequencies:unset"].getWritable( - Permissions::Interior), + Regions::Interior), BoutException); EXPECT_THROW( - guarded_opts["species"]["d"]["pressure_suffix"].getWritable(Permissions::Interior), + guarded_opts["species"]["d"]["pressure_suffix"].getWritable(Regions::Interior), BoutException); } TEST_F(GuardedOptionsTests, TestUnreadItems) { - std::map - expected1 = {{"species:he:charge", Permissions::AllRegions}, - {"species:he:density", Permissions::AllRegions}, - {"species:he:velocity", Permissions::Boundaries}, - {"species:d", Permissions::AllRegions}, - {"unused:option", Permissions::AllRegions}}, - expected2 = {{"species:he:charge", Permissions::AllRegions}, - {"species:he:density", Permissions::Boundaries}, - {"species:he:velocity", Permissions::Boundaries}, - {"species:d", Permissions::AllRegions}, - {"unused:option", Permissions::AllRegions}}, - expected3 = {{"species:d", Permissions::AllRegions}}, expected4; + std::map expected1 = {{"species:he:charge", Regions::All}, + {"species:he:density", Regions::All}, + {"species:he:velocity", + Regions::Boundaries}, + {"species:d", Regions::All}, + {"unused:option", Regions::All}}, + expected2 = {{"species:he:charge", Regions::All}, + {"species:he:density", Regions::Boundaries}, + {"species:he:velocity", + Regions::Boundaries}, + {"species:d", Regions::All}, + {"unused:option", Regions::All}}, + expected3 = {{"species:d", Regions::All}}, expected4; EXPECT_EQ(guarded_opts.unreadItems(), expected1); - guarded_opts["species:he:density"].get(Permissions::Interior); - guarded_opts["species:d:pressure"].get(Permissions::Interior); + guarded_opts["species:he:density"].get(Regions::Interior); + guarded_opts["species:d:pressure"].get(Regions::Interior); guarded_opts["species:d:collision_frequencies:d_d_coll"].getWritable( - Permissions::Boundaries); + Regions::Boundaries); EXPECT_EQ(guarded_opts.unreadItems(), expected2); guarded_opts["species"]["he"]["charge"].get(); guarded_opts["species"]["he"]["density"].get(); - guarded_opts["species:he:velocity"].get(Permissions::Boundaries); + guarded_opts["species:he:velocity"].get(Regions::Boundaries); EXPECT_FALSE(guarded_opts["unused"]["option"].get().isSet()); EXPECT_EQ(guarded_opts.unreadItems(), expected3); @@ -166,28 +162,30 @@ TEST_F(GuardedOptionsTests, TestUnreadItems) { } TEST_F(GuardedOptionsTests, TestUnwrittenItems) { - std::map - expected1 = {{"species:he:pressure", Permissions::Interior}, - {"species:he:collision_frequency", Permissions::AllRegions}, - {"species:d:pressure", Permissions::Interior}, - {"species:d:collision_frequencies", Permissions::Boundaries}}, - expected2 = {{"species:he:collision_frequency", Permissions::AllRegions}, - {"species:d:pressure", Permissions::Interior}}, - expected3; + std::map expected1 = {{"species:he:pressure", Regions::Interior}, + {"species:he:collision_frequency", + Regions::All}, + {"species:d:pressure", Regions::Interior}, + {"species:d:collision_frequencies", + Regions::Boundaries}}, + expected2 = {{"species:he:collision_frequency", + Regions::All}, + {"species:d:pressure", Regions::Interior}}, + expected3; EXPECT_EQ(guarded_opts.unwrittenItems(), expected1); - guarded_opts["species:he:pressure"].get(Permissions::Interior); + guarded_opts["species:he:pressure"].get(Regions::Interior); guarded_opts["species:he:collision_frequency"].get(); EXPECT_EQ(guarded_opts.unwrittenItems(), expected1); - guarded_opts["species"]["he"]["pressure"].getWritable(Permissions::Interior); + guarded_opts["species"]["he"]["pressure"].getWritable(Regions::Interior); guarded_opts["species:d:collision_frequencies:d_d_coll"].getWritable( - Permissions::Boundaries); + Regions::Boundaries); EXPECT_EQ(guarded_opts.unwrittenItems(), expected2); guarded_opts["species:he:collision_frequency"].getWritable(); - guarded_opts["species:d:pressure"].getWritable(Permissions::Interior); + guarded_opts["species:d:pressure"].getWritable(Regions::Interior); EXPECT_EQ(guarded_opts.unwrittenItems(), expected3); } @@ -201,8 +199,8 @@ TEST_F(GuardedOptionsTests, TestNullOptions) { TEST_F(GuardedOptionsTests, TestNullPermissions) { GuardedOptions null_perms(&opts, nullptr); GuardedOptions sub_opt = null_perms["species:he:pressure"]; - EXPECT_THROW(sub_opt.get(Permissions::Interior), BoutException); - EXPECT_THROW(sub_opt.getWritable(Permissions::Interior), BoutException); + EXPECT_THROW(sub_opt.get(Regions::Interior), BoutException); + EXPECT_THROW(sub_opt.getWritable(Regions::Interior), BoutException); } TEST_F(GuardedOptionsTests, TestGetChildren) { diff --git a/tests/unit/test_permissions.cxx b/tests/unit/test_permissions.cxx index 89a1fad20..d7f2186d3 100644 --- a/tests/unit/test_permissions.cxx +++ b/tests/unit/test_permissions.cxx @@ -3,7 +3,7 @@ #include "../include/permissions.hxx" auto make_access = std::make_pair; -auto make_permission = std::make_pair; +auto make_permission = std::make_pair; TEST(PermissionsTests, TestCanAccess) { Permissions example({ @@ -11,21 +11,17 @@ TEST(PermissionsTests, TestCanAccess) { readOnly("species:he:density"), // Read and write permissions for pressure in the interior region {"species:he:pressure", - {Permissions::Nowhere, Permissions::Nowhere, Permissions::Interior, - Permissions::Nowhere}}, + {Regions::Nowhere, Regions::Nowhere, Regions::Interior, Regions::Nowhere}}, // Set the final value for collision frequency writeFinal("species:he:collision_frequency"), // Only allow reading of boundary velocity {"species:he:velocity", - {Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere, - Permissions::Nowhere}}, + {Regions::Nowhere, Regions::Boundaries, Regions::Nowhere, Regions::Nowhere}}, readOnly("species:d"), {"species:d:pressure", - {Permissions::Nowhere, Permissions::Nowhere, Permissions::Interior, - Permissions::Nowhere}}, + {Regions::Nowhere, Regions::Nowhere, Regions::Interior, Regions::Nowhere}}, {"species:d:collision_frequencies", - {Permissions::Nowhere, Permissions::Nowhere, Permissions::Boundaries, - Permissions::Nowhere}}, + {Regions::Nowhere, Regions::Nowhere, Regions::Boundaries, Regions::Nowhere}}, }); auto no_access = make_access(false, ""); @@ -41,316 +37,295 @@ TEST(PermissionsTests, TestCanAccess) { EXPECT_EQ(example.canAccess("unset"), no_access); // Check whether we have ReadIfSet permissions - EXPECT_EQ(example.canAccess("species:he:charge", Permissions::ReadIfSet), + EXPECT_EQ(example.canAccess("species:he:charge", PermissionTypes::ReadIfSet), make_access(true, "species:he:charge")); - EXPECT_EQ(example.canAccess("species:he:density", Permissions::ReadIfSet), + EXPECT_EQ(example.canAccess("species:he:density", PermissionTypes::ReadIfSet), make_access(true, "species:he:density")); - EXPECT_EQ(example.canAccess("unset", Permissions::ReadIfSet), no_access); + EXPECT_EQ(example.canAccess("unset", PermissionTypes::ReadIfSet), no_access); // Check whether we have write permission for the variables across the entire domain - EXPECT_EQ(example.canAccess("species:he:charge", Permissions::Write), no_access); - EXPECT_EQ(example.canAccess("species:he:density", Permissions::Write), no_access); - EXPECT_EQ(example.canAccess("species:he:pressure", Permissions::Write), no_access); - EXPECT_EQ(example.canAccess("species:he:collision_frequency", Permissions::Write), + EXPECT_EQ(example.canAccess("species:he:charge", PermissionTypes::Write), no_access); + EXPECT_EQ(example.canAccess("species:he:density", PermissionTypes::Write), no_access); + EXPECT_EQ(example.canAccess("species:he:pressure", PermissionTypes::Write), no_access); + EXPECT_EQ(example.canAccess("species:he:collision_frequency", PermissionTypes::Write), make_access(true, "species:he:collision_frequency")); - EXPECT_EQ(example.canAccess("species:he:velocity", Permissions::Write), no_access); - EXPECT_EQ(example.canAccess("unset", Permissions::Write), no_access); + EXPECT_EQ(example.canAccess("species:he:velocity", PermissionTypes::Write), no_access); + EXPECT_EQ(example.canAccess("unset", PermissionTypes::Write), no_access); // Check whether we have read permission at the boundaries EXPECT_EQ( - example.canAccess("species:he:charge", Permissions::Read, Permissions::Boundaries), + example.canAccess("species:he:charge", PermissionTypes::Read, Regions::Boundaries), no_access); EXPECT_EQ( - example.canAccess("species:he:density", Permissions::Read, Permissions::Boundaries), + example.canAccess("species:he:density", PermissionTypes::Read, Regions::Boundaries), make_access(true, "species:he:density")); - EXPECT_EQ(example.canAccess("species:he:pressure", Permissions::Read, - Permissions::Boundaries), + EXPECT_EQ(example.canAccess("species:he:pressure", PermissionTypes::Read, + Regions::Boundaries), no_access); - EXPECT_EQ(example.canAccess("species:he:collision_frequency", Permissions::Read, - Permissions::Boundaries), + EXPECT_EQ(example.canAccess("species:he:collision_frequency", PermissionTypes::Read, + Regions::Boundaries), make_access(true, "species:he:collision_frequency")); - EXPECT_EQ(example.canAccess("species:he:velocity", Permissions::Read, - Permissions::Boundaries), + EXPECT_EQ(example.canAccess("species:he:velocity", PermissionTypes::Read, + Regions::Boundaries), make_access(true, "species:he:velocity")); - EXPECT_EQ(example.canAccess("unset", Permissions::Read, Permissions::Boundaries), + EXPECT_EQ(example.canAccess("unset", PermissionTypes::Read, Regions::Boundaries), no_access); // Check permissions set for whole sections EXPECT_EQ(example.canAccess("species:d:pressure"), no_access); - EXPECT_EQ(example.canAccess("species:d:pressure", Permissions::Write), no_access); + EXPECT_EQ(example.canAccess("species:d:pressure", PermissionTypes::Write), no_access); EXPECT_EQ( - example.canAccess("species:d:pressure", Permissions::Write, Permissions::Interior), + example.canAccess("species:d:pressure", PermissionTypes::Write, Regions::Interior), make_access(true, "species:d:pressure")); EXPECT_EQ(example.canAccess("species:d:velocity"), make_access(true, "species:d")); - EXPECT_EQ(example.canAccess("species:d:velocity", Permissions::Write), no_access); + EXPECT_EQ(example.canAccess("species:d:velocity", PermissionTypes::Write), no_access); EXPECT_EQ( - example.canAccess("species:d:velocity", Permissions::Read, Permissions::Boundaries), + example.canAccess("species:d:velocity", PermissionTypes::Read, Regions::Boundaries), make_access(true, "species:d")); EXPECT_EQ(example.canAccess("species:d:collision_frequencies:d_d_coll"), no_access); - EXPECT_EQ( - example.canAccess("species:d:collision_frequencies:d_d_coll", Permissions::Write), - no_access); EXPECT_EQ(example.canAccess("species:d:collision_frequencies:d_d_coll", - Permissions::Write, Permissions::Boundaries), + PermissionTypes::Write), + no_access); + EXPECT_EQ(example.canAccess("species:d:collision_frequencies:d_d_coll", + PermissionTypes::Write, Regions::Boundaries), make_access(true, "species:d:collision_frequencies")); // Check permissions for a species that might be mistaken for one of // the sections we've given permissions for EXPECT_EQ(example.canAccess("species:d+"), no_access); - EXPECT_EQ(example.canAccess("species:d+", Permissions::Write), no_access); - EXPECT_EQ(example.canAccess("species:d+", Permissions::Read, Permissions::Interior), + EXPECT_EQ(example.canAccess("species:d+", PermissionTypes::Write), no_access); + EXPECT_EQ(example.canAccess("species:d+", PermissionTypes::Read, Regions::Interior), no_access); - EXPECT_EQ(example.canAccess("species:d+", Permissions::Read, Permissions::Boundaries), + EXPECT_EQ(example.canAccess("species:d+", PermissionTypes::Read, Regions::Boundaries), no_access); } TEST(PermissionsTests, TestGetHighestPermission) { Permissions example({ {"species:he:charge", - {Permissions::AllRegions, Permissions::Nowhere, Permissions::Nowhere, - Permissions::Nowhere}}, + {Regions::All, Regions::Nowhere, Regions::Nowhere, Regions::Nowhere}}, {"species:he:density", - {Permissions::Nowhere, Permissions::AllRegions, Permissions::Nowhere, - Permissions::Boundaries}}, + {Regions::Nowhere, Regions::All, Regions::Nowhere, Regions::Boundaries}}, // Read and write permissions for pressure in the interior region {"species:he:pressure", - {Permissions::Nowhere, Permissions::Boundaries, Permissions::Interior, - Permissions::Nowhere}}, + {Regions::Nowhere, Regions::Boundaries, Regions::Interior, Regions::Nowhere}}, // Set the final value for collision frequency {"species:he:collision_frequency", - {Permissions::Nowhere, Permissions::Interior, Permissions::Nowhere, - Permissions::AllRegions}}, + {Regions::Nowhere, Regions::Interior, Regions::Nowhere, Regions::All}}, // Only allow reading of boundary velocity {"species:he:velocity", - {Permissions::Nowhere, Permissions::Boundaries, Permissions::Nowhere, - Permissions::Nowhere}}, - {"species:d", - {Permissions::Nowhere, Permissions::AllRegions, Permissions::Nowhere, - Permissions::Nowhere}}, + {Regions::Nowhere, Regions::Boundaries, Regions::Nowhere, Regions::Nowhere}}, + {"species:d", {Regions::Nowhere, Regions::All, Regions::Nowhere, Regions::Nowhere}}, {"species:d:pressure", - {Permissions::Nowhere, Permissions::Nowhere, Permissions::Interior, - Permissions::Nowhere}}, + {Regions::Nowhere, Regions::Nowhere, Regions::Interior, Regions::Nowhere}}, {"species:d:collision_frequencies", - {Permissions::Nowhere, Permissions::Nowhere, Permissions::Boundaries, - Permissions::Nowhere}}, + {Regions::Nowhere, Regions::Nowhere, Regions::Boundaries, Regions::Nowhere}}, }); - auto no_permission = make_permission(Permissions::None, ""); + auto no_permission = make_permission(PermissionTypes::None, ""); // Get the highest permission that covers the entire domain EXPECT_EQ(example.getHighestPermission("species:he:charge"), - make_permission(Permissions::ReadIfSet, "species:he:charge")); + make_permission(PermissionTypes::ReadIfSet, "species:he:charge")); EXPECT_EQ(example.getHighestPermission("species:he:density"), - make_permission(Permissions::Read, "species:he:density")); + make_permission(PermissionTypes::Read, "species:he:density")); EXPECT_EQ(example.getHighestPermission("species:he:pressure"), - make_permission(Permissions::Read, "species:he:pressure")); + make_permission(PermissionTypes::Read, "species:he:pressure")); EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency"), - make_permission(Permissions::Final, "species:he:collision_frequency")); + make_permission(PermissionTypes::Final, "species:he:collision_frequency")); EXPECT_EQ(example.getHighestPermission("species:he:velocity"), - make_permission(Permissions::None, "species:he:velocity")); + make_permission(PermissionTypes::None, "species:he:velocity")); EXPECT_EQ(example.getHighestPermission("species:d:pressure"), - make_permission(Permissions::None, "species:d:pressure")); + make_permission(PermissionTypes::None, "species:d:pressure")); EXPECT_EQ(example.getHighestPermission("species:d:velocity"), - make_permission(Permissions::Read, "species:d")); + make_permission(PermissionTypes::Read, "species:d")); EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll"), - make_permission(Permissions::None, "species:d:collision_frequencies")); + make_permission(PermissionTypes::None, "species:d:collision_frequencies")); EXPECT_EQ(example.getHighestPermission("unset"), no_permission); // Get the highest permission on the boundaries - EXPECT_EQ(example.getHighestPermission("species:he:charge", Permissions::Boundaries), - make_permission(Permissions::ReadIfSet, "species:he:charge")); - EXPECT_EQ(example.getHighestPermission("species:he:density", Permissions::Boundaries), - make_permission(Permissions::Final, "species:he:density")); - EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Boundaries), - make_permission(Permissions::Read, "species:he:pressure")); - EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency", - Permissions::Boundaries), - make_permission(Permissions::Final, "species:he:collision_frequency")); - EXPECT_EQ(example.getHighestPermission("species:he:velocity", Permissions::Boundaries), - make_permission(Permissions::Read, "species:he:velocity")); - EXPECT_EQ(example.getHighestPermission("species:d:pressure", Permissions::Boundaries), - make_permission(Permissions::None, "species:d:pressure")); - EXPECT_EQ(example.getHighestPermission("species:d:velocity", Permissions::Boundaries), - make_permission(Permissions::Read, "species:d")); + EXPECT_EQ(example.getHighestPermission("species:he:charge", Regions::Boundaries), + make_permission(PermissionTypes::ReadIfSet, "species:he:charge")); + EXPECT_EQ(example.getHighestPermission("species:he:density", Regions::Boundaries), + make_permission(PermissionTypes::Final, "species:he:density")); + EXPECT_EQ(example.getHighestPermission("species:he:pressure", Regions::Boundaries), + make_permission(PermissionTypes::Read, "species:he:pressure")); + EXPECT_EQ( + example.getHighestPermission("species:he:collision_frequency", Regions::Boundaries), + make_permission(PermissionTypes::Final, "species:he:collision_frequency")); + EXPECT_EQ(example.getHighestPermission("species:he:velocity", Regions::Boundaries), + make_permission(PermissionTypes::Read, "species:he:velocity")); + EXPECT_EQ(example.getHighestPermission("species:d:pressure", Regions::Boundaries), + make_permission(PermissionTypes::None, "species:d:pressure")); + EXPECT_EQ(example.getHighestPermission("species:d:velocity", Regions::Boundaries), + make_permission(PermissionTypes::Read, "species:d")); EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll", - Permissions::Boundaries), - make_permission(Permissions::Write, "species:d:collision_frequencies")); - EXPECT_EQ(example.getHighestPermission("unset", Permissions::Boundaries), - no_permission); + Regions::Boundaries), + make_permission(PermissionTypes::Write, "species:d:collision_frequencies")); + EXPECT_EQ(example.getHighestPermission("unset", Regions::Boundaries), no_permission); // Get the highest permission on the interior - EXPECT_EQ(example.getHighestPermission("species:he:charge", Permissions::Interior), - make_permission(Permissions::ReadIfSet, "species:he:charge")); - EXPECT_EQ(example.getHighestPermission("species:he:density", Permissions::Interior), - make_permission(Permissions::Read, "species:he:density")); - EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Interior), - make_permission(Permissions::Write, "species:he:pressure")); - EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency", - Permissions::Interior), - make_permission(Permissions::Final, "species:he:collision_frequency")); - EXPECT_EQ(example.getHighestPermission("species:he:velocity", Permissions::Interior), - make_permission(Permissions::None, "species:he:velocity")); - EXPECT_EQ(example.getHighestPermission("species:d:pressure", Permissions::Interior), - make_permission(Permissions::Write, "species:d:pressure")); - EXPECT_EQ(example.getHighestPermission("species:d:velocity", Permissions::Interior), - make_permission(Permissions::Read, "species:d")); + EXPECT_EQ(example.getHighestPermission("species:he:charge", Regions::Interior), + make_permission(PermissionTypes::ReadIfSet, "species:he:charge")); + EXPECT_EQ(example.getHighestPermission("species:he:density", Regions::Interior), + make_permission(PermissionTypes::Read, "species:he:density")); + EXPECT_EQ(example.getHighestPermission("species:he:pressure", Regions::Interior), + make_permission(PermissionTypes::Write, "species:he:pressure")); + EXPECT_EQ( + example.getHighestPermission("species:he:collision_frequency", Regions::Interior), + make_permission(PermissionTypes::Final, "species:he:collision_frequency")); + EXPECT_EQ(example.getHighestPermission("species:he:velocity", Regions::Interior), + make_permission(PermissionTypes::None, "species:he:velocity")); + EXPECT_EQ(example.getHighestPermission("species:d:pressure", Regions::Interior), + make_permission(PermissionTypes::Write, "species:d:pressure")); + EXPECT_EQ(example.getHighestPermission("species:d:velocity", Regions::Interior), + make_permission(PermissionTypes::Read, "species:d")); EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll", - Permissions::Interior), - make_permission(Permissions::None, "species:d:collision_frequencies")); - EXPECT_EQ(example.getHighestPermission("unset", Permissions::Interior), no_permission); + Regions::Interior), + make_permission(PermissionTypes::None, "species:d:collision_frequencies")); + EXPECT_EQ(example.getHighestPermission("unset", Regions::Interior), no_permission); // Check the permission for the "Nowhere" region is always "None" - EXPECT_EQ(example.getHighestPermission("species:he:charge", Permissions::Nowhere), - no_permission); - EXPECT_EQ(example.getHighestPermission("species:he:density", Permissions::Nowhere), + EXPECT_EQ(example.getHighestPermission("species:he:charge", Regions::Nowhere), no_permission); - EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Nowhere), + EXPECT_EQ(example.getHighestPermission("species:he:density", Regions::Nowhere), no_permission); - EXPECT_EQ(example.getHighestPermission("species:he:collision_frequency", - Permissions::Nowhere), + EXPECT_EQ(example.getHighestPermission("species:he:pressure", Regions::Nowhere), no_permission); - EXPECT_EQ(example.getHighestPermission("species:he:velocity", Permissions::Nowhere), + EXPECT_EQ( + example.getHighestPermission("species:he:collision_frequency", Regions::Nowhere), + no_permission); + EXPECT_EQ(example.getHighestPermission("species:he:velocity", Regions::Nowhere), no_permission); - EXPECT_EQ(example.getHighestPermission("species:d:pressure", Permissions::Nowhere), + EXPECT_EQ(example.getHighestPermission("species:d:pressure", Regions::Nowhere), no_permission); - EXPECT_EQ(example.getHighestPermission("species:d:velocity", Permissions::Nowhere), + EXPECT_EQ(example.getHighestPermission("species:d:velocity", Regions::Nowhere), no_permission); EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll", - Permissions::Nowhere), + Regions::Nowhere), no_permission); - EXPECT_EQ(example.getHighestPermission("unset", Permissions::Nowhere), no_permission); + EXPECT_EQ(example.getHighestPermission("unset", Regions::Nowhere), no_permission); // Check permissions for a species that might be mistaken for one of // the sections we've given permissions for EXPECT_EQ(example.getHighestPermission("species:d+"), no_permission); - EXPECT_EQ(example.getHighestPermission("species:d+", Permissions::Interior), - no_permission); - EXPECT_EQ(example.getHighestPermission("species:d+", Permissions::Boundaries), + EXPECT_EQ(example.getHighestPermission("species:d+", Regions::Interior), no_permission); + EXPECT_EQ(example.getHighestPermission("species:d+", Regions::Boundaries), no_permission); } TEST(PermissionsTests, TestSetAccess) { Permissions example({ {"species:he:density", - {Permissions::Nowhere, Permissions::AllRegions, Permissions::Nowhere, - Permissions::Nowhere}}, + {Regions::Nowhere, Regions::All, Regions::Nowhere, Regions::Nowhere}}, // Read and write permissions for pressure in the interior region {"species:he:pressure", - {Permissions::Nowhere, Permissions::Nowhere, Permissions::Interior, - Permissions::Nowhere}}, + {Regions::Nowhere, Regions::Nowhere, Regions::Interior, Regions::Nowhere}}, }); EXPECT_EQ(example.getHighestPermission("species:he:density"), - make_permission(Permissions::Read, "species:he:density")); - example.setAccess("species:he:density", - {Permissions::Nowhere, Permissions::Nowhere, Permissions::Boundaries, - Permissions::Nowhere}); + make_permission(PermissionTypes::Read, "species:he:density")); + example.setAccess("species:he:density", {Regions::Nowhere, Regions::Nowhere, + Regions::Boundaries, Regions::Nowhere}); EXPECT_EQ(example.getHighestPermission("species:he:density"), - make_permission(Permissions::None, "species:he:density")); - EXPECT_EQ(example.getHighestPermission("species:he:density", Permissions::Boundaries), - make_permission(Permissions::Write, "species:he:density")); - - EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Interior), - make_permission(Permissions::Write, "species:he:pressure")); - EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::AllRegions), - make_permission(Permissions::None, "species:he:pressure")); - example.setAccess("species:he:pressure", {Permissions::Nowhere, Permissions::AllRegions, - Permissions::Nowhere, Permissions::Nowhere}); - EXPECT_EQ(example.getHighestPermission("species:he:pressure", Permissions::Interior), - make_permission(Permissions::Read, "species:he:pressure")); + make_permission(PermissionTypes::None, "species:he:density")); + EXPECT_EQ(example.getHighestPermission("species:he:density", Regions::Boundaries), + make_permission(PermissionTypes::Write, "species:he:density")); + + EXPECT_EQ(example.getHighestPermission("species:he:pressure", Regions::Interior), + make_permission(PermissionTypes::Write, "species:he:pressure")); + EXPECT_EQ(example.getHighestPermission("species:he:pressure", Regions::All), + make_permission(PermissionTypes::None, "species:he:pressure")); + example.setAccess("species:he:pressure", + {Regions::Nowhere, Regions::All, Regions::Nowhere, Regions::Nowhere}); + EXPECT_EQ(example.getHighestPermission("species:he:pressure", Regions::Interior), + make_permission(PermissionTypes::Read, "species:he:pressure")); EXPECT_EQ(example.getHighestPermission("species:he:pressure"), - make_permission(Permissions::Read, "species:he:pressure")); - - EXPECT_EQ(example.getHighestPermission("unset", Permissions::Interior), - make_permission(Permissions::None, "")); - EXPECT_EQ(example.getHighestPermission("unset", Permissions::Boundaries), - make_permission(Permissions::None, "")); - example.setAccess("unset", {Permissions::Nowhere, Permissions::Interior, - Permissions::Nowhere, Permissions::Boundaries}); - EXPECT_EQ(example.getHighestPermission("unset", Permissions::AllRegions), - make_permission(Permissions::Read, "unset")); - EXPECT_EQ(example.getHighestPermission("unset", Permissions::Boundaries), - make_permission(Permissions::Final, "unset")); + make_permission(PermissionTypes::Read, "species:he:pressure")); + + EXPECT_EQ(example.getHighestPermission("unset", Regions::Interior), + make_permission(PermissionTypes::None, "")); + EXPECT_EQ(example.getHighestPermission("unset", Regions::Boundaries), + make_permission(PermissionTypes::None, "")); + example.setAccess("unset", {Regions::Nowhere, Regions::Interior, Regions::Nowhere, + Regions::Boundaries}); + EXPECT_EQ(example.getHighestPermission("unset", Regions::All), + make_permission(PermissionTypes::Read, "unset")); + EXPECT_EQ(example.getHighestPermission("unset", Regions::Boundaries), + make_permission(PermissionTypes::Final, "unset")); } TEST(PermissionsTests, TestGetVariablesWithPermissions) { - Permissions example({{"species:he:density", - {Permissions::Nowhere, Permissions::AllRegions, - Permissions::Nowhere, Permissions::Boundaries}}, - // Read and write permissions for pressure in the interior region - {"species:he:pressure", - {Permissions::Nowhere, Permissions::Boundaries, - Permissions::Interior, Permissions::Nowhere}}, - // Set the final value for collision frequency - {"species:he:collision_frequency", - {Permissions::Nowhere, Permissions::Interior, - Permissions::Nowhere, Permissions::AllRegions}}, - // Only allow reading of boundary velocity - {"species:he:velocity", - {Permissions::Nowhere, Permissions::Boundaries, - Permissions::Nowhere, Permissions::Nowhere}}}); - - auto read_only = example.getVariablesWithPermission(Permissions::Read); + Permissions example( + {{"species:he:density", + {Regions::Nowhere, Regions::All, Regions::Nowhere, Regions::Boundaries}}, + // Read and write permissions for pressure in the interior region + {"species:he:pressure", + {Regions::Nowhere, Regions::Boundaries, Regions::Interior, Regions::Nowhere}}, + // Set the final value for collision frequency + {"species:he:collision_frequency", + {Regions::Nowhere, Regions::Interior, Regions::Nowhere, Regions::All}}, + // Only allow reading of boundary velocity + {"species:he:velocity", + {Regions::Nowhere, Regions::Boundaries, Regions::Nowhere, Regions::Nowhere}}}); + + auto read_only = example.getVariablesWithPermission(PermissionTypes::Read); EXPECT_EQ(read_only.size(), 3); - EXPECT_EQ(read_only["species:he:density"], Permissions::Interior); - EXPECT_EQ(read_only["species:he:pressure"], Permissions::Boundaries); - EXPECT_EQ(read_only["species:he:velocity"], Permissions::Boundaries); + EXPECT_EQ(read_only["species:he:density"], Regions::Interior); + EXPECT_EQ(read_only["species:he:pressure"], Regions::Boundaries); + EXPECT_EQ(read_only["species:he:velocity"], Regions::Boundaries); - auto readable = example.getVariablesWithPermission(Permissions::Read, false); + auto readable = example.getVariablesWithPermission(PermissionTypes::Read, false); EXPECT_EQ(readable.size(), 4); - EXPECT_EQ(readable["species:he:density"], Permissions::AllRegions); - EXPECT_EQ(readable["species:he:pressure"], Permissions::AllRegions); - EXPECT_EQ(readable["species:he:collision_frequency"], Permissions::AllRegions); - EXPECT_EQ(readable["species:he:velocity"], Permissions::Boundaries); + EXPECT_EQ(readable["species:he:density"], Regions::All); + EXPECT_EQ(readable["species:he:pressure"], Regions::All); + EXPECT_EQ(readable["species:he:collision_frequency"], Regions::All); + EXPECT_EQ(readable["species:he:velocity"], Regions::Boundaries); - auto write_nonfinal = example.getVariablesWithPermission(Permissions::Write, true); + auto write_nonfinal = example.getVariablesWithPermission(PermissionTypes::Write, true); EXPECT_EQ(write_nonfinal.size(), 1); - EXPECT_EQ(write_nonfinal["species:he:pressure"], Permissions::Interior); + EXPECT_EQ(write_nonfinal["species:he:pressure"], Regions::Interior); - auto writable = example.getVariablesWithPermission(Permissions::Write, false); + auto writable = example.getVariablesWithPermission(PermissionTypes::Write, false); EXPECT_EQ(writable.size(), 3); - EXPECT_EQ(writable["species:he:density"], Permissions::Boundaries); - EXPECT_EQ(writable["species:he:pressure"], Permissions::Interior); - EXPECT_EQ(writable["species:he:collision_frequency"], Permissions::AllRegions); + EXPECT_EQ(writable["species:he:density"], Regions::Boundaries); + EXPECT_EQ(writable["species:he:pressure"], Regions::Interior); + EXPECT_EQ(writable["species:he:collision_frequency"], Regions::All); - auto final_write = example.getVariablesWithPermission(Permissions::Final); + auto final_write = example.getVariablesWithPermission(PermissionTypes::Final); EXPECT_EQ(final_write.size(), 2); - EXPECT_EQ(final_write["species:he:density"], Permissions::Boundaries); - EXPECT_EQ(final_write["species:he:collision_frequency"], Permissions::AllRegions); + EXPECT_EQ(final_write["species:he:density"], Regions::Boundaries); + EXPECT_EQ(final_write["species:he:collision_frequency"], Regions::All); } TEST(PermissionsTests, TestSubstitute) { - Permissions example({{"species:{s1}:collision_frequencies:{s1}_{s2}_coll", - {Permissions::Nowhere, Permissions::AllRegions, - Permissions::Nowhere, Permissions::Nowhere}}, - readIfSet("d")}); + Permissions example( + {{"species:{s1}:collision_frequencies:{s1}_{s2}_coll", + {Regions::Nowhere, Regions::All, Regions::Nowhere, Regions::Nowhere}}, + readIfSet("d")}); - example.setAccess("{var}", {Permissions::Nowhere, Permissions::Nowhere, - Permissions::Interior, Permissions::Nowhere}); + example.setAccess( + "{var}", {Regions::Nowhere, Regions::Nowhere, Regions::Interior, Regions::Nowhere}); example.substitute("s1", {"e", "d+"}); example.substitute("s2", {"e", "d+"}); - auto readable = example.getVariablesWithPermission(Permissions::Read, false); + auto readable = example.getVariablesWithPermission(PermissionTypes::Read, false); EXPECT_EQ(readable.size(), 5); - EXPECT_EQ(readable["{var}"], Permissions::Interior); - EXPECT_EQ(readable["species:e:collision_frequencies:e_e_coll"], - Permissions::AllRegions); - EXPECT_EQ(readable["species:e:collision_frequencies:e_d+_coll"], - Permissions::AllRegions); - EXPECT_EQ(readable["species:d+:collision_frequencies:d+_e_coll"], - Permissions::AllRegions); - EXPECT_EQ(readable["species:d+:collision_frequencies:d+_d+_coll"], - Permissions::AllRegions); + EXPECT_EQ(readable["{var}"], Regions::Interior); + EXPECT_EQ(readable["species:e:collision_frequencies:e_e_coll"], Regions::All); + EXPECT_EQ(readable["species:e:collision_frequencies:e_d+_coll"], Regions::All); + EXPECT_EQ(readable["species:d+:collision_frequencies:d+_e_coll"], Regions::All); + EXPECT_EQ(readable["species:d+:collision_frequencies:d+_d+_coll"], Regions::All); example.substitute("var", {"a", "b", "c", "d"}); - auto writable = example.getVariablesWithPermission(Permissions::Write); + auto writable = example.getVariablesWithPermission(PermissionTypes::Write); EXPECT_EQ(writable.size(), 3); - EXPECT_EQ(writable["a"], Permissions::Interior); - EXPECT_EQ(writable["b"], Permissions::Interior); - EXPECT_EQ(writable["c"], Permissions::Interior); + EXPECT_EQ(writable["a"], Regions::Interior); + EXPECT_EQ(writable["b"], Regions::Interior); + EXPECT_EQ(writable["c"], Regions::Interior); EXPECT_EQ(example.getHighestPermission("d"), - make_permission(Permissions::ReadIfSet, "d")); + make_permission(PermissionTypes::ReadIfSet, "d")); } From 6102685efabad7198982787d3d9565ebb6e91db7 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Mon, 17 Nov 2025 16:46:06 +0000 Subject: [PATCH 24/63] Fixed various issues flagged in review --- include/guarded_options.hxx | 34 ++++++++++-- include/permissions.hxx | 107 +++++++++++++++++++++--------------- include/reaction.hxx | 2 +- src/guarded_options.cxx | 34 ------------ src/permissions.cxx | 76 ++++++++----------------- src/sheath_boundary.cxx | 6 +- 6 files changed, 119 insertions(+), 140 deletions(-) diff --git a/include/guarded_options.hxx b/include/guarded_options.hxx index 5f7343e32..64339713f 100644 --- a/include/guarded_options.hxx +++ b/include/guarded_options.hxx @@ -22,10 +22,22 @@ public: /// Get a subsection or value. The result will also be wrapped in a /// GuardedOptions object, with the same permissions as this one. - GuardedOptions operator[](const std::string& name); + GuardedOptions operator[](const std::string& name) { + if (options == nullptr) + throw BoutException( + "Trying to access GuardedOptions when underlying options are nullptr."); + return GuardedOptions(&(*options)[name], permissions, unread_variables, + unwritten_variables); + } GuardedOptions operator[](const char* name) { return (*this)[std::string(name)]; } - const GuardedOptions operator[](const std::string& name) const; + const GuardedOptions operator[](const std::string& name) const { + if (options == nullptr) + throw BoutException( + "Trying to access GuardedOptions when underlying options are nullptr."); + return GuardedOptions(&(*options)[name], permissions, unread_variables, + unwritten_variables); + } const GuardedOptions operator[](const char* name) const { return (*this)[std::string(name)]; } std::map getChildren(); @@ -46,11 +58,25 @@ public: /// Returns a list of variables with read-only permission but which /// have not been accessed using the `get()` method. - std::map unreadItems() const; + std::map unreadItems() const { +#if CHECKLEVEL >= 1 + return *unread_variables; +#else + throw BoutException( + "Reading of items in GuardedOptions is not tracked when CHECKLEVEL < 1"); +#endif + } /// Returns a list of variables with read-write permission but which /// have not been accessed using the `getWritable()` method. - std::map unwrittenItems() const; + std::map unwrittenItems() const { +#if CHECKLEVEL >= 1 + return *unwritten_variables; +#else + throw BoutException( + "Reading of items in GuardedOptions is not tracked when CHECKLEVEL < 1"); +#endif + } bool operator==(const GuardedOptions& other) const; bool operator!=(const GuardedOptions& other) const; diff --git a/include/permissions.hxx b/include/permissions.hxx index 6689dedd7..aad8040e2 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -22,15 +22,17 @@ enum class Regions { All = Interior | Boundaries }; -inline Regions operator&(Regions a, Regions b) { +constexpr Regions operator&(Regions a, Regions b) { return static_cast(static_cast(a) & static_cast(b)); } -inline Regions operator|(Regions a, Regions b) { +constexpr Regions operator|(Regions a, Regions b) { return static_cast(static_cast(a) | static_cast(b)); } -inline Regions operator~(Regions a) { return static_cast(~static_cast(a)); } +constexpr Regions operator~(Regions a) { + return static_cast(~static_cast(a)); +} /// Class to store information on whether particular variables an be /// read from and/or written to. These permissions can apply on @@ -48,16 +50,25 @@ public: /// Data type for storing the regions of a variable which have a /// particular level of permission. Some examples can be seen below: /// - /// AccessRights only_read_if_set = { AllRegions, Nowhere, Nowhere, Nowhere }, - /// read_only = { Nowhere, AllRegions, Nowhere, Nowhere }, - /// write_boundaries = { Nowhere, Nowhere, Boundaries, Nowhere }, - /// read_and_write_everywhere = { Nowhere, AllReginos, AllRegions, - /// Nowhere - /// }, final_write_boundaries_read_interior = { Interior, Nowhere, - /// Boundaries }; + /// AccessRights only_read_if_set = { Regions::All, Regions::Nowhere, + /// Regions::Nowhere, Regions::Nowhere }, + /// read_only = { Regions::Nowhere, Regions::All, Regions::Nowhere, + /// Regions::Nowhere }, + /// write_boundaries = { Regions::Nowhere, Regions::Nowhere, + /// Regions::Boundaries, Regions::Nowhere }, + /// read_and_write_everywhere = { Regions::Nowhere, + /// Regions::AllReginos, Regions::All, Regions::Nowhere }, + /// final_write_boundaries_read_interior = { Regions::Nowhere, + /// Regions::Interior, Regions::Nowhere, Regions::Boundaries }; /// using AccessRights = std::array(PermissionTypes::END)>; + /// Data used to specify what access rights apply to the named variable. + struct VarRights { + std::string name; + AccessRights rights; + }; + /// Create permission from an initialiser list. Each item in the /// initialiser list should be a pair made up of the name of a /// variable stored in an Options object (with a colon separating @@ -69,20 +80,20 @@ public: /// /// Permissions example({ /// // Permission to read charge only if it has been set - /// {"species:he:charge", {Regions::AllRegions, Regions::Nowhere, + /// {"species:he:charge", {Regions::All, Regions::Nowhere, /// Regions::Nowhere,, Regions::Nowhere}}, /// // Read permission for atomic mass - /// {"species:he:AA", {Regions::Nowhere, Regions::AllRegions, + /// {"species:he:AA", {Regions::Nowhere, Regions::All, /// Regions::Nowhere, Regions::Nowhere}}, /// // Read permissions for density - /// {"species:he:density", {Regions::Nowhere, Regions::AllRegions, + /// {"species:he:density", {Regions::Nowhere, Regions::All, /// Regions::Nowhere, Regions::Nowhere}}, /// // Read and write permissions for pressure in the interior region /// {"species:he:pressure", {Regions::Nowhere, Regions::Nowhere, /// Regions::Interior, Regions::Nowhere}}, /// // Set the final value for collision frequency /// {"species:he:collision_frequency", {Regions::Nowhere, - /// Regions::Nowhere, Regions::Nowhere, Regions::AllRegions}} + /// Regions::Nowhere, Regions::Nowhere, Regions::All}} /// }); /// /// If a variable is not included in the initialiser list then it is @@ -101,11 +112,11 @@ public: /// /// Permissions example2({ /// {"species:{name}:collision_frequency", {Regions::Nowhere, - /// Regions::AllRegions, Regions::Nowhere, Regions::Nowhere}} + /// Regions::All, Regions::Nowhere, Regions::Nowhere}} /// }); /// example2.substitute("name", {"he+", "d+", "e", "d", "he"}); /// - Permissions(std::initializer_list> data); + Permissions(std::initializer_list data); /// Set the level of access for the various regions of the /// variable. This uses the same logic as the constructor. For @@ -113,7 +124,7 @@ public: /// everywhere but only writeable in the interior, you would use /// /// permissions.setAccess("species:he:density", - /// {Regions::Nowhere, Regions::AllRegions, + /// {Regions::Nowhere, Regions::All, /// Regions::Interior, Regions::Nowhere}) /// 0 /// or, equivalently, @@ -127,9 +138,7 @@ public: /// that section. Placeholder names can also be used. void setAccess(const std::string& variable, const AccessRights& rights); - void setAccess(const std::pair& info) { - setAccess(info.first, info.second); - } + void setAccess(const VarRights& info) { setAccess(info.name, info.rights); } void printAllPermissions() const; @@ -140,10 +149,10 @@ public: /// for every species. /// /// Permissions example({ - /// {"species:{name}:density", {Regions::Nowhere, Regions::AllRegions, + /// {"species:{name}:density", {Regions::Nowhere, Regions::All, /// Regions::Nowhere, Regions::Nowhere}}, /// {"species:{name}:collision_frequency", {Regions::Nowhere, - /// Regions::Nowhere, Regions::AllRegions, Regions::Nowhere}}, + /// Regions::Nowhere, Regions::All, Regions::Nowhere}}, /// }); /// example.substitute("name", {"d", "d+", "t", "t+", "he", "he+", "c", "c+", "e"}); /// @@ -164,10 +173,10 @@ public: Regions region = Regions::All) const; /// Get the highest permission level with which the given variable - /// can be accessed in the given region. The second item - /// returned indicates the name of the variable or section from - /// which the access rights are derived. If there is no matching - /// section then it will be an empty string. + /// can be accessed in the given region. The second item returned + /// indicates the name of the variable or section from which the + /// access rights are derived. If there is no matching section then + /// it will return `PermissionTypes::None` and an empty string. std::pair getHighestPermission(const std::string& variable, Regions region = Regions::All) const; @@ -176,8 +185,8 @@ public: /// true then it will only include variables/regions for which this /// is the highest permission. /// - /// Permissions example({"test", {Regions::Nowhere, Regions::AllRegions, - /// Regions::AllRegions, Permissions:Nowhere}}); + /// Permissions example({"test", {Regions::Nowhere, Regions::All, + /// Regions::All, Permissions:Nowhere}}); /// // Print variables which can be read /// for (const auto [varname, region] : /// example.getVariablesWithPermission(PermissionTypes::Read), false)) @@ -204,47 +213,55 @@ private: /// string indicates the name of the variable from which the access /// rights were derived. It will be empty if there are no matching /// entries. - std::pair bestMatchRights(const std::string& variable) const; - - /// Return a set of access rights where the lower permissions have - /// been updated so that they reflect higher permissions (e.g., read - /// permission will be set in all cases where write permission was - /// set). - static AccessRights applyLowerPermissions(const AccessRights& rights); + VarRights bestMatchRights(const std::string& variable) const; std::map variable_permissions; }; /// Convenience function to return an object expressing that the /// variable should have ReadIfSet permissions in the specified regions. -std::pair -readIfSet(std::string varname, Regions region = Regions::All); +inline Permissions::VarRights readIfSet(std::string varname, + Regions region = Regions::All) { + return {varname, {region, Regions::Nowhere, Regions::Nowhere, Regions::Nowhere}}; +} /// Convenience function to return an object expressing that the /// variable should have Read permissions in the specified regions. -std::pair readOnly(std::string varname, - Regions region = Regions::All); +inline Permissions::VarRights readOnly(std::string varname, + Regions region = Regions::All) { + return {varname, {Regions::Nowhere, region, Regions::Nowhere, Regions::Nowhere}}; +} /// Convenience function to return an object expressing that the /// variable should have Write permissions in the specified regions. -std::pair -readWrite(std::string varname, Regions region = Regions::All); +inline Permissions::VarRights readWrite(std::string varname, + Regions region = Regions::All) { + return {varname, {Regions::Nowhere, Regions::Nowhere, region, Regions::Nowhere}}; +} /// Convenience function to return an object expressing that the /// variable should have Final permissions in the specified regions. -std::pair -writeFinal(std::string varname, Regions region = Regions::All); +inline Permissions::VarRights writeFinal(std::string varname, + Regions region = Regions::All) { + return {varname, {Regions::Nowhere, Regions::Nowhere, Regions::Nowhere, region}}; +} /// Convenience function to return an object expressing that the /// variable should have Write permissions on the boundaries. It will /// have Read permissions in the interior, as this is normally /// required to set the boundaries correctly. -std::pair writeBoundary(std::string varname); +inline Permissions::VarRights writeBoundary(std::string varname) { + return {varname, + {Regions::Nowhere, Regions::Interior, Regions::Nowhere, Regions::Boundaries}}; +} /// Convenience function to return an object expressing that the /// variable should have Write permissions on the boundaries. It will /// have Read permissions in the interior if the interior is already set. -std::pair writeBoundaryIfSet(std::string varname); +inline Permissions::VarRights writeBoundaryIfSet(std::string varname) { + return {varname, + {Regions::Interior, Regions::Nowhere, Regions::Nowhere, Regions::Boundaries}}; +} // FIXME: Ideally there would be some way to express write permissions only if set // FIXME: Ideally we could express to write a boundary only if the interior is set diff --git a/include/reaction.hxx b/include/reaction.hxx index 96863a692..9b6d3cb80 100644 --- a/include/reaction.hxx +++ b/include/reaction.hxx @@ -7,7 +7,7 @@ #include "reaction_diagnostic.hxx" #include "reaction_parser.hxx" -typedef GuardedOptions && (*OPTYPE)(GuardedOptions&&, Field3D); +using OPTYPE = GuardedOptions &&(GuardedOptions&&, Field3D); /** * @brief Temporary struct to use as a base class for all reactions components. Ensures diff --git a/src/guarded_options.cxx b/src/guarded_options.cxx index 0c4e7cc7e..422b404e5 100644 --- a/src/guarded_options.cxx +++ b/src/guarded_options.cxx @@ -40,22 +40,6 @@ GuardedOptions::GuardedOptions(Options* options, Permissions* permissions) #endif } -GuardedOptions GuardedOptions::operator[](const std::string& name) { - if (options == nullptr) - throw BoutException( - "Trying to access GuardedOptions when underlying options are nullptr."); - return GuardedOptions(&(*options)[name], permissions, unread_variables, - unwritten_variables); -} - -const GuardedOptions GuardedOptions::operator[](const std::string& name) const { - if (options == nullptr) - throw BoutException( - "Trying to access GuardedOptions when underlying options are nullptr."); - return GuardedOptions(&(*options)[name], permissions, unread_variables, - unwritten_variables); -} - std::map GuardedOptions::getChildren() { std::map result; for (const auto& [varname, _] : options->getChildren()) { @@ -119,24 +103,6 @@ Options& GuardedOptions::getWritable(Regions region) { #endif } -std::map GuardedOptions::unreadItems() const { -#if CHECKLEVEL >= 1 - return *unread_variables; -#else - throw BoutException( - "Reading of items in GuardedOptions is not tracked when CHECKLEVEL < 1"); -#endif -} - -std::map GuardedOptions::unwrittenItems() const { -#if CHECKLEVEL >= 1 - return *unwritten_variables; -#else - throw BoutException( - "Reading of items in GuardedOptions is not tracked when CHECKLEVEL < 1"); -#endif -} - bool GuardedOptions::operator==(const GuardedOptions& other) const { return std::tie(options, permissions, unread_variables, unwritten_variables) == std::tie(other.options, other.permissions, other.unread_variables, diff --git a/src/permissions.cxx b/src/permissions.cxx index a408308a4..e3f2b9629 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -1,5 +1,6 @@ #include "../include/permissions.hxx" #include +#include // TODO: It might be useful to add an optional condition which must be // met for conditions to apply. So a variable is only read or written @@ -23,11 +24,26 @@ // There should be ways to do this with templates to allow greater inlining // +/// Return a set of access rights where the lower permissions have +/// been updated so that they reflect higher permissions (e.g., read +/// permission will be set in all cases where write permission was +/// set). +Permissions::AccessRights applyLowerPermissions(const Permissions::AccessRights& rights) { + Permissions::AccessRights result(rights); + for (int i = static_cast(PermissionTypes::ReadIfSet); + i < static_cast(PermissionTypes::END); i++) { + // Higher permissions imply lower permissions + for (int j = static_cast(PermissionTypes::ReadIfSet); j < i; j++) { + result[j] = result[j] | rights[i]; + } + } + return result; +} + const std::map Permissions::fundamental_regions = { {Regions::Interior, "Interior"}, {Regions::Boundaries, "Boundaries"}}; -Permissions::Permissions(std::initializer_list> data) - : variable_permissions() { +Permissions::Permissions(std::initializer_list data) : variable_permissions() { for (const auto& [varname, access] : data) { setAccess(varname, access); } @@ -82,11 +98,10 @@ void Permissions::substitute(const std::string& label, } } -std::pair -Permissions::bestMatchRights(const std::string& variable) const { +Permissions::VarRights Permissions::bestMatchRights(const std::string& variable) const { auto match = variable_permissions.find(variable); if (match != variable_permissions.end()) { - return *match; + return {match->first, match->second}; } Permissions::AccessRights best_candidate = {Regions::Nowhere, Regions::Nowhere, Regions::Nowhere}; @@ -148,57 +163,12 @@ Permissions::getVariablesWithPermission(PermissionTypes permission, return result; } -Permissions::AccessRights Permissions::applyLowerPermissions(const AccessRights& rights) { - AccessRights result(rights); - for (int i = static_cast(PermissionTypes::ReadIfSet); - i < static_cast(PermissionTypes::END); i++) { - result[i] = rights[i]; - // Higher permissions imply lower permissions - for (int j = static_cast(PermissionTypes::ReadIfSet); j < i; j++) { - result[j] = result[j] | rights[i]; - } - } - return result; -} - std::string Permissions::regionNames(const Regions regions) { - std::string result; + std::vector regions_present(fundamental_regions.size()); for (auto & [region, name] : fundamental_regions) { if ((regions & region) == region) { - if (result.size() > 0) result += ", "; - result += name; + regions_present.push_back(name); } } - return result; -} - -std::pair readIfSet(std::string varname, - Regions region) { - return {varname, {region, Regions::Nowhere, Regions::Nowhere, Regions::Nowhere}}; -} - -std::pair readOnly(std::string varname, - Regions region) { - return {varname, {Regions::Nowhere, region, Regions::Nowhere, Regions::Nowhere}}; -} - -std::pair readWrite(std::string varname, - Regions region) { - return {varname, {Regions::Nowhere, Regions::Nowhere, region, Regions::Nowhere}}; -} - -std::pair writeFinal(std::string varname, - Regions region) { - return {varname, {Regions::Nowhere, Regions::Nowhere, Regions::Nowhere, region}}; -} - -std::pair writeBoundary(std::string varname) { - return {varname, - {Regions::Nowhere, Regions::Interior, Regions::Nowhere, Regions::Boundaries}}; -} - -std::pair -writeBoundaryIfSet(std::string varname) { - return {varname, - {Regions::Interior, Regions::Nowhere, Regions::Nowhere, Regions::Boundaries}}; + return fmt::format("{}", fmt::join(regions_present, ", ")); } diff --git a/src/sheath_boundary.cxx b/src/sheath_boundary.cxx index 6fdb0bb7b..e2ee970de 100644 --- a/src/sheath_boundary.cxx +++ b/src/sheath_boundary.cxx @@ -122,9 +122,9 @@ SheathBoundary::SheathBoundary(std::string name, Options& alloptions, Solver*) // to change what writeBoundaryIfSet returns (and how we model // permissions, for that matter) state_variable_access.setAccess( - always_set_phi ? std::pair( - "fields:phi", - {Regions::Interior, Regions::Nowhere, Regions::Nowhere, Regions::Boundaries}) + always_set_phi ? Permissions::VarRights( + {"fields:phi", + {Regions::Interior, Regions::Nowhere, Regions::Nowhere, Regions::Boundaries}}) : writeBoundaryIfSet("fields:phi")); } From fbb0b6b42b849d915a91e7a3b6732a5df6b88f7e Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Thu, 27 Nov 2025 18:25:58 +0000 Subject: [PATCH 25/63] Fixed some permissions in components --- src/braginskii_friction.cxx | 4 ++-- src/braginskii_ion_viscosity.cxx | 4 +++- src/vorticity.cxx | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/braginskii_friction.cxx b/src/braginskii_friction.cxx index bb7f6cf78..2c90083cf 100644 --- a/src/braginskii_friction.cxx +++ b/src/braginskii_friction.cxx @@ -19,8 +19,8 @@ BraginskiiFriction::BraginskiiFriction(const std::string& name, Options& allopti readIfSet("species:{all_species}:velocity", Regions::Interior), readOnly("species:{all_species}:AA"), readIfSet("species:{all_species}:charge"), - readOnly("species:{all_species}:collision_frequencies:{all_species}_{" - "all_species2}_coll"), + readIfSet("species:{all_species}:collision_frequencies:{all_species}_{" + "all_species2}_coll"), readWrite("species:{all_species}:momentum_source")}) { AUTO_TRACE(); Options& options = alloptions[name]; diff --git a/src/braginskii_ion_viscosity.cxx b/src/braginskii_ion_viscosity.cxx index 905f2a2e5..1485c6d76 100644 --- a/src/braginskii_ion_viscosity.cxx +++ b/src/braginskii_ion_viscosity.cxx @@ -36,7 +36,6 @@ BraginskiiIonViscosity::BraginskiiIonViscosity(const std::string& name, readIfSet("species:{non_electrons}:velocity"), readIfSet("species:{non_electrons}:charge"), readIfSet("species:{non_electrons}:collision_frequencies:{coll_type}"), - readOnly("fields:phi"), readWrite("species:{non_electrons}:momentum_source"), readWrite("species:{non_electrons}:energy_source"), readWrite("fields:DivJextra"), @@ -125,6 +124,9 @@ BraginskiiIonViscosity::BraginskiiIonViscosity(const std::string& name, coll_types.push_back("{non_electrons}_{all_species}_coll"); coll_types.push_back("{non_electrons}_{all_species}_cx"); } + if (perpendicular) { + state_variable_access.setAccess(readOnly("fields:phi")); + } state_variable_access.substitute("coll_type", coll_types); } diff --git a/src/vorticity.cxx b/src/vorticity.cxx index 17822b2b3..767015319 100644 --- a/src/vorticity.cxx +++ b/src/vorticity.cxx @@ -225,7 +225,9 @@ Vorticity::Vorticity(std::string name, Options& alloptions, Solver* solver) if (phi_boundary_relax) { state_variable_access.setAccess(readOnly("time")); } else { - state_variable_access.setAccess(readOnly("species:e:AA")); + if (sheath_boundary) { + state_variable_access.setAccess(readOnly("species:e:AA")); + } state_variable_access.setAccess( readIfSet("species:e:temperature", Regions::Interior)); } From b0a6c8d3f65a8530d2176544e12def28fa154d05 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Tue, 2 Dec 2025 14:03:01 +0000 Subject: [PATCH 26/63] Updated docs to describe Permissions/GuardedOptions --- docs/sphinx/developer.rst | 122 +++++++++++++++++++++++++++++++++++++- include/permissions.hxx | 12 +++- 2 files changed, 130 insertions(+), 4 deletions(-) diff --git a/docs/sphinx/developer.rst b/docs/sphinx/developer.rst index 153775e9c..0eea7bd1c 100644 --- a/docs/sphinx/developer.rst +++ b/docs/sphinx/developer.rst @@ -181,7 +181,9 @@ are of unit length. See relevant `BOUT++ docs `_ for more info. There is also a data type called ``Options`` which is equivalent to a Python dictionary with extra functionality, and is used to store input -options, the entire simulation state and many other data. +options, the entire simulation state and many other data. Finally, +there is the ``GuardedOptions`` datatype, which wraps an ``Options`` +object and controls access to its contents. Adding new settings @@ -255,6 +257,94 @@ What is "Options"? Options is a dictionary-like class originally developed for parsing BOUT++ options. In Hermes-3, it is used as a general purpose dictionary. +What are Permissions? +~~~~~~~~~~~~~~ + +The ``Permissions`` class can be used to store information about which +variables within an ``Options`` object are allowed to be accessed and +for what purpose. This is used to control the variables used by a +``Component``. There are four levels of permissions, stored in the +`PermissionTypes` enum: + +ReadIfSet + Only allowed to read variable if it is already set. + +Read + Can read the contents of the variable. Assumes it has already been + set. + +Write + Can write variable. Makes no assumption about whether it has already + been written or will be written again in future. + +Final + This will be the last component to write to the variable. Only one + component may have ``Final`` permission for a given variable. + +Permissions apply to particular regions of the domain. This +allows, e.g., for there to be read permissions for the interior of the +domain but write permissions for the boundaries. These regions +are represented using the ``Regions`` enum, which functions as a +bitset. You can combine regions using bitwise logical operators. + +.. doxygengroup:: RegionsGroup + :members: + +A particular permission entry is a struct (``Permissions::VarRights``) +consisting of variable name and an array of ``Regions``. Each element +of the array corresponds to the member of the ``PermissionTypes`` enum +with that index. The contents of the element are the region(s) of the +domain with that permission type. This is an implementation detail +that you will seldom need to worry about in practice. The overwhelming +majority of permissions you would want to create can be constructed +using the provided factory functions. + +.. doxygengroup:: PermissionFactories + :members: + +This permission data can be used to construct a ``Permissions`` +object, which can be queried. Note that a permission applied to a +section of an ``Options`` object will apply to all variables contained +within that section, unless a more specific permission is also set. :: + + Permissions p({readOnly("time"), + readOnly("species:e:pressure"), + readWrite("species:e:momentum", Reegions::Interior)}); + +.. doxygenclass:: Permissions + :members: + + +What is "GuardedOptions"? +~~~~~~~~~~~~~~ + +``GuardedOptions`` objects combine a `Permissions` object and an +`Options` object. They can be indexed just like normal ``Options`` +objects but will return another ``GuardedOptions``, wrapping the +result. In order to read or write the contents of a ``GuardedOptions`` +object you must use the ``get()`` or ``getWritable()`` methods, +respectively. These will return the underlying (const) ``Options`` +object, if you have the necessary permissions to access it. Otherwise, +they will raise an exception. + +If ``CHECKLEVEL`` is 1 or above, then the ``GuardedOptions`` will track +which variables have actually been accessed. Lists of +unread/unwritten variables can be returned with the ``unreadItems()`` +and ``unwrittenItems()`` methods. If ``CHECKLEVEL`` is zero then +calling these methods will raise an exception. + +.. doxygenclass:: GuardedOptions + :members: + +.. note:: + When indexing a ``GuardedOptions`` object, it will create a new + ``GuardedOptions`` on-demand. This is unlike with a normal + ``Options`` object which returns a reference to a preexisting child + ``Options`` object. You generally should not store + ``GuardedOptions`` by reference. You may be able to pass them by + reference, but this requires you to think carefully about whether + the argument is going to be an r-value or an l-value. + Getting/setting values ~~~~~~~~~~~~~~ @@ -328,6 +418,9 @@ And there is a corresponding ``setBoundary`` that can be used for BC operations: return option; } +All of these functions are overloaded to accept both `Options` and +`GuardedOptions` objects. + These functions take a second argument which tells you where they were set, which is easier for debugging. They are wrapped into additional functions, ``GET_VALUE`` and ``GET_NOBOUNDARY`` which automatically include this argument. @@ -531,6 +624,8 @@ state can be modified. .. doxygenstruct:: Component :members: + :protected-members: + :private-members: Components are usually defined in separate files; sometimes multiple components in one file if they are small and related to each other (e.g. @@ -566,11 +661,32 @@ The `name` is a string labelling the instance. The `alloptions` tree contains at * `alloptions[name]` options for this instance * `alloptions['units']` +All component constructors must pass a `Permissions` object to the +constructor on the `Component::Component` base class. This specifies which +variables will be read/written by the `Component::transform` method and will be +used to construct a `GuardedOptions` object to be passed into +`Component::transform_impl`. The `Permissions` object +(`Component::state_variable_access`) can be further updated in the +body of the constructor of your component, based on what +configurations were specified in ``alloptions``. + +Note that a number of substitutions will be performed on your +permissions, so that you can specify permissions for some variables +for each species. The labels for these substitutions are wrapped in +curly braces. For example, the following permissions would give read +access to pressure for all species and density of ions:: + + MyComponent::MyComponent(const std::string &name, Options &options, + Solver *solver) : Component({readOnly("species:{all_species}:pressure"), + readOnly("species:{ions}:density")}) {} + +See the documentation for `Component::declareAllSpecies` for a list of +all substitutions that will be performed. + Component scheduler ~~~~~~~~~~~~~~ - The simulation model is created in `Hermes::init` by a call to the `ComponentScheduler`:: scheduler = ComponentScheduler::create(options, Options::root(), solver); @@ -893,4 +1009,4 @@ There are two simple integrated tests to make sure that the collision frequency across `neutral_mixed`, `evolve_pressure`, `ion_viscosity` and `neutral_parallel_diffusion`. A minimal 3D geometry is run for one RHS evaluation, and the test checks the log file to make sure the correct collisionalities were selected. One of the tests is for the `multispecies` -mode across all components, while the other is for `braginskii` for plasma and `afn` for neutrals. \ No newline at end of file +mode across all components, while the other is for `braginskii` for plasma and `afn` for neutrals. diff --git a/include/permissions.hxx b/include/permissions.hxx index aad8040e2..4b5863a84 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -13,6 +13,9 @@ /// execute components. enum class PermissionTypes { None = -1, ReadIfSet, Read, Write, Final, END }; +/// \defgroup RegionsGroup +/// @{ + /// The regions of the domain to which a particular permission /// apply. These are designed to be used as bit-flags. enum class Regions { @@ -34,6 +37,8 @@ constexpr Regions operator~(Regions a) { return static_cast(~static_cast(a)); } +/// @} + /// Class to store information on whether particular variables an be /// read from and/or written to. These permissions can apply on /// particular regions of the domain. @@ -126,7 +131,7 @@ public: /// permissions.setAccess("species:he:density", /// {Regions::Nowhere, Regions::All, /// Regions::Interior, Regions::Nowhere}) - /// 0 + /// /// or, equivalently, /// /// permissions.setAccess("species:he:density", @@ -218,6 +223,9 @@ private: std::map variable_permissions; }; +/// \defgroup PermissionFactories +/// @{ + /// Convenience function to return an object expressing that the /// variable should have ReadIfSet permissions in the specified regions. inline Permissions::VarRights readIfSet(std::string varname, @@ -263,5 +271,7 @@ inline Permissions::VarRights writeBoundaryIfSet(std::string varname) { {Regions::Interior, Regions::Nowhere, Regions::Nowhere, Regions::Boundaries}}; } +/// @} + // FIXME: Ideally there would be some way to express write permissions only if set // FIXME: Ideally we could express to write a boundary only if the interior is set From 884f8c6940fd0a603b9dbb088aaf3ea23ebaa703 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Mon, 8 Dec 2025 13:05:27 +0000 Subject: [PATCH 27/63] Apply suggestions from code review Co-authored-by: Peter Hill --- include/component.hxx | 4 ++-- include/guarded_options.hxx | 2 +- src/classical_diffusion.cxx | 3 ++- src/component.cxx | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/component.hxx b/include/component.hxx index 51e51e89d..806cd8932 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -68,7 +68,7 @@ struct SpeciesInformation { struct Component { /// Initialise the `state_variable_acceess` permissions. Note that /// `{all_species}` in any variable names will be replaced with the - /// names of all species being simulated (by claling + /// names of all species being simulated (by calling /// `declareAllSpecies()`, which is done after all components are /// created by a ComponentSchedular). Component(Permissions&& access_permissions) @@ -106,7 +106,7 @@ struct Component { Solver *solver); // Time integration solver /// Tell the component the name of all species in the simulation, by type. It - /// will use this information to substitute the following placeholders in `svate_variable_access`: + /// will use this information to substitute the following placeholders in `state_variable_access`: /// - electrons (any electron species) /// - electrons2 (same as above, used for cross-product) /// - neutrals (species with no charge) diff --git a/include/guarded_options.hxx b/include/guarded_options.hxx index 64339713f..622d66a62 100644 --- a/include/guarded_options.hxx +++ b/include/guarded_options.hxx @@ -94,6 +94,6 @@ private: GuardedOptions(Options* options, Permissions* permissions, std::shared_ptr> unread_vars, std::shared_ptr> unwritten_vars) - : options(options), permissions(permissions), unread_variables(unread_vars), + : options(options), permissions(std::move(permissions)), unread_variables(std::move(unread_vars)), unwritten_variables(unwritten_vars) {} }; diff --git a/src/classical_diffusion.cxx b/src/classical_diffusion.cxx index 6a54f6e0f..6e58d2faa 100644 --- a/src/classical_diffusion.cxx +++ b/src/classical_diffusion.cxx @@ -17,8 +17,9 @@ ClassicalDiffusion::ClassicalDiffusion(std::string name, Options& alloptions, So state_variable_access.substitute( "optional", {"charge", "pressure", "density", "velocity", "temperature"}); std::vector e_vals = {"AA", "density"}; - if (custom_D <= 0.) + if (custom_D <= 0.) { e_vals.push_back("collision_frequency"); + } state_variable_access.substitute("e_vals", e_vals); // FIXME: momentum and energy sources are only set if velocity and // temperature are defined (respectively). Collision frequency is diff --git a/src/component.cxx b/src/component.cxx index 66a93a622..ddbc8779c 100644 --- a/src/component.cxx +++ b/src/component.cxx @@ -61,7 +61,7 @@ bool isSetFinal(const GuardedOptions & option, const std::string& location) { bool set = option.isSet(); #if CHECKLEVEL >= 1 PermissionTypes perm = option.getHighestPermission(); - if (static_cast(perm) >= static_cast(PermissionTypes::Read) + if (perm >= PermissionTypes::Read or (perm == PermissionTypes::ReadIfSet and set)) { const Options& opt = option.get(); const_cast(opt).attributes["final"] = location; From 76fc9a8948881cb8deb491cc938821f93fb4b55b Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Mon, 8 Dec 2025 13:42:39 +0000 Subject: [PATCH 28/63] Make component variable access permissions private --- include/adas_reaction.hxx | 28 +++++------ include/amjuel_reaction.hxx | 15 +++--- include/braginskii_thermal_force.hxx | 20 ++++---- include/component.hxx | 17 ++++++- include/detachment_controller.hxx | 6 +-- include/fixed_density.hxx | 4 +- include/fixed_temperature.hxx | 2 +- include/fixed_velocity.hxx | 4 +- include/hydrogen_charge_exchange.hxx | 16 +++--- include/neutral_parallel_diffusion.hxx | 11 ++-- include/noflow_boundary.hxx | 6 +-- include/set_temperature.hxx | 4 +- include/simple_conduction.hxx | 5 +- include/simple_pump.hxx | 2 +- include/solkit_hydrogen_charge_exchange.hxx | 4 +- include/solkit_neutral_parallel_diffusion.hxx | 2 +- include/sound_speed.hxx | 6 +-- include/temperature_feedback.hxx | 4 +- include/upstream_density_feedback.hxx | 2 +- src/anomalous_diffusion.cxx | 8 +-- src/binormal_stpm.cxx | 5 +- src/braginskii_collisions.cxx | 50 ++++++++----------- src/braginskii_conduction.cxx | 26 +++++----- src/braginskii_friction.cxx | 2 +- src/braginskii_heat_exchange.cxx | 12 ++--- src/braginskii_ion_viscosity.cxx | 4 +- src/classical_diffusion.cxx | 12 ++--- src/diamagnetic_drift.cxx | 7 ++- src/electromagnetic.cxx | 2 +- src/evolve_density.cxx | 4 +- src/evolve_energy.cxx | 6 +-- src/evolve_momentum.cxx | 4 +- src/evolve_pressure.cxx | 6 +-- src/fixed_fraction_ions.cxx | 2 +- src/neutral_boundary.cxx | 6 +-- src/neutral_full_velocity.cxx | 4 +- src/neutral_mixed.cxx | 4 +- src/polarisation_drift.cxx | 8 +-- src/quasineutral.cxx | 4 +- src/reaction.cxx | 11 ++-- src/recycling.cxx | 19 ++++--- src/relax_potential.cxx | 9 ++-- src/sheath_boundary.cxx | 22 ++++---- src/sheath_boundary_insulating.cxx | 14 +++--- src/sheath_boundary_simple.cxx | 14 +++--- src/sheath_closure.cxx | 12 ++--- src/transform.cxx | 4 +- src/vorticity.cxx | 27 +++++----- src/zero_current.cxx | 2 +- 49 files changed, 228 insertions(+), 240 deletions(-) diff --git a/include/adas_reaction.hxx b/include/adas_reaction.hxx index 1f2b07eb2..3c2cd0021 100644 --- a/include/adas_reaction.hxx +++ b/include/adas_reaction.hxx @@ -68,15 +68,15 @@ struct OpenADAS : public ReactionBase { Tnorm = get(units["eV"]); Nnorm = get(units["inv_meters_cubed"]); FreqNorm = 1. / get(units["seconds"]); - state_variable_access.substitute("val", {"density", "temperature", "velocity"}); - state_variable_access.substitute("e_val", {"density", "temperature"}); - state_variable_access.substitute( - "w_val", {"density_source", "momentum_source", "energy_source"}); + substitutePermissions("val", {"density", "temperature", "velocity"}); + substitutePermissions("e_val", {"density", "temperature"}); + substitutePermissions("w_val", + {"density_source", "momentum_source", "energy_source"}); // FIXME: electron density_source only written if from_ion charge != to_ion charge. - state_variable_access.substitute( - "ew_val", {"density_source", "momentum_source", "energy_source"}); - state_variable_access.substitute("sp", {from_ion, to_ion}); - state_variable_access.substitute("from_ion", {from_ion}); + substitutePermissions("ew_val", + {"density_source", "momentum_source", "energy_source"}); + substitutePermissions("sp", {from_ion, to_ion}); + substitutePermissions("from_ion", {from_ion}); } /// Perform the calculation of rates, and transfer of particles/momentum/energy @@ -106,12 +106,12 @@ struct OpenADASChargeExchange : public ReactionBase { Tnorm = get(units["eV"]); Nnorm = get(units["inv_meters_cubed"]); FreqNorm = 1. / get(units["seconds"]); - state_variable_access.substitute("val", {"density", "temperature", "velocity"}); - state_variable_access.substitute("e_val", {"density", "temperature"}); - state_variable_access.substitute( - "w_val", {"density_source", "momentum_source", "energy_source"}); - state_variable_access.substitute("sp", {from_A, from_B, to_A, to_B}); - state_variable_access.substitute("from_ion", {from_A, from_B}); + substitutePermissions("val", {"density", "temperature", "velocity"}); + substitutePermissions("e_val", {"density", "temperature"}); + substitutePermissions("w_val", + {"density_source", "momentum_source", "energy_source"}); + substitutePermissions("sp", {from_A, from_B, to_A, to_B}); + substitutePermissions("from_ion", {from_A, from_B}); } /// Perform charge exchange /// diff --git a/include/amjuel_reaction.hxx b/include/amjuel_reaction.hxx index 907e4526d..a781e993a 100644 --- a/include/amjuel_reaction.hxx +++ b/include/amjuel_reaction.hxx @@ -43,21 +43,18 @@ struct AmjuelReaction : public Reaction { this->includes_sigma_v_e = amjuel_data.includes_sigma_v_e; // Most of the access information we need is inherited from the parent Reaction class. // The electron velocity will be read if it is set - state_variable_access.setAccess( - "species:e:velocity", - {Regions::All, Regions::Nowhere, Regions::Nowhere, Regions::Nowhere}); + setAccess("species:e:velocity", + {Regions::All, Regions::Nowhere, Regions::Nowhere, Regions::Nowhere}); // The energy source is set for electrons - state_variable_access.setAccess( - "species:e:energy_source", - {Regions::Nowhere, Regions::Nowhere, Regions::All, Regions::Nowhere}); + setAccess("species:e:energy_source", + {Regions::Nowhere, Regions::Nowhere, Regions::All, Regions::Nowhere}); std::string heavy_reactant = this->parser->get_species(species_filter::reactants, species_filter::heavy)[0], heavy_product = this->parser->get_species(species_filter::products, species_filter::heavy)[0], neutral = this->parser->get_species(species_filter::neutral)[0]; - state_variable_access.setAccess( - readWrite(fmt::format("species:{}:collision_frequencies:{}_{}_{}", neutral, - heavy_reactant, heavy_product, short_reaction_type))); + setAccess(readWrite(fmt::format("species:{}:collision_frequencies:{}_{}_{}", neutral, + heavy_reactant, heavy_product, short_reaction_type))); } protected: diff --git a/include/braginskii_thermal_force.hxx b/include/braginskii_thermal_force.hxx index a0c696607..315943131 100644 --- a/include/braginskii_thermal_force.hxx +++ b/include/braginskii_thermal_force.hxx @@ -45,27 +45,25 @@ struct BraginskiiThermalForce : public Component { .withDefault(false); if (electron_ion or ion_ion) { - state_variable_access.setAccess(readIfSet("species:{all_species}:charge")); + setAccess(readIfSet("species:{all_species}:charge")); } if (electron_ion) { // FIXME: These are only accessed if electrons present - state_variable_access.setAccess(readOnly("species:e:temperature")); - state_variable_access.setAccess( - readOnly("species:{ions}:density", Regions::Interior)); - state_variable_access.setAccess(readWrite("species:{ions}:momentum_source")); - state_variable_access.setAccess(readWrite("species:e:momentum_source")); + setAccess(readOnly("species:e:temperature")); + setAccess(readOnly("species:{ions}:density", Regions::Interior)); + setAccess(readWrite("species:{ions}:momentum_source")); + setAccess(readWrite("species:e:momentum_source")); } else if (ion_ion) { } if (ion_ion) { - state_variable_access.setAccess(readOnly("species:{ions}:AA")); + setAccess(readOnly("species:{ions}:AA")); // FIXME: This is only accessed for light ions - state_variable_access.setAccess(readOnly("species:{ions}:temperature")); + setAccess(readOnly("species:{ions}:temperature")); if (!electron_ion) { // FIXME: This is only accessed for heavy ions - state_variable_access.setAccess( - readOnly("species:{ions}:density", Regions::Interior)); + setAccess(readOnly("species:{ions}:density", Regions::Interior)); // FIXME: This is only set for heavy and light ions, not intermediate - state_variable_access.setAccess(readWrite("species:{ions}:momentum_source")); + setAccess(readWrite("species:{ions}:momentum_source")); } } } diff --git a/include/component.hxx b/include/component.hxx index 806cd8932..baba87f4d 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -126,10 +126,25 @@ struct Component { void declareAllSpecies(const SpeciesInformation & info); protected: + /// Set the level of access needed by this component for a particular variable. + void setAccess(const std::string& variable, const Permissions::AccessRights& rights) { + state_variable_access.setAccess(variable, rights); + } + void setAccess(const Permissions::VarRights& info) { + setAccess(info.name, info.rights); + } + + /// Replace a placeholder in the name of variables stored in the access control + /// information for this component. + void substitutePermissions(const std::string& label, + const std::vector& substitution) { + state_variable_access.substitute(label, substitution); + } + +private: /// Information on which state variables the transform method will read and write. Permissions state_variable_access; -private: /// Modify the given simulation state. All components must /// implement this function. It will only allow the reading /// from/writing to state variables with the appropriate permissiosn diff --git a/include/detachment_controller.hxx b/include/detachment_controller.hxx index fdea2e584..58ee5ad8d 100644 --- a/include/detachment_controller.hxx +++ b/include/detachment_controller.hxx @@ -157,8 +157,8 @@ struct DetachmentController : public Component { .doc("Print debugging information to the screen (0 for none, 1 for basic, 2 for extensive).") .withDefault(0); - state_variable_access.substitute("neutral", {neutral_species}); - state_variable_access.substitute( + substitutePermissions("neutral", {neutral_species}); + substitutePermissions( "sp", std::vector(species_list.begin(), species_list.end())); std::string output; if (control_mode == control_power) { @@ -168,7 +168,7 @@ struct DetachmentController : public Component { } else { ASSERT2(false); } - state_variable_access.substitute("output", {output}); + substitutePermissions("output", {output}); }; void outputVars(Options& state) override { diff --git a/include/fixed_density.hxx b/include/fixed_density.hxx index bb18a060c..41a937a8d 100644 --- a/include/fixed_density.hxx +++ b/include/fixed_density.hxx @@ -27,8 +27,8 @@ struct FixedDensity : public Component { // Get the density and normalise N = options["density"].as() / Nnorm; - state_variable_access.substitute("name", {name}); - state_variable_access.substitute("vars", {"AA", "charge", "density"}); + substitutePermissions("name", {name}); + substitutePermissions("vars", {"AA", "charge", "density"}); } void outputVars(Options& state) override { diff --git a/include/fixed_temperature.hxx b/include/fixed_temperature.hxx index 69bb46cd3..ae4a513bc 100644 --- a/include/fixed_temperature.hxx +++ b/include/fixed_temperature.hxx @@ -31,7 +31,7 @@ struct FixedTemperature : public Component { .doc("Save additional output diagnostics") .withDefault(false); - state_variable_access.substitute("name", {name}); + substitutePermissions("name", {name}); } void outputVars(Options& state) override { diff --git a/include/fixed_velocity.hxx b/include/fixed_velocity.hxx index 970384cb7..1113b196f 100644 --- a/include/fixed_velocity.hxx +++ b/include/fixed_velocity.hxx @@ -31,9 +31,9 @@ struct FixedVelocity : public Component { // Option overrides mesh value // so use mesh value (if any) as default value. V = options["velocity"].withDefault(V) / Cs0; - state_variable_access.substitute("name", {name}); + substitutePermissions("name", {name}); // FIXME: Momentum is only written if density is set - state_variable_access.substitute("output", {"velocity", "momentum"}); + substitutePermissions("output", {"velocity", "momentum"}); } void outputVars(Options& state) override { diff --git a/include/hydrogen_charge_exchange.hxx b/include/hydrogen_charge_exchange.hxx index 6e9a01f75..44057c128 100644 --- a/include/hydrogen_charge_exchange.hxx +++ b/include/hydrogen_charge_exchange.hxx @@ -148,14 +148,14 @@ struct HydrogenIsotopeChargeExchange : public HydrogenChargeExchange { if constexpr (Isotope1 != Isotope2) { writevals.push_back("density_source"); } - state_variable_access.substitute("reactant", {{Isotope1}, {Isotope2, '+'}}); - state_variable_access.substitute("react_vals", {"density", "temperature"}); - state_variable_access.substitute("read_vals", {"AA", "velocity"}); - state_variable_access.substitute( - "sp", {{Isotope1}, {Isotope2, '+'}, {Isotope1, '+'}, {Isotope2}}); - state_variable_access.substitute("writevals", writevals); - state_variable_access.substitute("atom", {{Isotope1}}); - state_variable_access.substitute("ion", {{Isotope2, '+'}}); + substitutePermissions("reactant", {{Isotope1}, {Isotope2, '+'}}); + substitutePermissions("react_vals", {"density", "temperature"}); + substitutePermissions("read_vals", {"AA", "velocity"}); + substitutePermissions("sp", + {{Isotope1}, {Isotope2, '+'}, {Isotope1, '+'}, {Isotope2}}); + substitutePermissions("writevals", writevals); + substitutePermissions("atom", {{Isotope1}}); + substitutePermissions("ion", {{Isotope2, '+'}}); } void outputVars(Options& state) override { diff --git a/include/neutral_parallel_diffusion.hxx b/include/neutral_parallel_diffusion.hxx index 19e265855..9b93f85eb 100644 --- a/include/neutral_parallel_diffusion.hxx +++ b/include/neutral_parallel_diffusion.hxx @@ -54,13 +54,12 @@ struct NeutralParallelDiffusion : public Component { .withDefault(true); // FIXME: strictly speaking, momentum is not optional if velocity has been set - state_variable_access.substitute("optional_inputs", - {"pressure", "velocity", "momentum"}); - state_variable_access.substitute( - "inputs", {"AA", "collision_frequencies", "density", "temperature"}); + substitutePermissions("optional_inputs", {"pressure", "velocity", "momentum"}); + substitutePermissions("inputs", + {"AA", "collision_frequencies", "density", "temperature"}); // FIXME: momentum_source is only set if velocity was set. - state_variable_access.substitute( - "outputs", {"density_source", "energy_source", "momentum_source"}); + substitutePermissions("outputs", + {"density_source", "energy_source", "momentum_source"}); } /// Save variables to the output diff --git a/include/noflow_boundary.hxx b/include/noflow_boundary.hxx index b2fee4a3c..5d19e5749 100644 --- a/include/noflow_boundary.hxx +++ b/include/noflow_boundary.hxx @@ -16,9 +16,9 @@ struct NoFlowBoundary : public Component { noflow_upper_y = options["noflow_upper_y"] .doc("No-flow boundary on upper y?") .withDefault(true); - state_variable_access.substitute("name", {name}); - state_variable_access.substitute( - "variables", {"density", "temperature", "pressure", "velocity", "momentum"}); + substitutePermissions("name", {name}); + substitutePermissions("variables", + {"density", "temperature", "pressure", "velocity", "momentum"}); } private: diff --git a/include/set_temperature.hxx b/include/set_temperature.hxx index d40e6e785..09d433fc8 100644 --- a/include/set_temperature.hxx +++ b/include/set_temperature.hxx @@ -41,8 +41,8 @@ struct SetTemperature : public Component { .doc("Save additional output diagnostics") .withDefault(false); - state_variable_access.substitute("name", {name}); - state_variable_access.substitute("from", {temperature_from}); + substitutePermissions("name", {name}); + substitutePermissions("from", {temperature_from}); } void outputVars(Options& state) override { diff --git a/include/simple_conduction.hxx b/include/simple_conduction.hxx index 700dd7e45..3c8b97b58 100644 --- a/include/simple_conduction.hxx +++ b/include/simple_conduction.hxx @@ -60,10 +60,9 @@ struct SimpleConduction : public Component { .withDefault(false); if (density <= 0.0) { - state_variable_access.setAccess( - readOnly("species:{name}:density", Regions::Interior)); + setAccess(readOnly("species:{name}:density", Regions::Interior)); } - state_variable_access.substitute("name", {name}); + substitutePermissions("name", {name}); } private: diff --git a/include/simple_pump.hxx b/include/simple_pump.hxx index 1a664c727..9a18b3b6a 100644 --- a/include/simple_pump.hxx +++ b/include/simple_pump.hxx @@ -34,7 +34,7 @@ struct SimplePump : public Component { .doc("Output additional diagnostics?") .withDefault(false); - state_variable_access.substitute("name", {name}); + substitutePermissions("name", {name}); }; void outputVars(Options& state) override { diff --git a/include/solkit_hydrogen_charge_exchange.hxx b/include/solkit_hydrogen_charge_exchange.hxx index 4e3d958b6..36eb95611 100644 --- a/include/solkit_hydrogen_charge_exchange.hxx +++ b/include/solkit_hydrogen_charge_exchange.hxx @@ -49,8 +49,8 @@ struct SOLKITHydrogenChargeExchangeIsotope : public SOLKITHydrogenChargeExchange SOLKITHydrogenChargeExchangeIsotope(std::string name, Options& alloptions, Solver* solver) : SOLKITHydrogenChargeExchange(name, alloptions, solver) { - state_variable_access.substitute("sp", {{Isotope}, {Isotope, '+'}}); - state_variable_access.substitute("readvals", {"AA", "density"}); + substitutePermissions("sp", {{Isotope}, {Isotope, '+'}}); + substitutePermissions("readvals", {"AA", "density"}); } private: diff --git a/include/solkit_neutral_parallel_diffusion.hxx b/include/solkit_neutral_parallel_diffusion.hxx index 262512197..20ae154d4 100644 --- a/include/solkit_neutral_parallel_diffusion.hxx +++ b/include/solkit_neutral_parallel_diffusion.hxx @@ -34,7 +34,7 @@ struct SOLKITNeutralParallelDiffusion : public Component { auto rho_s0 = get(alloptions["units"]["meters"]); area_norm = 1. / (Nnorm * rho_s0); - state_variable_access.substitute("inputs", {"charge", "density"}); + substitutePermissions("inputs", {"charge", "density"}); } private: diff --git a/include/sound_speed.hxx b/include/sound_speed.hxx index 83c0862ae..8cf281476 100644 --- a/include/sound_speed.hxx +++ b/include/sound_speed.hxx @@ -46,9 +46,9 @@ struct SoundSpeed : public Component { temperature_floor /= get(alloptions["units"]["eV"]); } - state_variable_access.substitute( - "sp", {electron_dynamics ? "{all_species}" : "{non_electrons}"}); - state_variable_access.substitute("opt_inputs", {"density", "temperature"}); + substitutePermissions("sp", + {electron_dynamics ? "{all_species}" : "{non_electrons}"}); + substitutePermissions("opt_inputs", {"density", "temperature"}); } private: diff --git a/include/temperature_feedback.hxx b/include/temperature_feedback.hxx index 476fd862a..f36459288 100644 --- a/include/temperature_feedback.hxx +++ b/include/temperature_feedback.hxx @@ -89,8 +89,8 @@ struct TemperatureFeedback : public Component { std::vector species_stripped; std::transform(species_list.begin(), species_list.end(), species_stripped.begin(), [](const std::string& val) { return trim(val); }); - state_variable_access.substitute("name", {name}); - state_variable_access.substitute("sp", species_stripped); + substitutePermissions("name", {name}); + substitutePermissions("sp", species_stripped); } void outputVars(Options& state) override { diff --git a/include/upstream_density_feedback.hxx b/include/upstream_density_feedback.hxx index b9ed47ff8..0975e1219 100644 --- a/include/upstream_density_feedback.hxx +++ b/include/upstream_density_feedback.hxx @@ -69,7 +69,7 @@ struct UpstreamDensityFeedback : public Component { .doc("Output additional diagnostics?") .withDefault(false); - state_variable_access.substitute("name", {name}); + substitutePermissions("name", {name}); } void outputVars(Options& state) override { diff --git a/src/anomalous_diffusion.cxx b/src/anomalous_diffusion.cxx index 17481337a..07ad12f4e 100644 --- a/src/anomalous_diffusion.cxx +++ b/src/anomalous_diffusion.cxx @@ -54,8 +54,8 @@ AnomalousDiffusion::AnomalousDiffusion(std::string name, Options& alloptions, So .doc("Output additional diagnostics?") .withDefault(false); - state_variable_access.substitute("name", {name}); - state_variable_access.substitute("optional", {"temperature", "velocity"}); + substitutePermissions("name", {name}); + substitutePermissions("optional", {"temperature", "velocity"}); std::vector output_vars; if (include_D) { output_vars.push_back("density_source"); @@ -68,12 +68,12 @@ AnomalousDiffusion::AnomalousDiffusion(std::string name, Options& alloptions, So output_vars.push_back("energy_flow_ylow"); } if (include_D or include_nu) { - state_variable_access.setAccess(readOnly(fmt::format("species:{}:AA", name))); + setAccess(readOnly(fmt::format("species:{}:AA", name))); output_vars.push_back("momentum_source"); output_vars.push_back("momentum_flow_xlow"); output_vars.push_back("momentum_flow_ylow"); } - state_variable_access.substitute("output", output_vars); + substitutePermissions("output", output_vars); } void AnomalousDiffusion::transform_impl(GuardedOptions& state) { diff --git a/src/binormal_stpm.cxx b/src/binormal_stpm.cxx index 2b95aeca3..ce4c8ba95 100644 --- a/src/binormal_stpm.cxx +++ b/src/binormal_stpm.cxx @@ -51,9 +51,8 @@ BinormalSTPM::BinormalSTPM(std::string name, Options& alloptions, .doc("Output diagnostics?") .withDefault(false); - state_variable_access.substitute("input", {"density", "temperature", "momentum"}); - state_variable_access.substitute( - "output", {"energy_source", "momentum_source", "density_source"}); + substitutePermissions("input", {"density", "temperature", "momentum"}); + substitutePermissions("output", {"energy_source", "momentum_source", "density_source"}); } void BinormalSTPM::transform_impl(GuardedOptions& state) { diff --git a/src/braginskii_collisions.cxx b/src/braginskii_collisions.cxx index a49d53d3d..6c10f0b2f 100644 --- a/src/braginskii_collisions.cxx +++ b/src/braginskii_collisions.cxx @@ -62,62 +62,52 @@ BraginskiiCollisions::BraginskiiCollisions(const std::string& name, Options& all diagnose = options["diagnose"].doc("Output additional diagnostics?").withDefault(false); - state_variable_access.setAccess( - readOnly("species:{electrons}:temperature", Regions::Interior)); - state_variable_access.setAccess( - readOnly("species:{electrons}:density", Regions::Interior)); + setAccess(readOnly("species:{electrons}:temperature", Regions::Interior)); + setAccess(readOnly("species:{electrons}:density", Regions::Interior)); if (electron_electron) { - state_variable_access.setAccess(readWrite( + setAccess(readWrite( "species:{electrons}:collision_frequencies:{electrons}_{electrons2}_coll")); } if (electron_ion) { - state_variable_access.setAccess( - readOnly("species:{positive_ions}:temperature", Regions::Interior)); - state_variable_access.setAccess( - readWrite("species:{positive_ions}:collision_frequencies:{positive_ions}_{" - "electrons}_coll")); - state_variable_access.setAccess(readWrite( + setAccess(readOnly("species:{positive_ions}:temperature", Regions::Interior)); + setAccess(readWrite("species:{positive_ions}:collision_frequencies:{positive_ions}_{" + "electrons}_coll")); + setAccess(readWrite( "species:{electrons}:collision_frequencies:{electrons}_{positive_ions}_coll")); } else { - state_variable_access.setAccess( - readIfSet("species:{positive_ions}:temperature", Regions::Interior)); + setAccess(readIfSet("species:{positive_ions}:temperature", Regions::Interior)); } if (electron_neutral) { - state_variable_access.setAccess( - readOnly("species:{neutrals}:temperature", Regions::Interior)); - state_variable_access.setAccess(readWrite( + setAccess(readOnly("species:{neutrals}:temperature", Regions::Interior)); + setAccess(readWrite( "species:{neutrals}:collision_frequencies:{neutrals}_{electrons}_coll")); - state_variable_access.setAccess(readWrite( + setAccess(readWrite( "species:{electrons}:collision_frequencies:{electrons}_{neutrals}_coll")); } else { - state_variable_access.setAccess( - readIfSet("species:{neutrals}:temperature", Regions::Interior)); + setAccess(readIfSet("species:{neutrals}:temperature", Regions::Interior)); } if (ion_ion) { - state_variable_access.setAccess( - readWrite("species:{ions}:collision_frequencies:{ions}_{ions2}_coll")); + setAccess(readWrite("species:{ions}:collision_frequencies:{ions}_{ions2}_coll")); } if (ion_neutral) { - state_variable_access.setAccess( - readWrite("species:{ions}:collision_frequencies:{ions}_{neutrals}_coll")); - state_variable_access.setAccess( + setAccess(readWrite("species:{ions}:collision_frequencies:{ions}_{neutrals}_coll")); + setAccess( readWrite("species:{neutrals}:collision_frequencies:{neutrals}_{ions}_coll")); } if (neutral_neutral) { - state_variable_access.setAccess(readWrite( + setAccess(readWrite( "species:{neutrals}:collision_frequencies:{neutrals}_{neutrals2}_coll")); } if (electron_electron or electron_ion or electron_neutral) { - state_variable_access.setAccess(readWrite("species:{electrons}:collision_frequency")); + setAccess(readWrite("species:{electrons}:collision_frequency")); } if (ion_ion or ion_neutral) { - state_variable_access.setAccess(readWrite("species:{ions}:collision_frequency")); + setAccess(readWrite("species:{ions}:collision_frequency")); } else if (electron_ion) { - state_variable_access.setAccess( - readWrite("species:{positive_ions}:collision_frequency")); + setAccess(readWrite("species:{positive_ions}:collision_frequency")); } if (neutral_neutral or electron_neutral or ion_neutral) { - state_variable_access.setAccess(readWrite("species:{neutrals}:collision_frequency")); + setAccess(readWrite("species:{neutrals}:collision_frequency")); } } diff --git a/src/braginskii_conduction.cxx b/src/braginskii_conduction.cxx index 50884cdaa..24757d1da 100644 --- a/src/braginskii_conduction.cxx +++ b/src/braginskii_conduction.cxx @@ -91,28 +91,28 @@ BraginskiiConduction::BraginskiiConduction(const std::string&, Options& alloptio std::vector coll_types; - state_variable_access.substitute("input_vars", {"AA", "density", "temperature"}); - state_variable_access.substitute("output_vars", - {"energy_source", "kappa_par", "energy_flow_ylow"}); + substitutePermissions("input_vars", {"AA", "density", "temperature"}); + substitutePermissions("output_vars", + {"energy_source", "kappa_par", "energy_flow_ylow"}); std::vector species; for (const auto& [sp, mode] : all_conduction_collisions_mode) { species.push_back(sp); if (mode == "braginskii" and identifySpeciesType(sp) != SpeciesType::neutral) { - state_variable_access.setAccess(readIfSet( + setAccess(readIfSet( fmt::format("species:{}:collision_frequencies:{}_{}_coll", sp, sp, sp))); } else if (mode == "multispecies") { - state_variable_access.setAccess(readIfSet(fmt::format( - "species:{}:collision_frequencies:{}_{}_coll", sp, sp, "{all_species}"))); - state_variable_access.setAccess(readIfSet(fmt::format( - "species:{}:collision_frequencies:{}_{}_cx", sp, sp, "{all_species}"))); + setAccess(readIfSet(fmt::format("species:{}:collision_frequencies:{}_{}_coll", sp, + sp, "{all_species}"))); + setAccess(readIfSet(fmt::format("species:{}:collision_frequencies:{}_{}_cx", sp, sp, + "{all_species}"))); } else if (mode == "AFN" and identifySpeciesType(sp) == SpeciesType::neutral) { - state_variable_access.setAccess(readIfSet(fmt::format( - "species:{}:collision_frequencies:{}_{}_cx", sp, sp, "{positive_ions}"))); - state_variable_access.setAccess(readIfSet(fmt::format( - "species:{}:collision_frequencies:{}_{}_iz", sp, sp, "{positive_ions}"))); + setAccess(readIfSet(fmt::format("species:{}:collision_frequencies:{}_{}_cx", sp, sp, + "{positive_ions}"))); + setAccess(readIfSet(fmt::format("species:{}:collision_frequencies:{}_{}_iz", sp, sp, + "{positive_ions}"))); } } - state_variable_access.substitute("sp", species); + substitutePermissions("sp", species); } void BraginskiiConduction::transform_impl(GuardedOptions& state) { diff --git a/src/braginskii_friction.cxx b/src/braginskii_friction.cxx index 2c90083cf..100196d27 100644 --- a/src/braginskii_friction.cxx +++ b/src/braginskii_friction.cxx @@ -31,7 +31,7 @@ BraginskiiFriction::BraginskiiFriction(const std::string& name, Options& allopti options["diagnose"].doc("Output additional diagnostics?").withDefault(false); if (frictional_heating) { - state_variable_access.setAccess(readWrite("species:{all_species}:energy_source")); + setAccess(readWrite("species:{all_species}:energy_source")); } } diff --git a/src/braginskii_heat_exchange.cxx b/src/braginskii_heat_exchange.cxx index 09ae45fc7..ad655c572 100644 --- a/src/braginskii_heat_exchange.cxx +++ b/src/braginskii_heat_exchange.cxx @@ -23,13 +23,13 @@ BraginskiiHeatExchange::BraginskiiHeatExchange(const std::string& name, diagnose = alloptions[name]["diagnose"] .doc("Output additional diagnostics?") .withDefault(false); - state_variable_access.substitute("input_vars", {"AA", "density"}); + substitutePermissions("input_vars", {"AA", "density"}); // FIXME: We don't access the self-collision rate - state_variable_access.substitute( - "optional_vars", - {"charge", "collision_frequencies:{all_species}_{all_species2}_coll", - "temperature"}); - state_variable_access.substitute("output_vars", {"momentum_source", "energy_source"}); + substitutePermissions("optional_vars", + {"charge", + "collision_frequencies:{all_species}_{all_species2}_coll", + "temperature"}); + substitutePermissions("output_vars", {"momentum_source", "energy_source"}); } void BraginskiiHeatExchange::transform_impl(GuardedOptions& state) { diff --git a/src/braginskii_ion_viscosity.cxx b/src/braginskii_ion_viscosity.cxx index 1485c6d76..1058fc971 100644 --- a/src/braginskii_ion_viscosity.cxx +++ b/src/braginskii_ion_viscosity.cxx @@ -125,9 +125,9 @@ BraginskiiIonViscosity::BraginskiiIonViscosity(const std::string& name, coll_types.push_back("{non_electrons}_{all_species}_cx"); } if (perpendicular) { - state_variable_access.setAccess(readOnly("fields:phi")); + setAccess(readOnly("fields:phi")); } - state_variable_access.substitute("coll_type", coll_types); + substitutePermissions("coll_type", coll_types); } void BraginskiiIonViscosity::transform_impl(GuardedOptions& state) { diff --git a/src/classical_diffusion.cxx b/src/classical_diffusion.cxx index 6e58d2faa..a4cdb7f45 100644 --- a/src/classical_diffusion.cxx +++ b/src/classical_diffusion.cxx @@ -14,22 +14,20 @@ ClassicalDiffusion::ClassicalDiffusion(std::string name, Options& alloptions, So diagnose = options["diagnose"].doc("Output additional diagnostics?").withDefault(false); custom_D = options["custom_D"].doc("Custom diffusion coefficient override. -1: Off, calculate D normally").withDefault(-1); - state_variable_access.substitute( - "optional", {"charge", "pressure", "density", "velocity", "temperature"}); + substitutePermissions("optional", + {"charge", "pressure", "density", "velocity", "temperature"}); std::vector e_vals = {"AA", "density"}; if (custom_D <= 0.) { e_vals.push_back("collision_frequency"); } - state_variable_access.substitute("e_vals", e_vals); + substitutePermissions("e_vals", e_vals); // FIXME: momentum and energy sources are only set if velocity and // temperature are defined (respectively). Collision frequency is // only used if temperature is set. Nothing happens if the charge or // density are unset. - state_variable_access.substitute( - "output", {"density_source", "momentum_source", "energy_source"}); + substitutePermissions("output", {"density_source", "momentum_source", "energy_source"}); if (custom_D < 0.) - state_variable_access.setAccess( - readOnly("species:{all_species}:collision_frequency")); + setAccess(readOnly("species:{all_species}:collision_frequency")); } void ClassicalDiffusion::transform_impl(GuardedOptions& state) { diff --git a/src/diamagnetic_drift.cxx b/src/diamagnetic_drift.cxx index 2fc080f01..79f3c192c 100644 --- a/src/diamagnetic_drift.cxx +++ b/src/diamagnetic_drift.cxx @@ -61,13 +61,12 @@ DiamagneticDrift::DiamagneticDrift(std::string name, Options& alloptions, // FIXME: density, pressure, and momentum will not be read even if // they are defined if charge and temperature were not defined for // that species. - state_variable_access.substitute( - "input", {"charge", "temperature", "density", "pressure", "momentum"}); + substitutePermissions("input", + {"charge", "temperature", "density", "pressure", "momentum"}); // FIXME: These will actually only be written if density, pressure, // and momentum are set, respectively. They also require charge and // temperature to have been set. - state_variable_access.substitute( - "output", {"density_source", "energy_source", "momentum_source"}); + substitutePermissions("output", {"density_source", "energy_source", "momentum_source"}); } void DiamagneticDrift::transform_impl(GuardedOptions& state) { diff --git a/src/electromagnetic.cxx b/src/electromagnetic.cxx index 31aa1b39b..ad8602e34 100644 --- a/src/electromagnetic.cxx +++ b/src/electromagnetic.cxx @@ -87,7 +87,7 @@ Electromagnetic::Electromagnetic(std::string name, Options& alloptions, Solver* .withDefault(false); if (magnetic_flutter) - state_variable_access.setAccess(readWrite("fields:Apar_flutter")); + setAccess(readWrite("fields:Apar_flutter")); } void Electromagnetic::restartVars(Options& state) { diff --git a/src/evolve_density.cxx b/src/evolve_density.cxx index 4039799fb..979a85e3e 100644 --- a/src/evolve_density.cxx +++ b/src/evolve_density.cxx @@ -138,8 +138,8 @@ EvolveDensity::EvolveDensity(std::string name, Options& alloptions, Solver* solv if (low_n_diffuse) { outputs.push_back("low_n_coeff"); } - state_variable_access.substitute("name", {name}); - state_variable_access.substitute("outputs", outputs); + substitutePermissions("name", {name}); + substitutePermissions("outputs", outputs); } void EvolveDensity::transform_impl(GuardedOptions& state) { diff --git a/src/evolve_energy.cxx b/src/evolve_energy.cxx index 810524c42..7f9edde77 100644 --- a/src/evolve_energy.cxx +++ b/src/evolve_energy.cxx @@ -114,9 +114,9 @@ EvolveEnergy::EvolveEnergy(std::string name, Options& alloptions, Solver* solver .doc("Include parallel heat conduction?") .withDefault(true); - state_variable_access.substitute("name", {name}); - state_variable_access.substitute("inputs", {"AA", "density", "velocity"}); - state_variable_access.substitute("outputs", {"pressure", "temperature"}); + substitutePermissions("name", {name}); + substitutePermissions("inputs", {"AA", "density", "velocity"}); + substitutePermissions("outputs", {"pressure", "temperature"}); } void EvolveEnergy::transform_impl(GuardedOptions& state) { diff --git a/src/evolve_momentum.cxx b/src/evolve_momentum.cxx index 045eab67b..76c1a3783 100644 --- a/src/evolve_momentum.cxx +++ b/src/evolve_momentum.cxx @@ -63,8 +63,8 @@ EvolveMomentum::EvolveMomentum(std::string name, Options& alloptions, Solver* so momentum_source = 0.0; NV_err = 0.0; - state_variable_access.substitute("name", {name}); - state_variable_access.substitute("outputs", {"velocity", "momentum"}); + substitutePermissions("name", {name}); + substitutePermissions("outputs", {"velocity", "momentum"}); } void EvolveMomentum::transform_impl(GuardedOptions& state) { diff --git a/src/evolve_pressure.cxx b/src/evolve_pressure.cxx index 6d1a123f6..f4ce82b98 100644 --- a/src/evolve_pressure.cxx +++ b/src/evolve_pressure.cxx @@ -165,9 +165,9 @@ EvolvePressure::EvolvePressure(std::string name, Options& alloptions, Solver* so .doc("Include parallel heat conduction?") .withDefault(true); - state_variable_access.substitute("name", {name}); - state_variable_access.substitute("inputs", {"density"}); - state_variable_access.substitute("outputs", {"pressure", "temperature"}); + substitutePermissions("name", {name}); + substitutePermissions("inputs", {"density"}); + substitutePermissions("outputs", {"pressure", "temperature"}); } void EvolvePressure::transform_impl(GuardedOptions& state) { diff --git a/src/fixed_fraction_ions.cxx b/src/fixed_fraction_ions.cxx index 09fa3d4f9..69113d548 100644 --- a/src/fixed_fraction_ions.cxx +++ b/src/fixed_fraction_ions.cxx @@ -30,7 +30,7 @@ FixedFractionIons::FixedFractionIons(std::string name, Options& alloptions, throw BoutException("No ion species specified. Got fractions = '%s'", fractions_str.c_str()); } - state_variable_access.substitute("sp", specified_species); + substitutePermissions("sp", specified_species); } void FixedFractionIons::transform_impl(GuardedOptions& state) { diff --git a/src/neutral_boundary.cxx b/src/neutral_boundary.cxx index 89558c395..c4d68f68a 100644 --- a/src/neutral_boundary.cxx +++ b/src/neutral_boundary.cxx @@ -53,9 +53,9 @@ NeutralBoundary::NeutralBoundary(std::string name, Options& alloptions, .doc("Fraction of neutrals that are undergoing fast reflection at the pfr") .withDefault(0.8); - state_variable_access.substitute("name", {name}); - state_variable_access.substitute("outputs", {"density", "temperature", "pressure"}); - state_variable_access.substitute("conditional_outputs", {"velocity", "momentum"}); + substitutePermissions("name", {name}); + substitutePermissions("outputs", {"density", "temperature", "pressure"}); + substitutePermissions("conditional_outputs", {"velocity", "momentum"}); } void NeutralBoundary::transform_impl(GuardedOptions& state) { diff --git a/src/neutral_full_velocity.cxx b/src/neutral_full_velocity.cxx index 287318329..255328316 100644 --- a/src/neutral_full_velocity.cxx +++ b/src/neutral_full_velocity.cxx @@ -188,8 +188,8 @@ NeutralFullVelocity::NeutralFullVelocity(const std::string& name, Options& allop // Ensure that guard cells are filled and consistent between processors mesh->communicate(Urx, Ury, Uzx, Uzy); mesh->communicate(Txr, Txz, Tyr, Tyz); - state_variable_access.substitute("name", {name}); - state_variable_access.substitute( + substitutePermissions("name", {name}); + substitutePermissions( "outputs", {"AA", "density", "pressure", "temperature", "momentum", "velocity"}); } diff --git a/src/neutral_mixed.cxx b/src/neutral_mixed.cxx index fcc392f82..4b3cda8ea 100644 --- a/src/neutral_mixed.cxx +++ b/src/neutral_mixed.cxx @@ -170,8 +170,8 @@ NeutralMixed::NeutralMixed(const std::string& name, Options& alloptions, Solver* DnnPn.setBoundary(std::string("Dnn") + name); DnnNVn.setBoundary(std::string("Dnn") + name); - state_variable_access.substitute("name", {name}); - state_variable_access.substitute( + substitutePermissions("name", {name}); + substitutePermissions( "outputs", {"AA", "density", "pressure", "temperature", "momentum", "velocity"}); } diff --git a/src/polarisation_drift.cxx b/src/polarisation_drift.cxx index 5b9faf172..18e639392 100644 --- a/src/polarisation_drift.cxx +++ b/src/polarisation_drift.cxx @@ -75,12 +75,12 @@ PolarisationDrift::PolarisationDrift(std::string name, Options& alloptions, outputs.push_back("density_source"); outputs.push_back("momentum_source"); } - state_variable_access.substitute("inputs", {"AA", "density"}); - state_variable_access.substitute("optional_inputs", {"momentum, pressure"}); - state_variable_access.substitute("fields", fields); + substitutePermissions("inputs", {"AA", "density"}); + substitutePermissions("optional_inputs", {"momentum, pressure"}); + substitutePermissions("fields", fields); // FIXME: energy_source and momentum source are only set if pressure // and momentum were set, respectively - state_variable_access.substitute("outputs", outputs); + substitutePermissions("outputs", outputs); } void PolarisationDrift::transform_impl(GuardedOptions& state) { diff --git a/src/quasineutral.cxx b/src/quasineutral.cxx index 5ca591ad9..53f615730 100644 --- a/src/quasineutral.cxx +++ b/src/quasineutral.cxx @@ -16,8 +16,8 @@ Quasineutral::Quasineutral(std::string name, Options& alloptions, Solver* UNUSED AA = options["AA"].doc("Particle atomic mass. Proton = 1"); ASSERT0(charge != 0.0); - state_variable_access.substitute("name", {name}); - state_variable_access.substitute("outputs", {"AA", "charge", "density"}); + substitutePermissions("name", {name}); + substitutePermissions("outputs", {"AA", "charge", "density"}); } void Quasineutral::transform_impl(GuardedOptions& state) { diff --git a/src/reaction.cxx b/src/reaction.cxx index c1dc5a563..89c0f5f30 100644 --- a/src/reaction.cxx +++ b/src/reaction.cxx @@ -47,11 +47,10 @@ Reaction::Reaction(std::string name, Options& options) this->pfactors[sp] = 1; } - state_variable_access.substitute("sp", species); - state_variable_access.substitute("r_val", {"AA", "density", "velocity", "temperature"}); - state_variable_access.substitute("e_val", {"density", "temperature"}); - state_variable_access.substitute( - "w_val", {"momentum_source", "energy_source", "density_source"}); + substitutePermissions("sp", species); + substitutePermissions("r_val", {"AA", "density", "velocity", "temperature"}); + substitutePermissions("e_val", {"density", "temperature"}); + substitutePermissions("w_val", {"momentum_source", "energy_source", "density_source"}); // Initialise weight sums with dummy values. Real values are set on first call to // transform(). @@ -86,7 +85,7 @@ void Reaction::add_diagnostic(const std::string& sp_name, const std::string& dia diag_key, ReactionDiagnostic(diag_name, description, type, data_source, standard_name, transformer))); } - state_variable_access.setAccess(readWrite(diag_name)); + setAccess(readWrite(diag_name)); } /** diff --git a/src/recycling.cxx b/src/recycling.cxx index a0762394e..44bda53e2 100644 --- a/src/recycling.cxx +++ b/src/recycling.cxx @@ -157,20 +157,19 @@ Recycling::Recycling(std::string name, Options& alloptions, Solver*) } if (target_recycle) { - state_variable_access.setAccess(readIfSet("species:{from}:energy_flow_ylow")); + setAccess(readIfSet("species:{from}:energy_flow_ylow")); } if (sol_recycle or pfr_recycle) { - state_variable_access.setAccess(readIfSet("species:{from}:energy_flow_xlow")); - state_variable_access.setAccess(readIfSet("species:{from}:particle_flow_xlow")); + setAccess(readIfSet("species:{from}:energy_flow_xlow")); + setAccess(readIfSet("species:{from}:particle_flow_xlow")); } - state_variable_access.substitute( - "to", std::vector(to_species.begin(), to_species.end())); - state_variable_access.substitute( + substitutePermissions("to", + std::vector(to_species.begin(), to_species.end())); + substitutePermissions( "from", std::vector(from_species.begin(), from_species.end())); - state_variable_access.substitute("to_inputs", - {"AA", "density", "pressure", "temperature"}); - state_variable_access.substitute("from_inputs", {"density", "velocity", "temperature"}); - state_variable_access.substitute("outputs", {"density_source", "energy_source"}); + substitutePermissions("to_inputs", {"AA", "density", "pressure", "temperature"}); + substitutePermissions("from_inputs", {"density", "velocity", "temperature"}); + substitutePermissions("outputs", {"density_source", "energy_source"}); } void Recycling::transform_impl(GuardedOptions& state) { diff --git a/src/relax_potential.cxx b/src/relax_potential.cxx index 9e5c7e670..aee92696e 100644 --- a/src/relax_potential.cxx +++ b/src/relax_potential.cxx @@ -46,15 +46,14 @@ RelaxPotential::RelaxPotential(std::string name, Options& alloptions, Solver* so if (diamagnetic) { // FIXME: These will only be read if BOTH charge and pressure are set - state_variable_access.setAccess( - readIfSet("species:{charged}:pressure", Regions::Interior)); - state_variable_access.setAccess(readIfSet("species:{all_species}:charge")); + setAccess(readIfSet("species:{charged}:pressure", Regions::Interior)); + setAccess(readIfSet("species:{all_species}:charge")); // FIXME: The weay transform_impl is currently written, // energy_source is set for neutral species with an explicit // charge declared as 0 if diamagnetic_polarisation == true. I // suspect that's a mistake though. - state_variable_access.setAccess(readWrite("species:{all_species}:energy_source")); - state_variable_access.setAccess(readWrite("fields:DivJdia")); + setAccess(readWrite("species:{all_species}:energy_source")); + setAccess(readWrite("fields:DivJdia")); // Read curvature vector try { diff --git a/src/sheath_boundary.cxx b/src/sheath_boundary.cxx index e2ee970de..4571f82bb 100644 --- a/src/sheath_boundary.cxx +++ b/src/sheath_boundary.cxx @@ -110,22 +110,22 @@ SheathBoundary::SheathBoundary(std::string name, Options& alloptions, Solver*) .doc("Apply a floor to wall potential when calculating Ve?") .withDefault(true); - state_variable_access.substitute("e_whole_domain", {"AA", "charge", "adiabatic"}); - state_variable_access.substitute("e_boundary", {"density", "temperature"}); - state_variable_access.substitute("e_optional", {"pressure", "velocity"}); - state_variable_access.substitute("ion_whole_domain", {"charge", "adiabatic"}); - state_variable_access.substitute("ion_boundary", {"density", "temperature"}); + substitutePermissions("e_whole_domain", {"AA", "charge", "adiabatic"}); + substitutePermissions("e_boundary", {"density", "temperature"}); + substitutePermissions("e_optional", {"pressure", "velocity"}); + substitutePermissions("ion_whole_domain", {"charge", "adiabatic"}); + substitutePermissions("ion_boundary", {"density", "temperature"}); // FIXME: velocity and momentum will only be set on boundaries if already set on // interior - state_variable_access.substitute("ion_optional", {"pressure", "velocity", "momentum"}); + substitutePermissions("ion_optional", {"pressure", "velocity", "momentum"}); // FIXME: The two results of the ternary are actually the same; need // to change what writeBoundaryIfSet returns (and how we model // permissions, for that matter) - state_variable_access.setAccess( - always_set_phi ? Permissions::VarRights( - {"fields:phi", - {Regions::Interior, Regions::Nowhere, Regions::Nowhere, Regions::Boundaries}}) - : writeBoundaryIfSet("fields:phi")); + setAccess(always_set_phi + ? Permissions::VarRights({"fields:phi", + {Regions::Interior, Regions::Nowhere, + Regions::Nowhere, Regions::Boundaries}}) + : writeBoundaryIfSet("fields:phi")); } void SheathBoundary::transform_impl(GuardedOptions& state) { diff --git a/src/sheath_boundary_insulating.cxx b/src/sheath_boundary_insulating.cxx index a2c98b069..8eaa89d97 100644 --- a/src/sheath_boundary_insulating.cxx +++ b/src/sheath_boundary_insulating.cxx @@ -93,15 +93,15 @@ SheathBoundaryInsulating::SheathBoundaryInsulating(std::string name, Options& al .doc("Electron sheath heat transmission coefficient") .withDefault(3.5); - state_variable_access.substitute("e_whole_domain", {"AA", "charge", "adiabatic"}); - state_variable_access.substitute("e_boundary", {"density", "temperature"}); - state_variable_access.substitute("e_optional", {"velocity", "momentum"}); - state_variable_access.substitute("ion_whole_domain", {"charge", "adiabatic"}); - state_variable_access.substitute("ion_boundary", {"density", "temperature"}); + substitutePermissions("e_whole_domain", {"AA", "charge", "adiabatic"}); + substitutePermissions("e_boundary", {"density", "temperature"}); + substitutePermissions("e_optional", {"velocity", "momentum"}); + substitutePermissions("ion_whole_domain", {"charge", "adiabatic"}); + substitutePermissions("ion_boundary", {"density", "temperature"}); // FIXME: velocity and momentum will only be set on boundaries if already set on // interior - state_variable_access.substitute("ion_optional", {"velocity", "momentum"}); - state_variable_access.setAccess(writeBoundaryIfSet("fields:phi")); + substitutePermissions("ion_optional", {"velocity", "momentum"}); + setAccess(writeBoundaryIfSet("fields:phi")); } void SheathBoundaryInsulating::transform_impl(GuardedOptions& state) { diff --git a/src/sheath_boundary_simple.cxx b/src/sheath_boundary_simple.cxx index 9c6777695..174eab466 100644 --- a/src/sheath_boundary_simple.cxx +++ b/src/sheath_boundary_simple.cxx @@ -148,15 +148,15 @@ SheathBoundarySimple::SheathBoundarySimple(std::string name, Options& alloptions .doc("Save additional output diagnostics") .withDefault(false); - state_variable_access.substitute("e_whole_domain", {"AA", "charge", "adiabatic"}); - state_variable_access.substitute("e_boundary", {"density", "temperature"}); - state_variable_access.substitute("e_optional", {"velocity", "momentum"}); - state_variable_access.substitute("ion_whole_domain", {"charge", "adiabatic"}); - state_variable_access.substitute("ion_boundary", {"density", "temperature"}); + substitutePermissions("e_whole_domain", {"AA", "charge", "adiabatic"}); + substitutePermissions("e_boundary", {"density", "temperature"}); + substitutePermissions("e_optional", {"velocity", "momentum"}); + substitutePermissions("ion_whole_domain", {"charge", "adiabatic"}); + substitutePermissions("ion_boundary", {"density", "temperature"}); // FIXME: velocity and momentum will only be set on boundaries if already set on // interior - state_variable_access.substitute("ion_optional", {"velocity", "momentum"}); - state_variable_access.setAccess(writeBoundaryIfSet("fields:phi")); + substitutePermissions("ion_optional", {"velocity", "momentum"}); + setAccess(writeBoundaryIfSet("fields:phi")); } void SheathBoundarySimple::transform_impl(GuardedOptions& state) { diff --git a/src/sheath_closure.cxx b/src/sheath_closure.cxx index f9ce9628f..289edf67b 100644 --- a/src/sheath_closure.cxx +++ b/src/sheath_closure.cxx @@ -35,13 +35,13 @@ SheathClosure::SheathClosure(std::string name, Options& alloptions, Solver*) output.write("\tL_par = {:e} (normalised)\n", L_par); if (sinks) { - state_variable_access.setAccess(readOnly("species:e:temperature")); - state_variable_access.setAccess(readOnly("species:{non_electrons}:{inputs}")); - state_variable_access.setAccess(readWrite("species:{non_electrons}:{outputs}")); - state_variable_access.substitute("inputs", {"AA", "density", "temperature"}); - state_variable_access.substitute("output", {"density_source", "energy_source"}); + setAccess(readOnly("species:e:temperature")); + setAccess(readOnly("species:{non_electrons}:{inputs}")); + setAccess(readWrite("species:{non_electrons}:{outputs}")); + substitutePermissions("inputs", {"AA", "density", "temperature"}); + substitutePermissions("output", {"density_source", "energy_source"}); } else { - state_variable_access.setAccess(readIfSet("species:e:temperature")); + setAccess(readIfSet("species:e:temperature")); } } diff --git a/src/transform.cxx b/src/transform.cxx index 03dc0cb03..7f439f93d 100644 --- a/src/transform.cxx +++ b/src/transform.cxx @@ -29,8 +29,8 @@ Transform::Transform(std::string name, Options& alloptions, Solver* UNUSED(solve outputs.push_back(right); } - state_variable_access.substitute("inputs", inputs); - state_variable_access.substitute("outputs", outputs); + substitutePermissions("inputs", inputs); + substitutePermissions("outputs", outputs); } void Transform::transform_impl(GuardedOptions& state) { diff --git a/src/vorticity.cxx b/src/vorticity.cxx index 767015319..4b0a063e1 100644 --- a/src/vorticity.cxx +++ b/src/vorticity.cxx @@ -210,33 +210,30 @@ Vorticity::Vorticity(std::string name, Options& alloptions, Solver* solver) if (diamagnetic or diamagnetic_polarisation) { // FIXME: These will only be read if BOTH charge and pressure (and possibly AA) are // set - state_variable_access.setAccess( - readIfSet("species:{charged}:pressure", Regions::Interior)); - state_variable_access.setAccess(readIfSet("species:{all_species}:charge")); + setAccess(readIfSet("species:{charged}:pressure", Regions::Interior)); + setAccess(readIfSet("species:{all_species}:charge")); } if (diamagnetic) { - state_variable_access.setAccess(readWrite("species:{charged}:energy_source")); - state_variable_access.setAccess(readWrite("fields:DivJdia")); + setAccess(readWrite("species:{charged}:energy_source")); + setAccess(readWrite("fields:DivJdia")); } if (diamagnetic_polarisation or collisional_friction) { // FIXME: Only read if pressure also set - state_variable_access.setAccess(readIfSet("species:{charged}:AA")); + setAccess(readIfSet("species:{charged}:AA")); } if (phi_boundary_relax) { - state_variable_access.setAccess(readOnly("time")); + setAccess(readOnly("time")); } else { if (sheath_boundary) { - state_variable_access.setAccess(readOnly("species:e:AA")); + setAccess(readOnly("species:e:AA")); } - state_variable_access.setAccess( - readIfSet("species:e:temperature", Regions::Interior)); + setAccess(readIfSet("species:e:temperature", Regions::Interior)); } if (collisional_friction) { - state_variable_access.setAccess(readIfSet("species:{all_species}:charge")); - state_variable_access.setAccess(readOnly("species:{positive_ions}:density")); - state_variable_access.setAccess( - readIfSet("species:{positive_ions}:collision_frequency")); - state_variable_access.setAccess(readWrite("fields:DivJcol")); + setAccess(readIfSet("species:{all_species}:charge")); + setAccess(readOnly("species:{positive_ions}:density")); + setAccess(readIfSet("species:{positive_ions}:collision_frequency")); + setAccess(readWrite("fields:DivJcol")); } } diff --git a/src/zero_current.cxx b/src/zero_current.cxx index 8883ccf54..4c20ba1ea 100644 --- a/src/zero_current.cxx +++ b/src/zero_current.cxx @@ -15,7 +15,7 @@ ZeroCurrent::ZeroCurrent(std::string name, Options& alloptions, Solver*) ASSERT0(charge != 0.0); - state_variable_access.substitute("inputs", {"density", "velocity"}); + substitutePermissions("inputs", {"density", "velocity"}); } void ZeroCurrent::transform_impl(GuardedOptions& state) { From ced1eaa96288d3fa8987a088a0deecb78c24b1f7 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Mon, 8 Dec 2025 21:11:45 +0000 Subject: [PATCH 29/63] Various minor fixes requested by @ZedThree --- include/guarded_options.hxx | 15 +++------------ include/permissions.hxx | 20 ++++++++++---------- src/component.cxx | 3 +-- tests/unit/test_guarded_options.cxx | 4 ++-- 4 files changed, 16 insertions(+), 26 deletions(-) diff --git a/include/guarded_options.hxx b/include/guarded_options.hxx index 622d66a62..2cedc5506 100644 --- a/include/guarded_options.hxx +++ b/include/guarded_options.hxx @@ -11,7 +11,7 @@ /// from and writing to the underlying data. class GuardedOptions { public: - GuardedOptions() = default; + GuardedOptions() = delete; /// Create a guarded options object which applies the specified /// permissions to the underlying options object. Note that the @@ -22,23 +22,14 @@ public: /// Get a subsection or value. The result will also be wrapped in a /// GuardedOptions object, with the same permissions as this one. - GuardedOptions operator[](const std::string& name) { + GuardedOptions operator[](const std::string& name) const { if (options == nullptr) throw BoutException( "Trying to access GuardedOptions when underlying options are nullptr."); return GuardedOptions(&(*options)[name], permissions, unread_variables, unwritten_variables); } - GuardedOptions operator[](const char* name) { return (*this)[std::string(name)]; } - - const GuardedOptions operator[](const std::string& name) const { - if (options == nullptr) - throw BoutException( - "Trying to access GuardedOptions when underlying options are nullptr."); - return GuardedOptions(&(*options)[name], permissions, unread_variables, - unwritten_variables); - } - const GuardedOptions operator[](const char* name) const { return (*this)[std::string(name)]; } + GuardedOptions operator[](const char* name) const { return (*this)[std::string(name)]; } std::map getChildren(); bool isSection(const std::string& name) const { return options->isSection(name); } diff --git a/include/permissions.hxx b/include/permissions.hxx index 4b5863a84..357d9de17 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -56,14 +56,14 @@ public: /// particular level of permission. Some examples can be seen below: /// /// AccessRights only_read_if_set = { Regions::All, Regions::Nowhere, - /// Regions::Nowhere, Regions::Nowhere }, - /// read_only = { Regions::Nowhere, Regions::All, Regions::Nowhere, - /// Regions::Nowhere }, - /// write_boundaries = { Regions::Nowhere, Regions::Nowhere, - /// Regions::Boundaries, Regions::Nowhere }, - /// read_and_write_everywhere = { Regions::Nowhere, - /// Regions::AllReginos, Regions::All, Regions::Nowhere }, - /// final_write_boundaries_read_interior = { Regions::Nowhere, + /// Regions::Nowhere, Regions::Nowhere }; + /// AccessRights read_only = { Regions::Nowhere, Regions::All, Regions::Nowhere, + /// Regions::Nowhere }; + /// AccessRights write_boundaries = { Regions::Nowhere, Regions::Nowhere, + /// Regions::Boundaries, Regions::Nowhere }; + /// AccessRights read_and_write_everywhere = { Regions::Nowhere, + /// Regions::AllReginos, Regions::All, Regions::Nowhere }; + /// AccessRights final_write_boundaries_read_interior = { Regions::Nowhere, /// Regions::Interior, Regions::Nowhere, Regions::Boundaries }; /// using AccessRights = std::array(PermissionTypes::END)>; @@ -86,7 +86,7 @@ public: /// Permissions example({ /// // Permission to read charge only if it has been set /// {"species:he:charge", {Regions::All, Regions::Nowhere, - /// Regions::Nowhere,, Regions::Nowhere}}, + /// Regions::Nowhere, Regions::Nowhere}}, /// // Read permission for atomic mass /// {"species:he:AA", {Regions::Nowhere, Regions::All, /// Regions::Nowhere, Regions::Nowhere}}, @@ -191,7 +191,7 @@ public: /// is the highest permission. /// /// Permissions example({"test", {Regions::Nowhere, Regions::All, - /// Regions::All, Permissions:Nowhere}}); + /// Regions::All, Permissions:Nowhere}}); /// // Print variables which can be read /// for (const auto [varname, region] : /// example.getVariablesWithPermission(PermissionTypes::Read), false)) diff --git a/src/component.cxx b/src/component.cxx index ddbc8779c..e5294e73a 100644 --- a/src/component.cxx +++ b/src/component.cxx @@ -84,8 +84,7 @@ bool isSetFinalNoBoundary(const GuardedOptions & option, const std::string& loca bool set = option.isSet(); #if CHECKLEVEL >= 1 PermissionTypes perm = option.getHighestPermission(Regions::Interior); - if (static_cast(perm) >= static_cast(PermissionTypes::Read) - or (perm == PermissionTypes::ReadIfSet and set)) { + if (perm >= PermissionTypes::Read or (perm == PermissionTypes::ReadIfSet and set)) { // Mark option as final inside the domain, but not in the boundary const_cast(option.get(Regions::Interior)).attributes["final-domain"] = location; diff --git a/tests/unit/test_guarded_options.cxx b/tests/unit/test_guarded_options.cxx index 23fa70667..e55503bad 100644 --- a/tests/unit/test_guarded_options.cxx +++ b/tests/unit/test_guarded_options.cxx @@ -208,9 +208,9 @@ TEST_F(GuardedOptionsTests, TestGetChildren) { EXPECT_EQ(guarded_children.size(), 2); EXPECT_EQ(guarded_children.count("he"), 1); EXPECT_EQ(guarded_children.count("d"), 1); - EXPECT_EQ(&(guarded_children["d"].get()), &(opts["species"]["d"])); + EXPECT_EQ(&(guarded_children.at("d").get()), &(opts["species"]["d"])); // We do not have access to the whole "he" section - EXPECT_THROW(guarded_children["he"].get(), BoutException); + EXPECT_THROW(guarded_children.at("he").get(), BoutException); } TEST_F(GuardedOptionsTests, TestIsThisSection) { From e021b15682b138b8efb4b9585725bab0960e0523 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Mon, 8 Dec 2025 21:28:14 +0000 Subject: [PATCH 30/63] Simplified checking for nullptr --- include/guarded_options.hxx | 7 +--- src/guarded_options.cxx | 64 +++++++++++++---------------- tests/unit/test_guarded_options.cxx | 10 +---- 3 files changed, 32 insertions(+), 49 deletions(-) diff --git a/include/guarded_options.hxx b/include/guarded_options.hxx index 2cedc5506..0a3e299b9 100644 --- a/include/guarded_options.hxx +++ b/include/guarded_options.hxx @@ -23,9 +23,6 @@ public: /// Get a subsection or value. The result will also be wrapped in a /// GuardedOptions object, with the same permissions as this one. GuardedOptions operator[](const std::string& name) const { - if (options == nullptr) - throw BoutException( - "Trying to access GuardedOptions when underlying options are nullptr."); return GuardedOptions(&(*options)[name], permissions, unread_variables, unwritten_variables); } @@ -77,8 +74,8 @@ public: } private: - Options* options{nullptr}; - Permissions* permissions{nullptr}; + Options* options; + Permissions* permissions; mutable std::shared_ptr> unread_variables, unwritten_variables; diff --git a/src/guarded_options.cxx b/src/guarded_options.cxx index 422b404e5..34ce7301a 100644 --- a/src/guarded_options.cxx +++ b/src/guarded_options.cxx @@ -20,23 +20,26 @@ GuardedOptions::GuardedOptions(Options* options, Permissions* permissions) : options(options), permissions(permissions), unread_variables(std::make_shared>()), unwritten_variables(std::make_shared>()) { + if (options == nullptr) { + throw BoutException("Can not construct GuardedOptions with null options pointer."); + } + if (permissions == nullptr) { + throw BoutException( + "Can not construct GuardedOptions with null permissions pointer."); + } #if CHECKLEVEL >= 1 - if (permissions != nullptr) { - *unread_variables = permissions->getVariablesWithPermission(PermissionTypes::Read); - // Only add variables with permission ReadIfSet to - // unread_variables if they are already present in the options - // object - if (options != nullptr) { - for (auto& [varname, region] : - permissions->getVariablesWithPermission(PermissionTypes::ReadIfSet)) { - if (isSetRecursive(*options, varname)) { - unread_variables->insert({varname, region}); - } - } + *unread_variables = permissions->getVariablesWithPermission(PermissionTypes::Read); + // Only add variables with permission ReadIfSet to + // unread_variables if they are already present in the options + // object + for (auto& [varname, region] : + permissions->getVariablesWithPermission(PermissionTypes::ReadIfSet)) { + if (isSetRecursive(*options, varname)) { + unread_variables->insert({varname, region}); } - *unwritten_variables = - permissions->getVariablesWithPermission(PermissionTypes::Write, false); } + *unwritten_variables = + permissions->getVariablesWithPermission(PermissionTypes::Write, false); #endif } @@ -61,22 +64,16 @@ void updateAccessRecords(std::map& records, const std::str } const Options& GuardedOptions::get(Regions region) const { - if (options == nullptr) - throw BoutException( - "Trying to access GuardedOptions when underlying options are nullptr."); #if CHECKLEVEL >= 1 std::string name = options->str(); - if (permissions != nullptr) { - auto [permission, varname] = permissions->getHighestPermission(name, region); - if (permission >= PermissionTypes::ReadIfSet) { - if (permission == PermissionTypes::ReadIfSet && !options->isSet()) { - throw BoutException( - "Only have permission to read {} if it is already set, which it is not.", - name); - } - updateAccessRecords(*unread_variables, varname, region); - return *options; + auto [permission, varname] = permissions->getHighestPermission(name, region); + if (permission >= PermissionTypes::ReadIfSet) { + if (permission == PermissionTypes::ReadIfSet && !options->isSet()) { + throw BoutException( + "Only have permission to read {} if it is already set, which it is not.", name); } + updateAccessRecords(*unread_variables, varname, region); + return *options; } throw BoutException("Do not have read permission for {}.", name); #else @@ -85,17 +82,12 @@ const Options& GuardedOptions::get(Regions region) const { } Options& GuardedOptions::getWritable(Regions region) { - if (options == nullptr) - throw BoutException( - "Trying to access GuardedOptions when underlying options are nullptr."); #if CHECKLEVEL >= 1 std::string name = options->str(); - if (permissions != nullptr) { - auto [access, varname] = permissions->canAccess(name, PermissionTypes::Write, region); - if (access) { - updateAccessRecords(*unwritten_variables, varname, region); - return *options; - } + auto [access, varname] = permissions->canAccess(name, PermissionTypes::Write, region); + if (access) { + updateAccessRecords(*unwritten_variables, varname, region); + return *options; } throw BoutException("Do not have write permission for {}.", options->str()); #else diff --git a/tests/unit/test_guarded_options.cxx b/tests/unit/test_guarded_options.cxx index e55503bad..00ba13a8a 100644 --- a/tests/unit/test_guarded_options.cxx +++ b/tests/unit/test_guarded_options.cxx @@ -190,17 +190,11 @@ TEST_F(GuardedOptionsTests, TestUnwrittenItems) { } TEST_F(GuardedOptionsTests, TestNullOptions) { - GuardedOptions null_opts(nullptr, &permissions); - EXPECT_THROW(null_opts["species:he:collision_frequency"], BoutException); - EXPECT_THROW(null_opts.get(), BoutException); - EXPECT_THROW(null_opts.getWritable(), BoutException); + EXPECT_THROW(GuardedOptions(nullptr, &permissions), BoutException); } TEST_F(GuardedOptionsTests, TestNullPermissions) { - GuardedOptions null_perms(&opts, nullptr); - GuardedOptions sub_opt = null_perms["species:he:pressure"]; - EXPECT_THROW(sub_opt.get(Regions::Interior), BoutException); - EXPECT_THROW(sub_opt.getWritable(Regions::Interior), BoutException); + EXPECT_THROW(GuardedOptions(&opts, nullptr), BoutException); } TEST_F(GuardedOptionsTests, TestGetChildren) { From 35f694ef8513f4fd40f9f801966b97c0db2c2aac Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Tue, 9 Dec 2025 15:19:12 +0000 Subject: [PATCH 31/63] Rename Component::setAccess --- include/amjuel_reaction.hxx | 13 +++++---- include/braginskii_thermal_force.hxx | 18 ++++++------ include/component.hxx | 7 +++-- include/simple_conduction.hxx | 2 +- src/anomalous_diffusion.cxx | 2 +- src/braginskii_collisions.cxx | 42 +++++++++++++++------------- src/braginskii_conduction.cxx | 18 ++++++------ src/braginskii_friction.cxx | 2 +- src/braginskii_ion_viscosity.cxx | 2 +- src/classical_diffusion.cxx | 2 +- src/electromagnetic.cxx | 2 +- src/reaction.cxx | 2 +- src/recycling.cxx | 6 ++-- src/relax_potential.cxx | 8 +++--- src/sheath_boundary.cxx | 10 +++---- src/sheath_boundary_insulating.cxx | 2 +- src/sheath_boundary_simple.cxx | 2 +- src/sheath_closure.cxx | 8 +++--- src/vorticity.cxx | 24 ++++++++-------- 19 files changed, 88 insertions(+), 84 deletions(-) diff --git a/include/amjuel_reaction.hxx b/include/amjuel_reaction.hxx index a781e993a..c10e115ed 100644 --- a/include/amjuel_reaction.hxx +++ b/include/amjuel_reaction.hxx @@ -43,18 +43,19 @@ struct AmjuelReaction : public Reaction { this->includes_sigma_v_e = amjuel_data.includes_sigma_v_e; // Most of the access information we need is inherited from the parent Reaction class. // The electron velocity will be read if it is set - setAccess("species:e:velocity", - {Regions::All, Regions::Nowhere, Regions::Nowhere, Regions::Nowhere}); + setPermissions("species:e:velocity", + {Regions::All, Regions::Nowhere, Regions::Nowhere, Regions::Nowhere}); // The energy source is set for electrons - setAccess("species:e:energy_source", - {Regions::Nowhere, Regions::Nowhere, Regions::All, Regions::Nowhere}); + setPermissions("species:e:energy_source", + {Regions::Nowhere, Regions::Nowhere, Regions::All, Regions::Nowhere}); std::string heavy_reactant = this->parser->get_species(species_filter::reactants, species_filter::heavy)[0], heavy_product = this->parser->get_species(species_filter::products, species_filter::heavy)[0], neutral = this->parser->get_species(species_filter::neutral)[0]; - setAccess(readWrite(fmt::format("species:{}:collision_frequencies:{}_{}_{}", neutral, - heavy_reactant, heavy_product, short_reaction_type))); + setPermissions( + readWrite(fmt::format("species:{}:collision_frequencies:{}_{}_{}", neutral, + heavy_reactant, heavy_product, short_reaction_type))); } protected: diff --git a/include/braginskii_thermal_force.hxx b/include/braginskii_thermal_force.hxx index 315943131..cf8b1068f 100644 --- a/include/braginskii_thermal_force.hxx +++ b/include/braginskii_thermal_force.hxx @@ -45,25 +45,25 @@ struct BraginskiiThermalForce : public Component { .withDefault(false); if (electron_ion or ion_ion) { - setAccess(readIfSet("species:{all_species}:charge")); + setPermissions(readIfSet("species:{all_species}:charge")); } if (electron_ion) { // FIXME: These are only accessed if electrons present - setAccess(readOnly("species:e:temperature")); - setAccess(readOnly("species:{ions}:density", Regions::Interior)); - setAccess(readWrite("species:{ions}:momentum_source")); - setAccess(readWrite("species:e:momentum_source")); + setPermissions(readOnly("species:e:temperature")); + setPermissions(readOnly("species:{ions}:density", Regions::Interior)); + setPermissions(readWrite("species:{ions}:momentum_source")); + setPermissions(readWrite("species:e:momentum_source")); } else if (ion_ion) { } if (ion_ion) { - setAccess(readOnly("species:{ions}:AA")); + setPermissions(readOnly("species:{ions}:AA")); // FIXME: This is only accessed for light ions - setAccess(readOnly("species:{ions}:temperature")); + setPermissions(readOnly("species:{ions}:temperature")); if (!electron_ion) { // FIXME: This is only accessed for heavy ions - setAccess(readOnly("species:{ions}:density", Regions::Interior)); + setPermissions(readOnly("species:{ions}:density", Regions::Interior)); // FIXME: This is only set for heavy and light ions, not intermediate - setAccess(readWrite("species:{ions}:momentum_source")); + setPermissions(readWrite("species:{ions}:momentum_source")); } } } diff --git a/include/component.hxx b/include/component.hxx index baba87f4d..a264c510f 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -127,11 +127,12 @@ struct Component { protected: /// Set the level of access needed by this component for a particular variable. - void setAccess(const std::string& variable, const Permissions::AccessRights& rights) { + void setPermissions(const std::string& variable, + const Permissions::AccessRights& rights) { state_variable_access.setAccess(variable, rights); } - void setAccess(const Permissions::VarRights& info) { - setAccess(info.name, info.rights); + void setPermissions(const Permissions::VarRights& info) { + setPermissions(info.name, info.rights); } /// Replace a placeholder in the name of variables stored in the access control diff --git a/include/simple_conduction.hxx b/include/simple_conduction.hxx index 3c8b97b58..4eade14bb 100644 --- a/include/simple_conduction.hxx +++ b/include/simple_conduction.hxx @@ -60,7 +60,7 @@ struct SimpleConduction : public Component { .withDefault(false); if (density <= 0.0) { - setAccess(readOnly("species:{name}:density", Regions::Interior)); + setPermissions(readOnly("species:{name}:density", Regions::Interior)); } substitutePermissions("name", {name}); } diff --git a/src/anomalous_diffusion.cxx b/src/anomalous_diffusion.cxx index 07ad12f4e..45fac7041 100644 --- a/src/anomalous_diffusion.cxx +++ b/src/anomalous_diffusion.cxx @@ -68,7 +68,7 @@ AnomalousDiffusion::AnomalousDiffusion(std::string name, Options& alloptions, So output_vars.push_back("energy_flow_ylow"); } if (include_D or include_nu) { - setAccess(readOnly(fmt::format("species:{}:AA", name))); + setPermissions(readOnly(fmt::format("species:{}:AA", name))); output_vars.push_back("momentum_source"); output_vars.push_back("momentum_flow_xlow"); output_vars.push_back("momentum_flow_ylow"); diff --git a/src/braginskii_collisions.cxx b/src/braginskii_collisions.cxx index 6c10f0b2f..5bd1c8ccb 100644 --- a/src/braginskii_collisions.cxx +++ b/src/braginskii_collisions.cxx @@ -62,52 +62,54 @@ BraginskiiCollisions::BraginskiiCollisions(const std::string& name, Options& all diagnose = options["diagnose"].doc("Output additional diagnostics?").withDefault(false); - setAccess(readOnly("species:{electrons}:temperature", Regions::Interior)); - setAccess(readOnly("species:{electrons}:density", Regions::Interior)); + setPermissions(readOnly("species:{electrons}:temperature", Regions::Interior)); + setPermissions(readOnly("species:{electrons}:density", Regions::Interior)); if (electron_electron) { - setAccess(readWrite( + setPermissions(readWrite( "species:{electrons}:collision_frequencies:{electrons}_{electrons2}_coll")); } if (electron_ion) { - setAccess(readOnly("species:{positive_ions}:temperature", Regions::Interior)); - setAccess(readWrite("species:{positive_ions}:collision_frequencies:{positive_ions}_{" - "electrons}_coll")); - setAccess(readWrite( + setPermissions(readOnly("species:{positive_ions}:temperature", Regions::Interior)); + setPermissions( + readWrite("species:{positive_ions}:collision_frequencies:{positive_ions}_{" + "electrons}_coll")); + setPermissions(readWrite( "species:{electrons}:collision_frequencies:{electrons}_{positive_ions}_coll")); } else { - setAccess(readIfSet("species:{positive_ions}:temperature", Regions::Interior)); + setPermissions(readIfSet("species:{positive_ions}:temperature", Regions::Interior)); } if (electron_neutral) { - setAccess(readOnly("species:{neutrals}:temperature", Regions::Interior)); - setAccess(readWrite( + setPermissions(readOnly("species:{neutrals}:temperature", Regions::Interior)); + setPermissions(readWrite( "species:{neutrals}:collision_frequencies:{neutrals}_{electrons}_coll")); - setAccess(readWrite( + setPermissions(readWrite( "species:{electrons}:collision_frequencies:{electrons}_{neutrals}_coll")); } else { - setAccess(readIfSet("species:{neutrals}:temperature", Regions::Interior)); + setPermissions(readIfSet("species:{neutrals}:temperature", Regions::Interior)); } if (ion_ion) { - setAccess(readWrite("species:{ions}:collision_frequencies:{ions}_{ions2}_coll")); + setPermissions(readWrite("species:{ions}:collision_frequencies:{ions}_{ions2}_coll")); } if (ion_neutral) { - setAccess(readWrite("species:{ions}:collision_frequencies:{ions}_{neutrals}_coll")); - setAccess( + setPermissions( + readWrite("species:{ions}:collision_frequencies:{ions}_{neutrals}_coll")); + setPermissions( readWrite("species:{neutrals}:collision_frequencies:{neutrals}_{ions}_coll")); } if (neutral_neutral) { - setAccess(readWrite( + setPermissions(readWrite( "species:{neutrals}:collision_frequencies:{neutrals}_{neutrals2}_coll")); } if (electron_electron or electron_ion or electron_neutral) { - setAccess(readWrite("species:{electrons}:collision_frequency")); + setPermissions(readWrite("species:{electrons}:collision_frequency")); } if (ion_ion or ion_neutral) { - setAccess(readWrite("species:{ions}:collision_frequency")); + setPermissions(readWrite("species:{ions}:collision_frequency")); } else if (electron_ion) { - setAccess(readWrite("species:{positive_ions}:collision_frequency")); + setPermissions(readWrite("species:{positive_ions}:collision_frequency")); } if (neutral_neutral or electron_neutral or ion_neutral) { - setAccess(readWrite("species:{neutrals}:collision_frequency")); + setPermissions(readWrite("species:{neutrals}:collision_frequency")); } } diff --git a/src/braginskii_conduction.cxx b/src/braginskii_conduction.cxx index 24757d1da..b9a5692c7 100644 --- a/src/braginskii_conduction.cxx +++ b/src/braginskii_conduction.cxx @@ -98,18 +98,18 @@ BraginskiiConduction::BraginskiiConduction(const std::string&, Options& alloptio for (const auto& [sp, mode] : all_conduction_collisions_mode) { species.push_back(sp); if (mode == "braginskii" and identifySpeciesType(sp) != SpeciesType::neutral) { - setAccess(readIfSet( + setPermissions(readIfSet( fmt::format("species:{}:collision_frequencies:{}_{}_coll", sp, sp, sp))); } else if (mode == "multispecies") { - setAccess(readIfSet(fmt::format("species:{}:collision_frequencies:{}_{}_coll", sp, - sp, "{all_species}"))); - setAccess(readIfSet(fmt::format("species:{}:collision_frequencies:{}_{}_cx", sp, sp, - "{all_species}"))); + setPermissions(readIfSet(fmt::format("species:{}:collision_frequencies:{}_{}_coll", + sp, sp, "{all_species}"))); + setPermissions(readIfSet(fmt::format("species:{}:collision_frequencies:{}_{}_cx", + sp, sp, "{all_species}"))); } else if (mode == "AFN" and identifySpeciesType(sp) == SpeciesType::neutral) { - setAccess(readIfSet(fmt::format("species:{}:collision_frequencies:{}_{}_cx", sp, sp, - "{positive_ions}"))); - setAccess(readIfSet(fmt::format("species:{}:collision_frequencies:{}_{}_iz", sp, sp, - "{positive_ions}"))); + setPermissions(readIfSet(fmt::format("species:{}:collision_frequencies:{}_{}_cx", + sp, sp, "{positive_ions}"))); + setPermissions(readIfSet(fmt::format("species:{}:collision_frequencies:{}_{}_iz", + sp, sp, "{positive_ions}"))); } } substitutePermissions("sp", species); diff --git a/src/braginskii_friction.cxx b/src/braginskii_friction.cxx index 100196d27..b812cff3c 100644 --- a/src/braginskii_friction.cxx +++ b/src/braginskii_friction.cxx @@ -31,7 +31,7 @@ BraginskiiFriction::BraginskiiFriction(const std::string& name, Options& allopti options["diagnose"].doc("Output additional diagnostics?").withDefault(false); if (frictional_heating) { - setAccess(readWrite("species:{all_species}:energy_source")); + setPermissions(readWrite("species:{all_species}:energy_source")); } } diff --git a/src/braginskii_ion_viscosity.cxx b/src/braginskii_ion_viscosity.cxx index 1058fc971..6b8fe23fc 100644 --- a/src/braginskii_ion_viscosity.cxx +++ b/src/braginskii_ion_viscosity.cxx @@ -125,7 +125,7 @@ BraginskiiIonViscosity::BraginskiiIonViscosity(const std::string& name, coll_types.push_back("{non_electrons}_{all_species}_cx"); } if (perpendicular) { - setAccess(readOnly("fields:phi")); + setPermissions(readOnly("fields:phi")); } substitutePermissions("coll_type", coll_types); } diff --git a/src/classical_diffusion.cxx b/src/classical_diffusion.cxx index a4cdb7f45..6bf7270e3 100644 --- a/src/classical_diffusion.cxx +++ b/src/classical_diffusion.cxx @@ -27,7 +27,7 @@ ClassicalDiffusion::ClassicalDiffusion(std::string name, Options& alloptions, So // density are unset. substitutePermissions("output", {"density_source", "momentum_source", "energy_source"}); if (custom_D < 0.) - setAccess(readOnly("species:{all_species}:collision_frequency")); + setPermissions(readOnly("species:{all_species}:collision_frequency")); } void ClassicalDiffusion::transform_impl(GuardedOptions& state) { diff --git a/src/electromagnetic.cxx b/src/electromagnetic.cxx index ad8602e34..464ad5001 100644 --- a/src/electromagnetic.cxx +++ b/src/electromagnetic.cxx @@ -87,7 +87,7 @@ Electromagnetic::Electromagnetic(std::string name, Options& alloptions, Solver* .withDefault(false); if (magnetic_flutter) - setAccess(readWrite("fields:Apar_flutter")); + setPermissions(readWrite("fields:Apar_flutter")); } void Electromagnetic::restartVars(Options& state) { diff --git a/src/reaction.cxx b/src/reaction.cxx index 89c0f5f30..29207b7d1 100644 --- a/src/reaction.cxx +++ b/src/reaction.cxx @@ -85,7 +85,7 @@ void Reaction::add_diagnostic(const std::string& sp_name, const std::string& dia diag_key, ReactionDiagnostic(diag_name, description, type, data_source, standard_name, transformer))); } - setAccess(readWrite(diag_name)); + setPermissions(readWrite(diag_name)); } /** diff --git a/src/recycling.cxx b/src/recycling.cxx index 44bda53e2..a55403f7b 100644 --- a/src/recycling.cxx +++ b/src/recycling.cxx @@ -157,11 +157,11 @@ Recycling::Recycling(std::string name, Options& alloptions, Solver*) } if (target_recycle) { - setAccess(readIfSet("species:{from}:energy_flow_ylow")); + setPermissions(readIfSet("species:{from}:energy_flow_ylow")); } if (sol_recycle or pfr_recycle) { - setAccess(readIfSet("species:{from}:energy_flow_xlow")); - setAccess(readIfSet("species:{from}:particle_flow_xlow")); + setPermissions(readIfSet("species:{from}:energy_flow_xlow")); + setPermissions(readIfSet("species:{from}:particle_flow_xlow")); } substitutePermissions("to", std::vector(to_species.begin(), to_species.end())); diff --git a/src/relax_potential.cxx b/src/relax_potential.cxx index aee92696e..539e77e55 100644 --- a/src/relax_potential.cxx +++ b/src/relax_potential.cxx @@ -46,14 +46,14 @@ RelaxPotential::RelaxPotential(std::string name, Options& alloptions, Solver* so if (diamagnetic) { // FIXME: These will only be read if BOTH charge and pressure are set - setAccess(readIfSet("species:{charged}:pressure", Regions::Interior)); - setAccess(readIfSet("species:{all_species}:charge")); + setPermissions(readIfSet("species:{charged}:pressure", Regions::Interior)); + setPermissions(readIfSet("species:{all_species}:charge")); // FIXME: The weay transform_impl is currently written, // energy_source is set for neutral species with an explicit // charge declared as 0 if diamagnetic_polarisation == true. I // suspect that's a mistake though. - setAccess(readWrite("species:{all_species}:energy_source")); - setAccess(readWrite("fields:DivJdia")); + setPermissions(readWrite("species:{all_species}:energy_source")); + setPermissions(readWrite("fields:DivJdia")); // Read curvature vector try { diff --git a/src/sheath_boundary.cxx b/src/sheath_boundary.cxx index 4571f82bb..75273cac3 100644 --- a/src/sheath_boundary.cxx +++ b/src/sheath_boundary.cxx @@ -121,11 +121,11 @@ SheathBoundary::SheathBoundary(std::string name, Options& alloptions, Solver*) // FIXME: The two results of the ternary are actually the same; need // to change what writeBoundaryIfSet returns (and how we model // permissions, for that matter) - setAccess(always_set_phi - ? Permissions::VarRights({"fields:phi", - {Regions::Interior, Regions::Nowhere, - Regions::Nowhere, Regions::Boundaries}}) - : writeBoundaryIfSet("fields:phi")); + setPermissions(always_set_phi + ? Permissions::VarRights({"fields:phi", + {Regions::Interior, Regions::Nowhere, + Regions::Nowhere, Regions::Boundaries}}) + : writeBoundaryIfSet("fields:phi")); } void SheathBoundary::transform_impl(GuardedOptions& state) { diff --git a/src/sheath_boundary_insulating.cxx b/src/sheath_boundary_insulating.cxx index 8eaa89d97..1a315fa52 100644 --- a/src/sheath_boundary_insulating.cxx +++ b/src/sheath_boundary_insulating.cxx @@ -101,7 +101,7 @@ SheathBoundaryInsulating::SheathBoundaryInsulating(std::string name, Options& al // FIXME: velocity and momentum will only be set on boundaries if already set on // interior substitutePermissions("ion_optional", {"velocity", "momentum"}); - setAccess(writeBoundaryIfSet("fields:phi")); + setPermissions(writeBoundaryIfSet("fields:phi")); } void SheathBoundaryInsulating::transform_impl(GuardedOptions& state) { diff --git a/src/sheath_boundary_simple.cxx b/src/sheath_boundary_simple.cxx index 174eab466..c856b0809 100644 --- a/src/sheath_boundary_simple.cxx +++ b/src/sheath_boundary_simple.cxx @@ -156,7 +156,7 @@ SheathBoundarySimple::SheathBoundarySimple(std::string name, Options& alloptions // FIXME: velocity and momentum will only be set on boundaries if already set on // interior substitutePermissions("ion_optional", {"velocity", "momentum"}); - setAccess(writeBoundaryIfSet("fields:phi")); + setPermissions(writeBoundaryIfSet("fields:phi")); } void SheathBoundarySimple::transform_impl(GuardedOptions& state) { diff --git a/src/sheath_closure.cxx b/src/sheath_closure.cxx index 289edf67b..3484c6c33 100644 --- a/src/sheath_closure.cxx +++ b/src/sheath_closure.cxx @@ -35,13 +35,13 @@ SheathClosure::SheathClosure(std::string name, Options& alloptions, Solver*) output.write("\tL_par = {:e} (normalised)\n", L_par); if (sinks) { - setAccess(readOnly("species:e:temperature")); - setAccess(readOnly("species:{non_electrons}:{inputs}")); - setAccess(readWrite("species:{non_electrons}:{outputs}")); + setPermissions(readOnly("species:e:temperature")); + setPermissions(readOnly("species:{non_electrons}:{inputs}")); + setPermissions(readWrite("species:{non_electrons}:{outputs}")); substitutePermissions("inputs", {"AA", "density", "temperature"}); substitutePermissions("output", {"density_source", "energy_source"}); } else { - setAccess(readIfSet("species:e:temperature")); + setPermissions(readIfSet("species:e:temperature")); } } diff --git a/src/vorticity.cxx b/src/vorticity.cxx index 4b0a063e1..a9bcef324 100644 --- a/src/vorticity.cxx +++ b/src/vorticity.cxx @@ -210,30 +210,30 @@ Vorticity::Vorticity(std::string name, Options& alloptions, Solver* solver) if (diamagnetic or diamagnetic_polarisation) { // FIXME: These will only be read if BOTH charge and pressure (and possibly AA) are // set - setAccess(readIfSet("species:{charged}:pressure", Regions::Interior)); - setAccess(readIfSet("species:{all_species}:charge")); + setPermissions(readIfSet("species:{charged}:pressure", Regions::Interior)); + setPermissions(readIfSet("species:{all_species}:charge")); } if (diamagnetic) { - setAccess(readWrite("species:{charged}:energy_source")); - setAccess(readWrite("fields:DivJdia")); + setPermissions(readWrite("species:{charged}:energy_source")); + setPermissions(readWrite("fields:DivJdia")); } if (diamagnetic_polarisation or collisional_friction) { // FIXME: Only read if pressure also set - setAccess(readIfSet("species:{charged}:AA")); + setPermissions(readIfSet("species:{charged}:AA")); } if (phi_boundary_relax) { - setAccess(readOnly("time")); + setPermissions(readOnly("time")); } else { if (sheath_boundary) { - setAccess(readOnly("species:e:AA")); + setPermissions(readOnly("species:e:AA")); } - setAccess(readIfSet("species:e:temperature", Regions::Interior)); + setPermissions(readIfSet("species:e:temperature", Regions::Interior)); } if (collisional_friction) { - setAccess(readIfSet("species:{all_species}:charge")); - setAccess(readOnly("species:{positive_ions}:density")); - setAccess(readIfSet("species:{positive_ions}:collision_frequency")); - setAccess(readWrite("fields:DivJcol")); + setPermissions(readIfSet("species:{all_species}:charge")); + setPermissions(readOnly("species:{positive_ions}:density")); + setPermissions(readIfSet("species:{positive_ions}:collision_frequency")); + setPermissions(readWrite("fields:DivJcol")); } } From 14c9329752146ca61b9260811df14e907f2ee50d Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Tue, 9 Dec 2025 15:31:50 +0000 Subject: [PATCH 32/63] Set names of variables with read-permission --- include/fixed_fraction_radiation.hxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/fixed_fraction_radiation.hxx b/include/fixed_fraction_radiation.hxx index 97e2c0409..bb48a549b 100644 --- a/include/fixed_fraction_radiation.hxx +++ b/include/fixed_fraction_radiation.hxx @@ -372,6 +372,8 @@ struct FixedFractionRadiation : public Component { Tnorm = get(units["eV"]); Nnorm = get(units["inv_meters_cubed"]); FreqNorm = 1. / get(units["seconds"]); + + substitutePermissions("inputs", {"density", "temperature"}); } void outputVars(Options& state) override { From 2001ae703a1888cbb39388ca900bc9cd4444bb78 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Wed, 26 Nov 2025 11:37:38 +0000 Subject: [PATCH 33/63] Allow permission objects to be streamed and stored in Options --- include/permissions.hxx | 29 ++++++++++++++--- src/permissions.cxx | 57 +++++++++++++++++++++++++++++++++ tests/unit/test_permissions.cxx | 49 ++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 4 deletions(-) diff --git a/include/permissions.hxx b/include/permissions.hxx index 357d9de17..057128538 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -5,6 +5,8 @@ #include #include +#include + /// Ways in which someone is allowed to access the variable, with /// increasing levels of rights. "ReadIfSet" indicates that /// variables should only be read if already set. "Final" refers to @@ -39,6 +41,8 @@ constexpr Regions operator~(Regions a) { /// @} +inline auto format_as(Regions r) { return fmt::underlying(r); } + /// Class to store information on whether particular variables an be /// read from and/or written to. These permissions can apply on /// particular regions of the domain. @@ -211,6 +215,10 @@ public: /// Return a string version of the region names static std::string regionNames(const Regions regions); + // Allow to be streamed, so can be stored in Options. + friend std::ostream& operator<<(std::ostream& os, const Permissions& permissions); + friend std::istream& operator>>(std::istream& is, Permissions& permissions); + private: /// Returns the access rights for the most specific entry in this /// object which matches the variable name. If there are no matching @@ -255,8 +263,8 @@ inline Permissions::VarRights writeFinal(std::string varname, } /// Convenience function to return an object expressing that the -/// variable should have Write permissions on the boundaries. It will -/// have Read permissions in the interior, as this is normally +/// variable should have Final write permissions on the boundaries. It +/// will have Read permissions in the interior, as this is normally /// required to set the boundaries correctly. inline Permissions::VarRights writeBoundary(std::string varname) { return {varname, @@ -264,8 +272,9 @@ inline Permissions::VarRights writeBoundary(std::string varname) { } /// Convenience function to return an object expressing that the -/// variable should have Write permissions on the boundaries. It will -/// have Read permissions in the interior if the interior is already set. +/// variable should have Final write permissions on the boundaries. It +/// will have Read permissions in the interior if the interior is +/// already set. inline Permissions::VarRights writeBoundaryIfSet(std::string varname) { return {varname, {Regions::Interior, Regions::Nowhere, Regions::Nowhere, Regions::Boundaries}}; @@ -275,3 +284,15 @@ inline Permissions::VarRights writeBoundaryIfSet(std::string varname) { // FIXME: Ideally there would be some way to express write permissions only if set // FIXME: Ideally we could express to write a boundary only if the interior is set + +/// Write a string-representation of the permissions to an IO +/// stream. This is useful for allowing permission data to be stored +/// in Options objects. +std::ostream& operator<<(std::ostream& os, const Permissions& permissions); + +/// Read a string representation of permissions from an IO +/// stream. This is useful for allowing permissions data to be +/// retrieved from Options objects. Note that there is undefined +/// behaviour if the input is corrupted; an exception may be thrown or +/// the permissions that are read may be incomplete. +std::istream& operator>>(std::istream& is, Permissions& permissions); diff --git a/src/permissions.cxx b/src/permissions.cxx index e3f2b9629..9b47dbc67 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -1,4 +1,7 @@ +#include + #include "../include/permissions.hxx" +#include "bout/boutexception.hxx" #include #include @@ -143,6 +146,9 @@ Permissions::getHighestPermission(const std::string& variable, Regions region) c std::map Permissions::getVariablesWithPermission(PermissionTypes permission, bool highestOnly) const { + if (permission == PermissionTypes::None) { + throw BoutException("Can not return information on variables with no permission."); + } std::map result; if (highestOnly and static_cast(permission) < static_cast(PermissionTypes::END) - 1) { @@ -172,3 +178,54 @@ std::string Permissions::regionNames(const Regions regions) { } return fmt::format("{}", fmt::join(regions_present, ", ")); } + +std::ostream& operator<<(std::ostream& os, const Permissions& permissions) { + os << std::string("{"); + bool first = true; + for (const auto& [varname, rights] : permissions.variable_permissions) { + if (!first) { + os << std::string(","); + } else { + first = false; + } + os << fmt::format("{}:{}", varname, rights); + } + os << std::string("}"); + return os; +} + +#include +std::istream& operator>>(std::istream& is, Permissions& permissions) { + std::string tmp, object; + std::getline(is, tmp, '{'); + if (is.eof()) { + throw BoutException("Error parsing Permissions data; no opening bracket."); + } + std::getline(is, object, '}'); + if (is.eof()) { + throw BoutException("Error parsing Permissions data; no closing bracket."); + } + permissions.variable_permissions.clear(); + + // FIXME: This will just skip over any malformed content, without an error or warning + std::vector rights_patterns; + for (int i = 0; i < static_cast(PermissionTypes::END); i++) { + rights_patterns.push_back("\\s*(\\d+)\\s*"); + } + const std::regex re( + fmt::format("\\s*(\\w+)\\s*:\\s*\\[{}\\]", fmt::join(rights_patterns, ","))); + auto items_begin = std::sregex_iterator(object.begin(), object.end(), re); + auto items_end = std::sregex_iterator(); + + for (std::sregex_iterator i = items_begin; i != items_end; ++i) { + std::smatch item = *i; + Permissions::AccessRights rights; + for (int i = 0; i < static_cast(PermissionTypes::END); i++) { + std::string val = item[2 + i]; + rights[i] = static_cast(std::stoi(val)); + } + permissions.setAccess(item[1], rights); + } + + return is; +} diff --git a/tests/unit/test_permissions.cxx b/tests/unit/test_permissions.cxx index d7f2186d3..afe947d55 100644 --- a/tests/unit/test_permissions.cxx +++ b/tests/unit/test_permissions.cxx @@ -1,3 +1,4 @@ +#include "bout/boutexception.hxx" #include "gtest/gtest.h" #include "../include/permissions.hxx" @@ -296,6 +297,11 @@ TEST(PermissionsTests, TestGetVariablesWithPermissions) { EXPECT_EQ(final_write.size(), 2); EXPECT_EQ(final_write["species:he:density"], Regions::Boundaries); EXPECT_EQ(final_write["species:he:collision_frequency"], Regions::All); + + EXPECT_THROW(example.getVariablesWithPermission(PermissionTypes::None, true), + BoutException); + EXPECT_THROW(example.getVariablesWithPermission(PermissionTypes::None, false), + BoutException); } TEST(PermissionsTests, TestSubstitute) { @@ -329,3 +335,46 @@ TEST(PermissionsTests, TestSubstitute) { EXPECT_EQ(example.getHighestPermission("d"), make_permission(PermissionTypes::ReadIfSet, "d")); } + +TEST(PermissionsTests, TestIO) { + Permissions empty({}), single({readOnly("test")}), + multiple({readIfSet("a", Regions::Interior), writeBoundary("b"), readWrite("c")}), + new_perm; + + std::stringstream ss1, ss2, ss3; + + ss1 << empty; + ss1 >> new_perm; + EXPECT_EQ(new_perm.getVariablesWithPermission(PermissionTypes::ReadIfSet, false).size(), + 0); + + ss2 << single; + ss2 >> new_perm; + EXPECT_EQ(new_perm.getVariablesWithPermission(PermissionTypes::ReadIfSet, false).size(), + 1); + std::map read_only = + new_perm.getVariablesWithPermission(PermissionTypes::Read); + EXPECT_EQ(read_only.size(), 1); + EXPECT_EQ(read_only["test"], Regions::All); + + ss3 << multiple; + ss3 >> new_perm; + EXPECT_EQ(new_perm.getVariablesWithPermission(PermissionTypes::ReadIfSet, false).size(), + 3); + std::map read_if_set = + new_perm.getVariablesWithPermission(PermissionTypes::ReadIfSet); + EXPECT_EQ(read_if_set.size(), 1); + EXPECT_EQ(read_if_set["a"], Regions::Interior); + std::map read = + new_perm.getVariablesWithPermission(PermissionTypes::Read); + EXPECT_EQ(read.size(), 1); + EXPECT_EQ(read["b"], Regions::Interior); + std::map read_write = + new_perm.getVariablesWithPermission(PermissionTypes::Write); + EXPECT_EQ(read_write.size(), 1); + EXPECT_EQ(read_write["c"], Regions::All); + std::map write_final = + new_perm.getVariablesWithPermission(PermissionTypes::Final); + EXPECT_EQ(write_final.size(), 1); + EXPECT_EQ(write_final["b"], Regions::Boundaries); +} From d1ba77140989c73f9a9e1de82eeb13f3a60ef9eb Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Wed, 26 Nov 2025 18:32:27 +0000 Subject: [PATCH 34/63] Fixed parsing of Permissions for variables containing colons --- src/permissions.cxx | 3 +-- tests/unit/test_permissions.cxx | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/permissions.cxx b/src/permissions.cxx index 9b47dbc67..3d3d2f01c 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -194,7 +194,6 @@ std::ostream& operator<<(std::ostream& os, const Permissions& permissions) { return os; } -#include std::istream& operator>>(std::istream& is, Permissions& permissions) { std::string tmp, object; std::getline(is, tmp, '{'); @@ -213,7 +212,7 @@ std::istream& operator>>(std::istream& is, Permissions& permissions) { rights_patterns.push_back("\\s*(\\d+)\\s*"); } const std::regex re( - fmt::format("\\s*(\\w+)\\s*:\\s*\\[{}\\]", fmt::join(rights_patterns, ","))); + fmt::format("\\s*([:\\w]+)\\s*:\\s*\\[{}\\]", fmt::join(rights_patterns, ","))); auto items_begin = std::sregex_iterator(object.begin(), object.end(), re); auto items_end = std::sregex_iterator(); diff --git a/tests/unit/test_permissions.cxx b/tests/unit/test_permissions.cxx index afe947d55..5fd9da37e 100644 --- a/tests/unit/test_permissions.cxx +++ b/tests/unit/test_permissions.cxx @@ -338,7 +338,7 @@ TEST(PermissionsTests, TestSubstitute) { TEST(PermissionsTests, TestIO) { Permissions empty({}), single({readOnly("test")}), - multiple({readIfSet("a", Regions::Interior), writeBoundary("b"), readWrite("c")}), + multiple({readIfSet("a", Regions::Interior), writeBoundary("b"), readWrite("c:d")}), new_perm; std::stringstream ss1, ss2, ss3; @@ -372,7 +372,7 @@ TEST(PermissionsTests, TestIO) { std::map read_write = new_perm.getVariablesWithPermission(PermissionTypes::Write); EXPECT_EQ(read_write.size(), 1); - EXPECT_EQ(read_write["c"], Regions::All); + EXPECT_EQ(read_write["c:d"], Regions::All); std::map write_final = new_perm.getVariablesWithPermission(PermissionTypes::Final); EXPECT_EQ(write_final.size(), 1); From 8b789b59bf9451717ac73fcabc4b249e8d0f5d18 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Tue, 9 Dec 2025 17:54:38 +0000 Subject: [PATCH 35/63] Provide formatter to handle writing of Permissions --- include/permissions.hxx | 14 ++++++++++++-- src/permissions.cxx | 30 ++++++++---------------------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/include/permissions.hxx b/include/permissions.hxx index 057128538..7d1e4e03b 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -6,6 +6,7 @@ #include #include +#include /// Ways in which someone is allowed to access the variable, with /// increasing levels of rights. "ReadIfSet" indicates that @@ -149,8 +150,6 @@ public: void setAccess(const VarRights& info) { setAccess(info.name, info.rights); } - void printAllPermissions() const; - /// Replace a placeholder in the names of variables stored in this /// object. This is useful if you need to access the same variable /// for multiple species. For example, the following code gives @@ -216,6 +215,7 @@ public: static std::string regionNames(const Regions regions); // Allow to be streamed, so can be stored in Options. + friend fmt::formatter; friend std::ostream& operator<<(std::ostream& os, const Permissions& permissions); friend std::istream& operator>>(std::istream& is, Permissions& permissions); @@ -231,6 +231,16 @@ private: std::map variable_permissions; }; +/// Format `Permissions` to string. Format string specification is the +/// same as when formatting a `std::map`. +/// See https://fmt.dev/12.0/syntax/#range-format-specifications. +template <> +struct fmt::formatter + : formatter> { + auto format(const Permissions& p, format_context& ctx) const + -> format_context::iterator; +}; + /// \defgroup PermissionFactories /// @{ diff --git a/src/permissions.cxx b/src/permissions.cxx index 3d3d2f01c..877c6fd48 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -71,16 +71,6 @@ std::string replaceAll(const std::string& str, const std::string& from, return result; } -void Permissions::printAllPermissions() const { - for (const auto& [varname, perms] : variable_permissions) { - fmt::print("{}", varname); - for (int i = 0; i < static_cast(PermissionTypes::END); i++) { - fmt::print(" {}", static_cast(perms[i])); - } - fmt::print("\n"); - } -} - void Permissions::substitute(const std::string& label, const std::vector& substitutions) { for (auto it = variable_permissions.begin(); it != variable_permissions.end();) { @@ -179,18 +169,14 @@ std::string Permissions::regionNames(const Regions regions) { return fmt::format("{}", fmt::join(regions_present, ", ")); } +auto fmt::formatter::format(const Permissions& p, format_context& ctx) const + -> format_context::iterator { + return formatter>::format( + p.variable_permissions, ctx); +} + std::ostream& operator<<(std::ostream& os, const Permissions& permissions) { - os << std::string("{"); - bool first = true; - for (const auto& [varname, rights] : permissions.variable_permissions) { - if (!first) { - os << std::string(","); - } else { - first = false; - } - os << fmt::format("{}:{}", varname, rights); - } - os << std::string("}"); + os << fmt::format("{}", permissions); return os; } @@ -212,7 +198,7 @@ std::istream& operator>>(std::istream& is, Permissions& permissions) { rights_patterns.push_back("\\s*(\\d+)\\s*"); } const std::regex re( - fmt::format("\\s*([:\\w]+)\\s*:\\s*\\[{}\\]", fmt::join(rights_patterns, ","))); + fmt::format("\\s*\"([^\"]+)\"\\s*:\\s*\\[{}\\]", fmt::join(rights_patterns, ","))); auto items_begin = std::sregex_iterator(object.begin(), object.end(), re); auto items_end = std::sregex_iterator(); From d3871150df2343a23c800325b0a0cd51df94a74f Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Tue, 9 Dec 2025 18:30:01 +0000 Subject: [PATCH 36/63] Fixed various compiler warnings --- src/guarded_options.cxx | 2 +- src/permissions.cxx | 25 ++++++++++++------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/guarded_options.cxx b/src/guarded_options.cxx index 34ce7301a..ce780279d 100644 --- a/src/guarded_options.cxx +++ b/src/guarded_options.cxx @@ -54,7 +54,7 @@ std::map GuardedOptions::getChildren() { void updateAccessRecords(std::map& records, const std::string& name, Regions region) { if (records.count(name) > 0) { - Regions new_region = static_cast(records[name] & ~region); + Regions new_region = records[name] & ~region; if (new_region == Regions::Nowhere) { records.erase(name); } else { diff --git a/src/permissions.cxx b/src/permissions.cxx index 877c6fd48..327775a07 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -33,10 +33,10 @@ /// set). Permissions::AccessRights applyLowerPermissions(const Permissions::AccessRights& rights) { Permissions::AccessRights result(rights); - for (int i = static_cast(PermissionTypes::ReadIfSet); - i < static_cast(PermissionTypes::END); i++) { + for (auto i = static_cast(PermissionTypes::ReadIfSet); + i < static_cast(PermissionTypes::END); i++) { // Higher permissions imply lower permissions - for (int j = static_cast(PermissionTypes::ReadIfSet); j < i; j++) { + for (auto j = static_cast(PermissionTypes::ReadIfSet); j < i; j++) { result[j] = result[j] | rights[i]; } } @@ -99,7 +99,7 @@ Permissions::VarRights Permissions::bestMatchRights(const std::string& variable) Permissions::AccessRights best_candidate = {Regions::Nowhere, Regions::Nowhere, Regions::Nowhere}; std::string best_candidate_name = ""; - int max_len = 0; + size_t max_len = 0; for (const auto& [varname, rights] : variable_permissions) { if (varname.size() > max_len and variable.find(varname + ":") == 0) { max_len = varname.size(); @@ -114,7 +114,7 @@ std::pair Permissions::canAccess(const std::string& variable, PermissionTypes permission, Regions region) const { auto [match_name, match_rights] = bestMatchRights(variable); - if ((match_rights[static_cast(permission)] & region) == region) { + if ((match_rights[static_cast(permission)] & region) == region) { return {true, match_name}; } else { return {false, ""}; @@ -126,8 +126,8 @@ Permissions::getHighestPermission(const std::string& variable, Regions region) c if (region == Regions::Nowhere) return {PermissionTypes::None, ""}; auto [varname, rights] = bestMatchRights(variable); - int i = static_cast(PermissionTypes::ReadIfSet); - while (i < static_cast(PermissionTypes::END) and (rights[i] & region) == region) { + size_t i = static_cast(PermissionTypes::ReadIfSet); + while (i < static_cast(PermissionTypes::END) and (rights[i] & region) == region) { i++; } return {static_cast(i - 1), varname}; @@ -141,17 +141,16 @@ Permissions::getVariablesWithPermission(PermissionTypes permission, } std::map result; if (highestOnly - and static_cast(permission) < static_cast(PermissionTypes::END) - 1) { + and static_cast(permission) < static_cast(PermissionTypes::END) - 1) { for (const auto& [varname, rights] : variable_permissions) { - auto regions = rights[static_cast(permission)]; - auto perm_in_regions = rights[static_cast(permission)] - & ~rights[static_cast(permission) + 1]; + auto perm_in_regions = rights[static_cast(permission)] + & ~rights[static_cast(permission) + 1]; if (perm_in_regions != Regions::Nowhere) result.emplace(varname, perm_in_regions); } } else { for (const auto& [varname, rights] : variable_permissions) { - auto regions = rights[static_cast(permission)]; + auto regions = rights[static_cast(permission)]; if (regions != Regions::Nowhere) result.emplace(varname, regions); } @@ -205,7 +204,7 @@ std::istream& operator>>(std::istream& is, Permissions& permissions) { for (std::sregex_iterator i = items_begin; i != items_end; ++i) { std::smatch item = *i; Permissions::AccessRights rights; - for (int i = 0; i < static_cast(PermissionTypes::END); i++) { + for (size_t i = 0; i < static_cast(PermissionTypes::END); i++) { std::string val = item[2 + i]; rights[i] = static_cast(std::stoi(val)); } From 13134abd685fbe6090b4985df2eaa284e8dda5ac Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Tue, 9 Dec 2025 19:23:57 +0000 Subject: [PATCH 37/63] Applied recommendations from clang-tidy --- include/component.hxx | 21 ++++++++--- include/component_scheduler.hxx | 8 +++-- include/guarded_options.hxx | 9 +++-- include/permissions.hxx | 21 +++++++---- src/component.cxx | 16 ++++++--- src/component_scheduler.cxx | 29 ++++++++++----- src/guarded_options.cxx | 23 ++++++++---- src/permissions.cxx | 38 +++++++++++++------- tests/unit/test_guarded_options.cxx | 55 ++++++++++++++++------------- tests/unit/test_permissions.cxx | 27 +++++++++----- 10 files changed, 166 insertions(+), 81 deletions(-) diff --git a/include/component.hxx b/include/component.hxx index a264c510f..de2f179dd 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -3,12 +3,23 @@ #ifndef HERMES_COMPONENT_H #define HERMES_COMPONENT_H -#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include -#include #include +#include +#include +#include +#include #include "guarded_options.hxx" #include "permissions.hxx" @@ -31,7 +42,7 @@ struct SpeciesInformation { for (auto& sp : species) { // FIXME: identifySpecies only identifies positive ions // FIXME: identifySpecies has no concept of ebeam - SpeciesType type = identifySpeciesType(sp); + const SpeciesType type = identifySpeciesType(sp); if (type == SpeciesType::electron) { electrons.push_back(sp); } else if (type == SpeciesType::ion) { @@ -512,11 +523,11 @@ inline void set_with_attrs(Options& option, Field3D value, std::initializer_list } template<> inline void set_with_attrs(GuardedOptions & option, Field3D value, std::initializer_list> attrs) { - set_with_attrs(option.getWritable(), value, attrs); + set_with_attrs(option.getWritable(), std::move(value), attrs); } template<> inline void set_with_attrs(GuardedOptions && option, Field3D value, std::initializer_list> attrs) { - set_with_attrs(option.getWritable(), value, attrs); + set_with_attrs(option.getWritable(), std::move(value), attrs); } #endif diff --git a/include/component_scheduler.hxx b/include/component_scheduler.hxx index 2f93904ff..f848614a8 100644 --- a/include/component_scheduler.hxx +++ b/include/component_scheduler.hxx @@ -1,14 +1,16 @@ - #pragma once #ifndef COMPONENT_SCHEDULER_H #define COMPONENT_SCHEDULER_H -#include "component.hxx" - #include #include +#include +#include + +#include "component.hxx" + /// Creates and schedules model components /// /// Currently only one implementation, but in future alternative scheduler diff --git a/include/guarded_options.hxx b/include/guarded_options.hxx index 0a3e299b9..961ba5d7b 100644 --- a/include/guarded_options.hxx +++ b/include/guarded_options.hxx @@ -1,7 +1,11 @@ #pragma once +#include #include +#include +#include +#include #include #include "permissions.hxx" @@ -82,6 +86,7 @@ private: GuardedOptions(Options* options, Permissions* permissions, std::shared_ptr> unread_vars, std::shared_ptr> unwritten_vars) - : options(options), permissions(std::move(permissions)), unread_variables(std::move(unread_vars)), - unwritten_variables(unwritten_vars) {} + : options(std::move(options)), permissions(std::move(permissions)), + unread_variables(std::move(unread_vars)), + unwritten_variables(std::move(unwritten_vars)) {} }; diff --git a/include/permissions.hxx b/include/permissions.hxx index 7d1e4e03b..fff1ab2a8 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -1,10 +1,15 @@ #pragma once #include +#include #include +#include #include +#include #include +#include #include +#include #include #include @@ -248,28 +253,32 @@ struct fmt::formatter /// variable should have ReadIfSet permissions in the specified regions. inline Permissions::VarRights readIfSet(std::string varname, Regions region = Regions::All) { - return {varname, {region, Regions::Nowhere, Regions::Nowhere, Regions::Nowhere}}; + return {std::move(varname), + {region, Regions::Nowhere, Regions::Nowhere, Regions::Nowhere}}; } /// Convenience function to return an object expressing that the /// variable should have Read permissions in the specified regions. inline Permissions::VarRights readOnly(std::string varname, Regions region = Regions::All) { - return {varname, {Regions::Nowhere, region, Regions::Nowhere, Regions::Nowhere}}; + return {std::move(varname), + {Regions::Nowhere, region, Regions::Nowhere, Regions::Nowhere}}; } /// Convenience function to return an object expressing that the /// variable should have Write permissions in the specified regions. inline Permissions::VarRights readWrite(std::string varname, Regions region = Regions::All) { - return {varname, {Regions::Nowhere, Regions::Nowhere, region, Regions::Nowhere}}; + return {std::move(varname), + {Regions::Nowhere, Regions::Nowhere, region, Regions::Nowhere}}; } /// Convenience function to return an object expressing that the /// variable should have Final permissions in the specified regions. inline Permissions::VarRights writeFinal(std::string varname, Regions region = Regions::All) { - return {varname, {Regions::Nowhere, Regions::Nowhere, Regions::Nowhere, region}}; + return {std::move(varname), + {Regions::Nowhere, Regions::Nowhere, Regions::Nowhere, region}}; } /// Convenience function to return an object expressing that the @@ -277,7 +286,7 @@ inline Permissions::VarRights writeFinal(std::string varname, /// will have Read permissions in the interior, as this is normally /// required to set the boundaries correctly. inline Permissions::VarRights writeBoundary(std::string varname) { - return {varname, + return {std::move(varname), {Regions::Nowhere, Regions::Interior, Regions::Nowhere, Regions::Boundaries}}; } @@ -286,7 +295,7 @@ inline Permissions::VarRights writeBoundary(std::string varname) { /// will have Read permissions in the interior if the interior is /// already set. inline Permissions::VarRights writeBoundaryIfSet(std::string varname) { - return {varname, + return {std::move(varname), {Regions::Interior, Regions::Nowhere, Regions::Nowhere, Regions::Boundaries}}; } diff --git a/src/component.cxx b/src/component.cxx index e5294e73a..e5252818a 100644 --- a/src/component.cxx +++ b/src/component.cxx @@ -1,5 +1,13 @@ +#include +#include + +#include +#include +#include #include "../include/component.hxx" +#include "../include/guarded_options.hxx" +#include "../include/permissions.hxx" std::unique_ptr Component::create(const std::string &type, const std::string &name, @@ -58,9 +66,9 @@ bool isSetFinal(const Options& option, [[maybe_unused]] const std::string& locat } bool isSetFinal(const GuardedOptions & option, const std::string& location) { - bool set = option.isSet(); + const bool set = option.isSet(); #if CHECKLEVEL >= 1 - PermissionTypes perm = option.getHighestPermission(); + const PermissionTypes perm = option.getHighestPermission(); if (perm >= PermissionTypes::Read or (perm == PermissionTypes::ReadIfSet and set)) { const Options& opt = option.get(); @@ -81,9 +89,9 @@ bool isSetFinalNoBoundary(const Options& option, [[maybe_unused]] const std::str } bool isSetFinalNoBoundary(const GuardedOptions & option, const std::string& location) { - bool set = option.isSet(); + const bool set = option.isSet(); #if CHECKLEVEL >= 1 - PermissionTypes perm = option.getHighestPermission(Regions::Interior); + const PermissionTypes perm = option.getHighestPermission(Regions::Interior); if (perm >= PermissionTypes::Read or (perm == PermissionTypes::ReadIfSet and set)) { // Mark option as final inside the domain, but not in the boundary const_cast(option.get(Regions::Interior)).attributes["final-domain"] = diff --git a/src/component_scheduler.cxx b/src/component_scheduler.cxx index d0f8b79b2..abaf9e6b5 100644 --- a/src/component_scheduler.cxx +++ b/src/component_scheduler.cxx @@ -1,16 +1,26 @@ -#include "../include/component_scheduler.hxx" +#include +#include +#include +#include +#include #include // for trim, strsplit +#include "../include/component.hxx" +#include "../include/component_scheduler.hxx" + ComponentScheduler::ComponentScheduler(Options &scheduler_options, Options &component_options, Solver *solver) { - std::string component_names = scheduler_options["components"] - .doc("Components in order of execution") - .as(); + const std::string component_names = scheduler_options["components"] + .doc("Components in order of execution") + .as(); - std::vector electrons, neutrals, positive_ions, negative_ions; + std::vector electrons; + std::vector neutrals; + std::vector positive_ions; + std::vector negative_ions; // For now split on ','. Something like "->" might be better for (const auto &name : strsplit(component_names, ',')) { @@ -29,7 +39,7 @@ ComponentScheduler::ComponentScheduler(Options &scheduler_options, // reliable way to identify what is a species? else if (component_options[name_trimmed].isSet("AA")) { if (component_options[name_trimmed].isSet("charge")) { - BoutReal charge = component_options[name_trimmed]["charge"]; + const BoutReal charge = component_options[name_trimmed]["charge"]; if (charge > 1e-5) { positive_ions.push_back(name_trimmed); } else if (charge < -1e-5) { @@ -44,9 +54,10 @@ ComponentScheduler::ComponentScheduler(Options &scheduler_options, // For each component e.g. "e", several Component types can be created // but if types are not specified then the component name is used - std::string types = component_options[name_trimmed].isSet("type") - ? component_options[name_trimmed]["type"].as() - : name_trimmed; + const std::string types = + component_options[name_trimmed].isSet("type") + ? component_options[name_trimmed]["type"].as() + : name_trimmed; for (const auto &type : strsplit(types, ',')) { auto type_trimmed = trim(type, " \t\r()"); diff --git a/src/guarded_options.cxx b/src/guarded_options.cxx index ce780279d..da6225499 100644 --- a/src/guarded_options.cxx +++ b/src/guarded_options.cxx @@ -1,15 +1,24 @@ -#include +#include +#include +#include +#include +#include + +#include +#include +#include #include "../include/guarded_options.hxx" +#include "../include/permissions.hxx" /// Check whether an option is set, without creating any parent /// Options objects in the process. -bool isSetRecursive(Options& opt, std::string varname) { - size_t colon = varname.find(":"); +bool isSetRecursive(Options& opt, const std::string& varname) { + const size_t colon = varname.find(":"); if (colon == std::string::npos) { return opt.isSet(varname); } - std::string fragment = varname.substr(0, colon); + const std::string fragment = varname.substr(0, colon); if (not opt.isSection(fragment)) { return false; } @@ -54,7 +63,7 @@ std::map GuardedOptions::getChildren() { void updateAccessRecords(std::map& records, const std::string& name, Regions region) { if (records.count(name) > 0) { - Regions new_region = records[name] & ~region; + const Regions new_region = records[name] & ~region; if (new_region == Regions::Nowhere) { records.erase(name); } else { @@ -65,7 +74,7 @@ void updateAccessRecords(std::map& records, const std::str const Options& GuardedOptions::get(Regions region) const { #if CHECKLEVEL >= 1 - std::string name = options->str(); + const std::string name = options->str(); auto [permission, varname] = permissions->getHighestPermission(name, region); if (permission >= PermissionTypes::ReadIfSet) { if (permission == PermissionTypes::ReadIfSet && !options->isSet()) { @@ -83,7 +92,7 @@ const Options& GuardedOptions::get(Regions region) const { Options& GuardedOptions::getWritable(Regions region) { #if CHECKLEVEL >= 1 - std::string name = options->str(); + const std::string name = options->str(); auto [access, varname] = permissions->canAccess(name, PermissionTypes::Write, region); if (access) { updateAccessRecords(*unwritten_variables, varname, region); diff --git a/src/permissions.cxx b/src/permissions.cxx index 327775a07..688e6c5de 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -1,10 +1,20 @@ +#include +#include +#include +#include +#include #include +#include +#include +#include -#include "../include/permissions.hxx" -#include "bout/boutexception.hxx" -#include +#include +#include +#include #include +#include "../include/permissions.hxx" + // TODO: It might be useful to add an optional condition which must be // met for conditions to apply. So a variable is only read or written // if another variable is already set, for example. Could potentially @@ -60,8 +70,9 @@ void Permissions::setAccess(const std::string& variable, const AccessRights& rig std::string replaceAll(const std::string& str, const std::string& from, const std::string& to) { std::string result = str; - if (from.empty()) + if (from.empty()) { return result; + } size_t start_pos = 0; while ((start_pos = result.find(from, start_pos)) != std::string::npos) { result.replace(start_pos, from.length(), to); @@ -116,15 +127,15 @@ std::pair Permissions::canAccess(const std::string& variable, auto [match_name, match_rights] = bestMatchRights(variable); if ((match_rights[static_cast(permission)] & region) == region) { return {true, match_name}; - } else { - return {false, ""}; } + return {false, ""}; } std::pair Permissions::getHighestPermission(const std::string& variable, Regions region) const { - if (region == Regions::Nowhere) + if (region == Regions::Nowhere) { return {PermissionTypes::None, ""}; + } auto [varname, rights] = bestMatchRights(variable); size_t i = static_cast(PermissionTypes::ReadIfSet); while (i < static_cast(PermissionTypes::END) and (rights[i] & region) == region) { @@ -145,14 +156,16 @@ Permissions::getVariablesWithPermission(PermissionTypes permission, for (const auto& [varname, rights] : variable_permissions) { auto perm_in_regions = rights[static_cast(permission)] & ~rights[static_cast(permission) + 1]; - if (perm_in_regions != Regions::Nowhere) + if (perm_in_regions != Regions::Nowhere) { result.emplace(varname, perm_in_regions); + } } } else { for (const auto& [varname, rights] : variable_permissions) { auto regions = rights[static_cast(permission)]; - if (regions != Regions::Nowhere) + if (regions != Regions::Nowhere) { result.emplace(varname, regions); + } } } return result; @@ -180,7 +193,8 @@ std::ostream& operator<<(std::ostream& os, const Permissions& permissions) { } std::istream& operator>>(std::istream& is, Permissions& permissions) { - std::string tmp, object; + std::string tmp; + std::string object; std::getline(is, tmp, '{'); if (is.eof()) { throw BoutException("Error parsing Permissions data; no opening bracket."); @@ -202,10 +216,10 @@ std::istream& operator>>(std::istream& is, Permissions& permissions) { auto items_end = std::sregex_iterator(); for (std::sregex_iterator i = items_begin; i != items_end; ++i) { - std::smatch item = *i; + const std::smatch item = *i; Permissions::AccessRights rights; for (size_t i = 0; i < static_cast(PermissionTypes::END); i++) { - std::string val = item[2 + i]; + const std::string val = item[2 + i]; rights[i] = static_cast(std::stoi(val)); } permissions.setAccess(item[1], rights); diff --git a/tests/unit/test_guarded_options.cxx b/tests/unit/test_guarded_options.cxx index 00ba13a8a..bb653bc1b 100644 --- a/tests/unit/test_guarded_options.cxx +++ b/tests/unit/test_guarded_options.cxx @@ -1,6 +1,13 @@ -#include "../include/guarded_options.hxx" +#include +#include + +#include +#include #include "gtest/gtest.h" +#include "../include/guarded_options.hxx" +#include "../include/permissions.hxx" + class GuardedOptionsTests : public testing::Test { protected: GuardedOptionsTests() @@ -129,19 +136,20 @@ TEST_F(GuardedOptionsTests, TestGetWritableException) { } TEST_F(GuardedOptionsTests, TestUnreadItems) { - std::map expected1 = {{"species:he:charge", Regions::All}, - {"species:he:density", Regions::All}, - {"species:he:velocity", - Regions::Boundaries}, - {"species:d", Regions::All}, - {"unused:option", Regions::All}}, - expected2 = {{"species:he:charge", Regions::All}, - {"species:he:density", Regions::Boundaries}, - {"species:he:velocity", - Regions::Boundaries}, - {"species:d", Regions::All}, - {"unused:option", Regions::All}}, - expected3 = {{"species:d", Regions::All}}, expected4; + const std::map expected1 = { + {"species:he:charge", Regions::All}, + {"species:he:density", Regions::All}, + {"species:he:velocity", Regions::Boundaries}, + {"species:d", Regions::All}, + {"unused:option", Regions::All}}; + const std::map expected2 = { + {"species:he:charge", Regions::All}, + {"species:he:density", Regions::Boundaries}, + {"species:he:velocity", Regions::Boundaries}, + {"species:d", Regions::All}, + {"unused:option", Regions::All}}; + const std::map expected3 = {{"species:d", Regions::All}}; + const std::map expected4; EXPECT_EQ(guarded_opts.unreadItems(), expected1); @@ -162,16 +170,15 @@ TEST_F(GuardedOptionsTests, TestUnreadItems) { } TEST_F(GuardedOptionsTests, TestUnwrittenItems) { - std::map expected1 = {{"species:he:pressure", Regions::Interior}, - {"species:he:collision_frequency", - Regions::All}, - {"species:d:pressure", Regions::Interior}, - {"species:d:collision_frequencies", - Regions::Boundaries}}, - expected2 = {{"species:he:collision_frequency", - Regions::All}, - {"species:d:pressure", Regions::Interior}}, - expected3; + const std::map expected1 = { + {"species:he:pressure", Regions::Interior}, + {"species:he:collision_frequency", Regions::All}, + {"species:d:pressure", Regions::Interior}, + {"species:d:collision_frequencies", Regions::Boundaries}}; + const std::map expected2 = { + {"species:he:collision_frequency", Regions::All}, + {"species:d:pressure", Regions::Interior}}; + const std::map expected3; EXPECT_EQ(guarded_opts.unwrittenItems(), expected1); diff --git a/tests/unit/test_permissions.cxx b/tests/unit/test_permissions.cxx index 5fd9da37e..01af253ab 100644 --- a/tests/unit/test_permissions.cxx +++ b/tests/unit/test_permissions.cxx @@ -1,4 +1,9 @@ -#include "bout/boutexception.hxx" +#include +#include +#include +#include + +#include #include "gtest/gtest.h" #include "../include/permissions.hxx" @@ -7,7 +12,7 @@ auto make_access = std::make_pair; auto make_permission = std::make_pair; TEST(PermissionsTests, TestCanAccess) { - Permissions example({ + const Permissions example({ readIfSet("species:he:charge"), readOnly("species:he:density"), // Read and write permissions for pressure in the interior region @@ -102,7 +107,7 @@ TEST(PermissionsTests, TestCanAccess) { } TEST(PermissionsTests, TestGetHighestPermission) { - Permissions example({ + const Permissions example({ {"species:he:charge", {Regions::All, Regions::Nowhere, Regions::Nowhere, Regions::Nowhere}}, {"species:he:density", @@ -257,7 +262,7 @@ TEST(PermissionsTests, TestSetAccess) { } TEST(PermissionsTests, TestGetVariablesWithPermissions) { - Permissions example( + const Permissions example( {{"species:he:density", {Regions::Nowhere, Regions::All, Regions::Nowhere, Regions::Boundaries}}, // Read and write permissions for pressure in the interior region @@ -337,11 +342,15 @@ TEST(PermissionsTests, TestSubstitute) { } TEST(PermissionsTests, TestIO) { - Permissions empty({}), single({readOnly("test")}), - multiple({readIfSet("a", Regions::Interior), writeBoundary("b"), readWrite("c:d")}), - new_perm; - - std::stringstream ss1, ss2, ss3; + const Permissions empty({}); + const Permissions single({readOnly("test")}); + const Permissions multiple( + {readIfSet("a", Regions::Interior), writeBoundary("b"), readWrite("c:d")}); + Permissions new_perm; + + std::stringstream ss1; + std::stringstream ss2; + std::stringstream ss3; ss1 << empty; ss1 >> new_perm; From 7c760c5fb339f9cf5fd9eae946c323518f7c7619 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Fri, 12 Dec 2025 18:17:24 +0000 Subject: [PATCH 38/63] Check for missed substitutions in variable permissions --- include/component.hxx | 7 ++++++- include/permissions.hxx | 8 ++++++++ src/component.cxx | 1 + src/permissions.cxx | 15 +++++++++++++++ tests/unit/test_permissions.cxx | 13 +++++++++++++ 5 files changed, 43 insertions(+), 1 deletion(-) diff --git a/include/component.hxx b/include/component.hxx index de2f179dd..5b744cb40 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -117,7 +117,8 @@ struct Component { Solver *solver); // Time integration solver /// Tell the component the name of all species in the simulation, by type. It - /// will use this information to substitute the following placeholders in `state_variable_access`: + /// will use this information to substitute the following placeholders in + /// `state_variable_access`: /// - electrons (any electron species) /// - electrons2 (same as above, used for cross-product) /// - neutrals (species with no charge) @@ -134,6 +135,10 @@ struct Component { /// - non_electrons2 (same as above, used for cross-product) /// - all_species (ions, neutrals, and electrons) /// - all_species2 (same as above, used for cross-product) + /// + /// At the end of this function there is a call to + /// Permissions::checkNoRemainingSubstitutions. All substitutions + /// must be completed or else an exception will be thrown. void declareAllSpecies(const SpeciesInformation & info); protected: diff --git a/include/permissions.hxx b/include/permissions.hxx index fff1ab2a8..ea407c403 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -175,6 +176,11 @@ public: void substitute(const std::string& label, const std::vector& substitutions); + /// Check there are no remaining placeholders that have not been + /// substituted in any variables names. If there are, then throw an + /// exception. + void checkNoRemainingSubstitutions() const; + /// Check whether users are allowed to access this variable to the /// given permission level, in the given region. The second item /// returned indicates the name of the variable or section from @@ -234,6 +240,8 @@ private: VarRights bestMatchRights(const std::string& variable) const; std::map variable_permissions; + + static const std::regex LABEL_RE; }; /// Format `Permissions` to string. Format string specification is the diff --git a/src/component.cxx b/src/component.cxx index e5252818a..61a2d3f61 100644 --- a/src/component.cxx +++ b/src/component.cxx @@ -49,6 +49,7 @@ void Component::declareAllSpecies(const SpeciesInformation & info) { state_variable_access.substitute("non_electrons2", info.non_electrons); state_variable_access.substitute("all_species", info.all_species); state_variable_access.substitute("all_species2", info.all_species); + state_variable_access.checkNoRemainingSubstitutions(); } constexpr decltype(ComponentFactory::type_name) ComponentFactory::type_name; diff --git a/src/permissions.cxx b/src/permissions.cxx index 688e6c5de..873062824 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -37,6 +37,8 @@ // There should be ways to do this with templates to allow greater inlining // +const std::regex Permissions::LABEL_RE{"\\{([^}]+)\\}"}; + /// Return a set of access rights where the lower permissions have /// been updated so that they reflect higher permissions (e.g., read /// permission will be set in all cases where write permission was @@ -102,6 +104,19 @@ void Permissions::substitute(const std::string& label, } } +void Permissions::checkNoRemainingSubstitutions() const { + std::vector unsubstituted; + for (const auto& [varname, _] : variable_permissions) { + if (std::regex_search(varname, LABEL_RE)) { + unsubstituted.push_back(varname); + } + } + if (unsubstituted.size() > 0) { + throw BoutException("The following variable names have unsubstituted labels: {}", + fmt::join(unsubstituted, ", ")); + } +} + Permissions::VarRights Permissions::bestMatchRights(const std::string& variable) const { auto match = variable_permissions.find(variable); if (match != variable_permissions.end()) { diff --git a/tests/unit/test_permissions.cxx b/tests/unit/test_permissions.cxx index 01af253ab..f5796bcb3 100644 --- a/tests/unit/test_permissions.cxx +++ b/tests/unit/test_permissions.cxx @@ -341,6 +341,19 @@ TEST(PermissionsTests, TestSubstitute) { make_permission(PermissionTypes::ReadIfSet, "d")); } +TEST(PermissionsTests, TestRemainingSubstitutions) { + const Permissions p1 = {readOnly("species:h+:density"), readWrite("fields:phi")}; + const Permissions p2 = {readOnly("species:{all_species}:density"), + writeFinal("fields:phi")}; + const Permissions p3 = {readOnly("species:{all_species}:{inputs}"), + readIfSet("species:{ions}:{maybe_inputs}")}; + const Permissions p4 = {}; + p1.checkNoRemainingSubstitutions(); + EXPECT_THROW(p2.checkNoRemainingSubstitutions(), BoutException); + EXPECT_THROW(p3.checkNoRemainingSubstitutions(), BoutException); + p4.checkNoRemainingSubstitutions(); +} + TEST(PermissionsTests, TestIO) { const Permissions empty({}); const Permissions single({readOnly("test")}); From eddbbdc6b64bde7ed8296431ba4e8e9e9f1718ca Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Fri, 12 Dec 2025 18:18:48 +0000 Subject: [PATCH 39/63] Fixed typo --- docs/sphinx/developer.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sphinx/developer.rst b/docs/sphinx/developer.rst index 0eea7bd1c..0edefe7e7 100644 --- a/docs/sphinx/developer.rst +++ b/docs/sphinx/developer.rst @@ -309,7 +309,7 @@ within that section, unless a more specific permission is also set. :: Permissions p({readOnly("time"), readOnly("species:e:pressure"), - readWrite("species:e:momentum", Reegions::Interior)}); + readWrite("species:e:momentum", Regions::Interior)}); .. doxygenclass:: Permissions :members: From 982df7ad92e47f8b0027e06b5dd83074ef0a9b21 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Tue, 16 Dec 2025 15:29:19 +0000 Subject: [PATCH 40/63] Address issues raised by @mikekryjak in review --- docs/sphinx/developer.rst | 310 +++++++++++++++++++---------- include/amjuel_reaction.hxx | 6 +- include/component.hxx | 25 +-- include/permissions.hxx | 24 ++- include/sheath_boundary_simple.hxx | 2 - src/evolve_density.cxx | 4 + src/evolve_pressure.cxx | 3 + src/permissions.cxx | 2 +- src/sheath_boundary.cxx | 28 +-- src/sheath_boundary_insulating.cxx | 14 +- src/sheath_boundary_simple.cxx | 27 +-- 11 files changed, 272 insertions(+), 173 deletions(-) diff --git a/docs/sphinx/developer.rst b/docs/sphinx/developer.rst index 0edefe7e7..2dc0f56a2 100644 --- a/docs/sphinx/developer.rst +++ b/docs/sphinx/developer.rst @@ -257,94 +257,6 @@ What is "Options"? Options is a dictionary-like class originally developed for parsing BOUT++ options. In Hermes-3, it is used as a general purpose dictionary. -What are Permissions? -~~~~~~~~~~~~~~ - -The ``Permissions`` class can be used to store information about which -variables within an ``Options`` object are allowed to be accessed and -for what purpose. This is used to control the variables used by a -``Component``. There are four levels of permissions, stored in the -`PermissionTypes` enum: - -ReadIfSet - Only allowed to read variable if it is already set. - -Read - Can read the contents of the variable. Assumes it has already been - set. - -Write - Can write variable. Makes no assumption about whether it has already - been written or will be written again in future. - -Final - This will be the last component to write to the variable. Only one - component may have ``Final`` permission for a given variable. - -Permissions apply to particular regions of the domain. This -allows, e.g., for there to be read permissions for the interior of the -domain but write permissions for the boundaries. These regions -are represented using the ``Regions`` enum, which functions as a -bitset. You can combine regions using bitwise logical operators. - -.. doxygengroup:: RegionsGroup - :members: - -A particular permission entry is a struct (``Permissions::VarRights``) -consisting of variable name and an array of ``Regions``. Each element -of the array corresponds to the member of the ``PermissionTypes`` enum -with that index. The contents of the element are the region(s) of the -domain with that permission type. This is an implementation detail -that you will seldom need to worry about in practice. The overwhelming -majority of permissions you would want to create can be constructed -using the provided factory functions. - -.. doxygengroup:: PermissionFactories - :members: - -This permission data can be used to construct a ``Permissions`` -object, which can be queried. Note that a permission applied to a -section of an ``Options`` object will apply to all variables contained -within that section, unless a more specific permission is also set. :: - - Permissions p({readOnly("time"), - readOnly("species:e:pressure"), - readWrite("species:e:momentum", Regions::Interior)}); - -.. doxygenclass:: Permissions - :members: - - -What is "GuardedOptions"? -~~~~~~~~~~~~~~ - -``GuardedOptions`` objects combine a `Permissions` object and an -`Options` object. They can be indexed just like normal ``Options`` -objects but will return another ``GuardedOptions``, wrapping the -result. In order to read or write the contents of a ``GuardedOptions`` -object you must use the ``get()`` or ``getWritable()`` methods, -respectively. These will return the underlying (const) ``Options`` -object, if you have the necessary permissions to access it. Otherwise, -they will raise an exception. - -If ``CHECKLEVEL`` is 1 or above, then the ``GuardedOptions`` will track -which variables have actually been accessed. Lists of -unread/unwritten variables can be returned with the ``unreadItems()`` -and ``unwrittenItems()`` methods. If ``CHECKLEVEL`` is zero then -calling these methods will raise an exception. - -.. doxygenclass:: GuardedOptions - :members: - -.. note:: - When indexing a ``GuardedOptions`` object, it will create a new - ``GuardedOptions`` on-demand. This is unlike with a normal - ``Options`` object which returns a reference to a preexisting child - ``Options`` object. You generally should not store - ``GuardedOptions`` by reference. You may be able to pass them by - reference, but this requires you to think carefully about whether - the argument is going to be an r-value or an l-value. - Getting/setting values ~~~~~~~~~~~~~~ @@ -355,7 +267,7 @@ the variables in one place, which could allow some components to overwrite other In ``component.hxx`` there is the function ``get``, which once called sets the "final" and "final-domain" attributes: -.. code-bloc:: ini +.. code-block:: ini T get(const Options& option, const std::string& location = "") { #if CHECKLEVEL >= 1 @@ -609,15 +521,203 @@ Notes: - The species name convention is that the charge state is last, after the `+` or `-` sign: `n2+` is a singly charged nitrogen molecule, while `n+2` is a +2 charged nitrogen atom. + + +Permissions +~~~~~~~~~~~~~~ + +The ``Permissions`` class can be used to store information about which +variables within an ``Options`` object are allowed to be accessed and +for what purpose. This is used to control the variables used by a +``Component``. There are four levels of permissions, expressed in the +`PermissionTypes` `enum `__: + +ReadIfSet + Only allowed to read variable if it is already set. + +Read + Can read the contents of the variable. Assumes it has already been + set. + +Write + Can write variable. Makes no assumption about whether it has already + been written or will be written again in future. + +Final + This will be the last component to write to the variable. Only one + component may have ``Final`` permission for a given variable. + +The order these per permissions are listed in is significant: each +permission implies a component also has all lower permissions. E.g., +writer permission implies read permission as well. + +Permission information for a variable is stored in a +`Permissions::VarRights` object. The overwhelming majority of the +permissions you would want to create can be constructed using one of +the provided convenience-functions. For example:: + + Permissions::VarRights read_e_pressure = readOnly("species:e:pressure"); + Permissions::VarRights write_d_density = readWrite("species:d:density"); + Permissions::VarRights read_e_velocity_in_interior_if_set = + readIfSet("species:e:velocity", Regions::Interior); + +.. doxygengroup:: PermissionFactories + :members: + +This permission data can be used to construct a ``Permissions`` object +describing the permissions for multiple variables.:: + + Permissions p({readOnly("time"), + readOnly("species:e:pressure"), + readWrite("species:e:momentum", Regions::Interior)}); +A permission +applied to a section of an ``Options`` object will apply to all +variables contained within that section, unless a more specific +permission is also set. Therefore, if we have a state with variables +``species:e:pressure``, ``species:e:density``, ``species:e:velocity``, +and ``species:e:momentum``, then the following are equivalent:: + + Permissions p({readOnly("species:e"), + readWrite("species:e:momentum")}); + Permissions p({readOnly("species:e:pressure"), + readOnly("species:e:density"), + readOnly("species:e:velocity"), + readWrite("species:e:momentum")}); + +Variable names can include labels, marked in curly-braces, that will +later be substituted (using `Permissions::substitute` and +`Component::substitutePermissions`). Substitutions are necessary +because, when declaring permissions for a `Component`, you may need to +express that it can access some variable for all species (or all ions, +all neutrals, etc.), but you won't yet know the names of all the +species. For example, if you need to read the density of all species +and write the collision frequency of all ions then you would write:: + + Permissions p({readOnly("species:{all_spcies}:density"), + readWrite("species:{ions}:collision_frequency"}); + +If there are species e, d, d+, h, and h+ then the above will be +equivalent to:: + + Permissions p({readOnly("species:e:density"), + readOnly("species:d:density"), + readOnly("species:d+:density"), + readOnly("species:h:density"), + readOnly("species:h+:density"), + readWrite("species:d+:collision_frequency"), + readWrite("species:h+:collision_frequency")}); + +These substitutions will be performed in +`Component::declareAllSpecies`. See the documentation for that method +for a full list of the substitutions which it can perform. + +It can also be useful to define your own substitutions, to save +repetitive declarations. For example, you could declare read +permissions for electron density, pressure, temperature, velocity, and +momentum as follows:: + + Permissions p({readOnly("species:e:{inputs}"); + p.substitute({"density", "pressure", "temperature", "velocity", "momentum"}); + +This is equivalent to having written:: + + Permissions p({readOnly("species:e:density"), + readOnly("species:e:pressure")}, + readOnly("species:e:temperature")}, + readOnly("species:e:velocity")}, + readOnly("species:e:momentum")}); + +.. doxygenclass:: Permissions + :members: + +Further Implementation Details +`````````````````````````````` +The above information should be sufficient for users that are +developing or modifying components. The following explains in more +detail how permission data is stored and should be read by anyone +looking to modify the `Permissions` or `GuardedOptions` classes. + +The `PermissionTypes` are applied to particular regions of the domain. +This allows, e.g., for there to be read permissions for the interior +of the domain but write permissions for the boundaries. Regions are +expressed using the `Permissions::Regions` enum, which functions as a `bitset +`__. You can combine regions +using bitwise logical operators. + +.. doxygengroup:: RegionsGroup + :members: + +Permission information for a variable gets stored in +`Permissions::AccessRights` objects, which are arrays of +`Regions`. Each element of the array corresponds to information about +a permission level: ``{read_if_set, read, write, final}``. To access +the element for a desired permission level, you can index the array +with the corresponding member of the `PermissionTypes` enum:: + + Permissions::AccessRights rights; + Regions read_regions = rights[PermissionTypes::Read]; + Regions write_regions = rights[PermissionTypes::Write]; + +The contents of each element of an `Permissions::AccessRights` array +is the set of regions for which the permissions apply. For example:: + + Permissions::AccessRights read_boundaries_if_set = + {Regions::Boundaries, Regions::Nowhere, Regions::Nowhere, + Regions::Nowhere}; + Permissions::AccessRights read_interior_write_boundaries = + {Regions::Nowhere, Regions::Interior, Regions::Boundaries, + Regions::Nowhere}; + Permissions::AccessRights final_write_all_regions = + {Regions::Nowhere, Regions::Nowhere, Regions::Nowhere, Regions::All}; + +The `Permissions::VarRights` struct is used to pair a variable name +with a `Permissions::AccessRights` array containing the permission +information for that variable. + + +GuardedOptions +~~~~~~~~~~~~~~ + +``GuardedOptions`` objects combine a `Permissions` object and an +`Options` object. They can be indexed just like normal ``Options`` +objects but will return another ``GuardedOptions``, wrapping the +result. In order to read or write the contents of a ``GuardedOptions`` +object you must use the ``get()`` or ``getWritable()`` methods, +respectively. These will return the underlying (const) ``Options`` +object, if you have the necessary permissions to access it. Otherwise, +they will raise an exception. + +If ``CHECKLEVEL`` is 1 or above, then the ``GuardedOptions`` will track +which variables have actually been accessed. Lists of +unread/unwritten variables can be returned with the ``unreadItems()`` +and ``unwrittenItems()`` methods. If ``CHECKLEVEL`` is zero then +calling these methods will raise an exception. + +.. doxygenclass:: GuardedOptions + :members: + +.. note:: + When indexing a ``GuardedOptions`` object, it will create a new + ``GuardedOptions`` on-demand. This is unlike with a normal + ``Options`` object which returns a reference to a preexisting child + ``Options`` object. You generally should not store + ``GuardedOptions`` by reference. You may be able to pass them by + reference, but this requires you to think carefully about whether + the argument is going to be an r-value or an l-value. + Components ~~~~~~~~~~~~~~ The basic building block of all Hermes-3 models is the `Component`. This defines an interface to a class which takes a state -(a tree of dictionaries/maps), and transforms (modifies) it. After -all components have modified the state in turn, all components may -then implement a `finally` method to take the final state but not +(a tree of dictionaries/maps) and transforms (modifies) it. This is +done by calling the public `Component::transform` method. This will +call the private `Component::transform_impl` method, which must be +overriden for each Component implementation. + +After all components have modified the state in turn, all components +may then implement a `finally` method to take the final state but not modify it. This allows two components to depend on each other, but makes debugging and testing easier by limiting the places where the state can be modified. @@ -647,7 +747,7 @@ file using a code like:: where `MyComponent` is the component class, and "mycomponent" is the name that can be used in the BOUT.inp settings file to create a component of this type. Note that the name can be any string except it -can't contain commas or brackets (), and shouldn't start or end with +can't contain commas or brackets, and shouldn't start or end with whitespace. Inputs to the component constructors are: @@ -662,18 +762,18 @@ The `name` is a string labelling the instance. The `alloptions` tree contains at * `alloptions['units']` All component constructors must pass a `Permissions` object to the -constructor on the `Component::Component` base class. This specifies which -variables will be read/written by the `Component::transform` method and will be -used to construct a `GuardedOptions` object to be passed into -`Component::transform_impl`. The `Permissions` object -(`Component::state_variable_access`) can be further updated in the -body of the constructor of your component, based on what -configurations were specified in ``alloptions``. - -Note that a number of substitutions will be performed on your +`Component::Component` constructor of the base class. The +`Permissions` object can be further updated in the body of your +component's constructor using the `Component::setPermissions` and +`Component::substitutePermissions` methods. It specifies which +variables will be read/written by the `Component::transform_impl` +method. `Component::transform` will use the permissions to construct a +`GuardedOptions` object with which it will call +`Component::transform_impl`. + +As explained above, a number of substitutions will automatically be performed on your permissions, so that you can specify permissions for some variables -for each species. The labels for these substitutions are wrapped in -curly braces. For example, the following permissions would give read +for each species. For example, the following permissions would give read access to pressure for all species and density of ions:: MyComponent::MyComponent(const std::string &name, Options &options, @@ -715,7 +815,7 @@ scheduler looks up the options under the section of that name. This would create two `Component` objects, of type `component1` and `component2`. Each time `Hermes::rhs` is run, the `transform` -functions of `component1` amd then `component2` will be called, +functions of `component1` and then `component2` will be called, followed by their `finally` functions. It is often useful to group components together, for example to diff --git a/include/amjuel_reaction.hxx b/include/amjuel_reaction.hxx index c10e115ed..33a197349 100644 --- a/include/amjuel_reaction.hxx +++ b/include/amjuel_reaction.hxx @@ -43,11 +43,9 @@ struct AmjuelReaction : public Reaction { this->includes_sigma_v_e = amjuel_data.includes_sigma_v_e; // Most of the access information we need is inherited from the parent Reaction class. // The electron velocity will be read if it is set - setPermissions("species:e:velocity", - {Regions::All, Regions::Nowhere, Regions::Nowhere, Regions::Nowhere}); + setPermissions(readIfSet("species:e:velocity")); // The energy source is set for electrons - setPermissions("species:e:energy_source", - {Regions::Nowhere, Regions::Nowhere, Regions::All, Regions::Nowhere}); + setPermissions(readWrite("species:e:energy_source")); std::string heavy_reactant = this->parser->get_species(species_filter::reactants, species_filter::heavy)[0], heavy_product = this->parser->get_species(species_filter::products, diff --git a/include/component.hxx b/include/component.hxx index 5b744cb40..2be5527f0 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -120,21 +120,21 @@ struct Component { /// will use this information to substitute the following placeholders in /// `state_variable_access`: /// - electrons (any electron species) - /// - electrons2 (same as above, used for cross-product) + /// - electrons2 (same as above, used for Cartesian product) /// - neutrals (species with no charge) - /// - neutrals2 (same as above, used for cross-product) + /// - neutrals2 (same as above, used for Cartesian product) /// - positive_ions (ions with a positive charge) - /// - positive_ions2 (same as above, used for cross-product) + /// - positive_ions2 (same as above, used for Cartesian product) /// - negative_ions (ions with a negative charge) - /// - negative_ions2 (same as above, used for cross-product) + /// - negative_ions2 (same as above, used for Cartesian product) /// - ions (all ions, regardless of sign of charge) - /// - ions2 (same as above, used for cross-product) + /// - ions2 (same as above, used for Cartesian product) /// - charged (ions and electrons) - /// - charged2 (same as above, used for cross-product) + /// - charged2 (same as above, used for Cartesian product) /// - non_electrons (ions and neutrals) - /// - non_electrons2 (same as above, used for cross-product) + /// - non_electrons2 (same as above, used for Cartesian product) /// - all_species (ions, neutrals, and electrons) - /// - all_species2 (same as above, used for cross-product) + /// - all_species2 (same as above, used for Cartesian product) /// /// At the end of this function there is a call to /// Permissions::checkNoRemainingSubstitutions. All substitutions @@ -162,10 +162,11 @@ private: /// Information on which state variables the transform method will read and write. Permissions state_variable_access; - /// Modify the given simulation state. All components must - /// implement this function. It will only allow the reading - /// from/writing to state variables with the appropriate permissiosn - /// in `state_variable_access`. + /// The implementation of the transform method. Modify the given + /// simulation state. All components must implement this + /// function. It will only allow the reading from/writing to state + /// variables with the appropriate permissiosn in + /// `state_variable_access`. virtual void transform_impl(GuardedOptions &state) = 0; }; diff --git a/include/permissions.hxx b/include/permissions.hxx index ea407c403..f648e85a9 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -73,7 +73,7 @@ public: /// AccessRights write_boundaries = { Regions::Nowhere, Regions::Nowhere, /// Regions::Boundaries, Regions::Nowhere }; /// AccessRights read_and_write_everywhere = { Regions::Nowhere, - /// Regions::AllReginos, Regions::All, Regions::Nowhere }; + /// Regions::AllRegions, Regions::All, Regions::Nowhere }; /// AccessRights final_write_boundaries_read_interior = { Regions::Nowhere, /// Regions::Interior, Regions::Nowhere, Regions::Boundaries }; /// @@ -298,15 +298,29 @@ inline Permissions::VarRights writeBoundary(std::string varname) { {Regions::Nowhere, Regions::Interior, Regions::Nowhere, Regions::Boundaries}}; } -/// Convenience function to return an object expressing that the -/// variable should have Final write permissions on the boundaries. It -/// will have Read permissions in the interior if the interior is -/// already set. +/// Convenience function to return an object expressing that, if the +/// interior has been set, then the variable should have Final write +/// permissions on the boundaries and read permissions for the +/// interior. +/// +/// FIXME: Currently these permissiosn are not expressed properly, due +/// to limitations in how the permission system. The boundary will +/// have write permission regardless of whether or not the interior is +/// set. inline Permissions::VarRights writeBoundaryIfSet(std::string varname) { return {std::move(varname), {Regions::Interior, Regions::Nowhere, Regions::Nowhere, Regions::Boundaries}}; } +/// Convenience function to return an object expressing that the +/// interior has read-if-set permissions and the boundary has write +/// permissions. This differs from what `writeBoundaryIfSet` +/// is supposed to do because this will write the boundary +/// unconditionally, regardless of whether the interior is set. +inline Permissions::VarRights writeBoundaryReadInteriorIfSet(std::string varname) { + return {std::move(varname), + {Regions::Interior, Regions::Nowhere, Regions::Nowhere, Regions::Boundaries}}; +} /// @} // FIXME: Ideally there would be some way to express write permissions only if set diff --git a/include/sheath_boundary_simple.hxx b/include/sheath_boundary_simple.hxx index fa28f610e..76ea18bce 100644 --- a/include/sheath_boundary_simple.hxx +++ b/include/sheath_boundary_simple.hxx @@ -66,7 +66,6 @@ private: /// - pressure Optional /// - velocity Optional /// - mass Optional - /// - adiabatic Optional. Ratio of specific heats, default 5/3. /// - if charge is set (i.e. not neutrals) /// - charge /// - mass @@ -75,7 +74,6 @@ private: /// - pressure Optional /// - velocity Optional. Default 0 /// - momentum Optional. Default mass * density * velocity - /// - adiabatic Optional. Ratio of specific heats, default 5/3. /// - fields /// - phi Optional. If not set, calculated at boundary (see note below) /// diff --git a/src/evolve_density.cxx b/src/evolve_density.cxx index 979a85e3e..f4dbda028 100644 --- a/src/evolve_density.cxx +++ b/src/evolve_density.cxx @@ -138,6 +138,10 @@ EvolveDensity::EvolveDensity(std::string name, Options& alloptions, Solver* solv if (low_n_diffuse) { outputs.push_back("low_n_coeff"); } + + if (source_time_dependent) { + setPermissions(readOnly("time")); + } substitutePermissions("name", {name}); substitutePermissions("outputs", outputs); } diff --git a/src/evolve_pressure.cxx b/src/evolve_pressure.cxx index f4ce82b98..609aa2fd7 100644 --- a/src/evolve_pressure.cxx +++ b/src/evolve_pressure.cxx @@ -165,6 +165,9 @@ EvolvePressure::EvolvePressure(std::string name, Options& alloptions, Solver* so .doc("Include parallel heat conduction?") .withDefault(true); + if (source_time_dependent) { + setPermissions(readOnly("time")); + } substitutePermissions("name", {name}); substitutePermissions("inputs", {"density"}); substitutePermissions("outputs", {"pressure", "temperature"}); diff --git a/src/permissions.cxx b/src/permissions.cxx index 873062824..04787b879 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -96,7 +96,7 @@ void Permissions::substitute(const std::string& label, it = variable_permissions.erase(it); for (const std::string& val : substitutions) { const std::string newname = replaceAll(varname, pattern, val); - // Do not overwrite permissiosn that are already set + // Do not overwrite permissions that are already set if (variable_permissions.count(newname) == 0) { variable_permissions[newname] = access; } diff --git a/src/sheath_boundary.cxx b/src/sheath_boundary.cxx index 75273cac3..9ade3dc03 100644 --- a/src/sheath_boundary.cxx +++ b/src/sheath_boundary.cxx @@ -46,19 +46,18 @@ BoutReal limitFree(BoutReal fm, BoutReal fc) { } // namespace SheathBoundary::SheathBoundary(std::string name, Options& alloptions, Solver*) - // FIXME: writeBoundaryIfSet doesn't really express that boundary - // should only be written if the interior is set. Instead it just - // give the permission readIfSet to the interior and writeFinal to - // the boundary. : Component({ + readIfSet("species:{all_species}:charge"), readIfSet("species:e:{e_whole_domain}"), writeBoundary("species:e:{e_boundary}"), readWrite("species:e:energy_source"), writeBoundaryIfSet("species:e:{e_optional}"), - readIfSet("species:{ions}:{ion_whole_domain}"), + writeBoundaryReadInteriorIfSet("species:e:pressure"), + readIfSet("species:{ions}:adiabatic"), readOnly("species:{ions}:AA"), readWrite("species:{ions}:energy_source"), writeBoundary("species:{ions}:{ion_boundary}"), + writeBoundaryReadInteriorIfSet("species:{ions}:pressure"), writeBoundaryIfSet("species:{ions}:{ion_optional}"), }) { AUTO_TRACE(); @@ -110,22 +109,13 @@ SheathBoundary::SheathBoundary(std::string name, Options& alloptions, Solver*) .doc("Apply a floor to wall potential when calculating Ve?") .withDefault(true); - substitutePermissions("e_whole_domain", {"AA", "charge", "adiabatic"}); + substitutePermissions("e_whole_domain", {"AA", "adiabatic"}); substitutePermissions("e_boundary", {"density", "temperature"}); - substitutePermissions("e_optional", {"pressure", "velocity"}); - substitutePermissions("ion_whole_domain", {"charge", "adiabatic"}); + substitutePermissions("e_optional", {"velocity", "momentum"}); substitutePermissions("ion_boundary", {"density", "temperature"}); - // FIXME: velocity and momentum will only be set on boundaries if already set on - // interior - substitutePermissions("ion_optional", {"pressure", "velocity", "momentum"}); - // FIXME: The two results of the ternary are actually the same; need - // to change what writeBoundaryIfSet returns (and how we model - // permissions, for that matter) - setPermissions(always_set_phi - ? Permissions::VarRights({"fields:phi", - {Regions::Interior, Regions::Nowhere, - Regions::Nowhere, Regions::Boundaries}}) - : writeBoundaryIfSet("fields:phi")); + substitutePermissions("ion_optional", {"velocity", "momentum"}); + setPermissions(always_set_phi ? writeBoundaryReadInteriorIfSet("fields:phi") + : writeBoundaryIfSet("fields:phi")); } void SheathBoundary::transform_impl(GuardedOptions& state) { diff --git a/src/sheath_boundary_insulating.cxx b/src/sheath_boundary_insulating.cxx index 1a315fa52..f3a9047f3 100644 --- a/src/sheath_boundary_insulating.cxx +++ b/src/sheath_boundary_insulating.cxx @@ -51,19 +51,18 @@ BoutReal limitFree(BoutReal fm, BoutReal fc) { SheathBoundaryInsulating::SheathBoundaryInsulating(std::string name, Options& alloptions, Solver*) : Component({ + readIfSet("species:{all_species}:charge"), readIfSet("species:e:{e_whole_domain}"), writeBoundary("species:e:{e_boundary}"), readWrite("species:e:energy_source"), writeBoundaryIfSet("species:e:{e_optional}"), - {"species:e:pressure", - {Regions::Interior, Regions::Nowhere, Regions::Boundaries, Regions::Nowhere}}, - readIfSet("species:{ions}:{ion_whole_domain}"), + writeBoundaryReadInteriorIfSet("species:e:pressure"), + readIfSet("species:{ions}:adiabatic"), readOnly("species:{ions}:AA"), readWrite("species:{ions}:energy_source"), - {"species:{ions}:pressure", - {Regions::Interior, Regions::Nowhere, Regions::Boundaries, Regions::Nowhere}}, writeBoundary("species:{ions}:{ion_boundary}"), writeBoundaryIfSet("species:{ions}:{ion_optional}"), + writeBoundaryReadInteriorIfSet("species:{ions}:pressure"), }) { AUTO_TRACE(); @@ -93,13 +92,10 @@ SheathBoundaryInsulating::SheathBoundaryInsulating(std::string name, Options& al .doc("Electron sheath heat transmission coefficient") .withDefault(3.5); - substitutePermissions("e_whole_domain", {"AA", "charge", "adiabatic"}); + substitutePermissions("e_whole_domain", {"AA", "adiabatic"}); substitutePermissions("e_boundary", {"density", "temperature"}); substitutePermissions("e_optional", {"velocity", "momentum"}); - substitutePermissions("ion_whole_domain", {"charge", "adiabatic"}); substitutePermissions("ion_boundary", {"density", "temperature"}); - // FIXME: velocity and momentum will only be set on boundaries if already set on - // interior substitutePermissions("ion_optional", {"velocity", "momentum"}); setPermissions(writeBoundaryIfSet("fields:phi")); } diff --git a/src/sheath_boundary_simple.cxx b/src/sheath_boundary_simple.cxx index c856b0809..e1fd58ce5 100644 --- a/src/sheath_boundary_simple.cxx +++ b/src/sheath_boundary_simple.cxx @@ -64,17 +64,14 @@ SheathBoundarySimple::SheathBoundarySimple(std::string name, Options& alloptions readWrite("species:e:energy_source"), readWrite("species:e:energy_flow_ylow"), writeBoundaryIfSet("species:e:{e_optional}"), - {"species:e:pressure", - {Regions::Interior, Regions::Nowhere, Regions::Boundaries, Regions::Nowhere}}, - // FIXME: These only applies to ions, not to all species - readIfSet("species:{all_species}:{ion_whole_domain}"), - readOnly("species:{all_species}:AA"), - readWrite("species:{all_species}:energy_source"), - readWrite("species:{all_species}:energy_flow_ylow"), - {"species:{all_species}:pressure", - {Regions::Interior, Regions::Nowhere, Regions::Boundaries, Regions::Nowhere}}, - writeBoundary("species:{all_species}:{ion_boundary}"), - writeBoundaryIfSet("species:{all_species}:{ion_optional}"), + writeBoundaryReadInteriorIfSet("species:e:pressure"), + readIfSet("species:{all_species}:charge"), + readOnly("species:{ions}:AA"), + readWrite("species:{ions}:energy_source"), + readWrite("species:{ions}:energy_flow_ylow"), + writeBoundary("species:{ions}:{ion_boundary}"), + writeBoundaryReadInteriorIfSet("species:{ions}:pressure"), + writeBoundaryIfSet("species:{ions}:{ion_optional}"), }) { AUTO_TRACE(); @@ -148,15 +145,13 @@ SheathBoundarySimple::SheathBoundarySimple(std::string name, Options& alloptions .doc("Save additional output diagnostics") .withDefault(false); - substitutePermissions("e_whole_domain", {"AA", "charge", "adiabatic"}); + substitutePermissions("e_whole_domain", {"AA", "charge"}); substitutePermissions("e_boundary", {"density", "temperature"}); substitutePermissions("e_optional", {"velocity", "momentum"}); - substitutePermissions("ion_whole_domain", {"charge", "adiabatic"}); substitutePermissions("ion_boundary", {"density", "temperature"}); - // FIXME: velocity and momentum will only be set on boundaries if already set on - // interior substitutePermissions("ion_optional", {"velocity", "momentum"}); - setPermissions(writeBoundaryIfSet("fields:phi")); + setPermissions(always_set_phi ? writeBoundaryReadInteriorIfSet("fields:phi") + : writeBoundaryIfSet("fields:phi")); } void SheathBoundarySimple::transform_impl(GuardedOptions& state) { From c754df7ecfa598b8d4c14886b76ef3dac23e1b61 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Mon, 15 Dec 2025 17:23:45 +0000 Subject: [PATCH 41/63] Use some template black magic to avoid duplication - use forwarding reference `T&&` to avoid duplicating overloads on _l-values_/_r-values_ - use `std::enable_if` to hide templated functions from all `T` except `GuardedOptions` - use `decltype(auto)` and `return std::forward` to ensure return type is the same value category as input argument --- include/component.hxx | 96 ++++++++++++++++++------------------------- 1 file changed, 39 insertions(+), 57 deletions(-) diff --git a/include/component.hxx b/include/component.hxx index 2be5527f0..bd3dfe351 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -226,6 +227,12 @@ T getNonFinal(const GuardedOptions & option) { #define TOSTRING(x) TOSTRING_(x) +namespace hermes { +/// Enable a function if and only if `T` is a (subclass of) `GuardedOptions` +template +using EnableIfGuardedOption = std::enable_if_t>; +} + /// Faster non-printing getter for Options /// If this fails, it will throw BoutException /// @@ -318,13 +325,10 @@ T getNoBoundary(const Options& option, [[maybe_unused]] const std::string& locat #endif return getNonFinal(option); } -template -T getNoBoundary(const GuardedOptions & option, const std::string& location = "") { - return getNoBoundary(option.get(Regions::Interior), location); -} -template -T getNoBoundary(const GuardedOptions && option, const std::string& location = "") { - return getNoBoundary(option.get(Regions::Interior), location); + +template> +T getNoBoundary(GO&& option, const std::string& location = "") { + return getNoBoundary(std::forward(option).get(Regions::Interior), location); } #if CHECKLEVEL >= 1 @@ -399,15 +403,11 @@ Options& set(Options& option, T value) { option.force(std::move(value)); return option; } -template -GuardedOptions & set(GuardedOptions & option, T value) { - set(option.getWritable(), value); - return option; -} -template -GuardedOptions && set(GuardedOptions && option, T value) { - set(option.getWritable(), value); - return std::move(option); + +template> +decltype(auto) set(GO&& option, T value) { + set(std::forward(option).getWritable(), value); + return std::forward(option); } /// Set values in an option. This could be optimised, but @@ -430,15 +430,11 @@ Options& setBoundary(Options& option, T value) { option.force(std::move(value)); return option; } -template -GuardedOptions & setBoundary(GuardedOptions & option, T value) { - setBoundary(option.getWritable(Regions::Boundaries), value); - return option; -} -template -GuardedOptions && setBoundary(GuardedOptions && option, T value) { - setBoundary(option.getWritable(Regions::Boundaries), value); - return std::move(option); + +template> +decltype(auto) setBoundary(GO&& option, T value) { + setBoundary(std::forward(option).getWritable(Regions::Boundaries), value); + return std::forward(option); } /// Add value to a given option. If not already set, treats @@ -463,15 +459,11 @@ Options& add(Options& option, T value) { } } } -template -GuardedOptions & add(GuardedOptions & option, T value) { - add(option.getWritable(), value); - return option; -} -template -GuardedOptions && add(GuardedOptions && option, T value) { - add(option.getWritable(), value); - return std::move(option); + +template> +decltype(auto) add(GO&& option, T value) { + add(std::forward(option).getWritable(), value); + return std::forward(option); } /// Add value to a given option. If not already set, treats @@ -493,15 +485,11 @@ Options& subtract(Options& option, T value) { } } } -template -GuardedOptions & subtract(GuardedOptions & option, T value) { - subtract(option.getWritable(), value); - return option; -} -template -GuardedOptions && subtract(GuardedOptions && option, T value) { - subtract(option.getWritable(), value); - return std::move(option); + +template> +decltype(auto) subtract(GO&& option, T value) { + subtract(std::forward(option).getWritable(), value); + return std::forward(option); } template @@ -509,13 +497,10 @@ void set_with_attrs(Options& option, T value, std::initializer_list -void set_with_attrs(GuardedOptions & option, T value, std::initializer_list> attrs) { - set_with_attrs(option.getWritable(), value, attrs); -} -template -void set_with_attrs(GuardedOptions && option, T value, std::initializer_list> attrs) { - set_with_attrs(option.getWritable(), value, attrs); + +template> +void set_with_attrs(GO&& option, T value, std::initializer_list> attrs) { + set_with_attrs(std::forward(option).getWritable(), value, attrs); } #if CHECKLEVEL >= 1 @@ -527,13 +512,10 @@ inline void set_with_attrs(Options& option, Field3D value, std::initializer_list option.force(value); option.setAttributes(attrs); } -template<> -inline void set_with_attrs(GuardedOptions & option, Field3D value, std::initializer_list> attrs) { - set_with_attrs(option.getWritable(), std::move(value), attrs); -} -template<> -inline void set_with_attrs(GuardedOptions && option, Field3D value, std::initializer_list> attrs) { - set_with_attrs(option.getWritable(), std::move(value), attrs); + +template> +inline void set_with_attrs(GO&& option, Field3D value, std::initializer_list> attrs) { + set_with_attrs(std::forward(option).getWritable(), std::move(value), attrs); } #endif From e8f17dfc273bf49207870ff4f07e3b5dd33f0b88 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Mon, 5 Jan 2026 18:53:31 +0000 Subject: [PATCH 42/63] Reordered/changed heading of docs so reads better as introduction --- docs/sphinx/closure.rst | 4 +- docs/sphinx/developer.rst | 368 ++++++++++++++++++++------------------ 2 files changed, 196 insertions(+), 176 deletions(-) diff --git a/docs/sphinx/closure.rst b/docs/sphinx/closure.rst index 7ce0144be..b235ea71d 100644 --- a/docs/sphinx/closure.rst +++ b/docs/sphinx/closure.rst @@ -262,7 +262,7 @@ Input This top-level component calculates the frictional forces between each pair of species for which collisional frequencies have been calculated -(see :ref:`Braginskii Collisions`). As such, it must be run after +(see `Braginskii Collisions component`_). As such, it must be run after `braginskii_collisions`. If the option `frictional_heating` is enabled then it will also calculate the energy source arising from friction. @@ -346,7 +346,7 @@ Braginskii Heat Exchange Input ----- This top-level component calculates the heat exchange between species -due to collisions (see :ref:`Braginskii Collisions`). As such, it must be run after +due to collisions (see `Braginskii Collisions component`_). As such, it must be run after `braginskii_collisions`. There are no configurations for this component. Theory diff --git a/docs/sphinx/developer.rst b/docs/sphinx/developer.rst index 2dc0f56a2..f2d9a9a12 100644 --- a/docs/sphinx/developer.rst +++ b/docs/sphinx/developer.rst @@ -522,6 +522,152 @@ Notes: sign: `n2+` is a singly charged nitrogen molecule, while `n+2` is a +2 charged nitrogen atom. + +Components +~~~~~~~~~~~~~~ + +The basic building block of all Hermes-3 models is the +`Component`. This defines an interface to a class which takes a state +(a tree of dictionaries/maps) and transforms (modifies) it. This is +done by calling the public `Component::transform` method. This will +call the private `Component::transform_impl` method, which must be +overriden for each Component implementation. + +After all components have modified the state in turn, all components +may then implement a `finally` method to take the final state but not +modify it. This allows two components to depend on each other, but +makes debugging and testing easier by limiting the places where the +state can be modified. + +.. doxygenstruct:: Component + :members: + :protected-members: + :private-members: + +Components are usually defined in separate files; sometimes multiple +components in one file if they are small and related to each other (e.g. +atomic rates for the same species). To be able to create components, +they need to be registered in the factory. This is done in the header +file using a code like:: + + #include "component.hxx" + + struct MyComponent : public Component { + MyComponent(const std::string &name, Options &options, Solver *solver); + ... + }; + + namespace { + RegisterComponent registercomponentmine("mycomponent"); + } + +where `MyComponent` is the component class, and "mycomponent" is the +name that can be used in the BOUT.inp settings file to create a +component of this type. Note that the name can be any string except it +can't contain commas or brackets, and shouldn't start or end with +whitespace. + +Inputs to the component constructors are: + +* `name` +* `alloptions` +* `solver` + +The `name` is a string labelling the instance. The `alloptions` tree contains at least: + +* `alloptions[name]` options for this instance +* `alloptions['units']` + + +Component Permissions +````````````````````` + +All component constructors must pass a `Permissions` object (see +below) to the constructor on the `Component::Component` base +class. This specifies which variables will be read/written by the +`Component::transform` method and will be used to construct a +`GuardedOptions` object to be passed into +`Component::transform_impl`. The `Permissions` object can be further +updated in the body of the constructor of your component using the +`Component::setPermissions` and `Component::substitutePermissions` +methods. You should give read and write permissions to the minimum +number of variables necessary, to avoid circular dependencies arising +among components. + +A number of substitutions will automatically be performed on your +permissions (see `Permission Substitution`_), so that you can specify +permissions for some variables for each species. For example, the +following permissions would give read access to pressure for all +species and density of ions:: + + MyComponent::MyComponent(const std::string &name, Options &options, + Solver *solver) : Component({readOnly("species:{all_species}:pressure"), + readOnly("species:{ions}:density")}) {} + +See the documentation for `Component::declareAllSpecies` for a list of +all substitutions that will be performed. + + +Component scheduler +~~~~~~~~~~~~~~ + +The simulation model is created in `Hermes::init` by a call to the `ComponentScheduler`:: + + scheduler = ComponentScheduler::create(options, Options::root(), solver); + +and then in `Hermes::rhs` the components are run by a call:: + + scheduler->transform(state); + +The call to `ComponentScheduler::create` treats the "components" +option as a comma-separated list of names. The order of the components +is the order that they are run in. For each name in the list, the +scheduler looks up the options under the section of that name. + +.. code-block:: ini + + [hermes] + components = component1, component2 + + [component1] + + # options to control component1 + + [component2] + + # options to control component2 + +This would create two `Component` objects, of type `component1` and +`component2`. Each time `Hermes::rhs` is run, the `transform` +functions of `component1` and then `component2` will be called, +followed by their `finally` functions. + +It is often useful to group components together, for example to +define the governing equations for different species. A `type` setting +in the option section overrides the name of the section, and can be another list +of components + +.. code-block:: ini + + [hermes] + components = group1, component3 + + [group1] + type = component1, component2 + + # options to control component1 and component2 + + [component3] + + # options to control component3 + +This will create three components, which will be run in the order +`component1`, `component2`, `component3`: First all the components +in `group1`, and then `component3`. + +.. doxygenclass:: ComponentScheduler + :members: + Permissions ~~~~~~~~~~~~~~ @@ -529,27 +675,21 @@ Permissions The ``Permissions`` class can be used to store information about which variables within an ``Options`` object are allowed to be accessed and for what purpose. This is used to control the variables used by a -``Component``. There are four levels of permissions, expressed in the +``Component``. There is a hierarchy of four types of increasing +permission. These are expressed using the `PermissionTypes` `enum `__: -ReadIfSet - Only allowed to read variable if it is already set. - -Read - Can read the contents of the variable. Assumes it has already been - set. - -Write - Can write variable. Makes no assumption about whether it has already - been written or will be written again in future. - -Final - This will be the last component to write to the variable. Only one - component may have ``Final`` permission for a given variable. +#. **ReadIfSet:** Only allowed to read variable if it is already set. +#. **Read:** Can read the contents of the variable. Assumes it has already been set. +#. **Write:** Can write variable. Makes no assumption about whether it has already been written or will be written again in future. +#. **Final:** This will be the last component to write to the variable. Only one component may have ``Final`` permission for a given variable. The order these per permissions are listed in is significant: each -permission implies a component also has all lower permissions. E.g., -writer permission implies read permission as well. +higher permission implies a component also has all lower permissions. E.g., +write permission implies read permission as well. + +Declaring Permissions for Particular Variables +`````````````````````````````````````````````` Permission information for a variable is stored in a `Permissions::VarRights` object. The overwhelming majority of the @@ -561,18 +701,23 @@ the provided convenience-functions. For example:: Permissions::VarRights read_e_velocity_in_interior_if_set = readIfSet("species:e:velocity", Regions::Interior); -.. doxygengroup:: PermissionFactories - :members: +Permissions can be set to apply only to a particular region of the +domain (e.g., the boundary or the interior) using a `Regions` enum +(see `Specifying a Region`_). + +Creating Permissions Objects +```````````````````````````` -This permission data can be used to construct a ``Permissions`` object -describing the permissions for multiple variables.:: +Permission data like that created in the previous example can be used +to construct a ``Permissions`` object. These objects describe the +permissions for multiple variables.:: Permissions p({readOnly("time"), readOnly("species:e:pressure"), readWrite("species:e:momentum", Regions::Interior)}); -A permission -applied to a section of an ``Options`` object will apply to all -variables contained within that section, unless a more specific + +A permission applied to a section of an ``Options`` object will apply +to all variables contained within that section, unless a more specific permission is also set. Therefore, if we have a state with variables ``species:e:pressure``, ``species:e:density``, ``species:e:velocity``, and ``species:e:momentum``, then the following are equivalent:: @@ -584,6 +729,22 @@ and ``species:e:momentum``, then the following are equivalent:: readOnly("species:e:velocity"), readWrite("species:e:momentum")}); +Specifying a Region +``````````````````` + +The `PermissionTypes` are applied to particular regions of the domain. +This allows, e.g., for there to be read permissions for the interior +of the domain but write permissions for the boundaries. Regions are +expressed using the `Permissions::Regions` enum, which functions as a `bitset +`__. You can combine regions +using bitwise logical operators. + +.. doxygengroup:: RegionsGroup + :members: + +Permission Substitution +``````````````````````` + Variable names can include labels, marked in curly-braces, that will later be substituted (using `Permissions::substitute` and `Component::substitutePermissions`). Substitutions are necessary @@ -627,26 +788,25 @@ This is equivalent to having written:: readOnly("species:e:velocity")}, readOnly("species:e:momentum")}); +Permission Factory Functions +```````````````````````````` + +.. doxygengroup:: PermissionFactories + :members: + +Permissions Class +````````````````` .. doxygenclass:: Permissions :members: Further Implementation Details `````````````````````````````` + The above information should be sufficient for users that are developing or modifying components. The following explains in more detail how permission data is stored and should be read by anyone looking to modify the `Permissions` or `GuardedOptions` classes. -The `PermissionTypes` are applied to particular regions of the domain. -This allows, e.g., for there to be read permissions for the interior -of the domain but write permissions for the boundaries. Regions are -expressed using the `Permissions::Regions` enum, which functions as a `bitset -`__. You can combine regions -using bitwise logical operators. - -.. doxygengroup:: RegionsGroup - :members: - Permission information for a variable gets stored in `Permissions::AccessRights` objects, which are arrays of `Regions`. Each element of the array corresponds to information about @@ -705,146 +865,6 @@ calling these methods will raise an exception. reference, but this requires you to think carefully about whether the argument is going to be an r-value or an l-value. - -Components -~~~~~~~~~~~~~~ - -The basic building block of all Hermes-3 models is the -`Component`. This defines an interface to a class which takes a state -(a tree of dictionaries/maps) and transforms (modifies) it. This is -done by calling the public `Component::transform` method. This will -call the private `Component::transform_impl` method, which must be -overriden for each Component implementation. - -After all components have modified the state in turn, all components -may then implement a `finally` method to take the final state but not -modify it. This allows two components to depend on each other, but -makes debugging and testing easier by limiting the places where the -state can be modified. - -.. doxygenstruct:: Component - :members: - :protected-members: - :private-members: - -Components are usually defined in separate files; sometimes multiple -components in one file if they are small and related to each other (e.g. -atomic rates for the same species). To be able to create components, -they need to be registered in the factory. This is done in the header -file using a code like:: - - #include "component.hxx" - - struct MyComponent : public Component { - MyComponent(const std::string &name, Options &options, Solver *solver); - ... - }; - - namespace { - RegisterComponent registercomponentmine("mycomponent"); - } - -where `MyComponent` is the component class, and "mycomponent" is the -name that can be used in the BOUT.inp settings file to create a -component of this type. Note that the name can be any string except it -can't contain commas or brackets, and shouldn't start or end with -whitespace. - -Inputs to the component constructors are: - -* `name` -* `alloptions` -* `solver` - -The `name` is a string labelling the instance. The `alloptions` tree contains at least: - -* `alloptions[name]` options for this instance -* `alloptions['units']` - -All component constructors must pass a `Permissions` object to the -`Component::Component` constructor of the base class. The -`Permissions` object can be further updated in the body of your -component's constructor using the `Component::setPermissions` and -`Component::substitutePermissions` methods. It specifies which -variables will be read/written by the `Component::transform_impl` -method. `Component::transform` will use the permissions to construct a -`GuardedOptions` object with which it will call -`Component::transform_impl`. - -As explained above, a number of substitutions will automatically be performed on your -permissions, so that you can specify permissions for some variables -for each species. For example, the following permissions would give read -access to pressure for all species and density of ions:: - - MyComponent::MyComponent(const std::string &name, Options &options, - Solver *solver) : Component({readOnly("species:{all_species}:pressure"), - readOnly("species:{ions}:density")}) {} - -See the documentation for `Component::declareAllSpecies` for a list of -all substitutions that will be performed. - - -Component scheduler -~~~~~~~~~~~~~~ - -The simulation model is created in `Hermes::init` by a call to the `ComponentScheduler`:: - - scheduler = ComponentScheduler::create(options, Options::root(), solver); - -and then in `Hermes::rhs` the components are run by a call:: - - scheduler->transform(state); - -The call to `ComponentScheduler::create` treats the "components" -option as a comma-separated list of names. The order of the components -is the order that they are run in. For each name in the list, the -scheduler looks up the options under the section of that name. - -.. code-block:: ini - - [hermes] - components = component1, component2 - - [component1] - - # options to control component1 - - [component2] - - # options to control component2 - -This would create two `Component` objects, of type `component1` and -`component2`. Each time `Hermes::rhs` is run, the `transform` -functions of `component1` and then `component2` will be called, -followed by their `finally` functions. - -It is often useful to group components together, for example to -define the governing equations for different species. A `type` setting -in the option section overrides the name of the section, and can be another list -of components - -.. code-block:: ini - - [hermes] - components = group1, component3 - - [group1] - type = component1, component2 - - # options to control component1 and component2 - - [component3] - - # options to control component3 - -This will create three components, which will be run in the order -`component1`, `component2`, `component3`: First all the components -in `group1`, and then `component3`. - -.. doxygenclass:: ComponentScheduler - :members: - - .. _sec-tests: Tests From a2ac1e8a88e6d02e71ac715c3b75d09a7e04a4a9 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Tue, 6 Jan 2026 15:52:15 +0000 Subject: [PATCH 43/63] Fix issues when compiling with CHECKLEVEL 0 - Unused arguments in some of the functions for GuardedOptions - Unit tests expecting an exception to be thrown, but this only happens when checking is done. --- src/component.cxx | 4 ++-- src/guarded_options.cxx | 4 ++-- tests/unit/test_evolve_pressure.cxx | 2 ++ tests/unit/test_guarded_options.cxx | 6 ++++++ 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/component.cxx b/src/component.cxx index 61a2d3f61..b1c3db00d 100644 --- a/src/component.cxx +++ b/src/component.cxx @@ -66,7 +66,7 @@ bool isSetFinal(const Options& option, [[maybe_unused]] const std::string& locat return option.isSet(); } -bool isSetFinal(const GuardedOptions & option, const std::string& location) { +bool isSetFinal(const GuardedOptions & option, [[maybe_unused]] const std::string& location) { const bool set = option.isSet(); #if CHECKLEVEL >= 1 const PermissionTypes perm = option.getHighestPermission(); @@ -89,7 +89,7 @@ bool isSetFinalNoBoundary(const Options& option, [[maybe_unused]] const std::str return option.isSet(); } -bool isSetFinalNoBoundary(const GuardedOptions & option, const std::string& location) { +bool isSetFinalNoBoundary(const GuardedOptions & option, [[maybe_unused]] const std::string& location) { const bool set = option.isSet(); #if CHECKLEVEL >= 1 const PermissionTypes perm = option.getHighestPermission(Regions::Interior); diff --git a/src/guarded_options.cxx b/src/guarded_options.cxx index da6225499..ed4cd248a 100644 --- a/src/guarded_options.cxx +++ b/src/guarded_options.cxx @@ -72,7 +72,7 @@ void updateAccessRecords(std::map& records, const std::str } } -const Options& GuardedOptions::get(Regions region) const { +const Options& GuardedOptions::get([[maybe_unused]] Regions region) const { #if CHECKLEVEL >= 1 const std::string name = options->str(); auto [permission, varname] = permissions->getHighestPermission(name, region); @@ -90,7 +90,7 @@ const Options& GuardedOptions::get(Regions region) const { #endif } -Options& GuardedOptions::getWritable(Regions region) { +Options& GuardedOptions::getWritable([[maybe_unused]] Regions region) { #if CHECKLEVEL >= 1 const std::string name = options->str(); auto [access, varname] = permissions->canAccess(name, PermissionTypes::Write, region); diff --git a/tests/unit/test_evolve_pressure.cxx b/tests/unit/test_evolve_pressure.cxx index e55eecc9a..c41778682 100644 --- a/tests/unit/test_evolve_pressure.cxx +++ b/tests/unit/test_evolve_pressure.cxx @@ -63,8 +63,10 @@ TEST_F(EvolvePressureTest, Finally) { {"pressure", 1.0}, {"temperature", 1.0}, {"pressure_source", 0.5}}}}}}; +#if CHECKLEVEL >= 1 // Throws exception due to pressure_source EXPECT_THROW(component.finally(state), BoutException); +#endif const Options state2 = {{"species", {{"i", diff --git a/tests/unit/test_guarded_options.cxx b/tests/unit/test_guarded_options.cxx index bb653bc1b..c4399adc2 100644 --- a/tests/unit/test_guarded_options.cxx +++ b/tests/unit/test_guarded_options.cxx @@ -65,6 +65,7 @@ TEST_F(GuardedOptionsTests, TestGet) { .isSet()); } +#if CHECKLEVEL >= 1 TEST_F(GuardedOptionsTests, TestGetException) { EXPECT_THROW(guarded_opts["species:he:AA"].get(), BoutException); EXPECT_THROW(guarded_opts["species"]["he"]["temperature"].get(), BoutException); @@ -78,6 +79,7 @@ TEST_F(GuardedOptionsTests, TestGetException) { EXPECT_THROW(guarded_opts["no_permission"].get(), BoutException); EXPECT_THROW(guarded_opts["species:d+:velocity"].get(), BoutException); } +#endif TEST_F(GuardedOptionsTests, TestGetWritable) { auto& he_pressure = guarded_opts["species:he:pressure"].getWritable(Regions::Interior); @@ -111,6 +113,7 @@ TEST_F(GuardedOptionsTests, TestGetWritable) { EXPECT_EQ(opts["species:d:collision_frequencies:d_t+_coll"], 14); } +#if CHECKLEVEL >= 1 TEST_F(GuardedOptionsTests, TestGetWritableException) { EXPECT_THROW(guarded_opts["species"]["he"]["temperature"].getWritable(), BoutException); EXPECT_THROW(guarded_opts["unset"].getWritable(), BoutException); @@ -195,6 +198,7 @@ TEST_F(GuardedOptionsTests, TestUnwrittenItems) { guarded_opts["species:d:pressure"].getWritable(Regions::Interior); EXPECT_EQ(guarded_opts.unwrittenItems(), expected3); } +#endif TEST_F(GuardedOptionsTests, TestNullOptions) { EXPECT_THROW(GuardedOptions(nullptr, &permissions), BoutException); @@ -210,8 +214,10 @@ TEST_F(GuardedOptionsTests, TestGetChildren) { EXPECT_EQ(guarded_children.count("he"), 1); EXPECT_EQ(guarded_children.count("d"), 1); EXPECT_EQ(&(guarded_children.at("d").get()), &(opts["species"]["d"])); +#if CHECKLEVEL >= 1 // We do not have access to the whole "he" section EXPECT_THROW(guarded_children.at("he").get(), BoutException); +#endif } TEST_F(GuardedOptionsTests, TestIsThisSection) { From 7ef0f04e15d8846f259ee02cc78994e5c6a375de Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Fri, 31 Oct 2025 16:44:47 +0000 Subject: [PATCH 44/63] Switch transform method to using GuardedOptions Note that components still aren't setting up any permissions, so there would be runtime errors when executing the transform methods. Furthermore, there are some missing methods I realise I need for GuardedOptions. The unit tests are also failing to compile, although I don't understand what the problem is for them, yet. --- src/amjuel_reaction.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/amjuel_reaction.cxx b/src/amjuel_reaction.cxx index bbb9bff9e..f0fe74b98 100644 --- a/src/amjuel_reaction.cxx +++ b/src/amjuel_reaction.cxx @@ -120,7 +120,7 @@ void AmjuelReaction::transform_additional(GuardedOptions& state, Field3D& reacti Field3D T_e = get(electron["temperature"]); const int e_pop_change = this->parser->get_stoich().at("e"); if (e_pop_change != 0) { - if (electron.isSet("velocity")) { + if (IS_SET(electron["velocity"])) { // Transfer of electron kinetic to thermal energy due to density source // For ionisation: // Electrons with zero average velocity are created, diluting the kinetic energy. From 25d45a81fe43fd92c796169125c75862be0d1724 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Wed, 26 Nov 2025 13:44:02 +0000 Subject: [PATCH 45/63] Added unit test for topological sorting of components --- include/permissions.hxx | 2 + src/component_scheduler.cxx | 12 ++ src/permissions.cxx | 6 + tests/unit/test_component_scheduler.cxx | 188 ++++++++++++++++++++++++ 4 files changed, 208 insertions(+) diff --git a/include/permissions.hxx b/include/permissions.hxx index f648e85a9..33e4531b7 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -337,3 +337,5 @@ std::ostream& operator<<(std::ostream& os, const Permissions& permissions); /// behaviour if the input is corrupted; an exception may be thrown or /// the permissions that are read may be incomplete. std::istream& operator>>(std::istream& is, Permissions& permissions); + +std::string toString(const Permissions& value); diff --git a/src/component_scheduler.cxx b/src/component_scheduler.cxx index abaf9e6b5..4fb39e8f8 100644 --- a/src/component_scheduler.cxx +++ b/src/component_scheduler.cxx @@ -79,6 +79,18 @@ ComponentScheduler::ComponentScheduler(Options &scheduler_options, } } +// Use index to identify component +// std::map> dependencies; +// std::map var_written_by, var_final_written_by, read_dependencies; + +// Assemble the maps of which components write each variable +// Assemble read_dependencies using values from var_final_written_by, if present, else +// from var_written_by For each component +// For each write-final var, create dependency between component and all components that +// write it For each read-if-set var that has a write-final, create dependency between +// component and (final) writer(s) For each read-var create a dependency between +// component and (final) writers; if none then raise exception + std::unique_ptr ComponentScheduler::create(Options &scheduler_options, Options &component_options, Solver *solver) { diff --git a/src/permissions.cxx b/src/permissions.cxx index 04787b879..fa4adb845 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -242,3 +242,9 @@ std::istream& operator>>(std::istream& is, Permissions& permissions) { return is; } + +std::string toString(const Permissions& value) { + std::ostringstream ss; + ss << value; + return ss.str(); +} diff --git a/tests/unit/test_component_scheduler.cxx b/tests/unit/test_component_scheduler.cxx index c4e3c6f03..d8b106005 100644 --- a/tests/unit/test_component_scheduler.cxx +++ b/tests/unit/test_component_scheduler.cxx @@ -27,8 +27,26 @@ struct TestMultiply : public Component { } }; +struct OrderChecker : public Component { + OrderChecker(const std::string& name, Options& alloptions, Solver*) + : Component(getPermissions(name, alloptions)), name(name) {} + static Permissions getPermissions(const std::string& name, Options& alloptions) { + return alloptions[name]["permissions"].as(); + } + static void resetOrderInfo() { execution_order.clear(); } + + std::string name; + static std::vector execution_order; + +private: + void transform_impl(GuardedOptions&) override { execution_order.push_back(name); } +}; + +std::vector OrderChecker::execution_order; + RegisterComponent registertestcomponent("testcomponent"); RegisterComponent registertestcomponent2("multiply"); +RegisterComponent registercomponentorderchecker("orderchecker"); } // namespace TEST(SchedulerTest, OneComponent) { @@ -66,3 +84,173 @@ TEST(SchedulerTest, SubComponents) { ASSERT_TRUE(options["answer"] == 42 * 2); } +using Parameter = std::pair>; + +class ComponentOrderTest : public testing::TestWithParam { + void SetUp() override { OrderChecker::resetOrderInfo(); } +}; + +TEST_P(ComponentOrderTest, Sorted) { + Options options = GetParam().first.copy(); + auto scheduler = ComponentScheduler::create(options, options, nullptr); + scheduler->transform(options); + EXPECT_EQ(OrderChecker::execution_order, GetParam().second); +} + +INSTANTIATE_TEST_SUITE_P( + TopologicalSort, ComponentOrderTest, + testing::Values( + Parameter({{"components", ""}}, {}), + Parameter( + {{"components", "a"}, + {"a", {{"type", "orderchecker"}, {"permissions", toString(Permissions())}}}}, + {"a"}), + Parameter({{"components", "a"}, + {"a", + {{"type", "orderchecker"}, + {"permissions", + toString(Permissions({readWrite("1"), readWrite("2")}))}}}}, + {"a"}), + Parameter( + {{"components", "a,b"}, + {"a", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({readWrite("1"), readWrite("2")}))}}}, + {"b", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({readOnly("1"), readOnly("2")}))}}}}, + {"a", "b"}), + Parameter({{"components", "b,a"}, + {"b", + {{"type", "orderchecker"}, + {"permissions", + toString(Permissions({readOnly("1"), readOnly("2")}))}}}, + {"a", + {{"type", "orderchecker"}, + {"permissions", + toString(Permissions({readWrite("1"), readWrite("2")}))}}}}, + {"a", "b"}), + Parameter( + {{"components", "b,a,c"}, + {"b", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({readOnly("1"), readOnly("2")}))}}}, + {"a", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({readWrite("1"), readWrite("2")}))}}}, + {"c", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({readWrite("2"), readOnly("1")}))}}}}, + {"a", "c", "b"}), + Parameter({{"components", "a,b"}, + {"a", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({readIfSet("1")}))}}}, + {"b", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({readWrite("1")}))}}}}, + {"b", "a"}), + Parameter({{"components", "a,b,c"}, + {"a", + {{"type", "orderchecker"}, + {"permissions", + toString(Permissions({writeFinal("1"), readIfSet("3")}))}}}, + {"b", + {{"type", "orderchecker"}, + {"permissions", + toString(Permissions({readWrite("1"), readOnly("2")}))}}}, + {"c", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({readWrite("1"), readWrite("2"), + readIfSet("3")}))}}}}, + {"c", "b", "a"}), + Parameter({{"components", "a,b,c"}, + {"a", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({writeBoundary("1")}))}}}, + {"b", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({readOnly("1")}))}}}, + {"c", + {{"type", "orderchecker"}, + {"permissions", + toString(Permissions({readWrite("1", Regions::Interior)}))}}}}, + {"c", "a", "b"}), + Parameter({{"components", "a,b,c"}, + {"a", + {{"type", "orderchecker"}, + {"permissions", + toString(Permissions({readIfSet("1", Regions::Interior), + readWrite("2", Regions::All)}))}}}, + {"b", + {{"type", "orderchecker"}, + {"permissions", + toString(Permissions({writeFinal("1", Regions::Boundaries), + readIfSet("2", Regions::Interior)}))}}}, + {"c", + {{"type", "orderchecker"}, + {"permissions", + toString(Permissions({readOnly("1", Regions::Boundaries), + readOnly("2", Regions::Boundaries)}))}}}}, + {"b", "a", "c"}))); + +class InvalidComponentOrderTest : public testing::TestWithParam { + void SetUp() override { OrderChecker::resetOrderInfo(); } +}; + +TEST_P(InvalidComponentOrderTest, BadDAG) { + Options options = GetParam().copy(); + EXPECT_THROW(ComponentScheduler::create(options, options, nullptr), BoutException); +} + +INSTANTIATE_TEST_SUITE_P( + InvalidTopologicalSort, InvalidComponentOrderTest, + testing::Values( + // Unsatisfiable dependency + Options({{"components", "a,b"}, + {"a", + {{"type", "orderchecker"}, + {"permissions", + toString(Permissions({readOnly("1"), readWrite("2")}))}}}, + {"b", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({readWrite("3")}))}}}}), + // Circular dependency + Options({{"components", "a,b"}, + {"a", + {{"type", "orderchecker"}, + {"permissions", + toString(Permissions({readOnly("1"), readWrite("2")}))}}}, + {"b", + {{"type", "orderchecker"}, + {"permissions", + toString(Permissions({readWrite("2"), readOnly("1")}))}}}}), + // Circular dependency from readIfSet + Options({{"components", "a,b"}, + {"a", + {{"type", "orderchecker"}, + {"permissions", + toString(Permissions({readIfSet("1"), readWrite("2")}))}}}, + {"b", + {{"type", "orderchecker"}, + {"permissions", + toString(Permissions({readOnly("2"), readWrite("1")}))}}}}), + // Unsatisfiable dependency due to only setting one region + Options({{"components", "a,b"}, + {"a", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({readOnly("1")}))}}}, + {"b", + {{"type", "orderchecker"}, + {"permissions", + toString(Permissions({readWrite("1", Regions::Interior)}))}}}}), + // Circular dependency on only one region + Options({{"components", "a,b"}, + {"a", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({readOnly("1", Regions::Interior), + readWrite("2")}))}}}, + {"b", + {{"type", "orderchecker"}, + {"permissions", + toString(Permissions({readWrite("1"), readOnly("2")}))}}}}))); From e7a883cd5c212168592895e327179f02a82c750d Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Wed, 26 Nov 2025 16:41:21 +0000 Subject: [PATCH 46/63] Add ability to order components automatically --- include/component.hxx | 2 + src/component_scheduler.cxx | 131 +++++++++++++++++++++--- src/permissions.cxx | 2 +- tests/unit/test_component_scheduler.cxx | 4 +- 4 files changed, 124 insertions(+), 15 deletions(-) diff --git a/include/component.hxx b/include/component.hxx index bd3dfe351..4440a6d19 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -142,6 +142,8 @@ struct Component { /// must be completed or else an exception will be thrown. void declareAllSpecies(const SpeciesInformation & info); + const Permissions& getPermissions() const { return state_variable_access; } + protected: /// Set the level of access needed by this component for a particular variable. void setPermissions(const std::string& variable, diff --git a/src/component_scheduler.cxx b/src/component_scheduler.cxx index 4fb39e8f8..99f91a02b 100644 --- a/src/component_scheduler.cxx +++ b/src/component_scheduler.cxx @@ -9,6 +9,123 @@ #include "../include/component.hxx" #include "../include/component_scheduler.hxx" +/// Perform a depth-first topological sort, starting from `item`. +void topological_sort(const std::vector>& dependencies, size_t item, + std::vector& sorted, std::vector& processing, + std::vector& processed) { + if (processed[item]) { + return; + } + if (processing[item]) { + throw BoutException("Circular dependency among components."); + } + processing[item] = true; + + for (const auto dep : dependencies[item]) { + topological_sort(dependencies, dep, sorted, processing, processed); + } + processed[item] = true; + sorted.push_back(item); +} + +/// Consumes a list of components and returns a new one that has been +/// topolgically sorted to ensure variables are written and read in +/// the right order. +std::vector> +sortComponents(std::vector>&& components) { + using Var = std::pair; + std::map> nonfinal_writes, final_writes; + // Build up information on which components write each variable + for (size_t i = 0; i < components.size(); i++) { + const Permissions& permissions = components[i]->getPermissions(); + for (const auto& [name, regions] : + permissions.getVariablesWithPermission(PermissionTypes::Write)) { + for (const auto& [region, _] : Permissions::fundamental_regions) { + if ((regions & region) == region) { + nonfinal_writes[{name, region}].insert(i); + } + } + } + for (const auto& [name, regions] : + permissions.getVariablesWithPermission(PermissionTypes::Final)) { + for (const auto& [region, _] : Permissions::fundamental_regions) { + if ((regions & region) == region) { + final_writes[{name, region}].insert(i); + } + } + } + } + + std::vector> component_dependencies(components.size()); + + // Components which do a final write on a variable depend on all + // components which do non-final writes on that variable + for (const auto& [var, comp_indices] : final_writes) { + for (size_t i : comp_indices) { + const auto item = nonfinal_writes.find(var); + if (item != nonfinal_writes.end()) { + component_dependencies[i].merge(item->second); + } + } + } + + // Work out which component(s) last write a variable before it may be read + std::map> variable_writers = std::move(final_writes); + variable_writers.merge(std::move(nonfinal_writes)); + + for (size_t i = 0; i < components.size(); i++) { + const Permissions& permissions = components[i]->getPermissions(); + // Create dependencies between components that read variables and those that write + // them + for (const auto& [name, regions] : + permissions.getVariablesWithPermission(PermissionTypes::Read)) { + for (const auto& [region, _] : Permissions::fundamental_regions) { + if ((regions & region) == region) { + const auto item = variable_writers.find({name, region}); + if (item == variable_writers.end()) { + throw BoutException( + "Required variable {} (in region {}) is not written by any component.", + name, Permissions::fundamental_regions.at(region)); + } + component_dependencies[i].merge(item->second); + } + } + } + + // Create dependencies for ReadIfSet variables only if there exist another component + // which sets them + for (const auto& [name, regions] : + permissions.getVariablesWithPermission(PermissionTypes::ReadIfSet)) { + for (const auto& [region, _] : Permissions::fundamental_regions) { + if ((regions & region) == region) { + const auto item = variable_writers.find({name, region}); + if (item != variable_writers.end()) { + component_dependencies[i].merge(item->second); + } + } + } + } + } + + std::vector processing(components.size(), false), + processed(components.size(), false); + std::vector order; + + for (size_t i = 0; i < components.size(); i++) { + if (!processed[i]) { + topological_sort(component_dependencies, i, order, processing, processed); + } + } + + // Create the result with components in the desired order + std::vector> result(components.size()); + for (size_t i = 0; i < components.size(); i++) { + std::swap(result[i], components[order[i]]); + } + + return result; +} + ComponentScheduler::ComponentScheduler(Options &scheduler_options, Options &component_options, Solver *solver) { @@ -77,19 +194,9 @@ ComponentScheduler::ComponentScheduler(Options &scheduler_options, for (auto& component : components) { component->declareAllSpecies(species); } -} -// Use index to identify component -// std::map> dependencies; -// std::map var_written_by, var_final_written_by, read_dependencies; - -// Assemble the maps of which components write each variable -// Assemble read_dependencies using values from var_final_written_by, if present, else -// from var_written_by For each component -// For each write-final var, create dependency between component and all components that -// write it For each read-if-set var that has a write-final, create dependency between -// component and (final) writer(s) For each read-var create a dependency between -// component and (final) writers; if none then raise exception + components = sortComponents(std::move(components)); +} std::unique_ptr ComponentScheduler::create(Options &scheduler_options, Options &component_options, diff --git a/src/permissions.cxx b/src/permissions.cxx index fa4adb845..3fdc08482 100644 --- a/src/permissions.cxx +++ b/src/permissions.cxx @@ -187,7 +187,7 @@ Permissions::getVariablesWithPermission(PermissionTypes permission, } std::string Permissions::regionNames(const Regions regions) { - std::vector regions_present(fundamental_regions.size()); + std::vector regions_present; for (auto & [region, name] : fundamental_regions) { if ((regions & region) == region) { regions_present.push_back(name); diff --git a/tests/unit/test_component_scheduler.cxx b/tests/unit/test_component_scheduler.cxx index d8b106005..07a437226 100644 --- a/tests/unit/test_component_scheduler.cxx +++ b/tests/unit/test_component_scheduler.cxx @@ -176,7 +176,7 @@ INSTANTIATE_TEST_SUITE_P( {"permissions", toString(Permissions({readWrite("1", Regions::Interior)}))}}}}, {"c", "a", "b"}), - Parameter({{"components", "a,b,c"}, + Parameter({{"components", "b,a,c"}, {"a", {{"type", "orderchecker"}, {"permissions", @@ -192,7 +192,7 @@ INSTANTIATE_TEST_SUITE_P( {"permissions", toString(Permissions({readOnly("1", Regions::Boundaries), readOnly("2", Regions::Boundaries)}))}}}}, - {"b", "a", "c"}))); + {"a", "b", "c"}))); class InvalidComponentOrderTest : public testing::TestWithParam { void SetUp() override { OrderChecker::resetOrderInfo(); } From ea1b97e71710c1eca9e1f97ff3869dc9fe2f831e Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Wed, 26 Nov 2025 18:33:34 +0000 Subject: [PATCH 47/63] Ensure ordering of components accounts for pre-set state variables --- include/component_scheduler.hxx | 5 +++++ src/component_scheduler.cxx | 9 +++++++++ tests/unit/test_component_scheduler.cxx | 11 +++++++++++ 3 files changed, 25 insertions(+) diff --git a/include/component_scheduler.hxx b/include/component_scheduler.hxx index f848614a8..a67c27b06 100644 --- a/include/component_scheduler.hxx +++ b/include/component_scheduler.hxx @@ -57,6 +57,11 @@ public: /// Preconditioning void precon(const Options &state, BoutReal gamma); + + /// All the variable names which are pre-set in the state, before + /// any components are applied. + static const std::set predeclared_variables; + private: /// The components to be executed in order std::vector> components; diff --git a/src/component_scheduler.cxx b/src/component_scheduler.cxx index 99f91a02b..c016f9ae5 100644 --- a/src/component_scheduler.cxx +++ b/src/component_scheduler.cxx @@ -9,6 +9,10 @@ #include "../include/component.hxx" #include "../include/component_scheduler.hxx" +const std::set ComponentScheduler::predeclared_variables = { + "time", "linear", "units:inv_meters_cubed", "units:eV", "units:Tesla", + "units:seconds", "units:meters"}; + /// Perform a depth-first topological sort, starting from `item`. void topological_sort(const std::vector>& dependencies, size_t item, std::vector& sorted, std::vector& processing, @@ -79,6 +83,9 @@ sortComponents(std::vector>&& components) { // them for (const auto& [name, regions] : permissions.getVariablesWithPermission(PermissionTypes::Read)) { + std::cout << name << "\n"; + if (ComponentScheduler::predeclared_variables.count(name) > 0) + continue; for (const auto& [region, _] : Permissions::fundamental_regions) { if ((regions & region) == region) { const auto item = variable_writers.find({name, region}); @@ -96,6 +103,8 @@ sortComponents(std::vector>&& components) { // which sets them for (const auto& [name, regions] : permissions.getVariablesWithPermission(PermissionTypes::ReadIfSet)) { + if (ComponentScheduler::predeclared_variables.count(name) > 0) + continue; for (const auto& [region, _] : Permissions::fundamental_regions) { if ((regions & region) == region) { const auto item = variable_writers.find({name, region}); diff --git a/tests/unit/test_component_scheduler.cxx b/tests/unit/test_component_scheduler.cxx index 07a437226..d1568f3ea 100644 --- a/tests/unit/test_component_scheduler.cxx +++ b/tests/unit/test_component_scheduler.cxx @@ -120,6 +120,17 @@ INSTANTIATE_TEST_SUITE_P( {{"type", "orderchecker"}, {"permissions", toString(Permissions({readOnly("1"), readOnly("2")}))}}}}, {"a", "b"}), + Parameter({{"components", "a,b"}, + {"a", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({readWrite("1"), readWrite("2"), + readOnly("time")}))}}}, + {"b", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({readOnly("1"), readOnly("2"), + readIfSet("linear"), + readOnly("units:eV")}))}}}}, + {"a", "b"}), Parameter({{"components", "b,a"}, {"b", {{"type", "orderchecker"}, From 231e1fa4ce103bc63ad80fbbf92b4ec0005178fa Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Thu, 27 Nov 2025 18:15:56 +0000 Subject: [PATCH 48/63] Added support for sorting components based on section permissions --- include/permissions.hxx | 2 +- src/component_scheduler.cxx | 234 ++++++++++++++++++------ tests/unit/test_component_scheduler.cxx | 52 +++++- 3 files changed, 233 insertions(+), 55 deletions(-) diff --git a/include/permissions.hxx b/include/permissions.hxx index 33e4531b7..aa9353c4b 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -230,7 +230,6 @@ public: friend std::ostream& operator<<(std::ostream& os, const Permissions& permissions); friend std::istream& operator>>(std::istream& is, Permissions& permissions); -private: /// Returns the access rights for the most specific entry in this /// object which matches the variable name. If there are no matching /// entries then the result will indicate no access rights. The @@ -239,6 +238,7 @@ private: /// entries. VarRights bestMatchRights(const std::string& variable) const; +private: std::map variable_permissions; static const std::regex LABEL_RE; diff --git a/src/component_scheduler.cxx b/src/component_scheduler.cxx index c016f9ae5..62ce153f2 100644 --- a/src/component_scheduler.cxx +++ b/src/component_scheduler.cxx @@ -1,3 +1,4 @@ +#include #include #include #include @@ -5,6 +6,7 @@ #include #include #include // for trim, strsplit +#include #include "../include/component.hxx" #include "../include/component_scheduler.hxx" @@ -32,33 +34,176 @@ void topological_sort(const std::vector>& dependencies, size_t sorted.push_back(item); } -/// Consumes a list of components and returns a new one that has been -/// topolgically sorted to ensure variables are written and read in -/// the right order. -std::vector> -sortComponents(std::vector>&& components) { - using Var = std::pair; - std::map> nonfinal_writes, final_writes; - // Build up information on which components write each variable +std::set getParents(const std::string& name) { + std::set result; + size_t start = 0, position = name.find(":", start); + while (position != std::string::npos) { + result.insert(name.substr(0, position)); + start = position + 1; + position = name.find(":", start); + } + return result; +} + +/// Produce a map between Option paths and all variable names held within +/// that path. If a path does not refer to a section then it just maps +/// to itself. +std::map> +getVariableHierarchy(const std::vector>& components) { + // Build up a set of all variable names which are read only if they + // are set by another component + std::set conditional_names; + for (const auto& component : components) { + const Permissions& permissions = component->getPermissions(); + for (const auto& [varname, _] : + permissions.getVariablesWithPermission(PermissionTypes::ReadIfSet, true)) { + conditional_names.insert(varname); + } + } + + // Build up a set of all variable names which are definitely + // read/written by components, and the sections which they imply + // exist + std::set unconditional_names, unconditional_sections; + for (const auto& component : components) { + const Permissions& permissions = component->getPermissions(); + for (const auto& [varname, _] : + permissions.getVariablesWithPermission(PermissionTypes::Read, false)) { + unconditional_names.insert(varname); + unconditional_sections.merge(getParents(varname)); + } + } + + // Split the set of all variable names used by components into + // those that are sections and those that are not. + std::set sections_present, non_sections; + std::set_intersection(unconditional_names.begin(), unconditional_names.end(), + unconditional_sections.begin(), unconditional_sections.end(), + std::inserter(sections_present, sections_present.begin())); + std::set_difference(unconditional_names.begin(), unconditional_names.end(), + sections_present.begin(), sections_present.end(), + std::inserter(non_sections, non_sections.begin())); + + std::map> result; + + // ReadIfSet variables will only actually be used if they are + // reference elsewhere. We create them with empty sets, which will + // get filled if they are present. + for (const auto& name : conditional_names) { + result.insert({name, {name}}); + } + + // Non-sections map to themselves + for (const auto& name : non_sections) { + result[name] = {name}; + } + // Sections map to those variables which they contain + for (const auto& section : sections_present) { + auto& children = result[section]; + const std::string sec_suffixed = section + ':'; + for (const auto& name : non_sections) { + if (name.rfind(sec_suffixed, 0) == 0) { + children.insert(name); + } + } + } + + return result; +} + +/// Get all variables to which the name could be referring (e.g., its +/// children if it is a section name). These will be filtered to +/// remove any variables for which more specific permissions are +/// given. +std::set +expandVariableName(const std::map>& hierarchy, + const Permissions& permissions, const std::string& name) { + const std::set& candidates = hierarchy.at(name); + std::set result; + // Only return the values that do not have a more specific permission + std::copy_if(candidates.begin(), candidates.end(), + std::inserter(result, result.begin()), + [&permissions, &name](const std::string& candidate) -> bool { + return permissions.bestMatchRights(candidate).name == name; + }); + return result; +} + +using Var = std::pair; + +std::map> +getPermissionComponentMap(const std::vector>& components, + const std::map>& hierarchy, + PermissionTypes permission) { + std::map> result; for (size_t i = 0; i < components.size(); i++) { const Permissions& permissions = components[i]->getPermissions(); for (const auto& [name, regions] : - permissions.getVariablesWithPermission(PermissionTypes::Write)) { - for (const auto& [region, _] : Permissions::fundamental_regions) { - if ((regions & region) == region) { - nonfinal_writes[{name, region}].insert(i); + permissions.getVariablesWithPermission(permission)) { + for (const auto& sub_name : expandVariableName(hierarchy, permissions, name)) { + for (const auto& [region, _] : Permissions::fundamental_regions) { + if ((regions & region) == region) { + result[{sub_name, region}].insert(i); + } } } } + } + return result; +} + +/// Modifies component_dependencies to include information on which +/// variables each component reads. Returns a set of any read +/// variables which are not written by any component. +std::set +setReadDependencies(const std::vector>& components, + const std::map>& hierarchy, + const std::map>& writers, + PermissionTypes permission, + std::vector>& component_dependencies) { + std::set missing; + for (size_t i = 0; i < components.size(); i++) { + const Permissions& permissions = components[i]->getPermissions(); + // Create dependencies between components that read variables and those that write + // them for (const auto& [name, regions] : - permissions.getVariablesWithPermission(PermissionTypes::Final)) { - for (const auto& [region, _] : Permissions::fundamental_regions) { - if ((regions & region) == region) { - final_writes[{name, region}].insert(i); + permissions.getVariablesWithPermission(permission)) { + if (ComponentScheduler::predeclared_variables.count(name) > 0) + continue; + for (const auto& sub_name : expandVariableName(hierarchy, permissions, name)) { + for (const auto& [region, _] : Permissions::fundamental_regions) { + if ((regions & region) == region) { + const auto item = writers.find({sub_name, region}); + if (item == writers.end()) { + missing.insert( + fmt::format("{} ({})", sub_name, Permissions::regionNames(region))); + } else { + component_dependencies[i].insert(item->second.begin(), item->second.end()); + } + } } } } } + return missing; +} + +/// Consumes a list of components and returns a new one that has been +/// topolgically sorted to ensure variables are written and read in +/// the right order. +std::vector> +sortComponents(std::vector>&& components) { + // Map variables to the components that write them + std::map> variable_hierarchy = + getVariableHierarchy(components); + + // Get information on which components write each variable + std::map> nonfinal_writes = getPermissionComponentMap( + components, variable_hierarchy, + PermissionTypes::Write), + final_writes = getPermissionComponentMap( + components, variable_hierarchy, + PermissionTypes::Final); std::vector> component_dependencies(components.size()); @@ -68,6 +213,12 @@ sortComponents(std::vector>&& components) { for (size_t i : comp_indices) { const auto item = nonfinal_writes.find(var); if (item != nonfinal_writes.end()) { + // Note that calling merge actually removes the items from + // nonfinal_writes. This is fine because the only remaining + // thing for which we will use nonfinal_writes is setting up + // variable_writers. That doesn't use any information on + // nonfinal writes for variables which have a final write, so + // it won't do any harm to remove it. component_dependencies[i].merge(item->second); } } @@ -77,49 +228,26 @@ sortComponents(std::vector>&& components) { std::map> variable_writers = std::move(final_writes); variable_writers.merge(std::move(nonfinal_writes)); - for (size_t i = 0; i < components.size(); i++) { - const Permissions& permissions = components[i]->getPermissions(); - // Create dependencies between components that read variables and those that write - // them - for (const auto& [name, regions] : - permissions.getVariablesWithPermission(PermissionTypes::Read)) { - std::cout << name << "\n"; - if (ComponentScheduler::predeclared_variables.count(name) > 0) - continue; - for (const auto& [region, _] : Permissions::fundamental_regions) { - if ((regions & region) == region) { - const auto item = variable_writers.find({name, region}); - if (item == variable_writers.end()) { - throw BoutException( - "Required variable {} (in region {}) is not written by any component.", - name, Permissions::fundamental_regions.at(region)); - } - component_dependencies[i].merge(item->second); - } - } - } - - // Create dependencies for ReadIfSet variables only if there exist another component - // which sets them - for (const auto& [name, regions] : - permissions.getVariablesWithPermission(PermissionTypes::ReadIfSet)) { - if (ComponentScheduler::predeclared_variables.count(name) > 0) - continue; - for (const auto& [region, _] : Permissions::fundamental_regions) { - if ((regions & region) == region) { - const auto item = variable_writers.find({name, region}); - if (item != variable_writers.end()) { - component_dependencies[i].merge(item->second); - } - } - } - } + // Create dependency information for read variables + std::set missing = + setReadDependencies(components, variable_hierarchy, variable_writers, + PermissionTypes::Read, component_dependencies); + if (missing.size() > 0) { + throw BoutException( + "The following required variables are not written by any component:\n\t{}\n", + fmt::format("{}", fmt::join(missing, "\n\t"))); } + // If can not find a place where a ReadIfSet variable is written, it will just be + // skipped + setReadDependencies(components, variable_hierarchy, variable_writers, + PermissionTypes::ReadIfSet, component_dependencies); + // Create ancillary variables for sorting process std::vector processing(components.size(), false), processed(components.size(), false); std::vector order; + // Perform the sort for (size_t i = 0; i < components.size(); i++) { if (!processed[i]) { topological_sort(component_dependencies, i, order, processing, processed); diff --git a/tests/unit/test_component_scheduler.cxx b/tests/unit/test_component_scheduler.cxx index d1568f3ea..5882dbd64 100644 --- a/tests/unit/test_component_scheduler.cxx +++ b/tests/unit/test_component_scheduler.cxx @@ -175,6 +175,45 @@ INSTANTIATE_TEST_SUITE_P( {"permissions", toString(Permissions({readWrite("1"), readWrite("2"), readIfSet("3")}))}}}}, {"c", "b", "a"}), + Parameter({{"components", "a,b,c"}, + {"a", + {{"type", "orderchecker"}, + {"permissions", + toString(Permissions({writeFinal("1:1_1"), readWrite("1:1_2")}))}}}, + {"b", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({readWrite("1")}))}}}, + {"c", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({readOnly("1:1_1")}))}}}}, + {"b", "a", "c"}), + Parameter( + {{"components", "a,b,c"}, + {"a", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({writeFinal("1"), readOnly("2")}))}}}, + {"b", + {{"type", "orderchecker"}, + {"permissions", + toString(Permissions({readWrite("2:2_1"), writeFinal("2:2_2")}))}}}, + {"c", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({readWrite("1"), readOnly("2:2_1"), + readIfSet("3")}))}}}}, + {"b", "c", "a"}), + Parameter({{"components", "a,b,c"}, + {"a", + {{"type", "orderchecker"}, + {"permissions", + toString(Permissions({readOnly("1"), readWrite("1:1_1")}))}}}, + {"b", + {{"type", "orderchecker"}, + {"permissions", + toString(Permissions({readWrite("1:1_1"), readWrite("1:1_2")}))}}}, + {"c", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({readOnly("1:1_2")}))}}}}, + {"b", "a", "c"}), Parameter({{"components", "a,b,c"}, {"a", {{"type", "orderchecker"}, @@ -203,7 +242,18 @@ INSTANTIATE_TEST_SUITE_P( {"permissions", toString(Permissions({readOnly("1", Regions::Boundaries), readOnly("2", Regions::Boundaries)}))}}}}, - {"a", "b", "c"}))); + {"a", "b", "c"}), + Parameter({{"components", "a,b"}, + {"a", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({ + readOnly("1"), + }))}}}, + {"b", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({writeFinal("1:1_1"), + readIfSet("1:1_2")}))}}}}, + {"b", "a"}))); class InvalidComponentOrderTest : public testing::TestWithParam { void SetUp() override { OrderChecker::resetOrderInfo(); } From d1af962adb4c16115bc74b464a49ea8540479fc0 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Tue, 2 Dec 2025 14:45:39 +0000 Subject: [PATCH 49/63] Mention new sorting and remove references to component ordering --- docs/sphinx/boundary_conditions.rst | 4 ++-- docs/sphinx/closure.rst | 7 +++--- docs/sphinx/developer.rst | 33 +++++++++++++++++++++++------ docs/sphinx/equations.rst | 33 ++++++++++------------------- docs/sphinx/examples.rst | 10 ++++----- docs/sphinx/inputs.rst | 5 +++-- 6 files changed, 50 insertions(+), 42 deletions(-) diff --git a/docs/sphinx/boundary_conditions.rst b/docs/sphinx/boundary_conditions.rst index 9cfc2ef33..8a5c4798f 100644 --- a/docs/sphinx/boundary_conditions.rst +++ b/docs/sphinx/boundary_conditions.rst @@ -250,7 +250,7 @@ The boundary fluxes might be set by sheath boundary conditions, which potentially depend on the density and temperature of all species. Recycling therefore can't be calculated until all species boundary conditions have been set. It is therefore expected that this component is a top-level -component (i.e. in the `Hermes` section) which comes after boundary conditions are set. +component (i.e. in the `Hermes` section). Recycling has been implemented at the target, the SOL edge and the PFR edge. Each is off by default and must be activated with a separate flag. Each can be @@ -620,4 +620,4 @@ Note that if you have the density controller enabled, it will work to counteract function = 0.01 source = Pe:source source_time_dependent = true - source_prefactor = Pe:source_prefactor \ No newline at end of file + source_prefactor = Pe:source_prefactor diff --git a/docs/sphinx/closure.rst b/docs/sphinx/closure.rst index b235ea71d..e9c0c4b6e 100644 --- a/docs/sphinx/closure.rst +++ b/docs/sphinx/closure.rst @@ -262,8 +262,7 @@ Input This top-level component calculates the frictional forces between each pair of species for which collisional frequencies have been calculated -(see `Braginskii Collisions component`_). As such, it must be run after -`braginskii_collisions`. If the option `frictional_heating` is +(see `Braginskii Collisions component`_). If the option `frictional_heating` is enabled then it will also calculate the energy source arising from friction. @@ -346,8 +345,8 @@ Braginskii Heat Exchange Input ----- This top-level component calculates the heat exchange between species -due to collisions (see `Braginskii Collisions component`_). As such, it must be run after -`braginskii_collisions`. There are no configurations for this component. +due to collisions (see `Braginskii Collisions component`_). There are no +configurations for this component. Theory ------ diff --git a/docs/sphinx/developer.rst b/docs/sphinx/developer.rst index f2d9a9a12..c89f046b2 100644 --- a/docs/sphinx/developer.rst +++ b/docs/sphinx/developer.rst @@ -577,6 +577,17 @@ The `name` is a string labelling the instance. The `alloptions` tree contains at * `alloptions[name]` options for this instance * `alloptions['units']` + +All component constructors must pass a `Permissions` object to the +constructor on the `Component::Component` base class. This specifies +which variables will be read/written by the `Component::transform` +method and will be used to construct a `GuardedOptions` object to be +passed into `Component::transform_impl`. The `Permissions` object +(`Component::state_variable_access`) can be further updated in the +body of the constructor of your component, based on what +configurations were specified in ``alloptions``. You should give read +and write permissions to the minimum number of variables necessary, to +avoid circular dependencies arising among components. Component Permissions @@ -620,9 +631,14 @@ and then in `Hermes::rhs` the components are run by a call:: scheduler->transform(state); The call to `ComponentScheduler::create` treats the "components" -option as a comma-separated list of names. The order of the components -is the order that they are run in. For each name in the list, the -scheduler looks up the options under the section of that name. +option as a comma-separated list of names. For each name in the list, +the scheduler looks up the options under the section of that name. The +``ComponentScheduler`` will use permission information stored by each +component in `Component::state_variable_access` to work out the order +to execute components. It will ensure that all writes to a variable +have occurred before the first time it is read. If there is a variable +needed by some component which is never set or if there is a circular +dependency then it will throw an exception. .. code-block:: ini @@ -639,8 +655,9 @@ scheduler looks up the options under the section of that name. This would create two `Component` objects, of type `component1` and `component2`. Each time `Hermes::rhs` is run, the `transform` -functions of `component1` and then `component2` will be called, -followed by their `finally` functions. +functions of `component1` and `component2` will be called, with the +order depending on what state variables each reads and writes. This is +followed by a call to their `finally` functions. It is often useful to group components together, for example to define the governing equations for different species. A `type` setting @@ -662,8 +679,10 @@ of components # options to control component3 This will create three components, which will be run in the order -`component1`, `component2`, `component3`: First all the components -in `group1`, and then `component3`. +`component1`, `component2`, `component3`: First all the components in +`group1`, and then `component3`. Grouped components will be sorted +individually when determining the run order; they may not be run +together. .. doxygenclass:: ComponentScheduler :members: diff --git a/docs/sphinx/equations.rst b/docs/sphinx/equations.rst index f04795ea2..eb59424de 100644 --- a/docs/sphinx/equations.rst +++ b/docs/sphinx/equations.rst @@ -120,9 +120,8 @@ quasineutral ~~~~~~~~~~~~ This component sets the density of one species, so that the overall -charge density is zero everywhere. This must therefore be done after -all other charged species densities have been calculated. It only -makes sense to use this component for species with a non-zero charge. +charge density is zero everywhere. It only makes sense to use this +component for species with a non-zero charge. .. doxygenstruct:: Quasineutral :members: @@ -229,8 +228,8 @@ energy, :math:`\mathcal{E}`: \mathcal{E} = \frac{1}{\gamma - 1} P + \frac{1}{2}m nv_{||}^2 Note that this component requires the parallel velocity :math:`v_{||}` -to calculate the pressure. It must therefore be listed after a component -that sets the velocity, such as `evolve_momentum`: +to calculate the pressure. It must therefore be listed alongside a +component that sets the velocity, such as `evolve_momentum`: .. code-block:: ini @@ -294,13 +293,6 @@ conduction for all species that use :ref:`evolve_pressure` or desired for a particular species then it can be turned off by setting `thermal_conduction = false` in the input options for that species. -This component requires a collision time to have been calculated -(i.e., with the :ref:`Braginskii Collisions` component). It is -recommended that this be one of the last component to run, to ensure density, -pressure, and temperature have their final values. However, it must be -run before :ref:`Recycling`, as that component will need to use the -`energy_flow_ylow` value, to which conduction contributes. - The choice of collision frequency used for conduction is set by the flag `conduction_collisions_mode`: `multispecies` uses all available collision frequencies involving the chosen species, while `braginskii` @@ -382,7 +374,7 @@ using flows already calculated for other species. It is used like `quasineutral` .. code-block:: ini [hermes] - components = h+, ..., e, ... # Note: e after all other species + components = h+, ..., e, ... [e] type = ..., zero_current,... # Set e:velocity @@ -436,7 +428,7 @@ The electron parallel viscosity is \eta_e = \frac{4}{3} 0.73 p_e \tau_e where :math:`\tau_e` is the electron collision time. The collisions between electrons -and all other species therefore need to be calculated before this component is run: +and all other species therefore needs to be calculated: .. code-block:: ini @@ -451,9 +443,8 @@ and all other species therefore need to be calculated before this component is r braginskii_ion_viscosity ~~~~~~~~~~~~~~~~~~~~~~~~ -Adds ion viscosity terms to all charged species that are not electrons. -The collision frequency is required so this is a top-level component that -must be calculated after collisions: +Adds ion viscosity terms to all charged species that are not +electrons, calculated using collision frequencies. .. code-block:: ini @@ -623,8 +614,7 @@ has cross-field transport. This discrepancy is due to historical reasons and wil 1D: neutral_parallel_diffusion ~~~~~~~~~~~~~~~~~~~~~~~~~~ -This adds diffusion to **all** neutral species (those with no or zero charge), -because it needs to be calculated after the collision frequencies are known. +This adds diffusion to **all** neutral species (those with no or zero charge). .. code-block:: ini @@ -982,9 +972,8 @@ electrostatic potential :math:`\phi` in the frame of the fluid, with an ion diamagnetic contribution. This is calculated by inverting a Laplacian equation similar to that solved in the vorticity equation. -This component needs to be run after all other currents have been -calculated. It marks currents as used, so out-of-order modifications -should raise errors. +This component will be run after all other currents have been +calculated. See the `examples/blob2d-vpol` example, which contains: diff --git a/docs/sphinx/examples.rst b/docs/sphinx/examples.rst index 92345d870..e9d3d588f 100644 --- a/docs/sphinx/examples.rst +++ b/docs/sphinx/examples.rst @@ -276,9 +276,10 @@ so that the equation solved is where :math:`T_e` is the fixed electron temperature (5eV). -The :ref:`vorticity` component uses the pressure to calculate the diamagnetic current, -so must come after the `e` component. This component then calculates the potential. -Options to control the vorticity component are set in the `[vorticity]` section. +The :ref:`vorticity` component uses the pressure to calculate the +diamagnetic current. This component then calculates the potential. +Options to control the vorticity component are set in the +`[vorticity]` section. .. math:: @@ -287,8 +288,7 @@ Options to control the vorticity component are set in the `[vorticity]` section. \nabla\cdot\left(\frac{1}{B^2}\nabla_\perp\phi\right) = \omega \end{aligned} -The `sheath_closure` component uses the potential, so must come after :ref:`vorticity`. -Options are also set as +The `sheath_closure` component uses the potential. Options are also set as .. code-block:: ini diff --git a/docs/sphinx/inputs.rst b/docs/sphinx/inputs.rst index ff7a936b7..fbaac1d7f 100644 --- a/docs/sphinx/inputs.rst +++ b/docs/sphinx/inputs.rst @@ -19,8 +19,9 @@ number of output timesteps ``nout`` and the output timestep size Note that the solver timestep is adaptive and not user-settable. This is followed by ``[mesh]``, ``[solver]`` and ``[hermes]`` headers, where the ``[hermes]`` -section defines the list of components used. The component order -matters, as the components are executed in order. +section defines the list of components used. The component order doesn't +matters, as they will be sorted to ensure state variables are set +before they are used. .. code-block:: ini From 9145fae7f0096d5811edf963760cbddf18892016 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Tue, 2 Dec 2025 15:46:36 +0000 Subject: [PATCH 50/63] Check results are the same regardless of component order --- CMakeLists.txt | 1 + .../integrated/component-order/data/BOUT.inp | 164 ++++++++++++++++++ tests/integrated/component-order/runtest | 93 ++++++++++ 3 files changed, 258 insertions(+) create mode 100644 tests/integrated/component-order/data/BOUT.inp create mode 100755 tests/integrated/component-order/runtest diff --git a/CMakeLists.txt b/CMakeLists.txt index 42d4d70c3..51a96bc83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -283,6 +283,7 @@ if(HERMES_TESTS) hermes_add_integrated_test(alfven-wave) hermes_add_integrated_test(collfreq-braginskii-afn) hermes_add_integrated_test(collfreq-multispecies) + hermes_add_integrated_test(component-order) # Unit tests option(HERMES_UNIT_TESTS "Build the unit tests" ON) diff --git a/tests/integrated/component-order/data/BOUT.inp b/tests/integrated/component-order/data/BOUT.inp new file mode 100644 index 000000000..2614b19c8 --- /dev/null +++ b/tests/integrated/component-order/data/BOUT.inp @@ -0,0 +1,164 @@ +nout = 0 +timestep = 1 + + +[mesh] +# 1D simulation, use "y" as the dimension along the fieldline +nx = 10 +ny = 10 # Resolution along field-line +nz = 10 +ixseps1 = 0 +J = 1 + + +[hermes] +components = (d+, he+, he, d, e, + sheath_boundary_simple, braginskii_collisions, braginskii_friction, + braginskii_heat_exchange, reactions, electron_force_balance, + neutral_parallel_diffusion, braginskii_ion_viscosity, + braginskii_conduction, recycling) + + +[solver] +type = pvode + + +[sheath_boundary_simple] +lower_y = false +upper_y = true +diagnose = true + + +[neutral_parallel_diffusion] +dneut = 10 +diffusion_collisions_mode = multispecies + + +[braginskii_collisions] +electron_ion = true +electron_electron = true +electron_neutral = true +ion_ion = true +ion_neutral = true +neutral_neutral = true +diagnose = true + + +[braginskii_ion_viscosity] +viscosity_collisions_mode = multispecies + +[recycling] +species = d+, he+ + + +#################################### +[d+] +type = (evolve_density, evolve_pressure, evolve_momentum, + noflow_boundary) + +charge = 1 +AA = 2 +thermal_conduction = true +conduction_collisions_mode = multispecies +noflow_upper_y = false +recycle_as = d +target_recycle = true +diagnose = true + +[Nd+] +function = 1 + +[Pd+] +function = 1 + +[NVd+] +function = 1 + +#################################### +[he+] +type = (evolve_density, evolve_pressure, evolve_momentum, + noflow_boundary) + +charge = 2 +AA = 4 +thermal_conduction = true +conduction_collisions_mode = multispecies +noflow_upper_y = false +recycle_as = he +target_recycle = true +diagnose = true + +[Nhe+] +function = 0.01 + +[Phe+] +function = 0.001 + +[NVhe+] +function = 0.001 + +#################################### +[he] +type = (neutral_mixed, noflow_boundary) + +AA = 4 +diffusion_collisions_mode = multispecies +diagnose = true + +[Nhe] +function = 0.001 + +[Phe] +function = 0.0001 + +[NVhe] +function = 0.0001 + +#################################### +[d] +type = (neutral_mixed, noflow_boundary) + +AA = 2 +diffusion_collisions_mode = multispecies +diagnose = true + +[Nd] +function = 0.001 + +[Pd] +function = 0.0001 + +[NVd] +function = 0.0001 + +#################################### +[e] # Electrons +type = (quasineutral, evolve_pressure, zero_current, + noflow_boundary) + +noflow_upper_y = false + +charge = -1 +AA = 1/1836 +thermal_conduction = true # in evolve_pressure +conduction_collisions_mode = multispecies + +diagnose = true + +[Pe] + +function = `Pd+:function` + +#################################### + + +[reactions] +diagnose = true +type = ( + d + e -> d+ + 2e, # Deuterium ionisation + d+ + e -> d, # Deuterium recombination + d + d+ -> d+ + d, # Charge exchange + + he + e -> he+ + 2e, # Helium ionisation + he+ + e -> he, # Helium+ recombination + ) diff --git a/tests/integrated/component-order/runtest b/tests/integrated/component-order/runtest new file mode 100755 index 000000000..8b653027d --- /dev/null +++ b/tests/integrated/component-order/runtest @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 + +# Python script to run and analyse two test cases that are identical +# except the order in which the components are specified. + +from __future__ import division +from __future__ import print_function + +try: + from builtins import str +except: + pass + +from boututils.run_wrapper import shell, launch_safe, getmpirun +from boutdata.collect import collect +import numpy +import xhermes + +from numpy import sqrt, max, abs, mean, array, log, concatenate +from pathlib import Path + + +def check_value(name, val, target, **kws): + # override default tolerances to get desired behaviour + kws["atol"] = kws.pop("atol", 0.0) + kws["rtol"] = kws.pop("rtol", 0.0) + + success = numpy.isclose(val, target, **kws) + if not success: + # On failure, report the effective abs tolerance used by isclose + atol_eff = kws["atol"] + kws["rtol"] * abs(target) + print( + f"Expected\n {target-atol_eff} < {name} < {target+atol_eff}\nBut actual value was\n {val}" + ) + return success + + +cases = [ + "d+, he+, he, d, e, sheath_boundary_simple, braginskii_collisions, braginskii_friction, braginskii_heat_exchange, reactions, electron_force_balance, neutral_parallel_diffusion, braginskii_ion_viscosity, braginskii_conduction, recycling", + "recycling, d+, braginskii_friction, braginskii_heat_exchange, he, reactions, electron_force_balance, neutral_parallel_diffusion, d, braginskii_ion_viscosity, sheath_boundary_simple, he+, braginskii_conduction, braginskii_collisions, e" +] + +# Remove old test results and symlink executable + +if not Path("hermes-3").is_file(): + shell("ln -s ../../../hermes-3 hermes-3") + +results = [] + +# Run and exit if unsuccessful +for i, c in enumerate(cases): + for file in ["BOUT.dmp.0.nc", "BOUT.log.0", "BOUT.restart.0.nc", "BOUT.settings", ".BOUT.pid.0"]: + path = Path("data") / file + if path.is_file(): + print(f"Removing old file data/{file}") + path.unlink() + + s, out = launch_safe(f"./hermes-3 hermes:components='{c}'", nproc=1, pipe=True) + + if s != 0: + print(" => Test failed: ") + print(out) + exit(1) + + # Save output to log file + with open(f"run.{i}.log", "w") as f: + f.write(out) + + results.append(xhermes.open("data", unnormalise=False).isel(t=-1)) + +success = True + +rtol = 1e-10 +atol = 1e-10 +diagnostics = ["Nd+", "Pd+", "Vd+", "NVd+", "Nd", "Pd", "Vd", "NVd", "Nhe+", "Phe+", "Vhe+", "NVhe+", "Nhe", "Phe", "Vhe", "NVhe", "Ne", "Pe", "Ve"] +idx = (3, 5, 7) + +failing = [] + +# Check output is the same between the simulations +for d in diagnostics: + if not check_value(d, results[0][d].values[idx], results[1][d].values[idx], rtol=rtol, atol=atol): + success = False + failing.append(d) + + +# Final output +if success: + print(" => Test passed") + exit(0) +else: + print(f" => Test failed: \nDisagreement in diagnostic(s) {', '.join(failing)}") + exit(1) From 9c55bfd944e902be17f12ef7f4053fd12c9a3a66 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Tue, 2 Dec 2025 15:57:01 +0000 Subject: [PATCH 51/63] Check only one component can make final write --- src/component_scheduler.cxx | 4 ++++ tests/unit/test_component_scheduler.cxx | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/src/component_scheduler.cxx b/src/component_scheduler.cxx index 62ce153f2..16a540521 100644 --- a/src/component_scheduler.cxx +++ b/src/component_scheduler.cxx @@ -210,6 +210,10 @@ sortComponents(std::vector>&& components) { // Components which do a final write on a variable depend on all // components which do non-final writes on that variable for (const auto& [var, comp_indices] : final_writes) { + if (comp_indices.size() > 1) { + throw BoutException( + "Multiple components have permission to make final write to variable {}", var); + } for (size_t i : comp_indices) { const auto item = nonfinal_writes.find(var); if (item != nonfinal_writes.end()) { diff --git a/tests/unit/test_component_scheduler.cxx b/tests/unit/test_component_scheduler.cxx index 5882dbd64..c7e40be13 100644 --- a/tests/unit/test_component_scheduler.cxx +++ b/tests/unit/test_component_scheduler.cxx @@ -276,6 +276,14 @@ INSTANTIATE_TEST_SUITE_P( {"b", {{"type", "orderchecker"}, {"permissions", toString(Permissions({readWrite("3")}))}}}}), + // Multiple final writes + Options({{"components", "a,b"}, + {"a", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({writeFinal("1")}))}}}, + {"b", + {{"type", "orderchecker"}, + {"permissions", toString(Permissions({writeFinal("1")}))}}}}), // Circular dependency Options({{"components", "a,b"}, {"a", From daa7c69e13275cea13da580a8692605755cfa911 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Wed, 3 Dec 2025 13:10:03 +0000 Subject: [PATCH 52/63] Fixed some incorrect access controls causing errors --- include/permissions.hxx | 27 +++++++++++++++++++++++-- src/braginskii_conduction.cxx | 4 ++-- src/neutral_boundary.cxx | 4 ++-- src/sheath_boundary.cxx | 11 +++++----- src/sheath_boundary_insulating.cxx | 13 ++++++------ src/sheath_boundary_simple.cxx | 13 ++++++------ tests/unit/test_component_scheduler.cxx | 2 +- tests/unit/test_permissions.cxx | 19 ++++++++++++++++- 8 files changed, 68 insertions(+), 25 deletions(-) diff --git a/include/permissions.hxx b/include/permissions.hxx index aa9353c4b..0d9b7f197 100644 --- a/include/permissions.hxx +++ b/include/permissions.hxx @@ -290,16 +290,25 @@ inline Permissions::VarRights writeFinal(std::string varname, } /// Convenience function to return an object expressing that the -/// variable should have Final write permissions on the boundaries. It +/// variable should have write permissions on the boundaries. It /// will have Read permissions in the interior, as this is normally /// required to set the boundaries correctly. inline Permissions::VarRights writeBoundary(std::string varname) { + return {std::move(varname), + {Regions::Nowhere, Regions::Interior, Regions::Boundaries, Regions::Nowhere}}; +} + +/// Convenience function to return an object expressing that the +/// variable should have Final write permissions on the boundaries. It +/// will have Read permissions in the interior, as this is normally +/// required to set the boundaries correctly. +inline Permissions::VarRights writeBoundaryFinal(std::string varname) { return {std::move(varname), {Regions::Nowhere, Regions::Interior, Regions::Nowhere, Regions::Boundaries}}; } /// Convenience function to return an object expressing that, if the -/// interior has been set, then the variable should have Final write +/// interior has been set, then the variable should have write /// permissions on the boundaries and read permissions for the /// interior. /// @@ -308,6 +317,20 @@ inline Permissions::VarRights writeBoundary(std::string varname) { /// have write permission regardless of whether or not the interior is /// set. inline Permissions::VarRights writeBoundaryIfSet(std::string varname) { + return {std::move(varname), + {Regions::Interior, Regions::Nowhere, Regions::Boundaries, Regions::Nowhere}}; +} + +/// Convenience function to return an object expressing that, if the +/// interior has been set, then the variable should have Final write +/// permissions on the boundaries and read permissions for the +/// interior. +/// +/// FIXME: Currently these permissiosn are not expressed properly, due +/// to limitations in how the permission system. The boundary will +/// have write permission regardless of whether or not the interior is +/// set. +inline Permissions::VarRights writeBoundaryFinalIfSet(std::string varname) { return {std::move(varname), {Regions::Interior, Regions::Nowhere, Regions::Nowhere, Regions::Boundaries}}; } diff --git a/src/braginskii_conduction.cxx b/src/braginskii_conduction.cxx index b9a5692c7..f38d08ed2 100644 --- a/src/braginskii_conduction.cxx +++ b/src/braginskii_conduction.cxx @@ -28,7 +28,6 @@ using bout::globals::mesh; BraginskiiConduction::BraginskiiConduction(const std::string&, Options& alloptions, Solver*) : Component({readOnly("species:{sp}:{input_vars}"), - writeBoundary("species:{sp}:pressure"), readWrite("species:{sp}:{output_vars}")}) { AUTO_TRACE(); @@ -91,7 +90,8 @@ BraginskiiConduction::BraginskiiConduction(const std::string&, Options& alloptio std::vector coll_types; - substitutePermissions("input_vars", {"AA", "density", "temperature"}); + substitutePermissions("input_vars", + {"AA", "density", "temperature", "pressure"}); substitutePermissions("output_vars", {"energy_source", "kappa_par", "energy_flow_ylow"}); std::vector species; diff --git a/src/neutral_boundary.cxx b/src/neutral_boundary.cxx index c4d68f68a..53f48d760 100644 --- a/src/neutral_boundary.cxx +++ b/src/neutral_boundary.cxx @@ -7,8 +7,8 @@ using bout::globals::mesh; NeutralBoundary::NeutralBoundary(std::string name, Options& alloptions, [[maybe_unused]] Solver* solver) - : Component({writeBoundary("species:{name}:{outputs}"), - writeBoundaryIfSet("species:{name}:{conditional_outputs}"), + : Component({writeBoundaryFinal("species:{name}:{outputs}"), + writeBoundaryFinalIfSet("species:{name}:{conditional_outputs}"), readWrite("species:{name}:energy_source")}), name(name) { AUTO_TRACE(); diff --git a/src/sheath_boundary.cxx b/src/sheath_boundary.cxx index 9ade3dc03..d0d07108a 100644 --- a/src/sheath_boundary.cxx +++ b/src/sheath_boundary.cxx @@ -49,16 +49,16 @@ SheathBoundary::SheathBoundary(std::string name, Options& alloptions, Solver*) : Component({ readIfSet("species:{all_species}:charge"), readIfSet("species:e:{e_whole_domain}"), - writeBoundary("species:e:{e_boundary}"), + writeBoundaryFinal("species:e:{e_boundary}"), readWrite("species:e:energy_source"), - writeBoundaryIfSet("species:e:{e_optional}"), + writeBoundaryFinalIfSet("species:e:{e_optional}"), writeBoundaryReadInteriorIfSet("species:e:pressure"), - readIfSet("species:{ions}:adiabatic"), + readIfSet("species:{ions}:{ion_whole_domain}"), readOnly("species:{ions}:AA"), readWrite("species:{ions}:energy_source"), - writeBoundary("species:{ions}:{ion_boundary}"), + writeBoundaryFinal("species:{ions}:{ion_boundary}"), writeBoundaryReadInteriorIfSet("species:{ions}:pressure"), - writeBoundaryIfSet("species:{ions}:{ion_optional}"), + writeBoundaryFinalIfSet("species:{ions}:{ion_optional}"), }) { AUTO_TRACE(); @@ -112,6 +112,7 @@ SheathBoundary::SheathBoundary(std::string name, Options& alloptions, Solver*) substitutePermissions("e_whole_domain", {"AA", "adiabatic"}); substitutePermissions("e_boundary", {"density", "temperature"}); substitutePermissions("e_optional", {"velocity", "momentum"}); + substitutePermissions("ion_whole_domain", {"charge", "adiabatic"}); substitutePermissions("ion_boundary", {"density", "temperature"}); substitutePermissions("ion_optional", {"velocity", "momentum"}); setPermissions(always_set_phi ? writeBoundaryReadInteriorIfSet("fields:phi") diff --git a/src/sheath_boundary_insulating.cxx b/src/sheath_boundary_insulating.cxx index f3a9047f3..17098e3da 100644 --- a/src/sheath_boundary_insulating.cxx +++ b/src/sheath_boundary_insulating.cxx @@ -53,16 +53,17 @@ SheathBoundaryInsulating::SheathBoundaryInsulating(std::string name, Options& al : Component({ readIfSet("species:{all_species}:charge"), readIfSet("species:e:{e_whole_domain}"), - writeBoundary("species:e:{e_boundary}"), + writeBoundaryFinal("species:e:{e_boundary}"), readWrite("species:e:energy_source"), - writeBoundaryIfSet("species:e:{e_optional}"), + writeBoundaryFinalIfSet("species:e:{e_optional}"), writeBoundaryReadInteriorIfSet("species:e:pressure"), - readIfSet("species:{ions}:adiabatic"), + readIfSet("species:{ions}:{ion_whole_domain}"), readOnly("species:{ions}:AA"), readWrite("species:{ions}:energy_source"), - writeBoundary("species:{ions}:{ion_boundary}"), - writeBoundaryIfSet("species:{ions}:{ion_optional}"), + writeBoundaryFinal("species:{ions}:{ion_boundary}"), + writeBoundaryFinalIfSet("species:{ions}:{ion_optional}"), writeBoundaryReadInteriorIfSet("species:{ions}:pressure"), + writeBoundaryFinalIfSet("fields:phi") }) { AUTO_TRACE(); @@ -95,9 +96,9 @@ SheathBoundaryInsulating::SheathBoundaryInsulating(std::string name, Options& al substitutePermissions("e_whole_domain", {"AA", "adiabatic"}); substitutePermissions("e_boundary", {"density", "temperature"}); substitutePermissions("e_optional", {"velocity", "momentum"}); + substitutePermissions("ion_whole_domain", {"charge", "adiabatic"}); substitutePermissions("ion_boundary", {"density", "temperature"}); substitutePermissions("ion_optional", {"velocity", "momentum"}); - setPermissions(writeBoundaryIfSet("fields:phi")); } void SheathBoundaryInsulating::transform_impl(GuardedOptions& state) { diff --git a/src/sheath_boundary_simple.cxx b/src/sheath_boundary_simple.cxx index e1fd58ce5..d4057986d 100644 --- a/src/sheath_boundary_simple.cxx +++ b/src/sheath_boundary_simple.cxx @@ -60,18 +60,18 @@ BoutReal limitFree(BoutReal fm, BoutReal fc, BoutReal mode) { SheathBoundarySimple::SheathBoundarySimple(std::string name, Options& alloptions, Solver*) : Component({ readIfSet("species:e:{e_whole_domain}"), - writeBoundary("species:e:{e_boundary}"), + writeBoundaryFinal("species:e:{e_boundary}"), readWrite("species:e:energy_source"), readWrite("species:e:energy_flow_ylow"), - writeBoundaryIfSet("species:e:{e_optional}"), + writeBoundaryFinalIfSet("species:e:{e_optional}"), writeBoundaryReadInteriorIfSet("species:e:pressure"), - readIfSet("species:{all_species}:charge"), + readIfSet("species:{ions}:{ion_whole_domain}"), readOnly("species:{ions}:AA"), readWrite("species:{ions}:energy_source"), readWrite("species:{ions}:energy_flow_ylow"), - writeBoundary("species:{ions}:{ion_boundary}"), + writeBoundaryFinal("species:{ions}:{ion_boundary}"), writeBoundaryReadInteriorIfSet("species:{ions}:pressure"), - writeBoundaryIfSet("species:{ions}:{ion_optional}"), + writeBoundaryFinalIfSet("species:{ions}:{ion_optional}"), }) { AUTO_TRACE(); @@ -145,9 +145,10 @@ SheathBoundarySimple::SheathBoundarySimple(std::string name, Options& alloptions .doc("Save additional output diagnostics") .withDefault(false); - substitutePermissions("e_whole_domain", {"AA", "charge"}); + substitutePermissions("e_whole_domain", {"AA", "adiabatic"}); substitutePermissions("e_boundary", {"density", "temperature"}); substitutePermissions("e_optional", {"velocity", "momentum"}); + substitutePermissions("ion_whole_domain", {"charge", "adiabatic"}); substitutePermissions("ion_boundary", {"density", "temperature"}); substitutePermissions("ion_optional", {"velocity", "momentum"}); setPermissions(always_set_phi ? writeBoundaryReadInteriorIfSet("fields:phi") diff --git a/tests/unit/test_component_scheduler.cxx b/tests/unit/test_component_scheduler.cxx index c7e40be13..9e2df5ac0 100644 --- a/tests/unit/test_component_scheduler.cxx +++ b/tests/unit/test_component_scheduler.cxx @@ -217,7 +217,7 @@ INSTANTIATE_TEST_SUITE_P( Parameter({{"components", "a,b,c"}, {"a", {{"type", "orderchecker"}, - {"permissions", toString(Permissions({writeBoundary("1")}))}}}, + {"permissions", toString(Permissions({writeBoundaryFinal("1")}))}}}, {"b", {{"type", "orderchecker"}, {"permissions", toString(Permissions({readOnly("1")}))}}}, diff --git a/tests/unit/test_permissions.cxx b/tests/unit/test_permissions.cxx index f5796bcb3..4988fe4ff 100644 --- a/tests/unit/test_permissions.cxx +++ b/tests/unit/test_permissions.cxx @@ -126,6 +126,8 @@ TEST(PermissionsTests, TestGetHighestPermission) { {Regions::Nowhere, Regions::Nowhere, Regions::Interior, Regions::Nowhere}}, {"species:d:collision_frequencies", {Regions::Nowhere, Regions::Nowhere, Regions::Boundaries, Regions::Nowhere}}, + writeBoundary("fields:phi"), + writeBoundaryIfSet("species:he+:temperature"), }); auto no_permission = make_permission(PermissionTypes::None, ""); @@ -148,6 +150,10 @@ TEST(PermissionsTests, TestGetHighestPermission) { EXPECT_EQ(example.getHighestPermission("species:d:collision_frequencies:d_d_coll"), make_permission(PermissionTypes::None, "species:d:collision_frequencies")); EXPECT_EQ(example.getHighestPermission("unset"), no_permission); + EXPECT_EQ(example.getHighestPermission("fields:phi"), + make_permission(PermissionTypes::Read, "fields:phi")); + EXPECT_EQ(example.getHighestPermission("species:he+:temperature"), + make_permission(PermissionTypes::ReadIfSet, "species:he+:temperature")); // Get the highest permission on the boundaries EXPECT_EQ(example.getHighestPermission("species:he:charge", Regions::Boundaries), @@ -169,6 +175,10 @@ TEST(PermissionsTests, TestGetHighestPermission) { Regions::Boundaries), make_permission(PermissionTypes::Write, "species:d:collision_frequencies")); EXPECT_EQ(example.getHighestPermission("unset", Regions::Boundaries), no_permission); + EXPECT_EQ(example.getHighestPermission("fields:phi", Regions::Boundaries), + make_permission(PermissionTypes::Write, "fields:phi")); + EXPECT_EQ(example.getHighestPermission("species:he+:temperature", Regions::Boundaries), + make_permission(PermissionTypes::Write, "species:he+:temperature")); // Get the highest permission on the interior EXPECT_EQ(example.getHighestPermission("species:he:charge", Regions::Interior), @@ -190,6 +200,10 @@ TEST(PermissionsTests, TestGetHighestPermission) { Regions::Interior), make_permission(PermissionTypes::None, "species:d:collision_frequencies")); EXPECT_EQ(example.getHighestPermission("unset", Regions::Interior), no_permission); + EXPECT_EQ(example.getHighestPermission("fields:phi", Regions::Interior), + make_permission(PermissionTypes::Read, "fields:phi")); + EXPECT_EQ(example.getHighestPermission("species:he+:temperature", Regions::Interior), + make_permission(PermissionTypes::ReadIfSet, "species:he+:temperature")); // Check the permission for the "Nowhere" region is always "None" EXPECT_EQ(example.getHighestPermission("species:he:charge", Regions::Nowhere), @@ -211,6 +225,9 @@ TEST(PermissionsTests, TestGetHighestPermission) { Regions::Nowhere), no_permission); EXPECT_EQ(example.getHighestPermission("unset", Regions::Nowhere), no_permission); + EXPECT_EQ(example.getHighestPermission("fields:phi", Regions::Nowhere), no_permission); + EXPECT_EQ(example.getHighestPermission("species:he+:temperature", Regions::Nowhere), + no_permission); // Check permissions for a species that might be mistaken for one of // the sections we've given permissions for @@ -358,7 +375,7 @@ TEST(PermissionsTests, TestIO) { const Permissions empty({}); const Permissions single({readOnly("test")}); const Permissions multiple( - {readIfSet("a", Regions::Interior), writeBoundary("b"), readWrite("c:d")}); + {readIfSet("a", Regions::Interior), writeBoundaryFinal("b"), readWrite("c:d")}); Permissions new_perm; std::stringstream ss1; From 3c578611e9722a08776ddbc931c14a1823145bcf Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Mon, 15 Dec 2025 15:02:59 +0000 Subject: [PATCH 53/63] Add better comments explaining component sorting process --- src/component_scheduler.cxx | 80 +++++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 22 deletions(-) diff --git a/src/component_scheduler.cxx b/src/component_scheduler.cxx index 16a540521..e97581a5b 100644 --- a/src/component_scheduler.cxx +++ b/src/component_scheduler.cxx @@ -15,7 +15,15 @@ const std::set ComponentScheduler::predeclared_variables = { "time", "linear", "units:inv_meters_cubed", "units:eV", "units:Tesla", "units:seconds", "units:meters"}; -/// Perform a depth-first topological sort, starting from `item`. +/// Perform a depth-first topological sort, starting from `item`. It +/// will finish once it reaches the end of `item`'s dependency chain, +/// so this needs to be called in a loop for all items. Information +/// about `item` is stored in the corresponding index of the vector +/// arguments. +/// +/// In pratice, `item` here represents the index of a particular +/// component. The indices of the dependencies of `item` are stored in +/// the corresponding element of `dependencies`. void topological_sort(const std::vector>& dependencies, size_t item, std::vector& sorted, std::vector& processing, std::vector& processed) { @@ -34,6 +42,8 @@ void topological_sort(const std::vector>& dependencies, size_t sorted.push_back(item); } +/// Get all the parent sections of a variable "path". Sections are +/// separated by colons in the path. std::set getParents(const std::string& name) { std::set result; size_t start = 0, position = name.find(":", start); @@ -45,9 +55,12 @@ std::set getParents(const std::string& name) { return result; } -/// Produce a map between Option paths and all variable names held within -/// that path. If a path does not refer to a section then it just maps -/// to itself. +/// Produce a map between Option paths and all variable names held +/// within that path. If the path refers to a section then it maps to +/// the set of all variables contained in that section and any +/// sub-sections. Otherwise the path corresponds to a variable and +/// just maps to itself. Only paths which are explicitly given a +/// permission by at least one component will be present. std::map> getVariableHierarchy(const std::vector>& components) { // Build up a set of all variable names which are read only if they @@ -61,7 +74,7 @@ getVariableHierarchy(const std::vector>& components) } } - // Build up a set of all variable names which are definitely + // Build up a set of all section/variable names which are definitely // read/written by components, and the sections which they imply // exist std::set unconditional_names, unconditional_sections; @@ -74,12 +87,15 @@ getVariableHierarchy(const std::vector>& components) } } - // Split the set of all variable names used by components into - // those that are sections and those that are not. - std::set sections_present, non_sections; + /// Assemble the set of all section names which are referred to + /// explicitly in the component permissions. + std::set sections_present; std::set_intersection(unconditional_names.begin(), unconditional_names.end(), unconditional_sections.begin(), unconditional_sections.end(), std::inserter(sections_present, sections_present.begin())); + /// Assemble the set of all variable names which are definitlye + /// read/written by components (i.e., not including sections) + std::set non_sections; std::set_difference(unconditional_names.begin(), unconditional_names.end(), sections_present.begin(), sections_present.end(), std::inserter(non_sections, non_sections.begin())); @@ -90,7 +106,9 @@ getVariableHierarchy(const std::vector>& components) // reference elsewhere. We create them with empty sets, which will // get filled if they are present. for (const auto& name : conditional_names) { - result.insert({name, {name}}); + // FIXME: this isn't an empty set + //result.insert({name, {name}}); + result.insert({name, {}}); } // Non-sections map to themselves @@ -131,6 +149,8 @@ expandVariableName(const std::map>& hierarchy using Var = std::pair; +/// Create a map between a variable and the set of components that +/// access it with the specified permission level. std::map> getPermissionComponentMap(const std::vector>& components, const std::map>& hierarchy, @@ -153,8 +173,11 @@ getPermissionComponentMap(const std::vector>& compone } /// Modifies component_dependencies to include information on which -/// variables each component reads. Returns a set of any read -/// variables which are not written by any component. +/// components depend on each other. It does this by making components +/// which read a variable depend on whichever component(s) write that +/// variable (information contained in the `writers` +/// argument). Returns a set of any read variables which are not +/// written by any component. std::set setReadDependencies(const std::vector>& components, const std::map>& hierarchy, @@ -193,7 +216,10 @@ setReadDependencies(const std::vector>& components, /// the right order. std::vector> sortComponents(std::vector>&& components) { - // Map variables to the components that write them + // Map between variable/section names specified by component + // permissions and the variables they contain. In the case of + // sections this is all variables within the section and any + // sub-sections. Non-section viarables map to themselves. std::map> variable_hierarchy = getVariableHierarchy(components); @@ -205,6 +231,10 @@ sortComponents(std::vector>&& components) { components, variable_hierarchy, PermissionTypes::Final); + // Object mapping between components (reprsented by the index of + // that component in the `components` argument) and the components + // each of these depends upon (represented by a set of the indices + // for those components). std::vector> component_dependencies(components.size()); // Components which do a final write on a variable depend on all @@ -217,22 +247,27 @@ sortComponents(std::vector>&& components) { for (size_t i : comp_indices) { const auto item = nonfinal_writes.find(var); if (item != nonfinal_writes.end()) { - // Note that calling merge actually removes the items from - // nonfinal_writes. This is fine because the only remaining - // thing for which we will use nonfinal_writes is setting up - // variable_writers. That doesn't use any information on - // nonfinal writes for variables which have a final write, so - // it won't do any harm to remove it. + // Note that calling merge actually removes the items from the + // sets stored in nonfinal_writes. This is fine because the + // only remaining thing for which we will use nonfinal_writes + // is setting up variable_writers and that doesn't use any + // information on variables which have a final + // write. Therefore it won't do any harm hear to remove + // information about variables which have a final write.. component_dependencies[i].merge(item->second); } } } - // Work out which component(s) last write a variable before it may be read + // Work out which component(s) last write a variable before it may + // be read. For variables with a final-write, it is whichever + // component performs that final write. std::map> variable_writers = std::move(final_writes); + // For other variables, it is the set of all components which have + // write permission. variable_writers.merge(std::move(nonfinal_writes)); - // Create dependency information for read variables + // Insert dependency information for components that (unconditionally) read variables std::set missing = setReadDependencies(components, variable_hierarchy, variable_writers, PermissionTypes::Read, component_dependencies); @@ -241,8 +276,9 @@ sortComponents(std::vector>&& components) { "The following required variables are not written by any component:\n\t{}\n", fmt::format("{}", fmt::join(missing, "\n\t"))); } - // If can not find a place where a ReadIfSet variable is written, it will just be - // skipped + // Insert dependency information for components that read variables + // if those variables have been set. If can not find a place where + // the variable is written, it will just be skipped. setReadDependencies(components, variable_hierarchy, variable_writers, PermissionTypes::ReadIfSet, component_dependencies); From b1dfb25a20f1e66d574751158fc762ce34a569cb Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Fri, 2 Jan 2026 13:38:02 +0000 Subject: [PATCH 54/63] Responded to some comments by @ZedThree The comments were in PR #445, but some of them actually relate to work done in #428. Only the latter are dealt with in this PR. --- include/component.hxx | 66 +++++++++++++++++---------------- include/component_scheduler.hxx | 5 ++- src/component_scheduler.cxx | 47 +++++++++++++---------- 3 files changed, 64 insertions(+), 54 deletions(-) diff --git a/include/component.hxx b/include/component.hxx index 4440a6d19..d0b18aaf0 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -1,17 +1,7 @@ #pragma once - #ifndef HERMES_COMPONENT_H #define HERMES_COMPONENT_H -#include -#include -#include -#include -#include -#include -#include -#include - #include #include #include @@ -22,6 +12,15 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include + #include "guarded_options.hxx" #include "permissions.hxx" #include "hermes_utils.hxx" @@ -34,8 +33,10 @@ struct SpeciesInformation { SpeciesInformation(const std::vector& electrons, const std::vector& neutrals, const std::vector& positive_ions, - const std::vector & negative_ions) - : electrons(electrons), neutrals(neutrals), positive_ions(positive_ions), negative_ions(negative_ions), ions(positive_ions) { + const std::vector& negative_ions) + : electrons(std::move(electrons)), neutrals(std::move(neutrals)), + positive_ions(std::move(positive_ions)), negative_ions(std::move(negative_ions)), + ions(std::move(positive_ions)) { finish_construction(); } @@ -112,10 +113,9 @@ struct Component { /// @param name The species/name for this instance. /// @param options Component settings: options[name] are specific to this component /// @param solver Time-integration solver - static std::unique_ptr create(const std::string &type, // The type to create - const std::string &name, // The species/name for this instance - Options &options, // Component settings: options[name] are specific to this component - Solver *solver); // Time integration solver + static std::unique_ptr create(const std::string& type, + const std::string& name, Options& options, + Solver* solver); /// Tell the component the name of all species in the simulation, by type. It /// will use this information to substitute the following placeholders in @@ -451,14 +451,15 @@ template Options& add(Options& option, T value) { if (!option.isSet()) { return set(option, value); - } else { - try { - return set(option, value + bout::utils::variantStaticCastOrThrow(option.value)); - } catch (const std::bad_cast &e) { - // Convert to a more useful error message - throw BoutException("Could not convert {:s} to type {:s}", - option.str(), typeid(T).name()); - } + } + try { + return set(option, value + + bout::utils::variantStaticCastOrThrow( + option.value)); + } catch (const std::bad_cast& e) { + // Convert to a more useful error message + throw BoutException("Could not convert {:s} to type {:s}", option.str(), + typeid(T).name()); } } @@ -477,14 +478,15 @@ template Options& subtract(Options& option, T value) { if (!option.isSet()) { return set(option, -value); - } else { - try { - return set(option, bout::utils::variantStaticCastOrThrow(option.value) - value); - } catch (const std::bad_cast &e) { - // Convert to a more useful error message - throw BoutException("Could not convert {:s} to type {:s}", - option.str(), typeid(T).name()); - } + } + try { + return set(option, + bout::utils::variantStaticCastOrThrow(option.value) + - value); + } catch (const std::bad_cast& e) { + // Convert to a more useful error message + throw BoutException("Could not convert {:s} to type {:s}", option.str(), + typeid(T).name()); } } diff --git a/include/component_scheduler.hxx b/include/component_scheduler.hxx index a67c27b06..c02cbd2ad 100644 --- a/include/component_scheduler.hxx +++ b/include/component_scheduler.hxx @@ -1,10 +1,11 @@ #pragma once - #ifndef COMPONENT_SCHEDULER_H #define COMPONENT_SCHEDULER_H -#include #include +#include +#include +#include #include #include diff --git a/src/component_scheduler.cxx b/src/component_scheduler.cxx index e97581a5b..3eb83baf7 100644 --- a/src/component_scheduler.cxx +++ b/src/component_scheduler.cxx @@ -1,15 +1,23 @@ #include +#include +#include +#include #include +#include #include +#include #include #include +#include #include #include // for trim, strsplit +#include #include #include "../include/component.hxx" #include "../include/component_scheduler.hxx" +#include "../include/permissions.hxx" const std::set ComponentScheduler::predeclared_variables = { "time", "linear", "units:inv_meters_cubed", "units:eV", "units:Tesla", @@ -46,7 +54,8 @@ void topological_sort(const std::vector>& dependencies, size_t /// separated by colons in the path. std::set getParents(const std::string& name) { std::set result; - size_t start = 0, position = name.find(":", start); + size_t start = 0; + size_t position = name.find(":", start); while (position != std::string::npos) { result.insert(name.substr(0, position)); start = position + 1; @@ -77,7 +86,8 @@ getVariableHierarchy(const std::vector>& components) // Build up a set of all section/variable names which are definitely // read/written by components, and the sections which they imply // exist - std::set unconditional_names, unconditional_sections; + std::set unconditional_names; + std::set unconditional_sections; for (const auto& component : components) { const Permissions& permissions = component->getPermissions(); for (const auto& [varname, _] : @@ -191,8 +201,9 @@ setReadDependencies(const std::vector>& components, // them for (const auto& [name, regions] : permissions.getVariablesWithPermission(permission)) { - if (ComponentScheduler::predeclared_variables.count(name) > 0) + if (ComponentScheduler::predeclared_variables.count(name) > 0) { continue; + } for (const auto& sub_name : expandVariableName(hierarchy, permissions, name)) { for (const auto& [region, _] : Permissions::fundamental_regions) { if ((regions & region) == region) { @@ -211,25 +222,21 @@ setReadDependencies(const std::vector>& components, return missing; } -/// Consumes a list of components and returns a new one that has been -/// topolgically sorted to ensure variables are written and read in -/// the right order. -std::vector> -sortComponents(std::vector>&& components) { +/// Topologically sorts the list of components to ensure variables are +/// written and read in the right order. +void sortComponents(std::vector>& components) { // Map between variable/section names specified by component // permissions and the variables they contain. In the case of // sections this is all variables within the section and any // sub-sections. Non-section viarables map to themselves. - std::map> variable_hierarchy = + const std::map> variable_hierarchy = getVariableHierarchy(components); // Get information on which components write each variable - std::map> nonfinal_writes = getPermissionComponentMap( - components, variable_hierarchy, - PermissionTypes::Write), - final_writes = getPermissionComponentMap( - components, variable_hierarchy, - PermissionTypes::Final); + std::map> nonfinal_writes = + getPermissionComponentMap(components, variable_hierarchy, PermissionTypes::Write); + std::map> final_writes = + getPermissionComponentMap(components, variable_hierarchy, PermissionTypes::Final); // Object mapping between components (reprsented by the index of // that component in the `components` argument) and the components @@ -244,7 +251,7 @@ sortComponents(std::vector>&& components) { throw BoutException( "Multiple components have permission to make final write to variable {}", var); } - for (size_t i : comp_indices) { + for (const size_t i : comp_indices) { const auto item = nonfinal_writes.find(var); if (item != nonfinal_writes.end()) { // Note that calling merge actually removes the items from the @@ -283,8 +290,8 @@ sortComponents(std::vector>&& components) { PermissionTypes::ReadIfSet, component_dependencies); // Create ancillary variables for sorting process - std::vector processing(components.size(), false), - processed(components.size(), false); + std::vector processing(components.size(), false); + std::vector processed(components.size(), false); std::vector order; // Perform the sort @@ -300,7 +307,7 @@ sortComponents(std::vector>&& components) { std::swap(result[i], components[order[i]]); } - return result; + components = std::move(result); } ComponentScheduler::ComponentScheduler(Options &scheduler_options, @@ -372,7 +379,7 @@ ComponentScheduler::ComponentScheduler(Options &scheduler_options, component->declareAllSpecies(species); } - components = sortComponents(std::move(components)); + sortComponents(components); } std::unique_ptr ComponentScheduler::create(Options &scheduler_options, From 04a8977d97582979db44ea2cf2b60c37e7e4b457 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Mon, 5 Jan 2026 16:50:21 +0000 Subject: [PATCH 55/63] Add missed variable permission --- src/braginskii_conduction.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braginskii_conduction.cxx b/src/braginskii_conduction.cxx index f38d08ed2..ff4f9a73a 100644 --- a/src/braginskii_conduction.cxx +++ b/src/braginskii_conduction.cxx @@ -27,7 +27,7 @@ using bout::globals::mesh; BraginskiiConduction::BraginskiiConduction(const std::string&, Options& alloptions, Solver*) - : Component({readOnly("species:{sp}:{input_vars}"), + : Component({readIfSet("fields:Apar_flutter"), readOnly("species:{sp}:{input_vars}"), readWrite("species:{sp}:{output_vars}")}) { AUTO_TRACE(); From bec7513e148144d5c1bfd11cc79ccdd623ce22c8 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Thu, 11 Dec 2025 17:26:49 +0000 Subject: [PATCH 56/63] Add ability for components to request additional components --- include/component.hxx | 37 ++++++++++++++++---- src/component_scheduler.cxx | 21 ++++++++--- tests/unit/test_component_scheduler.cxx | 46 +++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 11 deletions(-) diff --git a/include/component.hxx b/include/component.hxx index d0b18aaf0..b813eb804 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -73,6 +73,24 @@ struct SpeciesInformation { } }; +/// Lightweight type to store information used to build a component. +struct ComponentInformation { + std::string name; + std::string type; + + ComponentInformation(const std::string& name_, const std::string& type_) : name(name_), type(type_) {} + + ComponentInformation(std::string&& name_, std::string&& type_) : name(std::move(name_)), type(std::move(type_)) {} + + bool operator<(const ComponentInformation& other) const { + return std::pair(name, type) < std::pair(other.name, other.type); + } + + bool operator==(const ComponentInformation& other) const { + return std::pair(name, type) == std::pair(other.name, other.type); + } +}; + /// Interface for a component of a simulation model /// /// The constructor of derived types should have signature @@ -89,24 +107,29 @@ struct Component { virtual ~Component() {} + /// Return a list of names/types of other components needed by this + /// component. All configurations for these components will take the + /// default value, unless set in the input file. + virtual std::vector additionalComponents() { return {}; } + /// Modify the given simulation state. This method will wrap the /// state in a GuardedOptions object and pass that to the private /// implementation of transform provided by each component. - void transform(Options &state); - + void transform(Options& state); + /// Use the final simulation state to update internal state /// (e.g. time derivatives) - virtual void finally(const Options &UNUSED(state)) { } + virtual void finally(const Options& UNUSED(state)) {} /// Add extra fields for output, or set attributes e.g docstrings - virtual void outputVars(Options &UNUSED(state)) { } + virtual void outputVars(Options& UNUSED(state)) {} /// Add extra fields to restart files - virtual void restartVars(Options &UNUSED(state)) { } + virtual void restartVars(Options& UNUSED(state)) {} /// Preconditioning - virtual void precon(const Options &UNUSED(state), BoutReal UNUSED(gamma)) { } - + virtual void precon(const Options& UNUSED(state), BoutReal UNUSED(gamma)) {} + /// Create a Component /// /// @param type The name of the component type to create (e.g. "evolve_density") diff --git a/src/component_scheduler.cxx b/src/component_scheduler.cxx index 3eb83baf7..a290b127b 100644 --- a/src/component_scheduler.cxx +++ b/src/component_scheduler.cxx @@ -318,6 +318,8 @@ ComponentScheduler::ComponentScheduler(Options &scheduler_options, .doc("Components in order of execution") .as(); + std::set required_components; + std::vector electrons; std::vector neutrals; std::vector positive_ions; @@ -366,11 +368,22 @@ ComponentScheduler::ComponentScheduler(Options &scheduler_options, continue; } - components.push_back(Component::create(type_trimmed, - name_trimmed, - component_options, - solver)); + required_components.emplace(name_trimmed, type_trimmed); + } + } + + std::set created_components; + for (auto it = required_components.begin(); it != required_components.end(); + it = required_components.begin()) { + auto comp = Component::create(it->type, it->name, component_options, solver); + for (const auto& sub_comp : comp->additionalComponents()) { + if (required_components.count(sub_comp) == 0 + and created_components.count(sub_comp) == 0) { + required_components.insert(sub_comp); + } } + components.push_back(std::move(comp)); + created_components.insert(required_components.extract(it)); } const SpeciesInformation species(electrons, neutrals, positive_ions, negative_ions); diff --git a/tests/unit/test_component_scheduler.cxx b/tests/unit/test_component_scheduler.cxx index 9e2df5ac0..5cda9961b 100644 --- a/tests/unit/test_component_scheduler.cxx +++ b/tests/unit/test_component_scheduler.cxx @@ -27,6 +27,18 @@ struct TestMultiply : public Component { } }; +struct TestAdditionalComponent : public Component { + TestAdditionalComponent(const std::string&, Options&, Solver*) + : Component({}) {} + + void transform_impl(GuardedOptions&) override { + } + + std::vector additionalComponents() override { + return {{"TestComponent", "testcomponent"}, {"component2", "multiply"}}; + } +}; + struct OrderChecker : public Component { OrderChecker(const std::string& name, Options& alloptions, Solver*) : Component(getPermissions(name, alloptions)), name(name) {} @@ -46,6 +58,7 @@ std::vector OrderChecker::execution_order; RegisterComponent registertestcomponent("testcomponent"); RegisterComponent registertestcomponent2("multiply"); +RegisterComponent registertestcomponent3("additionalcomponent"); RegisterComponent registercomponentorderchecker("orderchecker"); } // namespace @@ -84,6 +97,39 @@ TEST(SchedulerTest, SubComponents) { ASSERT_TRUE(options["answer"] == 42 * 2); } +TEST(SchedulerTest, AdditionalComponents) { + Options options; + options["components"] = "additionalcomponent"; + auto scheduler = ComponentScheduler::create(options, options, nullptr); + + EXPECT_FALSE(options.isSet("answer")); + scheduler->transform(options); + ASSERT_TRUE(options.isSet("answer")); + ASSERT_TRUE(options["answer"] == 42 * 2); +} + +TEST(SchedulerTest, AdditionalComponentsPredeclared) { + Options options; + options["components"] = "testcomponent, additionalcomponent"; + auto scheduler = ComponentScheduler::create(options, options, nullptr); + + EXPECT_FALSE(options.isSet("answer")); + scheduler->transform(options); + ASSERT_TRUE(options.isSet("answer")); + ASSERT_TRUE(options["answer"] == 42 * 2); +} + +TEST(SchedulerTest, AdditionalComponentsPredeclared2) { + Options options; + options["components"] = "additionalcomponent, testcomponent"; + auto scheduler = ComponentScheduler::create(options, options, nullptr); + + EXPECT_FALSE(options.isSet("answer")); + scheduler->transform(options); + ASSERT_TRUE(options.isSet("answer")); + ASSERT_TRUE(options["answer"] == 42 * 2); +} + using Parameter = std::pair>; class ComponentOrderTest : public testing::TestWithParam { From cb1d7a58bb5719b7555eba53e8b4abccf4fcc2b7 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Thu, 11 Dec 2025 17:28:14 +0000 Subject: [PATCH 57/63] Add Braginskii meta-component to activate all parts of closure --- CMakeLists.txt | 1 + hermes-3.cxx | 1 + include/braginskii_closure.hxx | 59 +++++++++++++++++ tests/unit/test_braginskii_closure.cxx | 89 ++++++++++++++++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 include/braginskii_closure.hxx create mode 100644 tests/unit/test_braginskii_closure.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 51a96bc83..9d35589fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,6 +115,7 @@ set(HERMES_SOURCES include/anomalous_diffusion.hxx include/classical_diffusion.hxx include/binormal_stpm.hxx + include/braginskii_closure.hxx include/braginskii_collisions.hxx include/braginskii_conduction.hxx include/braginskii_electron_viscosity.hxx diff --git a/hermes-3.cxx b/hermes-3.cxx index 46f6a7f6e..aea8a87bf 100644 --- a/hermes-3.cxx +++ b/hermes-3.cxx @@ -30,6 +30,7 @@ #include "include/amjuel_hydrogen.hxx" #include "include/anomalous_diffusion.hxx" #include "include/binormal_stpm.hxx" +#include "include/braginskii_closure.hxx" #include "include/braginskii_collisions.hxx" #include "include/braginskii_conduction.hxx" #include "include/braginskii_electron_viscosity.hxx" diff --git a/include/braginskii_closure.hxx b/include/braginskii_closure.hxx new file mode 100644 index 000000000..e70e311cf --- /dev/null +++ b/include/braginskii_closure.hxx @@ -0,0 +1,59 @@ +#pragma once +#ifndef BRAGINSKII_H +#define BRAGINSKII_H + +#include + +#include "component.hxx" + +/// Meta-component to set up all components necessary for the +/// Braginskii closure. +class BraginskiiClosure : public Component { +public: + BraginskiiClosure(std::string name, Options& alloptions, Solver*) : Component({}) { + Options& options = alloptions[name]; + electron_viscosity = options["electron_viscosity"] + .doc("Include electron viscosity terms?") + .withDefault(true); + ion_viscosity = options["ion_viscosity"] + .doc("Include ion viscosity terms?") + .withDefault(true); + thermal_force = options["thermal_force"] + .doc("Include thermal force terms?") + .withDefault(true); + } + + virtual std::vector additionalComponents() override { + std::vector result = { + {"braginskii_collisions", "braginskii_collisions"}, + {"braginskii_friction", "braginskii_friction"}, + {"braginskii_heat_exchange", "braginskii_heat_exchange"}, + {"braginskii_conduction", "braginskii_conduction"}}; + if (electron_viscosity) { + result.emplace_back("braginskii_electron_viscosity", + "braginskii_electron_viscosity"); + } + if (ion_viscosity) { + result.emplace_back("braginskii_ion_viscosity", "braginskii_ion_viscosity"); + } + if (thermal_force) { + result.emplace_back("braginskii_thermal_force", "braginskii_thermal_force"); + } + return result; + } + +private: + bool electron_viscosity; /// Whether to include electron viscosity terms + bool ion_viscosity; /// Whether to include ion viscosity terms + bool thermal_force; /// Whether to include thermal force terms + + /// Empty transform; all the work actually happens in the subcomponents. + void transform_impl(GuardedOptions&) override {} +}; + +namespace { +RegisterComponent + registercomponentbraginskiiclosure("braginskii_closure"); +} + +#endif // BRAGINSKII_H diff --git a/tests/unit/test_braginskii_closure.cxx b/tests/unit/test_braginskii_closure.cxx new file mode 100644 index 000000000..3f9da557f --- /dev/null +++ b/tests/unit/test_braginskii_closure.cxx @@ -0,0 +1,89 @@ + +#include "gtest/gtest.h" + +#include "fake_mesh_fixture.hxx" +#include "test_extras.hxx" // FakeMesh + +#include "../../include/braginskii_closure.hxx" + +/// Global mesh +namespace bout { +namespace globals { +extern Mesh* mesh; +} // namespace globals +} // namespace bout + +// The unit tests use the global mesh +using namespace bout::globals; + +// Reuse the "standard" fixture for FakeMesh +using BraginskiiClosureTest = FakeMeshFixture; + +std::set makeExpected(std::initializer_list names) { + std::set result; + for (const auto& name : names) { + result.emplace(name, name); + } + return result; +} + +std::set toSet(std::vector input) { + return std::set(input.begin(), input.end()); +} + +TEST_F(BraginskiiClosureTest, CreateDefault) { + Options options; + BraginskiiClosure component("test", options, nullptr); + EXPECT_EQ(toSet(component.additionalComponents()), + makeExpected({"braginskii_collisions", "braginskii_friction", + "braginskii_heat_exchange", "braginskii_conduction", + "braginskii_electron_viscosity", "braginskii_ion_viscosity", + "braginskii_thermal_force"})); +} + +TEST_F(BraginskiiClosureTest, CreateMinimal) { + Options options = {{"test", + {{"electron_viscosity", false}, + {"ion_viscosity", false}, + {"thermal_force", false}}}}; + BraginskiiClosure component("test", options, nullptr); + EXPECT_EQ(toSet(component.additionalComponents()), + makeExpected({"braginskii_collisions", "braginskii_friction", + "braginskii_heat_exchange", "braginskii_conduction"})); +} + +TEST_F(BraginskiiClosureTest, CreateThermalForce) { + Options options = {{"test", + {{"electron_viscosity", false}, + {"ion_viscosity", false}, + {"thermal_force", true}}}}; + BraginskiiClosure component("test", options, nullptr); + EXPECT_EQ(toSet(component.additionalComponents()), + makeExpected({"braginskii_collisions", "braginskii_friction", + "braginskii_heat_exchange", "braginskii_conduction", + "braginskii_thermal_force"})); +} + +TEST_F(BraginskiiClosureTest, CreateIonViscosity) { + Options options = {{"test", + {{"electron_viscosity", false}, + {"ion_viscosity", true}, + {"thermal_force", false}}}}; + BraginskiiClosure component("test", options, nullptr); + EXPECT_EQ(toSet(component.additionalComponents()), + makeExpected({"braginskii_collisions", "braginskii_friction", + "braginskii_heat_exchange", "braginskii_conduction", + "braginskii_ion_viscosity"})); +} + +TEST_F(BraginskiiClosureTest, CreateElectronViscosity) { + Options options = {{"test", + {{"electron_viscosity", true}, + {"ion_viscosity", false}, + {"thermal_force", false}}}}; + BraginskiiClosure component("test", options, nullptr); + EXPECT_EQ(toSet(component.additionalComponents()), + makeExpected({"braginskii_collisions", "braginskii_friction", + "braginskii_heat_exchange", "braginskii_conduction", + "braginskii_electron_viscosity"})); +} From 685ccde57184031202daace03cd451ad0cf74336 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Mon, 15 Dec 2025 14:19:38 +0000 Subject: [PATCH 58/63] Add documentation for creating sub-components. --- docs/sphinx/closure.rst | 7 ++++++- docs/sphinx/developer.rst | 16 +++++++++++++++- include/braginskii_closure.hxx | 11 ++++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/docs/sphinx/closure.rst b/docs/sphinx/closure.rst index e9c0c4b6e..ed90ed926 100644 --- a/docs/sphinx/closure.rst +++ b/docs/sphinx/closure.rst @@ -39,7 +39,12 @@ The parallel projection of diffusion from the wall in 1D is captured in the :ref:`neutral_parallel_diffusion` top-level component, while both parallel Braginskii transport and perpendicular pressure-diffusion for 2D/3D are captured in the :ref:`neutral_mixed` species-level component. - + +A user can automatically activate all of these components at once +using the `BraginskiiClosure` component. + +.. doxygenclass:: BraginskiiClosure + :members: Collision frequency selection diff --git a/docs/sphinx/developer.rst b/docs/sphinx/developer.rst index c89f046b2..9a40acccf 100644 --- a/docs/sphinx/developer.rst +++ b/docs/sphinx/developer.rst @@ -618,6 +618,17 @@ species and density of ions:: See the documentation for `Component::declareAllSpecies` for a list of all substitutions that will be performed. +Components may request the creation of additional components, upon +which they depend. This is done by overriding the +`Component::additionalComponents` method, which returns a list of +`ComponentInformation` structs that specify the names and types of +components required. These extra components will use the default +settings for components of this type, unless other values are +specified in a section of the input file with the component name. + +.. doxygenstruct:: ComponentInformation + :members: + Component scheduler ~~~~~~~~~~~~~~ @@ -632,7 +643,10 @@ and then in `Hermes::rhs` the components are run by a call:: The call to `ComponentScheduler::create` treats the "components" option as a comma-separated list of names. For each name in the list, -the scheduler looks up the options under the section of that name. The +the scheduler looks up the options under the section of that name. If +any of the listed components request further components +(via the `Component::AdditionalComponents` method) then these will be +created too. The ``ComponentScheduler`` will use permission information stored by each component in `Component::state_variable_access` to work out the order to execute components. It will ensure that all writes to a variable diff --git a/include/braginskii_closure.hxx b/include/braginskii_closure.hxx index e70e311cf..997b51acf 100644 --- a/include/braginskii_closure.hxx +++ b/include/braginskii_closure.hxx @@ -7,9 +7,18 @@ #include "component.hxx" /// Meta-component to set up all components necessary for the -/// Braginskii closure. +/// Braginskii closure: `braginskii_collisions`, +/// `braginskii_friction`, `braginskii_heat_exchange`, +/// `braginskii_conduction`, `braginskii_electron_viscosity`, +/// `braginskii_ion_viscosity`, and `braginskii_thermal_force`. Each +/// of these components will have the same name as its type. class BraginskiiClosure : public Component { public: + /// @param alloptions Settings, which may include + /// - + /// - electron_viscosity : bool Include electron viscosity? (default: true) + /// - ion_viscosity : bool Include ion viscosity? (default: true) + /// - thermal_force : bool Inlucde thermal force between species? (default: true) BraginskiiClosure(std::string name, Options& alloptions, Solver*) : Component({}) { Options& options = alloptions[name]; electron_viscosity = options["electron_viscosity"] From c5bd5898e48b497337fba06b0474e897eeb2c7be Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Tue, 6 Jan 2026 15:26:34 +0000 Subject: [PATCH 59/63] Allow integration tests to be run in parallel in CI --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c181f0756..cddb7a76a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -82,7 +82,7 @@ jobs: echo ~/.local/bin >> $GITHUB_PATH - name: Run tests run: cd build && - ctest --output-on-failure --timeout 300 -L $TEST_TYPE + ctest --output-on-failure --timeout 300 -L $TEST_TYPE -j4 - name: Upload test coverage data uses: codecov/codecov-action@v5 if: ${{ matrix.build_type.coverage == 'ON' }} From 736052beab200e18f3bdf9483f7fe46df4a0278f Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Tue, 6 Jan 2026 15:27:10 +0000 Subject: [PATCH 60/63] Ensure reactions do not depend on order in which they are built --- include/amjuel_helium.hxx | 4 ++-- include/amjuel_hydrogen.hxx | 8 ++++---- include/amjuel_reaction.hxx | 4 ++-- include/reaction.hxx | 2 +- src/reaction.cxx | 16 +--------------- tests/unit/test_amjueldata.cxx | 12 ++++++------ 6 files changed, 16 insertions(+), 30 deletions(-) diff --git a/include/amjuel_helium.hxx b/include/amjuel_helium.hxx index 95d8c88ba..954c4553f 100644 --- a/include/amjuel_helium.hxx +++ b/include/amjuel_helium.hxx @@ -11,7 +11,7 @@ */ struct AmjuelHeIonisation01 : public AmjuelReaction { AmjuelHeIonisation01(std::string name, Options& alloptions, Solver*) - : AmjuelReaction(name, "iz", "H.x_2.3.9a", "he", "he+", alloptions) { + : AmjuelReaction(name, "iz", "H.x_2.3.9a", "he + e -> he+ + 2e", "he", "he+", alloptions) { rate_multiplier = alloptions[std::string("he")]["ionisation_rate_multiplier"] .doc("Scale the ionisation rate by this factor") @@ -32,7 +32,7 @@ struct AmjuelHeIonisation01 : public AmjuelReaction { */ struct AmjuelHeRecombination10 : public AmjuelReaction { AmjuelHeRecombination10(std::string name, Options& alloptions, Solver*) - : AmjuelReaction(name, "rec", "H.x_2.3.13a", "he+", "he", alloptions) { + : AmjuelReaction(name, "rec", "H.x_2.3.13a", "he+ + e -> he", "he+", "he", alloptions) { rate_multiplier = alloptions[name]["recombination_rate_multiplier"] .doc("Scale the recombination rate by this factor") diff --git a/include/amjuel_hydrogen.hxx b/include/amjuel_hydrogen.hxx index f844bb96b..d55af52af 100644 --- a/include/amjuel_hydrogen.hxx +++ b/include/amjuel_hydrogen.hxx @@ -19,9 +19,9 @@ static std::map long_reaction_types_map = { template struct AmjuelHydIsotopeReaction : public AmjuelReaction { AmjuelHydIsotopeReaction(std::string name, std::string short_reaction_type, - std::string amjuel_label, std::string from_species, + std::string amjuel_label, const std::string & reaction_str, std::string from_species, std::string to_species, Options& alloptions) - : AmjuelReaction(name, short_reaction_type, amjuel_label, from_species, to_species, + : AmjuelReaction(name, short_reaction_type, amjuel_label, reaction_str, from_species, to_species, alloptions) { if (this->diagnose) { // Set up diagnostics @@ -82,7 +82,7 @@ struct AmjuelHydIsotopeReaction : public AmjuelReaction { template struct AmjuelHydRecombinationIsotope : public AmjuelHydIsotopeReaction { AmjuelHydRecombinationIsotope(std::string name, Options& alloptions, Solver*) - : AmjuelHydIsotopeReaction(name, "rec", "H.x_2.1.8", {Isotope, '+'}, + : AmjuelHydIsotopeReaction(name, "rec", "H.x_2.1.8", fmt::format("{}+ + e -> {}", Isotope, Isotope), {Isotope, '+'}, {Isotope}, alloptions) { this->rate_multiplier = alloptions[{Isotope}]["K_rec_multiplier"] @@ -104,7 +104,7 @@ struct AmjuelHydRecombinationIsotope : public AmjuelHydIsotopeReaction template struct AmjuelHydIonisationIsotope : public AmjuelHydIsotopeReaction { AmjuelHydIonisationIsotope(std::string name, Options& alloptions, Solver*) - : AmjuelHydIsotopeReaction(name, "iz", "H.x_2.1.5", {Isotope}, + : AmjuelHydIsotopeReaction(name, "iz", "H.x_2.1.5", fmt::format("{} + e -> {}+ + 2e", Isotope, Isotope), {Isotope}, {Isotope, '+'}, alloptions) { this->rate_multiplier = alloptions[{Isotope}]["K_iz_multiplier"] .doc("Scale the ionisation rate by this factor") diff --git a/include/amjuel_reaction.hxx b/include/amjuel_reaction.hxx index 33a197349..2580a5886 100644 --- a/include/amjuel_reaction.hxx +++ b/include/amjuel_reaction.hxx @@ -33,9 +33,9 @@ static inline std::filesystem::path get_json_db_dir(Options& options) { */ struct AmjuelReaction : public Reaction { AmjuelReaction(std::string name, std::string short_reaction_type, - std::string amjuel_lbl, std::string from_species, std::string to_species, + std::string amjuel_lbl, const std::string & reaction_str, std::string from_species, std::string to_species, Options& alloptions) - : Reaction(name, alloptions), amjuel_src(std::string("Amjuel ") + amjuel_lbl), + : Reaction(name, alloptions, reaction_str), amjuel_src(std::string("Amjuel ") + amjuel_lbl), short_reaction_type(short_reaction_type), from_species(from_species), to_species(to_species), amjuel_data(get_json_db_dir(alloptions), short_reaction_type, amjuel_lbl) { diff --git a/include/reaction.hxx b/include/reaction.hxx index 9b6d3cb80..81e17ac5c 100644 --- a/include/reaction.hxx +++ b/include/reaction.hxx @@ -31,7 +31,7 @@ protected: * */ struct Reaction : public ReactionBase { - Reaction(std::string name, Options& alloptions); + Reaction(std::string name, Options& alloptionss, const std::string & reaction_str); void outputVars(Options& state) override final; diff --git a/src/reaction.cxx b/src/reaction.cxx index 29207b7d1..020753dd8 100644 --- a/src/reaction.cxx +++ b/src/reaction.cxx @@ -9,7 +9,7 @@ #include "integrate.hxx" -Reaction::Reaction(std::string name, Options& options) +Reaction::Reaction(std::string name, Options& options, const std::string & reaction_str) : ReactionBase({readOnly("species:{sp}:{r_val}"), readOnly("species:e:{e_val}"), readWrite("species:{sp}:{w_val}")}), name(name) { @@ -24,20 +24,6 @@ Reaction::Reaction(std::string name, Options& options) .doc("Output additional diagnostics?") .withDefault(false); - /* - * Awful hack to extract the correct reaction expression from the params; depends on - * instantiation order matching the order reactions are listed in the input file. There - * must be a better way... - */ - std::string reaction_grp_str = options[name]["type"]; - std::regex match_parentheses("\\(|\\)"); - reaction_grp_str = std::regex_replace(reaction_grp_str, match_parentheses, ""); - std::string reaction_str; - std::stringstream ss(reaction_grp_str); - for (auto ii = 0; ii < this->inst_num; ii++) { - std::getline(ss, reaction_str, ','); - } - // Parse the reaction string this->parser = std::make_unique(reaction_str); diff --git a/tests/unit/test_amjueldata.cxx b/tests/unit/test_amjueldata.cxx index b4d97ad6d..df2aa60d6 100644 --- a/tests/unit/test_amjueldata.cxx +++ b/tests/unit/test_amjueldata.cxx @@ -23,7 +23,7 @@ static Options valid_options{ /// @brief Test that setting an invalid json db dir throws. TEST(AmjuelDataTest, InvalidCustomDataDir) { Options options{{"json_database_dir", "/invalid/file/path"}}; - ASSERT_THROW(AmjuelReaction("dummy_name", "iz", "test", "dummy_from_species", + ASSERT_THROW(AmjuelReaction("dummy_name", "iz", "test", "dummy_from_species -> dummy_to_species", "dummy_from_species", "dummy_to_species", options), BoutException); } @@ -32,7 +32,7 @@ TEST(AmjuelDataTest, InvalidCustomDataDir) { TEST(AmjuelDataTest, InValidFilename) { std::string invalid_amjuel_lbl = "invalid_lbl"; if (std::filesystem::is_directory(test_json_db_path)) { - ASSERT_THROW(AmjuelReaction("test", "valid", invalid_amjuel_lbl, "dummy_from_species", + ASSERT_THROW(AmjuelReaction("test", "valid", invalid_amjuel_lbl, "dummy_from_species -> dummy_to_species", "dummy_from_species", "dummy_to_species", valid_options), BoutException); } else { @@ -45,7 +45,7 @@ TEST(AmjuelDataTest, InValidFilename) { /// @brief Test that trying to read invalid data throws. TEST(AmjuelDataTest, InValidData) { if (std::filesystem::is_directory(test_json_db_path)) { - ASSERT_THROW(AmjuelReaction("test", "invalid", "test", "dummy_from_species", + ASSERT_THROW(AmjuelReaction("test", "invalid", "test", "dummy_from_species -> dummy_to_species", "dummy_from_species", "dummy_to_species", valid_options), BoutException); } else { @@ -58,7 +58,7 @@ TEST(AmjuelDataTest, InValidData) { /// Test that json_database_dir can be overridden with a valid path TEST(AmjuelDataTest, ValidCustomDataDir) { if (std::filesystem::is_directory(test_json_db_path)) { - ASSERT_NO_THROW(AmjuelReaction("test", "valid", "test", "dummy_from_species", + ASSERT_NO_THROW(AmjuelReaction("test", "valid", "test", "dummy_from_species -> dummy_to_species", "dummy_from_species", "dummy_to_species", valid_options)); } else { // If tests are run on a filesystem where repo path isn't accessible, just skip @@ -70,7 +70,7 @@ TEST(AmjuelDataTest, ValidCustomDataDir) { /// Test that reading data without coefficients works TEST(AmjuelDataTest, ValidNoSigmavEData) { if (std::filesystem::is_directory(test_json_db_path)) { - ASSERT_NO_THROW(AmjuelReaction("test", "valid_no-sigma-v-E", "test", + ASSERT_NO_THROW(AmjuelReaction("test", "valid_no-sigma-v-E", "test", "dummy_from_species -> dummy_to_species", "dummy_from_species", "dummy_to_species", valid_options)); } else { @@ -78,4 +78,4 @@ TEST(AmjuelDataTest, ValidNoSigmavEData) { GTEST_SKIP() << "Couldn't access test json db dir at " << test_json_db_path << ", skipping!"; } -} \ No newline at end of file +} From 248537831ec7f1d613d73e46e1108b843e28076a Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Tue, 6 Jan 2026 16:42:52 +0000 Subject: [PATCH 61/63] Revert "Allow integration tests to be run in parallel in CI" This reverts commit c5bd5898e48b497337fba06b0474e897eeb2c7be, as running integration tests in parallel on the CI machine proved to be slower. Perhaps it is memory-bounded? --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cddb7a76a..c181f0756 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -82,7 +82,7 @@ jobs: echo ~/.local/bin >> $GITHUB_PATH - name: Run tests run: cd build && - ctest --output-on-failure --timeout 300 -L $TEST_TYPE -j4 + ctest --output-on-failure --timeout 300 -L $TEST_TYPE - name: Upload test coverage data uses: codecov/codecov-action@v5 if: ${{ matrix.build_type.coverage == 'ON' }} From d4e11f474794cb5d53ff90e21487c82971cd0da3 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Fri, 9 Jan 2026 18:58:05 +0000 Subject: [PATCH 62/63] Add ability to print component order and deactivate sorting --- include/component.hxx | 16 +++++++++ src/component.cxx | 6 ++++ src/component_scheduler.cxx | 65 +++++++++++++++++++++++++++++-------- 3 files changed, 74 insertions(+), 13 deletions(-) diff --git a/include/component.hxx b/include/component.hxx index b813eb804..07c6d051b 100644 --- a/include/component.hxx +++ b/include/component.hxx @@ -78,6 +78,8 @@ struct ComponentInformation { std::string name; std::string type; + ComponentInformation() {}; + ComponentInformation(const std::string& name_, const std::string& type_) : name(name_), type(type_) {} ComponentInformation(std::string&& name_, std::string&& type_) : name(std::move(name_)), type(std::move(type_)) {} @@ -91,6 +93,20 @@ struct ComponentInformation { } }; +/// Format `ComponentInformation` to string. Format string specification is the +/// same as when formatting a string. +/// See https://fmt.dev/12.0/syntax/#format-specification-mini-language. +/// +/// TODO: provide custom formatting to configure exactly how the +/// component name and type are displayed. +template <> +struct fmt::formatter + : formatter { + auto format(const ComponentInformation& ci, format_context& ctx) const + -> format_context::iterator; +}; + + /// Interface for a component of a simulation model /// /// The constructor of derived types should have signature diff --git a/src/component.cxx b/src/component.cxx index b1c3db00d..c53cceb83 100644 --- a/src/component.cxx +++ b/src/component.cxx @@ -9,6 +9,12 @@ #include "../include/guarded_options.hxx" #include "../include/permissions.hxx" +auto fmt::formatter::format(const ComponentInformation& ci, format_context& ctx) const + -> format_context::iterator { + return formatter::format( + fmt::format("{} ({})", ci.name, ci.type), ctx); +} + std::unique_ptr Component::create(const std::string &type, const std::string &name, Options &alloptions, diff --git a/src/component_scheduler.cxx b/src/component_scheduler.cxx index a290b127b..4222fa867 100644 --- a/src/component_scheduler.cxx +++ b/src/component_scheduler.cxx @@ -10,6 +10,7 @@ #include #include +#include #include #include // for trim, strsplit #include @@ -222,9 +223,19 @@ setReadDependencies(const std::vector>& components, return missing; } + +void printComponents(const std::vector& component_order) { + if (component_order.size() > 0) { + output_info << "Components will be executed in the following order:\n"; + } + for (const auto & comp : component_order) { + output_info << fmt::format("\t{}\n", comp); + } +} + /// Topologically sorts the list of components to ensure variables are /// written and read in the right order. -void sortComponents(std::vector>& components) { +void sortComponents(std::vector>& components, const std::vector component_info) { // Map between variable/section names specified by component // permissions and the variables they contain. In the case of // sections this is all variables within the section and any @@ -301,13 +312,18 @@ void sortComponents(std::vector>& components) { } } + std::vector component_order(component_info.size()); + // Create the result with components in the desired order - std::vector> result(components.size()); + std::vector> + result(components.size()); for (size_t i = 0; i < components.size(); i++) { + component_order[i] = component_info[order[i]]; std::swap(result[i], components[order[i]]); } components = std::move(result); + printComponents(component_order); } ComponentScheduler::ComponentScheduler(Options &scheduler_options, @@ -317,8 +333,9 @@ ComponentScheduler::ComponentScheduler(Options &scheduler_options, const std::string component_names = scheduler_options["components"] .doc("Components in order of execution") .as(); + const bool autosort = scheduler_options["autosort"].doc("Perform a topological sort to ensure components executed in the right order?").withDefault(true); - std::set required_components; + std::list required_components; std::vector electrons; std::vector neutrals; @@ -368,22 +385,40 @@ ComponentScheduler::ComponentScheduler(Options &scheduler_options, continue; } - required_components.emplace(name_trimmed, type_trimmed); + required_components.emplace_back(name_trimmed, type_trimmed); } } + // Use sets for efficient lookup of whether a component is already + // in the queue to be created or already has been created. + // + // We use a vector to decide the order in which to create + // components, to keep this close to what is in teh file. + std::set unbuilt_components(required_components.begin(), required_components.end()); std::set created_components; - for (auto it = required_components.begin(); it != required_components.end(); - it = required_components.begin()) { - auto comp = Component::create(it->type, it->name, component_options, solver); - for (const auto& sub_comp : comp->additionalComponents()) { - if (required_components.count(sub_comp) == 0 - and created_components.count(sub_comp) == 0) { - required_components.insert(sub_comp); + // We use a vector to keep track of the actual order in which + // components are created + std::vector component_order; + + // FIXME: This is a hack, so I can debug the failing case + if (autosort) { + required_components = std::list(unbuilt_components.begin(), unbuilt_components.end()); + } + while (required_components.size() > 0) { + const ComponentInformation component = required_components.front(); + required_components.pop_front(); + auto comp = Component::create(component.type, component.name, component_options, solver); + std::vector sub_components = comp->additionalComponents(); + for (auto sub_comp = sub_components.rbegin(); sub_comp != sub_components.rend(); ++sub_comp) { + if (unbuilt_components.count(*sub_comp) == 0 + and created_components.count(*sub_comp) == 0) { + required_components.push_front(*sub_comp); + unbuilt_components.insert(*sub_comp); } } + component_order.push_back(component); components.push_back(std::move(comp)); - created_components.insert(required_components.extract(it)); + created_components.insert(unbuilt_components.extract(component)); } const SpeciesInformation species(electrons, neutrals, positive_ions, negative_ions); @@ -392,7 +427,11 @@ ComponentScheduler::ComponentScheduler(Options &scheduler_options, component->declareAllSpecies(species); } - sortComponents(components); + if (autosort) { + sortComponents(components, component_order); + } else { + printComponents(component_order); + } } std::unique_ptr ComponentScheduler::create(Options &scheduler_options, From d6cabb9321d224ebdbc762b20525cf3c0e948ff4 Mon Sep 17 00:00:00 2001 From: Chris MacMackin Date: Fri, 9 Jan 2026 18:58:24 +0000 Subject: [PATCH 63/63] Fixed some minor permission issues --- include/electron_force_balance.hxx | 2 +- src/braginskii_collisions.cxx | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/include/electron_force_balance.hxx b/include/electron_force_balance.hxx index ab8c61351..02ec096ed 100644 --- a/include/electron_force_balance.hxx +++ b/include/electron_force_balance.hxx @@ -24,7 +24,7 @@ struct ElectronForceBalance : public Component { readOnly("species:e:density", Regions::Interior), readOnly("species:e:charge"), // FIXME: Only writes if already exists - readWrite("species:e:momentum_source"), + readIfSet("species:e:momentum_source"), readIfSet("species:{non_electrons}:density", Regions::Interior), readIfSet("species:{non_electrons}:charge"), // FIXME: Only written if density and charge have been set. diff --git a/src/braginskii_collisions.cxx b/src/braginskii_collisions.cxx index 5bd1c8ccb..8fca60f0d 100644 --- a/src/braginskii_collisions.cxx +++ b/src/braginskii_collisions.cxx @@ -21,7 +21,8 @@ #include "../include/hermes_utils.hxx" BraginskiiCollisions::BraginskiiCollisions(const std::string& name, Options& alloptions, Solver*) - : Component({readOnly("species:{non_electrons}:density", Regions::Interior), + : Component({readOnly("species:{all_species}:density", Regions::Interior), + readOnly("species:{electrons}:temperature", Regions::Interior), readIfSet("species:{non_electrons}:charge"), readIfSet("species:{negative_ions}:temperature", Regions::Interior), readOnly("species:{all_species}:AA")}) { @@ -62,8 +63,6 @@ BraginskiiCollisions::BraginskiiCollisions(const std::string& name, Options& all diagnose = options["diagnose"].doc("Output additional diagnostics?").withDefault(false); - setPermissions(readOnly("species:{electrons}:temperature", Regions::Interior)); - setPermissions(readOnly("species:{electrons}:density", Regions::Interior)); if (electron_electron) { setPermissions(readWrite( "species:{electrons}:collision_frequencies:{electrons}_{electrons2}_coll"));