Source code for biosteam.wastewater.high_rate.sludge_handling

# -*- coding: utf-8 -*-
# Bioindustrial-Park: BioSTEAM's Premier Biorefinery Models and Results
# Copyright (C) 2021-2024, Yalin Li <mailto.yalin.li@gmail.com>
#
# This module is under the UIUC open-source license. See
# github.com/BioSTEAMDevelopmentGroup/biosteam/blob/master/LICENSE.txt
# for license details.

import math, flexsolve as flx
import biosteam as bst
from warnings import warn

__all__ = ('SludgeHandling', 'BeltThickener', 'SludgeCentrifuge')


# %%

class SludgeHandling(bst.Unit):
    """
    A generic class for handling of wastewater treatment sludge [6]_. Two pumps 
    (one for the supernatant and one for sludge) are included.

    Separation split is determined by the moisture (i.e., water) content
    of the sludge, soluble chemicals will have the same split as water,
    insolubles chemicals will all go to the retentate.

    Parameters
    ----------
    ins : 
        Influent
    outs : 
        * [0] water-rich supernatant
        * [1] solid-rich sludge
    sludge_moisture : float
        Moisture content of the sludge, [wt. % water].
    solubles : tuple
        IDs of the soluble chemicals.
        Note that all chemicals that are not included in this tuple and not
        locked as gas phase (i.e., `chemical.locked_state` != "g") will be
        treated as solids in simulation.

    """

    SKIPPED = False
    _graphics = bst.Splitter._graphics
    _ins_size_is_fixed = False
    _N_outs = 2
    auxiliary_unit_names = ('effluent_pump', 'sludge_pump')


    def _init(self, sludge_moisture=0.96, solubles=()):
        self.sludge_moisture = sludge_moisture
        self.solubles = tuple(solubles)
        self.solids = tuple(i.ID for i in self.chemicals
                            if (i.ID not in solubles) and (i.locked_state!='g'))
        ID = self.ID
        self._mixed = bst.Stream(f'{ID}_mixed')
        # Add '.' in ID for auxiliary units
        self.effluent_pump = bst.Pump(f'.{ID}_eff_pump', ins=self.outs[0].proxy(f'{ID}_eff'))
        self.sludge_pump = bst.Pump(f'.{ID}_sludge_pump', ins=self.outs[1].proxy(f'{ID}_sludge'))


    @staticmethod
    def _mc_at_split(split, solubles, mixed, eff, sludge, target_mc):
        eff.imass[solubles] = mixed.imass[solubles] * split
        sludge.imass[solubles] = mixed.imass[solubles] - eff.imass[solubles]
        mc = sludge.imass['Water'] / sludge.F_mass
        return mc-target_mc


    def _run(self):
        eff, sludge = self.outs
        solubles, solids = self.solubles, self.solids

        mixed = self._mixed
        mixed.mix_from(self.ins)
        eff.T = sludge.T = mixed.T

        if mixed.F_mass == 0:
            eff.empty()
            sludge.empty()
            self.SKIPPED = True
            return

        mc = mixed.imass['Water']/mixed.F_mass
        target_mc = self.sludge_moisture
        if mc < target_mc:
            if self.SKIPPED == False:
                warn(f'The moisture of influent ({mc:.2%}) is smaller than the desired {target_mc:.0%}, '
                     'simulation skipped.')
                self.SKIPPED = True
            sludge.copy_like(mixed)
            eff.empty()
            return

        sludge.copy_flow(mixed, solids, remove=True) # all solids go to sludge
        eff.copy_flow(mixed, solubles)

        flx.IQ_interpolation(
                f=self._mc_at_split, x0=1e-3, x1=1.-1e-3, ytol=1e-3, maxiter=100,
                args=(solubles, mixed, eff, sludge, target_mc),
                checkbounds=False)
        self.SKIPPED = False


    def _cost(self):
        if self.SKIPPED == False:
            pumps = (self.effluent_pump, self.sludge_pump)
            for i in range(2): pumps[i].simulate()
        else:
            self.baseline_purchase_costs.clear()
            self.power_utility.rate = 0


[docs] class BeltThickener(SludgeHandling): """ Gravity belt thickener (GBT) designed based on the manufacture specification data sheet. [9]_ Key parameters include: * Capacity: 80-100 m3/h. * Influent solids concentration: 0.2-1%. * Sludge cake moisture content: 90-96%. * Motor power: 3 (driving motor) and 1.1 (agitator motor) kW. * Belt width: 2.5 m. * Weight: 2350 kg. * Quote price: $3680 ea for three or more sets. The bare module (installation) factor is from Table 25 in Humbird et al. [2]_ (solids handling equipment). Parameters ---------- ins : Influent outs : * [0] water-rich supernatant * [1] solid-rich sludge sludge_moisture : float Moisture content of the thickened sludge, [wt% water]. solubles : tuple IDs of the soluble chemicals. Note that all chemicals that are not included in this tuple and not locked as gas phase (i.e., `chemical.locked_state!='g'`) will be treated as solids in simulation. max_capacity : float Maximum hydraulic loading per belt thickener, [m3/h]. power_demand : float Total power demand of each belt thickener, [kW]. """ def _init(self, sludge_moisture=0.96, solubles=(), max_capacity=100, power_demand=4.1): SludgeHandling._init(self, sludge_moisture=sludge_moisture, solubles=solubles) self.max_capacity = max_capacity self.power_demand = power_demand def _design(self): self._N_thickener = N = math.ceil(self._mixed.F_vol/self.max_capacity) self.design_results['Number of thickeners'] = N self.F_BM['Thickeners'] = 1.7 # ref [2] self.baseline_purchase_costs['Thickeners'] = 4000 * N def _cost(self): super()._cost() self.power_utility.rate += self.power_demand * self.N_thickener @property def N_thickener(self): """[int] Number of required belt thickeners.""" return self._N_thickener
[docs] class SludgeCentrifuge(SludgeHandling, bst.SolidsCentrifuge): """ Solid centrifuge for sludge dewatering. The simulation (:meth:`~biosteam.Unit._run` method) and costing (:meth:`~biosteam.Unit._cost` method) are based on :class:`~biosteam.SludgeHandling` and the sizing (:meth:`~biosteam.Unit._design` method) is based on :class:`~biosteam.units.solids_separation.SolidsCentrifuge`. Parameters ---------- ins : Influent outs : * [0] water-rich supernatant * [1] solid-rich sludge sludge_moisture : float Moisture content of the thickened sludge, [wt% water]. solubles : tuple IDs of the soluble chemicals. Note that all chemicals that are not included in this tuple and not locked as gas phase (i.e., `chemical.locked_state!='g'`) will be treated as solids in simulation. """ def _init(self, sludge_moisture=0.8, solubles=(), centrifuge_type='scroll_solid_bowl'): SludgeHandling._init(self, sludge_moisture=sludge_moisture, solubles=solubles) self.centrifuge_type = centrifuge_type _run = SludgeHandling._run _design = bst.SolidsCentrifuge._design _cost = SludgeHandling._cost