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

Catch singular explosions in saturation_PHSU_pure #2250

Merged
merged 6 commits into from
Jan 16, 2024

Conversation

msaitta-mpr
Copy link
Contributor

Description of the Change

Sometimes, the Akasaka solver has issues. This is usually caught and then it is retried with a new omega. However, sometimes it goes bad because the J matrix is singular, and this is not caught because the error is not recalculated.

This PR recalculates the error term to prevent a bad result.

Benefits

This prevents saturation_PHSU_pure from providing wrong answers.

This closes #2245.

Possible Drawbacks

This slows calculations because it requires that the error term be recalculated an extra time for each update. However, it prevents bad results, which is usually worth it.

This does not solve the underlying issue of a singular matrix forming in the Akasaka solver. Therefore, we may want to look into that in the future.

Verification Process

I ran the following C++ code and verified that the results look smooth.

// Test issue #2245
#pragma once
#include "CoolProp.h"
#include "AbstractState.h"
#include <iostream>
#include <memory>
#include <vector>

std::vector <double> linspace(double start, double stop, int num_pts){
    std::vector<double> vals (num_pts);
    for (int ii=0; ii < num_pts; ii++){
        vals.at(ii) = start + (stop - start) * double(ii) / double(num_pts-1);
    }
    return vals;
}

int main()
{
    std::shared_ptr<CoolProp::AbstractState> AS = std::shared_ptr<CoolProp::AbstractState>(CoolProp::AbstractState::factory("HEOS", "Ammonia"));
    std::vector<double> p_vals = linspace(50000.0, 100000, 100);
    for (double p : p_vals) {
        AS->update(CoolProp::PQ_INPUTS, p, 1);
        std::cout << p << "  " << AS->T() << "  " << CoolProp::PropsSI("T", "P", p, "Q", 1.0, "Ammonia") << std::endl;
    }
    return 0;
}

Applicable Issues

Closes #2245

Sometimes, the Akasaka solver has issues. This is usually caught and
then it is retried with a new omega. However, sometimes it goes bad
because the J matrix is singular, and this is not caught because the
error is not recalculated.

This commit recalculates the error term to prevent a bad result.
Copy link

@danielkwalsh danielkwalsh left a comment

Choose a reason for hiding this comment

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

Maybe it would be a good idea to explain your intent of recomputing the error as a code comment? I don't fully understand the problem, but based on the message in your PR, it sounds like this extra check is superfluous provided that the Akasaka solver's Jacobian works as expected. When that issue is addressed in the future, I'm hoping that my proposed comment could be a reminder that the changes you made here could be omitted to cut out the extra error calculation you described. Just a thought.

@ibell
Copy link
Contributor

ibell commented May 12, 2023

Those additional checks should be very cheap, so if this fixes the iteration failures then I would say we should merge them.

@ibell
Copy link
Contributor

ibell commented May 12, 2023

But I also agree with @danielkwalsh that a few comments would be good

@msaitta-mpr
Copy link
Contributor Author

I added a comment which I hope captures what you think should be communicated. If I am off, please let me know.

@danielkwalsh
Copy link

@msaitta-mpr yeah, I think this looks great. Thanks for adding this!

@danielkwalsh
Copy link

danielkwalsh commented May 16, 2023

@ibell @msaitta-mpr Seems like there are two tests that are failing. Is that expected?

@msaitta-mpr
Copy link
Contributor Author

These also fail in the master branch. I have been meaning to track down the cause and fix it, but I haven't had a chance.

@ibell
Copy link
Contributor

ibell commented May 17, 2023

Should I go ahead and merge, or wait?

@msaitta-mpr
Copy link
Contributor Author

On a hunch, I reran my test case with much greater point density and found that there are still a few points that do not behave. So, while the PR has so far fixed a lot of these issues, there is still some underlying issue. I will try to look into it. I think it makes sense to leave the PR open for the moment. Hopefully these commits can serve as an interim patch for users who need a quick fix.

@ibell
Copy link
Contributor

ibell commented May 19, 2023

Hmm that's weird. I wonder what is going on. If this improves this, it might be worth it to merge it. But I also wonder whether there is something wrong with the ancillary functions it fails so hard. I don't recall similar problems with other EOS.

@danielkwalsh
Copy link

@ibell @msaitta-mpr Have we assessed whether this is an ammonia-specific issue, or does it exist for other fluids as well?

@msaitta-mpr
Copy link
Contributor Author

Hmm that's weird. I wonder what is going on. If this improves this, it might be worth it to merge it. But I also wonder whether there is something wrong with the ancillary functions it fails so hard. I don't recall similar problems with other EOS.

Surprisingly, I do not think that it is the ancillary. Looking at a couple of the failing points, the ancillary is within 0.01% of the correct solution.

The issue seems to be in the update of SatL and SatV. A DT update is performed, but the resultant state has a p value that is not correct (i.e., what you get if you just give CoolProp these values directly).

@ibell
Copy link
Contributor

ibell commented May 23, 2023 via email

During saturated PHSU flash calculations, SatL and SatV states have an
imposed phase. This is good for stability, but there is a small chance
that they can both up up with a matching third variable (e.g.,
pressure) that is not actually at the saturation point. This commit
forces a final DT update without this requirement. If an actual solution
has been found, the the error term will still be small. If not, then we
throw an exception and try again.

This continues work on CoolProp#2245.
In saturation_PHSU_pure, we unspecify the phase of SatL and SatV to
perform a final check. However, for any future updates, these states
*must* be set with specified phase. This commit ensures that no matter
what happens (exception, etc.) the phase is always specified again.
@msaitta-mpr
Copy link
Contributor Author

The "error" in the DT update seems to be due to the imposed phase on SatL and SatV. I added a final update without this phase specification. This now gives a completely smooth result.

@ibell
Copy link
Contributor

ibell commented May 28, 2023 via email

@msaitta-mpr
Copy link
Contributor Author

That is what I would have thought as well, but you can get some strange results even when doing DT updates. Here is some example Python:

import CoolProp
rho = 16425.375470309176
t = 337.43858705452624
state = CoolProp.AbstractState("HEOS", "AMMONIA")
state.update(CoolProp.DmolarT_INPUTS, rho, t)
print(state.p())
state.specify_phase(CoolProp.iphase_liquid)
state.update(CoolProp.DmolarT_INPUTS, rho, t)
print(state.p())

This prints

2898759.978561249
56513.00000000532

whereas I would have expected the same value to be printed in either case.

Is the issue in the equation of state, or in the way that the pressure is found with some underlying phase assumption?

I will try to take a look into the intermediate derivative values.

@ibell
Copy link
Contributor

ibell commented May 29, 2023

Well, in your test case, the first set of inputs are two-phase, so that is why the results are different:

import CoolProp
rho = 16425.375470309176
t = 337.43858705452624
state = CoolProp.AbstractState("HEOS", "AMMONIA")
state.update(CoolProp.DmolarT_INPUTS, rho, t)
print(state.p(), state.Q())
state.specify_phase(CoolProp.iphase_liquid)
state.update(CoolProp.DmolarT_INPUTS, rho, t)
print(state.p(), state.Q())

yielding

2898759.978561249 0.04091462363285387
56513.00000000532 -1.0

and this is general true. If the inputs are two-phase you should expect different answers.

@danielkwalsh
Copy link

Hey, all. What's the status on this?

@msaitta-mpr
Copy link
Contributor Author

@danielkwalsh Sorry for the slow progress on this, but I am a bit stuck. In short, the current version of the PR fixes the immediate issue, but it does so in an awkward way that might not be best for merging into master. If you need a short term fix, you can probably just use this branch as it seems to make all of my tests work.

To elaborate a bit further, there seem to have been two issues at play:

  1. Sometimes, the saturation solver would diverge in the final iteration, causing a value that was incorrect to be passed. This was corrected in 41739c5 and c1dc498.
  2. Sometimes, the saturation solver converges to a point where the provided residuals are all 0 (or very close to it). However, rather than finding points on the saturation curve, it has instead found points within the vapor dome. My solution to this was to force it to recompute the pressure, which seems to work. However, ibell believes that this might have an issue of a nested phase calculation. Therefore, there exists a way to determine that the found DT combination is incorrect, but I have not yet had the time to come up with a clever way of doing so.

@danielkwalsh
Copy link

danielkwalsh commented Jun 26, 2023

@msaitta-mpr Perhaps I am missing something, but the saturation pressure applies to any point under the dome. Any isobar here will also be an isotherm, since the liquid and gaseous phases are in equilibrium. While I don't know the exact behavior of the solver being used here, nor do I understand the exact issue you are seeing, I could imagine that due to this degeneracy, asking for a state at a given pressure on the phase line could be multivalued due to this degeneracy, and consequently, the solver may give different results under different circumstances corresponding to different enthalpies. This is fine, since we are only interested in determining for a fluid at a given pressure, its corresponding vapor temperature, or vice versa. I'm happy to help try to debug this issue with you if you'd like, but unfortunately I don't understand the backend of the code well enough to do so on my own. Perhaps @ibell has some thoughts?

On the CoolProp low-level API page, I find the following:

Warning When specifying an imposed phase, it is absolutely critical that the input pair actually lie within the imposed phase region. If an incorrect phase is imposed for the given input pair, update() may throw unexpected errors or incorrect results may possibly be returned from the property functions. If the state point phase is not absolutely known, it is best to let CoolProp determine the phase at the cost of some computational efficiency.

@msaitta-mpr
Copy link
Contributor Author

@danielkwalsh The low-level API page that you linked is key to this issue. We want to find the phase boundary. To do so, we perform repeated evaluations with two different states with imposed phases: one assumed liquid and the other gas. This works well, until they are evaluated within the two phase region. As the documentation states, they may return incorrect results in this region. The trick is that I do not have a good way to detect the phase to verify that they are not in the two phase region without recursing to another attempt at finding the phase boundary.

@danielkwalsh
Copy link

@msaitta-mpr @ibell It seems that a common approach used is to pre-calculate the phase boundary. See the "Vapor-Liquid Equilibrium" section of this link for more details.

This reverts commit c6b650b.

The commit caused potential recursive lookups and did not solve the
issue at hand.
The rhoV ancillary gave somewhat wrong results. This commit provides a
closer fit that prevents errors downstream.
@msaitta-mpr
Copy link
Contributor Author

msaitta-mpr commented Sep 22, 2023

@ibell Looking at this again, I think that the issue actually is with the ancillary function. I went ahead and quickly remade the ancillary and things seem to be better now.

Figure
Figure_1

@danielkwalsh
Copy link

Hey, @msaitta-mpr, does this mean the issue is fixed?

Comment on lines +557 to +561
// Recalculate error
// The result has changed since the last error calculation.
// In rare scenarios, the final step can become unstable due to solving a singular
// J matrix. This final error check verifies that the solution is still good.
// Furthermore, the forced phase of SatL and SatV may have caused errors. We will recalculate them without this assumption.
Copy link

Choose a reason for hiding this comment

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

Does the singular J matrix still crop up with the fixed ancillary? I'm wondering if this part of the code is still needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I never actually checked whether it is still needed. I figure that it is a cheap enough check that it is probably worth leaving in.

Choose a reason for hiding this comment

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

Okay, great. I'm satisfied with that.

Choose a reason for hiding this comment

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

@msaitta-mpr @ibell, so what are the next steps to get this merged?

Copy link
Contributor

Choose a reason for hiding this comment

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

If everyone agrees we are ready I can merge it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe that in its current state that it fixes the known issue and does not introduce any others. It raises the question of whether there are other ancillary issues hidden elsewhere, but I think that we should merge for now and reevaluate the ancillaries some other time.

@ibell ibell merged commit 443a2fd into CoolProp:master Jan 16, 2024
1 check passed
@ibell
Copy link
Contributor

ibell commented Jan 16, 2024

@msaitta-mpr @danielkwalsh please feel free to introduce yourselves here: #2206

@danielkwalsh
Copy link

danielkwalsh commented Jan 16, 2024

@ibell, @msaitta-mpr, thanks for merging! When do we get a new release as a new version?

@ibell
Copy link
Contributor

ibell commented Jan 16, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Incorrect Saturation Temperature Calculation
3 participants