Skip to content

calculate con2prim enthalpy bound#241

Merged
Yurlungur merged 12 commits intomainfrom
law/enthalpy-bound
Apr 1, 2026
Merged

calculate con2prim enthalpy bound#241
Yurlungur merged 12 commits intomainfrom
law/enthalpy-bound

Conversation

@logalexw
Copy link
Copy Markdown
Collaborator

PR Summary

This is to address a bug I encountered when working on the CCSNe problem; the root find in con2prim_robust.cpp isn't bracketed when initializing to the gr grid. This occurs when using a Stellar Collapse EOS (SFHo) for a progenitor at infall.

In Kastaun et al. 2021 there’s a required lower bound (h0) for relativistic enthalpy (h) s.t. 0 < h0 ≤ h. This also serves as the upper bound for the unbracketed root find, i.e. root find using regula falsi from (0, 1/h0].

h0 is currently hardcoded to 1.0, which breaks our upper bound condition if h < 1, which does happen if you’re using a StellarCollapse EOS (see fig. 1 below), espeically at Ye values that are relevant for SNe. When looking at the evolution of the root find upper bound for CCSNe progenitor, it's visible that the unbracketed root find (red dashed line) occurs when 1/h ≥ 1/h0 1 (see fig. 2 below).

Goal is to fix with something that's eos-conscious; there's existing primitives in con2prim that should be able to calculate a reasonable bound using singularity-EOS (the existing method in compute_upper_bound() isn't sufficient for CCSNe problem).

Unknown-1

Fig. 1. Minimum enthalpy for the SFHo table at relevant electron fraction (Ye) range.


Unknown-2

Fig. 2. Evolution of upper bound (1/h0) for a CCSNe progenitor, with location of unbracketed root find marked in red. h has been scaled so that the evolution is visible in the same range as the specific internal energy.

PR Checklist

  • Adds a test for any bugs fixed. Adds tests for new features.
  • Format your changes by calling scripts/bash/format.sh.
  • Explain what you did.
  • Make any necessary changes to the documentation.

@logalexw logalexw self-assigned this Mar 11, 2026
@logalexw logalexw added bug Something isn't working enhancement New feature or request labels Mar 11, 2026
@Yurlungur
Copy link
Copy Markdown
Collaborator

Good catch! Should have known we would need to worry about this--nubhlight also has to handle this case, as does the torus initial conditions.

@c-prather
Copy link
Copy Markdown
Collaborator

Oh, I would benefit from stealing this too I think. Though iirc I set the brackets following AthenaK's implementation, so maybe I ended up with an enthalpy bound from that.
It might be worth your time looking at KHARMA's stolen & modded version of this function, we have some downstream tweaks that really improve stability.

@Yurlungur
Copy link
Copy Markdown
Collaborator

@logalexw make sure to remove "WIP" and ping when you're ready for review. No rush though!

@logalexw
Copy link
Copy Markdown
Collaborator Author

logalexw commented Mar 25, 2026

I've refactored the original fix to add an parameter that stors the enthalpy lower bound and is able to be utilized in conserved to primitive reconstruction (both initialization and fixup)!

Unfortunately, there seems to be another bug (absolute value??) that causes epsilon to be strictly positive. The new updates to con2prim_robust.cpp produce the dashed line, which approximates our expected initial SIE well, but phoebus produces the "mirrored" SIE curve (in blue) on the zeroth timestep. Any ideas where this is happening? I've had no luck finding the source in src/fixup so far, which is what @AstroBarker originally suspected.


image

@Yurlungur
Copy link
Copy Markdown
Collaborator

Is epsilon here specific internal energy? It's like the floors.

@Yurlungur
Copy link
Copy Markdown
Collaborator

An energy floor is an optional feature in phoebus. You can try disabling or making the floor very small in the input deck.

@logalexw
Copy link
Copy Markdown
Collaborator Author

I thought it was the floors as well, I've set them to a reasonable negative value (for the SC EOS tables + CCSNe problem)– I was debugging with verbose output and the floors (sie, u, rho) were all at expected values (SFHo table minimum for sie, very small values for rho, etc.)

@Yurlungur
Copy link
Copy Markdown
Collaborator

Ah ok. Well if it's not floors, the next thing I would check would just to grep through the code for calls to std::abs or fabs. There will be a lot... but hopefully you can quickly narrow it down. I agree an absolute value seems like a suspect.

@AstroBarker
Copy link
Copy Markdown
Collaborator

Good work @logalexw ! When you have time could you share your workflow to reproduce this (cmake build line for example). And this flipped behavior is present in the 00000.phdf output? And you're plotting the sie output directly? And your fixup block looks like this:

<fixup>
enable_floors = true
enable_ceilings = true
enable_flux_fixup = true
enable_c2p_fixup = true
floor_type = ConstantRhoSie
# updated floors!
rho0_floor = 1.0e-10
sie0_floor = -1.0e2 # << changed back to original oom.
ceiling_type = ConstantGamSie
sie0_ceiling = 1.0e3
gam0_ceiling = 1.0e4

I am asking as I am having some trouble reproducing the behavior using this branch.

@AstroBarker
Copy link
Copy Markdown
Collaborator

AstroBarker commented Mar 25, 2026

And I am seeing that the docs action is failing with a sphinx-multiversion issue.. This thing is so unstable, they changed the version of something under the hood in runner. Happened to me on another project. I'll check for the fix before we merge.

@Yurlungur
Copy link
Copy Markdown
Collaborator

And I am seeing that the docs action is failing with a sphinx-multiversion issue.. This thing is so unstable, they changed the version of something under the hood in runner. Happened to me on another project. I'll check for the fix before we merge.

Yeah just need to pin sphinx. Here's the fix in singularity-eos
https://github.com/lanl/singularity-eos/blob/main/.github/workflows/docs.yml#L34

@logalexw
Copy link
Copy Markdown
Collaborator Author

Good work @logalexw ! When you have time could you share your workflow to reproduce this (cmake build line for example). And this flipped behavior is present in the 00000.phdf output? And you're plotting the sie output directly? And your fixup block looks like this:

<fixup>
enable_floors = true
enable_ceilings = true
enable_flux_fixup = true
enable_c2p_fixup = true
floor_type = ConstantRhoSie
# updated floors!
rho0_floor = 1.0e-10
sie0_floor = -1.0e2 # << changed back to original oom.
ceiling_type = ConstantGamSie
sie0_ceiling = 1.0e3
gam0_ceiling = 1.0e4

I am asking as I am having some trouble reproducing the behavior using this branch.

Yes, these are the correct parameters, and yes, the flip happens in 00000.phdf - I get the same result plotting both sie directly and using p.energy/p.density.

I've built this on UA HPC using cmake .. -DPHOEBUS_GEOMETRY=MonopoleSph; make -j8

@logalexw
Copy link
Copy Markdown
Collaborator Author

Another thing I just saw - is it intended that both drho, du and other derived quantities are always positive in fixup.cpp? Allowing for negative energy floors breaks that assumption.

@Yurlungur
Copy link
Copy Markdown
Collaborator

Another thing I just saw - is it intended that both drho, du and other derived quantities are always positive in fixup.cpp? Allowing for negative energy floors breaks that assumption.

I'm not sure I understand... shouldn't they still be always positive? If u < uflr, and |u| > |uflr|, then |du| > 0 still to add energy, right?

@logalexw logalexw changed the title WIP: calculate con2prim enthalpy bound calculate con2prim enthalpy bound Mar 25, 2026
@logalexw
Copy link
Copy Markdown
Collaborator Author

previous issue with mirrored sie is resolved (thanks to @AstroBarker for the catch)!

@logalexw logalexw requested a review from AstroBarker March 25, 2026 23:31
Copy link
Copy Markdown
Collaborator

@AstroBarker AstroBarker left a comment

Choose a reason for hiding this comment

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

Nice work fixing this! Looks good, a couple of small suggestions for mostly cleanliness and organization.

#include "phoebus_utils/unit_conversions.hpp"
#include "phoebus_utils/variables.hpp"

// inspired by SPACELOOP in geometry utils, util for global lower bound for enthalpy
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 would suggest either removing this and writing the for loops out explicitly below or moving it to a better home, maybe somewhere under phoebus_utils and including the relevant header in this file. Either is fine.

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 it's fine here... It's local to this implementation file, so it can clearly be seen what it's used for. That said, the loops in geometry are written to convey intent... the bounds are always the same. If this macro is to be generalized it needs a more useful name than LOOP, like LOOP1D or something. Or PHOEBUSFOR or similar.

Personally I think this isn't really saving us enough characters that I feel the need to factor it out to utils.

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 agree with Brandon; the macro originally had more functionality before I simplified the logic, but in this form the intent would probably be just as clear as a set of explicit for loops (and it's not really saving that much room).

Copy link
Copy Markdown
Collaborator

@Yurlungur Yurlungur left a comment

Choose a reason for hiding this comment

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

Nice work! I have a few comments about the enthalpy finding loop but my comments are non-blocking.

#include "phoebus_utils/unit_conversions.hpp"
#include "phoebus_utils/variables.hpp"

// inspired by SPACELOOP in geometry utils, util for global lower bound for enthalpy
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 it's fine here... It's local to this implementation file, so it can clearly be seen what it's used for. That said, the loops in geometry are written to convey intent... the bounds are always the same. If this macro is to be generalized it needs a more useful name than LOOP, like LOOP1D or something. Or PHOEBUSFOR or similar.

Personally I think this isn't really saving us enough characters that I feel the need to factor it out to utils.

Comment on lines +180 to +192
LOOP(y, n / 2) {
lambda[0] = ye;
LOOP(r, n) {
LOOP(t, n) {
eps = eos_sc.InternalEnergyFromDensityTemperature(rho, T, lambda) / sie_unit;
P = eos_sc.PressureFromDensityTemperature(rho, T, lambda) / press_unit;
h_min = std::min(h_min, 1 + eps + robust::ratio(P, rho));
T += dT;
}
rho += drho;
}
ye += dye;
}
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.

This is fine for now but I think it can probably be done better... For example, one could do a Newton method to find the point of minimal enthalpy... Or by having the EOS report this itself internally. Food for thought for later.

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.

@carlnotsagan and I were talking about this as a later wishlist item for the EOS as well. I'm definitely open to doing something more efficient in the future as well, i don't think this is the best fix per se.

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.

Performancewise it probably doesn't matter, since it's just initial conditions. But I worry it's a bit brittle.

Comment on lines +171 to +178
// spacing for each dimension (i.e. np.linspace)
dT = (T_max - T_min) / n;
drho = (rho_max - rho_min) / n;
dye = (ye_max - ye_min) / (n / 2); // we don't need to resolve ye as much
// initial conditions
T = T_min;
rho = rho_min;
ye = ye_min;
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.

The table spans many orders of magnitude. I think you can use a coarser spacing if you used a log spacing in density and temperature. Or, realistically, you can simply only look at relatively low densities and temperatures, as high densities and temperatures simply won't have a small enthalpy.

@logalexw
Copy link
Copy Markdown
Collaborator Author

logalexw commented Apr 1, 2026

final formatting updates from the comments above are complete! if there are no further adjustments/suggestions, this should be ready to merge.

@Yurlungur Yurlungur merged commit 7704c35 into main Apr 1, 2026
3 checks passed
@Yurlungur
Copy link
Copy Markdown
Collaborator

Merged. Thanks for the fix @logalexw !

@Yurlungur Yurlungur deleted the law/enthalpy-bound branch April 1, 2026 20:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants