Source code for thermosteam._chemicals

# -*- 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)}])"