Skip to content
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

Add the ability to pull an unmodified EOS out of a modified one #254

Merged
merged 3 commits into from
Apr 12, 2023

Conversation

Yurlungur
Copy link
Collaborator

@Yurlungur Yurlungur commented Apr 11, 2023

PR Summary

Sometimes you may wish to have access to the unmodified model... for example, because you wish to access model-specific features (i.e., the mass fractions in the stellar collapse tables), because you want to drop out of code units and use cgs, or because you wish to swap one modifier for another at runtime.

This PR lets you do those things. I provide three functions, shared across all models, that let you do this:

  1. bool Model::IsModified() const returns true if the EOS has a modifier applied to it and false otherwise.
  2. auto UnmodifyOnce() if the EOS is modified, pull off the most recent modifier. Returns either the variant or an appropriate type.
  3. auto GetUnmodifiedObject() returns the underlying EOS with all modifiers removed. Type again is either the variant or the appropriate type.

This feature is needed by the NGP codes but I believe it will be useful broadly for any downstream code that uses C++. (Obviously it's of no help to the fortran codes.)

PR Checklist

  • Adds a test for any bugs fixed. Adds tests for new features.
  • Format your changes by using the make format command after configuring with cmake.
  • Document any new features, update documentation for changes made.
  • Make sure the copyright notice on any files you modified is up to date.
  • After creating a pull request, note it in the CHANGELOG.md file
  • If preparing for a new release, update the version in cmake.

@Yurlungur Yurlungur added the enhancement New feature or request label Apr 11, 2023
@Yurlungur Yurlungur self-assigned this Apr 11, 2023
Comment on lines +428 to +434
// Tooling for modifiers
PORTABLE_FORCEINLINE_FUNCTION
bool IsModified() const { return false; }
PORTABLE_FORCEINLINE_FUNCTION
auto UnmodifyOnce() { return *static_cast<CRTP *>(this); }
PORTABLE_FORCEINLINE_FUNCTION
auto GetUnmodifiedObject() { return *static_cast<CRTP *>(this); }
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

These provide default behavior for the "unmodified" EOS models. The modifiers must implement these differently and shadow them, (although there's an obvious template).

Comment on lines +272 to +277
PORTABLE_FORCEINLINE_FUNCTION
bool IsModified() const { return true; }
PORTABLE_FORCEINLINE_FUNCTION
T UnmodifyOnce() { return t_; }
PORTABLE_FORCEINLINE_FUNCTION
auto GetUnmodifiedObject() { return t_.GetUnmodifiedObject(); }
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Unfortunately, these are copy-pasted into each modifier definition. It's maybe possible to add them all with a macro or some other trick, although I don't know how worthwhile that is.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Two tier CRTP? There would be the standard base class and then a modifier base class that inherits from the standard base class and provides these functions. I played around with it a bit a while back and it is doable.
https://stackoverflow.com/a/18174442/3491638

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm... that could work. If we add more modifier-specific stuff, let's consider it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

For consideration: mixins fit this pattern well, tho (from experience) I can say using mixins and crtp can be give tongue-twisty code. https://www.fluentcpp.com/2017/12/12/mixin-classes-yang-crtp/

Copy link
Collaborator

Choose a reason for hiding this comment

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

If @jhp-lanl wants to make an PR, I'd be interested to see how tongue-twisty it is. May be worth it, though the current duplication overhead is small and not likely to be modified often.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@mauneyc-LANL I was looking at mixins as well a little bit ago but I thought that they weren't as elegant with our Variant class. We define a closed set of functions that are available for all types, whereas I feel a mixin is about expanding the functionality. If we were using class inheritance for our type erasure, maybe a mixin would be the preferable approach, but with the Variant, I lean towards CRTP since the end user doesn't have to change anything.

However, I wonder if a mixin could enable functionality like this?

EOS my_modified_eos =  Shifted<Scaled<IdealGas(...)>>>;
EOS once_unmodified = UnmodifyOnce(my_modified_eos);
EOS unmodified_eos = UnmodifyAll(my_modified_eos);

@@ -232,7 +232,7 @@ SCENARIO("EOS Variant Type", "[Variant][EOS]") {
std::cout << demangle(typeid(EOS).name()) << std::endl;
}

SCENARIO("EOS Builder and Modifiers", "[EOSBuilder],[Modifiers][IdealGas]") {
SCENARIO("EOS Builder and Modifiers", "[EOSBuilder][Modifiers][IdealGas]") {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was a harmless bug. The comma never should have been there. We need it not there, in fact, to correctly parse this string for categories with catch.

@dholladay00
Copy link
Collaborator

@jhp-lanl I can't remember exactly, but I want to make sure that this won't break any plans for vector interface modifiers. There is something about multiple parallel_fors but I am forgetting why. I don't think we modify any underlying data such that an unmodify would require (un)modifying internal EOS state (like tables), but I'd like you to think that through and double check me on that.

@jhp-lanl
Copy link
Collaborator

jhp-lanl commented Apr 12, 2023

@jhp-lanl I can't remember exactly, but I want to make sure that this won't break any plans for vector interface modifiers. There is something about multiple parallel_fors but I am forgetting why.

I think the underlying issue was with modifiers being applied to EOSPAC. Currently the modifiers inherit from the base class, their default vector behavior is to call their own scalar functions which modify scalar EOS calls. In the EOSPAC case, we need the vector modifier to call the vector EOS call with the appropriate modifiers applied to the inputs/outputs.

I remember we had discussed whether this required another set of parallel for loops or whether there was a better way that maybe could use serial for loops to apply the modifiers and then use the parallel for to apply the modifier. This might be less performant for EOS other than EOSPAC though, so we would want to do some profiling.

I don't think we modify any underlying data such that an unmodify would require (un)modifying internal EOS state (like tables), but I'd like you to think that through and double check me on that.

If we did apply the modifiers to the data rather than the inputs/outputs, you'd be correct that this might be a non-trivial operation, but right now we don't do that. I'd advocate for trying out a simple for loop to modify inputs/outputs and assess the performance impact from such an approach before we go down the road of modifying internal class states.

EDIT - There are also probably EOSPAC options we could use to let EOSPAC do the modifying, but I'd like to test just simple for loops against a "pure" EOSPAC implementation to see how much of a detriment is incurred. Getting the EOSPAC options to work in a general modifier framework would be a challenge.

Copy link
Collaborator

@jhp-lanl jhp-lanl left a comment

Choose a reason for hiding this comment

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

I don't like the copy/paste but it's not enough for me not to approve this pending the fix to the doc issue.

@Yurlungur
Copy link
Collaborator Author

I'm actually not sure what's going on with the docs. The docs built, but the deploy stage failed because the hash is wrong. Very strange.

@Yurlungur
Copy link
Collaborator Author

Must have been a momentary thing. Docs build now.

@Yurlungur
Copy link
Collaborator Author

Gotten approval from @jhp-lanl and(verbally) from @dholladay00 so I'm merging it. Please feel free to add feedback after the fact though. We're not set in stone here.

@Yurlungur Yurlungur merged commit b4c9ce7 into main Apr 12, 2023
@Yurlungur Yurlungur deleted the jmm/unmodify branch April 12, 2023 01:35
Comment on lines +844 to +857
PORTABLE_FORCEINLINE_FUNCTION
bool IsModified() const {
return mpark::visit([](const auto &eos) { return eos.IsModified(); }, eos_);
}
PORTABLE_FORCEINLINE_FUNCTION
Variant UnmodifyOnce() {
return mpark::visit(
[](auto &eos) { return eos_variant<EOSs...>(eos.UnmodifyOnce()); }, eos_);
}
PORTABLE_FORCEINLINE_FUNCTION
Variant GetUnmodifiedObject() {
return mpark::visit(
[](auto &eos) { return eos_variant<EOSs...>(eos.GetUnmodifiedObject()); }, eos_);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

There's a discussion to have here about how data is being copied. Shortly: Rather than construct new objects, it would be better to for the visit pattern to filter out modifiers (see the example at https://en.cppreference.com/w/cpp/utility/variant/visit for "type-matching" visitor).

I don't have any wunderkind one-liner to make this happen, and unfortunately all my immediate ideas involve some moderately sized refactors (the C++14 restrict is also very constraining).

Anyway, as is it would be worth double-checking that copying(constructing) these objects isn't going to have any ill-considered side-effects. For example, do the Databox's get duplicated? This could lead to issues with memory ownership conflicts. (it may not, but just pointing to a possibility)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

All EOS's are assumed to have reference semantics, including databoxes, so copying will not cause correctness problems, though it may cause performance bottlenecks.

I was not aware that one could do a type-matching visit. That's cool. There may be other uses for that.

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.

5 participants