Source code for aguaclara.design.cdc

"""The chemical dose controller (CDC) of an AguaClara plant uses the linear
relation between flow rate and entrance tank water level (set by the LFOM) to
dose the correct amount of coagulant and chlorine into the entrance tank.

Example:
    >>> from aguaclara.design.cdc import *
    >>> cdc = CDC(q = 20 * u.L/u.s, coag_type = 'pacl')
    >>> cdc.coag_stock_vol
    <Quantity(2500.0, 'liter')>
"""
import aguaclara.core.physchem as pc
import aguaclara.core.utility as ut
from aguaclara.core.units import u
from aguaclara.design.component import Component
import warnings

import numpy as np

[docs]class CDC(Component): """Design an AguaClara plant's chemical dose controller. Design Inputs: - ``q (float * u.L / u.s)``: Flow rate (required) """ def __init__(self, **kwargs): self.hl = 20 * u.cm self.coag_type='pacl' if self.coag_type.lower() not in ['pacl', 'alum']: raise ValueError('coag_type must be either PACl or Alum.') self.coag_dose_conc_max=100 * u.mg / u.L self.coag_stock_conc=150 * u.g / u.L self.coag_stock_conc_est=150 * u.g / u.L # Deprecated since January 2021 self.coag_stock_min_est_time=1 * u.day self.chem_tank_vol_supplier=[208.198, 450, 600, 750, 1100, 2500] * u.L self.chem_tank_dimensions_supplier=[ [0.571, 0.851], [0.85, 0.99], [0.96, 1.10], [1.10, 1.02], [1.10, 1.39], [1.55, 1.65] ] * u.m self.train_n=1 self.coag_sack_mass = 25 * u.kg self.coag_tube_id = 0.125 * u.inch self.error_ratio=0.1 self.tube_k = 2 super().__init__(**kwargs)
[docs] def alum_nu(self, coag_conc): """ Return the dynamic viscosity of water at a given temperature. If given units, the function will automatically convert to Kelvin. If not given units, the function will assume Kelvin. This function assumes that the temperature dependence can be explained based on the effect on water and that there is no confounding effect from the coagulant. """ alum_nu = \ (1 + (4.255 * 10 ** -6) * (coag_conc/(u.kg/u.m**3)).to(u.dimensionless) ** 2.289) * \ pc.viscosity_kinematic_water(self.temp) return alum_nu
def _alum_nu(self, coag_conc): """ .. deprecated:: `_alum_nu` is deprecated; use `alum_nu` instead. """ # Deprecated since January 2021 warnings.warn('_alum_nu is deprecated; use alum_nu instead.', UserWarning) return self.alum_nu(coag_conc)
[docs] def pacl_nu(self, coag_conc): """Return the dynamic viscosity of water at a given temperature. If given units, the function will automatically convert to Kelvin. If not given units, the function will assume Kelvin. This function assumes that the temperature dependence can be explained based on the effect on water and that there is no confounding effect from the coagulant. """ pacl_nu = \ (1 + (2.383 * 10 ** -5) * (coag_conc/(u.kg/u.m**3)).to(u.dimensionless) ** 1.893) * \ pc.viscosity_kinematic_water(self.temp) return pacl_nu
def _pacl_nu(self, coag_conc): """ .. deprecated:: `_pacl_nu` is deprecated; use `pacl_nu` instead. """ # Deprecated since January 2021 warnings.warn('_pacl_nu is deprecated; use pacl_nu instead.', UserWarning) return self.pacl_nu(coag_conc) def _coag_nu(self, coag_conc, coag_type): """ .. deprecated:: `_coag_nu` is deprecated; use `coag_nu` instead. """ # Deprecated since January 2021 warnings.warn('_coag_nu is deprecated; use coag_nu instead.', UserWarning) return self.coag_nu(coag_conc, coag_type)
[docs] def coag_nu(self, coag_conc, coag_type): """Return the dynamic viscosity of water at a given temperature. If given units, the function will automatically convert to Kelvin. If not given units, the function will assume Kelvin. """ if coag_type.lower() == 'alum': coag_nu = self.alum_nu(coag_conc) elif coag_type.lower() == 'pacl': coag_nu = self.pacl_nu(coag_conc) return coag_nu
@property def coag_q_max_est(self): """The estimated maximum permissible flow rate of the coagulant stock, based on a whole number of sacks of coagulant used. .. deprecated:: `coag_q_max_est` is deprecated; use `coag_q_max` instead, which is based on the exact user-defined coagulant stock concentration, `coag_stock_conc`. """ # Deprecated since January 2021 warnings.warn('coag_q_max_est is deprecated; use coag_q_max instead,\ which is based on the exact user-defined coagulant stock \ concentration, coag_stock_conc.', UserWarning) coag_q_max_est = self.q * self.coag_dose_conc_max / \ self.coag_stock_conc_est return coag_q_max_est @property def coag_q_max(self): """The maximum permissible flow rate of the coagulant stock.""" coag_q_max = self.q * self.coag_dose_conc_max / self.coag_stock_conc return coag_q_max.to(u.L / u.s) @property def coag_stock_vol(self): """The volume of the coagulant stock tank, based on available sizes from the supplier. """ coag_stock_vol = ut.ceil_nearest( self.coag_stock_min_est_time * self.train_n * self.coag_q_max, self.chem_tank_vol_supplier ) return coag_stock_vol @property def coag_sack_n(self): """The number of sacks of coagulant needed to make the coagulant stock, rounded to the nearest whole number. """ coag_sack_n = round( (self.coag_stock_vol * self.coag_stock_conc / self.coag_sack_mass).to_base_units() ) return coag_sack_n # Commented out January 2021 # @property # def coag_stock_conc(self): # """The concentration of the coagulant stock.""" # coag_stock_conc = self.coag_sack_n * self.coag_sack_mass / \ # self.coag_stock_vol # return coag_stock_conc @property def coag_stock_time_min(self): """The minimum amount of time that the coagulant stock will last.""" return self.coag_stock_vol / (self.train_n * self.coag_q_max) @property def coag_stock_nu(self): """The kinematic viscosity of the coagulant stock.""" return self.coag_nu(self.coag_stock_conc, self.coag_type) #============================================================================== # Small-diameter Tube Design #============================================================================== @property def _coag_tube_q_max(self): """The maximum permissible flow through a coagulant tube.""" coag_tube_q_max = ((np.pi * self.coag_tube_id ** 2)/4) * \ np.sqrt((2 * self.error_ratio * self.hl * u.gravity)/self.tube_k) return coag_tube_q_max.to(u.L / u.s) @property def coag_tubes_active_n(self): """The number of coagulant tubes in use.""" coag_tubes_active_n = \ np.ceil((self.coag_q_max / self._coag_tube_q_max).to_base_units()) return coag_tubes_active_n @property def coag_tubes_n(self): """The number of coagulant tubes in use, plus a spare tube for maintenance. """ coag_tubes_n = self.coag_tubes_active_n + 1 return coag_tubes_n @property def coag_tube_operating_q_max(self): """The maximum flow through a coagulant tube during actual operation.""" coag_tube_operating_q_max = self.coag_q_max / self.coag_tubes_active_n return coag_tube_operating_q_max @property def coag_tube_l(self): """The length of a coagulant tube.""" coag_tube_l = ( self.hl * u.gravity * np.pi * self.coag_tube_id ** 4 / (128 * self.coag_stock_nu * self.coag_tube_operating_q_max) ) - ( 8 * self.coag_tube_operating_q_max * self.tube_k / (128 * np.pi * self.coag_stock_nu) ) return coag_tube_l.to_base_units() @property def coag_tank_r(self): """The radius of the coagulant stock tank, based on available sizes from the supplier.""" index = np.argmax(self.chem_tank_vol_supplier == self.coag_stock_vol) coag_tank_r = self.chem_tank_dimensions_supplier[index][0] / 2 return coag_tank_r @property def coag_tank_h(self): """The height of the coagulant stock tank, based on available sizes from the supplier.""" index = np.argmax(self.chem_tank_vol_supplier == self.coag_stock_vol) coag_tank_h = self.chem_tank_dimensions_supplier[index][1] return coag_tank_h def _DiamTubeAvail(self, en_tube_series = True): if en_tube_series: return 1*u.mm else: return (1/16)*u.inch