Ejemplo n.º 1
0
bool
Elevation::operator< (const Elevation& rhs) const {
	const int block_diff = block() - rhs.block();
	// rhs is in a block above
	if (block_diff > 0) {
		return true;
	}
	else { /* block_diff <= 0 */
		// rhs is in the same block
		if (block_diff == 0) {
			return fraction() < rhs.fraction();
		}
		// rhs is in a block below
		else {
			return false;
		}
	}
}
Ejemplo n.º 2
0
double
VertEqUpscaler::eval (
		int col,
		const rlw_double& dpt,
    const Elevation zeta) const {

	// number of whole blocks to include
	const int row = zeta.block ();

	// direct pointer to the weights
	const double* dpt_col = dpt[col];

	// if any blocks before, take those values. unfortunately the implied
	// zero value in front of the array cause a branch; hopefully branch
	// prediction in the CPU will be able to alliviate some of that cost.
	// if we add an explicit zero, then the memory sizes to store the values
	// are not the same as in the top grid, and we'll have to adjust indices
	// all the time.
	const double before = row == 0 ? 0. : dpt_col[row-1];

	// then we add the fractional value of just this block. it is just the
	// last block that we want the fraction for
	return before + (dpt_col[row] - before) * zeta.fraction ();
}
Ejemplo n.º 3
0
    virtual void downscale_saturation (const double* coarseSaturation,
                                       double* fineSaturation) {
        // scratch vectors that will hold the minimum and maximum, resp.
        // CO2 saturation. we could get these from res_xxx_vol, but then
        // we would have to dig up the porosity for each cell and divide
        // which is not necessarily faster. if we save this data in the
        // object itself, it may add to memory pressure; I assume instead
        // that it is not expensive for the underlaying properties object
        // to deliver these values on-demand.
        vector <double> sgr   (ts.max_vert_res * NUM_PHASES, 0.); // residual CO2
        vector <double> l_swr (ts.max_vert_res * NUM_PHASES, 0.); // 1 - residual brine

        // indexing object that helps us find the cell in a particular column
        const rlw_int col_cells (ts.number_of_cells, ts.col_cellpos, ts.col_cells);

        // downscale each column individually
        for (int col = 0; col < ts.number_of_cells; ++col) {
            // current height of mobile CO2
            const double gas_hgt = coarseSaturation[col * NUM_PHASES + GAS];

            // height of the interface of residual and mobile CO2, resp.
            const Elevation res_gas = res_elev (col, gas_hgt);   // zeta_R
            const Elevation mob_gas = intf_elev (col, gas_hgt);  // zeta_M

            // query the fine properties for the residual saturations; notice
            // that only every other item holds the value for CO2
            const int* ids = col_cells[col];
            fp.satRange (col_cells.size (col), ids, &sgr[0], &l_swr[0]);

            // fill the number of whole blocks which contain mobile CO2 and
            // only residual water (maximum CO2)
            for (int row = 0; row < mob_gas.block (); ++row) {
                const double gas_sat = l_swr[row * NUM_PHASES + GAS];
                const int block = ids[row];
                fineSaturation[block * NUM_PHASES + GAS] = gas_sat;
                fineSaturation[block * NUM_PHASES + WAT] = 1 - gas_sat;
            }

            // then fill the number of *whole* blocks which contain only
            // residual CO2. we start out in the block that was not filled
            // with mobile CO2, i.e. these only fill the *extra* blocks
            // where the plume once was but is not anymore
            for (int row = mob_gas.block(); row < res_gas.block(); ++row) {
                const double gas_sat = sgr[row * NUM_PHASES + GAS];
                const int block = ids[row];
                fineSaturation[block * NUM_PHASES + GAS] = gas_sat;
                fineSaturation[block * NUM_PHASES + WAT] = 1 - gas_sat;
            }

            // fill the remaining of the blocks in the column with pure brine
            for (int row = res_gas.block(); row < col_cells.size (col); ++row) {
                const int block = ids[row];
                fineSaturation[block * NUM_PHASES + GAS] = 0.;
                fineSaturation[block * NUM_PHASES + WAT] = 1.;
            }

            // adjust the block with the mobile/residual interface with its
            // fraction of mobile CO2. since we only have a resolution of one
            // block this sharp interface will only be seen on the visualization
            // as a slightly differently colored block. only do this if there
            // actually is a partially filled block.
            const int intf_block = ids[mob_gas.block ()];
            if (intf_block != col_cells.size(col)) {
                // there will already be residual gas in this block thanks to the
                // loop above; we must only fill a fraction of it with mobile gas,
                // which is the difference between the maximum and minimum filling
                const double intf_gas_sat_incr = mob_gas.fraction () *
                                                 (l_swr[intf_block * NUM_PHASES + GAS]
                                                  - sgr[intf_block * NUM_PHASES + GAS]);
                fineSaturation[intf_block * NUM_PHASES + GAS] += intf_gas_sat_incr;
                // we could have written at the brine saturations afterwards to
                // avoid this extra adjustment, but the data locality will be bad
                fineSaturation[intf_block * NUM_PHASES + WAT] -= intf_gas_sat_incr;
            }

            // do the same drill, but with the fraction of where the residual
            // zone ends (the outermost historical edge of the plume)
            const int res_block = ids[res_gas.block()];
            if (res_block != col_cells.size(col)) {
                const double res_gas_sat_incr = res_gas.fraction() *
                                                sgr[res_block * NUM_PHASES + GAS];
                fineSaturation[res_block * NUM_PHASES + GAS] += res_gas_sat_incr;
                fineSaturation[res_block * NUM_PHASES + WAT] -= res_gas_sat_incr;
            }
        }
    }
Ejemplo n.º 4
0
    virtual void capPress (const int n,
                           const double *s,
                           const int *cells,
                           double *pc,
                           double *dpcds) const {
        // cache this on the outside of the loop; the phase properties
        // are the same in every block
        const double dens_gas = density ()[GAS];
        const double dens_wat = density ()[WAT];
        const double dens_diff = dens_gas - dens_wat;

        // wrappers to make sure that we can access this matrix without
        // doing index calculations ourselves
        const rlw_double ts_h (ts.number_of_cells, ts.col_cellpos, ts.h);
        const rlw_double ts_dz (ts.number_of_cells, ts.col_cellpos, ts.dz);
        const rlw_int col_cells (ts.number_of_cells, ts.col_cellpos, ts.col_cells);

        // process each column/cell individually
        for (int i = 0; i < n; ++i) {
            // index (into the upscaled grid) of the column
            const int col = cells[i];

            // get the (upscaled) CO2 saturation
            const double Sg = s[i * NUM_PHASES + GAS];

            // get the block number that contains the active interface
            const Elevation intf = intf_elev (col, Sg); // zeta_M

            // heights from top surface to the interface, and to bottom
            const double intf_hgt = up.eval (col, ts_h, intf); // \zeta_T - \zeta_M

            // the slopes of the pressure curves are different. the distance
            // between them (at the top for instance) is dependent on where
            // they intersect (i.e. at the interface between the phases). if
            // the coordinate system is tilted, we assume that the 'gravity'
            // scalar here is the inner product between the vertical axis and
            // the real gravity vector.
            const double hyd_diff = -gravity * (intf_hgt * dens_diff);

            // find the fine-scale element that holds the interface; we already
            // know the relative index in the column; ask the top surface for
            // global identity
            const int glob_id = col_cells[col][intf.block()];

            // find the entry pressure in this block. this code could
            // be optimized so it only called the capillary pressure
            // function for the fine-scale properties once instead of
            // inside the loop, but that would require us to allocate
            // arrays to hold all input and output, instead of just using
            // local variables. BTW; why the number of outputs?
            double fine_sat[NUM_PHASES];
            double fine_pc[NUM_PHASES];               // entry pressures
            double fine_dpc[NUM_PHASES_SQ];           // derivatives
            fine_sat[GAS] = intf.fraction ();
            fine_sat[WAT] = 1 - fine_sat[GAS];
            fp.capPress (1, fine_sat, &glob_id, fine_pc, fine_dpc);

            // total capillary pressure. the fine scale entry pressure is
            // a wedge between the slopes of the hydrostatic pressures.
            const double fine_pc_GAS = phase_sign * fine_pc[0];
            const double cap_pres = fine_pc_GAS + hyd_diff;

            // assign to output; only the first phase is set, the other should
            // be set to zero (?), see method SimpleFluid2pWrappingProps::pc in
            // opm/core/transport/implicit/SimpleFluid2pWrappingProps_impl.hpp
            pc[i * NUM_PHASES + 0] = phase_sign * cap_pres;
            pc[i * NUM_PHASES + 1] = 0.;

            // interested in the derivatives of the capillary pressure as well?
            if (dpcds) {
                // volume available for the mobile liquid/gas: \phi (1-s_{w,r}-s_{g,r})
                const double mob_vol = up.eval (col, mob_mix_vol, intf);

                // change of interface height per of upscaled saturation; d\zeta_M/dS
                const double dh_dSg = -(ts.h_tot[col] * upscaled_poro[col]) / mob_vol;

                // change of hydrostatic pressure diff per change in interface height
                const double hyd_dPc_dh = -gravity * dens_diff; // dPc/d\zeta_M

                // change in entry pressure per *fine* saturation; notice that only one
                // of the derivatives is set; see the code below for dpcds for the sign
                const double dpe_dsg = GAS < WAT ?
                                       +fine_dpc[NUM_PHASES * GAS + GAS] :
                                       -fine_dpc[NUM_PHASES * WAT + WAT] ;

                // change in fine saturation per interface height (in this block)
                const double dsg_dh = 1 / ts_dz[col][intf.block()];

                // derivative with respect to upscaled saturation
                const double dPc_dSg = (dpe_dsg * dsg_dh + hyd_dPc_dh) * dh_dSg;

                // assign to output: since Sw = 1 - Sg, then dpc_g/ds_w = -dkr_g/ds_g
                // viewed as a 2x2 record; the minor index designates the denominator
                // (saturation) and the major index designates the numerator (rel.perm.)
                // here too (like for pc) only the first phase is set, the others should
                // have the magic value zero hard-coded (?)
                dpcds[i * NUM_PHASES_SQ + NUM_PHASES * 0 + 0] = phase_sign * dPc_dSg;
                dpcds[i * NUM_PHASES_SQ + NUM_PHASES * 0 + 1] = 0.;
                dpcds[i * NUM_PHASES_SQ + NUM_PHASES * 1 + 0] = 0.;
                dpcds[i * NUM_PHASES_SQ + NUM_PHASES * 1 + 1] = 0.;
            }
        }
    }