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

Generic property package: estimating bubble and dew temperatures #1555

Open
alma-walmsley opened this issue Dec 23, 2024 · 0 comments
Open

Comments

@alma-walmsley
Copy link

Summary

I was having problems with the "Bubble, dew, and critical point initialization" step in the generic property package initialization. I ended up finding out that the initial guess for temperature_bubble was higher than that of temperature_dew, which I suspect was the key cause of infeasibility. I then had a look through the estimate_Tbub and estimate_Tdew methods in idaes.models.properties.modular_properties.base.utility. These use Newton's method to converge on a guess for temperature_bubble and temperature_dew respectively. I eventually realized that I was hitting the iteration limit of 30, which meant I got a bad guess which would cause major issues.

I was able to resolve this by increasing the iteration limit via idaes.models.properties.modular_properties.base.utility.MAX_ITER = 1000. This ended up giving me a much better guess since I was actually converging rather than stopping early. Note, I don't have much chemical engineering knowledge, so I am not sure if this is a sign of a modelling error.

I decided to open this issue since it took me a while to debug.

Potential actions:

a) Log a warning:

  • At a minimum, I think a warning should be logged if MAX_ITER is reached in the estimate_Tbub and estimate_Tdew methods.

b) Increase default max iterations:

  • Assume that failing to converge in MAX_ITER iterations will cause major issues, so increase the default number of max iterations. The argument against this is that a poorly-defined model (ie. one that never manages to converge) would take extra time to end up reaching the same result. Also, I can imagine if MAX_ITER were set to something like 100 instead of 30, I never would have realized there was an issue (and then we get "flaky" initialization when a more complex model is used, which is not fun). However, some combination of a) and b) may be useful.

c) Switch to a solver instead of using Newton's method

  • Despite the added overhead, I was able to get increased performance (time-wise) by defining the model in pyomo and solving externally rather than solving directly in python. ipopt could solve in ~"3-5 iterations" compared to what was ~60 iterations when using Newton's method in python. I imagine it is more scalable too (for complex models).

For example,

def _custom_estimate_Tbub(blk, T_units, raoult_comps, henry_comps, liquid_phase):
    """
    Function to estimate bubble point temperature
    Args:
        blk: StateBlock to use
        T_units: units of temperature
        raoult_comps: list of components that follow Raoult's Law
        henry_comps: list of components that follow Henry's Law
        liquid_phase: name of liquid phase
    Returns:
        Estimated bubble point temperature as a float.
    """
    # Use lowest component temperature_crit as starting point
    # Starting high and moving down generally works better,
    # as it under-predicts next step due to exponential form of
    # Psat.
    # Subtract 1 to avoid potential singularities at Tcrit
    Tbub_initial = (
        min(blk.params.get_component(j).temperature_crit.value for j in raoult_comps)
        - 1
    )
    blk.tbub_initialize = Block()
    m = blk.tbub_initialize
    m.Tbub = Var(initialize=Tbub_initial)
    m.f = Expression(expr=(
        sum(
            get_method(blk, "pressure_sat_comp", j)(
                blk, blk.params.get_component(j), m.Tbub * T_units
            )
            * blk.mole_frac_comp[j]
            for j in raoult_comps
        )
        + sum(
            blk.mole_frac_comp[j]
            * blk.params.get_component(j)
            .config.henry_component[liquid_phase]["method"]
            .return_expression(blk, liquid_phase, j, m.Tbub * T_units)
            for j in henry_comps
        )
        - blk.pressure
    ))
    m.df = Expression(expr=(
        sum(
            get_method(blk, "pressure_sat_comp", j)(
                blk, blk.params.get_component(j), m.Tbub * T_units, dT=True
            )
            * blk.mole_frac_comp[j]
            for j in raoult_comps
        )
        + sum(
            blk.mole_frac_comp[j]
            * blk.params.get_component(j)
            .config.henry_component[liquid_phase]["method"]
            .dT_expression(blk, liquid_phase, j, m.Tbub * T_units)
            for j in henry_comps
        )
    ))
    m.tbub_constraint = Constraint(rule=(
        m.Tbub == m.Tbub - m.f / m.df
    ))
    solver = get_solver("ipopt")
    solver.options["tol"] = TOL  # default currently 1e-1
    solver.options["max_iter"] = MAX_ITER  # default currently 30
    solver.solve(m)

    # probably need to check for infeasibility here
    # ...

    del blk.tbub_initialize  # cleanup
    return m.Tbub.value

Feedback would be useful 🙂

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

No branches or pull requests

1 participant