Source code for Water.tanks

from __future__ import print_function, division
from math import pi, acos, sqrt
from Water import tools

[docs]class Tank: '''Defines Tank object to calculate storage and other tank properties. :param \**kwargs: keyword arguments :type \**kwargs: dictionary :return: Tank object :rtype: object :keyword arguments: :name: (*string*) - tank name :diameter: (*int/float*) - tank diameter in ft :height: (*int/float*) - tank height in ft, *default 0* :length: (*int/float*) - tank length in ft, *default 0* :width: (*int/float*) - tank width in ft, *default 0* :freeboard: (*int/float*) - distance from the top ring of tank to the highest water level in ft, *default 0* :deadstorage: (*int/float*) - distance from the lowest water level to the bottom of the tank in ft, *defalut 0* :elevation: (*int/float*) - tank's base elevation in ft, *default 0* :shape: (*string*) - tank shape ('horizontal', 'vertical', or 'box'), *default 'vertical'* :operational: (*int/float*) - operational storage partition in ft, *default 0* :equalizing: (*int/float*) - equalizing storage partition in ft, *default 0* :standby: (*int/float*) - standby storage partition in ft, *default 0* :fire: (*int/float*) - fire-flow storage partition in ft, *default 0* :Example: ..code-block:: # properties for 30 ft diameter 45 ft tall cylindrical storage tank tank_data = { 'name' : 'Tank 1', 'diameter' : 30, 'height' : 45, 'freeboard' : 2, 'deadstorage' : 1, 'elevation' : 230, } tank_1 = Tank(**tank_data) # or tank object can be instantiated with individually specified parameters tank_2 = Tank(name='Tank 2', diameter=30, height=45) ''' def __init__(self, **kwargs): self.name = kwargs.get('name') self.diameter = kwargs.get('diameter') # ft self.height = kwargs.get('height', 0) # ft self.freeboard = kwargs.get('freeboard', 0) # ft self.deadstorage = kwargs.get('deadstorage', 0) # ft self.elevation = kwargs.get('elevation', 0) # ft self.shape = kwargs.get('shape', 'vertical') # 'vertical' 'horizontal' 'box' self.length = kwargs.get('length', 0) # ft self.width = kwargs.get('width', 0) # ft self.operational = kwargs.get('operational', 0) # ft self.equalizing = kwargs.get('equalizing', 0) # ft self.standby = kwargs.get('standby', 0) # ft self.fire = kwargs.get('fireflow', 0) # ft @property def area(self): '''returns cross-sectional area of tank in sqft''' if self.shape == 'vertical' or self.shape == 'horizontal': a = pi * self.diameter**2 / 4 elif self.shape == 'box': a = self.width * self.length else: print('tank shape must be vertical, horizontal or box') a = None return a @property def vol(self): '''returns dry volume of tank in gallons''' if self.shape == 'horizontal': return self.horizontal_vol(self.diameter) else: return tools.cuft2gal(self.area * self.height) @property def useable(self): '''returns useable volume of tank in gallons''' if self.shape == 'horizontal': v_dead = tools.cuft2gal(self.horizontal_vol(self.deadstorage)) v_free = tools.cuft2gal(self.horizontal_vol(self.freeboard)) return self.vol - v_dead - v_free else: return tools.cuft2gal((self.height - self.deadstorage - self.freeboard) * self.area)
[docs] def horizontal_vol(self, height): '''calculate filled volume of horizontal tank at a specific height .. math:: A_w &= \\pi r^2 - r^2 arcos\\big{(}\\frac{(r-h)}{r}\\big{)} + (r-h)\\sqrt{2rh - h^2} V &= A_w \cdot L :param height: water level :type height: int/float :return: water volume (gallons) :rtype: float ''' L = self.length R = self.diameter/2 h = height v = L*(R**2 * acos((R-h)/R) - (R-h) * sqrt(2*R*h - h**2)) return tools.cuft2gal(v)
def _horizontal_vol_dict(self): '''returns a dict of volumes at heights ranging from 0 to self.diameter This is used to get the water level in a horizontal tank, given the water volume. Opted to just create a "lookup table" rather than solve for h. ''' L = self.length R = self.diameter/2 l = self.diameter/1001 h_arr = [l* x for x in range(1001)] # create an array much like np.linspace vol = [] for h in h_arr: vol.append(L*(R**2 * acos((R-h)/R) - (R-h) * sqrt(2*R*h - h**2))) vol_lookup = dict(zip(vol, h_arr)) return vol_lookup
[docs] def getPercent(self, vol, of_vol): '''simple percentage calculation used to get the tank volume percentage of total system volume :param vol: tank or partition volume (numerator) :param of_vol: total system or tank volume (denominator) :type vol: int/float :type of_vol: int/float :return: percentage of total/system volume :rtype: float ''' try: p = vol/of_vol except ZeroDivisionError as e: print(e) return None else: return p
[docs] def getHeight(self, vol, units='gal'): '''calculates the water level in ft at given volume :param vol: water volume :param units: units volume is given ('gal' or 'cuft') :type vol: int/float :type units: string :return: water level in ft :rtype: float ''' units = units.lower() assert (units == 'gal' or units == 'cuft'), "units must be either 'gal' or 'cuft'" if units == 'gal': v = tools.gal2cuft(vol) elif units == 'cuft': v = vol if self.shape == 'horizontal': v_dict = self._horizontal_vol_dict() # select height from v_dictionary if present, else take the closest lower value h = v_dict[vol] if vol in v_dict else v_dict[min(v_dict.keys(), key=lambda k: abs(k-vol))] return h else: h = v/self.area return h
[docs] def getInfo(self, SB=0, ES=0, OS=0, FFS=0, total_vol=0, details=False): '''returns string of the current tank properties :param SB: system's required standby storage in gallons, *default 0* :param ES: system's required equalizing in gallons, *default 0* :param OS: system's required operational storagein gallons, *default 0* :param FFS: system's required fire-flow storage in gallons, *default 0* :param total_vol: total system volume :param details: show storage partition details :type SB: int/float :type ES: int/float :type OS: int/float :type FFS: int/float :type total_vol: int/float :type details: boolean :return: tank object information, if details True it shows storage partition volumes and heights relative to whole system :rtype: string ''' if self.shape == 'horizontal': info = f''' {self.name} \r\n Base Elevation:------------- {self.elevation} ft Orientation:---------------- {self.shape} Tank Length:---------------- {self.length} ft Tank Diameter:-------------- {self.diameter} ft Tank Cross-Sectional Area:-- {self.area:.1f} ft^2 Total Volume:--------------- {self.vol:.1f} gal Effective Volume:----------- {self.useable:.1f} gal ''' elif self.shape == 'box': info = f''' {self.name} \r\n Base Elevation:------------- {self.elevation} ft Orientation:---------------- {self.shape} Tank Length:---------------- {self.length} ft Tank Width:----------------- {self.width} ft Tank Cross-Sectional Area:-- {self.area:.1f} ft^2 Total Volume:--------------- {self.vol:.1f} gal Effective Volume:----------- {self.useable:.1f} gal ''' else: info = f''' {self.name} \r\n Base Elevation:------------- {self.elevation} ft Orientation:---------------- {self.shape} Tank Height:---------------- {self.height} ft Tank Diameter:-------------- {self.diameter} ft Tank cross-sectional area:-- {self.area:.1f} ft^2 Total volume:--------------- {self.vol:.1f} gal Effective volume:----------- {self.useable:.1f} gal ''' if details: perc = self.getPercent(self.useable, total_vol) vols = [vo * perc for vo in [FFS, SB, ES, OS]] # height referenced from base of tank total_h = self.getHeight(sum(vols)) + self.deadstorage + self.freeboard ds_vol = tools.cuft2gal(self.deadstorage*self.area) fb_vol = tools.cuft2gal(self.freeboard*self.area) total_calc_vol = ds_vol + fb_vol + sum(vols) vols.insert(0,ds_vol) vols.append(fb_vol) vols.append(total_calc_vol) info +=f''' Storage Partition |\tVol (gal) | Height(ft) ------------------------------------------------------ Dead Storage\t{ds_vol:.1f}\t\t{self.deadstorage:.1f} Fire-Flow\t\t{FFS*perc:.1f}\t\t{self.getHeight(FFS*perc):.1f} Standby\t\t{SB*perc:.1f}\t\t{self.getHeight(SB*perc):.1f} Equalizing\t\t{ES*perc:.1f}\t\t{self.getHeight(ES*perc):.1f} Operational\t\t{OS*perc:.1f}\t\t{self.getHeight(OS*perc):.1f} Freeboard\t\t{fb_vol:.1f}\t\t{self.freeboard:.1f} ------------------------------------------------------ TOTALS\t\t{total_calc_vol:.1f}\t\t{total_h:.1f} ''' return info
#----------------------------------------------------------------- if __name__=='__main__': print("Test Script") horiz_tank_data = { 'name' : 'horizontal tank', 'diameter' : 8, 'length' : 22, 'freeboard' : 1, 'deadstorage' : .1, 'elevation' : 100, 'shape' : 'horizontal' } vert_tank_data = { 'name' : 'vertical tank', 'diameter' : 8, 'height' : 22, 'freeboard' : 1, 'deadstorage' : .1, 'elevation' : 100, 'shape' : 'vertical' } h_tank = Tank(**horiz_tank_data) v_tank = Tank(**vert_tank_data) total = v_tank.vol + h_tank.vol rep1 = h_tank.getInfo() print(rep1) rep2 = v_tank.getInfo() print(rep2) rep3 = h_tank.getInfo(SB=1000, ES=1000, OS=1000, total_vol=total, details=True) rep4 = v_tank.getInfo(SB=1000, ES=1000, OS=1000, total_vol=total, details=True) print(rep3) print(rep4)