diff --git a/.gitignore b/.gitignore index 3fcc0393..1f66efca 100644 --- a/.gitignore +++ b/.gitignore @@ -17,8 +17,6 @@ _build prof *Pyreverse* monkeytype.sqlite3 - -# Spyder *.spyproject* # Docs # diff --git a/docs/source/tutorials/3_WasteStream.ipynb b/docs/source/tutorials/3_WasteStream.ipynb index c1228f01..11ff9a96 100644 --- a/docs/source/tutorials/3_WasteStream.ipynb +++ b/docs/source/tutorials/3_WasteStream.ipynb @@ -28,7 +28,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "This tutorial is made with qsdsan v0.3.2.\n" + "This tutorial is made with qsdsan v0.3.5.\n" ] } ], @@ -260,7 +260,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -270,7 +270,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -368,7 +368,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -409,7 +409,7 @@ " ])" ] }, - "execution_count": 11, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -424,7 +424,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -451,7 +451,7 @@ " 1.200e-02, 5.448e+01])" ] }, - "execution_count": 12, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -465,7 +465,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -508,7 +508,7 @@ " ])" ] }, - "execution_count": 13, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -521,7 +521,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "metadata": {}, "outputs": [ { @@ -539,7 +539,7 @@ " 1.200e+01, 9.815e+05])" ] }, - "execution_count": 14, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -551,7 +551,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -572,7 +572,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -634,7 +634,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "metadata": {}, "outputs": [ { @@ -666,7 +666,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "metadata": {}, "outputs": [ { @@ -731,7 +731,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ @@ -742,7 +742,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "metadata": {}, "outputs": [ { @@ -759,7 +759,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, "metadata": {}, "outputs": [], "source": [ @@ -772,7 +772,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, "metadata": {}, "outputs": [ { @@ -798,7 +798,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 24, "metadata": {}, "outputs": [ { @@ -831,7 +831,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 25, "metadata": {}, "outputs": [ { @@ -840,7 +840,7 @@ "251.9652894584042" ] }, - "execution_count": 24, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } @@ -853,7 +853,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 26, "metadata": {}, "outputs": [], "source": [ @@ -866,7 +866,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 27, "metadata": {}, "outputs": [ { @@ -889,7 +889,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 28, "metadata": {}, "outputs": [ { @@ -916,7 +916,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 29, "metadata": {}, "outputs": [], "source": [ @@ -925,7 +925,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 30, "metadata": {}, "outputs": [ { @@ -934,7 +934,7 @@ "77.01571037585582" ] }, - "execution_count": 29, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -947,7 +947,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 31, "metadata": {}, "outputs": [ { @@ -956,7 +956,7 @@ "0.0" ] }, - "execution_count": 30, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } @@ -968,7 +968,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 32, "metadata": {}, "outputs": [ { @@ -977,7 +977,7 @@ "0.0" ] }, - "execution_count": 31, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -989,7 +989,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 33, "metadata": {}, "outputs": [ { @@ -998,7 +998,7 @@ "80.62889262668934" ] }, - "execution_count": 32, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" } @@ -1010,7 +1010,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 34, "metadata": {}, "outputs": [ { @@ -1019,7 +1019,7 @@ "485.2265687216927" ] }, - "execution_count": 33, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } @@ -1031,7 +1031,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 35, "metadata": {}, "outputs": [ { @@ -1040,7 +1040,7 @@ "13.99807163657801" ] }, - "execution_count": 34, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } @@ -1074,7 +1074,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.2" + "version": "3.8.11" } }, "nbformat": 4, diff --git a/qsdsan/_sanunit.py b/qsdsan/_sanunit.py index 2b63aad3..b8e1d684 100644 --- a/qsdsan/_sanunit.py +++ b/qsdsan/_sanunit.py @@ -372,8 +372,17 @@ def results(self, with_units=True, include_utilities=True, results.loc[('Additional OPEX', ''), :] = ('USD/hr', 0) else: for k, v in self.add_OPEX.items(): - try: results.loc[(k, ''), :] = ('USD/hr', v) - except: breakpoint() + if not with_units: + results.loc[(k, '')] = v + else: + try: results.loc[(k, ''), :] = ('USD/hr', v) + # When `results` is a series instead of dataframe, + # might not need this + except ValueError: + results = results.to_frame(name=self.ID) + results.insert(0, 'Units', '') + results.loc[(k, ''), :] = ('USD/hr', v) + results.columns.name = type(self).__name__ if with_units: results.replace({'USD': f'{currency}', 'USD/hr': f'{currency}/hr'}, inplace=True) diff --git a/qsdsan/_waste_stream.py b/qsdsan/_waste_stream.py index a72d9651..ea979d16 100644 --- a/qsdsan/_waste_stream.py +++ b/qsdsan/_waste_stream.py @@ -26,7 +26,8 @@ # %% import numpy as np -from thermosteam import settings +from free_properties import PropertyFactory, property_array +from thermosteam import settings, indexer from . import Components, Stream, MultiStream, SanStream, MissingSanStream, \ set_thermo from .utils import auom, copy_attr @@ -119,6 +120,69 @@ def _calib_XBsub_fBODCOD(components, concentrations, substrate_IDs, BOD): return fbodtocod_sub +# Indexer for nicer display +@property +def group_conc_compositions(self): + raise AttributeError('cannot set group by concentratino') + +ComponentConcentrationIndexer, ConcentrationIndexer = \ + indexer._new_Indexer('Concentration', 'mg/L', group_conc_compositions) + +ChemicalMolarFlowIndexer = indexer.ChemicalMolarFlowIndexer + +@PropertyFactory(slots=('name', 'mol', 'index', 'F_vol', 'MW', + 'phase', 'phase_container')) +def ConcentrationProperty(self): + '''Concentration flow, in mg/L (g/m3).''' + f_mass = self.mol[self.index] * self.MW + phase = self.phase or self.phase_container.phase + if phase != 'l': + raise AttributeError('Concentration only valid for liquid phase.') + V_sum = self.F_vol + if V_sum==0: + raise RuntimeError('WasteStream is empty, concentration cannot be calculated.') + return 1000. * f_mass / V_sum if f_mass else 0. +@ConcentrationProperty.setter +def ConcentrationProperty(self, value): + raise AttributeError('Cannot set flow rate by concentration.') + +def by_conc(self, TP): + ''' + Return a ComponentConcentrationIndexer that references this object's + molar and volume data (volume relies on molar). + + Parameters + ---------- + TP : ThermalCondition + + ''' + try: + conc = self._data_cache[TP] + except: + cmps = self.chemicals + mol = self.data + F_vol = self.by_volume(TP).data.sum() + conc = np.zeros_like(mol, dtype=object) + for i, cmp in enumerate(cmps): + conc[i] = ConcentrationProperty(cmp.ID, mol, i, F_vol, cmp.MW, + None, self._phase) + self._data_cache[TP] = \ + conc = ComponentConcentrationIndexer.from_data(property_array(conc), + self._phase, cmps, + False) + return conc +indexer.ChemicalMolarFlowIndexer.by_conc = by_conc + +def by_conc(self, TP): + ''' + Raise an error for attempt multi-phase usage + as concentration only valid for liquid). + ''' + raise AttributeError('Concentration only valid for liquid phase.') + +indexer.MolarFlowIndexer.by_conc = by_conc; del by_conc +del PropertyFactory + # %% @@ -627,10 +691,24 @@ def dry_mass(self): # def charge(self): # return self._liq_sol_properties('charge', self.composite('charge')) + + + @property + def iconc(self): + '''[Indexer] Mass concentrations, in mg/L (g/m3).''' + return self._imol.by_conc(self._thermal_condition) + @property - def Conc(self): - '''Mass concentrations, in g/m3.''' - return self.get_mass_concentration() + def conc(self): + '''[property_array] Mass concentrations, in mg/L (g/m3).''' + return self.iconc.data + # mass = self.mass + # try: mass[:] = self.get_mass_concentration() + # except: breakpoint() + # printed = mass.__repr__() + # printed = printed.replace('kg/hr', 'mg/L') + # print(printed) + # return printed def copy(self, new_ID='', ws_properties=True): diff --git a/qsdsan/sanunits/_uddt.py b/qsdsan/sanunits/_uddt.py index f6d7a308..24ae7d51 100644 --- a/qsdsan/sanunits/_uddt.py +++ b/qsdsan/sanunits/_uddt.py @@ -70,7 +70,7 @@ class UDDT(Toilet): def __init__(self, ID='', ins=None, outs=(), thermo=None, init_with='WasteStream', degraded_components=('OtherSS',), N_user=1, N_toilet=1, lifetime=8, - if_toilet_paper=True, if_flushing=True, if_cleansing=False, + if_toilet_paper=True, if_flushing=False, if_cleansing=False, if_desiccant=True, if_air_emission=True, if_ideal_emptying=True, CAPEX=553, OPEX_over_CAPEX=0.1, T=273.15+24, safety_factor=1, if_prep_loss=True, if_treatment=False, diff --git a/qsdsan/utils/getters.py b/qsdsan/utils/getters.py index f58da036..e6301f13 100644 --- a/qsdsan/utils/getters.py +++ b/qsdsan/utils/getters.py @@ -20,14 +20,15 @@ class AttrGetter: - __slots__ = ('obj', 'attr', 'hook') - def __init__(self, obj, attr, hook=lambda i: i): + __slots__ = ('obj', 'attr', 'hook', 'hook_param') + def __init__(self, obj, attr, hook=lambda i: i, hook_param=None): self.obj = obj self.attr = attr self.hook = hook + self.hook_param = hook_param def __call__(self): - return self.hook(getattr(self.obj, self.attr)) + return self.hook(getattr(self.obj, self.attr), *self.hook_param) # # The below one needs updating # class AttrGetter: