def validate_asd(**kwargs): """ Validation of all input parameters that go into std_step_clampon, std_step_emerson and sand_rate functions; """ for i in ['v_m', 'GLR', 'GOR']: if i in kwargs: if kwargs[i] is None: raise exc.FunctionInputFail('No calculation is done due to missing {}'.format(i)) if not isinstance(kwargs[i], (float, int)): raise exc.FunctionInputFail('{} is not a number'.format(i)) if not kwargs[i] >= 0: logger.warning('The model has got negative value(s) of {} and returned nan.'.format(i)) return True
def sand_rate(raw, zero, step, exp=1): ''' ASD sand rate calculation :param raw: raw value from ASD :param zero: Background noise zero value (no sand production) :param step: Sand noise :param exp: Linearity exponent (calibration constant) :return: Sand rate [g/s] ''' for key, value in {'raw': raw, 'zero': zero, 'step': step, 'exp': exp}.items(): if value is None: raise exc.FunctionInputFail('No calculation is done due to missing {}'.format(key)) if raw > zero: try: Qs = (raw - zero)**exp / step except ZeroDivisionError: logger.warning('Step value equal to zero, sand rate set to NaN') Qs = np.nan else: if Qs < 0: logger.warning('Negative step. Sand rate set to NaN') Qs = np.nan else: Qs = 0 return Qs
def validate_fluid_props(**kwargs): """ Validation of all input parameters that go into fluid properties models """ for i in [ 'P', 'T', 'Qo', 'Qw', 'Qg', 'Z', 'D', 'rho_o', 'rho_w', 'MW', 'mu_o', 'mu_w', 'mu_g' ]: if i in kwargs: if kwargs[i] is None: raise exc.FunctionInputFail( 'No fluid properties are calculated due to missing {}'. format(i)) if not isinstance(kwargs[i], (float, int)): raise exc.FunctionInputFail('{} is not a number'.format(i)) if not kwargs[i] >= 0: logger.warning( 'The model has got negative value(s) of {} and returned nan.' .format(i)) return True
def validate_inputs(**kwargs): for i in ['rho_m', 'rho_l', 'mu_m', 'mu_l', 'd_p', 'e', 'rho_p', 'D']: if i in kwargs: if not isinstance(kwargs[i], (float, int)) or np.isnan(kwargs[i]): raise exc.FunctionInputFail('{} is not a number'.format(i)) if not kwargs[i] >= 0: logger.warning( 'The model got negative value(s) of {} and returned nan.'. format(i)) return True if 'angle' in kwargs: if not isinstance(kwargs['angle'], (float, int)) or np.isnan(kwargs['angle']): raise exc.FunctionInputFail('angle is not a number') if (kwargs['angle'] < 0) or (kwargs['angle'] > 89): raise exc.FunctionInputFail( 'The inclination has to be positive and below 90 deg.') return
def validate_inputs(**kwargs): """ Validation of all input parameters that go into probe models; Besides validating for illegal data input, model parameters are limited within RP-O501 boundaries: ---------------------------------------------------------------------------- Model parameter --- Lower boundary --- Upper boundary ---------------------------------------------------------------------------- Measured erosion rate (E_meas) --- 0 --- --- Mix velocity (v_m) --- 5 --- --- ---------------------------------------------------------------------------- """ for i in kwargs: if i in ['E_meas', 'v_m']: if not isinstance(kwargs[i], (float, int)): raise exc.FunctionInputFail('{} is not a number'.format(i)) if kwargs[i] < 0: raise exc.FunctionInputFail('{} cannot be negative.'.format(i)) if i == 'v_m': if kwargs[i] < 5: logger.warning('Mix velocity, v_m = {} m/s, is too low (< 5 m/s) ' 'to trust the quantification model.'.format(kwargs[i]))
def F(a_rad, angle_dependency): """ Angle dependency function, reference to DNVGL RP-O501, August 2015 (3.3) :param a_rad: impact angle [radians] :param angle_dependency: angle dependency (ductile or brittle) :return: ductility factor """ if angle_dependency == 'ductile': A, B, C, k = .6, 7.2, 20, .6 return A * (np.sin(a_rad) + B * (np.sin(a_rad) - np.sin(a_rad) ** 2))**k * (1 - np.exp(-C * a_rad)) elif angle_dependency == 'brittle': return 2 * a_rad / np.pi else: raise exc.FunctionInputFail('Angle dependency {} is not defined.'.format(angle_dependency))
def material_properties(material): """ 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 """ properties = {'carbon_steel': (7800, 2e-9, 2.6, 'ductile'), 'duplex': (7850, 2e-9, 2.6, 'ductile'), 'ss316': (8000, 2e-9, 2.6, 'ductile'), 'inconel': (8440, 2e-9, 2.6, 'ductile'), 'grp_epoxy': (1800, 3e-10, 3.6, 'ductile'), 'grp_vinyl_ester': (1800, 6e-10, 3.6, 'ductile'), 'hdpe': (1150, 3.5e-9, 2.9, 'ductile'), 'aluminium': (2700, 5.8e-9, 2.3, 'ductile'), 'dc_05_tungsten': (15250, 1.1e-10, 2.3, 'brittle'), 'cs_10_tungsten': (14800, 3.2e-10, 2.2, 'brittle'), 'cr_37_tungsten': (14600, 8.8e-11, 2.5, 'brittle'), '95_alu_oxide': (3700, 6.8e-8, 2, 'brittle'), '99_alu_oxide': (3700, 9.5e-7, 1.2, 'brittle'), 'psz_ceramic_zirconia': (5700, 4.1e-9, 2.5, 'brittle'), 'ZrO2-Y3_ceramic_zirconia': (6070, 4e-11, 2.7, 'brittle'), 'SiC_silicon_carbide': (3100, 6.5e-9, 1.9, 'brittle'), 'Si3N4_silicon_nitride': (3200, 2e-10, 2, 'brittle'), 'TiB2_titanium_diboride': (4250, 9.3e-9, 1.9, 'brittle'), 'B4C_boron_carbide': (2500, 3e-8, .9, 'brittle'), 'SiSiC_ceramic_carbide': (3100, 7.4e-11, 2.7, 'brittle')} if material == 'list': return list(properties.keys()) 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][0] K = properties[material][1] n = properties[material][2] angle_dependency = properties[material][3] return rho_t, K, n, angle_dependency
def welded_joint(v_m, rho_m, D, d_p, h, alpha=60, location='downstream', material='duplex'): ''' Particle erosion in welded joints, model reference to DNVGL RP-O501, August 2015 :param v_m: Mix velocity [m/s] :param rho_m: Mix density [kg/m3] :param D: Pipe diameter [m] :param d_p: Particle diameter [mm] :param h: height of the weld [m] :param alpha: particle impact angle [degrees], default = 60 :param location: Erosion calculation locations 'downstream' or 'upstream' of weld, default = 'downstream' :param material: Material exposed to erosion, default = 'duplex' (duplex steel). For others, run: materials() :return: E_up: Relative erosion at flow facing part of weld [mm/ton] :return: E_down: Relative erosion downstream of weld [mm/ton] ''' # Input validation kwargs = {'v_m': v_m, 'rho_m': rho_m, 'D': D, 'd_p': d_p, 'h': h, 'alpha': alpha} if validate_inputs(**kwargs): return np.nan 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) A_pipe = np.pi * D**2 / 4 a_rad = np.deg2rad(alpha) C_unit = 3.15e10 # Conversion factor from m/s to mm/year (4.24) C2 = 10**6 * d_p / 1000 / (30 * rho_m**.5) # Particle size and fluid density correction factor (4.25) if C2 >= 1: C2 = 1 if location == 'downstream': E_down = 3.3e-2 * (7.5e-4 + h) * v_m**n * D**(-2) * (1e6/C_unit) return E_down elif location == 'upstream': E_up = K * F(a_rad, ad) * v_m ** n * np.sin(a_rad) / (rho_t * A_pipe) * C2 * 10**6 return E_up else: raise exc.FunctionInputFail('Location must be either downstream or upstream. {} is passed.'.format(location))
def validate_inputs(**kwargs): """ Validation of all input parameters that go into erosion models; Besides validating for illegal data input, model parameters are limited within RP-O501 boundaries: ------------------------------------------------------------------------- Model parameter --- Lower boundary --- Upper boundary ------------------------------------------------------------------------- Mix velocity --- 0 --- 200 --- Mix density --- 1 --- 1500 --- Mix viscosity --- 1e-6 --- 1e-2 --- Particle concentration [ppmV] --- 0 --- 500 --- Particle diameter --- 0.02 --- 5 --- Pipe inner diameter(D) --- 0.01 --- 1 --- Particle impact angle --- 0 --- 90 --- Bend radius --- 0.5 --- 50 --- Manifold diameter --- D --- --- Height of the weld --- 0 --- D --- Radius of choke gallery (Rc) --- 0 --- --- Gap cage and choke body (gap) --- 0 --- Rc --- Height of gallery (H) --- 0 --- --- ------------------------------------------------------------------------- Geometry factor can only be 1, 2, 3 or 4 """ if not 'rho_p' in kwargs: kwargs['rho_p'] = 2650 for i in ['v_m', 'rho_m', 'mu_m', 'Q_s']: if i in kwargs: if not isinstance(kwargs[i], (float, int)) or np.isnan(kwargs[i]): raise exc.FunctionInputFail('{} is not a number'.format(i)) if not kwargs[i] >= 0: logger.warning('The model has got negative value(s) of {} and returned nan.'.format(i)) return True if 'v_m' in kwargs: if kwargs['v_m'] > 200: logger.warning('Mix velocity, v_m, is outside RP-O501 model boundaries (0-200 m/s).') if 'rho_m' in kwargs: if (kwargs['rho_m'] < 1) or (kwargs['rho_m'] > 1500): logger.warning('Mix density, rho_m, is outside RP-O501 model boundaries (1-1500 kg/m3).') if 'mu_m' in kwargs: if (kwargs['mu_m'] < 1e-6) or (kwargs['mu_m'] > 1e-2): logger.warning( 'Mix viscosity, mu_m, is outside RP-O501 model boundaries (1e-6 - 1e-2 kg/ms).') if ('Q_s' in kwargs) and ('rho_p' in kwargs) and ('v_m' in kwargs) and ('D' in kwargs): ppmV = kwargs['Q_s'] / (kwargs['rho_p'] * kwargs['v_m'] * np.pi/4*kwargs['D']**2) * 1e3 # (4.20 in RP-O501) if (ppmV < 0) or (ppmV > 500): logger.warning('The particle concentration is outside RP-O501 model boundaries ( 0-500 ppmV).') for j in ['R', 'GF', 'D', 'd_p', 'h', 'Dm', 'D1', 'D2', 'R_c', 'gap', 'H', 'alpha', 'At']: if j in kwargs: if not isinstance(kwargs[j], (int, float)) or np.isnan(kwargs[j]): raise exc.FunctionInputFail('{} is not a number'.format(j)) for k in ['D', 'D1', 'D2']: if k in kwargs: if (kwargs[k] < 0.01) or (kwargs[k] > 1): logger.warning('Pipe inner diameter, {}, is outside RP-O501 model boundaries (0.01 - 1 m).'.format(k)) if not kwargs[k] > 0: raise exc.FunctionInputFail(' Pipe inner diameter, {}, must be positive'.format(k)) if 'd_p' in kwargs: if (kwargs['d_p'] < 0.02) or (kwargs['d_p'] > 5): logger.warning('Particle diameter, d_p, is outside RP-O501 model boundaries (0.02 - 5 mm).') 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') # bend/choke gallery if 'R' in kwargs: if (kwargs['R'] < 0.5) or (kwargs['R'] > 50): logger.warning('Bend radius, R, is outside RP-O501 model boundaries.') # manifold if 'Dm' in kwargs: if kwargs['Dm'] < kwargs['D']: logger.warning('Manifold diameter, Dm, is expected to be bigger than branch pipe diameter, D') # welded joint if 'h' in kwargs: if (kwargs['h'] < 0) or (kwargs['h'] > kwargs['D']): 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)) 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') # Nozzlevalve wall if 'model' in kwargs: if kwargs['model'] == 'nozzlevalve_wall' and kwargs['d_p'] > 0.6: logger.warning('Particle diameter, d_p, is higher than CFD-study boundary (0.6 mm).')