示例#1
0
def test_ic():
    """
    Test the initial conditions function for the single bubble model
    
    Test that the initial conditions returned by `sbm_ic` are correct based
    on the input and expected output
    
    """
    # Set up the inputs
    profile = get_profile()
    T0 = 273.15 + 15.
    z0 = 1500.
    P = profile.get_values(z0, ['pressure'])
    composition = ['methane', 'ethane', 'propane', 'oxygen']
    bub = dbm.FluidParticle(composition)
    yk = np.array([0.85, 0.07, 0.08, 0.0])
    de = 0.005
    K = 1.
    K_T = 1.
    fdis = 1.e-4
    t_hyd = 0.
    lag_time = True

    # Get the initial conditions
    (bub_obj, y0) = single_bubble_model.sbm_ic(profile, bub,
                                               np.array([0., 0.,
                                                         z0]), de, yk, T0, K,
                                               K_T, fdis, t_hyd, lag_time)

    # Check the initial condition values
    assert y0[0] == 0.
    assert y0[1] == 0.
    assert y0[2] == z0
    assert y0[-1] == T0 * np.sum(y0[3:-1]) * seawater.cp() * 0.5
    assert_approx_equal(bub.diameter(y0[3:-1], T0, P), de, significant=6)

    # Check the bub_obj parameters
    for i in range(len(composition)):
        assert bub_obj.composition[i] == composition[i]
    assert bub_obj.T0 == T0
    assert bub_obj.cp == seawater.cp() * 0.5
    assert bub_obj.K == K
    assert bub_obj.K_T == K_T
    assert bub_obj.fdis == fdis
    assert bub_obj.t_hyd == t_hyd
    for i in range(len(composition) - 1):
        assert bub_obj.diss_indices[i] == True
    assert bub_obj.diss_indices[-1] == False
示例#2
0
def test_ic():
    """
    Test the initial conditions function for the single bubble model
    
    Test that the initial conditions returned by `sbm_ic` are correct based
    on the input and expected output
    
    """
    # Set up the inputs
    profile = get_profile()
    T0 = 273.15 + 15.
    z0 = 1500.
    P = profile.get_values(z0, ['pressure'])
    composition = ['methane', 'ethane', 'propane', 'oxygen']
    bub = dbm.FluidParticle(composition)
    yk = np.array([0.85, 0.07, 0.08, 0.0])
    de = 0.005
    K = 1.
    K_T = 1.
    fdis = 1.e-4
    t_hyd = 0.
    lag_time = True
    
    # Get the initial conditions
    (bub_obj, y0) = single_bubble_model.sbm_ic(profile, bub, 
                    np.array([0., 0., z0]), de, yk, T0, K, K_T, fdis, t_hyd,
                    lag_time)
    
    # Check the initial condition values
    assert y0[0] == 0.
    assert y0[1] == 0.
    assert y0[2] == z0
    assert y0[-1] == T0 * np.sum(y0[3:-1]) * seawater.cp() * 0.5
    assert_approx_equal(bub.diameter(y0[3:-1], T0, P), de, significant=6)
    
    # Check the bub_obj parameters
    for i in range(len(composition)):
        assert bub_obj.composition[i] == composition[i]
    assert bub_obj.T0 == T0
    assert bub_obj.cp == seawater.cp() * 0.5
    assert bub_obj.K == K
    assert bub_obj.K_T == K_T
    assert bub_obj.fdis == fdis
    assert bub_obj.t_hyd == t_hyd
    for i in range(len(composition)-1):
        assert bub_obj.diss_indices[i] == True
    assert bub_obj.diss_indices[-1] == False
示例#3
0
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))
示例#4
0
文件: smp.py 项目: socolofs/tamoc
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))
示例#5
0
文件: lmp.py 项目: changks/tamoc
def bent_plume_ic(profile, particles, Qj, A, D, X, phi_0, theta_0, Tj, Sj, Pj,
                  rho_j, cj, chem_names, tracers, p):
    """
    Build the Lagragian plume state space given the initial conditions
    
    Constructs the initial state space for a Lagrangian plume element from 
    the initial values for the base plume variables (e.g., Q, J, u, S, T, 
    etc.).
    
    Parameters
    ----------
    profile : `ambient.Profile` object
        The ambient CTD object used by the single bubble model simulation.
    particles : list of `Particle` objects
        List of `bent_plume_model.Particle` objects containing the dispersed 
        phase local conditions and behavior.
    Qj : Volume flux of continuous phase fluid at the discharge (m^3/s)
    A : Cross-sectional area of the discharge (M^2)
    D : float
        Diameter for the equivalent circular cross-section of the release 
        (m)
    X : ndarray
        Release location (x, y, z) in (m)
    phi_0 : float
        Vertical angle from the horizontal for the discharge orientation 
        (rad in range +/- pi/2)
    theta_0 : float
        Horizontal angle from the x-axis for the discharge orientation.  
        The x-axis is taken in the direction of the ambient current.  
        (rad in range 0 to 2 pi)
    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)
    cj : ndarray
        Concentration of passive tracers in the discharge (user-defined)
    chem_names : string list
        List of chemical parameters to track for the dissolution.  Only the 
        parameters in this list will be used to set background concentration
        for the dissolution, and the concentrations of these parameters are 
        computed separately from those listed in `tracers` or inputed from
        the discharge through `cj`.
    tracers : string list
        List of passive tracers in the discharge.  These can be chemicals 
        present in the ambient `profile` data, and if so, entrainment of 
        these chemicals will change the concentrations computed for these 
        tracers.  However, none of these concentrations are used in the 
        dissolution of the dispersed phase.  Hence, `tracers` should not 
        contain any chemicals present in the dispersed phase particles.
    p : `stratified_plume_model.ModelParams` object
        Object containing the fixed model parameters for the stratified 
        plume model.
    
    Returns
    -------
    t : float
        Initial time for the simulation (s)
    q : ndarray
        Initial value of the plume state space
    
    """

    # Set the dimensions of the initial Lagrangian plume element.
    b = D / 2.
    h = D / 5.

    # Measure the arc length along the plume
    s0 = 0.

    # The total discharge volume flux is the jet discharge since we assume
    # the void fraction of gas is negligible
    Q = Qj

    # Determine the time to fill the initial Lagrangian element
    dt = np.pi * b**2 * h / Q

    # Compute the mass of jet discharge in the initial Lagrangian element
    Mj = Qj * dt * rho_j

    # Evaluate the mass of particles in the intial Lagrangian element.  Since
    # particles are tracked by number and mass per particle, we need to know
    # how many particles enter the Lagrangian element.  This should be the
    # number flux in #/s time the fill time for the Lagrangian element, dt.
    # Store this value in the `Particle` objects for use throughout the model.
    nbe = np.zeros(len(particles))
    for i in range(len(particles)):
        nbe[i] = particles[i].nb0 * dt
        particles[i].nbe = nbe[i]

    # Get the velocity in the component directions
    Uj = flux_to_velocity(Qj, A, phi_0, theta_0)

    # Compute the magnitude of the exit velocity
    V = np.sqrt(Uj[0]**2 + Uj[1]**2 + Uj[2]**2)

    # Build the continuous-phase portion of the model state space vector
    t = 0.
    q = [
        Mj, Mj * Sj, Mj * seawater.cp() * Tj, Mj * Uj[0], Mj * Uj[1],
        Mj * Uj[2], h / V, X[0], X[1], X[2], s0
    ]

    # Add in the state space for the dispersed phase particles
    q.extend(dispersed_phases.particles_state_space(particles, nbe))

    # Add the ambient concentrations of the dispersed-phase chemicals
    ca = profile.get_values(X[2], chem_names)
    q.extend(Mj / rho_j * ca)

    # Add in the tracers discharged with the jet
    q.extend(Mj * cj)

    # Return the complete initial conditions
    return (t, np.array(q))
示例#6
0
def inner_plume_ic(profile, particles, p, z, Q, A, S, T, chem_names):
    """
    Build the inner plume state space given the initial conditions
    
    Constructs the state space for the inner plume from the initial values for
    Q, J, concentrations, and particle properties.  The state space vector is
    organized as follows:
    
        y[0] = Q : Flow rate of entrained fluid
        y[1] = J : Momentum flux of entrained fluid
        y[2] = S : Salinity flux of entrained fluid
        y[3] = H : Heat flux of entrained fluid
        y[4:4 + np * (nchems + 1)] : Dispersed phase mass and heat fluxes
        y[5 + np * (nchems + 1):] : Mass fluxes of the dissolved components
    
    Parameters
    ----------
    profile : `ambient.Profile` object
        The ambient CTD object used by the single bubble model simulation.
    particles : list of `Particle` objects
        List of `Particle` objects containing the dispersed phase initial
        conditions
    p : `stratified_plume_model.ModelParams` object
        Object containing the fixed model parameters for the stratified 
        plume model.
    z : float
        Depth of the release point (m)
    Q : float
        Initial volume flux of entrained seawater (m^3/s)
    A : float
        Cross-sectional area of the discharge (m^2)
    S : float
        Salinity of the entrained seawater (psu)
    T : float
        Temperature of the entrained seawater (K)
    chem_names : string list
        List of the names of the chemicals that will be tracked in the 
        dissolved phase
    
    Returns
    -------
    z : float
        Depth at the initial point of the plume (m)
    y : ndarray
        Initial inner plume state space (see description above)
    
    """
    # Sequentially build the inner plume state space
    y = [Q, Q**2 / A, S * Q, p.rho_r * seawater.cp() * T * Q]

    # Add in the state space of the multiphase components
    nb0 = np.zeros(len(particles))
    for i in range(len(particles)):
        nb0[i] = particles[i].nb0
    y.extend(dispersed_phases.particles_state_space(particles, nb0))

    # And the mass fluxes of dissolved components
    ca = profile.get_values(z, chem_names)
    y.extend(ca * Q)

    # Return the initial state space
    return (z, np.array(y))
示例#7
0
def derivs_inner(z, y, yi, yo, particles, profile, p, neighbor):
    """
    Calculate the derivatives for the system of ODEs for the inner plume
    
    Calculates the right-hand-side of the system of ODEs for the inner plume
    state space.  These equations follow Socolofsky et al. (2008) very 
    closely, with the exception that multiple dispersed phase particles are
    allowed within the inner plume.  Heat transfer between the dispersed
    and continuous phase is also added.
    
    Parameters
    ----------
    z : float
        Current value for the independent variable (depth in m).
    y : ndarray
        Current value for the inner plume state space vector.
    yi : `InnerPlume`
        Object for manipulating the inner plume state space
    yo : `OuterPlume`
        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 outer plume state
        space
    
    Returns
    -------
    yp : ndarray
        A vector of the derivatives of the inner plume state space.
    
    See Also
    --------
    stratified_plume_model.InnerPlume, stratified_plume_model.OuterPlume, 
    stratified_plume_model.inner_main, calculate
    
    Notes
    -----
    It is important that the inner plume entrains fluid from either the 
    ambient water (whenever the outer plume is not present) or the outer 
    plume (whenever it is shrouding the inner plume).  This is accomplished
    in `stratified_plume_model.OuterPlume`:  if there is no outer plume 
    segment, then the ambient conditions are stored in the outer plume 
    variables.  Thus, `yo.c[i]` is equivalent to `ca[i]` when there is no 
    outer plume.  This behavior is true for temperature, salinity, density
    and concentration.
    
    """
    # Set up the output from the fuction to have the right size and type
    yp = np.zeros((yi.len, 1))

    # Update the inner plume object with the corrent solution and compute
    # the inner plume shear entrainment coefficient
    yi.update(z, y, particles, profile, p)

    # Update the outer plume object at the current depth
    if z < np.min(neighbor.x):
        # This plume is above any existing outer plumes
        yo.update(z, np.zeros(yo.len), profile, p, yi.b)
    else:
        # Interpolate the outer plume solution to the current depth
        yo.update(z, neighbor(z), profile, p, yi.b)

    # Conservation of mass
    yp[0] = 2. * np.pi * yi.b * (yi.alpha_s * (yi.u + p.c1 * yo.u) + \
            p.alpha_2 * yo.u) + yi.Ep

    # Conservation of momentum
    yp[1] = 1. / p.gamma_i * (np.pi * p.g * yi.b**2 / p.rho_r * \
            (yi.Fb + p.lambda_2**2 * (1. - yi.Xi) * (yi.rho_a - \
            yi.rho)) + 2. * np.pi * yi.b * (yi.alpha_s * (yi.u + p.c1 * \
            yo.u) * yo.u + p.alpha_2 * yo.u * yi.u) + yi.Ep * yi.u)

    # Conservation of salinity
    yp[2] = 2. * np.pi * yi.b * (yi.alpha_s * (yi.u + p.c1 * yo.u) * \
            yo.s + p.alpha_2 * yo.u * yi.s) + yi.Ep * yi.s

    # Conservation of continuous phase fluid heat
    yp[3] = p.rho_r * seawater.cp() * (2. * np.pi * yi.b * (yi.alpha_s * \
            (yi.u + p.c1 * yo.u) * yo.T + p.alpha_2 * yo.u * yi.T) + \
            yi.Ep * yi.T)

    # Conservation equations for each dispersed phase
    idx = 4

    # Track the mass dissolving into the continuous phase
    delDiss = np.zeros(yi.nchems)
    for i in range(yi.np):
        delDiss_p = np.zeros(yi.nchems)

        if particles[i].particle.issoluble:
            for j in range(yi.nchems):

                # Conservation of particle mass for soluble particles
                yp[idx] = -(particles[i].A * particles[i].nb0 /
                            (yi.u + particles[i].us) * particles[i].beta[j] *
                            (particles[i].Cs[j] - yi.c[j]))
                delDiss[j] += yp[idx]
                delDiss_p[j] += yp[idx]

                # Update continuous phase temperature with heat of solution
                yp[3] += yp[idx] * particles[i].particle.neg_dH_solR[j] * p.Ru / \
                        particles[i].particle.M[j]
                idx += 1

        else:
            # Conservation of particle mass for insoluble particles
            yp[idx] = 0.
            idx += 1

        # Conservation of particle heat including dissolution mass transfer
        yp[idx] = -particles[i].A * particles[i].nb0 / (yi.u +
                  particles[i].us) * particles[i].rho_p * particles[i].cp * \
                  particles[i].beta_T * (particles[i].T - yi.T) + \
                  np.sum(delDiss_p) * particles[i].cp * particles[i].T

        # Take the heat leaving the particle and put it in the continuous
        # phase fluid
        yp[3] -= yp[idx]
        idx += 1

        # Track the age of each particle by following its advection
        if yi.u + particles[i].us == 0.:
            yp[idx] = 0.
        else:
            yp[idx] = 1. / (yi.u + particles[i].us)
        idx += 1

        # Track the location of each particle relative to the plume centerline
        yp[idx:idx + 3] = 0.
        idx += 3

    # Conservation equations for the dissolved constituents.
    for i in range(yi.nchems):
        yp[idx] = 2. * np.pi * yi.b * (yi.alpha_s * (yi.u + p.c1 * yo.u) * \
                  yo.c[i] + p.alpha_2 * yo.u * yi.c[i]) + yi.Ep * \
                  yi.c[i] - delDiss[i]
        idx += 1

    # z is positive downward (depth)
    return -yp
示例#8
0
def derivs_outer(z, y, yi, yo, particles, profile, p, neighbor):
    """
    Calculate the derivatives for the system of ODEs for the outer plume
    
    Calculates the right-hand-side of the system of ODEs for the outer plume
    state space.  These equations follow those in Socolofsky et al. (2008).
    
    Parameters
    ----------
    z : float
        Current value for the independent variable (depth in m).
    y : ndarray
        Current value for the outer plume state space vector.
    yi : `InnerPlume`
        Object for manipulating the inner plume state space
    yo : `OuterPlume`
        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 outer plume state
        space
    
    Returns
    -------
    yp : ndarray
        A vector of the derivatives of the outer plume state space.
    
    See Also
    --------
    stratified_plume_model.InnerPlume, stratified_plume_model.OuterPlume, 
    stratified_plume_model.outer_main, calculate
    
    """
    # Set up the output from the function to have the correct size and type
    yp = np.zeros((yo.len, 1))

    # Update the inner plume object at the current depth and the inner plume
    # shear entrainment coefficient
    if z > np.max(neighbor.x):
        yi.update(z, np.zeros(yi.len), particles, profile, p)
    else:
        yi.update(z, neighbor(z), particles, profile, p)

    # Update the outer plume object with the current solution
    yo.update(z, y, profile, p, yi.b)

    # Conservation of Mass:
    yp[0] = 2. * np.pi * yi.b * (yi.alpha_s * (yi.u + p.c1 * yo.u) + \
            p.alpha_2 * yo.u) + 2. * np.pi * yo.b * p.alpha_3 * yo.u + yi.Ep

    # Conservation of Momentum:
    yp[1] = 1. / p.gamma_o * (-np.pi * p.g * (yo.b**2 - yi.b**2) / p.rho_r * \
            (yo.rho_a - yo.rho) + 2. * np.pi * yi.b * (yi.alpha_s * (yi.u + \
            p.c1 * yo.u) * yo.u + p.alpha_2 * yo.u * yi.u) + yi.Ep * yi.u)

    # Conservation of Salinity:
    yp[2] = 2. * np.pi * yi.b * (yi.alpha_s * (yi.u + p.c1 * yo.u) * yo.s + \
            p.alpha_2 * yo.u * yi.s) + 2. * np.pi * yo.b * p.alpha_3 * \
            yo.u * yo.Sa + yi.Ep * yi.s

    # Conservation of Heat:
    yp[3] = p.rho_r * seawater.cp() * (2. * np.pi * yi.b * (yi.alpha_s * \
            (yi.u + p.c1 * yo.u) * yo.T + p.alpha_2 * yo.u * yi.T) + \
            2. * np.pi * yo.b * p.alpha_3 * yo.u * yo.Ta + yi.Ep * yi.T)

    # Conservation of tracked chemical constituents:
    idx = 4
    for i in range(yo.nchems):
        yp[idx] = 2. * np.pi * yi.b * (yi.alpha_s * (yi.u + p.c1 * yo.u) * \
                yo.c[i] + p.alpha_2 * yo.u * yi.c[i]) + 2. * np.pi * yo.b * \
                p.alpha_3 * yo.u * yo.ca[i] + yi.Ep * yi.c[i]
        idx += 1

    # z is positive downward (depth)
    return yp
示例#9
0
文件: test_spm.py 项目: changks/tamoc
def test_particle_obj():
    """
    Test the object behavior for the `PlumeParticle` object
    
    Test the instantiation and attribute data for the `PlumeParticle` object 
    of the `stratified_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

    # 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 = dispersed_phases.PlumeParticle(bub, m0, T, nb0, lambda_1, P, Sa,
                                             Ta, K, Kt, fdis)

    # Check if the initialized 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_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

    # No need to test the properties or diameter objects since they are
    # inherited from the `single_bubble_model` and tested in `test_sbm`.

    # Check functionality of insoluble particle
    drop = dbm.InsolubleParticle(isfluid=True, iscompressible=True)
    m0 = drop.mass_by_diameter(de, T, P, Sa, Ta)
    drop_obj = dispersed_phases.PlumeParticle(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
示例#10
0
文件: smp.py 项目: socolofs/tamoc
def derivs_outer(z, y, yi, yo, particles, profile, p, neighbor):
    """
    Calculate the derivatives for the system of ODEs for the outer plume
    
    Calculates the right-hand-side of the system of ODEs for the outer plume
    state space.  These equations follow those in Socolofsky et al. (2008).
    
    Parameters
    ----------
    z : float
        Current value for the independent variable (depth in m).
    y : ndarray
        Current value for the outer plume state space vector.
    yi : `InnerPlume`
        Object for manipulating the inner plume state space
    yo : `OuterPlume`
        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 outer plume state
        space
    
    Returns
    -------
    yp : ndarray
        A vector of the derivatives of the outer plume state space.
    
    See Also
    --------
    stratified_plume_model.InnerPlume, stratified_plume_model.OuterPlume, 
    stratified_plume_model.outer_main, calculate
    
    """
    # Set up the output from the function to have the correct size and type
    yp = np.zeros((yo.len,1))
    
    # Update the inner plume object at the current depth and the inner plume
    # shear entrainment coefficient
    if z > np.max(neighbor.x):
        yi.update(z, np.zeros(yi.len), particles, profile, p)
    else:
        yi.update(z, neighbor(z), particles, profile, p)
    
    # Update the outer plume object with the current solution
    yo.update(z, y, profile, p, yi.b)
    
    # Conservation of Mass:
    yp[0] = 2. * np.pi * yi.b * (yi.alpha_s * (yi.u + p.c1 * yo.u) + \
            p.alpha_2 * yo.u) + 2. * np.pi * yo.b * p.alpha_3 * yo.u + yi.Ep
    
    # Conservation of Momentum:
    yp[1] = 1. / p.gamma_o * (-np.pi * p.g * (yo.b**2 - yi.b**2) / p.rho_r * \
            (yo.rho_a - yo.rho) + 2. * np.pi * yi.b * (yi.alpha_s * (yi.u + \
            p.c1 * yo.u) * yo.u + p.alpha_2 * yo.u * yi.u) + yi.Ep * yi.u)
    
    # Conservation of Salinity:
    yp[2] = 2. * np.pi * yi.b * (yi.alpha_s * (yi.u + p.c1 * yo.u) * yo.s + \
            p.alpha_2 * yo.u * yi.s) + 2. * np.pi * yo.b * p.alpha_3 * \
            yo.u * yo.Sa + yi.Ep * yi.s
    
    # Conservation of Heat:
    yp[3] = p.rho_r * seawater.cp() * (2. * np.pi * yi.b * (yi.alpha_s * \
            (yi.u + p.c1 * yo.u) * yo.T + p.alpha_2 * yo.u * yi.T) + \
            2. * np.pi * yo.b * p.alpha_3 * yo.u * yo.Ta + yi.Ep * yi.T)
    
    # Conservation of tracked chemical constituents:
    idx = 4
    for i in range(yo.nchems):
        yp[idx] = 2. * np.pi * yi.b * (yi.alpha_s * (yi.u + p.c1 * yo.u) * \
                yo.c[i] + p.alpha_2 * yo.u * yi.c[i]) + 2. * np.pi * yo.b * \
                p.alpha_3 * yo.u * yo.ca[i] + yi.Ep * yi.c[i]
        idx += 1
    
    # z is positive downward (depth)
    return yp
示例#11
0
文件: lmp.py 项目: socolofs/tamoc
def bent_plume_ic(profile, particles, Qj, A, D, X, phi_0, theta_0, Tj, Sj, 
                  Pj, rho_j, cj, chem_names, tracers, p):
    """
    Build the Lagragian plume state space given the initial conditions
    
    Constructs the initial state space for a Lagrangian plume element from 
    the initial values for the base plume variables (e.g., Q, J, u, S, T, 
    etc.).
    
    Parameters
    ----------
    profile : `ambient.Profile` object
        The ambient CTD object used by the single bubble model simulation.
    particles : list of `Particle` objects
        List of `bent_plume_model.Particle` objects containing the dispersed 
        phase local conditions and behavior.
    Qj : Volume flux of continuous phase fluid at the discharge (m^3/s)
    A : Cross-sectional area of the discharge (M^2)
    D : float
        Diameter for the equivalent circular cross-section of the release 
        (m)
    X : ndarray
        Release location (x, y, z) in (m)
    phi_0 : float
        Vertical angle from the horizontal for the discharge orientation 
        (rad in range +/- pi/2)
    theta_0 : float
        Horizontal angle from the x-axis for the discharge orientation.  
        The x-axis is taken in the direction of the ambient current.  
        (rad in range 0 to 2 pi)
    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)
    cj : ndarray
        Concentration of passive tracers in the discharge (user-defined)
    chem_names : string list
        List of chemical parameters to track for the dissolution.  Only the 
        parameters in this list will be used to set background concentration
        for the dissolution, and the concentrations of these parameters are 
        computed separately from those listed in `tracers` or inputed from
        the discharge through `cj`.
    tracers : string list
        List of passive tracers in the discharge.  These can be chemicals 
        present in the ambient `profile` data, and if so, entrainment of 
        these chemicals will change the concentrations computed for these 
        tracers.  However, none of these concentrations are used in the 
        dissolution of the dispersed phase.  Hence, `tracers` should not 
        contain any chemicals present in the dispersed phase particles.
    p : `stratified_plume_model.ModelParams` object
        Object containing the fixed model parameters for the stratified 
        plume model.
    
    Returns
    -------
    t : float
        Initial time for the simulation (s)
    q : ndarray
        Initial value of the plume state space
    
    """
    
    # Set the dimensions of the initial Lagrangian plume element.
    b = D / 2.
    h = D / 5.
    
    # Measure the arc length along the plume
    s0 = 0.
    
    # The total discharge volume flux is the jet discharge since we assume
    # the void fraction of gas is negligible
    Q = Qj
    
    # Determine the time to fill the initial Lagrangian element
    dt = np.pi * b**2 * h / Q
    
    # Compute the mass of jet discharge in the initial Lagrangian element
    Mj = Qj * dt * rho_j
    
    # Evaluate the mass of particles in the intial Lagrangian element.  Since
    # particles are tracked by number and mass per particle, we need to know
    # how many particles enter the Lagrangian element.  This should be the 
    # number flux in #/s time the fill time for the Lagrangian element, dt.
    # Store this value in the `Particle` objects for use throughout the model.
    nbe = np.zeros(len(particles))
    for i in range(len(particles)):
        nbe[i] = particles[i].nb0 * dt
        particles[i].nbe = nbe[i]
    
    # Get the velocity in the component directions
    Uj = flux_to_velocity(Qj, A, phi_0, theta_0)
    
    # Compute the magnitude of the exit velocity 
    V = np.sqrt(Uj[0]**2 + Uj[1]**2 + Uj[2]**2)
    
    # Build the continuous-phase portion of the model state space vector
    t = 0.
    q = [Mj, Mj * Sj, Mj * seawater.cp() * Tj, Mj * Uj[0], Mj * Uj[1], 
          Mj * Uj[2], h / V, X[0], X[1], X[2], s0]
    
    # Add in the state space for the dispersed phase particles
    q.extend(dispersed_phases.particles_state_space(particles, nbe))
    
    # Add the ambient concentrations of the dispersed-phase chemicals
    ca = profile.get_values(X[2], chem_names)
    q.extend(Mj / rho_j * ca)
    
    # Add in the tracers discharged with the jet
    q.extend(Mj*cj)
    
    # Return the complete initial conditions
    return (t, np.array(q))
示例#12
0
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)
示例#13
0
def outer_dis(yi, particles, profile, p, neighbor, z_0):
    """
    Compute the initial condition for the outer plume at the DMPR
    
    Computes the initial conditions for the an outer plume segment at the 
    depth of maximum plume rise (DMPR) following full dissolution of the 
    dispersed phases.  
    
    Parameters
    ----------
    yi : `stratified_plume_model.InnerPlume` object
        Object for manipulating the inner 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.
    
    """
    # Search for the maximum flux near the top of the plume
    Qmax = neighbor.y[0, 0]
    imax = 1
    while Qmax < neighbor.y[imax, 0]:
        Qmax = neighbor.y[imax, 0]
        imax += 1

    # Since most of this fluid will be regained as the outer plume descends
    # through Ep, take the initial volume flux as a small fraction (given
    # by model parameter qdis_ic)
    Q = p.qdis_ic * Qmax

    # Get the local plume properties at the top of the plume
    yi.update(z_0, neighbor(z_0), particles, profile, p)
    rho = yi.rho

    # Use a Froude number approach to set the initial width and velocity
    u = outer_fr(0.05, 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(yi.s * Q)
    y0.append(p.rho_r * seawater.cp() * yi.T * Q)
    y0.extend(yi.c * Q)

    # Return the outer plume initial condition
    return (yi.z, np.array(y0))
示例#14
0
文件: lmp.py 项目: changks/tamoc
def derivs(t, q, q0_local, q1_local, profile, p, particles):
    """
    Calculate the derivatives for the system of ODEs for a Lagrangian plume
    
    Calculates the right-hand-side of the system of ODEs for a Lagrangian 
    plume integral model.  The continuous phase model matches very closely 
    the model of Lee and Cheung (1990), with adaptations for the shear 
    entrainment following Jirka (2004).  Multiphase extensions following the
    strategy in Socolofsky et al. (2008) with adaptation to Lagrangian plume
    models by Johansen (2000, 2003) and Yapa and Zheng (1997).  
    
    Parameters
    ----------
    t : float
        Current value for the independent variable (time in s).
    q : ndarray
        Current value for the plume state space vector.
    q0_local : `bent_plume_model.LagElement`
        Object containing the numerical solution at the previous time step
    q1_local : `bent_plume_model.LagElement`
        Object containing the numerical solution at the current time step
    profile : `ambient.Profile` object
        The ambient CTD object used by the simulation.
    p : `ModelParams` object
        Object containing the fixed model parameters for the bent
        plume model.
    particles : list of `Particle` objects
        List of `bent_plume_model.Particle` objects containing the dispersed 
        phase local conditions and behavior.
    
    Returns
    -------
    yp : ndarray
        A vector of the derivatives of the plume state space.
    
    See Also
    --------
    calculate
    
    """
    # Set up the output from the function to have the right size and shape
    qp = np.zeros(q.shape)

    # Update the local Lagrangian element properties
    q1_local.update(t, q, profile, p, particles)

    # Get the entrainment flux
    md = entrainment(q0_local, q1_local, p)

    # Get the dispersed phase tracking variables
    (fe, up, dtp_dt) = track_particles(q0_local, q1_local, md, particles)

    # Conservation of Mass
    qp[0] = md

    # Conservation of salt and heat
    qp[1] = md * q1_local.Sa
    qp[2] = md * seawater.cp() * q1_local.Ta

    # Conservation of continuous phase momentum.  Note that z is positive
    # down (depth).
    qp[3] = md * q1_local.ua
    qp[4] = md * q1_local.va
    qp[5] = -p.g / (p.gamma * p.rho_r) * (
        q1_local.Fb + q1_local.M *
        (q1_local.rho_a - q1_local.rho)) + md * q1_local.wa

    # Constant h/V thickeness to velocity ratio
    qp[6] = 0.

    # Lagrangian plume element advection (x, y, z) and s along the centerline
    # trajectory
    qp[7] = q1_local.u
    qp[8] = q1_local.v
    qp[9] = q1_local.w
    qp[10] = q1_local.V

    # Conservation equations for each dispersed phase
    idx = 11

    # Track the mass dissolving into the continuous phase per unit time
    dm = np.zeros(q1_local.nchems)

    # Compute mass and heat transfer for each particle
    for i in range(len(particles)):

        # Only simulate particles inside the plume
        if particles[i].integrate:

            # Dissolution and Biodegradation
            if particles[i].particle.issoluble:
                # Dissolution mass transfer for each particle component
                dm_pc = - particles[i].A * particles[i].nbe * \
                           particles[i].beta * (particles[i].Cs -
                           q1_local.c_chems) * dtp_dt[i]

                # Update continuous phase temperature with heat of
                # solution
                qp[2] += np.sum(dm_pc * \
                         particles[i].particle.neg_dH_solR \
                         * p.Ru / particles[i].particle.M)

                # Biodegradation for for each particle component
                dm_pb = -particles[i].k_bio * particles[i].m * \
                     particles[i].nbe * dtp_dt[i]

                # Conservation of mass for dissolution and biodegradation
                qp[idx:idx + q1_local.nchems] = dm_pc + dm_pb

                # Update position in state space
                idx += q1_local.nchems

            else:
                # No dissolution
                dm_pc = np.zeros(q1_local.nchems)
                # Biodegradation for insoluble particles
                dm_pb = -particles[i].k_bio * particles[i].m * \
                    particles[i].nbe * dtp_dt[i]
                qp[idx] = dm_pb
                idx += 1

            # Update the total mass dissolved
            dm += dm_pc

            # Heat transfer between the particle and the ambient
            qp[idx] = - particles[i].A * particles[i].nbe * \
                        particles[i].rho_p * particles[i].cp * \
                        particles[i].beta_T * (particles[i].T - \
                        q1_local.T) * dtp_dt[i]

            # Heat loss due to mass loss
            qp[idx] += np.sum(dm_pc + dm_pb) * particles[i].cp * \
                       particles[i].T

            # Take the heat leaving the particle and put it in the continuous
            # phase fluid
            qp[2] -= qp[idx]
            idx += 1

            # Particle age
            qp[idx] = dtp_dt[i]
            idx += 1

            # Follow the particles in the local coordinate system (l,n,m)
            # relative to the plume centerline
            qp[idx] = 0.
            idx += 1
            qp[idx] = (up[i, 1] - fe * q[idx]) * dtp_dt[i]
            idx += 1
            qp[idx] = (up[i, 2] - fe * q[idx]) * dtp_dt[i]
            idx += 1

        else:
            idx += particles[i].particle.nc + 5

    # Conservation equations for the dissolved constituents in the plume
    qp[idx:idx+q1_local.nchems] = md / q1_local.rho_a * q1_local.ca_chems \
        - dm - q1_local.k_bio * q1_local.cpe
    idx += q1_local.nchems

    # Conservation equation for the passive tracers in the plume
    qp[idx:] = md / q1_local.rho_a * q1_local.ca_tracers

    # Return the slopes
    return qp
示例#15
0
文件: smp.py 项目: socolofs/tamoc
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)
示例#16
0
文件: smp.py 项目: socolofs/tamoc
def outer_dis(yi, particles, profile, p, neighbor, z_0):
    """
    Compute the initial condition for the outer plume at the DMPR
    
    Computes the initial conditions for the an outer plume segment at the 
    depth of maximum plume rise (DMPR) following full dissolution of the 
    dispersed phases.  
    
    Parameters
    ----------
    yi : `stratified_plume_model.InnerPlume` object
        Object for manipulating the inner 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.
    
    """
    # Search for the maximum flux near the top of the plume
    Qmax = neighbor.y[0,0]
    imax = 1
    while Qmax < neighbor.y[imax,0]:
        Qmax = neighbor.y[imax,0]
        imax += 1
    
    # Since most of this fluid will be regained as the outer plume descends
    # through Ep, take the initial volume flux as a small fraction (given
    # by model parameter qdis_ic)
    Q = p.qdis_ic * Qmax
    
    # Get the local plume properties at the top of the plume
    yi.update(z_0, neighbor(z_0), particles, profile, p)
    rho = yi.rho
    
    # Use a Froude number approach to set the initial width and velocity
    u = outer_fr(0.05, 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(yi.s * Q)
    y0.append(p.rho_r * seawater.cp() * yi.T * Q)
    y0.extend(yi.c * Q)
    
    # Return the outer plume initial condition
    return (yi.z, np.array(y0))
示例#17
0
文件: smp.py 项目: socolofs/tamoc
def inner_plume_ic(profile, particles, p, z, Q, A, S, T, chem_names):
    """
    Build the inner plume state space given the initial conditions
    
    Constructs the state space for the inner plume from the initial values for
    Q, J, concentrations, and particle properties.  The state space vector is
    organized as follows:
    
        y[0] = Q : Flow rate of entrained fluid
        y[1] = J : Momentum flux of entrained fluid
        y[2] = S : Salinity flux of entrained fluid
        y[3] = H : Heat flux of entrained fluid
        y[4:4 + np * (nchems + 1)] : Dispersed phase mass and heat fluxes
        y[5 + np * (nchems + 1):] : Mass fluxes of the dissolved components
    
    Parameters
    ----------
    profile : `ambient.Profile` object
        The ambient CTD object used by the single bubble model simulation.
    particles : list of `Particle` objects
        List of `Particle` objects containing the dispersed phase initial
        conditions
    p : `stratified_plume_model.ModelParams` object
        Object containing the fixed model parameters for the stratified 
        plume model.
    z : float
        Depth of the release point (m)
    Q : float
        Initial volume flux of entrained seawater (m^3/s)
    A : float
        Cross-sectional area of the discharge (m^2)
    S : float
        Salinity of the entrained seawater (psu)
    T : float
        Temperature of the entrained seawater (K)
    chem_names : string list
        List of the names of the chemicals that will be tracked in the 
        dissolved phase
    
    Returns
    -------
    z : float
        Depth at the initial point of the plume (m)
    y : ndarray
        Initial inner plume state space (see description above)
    
    """
    # Sequentially build the inner plume state space
    y = [Q, Q**2 / A, S * Q, p.rho_r * seawater.cp() * T * Q]
    
    # Add in the state space of the multiphase components
    nb0 = np.zeros(len(particles))
    for i in range(len(particles)):
        nb0[i] = particles[i].nb0
    y.extend(dispersed_phases.particles_state_space(particles, nb0))
    
    # And the mass fluxes of dissolved components
    ca = profile.get_values(z, chem_names)
    y.extend(ca * Q)
    
    # Return the initial state space
    return (z, np.array(y))
示例#18
0
文件: smp.py 项目: socolofs/tamoc
def derivs_inner(z, y, yi, yo, particles, profile, p, neighbor):
    """
    Calculate the derivatives for the system of ODEs for the inner plume
    
    Calculates the right-hand-side of the system of ODEs for the inner plume
    state space.  These equations follow Socolofsky et al. (2008) very 
    closely, with the exception that multiple dispersed phase particles are
    allowed within the inner plume.  Heat transfer between the dispersed
    and continuous phase is also added.
    
    Parameters
    ----------
    z : float
        Current value for the independent variable (depth in m).
    y : ndarray
        Current value for the inner plume state space vector.
    yi : `InnerPlume`
        Object for manipulating the inner plume state space
    yo : `OuterPlume`
        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 outer plume state
        space
    
    Returns
    -------
    yp : ndarray
        A vector of the derivatives of the inner plume state space.
    
    See Also
    --------
    stratified_plume_model.InnerPlume, stratified_plume_model.OuterPlume, 
    stratified_plume_model.inner_main, calculate
    
    Notes
    -----
    It is important that the inner plume entrains fluid from either the 
    ambient water (whenever the outer plume is not present) or the outer 
    plume (whenever it is shrouding the inner plume).  This is accomplished
    in `stratified_plume_model.OuterPlume`:  if there is no outer plume 
    segment, then the ambient conditions are stored in the outer plume 
    variables.  Thus, `yo.c[i]` is equivalent to `ca[i]` when there is no 
    outer plume.  This behavior is true for temperature, salinity, density
    and concentration.
    
    """
    # Set up the output from the fuction to have the right size and type
    yp = np.zeros((yi.len, 1))
    
    # Update the inner plume object with the corrent solution and compute
    # the inner plume shear entrainment coefficient
    yi.update(z, y, particles, profile, p)
    
    # Update the outer plume object at the current depth
    if z < np.min(neighbor.x):
        # This plume is above any existing outer plumes
        yo.update(z, np.zeros(yo.len), profile, p, yi.b)
    else:
        # Interpolate the outer plume solution to the current depth
        yo.update(z, neighbor(z), profile, p, yi.b)
    
    # Conservation of mass
    yp[0] = 2. * np.pi * yi.b * (yi.alpha_s * (yi.u + p.c1 * yo.u) + \
            p.alpha_2 * yo.u) + yi.Ep
            
    # Conservation of momentum
    yp[1] = 1. / p.gamma_i * (np.pi * p.g * yi.b**2 / p.rho_r * \
            (yi.Fb + p.lambda_2**2 * (1. - yi.Xi) * (yi.rho_a - \
            yi.rho)) + 2. * np.pi * yi.b * (yi.alpha_s * (yi.u + p.c1 * \
            yo.u) * yo.u + p.alpha_2 * yo.u * yi.u) + yi.Ep * yi.u)
            
    # Conservation of salinity
    yp[2] = 2. * np.pi * yi.b * (yi.alpha_s * (yi.u + p.c1 * yo.u) * \
            yo.s + p.alpha_2 * yo.u * yi.s) + yi.Ep * yi.s
            
    # Conservation of continuous phase fluid heat
    yp[3] = p.rho_r * seawater.cp() * (2. * np.pi * yi.b * (yi.alpha_s * \
            (yi.u + p.c1 * yo.u) * yo.T + p.alpha_2 * yo.u * yi.T) + \
            yi.Ep * yi.T)
            
    # Conservation equations for each dispersed phase
    idx = 4
    
    # Track the mass dissolving into the continuous phase
    delDiss = np.zeros(yi.nchems)
    for i in range(yi.np):
        delDiss_p = np.zeros(yi.nchems)
        
        if particles[i].particle.issoluble:
            for j in range(yi.nchems):
                
                # Conservation of particle mass for soluble particles 
                yp[idx] = -(particles[i].A * particles[i].nb0 / (yi.u + 
                            particles[i].us) * particles[i].beta[j] * 
                            (particles[i].Cs[j] - yi.c[j]))
                delDiss[j] += yp[idx]
                delDiss_p[j] += yp[idx]
                
                # Update continuous phase temperature with heat of solution
                yp[3] += yp[idx] * particles[i].particle.neg_dH_solR[j] * p.Ru / \
                        particles[i].particle.M[j]
                idx += 1
            
        else:
            # Conservation of particle mass for insoluble particles
            yp[idx] = 0.
            idx += 1
        
        # Conservation of particle heat including dissolution mass transfer
        yp[idx] = -particles[i].A * particles[i].nb0 / (yi.u + 
                  particles[i].us) * particles[i].rho_p * particles[i].cp * \
                  particles[i].beta_T * (particles[i].T - yi.T) + \
                  np.sum(delDiss_p) * particles[i].cp * particles[i].T
        
        # Take the heat leaving the particle and put it in the continuous 
        # phase fluid
        yp[3] -= yp[idx] 
        idx += 1
        
        # Track the age of each particle by following its advection
        if yi.u + particles[i].us == 0.:
            yp[idx] = 0.
        else:
            yp[idx] = 1. / (yi.u + particles[i].us)
        idx += 1
        
        # Track the location of each particle relative to the plume centerline
        yp[idx:idx+3] = 0.
        idx += 3
    
    # Conservation equations for the dissolved constituents.
    for i in range(yi.nchems):
        yp[idx] = 2. * np.pi * yi.b * (yi.alpha_s * (yi.u + p.c1 * yo.u) * \
                  yo.c[i] + p.alpha_2 * yo.u * yi.c[i]) + yi.Ep * \
                  yi.c[i] - delDiss[i]
        idx += 1
    
    # z is positive downward (depth)
    return -yp
示例#19
0
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)
示例#20
0
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
示例#21
0
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)
示例#22
0
文件: lmp.py 项目: socolofs/tamoc
def derivs(t, q, q0_local, q1_local, profile, p, particles):
    """
    Calculate the derivatives for the system of ODEs for a Lagrangian plume
    
    Calculates the right-hand-side of the system of ODEs for a Lagrangian 
    plume integral model.  The continuous phase model matches very closely 
    the model of Lee and Cheung (1990), with adaptations for the shear 
    entrainment following Jirka (2004).  Multiphase extensions following the
    strategy in Socolofsky et al. (2008) with adaptation to Lagrangian plume
    models by Johansen (2000, 2003) and Yapa and Zheng (1997).  
    
    Parameters
    ----------
    t : float
        Current value for the independent variable (time in s).
    q : ndarray
        Current value for the plume state space vector.
    q0_local : `bent_plume_model.LagElement`
        Object containing the numerical solution at the previous time step
    q1_local : `bent_plume_model.LagElement`
        Object containing the numerical solution at the current time step
    profile : `ambient.Profile` object
        The ambient CTD object used by the simulation.
    p : `ModelParams` object
        Object containing the fixed model parameters for the bent
        plume model.
    particles : list of `Particle` objects
        List of `bent_plume_model.Particle` objects containing the dispersed 
        phase local conditions and behavior.
    
    Returns
    -------
    yp : ndarray
        A vector of the derivatives of the plume state space.
    
    See Also
    --------
    calculate
    
    """
    # Set up the output from the function to have the right size and shape
    qp = np.zeros(q.shape)
    
    # Update the local Lagrangian element properties
    q1_local.update(t, q, profile, p, particles)
    
    # Get the entrainment flux
    md = entrainment(q0_local, q1_local, p)
    
    # Get the dispersed phase tracking variables
    (fe, up, dtp_dt) = track_particles(q0_local, q1_local, md, particles)
    
    # Conservation of Mass
    qp[0] = md
    
    # Conservation of salt and heat
    qp[1] = md * q1_local.Sa
    qp[2] = md * seawater.cp() * q1_local.Ta
    
    # Conservation of continuous phase momentum.  Note that z is positive
    # down (depth).
    qp[3] = md * q1_local.ua
    qp[4] = md * q1_local.va
    qp[5] = - p.g / (p.gamma * p.rho_r) * (q1_local.Fb + q1_local.M * 
            (q1_local.rho_a - q1_local.rho)) + md * q1_local.wa
    
    # Constant h/V thickeness to velocity ratio
    qp[6] = 0.
    
    # Lagrangian plume element advection (x, y, z) and s along the centerline
    # trajectory
    qp[7] = q1_local.u
    qp[8] = q1_local.v
    qp[9] = q1_local.w
    qp[10] = q1_local.V
    
    # Conservation equations for each dispersed phase
    idx = 11
    
    # Track the mass dissolving into the continuous phase per unit time
    dm = np.zeros(q1_local.nchems)
    
    # Compute mass and heat transfer for each particle
    for i in range(len(particles)):

        # Only simulate particles inside the plume
        if particles[i].integrate:
            
            # Dissolution and Biodegradation
            if particles[i].particle.issoluble:
                # Dissolution mass transfer for each particle component
                dm_pc = - particles[i].A * particles[i].nbe * \
                           particles[i].beta * (particles[i].Cs - 
                           q1_local.c_chems) * dtp_dt[i]
                
                # Update continuous phase temperature with heat of 
                # solution
                qp[2] += np.sum(dm_pc * \
                         particles[i].particle.neg_dH_solR \
                         * p.Ru / particles[i].particle.M)
                
                # Biodegradation for for each particle component
                dm_pb = -particles[i].k_bio * particles[i].m * \
                     particles[i].nbe * dtp_dt[i]
                
                # Conservation of mass for dissolution and biodegradation
                qp[idx:idx+q1_local.nchems] = dm_pc + dm_pb
                
                # Update position in state space 
                idx += q1_local.nchems
                
            else:
                # No dissolution
                dm_pc = np.zeros(q1_local.nchems)
                # Biodegradation for insoluble particles
                dm_pb = -particles[i].k_bio * particles[i].m * \
                    particles[i].nbe * dtp_dt[i]
                qp[idx] = dm_pb
                idx += 1
            
            # Update the total mass dissolved
            dm += dm_pc
            
            # Heat transfer between the particle and the ambient
            qp[idx] = - particles[i].A * particles[i].nbe * \
                        particles[i].rho_p * particles[i].cp * \
                        particles[i].beta_T * (particles[i].T - \
                        q1_local.T) * dtp_dt[i]
            
            # Heat loss due to mass loss
            qp[idx] += np.sum(dm_pc + dm_pb) * particles[i].cp * \
                       particles[i].T
            
            # Take the heat leaving the particle and put it in the continuous 
            # phase fluid
            qp[2] -= qp[idx]
            idx += 1
            
            # Particle age
            qp[idx] = dtp_dt[i]
            idx += 1
            
            # Follow the particles in the local coordinate system (l,n,m) 
            # relative to the plume centerline
            qp[idx] = 0.
            idx += 1
            qp[idx] = (up[i,1] - fe * q[idx]) * dtp_dt[i]
            idx += 1
            qp[idx] = (up[i,2] - fe * q[idx]) * dtp_dt[i]
            idx += 1
            
        else:
            idx += particles[i].particle.nc + 5
    
    # Conservation equations for the dissolved constituents in the plume
    qp[idx:idx+q1_local.nchems] = md / q1_local.rho_a * q1_local.ca_chems \
        - dm - q1_local.k_bio * q1_local.cpe
    idx += q1_local.nchems
    
    # Conservation equation for the passive tracers in the plume
    qp[idx:] = md / q1_local.rho_a * q1_local.ca_tracers
    
    # Return the slopes
    return qp