Skip to content

Commit

Permalink
added SanStream, more flexible SanUnit initialization and stream mixing
Browse files Browse the repository at this point in the history
  • Loading branch information
yalinli2 committed Mar 22, 2021
1 parent 68f21e2 commit 4acc8d7
Show file tree
Hide file tree
Showing 19 changed files with 428 additions and 412 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ Change Log
This document records notable changes to `~ <https://github.com/QSD-Group/QSDsan>`_. We aim to follow `Semantic Versioning <https://semver.org/>`_.


Ongoing
-------
- Updated :class:`SanUnit` so that it can be initialized with any of :class:`thermosteam.Stream`, :class:`~.SanStream`, or :class:`~.WasteStream`.

- These three classes can now be mixed.

- Added :class:`~.SanStream` for non-waste streams (e.g., gases).
- Split the ``systems`` module into an individual package `EXPOsan <https://github.com/QSD-Group/exposan>`_.


`0.2.0`_ (2021-03-17)
---------------------
- Added :class:`~.Process`, :class:`~.Processes`, and :class:`~.CompiledProcesses` classes for stoichiometric process and its kinetics.
Expand Down
13 changes: 13 additions & 0 deletions docs/source/Component.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,17 @@ Component
=========

.. autoclass:: qsdsan.Component
:members:

Components
----------

.. autoclass:: qsdsan.Components
:members:


CompiledComponents
------------------

.. autoclass:: qsdsan.CompiledComponents
:members:
12 changes: 0 additions & 12 deletions docs/source/Components.rst

This file was deleted.

26 changes: 26 additions & 0 deletions docs/source/Streams.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
SanStream and WasteStream
=========================

SanStream
---------

.. autoclass:: qsdsan.SanStream
:members:

MissingSanStream
----------------

.. autoclass:: qsdsan.MissingSanStream
:members:

WasteStream
-----------

.. autoclass:: qsdsan.WasteStream
:members:

MissingWasteStream
------------------

.. autoclass:: qsdsan.MissingWasteStream
:members:
5 changes: 0 additions & 5 deletions docs/source/WasteStream.rst

This file was deleted.

5 changes: 2 additions & 3 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ The above Unified Modeling Language (UML) diagram of the package shows the relat
In particular, ``QSDsan`` introduces:

- :class:`~.Component`, a subclass of :class:`thermosteam.Chemical`, instance of this class does not necessarily corresponds to a specific chemical, but represents commonly used/modeled component such as biodegradable colloidal substrate.
- :class:`~.WasteStream`, a sublcass of :class:`thermosteam.Stream`, instance of this class has additional composite properties such as chemical oxygen demand (COD) that are widely used in sanitation systems.
- :class:`~.SanStream` and :class:`~.WasteStream`. :class:`~.SanStream` is a sublcass of :class:`thermosteam.Stream` that has an additional attribute ``impact_item`` for life cycle assessment. :class:`~.WasteStream` is a subclass of :class:`~.SanStream` with additional composite properties such as chemical oxygen demand (COD) that are widely used in sanitation systems.
- :class:`~.Process`, a new class that describes a certain biological, chemical, or physical process in a unit operation, it is similar in concept with :class:`thermosteam.Reaction`, but has unique features and utilities.


Expand All @@ -55,17 +55,16 @@ In particular, ``QSDsan`` introduces:
:caption: API

Component
Components
Construction
Equipment
ImpactIndicator
ImpactItem
LCA
Process
Streams
SanUnit
SimpleTEA
Transportation
WasteStream
sanunits/sanunits
stats

Expand Down
4 changes: 3 additions & 1 deletion qsdsan/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
del bst
currency = 'USD'

# from .utils import loading, checkers, descriptors
from . import utils
from ._component import *
from ._components import *
from ._sanstream import *
from ._waste_stream import *
from ._process import *
from ._impact_indicator import *
Expand All @@ -46,6 +46,7 @@
_units_of_measure, # if not included here, then need to add to setup.py
_component,
_components,
_sanstream,
_waste_stream,
_impact_indicator,
_impact_item,
Expand All @@ -66,6 +67,7 @@
__all__ = (
*_component.__all__,
*_components.__all__,
*_sanstream.__all__,
*_waste_stream.__all__,
*_process.__all__,
*_impact_indicator.__all__,
Expand Down
6 changes: 3 additions & 3 deletions qsdsan/_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,9 +564,9 @@ def from_chemical(cls, ID, chemical=None, phase=None, measured_as=None,
new.f_uBOD_COD = f_uBOD_COD
new.f_Vmass_Totmass = f_Vmass_Totmass
new.description = description
new.particle_size = particle_size
new.degradability = degradability
new.organic = organic
new._particle_size = particle_size
new._degradability = degradability
new._organic = organic
new.i_COD = i_COD
new.i_NOD = i_NOD
for i,j in data.items():
Expand Down
125 changes: 125 additions & 0 deletions qsdsan/_sanstream.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

'''
QSDsan: Quantitative Sustainable Design for sanitation and resource recovery systems
This module is developed by:
Yalin Li <zoe.yalin.li@gmail.com>
This module is under the University of Illinois/NCSA Open Source License.
Please refer to https://github.com/QSD-Group/QSDsan/blob/master/LICENSE.txt
for license details.
'''

# %%

from thermosteam import Stream
from biosteam.utils import MissingStream

__all__ = ('SanStream', 'MissingSanStream')

setattr = object.__setattr__

class SanStream(Stream):
'''
A subclass of :class:`thermosteam.Stream` with additional attributes
for environmental impacts.
See Also
--------
`thermosteam.Stream <https://thermosteam.readthedocs.io/en/latest/Stream.html>`_
'''

__slots__ = (*Stream.__slots__, '_impact_item')
ticket_name = 'ss'

def __init__(self, ID='', flow=(), phase='l', T=298.15, P=101325.,
units='kg/hr', price=0., thermo=None, impact_item=None,
**chemical_flows):

super().__init__(ID=ID, flow=flow, phase=phase, T=T, P=P,
units=units, price=price, thermo=thermo,
**chemical_flows)

if impact_item:
impact_item._linked_stream = self
self._impact_item = impact_item

def copy(self, ID=None):
new = super().copy()
if self.impact_item:
self.impact_item.copy(stream=new)
return new

__copy__ = copy

@staticmethod
def from_stream(cls, stream, **kwargs):
'''
Cast a :class:`thermosteam.Stream` or :class:`biosteam.utils.MissingStream`
to the designated equivalent.
'''
new = stream

if not isinstance(stream, cls):
stream.registry.untrack((stream,))
ID = '' if (stream.ID[0] == 's' and stream.ID[1:].isnumeric()) else stream.ID
new = cls.__new__(cls, **kwargs)
new.ID = ID
new._link = None
new._sink = stream._sink
new._source = stream._source
new._thermo = stream._thermo
new._imol = stream._imol.copy()
new._thermal_condition = stream._thermal_condition.copy()
new.reset_cache()
new.price = 0
new.impact_item = None

stream._link = stream._sink = stream._source = None

elif isinstance(stream, MissingStream):
setattr(stream, '__class__', MissingSanStream)
return stream

return new

@property
def impact_item(self):
'''[StreamImpactItem] The :class:`StreamImpactItem` this waste stream is linked to.'''
return self._impact_item
@impact_item.setter
def impact_item(self, i):
self._impact_item = i
if i:
i.linked_stream = self

@property
def components(self):
return self.chemicals


# %%

class MissingSanStream(MissingStream):
'''
A subclass of :class:`biosteam.MissingStream`, create a special object
that acts as a dummy until replaced by an actual :class:`SanStream`.
'''

@property
def impact_item(self):
return None

def __repr__(self):
return '<MissingSanStream>'

def __str__(self):
return 'missing sanstream'





70 changes: 54 additions & 16 deletions qsdsan/_sanunit.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@
for license details.
'''

'''
TODO:
Add the _impact method, then unhide the _summary method
'''



# %%

import biosteam as bst
from . import currency, Construction, Transportation
from .utils.piping import WSIns, WSOuts
from thermosteam import Stream
from . import currency, SanStream, WasteStream, Construction, Transportation

NotImplementedMethod = bst.utils.NotImplementedMethod
format_title = bst.utils.misc.format_title
Expand All @@ -30,8 +36,8 @@
class SanUnit(bst.Unit, isabstract=True):

'''
Subclass of :class:`biosteam.Unit`, is initialized with :class:`WasteStream`
rather than :class:`biosteam.Stream`.
Subclass of :class:`biosteam.Unit`, is initialized with :class:`~.SanStream`
rather than :class:`thermosteam.Stream`.
Parameters
----------
Expand All @@ -56,19 +62,20 @@ class SanUnit(bst.Unit, isabstract=True):
See Also
--------
`biosteam.Unit <https://biosteam.readthedocs.io/en/latest/Unit.html>`_
`thermosteam.Stream <https://thermosteam.readthedocs.io/en/latest/Stream.html>`_
'''

_stacklevel = 7
ticket_name = 'SU'
# _stacklevel =
# ticket_name = 'SU'

def __init__(self, ID='', ins=None, outs=(), thermo=None,
def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream',
equipments=(), **kwargs):
self._register(ID)
self._specification = None
self._load_thermo(thermo)
self._init_ins(ins)
self._init_outs(outs)
self._init_ins(ins, init_with)
self._init_outs(outs, init_with)
self._init_utils()
self._init_results()
self._assert_compatible_property_package()
Expand All @@ -80,13 +87,45 @@ def __init__(self, ID='', ins=None, outs=(), thermo=None,
for attr, val in kwargs.items():
setattr(self, attr, val)

def _init_ins(self, ins):
self._ins = WSIns(self, self._N_ins, ins, self._thermo,
self._ins_size_is_fixed, self._stacklevel)
@staticmethod
def _from_stream(streams, init_with):
if not streams:
return []

kind = init_with.lower()
if kind == 'stream':
return [streams]
elif not kind in ('sanstream', 'wastestream'):
raise ValueError('init_with can only be "Stream", "SanStream", or "WasteStream", ' \
f'not "{init_with}".')

if kind == 'sanstream':
if isinstance(streams, Stream):
SanStream.from_stream(SanStream, streams)
return [streams]
converted = []
for s in streams:
converted.append(SanStream.from_stream(SanStream, s))

else:
if isinstance(streams, Stream):
WasteStream.from_stream(WasteStream. streams)
return [streams]
converted = []
for s in streams:
converted.append(WasteStream.from_stream(WasteStream, s))

return converted


def _init_ins(self, ins, init_with):
super()._init_ins(ins)
self._ins = self._from_stream(self.ins, init_with)


def _init_outs(self, outs):
self._outs = WSOuts(self, self._N_outs, outs, self._thermo,
self._outs_size_is_fixed, self._stacklevel)
def _init_outs(self, outs, init_with):
super()._init_outs(outs)
self._outs = self._from_stream(self.outs, init_with)

def _init_results(self):
super()._init_results()
Expand All @@ -113,7 +152,6 @@ def _info(self, T, P, flow, composition, N, IDs, _stream_info):
i += 1
continue
if _stream_info:
# breakpoint()
stream_info = stream._info(T, P, flow, composition, N, IDs) + \
'\n' + stream._wastestream_info()
else:
Expand Down

0 comments on commit 4acc8d7

Please sign in to comment.