def get_profile(profile_fname, z0, D, uj, vj, wj, Tj, Sj, ua, T, F, sign_dp, H): """ Create and ambient profile dataset for an integral jet model simulation """ # Use mks units g = 9.81 rho_w = 1000. Pa = 101325. # Get the ambient density at the discharge from F (Assume water is # incompressible for these laboratory experiments) rho_j = seawater.density(Tj, Sj, 101325. + rho_w * g * z0) Vj = np.sqrt(uj**2 + vj**2 + wj**2) if F == 0.: rho_a = rho_j else: rho_a = rho_j / (1. - Vj**2 / (sign_dp * F**2 * D * g)) # Get the ambient stratification at the discharge from T if T == 0.: dpdz = 0. else: dpdz = sign_dp * (rho_a - rho_j) / (T * D) # Find the salinity at the discharge assuming the discharge temperature # matches the ambient temperature Ta = Tj def residual(Sa, rho, H): """ docstring for residual """ return rho - seawater.density(Ta, Sa, Pa + rho_w * g * H) Sa = fsolve(residual, 0., args=(rho_a, z0)) # Find the salinity at the top and bottom assuming linear stratification if dpdz == 0.: S0 = Sa SH = Sa else: rho_H = dpdz * (H - z0) + rho_a rho_0 = dpdz * (0.- z0) + rho_a # Use potential density to get the salinity SH = fsolve(residual, Sa, args=(rho_H, z0)) S0 = fsolve(residual, Sa, args=(rho_0, z0)) # Build the ambient data arrays z = np.array([0., H]) T = np.array([Ta, Ta]) S = np.array([S0, SH]) ua = np.array([ua, ua]) # Build the profile profile = build_profile(profile_fname, z, T, S, ua) # Return the ambient data return profile
def __init__(self, profile): super(ModelParams, self).__init__() # Store a reference density for the water column z_ave = profile.z_max - (profile.z_max - profile.z_min) / 2. T, S, P = profile.get_values(z_ave, ['temperature', 'salinity', 'pressure']) self.rho_r = seawater.density(T, S, P) # Store some physical constants self.g = 9.81 self.Ru = 8.314510
def outer_surf(yi, p): """ Compute the initial condition for the outer plume at the sea surface Computes the initial conditions for the first outer plume segment after the inner plume impinges on the free surface of the water body. It is assumed that the inner plume had significant volume flux and that this first outer plume segment will be viable. Parameters ---------- yi : `stratified_plume_model.InnerPlume` object Object for manipulating the inner plume state space p : `ModelParams` object Object containing the fixed model parameters for the stratified plume model. Returns ------- z0 : float Initial depth of the outer plume segment (m). y0 : ndarray Initial dependent variables state space for the outer plume segment. """ # The outer plume is a mixture of inner plume fluid and ambient fluid # entrained from the water surface Q = (1. + p.fe) * yi.Q T = (yi.T + yi.Ta * p.fe) * yi.Q / Q s = (yi.s + yi.Sa * p.fe) * yi.Q / Q c = (yi.c + yi.ca * p.fe) * yi.Q / Q rho = seawater.density(T, s, yi.P) # Use a Froude number approach to set the initial width and velocity u = outer_fr(yi.u, Q, yi.b, yi.rho_a, rho, p.g, p.Fro_0) # Calculate the outer plume state space variables y0 = [] Q = -Q y0.append(Q) y0.append(Q * (-u)) y0.append(s * Q) y0.append(p.rho_r * seawater.cp() * T * Q) y0.extend(c * Q) # Return the outer plume initial condition return (yi.z, np.array(y0))
def create_ambient_profile(data, labels, units, comments, nc_name, summary, source, sea_name, p_lat, p_lon, p_time, ca=[]): """ Create an ambient Profile object from given data Create an ambient.Profile object using the given CTD and current data. This function performs some standard operations to this data (unit conversion, computation of pressure, insertion of concentrations for dissolved gases, etc.) and returns the working ambient.Profile object. The idea behind this function is to separate data manipulation and creation of the ambient.Profile object from fetching of the data itself. Parameters ---------- data : np.array Array of the ambient ocean data to write to the CTD file. The contents and dimensions of this data are specified in the labels and units lists, below. labels : list List of string names of each variable in the data array. units : list List of units as strings for each variable in the data array. comments : list List of comments as strings that explain the types of data in the data array. Typical comments include 'measured', 'modeled', or 'computed'. nc_name : str String containing the file path and file name to use when creating the netCDF4 dataset that will contain this data. summary : str String describing the simulation for which this data will be used. source : str String documenting the source of the ambient ocean data provided. sea_name : str NC-compliant name for the ocean water body as a string. p_lat : float Latitude (deg) p_lon : float Longitude, negative is west of 0 (deg) p_time : netCDF4 time format Date and time of the CTD data using netCDF4.date2num(). ca : list, default=[] List of gases for which to compute a standard dissolved gas profile; choices are 'nitrogen', 'oxygen', 'argon', and 'carbon_dioxide'. Returns ------- profile : ambient.Profile Returns an ambient.Profile object for manipulating ambient water column data in TAMOC. """ # Convert the data to standard units data, units = ambient.convert_units(data, units) # Create an empty netCDF4-classic datast to store this CTD data nc = ambient.create_nc_db(nc_name, summary, source, sea_name, p_lat, p_lon, p_time) # Put the CTD and current profile data into the ambient netCDF file nc = ambient.fill_nc_db(nc, data, labels, units, comments, 0) # Compute and insert the pressure data z = nc.variables['z'][:] T = nc.variables['temperature'][:] S = nc.variables['salinity'][:] P = ambient.compute_pressure(z, T, S, 0) P_data = np.vstack((z, P)).transpose() nc = ambient.fill_nc_db(nc, P_data, ['z', 'pressure'], ['m', 'Pa'], ['measured', 'computed'], 0) # Use this netCDF file to create an ambient object profile = ambient.Profile( nc, ztsp=['z', 'temperature', 'salinity', 'pressure', 'ua', 'va']) # Compute dissolved gas profiles to add to this dataset if len(ca) > 0: # Create a gas mixture object for air gases = ['nitrogen', 'oxygen', 'argon', 'carbon_dioxide'] air = dbm.FluidMixture(gases) yk = np.array([0.78084, 0.20946, 0.009340, 0.00036]) m = air.masses(yk) # Set atmospheric conditions Pa = 101325. # Compute the desired concentrations for i in range(len(ca)): # Initialize a dataset of concentration data conc = np.zeros(len(profile.z)) # Compute the concentrations at each depth for j in range(len(conc)): # Get the local water column properties T, S, P = profile.get_values( profile.z[j], ['temperature', 'salinity', 'pressure']) # Compute the gas solubility at this temperature and salinity # at the sea surface Cs = air.solubility(m, T, Pa, S)[0, :] # Adjust the solubility to the present depth Cs = Cs * seawater.density(T, S, P) / \ seawater.density(T, S, 101325.) # Extract the right chemical conc[j] = Cs[gases.index(ca[i])] # Add this computed dissolved gas to the Profile dataset data = np.vstack((profile.z, conc)).transpose() symbols = ['z', ca[i]] units = ['m', 'kg/m^3'] comments = ['measured', 'computed from CTD data'] profile.append(data, symbols, units, comments, 0) # Close the netCDF dataset profile.close_nc() # Return the profile object return profile
def get_variables(self, z0, u_inf): """ Compute the governing variables at a given depth Compute the governing variables B (kinematic buoyancy flux), N (buoyancy frequency) and u_slip (dispersed-phase slip velocity) at the given depth and cross-flow velocity. These are the main ingredients to each of the scales calculations. Parameters ---------- z0 : float Depth to evaluate the governing variables (m) u_inf : float Magnitude of the local ambient cross-flow velocity (m/s) Returns ------- A tuble containing the governing variables: B : float Total kinematic buoyancy flux of all dispersed phases together (m^4/s^3) N : float Local value of the ambient buoyancy frequency (1/s) u_slip : float Slip velocity of the dispersed phase containing the greatest buoyancy flux (m/s) u_inf : float Magnitude of the local ambient cross-flow velocity (m/s) TODO (S. Socolofsky, October 2013): Eventually, this should be read from the ambient CTD data and removed as an input to this method. Notes ----- When more than one dispersed phase particle is present, the slip velocity used as the governing variables is the value for the dispersed phase particle that has the greatest effect on the dynamics of the plume. This particle is the one for which the buoyancy flux is highest. The governing variables, B, on the other hand is the total buoyancy flux of all dispersed phase particles combined. This is consistent with the way the governing parameters have been used in papers by Socolofsky and Adams (e.g., Socolofsky et al. 2011). """ # Get the ambient data from the CTD profile Ta, Sa, P = self.profile.get_values(z0, ['temperature', 'salinity', 'pressure']) rho = seawater.density(Ta, Sa, P) # Compute the properties of each dispersed-phase particle us = np.zeros(len(self.particles)) rho_p = np.zeros(len(self.particles)) m_p = np.zeros(len(self.particles)) B_p = np.zeros(len(self.particles)) for i in range(len(self.particles)): m0 = self.particles[i].m0 T0 = self.particles[i].T0 m_p[i] = np.sum(m0) * self.particles[i].nb0 if m_p[i] > 0.: # Particles exist, get properties. Make sure the algorithm # uses the dirty bubble properties since this is supposed # to be the rise velocity averaged over the whole plume. us[i], rho_p[i]= self.particles[i].properties(m0, T0, P, Sa, Ta, np.inf)[0:2] B_p[i] = (rho - rho_p[i]) / rho * 9.81 * (m_p[i] / rho_p[i]) else: # Particles dissolved, set to ambient conditions us[i] = 0. rho_p[i] = rho B_p[i] = 0. # Select the correct slip velocity u_slip = us[0] for i in range(len(self.particles) - 1): if B_p[i+1] > B_p[i]: u_slip = us[i+1] # Compute the total buoyancy flux B = np.sum(B_p) # Get the ambient buoyancy frequency N = self.profile.buoyancy_frequency(z0) # Return the governing parameters return (B, N, u_slip, u_inf)
# Simulate an experiment from Brandvik et al. (2013). Their data uses # Oseberg oil, with the following reported properties rho_oil = 839.3 mu_oil = 5.e-3 sigma = 15.5e-3 # We will simulate data from Table 3 in the Brandvik et al. (2013) paper. # These experiments have a nozzle diameter of 1.5 mm d0 = 0.0015 # They also used seawater (assumed salinity of 34.5 psu) and released the # oil from a depth of about 6 m at a temperature of 13 deg C T = 273.15 + 13. S = 34.5 rho = seawater.density(T, S, 101325.) P = 101325. + rho * 9.81 * 6. rho = seawater.density(T, S, P) mu = seawater.mu(T, S, P) # With this information, we can initialize a # `particle_size_models.PureJet` object jet = particle_size_models.PureJet(rho_oil, mu_oil, sigma, rho, mu, fp_type=1) # Brandvik et al. (2013) report the exit velocity at the nozzle. We # need to convert this to a mass flow rate. The mass flow rate should
def residual(Sa, rho, H): """ docstring for residual """ return rho - seawater.density(Ta, Sa, Pa + rho_w * g * H)
def test_particle_obj(): """ Test the object behavior for the `Particle` object Test the instantiation and attribute data for the `Particle` object of the `single_bubble_model` module. """ # Set up the base parameters describing a particle object T = 273.15 + 15. P = 150e5 Sa = 35. Ta = 273.15 + 4. composition = ['methane', 'ethane', 'propane', 'oxygen'] yk = np.array([0.85, 0.07, 0.08, 0.0]) de = 0.005 K = 1. Kt = 1. fdis = 1e-6 # Compute a few derived quantities bub = dbm.FluidParticle(composition) m0 = bub.masses_by_diameter(de, T, P, yk) # Create a `SingleParticle` object bub_obj = dispersed_phases.SingleParticle(bub, m0, T, K, fdis=fdis, K_T=Kt) # Check if the initial attributes are correct for i in range(len(composition)): assert bub_obj.composition[i] == composition[i] assert_array_almost_equal(bub_obj.m0, m0, decimal=6) assert bub_obj.T0 == T assert bub_obj.cp == seawater.cp() * 0.5 assert bub_obj.K == K assert bub_obj.K_T == Kt assert bub_obj.fdis == fdis for i in range(len(composition)-1): assert bub_obj.diss_indices[i] == True assert bub_obj.diss_indices[-1] == False # Check if the values returned by the `properties` method match the input (us, rho_p, A, Cs, beta, beta_T, T_ans) = bub_obj.properties(m0, T, P, Sa, Ta, 0.) us_ans = bub.slip_velocity(m0, T, P, Sa, Ta) rho_p_ans = bub.density(m0, T, P) A_ans = bub.surface_area(m0, T, P, Sa, Ta) Cs_ans = bub.solubility(m0, T, P, Sa) beta_ans = bub.mass_transfer(m0, T, P, Sa, Ta) beta_T_ans = bub.heat_transfer(m0, T, P, Sa, Ta) assert us == us_ans assert rho_p == rho_p_ans assert A == A_ans assert_array_almost_equal(Cs, Cs_ans, decimal=6) assert_array_almost_equal(beta, beta_ans, decimal=6) assert beta_T == beta_T_ans assert T == T_ans # Check that dissolution shuts down correctly m_dis = np.array([m0[0]*1e-10, m0[1]*1e-8, m0[2]*1e-3, 1.5e-5]) (us, rho_p, A, Cs, beta, beta_T, T_ans) = bub_obj.properties(m_dis, T, P, Sa, Ta, 0) assert beta[0] == 0. assert beta[1] == 0. assert beta[2] > 0. assert beta[3] > 0. m_dis = np.array([m0[0]*1e-10, m0[1]*1e-8, m0[2]*1e-7, 1.5e-16]) (us, rho_p, A, Cs, beta, beta_T, T_ans) = bub_obj.properties(m_dis, T, P, Sa, Ta, 0.) assert np.sum(beta[0:-1]) == 0. assert us == 0. assert rho_p == seawater.density(Ta, Sa, P) # Check that heat transfer shuts down correctly (us, rho_p, A, Cs, beta, beta_T, T_ans) = bub_obj.properties(m_dis, Ta, P, Sa, Ta, 0) assert beta_T == 0. (us, rho_p, A, Cs, beta, beta_T, T_ans) = bub_obj.properties(m_dis, T, P, Sa, Ta, 0) assert beta_T == 0. # Check the value returned by the `diameter` method de_p = bub_obj.diameter(m0, T, P, Sa, Ta) assert_approx_equal(de_p, de, significant=6) # Check functionality of insoluble particle drop = dbm.InsolubleParticle(isfluid=True, iscompressible=True) m0 = drop.mass_by_diameter(de, T, P, Sa, Ta) # Create a `Particle` object drop_obj = dispersed_phases.SingleParticle(drop, m0, T, K, fdis=fdis, K_T=Kt) # Check if the values returned by the `properties` method match the input (us, rho_p, A, Cs, beta, beta_T, T_ans) = drop_obj.properties( np.array([m0]), T, P, Sa, Ta, 0) us_ans = drop.slip_velocity(m0, T, P, Sa, Ta) rho_p_ans = drop.density(T, P, Sa, Ta) A_ans = drop.surface_area(m0, T, P, Sa, Ta) beta_T_ans = drop.heat_transfer(m0, T, P, Sa, Ta) assert us == us_ans assert rho_p == rho_p_ans assert A == A_ans assert beta_T == beta_T_ans # Check that heat transfer shuts down correctly (us, rho_p, A, Cs, beta, beta_T, T_ans) = drop_obj.properties(m_dis, Ta, P, Sa, Ta, 0) assert beta_T == 0. (us, rho_p, A, Cs, beta, beta_T, T_ans) = drop_obj.properties(m_dis, T, P, Sa, Ta, 0) assert beta_T == 0. # Check the value returned by the `diameter` method de_p = drop_obj.diameter(m0, T, P, Sa, Ta) assert_approx_equal(de_p, de, significant=6)
def get_variables(self, z0, u_inf): """ Compute the governing variables at a given depth Compute the governing variables B (kinematic buoyancy flux), N (buoyancy frequency) and u_slip (dispersed-phase slip velocity) at the given depth and cross-flow velocity. These are the main ingredients to each of the scales calculations. Parameters ---------- z0 : float Depth to evaluate the governing variables (m) u_inf : float Magnitude of the local ambient cross-flow velocity (m/s) Returns ------- A tuble containing the governing variables: B : float Total kinematic buoyancy flux of all dispersed phases together (m^4/s^3) N : float Local value of the ambient buoyancy frequency (1/s) u_slip : float Slip velocity of the dispersed phase containing the greatest buoyancy flux (m/s) u_inf : float Magnitude of the local ambient cross-flow velocity (m/s) TODO (S. Socolofsky, October 2013): Eventually, this should be read from the ambient CTD data and removed as an input to this method. Notes ----- When more than one dispersed phase particle is present, the slip velocity used as the governing variables is the value for the dispersed phase particle that has the greatest effect on the dynamics of the plume. This particle is the one for which the buoyancy flux is highest. The governing variables, B, on the other hand is the total buoyancy flux of all dispersed phase particles combined. This is consistent with the way the governing parameters have been used in papers by Socolofsky and Adams (e.g., Socolofsky et al. 2011). """ # Get the ambient data from the CTD profile Ta, Sa, P = self.profile.get_values( z0, ['temperature', 'salinity', 'pressure']) rho = seawater.density(Ta, Sa, P) # Compute the properties of each dispersed-phase particle us = np.zeros(len(self.particles)) rho_p = np.zeros(len(self.particles)) m_p = np.zeros(len(self.particles)) B_p = np.zeros(len(self.particles)) for i in range(len(self.particles)): m0 = self.particles[i].m0 T0 = self.particles[i].T0 m_p[i] = np.sum(m0) * self.particles[i].nb0 if m_p[i] > 0.: # Particles exist, get properties. Make sure the algorithm # uses the dirty bubble properties since this is supposed # to be the rise velocity averaged over the whole plume. us[i], rho_p[i] = self.particles[i].properties( m0, T0, P, Sa, Ta, np.inf)[0:2] B_p[i] = (rho - rho_p[i]) / rho * 9.81 * (m_p[i] / rho_p[i]) else: # Particles dissolved, set to ambient conditions us[i] = 0. rho_p[i] = rho B_p[i] = 0. # Select the correct slip velocity u_slip = us[0] for i in range(len(self.particles) - 1): if B_p[i + 1] > B_p[i]: u_slip = us[i + 1] # Compute the total buoyancy flux B = np.sum(B_p) # Get the ambient buoyancy frequency N = self.profile.buoyancy_frequency(z0) # Return the governing parameters return (B, N, u_slip, u_inf)
ds = ambient.Profile(nc) # Close the netCDF dataset ds.nc.close() # Since the netCDF file is now fully stored on the hard drive in the # correct format, we can initialize an ambient.Profile object directly # from the netCDF file ds = ambient.Profile(nc_file, chem_names='all') # Plot the density profile using the interpolation function z = np.linspace(ds.nc.variables['z'].valid_min, ds.nc.variables['z'].valid_max, 250) rho = np.zeros(z.shape) tsp = ds.get_values(z, ['temperature', 'salinity', 'pressure']) for i in range(len(z)): rho[i] = seawater.density(tsp[i,0], tsp[i,1], tsp[i,2]) fig = plt.figure() ax1 = plt.subplot(121) ax1.plot(rho, z) ax1.set_xlabel('Density (kg/m^3)') ax1.set_ylabel('Depth (m)') ax1.invert_yaxis() ax1.set_title('Computed data') plt.show() # Close the netCDF dataset ds.nc.close()
# Get the ambient.Profile object with the original CTD data profile = get_ctd_profile() # Compute a dissolved nitrogen profile...start with a model for air air = dbm.FluidMixture(['nitrogen', 'oxygen', 'argon', 'carbon_dioxide']) yk = np.array([0.78084, 0.20946, 0.009340, 0.00036]) m = air.masses(yk) # Compute the solubility of nitrogen at the air-water interface, then # correct for seawater compressibility n2_conc = np.zeros(len(profile.z)) for i in range(len(profile.z)): T, S, P = profile.get_values(profile.z[i], ['temperature', 'salinity', 'pressure']) Cs = air.solubility(m, T, 101325., S)[0,:] * \ seawater.density(T, S, P) / seawater.density(T, S, 101325.) n2_conc[i] = Cs[0] # Add this computed nitrogen profile to the Profile dataset data = np.vstack((profile.z, n2_conc)).transpose() symbols = ['z', 'nitrogen'] units = ['m', 'kg/m^3'] comments = ['measured', 'computed from CTD data'] profile.append(data, symbols, units, comments, 0) # Close the dataset profile.close_nc() # Plot the oxygen and nitrogren profiles to show that data have been # added to the Profile object z = np.linspace(profile.z_min, profile.z_max, 250)
def zfe_volume_flux(profile, X0, R, Vj, Sj, Tj): """ Compute the volume flux of continous phase discharge fluid at the release If the release includes continous phase fluid, this function computes the flow rate and geometry of the release Parameters ---------- profile : `ambient.Profile` object The ambient CTD object used by the single bubble model simulation. X0 : ndarray Release location (x, y, z) in (m) R : float Radius for the equivalent circular cross-section of the release (m) Vj : float Scalar value of the magnitude of the discharge velocity for continuous phase fluid in the discharge. This variable should be 0 or None for a pure multiphase discharge. Sj : float Salinity of the continuous phase fluid in the discharge (psu) Tj : float Temperature of the continuous phase fluid in the discharge (T) Returns ------- Q : Volume flux of continuous phase fluid at the discharge (m^3/s) A : Cross-sectional area of the discharge (M^2) X : ndarray Release location (x, y, z) in (m) Tj : float Temperature of the continuous phase fluid in the discharge (T) Sj : float Salinity of the continuous phase fluid in the discharge (psu) Pj : float Pressure at the discharge (Pa) rho_j : float Density of the continous phase fluid in the discharge (kg/m^3) """ # The Lagrangian plume model starts at the discharge. X = X0 # Get the jet density from the discharge characteristics Ta, Sa, P = profile.get_values(X[2], ['temperature', 'salinity', 'pressure']) rho_j = seawater.density(Ta, Sa, P) # Pressure at the discharge is the ambient pressure Pj = P # The discharge area if the full port area A = np.pi * R**2 # Compute the volume flux of discharge fluid Q = A * Vj # Return the initial conditions with salinity and temperature of the # discharge equal to the jet values return (Q, A, X, Tj, Sj, Pj, rho_j)
def test_particle_obj(): """ Test the object behavior for the `Particle` object Test the instantiation and attribute data for the `Particle` object of the `bent_plume_model` module. """ # Set up the base parameters describing a particle object T = 273.15 + 15. P = 150e5 Sa = 35. Ta = 273.15 + 4. composition = ['methane', 'ethane', 'propane', 'oxygen'] yk = np.array([0.85, 0.07, 0.08, 0.0]) de = 0.005 lambda_1 = 0.85 K = 1. Kt = 1. fdis = 1e-6 x = 0. y = 0. z = 0. # Compute a few derived quantities bub = dbm.FluidParticle(composition) nb0 = 1.e5 m0 = bub.masses_by_diameter(de, T, P, yk) # Create a `PlumeParticle` object bub_obj = bent_plume_model.Particle(x, y, z, bub, m0, T, nb0, lambda_1, P, Sa, Ta, K, Kt, fdis) # Check if the initialized attributes are correct assert bub_obj.integrate == True assert bub_obj.sim_stored == False assert bub_obj.farfield == False assert bub_obj.t == 0. assert bub_obj.x == x assert bub_obj.y == y assert bub_obj.z == z for i in range(len(composition)): assert bub_obj.composition[i] == composition[i] assert_array_almost_equal(bub_obj.m0, m0, decimal=6) assert bub_obj.T0 == T assert_array_almost_equal(bub_obj.m, m0, decimal=6) assert bub_obj.T == T assert bub_obj.cp == seawater.cp() * 0.5 assert bub_obj.K == K assert bub_obj.K_T == Kt assert bub_obj.fdis == fdis for i in range(len(composition)-1): assert bub_obj.diss_indices[i] == True assert bub_obj.diss_indices[-1] == False assert bub_obj.nb0 == nb0 assert bub_obj.lambda_1 == lambda_1 # Including the values after the first call to the update method us_ans = bub.slip_velocity(m0, T, P, Sa, Ta) rho_p_ans = bub.density(m0, T, P) A_ans = bub.surface_area(m0, T, P, Sa, Ta) Cs_ans = bub.solubility(m0, T, P, Sa) beta_ans = bub.mass_transfer(m0, T, P, Sa, Ta) beta_T_ans = bub.heat_transfer(m0, T, P, Sa, Ta) assert bub_obj.us == us_ans assert bub_obj.rho_p == rho_p_ans assert bub_obj.A == A_ans assert_array_almost_equal(bub_obj.Cs, Cs_ans, decimal=6) assert_array_almost_equal(bub_obj.beta, beta_ans, decimal=6) assert bub_obj.beta_T == beta_T_ans # Test the bub_obj.outside() method bub_obj.outside(Ta, Sa, P) assert bub_obj.us == 0. assert bub_obj.rho_p == seawater.density(Ta, Sa, P) assert bub_obj.A == 0. assert_array_almost_equal(bub_obj.Cs, np.zeros(len(composition))) assert_array_almost_equal(bub_obj.beta, np.zeros(len(composition))) assert bub_obj.beta_T == 0. assert bub_obj.T == Ta # No need to test the properties or diameter objects since they are # inherited from the `single_bubble_model` and tested in `test_sbm`. # No need to test the bub_obj.track(), bub_obj.run_sbm() since they will # be tested below for the simulation cases. # Check functionality of insoluble particle drop = dbm.InsolubleParticle(isfluid=True, iscompressible=True) m0 = drop.mass_by_diameter(de, T, P, Sa, Ta) drop_obj = bent_plume_model.Particle(x, y, z, drop, m0, T, nb0, lambda_1, P, Sa, Ta, K, fdis=fdis, K_T=Kt) assert len(drop_obj.composition) == 1 assert drop_obj.composition[0] == 'inert' assert_array_almost_equal(drop_obj.m0, m0, decimal=6) assert drop_obj.T0 == T assert_array_almost_equal(drop_obj.m, m0, decimal=6) assert drop_obj.T == T assert drop_obj.cp == seawater.cp() * 0.5 assert drop_obj.K == K assert drop_obj.K_T == Kt assert drop_obj.fdis == fdis assert drop_obj.diss_indices[0] == True assert drop_obj.nb0 == nb0 assert drop_obj.lambda_1 == lambda_1 # Including the values after the first call to the update method us_ans = drop.slip_velocity(m0, T, P, Sa, Ta) rho_p_ans = drop.density(T, P, Sa, Ta) A_ans = drop.surface_area(m0, T, P, Sa, Ta) beta_T_ans = drop.heat_transfer(m0, T, P, Sa, Ta) assert drop_obj.us == us_ans assert drop_obj.rho_p == rho_p_ans assert drop_obj.A == A_ans assert drop_obj.beta_T == beta_T_ans
def get_particles(self, composition, data, md_gas0, md_oil0, profile, d50_gas, d50_oil, nbins, T0, z0, dispersant, sigma_fac, oil, mass_frac, hydrate, inert_drop): """ docstring for get_particles """ # Reduce surface tension if dispersant is applied if dispersant is True: sigma = np.array([[1.], [1.]]) * sigma_fac else: sigma = np.array([[1.], [1.]]) # Create DBM objects for the bubbles and droplets bubl = dbm.FluidParticle(composition, fp_type=0, sigma_correction=sigma[0], user_data=data) drop = dbm.FluidParticle(composition, fp_type=1, sigma_correction=sigma[1], user_data=data) # Get the local ocean conditions T, S, P = profile.get_values(z0, ['temperature', 'salinity', 'pressure']) rho = seawater.density(T, S, P) # Get the mole fractions of the released fluids molf_gas = bubl.mol_frac(md_gas0) molf_oil = drop.mol_frac(md_oil0) print molf_gas print molf_oil # Use the Rosin-Rammler distribution to get the mass flux in each # size class # de_gas, md_gas = sintef.rosin_rammler(nbins, d50_gas, np.sum(md_gas0), # bubl.interface_tension(md_gas0, T0, S, P), # bubl.density(md_gas0, T0, P), rho) # de_oil, md_oil = sintef.rosin_rammler(nbins, d50_oil, np.sum(md_oil0), # drop.interface_tension(md_oil0, T0, S, P), # drop.density(md_oil0, T0, P), rho) # Get the user defined particle size distibution de_oil, vf_oil, de_gas, vf_gas = self.userdefined_de() md_gas = np.sum(md_gas0) * vf_gas md_oil = np.sum(md_oil0) * vf_oil # Define a inert particle to be used if inert liquid particles are use # in the simulations molf_inert = 1. isfluid = True iscompressible = True rho_o = drop.density(md_oil0, T0, P) inert = dbm.InsolubleParticle(isfluid, iscompressible, rho_p=rho_o, gamma=40., beta=0.0007, co=2.90075e-9) # Create the particle objects particles = [] t_hyd = 0. # Bubbles for i in range(nbins): if md_gas[i] > 0.: (m0, T0, nb0, P, Sa, Ta) = dispersed_phases.initial_conditions( profile, z0, bubl, molf_gas, md_gas[i], 2, de_gas[i], T0) # Get the hydrate formation time for bubbles if hydrate is True and dispersant is False: t_hyd = dispersed_phases.hydrate_formation_time(bubl, z0, m0, T0, profile) if np.isinf(t_hyd): t_hyd = 0. else: t_hyd = 0. particles.append(bpm.Particle(0., 0., z0, bubl, m0, T0, nb0, 1.0, P, Sa, Ta, K=1., K_T=1., fdis=1.e-6, t_hyd=t_hyd)) # Droplets for i in range(len(de_oil)): # Add the live droplets to the particle list if md_oil[i] > 0. and not inert_drop: (m0, T0, nb0, P, Sa, Ta) = dispersed_phases.initial_conditions( profile, z0, drop, molf_oil, md_oil[i], 2, de_oil[i], T0) # Get the hydrate formation time for bubbles if hydrate is True and dispersant is False: t_hyd = dispersed_phases.hydrate_formation_time(drop, z0, m0, T0, profile) if np.isinf(t_hyd): t_hyd = 0. else: t_hyd = 0. particles.append(bpm.Particle(0., 0., z0, drop, m0, T0, nb0, 1.0, P, Sa, Ta, K=1., K_T=1., fdis=1.e-6, t_hyd=t_hyd)) # Add the inert droplets to the particle list if md_oil[i] > 0. and inert_drop is True: (m0, T0, nb0, P, Sa, Ta) = dispersed_phases.initial_conditions( profile, z0, inert, molf_oil, md_oil[i], 2, de_oil[i], T0) particles.append(bpm.Particle(0., 0., z0, inert, m0, T0, nb0, 1.0, P, Sa, Ta, K=1., K_T=1., fdis=1.e-6, t_hyd=0.)) # Define the lambda for particles model = params.Scales(profile, particles) for j in range(len(particles)): particles[j].lambda_1 = model.lambda_1(z0, j) # Return the particle list return particles
# Get the ambient.Profile object with the original CTD data ctd = get_ctd_profile() # Print the buoyancy frequency at a few selected depths z = np.array([500., 1000., 1500.]) N = ctd.buoyancy_frequency(z) print('Buoyancy frequency is: ') for i in range(len(z)): print(' N(%d m) = %g (1/s) ' % (z[i], N[i])) # Plot the potential density profile and corresponding buoyancy frequency z_min = ctd.nc.variables['z'].valid_min z_max = ctd.nc.variables['z'].valid_max z = np.linspace(z_min, z_max, 500) ts = ctd.get_values(z, ['temperature', 'salinity']) rho = seawater.density(ts[:, 0], ts[:, 1], 101325.) N = ctd.buoyancy_frequency(z) fig = plt.figure(1) plt.clf() ax1 = plt.subplot(121) ax1.plot(rho, z) ax1.set_xlabel('Potential density, (kg/m^3)') ax1.set_ylabel('Depth, (m)') ax1.set_ylim([0., 2500.]) ax1.invert_yaxis() ax2 = plt.subplot(122) ax2.plot(N, z) ax2.set_xlabel('N, (1/s)') ax2.set_ylim([0., 2500.]) ax2.invert_yaxis() plt.show()
# Get the ambient.Profile object with the original CTD data ctd = get_ctd_profile() # Print the buoyancy frequency at a few selected depths z = np.array([500., 1000., 1500.]) N = ctd.buoyancy_frequency(z) print 'Buoyancy frequency is: ' for i in range(len(z)): print ' N(%d m) = %g (1/s) ' % (z[i], N[i]) # Plot the potential density profile and corresponding buoyancy frequency z_min = ctd.nc.variables['z'].valid_min z_max = ctd.nc.variables['z'].valid_max z = np.linspace(z_min, z_max, 500) ts = ctd.get_values(z, ['temperature', 'salinity']) rho = seawater.density(ts[:,0], ts[:,1], 101325.) N = ctd.buoyancy_frequency(z) fig = plt.figure(1) plt.clf() ax1 = plt.subplot(121) ax1.plot(rho, z) ax1.set_xlabel('Potential density, (kg/m^3)') ax1.set_ylabel('Depth, (m)') ax1.set_ylim([0., 2500.]) ax1.invert_yaxis() ax2 = plt.subplot(122) ax2.plot(N, z) ax2.set_xlabel('N, (1/s)') ax2.set_ylim([0., 2500.]) ax2.invert_yaxis() plt.show()
def outer_cpic(yi, yo, particles, profile, p, neighbor, z_0): """ Compute the initial condition for the outer plume at depth Computes the initial conditions for the an outer plume segment within the reservoir body. Part of the calculation determines whether or not the computed initial condition has enough downward momentum to be viable as an initial condition (e.g., whether or not it will be overwhelmed by the upward drag of the inner plume). Parameters ---------- yi : `stratified_plume_model.InnerPlume` object Object for manipulating the inner plume state space. yo : `stratified_plume_model.OuterPlume` object Object for manipulating the outer plume state space. particles : list of `Particle` objects List of `Particle` objects containing the dispersed phase local conditions and behavior. profile : `ambient.Profile` object The ambient CTD object used by the simulation. p : `ModelParams` object Object containing the fixed model parameters for the stratified plume model. neighbor : `scipy.interpolate.interp1d` object Container holding the latest solution for the inner plume state space. z_0 : float Top of the inner plume calculation (m). Returns ------- z0 : float Initial depth of the outer plume segment (m). y0 : ndarray Initial dependent variables state space for the outer plume segment. flag : bool Outer plume viability flag: `True` means the outer plume segment is viable and should be integrated; `False` means the outer plume segment is too weak and should be discarded, moving down the inner plume to calculate the next outer plume initial condition. Notes ----- The iteration required to find a viable outer plume segment is conducted by the `stratified_plume_model.outer_main` function. This function computes the initial conditions for one attempt to find an outer plume segment and reports back (through `flag`) on the success. There is one caveat to the above statement. The model parameter `p.nwidths` determines the vertical scale over which this function may integrate to find the start to an outer plume, given as a integer number of times of the inner plume half-width. This function starts by searching one half-width. If `p.nwidths` is greater than one, it will continue to expand the search region. The physical interpretation of `p.nwidths` is to set a reasonable upper bound on the diameter of eddies shed from the inner plume in the peeling region into the outer plume. While the integral model does not have "eddies" per se, the search window size should still be representative of this type of length scale. """ # Start the iteration counters iter = 0 done = False # Compute the outer plume initial conditions until the outer plume is # viable or until the maximum number of widths is integrated while not done and iter < p.nwidths: # Update iteration counter iter += 1 # Get the inner plume properties at the top of this peeling region yi.update(z_0, neighbor(z_0), particles, profile, p) # Set the range to integrate to get the current peeling flux z_upper = z_0 z_lower = z_0 + iter * yi.b # Check if the bottom of the reservoir is encountered. if z_lower > profile.z_max: z_lower = profile.z_max # Find the indices in the raw data for the inner plume solution close # to where z_upper and z_lower occur i_upper = np.min(np.where(neighbor.x >= z_upper)[0]) i_lower = np.max(np.where(neighbor.x <= z_lower)[0]) # Get the grid of elevations where we will integrate the solution to # obtain the initial flux for the outer plume. This is needed # because the solution is so stiff: if we integrated over a fixed # step size, we could easily miss dramatic changes in the solution. # Hence, we integrate over the steps in the numerical solution # itself. n_grid = i_lower - i_upper + 3 zi = np.zeros(n_grid) zi[0] = z_upper zi[-1] = z_lower zi[1:-1] = neighbor.x[i_upper:i_lower + 1] # Integrate the peeling fluid over this grid to get the total # contributions going into the outer plume Q = 0. tracer_vars = np.zeros(2 + yi.nchems) for i in range(len(zi) - 1): yi.update(zi[i], neighbor(zi[i]), particles, profile, p) dz = zi[i + 1] - zi[i] Q = Q + yi.Ep * dz tracer_vars = tracer_vars + np.hstack((yi.s, yi.T * p.rho_r * \ seawater.cp(), yi.c)) * yi.Ep * dz # Get the initial velocity of the peeling fluid using the modified # outer plume Froude number condition T = tracer_vars[1] / (Q * p.rho_r * seawater.cp()) s = tracer_vars[0] / Q c = tracer_vars[2:] / Q rho = seawater.density(T, s, yi.P) u = outer_fr(0.05, -Q, yi.b, yi.rho_a, rho, p.g, p.Fro_0) b = np.sqrt(Q**2 / (np.pi * (-Q) * u) + yi.b**2) dQdz = 2. * np.pi * yi.b * (p.alpha_1 * (yi.u + p.c1 * (-u)) + \ p.alpha_2 * (-u)) + 2. * np.pi * b * p.alpha_3 * (-u) + yi.Ep # Check whether this outer plume segment will be viable if dQdz > 0 or Q > 0 or np.isnan(Q): # This outer plume segment is not viable flag = False z0 = np.array([z_0, z_lower]) y0 = np.array([np.zeros(yo.len), np.zeros(yo.len)]) else: # This outer plume segmet is viable...stop integrating widths done = True # Check where the diffuser is if z_lower >= yi.z0: # This outer plume segment should not exist flag = False z0 = np.array([z_0, z_lower]) y0 = np.array([np.zeros(yo.len), np.zeros(yo.len)]) else: # This is the next outer plume segment to integrate flag = True z0 = z_lower y0 = [] y0.append(Q) y0.append(Q * (-u)) y0.append(s * Q) y0.append(p.rho_r * seawater.cp() * T * Q) y0.extend(c * Q) # Return the results of the initial conditions search return (z0, np.array(y0), flag)
def update_properties(self, profile, oil_mixture, m_mixture, z0, Tj=None): """ Set the thermodynamic properties of the released and receiving fluids Store the density, viscosity, and interfacial tension of the fluids involved in a jet breakup scenario. Parameters ---------- profile : `ambient.Profile` object Profile containing ambient CTD data oil_mixture : `dbm.FluidMixture` object A `dbm.FluidMixture` object that contains the chemical description of an oil mixture. m_mixture : ndarray An array of mass fluxes (kg/s) of each pseudo-component in the live-oil mixture. z0 : float Release point of the jet orifice (m) Tj : float Temperature of the released fluids (K) Notes ----- This method allows the complete release to be redefined. If you only want to update the release depth or release temperature, use `.update_z0()` or `update_Tj()`, instead. """ # Set the flags initially to False self.sim_stored = False self.distribution_stored = False # Record the input parameters self.profile = profile self.oil_mixture = oil_mixture self.m_mixture = m_mixture self.z0 = z0 self.Tj = Tj # Compute the properties of seawater self.T, self.S, self.P = self.profile.get_values(self.z0, ['temperature', 'salinity', 'pressure'] ) self.rho = seawater.density(self.T, self.S, self.P) self.mu = seawater.mu(self.T, self.S, self.P) # Set jet temperature either to ambient or input value if Tj == None: # Use ambient temperature self.Tj = self.T else: # Use input temperature self.Tj = Tj # Compute the gas/liquid equilibrium m_eq, xi, K = self.oil_mixture.equilibrium(self.m_mixture, self.Tj, self.P) # Compute the gas phase properties if np.sum(m_eq[0,:]) == 0: self.gas = None self.m_gas = m_eq[0,:] self.rho_gas = None self.mu_gas = None self.sigma_gas = None else: self.gas = dbm.FluidParticle(self.oil_mixture.composition, fp_type=0, delta=oil_mixture.delta, user_data=oil_mixture.user_data) self.m_gas = m_eq[0,:] self.rho_gas = self.gas.density(self.m_gas, self.Tj, self.P) self.mu_gas = self.gas.viscosity(self.m_gas, self.Tj, self.P) self.sigma_gas = self.gas.interface_tension(self.m_gas, self.Tj, self.S, self.P) # Compute the liquid phase properties if np.sum(m_eq[1,:]) == 0: self.oil = None self.m_oil = m_eq[1,:] self.rho_oil = None self.mu_oil = None self.sigma_oil = None else: self.oil = dbm.FluidParticle(self.oil_mixture.composition, fp_type=1, delta=oil_mixture.delta, user_data=oil_mixture.user_data) self.m_oil = m_eq[1,:] self.rho_oil = self.oil.density(self.m_oil, self.Tj, self.P) self.mu_oil = self.oil.viscosity(self.m_oil, self.Tj, self.P) self.sigma_oil = self.oil.interface_tension(self.m_oil, self.Tj, self.S, self.P)
# Create an ambient.Profile object for this dataset roms = ambient.Profile(nc, chem_names=['dye_01', 'dye_02']) # Close the netCDF dataset roms.nc.close() # Since the netCDF file is now fully stored on the hard drive in the # correct format, we can initialize an ambient.Profile object directly # from the netCDF file roms = ambient.Profile(nc_file, chem_names='all') # Plot the density profile using the interpolation function z = np.linspace(roms.nc.variables['z'].valid_min, roms.nc.variables['z'].valid_max, 250) rho = np.zeros(z.shape) tsp = roms.get_values(z, ['temperature', 'salinity', 'pressure']) for i in range(len(z)): rho[i] = seawater.density(tsp[i, 0], tsp[i, 1], tsp[i, 2]) fig = plt.figure() ax1 = plt.subplot(121) ax1.plot(rho, z) ax1.set_xlabel('Density (kg/m^3)') ax1.set_ylabel('Depth (m)') ax1.invert_yaxis() ax1.set_title('Computed data') plt.show() # Close the netCDF dataset roms.nc.close()
def get_profile(profile_fname, z0, D, uj, vj, wj, Tj, Sj, ua, T, F, sign_dp, H): """ Create and ambient profile dataset for an integral jet model simulation """ # Use mks units g = 9.81 rho_w = 1000. Pa = 101325. # Get the ambient density at the discharge from F (Assume water is # incompressible for these laboratory experiments) rho_j = seawater.density(Tj, Sj, 101325. + rho_w * g * z0) Vj = np.sqrt(uj**2 + vj**2 + wj**2) if F == 0.: rho_a = rho_j else: rho_a = rho_j / (1. - Vj**2 / (sign_dp * F**2 * D * g)) # Get the ambient stratification at the discharge from T if T == 0.: dpdz = 0. else: dpdz = sign_dp * (rho_a - rho_j) / (T * D) # Find the salinity at the discharge assuming the discharge temperature # matches the ambient temperature Ta = Tj def residual(Sa, rho, H): """ docstring for residual """ return rho - seawater.density(Ta, Sa, Pa + rho_w * g * H) Sa = fsolve(residual, 0., args=(rho_a, z0)) # Find the salinity at the top and bottom assuming linear stratification if dpdz == 0.: S0 = Sa SH = Sa else: rho_H = dpdz * (H - z0) + rho_a rho_0 = dpdz * (0. - z0) + rho_a # Use potential density to get the salinity SH = fsolve(residual, Sa, args=(rho_H, z0)) S0 = fsolve(residual, Sa, args=(rho_0, z0)) # Build the ambient data arrays z = np.array([0., H]) T = np.array([Ta, Ta]) S = np.array([S0, SH]) ua = np.array([ua, ua]) # Build the profile profile = build_profile(profile_fname, z, T, S, ua) # Return the ambient data return profile
def test_particle_obj(): """ Test the object behavior for the `Particle` object Test the instantiation and attribute data for the `Particle` object of the `single_bubble_model` module. """ # Set up the base parameters describing a particle object T = 273.15 + 15. P = 150e5 Sa = 35. Ta = 273.15 + 4. composition = ['methane', 'ethane', 'propane', 'oxygen'] yk = np.array([0.85, 0.07, 0.08, 0.0]) de = 0.005 K = 1. Kt = 1. fdis = 1e-6 # Compute a few derived quantities bub = dbm.FluidParticle(composition) m0 = bub.masses_by_diameter(de, T, P, yk) # Create a `SingleParticle` object bub_obj = dispersed_phases.SingleParticle(bub, m0, T, K, fdis=fdis, K_T=Kt) # Check if the initial attributes are correct for i in range(len(composition)): assert bub_obj.composition[i] == composition[i] assert_array_almost_equal(bub_obj.m0, m0, decimal=6) assert bub_obj.T0 == T assert bub_obj.cp == seawater.cp() * 0.5 assert bub_obj.K == K assert bub_obj.K_T == Kt assert bub_obj.fdis == fdis for i in range(len(composition) - 1): assert bub_obj.diss_indices[i] == True assert bub_obj.diss_indices[-1] == False # Check if the values returned by the `properties` method match the input (us, rho_p, A, Cs, beta, beta_T, T_ans) = bub_obj.properties(m0, T, P, Sa, Ta, 0.) us_ans = bub.slip_velocity(m0, T, P, Sa, Ta) rho_p_ans = bub.density(m0, T, P) A_ans = bub.surface_area(m0, T, P, Sa, Ta) Cs_ans = bub.solubility(m0, T, P, Sa) beta_ans = bub.mass_transfer(m0, T, P, Sa, Ta) beta_T_ans = bub.heat_transfer(m0, T, P, Sa, Ta) assert us == us_ans assert rho_p == rho_p_ans assert A == A_ans assert_array_almost_equal(Cs, Cs_ans, decimal=6) assert_array_almost_equal(beta, beta_ans, decimal=6) assert beta_T == beta_T_ans assert T == T_ans # Check that dissolution shuts down correctly m_dis = np.array([m0[0] * 1e-10, m0[1] * 1e-8, m0[2] * 1e-3, 1.5e-5]) (us, rho_p, A, Cs, beta, beta_T, T_ans) = bub_obj.properties(m_dis, T, P, Sa, Ta, 0) assert beta[0] == 0. assert beta[1] == 0. assert beta[2] > 0. assert beta[3] > 0. m_dis = np.array([m0[0] * 1e-10, m0[1] * 1e-8, m0[2] * 1e-7, 1.5e-16]) (us, rho_p, A, Cs, beta, beta_T, T_ans) = bub_obj.properties(m_dis, T, P, Sa, Ta, 0.) assert np.sum(beta[0:-1]) == 0. assert us == 0. assert rho_p == seawater.density(Ta, Sa, P) # Check that heat transfer shuts down correctly (us, rho_p, A, Cs, beta, beta_T, T_ans) = bub_obj.properties(m_dis, Ta, P, Sa, Ta, 0) assert beta_T == 0. (us, rho_p, A, Cs, beta, beta_T, T_ans) = bub_obj.properties(m_dis, T, P, Sa, Ta, 0) assert beta_T == 0. # Check the value returned by the `diameter` method de_p = bub_obj.diameter(m0, T, P, Sa, Ta) assert_approx_equal(de_p, de, significant=6) # Check functionality of insoluble particle drop = dbm.InsolubleParticle(isfluid=True, iscompressible=True) m0 = drop.mass_by_diameter(de, T, P, Sa, Ta) # Create a `Particle` object drop_obj = dispersed_phases.SingleParticle(drop, m0, T, K, fdis=fdis, K_T=Kt) # Check if the values returned by the `properties` method match the input (us, rho_p, A, Cs, beta, beta_T, T_ans) = drop_obj.properties(np.array([m0]), T, P, Sa, Ta, 0) us_ans = drop.slip_velocity(m0, T, P, Sa, Ta) rho_p_ans = drop.density(T, P, Sa, Ta) A_ans = drop.surface_area(m0, T, P, Sa, Ta) beta_T_ans = drop.heat_transfer(m0, T, P, Sa, Ta) assert us == us_ans assert rho_p == rho_p_ans assert A == A_ans assert beta_T == beta_T_ans # Check that heat transfer shuts down correctly (us, rho_p, A, Cs, beta, beta_T, T_ans) = drop_obj.properties(m_dis, Ta, P, Sa, Ta, 0) assert beta_T == 0. (us, rho_p, A, Cs, beta, beta_T, T_ans) = drop_obj.properties(m_dis, T, P, Sa, Ta, 0) assert beta_T == 0. # Check the value returned by the `diameter` method de_p = drop_obj.diameter(m0, T, P, Sa, Ta) assert_approx_equal(de_p, de, significant=6)
def outer_cpic(yi, yo, particles, profile, p, neighbor, z_0): """ Compute the initial condition for the outer plume at depth Computes the initial conditions for the an outer plume segment within the reservoir body. Part of the calculation determines whether or not the computed initial condition has enough downward momentum to be viable as an initial condition (e.g., whether or not it will be overwhelmed by the upward drag of the inner plume). Parameters ---------- yi : `stratified_plume_model.InnerPlume` object Object for manipulating the inner plume state space. yo : `stratified_plume_model.OuterPlume` object Object for manipulating the outer plume state space. particles : list of `Particle` objects List of `Particle` objects containing the dispersed phase local conditions and behavior. profile : `ambient.Profile` object The ambient CTD object used by the simulation. p : `ModelParams` object Object containing the fixed model parameters for the stratified plume model. neighbor : `scipy.interpolate.interp1d` object Container holding the latest solution for the inner plume state space. z_0 : float Top of the inner plume calculation (m). Returns ------- z0 : float Initial depth of the outer plume segment (m). y0 : ndarray Initial dependent variables state space for the outer plume segment. flag : bool Outer plume viability flag: `True` means the outer plume segment is viable and should be integrated; `False` means the outer plume segment is too weak and should be discarded, moving down the inner plume to calculate the next outer plume initial condition. Notes ----- The iteration required to find a viable outer plume segment is conducted by the `stratified_plume_model.outer_main` function. This function computes the initial conditions for one attempt to find an outer plume segment and reports back (through `flag`) on the success. There is one caveat to the above statement. The model parameter `p.nwidths` determines the vertical scale over which this function may integrate to find the start to an outer plume, given as a integer number of times of the inner plume half-width. This function starts by searching one half-width. If `p.nwidths` is greater than one, it will continue to expand the search region. The physical interpretation of `p.nwidths` is to set a reasonable upper bound on the diameter of eddies shed from the inner plume in the peeling region into the outer plume. While the integral model does not have "eddies" per se, the search window size should still be representative of this type of length scale. """ # Start the iteration counters iter = 0 done = False # Compute the outer plume initial conditions until the outer plume is # viable or until the maximum number of widths is integrated while not done and iter < p.nwidths: # Update iteration counter iter += 1 # Get the inner plume properties at the top of this peeling region yi.update(z_0, neighbor(z_0), particles, profile, p) # Set the range to integrate to get the current peeling flux z_upper = z_0 z_lower = z_0 + iter * yi.b # Check if the bottom of the reservoir is encountered. if z_lower > profile.z_max: z_lower = profile.z_max # Find the indices in the raw data for the inner plume solution close # to where z_upper and z_lower occur i_upper = np.min(np.where(neighbor.x >= z_upper)[0]) i_lower = np.max(np.where(neighbor.x <= z_lower)[0]) # Get the grid of elevations where we will integrate the solution to # obtain the initial flux for the outer plume. This is needed # because the solution is so stiff: if we integrated over a fixed # step size, we could easily miss dramatic changes in the solution. # Hence, we integrate over the steps in the numerical solution # itself. n_grid = i_lower - i_upper + 3 zi = np.zeros(n_grid) zi[0] = z_upper zi[-1] = z_lower zi[1:-1] = neighbor.x[i_upper:i_lower+1] # Integrate the peeling fluid over this grid to get the total # contributions going into the outer plume Q = 0. tracer_vars = np.zeros(2 + yi.nchems) for i in range(len(zi)-1): yi.update(zi[i], neighbor(zi[i]), particles, profile, p) dz = zi[i+1] - zi[i] Q = Q + yi.Ep * dz tracer_vars = tracer_vars + np.hstack((yi.s, yi.T * p.rho_r * \ seawater.cp(), yi.c)) * yi.Ep * dz # Get the initial velocity of the peeling fluid using the modified # outer plume Froude number condition T = tracer_vars[1] / (Q * p.rho_r * seawater.cp()) s = tracer_vars[0] / Q c = tracer_vars[2:] / Q rho = seawater.density(T, s, yi.P) u = outer_fr(0.05, -Q, yi.b, yi.rho_a, rho, p.g, p.Fro_0) b = np.sqrt(Q**2 / (np.pi * (-Q) * u) + yi.b**2) dQdz = 2. * np.pi * yi.b * (p.alpha_1 * (yi.u + p.c1 * (-u)) + \ p.alpha_2 * (-u)) + 2. * np.pi * b * p.alpha_3 * (-u) + yi.Ep # Check whether this outer plume segment will be viable if dQdz > 0 or Q > 0 or np.isnan(Q): # This outer plume segment is not viable flag = False z0 = np.array([z_0, z_lower]) y0 = np.array([np.zeros(yo.len), np.zeros(yo.len)]) else: # This outer plume segmet is viable...stop integrating widths done = True # Check where the diffuser is if z_lower >= yi.z0: # This outer plume segment should not exist flag = False z0 = np.array([z_0, z_lower]) y0 = np.array([np.zeros(yo.len), np.zeros(yo.len)]) else: # This is the next outer plume segment to integrate flag = True z0 = z_lower y0 = [] y0.append(Q) y0.append(Q * (-u)) y0.append(s * Q) y0.append(p.rho_r * seawater.cp() * T * Q) y0.extend(c * Q) # Return the results of the initial conditions search return (z0, np.array(y0), flag)
# Create a DBM FluidParticle object for this simple oil assuming zeros # for all the binary interaction coefficients delta = np.zeros((4,4)) oil = dbm.FluidParticle(composition, fl_type, delta) # Specify some generic deepwater ocean conditions P = 150.0 * 1.0e5 Ta = 273.15 + 4.0 Sa = 34.5 # Echo the ambient conditions to the screen print '\nAmbient conditions: \n' print ' P = %g (Pa)' % P print ' T = %g (K)' % Ta print ' S = %g (psu)' % Sa print ' rho_sw = %g (kg/m^3)' % (seawater.density(Ta, Sa, P)) # Get the general properties of the oil mf = oil.mass_frac(mol_frac) T = 273.15 + 60. print '\nBasic properties of liquid oil: \n' print ' T = %g (K)' % T print ' mol_frac = [' + ', '.join('%g' % mol_frac[i] for i in range(oil.nc)) + '] (--)' print ' mass_frac = [' + ', '.join('%g' % mf[i] for i in range(oil.nc)) + '] (--)' print ' rho_p = %g (kg/m^3) at %g (K) and %g (Pa)' % \ (oil.density(mf, T, P), T, P) # Get the masses in a 1.0 cm effective diameter droplet de = 0.01
iscompressible, gamma=gamma, beta=beta, co=co) # Specify some generic deepwater ocean conditions P = 150.0 * 1.0e5 Ta = 273.15 + 4.0 Sa = 34.5 # Echo the ambient conditions to the screen print '\nAmbient conditions: \n' print ' P = %g (Pa)' % P print ' T = %g (K)' % Ta print ' S = %g (psu)' % Sa print ' rho_sw = %g (kg/m^3)' % (seawater.density(Ta, Sa, P)) # Get the general properties of the oil T = 273.15 + 60. print '\nBasic properties of liquid oil: \n' print ' T = %g (K)' % T print ' rho_p = %g (kg/m^3) at %g (K) and %g (Pa)' % \ (oil.density(T, P, Sa, Ta), T, P) # Get the masses in a 1.0 cm effective diameter droplet de = 0.01 m = oil.mass_by_diameter(de, T, P, Sa, Ta) # Echo the properties of the droplet to the screen print '\nBasic droplet properties: \n' print ' de = %g (m)' % (oil.diameter(m, T, P, Sa, Ta))