Skip to content

Commit

Permalink
Sedimentation tank design code (#175)
Browse files Browse the repository at this point in the history
* `aguaclara.core.physchem`: Add new functions for calculating manifold and channel sizing
* `aguaclara.core.pipeline`: Add placeholder classes for `PipeComponent` objects
* `aguaclara.core.pipe`: Add function for calculating a fitting, socket, and cap sizes
* `aguaclara.design`: Refine `Component` class structure and implement in design classes
* Sedimentor design code: Write, test, and document functions associated with the sedimentor
  • Loading branch information
oliver-leung authored Jul 15, 2019
1 parent 8fc30bb commit 53ffb56
Show file tree
Hide file tree
Showing 30 changed files with 1,412 additions and 871 deletions.
92 changes: 91 additions & 1 deletion aguaclara/core/physchem.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import aguaclara.core.materials as mat
import aguaclara.core.constants as con
import aguaclara.core.utility as ut
import aguaclara.core.pipes as pipe

import numpy as np
from scipy import interpolate, integrate
Expand Down Expand Up @@ -369,7 +370,7 @@ def headloss_manifold(FlowRate, Diam, Length, KMinor, Nu, PipeRough, NumOutlets)
* ((1/3 )
+ (1 / (2*NumOutlets))
+ (1 / (6*NumOutlets**2))
)
)
)


Expand Down Expand Up @@ -713,3 +714,92 @@ def headloss_kozeny(Length, Diam, Vel, Porosity, Nu):
/ gravity.magnitude * (1-Porosity)**2
/ Porosity**3 * 36 * Vel
/ Diam ** 2)


@u.wraps(u.m, [u.m**3/u.s, u.m], False)
def pipe_ID(FlowRate, Pressure):
"""Return the internal diameter of a pipe for a given pressure
recovery constraint. """
#Checking input validity
ut.check_range([FlowRate, ">0", "Flow rate"], [Pressure, ">0", "Pressure"])
return np.sqrt(FlowRate/((np.pi/4)*np.sqrt(2*gravity.magnitude*Pressure)))

def manifold_id_alt(q, pr_max):
"""Return the inner diameter of a manifold when major losses are
negligible.
"""
manifold_id_alt = np.sqrt(
4 * q / (
np.pi * np.sqrt(
2 * con.GRAVITY * pr_max
)
)
)
return manifold_id_alt

def manifold_id(q, h, l, q_ratio, nu, eps, k, n):
id_new = 2 * u.inch
id_old = 0 * u.inch
error = 1
while error > 0.01:
id_old = id_new
id_new = (
((8 * q ** 2) / (con.GRAVITY * np.pi ** 2 * h)) *
(
(
1 + fric(q, id_old, nu, eps) *
(1 / 3 + 1 / (2 * n) + 1 / (6 * n ** 2))
) /
(1 - q_ratio ** 2)
)
) ** (1 / 4)
error = np.abs(id_old - id_new) / id_new
return id_new

def manifold_nd(q, h, l, q_ratio, nu, eps, k, n, sdr):
manifold_nd = pipe.ND_SDR_available(
manifold_id(q, h, l, q_ratio, nu, eps, k, n),
sdr
)
return manifold_nd

def horiz_chan_w(q, depth, hl, l, nu, eps, manifold, k):
hl = min(hl, depth / 3)
horiz_chan_w_new = q / ((depth - hl) * np.sqrt(2 * con.GRAVITY * hl))

error = 1
i = 0
while error > 0.001 and i < 20:
w = horiz_chan_w_new
i = i + 1
horiz_chan_w_new = np.sqrt(
(
1 + k +
fric_rect(q, w, depth - hl, nu, eps, True) *
(l / (4 * radius_hydraulic(w, depth - hl, True))) *
(1 - (2 * (int(manifold) / 3)))
) / (2 * con.GRAVITY * hl)
) * (q / (depth - hl))
error = np.abs(horiz_chan_w_new - w) / (horiz_chan_w_new + w)
return horiz_chan_w_new.to(u.m)

def horiz_chan_h(q, w, hl, l, nu, eps, manifold):
h_new = (q / (w * np.sqrt(2 * con.GRAVITY * hl))) + hl
error = 1
i = 0
while error > 0.001 and i < 200:
h = h_new
hl_local = min(hl, h / 3)
i = i + 1
h_new = (q/ w) * np.sqrt((1 + \
fric_rect(q, w, h - hl_local, nu, eps, True) * (l / (4 * \
radius_hydraulic(w, h - hl_local, True))) * (1 - 2 * (int(manifold) / 3))
)/ (2 * con.GRAVITY * hl_local)) + (hl_local)
error = np.abs(h_new - h) / (h_new + h)
return h_new.to(u.m)

def pipe_flow_nd(q, sdr, hl, l, nu, eps, k):
i = 0
while q > flow_pipe(pipe.ID_SDR_all_available(sdr)[i], hl, l, nu, eps, k):
i += 1
return pipe.ND_SDR_available(pipe.ID_SDR_all_available(sdr)[i], sdr)
28 changes: 28 additions & 0 deletions aguaclara/core/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,33 @@
import aguaclara.core.materials as mats
import numpy

class PipeComponent:
"""This class has functions and fields common to Pipe and Connector"""
def __init__(self, diameter_inner = (1/8)*u.inch):
"""Instantiates a PipeComponent fields common to both class Pipe and Connector with the specified values.
Args:
diameter_inner (float *u.in): inner diameter of the pipes
Returns:
PipeComponent object."""
self.diameter_inner = diameter_inner

class Pipe(PipeComponent):
"""This class calculates necessary functions and holds fields for pipes"""
def __init__(self, diameter_inner):
super().__init__(self, diameter_inner)
# length


class Connector(PipeComponent):
"""This class calculates necessary functions and holds fields for connectors"""
def __init__(self, diameter_inner):
super().__init__(self, diameter_inner)
# angle



@u.wraps(u.m**3/u.s, [u.m, u.m, None, u.m], False)
def flow_pipeline(diameters, lengths, k_minors, target_headloss,
nu=con.WATER_NU, pipe_rough=mats.PVC_PIPE_ROUGH):
Expand Down Expand Up @@ -54,3 +81,4 @@ def flow_pipeline(diameters, lengths, k_minors, target_headloss,
flow = flow + err * flow

return flow

27 changes: 20 additions & 7 deletions aguaclara/core/pipes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""This file contains functions which convert between the nominal, inner, and
outer diameters of pipes based on their standard dimension ratio (SDR).
"""

#Let's begin to create the pipe database
Expand Down Expand Up @@ -46,14 +45,21 @@ def OD(ND):
given nominal diameter have the same outer diameter.
Steps:
1. Find the index of the closest nominal diameter.
(Should this be changed to find the next largest ND?)
2. Take the values of the array, subtract the ND, take the absolute
value, find the index of the minimium value.
1. Find the index of the closest nominal diameter.
(Should this be changed to find the next largest ND?)
2. Take the values of the array, subtract the ND, take the absolute
value, find the index of the minimium value.
"""
index = (np.abs(np.array(pipedb['NDinch']) - (ND))).argmin()
return pipedb.iloc[index, 1]

def fitting_od(pipe_nd, fitting_sdr=41):
pipe_od = OD(pipe_nd)
fitting_nd = ND_SDR_available(pipe_od, fitting_sdr)
fitting_od = OD(fitting_nd)
return fitting_od


@u.wraps(u.inch, [u.inch, None], False)
def ID_SDR(ND, SDR):
"""Return the inner diameter for SDR(standard diameter ratio) pipes.
Expand Down Expand Up @@ -138,11 +144,18 @@ def ND_available(NDguess):

def od_available(od_guess):
"""Return the minimum OD that is available.
1. Extract the magnitude in inches from the outer diameter.
2. Find the index of the closest outer diameter.
3. Take the values of the array, subtract the OD, take the
absolute value, find the index of the minimium value.
"""
myindex = (od_all_available() >= od_guess)
return min(od_all_available()[myindex])
return min(od_all_available()[myindex])

def socket_depth(nd):
return nd / 2

def cap_thickness(nd):
cap_thickness = (fitting_od(nd) - OD(ND_available(nd))) / 2
return cap_thickness
59 changes: 24 additions & 35 deletions aguaclara/design/cdc.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,42 +22,31 @@ class CDC(Component):
Design Inputs:
- ``q (float * u.L / u.s)``: Flow rate (required)
"""
def __init__(self, q=20.0 * u.L/u.s, temp=20 * u.degC,
hl = 20 * u.cm,
coag_type='pacl',
coag_dose_conc_max=2 * u.g / u.L, #What should this default to? -Oliver L., 6 Jun 19
coag_stock_conc_est=150 * u.g / u.L,
coag_stock_min_est_time=1 * u.day,
chem_tank_vol_supplier=[208.198, 450, 600, 750, 1100, 2500] * u.L,
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,
train_n=1,
coag_sack_mass = 25 * u.kg,
coag_tube_id = 0.125 * u.inch,
error_ratio=0.1,
tube_k = 2):

super().__init__(q= q, temp = temp)
if coag_type.lower() not in ['pacl', 'alum']:
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_type = coag_type
self.coag_dose_conc_max = coag_dose_conc_max
self.coag_stock_conc_est = coag_stock_conc_est
self.chem_tank_vol_supplier = chem_tank_vol_supplier
self.chem_tank_dimensions_supplier = chem_tank_dimensions_supplier
self.coag_stock_min_est_time = coag_stock_min_est_time
self.train_n = train_n
self.coag_sack_mass = coag_sack_mass
self.coag_tube_id = coag_tube_id
self.error_ratio = error_ratio
self.hl = hl
self.tube_k = tube_k

self.coag_dose_conc_max=2 * u.g / u.L #What should this default to? -Oliver L., 6 Jun 19
self.coag_stock_conc_est=150 * u.g / u.L
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)

def _alum_nu(self, coag_conc):
"""Return the dynamic viscosity of water at a given temperature.
Expand Down
Loading

0 comments on commit 53ffb56

Please sign in to comment.