From 813e5d2cc0f26d37f02dff7ddd36f49d8876ea35 Mon Sep 17 00:00:00 2001 From: Hannah Si <35944490+HannahSi@users.noreply.github.com> Date: Tue, 7 Jan 2020 21:51:08 -0600 Subject: [PATCH] Widely implemented list handler and removed unit wrapper (#260) * utility.list_handler takes positional and keyword args and no unit wrapper * Documented utility.list_handler * Updating physchem with new utility.list_handler and no unit wrapper * Updated physchem up to physchem functions * Updated physchem up to friction functions * Updated physchem up to headloss(), wrote Ergun head loss equations * Updated physchem up to flow_orifice_vert() * Working on applying list handler to pipes * List handler accepts outputs with units * Tested and working list handler * Physchem functions updated up to flow_swamee() * Updated physchem til f_Ergun and fxns added recently by AIDE * Remove redundant import * Tested physchem, renaming functions * Renamed and deprecated functions until headloss_fric * Renamed and deprecated functions through head loss functions * list handler widely implemented and unit wrapper removed * Debugging doctests --- aguaclara/core/constants.py | 11 - aguaclara/core/head_loss.py | 48 +- aguaclara/core/physchem.py | 2016 +++++++++++++---- aguaclara/core/pipes.py | 58 +- aguaclara/core/utility.py | 129 +- aguaclara/design/cdc.py | 29 +- aguaclara/design/ent.py | 24 +- aguaclara/design/floc.py | 22 +- aguaclara/design/lfom.py | 21 +- aguaclara/design/pipeline.py | 170 +- aguaclara/design/sed_chan.py | 118 +- aguaclara/design/sed_tank.py | 94 +- .../environmental_processes_analysis.py | 8 + aguaclara/research/floc_model.py | 37 +- aguaclara/research/peristaltic_pump.py | 5 + aguaclara/research/stock_qc.py | 11 + setup.py | 2 +- tests/core/test_head_loss.py | 40 +- tests/core/test_physchem.py | 1403 +++++------- tests/core/test_utility.py | 89 +- tests/design/test_ent.py | 7 +- tests/design/test_ent_floc.py | 8 +- tests/design/test_lfom.py | 14 +- tests/design/test_pipeline.py | 6 +- tests/design/test_sed.py | 2 - tests/design/test_sed_chan.py | 11 +- tests/design/test_sed_tank.py | 8 +- tests/research/test_floc_model.py | 4 +- 28 files changed, 2748 insertions(+), 1647 deletions(-) diff --git a/aguaclara/core/constants.py b/aguaclara/core/constants.py index 8fb750f0..c37760fe 100644 --- a/aguaclara/core/constants.py +++ b/aguaclara/core/constants.py @@ -46,17 +46,6 @@ #: in the AguaClara textbook for more details. JET_PLANE_RATIO = 0.0124 -#: DEPRECATED: will be removed by 21 Dec 2019. -LFP_FLOW_MAX = 16.1 * u.L / u.s - -#: Between fittings and tank wall in a tank. -#: -#: DEPRECATED: will be removed by 21 Dec 2019. -FITTING_S_MIN = 5 * u.cm - -#: DEPRECATED: will be removed by 21 Dec 2019. -CHANNEL_W_MIN = 15 * u.cm - #: Vena contracta coefficient through an orifice with 90˚ bends. This is the #: ratio of the flow area at the point of maximal contraction to the flow area #: before the contraction. diff --git a/aguaclara/core/head_loss.py b/aguaclara/core/head_loss.py index 9444ac35..e225334f 100644 --- a/aguaclara/core/head_loss.py +++ b/aguaclara/core/head_loss.py @@ -16,7 +16,6 @@ # TODO: Add units to docstrings. - Oliver Leung (oal22) -@u.wraps(u.dimensionless, [u.m, u.m, u.L / u.s], strict=False) @ut.list_handler() def k_value_expansion(ent_pipe_id, exit_pipe_id, q, fitting_angle=180, rounded=False, @@ -51,23 +50,23 @@ def k_value_expansion(ent_pipe_id, exit_pipe_id, q, fitting_angle, rounded, nu, pipe_rough) - f = pc.fric(q, ent_pipe_id, nu, pipe_rough) # Darcy friction factor. + f = pc.fric_pipe(q, ent_pipe_id, nu, pipe_rough) # Darcy friction factor. re = pc.re_pipe(q, ent_pipe_id, nu) # Entrance pipe's Reynolds number. fitting_type = _get_fitting_type(fitting_angle, rounded) if fitting_type == 'square': - return _k_value_square_expansion(ent_pipe_id, exit_pipe_id, re, f) + result = _k_value_square_expansion(ent_pipe_id, exit_pipe_id, re, f) elif fitting_type == 'tapered': - return _k_value_tapered_expansion(ent_pipe_id, exit_pipe_id, re, f) + result = _k_value_tapered_expansion(ent_pipe_id, exit_pipe_id, re, f) elif fitting_type == 'rounded': - return _k_value_rounded_expansion(ent_pipe_id, exit_pipe_id, re) + result = _k_value_rounded_expansion(ent_pipe_id, exit_pipe_id, re) elif fitting_type == 'ambiguous': - raise ValueError('The fitting is ambiguously both tapered and rounded. ' + result = ValueError('The fitting is ambiguously both tapered and rounded. ' 'Please set only either fitting_angle or rounded.') + return result.to(u.dimensionless) -@u.wraps(u.dimensionless, [u.m, u.m, u.L / u.s], strict=False) @ut.list_handler() def k_value_reduction(ent_pipe_id, exit_pipe_id, q, fitting_angle=180, rounded=False, @@ -102,23 +101,23 @@ def k_value_reduction(ent_pipe_id, exit_pipe_id, q, fitting_angle, rounded, nu, pipe_rough) - f = pc.fric(q, ent_pipe_id, nu, pipe_rough) # Darcy friction factor. + f = pc.fric_pipe(q, ent_pipe_id, nu, pipe_rough) # Darcy friction factor. re = pc.re_pipe(q, ent_pipe_id, nu) # Entrance pipe's Reynolds number. fitting_type = _get_fitting_type(fitting_angle, rounded) if fitting_type == 'square': - return _k_value_square_reduction(ent_pipe_id, exit_pipe_id, re, f) + result = _k_value_square_reduction(ent_pipe_id, exit_pipe_id, re, f) elif fitting_type == 'tapered': - return _k_value_tapered_reduction(ent_pipe_id, exit_pipe_id, fitting_angle, re, f) + result = _k_value_tapered_reduction(ent_pipe_id, exit_pipe_id, fitting_angle, re, f) elif fitting_type == 'rounded': - return _k_value_rounded_reduction(ent_pipe_id, exit_pipe_id, re) + result = _k_value_rounded_reduction(ent_pipe_id, exit_pipe_id, re) elif fitting_type == 'ambiguous': raise ValueError('The fitting is ambiguously both tapered and rounded.' 'Please set only either fitting_angle or rounded.') + return result.to(u.dimensionless) -@u.wraps(u.dimensionless, [u.m, u.m, u.m, u.m ** 3 / u.s], strict=False) @ut.list_handler() def k_value_orifice(pipe_id, orifice_id, orifice_l, q, nu=con.WATER_NU): @@ -147,12 +146,13 @@ def k_value_orifice(pipe_id, orifice_id, orifice_l, q, orifice_type = _get_orifice_type(orifice_l, orifice_id) if orifice_type == 'thin': - return _k_value_thin_sharp_orifice(pipe_id, orifice_id, re) + result = _k_value_thin_sharp_orifice(pipe_id, orifice_id, re) elif orifice_type == 'thick': - return _k_value_thick_orifice(pipe_id, orifice_id, orifice_l, re) + result = _k_value_thick_orifice(pipe_id, orifice_id, orifice_l, re) elif orifice_type == 'oversize': - return k_value_reduction(pipe_id, orifice_id, q) \ + result = k_value_reduction(pipe_id, orifice_id, q) \ + k_value_expansion(orifice_id, pipe_id, q) + return result.to(u.dimensionless) def _k_value_square_reduction(ent_pipe_id, exit_pipe_id, re, f): @@ -283,32 +283,44 @@ def _get_orifice_type(orifice_l, orifice_id): else: return 'oversize' -##90 deg elbow +#: 90 degree elbow EL90_K_MINOR = 0.9 +#: EL45_K_MINOR = 0.45 -##The loss coefficient for the channel transition in a 90 degree turn + +#: The loss coefficient for the channel transition in a 90 degree turn RIGHT_ANGLE_K_MINOR = 0.4 +#: ANGLE_VALVE_K_MINOR = 4.3 +#: GLOBE_VALVE_K_MINOR = 10 +#: GATE_VALVE_K_MINOR = 0.39 +#: CHECK_VALVE_CONV_K_MINOR = 4 +#: CHECK_VALVE_BALL_K_MINOR = 4.5 -##headloss coefficient of jet +#: Headloss coefficient of a jet EXP_K_MINOR = 1 +#: TEE_FLOW_RUN_K_MINOR = 0.6 +#: TEE_FLOW_BR_K_MINOR = 1.8 +#: PIPE_ENTRANCE_K_MINOR = 0.5 +#: PIPE_EXIT_K_MINOR = 1 +#: RM_GATE_VIN_K_MINOR = 25 diff --git a/aguaclara/core/physchem.py b/aguaclara/core/physchem.py index 0697ff5b..c71df7a7 100644 --- a/aguaclara/core/physchem.py +++ b/aguaclara/core/physchem.py @@ -1,733 +1,1831 @@ -"""Contains unit process functions pertaining to the design of physical -and chemical unit processes for AguaClara water treatment plants. +"""Contains functions pertaining to the design of physical and chemical unit +processes of AguaClara water treatment plants. """ from aguaclara.core.units import u -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 +import warnings -gravity = con.GRAVITY +############################ Gas ############################## -######################Air################################ + +@ut.list_handler() def density_air(Pressure, MolarMass, Temperature): - """Return the density of the air. + """ + .. deprecated:: + `density_air` is deprecated; use `density_gas` instead. + """ + warnings.warn('density_air is deprecated; use density_gas instead.', + UserWarning) + return density_gas(Pressure, MolarMass, Temperature) - :param Pressure: pressure of the air in the system - :type Pressure: float - :param MolarMass: molar mass of the air in the system - :type MolarMass: float - :param Temperature: Temperature of the air in the system - :type Temperature: float - :return: density of the air in the system - :rtype: float +@ut.list_handler() +def density_gas(Pressure, MolarMass, Temperature): + """Return the density of air at the given pressure, molar mass, and + temperature. + + :param Pressure: pressure of air in the system + :type Pressure: u.pascal + :param MolarMass: molar mass of air in the system + :type MolarMass: u.gram/u.mol + :param Temperature: Temperature of air in the system + :type Temperature: u.degK + + :return: density of air in the system + :rtype: u.kg/u.m**3 """ return (Pressure * MolarMass / (u.R * Temperature)).to(u.kg/u.m**3) -###################### Simple geometry ###################### -"""A few equations for useful geometry. -Is there a geometry package that we should be using? -""" +########################## Geometry ########################### -@u.wraps(u.m**2, u.m, False) + +@ut.list_handler() def area_circle(DiamCircle): - """Return the area of a circle.""" - ut.check_range([DiamCircle, ">0", "DiamCircle"]) - return np.pi / 4 * DiamCircle**2 + """Return the area of a circle given its diameter. + + :param DiamCircle: diameter of circle + :type DiamCircle: u.m + + :return: area of circle + :rtype: u.m**2 + """ + ut.check_range([DiamCircle.magnitude, ">0", "DiamCircle"]) + return (np.pi / 4 * DiamCircle**2) -@u.wraps(u.m, u.m**2, False) +@ut.list_handler() def diam_circle(AreaCircle): - """Return the diameter of a circle.""" - ut.check_range([AreaCircle, ">0", "AreaCircle"]) + """Return the diameter of a circle given its area. + + :param AreaCircle: area of circle + :type AreaCircle: u.m**2 + + :return: diameter of circle + :rtype: u.m + """ + + ut.check_range([AreaCircle.magnitude, ">0", "AreaCircle"]) return np.sqrt(4 * AreaCircle / np.pi) -######################### Hydraulics ######################### +####################### Water Properties ####################### + +#: RE_TRANSITION_PIPE = 2100 -""" """ -K_KOZENY = con.K_KOZENY +#: Table of temperatures and the corresponding water density. +#: +#: WATER_DENSITY_TABLE[0] is a list of water temperatures, in Kelvin. +#: WATER_DENSITY_TABLE[1] is the corresponding densities, in kg/m³. WATER_DENSITY_TABLE = [(273.15, 278.15, 283.15, 293.15, 303.15, 313.15, 323.15, 333.15, 343.15, 353.15, 363.15, 373.15 ), (999.9, 1000, 999.7, 998.2, 995.7, 992.2, 988.1, 983.2, 977.8, 971.8, 965.3, 958.4 ) ] -"""Table of temperatures and the corresponding water density. - -Index[0] is a list of water temperatures, in Kelvin. -Index[1] is the corresponding densities, in kg/m³. -""" -@u.wraps(u.kg/(u.m*u.s), [u.degK], False) +@ut.list_handler() def viscosity_dynamic(temp): + """ + .. deprecated:: + `viscosity_dynamic` is deprecated; use `viscosity_dynamic_water` + instead. + """ + warnings.warn('viscosity_dynamic is deprecated; use ' + 'viscosity_dynamic_water instead.', UserWarning) + return viscosity_dynamic_water(temp) + + +@ut.list_handler() +def viscosity_dynamic_water(Temperature): """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. + :param Temperature: temperature of water + :type Temperature: u.degK + + :return: dynamic viscosity of water + :rtype: u.kg/(u.m*u.s) """ - ut.check_range([temp, ">0", "Temperature in Kelvin"]) - return 2.414 * (10**-5) * 10**(247.8 / (temp-140)) + ut.check_range([Temperature.magnitude, ">0", "Temperature in Kelvin"]) + return 2.414 * (10**-5) * u.kg/(u.m*u.s) * 10**(247.8*u.degK / + (Temperature - 140*u.degK)) -@u.wraps(u.kg/u.m**3, [u.degK], False) -def density_water(temp): +@ut.list_handler() +def density_water(Temperature=None, *, temp=None): """Return the density 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. + :param Temperature: temperature of water + :type Temperature: u.degK + + :param temp: deprecated; use Temperature instead + + :return: density of water + :rtype: u.kg/u.m**3 """ - ut.check_range([temp, ">0", "Temperature in Kelvin"]) + if Temperature is not None and temp is not None: + raise TypeError("density_water received both Temperature and temp") + elif Temperature is None and temp is None: + raise TypeError("density_water missing Temperature argument") + elif temp is not None: + warnings.warn("temp is deprecated; use Temperature instead.", + UserWarning) + Temperature = temp + + ut.check_range([Temperature.magnitude, ">0", "Temperature in Kelvin"]) rhointerpolated = interpolate.CubicSpline(WATER_DENSITY_TABLE[0], - WATER_DENSITY_TABLE[1]) - return rhointerpolated(temp).item() + WATER_DENSITY_TABLE[1]) + Temperature = Temperature.to(u.degK).magnitude + return rhointerpolated(Temperature).item() * u.kg/u.m**3 -@u.wraps(u.m**2/u.s, [u.degK], False) +@ut.list_handler() def viscosity_kinematic(temp): + """ + .. deprecated:: + `viscosity_kinematic` is deprecated; use `viscosity_kinematic_water` + instead. + """ + warnings.warn('viscosity_kinematic is deprecated; use ' + 'viscosity_kinematic_water instead.', UserWarning) + return viscosity_kinematic_water(temp) + + +@ut.list_handler() +def viscosity_kinematic_water(Temperature): """Return the kinematic 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. - """ - ut.check_range([temp, ">0", "Temperature in Kelvin"]) - return (viscosity_dynamic(temp).magnitude - / density_water(temp).magnitude) + :param Temperature: temperature of water + :type Temperature: u.degK + :return: kinematic viscosity of water + :rtype: u.m**2/u.s + """ + ut.check_range([Temperature.magnitude, ">0", "Temperature in Kelvin"]) + return (viscosity_dynamic_water(Temperature) / density_water(Temperature)) -@u.wraps(None, [u.m**3/u.s, u.m, u.m**2/u.s], False) -def re_pipe(FlowRate, Diam, Nu): - """Return the Reynolds Number for a pipe.""" - #Checking input validity - ut.check_range([FlowRate, ">0", "Flow rate"], [Diam, ">0", "Diameter"], - [Nu, ">0", "Nu"]) - return (4 * FlowRate) / (np.pi * Diam * Nu) +####################### Hydraulic Radius ####################### -@u.wraps(u.m, [u.m, u.m], False) @ut.list_handler() -def radius_hydraulic(Width, DistCenter, openchannel): - """Return the hydraulic radius. +def radius_hydraulic(Width, Depth, openchannel): + """ + .. deprecated:: + `radius_hydraulic` is deprecated; use `radius_hydraulic_rect` instead. + """ + warnings.warn('radius_hydraulic is deprecated; use radius_hydraulic_rect ' + 'instead.', UserWarning) + return radius_hydraulic_rect(Width, Depth, openchannel) - Width and DistCenter are length values and openchannel is a boolean. + +@ut.list_handler() +def radius_hydraulic_rect(Width, Depth, OpenChannel): + """Return the hydraulic radius of a rectangular channel given width and + depth of water. + + :param Width: width of channel + :type Width: u.m + :param Depth: depth of water in channel + :type Depth: u.m + :param OpenChannel: true if channel is open, false if closed + :type OpenChannel: boolean + + :return: hydraulic radius of rectangular channel + :rtype: u.m """ - ut.check_range([Width, ">0", "Width"], [DistCenter, ">0", "DistCenter"], - [openchannel, "boolean", "openchannel"]) - if openchannel: - return (Width*DistCenter) / (Width + 2*DistCenter) - # if openchannel is True, the channel is open. Otherwise, the channel - # is assumed to have a top. + ut.check_range([Width.magnitude, ">0", "Width"], + [Depth.magnitude, ">0", "Depth"], + [OpenChannel, "boolean", "OpenChannel"]) + if OpenChannel: + return ((Width*Depth) / (Width + 2*Depth)) else: - return (Width*DistCenter) / (2 * (Width+DistCenter)) + return ((Width*Depth) / (2 * (Width+Depth))) -@u.wraps(u.m, [u.m**2, u.m], False) +@ut.list_handler() def radius_hydraulic_general(Area, PerimWetted): - """Return the general hydraulic radius.""" - ut.check_range([Area, ">0", "Area"], [PerimWetted, ">0", "Wetted perimeter"]) - return Area / PerimWetted + """ + .. deprecated:: + `radius_hydraulic_general` is deprecated; use + `radius_hydraulic_channel` instead. + """ + warnings.warn('radius_hydraulic_general is deprecated; use ' + 'radius_hydraulic_channel instead.', UserWarning) + return radius_hydraulic_channel(Area, PerimWetted) -@u.wraps(None, [u.m**3/u.s, u.m, u.m, u.m**2/u.s], False) -def re_rect(FlowRate, Width, DistCenter, Nu, openchannel): - """Return the Reynolds Number for a rectangular channel.""" - #Checking input validity - inputs not checked here are checked by - #functions this function calls. - ut.check_range([FlowRate, ">0", "Flow rate"], [Nu, ">0", "Nu"]) - return (4 * FlowRate - * radius_hydraulic(Width, DistCenter, openchannel).magnitude - / (Width * DistCenter * Nu)) - #Reynolds Number for rectangular channel; open = False if all sides - #are wetted; l = Diam and Diam = 4*R.h +@ut.list_handler() +def radius_hydraulic_channel(Area, PerimWetted): + """Return the hydraulic radius of a general channel given cross sectional + area and wetted perimeter. + + :param Area: cross sectional area of channel + :type Area: u.m**2 + :param PerimWetted: wetted perimeter of channel + :type PerimWetted: u.m + :return: hydraulic radius of general channel + :rtype: u.m + """ + ut.check_range([Area.magnitude, ">0", "Area"], + [PerimWetted.magnitude, ">0", "Wetted perimeter"]) + return (Area / PerimWetted) + +####################### Reynolds Number ####################### + + +@ut.list_handler() +def re_pipe(FlowRate, Diam, Nu): + """Return the Reynolds number of flow through a pipe. -@u.wraps(None, [u.m/u.s, u.m**2, u.m, u.m**2/u.s], False) + :param FlowRate: flow rate through pipe + :type FlowRate: u.m**3/u.s + :param Diam: diameter of pipe + :type Diam: u.m + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + + :return: Reynolds number of flow through pipe + :rtype: u.dimensionless + """ + ut.check_range([FlowRate.magnitude, ">0", "Flow rate"], + [Diam.magnitude, ">0", "Diameter"], + [Nu.magnitude, ">0", "Nu"]) + return ((4 * FlowRate) / (np.pi * Diam * Nu)).to(u.dimensionless) + + +@ut.list_handler() +def re_rect(FlowRate, Width, Depth, Nu, OpenChannel=None, *, openchannel=None): + """Return the Reynolds number of flow through a rectangular channel. + + :param FlowRate: flow rate through channel + :type FlowRate: u.m**3/u.s + :param Width: width of channel + :type Width: u.m + :param Depth: depth of water in channel + :type Depth: u.m + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + :param OpenChannel: true if channel is open, false if closed + :type OpenChannel: boolean + + :param openchannel: deprecated; use OpenChannel instead + + :return: Reynolds number of flow through rectangular channel + :rtype: u.dimensionless + """ + ut.check_range([FlowRate.magnitude, ">0", "Flow rate"], + [Nu.magnitude, ">0", "Nu"]) + if OpenChannel is not None and openchannel is not None: + raise TypeError("re_rect received both OpenChannel and openchannel") + elif OpenChannel is None and openchannel is None: + raise TypeError("re_rect missing OpenChannel argument") + elif openchannel is not None: + warnings.warn("openchannel is deprecated; use OpenChannel instead.", + UserWarning) + OpenChannel = openchannel + + return (4 * FlowRate * radius_hydraulic_rect(Width, Depth, OpenChannel) + / (Width * Depth * Nu)).to(u.dimensionless) + + +@ut.list_handler() def re_general(Vel, Area, PerimWetted, Nu): - """Return the Reynolds Number for a general cross section.""" - #Checking input validity - inputs not checked here are checked by - #functions this function calls. - ut.check_range([Vel, ">=0", "Velocity"], [Nu, ">0", "Nu"]) - return 4 * radius_hydraulic_general(Area, PerimWetted).magnitude * Vel / Nu + """ + .. deprecated:: + `re_general` is deprecated; use `re_channel` instead. + """ + warnings.warn('re_general is deprecated; use re_channel instead.', + UserWarning) + return re_channel(Vel, Area, PerimWetted, Nu) + + +@ut.list_handler() +def re_channel(Vel, Area, PerimWetted, Nu): + """Return the Reynolds number of flow through a general cross section. + + :param Vel: velocity of fluid + :type Vel: u.m/u.s + :param Area: cross sectional area of channel + :type Area: u.m**2 + :param PerimWetted: wetted perimeter of channel + :type PerimWetted: u.m + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + + :return: Reynolds number of flow through general cross section + :rtype: u.dimensionless + """ + ut.check_range([Vel.magnitude, ">=0", "Velocity"], + [Nu.magnitude, ">0", "Nu"]) + return (4 * radius_hydraulic_channel(Area, PerimWetted) * Vel / Nu).to(u.dimensionless) +########################### Friction ########################### -@u.wraps(None, [u.m**3/u.s, u.m, u.m**2/u.s, u.m], False) + +@ut.list_handler() def fric(FlowRate, Diam, Nu, PipeRough): + """ + .. deprecated:: + `fric` is deprecated; use `fric_pipe` instead. + """ + warnings.warn('fric is deprecated; use fric_pipe instead', UserWarning) + return fric_pipe(FlowRate, Diam, Nu, PipeRough) + + +@ut.list_handler() +def fric_pipe(FlowRate, Diam, Nu, Roughness): """Return the friction factor for pipe flow. - This equation applies to both laminar and turbulent flows. + For laminar flow, the friction factor is 64 is divided the Reynolds number. + For turbulent flows, friction factor is calculated using the Swamee-Jain + equation, which works best for Re > 3000 and ε/Diam < 0.02. + + :param FlowRate: flow rate through pipe + :type FlowRate: u.m**3/u.s + :param Diam: diameter of pipe + :type Diam: u.m + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + :param Roughness: roughness of pipe + :type Roughness: u.m + + :return: friction factor of flow through pipe + :rtype: u.dimensionless """ - #Checking input validity - inputs not checked here are checked by - #functions this function calls. - ut.check_range([PipeRough, "0-1", "Pipe roughness"]) + ut.check_range([Roughness.magnitude, ">=0", "Pipe roughness"]) if re_pipe(FlowRate, Diam, Nu) >= RE_TRANSITION_PIPE: - #Swamee-Jain friction factor for turbulent flow; best for - #Re>3000 and ε/Diam < 0.02 - f = (0.25 / (np.log10(PipeRough / (3.7 * Diam) + f = (0.25 / (np.log10(Roughness / (3.7 * Diam) + 5.74 / re_pipe(FlowRate, Diam, Nu) ** 0.9 ) ) ** 2 ) else: f = 64 / re_pipe(FlowRate, Diam, Nu) - return f + return f * u.dimensionless -@u.wraps(None, [u.m**3/u.s, u.m, u.m, u.m**2/u.s, u.m], False) @ut.list_handler() -def fric_rect(FlowRate, Width, DistCenter, Nu, PipeRough, openchannel): - """Return the friction factor for a rectangular channel.""" - #Checking input validity - inputs not checked here are checked by - #functions this function calls. - ut.check_range([PipeRough, "0-1", "Pipe roughness"]) - if re_rect(FlowRate,Width,DistCenter,Nu,openchannel) >= RE_TRANSITION_PIPE: - #Swamee-Jain friction factor adapted for rectangular channel. - #Diam = 4*R_h in this case. - return (0.25 - / (np.log10((PipeRough +def fric_rect(FlowRate, Width, Depth, Nu, Roughness=None, OpenChannel=None, *, + PipeRough=None, openchannel=None): + """Return the friction factor of a rectangular channel. + + The Swamee-Jain equation is adapted for a rectangular channel. + + :param FlowRate: flow rate through channel + :type FlowRate: u.m**3/u.s + :param Width: width of channel + :type Width: u.m + :param Depth: depth of water in channel + :type Depth: u.m + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + :param Roughness: roughness of channel + :type Roughness: u.m + :param OpenChannel: true if channel is open, false if closed + :type OpenChannel: boolean + + :param PipeRough: deprecated; use Roughness instead + :param openchannel: deprecated; use OpenChannel instead + + :return: friction factor of flow through rectangular channel + :rtype: u.dimensionless + """ + if Roughness is not None and PipeRough is not None: + raise TypeError("fric_rect received both Roughness and PipeRough") + elif Roughness is None and PipeRough is None: + raise TypeError("fric_rect missing Roughness argument") + elif OpenChannel is not None and openchannel is not None: + raise TypeError("fric_rect received both OpenChannel and openchannel") + elif OpenChannel is None and openchannel is None: + raise TypeError("fric_rect missing OpenChannel argument") + else: + if PipeRough is not None: + warnings.warn("PipeRough is deprecated; use Roughness instead.", + UserWarning) + Roughness = PipeRough + if openchannel is not None: + warnings.warn("openchannel is deprecated; use OpenChannel instead.", + UserWarning) + OpenChannel = openchannel + + ut.check_range([Roughness.magnitude, ">=0", "Pipe roughness"]) + if re_rect(FlowRate, Width, Depth, Nu, OpenChannel) >= RE_TRANSITION_PIPE: + # Diam = 4*R_h in adapted Swamee-Jain equation + return (0.25 * u.dimensionless + / (np.log10((Roughness / (3.7 * 4 - * radius_hydraulic(Width, DistCenter, - openchannel).magnitude + * radius_hydraulic_rect(Width, Depth, + OpenChannel) ) ) - + (5.74 / (re_rect(FlowRate, Width, DistCenter, - Nu, openchannel) ** 0.9) + + (5.74 / (re_rect(FlowRate, Width, Depth, + Nu, OpenChannel) ** 0.9) ) ) - ) ** 2 + ) ** 2 ) else: - return 64 / re_rect(FlowRate, Width, DistCenter, Nu, openchannel) + return 64 * u.dimensionless / re_rect(FlowRate, Width, Depth, Nu, + OpenChannel) -@u.wraps(None, [u.m**2, u.m, u.m/u.s, u.m**2/u.s, u.m], False) @ut.list_handler() def fric_general(Area, PerimWetted, Vel, Nu, PipeRough): - """Return the friction factor for a general channel.""" - #Checking input validity - inputs not checked here are checked by - #functions this function calls. - ut.check_range([PipeRough, "0-1", "Pipe roughness"]) - if re_general(Vel, Area, PerimWetted, Nu) >= RE_TRANSITION_PIPE: - #Swamee-Jain friction factor adapted for any cross-section. - #Diam = 4*R*h - f= (0.25 / - (np.log10((PipeRough - / (3.7 * 4 - * radius_hydraulic_general(Area, PerimWetted).magnitude + """ + .. deprecated:: + `fric_general` is deprecated; use `fric_channel` instead. + """ + warnings.warn('fric_general is deprecated; use fric_channel instead.', + UserWarning) + return fric_channel(Area, PerimWetted, Vel, Nu, PipeRough) + + +@ut.list_handler() +def fric_channel(Area, PerimWetted, Vel, Nu, Roughness): + """Return the friction factor for a general channel. + + The Swamee-Jain equation is adapted for a general cross-section. + + :param Area: cross sectional area of channel + :type Area: u.m**2 + :param PerimWetted: wetted perimeter of channel + :type PerimWetted: u.m + :param Vel: velocity of fluid + :type Vel: u.m/u.s + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + :param PipeRough: roughness of channel + :type PipeRough: u.m + + :return: friction factor for flow through general channel + :rtype: u.dimensionless + """ + ut.check_range([Roughness.magnitude, ">=0", "Pipe roughness"]) + if re_channel(Vel, Area, PerimWetted, Nu) >= RE_TRANSITION_PIPE: + # Diam = 4*R_h in adapted Swamee-Jain equation + f = (0.25 / + (np.log10((Roughness + / (3.7 * 4 + * radius_hydraulic_channel(Area, PerimWetted) + ) + ) + + (5.74 + / re_channel(Vel, Area, PerimWetted, Nu) ** 0.9 ) ) - + (5.74 - / re_general(Vel, Area, PerimWetted, Nu) ** 0.9 - ) - ) - ) ** 2 - ) + ) ** 2 + ) else: - f = 64 / re_general(Vel, Area, PerimWetted, Nu) - return f + f = 64 / re_channel(Vel, Area, PerimWetted, Nu) + return f * u.dimensionless + +######################### Head Loss ######################### -@u.wraps(u.m, [u.m**3/u.s, u.m, u.m, u.m**2/u.s, u.m], False) +@ut.list_handler() def headloss_fric(FlowRate, Diam, Length, Nu, PipeRough): + """ + .. deprecated:: + `headloss_fric` is deprecated; use `headloss_major_pipe` instead. + """ + warnings.warn('headloss_fric is deprecated; use headloss_major_pipe instead', + UserWarning) + return headloss_major_pipe(FlowRate, Diam, Length, Nu, PipeRough) + + +@ut.list_handler() +def headloss_major_pipe(FlowRate, Diam, Length, Nu, Roughness): """Return the major head loss (due to wall shear) in a pipe. - This equation applies to both laminar and turbulent flows. + This function applies to both laminar and turbulent flows. + + :param FlowRate: flow rate through pipe + :type FlowRate: u.m**3/u.s + :param Diam: diameter of pipe + :type Diam: u.m + :param Length: length of pipe + :type Length: u.m + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + :param Roughness: roughness of pipe + :type Roughness: u.m + + :return: major head loss in pipe + :rtype: u.m """ - #Checking input validity - inputs not checked here are checked by - #functions this function calls. - ut.check_range([Length, ">0", "Length"]) - return (fric(FlowRate, Diam, Nu, PipeRough) - * 8 / (gravity.magnitude * np.pi**2) + ut.check_range([Length.magnitude, ">0", "Length"]) + return (fric_pipe(FlowRate, Diam, Nu, Roughness) + * 8 / (u.gravity * np.pi**2) * (Length * FlowRate**2) / Diam**5 - ) + ).to(u.m) -@u.wraps(u.m, [u.m**3/u.s, u.m], False) +@ut.list_handler() def headloss_exp(FlowRate, Diam, KMinor): - """Return the minor head loss (due to expansions) in a pipe. + """ + .. deprecated:: + `headloss_exp` is deprecated; use `headloss_minor_pipe` instead. + """ + warnings.warn('headloss_exp is deprecated; use headloss_minor_pipe instead', + UserWarning) + return headloss_minor_pipe(FlowRate, Diam, KMinor) - This equation applies to both laminar and turbulent flows. + +@ut.list_handler() +def headloss_minor_pipe(FlowRate, Diam, KMinor): + """Return the minor head loss (due to changes in geometry) in a pipe. + + This function applies to both laminar and turbulent flows. + + :param FlowRate: flow rate through pipe + :type FlowRate: u.m**3/u.s + :param Diam: diameter of pipe + :type Diam: u.m + :param KMinor: minor loss coefficient + :type KMinor: u.dimensionless or unitless + + :return: minor head loss in pipe + :rtype: u.m """ - #Checking input validity - ut.check_range([FlowRate, ">0", "Flow rate"], [Diam, ">0", "Diameter"], + ut.check_range([FlowRate.magnitude, ">0", "Flow rate"], + [Diam.magnitude, ">0", "Diameter"], [KMinor, ">=0", "K minor"]) - return KMinor * 8 / (gravity.magnitude * np.pi**2) * FlowRate**2 / Diam**4 + return (KMinor * 8 / (u.gravity * np.pi**2) * FlowRate**2 / Diam**4).to(u.m) -@u.wraps(u.m, [u.m**3/u.s, u.m, u.m, u.m**2/u.s, u.m], False) +@ut.list_handler() def headloss(FlowRate, Diam, Length, Nu, PipeRough, KMinor): + """ + .. deprecated:: + `headloss` is deprecated; use `headloss_pipe` instead. + """ + warnings.warn('headloss is deprecated; use headloss_pipe instead', + UserWarning) + return headloss_pipe(FlowRate, Diam, Length, Nu, PipeRough, KMinor) + + +@ut.list_handler() +def headloss_pipe(FlowRate, Diam, Length, Nu, Roughness, KMinor): """Return the total head loss from major and minor losses in a pipe. - This equation applies to both laminar and turbulent flows. + This function applies to both laminar and turbulent flows. + + :param FlowRate: flow rate through pipe + :type FlowRate: u.m**3/u.s + :param Diam: diameter of pipe + :type Diam: u.m + :param Length: length of pipe + :type Length: u.m + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + :param Roughness: roughness of pipe + :type Roughness: u.m + :param KMinor: minor loss coefficient + :type KMinor: u.dimensionless or unitless + + :return: total head loss in pipe + :rtype: u.m + """ + return (headloss_major_pipe(FlowRate, Diam, Length, Nu, Roughness) + + headloss_minor_pipe(FlowRate, Diam, KMinor)) + + +@ut.list_handler() +def headloss_fric_rect(FlowRate, Width, Depth, Length, Nu, PipeRough, openchannel): + """ + .. deprecated:: + `headloss_fric_rect` is deprecated; use `headloss_major_rect` instead. """ - #Inputs do not need to be checked here because they are checked by - #functions this function calls. - return (headloss_fric(FlowRate, Diam, Length, Nu, PipeRough).magnitude - + headloss_exp(FlowRate, Diam, KMinor).magnitude) + warnings.warn('headloss_fric_rect is deprecated; use headloss_major_rect instead', + UserWarning) + return headloss_major_rect(FlowRate, Width, Depth, Length, Nu, PipeRough, openchannel) -@u.wraps(u.m, [u.m**3/u.s, u.m, u.m, u.m, u.m**2/u.s, u.m], False) -def headloss_fric_rect(FlowRate, Width, DistCenter, Length, Nu, PipeRough, openchannel): +@ut.list_handler() +def headloss_major_rect(FlowRate, Width, Depth, Length, Nu, Roughness, OpenChannel): """Return the major head loss due to wall shear in a rectangular channel. This equation applies to both laminar and turbulent flows. + + :param FlowRate: flow rate through channel + :type FlowRate: u.m**3/u.s + :param Width: width of channel + :type Width: u.m + :param Depth: depth of water in channel + :type Depth: u.m + :param Length: length of channel + :type Length: u.m + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + :param Roughness: roughness of channel + :type Roughness: u.m + :param OpenChannel: true if channel is open, false if closed + :type OpenChannel: boolean + + :return: major head loss in rectangular channel + :rtype: u.m """ - #Checking input validity - inputs not checked here are checked by - #functions this function calls. - ut.check_range([Length, ">0", "Length"]) - return (fric_rect(FlowRate, Width, DistCenter, Nu, - PipeRough, openchannel) + ut.check_range([Length.magnitude, ">0", "Length"]) + return (fric_rect(FlowRate, Width, Depth, Nu, + Roughness, OpenChannel) * Length - / (4 * radius_hydraulic(Width, DistCenter, openchannel).magnitude) + / (4 * radius_hydraulic_rect(Width, Depth, OpenChannel)) * FlowRate**2 - / (2 * gravity.magnitude * (Width*DistCenter)**2) - ) + / (2 * u.gravity * (Width*Depth)**2) + ).to(u.m) -@u.wraps(u.m, [u.m**3/u.s, u.m, u.m], False) -def headloss_exp_rect(FlowRate, Width, DistCenter, KMinor): +@ut.list_handler() +def headloss_exp_rect(FlowRate, Width, Depth, KMinor): + """ + .. deprecated:: + `headloss_exp_rect` is deprecated; use `headloss_minor_rect` instead. + """ + warnings.warn('headloss_exp_rect is deprecated; use headloss_minor_rect instead', + UserWarning) + return headloss_minor_rect(FlowRate, Width, Depth, KMinor) + + +@ut.list_handler() +def headloss_minor_rect(FlowRate, Width, Depth, KMinor): """Return the minor head loss due to expansion in a rectangular channel. This equation applies to both laminar and turbulent flows. + + :param FlowRate: flow rate through channel + :type FlowRate: u.m**3/u.s + :param Width: width of channel + :type Width: u.m + :param Depth: depth of water in channel + :type Depth: u.m + :param KMinor: minor loss coefficient + :type KMinor: u.dimensionless or unitless + + :return: minor head loss in rectangular channel + :rtype: u.m """ - #Checking input validity - ut.check_range([FlowRate, ">0", "Flow rate"], [Width, ">0", "Width"], - [DistCenter, ">0", "DistCenter"], [KMinor, ">=0", "K minor"]) + ut.check_range([FlowRate.magnitude, ">0", "Flow rate"], + [Width.magnitude, ">0", "Width"], + [Depth.magnitude, ">0", "Depth"], + [KMinor, ">=0", "K minor"]) return (KMinor * FlowRate**2 - / (2 * gravity.magnitude * (Width*DistCenter)**2) - ) + / (2 * u.gravity * (Width*Depth)**2) + ).to(u.m) -@u.wraps(u.m, [u.m**3/u.s, u.m, u.m, u.m, None, u.m**2/u.s, u.m], False) -def headloss_rect(FlowRate, Width, DistCenter, Length, - KMinor, Nu, PipeRough, openchannel): - """Return the total head loss in a rectangular channel. +@ut.list_handler() +def headloss_rect(FlowRate, Width, Depth, Length, KMinor, Nu, Roughness=None, + OpenChannel=None, *, PipeRough=None, openchannel=None): + """Return the total head loss from major and minor losses in a rectangular + channel. - Total head loss is a combination of the major and minor losses. This equation applies to both laminar and turbulent flows. + + :param FlowRate: flow rate through channel + :type FlowRate: u.m**3/u.s + :param Width: width of channel + :type Width: u.m + :param Depth: depth of water in channel + :type Depth: u.m + :param Length: length of channel + :type Length: u.m + :param KMinor: minor loss coefficient + :type KMinor: u.dimensionless or unitless + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + :param Roughness: roughness of channel + :type Roughness: u.m + :param OpenChannel: true if channel is open, false if closed + :type OpenChannel: boolean + + :param PipeRough: deprecated; use Roughness instead + :type openchannel: deprecated; use OpenChannel instead + + :return: total head loss in rectangular channel + :rtype: u.m """ - #Inputs do not need to be checked here because they are checked by - #functions this function calls. - return (headloss_exp_rect(FlowRate, Width, DistCenter, KMinor).magnitude - + headloss_fric_rect(FlowRate, Width, DistCenter, Length, - Nu, PipeRough, openchannel).magnitude) + if Roughness is not None and PipeRough is not None: + raise TypeError("headloss_rect received both Roughness and PipeRough") + elif Roughness is None and PipeRough is None: + raise TypeError("headloss_rect missing Roughness argument") + elif OpenChannel is not None and openchannel is not None: + raise TypeError("headloss_rect received both OpenChannel and openchannel") + elif OpenChannel is None and openchannel is None: + raise TypeError("headloss_rect missing OpenChannel argument") + else: + if PipeRough is not None: + warnings.warn("PipeRough is deprecated; use Roughness instead.", + UserWarning) + Roughness = PipeRough + if openchannel is not None: + warnings.warn("openchannel is deprecated; use OpenChannel instead.", + UserWarning) + OpenChannel = openchannel + + return (headloss_minor_rect(FlowRate, Width, Depth, KMinor) + + headloss_major_rect(FlowRate, Width, Depth, Length, + Nu, Roughness, OpenChannel)) -@u.wraps(u.m, [u.m**2, u.m, u.m/u.s, u.m, u.m**2/u.s, u.m], False) +@ut.list_handler() def headloss_fric_general(Area, PerimWetted, Vel, Length, Nu, PipeRough): - """Return the major head loss due to wall shear in the general case. + """ + .. deprecated:: + `headloss_fric_general` is deprecated; use `headloss_major_channel` instead. + """ + warnings.warn('headloss_fric_general` is deprecated; use `headloss_major_channel` instead', + UserWarning) + return headloss_major_channel(Area, PerimWetted, Vel, Length, Nu, PipeRough) + + +@ut.list_handler() +def headloss_major_channel(Area, PerimWetted, Vel, Length, Nu, Roughness): + """Return the major head loss due to wall shear in a general channel. This equation applies to both laminar and turbulent flows. + + :param Area: cross sectional area of channel + :type Area: u.m**2 + :param PerimWetted: wetted perimeter of channel + :type PerimWetted: u.m + :param Vel: velocity of fluid + :type Vel: u.m/u.s + :param Length: length of channel + :type Length: u.m + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + :param Roughness: roughness of channel + :type Roughness: u.m + + :return: major head loss in general channel + :rtype: u.m """ - #Checking input validity - inputs not checked here are checked by - #functions this function calls. - ut.check_range([Length, ">0", "Length"]) - return (fric_general(Area, PerimWetted, Vel, Nu, PipeRough) * Length - / (4 * radius_hydraulic_general(Area, PerimWetted).magnitude) - * Vel**2 / (2*gravity.magnitude) - ) + ut.check_range([Length.magnitude, ">0", "Length"]) + return (fric_channel(Area, PerimWetted, Vel, Nu, Roughness) * Length + / (4 * radius_hydraulic_channel(Area, PerimWetted)) + * Vel**2 / (2*u.gravity) + ).to(u.m) -@u.wraps(u.m, [u.m/u.s], False) +@ut.list_handler() def headloss_exp_general(Vel, KMinor): - """Return the minor head loss due to expansion in the general case. + """ + .. deprecated:: + `headloss_exp_general` is deprecated; use `headloss_minor_channel` instead. + """ + warnings.warn('headloss_exp_general` is deprecated; use `headloss_minor_channel` instead', + UserWarning) + return headloss_minor_channel(Vel, KMinor) + + +@ut.list_handler() +def headloss_minor_channel(Vel, KMinor): + """Return the minor head loss due to expansion in a general channel. This equation applies to both laminar and turbulent flows. + + :param Vel: velocity of fluid + :type Vel: u.m/u.s + :param KMinor: minor loss coefficient + :type KMinor: u.dimensionless or unitless + + :return: minor head loss in general channel + :rtype: u.m """ - #Checking input validity - ut.check_range([Vel, ">0", "Velocity"], [KMinor, '>=0', 'K minor']) - return KMinor * Vel**2 / (2*gravity.magnitude) + ut.check_range([Vel.magnitude, ">0", "Velocity"], + [KMinor, '>=0', 'K minor']) + return (KMinor * Vel**2 / (2*u.gravity)).to(u.m) -@u.wraps(u.m, [u.m**2, u.m/u.s, u.m, u.m, None, u.m**2/u.s, u.m], False) +@ut.list_handler() def headloss_gen(Area, Vel, PerimWetted, Length, KMinor, Nu, PipeRough): - """Return the total head lossin the general case. + """ + .. deprecated:: + `headloss_gen` is deprecated; use `headloss_channel` instead. + """ + warnings.warn('headloss_gen` is deprecated; use `headloss_channel` instead', + UserWarning) + return headloss_channel(Area, Vel, PerimWetted, Length, KMinor, Nu, PipeRough) + + +@ut.list_handler() +def headloss_channel(Area, Vel, PerimWetted, Length, KMinor, Nu, Roughness): + """Return the total head loss from major and minor losses in a general + channel. - Total head loss is a combination of major and minor losses. This equation applies to both laminar and turbulent flows. + + :param Area: cross sectional area of channel + :type Area: u.m**2 + :param Vel: velocity of fluid + :type Vel: u.m/u.s + :param PerimWetted: wetted perimeter of channel + :type PerimWetted: u.m + :param Length: length of channel + :type Length: u.m + :param KMinor: minor loss coefficient + :type KMinor: u.dimensionless or unitless + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + :param Roughness: roughness of channel + :type Roughness: u.m + + :return: total head loss in general channel + :rtype: u.m """ - #Inputs do not need to be checked here because they are checked by - #functions this function calls. - return (headloss_exp_general(Vel, KMinor).magnitude - + headloss_fric_general(Area, PerimWetted, Vel, - Length, Nu, PipeRough).magnitude) + return (headloss_minor_channel(Vel, KMinor) + + headloss_major_channel(Area, PerimWetted, Vel, + Length, Nu, Roughness)).to(u.m) -@u.wraps(u.m, [u.m**3/u.s, u.m, u.m, None, - u.m**2/u.s, u.m], False) -def headloss_manifold(FlowRate, Diam, Length, KMinor, Nu, PipeRough, NumOutlets): - """Return the total head loss through the manifold.""" - #Checking input validity - inputs not checked here are checked by - #functions this function calls. +@ut.list_handler() +def headloss_manifold(FlowRate, Diam, Length, KMinor, Nu, Roughness=None, NumOutlets=None, *, PipeRough=None): + """Return the total head loss through the manifold. + + :param FlowRate: flow rate through manifold + :type FlowRate: u.m**3/u.s + :param Diam: diameter of manifold + :type Diam: u.m + :param Length: length of manifold + :type Length: u.m + :param KMinor: minor loss coefficient + :type KMinor: u.dimensionless or unitless + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + :param Roughness: roughness of manifold + :type Roughness: u.m + :param NumOutlets: number of outlets from manifold + :type NumOutlets: u.dimensionless or unitless + + :param PipeRough: deprecated; use Roughness instead + + :return: total headloss through manifold + :rtype: u.m + """ ut.check_range([NumOutlets, ">0, int", 'Number of outlets']) - return (headloss(FlowRate, Diam, Length, Nu, PipeRough, KMinor).magnitude - * ((1/3 ) + + if Roughness is not None and PipeRough is not None: + raise TypeError("headloss_manifold received both Roughness and PipeRough") + elif Roughness is None and PipeRough is None: + raise TypeError("headloss_manifold missing Roughness argument") + elif NumOutlets is None: + raise TypeError("headloss_manifold missing NumOutlets argument") + elif PipeRough is not None: + warnings.warn("PipeRough is deprecated; use Roughness instead.", + UserWarning) + Roughness = PipeRough + + return (headloss_pipe(FlowRate, Diam, Length, Nu, Roughness, KMinor) + * ((1/3) + (1 / (2*NumOutlets)) + (1 / (6*NumOutlets**2)) - ) - ) + ) + ).to(u.m) + +@ut.list_handler() def elbow_minor_loss(q, id_, k): - vel = q / area_circle(id_) - minor_loss = k * vel ** 2 / (2 * con.GRAVITY) + """ + .. deprecated:: + `elbow_minor_loss` is deprecated; use `headloss_minor_elbow` instead. + """ + warnings.warn('elbow_minor_loss is deprecated; use headloss_minor_elbow instead', + UserWarning) + return headloss_minor_elbow(q, id_, k) + + +@ut.list_handler() +def headloss_minor_elbow(FlowRate, Diam, KMinor): + """Return the minor head loss (due to changes in geometry) in an elbow. + + :param FlowRate: flow rate through pipe + :type FlowRate: u.m**3/u.s + :param Diam: diameter of pipe + :type Diam: u.m + :param KMinor: minor loss coefficient + :type KMinor: u.dimensionless or unitless + + :return: minor head loss in pipe + :rtype: u.m + """ + vel = FlowRate / area_circle(Diam) + minor_loss = KMinor * vel ** 2 / (2 * u.gravity) return minor_loss.to(u.m) -@u.wraps(u.m**3/u.s, [u.m, u.m], False) +######################### Orifices ######################### + + @ut.list_handler() def flow_orifice(Diam, Height, RatioVCOrifice): - """Return the flow rate of the orifice.""" - #Checking input validity - ut.check_range([Diam, ">0", "Diameter"], + """Return the flow rate of the orifice. + + :param Diam: diameter of orifice + :type Diam: u.m + :param Height: piezometric height of orifice + :type Height: u.m + :param RatioVCOrifice: vena contracta ratio of orifice + :type RatioVCOrifice: u.dimensionless or unitless + + :return: flow rate of orifice + :rtype: u.m**3/u.s + """ + ut.check_range([Diam.magnitude, ">0", "Diameter"], [RatioVCOrifice, "0-1", "VC orifice ratio"]) - if Height > 0: - return (RatioVCOrifice * area_circle(Diam).magnitude - * np.sqrt(2 * gravity.magnitude * Height)) + if Height.magnitude > 0: + return (RatioVCOrifice * area_circle(Diam) + * np.sqrt(2 * u.gravity * Height)).to(u.m**3/u.s) else: - return 0 + return 0 * u.m**3/u.s -#Deviates from the MathCad at the 6th decimal place. Worth investigating or not? -@u.wraps(u.m**3/u.s, [u.m, u.m], False) @ut.list_handler() def flow_orifice_vert(Diam, Height, RatioVCOrifice): - """Return the vertical flow rate of the orifice.""" - #Checking input validity + """Return the vertical flow rate of the orifice. + + :param Diam: diameter of orifice + :type Diam: u.m + :param Height: piezometric height of orifice + :type Height: u.m + :param RatioVCOrifice: vena contracta ratio of orifice + :type RatioVCOrifice: u.dimensionless or unitless + + :return: vertical flow rate of orifice + :rtype: u.m**3/u.s + """ ut.check_range([RatioVCOrifice, "0-1", "VC orifice ratio"]) + Diam = Diam.to(u.m) + Height = Height.to(u.m) if Height > -Diam / 2: - flow_vert = integrate.quad(lambda z: (Diam * np.sin(np.arccos(z/(Diam/2))) - * np.sqrt(Height - z) - ), - - Diam / 2, - min(Diam/2, Height)) - return flow_vert[0] * RatioVCOrifice * np.sqrt(2 * gravity.magnitude) + flow_vert = integrate.quad(lambda z: (Diam*np.sin(np.arccos(z*u.m/(Diam/2))) + * np.sqrt(Height - z*u.m) + ).magnitude, + - Diam.magnitude / 2, + min(Diam/2, Height).magnitude) + return (flow_vert[0] * u.m**2.5 * RatioVCOrifice * + np.sqrt(2 * u.gravity)).to(u.m**3/u.s) else: - return 0 + return 0 * u.m**3/u.s -@u.wraps(u.m, [u.m, None, u.m**3/u.s], False) +@ut.list_handler() def head_orifice(Diam, RatioVCOrifice, FlowRate): - """Return the head of the orifice.""" - #Checking input validity - ut.check_range([Diam, ">0", "Diameter"], [FlowRate, ">0", "Flow rate"], + """Return the piezometric head of the orifice. + + :param Diam: diameter of orifice + :type Diam: u.m + :param RatioVCOrifice: vena contracta ratio of orifice + :type RatioVCOrifice: u.dimensionless or unitless + :param FlowRate: flow rate of orifice + :type FlowRate: u.m**3/u.s + + :return: head of orifice + :rtype: u.m + """ + ut.check_range([Diam.magnitude, ">0", "Diameter"], + [FlowRate.magnitude, ">0", "Flow rate"], [RatioVCOrifice, "0-1", "VC orifice ratio"]) return ((FlowRate - / (RatioVCOrifice * area_circle(Diam).magnitude) + / (RatioVCOrifice * area_circle(Diam)) )**2 - / (2*gravity.magnitude) - ) + / (2*u.gravity) + ).to(u.m) -@u.wraps(u.m**2, [u.m, None, u.m**3/u.s], False) +@ut.list_handler() def area_orifice(Height, RatioVCOrifice, FlowRate): - """Return the area of the orifice.""" - #Checking input validity - ut.check_range([Height, ">0", "Height"], [FlowRate, ">0", "Flow rate"], + """Return the area of the orifice. + + :param Height: piezometric height of orifice + :type Height: u.m + :param RatioVCOrifice: vena contracta ratio of orifice + :type RatioVCOrifice: u.dimensionless or unitless + :param FlowRate: flow rate of orifice + :type FlowRate: u.m**3/u.s + + :return: area of orifice + :rtype: u.m**2 + """ + ut.check_range([Height.magnitude, ">0", "Height"], + [FlowRate.magnitude, ">0", "Flow rate"], [RatioVCOrifice, "0-1, >0", "VC orifice ratio"]) - return FlowRate / (RatioVCOrifice * np.sqrt(2 * gravity.magnitude * Height)) + return (FlowRate / (RatioVCOrifice * np.sqrt(2 * u.gravity * + Height))).to(u.m**2) -@u.wraps(None, [u.m**3/u.s, None, u.m, u.m], False) -def num_orifices(FlowPlant, RatioVCOrifice, HeadLossOrifice, DiamOrifice): - """Return the number of orifices.""" - #Inputs do not need to be checked here because they are checked by - #functions this function calls. - return np.ceil(area_orifice(HeadLossOrifice, RatioVCOrifice, - FlowPlant).magnitude - / area_circle(DiamOrifice).magnitude) +@ut.list_handler() +def num_orifices(FlowRate, RatioVCOrifice, HeadLossOrifice, DiamOrifice): + """Return the number of orifices. + + :param FlowRate: flow rate of orifice + :type FlowRate: u.m**3/u.s + :param RatioVCOrifice: vena contracta ratio of orifice + :type RatioVCOrifice: u.dimensionless or unitless + :param HeadLossOrifice: head loss of orifice + :type HeadLossOrifice: u.m + :param DiamOrifice: diameter of orifice + :type DiamOrifice: u.m + + :return: number of orifices + :rtype: u.dimensionless + """ + return np.ceil(area_orifice(HeadLossOrifice, RatioVCOrifice, FlowRate) + / area_circle(DiamOrifice)).to(u.dimensionless) + +########################### Flows ########################### -# Here we define functions that return the flow rate. -@u.wraps(u.m**3/u.s, [u.m, u.m**2/u.s], False) +@ut.list_handler() def flow_transition(Diam, Nu): """Return the flow rate for the laminar/turbulent transition. - This equation is used in some of the other equations for flow. - """ - #Checking input validity - ut.check_range([Diam, ">0", "Diameter"], [Nu, ">0", "Nu"]) - return np.pi * Diam * RE_TRANSITION_PIPE * Nu / 4 - - -@u.wraps(u.m**3/u.s, [u.m, u.m, u.m, u.m**2/u.s], False) -def flow_hagen(Diam, HeadLossFric, Length, Nu): - """Return the flow rate for laminar flow with only major losses.""" - #Checking input validity - ut.check_range([Diam, ">0", "Diameter"], [Length, ">0", "Length"], - [HeadLossFric, ">=0", "Headloss due to friction"], - [Nu, ">0", "Nu"]) - return (np.pi*Diam**4) / (128*Nu) * gravity.magnitude * HeadLossFric / Length - - -@u.wraps(u.m**3/u.s, [u.m, u.m, u.m, u.m**2/u.s, u.m], False) -def flow_swamee(Diam, HeadLossFric, Length, Nu, PipeRough): - """Return the flow rate for turbulent flow with only major losses.""" - #Checking input validity - ut.check_range([Diam, ">0", "Diameter"], [Length, ">0", "Length"], - [HeadLossFric, ">0", "Headloss due to friction"], - [Nu, ">0", "Nu"], [PipeRough, "0-1", "Pipe roughness"]) - logterm = np.log10(PipeRough / (3.7 * Diam) - + 2.51 * Nu * np.sqrt(Length / (2 * gravity.magnitude - * HeadLossFric - * Diam**3) - ) + :param Diam: diameter of pipe + :type Diam: u.m + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + + :return: flow rate for laminar/turbulent transition + :rtype: u.m**3/u.s + """ + ut.check_range([Diam.magnitude, ">0", "Diameter"], + [Nu.magnitude, ">0", "Nu"]) + return (np.pi * Diam * RE_TRANSITION_PIPE * Nu / 4).to(u.m**3/u.s) + + +@ut.list_handler() +def flow_hagen(Diam, HeadLossMajor=None, Length=None, Nu=None, *, HeadLossFric=None): + """Return the flow rate for laminar flow with only major losses. + + :param Diam: diameter of pipe + :type Diam: u.m + :param HeadLossMajor: head loss due to friction + :type HeadLossMajor: u.m + :param Length: length of pipe + :type Length: u.m + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + + :param HeadLossFric: deprecated; use HeadLossMajor instead + + :return: flow rate for laminar flow with only major losses + :rtype: u.m**3/u.s + """ + if HeadLossMajor is not None and HeadLossFric is not None: + raise TypeError("flow_hagen received both HeadLossMajor and HeadLossFric") + elif HeadLossMajor is None and HeadLossFric is None: + raise TypeError("flow_hagen missing HeadLossMajor argument") + elif Length is None: + raise TypeError("flow_hagen missing Length argument") + elif Nu is None: + raise TypeError("flow_hagen missing Nu argument") + elif HeadLossFric is not None: + warnings.warn("HeadLossFric is deprecated; use HeadLossMajor instead.", + UserWarning) + HeadLossMajor = HeadLossFric + + ut.check_range([Diam.magnitude, ">0", "Diameter"], + [Length.magnitude, ">0", "Length"], + [HeadLossMajor.magnitude, ">=0", "Headloss due to friction"], + [Nu.magnitude, ">0", "Nu"]) + return ((np.pi*Diam**4) / (128*Nu) * u.gravity * HeadLossMajor + / Length).to(u.m**3/u.s) + + +@ut.list_handler() +def flow_swamee(Diam, HeadLossMajor=None, Length=None, Nu=None, Roughness=None, *, HeadLossFric=None, PipeRough=None): + """Return the flow rate for turbulent flow with only major losses. + + :param Diam: diameter of pipe + :type Diam: u.m + :param HeadLossMajor: head loss due to friction + :type HeadLossMajor: u.m + :param Length: length of pipe + :type Length: u.m + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + :param Roughness: roughness of pipe + :type Roughness: u.m + + :param HeadLossFric: deprecated; use HeadLossMajor instead + :param PipeRough: deprecated; use Roughness instead + + :return: flow rate for turbulent flow with only major losses + :rtype: u.m**3/u.s + """ + if HeadLossMajor is not None and HeadLossFric is not None: + raise TypeError("flow_swamee received both HeadLossMajor and HeadLossFric") + elif HeadLossMajor is None and HeadLossFric is None: + raise TypeError("flow_swamee missing HeadLossMajor argument") + elif Length is None: + raise TypeError("flow_swamee missing Length argument") + elif Nu is None: + raise TypeError("flow_swamee missing Nu argument") + elif Roughness is not None and PipeRough is not None: + raise TypeError("flow_swamee received both Roughness and PipeRough") + elif Roughness is None and PipeRough is None: + raise TypeError("flow_swamee missing Roughness argument") + else: + if HeadLossFric is not None: + warnings.warn("HeadLossFric is deprecated; use HeadLossMajor instead.", + UserWarning) + HeadLossMajor = HeadLossFric + if PipeRough is not None: + warnings.warn("PipeRough is deprecated; use Roughness instead.", + UserWarning) + Roughness = PipeRough + + ut.check_range([Diam.magnitude, ">0", "Diameter"], + [Length.magnitude, ">0", "Length"], + [HeadLossMajor.magnitude, ">0", "Headloss due to friction"], + [Nu.magnitude, ">0", "Nu"], + [Roughness.magnitude, ">=0", "Pipe roughness"]) + logterm = np.log10(Roughness / (3.7 * Diam) + + 2.51 * Nu * np.sqrt(Length / (2 * u.gravity + * HeadLossMajor + * Diam**3) + ) ) return ((-np.pi / np.sqrt(2)) * Diam**(5/2) * logterm - * np.sqrt(gravity.magnitude * HeadLossFric / Length) - ) + * np.sqrt(u.gravity * HeadLossMajor / Length) + ).to(u.m**3/u.s) -@u.wraps(u.m**3/u.s, [u.m, u.m, u.m, u.m**2/u.s, u.m], False) @ut.list_handler() def flow_pipemajor(Diam, HeadLossFric, Length, Nu, PipeRough): + """ + .. deprecated:: + `flow_pipemajor` is deprecated; use `flow_major_pipe` instead. + """ + warnings.warn('flow_pipemajor is deprecated; use ' + 'flow_major_pipe instead.', UserWarning) + return flow_major_pipe(Diam, HeadLossFric, Length, Nu, PipeRough) + + +@ut.list_handler() +def flow_major_pipe(Diam, HeadLossMajor, Length, Nu, Roughness): """Return the flow rate with only major losses. This function applies to both laminar and turbulent flows. + + :param Diam: diameter of pipe + :type Diam: u.m + :param HeadLossMajor: head loss due to friction + :type HeadLossMajor: u.m + :param Length: length of pipe + :type Length: u.m + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + :param Roughness: roughness of pipe + :type Roughness: u.m + + :return: flow rate with only major losses + :rtype: u.m**3/u.s """ - #Inputs do not need to be checked here because they are checked by - #functions this function calls. - FlowHagen = flow_hagen(Diam, HeadLossFric, Length, Nu).magnitude - if FlowHagen < flow_transition(Diam, Nu).magnitude: + FlowHagen = flow_hagen(Diam, HeadLossMajor, Length, Nu) + if FlowHagen < flow_transition(Diam, Nu): return FlowHagen else: - return flow_swamee(Diam, HeadLossFric, Length, Nu, PipeRough).magnitude + return flow_swamee(Diam, HeadLossMajor, Length, Nu, Roughness) -@u.wraps(u.m**3/u.s, [u.m, u.m], False) +@ut.list_handler() def flow_pipeminor(Diam, HeadLossExpans, KMinor): + """ + .. deprecated:: + `flow_pipeminor` is deprecated; use `flow_minor_pipe` instead. + """ + warnings.warn('flow_pipeminor is deprecated; use ' + 'flow_minor_pipe instead.', UserWarning) + return flow_minor_pipe(Diam, HeadLossExpans, KMinor) + + +@ut.list_handler() +def flow_minor_pipe(Diam, HeadLossMinor, KMinor): """Return the flow rate with only minor losses. This function applies to both laminar and turbulent flows. + + :param Diam: diameter of pipe + :type Diam: u.m + :param HeadLossExpans: head loss due to expansion + :type HeadLossExpans: u.m + :param KMinor: minor loss coefficient + :type KMinor: u.dimensionless or unitless + + :return: flow rate with only minor losses + :rtype: u.m**3/u.s """ - #Checking input validity - inputs not checked here are checked by - #functions this function calls. - ut.check_range([HeadLossExpans, ">=0", "Headloss due to expansion"], + ut.check_range([HeadLossMinor.magnitude, ">=0", + "Headloss due to expansion"], [KMinor, ">0", "K minor"]) - return (area_circle(Diam).magnitude * np.sqrt(2 * gravity.magnitude - * HeadLossExpans - / KMinor) - ) + return (area_circle(Diam) * np.sqrt(2 * u.gravity * HeadLossMinor + / KMinor) + ).to(u.m**3/u.s) -# Now we put all of the flow equations together and calculate the flow in a -# straight pipe that has both major and minor losses and might be either -# laminar or turbulent. -@u.wraps(u.m**3/u.s, (u.m, u.m, u.m, u.m**2/u.s, u.m), False) -@ut.list_handler() -def flow_pipe(Diam, HeadLoss, Length, Nu, PipeRough, KMinor): - """Return the the flow in a straight pipe. - This function works for both major and minor losses and - works whether the flow is laminar or turbulent. +@ut.list_handler() +def flow_pipe(Diam, HeadLoss, Length, Nu, Roughness=None, KMinor=None, *, PipeRough=None): + """Return the flow rate in a pipe. + + This function works for both major and minor losses as well as + both laminar and turbulent flows. + + :param Diam: diameter of pipe + :type Diam: u.m + :param HeadLoss: total head loss from major and minor losses + :type HeadLoss: u.m + :param Length: length of pipe + :type Length: u.m + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + :param Roughness: roughness of pipe + :type Roughness: u.m + :param KMinor: minor loss coefficient + :type KMinor: u.dimensionless or unitless + + :param PipeRough: deprecated; use Roughness instead + + :return: flow rate in pipe + :rtype: u.m**3/u.s """ - #Inputs do not need to be checked here because they are checked by - #functions this function calls. + if Roughness is not None and PipeRough is not None: + raise TypeError("flow_pipe received both Roughness and PipeRough") + elif Roughness is None and PipeRough is None: + raise TypeError("flow_pipe missing Roughness argument") + elif KMinor is None: + raise TypeError("flow_pipe missing KMinor argument") + elif PipeRough is not None: + warnings.warn("PipeRough is deprecated; use Roughness instead.", + UserWarning) + Roughness = PipeRough + if KMinor == 0: - FlowRate = flow_pipemajor(Diam, HeadLoss, Length, Nu, - PipeRough).magnitude + FlowRate = flow_major_pipe(Diam, HeadLoss, Length, Nu, + Roughness) else: FlowRatePrev = 0 err = 1.0 - FlowRate = min(flow_pipemajor(Diam, HeadLoss, Length, - Nu, PipeRough).magnitude, - flow_pipeminor(Diam, HeadLoss, KMinor).magnitude + FlowRate = min(flow_major_pipe(Diam, HeadLoss, Length, + Nu, Roughness), + flow_minor_pipe(Diam, HeadLoss, KMinor) ) while err > 0.01: FlowRatePrev = FlowRate - HLFricNew = (HeadLoss * headloss_fric(FlowRate, Diam, Length, - Nu, PipeRough).magnitude - / (headloss_fric(FlowRate, Diam, Length, - Nu, PipeRough).magnitude - + headloss_exp(FlowRate, Diam, KMinor).magnitude + HLFricNew = (HeadLoss * headloss_major_pipe(FlowRate, Diam, Length, + Nu, Roughness) + / (headloss_major_pipe(FlowRate, Diam, Length, + Nu, Roughness) + + headloss_minor_pipe(FlowRate, Diam, KMinor) ) ) - FlowRate = flow_pipemajor(Diam, HLFricNew, Length, - Nu, PipeRough).magnitude + FlowRate = flow_major_pipe(Diam, HLFricNew, Length, + Nu, Roughness) if FlowRate == 0: err = 0.0 else: err = (abs(FlowRate - FlowRatePrev) / ((FlowRate + FlowRatePrev) / 2) ) - return FlowRate + return FlowRate.to(u.m**3/u.s) +########################## Diameters ########################## -@u.wraps(u.m, [u.m**3/u.s, u.m, u.m, u.m**2/u.s], False) -def diam_hagen(FlowRate, HeadLossFric, Length, Nu): - #Checking input validity - ut.check_range([FlowRate, ">0", "Flow rate"], [Length, ">0", "Length"], - [HeadLossFric, ">0", "Headloss due to friction"], - [Nu, ">0", "Nu"]) - return ((128 * Nu * FlowRate * Length) - / (gravity.magnitude * HeadLossFric * np.pi) - ) ** (1/4) +@ut.list_handler() +def diam_hagen(FlowRate, HeadLossMajor=None, Length=None, Nu=None, *, HeadLossFric=None): + """Return the inner diameter of a pipe with laminar flow and no minor losses. -@u.wraps(u.m, [u.m**3/u.s, u.m, u.m, u.m**2/u.s, u.m], False) -def diam_swamee(FlowRate, HeadLossFric, Length, Nu, PipeRough): - """Return the inner diameter of a pipe. + The Hagen Poiseuille equation is dimensionally correct and returns the + inner diameter of a pipe given the flow rate and the head loss due + to shear on the pipe walls. The Hagen Poiseuille equation does NOT take + minor losses into account. This equation ONLY applies to laminar flow. + + :param FlowRate: flow rate of pipe + :type FlowRate: u.m**3/u.s + :param HeadLossFric: head loss due to friction + :type HeadLossFric: u.m + :param Length: length of pipe + :type Length: u.m + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + + :param HeadLossFric: deprecated; use HeadLossMajor instead + + :return: inner diameter of pipe + :rtype: u.m + """ + if HeadLossMajor is not None and HeadLossFric is not None: + raise TypeError("diam_hagen received both HeadLossMajor and HeadLossFric") + elif HeadLossMajor is None and HeadLossFric is None: + raise TypeError("diam_hagen missing HeadLossMajor argument") + elif Length is None: + raise TypeError("diam_hagen missing Length argument") + elif Nu is None: + raise TypeError("diam_hagen missing Nu argument") + elif HeadLossFric is not None: + warnings.warn("HeadLossFric is deprecated; use HeadLossMajor instead.", + UserWarning) + HeadLossMajor = HeadLossFric + + ut.check_range([FlowRate.magnitude, ">0", "Flow rate"], + [Length.magnitude, ">0", "Length"], + [HeadLossMajor.magnitude, ">0", "Headloss due to friction"], + [Nu.magnitude, ">0", "Nu"]) + return (((128 * Nu * FlowRate * Length) + / (u.gravity * HeadLossMajor * np.pi) + ) ** (1/4)).to(u.m) + + +@ut.list_handler() +def diam_swamee(FlowRate, HeadLossMajor=None, Length=None, Nu=None, Roughness=None, *, HeadLossFric=None, PipeRough=None): + """Return the inner diameter of a pipe with turbulent flow and no minor losses. The Swamee Jain equation is dimensionally correct and returns the inner diameter of a pipe given the flow rate and the head loss due to shear on the pipe walls. The Swamee Jain equation does NOT take minor losses into account. This equation ONLY applies to turbulent flow. + + :param FlowRate: flow rate of pipe + :type FlowRate: u.m**3/u.s + :param HeadLossFric: head loss due to friction + :type HeadLossFric: u.m + :param Length: length of pipe + :type Length: u.m + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + :param PipeRough: roughness of pipe + :type PipeRough: u.m + + :param HeadLossFric: deprecated; use HeadLossMajor instead + :param PipeRough: deprecated; use Roughness instead + + :return: inner diameter of pipe + :rtype: u.m """ - #Checking input validity - ut.check_range([FlowRate, ">0", "Flow rate"], [Length, ">0", "Length"], - [HeadLossFric, ">0", "Headloss due to friction"], - [Nu, ">0", "Nu"], [PipeRough, "0-1", "Pipe roughness"]) - a = ((PipeRough ** 1.25) + if HeadLossMajor is not None and HeadLossFric is not None: + raise TypeError("diam_swamee received both HeadLossMajor and HeadLossFric") + elif HeadLossMajor is None and HeadLossFric is None: + raise TypeError("diam_swamee missing HeadLossMajor argument") + elif Length is None: + raise TypeError("diam_swamee missing Length argument") + elif Nu is None: + raise TypeError("diam_swamee missing Nu argument") + elif Roughness is not None and PipeRough is not None: + raise TypeError("diam_swamee received both Roughness and PipeRough") + elif Roughness is None and PipeRough is None: + raise TypeError("diam_swamee missing Roughness argument") + else: + if HeadLossFric is not None: + warnings.warn("HeadLossFric is deprecated; use HeadLossMajor instead.", + UserWarning) + HeadLossMajor = HeadLossFric + if PipeRough is not None: + warnings.warn("PipeRough is deprecated; use Roughness instead.", + UserWarning) + Roughness = PipeRough + + ut.check_range([FlowRate.magnitude, ">0", "Flow rate"], + [Length.magnitude, ">0", "Length"], + [HeadLossMajor.magnitude, ">0", "Headloss due to friction"], + [Nu.magnitude, ">0", "Nu"], + [Roughness.magnitude, ">=0", "Pipe roughness"]) + a = ((Roughness ** 1.25) * ((Length * FlowRate**2) - / (gravity.magnitude * HeadLossFric) + / (u.gravity * HeadLossMajor) )**4.75 - ) - b = (Nu * FlowRate**9.4 - * (Length / (gravity.magnitude * HeadLossFric)) ** 5.2 - ) - return 0.66 * (a+b)**0.04 + ).to_base_units() + b = (Nu**5 * FlowRate**47 + * (Length / (u.gravity * HeadLossMajor)) ** 26 + ).to_base_units()**0.2 + return (0.66 * (a+b)**0.04).to(u.m) -@u.wraps(u.m, [u.m**3/u.s, u.m, u.m, u.m**2/u.s, u.m], False) @ut.list_handler() def diam_pipemajor(FlowRate, HeadLossFric, Length, Nu, PipeRough): - """Return the pipe IDiam that would result in given major losses. + """ + .. deprecated:: + `diam_pipemajor` is deprecated; use `diam_major_pipe` instead. + """ + warnings.warn('diam_pipemajor is deprecated; use ' + 'diam_major_pipe instead.', UserWarning) + return diam_major_pipe(FlowRate, HeadLossFric, Length, Nu, PipeRough) + + +@ut.list_handler() +def diam_major_pipe(FlowRate, HeadLossMajor, Length, Nu, Roughness): + """Return the pipe inner diameter that would result in given major losses. + This function applies to both laminar and turbulent flow. + + :param FlowRate: flow rate of pipe + :type FlowRate: u.m**3/u.s + :param HeadLossMajor: head loss due to friction + :type HeadLossMajor: u.m + :param Length: length of pipe + :type Length: u.m + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + :param Roughness: roughness of pipe + :type Roughness: u.m + + :return: inner diameter of pipe + :rtype: u.m """ - #Inputs do not need to be checked here because they are checked by - #functions this function calls. - DiamLaminar = diam_hagen(FlowRate, HeadLossFric, Length, Nu).magnitude + DiamLaminar = diam_hagen(FlowRate, HeadLossMajor, Length, Nu) if re_pipe(FlowRate, DiamLaminar, Nu) <= RE_TRANSITION_PIPE: return DiamLaminar else: - return diam_swamee(FlowRate, HeadLossFric, Length, - Nu, PipeRough).magnitude + return diam_swamee(FlowRate, HeadLossMajor, Length, + Nu, Roughness) -@u.wraps(u.m, [u.m**3/u.s, u.m], False) + +@ut.list_handler() def diam_pipeminor(FlowRate, HeadLossExpans, KMinor): - """Return the pipe ID that would result in the given minor losses. + """ + .. deprecated:: + `diam_pipeminor` is deprecated; use `diam_minor_pipe` instead. + """ + warnings.warn('diam_pipeminor is deprecated; use ' + 'diam_minor_pipe instead.', UserWarning) + return diam_minor_pipe(FlowRate, HeadLossExpans, KMinor) + + +@ut.list_handler() +def diam_minor_pipe(FlowRate, HeadLossMinor, KMinor): + """Return the pipe inner diameter that would result in the given minor losses. This function applies to both laminar and turbulent flow. + + :param FlowRate: flow rate of pipe + :type FlowRate: u.m**3/u.s + :param HeadLossMinor: head loss due to expansion + :type HeadLossMinor: u.m + :param KMinor: minor loss coefficient + :type KMinor: u.dimensionless or unitless + + :return: inner diameter of pipe + :rtype: u.m """ - #Checking input validity - ut.check_range([FlowRate, ">0", "Flow rate"], [KMinor, ">=0", "K minor"], - [HeadLossExpans, ">0", "Headloss due to expansion"]) + ut.check_range([FlowRate.magnitude, ">0", "Flow rate"], + [KMinor, ">=0", "K minor"], + [HeadLossMinor.magnitude, ">0", "Headloss due to expansion"]) return (np.sqrt(4 * FlowRate / np.pi) - * (KMinor / (2 * gravity.magnitude * HeadLossExpans)) ** (1/4) - ) + * (KMinor / (2 * u.gravity * HeadLossMinor)) ** (1/4) + ).to(u.m) -@u.wraps(u.m, [u.m**3/u.s, u.m, u.m, u.m**2/u.s, u.m], False) @ut.list_handler() def diam_pipe(FlowRate, HeadLoss, Length, Nu, PipeRough, KMinor): - """Return the pipe ID that would result in the given total head loss. + """Return the pipe inner diameter that would result in the given total head + loss. This function applies to both laminar and turbulent flow and incorporates both minor and major losses. + + :param FlowRate: flow rate of pipe + :type FlowRate: u.m**3/u.s + :param HeadLoss: total head loss from major and minor losses + :type HeadLoss: u.m + :param Length: length of pipe + :type Length: u.m + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + :param PipeRough: roughness of pipe + :type PipeRough: u.m + :param KMinor: minor loss coefficient + :type KMinor: u.dimensionless or unitless + + :return: inner diameter of pipe + :rtype: u.m """ - #Inputs do not need to be checked here because they are checked by - #functions this function calls. if KMinor == 0: - Diam = diam_pipemajor(FlowRate, HeadLoss, Length, Nu, - PipeRough).magnitude + Diam = diam_major_pipe(FlowRate, HeadLoss, Length, Nu, + PipeRough) else: - Diam = max(diam_pipemajor(FlowRate, HeadLoss, - Length, Nu, PipeRough).magnitude, - diam_pipeminor(FlowRate, HeadLoss, KMinor).magnitude) + Diam = max(diam_major_pipe(FlowRate, HeadLoss, + Length, Nu, PipeRough), + diam_minor_pipe(FlowRate, HeadLoss, KMinor)) err = 1.00 while err > 0.001: DiamPrev = Diam - HLFricNew = (HeadLoss * headloss_fric(FlowRate, Diam, Length, - Nu, PipeRough - ).magnitude - / (headloss_fric(FlowRate, Diam, Length, - Nu, PipeRough - ).magnitude - + headloss_exp(FlowRate, - Diam, KMinor - ).magnitude + HLFricNew = (HeadLoss * headloss_major_pipe(FlowRate, Diam, Length, + Nu, PipeRough + ) + / (headloss_major_pipe(FlowRate, Diam, Length, + Nu, PipeRough + ) + + headloss_minor_pipe(FlowRate, Diam, KMinor + ) ) ) - Diam = diam_pipemajor(FlowRate, HLFricNew, Length, Nu, PipeRough - ).magnitude + Diam = diam_major_pipe(FlowRate, HLFricNew, Length, Nu, PipeRough + ) err = abs(Diam - DiamPrev) / ((Diam + DiamPrev) / 2) - return Diam + return Diam.to(u.m) + + +@ut.list_handler() +def pipe_ID(FlowRate, Pressure): + """Return the inner diameter of a pipe for a given pressure + recovery constraint. + + :param FlowRate: flow rate of pipe + :type FlowRate: u.m**3/u.s + :param Pressure: pressure recovery constraint + :type Pressure: u.m -# Weir head loss equations -@u.wraps(u.m, [u.m**3/u.s, u.m], False) + :return: inner diameter of pipe + :rtype: u.m + """ + ut.check_range([FlowRate.magnitude, ">0", "Flow rate"], + [Pressure.magnitude, ">0", "Pressure"]) + return np.sqrt(FlowRate/((np.pi/4)*np.sqrt(2*u.gravity*Pressure))).to(u.m) + +############################ Weirs ############################ + + +@ut.list_handler() def width_rect_weir(FlowRate, Height): - """Return the width of a rectangular weir.""" - #Checking input validity - ut.check_range([FlowRate, ">0", "Flow rate"], [Height, ">0", "Height"]) - return ((3 / 2) * FlowRate - / (con.VC_ORIFICE_RATIO * np.sqrt(2 * gravity.magnitude) * Height ** (3 / 2)) - ) + """ + .. deprecated:: + `width_rect_weir` is deprecated; use `width_weir_rect` instead. + """ + warnings.warn('width_rect_weir is deprecated; use ' + 'width_weir_rect instead.', UserWarning) + return width_weir_rect(FlowRate, Height) + + +@ut.list_handler() +def width_weir_rect(FlowRate, Height): + """Return the width of a rectangular weir given its flow rate and the + height of the water above the weir. For a weir that is a vertical pipe, + this value is the circumference. + + :param FlowRate: flow rate over weir + :type FlowRate: u.m**3/u.s + :param Height: height of water above weir + :type Height: u.m + + :return: width of weir + :rtypes: u.m + """ + ut.check_range([FlowRate.magnitude, ">0", "Flow rate"], + [Height.magnitude, ">0", "Height"]) + return ((3 / 2) * FlowRate / (con.VC_ORIFICE_RATIO + * np.sqrt(2 * u.gravity) * Height ** (3 / 2)) + ).to(u.m) -# For a pipe, Width is the circumference of the pipe. -# Head loss for a weir is the difference in height between the water -# upstream of the weir and the top of the weir. -@u.wraps(u.m, [u.m**3/u.s, u.m], False) +@ut.list_handler() def headloss_weir(FlowRate, Width): - """Return the headloss of a weir.""" - #Checking input validity - ut.check_range([FlowRate, ">0", "Flow rate"], [Width, ">0", "Width"]) - return (((3/2) * FlowRate - / (con.VC_ORIFICE_RATIO * np.sqrt(2 * gravity.magnitude) * Width) - ) ** (2/3)) + """ + .. deprecated:: + `headloss_weir` is deprecated; use `headloss_weir_rect` instead. + """ + warnings.warn('headloss_weir is deprecated; use ' + 'headloss_weir_rect instead.', UserWarning) + return headloss_weir_rect(FlowRate, Width) -@u.wraps(u.m, [u.m, u.m], False) +@ut.list_handler() +def headloss_weir_rect(FlowRate, Width): + """Return the head loss of a rectangular or vertical pipe weir. + + Head loss for a weir is the difference in height between the water + upstream of the weir and the top of the weir. + + :param FlowRate: flow rate over weir + :type FlowRate: u.m**3/u.s + :param Width: width of weir (circumference for a vertical pipe) + :type Width: u.m + + :return: head loss of weir + :rtypes: u.m + """ + ut.check_range([FlowRate.magnitude, ">0", "Flow rate"], + [Width.magnitude, ">0", "Width"]) + return ((((3/2) * FlowRate + / (con.VC_ORIFICE_RATIO * np.sqrt(2 * u.gravity) * Width) + ) ** 2).to(u.m**3)) ** (1/3) + + +@ut.list_handler() def flow_rect_weir(Height, Width): - """Return the flow of a rectangular weir.""" - #Checking input validity - ut.check_range([Height, ">0", "Height"], [Width, ">0", "Width"]) + """ + .. deprecated:: + `flow_rect_weir` is deprecated; use `flow_weir_rect` instead. + """ + warnings.warn('flow_rect_weir is deprecated; use ' + 'flow_weir_rect instead.', UserWarning) + return flow_weir_rect(Height, Width) + + +@ut.list_handler() +def flow_weir_rect(Height, Width): + """Return the flow rate of a rectangular or vertical pipe weir. + + :param Height: height of water above weir + :type Height: u.m + :param Width: width of weir (circumference for a vertical pipe) + :type Width: u.m + + :return: flow of weir + :rtype: u.m**3/u.s + """ + ut.check_range([Height.magnitude, ">0", "Height"], + [Width.magnitude, ">0", "Width"]) return ((2/3) * con.VC_ORIFICE_RATIO - * (np.sqrt(2*gravity.magnitude) * Height**(3/2)) - * Width) + * (np.sqrt(2*u.gravity) * Height**(3/2)) + * Width).to(u.m**3/u.s) +######################## Porous Media ######################## -@u.wraps(u.m, [u.m**3/u.s, u.m], False) + +@ut.list_handler() +def headloss_kozeny(Length, DiamMedia=None, ApproachVel=None, Porosity=None, Nu=None, *, Diam=None, Vel=None): + """Return the Carman Kozeny sand bed head loss. + + :param Length: height of bed + :type Length: u.m + :param DiamMedia: diameter of sand particle + :type DiamMedia: u.m + :param ApproachVel: superficial velocity + :type ApproachVel: u.m/u.s + :param Porosity: porosity of bed + :type Porosity: u.dimensionless or unitless + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + + :param Diam: deprecated; use DiamMedia instead + :param Vel: deprecated, use ApproachVel instead + + :return: head loss in sand bed + :rtype: u.m + """ + if DiamMedia is not None and Diam is not None: + raise TypeError("headloss_kozeny received both DiamMedia and Diam") + elif DiamMedia is None and Diam is None: + raise TypeError("headloss_kozeny missing DiamMedia argument") + elif ApproachVel is not None and Vel is not None: + raise TypeError("headloss_kozeny received both ApproachVel and Vel") + elif ApproachVel is None and Vel is None: + raise TypeError("headloss_kozeny missing ApproachVel argument") + elif Porosity is None: + raise TypeError("headloss_kozeny missing Porosity argument") + elif Nu is None: + raise TypeError("headloss_kozeny missing Nu argument") + else: + if Diam is not None: + warnings.warn("Diam is deprecated; use DiamMedia instead.", + UserWarning) + DiamMedia = Diam + if Vel is not None: + warnings.warn("Vel is deprecated; use ApproachVel instead.", + UserWarning) + ApproachVel = Vel + + ut.check_range([Length.magnitude, ">0", "Length"], + [DiamMedia.magnitude, ">0", "Diam"], + [ApproachVel.magnitude, ">0", "Velocity"], + [Nu.magnitude, ">0", "Nu"], + [Porosity, "0-1", "Porosity"]) + return (con.K_KOZENY * Length * Nu + / u.gravity * (1-Porosity)**2 + / Porosity**3 * 36 * ApproachVel + / DiamMedia ** 2).to(u.m) + + +@ut.list_handler() +def re_ergun(ApproachVel, DiamMedia, Temperature, Porosity): + """Return the Reynolds number for flow through porous media. + + :param ApproachVel: approach velocity or superficial fluid velocity + :type ApproachVel: u.m/u.s + :param DiamMedia: particle diameter + :type DiamMedia: u.m + :param Temperature: temperature of porous medium + :type Temperature: u.degK + :param Porosity: porosity of porous medium + :type Porosity: u.dimensionless or unitless + + :return: Reynolds number for flow through porous media + :rtype: u.dimensionless + """ + ut.check_range([ApproachVel.magnitude, ">0", "ApproachVel"], + [DiamMedia.magnitude, ">0", "DiamMedia"], + [Porosity, "0-1", "Porosity"]) + return (ApproachVel * DiamMedia / + (viscosity_kinematic_water(Temperature) + * (1 - Porosity))).to(u.dimensionless) + + +@ut.list_handler() +def fric_ergun(ApproachVel, DiamMedia, Temperature, Porosity): + """Return the friction factor for flow through porous media. + + :param ApproachVel: superficial fluid velocity (VelSuperficial?) + :type ApproachVel: u.m/u.s + :param DiamMedia: particle diameter + :type DiamMedia: u.m + :param Temperature: temperature of porous medium + :type Temperature: u.degK + :param Porosity: porosity of porous medium + :type Porosity: u.dimensionless or unitless + + :return: friction factor for flow through porous media + :rtype: u.dimensionless + """ + return (300 / re_ergun(ApproachVel, DiamMedia, Temperature, Porosity) + + 3.5 * u.dimensionless) + + +@ut.list_handler() +def headloss_ergun(ApproachVel, DiamMedia, Temperature, Porosity, Length): + """Return the frictional head loss for flow through porous media. + + :param ApproachVel: superficial fluid velocity (VelSuperficial?) + :type ApproachVel: u.m/u.s + :param DiamMedia: particle diameter + :type DiamMedia: u.m + :param Temperature: temperature of porous medium + :type Temperature: u.degK + :param Porosity: porosity of porous medium + :type Porosity: u.dimensionless or unitless + :param Length: length of pipe or duct + :type Length: u.m + + :return: frictional head loss for flow through porous media + :rtype: u.m + """ + return (fric_ergun(ApproachVel, DiamMedia, Temperature, Porosity) + * Length / DiamMedia * ApproachVel**2 / (2*u.gravity) * (1-Porosity) + / Porosity**3).to(u.m) + + +@ut.list_handler() +def g_cs_ergun(ApproachVel, DiamMedia, Temperature, Porosity): + """Camp Stein velocity gradient for flow through porous media. + + :param ApproachVel: superficial fluid velocity (VelSuperficial?) + :type ApproachVel: u.m/u.s + :param DiamMedia: particle diameter + :type DiamMedia: u.m + :param Temperature: temperature of porous medium + :type Temperature: u.degK + :param Porosity: porosity of porous medium + :type Porosity: u.dimensionless or unitless + + :return: Camp Stein velocity gradient for flow through porous media + :rtype: u.Hz + """ + return np.sqrt(fric_ergun(ApproachVel, DiamMedia, Temperature, Porosity) + * ApproachVel**3 * (1-Porosity) + / (2 * viscosity_kinematic_water(Temperature) * DiamMedia + * Porosity**4)).to(u.Hz) + +######################## Miscellaneous ######################## + + +@ut.list_handler() def height_water_critical(FlowRate, Width): - """Return the critical local water depth.""" - #Checking input validity - ut.check_range([FlowRate, ">0", "Flow rate"], [Width, ">0", "Width"]) - return (FlowRate / (Width * np.sqrt(gravity.magnitude))) ** (2/3) + """Return the critical local water height. + :param FlowRate: flow rate of water + :type FlowRate: u.m**3/u.s + :param Width: width of channel (????????) + :type Width: u.m -@u.wraps(u.m/u.s, u.m, False) + :return: critical water height + :rtype: u.m + """ + ut.check_range([FlowRate.magnitude, ">0", "Flow rate"], + [Width.magnitude, ">0", "Width"]) + return ((FlowRate / (Width * np.sqrt(1*u.gravity))) ** (2/3)).to(u.m) + + +@ut.list_handler() def vel_horizontal(HeightWaterCritical): - """Return the horizontal velocity.""" - #Checking input validity - ut.check_range([HeightWaterCritical, ">0", "Critical height of water"]) - return np.sqrt(gravity.magnitude * HeightWaterCritical) - - -@u.wraps(u.m, [u.m, u.m, u.m/u.s, u.m, u.m**2/u.s], False) -def headloss_kozeny(Length, Diam, Vel, Porosity, Nu): - """Return the Carmen Kozeny Sand Bed head loss.""" - #Checking input validity - ut.check_range([Length, ">0", "Length"], [Diam, ">0", "Diam"], - [Vel, ">0", "Velocity"], [Nu, ">0", "Nu"], - [Porosity, "0-1", "Porosity"]) - return (K_KOZENY * Length * Nu - / gravity.magnitude * (1-Porosity)**2 - / Porosity**3 * 36 * Vel - / Diam ** 2) + """Return the horizontal velocity. (at the critical water depth??????) + :param HeightWaterCritical: critical water height + :type HeightWaterCritical: u.m + + :return: horizontal velocity + :rtype: u.m/u.s + """ + ut.check_range([HeightWaterCritical.magnitude, ">0", "Critical height of water"]) + return np.sqrt(u.gravity * HeightWaterCritical).to(u.m/u.s) -@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))) +@ut.list_handler() def manifold_id_alt(q, pr_max): """Return the inner diameter of a manifold when major losses are negligible. @@ -735,12 +1833,14 @@ def manifold_id_alt(q, pr_max): manifold_id_alt = np.sqrt( 4 * q / ( np.pi * np.sqrt( - 2 * con.GRAVITY * pr_max + 2 * u.gravity * pr_max ) ) ) return manifold_id_alt + +@ut.list_handler() def manifold_id(q, h, l, q_ratio, nu, eps, k, n): id_new = 2 * u.inch id_old = 0 * u.inch @@ -748,10 +1848,10 @@ def manifold_id(q, h, l, q_ratio, nu, eps, k, n): while error > 0.01: id_old = id_new id_new = ( - ((8 * q ** 2) / (con.GRAVITY * np.pi ** 2 * h)) * + ((8 * q ** 2) / (u.gravity * np.pi ** 2 * h)) * ( ( - 1 + fric(q, id_old, nu, eps) * + 1 + fric_pipe(q, id_old, nu, eps) * (1 / 3 + 1 / (2 * n) + 1 / (6 * n ** 2)) ) / (1 - q_ratio ** 2) @@ -760,17 +1860,21 @@ def manifold_id(q, h, l, q_ratio, nu, eps, k, n): error = np.abs(id_old - id_new) / id_new return id_new + +@ut.list_handler() 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), + manifold_id(q, h, l, q_ratio, nu, eps, k, n), sdr ) return manifold_nd + +@ut.list_handler() 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)) - + horiz_chan_w_new = q / ((depth - hl) * np.sqrt(2 * u.gravity * hl)) + error = 1 i = 0 while error > 0.001 and i < 20: @@ -779,16 +1883,18 @@ def horiz_chan_w(q, depth, hl, l, nu, eps, manifold, k): 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))) * + fric_rect(q, w, depth - hl, nu, eps, True) * + (l / (4 * radius_hydraulic_rect(w, depth - hl, True))) * (1 - (2 * (int(manifold) / 3))) - ) / (2 * con.GRAVITY * hl) - ) * (q / (depth - hl)) + ) / (2 * u.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) + +@ut.list_handler() def horiz_chan_h(q, w, hl, l, nu, eps, manifold): - h_new = (q / (w * np.sqrt(2 * con.GRAVITY * hl))) + hl + h_new = (q / (w * np.sqrt(2 * u.gravity * hl))) + hl error = 1 i = 0 while error > 0.001 and i < 200: @@ -797,15 +1903,17 @@ def horiz_chan_h(q, w, hl, l, nu, eps, manifold): 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) + radius_hydraulic_rect(w, h - hl_local, True))) * (1 - 2 * (int(manifold) / 3)) + )/ (2 * u.gravity * hl_local)) + (hl_local) error = np.abs(h_new - h) / (h_new + h) return h_new.to(u.m) + +@ut.list_handler() def pipe_flow_nd(q, sdr, hl, l, nu, eps, k): i = 0 id_sdr_all_available = pipe.ID_SDR_all_available(sdr) while q > flow_pipe(id_sdr_all_available[i], hl, l, nu, eps, k): i_d = id_sdr_all_available[i] i += 1 - return pipe.ND_SDR_available(i_d, sdr) \ No newline at end of file + return pipe.ND_SDR_available(i_d, sdr) diff --git a/aguaclara/core/pipes.py b/aguaclara/core/pipes.py index 821e5af5..76d11280 100644 --- a/aguaclara/core/pipes.py +++ b/aguaclara/core/pipes.py @@ -2,6 +2,7 @@ outer diameters of pipes based on their standard dimension ratio (SDR). """ from aguaclara.core.units import u +import aguaclara.core.utility as ut import numpy as np import pandas as pd @@ -37,22 +38,31 @@ def id_sch40(self): myindex = (np.abs(np.array(pipedb['NDinch']) - self.nd.magnitude)).argmin() return (pipedb.iloc[myindex, 1] - 2 * (pipedb.iloc[myindex, 5])) * u.inch -@u.wraps(u.inch, u.inch, False) + +@ut.list_handler() def OD(ND): """Return a pipe's outer diameter according to its nominal diameter. - The pipe schedule is not required here because all of the pipes of a - given nominal diameter have the same outer diameter. + :param ND: nominal diameter of pipe + :type ND: u.inch - 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. + :return: outer diameter of pipe, in inches + :rtype: u.inch """ + # The pipe schedule is not required here because all of the pipes of a + # 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. + ND = ND.to(u.inch).magnitude index = (np.abs(np.array(pipedb['NDinch']) - (ND))).argmin() - return pipedb.iloc[index, 1] + return pipedb.iloc[index, 1] * u.inch + +@ut.list_handler() def fitting_od(pipe_nd, fitting_sdr=41): pipe_od = OD(pipe_nd) fitting_nd = ND_SDR_available(pipe_od, fitting_sdr) @@ -60,16 +70,17 @@ def fitting_od(pipe_nd, fitting_sdr=41): return fitting_od -@u.wraps(u.inch, [u.inch, None], False) +@ut.list_handler() def ID_SDR(ND, SDR): - """Return the inner diameter for SDR(standard diameter ratio) pipes. + """Return the inner diameter of a pipe given its nominal diameter and SDR + (standard diameter ratio). - For these pipes the wall thickness is the outer diameter divided by - the SDR. + SDR is the outer diameter divided by the wall thickness. """ - return OD(ND).magnitude * (SDR-2) / SDR + return OD(ND) * (SDR-2) / SDR -@u.wraps(u.inch, u.inch, False) + +@ut.list_handler() def ID_sch40(ND): """Return the inner diameter for schedule 40 pipes. @@ -78,8 +89,9 @@ def ID_sch40(ND): Take the values of the array, subtract the ND, take the absolute value, find the index of the minimium value. """ + ND = ND.to(u.inch).magnitude myindex = (np.abs(np.array(pipedb['NDinch']) - (ND))).argmin() - return (pipedb.iloc[myindex, 1] - 2*(pipedb.iloc[myindex, 5])) + return (pipedb.iloc[myindex, 1] - 2*(pipedb.iloc[myindex, 5])) * u.inch def ND_all_available(): @@ -94,6 +106,7 @@ def ND_all_available(): ND_all_available.append((pipedb['NDinch'][i])) return ND_all_available * u.inch + def od_all_available(): """Return an array of available outer diameters. @@ -107,6 +120,7 @@ def od_all_available(): return od_all_available * u.inch +@ut.list_handler() def ID_SDR_all_available(SDR): """Return an array of inner diameters with a given SDR. @@ -120,6 +134,7 @@ def ID_SDR_all_available(SDR): return ID * u.inch +@ut.list_handler() def ND_SDR_available(ID, SDR): """ Return an available ND given an ID and a schedule. @@ -131,6 +146,7 @@ def ND_SDR_available(ID, SDR): return ND_all_available()[i] +@ut.list_handler() def ND_available(NDguess): """Return the minimum ND that is available. @@ -142,9 +158,11 @@ def ND_available(NDguess): myindex = (ND_all_available() >= NDguess) return min(ND_all_available()[myindex]) + +@ut.list_handler() 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 @@ -153,9 +171,13 @@ def od_available(od_guess): myindex = (od_all_available() >= od_guess) return min(od_all_available()[myindex]) + +@ut.list_handler() def socket_depth(nd): return nd / 2 + +@ut.list_handler() def cap_thickness(nd): cap_thickness = (fitting_od(nd) - OD(ND_available(nd))) / 2 - return cap_thickness \ No newline at end of file + return cap_thickness diff --git a/aguaclara/core/utility.py b/aguaclara/core/utility.py index f493f279..49f9823c 100644 --- a/aguaclara/core/utility.py +++ b/aguaclara/core/utility.py @@ -9,12 +9,12 @@ 1230000 """ from aguaclara.core.units import u - import numpy as np from math import log10, floor, ceil import warnings import functools + def optional_units(arg_positions, keys): """Wrap a function so that arguments may optionally have units. @@ -57,6 +57,7 @@ def wrapper(*args, **kwargs): return wrapper return decorator + @optional_units([0], ['num']) def round_sig_figs(num, figs=4): """Round a number to some amount of significant figures. @@ -73,6 +74,7 @@ def round_sig_figs(num, figs=4): return num + def round_sf(num, figs=4): """Round a number to some amount of significant figures. @@ -230,90 +232,91 @@ def get_sdr(spec): raise ValueError('Not a valid SDR.') return int(spec[3:]) -def list_handler(HandlerResult="nparray"): - """Wraps a function to handle list inputs.""" +def list_handler(): + """Wraps a scalar function to output a NumPy array if passed one or more inputs + as sequences (lists, tuples or NumPy arrays). For each sequence input, this + wrapper will recursively evaluate the function with the sequence replaced + by each of its elements and return the results in n-dimensional NumPy array, + where n is the number of sequence inputs. + + For a function "f" of one argument, f([x_1, ..., x_n]) would be evaluated to + [f(x_1), ..., f(x_n)]. For a function passed multiple sequences of + dimensions d_1, ..., d_n (from left to right), the result would be a + d_1 x ... x d_n array. + """ def decorate(func): @functools.wraps(func) # For Sphinx documentation of decorated functions def wrapper(*args, **kwargs): """Run through the wrapped function once for each array element. - - :param HandlerResult: output type. Defaults to numpy arrays. """ - sequences = [] - enumsUnitCheck = enumerate(args) - argsList = list(args) - #This for loop identifies pint unit objects and strips them - #of their units. - for num, arg in enumsUnitCheck: - if type(arg) == type(1 * u.m): - argsList[num] = arg.to_base_units().magnitude - enumsUnitless = enumerate(argsList) - #This for loop identifies arguments that are sequences and - #adds their index location to the list 'sequences'. - for num, arg in enumsUnitless: + # Identify the first positional argument that is a sequence. + # Pint units must be ignored to include sequences with units. + argsFirstSequence = None + for num, arg in enumerate(args): # args is a tuple + if isinstance(arg, u.Quantity): + arg = arg.to_base_units().magnitude if isinstance(arg, (list, tuple, np.ndarray)): - sequences.append(num) - #If there are no sequences to iterate through, simply return - #the function. - if len(sequences) == 0: - result = func(*args, **kwargs) + argsFirstSequence = num + break + # Identify the first keyword argument that is a sequence. + kwargsFirstSequence = None + for keyword, arg in kwargs.items(): # kwargs is a dictionary + if isinstance(arg, u.Quantity): + arg = arg.to_base_units().magnitude + if isinstance(arg, (list, tuple, np.ndarray)): + kwargsFirstSequence = keyword + break + + # If there are no sequences, evaluate the function. + if argsFirstSequence is None and kwargsFirstSequence is None: + return func(*args, **kwargs) + # If there are sequences, iterate through them from left to right. + # This means beginning with positional arguments. + elif argsFirstSequence is not None: + result = [] + argsList = list(args) + # For each element of the leftmost sequence, evaluate the + # function with the sequence replaced by the single element. + # Store the results of all the elements in result. + for arg in argsList[argsFirstSequence]: + # We can safely redefine the entire list argument because + # the new definition remains within this namespace; it does + # alter the loop or penetrate further up the function. + argsList[argsFirstSequence] = arg + # This recursive call creates a multi-dimensional array if + # there are multiple sequence arguments. + result.append(wrapper(*argsList, **kwargs)) + # If there are no sequences in the positional arguments, iterate + # through those in the keyword arguments. else: - #iterant keeps track of how many times we've iterated and - #limiter stops the loop once we've iterated as many times - #as there are list elements. Without this check, a few - #erroneous runs will occur, appending the last couple values - #to the end of the list multiple times. - # - #We only care about the length of sequences[0] because this - #function is recursive, and sequences[0] is always the relevant - #sequences for any given run. - limiter = len(argsList[sequences[0]]) - iterant = 0 result = [] - for num in sequences: - for arg in argsList[num]: - if iterant >= limiter: - break - #We can safely replace the entire list argument - #with a single element from it because of the looping - #we're doing. We redefine the object, but that - #definition remains within this namespace and does - #not penetrate further up the function. - argsList[num] = arg - #Here we dive down the rabbit hole. This ends up - #creating a multi-dimensional array shaped by the - #sizes and shapes of the lists passed. - result.append(wrapper(*argsList, - HandlerResult=HandlerResult, **kwargs)) - iterant += 1 - #HandlerResult allows the user to specify what type to - #return the generated sequence as. It defaults to numpy - #arrays because functions tend to handle them better, but if - #the user does not wish to import numpy the base Python options - #are available to them. - if HandlerResult == "nparray": - result = np.array(result) - elif HandlerResult == "tuple": - result = tuple(result) - elif HandlerResult == "list": - result == list(result) - return result + for arg in kwargs[kwargsFirstSequence]: + kwargs[kwargsFirstSequence] = arg + result.append(wrapper(*args, **kwargs)) + + if isinstance(result[0], u.Quantity): + units = result[0].units + return np.array([r.magnitude for r in result]) * units + else: + return np.array(result) + return wrapper return decorate def check_range(*args): - """ - Check whether passed paramters fall within approved ranges. + """Check whether passed paramters fall within approved ranges. Does not return anything, but will raise an error if a parameter falls outside of its defined range. Input should be passed as an array of sequences, with each sequence having three elements: + [0] is the value being checked, [1] is the range parameter(s) within which the value should fall, and [2] is the name of the parameter, for better error messages. + If [2] is not supplied, "Input" will be appended as a generic name. Range requests that this function understands are listed in the diff --git a/aguaclara/design/cdc.py b/aguaclara/design/cdc.py index 3350d194..60e965c7 100644 --- a/aguaclara/design/cdc.py +++ b/aguaclara/design/cdc.py @@ -18,7 +18,7 @@ class CDC(Component): """Design an AguaClara plant's chemical dose controller. - + Design Inputs: - ``q (float * u.L / u.s)``: Flow rate (required) """ @@ -27,7 +27,7 @@ def __init__(self, **kwargs): 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=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 @@ -59,7 +59,7 @@ def _alum_nu(self, coag_conc): """ alum_nu = \ (1 + (4.255 * 10 ** -6) * coag_conc.magnitude ** 2.289) * \ - pc.viscosity_kinematic(self.temp) + pc.viscosity_kinematic_water(self.temp) return alum_nu def _pacl_nu(self, coag_conc): @@ -73,7 +73,7 @@ def _pacl_nu(self, coag_conc): """ pacl_nu = \ (1 + (2.383 * 10 ** -5) * (coag_conc).magnitude ** 1.893) * \ - pc.viscosity_kinematic(self.temp) + pc.viscosity_kinematic_water(self.temp) return pacl_nu def _coag_nu(self, coag_conc, coag_type): @@ -93,16 +93,16 @@ def coag_q_max_est(self): coag_q_max_est = self.q * self.coag_dose_conc_max / \ self.coag_stock_conc_est return coag_q_max_est - + @property def coag_stock_vol(self): coag_stock_vol = ut.ceil_nearest( - self.coag_stock_min_est_time * self.train_n * + self.coag_stock_min_est_time * self.train_n * self.coag_q_max_est, self.chem_tank_vol_supplier ) return coag_stock_vol - + @property def coag_sack_n(self): coag_sack_n = round( @@ -110,7 +110,7 @@ def coag_sack_n(self): self.coag_sack_mass).to_base_units() ) return coag_sack_n - + @property def coag_stock_conc(self): coag_stock_conc = self.coag_sack_n * self.coag_sack_mass / \ @@ -128,7 +128,7 @@ def coag_stock_time_min(self): @property def coag_stock_nu(self): - return self._coag_nu(self.coag_stock_conc, self.coag_type) + return self._coag_nu(self.coag_stock_conc, self.coag_type) #============================================================================== # Small-diameter Tube Design #============================================================================== @@ -138,7 +138,7 @@ def _coag_tube_q_max(self): coag_tube_q_max = ((np.pi * self.coag_tube_id ** 2)/4) * \ np.sqrt((2 * self.error_ratio * self.hl * con.GRAVITY)/self.tube_k) return coag_tube_q_max - + @property def coag_tubes_active_n(self): coag_tubes_active_n = \ @@ -147,15 +147,15 @@ def coag_tubes_active_n(self): @property def coag_tubes_n(self): - coag_tubes_n = self.coag_tubes_active_n + 1 + 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): coag_tube_l = ( @@ -172,7 +172,7 @@ def coag_tank_r(self): index = np.where(self.chem_tank_vol_supplier == self.coag_stock_vol) coag_tank_r = self.chem_tank_dimensions_supplier[0][index] / 2 return coag_tank_r - + @property def coag_tank_h(self): index = np.where(self.chem_tank_vol_supplier == self.coag_stock_vol) @@ -184,4 +184,3 @@ def _DiamTubeAvail(self, en_tube_series = True): return 1*u.mm else: return (1/16)*u.inch - diff --git a/aguaclara/design/ent.py b/aguaclara/design/ent.py index 97dc449e..8752614e 100644 --- a/aguaclara/design/ent.py +++ b/aguaclara/design/ent.py @@ -24,9 +24,9 @@ import numpy as np -class EntranceTank(Component): +class EntranceTank(Component): """Design an AguaClara plant's entrance tank. - + An entrance tank's design relies on the LFOM's and flocculator's design in the same plant, but assumed/default values may be used to design an entrance tank by itself. To design these components in tandem, use @@ -44,19 +44,19 @@ class EntranceTank(Component): (recommended, defaults to 2m) - ``plate_s (float * u.cm)``: The spacing between plates in a plate settler (optional, defaults to 2.5cm) - - ``plate_thickness (float * u.deg)``: The thickness of a plate in a + - ``plate_thickness (float * u.deg)``: The thickness of a plate in a plate settler (optional, defaults to 2mm) - - ``plate_angle (float * u.deg)``: The angle of the plate settler + - ``plate_angle (float * u.deg)``: The angle of the plate settler (optional, defaults to 60 degrees) - - ``plate_capture_vel (float * u.mm / u.s)``: The capture velocity of the + - ``plate_capture_vel (float * u.mm / u.s)``: The capture velocity of the plate settler (optional, defaults to 8m/s) - - ``fab_s(float * u.cm)``: The space needed for a person to remove + - ``fab_s(float * u.cm)``: The space needed for a person to remove the drain pipe (optional, defaults to 5cm) - ``sdr (float)``: Standard demension ratio (optional, - defaults to 41) + defaults to 41) """ def __init__(self, **kwargs): - self.lfom_nd = 2.0 * u.inch # May be innacurate, check with Monroe -Oliver L., oal22, 4 Jun '19 + self.lfom_nd = 2.0 * u.inch # May be innacurate, check with Monroe -Oliver L., oal22, 4 Jun '19 self.floc_chan_w = 42.0 * u.inch self.floc_end_depth = 2.0 * u.m self.plate_s = 2.5 * u.cm @@ -72,13 +72,13 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self._set_drain_pipe() super().set_subcomponents() - + def _set_drain_pipe(self): """The inner diameter of the entrance tank drain pipe.""" drain_pipe_k_minor = \ hl.PIPE_ENTRANCE_K_MINOR + hl.PIPE_EXIT_K_MINOR + hl.EL90_K_MINOR - nu = pc.viscosity_kinematic(self.temp) + nu = pc.viscosity_kinematic_water(self.temp) drain_id = pc.diam_pipe(self.q, self.floc_end_depth, self.floc_end_depth, @@ -91,7 +91,7 @@ def _set_drain_pipe(self): k_minor = drain_pipe_k_minor, spec = self.spec ) - + @property def plate_n(self): """The number of plates in the plate settlers.""" @@ -124,7 +124,7 @@ def l(self): plate_array_thickness = \ (self.plate_thickness * self.plate_n) + \ (self.plate_s * (self.plate_n - 1)) - + l = self.drain_pipe.od + (self.fab_s * 2) + \ ( plate_array_thickness * np.cos(((90 * u.deg) - diff --git a/aguaclara/design/floc.py b/aguaclara/design/floc.py index 3758132a..c42e6462 100644 --- a/aguaclara/design/floc.py +++ b/aguaclara/design/floc.py @@ -25,7 +25,7 @@ class Flocculator(Component): 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 + itself. To design these components in tandem, use :class:`aguaclara.design.ent_floc.EntTankFloc`. Attributes: @@ -35,7 +35,7 @@ class Flocculator(Component): - ``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 + from each other Design Inputs: - ``q (float * u.L/u.s)``: Flow rate (required) @@ -48,9 +48,9 @@ class Flocculator(Component): - ``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 + - ``end_water_depth (float * u.m)``: Depth at the end (optional, defaults to 2m) - - ``drain_t (float * u.min)``: Drain time (optional, + - ``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) @@ -103,12 +103,12 @@ def __init__(self, **kwargs): def vel_grad_avg(self): """The average velocity gradient of water.""" vel_grad_avg = ((u.standard_gravity * self.hl) / - (pc.viscosity_kinematic(self.temp) * self.gt)).to(u.s ** -1) + (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 + """The hydraulic retention time neglecting the volume created by head loss. """ retention_time = (self.gt / self.vel_grad_avg).to(u.s) @@ -127,7 +127,7 @@ def chan_w_min_hs_ratio(self): ( self.BAFFLE_K / ( 2 * self.end_water_depth * - pc.viscosity_kinematic(self.temp) * + pc.viscosity_kinematic_water(self.temp) * self.vel_grad_avg ** 2 ) ) ** (1/3) @@ -199,7 +199,7 @@ def expansion_h_max(self): ( (self.BAFFLE_K / ( - 2 * pc.viscosity_kinematic(self.temp) * + 2 * pc.viscosity_kinematic_water(self.temp) * (self.vel_grad_avg ** 2) ) ) * @@ -225,9 +225,9 @@ def baffle_s(self): (self.BAFFLE_K / ( (2 * self.expansion_h * (self.vel_grad_avg ** 2) * - pc.viscosity_kinematic(self.temp)) + pc.viscosity_kinematic_water(self.temp)) ).to_base_units() - ) ** (1/3) * + ) ** (1/3) * self.q / self.chan_w ).to(u.cm) return baffle_s @@ -244,7 +244,7 @@ def contraction_s(self): @property def obstacle_pipe_od(self): - """The outer diameter of an obstacle pipe. If the available pipe is + """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) diff --git a/aguaclara/design/lfom.py b/aguaclara/design/lfom.py index c01c3691..ae8c3d5b 100644 --- a/aguaclara/design/lfom.py +++ b/aguaclara/design/lfom.py @@ -22,7 +22,7 @@ class LFOM(Component): """Design and AguaClara plant's LFOM. - + Design Inputs: - ``q (float * u.L/u.s)``: Flow rate (recommended, defaults to 20L/s) - ``temp (float * u.degC)``: Water temperature (recommended, defaults to @@ -30,12 +30,12 @@ class LFOM(Component): - ``hl (float * u.cm)``: Head loss (optional, defaults to 20cm) - ``safety_factor (float)``: Safety factor (optional, defaults to 1.5) - ``sdr (float)``: Standard dimension ratio (optional, defaults to 26) - - ``drill_bits (float * u.inch array)``: List of drill bits - (optional) - - ``orifice_s (float * u.cm)``: The spacing between orifices (optional, + - ``drill_bits (float * u.inch array)``: List of drill bits + (optional) + - ``orifice_s (float * u.cm)``: The spacing between orifices (optional, defaults to 0.5cm) """ - def __init__(self, **kwargs): + def __init__(self, **kwargs): self.hl = 20.0 * u.cm self.safety_factor = 1.5 self.sdr = 26.0 @@ -46,11 +46,11 @@ def __init__(self, **kwargs): def stout_w_per_flow(self, h): """The width of a stout weir at a given elevation. - + Args: - ``h (float * u.m)``: Elevation height """ - w_per_flow = 2 / ((2 * pc.gravity * h) ** (1 / 2) * + w_per_flow = 2 / ((2 * u.gravity * h) ** (1 / 2) * con.VC_ORIFICE_RATIO * np.pi * self.hl) return w_per_flow.to_base_units() @@ -71,7 +71,7 @@ def row_b(self): def vel_critical(self): """The average vertical velocity of the water inside the LFOM pipe at the bottom of the orfices.""" - return (4 / (3 * math.pi) * (2 * pc.gravity * self.hl) ** \ + return (4 / (3 * math.pi) * (2 * u.gravity * self.hl) ** \ (1 / 2)).to(u.m/u.s) @property @@ -130,7 +130,7 @@ def orifice_h_per_row(self): def q_submerged(self, row_n, orifice_n_per_row): """The flow rate through some number of submerged rows. - + Args: - ``row_n``: Number of submerged rows """ @@ -160,7 +160,7 @@ def orifice_n_per_row(self): @property def error_per_row(self): - """The error of the design based off the predicted flow rate and + """The error of the design based off the predicted flow rate and the actual flow rate.""" q_error = np.zeros(self.row_n) for i in range(self.row_n): @@ -168,4 +168,3 @@ def error_per_row(self): q_error[i] = (((actual_flow - self.q_per_row[i]) / \ self.q_per_row[i]).to(u.dimensionless)).magnitude return q_error - \ No newline at end of file diff --git a/aguaclara/design/pipeline.py b/aguaclara/design/pipeline.py index fb88c578..b557cd67 100644 --- a/aguaclara/design/pipeline.py +++ b/aguaclara/design/pipeline.py @@ -57,9 +57,9 @@ class PipelineComponent(Component, ABC): """An abstract representation of pipeline components - + This abstract base class (ABC) contains common functionality for: - + #. describing and designing readily constructible pipeline components #. calculating the head loss of a pipeline given its flow rate, and vice-versa. @@ -107,13 +107,13 @@ def nu(self): component. """ if self.fluid_type == 'water': - return pc.viscosity_kinematic(self.temp) + return pc.viscosity_kinematic_water(self.temp) elif self.fluid_type == 'pacl': print('unimplemented') pass - elif self.fluid_type == 'alum': + elif self.fluid_type == 'alum': print('unimplemented') - pass + pass def _get_available_size(self, size): """Return the next larger size which is available, given the list of @@ -133,7 +133,7 @@ def headloss_pipeline(self): return self.headloss else: return self.headloss + self.next.headloss_pipeline - + def _set_next_components_q(self): """Set the flow rates of the next components in this pipeline to be the same as this component. @@ -151,11 +151,11 @@ def flow_pipeline(self, target_headloss): the pipeline """ if type(self) is Pipe: - flow = pc.flow_pipe(self.id, - target_headloss, - self.l, + flow = pc.flow_pipe(self.id, + target_headloss, + self.l, self.nu, - self.pipe_rough, + self.pipe_rough, self.k_minor) else: try: @@ -171,7 +171,7 @@ def flow_pipeline(self, target_headloss): 'this pipeline are Pipe objects.') err = 1.0 headloss = self.headloss_pipeline - + while abs(err) > 0.01 : err = (target_headloss - headloss) / (target_headloss + headloss) flow = flow + err * flow @@ -179,7 +179,7 @@ def flow_pipeline(self, target_headloss): self._set_next_components_q() headloss = self.headloss_pipeline return flow.to(u.L / u.s) - + @abstractmethod def format_print(self): """The string representation of a pipeline component, disregarding other @@ -195,10 +195,10 @@ def _pprint(self): return self.format_print() else: return self.format_print() + '\n' + self.next._pprint() - + def __str__(self): return self._pprint() - + def __repr__(self): return self.__str__() @@ -208,20 +208,20 @@ def _rep_ok(self): """ if self.fluid_type not in self._AVAILABLE_FLUID_TYPES: raise ValueError('fluid_type must be in', self._AVAILABLE_FLUID_TYPES) - + if self.next is not None: if type(self) is Pipe and type(self.next) not in [Elbow, Tee]: raise TypeError('Pipes must be connected with fittings.') elif type(self) in [Elbow] and type(self.next) not in [Pipe]: raise TypeError('Fittings must be followed by pipes.') - - + + class Pipe(PipelineComponent): """Design class for a pipe Instantiate this class to create a readily constructible pipe and calculate its hydraulic features. - + ``Pipe``'s may be instantiated from a nominal size (to fit into an existing pipeline) or inner diameter (to follow hydraulic constraints), but not both. @@ -264,7 +264,7 @@ def __init__(self, **kwargs): self.size = self._get_size(self.id, self.spec) self._rep_ok() - + @property def od(self): """The outer diameter of the pipe""" @@ -275,7 +275,7 @@ def od(self): def _get_size(self, id_, spec): """Get the size of a pipe given an inner diameter and specification. - + Args: - ``id_ (float * u.inch)``: Inner diameter - ``spec (str)``: Pipe specification @@ -287,7 +287,7 @@ def _get_size(self, id_, spec): def _get_id(self, size, spec): """Get the inner diameter of a pipe given the size and specification. - + Args: - ``size (float * u.inch)``: Nominal size - ``spec (str)``: Pipe specifcation @@ -309,7 +309,7 @@ def _get_id_sdr(self, size, sdr): def _get_id_sch40(self, size): """Get the inner diameter of a SCH40 pipe. - + Args: - ``size (float * u.inch)``: Nominal size """ @@ -319,7 +319,7 @@ def _get_id_sch40(self, size): def _get_size_sdr(self, id_, sdr): """Get the size of an SDR pipe. - + Args: - ``id_ (float * u.inch)``: Inner diameter - ``sdr (int)``: Standard dimension ratio @@ -330,12 +330,12 @@ def _get_size_sdr(self, id_, sdr): def _get_size_sch40(self, id_): """Get the size of a SCH40 pipe. - + Args: - ``id_ (float * u.inch)``: Inner diameter """ myindex = (np.abs(AVAILABLE_IDS_SCH40 - id_)).argmin() - self.id = AVAILABLE_IDS_SCH40[myindex] + self.id = AVAILABLE_IDS_SCH40[myindex] return AVAILABLE_SIZES[myindex] def ID_SDR_all_available(self, SDR): @@ -344,7 +344,7 @@ def ID_SDR_all_available(self, SDR): for i in range(len(AVAILABLE_SIZES)): ID.append(self._get_id_sdr(AVAILABLE_SIZES[i], SDR).magnitude) return ID * u.inch - + @property def headloss(self): """Return the total head loss from major and minor losses in a pipe.""" @@ -356,7 +356,7 @@ def format_print(self): """Return the string representation of this pipe.""" return 'Pipe: (OD: {}, Size: {}, ID: {}, Length: {}, Spec: {})'.format( self.od, self.size, self.id, self.l, self.spec) - + def _rep_ok(self): """Verify that this representation of a Pipe is valid.""" if self.spec not in self.AVAILABLE_SPECS: @@ -366,36 +366,36 @@ def _rep_ok(self): raise ValueError('size of the next pipeline component must be the ' 'same size as the current pipeline component') - + class Elbow(PipelineComponent): """Design class for an Elbow - Instantiate this class to create a readily constructible Elbow fitting and + Instantiate this class to create a readily constructible Elbow fitting and calculate its hydraulic features. - + ``Elbow``'s may be instantiated from a nominal size (to fit into an existing pipeline) or inner diameter (to follow hydraulic constraints), but not both. Constants: - - ``AVAILABLE_ANGLES (int * u.deg list)``: The possible angles for this + - ``AVAILABLE_ANGLES (int * u.deg list)``: The possible angles for this fitting. - + Design Inputs: - ``q (float * u.L/u.s)``: Flow rate (recommended, defaults to 20L/s) - - ``temp (float * u.degC)``: Water temperature + - ``temp (float * u.degC)``: Water temperature (recommended, defaults to 20°C) - - ``size (float * u.inch)``: The nominal size + - ``size (float * u.inch)``: The nominal size (recommended, defaults to 0.5 in.) - ``fluid_type (str)``: Fluid type. Must be 'water', 'pacl', or 'alum' (optional, defaults to 'water') - - ``next (PipelineComponent)``: The next pipeline component after the - outlet, cannot be another Elbow or a Tee fitting. + - ``next (PipelineComponent)``: The next pipeline component after the + outlet, cannot be another Elbow or a Tee fitting. + outlet, cannot be another Elbow or a Tee fitting. outlet, cannot be another Elbow or a Tee fitting. - outlet, cannot be another Elbow or a Tee fitting. (optional, defaults to None) - - ``angle (float * u.deg)``: The angle of the fitting, which must be + - ``angle (float * u.deg)``: The angle of the fitting, which must be found in ``AVAILABLE_ANGLES`` (recommended, defaults to 90 °) - - ``id (float * u.inch)``: The inner diameter. + - ``id (float * u.inch)``: The inner diameter. (recommended, defaults to 0.848 * u.inch) """ AVAILABLE_ANGLES = [90 * u.deg, 45 * u.deg] @@ -405,14 +405,14 @@ def __init__(self, **kwargs): self.id = 0.848 * u.inch super().__init__(**kwargs) - + self._set_k_minor() if 'size' in kwargs: self.id = self._get_id(self.size) elif 'id' in kwargs: self.size = self._get_size(self.id) - + self._rep_ok() def _set_k_minor(self): @@ -421,11 +421,11 @@ def _set_k_minor(self): self.k_minor = hl.EL45_K_MINOR elif self.angle == 90 * u.deg: self.k_minor = hl.EL90_K_MINOR - - + + def _get_size(self, id_): """Get the size based off the inner diameter. - + Args: - ``id_ (float * u.inch)``: Inner diameter """ @@ -435,7 +435,7 @@ def _get_size(self, id_): def _get_id(self, size): """Get the inner diameter based off the size. - + Args: - ``size (float * u.inch)``: Nominal Size """ @@ -452,68 +452,68 @@ def format_print(self): """The string representation for an Elbow Fitting.""" return 'Elbow: (Size: {}, ID: {}, Angle: {})'.format( self.size, self.id, self.angle) - + def _rep_ok(self): """Verify that this representation of a Elbow is valid.""" if self.angle not in self.AVAILABLE_ANGLES: raise ValueError('angle must be in ', self.AVAILABLE_ANGLES) - + if self.next is not None and self.size != self.next.size: raise ValueError('The next component doesn\'t have the same size.') - + class Tee(PipelineComponent): """Design class for a Tee - Instantiate this class to create a readily constructible tee fitting and + Instantiate this class to create a readily constructible tee fitting and calculate its hydraulic features. - + ``Tee``'s may be instantiated from a nominal size (to fit into an existing pipeline) or inner diameter (to follow hydraulic constraints), but not both. Constants: - - ``AVAILABLE_PATHS (str list)``: The available paths for the left and - right outlet. Branch meaning the flow would turn, run meaning the flow - stays straight, and stopper meaning there is no flow for that outlet - due to a stopper. - + - ``AVAILABLE_PATHS (str list)``: The available paths for the left and + right outlet. Branch meaning the flow would turn, run meaning the flow + stays straight, and stopper meaning there is no flow for that outlet + due to a stopper. + Design Inputs: - ``q (float * u.L / u.s)``: Flow rate (recommended, defaults to 20L/s) - - ``temp (float * u.degC)``: Water temperature + - ``temp (float * u.degC)``: Water temperature (recommended, defaults to 20°C ) - ``size (float * u.inch)``: The size (recommended, defaults to 0.5 in.) - - ``fluid_type (str)``: The type of fluid flowing inside + - ``fluid_type (str)``: The type of fluid flowing inside (optional, defaults to water) - - ``left (PipelineComponent)``: The type of piping for the left outlet, + - ``left (PipelineComponent)``: The type of piping for the left outlet, cannot be an elbow or tee (recommended, defaults to None) - - ``left_type (str)``: The type of path for the left outlet, - can only be one of the elements in AVAILABLE_PATHS. - can only be one of the elements in AVAILABLE_PATHS. - can only be one of the elements in AVAILABLE_PATHS. + - ``left_type (str)``: The type of path for the left outlet, + can only be one of the elements in AVAILABLE_PATHS. + can only be one of the elements in AVAILABLE_PATHS. + can only be one of the elements in AVAILABLE_PATHS. (recommended, defaults to 'branch') - - ``right (PipelineComponent)``: The type of piping for the right outlet, + - ``right (PipelineComponent)``: The type of piping for the right outlet, cannot be an elbow or tee. (recommended, defaults to None) - - ``right_type (str)``: The type of path for the right outlet, - can only be one of the elements in AVAILABLE_PATHS. - can only be one of the elements in AVAILABLE_PATHS. - can only be one of the elements in AVAILABLE_PATHS. + - ``right_type (str)``: The type of path for the right outlet, + can only be one of the elements in AVAILABLE_PATHS. + can only be one of the elements in AVAILABLE_PATHS. + can only be one of the elements in AVAILABLE_PATHS. (recommended, defaults to 'stopper') - - ``id (float * u.inch)``: The inner diameter. + - ``id (float * u.inch)``: The inner diameter. (recommended, defaults to 0.848 * u.inch) - + """ AVAILABLE_PATHS = ['branch', 'run', 'stopper'] - + def __init__(self, **kwargs): self.left = None self.left_type = 'branch' self.right = None self.right_type = 'stopper' - + self.id = 0.848 * u.inch super().__init__(**kwargs) - + self._set_k_minor() self._set_next() @@ -521,7 +521,7 @@ def __init__(self, **kwargs): self.id = self._get_id(self.size) elif 'id' in kwargs: self.size = self._get_size(self.id) - + self._rep_ok() def _set_k_minor(self): @@ -539,9 +539,9 @@ def _set_k_minor(self): self.right_k_minor = hl.TEE_FLOW_RUN_K_MINOR elif self.right_type == 'stopper': self.right_k_minor = None - + def _set_next(self): - """Sets the next outlet as well the the type of branch for the next + """Sets the next outlet as well the the type of branch for the next outlet. """ if self.left_type == 'stopper': @@ -569,7 +569,7 @@ def headloss(self): def _get_size(self, id_): """Get the nominal size based off the inner diameter - + Args: - ``id_ (float * u.inch)``: Inner diameter """ @@ -579,36 +579,36 @@ def _get_size(self, id_): def _get_id(self, size): """Get the inner diameter based off the size. - + Args: - ``size (float * u.inch)``: Nominal size """ myindex = (np.abs(AVAILABLE_FITTING_SIZES - size)).argmin() - self.size = AVAILABLE_FITTING_SIZES[myindex] + self.size = AVAILABLE_FITTING_SIZES[myindex] return AVAILABLE_FITTING_IDS[myindex] def format_print(self): """The string representation of this tee.""" return 'Tee: (Size: {}, ID: {}, Next Path Type: {})'.format( self.size, self.id, self.next_type) - + def _rep_ok(self): """Verify that this representation of a Tee is valid.""" if [self.left_type, self.right_type].count('stopper') != 1: raise ValueError('All tees must have one stopper.') - + if self.left_type not in self.AVAILABLE_PATHS: raise ValueError( - 'type of branch for left outlet must be in ', + 'type of branch for left outlet must be in ', self.AVAILABLE_PATHS) - + if self.right_type not in self.AVAILABLE_PATHS: raise ValueError( - 'type of branch for right outlet must be in ', + 'type of branch for right outlet must be in ', self.AVAILABLE_PATHS) if self.next is not None and self.size != self.next.size: raise ValueError('The next component doesn\'t have the same size.') - + if self.next is not None and type(self.next) in [Elbow, Tee]: - raise ValueError('Tees cannot be followed by other fittings.') \ No newline at end of file + raise ValueError('Tees cannot be followed by other fittings.') diff --git a/aguaclara/design/sed_chan.py b/aguaclara/design/sed_chan.py index 4c711007..c4f17ab7 100644 --- a/aguaclara/design/sed_chan.py +++ b/aguaclara/design/sed_chan.py @@ -25,12 +25,12 @@ class SedimentationChannel(Component): """Design an AguaClara sedimentation channel. - + The sedimentation channel relies on the number and dimensions of the sedimentation tanks in the same plant, but assumed/default values may be used to design a sedimentation channel by itself. To design these components in tandem, use :class:`aguaclara.design.sed.Sedimentor`. - + Constants: - ``SED_TANK_Q_RATIO (float)``: Permissible ratio of influent flow between the sedimentation tanks @@ -83,7 +83,7 @@ class SedimentationChannel(Component): PLANT_FREEBOARD_H = 5.0 * u.cm WEIR_FREEBOARD_H = 2.0 * u.cm SED_DEPTH_EST = 2.0 * u.m - + def __init__(self, **kwargs): self.sed_tank_n=4 self.sed_tank_w_inner=42.0 * u.inch @@ -125,7 +125,7 @@ def l(self): @property def outlet_weir_hl(self): """Head loss over the outlet channel weir.""" - weir_exit_hl = pc.headloss_weir(self.q, self.l) + weir_exit_hl = pc.headloss_weir_rect(self.q, self.l) return weir_exit_hl @property @@ -141,7 +141,7 @@ def _inlet_w_pre_weir_plumbing_min(self): inlet_w_pre_weir_plumbing_min = pipe.fitting_od(self.sed_tank_inlet_man_nd) + \ 2 * self.fitting_s return inlet_w_pre_weir_plumbing_min - + @property def _inlet_w_pre_weir_hl_min(self): """Minimum width of the inlet channel (pre-weir) that doesn't exceed @@ -152,7 +152,7 @@ def _inlet_w_pre_weir_hl_min(self): self.inlet_depth_max, self.inlet_hl_max, self.l, - pc.viscosity_kinematic(self.temp), + pc.viscosity_kinematic_water(self.temp), mat.CONCRETE_PIPE_ROUGH, False, 0 @@ -163,7 +163,7 @@ def _inlet_w_pre_weir_hl_min(self): def inlet_w_pre_weir(self): """Width of the inlet channel (pre-weir).""" inlet_w_pre_weir = max( - self._inlet_w_pre_weir_plumbing_min, + self._inlet_w_pre_weir_plumbing_min, self._inlet_w_pre_weir_hl_min) return inlet_w_pre_weir @@ -181,25 +181,25 @@ def _inlet_depth_hl_min(self): loss. """ inlet_chan_hl_depth = pc.horiz_chan_h( - self.q, - self.inlet_w_pre_weir, - self.inlet_hl_max, - self.l, - pc.viscosity_kinematic(self.temp), + self.q, + self.inlet_w_pre_weir, + self.inlet_hl_max, + self.l, + pc.viscosity_kinematic_water(self.temp), mat.CONCRETE_PIPE_ROUGH, False) return inlet_chan_hl_depth - + @property - def inlet_depth(self): + def inlet_depth(self): """Depth of the inlet channel.""" - inlet_depth = max(self._inlet_depth_plumbing_min, self._inlet_depth_hl_min) + inlet_depth = max(self._inlet_depth_plumbing_min, self._inlet_depth_hl_min) return inlet_depth @property def inlet_weir_hl(self): """Head loss through the inlet channel weir.""" - inlet_weir_hl = pc.headloss_weir(self.q, self.l) + inlet_weir_hl = pc.headloss_weir_rect(self.q, self.l) return inlet_weir_hl @property @@ -213,19 +213,19 @@ def inlet_weir_h(self): """Height of the inlet channel weir.""" inlet_chan_h_weir = self.inlet_depth + self.WEIR_FREEBOARD_H return inlet_chan_h_weir - + @property def inlet_w_post_weir(self): """Width of the inlet channel (post-weir)""" inlet_w_post_weir = max( - self.w_min, + self.w_min, pc.horiz_chan_w( - self.q, - self.inlet_h, - self.inlet_h, + self.q, + self.inlet_h, + self.inlet_h, self.l, - pc.viscosity_kinematic(self.temp), - mat.CONCRETE_PIPE_ROUGH, + pc.viscosity_kinematic_water(self.temp), + mat.CONCRETE_PIPE_ROUGH, 1, 0 )) return inlet_w_post_weir @@ -243,14 +243,14 @@ def _set_drain_pipe(self): ut.get_sdr(self.drain_spec), self.SED_DEPTH_EST, self.SED_DEPTH_EST + self.inlet_w, - pc.viscosity_kinematic(self.temp), + pc.viscosity_kinematic_water(self.temp), mat.PVC_PIPE_ROUGH, drain_k_minor ) self.drain_pipe = Pipe( - size = drain_nd, - spec = self.drain_spec, + size = drain_nd, + spec = self.drain_spec, k_minor = drain_k_minor, ) @@ -266,7 +266,7 @@ def outlet_depth(self): outlet_depth = self.inlet_depth - self.sed_tank_outlet_man_hl - \ self.sed_tank_diffuser_hl return outlet_depth - + @property def outlet_weir_depth(self): """Depth of the outlet channel weir.""" @@ -274,16 +274,16 @@ def outlet_weir_depth(self): return outlet_weir_depth @property - def outlet_w_pre_weir(self): - """Width of the outlet channel (pre-weir).""" + def outlet_w_pre_weir(self): + """Width of the outlet channel (pre-weir).""" return self.w_min - + @property def outlet_pipe_k_minor(self): outlet_pipe_k_minor = 2 * hl.EL90_K_MINOR + hl.PIPE_ENTRANCE_K_MINOR + \ hl.PIPE_EXIT_K_MINOR return outlet_pipe_k_minor - + @property def outlet_pipe_l(self): outlet_pipe_l = ha.DRAIN_CHAN_WALKWAY_W + self.inlet_w + 1.0 * u.m @@ -296,8 +296,8 @@ def outlet_pipe_q_max(self): pipe.ID_SDR(self.outlet_pipe_nd_max, ut.get_sdr(self.outlet_pipe_spec)), self.outlet_pipe_hl_max, self.outlet_pipe_l, - pc.viscosity_kinematic(self.temp), - mat.PVC_PIPE_ROUGH, + pc.viscosity_kinematic_water(self.temp), + mat.PVC_PIPE_ROUGH, self.outlet_pipe_k_minor ) return ut.round_step( @@ -307,35 +307,35 @@ def outlet_pipe_q_max(self): def _set_outlet_pipe(self): outlet_pipe_q = self.q / self.outlet_pipe_n - + # outlet_pipe_nd = pc.pipe_flow_nd( - # outlet_pipe_q, - # ut.get_sdr(self.outlet_pipe_spec), - # self.outlet_pipe_hl_max, - # self.outlet_pipe_l, - # pc.viscosity_kinematic(self.temp), - # mat.PVC_PIPE_ROUGH, + # outlet_pipe_q, + # ut.get_sdr(self.outlet_pipe_spec), + # self.outlet_pipe_hl_max, + # self.outlet_pipe_l, + # pc.viscosity_kinematic_water(self.temp), + # mat.PVC_PIPE_ROUGH, # self.outlet_pipe_k_minor # ) outlet_pipe_nd = pc.pipe_flow_nd( - outlet_pipe_q, - ut.get_sdr(self.outlet_pipe_spec), - self.outlet_pipe_hl_max, - self.outlet_pipe_l, - pc.viscosity_kinematic(self.temp), - mat.PVC_PIPE_ROUGH, + outlet_pipe_q, + ut.get_sdr(self.outlet_pipe_spec), + self.outlet_pipe_hl_max, + self.outlet_pipe_l, + pc.viscosity_kinematic_water(self.temp), + mat.PVC_PIPE_ROUGH, 2 * hl.EL90_K_MINOR + hl.PIPE_ENTRANCE_K_MINOR + \ hl.PIPE_EXIT_K_MINOR ) self.outlet_pipe = Pipe( - l = self.outlet_pipe_l, - q = outlet_pipe_q, - size = outlet_pipe_nd, + l = self.outlet_pipe_l, + q = outlet_pipe_q, + size = outlet_pipe_nd, spec = self.outlet_pipe_spec, k_minor = self.outlet_pipe_k_minor - ) + ) @property def outlet_pipe_n(self): @@ -348,15 +348,15 @@ def outlet_post_weir_w(self): """Width of the outlet channel (post-weir).""" outlet_post_weir_w = max( #need self.outlet_to_filter_nd - self.fitting_s + pipe.fitting_od(self.outlet_pipe.size), - self.fitting_s + pipe.fitting_od(self.drain_pipe.size), - self.w_min, + self.fitting_s + pipe.fitting_od(self.outlet_pipe.size), + self.fitting_s + pipe.fitting_od(self.drain_pipe.size), + self.w_min, pc.horiz_chan_w( self.q, self.outlet_weir_depth - self.outlet_free_h, #what is outlet_free_h self.outlet_weir_depth, self.l, - pc.viscosity_kinematic(self.temp), + pc.viscosity_kinematic_water(self.temp), mat.PVC_PIPE_ROUGH, 1, hl.PIPE_ENTRANCE_K_MINOR + hl.PIPE_EXIT_K_MINOR + hl.EL90_K_MINOR @@ -385,28 +385,28 @@ def outlet_weir_h(self): """Height of the outlet channel weir.""" outlet_weir_h = self.outlet_weir_depth + self.WEIR_FREEBOARD_H return outlet_weir_h - + @property def w_outer(self): """Outer width of the sedimentation channel.""" w_outer = self.outlet_w + 2 * self.weir_thickness + self.inlet_w + self.sed_wall_thickness return w_outer - + @property def inlet_last_coupling_h(self): """Height of the last coupling in the inlet channel.""" last_coupling_h = self.outlet_weir_depth - 2 * u.cm return last_coupling_h - + @property def inlet_step_h(self): """Height of the steps between each pipe in the inlet channel.""" step_h = self.inlet_last_coupling_h / max(1, self.sed_tank_n - 1) return step_h - + @property def inlet_slope_l(self): """Length of the slopes between each pipe in the inlet channel.""" inlet_slope_l = self.l + self.sed_wall_thickness - \ - pipe.fitting_od(self.sed_tank_inlet_man_nd) - self.fitting_s + pipe.fitting_od(self.sed_tank_inlet_man_nd) - self.fitting_s return inlet_slope_l diff --git a/aguaclara/design/sed_tank.py b/aguaclara/design/sed_tank.py index b868c840..84a91517 100644 --- a/aguaclara/design/sed_tank.py +++ b/aguaclara/design/sed_tank.py @@ -23,78 +23,78 @@ class SedimentationTank(Component): """Design an AguaClara plant's sedimentation tank. - An sedimentation tank's design relies on the sedimentation channel's design + An sedimentation tank's design relies on the sedimentation channel's design in the same plant, but assumed/default values may be used to design an sedimentation tank by itself. To design these components in tandem, use :class:`aguaclara.design.sed.Sedimentor`. Constants: - - ``INLET_MAN_Q_RATIO (float)``: The ratio of the flow in the inlet + - ``INLET_MAN_Q_RATIO (float)``: The ratio of the flow in the inlet manifold. - ``OUTLET_MAN_HL (float * u.cm)``: The headloss of the outlet manifold - - ``JET_REVERSER_ND (float * u.inch)``: The nominal diameter of the jet + - ``JET_REVERSER_ND (float * u.inch)``: The nominal diameter of the jet reverser. - ``JET_PLANE_RATIO (float)``: The ratio for the jet plane - - ``JET_REVERSER_TO_DIFFUSERS_H (float * u.cm)``: The height between + - ``JET_REVERSER_TO_DIFFUSERS_H (float * u.cm)``: The height between the jet reverser and diffusers. - ``WALL_THICKNESS (float * u.m)``: The thickness of the sed tank walls - ``DIFFUSER_L (float * u.cm)``: The length of a diffuser. - + Design Inputs: - - ``q (float * u.L / u.s)``: Plant flow rate + - ``q (float * u.L / u.s)``: Plant flow rate (recommended, defaults to 20L/s) - - ``temp (float * u.degC)``: Water temperature (recommended, defaults to + - ``temp (float * u.degC)``: Water temperature (recommended, defaults to 20°C) - - ``vel_upflow (float * u.mm / u.s)``: Upflow velocity + - ``vel_upflow (float * u.mm / u.s)``: Upflow velocity (optional, defaults to 1mm/s) - ``l_inner (float * u.m)``: The inner length (optional, defaults to 5.8m) - - ``w_inner (float * u.inch)``: The inner width + - ``w_inner (float * u.inch)``: The inner width (optional, defaults to 42in.) - ``diffuser_vel_max (float * u.cm / u.s)``: The max velocity of a diffuser (optional, defaults to 44.29 cm/s) - - ``diffuser_n (int)``:The nunber of diffusers + - ``diffuser_n (int)``:The nunber of diffusers (optional, defaults to 108) - - ``diffuser_wall_thickness (float * u.inch)``: The thickness of the + - ``diffuser_wall_thickness (float * u.inch)``: The thickness of the wall of a diffuser (optional, defaults to 1.17in.) - ``diffuser_sdr (int)``: The standard dimension ratio of a diffuser (optional, defaults to 41) - ``inlet_man_hl (float * u.cm)``: The headloss of the inlet manifold (optional, defaults to 1cm) - - ``inlet_man_sdr (float)``: The standard dimension ratio of the inlet + - ``inlet_man_sdr (float)``: The standard dimension ratio of the inlet manifold (optional, defaults to 41) - - ``jet_reverser_sdr (int)``: The standard dimension ratio of the jet + - ``jet_reverser_sdr (int)``: The standard dimension ratio of the jet reverser (optional, defaults to 26) - - ``plate_settler_angle (float * u.deg)``: The angle of the plate + - ``plate_settler_angle (float * u.deg)``: The angle of the plate settler (optional, defaults to 60°) - - ``plate_settler_s (float * u.cm)``: Spacing in between plate settlers + - ``plate_settler_s (float * u.cm)``: Spacing in between plate settlers (optional, defaults to 2.5cm) - - ``plate_settler_thickness (float * u.mm)``: Thickness of a plate + - ``plate_settler_thickness (float * u.mm)``: Thickness of a plate settler (optional, defaults to 2mm) - - ``plate_settler_cantilever_l_max (float * u.cm)``: The max length of + - ``plate_settler_cantilever_l_max (float * u.cm)``: The max length of the plate settler cantilever (optional, defaults to 20cm) - - ``plate_settler_vel_capture (float * u.mm / u.s)``: The capture + - ``plate_settler_vel_capture (float * u.mm / u.s)``: The capture velocity of a plate settler (optional, defaults to 0.12mm/s) - - ``outlet_man_orifice_hl (float * u.cm)``: The headloss of the + - ``outlet_man_orifice_hl (float * u.cm)``: The headloss of the orifices in the outlet manifold (optional, defaults to 4cm) - - ``outlet_man_orifice_q_ratio_max (float)``: The max ratio of the flow + - ``outlet_man_orifice_q_ratio_max (float)``: The max ratio of the flow rate for the orifices of the outlet manifold (optional, defaults to 0.8) - - ``outlet_man_orifice_n_est (int)``: The estimated number of orifices + - ``outlet_man_orifice_n_est (int)``: The estimated number of orifices for the outlet manifold (optional, defaults to 58) - - ``outlet_man_sdr (int)``: The standard dimension ratio of the outlet + - ``outlet_man_sdr (int)``: The standard dimension ratio of the outlet manifold (optional, defaults to 41) - ``slope_angle (float * u.deg)``: The angle at the bottom of the sed tank (optional, defaults to 50°) - - ``side_slope_to_floc_weir_h_min (float * u.cm)``: The minimum height + - ``side_slope_to_floc_weir_h_min (float * u.cm)``: The minimum height between the side slope and the floc weir. (optional, defaults to 5cm) - - ``sed_chan_w_outer (float * u.cm)``: The outer width of the + - ``sed_chan_w_outer (float * u.cm)``: The outer width of the sedimentation channel (optional, defaults to 60cm) - - ``sed_chan_weir_thickness (float * u.cm)``: The thickness of the + - ``sed_chan_weir_thickness (float * u.cm)``: The thickness of the sedimentation channel weir (optional, defaults to 5cm) - - ``floc_weir_to_plate_frame_h (float * u.cm)``: The height from the - top of the floc weir to the plate settler frame (optional, defaults + - ``floc_weir_to_plate_frame_h (float * u.cm)``: The height from the + top of the floc weir to the plate settler frame (optional, defaults to 10cm) - - ``hopper_slope_vertical_angle (float * u.deg)``: The angle of the + - ``hopper_slope_vertical_angle (float * u.deg)``: The angle of the hopper wall slopes to vertical (optional, defaults to 60°) """ @@ -130,7 +130,7 @@ def __init__(self, **kwargs): self.outlet_man_orifice_q_ratio_max=0.8 self.outlet_man_orifice_n_est = 58 self.outlet_man_sdr=41 - + self.slope_angle=50. * u.deg self.side_slope_to_floc_weir_h_min = 5.0 * u.cm self.sed_chan_w_outer = 60.0 * u.cm @@ -145,18 +145,18 @@ def q_tank(self): """The flow rate present in the tank.""" q_tank = self.l_inner * self.w_inner * self.vel_upflow return q_tank.to(u.L / u.s) - + @property def diffuser_hl(self): """The headloss of the diffuser.""" return self.inlet_man_hl / self.diffuser_n - + @property def diffuser_vel(self): """The velocity of the diffuser""" diffuser_vel = np.sqrt(2 * con.GRAVITY * self.diffuser_hl) return diffuser_vel.to(u.mm / u.s) - + @property def diffuser_w_inner(self): """The inner width(neglecting walls) of the diffuser.""" @@ -178,7 +178,7 @@ def inlet_man_v_max(self): ) return vel_manifold_max.to(u.m / u.s) - @property + @property def inlet_man_nd(self): """The nominal diameter of the inlet manifold""" diam_inner = np.sqrt(4 * self.q_tank / (np.pi * self.inlet_man_v_max)) @@ -193,8 +193,8 @@ def outlet_man_nd(self): self.OUTLET_MAN_HL, self.l_inner, self.outlet_man_orifice_q_ratio_max, - pc.viscosity_kinematic(self.temp), - mat.PVC_PIPE_ROUGH.to(u.m), + pc.viscosity_kinematic_water(self.temp), + mat.PVC_PIPE_ROUGH.to(u.m), hl.PIPE_EXIT_K_MINOR, self.outlet_man_orifice_n_est, self.outlet_man_sdr @@ -220,7 +220,7 @@ def plate_l(self): np.cos(self.plate_settler_angle)) ).to(u.m) return L_sed_plate - + @property def outlet_man_orifice_q(self): """The flow rate in the orifices of the outlet manifold.""" @@ -235,9 +235,9 @@ def outlet_man_orifice_q(self): def outlet_man_orifice_spacing(self): """The spacing between orifices on the outlet manifold.""" outlet_man_orifice_spacing = ( - self.l_inner - - pipe.socket_depth(self.outlet_man_nd) - - pipe.cap_thickness(self.outlet_man_nd) - + self.l_inner - + pipe.socket_depth(self.outlet_man_nd) - + pipe.cap_thickness(self.outlet_man_nd) - self.outlet_man_orifice_d ) / ((self.q_tank / self.outlet_man_orifice_q) - 1) return outlet_man_orifice_spacing @@ -247,19 +247,19 @@ def outlet_man_orifice_n(self): """The number of orifices on the outlet manifold.""" outlet_orifice_n = math.floor( ( - self.l_inner - - pipe.socket_depth(self.outlet_man_nd) - - pipe.cap_thickness(self.outlet_man_nd) - + self.l_inner - + pipe.socket_depth(self.outlet_man_nd) - + pipe.cap_thickness(self.outlet_man_nd) - self.outlet_man_orifice_d ) / self.outlet_man_orifice_spacing - ) + 1 + ) + 1 return outlet_orifice_n @property def outlet_orifice_hl(self): """The headloss for the orifices of the outlet""" outlet_orifice_hl = pc.head_orifice( - self.outlet_man_nd, + self.outlet_man_nd, con.VC_ORIFICE_RATIO, self.q_tank / self.outlet_man_orifice_n ) @@ -269,7 +269,7 @@ def outlet_orifice_hl(self): def side_slopes_w(self): """The width of the side slopes.""" side_slopes_w = ( - self.w_inner - + self.w_inner - pipe.ID_SDR(self.JET_REVERSER_ND, self.jet_reverser_sdr) ) / 2 return side_slopes_w.to(u.m) @@ -286,7 +286,7 @@ def inlet_man_h(self): inlet_man_h = self.JET_REVERSER_TO_DIFFUSERS_H + self.DIFFUSER_L + \ ( pipe.OD(self.inlet_man_nd)/ 2 ) return inlet_man_h - + @property def floc_weir_h(self): """The height of the floc weir.""" @@ -295,4 +295,4 @@ def floc_weir_h(self): mat.CONCRETE_THICKNESS_MIN, self.side_slopes_h + self.side_slope_to_floc_weir_h_min ) - return floc_weir_h \ No newline at end of file + return floc_weir_h diff --git a/aguaclara/research/environmental_processes_analysis.py b/aguaclara/research/environmental_processes_analysis.py index 8d2db3e1..b1321cd8 100644 --- a/aguaclara/research/environmental_processes_analysis.py +++ b/aguaclara/research/environmental_processes_analysis.py @@ -1,5 +1,6 @@ from aguaclara.research.procoda_parser import * from aguaclara.core.units import u +import aguaclara.core.utility as ut import pandas as pd import numpy as np from scipy import special @@ -17,6 +18,7 @@ P_CO2 = 10**(-3.5) * u.atm +@ut.list_handler() def invpH(pH): """Calculate inverse pH, i.e. hydronium ion concentration, given pH. @@ -35,6 +37,7 @@ def invpH(pH): return 10**(-pH)*u.mol/u.L +@ut.list_handler() def alpha0_carbonate(pH): """Calculate the fraction of total carbonates in carbonic acid form (H2CO3) @@ -55,6 +58,7 @@ def alpha0_carbonate(pH): return alpha0_carbonate +@ut.list_handler() def alpha1_carbonate(pH): """Calculate the fraction of total carbonates in bicarbonate form (HCO3-) @@ -75,6 +79,7 @@ def alpha1_carbonate(pH): return alpha1_carbonate +@ut.list_handler() def alpha2_carbonate(pH): """Calculate the fraction of total carbonates in carbonate form (CO3-2) @@ -95,6 +100,7 @@ def alpha2_carbonate(pH): return alpha2_carbonate +@ut.list_handler() def ANC_closed(pH, total_carbonates): """Calculate the acid neutralizing capacity (ANC) under a closed system in which no carbonates are exchanged with the atmosphere during the @@ -120,6 +126,7 @@ def ANC_closed(pH, total_carbonates): 1 * u.eq/u.mol * Kw/invpH(pH) - 1 * u.eq/u.mol * invpH(pH)) +@ut.list_handler() def ANC_open(pH): """Calculate the acid neutralizing capacity (ANC) calculated under an open system based on pH. @@ -179,6 +186,7 @@ def aeration_data(DO_column, dirpath): return aeration_results +@ut.list_handler() def O2_sat(P_air, temp): """Calculate saturaed oxygen concentration in mg/L for 278 K < T < 318 K diff --git a/aguaclara/research/floc_model.py b/aguaclara/research/floc_model.py index e273ccdb..5482d897 100644 --- a/aguaclara/research/floc_model.py +++ b/aguaclara/research/floc_model.py @@ -409,7 +409,7 @@ def gamma_coag(ConcClay, ConcAluminum, coag, material, # @u.wraps(None, [u.kg/u.m**3, u.kg/u.m**3, None, None], False) -# @ut.list_handler() +@ut.list_handler() def gamma_humic_acid_to_coag(ConcAl, ConcNatOrgMat, NatOrgMat, coag): """Return the fraction of the coagulant that is coated with humic acid. @@ -434,6 +434,7 @@ def gamma_humic_acid_to_coag(ConcAl, ConcNatOrgMat, NatOrgMat, coag): # @u.wraps(None, [u.m, u.kg/u.m**3, u.kg/u.m**3, u.kg/u.m**3, None, # None, None, u.dimensionless], False) +@ut.list_handler() def pacl_term(DiamTube, ConcClay, ConcAl, ConcNatOrgMat, NatOrgMat, coag, material, RatioHeightDiameter): """Return the fraction of the surface area that is covered with coagulant @@ -468,6 +469,7 @@ def pacl_term(DiamTube, ConcClay, ConcAl, ConcNatOrgMat, NatOrgMat, # @u.wraps(None, [u.m, u.kg/u.m**3, u.kg/u.m**3, u.kg/u.m**3, # None, None, None, u.dimensionless], False) +@ut.list_handler() def alpha_pacl_clay(DiamTube, ConcClay, ConcAl, ConcNatOrgMat, NatOrgMat, coag, material, RatioHeightDiameter): """""" @@ -479,6 +481,7 @@ def alpha_pacl_clay(DiamTube, ConcClay, ConcAl, ConcNatOrgMat, # @u.wraps(None, [u.m, u.kg/u.m**3, u.kg/u.m**3, u.kg/u.m**3, # None, None, None, u.dimensionless], False) +@ut.list_handler() def alpha_pacl_pacl(DiamTube, ConcClay, ConcAl, ConcNatOrgMat, NatOrgMat, coag, material, RatioHeightDiameter): """""" @@ -489,6 +492,7 @@ def alpha_pacl_pacl(DiamTube, ConcClay, ConcAl, ConcNatOrgMat, # @u.wraps(None, [u.m, u.kg/u.m**3, u.kg/u.m**3, u.kg/u.m**3, # None, None, None, u.dimensionless], False) +@ut.list_handler() def alpha_pacl_nat_org_mat(DiamTube, ConcClay, ConcAl, ConcNatOrgMat, NatOrgMat, coag, material, RatioHeightDiameter): """""" @@ -502,6 +506,7 @@ def alpha_pacl_nat_org_mat(DiamTube, ConcClay, ConcAl, ConcNatOrgMat, # @u.wraps(None, [u.m, u.kg/u.m**3, u.kg/u.m**3, u.kg/u.m**3, # None, None, None, u.dimensionless], False) +@ut.list_handler() def alpha(DiamTube, ConcClay, ConcAl, ConcNatOrgMat, NatOrgMat, coag, material, RatioHeightDiameter): """""" @@ -518,13 +523,14 @@ def alpha(DiamTube, ConcClay, ConcAl, ConcNatOrgMat, # @u.wraps(None, [u.W/u.kg, u.degK, u.s, u.m, # u.kg/u.m**3, u.kg/u.m**3, u.kg/u.m**3, None, # None, None, u.dimensionless, u.dimensionless], False) +@ut.list_handler() def pc_viscous(EnergyDis, Temp, Time, DiamTube, ConcClay, ConcAl, ConcNatOrgMat, NatOrgMat, coag, material, FittingParam, RatioHeightDiameter): """""" return ((3/2) * np.log10((2/3) * np.pi * FittingParam * Time - * np.sqrt(EnergyDis / (pc.viscosity_kinematic(Temp)) + * np.sqrt(EnergyDis / (pc.viscosity_kinematic_water(Temp)) ) * alpha(DiamTube, ConcClay, ConcAl, ConcNatOrgMat, NatOrgMat, coag, material, RatioHeightDiameter) @@ -538,6 +544,7 @@ def pc_viscous(EnergyDis, Temp, Time, DiamTube, # @u.wraps(u.kg/u.m**3, [u.kg/u.m**3, u.kg/u.m**3, u.dimensionless, u.m, # None, None, u.degK], False) +@ut.list_handler() def dens_floc(ConcAl, ConcClay, DIM_FRACTAL, DiamTarget, coag, material, Temp): """Calculate floc density as a function of size.""" WaterDensity = pc.density_water(Temp) @@ -551,12 +558,13 @@ def dens_floc(ConcAl, ConcClay, DIM_FRACTAL, DiamTarget, coag, material, Temp): # @u.wraps(u.m/u.s, [u.kg/u.m**3, u.kg/u.m**3, None, None, u.dimensionless, # u.m, u.degK], False) +@ut.list_handler() def vel_term_floc(ConcAl, ConcClay, coag, material, DIM_FRACTAL, DiamTarget, Temp): """Calculate floc terminal velocity.""" WaterDensity = pc.density_water(Temp) return (((u.gravity * material.Diameter**2) - / (18 * PHI_FLOC * pc.viscosity_kinematic(Temp)) + / (18 * PHI_FLOC * pc.viscosity_kinematic_water(Temp)) ) * ((dens_floc_init(ConcAl, ConcClay, coag, material) - WaterDensity @@ -569,12 +577,13 @@ def vel_term_floc(ConcAl, ConcClay, coag, material, DIM_FRACTAL, # @u.wraps(u.m, [u.kg/u.m**3, u.kg/u.m**3, None, None, # u.dimensionless, u.m/u.s, u.degK], False) +@ut.list_handler() def diam_floc_vel_term(ConcAl, ConcClay, coag, material, DIM_FRACTAL, VelTerm, Temp): """Calculate floc diamter as a function of terminal velocity.""" WaterDensity = pc.density_water(Temp) return (material.Diameter * (((18 * VelTerm * PHI_FLOC - * pc.viscosity_kinematic(Temp) + * pc.viscosity_kinematic_water(Temp) ) / (u.gravity * material.Diameter**2) ) @@ -591,6 +600,7 @@ def diam_floc_vel_term(ConcAl, ConcClay, coag, material, # @u.wraps(u.s, [u.W/u.kg, u.degK, u.kg/u.m**3, u.kg/u.m**3, None, None, # u.m, u.m, u.dimensionless, u.dimensionless], # False) +@ut.list_handler() def time_col_laminar(EnergyDis, Temp, ConcAl, ConcClay, coag, material, DiamTarget, DiamTube, DIM_FRACTAL, RatioHeightDiameter): """Calculate single collision time for laminar flow mediated collisions. @@ -599,7 +609,7 @@ def time_col_laminar(EnergyDis, Temp, ConcAl, ConcClay, coag, material, """ return (((1/6) * ((6/np.pi)**(1/3)) * frac_vol_floc_initial(ConcAl, ConcClay, coag, material) ** (-2/3) - * (pc.viscosity_kinematic(Temp) / EnergyDis) ** (1 / 2) + * (pc.viscosity_kinematic_water(Temp) / EnergyDis) ** (1 / 2) * (DiamTarget / material.Diameter) ** (2*DIM_FRACTAL/3 - 2) ) # End of the numerator / (gamma_coag(ConcClay, ConcAl, coag, material, DiamTube, @@ -610,6 +620,7 @@ def time_col_laminar(EnergyDis, Temp, ConcAl, ConcClay, coag, material, # @u.wraps(u.s, [u.W/u.kg, u.kg/u.m**3, u.kg/u.m**3, None, None, # u.m, u.dimensionless], False) +@ut.list_handler() def time_col_turbulent(EnergyDis, ConcAl, ConcClay, coag, material, DiamTarget, DIM_FRACTAL): """Calculate single collision time for turbulent flow mediated collisions. @@ -625,17 +636,20 @@ def time_col_turbulent(EnergyDis, ConcAl, ConcClay, coag, material, ########### Kolmogorov and viscous length scales ########### # @u.wraps(u.m, [u.W/u.kg, u.degK], False) +@ut.list_handler() def eta_kolmogorov(EnergyDis, Temp): - return (((pc.viscosity_kinematic(Temp) ** 3) / EnergyDis) ** (1 / 4)).to(u.m) + return (((pc.viscosity_kinematic_water(Temp) ** 3) / EnergyDis) ** (1 / 4)).to(u.m) # @u.wraps(u.m, [u.W/u.kg, u.degK], False) +@ut.list_handler() def lambda_vel(EnergyDis, Temp): return (RATIO_KOLMOGOROV * eta_kolmogorov(EnergyDis, Temp)).to(u.m) # @u.wraps(u.m, [u.W/u.kg, u.degK, u.kg/u.m**3, u.kg/u.m**3, None, None, # u.dimensionless], False) +@ut.list_handler() def diam_kolmogorov(EnergyDis, Temp, ConcAl, ConcClay, coag, material, DIM_FRACTAL): """Return the size of the floc with separation distances equal to @@ -652,6 +666,7 @@ def diam_kolmogorov(EnergyDis, Temp, ConcAl, ConcClay, coag, material, # @u.wraps(u.m, [u.W/u.kg, u.degK, u.kg/u.m**3, u.kg/u.m**3, None, None, # u.dimensionless], False) +@ut.list_handler() def diam_vel(EnergyDis, Temp, ConcAl, ConcClay, coag, material, DIM_FRACTAL): return (material.Diameter * ((lambda_vel(EnergyDis, Temp) / material.Diameter) @@ -663,6 +678,7 @@ def diam_vel(EnergyDis, Temp, ConcAl, ConcClay, coag, material, DIM_FRACTAL): # @u.wraps(u.m, u.W/u.kg, False) +@ut.list_handler() def diam_floc_max(epsMax): """ .. deprecated:: 0.1.13 @@ -689,6 +705,7 @@ def diam_floc_max(epsMax): # @u.wraps(u.W/u.kg, u.m, False) +@ut.list_handler() def ener_dis_diam_floc(Diam): """ .. deprecated:: 0.1.13 @@ -705,17 +722,20 @@ def ener_dis_diam_floc(Diam): ##### Velocity gradient in tubing for lab scale laminar flow flocculators ##### # @u.wraps(1/u.s, [u.m**3/u.s, u.m], False) +@ut.list_handler() def g_straight(PlantFlow, IDTube): return (64 * PlantFlow / (3 * np.pi * IDTube**3)).to(1/u.s) # @u.wraps(None, [u.m**3/u.s, u.m, u.degK], False) +@ut.list_handler() def reynolds_rapid_mix(PlantFlow, IDTube, Temp): return (4 * PlantFlow / (np.pi * IDTube - * pc.viscosity_kinematic(Temp))).to(u.dimensionless) + * pc.viscosity_kinematic_water(Temp))).to(u.dimensionless) # @u.wraps(None, [u.m**3/u.s, u.m, u.m, u.degK], False) +@ut.list_handler() def dean_number(PlantFlow, IDTube, RadiusCoil, Temp): """Return the Dean Number. @@ -730,6 +750,7 @@ def dean_number(PlantFlow, IDTube, RadiusCoil, Temp): # @u.wraps(1/u.s, [u.m**3/u.s, u.m, u.m, u.degK], False) +@ut.list_handler() def g_coil(FlowPlant, IDTube, RadiusCoil, Temp): """We need a reference for this. @@ -744,12 +765,14 @@ def g_coil(FlowPlant, IDTube, RadiusCoil, Temp): # @u.wraps(u.s, [u.m, u.m, u.m**3/u.s], False) +@ut.list_handler() def time_res_tube(IDTube, LengthTube, FlowPlant): """Calculate residence time in the flocculator.""" return (LengthTube * np.pi * (IDTube**2 / 4) / FlowPlant).to(u.s) # @u.wraps(None, [u.m**3/u.s, u.m, u.m, u.m, u.degK], False) +@ut.list_handler() def g_time_res(FlowPlant, IDTube, RadiusCoil, LengthTube, Temp): """G Residence Time calculated for a coiled tube flocculator.""" return (g_coil(FlowPlant, IDTube, RadiusCoil, Temp) diff --git a/aguaclara/research/peristaltic_pump.py b/aguaclara/research/peristaltic_pump.py index 1718f187..f8bcdb8e 100644 --- a/aguaclara/research/peristaltic_pump.py +++ b/aguaclara/research/peristaltic_pump.py @@ -1,4 +1,5 @@ from aguaclara.core.units import u +import aguaclara.core.utility as ut import numpy as np import pandas as pd import os @@ -11,6 +12,7 @@ k_nonlinear = 13 +@ut.list_handler() def vol_per_rev_3_stop(color="", inner_diameter=0): """Return the volume per revolution of an Ismatec 6 roller pump given the inner diameter (ID) of 3-stop tubing. The calculation is @@ -48,6 +50,7 @@ def vol_per_rev_3_stop(color="", inner_diameter=0): return (term1 * term2).to(u.mL/u.rev) +@ut.list_handler() def ID_colored_tube(color): """Look up the inner diameter of Ismatec 3-stop tubing given its color code. @@ -75,6 +78,7 @@ def ID_colored_tube(color): return df[idx]['Diameter (mm)'].values[0] * u.mm +@ut.list_handler() def vol_per_rev_LS(id_number): """Look up the volume per revolution output by a Masterflex L/S pump through L/S tubing of the given ID number. @@ -101,6 +105,7 @@ def vol_per_rev_LS(id_number): return df[idx]['Flow (mL/rev)'].values[0] * u.mL/u.turn +@ut.list_handler() def flow_rate(vol_per_rev, rpm): """Return the flow rate from a pump given the volume of fluid pumped per revolution and the desired pump speed. diff --git a/aguaclara/research/stock_qc.py b/aguaclara/research/stock_qc.py index 4aa7572a..1ce4bd18 100644 --- a/aguaclara/research/stock_qc.py +++ b/aguaclara/research/stock_qc.py @@ -1,4 +1,5 @@ from aguaclara.core.units import u +import aguaclara.core.utility as ut class Stock(object): @@ -84,6 +85,7 @@ def C_stock(self): """ return self._C_sys * (self._Q_sys / self._Q_stock).to(u.dimensionless) + @ut.list_handler() def rpm(self, vol_per_rev): """Return the pump speed required for the reactor's stock of material given the volume of fluid output per revolution by the stock's pump. @@ -96,6 +98,7 @@ def rpm(self, vol_per_rev): """ return Stock.rpm(self, vol_per_rev, self._Q_stock).to(u.rev/u.min) + @ut.list_handler() def T_stock(self, V_stock): """Return the amount of time at which the stock of materal will be depleted. @@ -108,6 +111,7 @@ def T_stock(self, V_stock): """ return Stock.T_stock(self, V_stock, self._Q_stock).to(u.hr) + @ut.list_handler() def M_stock(self, V_stock): """Return the mass of undiluted material required for the stock concentration. @@ -120,6 +124,7 @@ def M_stock(self, V_stock): """ return Stock.M_stock(self, V_stock, self.C_stock()) + @ut.list_handler() def V_super_stock(self, V_stock, C_super_stock): """Return the volume of super (more concentrated) stock that must be diluted for the desired stock volume and required stock concentration. @@ -134,6 +139,7 @@ def V_super_stock(self, V_stock, C_super_stock): """ return Stock.V_super_stock(self, V_stock, self.C_stock(), C_super_stock) + @ut.list_handler() def dilution_factor(self, C_super_stock): """Return the dilution factor of the concentration of material in the stock relative to the super stock. @@ -210,6 +216,7 @@ def Q_stock(self): """ return self._Q_sys * (self._C_sys / self._C_stock).to(u.dimensionless) + @ut.list_handler() def rpm(self, vol_per_rev): """Return the pump speed required for the reactor's stock of material given the volume of fluid output per revolution by the stock's pump. @@ -222,6 +229,7 @@ def rpm(self, vol_per_rev): """ return Stock.rpm(self, vol_per_rev, self.Q_stock()).to(u.rev/u.min) + @ut.list_handler() def T_stock(self, V_stock): """Return the amount of time at which the stock of materal will be depleted. @@ -234,6 +242,7 @@ def T_stock(self, V_stock): """ return Stock.T_stock(self, V_stock, self.Q_stock()).to(u.hr) + @ut.list_handler() def M_stock(self, V_stock): """Return the mass of undiluted material required for the stock concentration. @@ -246,6 +255,7 @@ def M_stock(self, V_stock): """ return Stock.M_stock(self, V_stock, self._C_stock) + @ut.list_handler() def V_super_stock(self, V_stock, C_super_stock): """Return the volume of super (more concentrated) stock that must be diluted for the desired stock volume and stock concentration. @@ -260,6 +270,7 @@ def V_super_stock(self, V_stock, C_super_stock): """ return Stock.V_super_stock(self, V_stock, self._C_stock, C_super_stock) + @ut.list_handler() def dilution_factor(self, C_super_stock): """Return the dilution factor of the concentration of material in the stock relative to the super stock. diff --git a/setup.py b/setup.py index 7a81749d..253f99b8 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name = 'aguaclara', - version = '0.1.14', + version = '0.1.15', description = ( 'An open-source Python package for designing and performing research ' 'on AguaClara water treatment plants.' diff --git a/tests/core/test_head_loss.py b/tests/core/test_head_loss.py index 7851a628..17cacf39 100644 --- a/tests/core/test_head_loss.py +++ b/tests/core/test_head_loss.py @@ -8,45 +8,59 @@ class KValuesCalculationTest(unittest.TestCase): + def assertAlmostEqualQuantity(self, first, second, places=7): + self.assertAlmostEqual(first.magnitude, second.magnitude, places) + self.assertEqual(first.units, second.units, places) + # Test Reductions def test_k_value_reduction_square_turbulent(self): - self.assertAlmostEqual(k.k_value_reduction(pipe.OD(4), pipe.OD(2), 4 * u.L / u.s), 5.6677039356929662) + self.assertAlmostEqualQuantity(k.k_value_reduction(pipe.OD(4*u.inch), pipe.OD(2*u.inch), 4 * u.L / u.s), + 5.689246477984541*u.dimensionless) def test_k_value_reduction_laminar(self): - self.assertAlmostEqual(k.k_value_reduction(pipe.OD(1), pipe.OD(0.5), 0.1 * u.L / u.s), 2.1802730749680945) + self.assertAlmostEqualQuantity(k.k_value_reduction(pipe.OD(1*u.inch), pipe.OD(0.5*u.inch), 0.1 * u.L / u.s), + 2.2100363820127233*u.dimensionless) def test_k_value_reduction_from_very_large_pipe_turbulent(self): - self.assertAlmostEqual(k.k_value_reduction(pipe.OD(400), pipe.OD(4), 4 * u.L / u.s), 105560.31724275621) + self.assertAlmostEqualQuantity(k.k_value_reduction(pipe.OD(400*u.inch), pipe.OD(4*u.inch), 4 * u.L / u.s), + 222469.482*u.dimensionless, 2) # Test Expansions def test_k_value_expansion_into_large_tank(self): - self.assertAlmostEqual(k.k_value_expansion(pipe.OD(4), pipe.OD(400), 4 * u.L / u.s), 1.0110511331493719) + self.assertAlmostEqualQuantity(k.k_value_expansion(pipe.OD(4*u.inch), pipe.OD(400*u.inch), 4 * u.L / u.s), + 1.0148940670855733*u.dimensionless) def test_k_value_expansion_into_very_large_pipe_laminar(self): - self.assertAlmostEqual(k.k_value_expansion(pipe.OD(1), pipe.OD(400), 0.1 * u.L / u.s), 1.0216612503304363) + self.assertAlmostEqualQuantity(k.k_value_expansion(pipe.OD(1*u.inch), pipe.OD(400*u.inch), 0.1 * u.L / u.s), + 1.9999999165201428*u.dimensionless) # Test Orifices # Test private functions def test_k_value_thick_orifice_high_headloss(self): - self.assertAlmostEqual(k._k_value_thick_orifice(0.02, 0.002, 0.000002, 2), 1594340.3320537778) + self.assertAlmostEqual(k._k_value_thick_orifice(0.02, 0.002, 0.000002, 2), + 1594340.3320537778) def test_k_value_thin_orifice_high_headloss(self): - self.assertAlmostEqual(k._k_value_thin_sharp_orifice(0.02, 0.002, 2), 1594433.5406999998) + self.assertAlmostEqual(k._k_value_thin_sharp_orifice(0.02, 0.002, 2), + 1594433.5406999998) # Test public function def test_k_value_thin_orifice_regular_high_headloss(self): - self.assertAlmostEqual(k.k_value_orifice(0.02 * u.m, 0.002 * u.m, 0 * u.m, 1*u.L/u.s), 1697.9866773221295) + self.assertAlmostEqualQuantity(k.k_value_orifice(0.02 * u.m, 0.002 * u.m, 0 * u.m, 1*u.L/u.s), + 1697.9866773221295*u.dimensionless) - # def test_k_value_super_thick_orifice_high_headloss(self): - # self.assertAlmostEqual(k.k_value_orifice(pipe.OD(6), pipe.OD(4), 60*u.inch, 1 * u.L / u.s), 1.8350488368427034) + def test_k_value_super_thick_orifice_high_headloss(self): + self.assertAlmostEqualQuantity(k.k_value_orifice(pipe.OD(6*u.inch), pipe.OD(4*u.inch), 60*u.inch, 1 * u.L / u.s), + 1.8577290828680884*u.dimensionless) def test_k_value_thin_orifice(self): - self.assertAlmostEqual(k.k_value_orifice(pipe.OD(6), pipe.OD(4), 0*u.inch, 1 * u.L / u.s), 3.3497584836648246) + self.assertAlmostEqualQuantity(k.k_value_orifice(pipe.OD(6*u.inch), pipe.OD(4*u.inch), 0*u.inch, 1 * u.L / u.s), + 3.3497584836648246*u.dimensionless) def test_k_value_thick_orifice(self): - self.assertAlmostEqual(k.k_value_orifice(pipe.OD(6), pipe.OD(4), 1*u.inch, 1 * u.L / u.s), - 2.9070736824641181) + self.assertAlmostEqualQuantity(k.k_value_orifice(pipe.OD(6*u.inch), pipe.OD(4*u.inch), 1*u.inch, 1 * u.L / u.s), + 2.9070736824641181*u.dimensionless) if __name__ == '__main__': diff --git a/tests/core/test_physchem.py b/tests/core/test_physchem.py index e7c44e95..eb16d090 100644 --- a/tests/core/test_physchem.py +++ b/tests/core/test_physchem.py @@ -1,81 +1,76 @@ # -*- coding: utf-8 -*- -""" -Created on Fri Jul 7 11:51:51 2017 - -@author: Sage Weber-Shirk - -Last modified: Tue Jun 4 2019 -By: Hannah Si -""" - from aguaclara.core.units import u from aguaclara.core import physchem as pc import unittest -class AirTest(unittest.TestCase): - """Test the air density function""" + +class QuantityTest(unittest.TestCase): + def assertAlmostEqualQuantity(self, first, second, places=7): self.assertAlmostEqual(first.magnitude, second.magnitude, places) self.assertEqual(first.units, second.units, places) - def test_air_density(self): + +class GasTest(QuantityTest): + + def test_density_air(self): + self.assertWarns(UserWarning, pc.density_air, *(1*u.atm, 28.97*u.g/u.mol, 273*u.K)) + + def test_density_gas(self): + """Test the gas density function""" answer = 1.29320776*u.kg/u.m**3 - self.assertAlmostEqualQuantity(pc.density_air(1*u.atm, 28.97*u.g/u.mol, 273*u.K), answer) + self.assertAlmostEqualQuantity(pc.density_gas(1*u.atm, 28.97*u.g/u.mol, 273*u.K), answer) answer = 2.06552493*u.kg/u.m**3 - self.assertAlmostEqualQuantity(pc.density_air(5*u.atm, 10*u.g/u.mol, 295*u.K), answer) + self.assertAlmostEqualQuantity(pc.density_gas(5*u.atm, 10*u.g/u.mol, 295*u.K), answer) answer = 1.62487961*u.kg/u.m**3 - self.assertAlmostEqualQuantity(pc.density_air(101325*u.Pa, 40*u.g/u.mol, 300*u.K), answer) + self.assertAlmostEqualQuantity(pc.density_gas(101325*u.Pa, 40*u.g/u.mol, 300*u.K), answer) answer = 0.20786109*u.kg/u.m**3 - self.assertAlmostEqualQuantity(pc.density_air(700*u.mmHg, 5*u.g/u.mol, 270*u.K), answer) + self.assertAlmostEqualQuantity(pc.density_gas(700*u.mmHg, 5*u.g/u.mol, 270*u.K), answer) answer = 0*u.kg/u.m**3 - self.assertAlmostEqualQuantity(pc.density_air(0*u.atm, 28.97*u.g/u.mol, 273*u.K), answer) + self.assertAlmostEqualQuantity(pc.density_gas(0*u.atm, 28.97*u.g/u.mol, 273*u.K), answer) + -class GeometryTest(unittest.TestCase): +class GeometryTest(QuantityTest): """Test the circular area and diameter functions.""" - def test_area_circle(self): - """area_circle should should give known result with known input.""" - checks = ((1, 0.7853981633974483*u.m**2), - (495.6, 192908.99423885669*u.m**2)) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.area_circle(i[0]), i[1]) - def test_area_circle_units(self): - """area_circle should should give known result with known input and correct units""" - checks = ((1*u.m, 7853.981633974483*u.cm**2), - (495.6*u.cm, 19.290899423885669*u.m**2)) + def test_area_circle(self): + """area_circle should should give known result with known input.""" + checks = ((1*u.m, 0.7853981633974483*u.m**2), + (495.6*u.m, 192908.99423885669*u.m**2), + (495.6*u.m, 192908.99423885669*u.m**2)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.area_circle(i[0]), i[1]) + self.assertAlmostEqualQuantity(pc.area_circle(i[0]), i[1]) def test_area_circle_range(self): """area_circle should return errors with inputs <= 0.""" - checks = (0, -3) + checks = (0*u.m, -3*u.m) for i in checks: with self.subTest(i=i): self.assertRaises(ValueError, pc.area_circle, i) def test_diam_circle(self): """diam_circle should should give known result with known input.""" - checks = ((1, 1.1283791670955126), - (0.1, 0.3568248232305542), - (347, 21.019374919894773), - (10000 * u.cm**2, 1.1283791670955126)) + checks = ((1 * u.m**2, 1.1283791670955126 * u.m), + (0.1 * u.m**2, 0.3568248232305542 * u.m), + (347 * u.m**2, 21.019374919894773 * u.m), + (10000 * u.m**2, 112.83791670955126 * u.m)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.diam_circle(i[0]).magnitude, i[1]) + self.assertAlmostEqualQuantity(pc.diam_circle(i[0]), i[1]) def test_diam_circle_range(self): """diam_circle should return errors with inputs <= 0.""" - checks = ((0, ValueError), - (-3, ValueError)) + checks = ((0*u.m, ValueError), + (-3*u.m, ValueError)) for i in checks: with self.subTest(i=i): self.assertRaises(i[1], pc.diam_circle, i[0]) -class WaterPropertiesTest(unittest.TestCase): +class WaterPropertiesTest(QuantityTest): """Test the density and dynamic/kinematic viscosity functions.""" + def test_water_table(self): """The table density_water relies upon shouldn't need to be changed.""" table = pc.WATER_DENSITY_TABLE @@ -95,531 +90,424 @@ def test_water_table_units(self): def test_density_water_true(self): """density_water should give known result with known input.""" - checks = ((273.15, 999.9), - (300, 996.601907542082), - (343.15, 977.8)) + checks = ((273.15 * u.degK, 999.9 * u.kg/u.m**3), + (300 * u.degK, 996.601907542082 * u.kg/u.m**3), + (343.15 * u.degK, 977.8 * u.kg/u.m**3)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.density_water(i[0]).magnitude, i[1]) + self.assertAlmostEqualQuantity(pc.density_water(i[0]), i[1]) - def test_viscosity_dynamic(self): - """viscosity_dynamic should give known result with known input.""" - checks = ((300, 0.0008540578046518858), - (372, 0.00028238440851243975), - (274, 0.0017060470223965783)) + def test_density_water_warning(self): + checks = (lambda: pc.density_water(Temperature=1 * u.degK, temp=1 * u.degK), + lambda: pc.density_water()) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.viscosity_dynamic(i[0]).magnitude, i[1]) + self.assertRaises(TypeError, i) + + self.assertWarns(UserWarning, lambda: pc.density_water(temp=1 * u.degK)) + + def test_viscosity_dynamic(self): + self.assertWarns(UserWarning, pc.viscosity_dynamic, 300 * u.degK) - def test_viscosity_dynamic_units(self): - """viscosity_dynamic should give known result with known input.""" - checks = ((300 * u.degK, 0.0008540578046518858), - (26.85 * u.degC, 0.0008540578046518858)) + def test_viscosity_dynamic_water(self): + """viscosity_dynamic_water should give known result with known input.""" + checks = ((300 * u.degK, 0.0008540578046518858 * u.kg/(u.m*u.s)), + (372 * u.degK, 0.00028238440851243975 * u.kg/(u.m*u.s)), + (274 * u.degK, 0.0017060470223965783 * u.kg/(u.m*u.s)), + (26.85 * u.degC, 0.0008540578046518858 * u.kg/(u.m*u.s))) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.viscosity_dynamic(i[0]).magnitude, i[1]) + self.assertAlmostEqualQuantity(pc.viscosity_dynamic_water(i[0]), i[1]) def test_viscosity_kinematic(self): - """nu should give known results with known input.""" - checks = ((342, 4.1584506710898959e-07), - (297, 9.1670473903811879e-07), - (273.15, 1.7532330683680798e-06), - (373.15, 2.9108883329847625e-07)) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.viscosity_kinematic(i[0]).magnitude, i[1]) + self.assertWarns(UserWarning, pc.viscosity_kinematic, 300 * u.degK) - def test_viscosity_kinematic_units(self): - """nu should handle units correctly.""" - checks = ((342, 4.1584506710898959e-07), - (297 * u.degK, 9.1670473903811879e-07), - (0 * u.degC, 1.7532330683680798e-06), - (100 * u.degC, 2.9108883329847625e-07)) + def test_viscosity_kinematic_water(self): + """viscosity_kinematic_water should give known results with known input.""" + checks = ((342 * u.degK, 4.1584506710898959e-07 * u.m**2/u.s), + (297 * u.degK, 9.1670473903811879e-07 * u.m**2/u.s), + (273.15 * u.degK, 1.7532330683680798e-06 * u.m**2/u.s), + (373.15 * u.degK, 2.9108883329847625e-07 * u.m**2/u.s), + (100 * u.degC, 2.9108883329847625e-07 * u.m**2/u.s)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.viscosity_kinematic(i[0]).magnitude, i[1]) - self.assertAlmostEqual(pc.viscosity_kinematic(i[0]), - (pc.viscosity_dynamic(i[0]) / pc.density_water(i[0]))) + self.assertAlmostEqualQuantity(pc.viscosity_kinematic_water(i[0]), i[1]) + self.assertAlmostEqualQuantity(pc.viscosity_kinematic_water(i[0]), + (pc.viscosity_dynamic_water(i[0]) / pc.density_water(i[0]))) -class ReynoldsNumsTest(unittest.TestCase): - """Test the various Reynolds Number functions.""" - def test_re_pipe(self): - """re_pipe should return known results with known input.""" - checks = (((12, 6, 0.01), 254.64790894703253), - ((60, 1, 1), 76.39437268410977), - ((1, 12, .45), 0.23578510087688198)) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.re_pipe(*i[0]), i[1]) +class RadiusFuncsTest(QuantityTest): + """Test the various radius-acquisition functions.""" - def test_re_pipe_range(self): - """re_pipe should raise errors when inputs are out of bounds.""" - checks = ((0, 4, .5), (1, 0, .4), (1, 1, -0.1), (1, 1, 0)) + def test_radius_hydraulic(self): + self.assertWarns(UserWarning, pc.radius_hydraulic, *(10 * u.m, 4 * u.m, False)) + + def test_radius_hydraulic_rect(self): + """radius_hydraulic_rect should return known results with known input.""" + checks = (([10 * u.m, 4 * u.m, False], 1.4285714285714286 * u.m), + ([10 * u.m, 4 * u.m, True], 2.2222222222222223 * u.m)) for i in checks: with self.subTest(i=i): - self.assertRaises(ValueError, pc.re_pipe, *i) + self.assertAlmostEqualQuantity(pc.radius_hydraulic_rect(*i[0]), i[1]) - def test_re_pipe_units(self): - """re_pipe should handle units correctly.""" - base = pc.re_pipe(12, 6, 0.01) - checks = ([12 * u.m**3/u.s, 6 * u.m, 0.01 * u.m**2/u.s], - [12000 * u.L/u.s, 600 * u.cm, 0.000001 * u.ha/u.s], - [12, 0.006 * u.km, 100 * u.cm**2/u.s]) + def test_radius_hydraulic_range(self): + """radius_hydraulic should raise errors when inputs are out of bounds.""" + checks = (([0 * u.m, 4 * u.m, True], ValueError), + ([-1 * u.m, 4 * u.m, True], ValueError), + ([1 * u.m, 0 * u.m, True], ValueError), + ([10 * u.m, -1 * u.m, True], ValueError), + ([10 * u.m, 4 * u.m, 0 * u.m], TypeError), + ([10 * u.m, 4 * u.m, 6 * u.m], TypeError)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.re_pipe(*i), base) + self.assertRaises(i[1], pc.radius_hydraulic_rect, *i[0]) - def test_re_rect(self): - """re_rect should return known result with known input.""" - self.assertAlmostEqual(pc.re_rect(10, 4, 6, 1, True), 2.5) + def test_radius_hydraulic_general(self): + self.assertWarns(UserWarning, pc.radius_hydraulic_general, *(6 * u.m**2, 12 * u.m)) - def test_re_rect_range(self): - """re_rect should raise errors when inputs are out of bounds.""" - checks = ((0, 1, 1, 1, False), (1, 1, 1, 0, False)) + def test_radius_hydraulic_channel(self): + """radius_hydraulic_channel should return known results with known input.""" + checks = (([6 * u.m**2, 12 * u.m], 0.5 * u.m), + ([70 * u.m**2, 0.4 * u.m], 175 * u.m), + ([40000 * u.m**2, 7 * u.m], 5714.285714285715 * u.m)) for i in checks: with self.subTest(i=i): - self.assertRaises(ValueError, pc.re_rect, *i) + self.assertAlmostEqualQuantity(pc.radius_hydraulic_channel(*i[0]), i[1]) - def test_re_rect_units(self): - """re_rect should handle units correctly.""" - base = pc.re_rect(10, 4, 6, 1, True) - checks = ([10 * u.m**3/u.s, 4 * u.m, 6 * u.m, 1 * u.m**2/u.s, True], - [10000 * u.L/u.s, 4, 6, 1, True], - [10, 400 * u.cm, 6, 1, True], - [10, 4, 0.006 * u.km, 1, True], - [10, 4, 6, 0.0001 * u.ha/u.s, True]) + def test_radius_hydraulic_channel_range(self): + """radius_hydraulic_channel should not accept inputs of 0 or less.""" + checks = ([0 * u.m**2, 6 * u.m], [6 * u.m**2, 0 * u.m]) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.re_rect(*i), base) + self.assertRaises(ValueError, pc.radius_hydraulic_channel, *i) - def test_re_general(self): - """re_general should return known values with known input.""" - checks = (([1, 2, 3, 0.4], 6.666666666666666), - ([17, 6, 42, 1], 9.714285714285714), - ([0, 1, 2, 0.3], 0)) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.re_general(*i[0]), i[1]) - def test_re_general_range(self): - """re_general should raise errors when inputs are out of bounds.""" - checks = ((-1, 2, 3, 0.4), (1, 2, 3, 0)) - for i in checks: - with self.subTest(i=i): - self.assertRaises(ValueError, pc.re_general, *i) +class ReynoldsNumsTest(QuantityTest): + """Test the various Reynolds Number functions.""" - def test_re_general_units(self): - """re_general should handle units correctly.""" - base = pc.re_general(1, 2, 3, 0.4) - checks = ([1 * u.m/u.s, 2 * u.m**2, 3 * u.m, 0.4 * u.m**2/u.s], - [100 * u.cm/u.s, 2, 3, 0.4], - [1, 20000 * u.cm**2, 3, 0.4], - [1, 2, 0.003 * u.km, 0.4], - [1, 2, 3, 4000 * u.cm**2/u.s]) + def test_re_pipe(self): + """re_pipe should return known results with known input.""" + checks = (((12 * u.m**3/u.s, 6 * u.m, 0.01 * u.m**2/u.s), 254.64790894703253), + ((12000 * u.L/u.s, 600 * u.cm, 0.000001 * u.ha/u.s), 254.64790894703253), + ((60 * u.m**3/u.s, 1 * u.m, 1 * u.m**2/u.s), 76.39437268410977), + ((1 * u.m**3/u.s, 12 * u.m, .45 * u.m**2/u.s), 0.23578510087688198)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.re_general(*i), base) + self.assertAlmostEqualQuantity(pc.re_pipe(*i[0]), i[1]*u.dimensionless) - -class RadiusFuncsTest(unittest.TestCase): - """Test the various radius-acquisition functions.""" - def test_radius_hydraulic(self): - """radius_hydraulic should return known results with known input.""" - checks = (([10, 4, False], 1.4285714285714286), - ([10, 4, True], 2.2222222222222223)) + def test_re_pipe_range(self): + """re_pipe should raise errors when inputs are out of bounds.""" + checks = ((0 * u.m**3/u.s, 4 * u.m, .5 * u.m**2/u.s), + (1 * u.m**3/u.s, 0 * u.m, .4 * u.m**2/u.s), + (1 * u.m**3/u.s, 1 * u.m, -0.1 * u.m**2/u.s), + (1 * u.m**3/u.s, 1 * u.m, 0 * u.m**2/u.s)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.radius_hydraulic(*i[0]).magnitude, i[1]) + self.assertRaises(ValueError, pc.re_pipe, *i) - def test_radius_hydraulic_range(self): - """radius_hydraulic should raise errors when inputs are out of bounds.""" - checks = (([0, 4, True], ValueError), ([-1, 4, True], ValueError), - ([1, 0, True], ValueError), ([10, -1, True], ValueError), - ([10, 4, 0], TypeError), ([10, 4, 6], TypeError)) + def test_re_rect(self): + """re_rect should return known result with known input.""" + checks = (((10 * u.m**3/u.s, 4 * u.m, 6 * u.m, 1 * u.m**2/u.s, True), 2.5), + ((8 * u.m**3/u.s, 10 * u.m, 4 * u.m, 0.6 * u.m**2/u.s, False), 1.9047619047619049), + ((10000 * u.L/u.s, 4 * u.m, 6 * u.m, 0.0001 * u.ha/u.s, True), 2.5)) for i in checks: with self.subTest(i=i): - self.assertRaises(i[1], pc.radius_hydraulic, *i[0]) + self.assertAlmostEqualQuantity(pc.re_rect(*i[0]), i[1]*u.dimensionless) - def test_radius_hydraulic_units(self): - """radius_hydraulic should handle units correctly.""" - base = pc.radius_hydraulic(10, 4, False) - checks = ([1000 * u.cm, 4, False], - [0.01 * u.km, 40 * u.dm, False]) + def test_re_rect_range(self): + """re_rect should raise errors when inputs are out of bounds.""" + checks = ((0 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, False), + (1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 0 * u.m**2/u.s, False)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.radius_hydraulic(*i), base) + self.assertRaises(ValueError, pc.re_rect, *i) - def test_radius_hydraulic_general(self): - """radius_hydraulic_general should return known results with known input.""" - checks = (([6, 12], 0.5), ([70, 0.4], 175)) + def test_re_rect_warning(self): + """re_rect should raise warnings when passed a deprecated parameter""" + checks = (lambda: pc.re_rect(1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s), + lambda: pc.re_rect(1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, OpenChannel=False, openchannel=False)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.radius_hydraulic_general(*i[0]).magnitude, i[1]) + self.assertRaises(TypeError, i) + + self.assertWarns(UserWarning, lambda: pc.re_rect(1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, openchannel=False)) - def test_radius_hydraulic_general_range(self): - """radius_hydraulic_general should not accept inputs of 0 or less.""" - checks = ([0, 6], [6, 0]) + def test_re_general(self): + self.assertWarns(UserWarning, pc.re_general, *(1 * u.m/u.s, 2 * u.m**2, 3 * u.m, 0.4 * u.m**2/u.s)) + + def test_re_channel(self): + """re_channel should return known values with known input.""" + checks = (([1 * u.m/u.s, 2 * u.m**2, 3 * u.m, 0.4 * u.m**2/u.s], 6.666666666666666), + ([17 * u.m/u.s, 6 * u.m**2, 42 * u.m, 1 * u.m**2/u.s], 9.714285714285714), + ([0 * u.m/u.s, 1 * u.m**2, 2 * u.m, 0.3 * u.m**2/u.s], 0)) for i in checks: with self.subTest(i=i): - self.assertRaises(ValueError, pc.radius_hydraulic_general, *i) + self.assertAlmostEqualQuantity(pc.re_channel(*i[0]), i[1]*u.dimensionless) - def test_radius_hydraulic_general_units(self): - """radius_hydraulic_general should handle units correctly.""" - base = pc.radius_hydraulic_general(4, 7) - checks = ([4 * u.m**2, 7 * u.m], [40000 * u.cm**2, 7], - [4, 0.007 * u.km]) + def test_re_channel_range(self): + """re_channel should raise errors when inputs are out of bounds.""" + checks = ((-1 * u.m/u.s, 2 * u.m**2, 3 * u.m, 0.4 * u.m**2/u.s), + (1 * u.m/u.s, 2 * u.m**2, 3 * u.m, 0 * u.m**2/u.s)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.radius_hydraulic_general(*i), base) + self.assertRaises(ValueError, pc.re_channel, *i) -class FrictionFuncsTest(unittest.TestCase): +class FrictionFuncsTest(QuantityTest): """Test the friction functions.""" + def test_fric(self): - """fric should return known results with known input.""" - checks = (([100, 2, 0.001, 1], 0.33154589118654193), - ([100, 2, 0.1, 1], 0.10053096491487337), - ([100, 2, 0.001, 0], 0.019675384283293733), - ([46, 9, 0.001, 0.03], 0.039382681891291252), - ([55, 0.4, 0.5, 0.0001], 0.18278357257249706)) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.fric(*i[0]), i[1]) + self.assertWarns(UserWarning, pc.fric, *(100 * u.m**3/u.s, 2 * u.m, 0.001 * u.m**2/u.s, 1 * u.m)) - def test_fric_range(self): - """fric should raise an error if 0 <= PipeRough <= 1 is not true.""" - checks = ([1, 2, 0.1, -0.1], - [1, 2, 0.1, 1.1]) + def test_fric_pipe(self): + """fric_pipe should return known results with known input.""" + checks = (([100 * u.m**3/u.s, 2 * u.m, 0.001 * u.m**2/u.s, 1 * u.m], 0.33154589118654193), + ([100 * u.m**3/u.s, 2 * u.m, 0.1 * u.m**2/u.s, 1 * u.m], 0.10053096491487337), + ([100 * u.m**3/u.s, 2 * u.m, 0.001 * u.m**2/u.s, 0 * u.m], 0.019675384283293733), + ([46 * u.m**3/u.s, 9 * u.m, 0.001 * u.m**2/u.s, 0.03 * u.m], 0.039382681891291252), + ([55 * u.m**3/u.s, 0.4 * u.m, 0.5 * u.m**2/u.s, 0.0001 * u.m], 0.18278357257249706)) for i in checks: with self.subTest(i=i): - self.assertRaises(ValueError, pc.fric, *i) + self.assertAlmostEqualQuantity(pc.fric_pipe(*i[0]), i[1] * u.dimensionless) - def test_fric_units(self): - """fric should handle units correctly.""" - base = pc.fric(100, 2, 0.001, 1) - checks = ([100 * u.m**3/u.s, 2 * u.m, 0.001 * u.m**2/u.s, 1 * u.m], - [100000 * u.L/u.s, 2, 0.001, 1], - [100, 20 * u.dm, 0.001, 1], - [100, 2, 10 * u.cm**2/u.s, 1], - [100, 2, 0.001, 1000 * u.mm]) + def test_fric_range(self): + """fric_pipe should raise an error if 0 <= Roughness <= 1 is not true.""" + checks = ([1 * u.m**3/u.s, 2 * u.m, 0.1 * u.m**2/u.s, -0.1 * u.m],) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.fric(*i), base) + self.assertRaises(ValueError, pc.fric_pipe, *i) def test_fric_rect(self): """fric_rect should return known results with known inputs.""" - checks = (([60, 0.7, 1, 0.6, 0.001, True], 0.432), - ([60, 0.7, 1, 0.6, 0.001, False], 0.544), - ([120, 1, 0.04, 0.125, 0.6, True], 150.90859874356411), - ([120, 1, 0.04, 0.125, 0.6, False], 0.034666666666666665), - ([120, 1, 0.04, 0.125, 0, False], 0.034666666666666665), - ([120, 1, 0.04, 0.125, 0, True], 0.042098136441473824)) + checks = (([60 * u.m**3/u.s, 0.7 * u.m, 1 * u.m, 0.6 * u.m**2/u.s, 0.001 * u.m, True], 0.432), + ([60 * u.m**3/u.s, 0.7 * u.m, 1 * u.m, 0.6 * u.m**2/u.s, 0.001 * u.m, False], 0.544), + ([120 * u.m**3/u.s, 1 * u.m, 0.04 * u.m, 0.125 * u.m**2/u.s, 0.6 * u.m, True], 150.90859874356411), + ([120 * u.m**3/u.s, 1 * u.m, 0.04 * u.m, 0.125 * u.m**2/u.s, 0.6 * u.m, False], 0.034666666666666665), + ([120 * u.m**3/u.s, 1 * u.m, 0.04 * u.m, 0.125 * u.m**2/u.s, 0 * u.m, False], 0.034666666666666665), + ([120 * u.m**3/u.s, 1 * u.m, 0.04 * u.m, 0.125 * u.m**2/u.s, 0 * u.m, True], 0.042098136441473824)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.fric_rect(*i[0]), i[1]) + self.assertAlmostEqualQuantity(pc.fric_rect(*i[0]), i[1] * u.dimensionless) def test_fric_rect_range(self): """fric_rect should raise an error if 0 <= PipeRough <= 1 is not true.""" - checks = ([1, 1, 1, 1, 1.1, True],) + checks = ([1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, -1.1 * u.m, True],) for i in checks: with self.subTest(i=i): self.assertRaises(ValueError, pc.fric_rect, *i) - def test_fric_rect_units(self): - """fric_rect should handle units correctly.""" - base = pc.fric_rect(0.06, 0.1, 0.0625, 0.347, 0.06, True) - checks = ([0.06 * u.m**3/u.s, 0.1 * u.m, 0.0625 * u.m, - 0.347 * u.m**2/u.s, 0.06 * u.m, True], - [60 * u.L/u.s, 0.1, 0.0625, 0.347, 0.06, True], - [0.06, 10 * u.cm, 0.0625, 0.347, 0.06, True], - [0.06, 0.1, 6.25 * u.cm, 0.347, 0.06, True], - [0.06, 0.1, 0.0625, 3470 * u.cm**2/u.s, 0.06, True], - [0.06, 0.1, 0.0625, 0.347, 6 * u.cm, True]) - for i in checks: + def test_fric_rect_warning(self): + """fric_rect should raise warnings when passed deprecated parameters""" + error_checks = (lambda: pc.fric_rect(1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, Roughness=1 * u.m, PipeRough=1 * u.m, OpenChannel=True), + lambda: pc.fric_rect(1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, 1 * u.m, OpenChannel=True, openchannel=True)) + for i in error_checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.fric_rect(*i), base) + self.assertRaises(TypeError, i) - def test_fric_general(self): - """fric_general should return known results with known inputs.""" - checks = (([9, 0.67, 3, 0.987, 0.86], 0.3918755555555556), - ([1, 1, 1, 1, 1], 16), - ([120, 0.6, 12, 0.3, 0.002], 0.023024557179148988)) - for i in checks: + warning_checks = (lambda: pc.fric_rect(1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, PipeRough=1 * u.m, OpenChannel=True), + lambda: pc.fric_rect(1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, PipeRough=1 * u.m, openchannel=True), + lambda: pc.fric_rect(1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, 1 * u.m, openchannel=True)) + for i in warning_checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.fric_general(*i[0]), i[1]) + self.assertWarns(UserWarning, i) + + def test_fric_general(self): + self.assertWarns(UserWarning, pc.fric_general, *(9 * u.m**2, 0.67 * u.m, 3 * u.m/u.s, 0.987 * u.m**2/u.s, 0.86 * u.m)) - def test_fric_general_range(self): - """fric_general should raise an error if 0 <= PipeRough <= 1 is not true.""" - checks = ((1, 1, 1, 1, -0.0001), (1, 1, 1, 1, 1.1)) + def test_fric_channel(self): + """fric_channel should return known results with known inputs.""" + checks = (([9 * u.m**2, 0.67 * u.m, 3 * u.m/u.s, 0.987 * u.m**2/u.s, 0.86 * u.m], 0.3918755555555556), + ([1 * u.m**2, 1 * u.m, 1 * u.m/u.s, 1 * u.m**2/u.s, 1 * u.m], 16), + ([120 * u.m**2, 0.6 * u.m, 12 * u.m/u.s, 0.3 * u.m**2/u.s, 0.002 * u.m], 0.023024557179148988)) for i in checks: with self.subTest(i=i): - self.assertRaises(ValueError, pc.fric_general, *i) - - def test_fric_general_units(self): - """fric_general should handle units correctly.""" - base = pc.fric_general(46.2, 0.75, 1.23, 0.46, 0.002) - checks = ([46.2 * u.m**2, 0.75 * u.m, 1.23 * u.m/u.s, - 0.46 * u.m**2/u.s, 0.002 * u.m], - [462000 * u.cm**2, 0.75, 1.23, 0.46, 0.002], - [46.2, 750 * u.mm, 1.23, 0.46, 0.002], - [46.2, 0.75, 0.00123 * u.km/u.s, 0.46, 0.002], - [46.2, 0.75, 1.23, 4600 * u.cm**2/u.s, 0.002], - [46.2, 0.75, 1.23, 0.46, 2 * u.mm]) + self.assertAlmostEqualQuantity(pc.fric_channel(*i[0]), i[1] * u.dimensionless) + + def test_fric_channel_range(self): + """fric_channel should raise an error if 0 <= Roughness <= 1 is not true.""" + checks = ((1 * u.m**2, 1 * u.m, 1 * u.m**2/u.s, 1 * u.m**2/u.s, -0.0001 * u.m), + (1 * u.m**2, 1 * u.m, 1 * u.m**2/u.s, 1 * u.m**2/u.s, 1.1 * u.m)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.fric_general(*i), base) + self.assertRaises(ValueError, pc.fric_channel, *i) -class HeadlossFuncsTest(unittest.TestCase): +class HeadlossFuncsTest(QuantityTest): """Test the headloss functions.""" + def test_headloss_fric(self): - """headloss_fric should return known results with known inputs.""" - checks = (([100, 2, 4, 0.001, 1], 34.2549414191127), - ([100, 2, 4, 0.1, 1], 10.386744054168654), - ([100, 2, 4, 0.001, 0], 2.032838149828097), - ([46, 9, 12, 0.001, 0.03], 0.001399778168304583), - ([55, 0.4, 2, 0.5, 0.0001], 8926.108171551185)) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.headloss_fric(*i[0]).magnitude, i[1]) + self.assertWarns(UserWarning, pc.headloss_fric, *(100 * u.m**3/u.s, 2 * u.m, 4 * u.m, 0.001 * u.m**2/u.s, 1 * u.m)) - def test_headloss_fric_range(self): - """headloss_fric should raise an error if Length <= 0.""" - checks = ([1, 1, 0, 1, 1], [1, 1, -1, 1, 1]) + def test_headloss_major_pipe(self): + """headloss_major_pipe should return known results with known inputs.""" + checks = (([100 * u.m**3/u.s, 2 * u.m, 4 * u.m, 0.001 * u.m**2/u.s, 1 * u.m], 34.2549414191127 * u.m), + ([100 * u.m**3/u.s, 2 * u.m, 4 * u.m, 0.1 * u.m**2/u.s, 1 * u.m], 10.386744054168654 * u.m), + ([100 * u.m**3/u.s, 2 * u.m, 4 * u.m, 0.001 * u.m**2/u.s, 0 * u.m], 2.032838149828097 * u.m), + ([46 * u.m**3/u.s, 9 * u.m, 12 * u.m, 0.001 * u.m**2/u.s, 0.03 * u.m], 0.001399778168304583 * u.m), + ([55 * u.m**3/u.s, 0.4 * u.m, 2 * u.m, 0.5 * u.m**2/u.s, 0.0001 * u.m], 8926.108171551185 * u.m)) for i in checks: with self.subTest(i=i): - self.assertRaises(ValueError, pc.headloss_fric, *i) - - def test_headloss_fric_units(self): - """headloss_fric should handle units correctly.""" - base = pc.headloss_fric(100, 2, 4, 0.001, 0.03) - checks = ([100 * u.m**3/u.s, 2 * u.m, 4 * u.m, - 0.001 * u.m**2/u.s, 0.03 * u.m], - [10**5 * u.L/u.s, 2, 4, 0.001, 0.03], - [100, 200 * u.cm, 4, 0.001, 0.03], - [100, 2, 4000 * u.mm, 0.001, 0.03], - [100, 2, 4, 10 * u.cm**2/u.s, 0.03], - [100, 2, 4, 0.001, 3 * u.cm]) + self.assertAlmostEqualQuantity(pc.headloss_major_pipe(*i[0]), i[1]) + + def test_headloss_major_pipe_range(self): + """headloss_major_pipe should raise an error if Length <= 0.""" + checks = ([1 * u.m**3/u.s, 1 * u.m, 0 * u.m, 1 * u.m**2/u.s, 1 * u.m], + [1 * u.m**3/u.s, 1 * u.m, -1 * u.m, 1 * u.m**2/u.s, 1 * u.m]) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.headloss_fric(*i), base) + self.assertRaises(ValueError, pc.headloss_major_pipe, *i) def test_headloss_exp(self): - """headloss_exp should return known results with known input.""" - self.assertAlmostEqual(pc.headloss_exp(60, 0.9, 0.067).magnitude, - 30.386230766265214) + self.assertWarns(UserWarning, pc.headloss_exp, *(60 * u.m**3/u.s, 0.9 * u.m, 0.067)) - def test_headloss_exp_range(self): - """headloss_exp should raise errors when inputs are out of bounds.""" - checks = ([0, 1, 1], [1, 0, 1], [1, 1, -1]) + def test_headloss_minor_pipe(self): + """headloss_minor_pipe should return known results with known input.""" + checks = (([60 * u.m**3/u.s, 0.9 * u.m, 0.067], 30.386230766265214 * u.m), + ([60 * u.m**3/u.s, 0.9 * u.m, 0.067 * u.dimensionless], 30.386230766265214 * u.m)) for i in checks: with self.subTest(i=i): - self.assertRaises(ValueError, pc.headloss_exp, *i) - pc.headloss_exp(1, 1, 0) + self.assertAlmostEqualQuantity(pc.headloss_minor_pipe(*i[0]), i[1]) - def test_headloss_exp_units(self): - """headloss_exp should handle units correctly.""" - base = pc.headloss_exp(60, 0.9, 0.067).magnitude - checks = ([60 * u.m**3/u.s, 0.9 * u.m, 0.067 * u.dimensionless], - [60000 * u.L/u.s, 0.9, 0.067], - [60, 900 * u.mm, 0.067]) + def test_headloss_minor_pipe_range(self): + """headloss_minor_pipe should raise errors when inputs are out of bounds.""" + checks = ([0 * u.m**3/u.s, 1 * u.m, 1], [1 * u.m**3/u.s, 0 * u.m, 1], + [1 * u.m**3/u.s, 1 * u.m, -1]) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.headloss_exp(*i).magnitude, base) + self.assertRaises(ValueError, pc.headloss_minor_pipe, *i) def test_headloss(self): - """headloss should return known results with known inputs.""" - checks = (([100, 2, 4, 0.001, 1, 2], 137.57379509731857), - ([100, 2, 4, 0.1, 1, 0.4], 31.05051478980984), - ([100, 2, 4, 0.001, 0, 1.2], 64.024150356751633), - ([46, 9, 12, 0.001, 0.03, 4], 0.10802874052554703), - ([55, 0.4, 2, 0.5, 0.0001, 0.12], 10098.131417963332)) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.headloss(*i[0]).magnitude, i[1]) - - def test_headloss_units(self): - """headloss should handle units correctly.""" - base = pc.headloss(100, 2, 4, 0.001, 1, 2) - checks = ([100 * u.m**3/u.s, 2 * u.m, 4 * u.m, - 0.001 * u.m**2/u.s, 1 * u.m, 2], - [10**5 * u.L/u.s, 2, 4, 0.001, 1, 2], - [100, 200 * u.cm, 4, 0.001, 1, 2], - [100, 2, 4000 * u.mm, 0.001, 1, 2], - [100, 2, 4, 10 * u.cm**2/u.s, 1, 2], - [100, 2, 4, 0.001, 100 * u.cm, 2]) + self.assertWarns(UserWarning, pc.headloss, *(100 * u.m**3/u.s, 2 * u.m, 4 * u.m, 0.001 * u.m**2/u.s, 1 * u.m, 2)) + + def test_headloss_pipe(self): + """headloss_pipe should return known results with known inputs.""" + checks = (([100 * u.m**3/u.s, 2 * u.m, 4 * u.m, 0.001 * u.m**2/u.s, 1 * u.m, 2], 137.57379509731857 * u.m), + ([100 * u.m**3/u.s, 2 * u.m, 4 * u.m, 0.1 * u.m**2/u.s, 1 * u.m, 0.4], 31.05051478980984 * u.m), + ([100 * u.m**3/u.s, 2 * u.m, 4 * u.m, 0.001 * u.m**2/u.s, 0 * u.m, 1.2], 64.024150356751633 * u.m), + ([46 * u.m**3/u.s, 9 * u.m, 12 * u.m, 0.001 * u.m**2/u.s, 0.03 * u.m, 4], 0.10802874052554703 * u.m), + ([55 * u.m**3/u.s, 0.4 * u.m, 2 * u.m, 0.5 * u.m**2/u.s, 0.0001 * u.m, 0.12], 10098.131417963332 * u.m)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.headloss(*i), base) + self.assertAlmostEqualQuantity(pc.headloss_pipe(*i[0]), i[1]) def test_headloss_fric_rect(self): - """headloss_fric_rect should return known result with known inputs.""" - checks = (([0.06, 3, 0.2, 4, 0.5, 0.006, True], 1.3097688246694272), - ([0.06, 3, 0.2, 4, 0.5, 0.006, False], 4.640841787063992)) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.headloss_fric_rect(*i[0]).magnitude, i[1]) + self.assertWarns(UserWarning, pc.headloss_fric_rect, *(0.06 * u.m**3/u.s, 3 * u.m, 0.2 * u.m, 4 * u.m, 0.5 * u.m**2/u.s, 0.006 * u.m, True)) - def test_headloss_fric_rect_range(self): - """headloss_fric_rect should raise an error when Length <=0.""" - checks = ((1, 1, 1, 0, 1, 1, 1), (1, 1, 1, -1, 1, 1, 1)) + def test_headloss_major_rect(self): + """headloss_major_rect should return known result with known inputs.""" + checks = (([0.06 * u.m**3/u.s, 3 * u.m, 0.2 * u.m, 4 * u.m, 0.5 * u.m**2/u.s, 0.006 * u.m, True], 1.3097688246694272 * u.m), + ([0.06 * u.m**3/u.s, 3 * u.m, 0.2 * u.m, 4 * u.m, 0.5 * u.m**2/u.s, 0.006 * u.m, False], 4.640841787063992 * u.m)) for i in checks: with self.subTest(i=i): - self.assertRaises(ValueError, pc.headloss_fric_rect, *i) - - def test_headloss_fric_rect_units(self): - """headloss_fric_rect should handle units correctly.""" - base = pc.headloss_fric_rect(0.06, 2, 0.004, 3, 0.89, 0.07, True) - checks = ([0.06 * u.m**3/u.s, 2 * u.m, 0.004 * u.m, 3 * u.m, - 0.89 * u.m**2/u.s, 0.07 * u.m, True], - [60 * u.L/u.s, 2, 0.004, 3, 0.89, 0.07, True], - [0.06, 200 * u.cm, 0.004, 3, 0.89, 0.07, True], - [0.06, 2, 4 * u.mm, 3, 0.89, 0.07, True], - [0.06, 2, 0.004, 300 * u.cm, 0.89, 0.07, True], - [0.06, 2, 0.004, 3, 8900 * u.cm**2/u.s, 0.07, True], - [0.06, 2, 0.004, 3, 0.89, 7 * u.cm, True]) + self.assertAlmostEqualQuantity(pc.headloss_major_rect(*i[0]), i[1]) + + def test_headloss_major_rect_range(self): + """headloss_major_rect should raise an error when Length <=0.""" + checks = ((1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 0 * u.m, 1 * u.m**2/u.s, 1 * u.m, 1), + (1 * u.m**3/u.s, 1 * u.m, 1 * u.m, -1 * u.m, 1 * u.m**2/u.s, 1 * u.m, 1)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.headloss_fric_rect(*i), base) + self.assertRaises(ValueError, pc.headloss_major_rect, *i) def test_headloss_exp_rect(self): - """headloss_exp_rect should return known result for known input.""" - checks = ([0.06, 2, 0.004, 1], 2.8679518490004234) - self.assertAlmostEqual(pc.headloss_exp_rect(*checks[0]).magnitude, checks[1]) + self.assertWarns(UserWarning, pc.headloss_exp_rect, *(0.06 * u.m**3/u.s, 2 * u.m, 0.004 * u.m, 1)) - def test_headloss_exp_rect_range(self): - """headloss_exp_rect should raise errors when inputs are out of bounds.""" - checks = ((0, 1, 1, 1), (1, 0, 1, 1), (1, 1, 0, 1), (1, 1, 1, -1)) - for i in checks: - with self.subTest(i=i): - self.assertRaises(ValueError, pc.headloss_exp_rect, *i) - pc.headloss_exp_rect(1, 1, 1, 0) + def test_headloss_minor_rect(self): + """headloss_minor_rect should return known result for known input.""" + checks = ([0.06 * u.m**3/u.s, 2 * u.m, 0.004 * u.m, 1], 2.8679518490004234 * u.m) + self.assertAlmostEqualQuantity(pc.headloss_minor_rect(*checks[0]), checks[1]) - def test_headloss_exp_rect_units(self): - """headloss_exp_rect should handle units correctly.""" - base = pc.headloss_exp_rect(0.06, 2, 0.9, 1) - checks = ([0.06 * u.m**3/u.s, 2 * u.m, 0.9 * u.m, 1 * u.dimensionless], - [60 * u.L/u.s, 2, 0.9, 1], - [0.06, 200 * u.cm, 0.9, 1], - [0.06, 2, 900 * u.mm, 1]) + def test_headloss_minor_rect_range(self): + """headloss_minor_rect should raise errors when inputs are out of bounds.""" + checks = ((0 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1), (1 * u.m**3/u.s, 0 * u.m, 1 * u.m, 1), + (1 * u.m**3/u.s, 1 * u.m, 0 * u.m, 1), (1 * u.m**3/u.s, 1 * u.m, 1 * u.m, -1)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.headloss_exp_rect(*i), base) + self.assertRaises(ValueError, pc.headloss_minor_rect, *i) def test_headloss_rect(self): """headloss_rect should return known result for known inputs.""" - checks = (([0.06, 3, 0.2, 4, 1, 0.5, 0.006, True], 1.3102786827759163), - ([0.06, 3, 0.2, 4, 1, 0.5, 0.006, False], 4.641351645170481)) + checks = (([0.06 * u.m**3/u.s, 3 * u.m, 0.2 * u.m, 4 * u.m, 1 * u.dimensionless, 0.5 * u.m**2/u.s, 0.006 * u.m, True], 1.3102786827759163 * u.m), + ([0.06 * u.m**3/u.s, 3 * u.m, 0.2 * u.m, 4 * u.m, 1, 0.5 * u.m**2/u.s, 0.006 * u.m, False], 4.641351645170481 * u.m)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.headloss_rect(*i[0]).magnitude, i[1]) - - def test_headloss_rect_units(self): - """headloss_rect should handle units properly.""" - base = pc.headloss_rect(0.06, 3, 0.2, 4, 1, 0.5, 0.006, True) - checks = ([0.06 * u.m**3/u.s, 3 * u.m, 0.2 * u.m, 4 * u.m, - 1, 0.5 * u.m**2/u.s, 0.006 * u.m, True], - [60 * u.L/u.s, 3, 0.2, 4, 1, 0.5, 0.006, True], - [0.06, 300 * u.cm, 0.2, 4, 1, 0.5, 0.006, True], - [0.06, 3, 200 * u.mm, 4, 1, 0.5, 0.006, True], - [0.06, 3, 0.2, 0.004 * u.km, 1, 0.5, 0.006, True], - [0.06, 3, 0.2, 4, 1, 5000 * u.cm**2/u.s, 0.006, True], - [0.06, 3, 0.2, 4, 1, 0.5, 6 * u.mm, True]) - for i in checks: + self.assertAlmostEqualQuantity(pc.headloss_rect(*i[0]), i[1]) + + def test_headloss_rect_warning(self): + """headloss_rect should raise warnings when passed deprecated parameters""" + error_checks = (lambda: pc.headloss_rect(1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 4 * u.m, 1 * u.dimensionless, 1 * u.m**2/u.s, Roughness=1 * u.m, PipeRough=1 * u.m, OpenChannel=True), + lambda: pc.headloss_rect(1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 4 * u.m, 1 * u.dimensionless, 1 * u.m**2/u.s, 1 * u.m, OpenChannel=True, openchannel=True)) + for i in error_checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.headloss_rect(*i), base) - self.assertRaises(ValueError, pc.headloss_rect, - *[1, 1, 1, 1, 1 * u.m, 1, 1, True]) + self.assertRaises(TypeError, i) - def test_headloss_fric_general(self): - """headloss_fric_general should return known result for known inputs.""" - checks = (([1, 1, 1, 1, 1, 1], 0.20394324259558566), - ([25, 4, 0.6, 2, 1, 1], 0.006265136412536391)) - for i in checks: + warning_checks = (lambda: pc.headloss_rect(1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 4 * u.m, 1 * u.dimensionless, 1 * u.m**2/u.s, PipeRough=1 * u.m, OpenChannel=True), + lambda: pc.headloss_rect(1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 4 * u.m, 1 * u.dimensionless, 1 * u.m**2/u.s, PipeRough=1 * u.m, openchannel=True), + lambda: pc.headloss_rect(1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 4 * u.m, 1 * u.dimensionless, 1 * u.m**2/u.s, 1 * u.m, openchannel=True)) + for i in warning_checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.headloss_fric_general(*i[0]).magnitude, i[1]) + self.assertWarns(UserWarning, i) + + def test_headloss_fric_general(self): + self.assertWarns(UserWarning, pc.headloss_fric_general, *(1 * u.m**2, 1 * u.m, 1 * u.m/u.s, 1 * u.m, 1 * u.m**2/u.s, 1 * u.m)) - def test_headloss_fric_general_range(self): - """headloss_fric_general should raise an error when Length <= 0.""" - checks = ([1, 1, 1, 0, 1, 1], [1, 1, 1, -1, 1, 1]) + def test_headloss_major_channel(self): + """headloss_major_channel should return known result for known inputs.""" + checks = (([1 * u.m**2, 1 * u.m, 1 * u.m/u.s, 1 * u.m, 1 * u.m**2/u.s, 1 * u.m], 0.20394324259558566 * u.m), + ([25 * u.m**2, 4 * u.m, 0.6 * u.m/u.s, 2 * u.m, 1 * u.m**2/u.s, 1 * u.m], 0.006265136412536391 * u.m)) for i in checks: with self.subTest(i=i): - self.assertRaises(ValueError, pc.headloss_fric_general, *i) - - def test_headloss_fric_general_units(self): - """headloss_fric_general should handle units correctly.""" - base = pc.headloss_fric_general(36, 5, 0.2, 6, 0.4, 0.002) - checks = ([36 * u.m**2, 5 * u.m, 0.2 * u.m/u.s, - 6 * u.m, 0.4 * u.m**2/u.s, 0.002 * u.m], - [0.000036 * u.km**2, 5, 0.2, 6, 0.4, 0.002], - [36, 500 * u.cm, 0.2, 6, 0.4, 0.002], - [36, 5, 20 * u.cm/u.s, 6, 0.4, 0.002], - [36, 5, 0.2, 0.006 * u.km, 0.4, 0.002], - [36, 5, 0.2, 6, 4000 * u.cm**2/u.s, 0.002], - [36, 5, 0.2, 6, 0.4, 2 * u.mm]) + self.assertAlmostEqualQuantity(pc.headloss_major_channel(*i[0]), i[1]) + + def test_headloss_major_channel_range(self): + """headloss_major_channel should raise an error when Length <= 0.""" + checks = ([1 * u.m**2, 1 * u.m, 1 * u.m/u.s, 0 * u.m, 1 * u.m**2/u.s, 1 * u.m], + [15 * u.m**2, 1 * u.m, 1 * u.m/u.s, -1 * u.m, 1 * u.m**2/u.s, 1 * u.m]) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.headloss_fric_general(*i), base) + self.assertRaises(ValueError, pc.headloss_major_channel, *i) def test_headloss_exp_general(self): - """headloss_exp_general should return known result for known input.""" - self.assertAlmostEqual(pc.headloss_exp_general(0.06, 0.02).magnitude, - 3.670978366720542e-06) + self.assertWarns(UserWarning, pc.headloss_exp_general, *(0.06 * u.m/u.s, 0.02)) - def test_headloss_exp_general_range(self): - """headloss_exp_general should raise errors if inputs are out of bounds.""" - checks = ((0,1), (1, -1)) - for i in checks: - with self.subTest(i=i): - self.assertRaises(ValueError, pc.headloss_exp_general, *i) - pc.headloss_exp_general(1, 0) + def test_headloss_minor_channel(self): + """headloss_minor_channel should return known result for known input.""" + self.assertAlmostEqualQuantity(pc.headloss_minor_channel(0.06 * u.m/u.s, 0.02), + 3.670978366720542e-06 * u.m) - def test_headloss_exp_general_units(self): - """headloss_exp_general should handle units correctly.""" - base = pc.headloss_exp_general(0.06, 0.02).magnitude - checks = ([0.06 * u.m/u.s, 0.02 * u.dimensionless], - [6 * u.cm/u.s, 0.02]) + def test_headloss_minor_channel_range(self): + """headloss_minor_channel should raise errors if inputs are out of bounds.""" + checks = ((0 * u.m/u.s, 1), (1 * u.m/u.s, -1 * u.dimensionless)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.headloss_exp_general(*i).magnitude, base) + self.assertRaises(ValueError, pc.headloss_minor_channel, *i) def test_headloss_gen(self): - """headloss_gen should return known value for known inputs.""" - checks = (([36, 0.1, 4, 6, 0.02, 0.86, 0.0045], 0.0013093911519979546), - ([49, 2.4, 12, 3, 2, 4, 0.6], 0.9396236839032805)) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.headloss_gen(*i[0]).magnitude, i[1]) - - def test_headloss_gen_units(self): - """headloss_gen should handle units correctly.""" - base = pc.headloss_gen(49, 2.4, 12, 3, 2, 4, 0.6).magnitude - checks = ([49 * u.m**2, 2.4 * u.m/u.s, 12 * u.m, 3 * u.m, - 2 * u.dimensionless, 4 * u.m**2/u.s, 0.6 * u.m], - [490000 * u.cm**2, 2.4, 12, 3, 2, 4, 0.6], - [49, 240 * u.cm/u.s, 12, 3, 2, 4, 0.6], - [49, 2.4, 0.012 * u.km, 3, 2, 4, 0.6], - [49, 2.4, 12, 3000 * u.mm, 2, 4, 0.6], - [49, 2.4, 12, 3, 2, 40000 * u.cm**2/u.s, 0.6], - [49, 2.4, 12, 3, 2, 4, 60 * u.cm]) + self.assertWarns(UserWarning, pc.headloss_gen, *(36 * u.m**2, 0.1 * u.m/u.s, 4 * u.m, 6 * u.m, 0.02, 0.86 * u.m**2/u.s, 0.0045 * u.m)) + + def test_headloss_channel(self): + """headloss_channel should return known value for known inputs.""" + checks = (([36 * u.m**2, 0.1 * u.m/u.s, 4 * u.m, 6 * u.m, 0.02, 0.86 * u.m**2/u.s, 0.0045 * u.m], 0.0013093911519979546 * u.m), + ([49 * u.m**2, 2.4 * u.m/u.s, 12 * u.m, 3 * u.m, 2 * u.dimensionless, 4 * u.m**2/u.s, 0.6 * u.m], 0.9396236839032805 * u.m)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.headloss_gen(*i).magnitude, base) + self.assertAlmostEqualQuantity(pc.headloss_channel(*i[0]), i[1]) def test_headloss_manifold(self): """headloss_manifold should return known value for known input.""" - checks = (([0.12, 0.4, 6, 0.8, 0.75, 0.0003, 5], 38.57715300752375), - ([2, 6, 40, 5, 1.1, 0.04, 6], 0.11938889890999548)) + checks = (([0.12 * u.m**3/u.s, 0.4 * u.m, 6 * u.m, 0.8, 0.75 * u.m**2/u.s, 0.0003 * u.m, 5], 38.57715300752375 * u.m), + ([2 * u.m**3/u.s, 6 * u.m, 40 * u.m, 5, 1.1 * u.m**2/u.s, 0.04 * u.m, 6], 0.11938889890999548 * u.m)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.headloss_manifold(*i[0]).magnitude, i[1]) + self.assertAlmostEqualQuantity(pc.headloss_manifold(*i[0]), i[1]) def test_headloss_manifold_range(self): """headloss_manifold should object if NumOutlets is not a positive int.""" - failChecks = ((1, 1, 1, 1, 1, 1, -1), (1, 1, 1, 1, 1, 1, 0), - (1, 1, 1, 1, 1, 1, 0.1)) - passchecks = ((1, 1, 1, 1, 1, 1, 1.0), (1, 1, 1, 1, 1, 1, 47)) + failChecks = ((1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1, 1 * u.m**2/u.s, 1 * u.m, -1), + (1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1, 1 * u.m**2/u.s, 1 * u.m, 0), + (1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1, 1 * u.m**2/u.s, 1 * u.m, 0.1)) + passchecks = ((1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1, 1 * u.m**2/u.s, 1 * u.m, 1.0), + (1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1, 1 * u.m**2/u.s, 1 * u.m, 47)) for i in failChecks: with self.subTest(i=i): self.assertRaises((ValueError, TypeError), pc.headloss_manifold, *i) @@ -627,63 +515,66 @@ def test_headloss_manifold_range(self): with self.subTest(i=i): pc.headloss_manifold(*i) - def test_headloss_manifold_units(self): - """headloss_manifold should handle units correctly.""" - base = pc.headloss_manifold(2, 6, 40, 5, 1.1, 0.04, 6).magnitude - unitchecks = ([2 * u.m**3/u.s, 6 * u.m, 40 * u.m, 5* u.dimensionless, - 1.1 * u.m**2/u.s, 0.04 * u.m, 6* u.dimensionless], - [2 * u.m**3/u.s, 6, 40, 5, 1.1, 0.04, 6], - [2, 6000 * u.mm, 40, 5, 1.1, 0.04, 6], - [2, 6, 0.04 * u.km, 5, 1.1, 0.04, 6], - [2, 6, 40, 5, 11000 * u.cm**2/u.s, 0.04, 6], - [2, 6, 40, 5, 1.1, 4 * u.cm, 6]) - for i in unitchecks: + def test_headloss_manifold_warning(self): + """headloss_manifold should raise warnings when passed deprecated parameters""" + error_checks = (lambda: pc.headloss_manifold(1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1, 1 * u.m**2/u.s, Roughness=1 * u.m, NumOutlets=1, PipeRough=1 * u.m), + lambda: pc.headloss_manifold(1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1, 1 * u.m**2/u.s, NumOutlets=1), + lambda: pc.headloss_manifold(1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1, 1 * u.m**2/u.s, Roughness=1 * u.m)) + for i in error_checks: + with self.subTest(i=i): + self.assertRaises(TypeError, i) + + warning_checks = (lambda: pc.headloss_manifold(1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1, 1 * u.m**2/u.s, PipeRough=1 * u.m, NumOutlets = 1),) + + for i in warning_checks: + with self.subTest(i=i): + self.assertWarns(UserWarning, i) + + def test_elbow_minor_loss(self): + self.assertWarns(UserWarning, pc.elbow_minor_loss, *(.1*u.m**3/u.s, 0.1*u.m, 0.5)) + + def test_headloss_minor_elbow(self): + checks = (([.1*u.m**3/u.s, 0.1*u.m, 0.5], 4.132754147128235 * u.m), + ([.4*u.m**3/u.s, 0.3*u.m, 0.2], 0.32653859927926804 * u.m)) + for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.headloss_manifold(*i).magnitude, base) + self.assertAlmostEqualQuantity(pc.headloss_minor_elbow(*i[0]), i[1]) -class OrificeFuncsTest(unittest.TestCase): +class OrificeFuncsTest(QuantityTest): """Test the orifice functions.""" def test_flow_orifice(self): """flow_orifice should return known result for known input.""" - checks = (([0.4, 2, 0.46], 0.36204122788069698), - ([2, 0.04, 0.2], 0.55652566805118475), - ([7, 0, 1], 0), - ([1.4, 0.1, 0], 0)) + checks = (([0.4 * u.m, 2 * u.m, 0.46], 0.36204122788069698 * u.m**3/u.s), + ([2 * u.m, 0.04 * u.m, 0.2], 0.55652566805118475 * u.m**3/u.s), + ([7 * u.m, 0 * u.m, 1 * u.dimensionless], 0 * u.m**3/u.s), + ([1.4 * u.m, 0.1 * u.m, 0], 0 * u.m**3/u.s)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.flow_orifice(*i[0]).magnitude, i[1]) + self.assertAlmostEqualQuantity(pc.flow_orifice(*i[0]), i[1]) def test_flow_orifice_range(self): """flow_orifice should raise errors when inputs are out of bounds.""" - checks = ((0,1,1), (1, 1, 1.1), (1, 1, -1)) + checks = ((0 * u.m, 1 * u.m, 1), (1 * u.m, 1 * u.m, 1.1), + (1 * u.m, 1 * u.m, -1)) for i in checks: with self.subTest(i=i): self.assertRaises(ValueError, pc.flow_orifice, *i) - pc.flow_orifice(1, 1, 0) - - def test_flow_orifice_units(self): - """flow_orifice should handle units correctly.""" - base = pc.flow_orifice(2, 3, 0.5).magnitude - checks = ((2 * u.m, 3 * u.m, 0.5 * u.dimensionless), - (200 * u.cm, 3, 0.5), - (2, 0.003 * u.km, 0.5)) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.flow_orifice(*i).magnitude, base) def test_flow_orifice_vert(self): """flow_orifice_vert should return known values for known inputs.""" - checks = (([1, 3, 0.4], 2.4077258053173911), - ([0.3, 4, 0.67], 0.41946278400781861), ([2, -4, 0.2], 0)) + checks = (([1 * u.m, 3 * u.m, 0.4], 2.4077258053173911 * u.m**3/u.s), + ([0.3 * u.m, 4 * u.m, 0.67], 0.41946278400781861 * u.m**3/u.s), + ([2 * u.m, -4 * u.m, 0.2 * u.dimensionless], 0 * u.m**3/u.s)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.flow_orifice_vert(*i[0]).magnitude, i[1]) + self.assertAlmostEqualQuantity(pc.flow_orifice_vert(*i[0]), i[1]) def test_flow_orifice_vert_range(self): """flow_orifice_vert should raise errors when inputs are out of bounds.""" - errorChecks = ((1, 1, -1), (1, 1, 2)) - errorlessChecks = ((1, 1, 1), (1, 1, 0), (1, 1, 0.5)) + errorChecks = ((1 * u.m, 1 * u.m, -1), (1 * u.m, 1 * u.m, 2)) + errorlessChecks = ((1 * u.m, 1 * u.m, 1), (1 * u.m, 1 * u.m, 0), + (1 * u.m, 1 * u.m, 0.5)) for i in errorChecks: with self.subTest(i=i): self.assertRaises(ValueError, pc.flow_orifice_vert, *i) @@ -691,526 +582,440 @@ def test_flow_orifice_vert_range(self): with self.subTest(i=i): pc.flow_orifice_vert(*i) - def test_flow_orifice_vert_units(self): - """flow_orifice_vert should handle units correctly.""" - base = pc.flow_orifice_vert(1, 3, 0.4).magnitude - checks = ([1 * u.m, 3 * u.m, 0.4 * u.dimensionless], - [100 * u.cm, 3, 0.4], - [1, 0.003 * u.km, 0.4]) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.flow_orifice_vert(*i).magnitude, base) - def test_head_orifice(self): """head_orifice should return known value for known inputs.""" - checks = (([1, 1, 1], 0.08265508294256473), - ([1.2, 0.1, 0.12], 0.05739936315455882), - ([2, 0.5, 0.04], 3.3062033177025895e-05)) + checks = (([1 * u.m, 1, 1 * u.m**3/u.s], 0.08265508294256473 * u.m), + ([1.2 * u.m, 0.1, 0.12 * u.m**3/u.s], 0.05739936315455882 * u.m), + ([2 * u.m, 0.5 * u.dimensionless, 0.04 * u.m**3/u.s], 3.3062033177025895e-05 * u.m)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.head_orifice(*i[0]).magnitude, i[1]) + self.assertAlmostEqualQuantity(pc.head_orifice(*i[0]), i[1]) def test_head_orifice_range(self): """head_orifice should raise errors when passed invalid inputs.""" - failChecks = ((0,1,1), (1, 1, 0), (1, -1, 1), (1, 2, 1)) + failChecks = ((0 * u.m, 1, 1 * u.m**3/u.s), + (1 * u.m, 1, 0 * u.m**3/u.s), + (1 * u.m, -1, 1 * u.m**3/u.s), + (1 * u.m, 2, 1 * u.m**3/u.s)) for i in failChecks: with self.subTest(i=i): self.assertRaises(ValueError, pc.head_orifice, *i) - pc.head_orifice(1, 1, 1) - self.assertRaises(ZeroDivisionError, pc.head_orifice, *(1, 0, 1)) - - def test_head_orifice_units(self): - """head_orifice should handle units correctly.""" - base = pc.head_orifice(2, 0.5, 0.04).magnitude - checks = ([2 * u.m, 0.5 * u.dimensionless, 0.04 * u.m**3/u.s], - [200 * u.cm, 0.5, 0.04], - [2, 0.5, 40 * u.L/u.s]) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.head_orifice(*i).magnitude, base) + self.assertRaises(ZeroDivisionError, pc.head_orifice, *(1 * u.m, 0, 1 * u.m**3/u.s)) def test_area_orifice(self): """area_orifice should return known value for known inputs.""" - checks = (([3, 0.4, 0.06], 0.019554886342464974), - ([2, 0.1, 0.1], 0.15966497839052934), - ([0.5, 0.02, 3], 47.899493517158803)) + checks = (([3 * u.m, 0.4, 0.06 * u.m**3/u.s], 0.019554886342464974 * u.m**2), + ([2 * u.m, 0.1, 0.1 * u.m**3/u.s], 0.15966497839052934 * u.m**2), + ([0.5 * u.m, 0.02 * u.dimensionless, 3 * u.m**3/u.s], 47.899493517158803 * u.m**2)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.area_orifice(*i[0]).magnitude, i[1]) + self.assertAlmostEqualQuantity(pc.area_orifice(*i[0]), i[1]) def test_area_orifice_range(self): """area_orifice should raise errors when inputs are out of bounds.""" - failChecks = ((0, 1, 1), (1, 1, 0), (1, -1, 1), (1, 2, 1), (1, 0, 1)) + failChecks = ((0 * u.m, 1, 1 * u.m**3/u.s), + (1 * u.m, 1, 0 * u.m**3/u.s), + (1 * u.m, -1, 1 * u.m**3/u.s), + (1 * u.m, 2, 1 * u.m**3/u.s), + (1 * u.m, 0, 1 * u.m**3/u.s)) for i in failChecks: with self.subTest(i=i): self.assertRaises(ValueError, pc.area_orifice, *i) - pc.area_orifice(1, 1, 1) - - def test_area_orifice_units(self): - """area_orifice should handle units correctly.""" - base = pc.area_orifice(3, 0.4, 0.06).magnitude - checks = ([3 * u.m, 0.4 * u.dimensionless, 0.06 * u.m**3/u.s], - [300 * u.cm, 0.4, 0.06], - [3, 0.4, 60 * u.L/u.s]) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.area_orifice(*i).magnitude, base) def test_num_orifices(self): """num_orifices should return known value for known inputs.""" - checks = (([0.12, 0.04, 0.05, 2], 1), - ([6, 0.8, 0.08, 1.2], 6)) + checks = (([0.12 * u.m**3/u.s, 0.04, 0.05 * u.m, 2 * u.m], 1 * u.dimensionless), + ([6 * u.m**3/u.s, 0.8, 0.08 * u.m, 1.2 * u.m], 6 * u.dimensionless)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.num_orifices(*i[0]), i[1]) + self.assertAlmostEqualQuantity(pc.num_orifices(*i[0]), i[1]) - def test_num_orifices_units(self): - """num_orifices should handle units correctly.""" - base = pc.num_orifices(6, 0.8, 0.08, 1.2) - checks = ([6 * u.m**3/u.s, 0.8 * u.dimensionless, 0.08 * u.m, 1.2 * u.m], - [6000 * u.L/u.s, 0.8, 0.08, 1.2], - [6, 0.8, 8 * u.cm, 1.2], - [6, 0.8, 0.08, 0.0012 * u.km]) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.num_orifices(*i), base) -class FlowFuncsTest(unittest.TestCase): +class FlowFuncsTest(QuantityTest): """Test the flow functions.""" def test_flow_transition(self): """flow_transition should return known value for known inputs.""" - checks = (([2, 0.4], 1319.4689145077132), - ([0.8, 1.1], 1451.4158059584847)) + checks = (([2 * u.m, 0.4 * u.m**2/u.s], 1319.4689145077132 * u.m**3/u.s), + ([0.8 * u.m, 1.1 * u.m**2/u.s], 1451.4158059584847 * u.m**3/u.s)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.flow_transition(*i[0]).magnitude, i[1]) + self.assertAlmostEqualQuantity(pc.flow_transition(*i[0]), i[1]) def test_flow_transition_range(self): """flow_transition should not accept inputs <= 0.""" - checks = ((1, 0), (0, 1)) + checks = ((1 * u.m, 0 * u.m**2/u.s), (0 * u.m, 1 * u.m**2/u.s)) for i in checks: with self.subTest(i=i): self.assertRaises(ValueError, pc.flow_transition, *i) - def test_flow_transition_units(self): - """flow_transition should handle units correctly.""" - base = pc.flow_transition(2, 0.4).magnitude - checks = ([2 * u.m, 0.4 * u.m**2/u.s], - [200 * u.cm, 0.4], - [2, 4000 * u.cm**2/u.s]) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.flow_transition(*i).magnitude, base) - def test_flow_hagen(self): """flow_hagen should return known value for known inputs.""" - checks = (([1, 0.4, 5.21, 0.6], 0.03079864403023667), - ([0.05, 0.0006, 0.3, 1.1], 2.7351295806397676e-09)) + checks = (([1 * u.m, 0.4 * u.m, 5.21 * u.m, 0.6 * u.m**2/u.s], 0.03079864403023667 * u.m**3/u.s), + ([0.05 * u.m, 0.0006 * u.m, 0.3 * u.m, 1.1 * u.m**2/u.s], 2.7351295806397676e-09 * u.m**3/u.s)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.flow_hagen(*i[0]).magnitude, i[1]) + self.assertAlmostEqualQuantity(pc.flow_hagen(*i[0]), i[1]) def test_flow_hagen_range(self): """flow_hagen should raise errors when inputs are out of bounds.""" - failChecks = ((0, 1, 1, 1), (1, -1, 1, 1), (1, 1, 0, 1), (1, 1, 1, 0)) + failChecks = ((0 * u.m, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s), + (1 * u.m, -1 * u.m, 1 * u.m, 1 * u.m**2/u.s), + (1 * u.m, 1 * u.m, 0 * u.m, 1 * u.m**2/u.s), + (1 * u.m, 1 * u.m, 1 * u.m, 0 * u.m**2/u.s)) for i in failChecks: with self.subTest(i=i): self.assertRaises(ValueError, pc.flow_hagen, *i) - passChecks = ((1, 1, 1, 1), (1, 0, 1, 1)) + passChecks = ((1 * u.m, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s), + (1 * u.m, 0 * u.m, 1 * u.m, 1 * u.m**2/u.s)) for i in passChecks: with self.subTest(i=i): pc.flow_hagen(*i) - def test_flow_hagen_units(self): - """flow_hagen should handle units properly.""" - base = pc.flow_hagen(0.05, 0.0006, 0.3, 1.1).magnitude - checks = ([0.05 * u.m, 0.0006 * u.m, 0.3 * u.m, 1.1 * u.m**2/u.s], - [5 * u.cm, 0.0006, 0.3, 1.1], - [0.05, 0.6 * u.mm, 0.3, 1.1], - [0.05, 0.0006, 0.0003 * u.km, 1.1], - [0.05, 0.0006, 0.3, 11000 * u.cm**2/u.s]) - for i in checks: + def test_flow_hagen_warning(self): + """flow_hagen should raise warnings when passed deprecated parameters""" + error_checks = (lambda: pc.flow_hagen(1 * u.m, HeadLossMajor=1 * u.m, Length=1 * u.m, Nu=1 * u.m**2/u.s, HeadLossFric=1 * u.m), + lambda: pc.flow_hagen(1 * u.m, Length=1 * u.m, Nu=1 * u.m**2/u.s), + lambda: pc.flow_hagen(1 * u.m, HeadLossMajor=1 * u.m, Nu=1 * u.m**2/u.s), + lambda: pc.flow_hagen(1 * u.m, HeadLossMajor=1 * u.m, Length=1 * u.m)) + for i in error_checks: + with self.subTest(i=i): + self.assertRaises(TypeError, i) + + warning_checks = (lambda: pc.flow_hagen(1 * u.m, HeadLossFric=1 * u.m, Length=1 * u.m, Nu=1 * u.m**2/u.s),) + for i in warning_checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.flow_hagen(*i).magnitude, base) + self.assertWarns(UserWarning, i) def test_flow_swamee(self): """flow_swamee should return known value for known inputs.""" - checks = (([2, 0.04, 3, 0.1, 0.37], 2.9565931732010045),) + checks = (([2 * u.m, 0.04 * u.m, 3 * u.m, 0.1 * u.m**2/u.s, 0.37 * u.m], 2.9565931732010045 * u.m**3/u.s),) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.flow_swamee(*i[0]).magnitude, i[1]) + self.assertAlmostEqualQuantity(pc.flow_swamee(*i[0]), i[1]) def test_flow_swamee_range(self): """flow_swamee should raise errors when inputs are out of bounds.""" - failChecks = ((0, 1, 1, 1, 1), (1, 0, 1, 1, 1), (1, 1, 0, 1, 1), - (1, 1, 1, 0, 1), (1, 1, 1, 1, -0.1), (1, 1, 1, 1, 2)) + failChecks = ((0 * u.m, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, 1 * u.m), + (1 * u.m, 0 * u.m, 1 * u.m, 1 * u.m**2/u.s, 1 * u.m), + (1 * u.m, 1 * u.m, 0 * u.m, 1 * u.m**2/u.s, 1 * u.m), + (1 * u.m, 1 * u.m, 1 * u.m, 0 * u.m**2/u.s, 1 * u.m), + (1 * u.m, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, -0.1 * u.m), + (1 * u.m, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, -2 * u.m)) for i in failChecks: with self.subTest(i=i): self.assertRaises(ValueError, pc.flow_swamee, *i) - passChecks = ((1, 1, 1, 1, 1), (1, 1, 1, 1, 0)) + passChecks = ((1 * u.m, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, 1 * u.m), + (1 * u.m, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, 0 * u.m)) for i in passChecks: with self.subTest(i=i): pc.flow_swamee(*i) - def test_flow_swamee_units(self): - """flow_swamee should handle units correctly.""" - base = pc.flow_swamee(2, 0.04, 3, 0.1, 0.37).magnitude - checks = ([2 * u.m, 0.04 * u.m, 3 * u.m, 0.1 * u.m**2/u.s, 0.37 * u.m], - [200 * u.cm, 0.04, 3, 0.1, 0.37], - [2, 40 * u.mm, 3, 0.1, 0.37], - [2, 0.04, 0.003 * u.km, 0.1, 0.37], - [2, 0.04, 3, 1000 * u.cm**2/u.s, 0.37], - [2, 0.04, 3, 0.1, 37 * u.cm]) - for i in checks: + def test_flow_swamee_warning(self): + """flow_swamee should raise warnings when passed deprecated parameters""" + error_checks = (lambda: pc.flow_swamee(1 * u.m, HeadLossMajor=1 * u.m, Length=1 * u.m, Nu=1 * u.m**2/u.s, Roughness=1 * u.m, HeadLossFric=1 * u.m), + lambda: pc.flow_swamee(1 * u.m, Length=1 * u.m, Nu=1 * u.m**2/u.s, Roughness=1 * u.m), + lambda: pc.flow_swamee(1 * u.m, HeadLossMajor=1 * u.m, Nu=1 * u.m**2/u.s, Roughness=1 * u.m), + lambda: pc.flow_swamee(1 * u.m, HeadLossMajor=1 * u.m, Length=1 * u.m, Roughness=1 * u.m), + lambda: pc.flow_swamee(1 * u.m, HeadLossMajor=1 * u.m, Length=1 * u.m, Nu=1 * u.m**2/u.s, Roughness=1 * u.m, PipeRough=1 * u.m), + lambda: pc.flow_swamee(1 * u.m, HeadLossMajor=1 * u.m, Length=1 * u.m, Nu=1 * u.m**2/u.s)) + for i in error_checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.flow_swamee(*i).magnitude, base) + self.assertRaises(TypeError, i) - def test_flow_pipemajor(self): - """flow_pipemajor should return known result for known inputs.""" - checks = (([1, 0.97, 0.5, 0.025, 0.06], 18.677652880272845), - ([2, 0.62, 0.5, 0.036, 0.23], 62.457206502701297)) - for i in checks: + warning_checks = (lambda: pc.flow_swamee(1 * u.m, HeadLossFric=1 * u.m, Length=1 * u.m, Nu=1 * u.m**2/u.s, Roughness=1 * u.m), + lambda: pc.flow_swamee(1 * u.m, HeadLossMajor=1 * u.m, Length=1 * u.m, Nu=1 * u.m**2/u.s, PipeRough=1 * u.m)) + for i in warning_checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.flow_pipemajor(*i[0]).magnitude, i[1]) - - def test_flow_pipemajor_units(self): - """flow_pipemajor should handle units correctly.""" - base = pc.flow_pipemajor(2, 0.62, 0.5, 0.036, 0.23).magnitude - checks = ([2 * u.m, 0.62 * u.m, 0.5 * u.m, - 0.036 * u.m**2/u.s, 0.23 * u.m], - [200 * u.cm, 0.62, 0.5, 0.036, 0.23], - [2, 620 * u.mm, 0.5, 0.036, 0.23], - [2, 0.62, 0.0005 * u.km, 0.036, 0.23], - [2, 0.62, 0.5, 360 * u.cm**2/u.s, 0.23], - [2, 0.62, 0.5, 0.036, 23 * u.cm]) + self.assertWarns(UserWarning, i) + + def test_flow_pipemajor(self): + self.assertWarns(UserWarning, pc.flow_pipemajor, *(1 * u.m, 0.97 * u.m, 0.5 * u.m, 0.025 * u.m**2/u.s, 0.06 * u.m)) + + def test_flow_major_pipe(self): + """flow_major_pipe should return known result for known inputs.""" + checks = (([1 * u.m, 0.97 * u.m, 0.5 * u.m, 0.025 * u.m**2/u.s, 0.06 * u.m], 18.677652880272845 * u.m**3/u.s), + ([2 * u.m, 0.62 * u.m, 0.5 * u.m, 0.036 * u.m**2/u.s, 0.23 * u.m], 62.457206502701297 * u.m**3/u.s)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.flow_pipemajor(*i).magnitude, base) + self.assertAlmostEqualQuantity(pc.flow_major_pipe(*i[0]), i[1]) def test_flow_pipeminor(self): - """flow_pipeminor should return known results for known input.""" - self.assertAlmostEqual(pc.flow_pipeminor(1, 0.125, 3).magnitude, - 0.71000203931611083) + self.assertWarns(UserWarning, pc.flow_pipeminor, *(1 * u.m, 0.125 * u.m, 3)) - def test_flow_pipeminor_range(self): - """flow_pipeminor should raise errors when inputs are out of bounds.""" - failChecks = ((1, -1, 1), (1, 1, 0)) + def test_flow_minor_pipe(self): + """flow_minor_pipe should return known results for known input.""" + self.assertAlmostEqualQuantity(pc.flow_minor_pipe(1 * u.m, 0.125 * u.m, 3), + 0.71000203931611083 * u.m**3/u.s) + + def test_flow_minor_pipe_range(self): + """flow_minor_pipe should raise errors when inputs are out of bounds.""" + failChecks = ((1 * u.m, -1 * u.m, 1), + (1 * u.m, 1 * u.m, 0 * u.dimensionless)) for i in failChecks: with self.subTest(i=i): - self.assertRaises(ValueError, pc.flow_pipeminor, *i) - passChecks = ((1, 1, 1), (1, 0, 1)) + self.assertRaises(ValueError, pc.flow_minor_pipe, *i) + passChecks = ((1 * u.m, 1 * u.m, 1), (1 * u.m, 0 * u.m, 1)) for i in passChecks: with self.subTest(i=i): - pc.flow_pipeminor(*i) - - def test_flow_pipeminor_units(self): - """flow_pipeminor should handle units correctly.""" - base = pc.flow_pipeminor(1, 0.125, 3).magnitude - checks = ((1 * u.m, 0.125 * u.m, 3 * u.dimensionless), - (0.001 * u.km, 0.125, 3), - (1, 125 * u.mm, 3)) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.flow_pipeminor(*i).magnitude, base) + pc.flow_minor_pipe(*i) def test_flow_pipe(self): """flow_pipe should return known value for known inputs.""" - checks = (([0.25, 0.4, 2, 0.58, 0.029, 0], 0.000324207170118938), - ([0.25, 0.4, 2, 0.58, 0.029, 0.35], 0.000324206539183988)) + checks = (([0.25 * u.m, 0.4 * u.m, 2 * u.m, 0.58 * u.m**2/u.s, 0.029 * u.m, 0], 0.000324207170118938 * u.m**3/u.s), + ([0.25 * u.m, 0.4 * u.m, 2 * u.m, 0.58 * u.m**2/u.s, 0.029 * u.m, 0.35 * u.dimensionless], 0.000324206539183988 * u.m**3/u.s)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.flow_pipe(*i[0]).magnitude, i[1]) - - def test_flow_pipe_units(self): - """flow_pipe should handle units correctly.""" - base = pc.flow_pipe(0.25, 0.4, 2, 0.58, 0.029, 0.35).magnitude - checks = ([0.25 * u.m, 0.4 * u.m, 2 * u.m, - 0.58 * u.m**2/u.s, 0.029 * u.m, 0.35], - [25 * u.cm, 0.4, 2, 0.58, 0.029, 0.35], - [0.25, 400 * u.mm, 2, 0.58, 0.029, 0.35], - [0.25, 0.4, 0.002 * u.km, 0.58, 0.029, 0.35], - [0.25, 0.4, 2, 580000 * u.mm**2/u.s, 0.029, 0.35], - [0.25, 0.4, 2, 0.58, 29 * u.mm, 0.35]) - for i in checks: + self.assertAlmostEqualQuantity(pc.flow_pipe(*i[0]), i[1]) + + def test_flow_pipe_warning(self): + """flow_pipe should raise warnings when passed deprecated parameters""" + error_checks = (lambda: pc.flow_pipe(1 * u.m, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, Roughness=1 * u.m, KMinor=1, PipeRough=1 * u.m), + lambda: pc.flow_pipe(1 * u.m, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, KMinor=1), + lambda: pc.flow_pipe(1 * u.m, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, Roughness=1 * u.m),) + for i in error_checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.flow_pipe(*i).magnitude, base) + self.assertRaises(TypeError, i) + warning_checks = (lambda: pc.flow_pipe(1 * u.m, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, PipeRough=1 * u.m, KMinor=1),) + for i in warning_checks: + with self.subTest(i=i): + self.assertWarns(UserWarning, i) -class DiamFuncsTest(unittest.TestCase): +class DiamFuncsTest(QuantityTest): """Test the diameter functions.""" def test_diam_hagen(self): """diam_hagen should return known value for known inputs.""" - self.assertAlmostEqual(pc.diam_hagen(0.006, 0.00025, 0.75, 0.0004).magnitude, - 0.4158799465199102) + self.assertAlmostEqualQuantity(pc.diam_hagen(0.006 * u.m**3/u.s, 0.00025 * u.m, 0.75 * u.m, 0.0004 * u.m**2/u.s), + 0.4158799465199102 * u.m) def test_diam_hagen_range(self): """diam_hagen should raise errors when inputs are out of bounds.""" - failChecks = ((0, 1, 1, 1), (1, 0, 1, 1), (1, 1, 0, 1), (1, 1, 1, 0)) + failChecks = ((0 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s), + (1 * u.m**3/u.s, 0 * u.m, 1 * u.m, 1 * u.m**2/u.s), + (1 * u.m**3/u.s, 1 * u.m, 0 * u.m, 1 * u.m**2/u.s), + (1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 0 * u.m**2/u.s)) for i in failChecks: with self.subTest(i=i): self.assertRaises(ValueError, pc.diam_hagen, *i) - pc.diam_hagen(1, 1, 1, 1) - - def test_diam_hagen_units(self): - """diam_hagen should handle units correctly.""" - base = pc.diam_hagen(0.006, 0.00025, 0.75, 0.0004).magnitude - checks = ([0.006 * u.m**3/u.s, 0.00025 * u.m, - 0.75 * u.m, 0.0004 * u.m**2/u.s], - [6 * u.L/u.s, 0.00025, 0.75, 0.0004], - [0.006, 0.25 * u.mm, 0.75, 0.0004], - [0.006, 0.00025, 75 * u.cm, 0.0004], - [0.006, 0.00025, 0.75, 4 * u.cm**2/u.s]) - for i in checks: + + def test_diam_hagen_warning(self): + """flow_hagen should raise warnings when passed deprecated parameters""" + error_checks = (lambda: pc.diam_hagen(1 * u.m**3/u.s, HeadLossMajor=1 * u.m, Length=1 * u.m, Nu=1 * u.m**2/u.s, HeadLossFric=1 * u.m), + lambda: pc.diam_hagen(1 * u.m**3/u.s, Length=1 * u.m, Nu=1 * u.m**2/u.s), + lambda: pc.diam_hagen(1 * u.m**3/u.s, HeadLossMajor=1 * u.m, Nu=1 * u.m**2/u.s), + lambda: pc.diam_hagen(1 * u.m**3/u.s, HeadLossMajor=1 * u.m, Length=1 * u.m)) + for i in error_checks: + with self.subTest(i=i): + self.assertRaises(TypeError, i) + + warning_checks = (lambda: pc.diam_hagen(1 * u.m**3/u.s, HeadLossFric=1 * u.m, Length=1 * u.m, Nu=1 * u.m**2/u.s),) + for i in warning_checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.diam_hagen(*i).magnitude, base) + self.assertWarns(UserWarning, i) def test_diam_swamee(self): """diam_swamee should return known value for known input.""" - self.assertAlmostEqual(pc.diam_swamee(0.06, 1.2, 7, 0.2, 0.0004).magnitude, - 0.19286307314945772) + self.assertAlmostEqualQuantity(pc.diam_swamee(0.06 * u.m**3/u.s, 1.2 * u.m, 7 * u.m, 0.2* u.m**2/u.s, 0.0004 * u.m), + 0.19286307314945772 * u.m) def test_diam_swamee_range(self): """diam_swamee should raise errors if inputs are out of bounds.""" - failChecks = ((0, 1, 1, 1, 1), (1, 0, 1, 1, 1), (1, 1, 0, 1, 1), - (1, 1, 1, 0, 1), (1, 1, 1, 1, 2), (1, 1, 1, 1, -1)) + failChecks = ((0 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, 1 * u.m), + (1 * u.m**3/u.s, 0 * u.m, 1 * u.m, 1 * u.m**2/u.s, 1 * u.m), + (1 * u.m**3/u.s, 1 * u.m, 0 * u.m, 1 * u.m**2/u.s, 1 * u.m), + (1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 0 * u.m**2/u.s, 1 * u.m), + (1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, -2 * u.m), + (1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, -1 * u.m)) for i in failChecks: with self.subTest(i=i): self.assertRaises(ValueError, pc.diam_swamee, *i) - passChecks = ((1, 1, 1, 1, 1), (1, 1, 1, 1, 0)) + passChecks = ((1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, 1 * u.m), + (1 * u.m**3/u.s, 1 * u.m, 1 * u.m, 1 * u.m**2/u.s, 0 * u.m)) for i in passChecks: with self.subTest(i=i): pc.diam_swamee(*i) - def test_diam_swamee_units(self): - """diam_swamee should handle units correctly.""" - base = pc.diam_swamee(0.06, 1.2, 7, 0.2, 0.0004).magnitude - checks = ([0.06 * u.m**3/u.s, 1.2 * u.m, 7 * u.m, - 0.2 * u.m**2/u.s, 0.0004 * u.m], - [60 * u.L/u.s, 1.2, 7, 0.2, 0.0004], - [0.06, 0.0012 * u.km, 7, 0.2, 0.0004], - [0.06, 1.2, 700 * u.cm, 0.2, 0.0004], - [0.06, 1.2, 7, 2000 * u.cm**2/u.s, 0.0004], - [0.06, 1.2, 7, 0.2, 0.4 * u.mm]) - for i in checks: + def test_diam_swamee_warning(self): + """diam_swamee should raise warnings when passed deprecated parameters""" + error_checks = (lambda: pc.diam_swamee(1 * u.m**3/u.s, HeadLossMajor=1 * u.m, Length=1 * u.m, Nu=1 * u.m**2/u.s, Roughness=1 * u.m, HeadLossFric=1 * u.m), + lambda: pc.diam_swamee(1 * u.m**3/u.s, Length=1 * u.m, Nu=1 * u.m**2/u.s, Roughness=1 * u.m), + lambda: pc.diam_swamee(1 * u.m**3/u.s, HeadLossMajor=1 * u.m, Nu=1 * u.m**2/u.s, Roughness=1 * u.m), + lambda: pc.diam_swamee(1 * u.m**3/u.s, HeadLossMajor=1 * u.m, Length=1 * u.m, Roughness=1 * u.m), + lambda: pc.diam_swamee(1 * u.m**3/u.s, HeadLossMajor=1 * u.m, Length=1 * u.m, Nu=1 * u.m**2/u.s, Roughness=1 * u.m, PipeRough=1 * u.m), + lambda: pc.diam_swamee(1 * u.m**3/u.s, HeadLossMajor=1 * u.m, Length=1 * u.m, Nu=1 * u.m**2/u.s)) + for i in error_checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.diam_swamee(*i).magnitude, base) + self.assertRaises(TypeError, i) - def test_diam_pipemajor(self): - """diam_pipemajor should return known value for known inputs.""" - checks = (([0.005, 0.03, 1.6, 0.53, 0.002], 0.8753787620849313), - ([1, 2, 0.03, 0.004, 0.005], 0.14865504303291951)) - for i in checks: + warning_checks = (lambda: pc.diam_swamee(1 * u.m**3/u.s, HeadLossFric=1 * u.m, Length=1 * u.m, Nu=1 * u.m**2/u.s, Roughness=1 * u.m), + lambda: pc.diam_swamee(1 * u.m**3/u.s, HeadLossMajor=1 * u.m, Length=1 * u.m, Nu=1 * u.m**2/u.s, PipeRough=1 * u.m)) + for i in warning_checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.diam_pipemajor(*i[0]).magnitude, i[1]) - - def test_diam_pipemajor_units(self): - """diam_pipemajor should handle units correctly.""" - base = pc.diam_pipemajor(1, 2, 0.03, 0.004, 0.005).magnitude - checks = ([1 * u.m**3/u.s, 2 * u.m, 0.03 * u.m, - 0.004 * u.m**2/u.s, 0.005 * u.m], - [1000 * u.L/u.s, 2, 0.03, 0.004, 0.005], - [1, 0.002 * u.km, 0.03, 0.004, 0.005], - [1, 2, 3 * u.cm, 0.004, 0.005], - [1, 2, 0.03, 40 * u.cm**2/u.s, 0.005], - [1, 2, 0.03, 0.004, 5 * u.mm]) + self.assertWarns(UserWarning, i) + + def test_diam_pipemajor(self): + self.assertWarns(UserWarning, pc.diam_pipemajor, *(0.005 * u.m**3/u.s, 0.03 * u.m, 1.6 * u.m, 0.53 * u.m**2/u.s, 0.002 * u.m)) + + def test_diam_major_pipe(self): + """diam_major_pipe should return known value for known inputs.""" + checks = (([0.005 * u.m**3/u.s, 0.03 * u.m, 1.6 * u.m, 0.53 * u.m**2/u.s, 0.002 * u.m], 0.8753787620849313 * u.m), + ([1 * u.m**3/u.s, 2 * u.m, 0.03 * u.m, 0.004 * u.m**2/u.s, 0.005 * u.m], 0.14865504303291951 * u.m)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.diam_pipemajor(*i).magnitude, base) + self.assertAlmostEqualQuantity(pc.diam_major_pipe(*i[0]), i[1]) def test_diam_pipeminor(self): - """diam_pipeminor should return known value for known inputs.""" - checks = (([0.008, 0.012, 0.93], 0.14229440061589257), - ([0.015, 0.3, 0.472], 0.073547549463488848)) + self.assertWarns(UserWarning, pc.diam_pipeminor, *(0.008 * u.m**3/u.s, 0.012 * u.m, 0.93)) + + def test_diam_minor_pipe(self): + """diam_minor_pipe should return known value for known inputs.""" + checks = (([0.008 * u.m**3/u.s, 0.012 * u.m, 0.93], 0.14229440061589257 * u.m), + ([0.015 * u.m**3/u.s, 0.3 * u.m, 0.472 * u.dimensionless], 0.073547549463488848 * u.m)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.diam_pipeminor(*i[0]).magnitude, i[1]) + self.assertAlmostEqualQuantity(pc.diam_minor_pipe(*i[0]), i[1]) - def test_diam_pipeminor_range(self): - """diam_pipeminor should raise errors when inputs are out of bounds.""" - failChecks = ((0, 1, 1), (1, 0, 1), (1, 1, -1)) + def test_diam_minor_pipe_range(self): + """diam_minor_pipe should raise errors when inputs are out of bounds.""" + failChecks = ((0 * u.m**3/u.s, 1 * u.m, 1), + (1 * u.m**3/u.s, 0 * u.m, 1), + (1 * u.m**3/u.s, 1 * u.m, -1 * u.dimensionless)) for i in failChecks: with self.subTest(i=i): - self.assertRaises(ValueError, pc.diam_pipeminor, *i) - passChecks = ((1, 1, 1), (1, 1, 0)) + self.assertRaises(ValueError, pc.diam_minor_pipe, *i) + passChecks = ((1 * u.m**3/u.s, 1 * u.m, 1), + (1 * u.m**3/u.s, 1 * u.m, 0)) for i in passChecks: with self.subTest(i=i): - pc.diam_pipeminor(*i) - - def test_diam_pipeminor_units(self): - """diam_pipeminor should handle units correctly.""" - base = pc.diam_pipeminor(0.008, 0.012, 0.93).magnitude - checks = ([0.008 * u.m**3/u.s, 0.012 * u.m, 0.93 * u.dimensionless], - [8 * u.L/u.s, 0.012, 0.93], - [0.008, 1.2 * u.cm, 0.93]) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.diam_pipeminor(*i).magnitude, base) + pc.diam_minor_pipe(*i) def test_diam_pipe(self): """diam_pipe should return known value for known inputs.""" - checks = (([0.007, 0.04, 0.75, 0.16, 0.0079, 0], 0.5434876490369928), - ([0.007, 0.04, 0.75, 0.16, 0.0079, 0.8], 0.5436137491479152)) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.diam_pipe(*i[0]).magnitude, i[1]) - - def test_diam_pipe_units(self): - """diam_pipe should handle units correctly.""" - base = pc.diam_pipe(0.007, 0.04, 0.75, 0.16, 0.0079, 0.8).magnitude - checks = ([0.007 * u.m**3/u.s, 0.04 * u.m, 0.75 * u.m, - 0.16 * u.m**2/u.s, 0.0079 * u.m, 0.8], - [7 * u.L/u.s, 0.04, 0.75, 0.16, 0.0079, 0.8], - [0.007, 4 * u.cm, 0.75, 0.16, 0.0079, 0.8], - [0.007, 0.04, 75 * u.cm, 0.16, 0.0079, 0.8], - [0.007, 0.04, 0.75, 1600 * u.cm**2/u.s, 0.0079, 0.8], - [0.007, 0.04, 0.75, 0.16, 7.9 * u.mm, 0.8]) + checks = (([0.007 * u.m**3/u.s, 0.04 * u.m, 0.75 * u.m, 0.16 * u.m**2/u.s, 0.0079 * u.m, 0], 0.5434876490369928 * u.m), + ([0.007 * u.m**3/u.s, 0.04 * u.m, 0.75 * u.m, 0.16 * u.m**2/u.s, 0.0079 * u.m, 0.8 * u.dimensionless], 0.5436137491479152 * u.m)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.diam_pipe(*i).magnitude, base) + self.assertAlmostEqualQuantity(pc.diam_pipe(*i[0]), i[1]) -class WeirFuncsTest(unittest.TestCase): + +class WeirFuncsTest(QuantityTest): """Test the weir functions.""" def test_width_rect_weir(self): - """width_rect_weir should return known value for known inputs.""" - self.assertAlmostEqual(pc.width_rect_weir(0.005, 0.2).magnitude, - 0.030, places=3) + self.assertWarns(UserWarning, pc.width_rect_weir, *(0.005 * u.m**3/u.s, 0.2 * u.m)) - def test_width_rect_weir_range(self): - """width_rect_weird should raise errors when inputs are out of bounds.""" - checks = ((0, 1), (1, 0)) - for i in checks: - with self.subTest(i=i): - self.assertRaises(ValueError, pc.width_rect_weir, *i) - pc.width_rect_weir(1, 1) + def test_width_weir_rect(self): + """width_weir_rect should return known value for known inputs.""" + self.assertAlmostEqualQuantity(pc.width_weir_rect(0.005 * u.m**3/u.s, 0.2 * u.m), + 0.03005386871 * u.m) - def test_width_rect_weir_units(self): - """width_rect_weir should handle units correctly.""" - base = pc.width_rect_weir(0.005, 0.2).magnitude - checks = ([0.005 * u.m**3/u.s, 0.2 * u.m], - [5 * u.L/u.s, 0.2], - [0.005, 20 * u.cm]) + def test_width_weir_rect_range(self): + """width_weir_rect should raise errors when inputs are out of bounds.""" + checks = ((0 * u.m**3/u.s, 1 * u.m), (1 * u.m**3/u.s, 0 * u.m)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.width_rect_weir(*i).magnitude, base) + self.assertRaises(ValueError, pc.width_weir_rect, *i) def test_headloss_weir(self): - """headloss_weir should return known value for known inputs.""" - self.assertAlmostEqual(pc.headloss_weir(0.005, 1).magnitude, - 0.019, places=3) + self.assertWarns(UserWarning, pc.headloss_weir, *(0.005 * u.m**3/u.s, 1 * u.m)) - def test_headloss_weir_range(self): - """headloss_weir should raise errors when inputs are out of bounds.""" - checks = ((0, 1), (1, 0)) + def test_headloss_weir_rect(self): + """headloss_rect_weir should return known value for known inputs.""" + self.assertAlmostEqualQuantity(pc.headloss_weir_rect(0.005 * u.m**3/u.s, 1 * u.m), + 0.01933289619 * u.m) + + def test_headloss_weir_rect_range(self): + """headloss_weir_rect should raise errors when inputs are out of bounds.""" + checks = ((0 * u.m**3/u.s, 1 * u.m), (1 * u.m**3/u.s, 0 * u.m)) for i in checks: with self.subTest(i=i): - self.assertRaises(ValueError, pc.headloss_weir, *i) - pc.headloss_weir(1,1) + self.assertRaises(ValueError, pc.headloss_weir_rect, *i) + + def test_flow_rect_weir(self): + self.assertWarns(UserWarning, pc.flow_rect_weir, *(2 * u.m, 1 * u.m)) - def test_headloss_weir_units(self): - """headloss_weir should handle units correctly.""" - base = pc.headloss_weir(0.005, 1).magnitude - checks = ([0.005 * u.m**3/u.s, 1 * u.m], - [5 * u.L/u.s, 1], - [0.005, 100 * u.cm]) + def test_flow_weir_rect(self): + """flow_weir_rect should return known value for known inputs.""" + self.assertAlmostEqualQuantity(pc.flow_weir_rect(2 * u.m, 1 * u.m), + 5.2610159627 * u.m**3/u.s) + + def test_flow_weir_rect_range(self): + """flow_weir_rect should raise errors when inputs are out of bounds.""" + checks = ((0 * u.m, 1 * u.m), (1 * u.m, 0 * u.m)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.headloss_weir(*i).magnitude, base) + self.assertRaises(ValueError, pc.flow_weir_rect, *i) - def test_flow_rect_weir(self): - """flow_rect_weir should return known value for known inputs.""" - self.assertAlmostEqual(pc.flow_rect_weir(2, 1).magnitude, 5.261, places=3) - def test_flow_rect_weir_range(self): - """flow_rect_weir should raise errors when inputs are out of bounds.""" - checks = ((0, 1), (1, 0)) +class PorousMediaFuncsTest(QuantityTest): + def test_headloss_kozeny(self): + """headloss_kozeny should return known value for known input.""" + self.assertAlmostEqualQuantity(pc.headloss_kozeny(1 * u.m, 1.4 * u.m, 0.5 * u.m/u.s, 0.625, 0.8 * u.m**2/u.s), + 2.1576362645214617 * u.m) + + def test_headloss_kozeny_range(self): + """headloss_kozeny should raise errors when inputs are out of bounds.""" + checks = ((0 * u.m, 1 * u.m, 1 * u.m/u.s, 1, 1 * u.m**2/u.s), + (1 * u.m, 0 * u.m, 1 * u.m/u.s, 1, 1 * u.m**2/u.s), + (1 * u.m, 1 * u.m, 0 * u.m/u.s, 1, 1 * u.m**2/u.s), + (1 * u.m, 1 * u.m, 1 * u.m/u.s, 1, 0 * u.m**2/u.s), + (1 * u.m, 1 * u.m, 1 * u.m/u.s, -1, 1 * u.m**2/u.s), + (1 * u.m, 1 * u.m, 1 * u.m/u.s, 2, 1 * u.m**2/u.s)) for i in checks: with self.subTest(i=i): - self.assertRaises(ValueError, pc.flow_rect_weir, *i) - pc.flow_rect_weir(1, 1) + self.assertRaises(ValueError, pc.headloss_kozeny, *i) - def test_flow_rect_weir_units(self): - """flow_rect_weir should handle units correctly.""" - base = pc.flow_rect_weir(2, 1).magnitude - checks = ([2 * u.m, 1 * u.m], - [200 * u.cm, 1], - [2, 100 * u.cm]) + def test_re_ergun(self): + self.assertAlmostEqualQuantity(pc.re_ergun(0.1 * u.m/u.s, 10**-3 * u.m, 298 * u.degK, 0.2), + 139.49692604 * u.dimensionless) + + def test_re_ergun_range(self): + checks = ((0 * u.m/u.s, 1 * u.m, 1 * u.degK, 1), + (1 * u.m/u.s, 0 * u.m, 1 * u.degK, 1), + (1 * u.m/u.s, 1 * u.m, 0 * u.degK, 1 * u.dimensionless), + (1 * u.m/u.s, 1 * u.m, 1 * u.degK, -1 * u.dimensionless)) for i in checks: with self.subTest(i=i): - self.assertAlmostEqual(pc.flow_rect_weir(*i).magnitude, base) + self.assertRaises(ValueError, pc.re_ergun, *i) + + def test_fric_ergun(self): + self.assertAlmostEqualQuantity(pc.fric_ergun(0.1 * u.m/u.s, 10**-3 * u.m, 298 * u.degK, 0.2), + 5.6505850237 * u.dimensionless) + + def test_headloss_ergun(self): + self.assertAlmostEqualQuantity(pc.headloss_ergun(0.1 * u.m/u.s, 10**-3 * u.m, 298 * u.degK, 0.2, 4 * u.m), + 1152.39863230 * u.m) + def test_g_cs_ergun(self): + self.assertAlmostEqualQuantity(pc.g_cs_ergun(0.1 * u.m/u.s, 10**-3 * u.m, 298 * u.degK, 0.2), + 39704.892422*u.Hz, 5) -class MiscPhysFuncsTest(unittest.TestCase): + +class MiscPhysFuncsTest(QuantityTest): """Test the miscellaneous physchem functions.""" def test_height_water_critical(self): """height_water_critical should return known value for known inputs.""" - self.assertAlmostEqual(pc.height_water_critical(0.006, 1.2).magnitude, - 0.013660704939951886) + self.assertAlmostEqualQuantity(pc.height_water_critical(0.006 * u.m**3/u.s, 1.2 * u.m), + 0.013660704939951886 * u.m) def test_height_water_critical_range(self): """height_water_critical should raise errors when inputs are out of bounds.""" - checks = ((0, 1), (1, 0)) + checks = ((0 * u.m**3/u.s, 1 * u.m), (1 * u.m**3/u.s, 0 * u.m)) for i in checks: with self.subTest(i=i): self.assertRaises(ValueError, pc.height_water_critical, *i) - pc.height_water_critical(1, 1) - - def test_height_water_critical_units(self): - """height_water_critical should handle units correctly.""" - base = pc.height_water_critical(0.006, 1.2).magnitude - checks = ([0.006 * u.m**3/u.s, 1.2 * u.m], - [6 * u.L/u.s, 1.2], - [0.006, 120 * u.cm]) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.height_water_critical(*i).magnitude, base) def test_vel_horizontal(self): """vel_horizontal should return known value for known inputs.""" - self.assertAlmostEqual(pc.vel_horizontal(0.03).magnitude, 0.5424016039799292) + self.assertAlmostEqualQuantity(pc.vel_horizontal(0.03 * u.m), 0.5424016039799292 * u.m/u.s) def test_vel_horizontal_range(self): """vel_horizontzal should raise an errors when input is <= 0.""" - self.assertRaises(ValueError, pc.vel_horizontal, 0) - - def test_vel_horizontal_units(self): - """vel_horizontal should handle units correctly.""" - base = pc.vel_horizontal(0.03).magnitude - checks = (0.03 * u.m, 3 * u.cm) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.vel_horizontal(i).magnitude, base) - - def test_headloss_kozeny(self): - """headloss_kozeny should return known value for known input.""" - self.assertAlmostEqual(pc.headloss_kozeny(1, 1.4, 0.5, 0.625, 0.8).magnitude, - 2.1576362645214617) - - def test_headloss_kozeny_range(self): - """headloss_kozeny should raise errors when inputs are out of bounds.""" - checks = ((0, 1, 1, 1, 1), (1, 0, 1, 1, 1), (1, 1, 0, 1, 1), - (1, 1, 1, 1, 0), (1, 1, 1, -1, 1), (1, 1, 1, 2, 1)) - for i in checks: - with self.subTest(i=i): - self.assertRaises(ValueError, pc.headloss_kozeny, *i) - pc.headloss_kozeny(1, 1, 1, 1, 1) - - def test_headloss_kozeny_units(self): - """headloss_kozeny should handle units correctly.""" - base = pc.headloss_kozeny(1, 1.4, 0.5, 0.625, 0.8).magnitude - checks = ([1 * u.m, 1.4 * u.m, 0.5 * u.m/u.s, - 0.625 * u.m, 0.8 * u.m**2/u.s], - [100 * u.cm, 1.4, 0.5, 0.625, 0.8], - [1, 0.0014 * u.km, 0.5, 0.625, 0.8], - [1, 1.4, 30 * u.m/u.min, 0.625, 0.8], - [1, 1.4, 0.5, 625 * u.mm, 0.8], - [1, 1.4, 0.5, 0.625, 8000 * u.cm**2/u.s]) - for i in checks: - with self.subTest(i=i): - self.assertAlmostEqual(pc.headloss_kozeny(*i).magnitude, base) + self.assertRaises(ValueError, pc.vel_horizontal, 0 * u.m) def test_pipe_ID(self): """pipe_ID should return known value for known input""" - self.assertAlmostEqual(pc.pipe_ID(0.006, 1.2).magnitude, 0.039682379412712764) + self.assertAlmostEqualQuantity(pc.pipe_ID(0.006 * u.m**3/u.s, 1.2 * u.m), + 0.039682379412712764 * u.m) if __name__ == "__main__": diff --git a/tests/core/test_utility.py b/tests/core/test_utility.py index c4412acb..9c66628e 100644 --- a/tests/core/test_utility.py +++ b/tests/core/test_utility.py @@ -1,8 +1,25 @@ import unittest import aguaclara.core.utility as ut from aguaclara.core.units import u +import numpy as np -class UtilityTest(unittest.TestCase): + +class QuantityTest(unittest.TestCase): + def assertAlmostEqualQuantity(self, first, second, places=7): + self.assertAlmostEqual(first.magnitude, second.magnitude, places) + self.assertEqual(first.units, second.units, places) + + def assertAlmostEqualArray(self, first, second, places=7): + self.assertEqual(type(first), type(second)) + for i in range(len(first)): + self.assertAlmostEqual(first[i], second[i], places) + + def assertAlmostEqualArrayQuantity(self, first, second, places=7): + self.assertEqual(first.units, second.units, places) + self.assertAlmostEqualArray(first.magnitude, second.magnitude) + + +class UtilityTest(QuantityTest): def test_round_sig_figs(self): self.assertAlmostEqual(ut.round_sig_figs(123456.789, 8), 123456.79) @@ -10,12 +27,78 @@ def test_round_sig_figs(self): self.assertAlmostEqual(ut.round_sig_figs(-456.789 * u.L/u.s, 4), -456.8 * u.L/u.s) self.assertAlmostEqual(ut.round_sig_figs(0, 4), 0) self.assertAlmostEqual(ut.round_sig_figs(0 * u.m, 4), 0 * u.m) + def test_max(self): self.assertEqual(ut.max(2 * u.m, 4 * u.m),4 * u.m) self.assertEqual(ut.max(3 * u.m, 1 * u.m, 6 * u.m, 10 * u.m, 1.5 * u.m), 10 * u.m) - self.assertEqual(ut.max(2 * u.m),2 * u.m) + self.assertEqual(ut.max(2 * u.m), 2 * u.m) def test_min(self): self.assertEqual(ut.min(2 * u.m, 4 * u.m), 2 * u.m) self.assertEqual(ut.min(3 * u.m, 1 * u.m, 6 * u.m, 10 * u.m, 1.5 * u.m), 1 * u.m) - self.assertEqual(ut.min(2 * u.m), 2 * u.m) \ No newline at end of file + self.assertEqual(ut.min(2 * u.m), 2 * u.m) + + def test_list_handler_with_units(self): + @ut.list_handler() + def density_air(Pressure, MolarMass, Temperature): + """Return the density of air at the given pressure, molar mass, and + temperature. + + :param Pressure: pressure of air in the system + :type Pressure: u.pascal + :param MolarMass: molar mass of air in the system + :type MolarMass: u.gram/u.mol + :param Temperature: Temperature of air in the system + :type Temperature: u.degK + + :return: density of air in the system + :rtype: u.kg/u.m**3 + """ + return (Pressure * MolarMass / (u.R * Temperature)).to(u.kg/u.m**3) + + answer = 1.29320776*u.kg/u.m**3 + self.assertAlmostEqualQuantity(density_air(1*u.atm, 28.97*u.g/u.mol, 273*u.K), answer) + + answer = 1.29320776*u.kg/u.m**3 + self.assertAlmostEqualQuantity(density_air(MolarMass=28.97*u.g/u.mol, Temperature=273*u.K, Pressure=1*u.atm), answer) + + answer = np.array([1.29320776, 2.58641552, 3.87962328, 12.93207761])*u.kg/u.m**3 + self.assertAlmostEqualArrayQuantity(density_air([1, 2, 3, 10]*u.atm, 28.97*u.g/u.mol, 273*u.K), answer) + + answer = np.array([1.29320776, 2.58641552, 3.87962328, 12.93207761])*u.kg/u.m**3 + self.assertAlmostEqualArrayQuantity(density_air(MolarMass=28.97*u.g/u.mol, Temperature=273*u.K, Pressure=[1, 2, 3, 10]*u.atm), answer) + + answer = np.array([[1.29320776, 1.20526784, 1.07134919, 0.89279099], + [2.58641552, 2.41053569, 2.14269839, 1.78558199], + [3.87962328, 3.61580354, 3.21404759, 2.67837299], + [12.93207761, 12.05267848, 10.71349198, 8.92790998]])*u.kg/u.m**3 + output = density_air([1, 2, 3, 10]*u.atm, [28.97, 27, 24, 20]*u.g/u.mol, 273*u.K) + self.assertEqual(output.units, answer.units) + for i in range(len(output.magnitude)): + self.assertAlmostEqualArray(output.magnitude[i], answer.magnitude[i]) + + def test_list_handler_dimensionless(self): + @ut.list_handler() + def re_pipe(FlowRate, Diam, Nu): + """Return the Reynolds number of flow through a pipe. + + :param FlowRate: flow rate through pipe + :type FlowRate: u.m**3/u.s + :param Diam: diameter of pipe + :type Diam: u.m + :param Nu: kinematic viscosity of fluid + :type Nu: u.m**2/u.s + + :return: Reynolds number of flow through pipe + :rtype: u.dimensionless + """ + return ((4 * FlowRate) / (np.pi * Diam * Nu)) + + answer = 254.64790894703253 * u.dimensionless + self.assertAlmostEqualQuantity(re_pipe(12 * u.m**3/u.s, 6 * u.m, 0.01 * u.m**2/u.s), answer) + + answer = np.array([254.647908947, 218.26963624, 190.98593171]) * u.dimensionless + self.assertAlmostEqualArrayQuantity(re_pipe(12 * u.m**3/u.s, [6, 7, 8] * u.m, 0.01 * u.m**2/u.s), answer) + + answer = np.array([254.647908947, 218.26963624, 190.98593171]) + self.assertAlmostEqualArray(re_pipe(12, [6, 7, 8], 0.01), answer) diff --git a/tests/design/test_ent.py b/tests/design/test_ent.py index acf52a1f..65b345d1 100644 --- a/tests/design/test_ent.py +++ b/tests/design/test_ent.py @@ -19,5 +19,8 @@ ]) def test_ent(actual, expected): - assert actual == expected - + if (type(actual) == u.Quantity and type(expected) == u.Quantity): + assert actual.units == expected.units + assert actual.magnitude == pytest.approx(expected.magnitude) + else: + assert actual == pytest.approx(expected) diff --git a/tests/design/test_ent_floc.py b/tests/design/test_ent_floc.py index acf51e96..a7e7fbe5 100644 --- a/tests/design/test_ent_floc.py +++ b/tests/design/test_ent_floc.py @@ -20,7 +20,7 @@ (etf_20.floc.chan_n, 2), (etf_60.floc.chan_n, 2), - + (etf_20.floc.chan_w_min_gt, 32.02195253008654 * u.cm), (etf_60.floc.chan_w_min_gt, 96.24786648922903 * u.cm), @@ -31,4 +31,8 @@ (etf_20.ent.plate_n, 20), ]) def test_etf(actual, expected): - assert actual == expected \ No newline at end of file + if (type(actual) == u.Quantity and type(expected) == u.Quantity): + assert actual.units == expected.units + assert actual.magnitude == pytest.approx(expected.magnitude) + else: + assert actual == pytest.approx(expected) diff --git a/tests/design/test_lfom.py b/tests/design/test_lfom.py index c1f39d6f..3e0646cc 100644 --- a/tests/design/test_lfom.py +++ b/tests/design/test_lfom.py @@ -17,7 +17,7 @@ (lfom_20.row_b.to(u.m), 0.03333333333333333 * u.m), (lfom_60.row_b.to(u.m), 0.05 * u.m), # 5 - + (lfom_20.vel_critical.to(u.m/u.s), 0.8405802802312778 * u.m/u.s), (lfom_60.vel_critical.to(u.m/u.s), 0.8405802802312778 * u.m/u.s), @@ -26,13 +26,13 @@ (lfom_20.pipe_nd.to(u.inch), 10.0 * u.inch), # 10 (lfom_60.pipe_nd.to(u.inch), 16.0 * u.inch), - + (lfom_20.top_row_orifice_a.to(u.m**2), 0.0017763243361009463 * u.m ** 2), (lfom_60.top_row_orifice_a.to(u.m**2), 0.00818156664907796 * u.m ** 2), (lfom_20.orifice_d_max.to(u.m), 0.047557190718114956 * u.m), (lfom_60.orifice_d_max.to(u.m), 0.10206416704942245 * u.m), # 15 - + (lfom_20.orifice_d.to(u.m), 0.03175 * u.m), (lfom_60.orifice_d.to(u.m), 0.044449999999999996 * u.m), @@ -47,12 +47,16 @@ (lfom_20.q_submerged(3, [4, 3, 2]), 5.939085475350429 * u.L / u.s), (lfom_60.q_submerged(3, [4, 3, 2]), 14.34566338987966 * u.L / u.s), # 25 - + (lfom_20.orifice_n_per_row[0], 12), (lfom_60.orifice_n_per_row[0], 21) ]) def test_lfom(actual, expected): - assert actual == expected + if (type(actual) == u.Quantity and type(expected) == u.Quantity): + assert actual.units == expected.units + assert actual.magnitude == pytest.approx(expected.magnitude) + else: + assert actual == pytest.approx(expected) def test_error_per_row(): assert np.abs(np.average(lfom_20.error_per_row) + 0.22311742777836815) / \ diff --git a/tests/design/test_pipeline.py b/tests/design/test_pipeline.py index b415c44d..0304a082 100644 --- a/tests/design/test_pipeline.py +++ b/tests/design/test_pipeline.py @@ -51,4 +51,8 @@ (pipeline_fp.flow_pipeline(40 * u.cm), 31.45057786475188 * u.L / u.s), ]) def test_pipeline(actual, expected): - assert actual == expected \ No newline at end of file + if (type(actual) == u.Quantity and type(expected) == u.Quantity): + assert actual.units == expected.units + assert actual.magnitude == pytest.approx(expected.magnitude) + else: + assert actual == pytest.approx(expected) diff --git a/tests/design/test_sed.py b/tests/design/test_sed.py index 41eb38c2..69e81b3d 100644 --- a/tests/design/test_sed.py +++ b/tests/design/test_sed.py @@ -13,5 +13,3 @@ ]) def test_sed(actual, expected): assert actual == expected - - diff --git a/tests/design/test_sed_chan.py b/tests/design/test_sed_chan.py index 362c685e..4c71083f 100644 --- a/tests/design/test_sed_chan.py +++ b/tests/design/test_sed_chan.py @@ -31,7 +31,7 @@ (sed_chan_20.inlet_weir_h, 98.90522757137602 * u.cm), (sed_chan_60.inlet_weir_h, 100.73729553143386 * u.cm), - + (sed_chan_20.inlet_w_post_weir, 30. * u.cm), (sed_chan_60.inlet_w_post_weir, 30. * u.cm), @@ -58,7 +58,7 @@ (sed_chan_20.outlet_pipe.l, 3.7119999999999997 * u.m), (sed_chan_60.outlet_pipe.l, 3.7119999999999997 * u.m), - + (sed_chan_20.outlet_pipe_q_max, 7.8571 * u.L / u.s), (sed_chan_60.outlet_pipe_q_max, 7.8571 * u.L / u.s), @@ -95,5 +95,10 @@ (sed_chan_20.inlet_slope_l, 4.1052 * u.m), (sed_chan_60.inlet_slope_l, 4.1052 * u.m), ]) + def test_sed_chan(actual, expected): - assert actual == expected \ No newline at end of file + if (type(actual) == u.Quantity and type(expected) == u.Quantity): + assert actual.units == expected.units + assert actual.magnitude == pytest.approx(expected.magnitude) + else: + assert actual == pytest.approx(expected) diff --git a/tests/design/test_sed_tank.py b/tests/design/test_sed_tank.py index 825d5d3d..f69682b8 100644 --- a/tests/design/test_sed_tank.py +++ b/tests/design/test_sed_tank.py @@ -60,6 +60,8 @@ (sed_tank_60.outlet_man_orifice_spacing, 0.1048062404520667 * u.m), ]) def test_sed_tank(actual, expected): - assert actual == expected - - + if (type(actual) == u.Quantity and type(expected) == u.Quantity): + assert actual.units == expected.units + assert actual.magnitude == pytest.approx(expected.magnitude) + else: + assert actual == pytest.approx(expected) diff --git a/tests/research/test_floc_model.py b/tests/research/test_floc_model.py index cde2e30e..e8d7aadf 100644 --- a/tests/research/test_floc_model.py +++ b/tests/research/test_floc_model.py @@ -218,10 +218,10 @@ def test_diam_vel(self): 0.00078540208*u.m, 10) def test_diam_floc_max(self): - self.assertRaisesRegexp(FutureWarning, "diam_floc_max is deprecated and will be removed after Dec 1 2019. The underlying equation is under suspicion.") + self.assertRaisesRegex(FutureWarning, "diam_floc_max is deprecated and will be removed after Dec 1 2019. The underlying equation is under suspicion.") def test_ener_dis_diam_floc(self): - self.assertRaisesRegexp(FutureWarning, "ener_dis_diam_floc is deprecated and will be removed after Dec 1 2019. The underlying equation is under suspicion.") + self.assertRaisesRegex(FutureWarning, "ener_dis_diam_floc is deprecated and will be removed after Dec 1 2019. The underlying equation is under suspicion.") class TestVelocityGradient(QuantityTest):