From 1adb9fcdfeae2a62c60a26740e53b82ab8fbceef Mon Sep 17 00:00:00 2001 From: smolvik1 Date: Wed, 25 Oct 2023 21:49:28 +0200 Subject: [PATCH 01/12] chore: improved readability --- setup.py | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/setup.py b/setup.py index 2aff683..75d548d 100644 --- a/setup.py +++ b/setup.py @@ -1,21 +1,31 @@ import pathlib from setuptools import setup -exec(open('pysand/version.py').read()) -HERE = pathlib.Path(__file__).parent -README = (HERE / "README.md").read_text() +def get_version(): + with open('pysand/version.py') as version_file: + namespace = {} + exec(version_file.read(), namespace) + return namespace['__version__'] -setup(name='pysand', - version=__version__, - description='Sand management related calculations', - long_description=README, - long_description_content_type="text/markdown", - author='Equinor ASA', - author_email='stimo@equinor.com', - license='GNU GPL', - url='https://github.com/equinor/pysand', - classifiers=["Programming Language :: Python :: 3"], - packages=['pysand'], - install_requires=['pandas', 'numpy>=1.17', 'scipy'] -) \ No newline at end of file +VERSION = get_version() + +# Read the README.md file +home_dir = pathlib.Path(__file__).parent +README = (home_dir / "README.md").read_text() + +if __name__ == "__main__": + setup( + name='pysand', + version=VERSION, + description='Sand management related calculations', + long_description=README, + long_description_content_type="text/markdown", + author='Equinor ASA', + author_email='stimo@equinor.com', + license='GNU GPL', + url='https://github.com/equinor/pysand', + classifiers=["Programming Language :: Python :: 3"], + packages=['pysand'], + install_requires=['pandas', 'numpy>=1.17', 'scipy'] + ) \ No newline at end of file From 5e5dcf11bbdcff1bd907ed8844c77aa08345f948 Mon Sep 17 00:00:00 2001 From: smolvik1 Date: Wed, 25 Oct 2023 21:52:39 +0200 Subject: [PATCH 02/12] feat: added type hinting --- pysand/asd.py | 8 +- pysand/choke.py | 4 +- pysand/erosion.py | 150 +++++++++++++++++++++----------------- pysand/fluidproperties.py | 11 +-- pysand/probe.py | 2 +- pysand/transport.py | 4 +- 6 files changed, 98 insertions(+), 81 deletions(-) diff --git a/pysand/asd.py b/pysand/asd.py index 226503d..adeb4c1 100644 --- a/pysand/asd.py +++ b/pysand/asd.py @@ -5,7 +5,7 @@ logger = logging.getLogger(__name__) -def validate_asd(**kwargs): +def validate_asd(**kwargs: float) -> bool: """ Validation of all input parameters that go into std_step_clampon, std_step_emerson and sand_rate functions; """ @@ -19,7 +19,7 @@ def validate_asd(**kwargs): logger.warning('The model has got negative value(s) of {} and returned nan.'.format(i)) return True -def std_step_clampon(v_m, GLR): +def std_step_clampon(v_m: float, GLR: float) -> float: ''' Standard step calculation for Clampon ASD's :param v_m: fluid mix velocity at ASD [m/s] @@ -47,7 +47,7 @@ def std_step_clampon(v_m, GLR): return step -def std_step_emerson(v_m, GOR): +def std_step_emerson(v_m: float, GOR: float) -> float: ''' Standard step calculation for Emerson ASD's :param v_m: fluid mix velocity at ASD [m/s] @@ -70,7 +70,7 @@ def std_step_emerson(v_m, GOR): return step -def sand_rate(raw, zero, step, exp=1): +def sand_rate(raw: float, zero: float, step: float, exp: float=1) -> float: ''' ASD sand rate calculation :param raw: raw value from ASD diff --git a/pysand/choke.py b/pysand/choke.py index a3afe77..d425724 100644 --- a/pysand/choke.py +++ b/pysand/choke.py @@ -1,4 +1,4 @@ -def critical_velocity(p1, p2, rho_1, rho_2): +def critical_velocity(p1: float, p2: float, rho_1: float, rho_2: float) -> float: """ Throttling velocity in the choke DNVGL RP-O501, August 2015 - chapter 4.12.3 @@ -12,7 +12,7 @@ def critical_velocity(p1, p2, rho_1, rho_2): return v_c -def min_choke(Qs, v_c): +def min_choke(Qs: float, v_c: float) -> float: """ Recommended minimum choke opening for plug/cage and cage/sleeve types of chokes DNVGL RP-O501, August 2015 - chapter 4.12.3 diff --git a/pysand/erosion.py b/pysand/erosion.py index 68eba6c..7f09f9b 100644 --- a/pysand/erosion.py +++ b/pysand/erosion.py @@ -1,11 +1,12 @@ import numpy as np import logging import pysand.exceptions as exc +from typing import TypedDict logger = logging.getLogger(__name__) # Models from DNVGL RP-O501, equation references in parenthesis -def validate_inputs(**kwargs): +def validate_inputs(**kwargs) -> bool: """ Validation of all input parameters that go into erosion models; Besides validating for illegal data input, model parameters are limited within RP-O501 boundaries: @@ -26,7 +27,7 @@ def validate_inputs(**kwargs): Gap cage and choke body (gap) --- 0 --- Rc --- Height of gallery (H) --- 0 --- --- ------------------------------------------------------------------------- - Geometry factor can only be 1, 2, 3 or 4 + Geometry factor can only be 1 or higher """ if not 'rho_p' in kwargs: @@ -79,8 +80,8 @@ def validate_inputs(**kwargs): if kwargs['d_p'] < 0: exc.FunctionInputFail('Particle diameter cannot be negative') if 'GF' in kwargs: - if kwargs['GF'] not in [1, 2, 3, 4]: - logger.warning('Geometry factor, GF, can only be 1, 2, 3 or 4') + if kwargs['GF'] < 1: + logger.warning('Geometry factor, GF, can only be 1 or higher') # bend/choke gallery if 'R' in kwargs: @@ -112,7 +113,9 @@ def validate_inputs(**kwargs): logger.warning('Particle diameter, d_p, is higher than CFD-study boundary (0.6 mm).') -def bend(v_m, rho_m, mu_m, R, GF, D, d_p, material='duplex', rho_p=2650, crushed=False): +def bend(v_m: float, rho_m: float, mu_m: float, + R: float, GF: float, D: float, d_p: float, + material: str='duplex', rho_p: float=2650, crushed: bool=False) -> float: ''' Particle erosion in bends, model reference to DNVGL RP-O501, August 2015 :param v_m: Mix velocity [m/s] @@ -136,7 +139,7 @@ def bend(v_m, rho_m, mu_m, R, GF, D, d_p, material='duplex', rho_p=2650, crushed # Constants: C1 = 2.5 # Model geometry factor for pipe bends - rho_t, K, n, ad = material_properties(material) + rho_t, K, n, ad = get_material_properties(material) # Calculations a_rad = np.arctan(1 / (2 * R) ** (0.5)) # Pipe bend impact angle [radians] (4.28) @@ -161,7 +164,9 @@ def bend(v_m, rho_m, mu_m, R, GF, D, d_p, material='duplex', rho_p=2650, crushed return E_rel -def tee(v_m, rho_m, mu_m, GF, D, d_p, material='duplex', rho_p=2650, crushed=False): +def tee(v_m: float, rho_m: float, mu_m: float, GF: float, + D: float, d_p: float, material: str='duplex', + rho_p: float=2650, crushed: bool=False) -> float: ''' Particle erosion in blinded tees, model reference to DNVGL RP-O501, August 2015 :param v_m: Mix velocity [m/s] @@ -181,7 +186,7 @@ def tee(v_m, rho_m, mu_m, GF, D, d_p, material='duplex', rho_p=2650, crushed=Fal if validate_inputs(**kwargs): return np.nan - rho_t, K, n, _ = material_properties(material) + rho_t, K, n, _ = get_material_properties(material) # Calculations gamma = d_p / 1000 / D # Ratio of particle diameter to geometrical diameter (4.37) @@ -211,7 +216,7 @@ def tee(v_m, rho_m, mu_m, GF, D, d_p, material='duplex', rho_p=2650, crushed=Fal return E_rel -def straight_pipe(v_m, D, crushed=False): +def straight_pipe(v_m: float, D: float, crushed: bool=False) -> float: ''' Particle erosion in smooth and straight pipes, model reference to DNVGL RP-O501, August 2015 :param v_m: Mix velocity [m/s] @@ -232,7 +237,9 @@ def straight_pipe(v_m, D, crushed=False): return E_rel -def welded_joint(v_m, rho_m, D, d_p, h, alpha=60, location='downstream', material='duplex', crushed=False): +def welded_joint(v_m: float, rho_m: float, D: float, d_p: float, + h: float, alpha: float=60, location: str='downstream', + material: str='duplex', crushed: bool=False) -> float: ''' Particle erosion in welded joints, model reference to DNVGL RP-O501, August 2015 :param v_m: Mix velocity [m/s] @@ -256,7 +263,7 @@ def welded_joint(v_m, rho_m, D, d_p, h, alpha=60, location='downstream', materia if (alpha < 0) or (alpha > 90): logger.warning('Particle impact angle [degrees], alpha, is outside RP-O501 model boundaries (0-90 deg).') - rho_t, K, n, ad = material_properties(material) + rho_t, K, n, ad = get_material_properties(material) A_pipe = np.pi * D**2 / 4 a_rad = np.deg2rad(alpha) @@ -280,7 +287,9 @@ def welded_joint(v_m, rho_m, D, d_p, h, alpha=60, location='downstream', materia raise exc.FunctionInputFail('Location must be either downstream or upstream. {} is passed.'.format(location)) -def manifold(v_m, rho_m, mu_m, GF, D, d_p, Dm, rho_p=2650, material='duplex', crushed=False): +def manifold(v_m: float, rho_m: float, mu_m: float, GF: float, + D: float, d_p: float, Dm: float, rho_p: float=2650, + material: str='duplex', crushed: bool=False) -> float: ''' Manifold model, pending inclusion in DNVGL RP-O501. Velocity and fluid properties in branch line. :param v_m: Mix velocity [m/s] @@ -304,7 +313,9 @@ def manifold(v_m, rho_m, mu_m, GF, D, d_p, Dm, rho_p=2650, material='duplex', cr return bend(v_m, rho_m, mu_m, R, GF, D, d_p, rho_p=rho_p, material=material, crushed=crushed) # Relative surface thickness loss [mm/t] -def reducer(v_m, rho_m, D1, D2, d_p, GF=2, alpha=60, material='duplex', crushed=False): +def reducer(v_m: float, rho_m: float, D1: float, D2: float, + d_p: float, GF: float=2, alpha: float=60, + material: str='duplex', crushed: bool=False) -> float: ''' Particle erosion in reducers, model reference to DNVGL RP-O501, August 2015 :param v_m: Upstream mix velocity [m/s] @@ -327,7 +338,7 @@ def reducer(v_m, rho_m, D1, D2, d_p, GF=2, alpha=60, material='duplex', crushed= if (alpha < 10) or (alpha > 80): logger.warning('Particle impact angle [degrees], alpha, is outside RP-O501 model boundaries (10-80 deg).') - rho_t, K, n, ad = material_properties(material) + rho_t, K, n, ad = get_material_properties(material) a_rad = np.deg2rad(alpha) At = np.pi/(4 * np.sin(a_rad))*(D1**2-D2**2) # Characteristic particle impact area [m2] (4.50) @@ -344,7 +355,8 @@ def reducer(v_m, rho_m, D1, D2, d_p, GF=2, alpha=60, material='duplex', crushed= else: return E_rel -def probes(v_m, rho_m, D, d_p, alpha=60, material='duplex', crushed=False): +def probes(v_m: float, rho_m: float, D: float, d_p: float, + alpha: float=60, material: str='duplex', crushed: bool=False) -> float: ''' Particle erosion for intrusive erosion probes, model reference to DNVGL RP-O501, August 2015 :param v_m: Upstream mix velocity [m/s] @@ -365,7 +377,7 @@ def probes(v_m, rho_m, D, d_p, alpha=60, material='duplex', crushed=False): if (alpha < 10) or (alpha > 90): logger.warning('Particle impact angle [degrees], alpha, is outside RP-O501 model boundaries (10-90 deg).') - rho_t, K, n, ad = material_properties(material) + rho_t, K, n, ad = get_material_properties(material) a_rad = np.deg2rad(alpha) At = np.pi / 4 * D**2 * 1 / np.sin(a_rad) # Eqv particle impact area for homogeneously distributed particles (4.58) @@ -381,7 +393,8 @@ def probes(v_m, rho_m, D, d_p, alpha=60, material='duplex', crushed=False): return E_rel -def flexible(v_m, rho_m, mu_m, mbr, D, d_p, material='duplex', crushed=False): +def flexible(v_m: float, rho_m: float, mu_m: float, mbr: float, + D: float, d_p: float, material: str='duplex', crushed: bool=False) -> float: """ Particle erosion for flexible pipes with interlock carcass, model reference to DNVGL RP-O501, August 2015 :param v_m: Mix velocity [m/s] @@ -404,7 +417,8 @@ def flexible(v_m, rho_m, mu_m, mbr, D, d_p, material='duplex', crushed=False): return E_rel -def choke_gallery(v_m, rho_m, mu_m, GF, D, d_p, R_c, gap, H, material='cr_37_tungsten', crushed=False): +def choke_gallery(v_m: float, rho_m: float, mu_m: float, GF: float, D: float, d_p: float, + R_c: float, gap: float, H: float, material: str='cr_37_tungsten', crushed: bool=False) -> float: """ Particle erosion for angle style choke gallery, model reference to DNVGL RP-O501, August 2015 :param v_m: Upstream mix velocity [m/s] @@ -440,7 +454,7 @@ def choke_gallery(v_m, rho_m, mu_m, GF, D, d_p, R_c, gap, H, material='cr_37_tun return E_rel -def nozzlevalve_wall(v_m, d_p, GF, At, material='duplex', crushed=False): +def nozzlevalve_wall(v_m: float, d_p: float, GF: float, At: float, material: str='duplex', crushed: bool=False) -> float: """ Particle valve wall erosion for non-slam nozzle type check-valve. Based on DNVGL CFD-study of Johan Sverdrup Phase 1 check-valves (13.01.2020) Report No: 2019-1237 Rev.1, Document No: 547341 @@ -458,7 +472,7 @@ def nozzlevalve_wall(v_m, d_p, GF, At, material='duplex', crushed=False): return np.nan C1 = 8.33 * d_p**3 - 29.2 * d_p**2 + 22.8 * d_p + 1 # Model geometry factor - rho_t, K, n, _ = material_properties(material) # Material properties + rho_t, K, n, _ = get_material_properties(material) # Material properties E_rel = K * v_m ** n / (2 * rho_t * At) * C1 * GF * 10 ** 6 # Relative surface thickness loss [mm/t] @@ -468,56 +482,64 @@ def nozzlevalve_wall(v_m, d_p, GF, At, material='duplex', crushed=False): return E_rel - -def material_properties(material): +class MaterialInfo(TypedDict): + rho_t: float + K: float + n: float + angle_dependency: str + name: str + + +material_dict: dict[str: MaterialInfo] = { + 'carbon_steel': {'rho_t': 7800, 'K': 2e-9, 'n': 2.6, 'angle_dependency': 'ductile', 'name': 'Carbon steel'}, + 'duplex': {'rho_t': 7850, 'K': 2e-9, 'n': 2.6, 'angle_dependency': 'ductile', 'name': 'Duplex'}, + 'ss316': {'rho_t': 8000, 'K': 2e-9, 'n': 2.6, 'angle_dependency': 'ductile', 'name': 'SS316'}, + 'inconel': {'rho_t': 8440, 'K': 2e-9, 'n': 2.6, 'angle_dependency': 'ductile', 'name': 'Inconel'}, + 'grp_epoxy': {'rho_t': 1800, 'K': 3e-10, 'n': 3.6, 'angle_dependency': 'ductile', 'name': 'GRP Epoxy'}, + 'grp_vinyl_ester': {'rho_t': 1800, 'K': 6e-10, 'n': 3.6, 'angle_dependency': 'ductile', 'name': 'GRP Vinyl Ester'}, + 'hdpe': {'rho_t': 1150, 'K': 3.5e-9, 'n': 2.9, 'angle_dependency': 'ductile', 'name': 'HDPE'}, + 'aluminium': {'rho_t': 2700, 'K': 5.8e-9, 'n': 2.3, 'angle_dependency': 'ductile', 'name': 'Aluminium'}, + 'dc_05_tungsten': {'rho_t': 15250, 'K': 1.1e-10, 'n': 2.3, 'angle_dependency': 'brittle', 'name': 'DC-05 Tungsten'}, + 'cs_10_tungsten': {'rho_t': 14800, 'K': 3.2e-10, 'n': 2.2, 'angle_dependency': 'brittle', 'name': 'CS-10 Tungsten'}, + 'cr_37_tungsten': {'rho_t': 14600, 'K': 8.8e-11, 'n': 2.5, 'angle_dependency': 'brittle', 'name': 'CR-37 Tungsten'}, + '95_alu_oxide': {'rho_t': 3700, 'K': 6.8e-8, 'n': 2, 'angle_dependency': 'brittle', 'name': '95 Alu Oxide'}, + '99_alu_oxide': {'rho_t': 3700, 'K': 9.5e-7, 'n': 1.2, 'angle_dependency': 'brittle', 'name': '99 Alu Oxide'}, + 'psz_ceramic_zirconia': {'rho_t': 5700, 'K': 4.1e-9, 'n': 2.5, 'angle_dependency': 'brittle', 'name': 'PSZ Ceramic Zirconia'}, + 'ZrO2-Y3_ceramic_zirconia': {'rho_t': 6070, 'K': 4e-11, 'n': 2.7, 'angle_dependency': 'brittle', 'name': 'ZrO2-Y3 Ceramic Zirconia'}, + 'SiC_silicon_carbide': {'rho_t': 3100, 'K': 6.5e-9, 'n': 1.9, 'angle_dependency': 'brittle', 'name': 'Silicon Carbide'}, + 'Si3N4_silicon_nitride': {'rho_t': 3200, 'K': 2e-10, 'n': 2, 'angle_dependency': 'brittle', 'name': 'Silicon Nitride'}, + 'TiB2_titanium_diboride': {'rho_t': 4250, 'K': 9.3e-9, 'n': 1.9, 'angle_dependency': 'brittle', 'name': 'Titanium Diboride'}, + 'B4C_boron_carbide': {'rho_t': 2500, 'K': 3e-8, 'n': .9, 'angle_dependency': 'brittle', 'name': 'Boron Carbide'}, + 'SiSiC_ceramic_carbide': {'rho_t': 3100, 'K': 7.4e-11, 'n': 2.7, 'angle_dependency': 'brittle', 'name': 'Ceramic Carbide'}, +} + +def get_material_properties(material: str) -> tuple[float, float, float, str]: """ Function to deal with material properties, reference to table 3-1 in DNVGL RP-O501, August 2015 :param material: Material. For a full list of materials run: materials() :return: rho_t (material density), K (material constant), n (material exponent), angle_dependency, name """ - properties = { - 'carbon_steel': {'rho_t': 7800, 'K': 2e-9, 'n': 2.6, 'angle_dependency': 'ductile', 'name': 'Carbon steel'}, - 'duplex': {'rho_t': 7850, 'K': 2e-9, 'n': 2.6, 'angle_dependency': 'ductile', 'name': 'Duplex'}, - 'ss316': {'rho_t': 8000, 'K': 2e-9, 'n': 2.6, 'angle_dependency': 'ductile', 'name': 'SS316'}, - 'inconel': {'rho_t': 8440, 'K': 2e-9, 'n': 2.6, 'angle_dependency': 'ductile', 'name': 'Inconel'}, - 'grp_epoxy': {'rho_t': 1800, 'K': 3e-10, 'n': 3.6, 'angle_dependency': 'ductile', 'name': 'GRP Epoxy'}, - 'grp_vinyl_ester': {'rho_t': 1800, 'K': 6e-10, 'n': 3.6, 'angle_dependency': 'ductile', 'name': 'GRP Vinyl Ester'}, - 'hdpe': {'rho_t': 1150, 'K': 3.5e-9, 'n': 2.9, 'angle_dependency': 'ductile', 'name': 'HDPE'}, - 'aluminium': {'rho_t': 2700, 'K': 5.8e-9, 'n': 2.3, 'angle_dependency': 'ductile', 'name': 'Aluminium'}, - 'dc_05_tungsten': {'rho_t': 15250, 'K': 1.1e-10, 'n': 2.3, 'angle_dependency': 'brittle', 'name': 'DC-05 Tungsten'}, - 'cs_10_tungsten': {'rho_t': 14800, 'K': 3.2e-10, 'n': 2.2, 'angle_dependency': 'brittle', 'name': 'CS-10 Tungsten'}, - 'cr_37_tungsten': {'rho_t': 14600, 'K': 8.8e-11, 'n': 2.5, 'angle_dependency': 'brittle', 'name': 'CR-37 Tungsten'}, - '95_alu_oxide': {'rho_t': 3700, 'K': 6.8e-8, 'n': 2, 'angle_dependency': 'brittle', 'name': '95 Alu Oxide'}, - '99_alu_oxide': {'rho_t': 3700, 'K': 9.5e-7, 'n': 1.2, 'angle_dependency': 'brittle', 'name': '99 Alu Oxide'}, - 'psz_ceramic_zirconia': {'rho_t': 5700, 'K': 4.1e-9, 'n': 2.5, 'angle_dependency': 'brittle', 'name': 'PSZ Ceramic Zirconia'}, - 'ZrO2-Y3_ceramic_zirconia': {'rho_t': 6070, 'K': 4e-11, 'n': 2.7, 'angle_dependency': 'brittle', 'name': 'ZrO2-Y3 Ceramic Zirconia'}, - 'SiC_silicon_carbide': {'rho_t': 3100, 'K': 6.5e-9, 'n': 1.9, 'angle_dependency': 'brittle', 'name': 'Silicon Carbide'}, - 'Si3N4_silicon_nitride': {'rho_t': 3200, 'K': 2e-10, 'n': 2, 'angle_dependency': 'brittle', 'name': 'Silicon Nitride'}, - 'TiB2_titanium_diboride': {'rho_t': 4250, 'K': 9.3e-9, 'n': 1.9, 'angle_dependency': 'brittle', 'name': 'Titanium Diboride'}, - 'B4C_boron_carbide': {'rho_t': 2500, 'K': 3e-8, 'n': .9, 'angle_dependency': 'brittle', 'name': 'Boron Carbide'}, - 'SiSiC_ceramic_carbide': {'rho_t': 3100, 'K': 7.4e-11, 'n': 2.7, 'angle_dependency': 'brittle', 'name': 'Ceramic Carbide'}, - } - - if material == 'list': - return list(properties.keys()) - - if material == 'properties': - return properties - - if material not in properties: - raise exc.FunctionInputFail('The material {} is not defined. For a full list of materials run materials()' - .format(material)) - - rho_t = properties[material]['rho_t'] - K = properties[material]['K'] - n = properties[material]['n'] - angle_dependency = properties[material]['angle_dependency'] + if material not in material_dict: + raise exc.FunctionInputFail(f'The material {material} is not defined. For a full list of materials run get_materials()') + + rho_t = material_dict[material]['rho_t'] + K = material_dict[material]['K'] + n = material_dict[material]['n'] + angle_dependency = material_dict[material]['angle_dependency'] return rho_t, K, n, angle_dependency +def get_materials() -> list[str]: + """ + Function to return a list of all available materials + """ + return list(material_dict.keys()) + +def get_materials_data() -> dict[str, MaterialInfo]: + return material_dict -def F(a_rad, angle_dependency): +def F(a_rad: float, angle_dependency: str) -> float: """ Angle dependency function, reference to DNVGL RP-O501, August 2015 (3.3) :param a_rad: impact angle [radians] @@ -532,14 +554,8 @@ def F(a_rad, angle_dependency): else: raise exc.FunctionInputFail('Angle dependency {} is not defined.'.format(angle_dependency)) -def materials(): - print('--------------------') - print('Available materials:') - print('--------------------') - return material_properties('list') - -def erosion_rate(E_rel, Q_s): +def erosion_rate(E_rel: float, Q_s: float) -> float: """ :param E_rel: Relative Erosion [mm/ton] :param Q_s: Sand production rate [g/s] diff --git a/pysand/fluidproperties.py b/pysand/fluidproperties.py index 00ab3f8..ab90711 100644 --- a/pysand/fluidproperties.py +++ b/pysand/fluidproperties.py @@ -19,7 +19,7 @@ def validate_fluid_props(**kwargs): logger.warning('The model has got negative value(s) of {} and returned nan.'.format(i)) return True -def mix_velocity(P, T, Qo, Qw, Qg, Z, D): +def mix_velocity(P: float, T: float, Qo: float, Qw: float, Qg: float, Z: float, D: float) -> float: ''' Blackoil mixture velocity model, based on DNVGL RP-O501, August 2015 edition :param P: Pressure [bar] @@ -46,7 +46,8 @@ def mix_velocity(P, T, Qo, Qw, Qg, Z, D): return np.round(v_m, 2) -def mix_density(P, T, Qo, Qw, Qg, rho_o, rho_w, MW, Z): +def mix_density(P: float, T: float, Qo: float, Qw: float, Qg: float, + rho_o: float, rho_w: float, MW: float, Z: float) -> float: ''' Blackoil mixture density calculator, based on DNVGL RP-O501, August 2015 edition :param P: Pressure [bar] @@ -76,7 +77,7 @@ def mix_density(P, T, Qo, Qw, Qg, rho_o, rho_w, MW, Z): return np.round(rho_m, 2) -def mix_viscosity(P, T, Qo, Qw, Qg, mu_o, mu_w, mu_g, Z): +def mix_viscosity(P: float, T: float, Qo: float, Qw: float, Qg: float, mu_o: float, mu_w: float, mu_g: float, Z: float) -> float: ''' Blackoil mixture viscosity calculator, based on DNVGL RP-O501, August 2015 edition Output viscosity units equals input units @@ -106,7 +107,7 @@ def mix_viscosity(P, T, Qo, Qw, Qg, mu_o, mu_w, mu_g, Z): return np.round(mu_m, 6) -def liq_density(Qo, Qw, rho_o, rho_w): +def liq_density(Qo: float, Qw: float, rho_o: float, rho_w: float) -> float: """ Weighted average of oil and water densities :param Qo: Oil rate [Sm3/d] @@ -125,7 +126,7 @@ def liq_density(Qo, Qw, rho_o, rho_w): return rho_l -def liq_viscosity(Qo, Qw, mu_o, mu_w): +def liq_viscosity(Qo: float, Qw: float, mu_o: float, mu_w: float) -> float: """ Weighted average of oil and water viscosities :param Qo: Oil rate [Sm3/d] diff --git a/pysand/probe.py b/pysand/probe.py index 6282772..a5fd588 100644 --- a/pysand/probe.py +++ b/pysand/probe.py @@ -30,7 +30,7 @@ def validate_inputs(**kwargs): 'to trust the quantification model.'.format(kwargs[i])) -def er_sand_rate(E_meas, v_m, rho_m, D, d_p, alpha=60): +def er_sand_rate(E_meas: float, v_m: float, rho_m: float, D: float, d_p: float, alpha: float=60) -> float: """ ER probe sand rate calculation, model reference to DNVGL RP-O501, August 2015. This approach involve uncertainty, particularly at low bulk flow velocities; Should not be used when v_m < 5 m/s. diff --git a/pysand/transport.py b/pysand/transport.py index 72fe298..d6a01f6 100644 --- a/pysand/transport.py +++ b/pysand/transport.py @@ -9,7 +9,7 @@ g = 9.80665 # Standard gravity [m/s^2] -def hydro(D, rho_l, mu_l, d_p, e=5e-5, rho_p=2650): +def hydro(D: float, rho_l: float, mu_l: float, d_p: float, e: float=5e-5, rho_p: float=2650) -> list[float, float]: """ Equinor sand transport model for horizontal pipelines Based on T. Søntvedt (1995) and R. Schulkes (2002) work in Hydro @@ -90,7 +90,7 @@ def critical_u(u, m): return result -def stokes(rho_m, mu_m, d_p, angle, rho_p=2650): +def stokes(rho_m: float, mu_m: float, d_p: float, angle: float, rho_p: float=2650) -> float: """ Sand lifting rate calculation model for vertical or deviated (up to ~80 deg) inclination Based on Stokes law for settling velocities, but corrected for turbulent flow around sand grains From 88b3d2ae6923c80e5309c7ea75dab94596a331c9 Mon Sep 17 00:00:00 2001 From: smolvik1 Date: Wed, 25 Oct 2023 21:52:51 +0200 Subject: [PATCH 03/12] chore: update changelog --- changelog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/changelog.md b/changelog.md index 275f6ff..acfefbe 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,8 @@ +## 1.8 +* Implemented type hinting +* Removed warning for geometry factors above 4 +* Refactored material functions in `erosion.py` + ## 1.7.9 * Add name to material properties dictionary From 9b5842f1a53cfac1dcee3103c7bf2ab10f7e2bb6 Mon Sep 17 00:00:00 2001 From: smolvik1 Date: Wed, 25 Oct 2023 21:52:59 +0200 Subject: [PATCH 04/12] chore: bumped version --- pysand/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysand/version.py b/pysand/version.py index 99e9235..03de3f6 100644 --- a/pysand/version.py +++ b/pysand/version.py @@ -1 +1 @@ -__version__ = '1.7.9' +__version__ = '1.8' From c6e5c48c5db72c791b691d9aa1e398553adbe0a8 Mon Sep 17 00:00:00 2001 From: smolvik1 Date: Wed, 25 Oct 2023 21:53:10 +0200 Subject: [PATCH 05/12] chore: updated tests --- tests/test_erosion.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/tests/test_erosion.py b/tests/test_erosion.py index d000bf6..fa94d4a 100644 --- a/tests/test_erosion.py +++ b/tests/test_erosion.py @@ -4,7 +4,8 @@ import pysand.exceptions as exc import logging from pysand.erosion import validate_inputs, bend, tee, straight_pipe, \ - welded_joint, manifold, reducer, probes, flexible, choke_gallery, F, material_properties, erosion_rate, nozzlevalve_wall + welded_joint, manifold, reducer, probes, flexible, choke_gallery, F, \ + get_material_properties, get_materials, erosion_rate, nozzlevalve_wall def test_validate_inputs(caplog): @@ -99,10 +100,10 @@ def test_validate_inputs(caplog): "Particle diameter, d_p, is outside RP-O501 model boundaries (0.02 - 5 mm)." in s.message for s in info) # Test geometry factor limitations - kwargs = {'GF': 6} + kwargs = {'GF': 0.5} with caplog.at_level(logging.WARNING): validate_inputs(**kwargs) - assert "Geometry factor, GF, can only be 1, 2, 3 or 4" in str(caplog.records) + assert "Geometry factor, GF, can only be 1 or higher" in str(caplog.records) # Test bend radius boundaries kwargs = {'R': 1} @@ -329,21 +330,16 @@ def test_F(a_rad, angle_dependency, E): ('grp_epoxy', (1800, 3e-10, 3.6, 'ductile')), ('psz_ceramic_zirconia', (5700, 4.1e-9, 2.5, 'brittle'))] @pytest.mark.parametrize('material, E', material_validation) -def test_material_properties(material, E): - assert material_properties(material) == E +def test_get_material_properties(material, E): + assert get_material_properties(material) == E # Test material list and error raising -def test_material(): +def test_get_materials(): material_list = ['carbon_steel', 'duplex', 'ss316', 'inconel', 'grp_epoxy', 'grp_vinyl_ester', 'hdpe', 'aluminium', 'dc_05_tungsten', 'cs_10_tungsten', 'cr_37_tungsten', '95_alu_oxide', '99_alu_oxide', 'psz_ceramic_zirconia', 'ZrO2-Y3_ceramic_zirconia', 'SiC_silicon_carbide', 'Si3N4_silicon_nitride', 'TiB2_titanium_diboride', 'B4C_boron_carbide', 'SiSiC_ceramic_carbide'] - material = 'list' - assert material_properties(material) == material_list - - material = 'something_else' - with pytest.raises(exc.FunctionInputFail) as excinfo: - material_properties(material) + assert get_materials() == material_list def test_return_nan(): v_m = 29.3 From 15442719d0c02039e6e9bbf492817a15c39be117 Mon Sep 17 00:00:00 2001 From: smolvik1 Date: Wed, 25 Oct 2023 21:56:59 +0200 Subject: [PATCH 06/12] chore: updated docstring for hydro --- pysand/transport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysand/transport.py b/pysand/transport.py index d6a01f6..3c2b2b2 100644 --- a/pysand/transport.py +++ b/pysand/transport.py @@ -12,7 +12,7 @@ def hydro(D: float, rho_l: float, mu_l: float, d_p: float, e: float=5e-5, rho_p: float=2650) -> list[float, float]: """ Equinor sand transport model for horizontal pipelines - Based on T. Søntvedt (1995) and R. Schulkes (2002) work in Hydro + Derivative work based off of T. Søntvedt (1995) and R. Schulkes (2002) work in Hydro. :param D: Pipe diameter [m] :param rho_l: Liquid density [kg/m3] :param mu_l: Dynamic liquid viscosity [Pa.s] From a873362b386290e4b4d41843bb0c6d52dd4131c4 Mon Sep 17 00:00:00 2001 From: smolvik1 Date: Mon, 13 Nov 2023 08:45:31 +0100 Subject: [PATCH 07/12] chore: new convention --- pysand/erosion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pysand/erosion.py b/pysand/erosion.py index 7f09f9b..fb4a57b 100644 --- a/pysand/erosion.py +++ b/pysand/erosion.py @@ -30,7 +30,7 @@ def validate_inputs(**kwargs) -> bool: Geometry factor can only be 1 or higher """ - if not 'rho_p' in kwargs: + if 'rho_p' not in kwargs: kwargs['rho_p'] = 2650 for i in ['v_m', 'rho_m', 'mu_m', 'Q_s']: @@ -125,7 +125,7 @@ def bend(v_m: float, rho_m: float, mu_m: float, :param GF: Geometry factor [-] :param D: Pipe diameter [m] :param d_p: Particle diameter [mm] - :param material: Material exposed to erosion, default = 'duplex' (duplex steel). For others, run: materials() + :param material: Material exposed to erosion, default = 'duplex' (duplex steel). For others, run: get_materials() :param rho_p: Particle density [kg/m3], default = 2650 (quartz) :param crushed: True or False :return: Relative erosion rate [mm/ton] From fb388b9e0d0365a3af29305c846d35fba3751085 Mon Sep 17 00:00:00 2001 From: smolvik1 Date: Mon, 13 Nov 2023 08:45:50 +0100 Subject: [PATCH 08/12] feat: added test for invalid material --- tests/test_erosion.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_erosion.py b/tests/test_erosion.py index fa94d4a..498bdad 100644 --- a/tests/test_erosion.py +++ b/tests/test_erosion.py @@ -325,7 +325,7 @@ def test_F(a_rad, angle_dependency, E): F(a_rad, angle_dependency) -# Material properties function # +# Material properties function material_validation = [('carbon_steel', (7800, 2e-9, 2.6, 'ductile')), ('grp_epoxy', (1800, 3e-10, 3.6, 'ductile')), ('psz_ceramic_zirconia', (5700, 4.1e-9, 2.5, 'brittle'))] @@ -333,6 +333,11 @@ def test_F(a_rad, angle_dependency, E): def test_get_material_properties(material, E): assert get_material_properties(material) == E + +def test_get_material_properties_invalid_material(): + with pytest.raises(exc.FunctionInputFail): + get_material_properties(material='invalid material') + # Test material list and error raising def test_get_materials(): material_list = ['carbon_steel', 'duplex', 'ss316', 'inconel', 'grp_epoxy', 'grp_vinyl_ester', 'hdpe', 'aluminium', @@ -341,6 +346,7 @@ def test_get_materials(): 'TiB2_titanium_diboride', 'B4C_boron_carbide', 'SiSiC_ceramic_carbide'] assert get_materials() == material_list + def test_return_nan(): v_m = 29.3 rho_m = 30 From d13dc3e2b0353963868f276508db35697524996e Mon Sep 17 00:00:00 2001 From: smolvik1 Date: Mon, 13 Nov 2023 08:49:38 +0100 Subject: [PATCH 09/12] chore: removed exception aliases not being used --- tests/test_erosion.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_erosion.py b/tests/test_erosion.py index 498bdad..c935a1a 100644 --- a/tests/test_erosion.py +++ b/tests/test_erosion.py @@ -15,14 +15,14 @@ def test_validate_inputs(caplog): for inp in ['v_m', 'rho_m', 'mu_m', 'Q_s']: for non_number in [None, 'string', np.nan]: kwargs[inp] = non_number - with pytest.raises(exc.FunctionInputFail) as excinfo: + with pytest.raises(exc.FunctionInputFail): validate_inputs(**kwargs) kwargs[inp] = num kwargs = {'crushed': False} for non_bool in [None, 'string', np.nan]: kwargs['crushed'] = non_bool - with pytest.raises(exc.FunctionInputFail) as excinfo: + with pytest.raises(exc.FunctionInputFail): validate_inputs(**kwargs) # Test v_m boundaries @@ -69,7 +69,7 @@ def test_validate_inputs(caplog): for inp in ['R', 'GF', 'D', 'd_p', 'h', 'Dm', 'D1', 'D2', 'alpha']: for non_number in [None, 'string', np.nan, pd.Series(dtype='float64').any()]: kwargs[inp] = non_number - with pytest.raises(exc.FunctionInputFail) as excinfo: + with pytest.raises(exc.FunctionInputFail): validate_inputs(**kwargs) kwargs[inp] = num @@ -146,7 +146,7 @@ def test_validate_inputs(caplog): kwargs = {'R_c': R_c, 'gap': gap, 'H': H} for i in kwargs.keys(): kwargs[i] = -1 - with pytest.raises(exc.FunctionInputFail) as excinfo: + with pytest.raises(exc.FunctionInputFail): validate_inputs(**kwargs) kwargs['R_c'] = R_c @@ -321,7 +321,7 @@ def test_F(a_rad, angle_dependency, E): assert F(a_rad, angle_dependency) == E angle_dependency = 'something_else' - with pytest.raises(exc.FunctionInputFail) as excinfo: + with pytest.raises(exc.FunctionInputFail): F(a_rad, angle_dependency) From 09f6a0bc430cb84294f38815bf2223d21813f4bf Mon Sep 17 00:00:00 2001 From: smolvik1 Date: Mon, 13 Nov 2023 08:49:53 +0100 Subject: [PATCH 10/12] chore: improved readability --- pysand/erosion.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pysand/erosion.py b/pysand/erosion.py index fb4a57b..1d32d85 100644 --- a/pysand/erosion.py +++ b/pysand/erosion.py @@ -99,10 +99,10 @@ def validate_inputs(**kwargs) -> bool: logger.warning('Height of the weld, h, must positive number not exceeding pipe inner diameter size, D') # choke gallery - for l in ['R_c', 'gap', 'H']: - if l in kwargs: - if not kwargs[l] > 0: - raise exc.FunctionInputFail('{} has to be larger than 0'.format(l)) + for param in ['R_c', 'gap', 'H']: + if param in kwargs: + if not kwargs[param] > 0: + raise exc.FunctionInputFail(f'{param} has to be larger than 0') if 'R_c' in kwargs and 'gap' in kwargs: if kwargs['gap'] > kwargs['R_c']: raise exc.FunctionInputFail('The gap between the cage and choke body is larger than the radius') From 3deeae9a451b7ca2a26a42579ab9262e47ea90ae Mon Sep 17 00:00:00 2001 From: smolvik1 Date: Mon, 13 Nov 2023 08:55:52 +0100 Subject: [PATCH 11/12] chore: to support python 3.8 --- pysand/erosion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pysand/erosion.py b/pysand/erosion.py index 1d32d85..e0c75c0 100644 --- a/pysand/erosion.py +++ b/pysand/erosion.py @@ -1,7 +1,7 @@ import numpy as np import logging import pysand.exceptions as exc -from typing import TypedDict +from typing import TypedDict, List logger = logging.getLogger(__name__) # Models from DNVGL RP-O501, equation references in parenthesis @@ -530,7 +530,7 @@ def get_material_properties(material: str) -> tuple[float, float, float, str]: return rho_t, K, n, angle_dependency -def get_materials() -> list[str]: +def get_materials() -> List[str]: """ Function to return a list of all available materials """ From 2e76e78c6d364e4b6cef7137d12e3b6d0f5891d2 Mon Sep 17 00:00:00 2001 From: Stian Molvik <36161719+smolvik1@users.noreply.github.com> Date: Tue, 14 Nov 2023 08:29:55 +0100 Subject: [PATCH 12/12] revert python 3.8 support --- pysand/erosion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pysand/erosion.py b/pysand/erosion.py index e0c75c0..1d32d85 100644 --- a/pysand/erosion.py +++ b/pysand/erosion.py @@ -1,7 +1,7 @@ import numpy as np import logging import pysand.exceptions as exc -from typing import TypedDict, List +from typing import TypedDict logger = logging.getLogger(__name__) # Models from DNVGL RP-O501, equation references in parenthesis @@ -530,7 +530,7 @@ def get_material_properties(material: str) -> tuple[float, float, float, str]: return rho_t, K, n, angle_dependency -def get_materials() -> List[str]: +def get_materials() -> list[str]: """ Function to return a list of all available materials """