Source code for aguaclara.design.floc

"""The flocculator of an AguaClara water treatment plant uses turbulence to
cause coagulant and other particles to accumulate, forming flocs.

Example:
    >>> from aguaclara.design.floc import *
    >>> floc = Flocculator(q = 20 * u.L / u.s, hl = 40 * u.cm)
    >>> round(floc.chan_w)
    <Quantity(34, 'centimeter')>
"""
import aguaclara.core.head_loss as hl
import aguaclara.design.human_access as ha
import aguaclara.core.physchem as pc
import aguaclara.core.pipes as pipes
import aguaclara.core.utility as ut
from aguaclara.core.units import u
from aguaclara.design.component import Component
from aguaclara.design.pipeline import Pipe

import numpy as np
from urllib.parse import quote_plus


[docs]class Flocculator(Component): """Design an AguaClara plant's flocculator. A flocculator's design relies on the entrance tank's design in the same plant, but assumed/default values may be used to design a flocculator by itself. To design these components in tandem, use :class:`aguaclara.design.ent_floc.EntTankFloc`. Attributes: - ``BAFFLE_K (float)``: Minor loss coefficient around a baffle edge - ``CHAN_N_MIN (int)``: Minimum channel number - ``HS_RATIO_MIN (float)``: Minimum H/S ratio - ``HS_RATIO_MAX (float)``: Maximum H/S ratio - ``SDR (float)``: Standard dimension ratio - ``OBSTACLE_OFFSET (bool)``: Whether the baffle obstacles are offset from each other Design Inputs: - ``q (float * u.L/u.s)``: Flow rate (required) - ``temp (float * u.degC)``: Water temperature (optional, defaults to 20°C) - ``ent_l (float * u.m)``: Entrance tank length (recommmended, defaults to 1.5m) - ``chan_w_max (float * u.inch)``: Maximum width (optional, defaults to 42") - ``l_max (float * u.m)``: Maximum length (optional, defaults to 6m) - ``gt (float)``: Collision potential (optional, defaults to 37000) - ``hl (float * u.cm)``: Head loss (optional, defaults to 40cm) - ``end_water_depth (float * u.m)``: Depth at the end (optional, defaults to 2m) - ``drain_t (float * u.min)``: Drain time (optional, defaults to 30 mins) - ``polycarb_sheet_w (float * u.inch)``: Width of polycarbonate sheets used to construct baffles (optional, defaults to 42 in) - ``sed_chan_inlet_w_pre_weir (float * u.inch)``: Width of the inlet sedimentation channel pre-weir (optional, defaults to 42 in) - ``dividing_wall_thickness (float * u.cm)``: Thickness of dividing walls between each flocculator channel (optional, defaults to 15 cm) - ``chan_n_parity (str)``: Parity of the number of channels. Can be \'even\', \'odd\', or \'any\' (optional, defaults to \'even\') """ BAFFLE_K = 2.5 CHAN_N_MIN = 2 HS_RATIO_MIN = 3.0 HS_RATIO_MAX = 6.0 SDR = 41.0 # This is an expert input in ent, should this be an expert # input as well? -Oliver L., oal22, 5 Jun 19 OBSTACLE_OFFSET = True _onshape_url = ( "https://cad.onshape.com/documents/c3a8ce032e33ebe875b9aab4/w/de9ad5474448b34f33fef097/e/08f41d8bdd9a9c90ab396f8a" ) def __init__(self, **kwargs): self.ent_l = 1.5 * u.m self.chan_w_max = 42.0 * u.inch self.l_max = 6.0 * u.m self.gt = 37000 self.hl = 40.0 * u.cm self.end_water_depth = 2.0 * u.m self.drain_t = 30.0 * u.min self.spec = 'sdr41' self.drain_pipe = Pipe() self.subcomponents = [self.drain_pipe] self.polycarb_sheet_w = 42.0 * u.inch self.sed_chan_inlet_w_pre_weir = 42.0 * u.inch self.dividing_wall_thickness = 15.0 * u.cm self.chan_n_parity = 'even' super().__init__(**kwargs) self._set_drain_pipe() super().set_subcomponents() if self.chan_n_parity not in ('even', 'odd', 'any'): raise AttributeError( 'chan_n_parity must be set to \'even\', \'odd\', or \'any\'.' ) @property def vel_grad_avg(self): """The average velocity gradient of water.""" vel_grad_avg = ((u.standard_gravity * self.hl) / (pc.viscosity_kinematic_water(self.temp) * self.gt)).to(u.s ** -1) return vel_grad_avg @property def retention_time(self): """The hydraulic retention time neglecting the volume created by head loss. """ retention_time = (self.gt / self.vel_grad_avg).to(u.s) return retention_time @property def vol(self): """The target volume (not counting the volume added by head loss).""" return (self.q * self.retention_time).to(u.m ** 3) @property def chan_w_min_hs_ratio(self): """The minimum channel width.""" chan_w_min_hs_ratio = ( (self.HS_RATIO_MIN * self.q / self.end_water_depth) * ( self.BAFFLE_K / ( 2 * self.end_water_depth * pc.viscosity_kinematic_water(self.temp) * self.vel_grad_avg ** 2 ) ) ** (1/3) ).to(u.cm) return chan_w_min_hs_ratio @property def chan_w_min(self): """The minimum channel width.""" return ut.min(self.chan_w_min_hs_ratio, self.polycarb_sheet_w).to(u.cm) @property def chan_n(self): """The minimum number of channels based on the maximum possible channel width and the maximum length of the channels. """ if self.q < 16 * u.L / u.s: return 1 else: chan_n = (( (self.vol / (self.polycarb_sheet_w * self.end_water_depth) ) + self.ent_l ) / self.chan_l).to_base_units() if self.chan_n_parity == 'even': return ut.ceil_step(chan_n, step=2) elif self.chan_n_parity == 'odd': return ut.ceil_step(chan_n, step=2) - 1 elif self.chan_n_parity == 'any': return ut.ceil_step(chan_n, step=1) @property def chan_w_min_gt(self): """The channel width minimum regarding the collision potential.""" chan_w_min_gt = self.vol / ( self.end_water_depth * (self.chan_n * self.chan_l - self.ent_l) ) return chan_w_min_gt.to(u.cm) @property def chan_w(self): """The channel width.""" chan_w = ut.ceil_step( ut.max(self.chan_w_min_gt, self.chan_w_min), step=1 * u.cm ) return chan_w @property def l_max_vol(self): """The maximum length depeneding on the volume.""" l_max_vol = self.vol / \ (self.CHAN_N_MIN * self.chan_w_min * self.end_water_depth) return l_max_vol.to(u.m) @property def chan_l(self): """The channel length.""" chan_l = min(self.l_max, self.l_max_vol) return chan_l.to_base_units() @property def expansion_h_max(self): """"The maximum distance between expansions for the largest allowable H/S ratio. """ expansion_h_max = ( ( (self.BAFFLE_K / ( 2 * pc.viscosity_kinematic_water(self.temp) * (self.vel_grad_avg ** 2) ) ) * (self.q * self.HS_RATIO_MAX / self.chan_w) ** 3 ) ** (1/4) ).to(u.m) return expansion_h_max @property def expansion_n(self): """The minimum number of expansions per baffle space.""" return np.ceil(self.end_water_depth / self.expansion_h_max) @property def expansion_h(self): """The height between flow expansions.""" return (self.end_water_depth / self.expansion_n).to(u.cm) @property def baffle_s(self): """The spacing between baffles.""" baffle_s = ( (self.BAFFLE_K / ( (2 * self.expansion_h * (self.vel_grad_avg ** 2) * pc.viscosity_kinematic_water(self.temp)) ).to_base_units() ) ** (1/3) * self.q / self.chan_w ).to(u.cm) return baffle_s @property def obstacle_n(self): """The number of obstacles per baffle.""" return self.end_water_depth / self.expansion_h - 1 @property def contraction_s(self): """The space in the baffle by which the flow contracts.""" return self.baffle_s * 0.6 @property def obstacle_pipe_od(self): """The outer diameter of an obstacle pipe. If the available pipe is greater than 1.5 inches, the obstacle offset will become false.""" pipe_od = pipes.OD_available(self.contraction_s) if pipe_od > 1.5 * u.inch: self.OBSTACLE_OFFSET = False pipe_od = pipes.OD_available(pipe_od / 2) return pipe_od def _set_drain_pipe(self): drain_k_minor = \ hl.PIPE_ENTRANCE_K_MINOR + \ hl.PIPE_ENTRANCE_K_MINOR + \ hl.PIPE_EXIT_K_MINOR chan_pair_a = 2 * self.chan_l * self.chan_w drain_id = ( np.sqrt(8 * chan_pair_a / (np.pi * self.drain_t) * np.sqrt( self.end_water_depth * drain_k_minor / (2 * u.standard_gravity) ) ) ).to_base_units() self.drain_pipe = Pipe( id=drain_id, k_minor=drain_k_minor, spec=self.spec ) @property def onshape_url_configured(self): # Make the configuration string for the flocculator concrete. {{ and }} # are used so that str.format() doesn't recognize them. concrete_config = ( '{{"w_channel":"{}", "h_channel":"{}", "l_channel":"{}", "s_baffle"' ':"{}", "n_channel":{}, "t_wall":"{}"}}'.format( self.chan_w, self.end_water_depth, self.chan_l, self.baffle_s, self.chan_n, self.dividing_wall_thickness ) ) # concrete_config = quote_plus(concrete_config) encoded_config = '?configuration=' encoded_config += quote_plus('Concrete_config=' + concrete_config + ';') encoded_config += quote_plus('Channel_L=' + str(self.chan_l) + ';') configured_url = self._onshape_url + encoded_config return configured_url