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; } } }
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 (); }
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; } } }
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.; } } }