Skip to content

Commit

Permalink
reduce changing the number of phases
Browse files Browse the repository at this point in the history
  • Loading branch information
cortespea committed Dec 25, 2023
1 parent 06f697e commit 5d60769
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 29 deletions.
15 changes: 15 additions & 0 deletions tests/test_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,20 @@ def test_mixing_balance():
ms['l'].mix_from(streams)
assert ms['l'].imol['Water'] == total_flow

def test_mixing_phases():
tmo.settings.set_thermo(['Water'], cache=True)
ms = tmo.MultiStream(None, l=[('Water', 1)], g=[('Water', 2)])
stream = tmo.Stream(Water=1, phase='L')
ms.mix_from([ms, stream], conserve_phases=True)
assert ms.phases == ('L', 'g', 'l') # New phase is created
assert ms.imol['L', 'Water'] == ms.imol['l', 'Water'] == 1

ms = tmo.MultiStream(None, l=[('Water', 1)], g=[('Water', 2)])
stream = tmo.Stream(Water=1, phase='L')
ms.mix_from([ms, stream])
assert ms.phases == ('g', 'l') # 'L' phase gets added to 'l' phase
assert ms.imol['l', 'Water'] == 2

def test_mixing_pressure():
P = 111458
s1 = tmo.Stream(None, Water=1000, P=P)
Expand Down Expand Up @@ -431,4 +445,5 @@ def test_vle_critical_pure_component():
test_vle_critical_pure_component()
test_critical()
test_mixture()
test_mixing_phases()
test_mixing_pressure()
42 changes: 25 additions & 17 deletions thermosteam/_phase.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class PhaseIndexer:
True
"""
__slots__ = ('_index',)
__slots__ = ('_index', '_phases', '_compatibility')
_index_cache = {}

def __new__(cls, phases):
Expand All @@ -102,32 +102,40 @@ def __new__(cls, phases):
self = cache[phases]
else:
cache[phases] = self = new(cls)
self._index = index = {j:i for i,j in enumerate(sorted(phases))}
index[...] = slice(None)
self._phases = phases = tuple(sorted(phases))
self._index = index = {j: i for i,j in enumerate(phases)}
for phase, n in tuple(index.items()):
if phase.isupper():
phase = phase.lower()
else:
phase = phase.upper()
if phase not in index: index[phase] = n
self._compatibility = ''.join([i.lower() for i in phases])
index[...] = slice(None)
return self

@property
def phases(self):
return self._phases

def __contains__(self, phase):
return phase in self._index

def compatible_with(self, other):
return self._compatibility == other._compatibility

def __call__(self, phase):
try:
return self._index[phase]
except:
if phase.isupper():
phase = phase.lower()
else:
phase = phase.upper()
try:
return self._index[phase]
except:
raise UndefinedPhase(phase)

@property
def phases(self):
return tuple(self._index)[:-1]
raise UndefinedPhase(phase)

def __reduce__(self):
return PhaseIndexer, (self.phases,)
return PhaseIndexer, (self._phases,)

def __repr__(self):
return f"{type(self).__name__}({list(self.phases)})"
return f"{type(self).__name__}({[*self._phases]})"


class Phase:
__slots__ = ('_phase',)
Expand Down
8 changes: 5 additions & 3 deletions thermosteam/_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -1445,9 +1445,9 @@ def mix_from(self, others, energy_balance=True, vle=False, Q=0., conserve_phases
self.vle(T=self.T, P=P)
self.reduce_phases()
else:
if energy_balance: H = sum([i.H for i in streams], Q)
self._imol.mix_from([i._imol for i in streams])
if energy_balance and not self.isempty():
if energy_balance:
self._imol.mix_from([i._imol for i in streams])
H = sum([i.H for i in streams], Q)
if conserve_phases:
self.H = H
else:
Expand All @@ -1457,6 +1457,8 @@ def mix_from(self, others, energy_balance=True, vle=False, Q=0., conserve_phases
self.phases = self.phase + ''.join([i.phase for i in others])
self._imol.mix_from([i._imol for i in streams])
self.H = H
else:
self._imol.mix_from([i._imol for i in streams])

def split_to(self, s1, s2, split, energy_balance=True):
"""
Expand Down
47 changes: 38 additions & 9 deletions thermosteam/indexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -774,29 +774,46 @@ def sum_across_phases(self):

def copy_like(self, other):
if self is other: return
phase_indexer = self._phase_indexer
if isinstance(other, ChemicalIndexer):
self.empty()
other_data = other.data
self._expand_phases(other.phase)
phase_index = self.get_phase_index(other.phase)
phase = other.phase
if phase not in phase_indexer: self._expand_phases(phase)
phase_index = phase_indexer(phase)
if self.chemicals is other.chemicals:
self.data.rows[phase_index].copy_like(other_data)
else:
other_data = other.data
left_index, right_index = index_overlap(self._chemicals, other._chemicals, [*other_data.nonzero_keys()])
self.data.rows[phase_index][left_index] = other_data[right_index]
else:
other_phase_indexer = other._phase_indexer
if self.chemicals is other.chemicals:
self._expand_phases(other.phases)
self.data.copy_like(other.data)
if phase_indexer is other_phase_indexer:
self.data.copy_like(other.data)
elif phase_indexer.compatible_with(other_phase_indexer):
self.empty()
data = self.data
for i, j in other: data[phase_indexer(i)] = j
else:
self._expand_phases(other._phases)
self.data.copy_like(other.data)
else:
self.empty()
self._expand_phases(other.phases)
other_data = other.data
data = self.data
left_index, other_data = index_overlap(self._chemicals, other._chemicals, [*other_data.nonzero_keys()])
self.data[:, left_index] = other_data[:, right_index]
if phase_indexer is other_phase_indexer:
data[:, left_index] = other_data[:, right_index]
elif phase_indexer.compatible_with(other_phase_indexer):
for i, j in other: data[phase_indexer(i)] += j
else:
self._expand_phases(other._phases)
data[:, left_index] = other_data[:, right_index]


def _expand_phases(self, other_phases):
def _expand_phases(self, other_phases=None):
phases = self._phases
other_phases = set(other_phases)
new_phases = other_phases.difference(phases)
Expand All @@ -822,10 +839,22 @@ def mix_from(self, others):
else: raise ValueError("can only mix from chemical or material indexers")
other_phases = [i.phase for i in chemical_indexers]
for i in material_indexers: other_phases.extend(i._phases)
self._expand_phases(other_phases)
other_phases = set(other_phases)
phase_indexer = self._phase_indexer
new_phases = [i for i in other_phases if i not in phase_indexer]
phases = self._phases
if new_phases: self._expand_phases(other_phases)
scp_data = {i: [] for i in phases} # Same chemicals by phase
dcp_data = {i: [] for i in phases} # Different chemicals by phase
for i in other_phases.difference(phases):
if i.isupper():
ilow = i.lower()
scp_data[i] = scp_data[ilow]
dcp_data[i] = dcp_data[ilow]
else:
iup = i.upper()
scp_data[i] = scp_data[iup]
dcp_data[i] = dcp_data[iup]
for i in material_indexers:
ichemicals = i._chemicals
idata = i.data
Expand All @@ -835,7 +864,7 @@ def mix_from(self, others):
else:
left_index, right_index = index_overlap(chemicals, ichemicals, idata.nonzero_keys())
for i, j in zip(i._phases, i.data.rows):
dcp_data[i].append((idata, left_index, right_index))
dcp_data[i].append((j, left_index, right_index))
for i in chemical_indexers:
ichemicals = i._chemicals
idata = i.data
Expand Down

0 comments on commit 5d60769

Please sign in to comment.