-
Notifications
You must be signed in to change notification settings - Fork 35
Allow components to call for creation of sub components #445
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1748f12
996cf06
7fa9766
07a5b1f
b636bf4
84f53b4
f82ce21
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| #pragma once | ||
| #ifndef BRAGINSKII_H | ||
| #define BRAGINSKII_H | ||
|
|
||
| #include <bout/field3d.hxx> | ||
|
|
||
| #include "component.hxx" | ||
|
|
||
| /// Meta-component to set up all components necessary for the | ||
| /// 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 NamedComponent<BraginskiiClosure> { | ||
| public: | ||
| /// @param alloptions Settings, which may include | ||
| /// - <name> | ||
| /// - 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*) | ||
| : NamedComponent(name, {}) { | ||
| Options& options = alloptions[name]; | ||
| electron_viscosity = options["electron_viscosity"] | ||
| .doc("Include electron viscosity terms?") | ||
| .withDefault<bool>(true); | ||
| ion_viscosity = options["ion_viscosity"] | ||
| .doc("Include ion viscosity terms?") | ||
| .withDefault<bool>(true); | ||
| thermal_force = options["thermal_force"] | ||
| .doc("Include thermal force terms?") | ||
| .withDefault<bool>(true); | ||
| } | ||
|
|
||
| virtual std::vector<ComponentInformation> additionalComponents() override { | ||
| std::vector<ComponentInformation> result = { | ||
| {"braginskii_collisions", "braginskii_collisions"}, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is purely for future work and I know we heavily rely on strings throughout the code -- but what would we need to avoid string literals here? A result = {
{BraginskiiCollisions::name(), BraginskiiCollisions::type()}I guess I don't quite understand the difference between components' names and types, so maybe there's something else required.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The component type maps to the class name for the component. We could use a The name is more like a variable name and could vary for each instance of the component. It is the section-name in the |
||
| {"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; | ||
| } | ||
|
|
||
| static constexpr auto type = "braginskii_closure"; | ||
|
|
||
| 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<BraginskiiClosure> registercomponentbraginskiiclosure; | ||
| } | ||
|
|
||
| #endif // BRAGINSKII_H | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -76,6 +76,40 @@ private: | |
| } | ||
| }; | ||
|
|
||
| /// Lightweight type to store information used to build a component. | ||
| struct ComponentInformation { | ||
| std::string name; | ||
| std::string type; | ||
|
Comment on lines
+81
to
+82
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could use some docs or examples for these? |
||
|
|
||
| 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_)) {} | ||
|
|
||
| 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); | ||
| } | ||
| }; | ||
|
|
||
| /// 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<ComponentInformation> : formatter<std::string> { | ||
| 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 | ||
|
|
@@ -92,6 +126,11 @@ 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<ComponentInformation> 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. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ | |
| #include <bout/bout_types.hxx> | ||
| #include <bout/boutexception.hxx> | ||
| #include <bout/options.hxx> | ||
| #include <bout/output.hxx> | ||
| #include <bout/utils.hxx> // for trim, strsplit | ||
| #include <fmt/format.h> | ||
| #include <fmt/ranges.h> | ||
|
|
@@ -308,6 +309,15 @@ setReadDependencies(const std::vector<std::unique_ptr<Component>>& components, | |
| return missing; | ||
| } | ||
|
|
||
| void printComponents(const std::vector<std::unique_ptr<Component>>& components) { | ||
| if (!components.empty()) { | ||
| output_info << "Components will be executed in the following order:\n"; | ||
| } | ||
| for (const auto& comp : components) { | ||
| output_info << fmt::format("\t{}\n", *comp); | ||
| } | ||
| } | ||
|
|
||
| /// Topologically sorts the list of components to ensure variables are | ||
| /// written and read in the right order. | ||
| /// | ||
|
|
@@ -425,6 +435,7 @@ void sortComponents(std::vector<std::unique_ptr<Component>>& components) { | |
| } | ||
|
|
||
| components = std::move(result); | ||
| printComponents(components); | ||
| } | ||
| } // namespace | ||
|
|
||
|
|
@@ -434,6 +445,12 @@ ComponentScheduler::ComponentScheduler(Options& scheduler_options, | |
| const std::string component_names = scheduler_options["components"] | ||
| .doc("Components in order of execution") | ||
| .as<std::string>(); | ||
| const bool autosort = scheduler_options["autosort"] | ||
| .doc("Perform a topological sort to ensure components " | ||
| "executed in the right order?") | ||
| .withDefault<bool>(true); | ||
|
|
||
| std::list<ComponentInformation> required_components; | ||
|
|
||
| std::vector<std::string> electrons; | ||
| std::vector<std::string> neutrals; | ||
|
|
@@ -483,18 +500,52 @@ ComponentScheduler::ComponentScheduler(Options& scheduler_options, | |
| continue; | ||
| } | ||
|
|
||
| components.push_back( | ||
| Component::create(type_trimmed, name_trimmed, component_options, solver)); | ||
| 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 the file. | ||
| std::set<ComponentInformation> unbuilt_components(required_components.begin(), | ||
| required_components.end()); | ||
| std::set<ComponentInformation> created_components; | ||
| // We use a vector to keep track of the actual order in which | ||
| // components are created | ||
| std::vector<ComponentInformation> component_order; | ||
|
|
||
| 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<ComponentInformation> 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(unbuilt_components.extract(component)); | ||
| } | ||
|
Comment on lines
+514
to
+536
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This bit could use some commenting! |
||
|
|
||
| const SpeciesInformation species(electrons, neutrals, positive_ions, negative_ions); | ||
|
|
||
| for (auto& component : components) { | ||
| component->declareAllSpecies(species); | ||
| } | ||
|
|
||
| ::sortComponents(components); | ||
| if (autosort) { | ||
| ::sortComponents(components); | ||
| } else { | ||
| printComponents(components); | ||
| } | ||
| } | ||
|
|
||
| std::unique_ptr<ComponentScheduler> ComponentScheduler::create(Options& scheduler_options, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overrideimpliesvirtual, so we can drop the latter: