Source code for idaes.core.util.phase_equilibria

#################################################################################
# The Institute for the Design of Advanced Energy Systems Integrated Platform
# Framework (IDAES IP) was produced under the DOE Institute for the
# Design of Advanced Energy Systems (IDAES).
#
# Copyright (c) 2018-2024 by the software owners: The Regents of the
# University of California, through Lawrence Berkeley National Laboratory,
# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon
# University, West Virginia University Research Corporation, et al.
# All rights reserved.  Please see the files COPYRIGHT.md and LICENSE.md
# for full copyright and license information.
#################################################################################
"""
This module contains utility functions to generate phase equilibrium data and
plots.
"""
# Import plotting functions
import matplotlib.pyplot as plt
import numpy as np

# Import objects from pyomo package
from pyomo.environ import (
    check_optimal_termination,
    value,
    units as pyunits,
)

from idaes.core.solvers import get_solver
import idaes.logger as idaeslog


__author__ = "Alejandro Garciadiego"


[docs] def Txy_diagram( model, component_1, component_2, pressure, num_points=20, temperature=298.15, figure_name=None, print_legend=True, include_pressure=False, print_level=idaeslog.NOTSET, solver=None, solver_op=None, ): """ This method generates T-x-y plots. Given the components, pressure and property dictionary this function calls Txy_data() to generate T-x-y data and once the data has been generated calls build_txy_diagrams() to create a plot. Args: component_1: Component which composition will be plotted in x axis component_2: Component which composition will decrease in x axis pressure: Pressure at which the bubble and drew temperatures will be calculated temperature: Temperature at which to initialize state block num_points: Number of data point to be calculated properties: property package which contains parameters to calculate bubble and dew temperatures for the mixture of the components specified. figure_name: if a figure name is included the plot will save with the name figure_name.png print_legend (bool): = If True, include legend to distinguish between Bubble and dew temperature. The default is True. include_pressure (bool) = If True, print pressure at which the plot is calculated in legends. The default is False. print_level: printing level from initialization solver: solver to use (default=None, use IDAES default solver) solver_op: solver options Returns: Plot """ # Run txy_ data function to obtain bubble and dew twmperatures Txy_data_to_plot = Txy_data( model, component_1, component_2, pressure, num_points, temperature, print_level, solver, solver_op, ) # Run diagrams function to convert t-x-y data into a plot build_txy_diagrams(Txy_data_to_plot, figure_name, print_legend, include_pressure)
[docs] def Txy_data( model, component_1, component_2, pressure, num_points=20, temperature=298.15, print_level=idaeslog.NOTSET, solver="ipopt_v2", solver_op=None, solver_writer_config=None, ): """ Function to generate T-x-y data. The function builds a state block and extracts bubble and dew temperatures at P pressure for N number of compositions. As N is increased the time of the calculation will increase and create a smoother looking plot. Args: component_1: Component 1 component_2: Component 2 pressure: Pressure at which the bubble and drew temperatures will be calculated temperature: Temperature at which to initialize state block num_points: Number of data point to be calculated model: Model with initialized property package which contains data to calculate bubble and dew temperatures for component 1 and component 2 print_level: printing level from initialization solver: solver to use (default=None, use IDAES default solver) solver_op: solver options solver_writer_config: writer_config arguments for solver Returns: (Class): A class containing the T-x-y data """ components = list(model.params.component_list) components_used = [component_1, component_2] components_not_used = list(set(components) - set(components_used)) # Add properties parameter blocks to the flowsheet with specifications model.props = model.params.build_state_block([1], defined_state=True) # Set initial concentration of component 1 close to 1 x = 0.99 # Set conditions for flash unit model model.props[1].mole_frac_comp[component_1].fix(x) for i in components_not_used: model.props[1].mole_frac_comp[i].fix(1e-5) xs = sum(value(model.props[1].mole_frac_comp[i]) for i in components_not_used) model.props[1].mole_frac_comp[component_2].fix(1 - x - xs) model.props[1].flow_mol.fix(1) model.props[1].temperature.fix(temperature) model.props[1].pressure.fix(pressure) # Initialize flash unit model model.props[1].calculate_scaling_factors() # TODO: This will need to be updated at some point as we deprecate the old initialization API model.props.initialize(solver=solver, optarg=solver_op, outlvl=print_level) solver = get_solver( solver, solver_options=solver_op, writer_config=solver_writer_config ) # Create an array of compositions with N number of points x_d = np.linspace(x, 1 - x - xs, num_points) # Create empty arrays for concentration, bubble temperature and dew temperature X = [] Tbubb = [] Tdew = [] # Obtain pressure and temperature units from the unit model Punit = pyunits.get_units(model.props[1].pressure) Tunit = pyunits.get_units(model.props[1].temperature) count = 1 # Create and run loop to calculate temperatures at every composition for i, v in enumerate(x_d): model.props[1].mole_frac_comp[component_1].fix(v) model.props[1].mole_frac_comp[component_2].fix(1 - v - xs) # solve the model status = solver.solve(model, tee=False) # If solution is optimal store the concentration, and calculated temperatures in the created arrays if check_optimal_termination(status): print(f"Case: {count} Optimal. {component_1} x = {v:.2f}") if hasattr(model.props[1], "_mole_frac_tdew") and hasattr( model.props[1], "_mole_frac_tbub" ): Tbubb.append(value(model.props[1].temperature_bubble["Vap", "Liq"])) Tdew.append(value(model.props[1].temperature_dew["Vap", "Liq"])) elif hasattr(model.props[1], "_mole_frac_tdew"): print("One of the components only exists in vapor phase.") Tdew.append(value(model.props[1].temperature_dew["Vap", "Liq"])) elif hasattr(model.props[1], "_mole_frac_tbub"): print("One of the components only exists in liquid phase.") Tbubb.append(value(model.props[1].temperature_bubble["Vap", "Liq"])) X.append(v) # If the solver did not solve to an optimal solution, do not store the data point else: print(f"Case: {count} No Result {component_1} x = {x_d[i]:.2f}") count += 1 # Call TXYData function and store the data in TD class TD = TXYDataClass(component_1, component_2, Punit, Tunit, pressure) TD.TBubb = Tbubb TD.TDew = Tdew TD.x = X # Return the data class with all the information of the calculations return TD
# Author: Alejandro Garciadiego
[docs] class TXYDataClass: """ Write data needed for build_txy_diagrams() into a class. The class can be obtained by running Txy_data() or by assigining values to the class. """ def __init__(self, component_1, component_2, Punits, Tunits, pressure): """ Args: component_1: Component 1 component_2: Component 2 Punits: Initial value of heat of hot utility Tunits: Initial value of heat to be removed by cold utility pressure: Pressure at which the T-x-y data was evaluated Returns: (Class): A class containing the T-x-y data """ # Build self.Component_1 = component_1 self.Component_2 = component_2 # Assign units of pressure and temperature self.Punits = Punits self.Tunits = Tunits # Assign pressure at which the data has been calculated self.P = pressure # Create arrays for data self.TBubb = [] self.TDew = [] self.x = [] def Temp_Bubb(self, data_list): """ Args: data_list: Bubble temperature array Returns: None """ self.TBubb = data_list def Temp_Dew(self, data_list_2): """ Args: data_list_2: Dew temperature array Returns: None """ self.Tdew = data_list_2 def composition(self, data_list_3): """ Args: data_list_3: x data array Returns: None """ self.x = data_list_3
# Author: Alejandro Garciadiego
[docs] def build_txy_diagrams( txy_data, figure_name=None, print_legend=True, include_pressure=False ): """ Args: txy_data: Txy data class includes components bubble and dew temperatures, compositions, components, pressure, and units. figure_name: if a figure name is included the plot will save with the name figure_name.png print_legend (bool): = If True, include legend to distinguish between Bubble and dew temperature. The default is True. include_pressure (bool) = If True, print pressure at which the plot is calculated in legends. The default is False. Returns: t-x-y plot """ # Declare a plot and it's size ig, ax = plt.subplots(figsize=(12, 8)) # pylint: disable=unused-variable if len(txy_data.TBubb) and len(txy_data.TDew) > 0: if include_pressure is True: # Plot results for bubble temperature ax.plot( txy_data.x, txy_data.TBubb, "r", label="Bubble Temp P = " + str(txy_data.Press) + " " + str(txy_data.Punits), linewidth=1.5, ) # Plot results for dew temperature ax.plot( txy_data.x, txy_data.TDew, "b", label="Dew Temp P = " + str(txy_data.Press) + " " + str(txy_data.Punits), linewidth=1.5, ) else: # Plot results for bubble temperature ax.plot( txy_data.x, txy_data.TBubb, "r", label="Bubble Temp ", linewidth=1.5 ) # Plot results for dew temperature ax.plot(txy_data.x, txy_data.TDew, "b", label="Dew Temp", linewidth=1.5) elif len(txy_data.TDew) == 0: if include_pressure is True: # Plot results for bubble temperature # Plot results for dew temperature ax.plot( txy_data.x, txy_data.TBubb, "b", label="Dew Temp P = " + str(txy_data.Press) + " " + str(txy_data.Punits), linewidth=1.5, ) else: # Plot results for dew temperature ax.plot(txy_data.x, txy_data.TBubb, "b", label="Dew Temp", linewidth=1.5) elif len(txy_data.TBubb) == 0: if include_pressure is True: # Plot results for bubble temperature # Plot results for dew temperature ax.plot( txy_data.x, txy_data.TDew, "b", label="Bubble Temp P = " + str(txy_data.Press) + " " + str(txy_data.Punits), linewidth=1.5, ) else: # Plot results for dew temperature ax.plot(txy_data.x, txy_data.TDew, "b", label="Dew Temp", linewidth=1.5) # Include grid ax.grid() # Declare labels and fontsize plt.xlabel(txy_data.Component_1 + " concentration (mol/mol)", fontsize=20) plt.ylabel("Temperature [" + str(txy_data.Tunits) + "]", fontsize=20) # Declare plot title plt.title( "T-x-y diagram " + txy_data.Component_1 + "-" + txy_data.Component_2, fontsize=24, ) # Set limits of 0-1 mole fraction plt.xlim(0.0, 1) # Declare legend and fontsize if print_legend is True: plt.legend(fontsize=16) if figure_name: plt.savefig(str(figure_name) + ".png") # Show plot plt.show()