Source code for despasito.equations_of_state.interface

"""
    Interface needed to create further equation of state (EOS) objects.

    All folders in this directory refer back to this interface. Using this template
    all future EOS will be easily exchanged.
"""

# All folders in this directory refer back to this interface

import numpy as np
import logging
from abc import ABC, abstractmethod

logger = logging.getLogger(__name__)


# __________________ EOS Interface _________________
[docs] class EosTemplate(ABC): """ Interface used in all EOS object options. By using this template, all EOS objects are then easily exchanged. Parameters ---------- beads : list[str] List of unique bead names used among components bead_library : dict A dictionary where bead names are the keys to access EOS self interaction parameters cross_library : dict, Optional Optional library of bead cross interaction parameters. As many or as few of the desired parameters may be defined for whichever group combinations are desired. The remaining are estimated with mixing rules. kwargs Additional keywords from EOS object type Attributes ---------- beads : list[str] List of unique bead names used among components bead_library : dict A dictionary where bead names are the keys to access EOS self interaction parameters cross_library : dict Library of bead cross interaction parameters. As many or as few of the desired parameters may be defined for whichever group combinations are desired. parameter_types : list[str] This list of parameter names must be defined in an EOS object for parameter fitting parameter_bound_extreme : dict With each parameter names as an entry representing a list with the minimum and maximum feasible parameter value. number_of_components : int Number of components in mixture represented by given EOS object. """ def __init__(self, beads, bead_library, **kwargs): """Initiation of EOS object with attributes needed by other modules.""" self.parameter_types = None self.parameter_bound_extreme = None self.number_of_components = None for bead in beads: if bead not in bead_library: raise ValueError("The group, '{}', was not found in parameter library".format(bead)) self.beads = None self.bead_library = None self.cross_library = None
[docs] @abstractmethod def pressure(self, rho, T, xi): """ Compute pressure given system information Parameters ---------- rho : numpy.ndarray Number density of system [mol/m^3] T : float Temperature of the system [K] xi : list[float] Mole fraction of each component Returns ------- P : numpy.ndarray Array of pressure values [Pa] associated with each density and so equal in length """ pass
[docs] @abstractmethod def fugacity_coefficient(self, P, rho, xi, T): r""" Compute fugacity coefficient Parameters ---------- P : float Pressure of the system [Pa] rho : float Molar density of system [mol/m^3] T : float Temperature of the system [K] xi : list[float] Mole fraction of each component Returns ------- fugacity_coefficient : numpy.ndarray :math:`\mu_i`, Array of fugacity coefficient values for each component """ pass
[docs] @abstractmethod def density_max(self, xi, T, maxpack=0.9): """ Estimate the maximum density based on the hard sphere packing fraction. Parameters ---------- xi : list[float] Mole fraction of each component T : float Temperature of the system [K] maxpack : float, Optional, default=0.9 Maximum packing fraction Returns ------- max_density : float Maximum molar density [mol/m^3] """ pass
[docs] def guess_parameters(self, param_name, bead_names): """ Output a guess for the given parameter type. """ """ Generate initial guesses for the parameters to be fit. Parameters ---------- parameter : str Parameter to be fit. See EOS documentation for supported parameter names. bead_names : list Bead names to be changed. For a self interaction parameter, the length will be 1, for a cross interaction parameter, the length will be two. Returns ------- param_initial_guess : numpy.ndarray, An initial guess for parameter, it will be optimized throughout the p rocess. """ keys = ["beads", "parameter_types", "bead_library", "cross_library"] for key in keys: if getattr(self, key) is None: raise ValueError( "EOS object attribute, {}, cannot be None. Ensure EOS object " "initiates this attribute".format(key) ) if len(bead_names) > 2: raise ValueError( "The bead names {} were given, but only a maximum of 2 are " "permitted.".format(", ".join(bead_names)) ) if not set(bead_names).issubset(self.beads): raise ValueError( "The bead names {} were given, but they are not in the allowed" " list: {}".format(", ".join(bead_names), ", ".join(self.beads)) ) param_value = None # Self interaction parameter if len(bead_names) == 1: if bead_names[0] in self.bead_library and param_name in self.bead_library[bead_names[0]]: param_value = self.bead_library[bead_names[0]][param_name] # Cross interaction parameter elif len(bead_names) == 2: if bead_names[1] in self.cross_library and bead_names[0] in self.cross_library[bead_names[1]]: if param_name in self.cross_library[bead_names[1]][bead_names[0]]: param_value = self.cross_library[bead_names[1]][bead_names[0]][param_name] elif bead_names[0] in self.cross_library and bead_names[1] in self.cross_library[bead_names[0]]: if param_name in self.cross_library[bead_names[0]][bead_names[1]]: param_value = self.cross_library[bead_names[0]][bead_names[1]][param_name] if param_value is None: bounds = self.check_bounds(bead_names[0], param_name, np.empty(2)) param_value = (bounds[1] - bounds[0]) / 2 + bounds[0] return param_value
[docs] def check_bounds(self, parameter, param_name, bounds): """ Generate initial guesses for the parameters to be fit. Parameters ---------- parameter : str Parameter to be fit. See EOS documentation for supported parameter names. param_name : str Full parameter string to be fit. See EOS documentation for supported parameter names. bounds : list Upper and lower bound for given parameter type Returns ------- bounds : list A screened and possibly corrected low and a high value for the parameter, param_name """ keys = ["parameter_types", "parameter_bound_extreme"] for key in keys: if getattr(self, key) is None: raise ValueError( "EOS object attribute, {}, cannot be None. Ensure EOS object " "initiates this attribute".format(key) ) fit_parameter_names_list = param_name.split("_") parameter = fit_parameter_names_list[0] bounds_new = np.zeros(2) # Non bonded parameters if parameter in self.parameter_bound_extreme: if bounds[0] < self.parameter_bound_extreme[parameter][0]: logger.debug( "Given {} lower boundary, {}, is less than what is recommended by " "Eos object. Using value of {}.".format( param_name, bounds[0], self.parameter_bound_extreme[parameter][0], ) ) bounds_new[0] = self.parameter_bound_extreme[parameter][0] elif bounds[0] > self.parameter_bound_extreme[parameter][1]: logger.debug( "Given {} lower boundary, {}, is greater than what is recommended " "by Eos object. Using value of {}.".format( param_name, bounds[0], self.parameter_bound_extreme[parameter][0], ) ) bounds_new[0] = self.parameter_bound_extreme[parameter][0] else: bounds_new[0] = bounds[0] if bounds[1] > self.parameter_bound_extreme[parameter][1]: logger.debug( "Given {} upper boundary, {}, is greater than what is recommended " "by Eos object. Using value of {}.".format( param_name, bounds[1], self.parameter_bound_extreme[parameter][1], ) ) bounds_new[1] = self.parameter_bound_extreme[parameter][1] elif bounds[1] < self.parameter_bound_extreme[parameter][0]: logger.debug( "Given {} upper boundary, {}, is less than what is recommended " "by Eos object. Using value of {}.".format( param_name, bounds[1], self.parameter_bound_extreme[parameter][1], ) ) bounds_new[1] = self.parameter_bound_extreme[parameter][1] else: bounds_new[1] = bounds[1] else: raise ValueError( "The parameter name {} is not found in the allowed parameter types:" " {}".format(param_name, ", ".join(self.parameter_types)) ) return bounds_new
[docs] def update_parameter(self, param_name, bead_names, param_value): r""" Update a single parameter value during parameter fitting process. Parameters ---------- param_name : str Parameter to be fit. See EOS documentation for supported parameter names. Cross interaction parameter names should be composed of parameter name and the other bead type, separated by an underscore (e.g. epsilon_CO2). bead_names : list Bead names to be changed. For a self interaction parameter, the length will be 1, for a cross interaction parameter, the length will be two. param_value : float Value of parameter """ keys = ["beads", "parameter_types", "bead_library", "cross_library"] for key in keys: if getattr(self, key) is None: raise ValueError( "EOS object attribute, {}, cannot be None. Ensure EOS object " "initiates this attribute".format(key) ) if len(bead_names) > 2: raise ValueError( "The bead names {} were given, but only a maximum of 2 are " "permitted.".format(", ".join(bead_names)) ) if not set(bead_names).issubset(self.beads): raise ValueError( "The bead names {} were given, but they are not in the allowed " "list: {}".format(", ".join(bead_names), ", ".join(self.beads)) ) if not any([x in param_name for x in self.parameter_types]): raise ValueError( "The parameter name {} is not found in the allowed parameter " "types: {}".format(param_name, ", ".join(self.parameter_types)) ) # Self interaction parameter if len(bead_names) == 1: if bead_names[0] in self.bead_library: self.bead_library[bead_names[0]][param_name] = param_value else: self.bead_library[bead_names[0]] = {param_name: param_value} # Cross interaction parameter elif len(bead_names) == 2: if bead_names[1] in self.cross_library and bead_names[0] in self.cross_library[bead_names[1]]: self.cross_library[bead_names[1]][bead_names[0]][param_name] = param_value elif bead_names[0] in self.cross_library: if bead_names[1] in self.cross_library[bead_names[0]]: self.cross_library[bead_names[0]][bead_names[1]][param_name] = param_value else: self.cross_library[bead_names[0]][bead_names[1]] = {param_name: param_value} else: self.cross_library[bead_names[0]] = {bead_names[1]: {param_name: param_value}}