Esempio n. 1
0
void
VertEqUpscaler::gather (
		int col,
		double* buf,
		const double* data,
		int stride,
		int offset) const {

	// index into the fine grid for all columns
	const rlw_int col_cells (ts.number_of_cells, ts.col_cellpos, ts.col_cells);

	// get the indices for this particular column
	const int* fine_ndx = col_cells[col];

	// loop through each block in the column and fetch the property
	for (int row = 0; row < col_cells.size (col); ++row) {
		// index in the fine grid for this block
		const int block_ndx = fine_ndx[row];

		// calculate position in the data array
		const int pos = block_ndx * stride + offset;

		// move the data
		buf[row] = data[pos];
	}
}
Esempio n. 2
0
int
VertEqUpscaler::num_rows (
    int col) const {

	// use this helper object to query about the size of the column
	// (the compiler should be able to optimize most of it away)
	const rlw_int pos (ts.number_of_cells, ts.col_cellpos, ts.col_cells);
	return pos.size (col);
}
Esempio n. 3
0
    virtual void upscale_pressure (const double* coarseSaturation,
                                   const double* finePressure,
                                   double* coarsePressure) {
        // pressure locations we'll have to relate to
        static const int    FIRST_BLOCK = 0;    // relative index in the column
        static const double HALFWAY     = 0.5;  // center of the block

        // incompressible means that the density is the same everywhere
        // we can thus cache the phase properties outside of the loop
        const double gas_dens = density ()[GAS];
        const double wat_dens = density ()[WAT];

        // helper object to get the index (into the pressure array) and
        // the height of elements in a column
        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);

        // upscale each column separately. assume that something like the
        // EQUIL keyword has been used in the Eclipse file and that the
        // pressures are already in equilibrium. thus, we only need to
        // extract the pressure at the reference point (top surface)
        for (int col = 0; col < col_cells.cols (); ++col) {
            // location of the brine-co2 phase contact
            const double gas_sat = coarseSaturation[col * NUM_PHASES + GAS];
            const Elevation& intf_lvl = intf_elev (col, gas_sat);

            // what fraction of the first block from the pressure point (halfway)
            // up to the top surface is of each of the phases? if the interface
            // is below the first block, or if it is further down than halfway,
            // then everything, otherwise the fraction (less than 0.5)
            double gas_frac = (intf_lvl.block () == FIRST_BLOCK) ?
                              min (HALFWAY, intf_lvl.fraction ()) : HALFWAY;

            // id of the upper-most block of this column. if there is no
            // blocks, then the TopSurf object wouldn't generate a column.
            const int block_id = col_cells[col][FIRST_BLOCK];

            // height of the uppermost block (twice the distance from the top
            // to the center
            const double hgt = ts_dz[col][FIRST_BLOCK];

            // get the pressure in the middle of this block
            const double mid_pres = finePressure[block_id];

            // pressure at the reference point; adjust hydrostatically for
            // those phases that are on the way up from the center of the first
            // block in the column.
            const double ref_pres = mid_pres - gravity * hgt *
                                    (gas_frac * gas_dens + (HALFWAY - gas_frac) * wat_dens);

            // Eclipse uses non-aquous pressure (see Variable Sets in Formulation
            // of the Equations in the Technical Description) as the main unknown
            // in the pressure equation; there is assumed continuity at the
            // contact, so the pressure at the top should always be a CO2 pressure
            coarsePressure[col] = ref_pres;
        }
    }
Esempio n. 4
0
double
VertEqUpscaler::sum (
		int col,
		const double *val) const {

	// index into the fine grid for all columns
	const rlw_int col_cells (ts.number_of_cells, ts.col_cellpos, ts.col_cells);

	// get the indices for this particular column
	const int* fine_ndx = col_cells[col];

	// loop through each block in the column, accumulating as we go
	double the_sum = 0.;
	for (int row = 0; row < col_cells.size (col); ++row) {
		the_sum += val[fine_ndx[row]];
	}

	return the_sum;
}
Esempio n. 5
0
    virtual void downscale_pressure (const double* coarseSaturation,
                                     const double* coarsePressure,
                                     double* finePressure) {
        // pressure locations we'll have to relate to
        static const double HALFWAY     = 0.5;  // center of the block

        // helper object to get the index (into the pressure array) and
        // the height of elements in a column
        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);

        // incompressible means that the density is the same everywhere
        // we can thus cache the phase properties outside of the loop
        const double gas_dens = density ()[GAS];
        const double wat_dens = density ()[WAT];

        for (int col = 0; col < col_cells.cols (); ++col) {
            // location of the brine-co2 phase contact
            const double gas_sat = coarseSaturation[col * NUM_PHASES + GAS];
            const Elevation& intf_lvl = intf_elev (col, gas_sat);

            // get the pressure difference between the phases at top of this column
            const double sat[NUM_PHASES] = { gas_sat, 1-gas_sat };
            double pres_diff;
            capPress (1, sat, &col, &pres_diff, 0);

            // get the reference phase pressure at the top; notice that the CO2
            // pressure is the largest so we subtract the difference
            const double gas_ref = coarsePressure[col];
            const double wat_ref = gas_ref - pres_diff;

            // are we going to include the block with the interface
            const int incl_intf = intf_lvl.fraction () >= HALFWAY ? 1 : 0;
            const int num_gas_rows = intf_lvl.block () + incl_intf;

            // write all CO2 pressure blocks
            for (int row = 0; row < num_gas_rows; ++row) {
                // height of block center
                const double hgt = ts_h[col][row] + HALFWAY * ts_dz[col][row];

                // hydrostatically get the pressure for this block
                const double gas_pres = gas_ref + gravity * hgt * gas_dens;
                const int block = col_cells[col][row];

                // (scatter) write to output array
                finePressure[block] = gas_pres;
            }

            // then write the brine blocks, starting where we left off
            for (int row = num_gas_rows; row < col_cells.size (col); ++row) {
                // height of block center
                const double hgt = ts_h[col][row] + HALFWAY * ts_dz[col][row];

                // hydrostatically get the pressure for this block
                const double wat_pres = wat_ref + gravity * hgt * wat_dens;
                const int block = col_cells[col][row];

                // (scatter) write to output array
                finePressure[block] = wat_pres;
            }
        }
    }
Esempio n. 6
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;
            }
        }
    }
Esempio n. 7
0
    VertEqPropsImpl (const IncompPropertiesInterface& fineProps,
                     const TopSurf& topSurf,
                     const double* grav_vec)
        : fp (fineProps)
        , ts (topSurf)
        , up (ts)

          // assign which phase is which (e.g. CO2 is first, brine is second)
          // a basic assumption of the vertical equilibrium is that the CO2 is
          // the lightest phase and thus rise to the top of the reservoir
        , GAS (fp.density()[0] < fp.density()[1] ? 0 : 1)
        , WAT (1 - GAS)
        , phase_sign (GAS < WAT ? +1. : -1.)

          // allocate memory for intermediate integrals
        , res_gas_vol (ts.number_of_cells, ts.col_cellpos)
        , mob_mix_vol (ts.number_of_cells, ts.col_cellpos)
        , res_wat_vol (ts.number_of_cells, ts.col_cellpos)
        , res_gas_dpt (ts.number_of_cells, ts.col_cellpos)
        , mob_mix_dpt (ts.number_of_cells, ts.col_cellpos)
        , res_wat_dpt (ts.number_of_cells, ts.col_cellpos)

          // assume that there is no initial plume; first notification will
          // trigger an update of all columns where there actually is CO2
        , max_gas_sat (ts.number_of_cells, 0.)

        , prm_gas (ts.number_of_cells, ts.col_cellpos)
        , prm_gas_int (ts.number_of_cells, ts.col_cellpos)
        , prm_res (ts.number_of_cells, ts.col_cellpos)
        , prm_res_int (ts.number_of_cells, ts.col_cellpos)
        , prm_wat (ts.number_of_cells, ts.col_cellpos)
        , prm_wat_int (ts.number_of_cells, ts.col_cellpos)
        , gravity (grav_vec[THREE_DIMS - 1])	{

        // check that we only have two phases
        if (fp.numPhases () != NUM_PHASES) {
            throw OPM_EXC ("Expected %d phases, but got %d", NUM_PHASES, fp.numPhases ());
        }

        // allocate memory to store results for faster lookup later
        upscaled_poro.resize (ts.number_of_cells);
        upscaled_absperm.resize (ts.number_of_cells * PERM_MATRIX_2D);

        // buffers that holds intermediate values for each column;
        // pre-allocate to avoid doing that inside the loop
        vector <double> poro (ts.max_vert_res, 0.); // porosity
        vector <double> kxx (ts.max_vert_res, 0.);  // abs.perm.
        vector <double> kxy (ts.max_vert_res, 0.);
        vector <double> kyy (ts.max_vert_res, 0.);
        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
        vector <double> lkl (ts.max_vert_res); // magnitude of abs.perm.; k_||

        // saturations and rel.perms. of each phase, assuming maximum filling of...
        vector <double> wat_sat (ts.max_vert_res * NUM_PHASES, 0.); // brine; res. CO2
        vector <double> gas_sat (ts.max_vert_res * NUM_PHASES, 0.); // CO2; res. brine
        vector <double> wat_mob (ts.max_vert_res * NUM_PHASES, 0.); // k_r(S_c=S_{c,r})
        vector <double> gas_mob (ts.max_vert_res * NUM_PHASES, 0.); // k_r(S_c=1-S_{b,r})

        // pointer to all porosities in the fine grid
        const double* fine_poro = fp.porosity ();
        const double* fine_perm = fp.permeability ();

        // upscale each column separately
        for (int col = 0; col < ts.number_of_cells; ++col) {
            // retrieve the fine porosities for this column only
            up.gather (col, &poro[0], fine_poro, 1, 0);

            // compute the depth-averaged value and store
            upscaled_poro[col] = up.dpt_avg (col, &poro[0]);

            // retrieve the fine abs. perm. for this column only
            up.gather (col, &kxx[0], fine_perm, PERM_MATRIX_3D, KXX_OFS_3D);
            up.gather (col, &kxy[0], fine_perm, PERM_MATRIX_3D, KXY_OFS_3D);
            up.gather (col, &kyy[0], fine_perm, PERM_MATRIX_3D, KYY_OFS_3D);

            // compute upscaled values for each dimension separately
            const double up_kxx = up.dpt_avg (col, &kxx[0]);
            const double up_kxy = up.dpt_avg (col, &kxy[0]);
            const double up_kyy = up.dpt_avg (col, &kyy[0]);

            // store back into the interleaved format required by the 2D
            // simulator code (fetching a tensor at the time, probably)
            // notice that we take advantage of the tensor being symmetric
            // at the third line below
            upscaled_absperm[PERM_MATRIX_2D * col + KXX_OFS_2D] = up_kxx;
            upscaled_absperm[PERM_MATRIX_2D * col + KXY_OFS_2D] = up_kxy;
            upscaled_absperm[PERM_MATRIX_2D * col + KYX_OFS_2D] = up_kxy;
            upscaled_absperm[PERM_MATRIX_2D * col + KYY_OFS_2D] = up_kyy;

            // contract each fine perm. to a scalar, used for weight later
            for (int row = 0; row < up.num_rows (col); ++row) {
                lkl[row] = magnitude (kxx[row], kxy[row], kyy[row]);
            }

            // we only need the relative weight, so get the depth-averaged
            // total weight, which we'll use to scale the weights below
            const double tot_lkl = up.dpt_avg (col, &lkl[0]); // 1/K^{-1}

            // query the fine properties for the residual saturations;
            // notice that we implicitly get the brine saturation as the maximum
            // allowable co2 saturation; now we've got the values we need, but
            // only every other item (due to that both phases are stored)
            const rlw_int col_cells (ts.number_of_cells, ts.col_cellpos, ts.col_cells);
            fp.satRange (col_cells.size (col), col_cells[col], &sgr[0], &l_swr[0]);

            // cache pointers to this particular column to avoid recomputing
            // the starting point for each and every item
            double* res_gas_col = res_gas_vol[col];
            double* mob_mix_col = mob_mix_vol[col];
            double* res_wat_col = res_wat_vol[col];

            for (int row = 0; row < col_cells.size (col); ++row) {
                // multiply with num_phases because the saturations for *both*
                // phases are store consequtively (as a record); we only need
                // the residuals framed as co2 saturations
                const double sgr_ = sgr[row * NUM_PHASES + GAS];
                const double l_swr_ = l_swr[row * NUM_PHASES + GAS];

                // portions of the block that are filled with: residual co2,
                // mobile fluid and residual brine, respectively
                res_gas_col[row] = poro[row] * sgr_;            // \phi*S_{n,r}
                mob_mix_col[row] = poro[row] * (l_swr_ - sgr_); // \phi*(1-S_{w,r}-S_{n_r})
                res_wat_col[row] = poro[row] * l_swr_;          // \phi*(1-S_{w,r}
            }

            // weight the relative depth factor (how close are we towards a
            // completely filled column) with the volume portions. this call
            // to up.wgt_dpt is the same as 1/H int_{h}^{\Zeta_T} ... dz
            up.wgt_dpt (col, &res_gas_col[0], res_gas_dpt);
            up.wgt_dpt (col, &mob_mix_col[0], mob_mix_dpt);
            up.wgt_dpt (col, &res_wat_col[0], res_wat_dpt);

            // now, when we queried the saturation ranges, we got back the min.
            // and max. sat., and when there is min. of one, then there should
            // be max. of the other; however, these data are in different arrays!
            // cross-pick such that we get (min CO2, max brine), (max CO2, min brine)
            // instead of (min CO2, min brine), (max CO2, max brine). this code
            // has no other effect than to satisfy the ordering of items required
            // for the relperm() call
            for (int row = 0; row < col_cells.size (col); ++row) {
                wat_sat[row * NUM_PHASES + GAS] = sgr[row * NUM_PHASES + GAS];
                wat_sat[row * NUM_PHASES + WAT] = l_swr[row * NUM_PHASES + WAT];
                gas_sat[row * NUM_PHASES + GAS] = l_swr[row * NUM_PHASES + GAS];
                gas_sat[row * NUM_PHASES + WAT] = sgr[row * NUM_PHASES + WAT];
            }

            // get rel.perm. for those cases where one phase is (maximally) mobile
            // and the other one is immobile (at residual saturation); we get back
            // rel.perm. for both phases, although only one of them is of interest
            // for us (the other one should be zero). we have no interest in the
            // derivative of the fine-scale rel.perm.
            fp.relperm (col_cells.size (col), &wat_sat[0], col_cells[col], &wat_mob[0], 0);
            fp.relperm (col_cells.size (col), &gas_sat[0], col_cells[col], &gas_mob[0], 0);

            // cache the pointers here to avoid indexing in the loop
            double* prm_gas_col = prm_gas[col];
            double* prm_res_col = prm_res[col];
            double* prm_wat_col = prm_wat[col];

            for (int row = 0; row < up.num_rows (col); ++row) {
                // rel.perm. for CO2 when having maximal sat. (only residual brine); this
                // is the rel.perm. for the CO2 that is in the plume
                const double kr_plume = gas_mob[row * NUM_PHASES + GAS];

                // rel.perm. of brine, when residual CO2
                const double kr_brine = wat_mob[row + NUM_PHASES + WAT];

                // upscaled rel. perm. change for this block; we'll use this to weight
                // the depth fractions when we integrate to get the upscaled rel. perm.
                const double k_factor = lkl[row] / tot_lkl;
                prm_gas_col[row] = k_factor * kr_plume;
                prm_wat_col[row] = k_factor * kr_brine;
                prm_res_col[row] = k_factor * (1 - kr_brine);
            }

            // integrate the derivate to get the upscaled rel. perm.
            up.wgt_dpt (col, prm_gas_col, prm_gas_int);
            up.wgt_dpt (col, prm_wat_col, prm_wat_int);
            up.wgt_dpt (col, prm_res_col, prm_res_int);
        }
    }