# -*- coding: utf-8 -*-
# BioSTEAM: The Biorefinery Simulation and Techno-Economic Analysis Modules
# Copyright (C) 2020-2023, Yoel Cortes-Pena <yoelcortes@gmail.com>
#
# This module is under the UIUC open-source license. See
# github.com/BioSTEAMDevelopmentGroup/biosteam/blob/master/LICENSE.txt
# for license details.
"""
"""
from __future__ import annotations
import thermosteam as tmo
from typing import Optional, Iterable
from .units_of_measure import UnitsOfMeasure
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .equilibrium import (
ActivityCoefficients,
FugacityCoefficients,
PoyintingCorrectionFactors,
)
from .mixture import Mixture
__all__ = ('settings', 'ProcessSettings')
# %%
def raise_no_thermo_error():
raise RuntimeError("no available 'Thermo' object; "
"use settings.set_thermo to set the default thermo object")
def raise_no_flashpkg_error():
raise RuntimeError("no available 'FlashPackage' object; "
"use settings.set_flashpkg to set the default flash package object")
# %%
[docs]
class ProcessSettings:
"""
A compilation of all settings that may affect BioSTEAM results, including
thermodynamic property packages, utility agents, and characterization factors.
Examples
--------
Access or change the Chemical Engineering Plant Cost Index (CEPCI):
>>> from biosteam import settings, Stream
>>> settings.CEPCI # Defaults to average for year 2017
567.5
Access or change cooling agents:
>>> settings.cooling_agents
[<UtilityAgent: cooling_water>,
<UtilityAgent: chilled_water>,
<UtilityAgent: chilled_brine>,
<UtilityAgent: propane>,
<UtilityAgent: propylene>,
<UtilityAgent: ethylene>]
Access or change heating agents:
>>> settings.heating_agents
[<UtilityAgent: low_pressure_steam>,
<UtilityAgent: medium_pressure_steam>,
<UtilityAgent: high_pressure_steam>,
<UtilityAgent: natural_gas>]
Access or change the thermodynamic property package:
>>> settings.set_thermo(['Water'], cache=True)
>>> settings.thermo.show()
Thermo(
chemicals=CompiledChemicals([Water]),
mixture=IdealMixture(...
include_excess_energies=False
),
Gamma=DortmundActivityCoefficients,
Phi=IdealFugacityCoefficients,
PCF=MockPoyintingCorrectionFactors
)
Access defined chemicals:
>>> settings.chemicals
CompiledChemicals([Water])
Access defined mixture property algorithm:
>>> settings.mixture.show()
IdealMixture(...
include_excess_energies=False
)
Create stream with default property package:
>>> stream = Stream('stream', Water=2)
>>> stream.thermo is settings.thermo
True
"""
__slots__ = (
'_thermo',
'_flashpkg',
'ID_magic',
)
def __new__(cls):
return settings
[docs]
def set_thermo(self, thermo: tmo.Thermo|Iterable[str|tmo.Chemical],
mixture: Optional[Mixture]=None,
Gamma: Optional[type[ActivityCoefficients]]=None,
Phi: Optional[type[FugacityCoefficients]]=None,
PCF: Optional[type[PoyintingCorrectionFactors]]=None,
cache: Optional[bool]=None,
skip_checks: Optional[bool]=False,
ideal: Optional[bool]=False,
db: Optional[str]='default',
pkg=None,
):
"""
Set the default :class:`~thermosteam.Thermo` object. If `thermo` is
not a :class:`~thermosteam.Thermo` object, an attempt is made to
convert it to one.
Parameters
----------
thermo :
Thermodynamic property package.
Gamma :
Class for computing activity coefficients.
Phi :
Class for computing fugacity coefficients.
PCF :
Class for computing poynting correction factors.
cache :
Whether or not to use cached chemicals.
skip_checks :
Whether to skip checks for missing or invalid properties.
ideal :
Whether to use ideal phase equilibrium and mixture property
algorithms.
db : str, optional
Database to load any chemicals.
pkg : str, optional
Name of property package. Defaults to 'Dortmund-UNIFAC'.
"""
if not isinstance(thermo, (tmo.Thermo, tmo.IdealThermo)):
thermo = tmo.Thermo(thermo, mixture=mixture, cache=cache, skip_checks=skip_checks,
Gamma=Gamma, Phi=Phi, PCF=PCF, db=db, pkg=pkg)
if ideal: thermo = thermo.ideal()
self._thermo = thermo
def get_thermo(self): # For backwards compatibility
return self.thermo
def get_chemicals(self): # For backwards compatibility
return self.chemicals
@property
def thermo(self) -> tmo.Thermo:
"""Default thermodynamic property package."""
try:
return self._thermo
except AttributeError:
raise_no_thermo_error()
@property
def chemicals(self) -> tmo.Chemicals:
"""Default chemicals."""
return self.thermo.chemicals
@property
def mixture(self) -> tmo.Mixture:
"""Default mixture package."""
return self.thermo.mixture
[docs]
def get_default_thermo(self, thermo):
"""
Return the default :class:`~thermosteam.Thermo` object if `thermo` is None.
Otherwise, return the same object. Otherwise, of `thermo` is a
:class:`~thermosteam.Thermo` object, return the same object.
"""
if thermo is None:
thermo = self.thermo
elif not isinstance(thermo, (tmo.Thermo, tmo.IdealThermo)):
raise ValueError("thermo must be a 'Thermo' object")
return thermo
[docs]
def get_default_chemicals(self, chemicals):
"""
Return the default :class:`~thermosteam.Chemicals` object is chemicals
is None. Otherwise, if `chemicals` is a :class:`~thermosteam.Chemicals`
object, return the same object.
"""
if isinstance(chemicals, tmo.Chemicals):
chemicals.compile()
elif chemicals is None:
chemicals = self.chemicals
else:
raise ValueError("chemicals must be a 'Chemicals' object")
return chemicals
[docs]
def get_default_mixture(self, mixture):
"""
Return a default :class:`~thermosteam.Mixture` object.
Otherwise, if `mixture` is a :class:`~thermosteam.Mixture` object,
return the same object.
"""
if mixture is None:
mixture = self.mixture
elif not isinstance(mixture, tmo.Mixture):
raise ValueError("chemicals must be a 'Mixture' object")
return mixture
[docs]
def get_default_flashpkg(self, flashpkg):
"""
Warnings
--------
This method is not yet ready for users.
"""
if not flashpkg: flashpkg = self.get_flashpkg()
return flashpkg
[docs]
def get_flashpkg(self):
"""
Warnings
--------
This method is not yet ready for users.
"""
try:
flashpkg = self._flashpkg
except AttributeError:
self._flashpkg = flashpkg = tmo.equilibrium.FlashPackage()
return flashpkg
[docs]
def get_default_flasher(self, flasher):
"""
Warnings
--------
This method is not yet ready for users.
"""
if not flasher:
flashpkg = self.get_flashpkg()
return flashpkg.flahser()
return flasher
[docs]
def flasher(self, IDs=None, N_liquid=None, N_solid=None):
"""
Warnings
--------
This method is not yet ready for users.
"""
return self.get_flashpkg().flasher(IDs, N_liquid, N_solid)
[docs]
def set_flashpkg(self, flashpkg):
"""
Set the default FlashPackage object.
Parameters
----------
flashpkg : FlashPackage
A FlashPackage object that predefines algorithms for equilibrium
calculations.
Warnings
--------
This method is not yet ready for users.
"""
if isinstance(flashpkg, tmo.equilibrium.FlashPackage):
self._flashpkg = flashpkg
else:
raise ValueError(f"flashpkg must be a FlashPackage object, not a '{type(flashpkg).__name__}'")
#:
settings: ProcessSettings = object.__new__(ProcessSettings)
#: Whether to infer ID of unit operations, streams, and systems by variable assignment.
settings.ID_magic = True