Skip to content

Allow components to call for creation of sub components - #445

Open
cmacmackin wants to merge 7 commits into
cmacmackin/cache_bout_buildsfrom
cmacmackin/create_sub_components
Open

cmacmackin wants to merge 7 commits into
cmacmackin/cache_bout_buildsfrom
cmacmackin/create_sub_components

Conversation

@cmacmackin

Copy link
Copy Markdown
Collaborator

This PR adds a virtual method to Component classes which lists the names and types of additional components which they might need. This information is used by the ComponentScheduler class to create these additional components automatically. Users can override default values for the configurations of these additional components by adding a named section to their config file, as usual. A BraginskiiClosure component has been added, which does nothing except call for the creation of all the other Braginskii components.

Closes #386

@cmacmackin cmacmackin added the enhancement New feature or request label Dec 15, 2025

@ZedThree ZedThree left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I've tried to review the bits that I think have been modified for this feature.

My main comment is another architectural one, and we discussed this before: using a virtual method on Component means that we have to create a component before discovering if it creates any others. This basically takes those additional components out of the topological sort.

I guess in some ways this is YAGNI, but perhaps this is something we do need to consider?

Comment thread include/component.hxx Outdated
Comment thread include/component.hxx Outdated
Comment on lines +68 to +71
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_)) {}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should be able to rely on aggregate intialisation here, and so no need for constructors:

Suggested change
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_)) {}

Comment thread include/component.hxx Outdated
.withDefault<bool>(true);
}

virtual std::vector<ComponentInformation> additionalComponents() override {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

override implies virtual, so we can drop the latter:

Suggested change
virtual std::vector<ComponentInformation> additionalComponents() override {
std::vector<ComponentInformation> additionalComponents() override {

Comment thread include/component.hxx
Comment on lines +65 to +66
std::string name;
std::string type;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could use some docs or examples for these?

Comment thread src/component_scheduler.cxx
Comment thread src/component_scheduler.cxx Outdated
Comment thread src/component_scheduler.cxx Outdated
Comment on lines +287 to +288
// FIXME: Would there be any spcies without AA? Is there any other
// reliable way to identify what is a species?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I've seen this sort of thing in a few places, maybe there's some terrible magic we can wrought to store a Species struct of some kind in Options

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The problem is that the concept of species aren't really a first-class entity in Hermes-3. There isn't any fundamental concept of species in the input file syntax. There are just components, some of which will set variables for a particular species. There are many such components and a species can feature them in almost any combination. We could hypothetically have a list of all the components that can represent a species, but that risks becoming out-of-date as new components are written.

I suppose we could create a species sub-class of components, which those representing species should inherit from. I don't think there would be any way to enforce its use though.

@bendudson bendudson Jan 2, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think there are good reasons for not making species a first-class entity, as models may want to evolve things like groups of species (bundled charge states), different vibrational states, or energy groups (e.g. beam electrons), as distinct 'species', so what is meant by a species is intentionally flexible.

One possibility is to have the species 'self identify': Each species currently sets a 'charge' and 'AA' (mass) property in the state. We could just add a 'type' property that determines how it should be treated in e.g. collisions.

@cmacmackin cmacmackin Jan 2, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I suppose another option would be to have an additional virtual method on components:

virtual std::optional<std::string> species() const { return {}; }

If the component evolves a species then it would override the method to return that species' name (which is usually the same as the component name).

@cmacmackin cmacmackin Jan 2, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

We could then have a sub-class

struct SpeciesComponent : public Component {
  SpeciesComponent(std::string name, Permissions access_permissions) : 
                  Component(access_permissions), name(std::move(name)) {}
  std::optional<std::string> species() const override { return name; }

private:
  std::string name;
}

All the components corresponding to species (e.g., EvolveDensity, EvolveMomentum, Vorticity, etc.) could then inherit from that, so they don't need to bother reimplementing the method.

Comment on lines +320 to +332
std::set<ComponentInformation> 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));
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This bit could use some commenting!

@cmacmackin

Copy link
Copy Markdown
Collaborator Author

I've tried to review the bits that I think have been modified for this feature.

My main comment is another architectural one, and we discussed this before: using a virtual method on Component means that we have to create a component before discovering if it creates any others. This basically takes those additional components out of the topological sort.

I guess in some ways this is YAGNI, but perhaps this is something we do need to consider?

This shouldn't take the new components out of the topological sort. The sorting only happens after all components have been created, so adding more components won't affect that.

@cmacmackin

Copy link
Copy Markdown
Collaborator Author

@ZedThree A lot of these comments actually apply to #428. I should have mentioned in my opening comment that we should wait to review this PR until after that one has been merged.

cmacmackin added a commit that referenced this pull request Jan 2, 2026
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.
cmacmackin added a commit that referenced this pull request Jan 5, 2026
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.
@cmacmackin
cmacmackin force-pushed the cmacmackin/create_sub_components branch from 00a27c5 to 1d0d823 Compare January 5, 2026 18:17
@codecov

codecov Bot commented Jan 5, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.37838% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.24%. Comparing base (7b6b166) to head (f82ce21).

Files with missing lines Patch % Lines
include/electron_force_balance.hxx 28.57% 5 Missing ⚠️
include/braginskii_closure.hxx 86.36% 1 Missing and 2 partials ⚠️
src/braginskii_ion_viscosity.cxx 25.00% 2 Missing and 1 partial ⚠️
src/component_scheduler.cxx 90.00% 1 Missing and 2 partials ⚠️
src/component.cxx 0.00% 2 Missing ⚠️
Additional details and impacted files
@@                       Coverage Diff                        @@
##           cmacmackin/cache_bout_builds     #445      +/-   ##
================================================================
+ Coverage                         60.09%   60.24%   +0.15%     
================================================================
  Files                                97       98       +1     
  Lines                             10254    10313      +59     
  Branches                           1488     1496       +8     
================================================================
+ Hits                               6162     6213      +51     
- Misses                             3465     3469       +4     
- Partials                            627      631       +4     

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

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

cmacmackin added a commit that referenced this pull request Jan 6, 2026
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.
@cmacmackin
cmacmackin force-pushed the cmacmackin/create_sub_components branch from 1d0d823 to 736052b Compare January 6, 2026 16:07
@cmacmackin

Copy link
Copy Markdown
Collaborator Author

The 1D-recycling-dthe integration test is failing in CI, with relative differences on the order of >1e-2. (Far too high!) This appears to be due to the radically different order in which components end up being built, which is a side-effect of some of the changes in the PR. Previously, the sorting algorithm used the order given in the input file as its starting point. Now it basically sorts the components alphabetically by name and type before it does the topological sort. This means that the final order of the components after topological sorting is now radically different from what is given in the input file.

I've tried rewriting it slightly so that it will go back to using the order in the input file as its starting point for topological sorting. This makes the test pass, but I'm still concerned by what I'm seeing. It would appear that the permission information I am currently specifying on some components is not adequate to guarantee the same result. Perhaps something hasn't been marked as the final-write that should be. I note that this is the only integration test which has had this problem; all the others worked fine even with a radical reordering of the components. That might give us something to go on.

ZedThree pushed a commit that referenced this pull request Jan 7, 2026
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.
@cmacmackin

Copy link
Copy Markdown
Collaborator Author

Here is the order the components are executed when I get the error described above:

  • d (evolve_density)
  • d (evolve_pressure)
  • d+ (evolve_density)
  • d+ (evolve_pressure)
  • he (evolve_density)
  • he+ (evolve_density)
  • t (evolve_density)
  • t+ (evolve_density)
  • e (quasineutral)
  • e (evolve_pressure)
  • he (evolve_pressure)
  • he+ (evolve_pressure)
  • t (evolve_pressure)
  • t+ (evolve_pressure)
  • braginskii_collisions (braginskii_collisions)
  • d (evolve_momentum)
  • d (noflow_boundary)
  • he (evolve_momentum)
  • he (noflow_boundary)
  • d+ (evolve_momentum)
  • d+ (noflow_boundary)
  • he+ (evolve_momentum)
  • t (evolve_momentum)
  • t+ (evolve_momentum)
  • e (zero_current)
  • e (noflow_boundary)
  • he+ (noflow_boundary)
  • t+ (noflow_boundary)
  • sheath_boundary (sheath_boundary)
  • reactions (d + d+ -> d+ + d)
  • t (noflow_boundary)
  • reactions (d + t+ -> d+ + t)
  • reactions (t + d+ -> t+ + d)
  • reactions (t + t+ -> t+ + t)
  • braginskii_conduction (braginskii_conduction)
  • braginskii_ion_viscosity (braginskii_ion_viscosity)
  • braginskii_thermal_force (braginskii_thermal_force)
  • d+ (upstream_density_feedback)
  • reactions (d + e -> d+ + 2e)
  • reactions (d+ + e -> d)
  • reactions (he + e -> he+ + 2e)
  • reactions (he+ + e -> he)
  • reactions (t + e -> t+ + 2e)
  • reactions (t+ + e -> t)
  • electron_force_balance (electron_force_balance)
  • he+ (upstream_density_feedback)
  • neutral_parallel_diffusion (neutral_parallel_diffusion)
  • recycling (recycling)
  • t+ (upstream_density_feedback)

@mikekryjak, @bendudson is there anything in this ordering that stands out to you as wrong?

@bendudson

Copy link
Copy Markdown
Collaborator

Hi @cmacmackin !
This ordering looks cromulent to me. The only thing I can see that's different is t (noflow_boundary) comes after sheath_boundary (sheath_boundary). That shouldn't make a difference because sheath_boundary should ignore t because it's a neutral species.

@cmacmackin

cmacmackin commented Jan 7, 2026 •

Copy link
Copy Markdown
Collaborator Author

@bendudson
Thanks for taking a look. The fact that this ordering is giving badly wrong answers for some reaction stuff (and takes significantly longer to run) means that something is wrong. It would appear that there might be some implicit ordering or dependency that we've all missed. Here is the error message in case that helps us narrow things down:

Ed+t_cx differs from reference data by > 20000000000000.0 ULPs
 Value = -0.0004365854362020518
 Reference = -0.00044916942235749286
Edd+_cx differs from reference data by > 20000000000000.0 ULPs
 Value = 0.00015620791788336535
 Reference = 0.0001584462055586012
Edt+_cx differs from reference data by > 20000000000000.0 ULPs
 Value = -0.0004107942463354931
 Reference = -0.0004246825908348509
Et+d_cx differs from reference data by > 20000000000000.0 ULPs
 Value = -0.0006942134061277697
 Reference = -0.000703213458219236
Etd+_cx differs from reference data by > 20000000000000.0 ULPs
 Value = -0.000541220600109632
 Reference = -0.0005438529063749786
Ett+_cx differs from reference data by > 20000000000000.0 ULPs
 Value = 3.918542691027873e-05
 Reference = 4.1129091954560007e-05
Fd+t_cx differs from reference data by > 20000000000000.0 ULPs
 Value = -0.00011951942056416081
 Reference = -0.00012159622040998301
Fdd+_cx differs from reference data by > 20000000000000.0 ULPs
 Value = 0.00023478567579766294
 Reference = 0.00023601241685116078
Fdt+_cx differs from reference data by > 20000000000000.0 ULPs
 Value = -1.4960423611226284e-05
 Reference = -1.480474061534612e-05
Ft+d_cx differs from reference data by > 20000000000000.0 ULPs
 Value = -7.903779726718753e-05
 Reference = -8.170568455767375e-05
Ftd+_cx differs from reference data by > 20000000000000.0 ULPs
 Value = 4.858695424123787e-05
 Reference = 4.734980178143874e-05
Ftt+_cx differs from reference data by > 20000000000000.0 ULPs
 Value = 6.0414168626410105e-05
 Reference = 6.209672003436238e-05
Kdd+_cx differs from reference data by > 20000000000000.0 ULPs
 Value = 0.01075641223571999
 Reference = 0.010612777344029932
Kdt+_cx differs from reference data by > 20000000000000.0 ULPs
 Value = 0.0054615181858046835
 Reference = 0.005504396701388891
Ktd+_cx differs from reference data by > 20000000000000.0 ULPs
 Value = 0.010736211325369564
 Reference = 0.010560574694497988
Ktt+_cx differs from reference data by > 20000000000000.0 ULPs
 Value = 0.005450886137841182
 Reference = 0.005475965585803034
Sdt+_cx differs from reference data by > 20000000000000.0 ULPs
 Value = -0.0007744787669173392
 Reference = -0.0007849565120605158
Std+_cx differs from reference data by > 20000000000000.0 ULPs
 Value = -0.0006899011832115692
 Reference = -0.0006956104988655306

Edit: This is for the integration test 1D-recycling-dthe.

@cmacmackin

Copy link
Copy Markdown
Collaborator Author

I've added the ability to turn the sorting feature on and off using the hermes:autosort option. True (the default) indicates that sorting should be turned on. Otherwise it will use the order listed in the input file. This should make debugging easier, as we can get the failing behaviour using just this flag.

@cmacmackin

Copy link
Copy Markdown
Collaborator Author

I've worked out the two components that are causing problems (although not why): braginskii_ion_viscosity and upstream_density_feedback. It seems the latter has to be executed first, even though it doesn't appear to touch anything that would conflict with the viscosity.

cmacmackin added a commit that referenced this pull request Jun 25, 2026
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.
cmacmackin added a commit that referenced this pull request Jun 29, 2026
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.
cmacmackin added a commit that referenced this pull request Jun 29, 2026
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.
cmacmackin added a commit that referenced this pull request Jul 8, 2026
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.
cmacmackin added a commit that referenced this pull request Aug 27, 2026
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.
cmacmackin added a commit that referenced this pull request Sep 4, 2026
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.
cmacmackin added a commit that referenced this pull request Sep 4, 2026
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.
cmacmackin added a commit that referenced this pull request Sep 4, 2026
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.
@cmacmackin
cmacmackin changed the base branch from master to cmacmackin/cache_bout_builds September 4, 2026 16:39
@cmacmackin

Copy link
Copy Markdown
Collaborator Author

Rebasing this PR seems to have fixed some of the ordering problems (presumably due to various fixes we've made to permissions in the past months). However, I've found that you can still get issues depending on whether you put electron_force_balance before or after braginskii_friction in the list of components. For some reason it matters which gets executed first.

@cmacmackin
cmacmackin force-pushed the cmacmackin/create_sub_components branch from d6cabb9 to eaac8fe Compare September 7, 2026 13:57
@cmacmackin
cmacmackin force-pushed the cmacmackin/create_sub_components branch from eaac8fe to 84f53b4 Compare September 7, 2026 13:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow components to create new components for themselves

3 participants