# -*- coding: utf-8 -*-
# BioSTEAM: The Biorefinery Simulation and Techno-Economic Analysis Modules
# Copyright (C) 2020, 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 . import utils
from .exceptions import UndefinedChemical
from ._chemical import Chemical
from .indexer import ChemicalIndexer
import thermosteam as tmo
import numpy as np
__all__ = ('Chemicals', 'CompiledChemicals')
setattr = object.__setattr__
# %% Functions
def must_compile(*args, **kwargs): # pragma: no cover
raise TypeError("method valid only for compiled chemicals; "
"run <Chemicals>.compile() to compile")
def chemical_data_array(chemicals, attr):
getfield = getattr
data = np.asarray([getfield(i, attr) for i in chemicals], dtype=float)
data.setflags(0)
return data
# %% Chemicals
[docs]class Chemicals:
"""
Create a Chemicals object that contains Chemical objects as attributes.
Parameters
----------
chemicals : Iterable[str or Chemical]
Strings should be one of the following [-]:
* Name, in IUPAC form or common form or a synonym registered in PubChem
* InChI name, prefixed by 'InChI=1S/' or 'InChI=1/'
* InChI key, prefixed by 'InChIKey='
* PubChem CID, prefixed by 'PubChem='
* SMILES (prefix with 'SMILES=' to ensure smiles parsing)
* CAS number
cache : bool, optional
Wheather or not to use cached chemicals.
Examples
--------
Create a Chemicals object from chemical identifiers:
>>> from thermosteam import Chemicals
>>> chemicals = Chemicals(['Water', 'Ethanol'], cache=True)
>>> chemicals
Chemicals([Water, Ethanol])
All chemicals are stored as attributes:
>>> chemicals.Water, chemicals.Ethanol
(Chemical('Water'), Chemical('Ethanol'))
Chemicals can also be accessed as items:
>>> chemicals = Chemicals(['Water', 'Ethanol', 'Propane'], cache=True)
>>> chemicals['Ethanol']
Chemical('Ethanol')
>>> chemicals['Propane', 'Water']
[Chemical('Propane'), Chemical('Water')]
A Chemicals object can be extended with more chemicals:
>>> from thermosteam import Chemical
>>> Methanol = Chemical('Methanol')
>>> chemicals.append(Methanol)
>>> chemicals
Chemicals([Water, Ethanol, Propane, Methanol])
>>> new_chemicals = Chemicals(['Hexane', 'Octanol'], cache=True)
>>> chemicals.extend(new_chemicals)
>>> chemicals
Chemicals([Water, Ethanol, Propane, Methanol, Hexane, Octanol])
Chemical objects cannot be repeated:
>>> chemicals.append(chemicals.Water)
Traceback (most recent call last):
ValueError: Water already defined in chemicals
>>> chemicals.extend(chemicals['Ethanol', 'Octanol'])
Traceback (most recent call last):
ValueError: Ethanol already defined in chemicals
A Chemicals object can only contain Chemical objects:
>>> chemicals.append(10)
Traceback (most recent call last):
TypeError: only 'Chemical' objects can be appended, not 'int'
You can check whether a Chemicals object contains a given chemical:
>>> 'Water' in chemicals
True
>>> chemicals.Water in chemicals
True
>>> 'Butane' in chemicals
False
An attempt to access a non-existent chemical raises an UndefinedChemical error:
>>> chemicals['Butane']
Traceback (most recent call last):
UndefinedChemical: 'Butane'
"""
def __new__(cls, chemicals, cache=False):
self = super().__new__(cls)
isa = isinstance
setfield = setattr
for chem in chemicals:
if isa(chem, Chemical):
setfield(self, chem.ID, chem)
else:
setfield(self, chem, Chemical(chem, cache=cache))
return self
def __getnewargs__(self):
return (tuple(self),)
def __setattr__(self, ID, chemical):
raise TypeError("can't set attribute; use <Chemicals>.append instead")
def __setitem__(self, ID, chemical):
raise TypeError("can't set item; use <Chemicals>.append instead")
def __getitem__(self, key):
"""
Return a chemical or a list of chemicals.
Parameters
----------
key : Iterable[str] or str
Chemical identifiers.
"""
dct = self.__dict__
try:
if isinstance(key, str):
return dct[key]
else:
return [dct[i] for i in key]
except KeyError as key_error:
raise UndefinedChemical(key_error.args[0])
[docs] def copy(self):
"""Return a copy."""
copy = object.__new__(Chemicals)
for chem in self: setattr(copy, chem.ID, chem)
return copy
[docs] def append(self, chemical):
"""Append a Chemical."""
if not isinstance(chemical, Chemical):
raise TypeError("only 'Chemical' objects can be appended, "
f"not '{type(chemical).__name__}'")
ID = chemical.ID
if ID in self.__dict__:
raise ValueError(f"{ID} already defined in chemicals")
setattr(self, ID, chemical)
[docs] def extend(self, chemicals):
"""Extend with more Chemical objects."""
if isinstance(chemicals, Chemicals):
self.__dict__.update(chemicals.__dict__)
else:
for chemical in chemicals: self.append(chemical)
[docs] def subgroup(self, IDs):
"""
Create a new subgroup of chemicals.
Parameters
----------
IDs : Iterable[str]
Chemical identifiers.
Examples
--------
>>> chemicals = Chemicals(['Water', 'Ethanol', 'Propane'])
>>> chemicals.subgroup(['Propane', 'Water'])
Chemicals([Propane, Water])
"""
return type(self)([getattr(self, i) for i in IDs])
[docs] def compile(self, skip_checks=False):
"""
Cast as a CompiledChemicals object.
Parameters
----------
skip_checks : bool, optional
Whether to skip checks for missing or invalid properties.
Warning
-------
If checks are skipped, certain features in thermosteam (e.g. phase equilibrium)
cannot be guaranteed to function properly.
Examples
--------
Compile ethanol and water chemicals:
>>> import thermosteam as tmo
>>> chemicals = tmo.Chemicals(['Water', 'Ethanol'])
>>> chemicals.compile()
>>> chemicals
CompiledChemicals([Water, Ethanol])
Attempt to compile chemicals with missing properties:
>>> Substance = tmo.Chemical('Substance', search_db=False)
>>> chemicals = tmo.Chemicals([Substance])
>>> chemicals.compile()
Traceback (most recent call last):
RuntimeError: Substance is missing key thermodynamic properties
(V, S, H, Cn, Psat, Tb and Hvap); use the `<Chemical>.get_missing_properties()`
to check all missing properties
Compile chemicals with missing properties (skipping checks) and note
how certain features do not work:
>>> chemicals.compile(skip_checks=True)
>>> tmo.settings.set_thermo(chemicals)
>>> s = tmo.Stream('s', Substance=10)
>>> s.rho
Traceback (most recent call last):
DomainError: Substance (CAS: Substance) has no valid liquid molar
volume model at T=298.15 K and P=101325 Pa
"""
CompiledChemicals._compile(self, skip_checks)
setattr(self, '__class__', CompiledChemicals)
kwarray = array = index = indices = must_compile
def show(self):
print(self)
_ipython_display_ = show
def __len__(self):
return len(self.__dict__)
def __contains__(self, chemical):
if isinstance(chemical, str):
return chemical in self.__dict__
elif isinstance(chemical, Chemical):
return chemical in self.__dict__.values()
else: # pragma: no cover
return False
def __iter__(self):
yield from self.__dict__.values()
def __repr__(self):
return f"{type(self).__name__}([{', '.join(self.__dict__)}])"
[docs]@utils.read_only(methods=('append', 'extend', '__setitem__'))
class CompiledChemicals(Chemicals):
"""
Create a CompiledChemicals object that contains Chemical objects as attributes.
Parameters
----------
chemicals : Iterable[str or Chemical]
Strings should be one of the following [-]:
* Name, in IUPAC form or common form or a synonym registered in PubChem
* InChI name, prefixed by 'InChI=1S/' or 'InChI=1/'
* InChI key, prefixed by 'InChIKey='
* PubChem CID, prefixed by 'PubChem='
* SMILES (prefix with 'SMILES=' to ensure smiles parsing)
* CAS number
cache : optional
Whether or not to use cached chemicals.
Attributes
----------
tuple : tuple[Chemical]
All compiled chemicals.
size : int
Number of chemicals.
IDs : tuple[str]
IDs of all chemicals.
CASs : tuple[str]
CASs of all chemicals
MW : 1d ndarray
MWs of all chemicals.
Hf : 1d ndarray
Heats of formation of all chemicals.
Hc : 1d ndarray
Heats of combustion of all chemicals.
vle_chemicals : tuple[Chemical]
Chemicals that may have vapor and liquid phases.
lle_chemicals : tuple[Chemical]
Chemicals that may have two liquid phases.
heavy_chemicals : tuple[Chemical]
Chemicals that are only present in liquid or solid phases.
light_chemicals : tuple[Chemical]
IDs of chemicals that are only present in gas phases.
Examples
--------
Create a CompiledChemicals object from chemical identifiers
>>> from thermosteam import CompiledChemicals, Chemical
>>> chemicals = CompiledChemicals(['Water', 'Ethanol'], cache=True)
>>> chemicals
CompiledChemicals([Water, Ethanol])
All chemicals are stored as attributes:
>>> chemicals.Water, chemicals.Ethanol
(Chemical('Water'), Chemical('Ethanol'))
Note that because they are compiled, the append and extend methods do not work:
>>> Propane = Chemical('Propane', cache=True)
>>> chemicals.append(Propane)
Traceback (most recent call last):
TypeError: 'CompiledChemicals' object is read-only
You can check whether a Chemicals object contains a given chemical:
>>> 'Water' in chemicals
True
>>> chemicals.Water in chemicals
True
>>> 'Butane' in chemicals
False
"""
_cache = {}
def __new__(cls, chemicals, cache=None):
isa = isinstance
chemicals = tuple([chem if isa(chem, Chemical) else Chemical(chem, cache)
for chem in chemicals])
cache = cls._cache
if chemicals in cache:
self = cache[chemicals]
else:
self = object.__new__(cls)
setfield = setattr
for chem in chemicals:
setfield(self, chem.ID, chem)
self._compile()
cache[chemicals] = self
return self
def __dir__(self):
return ('append', 'array', 'compile', 'extend',
'get_combustion_reactions', 'get_index',
'get_lle_indices', 'get_synonyms',
'get_vle_indices', 'iarray', 'ikwarray',
'index', 'indices', 'kwarray', 'refresh_constants',
'set_synonym', 'subgroup') + self.IDs
def __reduce__(self):
return CompiledChemicals, (self.tuple,)
[docs] def compile(self):
"""Do nothing, CompiledChemicals objects are already compiled."""
[docs] def refresh_constants(self):
"""
Refresh constant arrays according to their chemical values,
including the molecular weight, heats of formation,
and heats of combustion.
"""
dct = self.__dict__
chemicals = self.tuple
dct['MW'] = chemical_data_array([i.MW for i in chemicals])
dct['Hf'] = chemical_data_array([i.Hf for i in chemicals])
dct['LHV'] = chemical_data_array([i.LHV for i in chemicals])
dct['HHV'] = chemical_data_array([i.HHV for i in chemicals])
[docs] def get_combustion_reactions(self):
"""
Return a ParallelReactions object with all defined combustion reactions.
Examples
--------
>>> chemicals = CompiledChemicals(['H2O', 'Methanol', 'Ethanol', 'CO2', 'O2'], cache=True)
>>> rxns = chemicals.get_combustion_reactions()
>>> rxns.show()
ParallelReaction (by mol):
index stoichiometry reactant X[%]
[0] Methanol + 1.5 O2 -> 2 H2O + CO2 Methanol 100.00
[1] Ethanol + 3 O2 -> 3 H2O + 2 CO2 Ethanol 100.00
"""
reactions = [i.get_combustion_reaction(self) for i in self]
return tmo.reaction.ParallelReaction([i for i in reactions if i is not None])
def _compile(self, skip_checks=False):
dct = self.__dict__
tuple_ = tuple
chemicals = tuple_(dct.values())
free_energies = ('H', 'S', 'H_excess', 'S_excess')
for chemical in chemicals:
if chemical.get_missing_properties(free_energies):
chemical.reset_free_energies()
if skip_checks: continue
key_properties = chemical.get_key_property_names()
missing_properties = chemical.get_missing_properties(key_properties)
if not missing_properties: continue
missing = utils.repr_listed_values(missing_properties)
raise RuntimeError(
f"{chemical} is missing key thermodynamic properties ({missing}); "
"use the `<Chemical>.get_missing_properties()` to check "
"all missing properties")
IDs = tuple_([i.ID for i in chemicals])
CAS = tuple_([i.CAS for i in chemicals])
size = len(IDs)
index = tuple_(range(size))
for i in chemicals: dct[i.CAS] = i
dct['tuple'] = chemicals
dct['size'] = size
dct['IDs'] = IDs
dct['CASs'] = tuple_([i.CAS for i in chemicals])
dct['MW'] = chemical_data_array(chemicals, 'MW')
dct['Hf'] = chemical_data_array(chemicals, 'Hf')
dct['LHV'] = chemical_data_array(chemicals, 'LHV')
dct['HHV'] = chemical_data_array(chemicals, 'HHV')
dct['_index'] = index = dict((*zip(CAS, index),
*zip(IDs, index)))
dct['_index_cache'] = {}
vle_chemicals = []
lle_chemicals = []
heavy_chemicals = []
light_chemicals = []
for i in chemicals:
locked_phase = i.locked_state
if locked_phase:
if locked_phase in ('s', 'l'):
heavy_chemicals.append(i)
if i.Dortmund or i.UNIFAC:
lle_chemicals.append(i)
elif locked_phase == 'g':
light_chemicals.append(i)
else:
raise Exception('chemical locked state has an invalid phase')
else:
vle_chemicals.append(i)
lle_chemicals.append(i)
dct['vle_chemicals'] = tuple_(vle_chemicals)
dct['lle_chemicals'] = tuple_(lle_chemicals)
dct['heavy_chemicals'] = tuple_(heavy_chemicals)
dct['light_chemicals'] = tuple_(light_chemicals)
dct['_has_vle'] = has_vle = np.zeros(size, dtype=bool)
dct['_has_lle'] = has_lle = np.zeros(size, dtype=bool)
dct['_heavy_indices'] = [index[i.ID] for i in heavy_chemicals]
dct['_light_indices'] = [index[i.ID] for i in light_chemicals]
vle_index = [index[i.ID] for i in vle_chemicals]
lle_index = [index[i.ID] for i in lle_chemicals]
has_vle[vle_index] = True
has_lle[lle_index] = True
@property
def formula_array(self):
"""
An array describing the formulas of all chemicals.
Each column is a chemical and each row an element.
Rows are ordered by atomic number.
Examples
--------
>>> chemicals = CompiledChemicals(['Water', 'Ethanol', 'Propane'], cache=True)
>>> chemicals.formula_array
array([[2., 6., 8.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 2., 3.],
[0., 0., 0.],
[1., 1., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
"""
try: return self._formula_array
except: pass
self.__dict__['_formula_array'] = formula_array = np.zeros((118, self.size))
atoms_to_array = tmo.chemicals.elements.atoms_to_array
for i, chemical in enumerate(self):
formula_array[:, i] = atoms_to_array(chemical.atoms)
formula_array.setflags(0)
return formula_array
[docs] def subgroup(self, IDs):
"""
Create a new subgroup of chemicals.
Parameters
----------
IDs : Iterable[str]
Chemical identifiers.
Examples
--------
>>> chemicals = CompiledChemicals(['Water', 'Ethanol', 'Propane'], cache=True)
>>> chemicals.subgroup(['Propane', 'Water'])
CompiledChemicals([Propane, Water])
"""
chemicals = self[IDs]
new = Chemicals(chemicals)
new.compile()
for i in new.IDs:
for j in self.get_synonyms(i):
try: new.set_synonym(i, j)
except: pass
return new
[docs] def get_synonyms(self, ID):
"""
Get all synonyms of a chemical.
Parameters
----------
ID : str
Chemical identifier.
Examples
--------
Get all synonyms of water:
>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water'], cache=True)
>>> chemicals.get_synonyms('Water')
['7732-18-5', 'Water']
"""
k = self._index[ID]
return [i for i, j in self._index.items() if j==k]
[docs] def set_synonym(self, ID, synonym):
"""
Set a new synonym for a chemical.
Parameters
----------
ID : str
Chemical identifier.
synonym : str
New identifier for chemical.
Examples
--------
Set new synonym for water:
>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Ethanol'], cache=True)
>>> chemicals.set_synonym('Water', 'H2O')
>>> chemicals.H2O is chemicals.Water
True
Note that you cannot use one synonym for two chemicals:
>>> chemicals.set_synonym('Ethanol', 'H2O')
Traceback (most recent call last):
ValueError: synonym 'H2O' already in use by Chemical('Water')
"""
chemical = getattr(self, ID)
dct = self.__dict__
if synonym in dct and dct[synonym] is not chemical:
raise ValueError(f"synonym '{synonym}' already in use by {repr(dct[synonym])}")
else:
self._index[synonym] = self._index[ID]
dct[synonym] = chemical
[docs] def zeros(self):
"""
Return an array of zeros with entries that correspond to the orded chemical IDs.
Examples
--------
>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Ethanol'], cache=True)
>>> chemicals.zeros()
array([0., 0.])
"""
return np.zeros(self.size)
[docs] def ones(self):
"""
Return an array of ones with entries that correspond to the orded chemical IDs.
Examples
--------
>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Ethanol'], cache=True)
>>> chemicals.ones()
array([1., 1.])
"""
return np.ones(self.size)
[docs] def kwarray(self, ID_data):
"""
Return an array with entries that correspond to the orded chemical IDs.
Parameters
----------
ID_data : dict
ID-data pairs.
Examples
--------
>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Ethanol'], cache=True)
>>> chemicals.kwarray(dict(Water=2))
array([2., 0.])
"""
return self.array(*zip(*ID_data.items()))
[docs] def array(self, IDs, data):
"""
Return an array with entries that correspond to the ordered chemical IDs.
Parameters
----------
IDs : iterable
Compound IDs.
data : array_like
Data corresponding to IDs.
Examples
--------
>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Ethanol'], cache=True)
>>> chemicals.array(['Water'], [2])
array([2., 0.])
"""
array = self.zeros()
array[self.get_index(tuple(IDs))] = data
return array
[docs] def iarray(self, IDs, data):
"""
Return a chemical indexer.
Parameters
----------
IDs : iterable
Chemical IDs.
data : array_like
Data corresponding to IDs.
Examples
--------
Create a chemical indexer from chemical IDs and data:
>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Methanol', 'Ethanol'], cache=True)
>>> chemical_indexer = chemicals.iarray(['Water', 'Ethanol'], [2., 1.])
>>> chemical_indexer.show()
ChemicalIndexer:
Water 2
Ethanol 1
Note that indexers allow for computationally efficient indexing using identifiers:
>>> chemical_indexer['Ethanol', 'Water']
array([1., 2.])
>>> chemical_indexer['Ethanol']
1.0
"""
array = self.array(IDs, data)
return ChemicalIndexer.from_data(array, chemicals=self)
[docs] def ikwarray(self, ID_data):
"""
Return a chemical indexer.
Parameters
----------
ID_data : Dict[str: float]
Chemical ID-value pairs.
Examples
--------
Create a chemical indexer from chemical IDs and data:
>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Methanol', 'Ethanol'], cache=True)
>>> chemical_indexer = chemicals.ikwarray(dict(Water=2., Ethanol=1.))
>>> chemical_indexer.show()
ChemicalIndexer:
Water 2
Ethanol 1
Note that indexers allow for computationally efficient indexing using identifiers:
>>> chemical_indexer['Ethanol', 'Water']
array([1., 2.])
>>> chemical_indexer['Ethanol']
1.0
"""
array = self.kwarray(ID_data)
return ChemicalIndexer.from_data(array, chemicals=self)
[docs] def isplit(self, split, order=None):
"""
Create a chemical indexer that represents chemical splits.
Parameters
----------
split : Should be one of the following
* [float] Split fraction
* [array_like] Componentwise split
* [dict] ID-split pairs
order=None : Iterable[str], options
Chemical order of split. Defaults to biosteam.settings.chemicals.IDs
Examples
--------
From a dictionary:
>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Methanol', 'Ethanol'], cache=True)
>>> chemical_indexer = chemicals.isplit(dict(Water=0.5, Ethanol=1.))
>>> chemical_indexer.show()
ChemicalIndexer:
Water 0.5
Ethanol 1
From iterable given the order:
>>> chemical_indexer = chemicals.isplit([0.5, 1], ['Water', 'Ethanol'])
>>> chemical_indexer.show()
ChemicalIndexer:
Water 0.5
Ethanol 1
From a fraction:
>>> chemical_indexer = chemicals.isplit(0.75)
>>> chemical_indexer.show()
ChemicalIndexer:
Water 0.75
Methanol 0.75
Ethanol 0.75
From an iterable (assuming same order as the Chemicals object):
>>> chemical_indexer = chemicals.isplit([0.5, 0, 1])
>>> chemical_indexer.show()
ChemicalIndexer:
Water 0.5
Ethanol 1
"""
if isinstance(split, dict):
assert not order, "cannot pass 'order' key word argument when split is a dictionary"
order, split = zip(*split.items())
if order:
isplit = self.iarray(order, split)
elif hasattr(split, '__len__'):
isplit = ChemicalIndexer.from_data(np.asarray(split),
phase=None,
chemicals=self)
else:
split = split * np.ones(self.size)
isplit = ChemicalIndexer.from_data(split,
phase=None,
chemicals=self)
return isplit
[docs] def index(self, ID):
"""
Return index of specified chemical.
Parameters
----------
ID: str
Chemical identifier.
Examples
--------
Index by ID:
>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Ethanol'])
>>> chemicals.index('Water')
0
Indices by CAS number:
>>> chemicals.index('7732-18-5')
0
"""
try: return self._index[ID]
except KeyError:
raise UndefinedChemical(ID)
[docs] def indices(self, IDs):
"""
Return indices of specified chemicals.
Parameters
----------
IDs : iterable
Chemical indentifiers.
Examples
--------
Indices by ID:
>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Ethanol'])
>>> chemicals.indices(['Water', 'Ethanol'])
[0, 1]
Indices by CAS number:
>>> chemicals.indices(['7732-18-5', '64-17-5'])
[0, 1]
"""
try:
dct = self._index
return [dct[i] for i in IDs]
except KeyError as key_error:
raise UndefinedChemical(key_error.args[0])
[docs] def get_index(self, IDs):
"""
Return index/indices of specified chemicals.
Parameters
----------
IDs : iterable[str] or str
Chemical identifiers.
Notes
-----
CAS numbers are also supported.
Examples
--------
Get multiple indices with a tuple of IDs:
>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Ethanol'], cache=True)
>>> IDs = ('Water', 'Ethanol')
>>> chemicals.get_index(IDs)
[0, 1]
Get a single index with a string:
>>> chemicals.get_index('Ethanol')
1
An Ellipsis returns a slice:
>>> chemicals.get_index(...)
slice(None, None, None)
Anything else returns an error:
>>> chemicals.get_index(['Water', 'Ethanol'])
Traceback (most recent call last):
TypeError: only strings, tuples, and ellipsis are valid index keys
"""
cache = self._index_cache
try:
index = cache[IDs]
except KeyError:
cache[IDs] = index = self._get_index(IDs)
utils.trim_cache(cache)
except TypeError:
raise TypeError("only strings, tuples, and ellipsis are valid index keys")
return index
def _get_index(self, IDs):
if isinstance(IDs, str):
return self.index(IDs)
elif isinstance(IDs, tuple):
return self.indices(IDs)
elif IDs is ...:
return slice(None)
else: # pragma: no cover
raise TypeError("only strings, tuples, and ellipsis are valid index keys")
def __len__(self):
return self.size
def __contains__(self, chemical):
if isinstance(chemical, str):
return chemical in self.__dict__
elif isinstance(chemical, Chemical):
return chemical in self.tuple
else: # pragma: no cover
return False
def __iter__(self):
return iter(self.tuple)
[docs] def get_vle_indices(self, nonzeros):
"""
Return indices of species in vapor-liquid equilibrium given an array
dictating whether or not the chemicals are present.
Examples
--------
>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Methanol', 'Ethanol'])
>>> data = chemicals.kwarray(dict(Water=2., Ethanol=1.))
>>> chemicals.get_vle_indices(data!=0)
[0, 2]
"""
return [i for i, j in enumerate(self._has_vle & nonzeros) if j]
[docs] def get_lle_indices(self, nonzeros):
"""
Return indices of species in liquid-liquid equilibrium given an array
dictating whether or not the chemicals are present.
Examples
--------
>>> from thermosteam import CompiledChemicals
>>> chemicals = CompiledChemicals(['Water', 'Methanol', 'Ethanol'])
>>> data = chemicals.kwarray(dict(Water=2., Ethanol=1.))
>>> chemicals.get_lle_indices(data!=0)
[0, 2]
"""
return [i for i, j in enumerate(self._has_lle & nonzeros) if j]
def __repr__(self):
return f"{type(self).__name__}([{', '.join(self.IDs)}])"