inline typename GI::Vector EulerUpstreamResidual<GI, RP, BC>:: estimateCapPressureGradient(const FIt& f, const FIt& nbf, const std::vector<double>& saturation) const { typedef typename GI::CellIterator::FaceIterator Face; typedef typename Face::Cell Cell; typedef typename GI::Vector Vector; // At nonperiodic boundaries, we return a zero gradient. // That is (sort of) a trivial Neumann (noflow) condition for the capillary pressure. if (f->boundary() && !pboundary_->satCond(*f).isPeriodic()) { return Vector(0.0); } // Find neighbouring cell and face: nbc and nbf. // If we are not on a periodic boundary, nbf is of course equal to f. Cell c = f->cell(); Cell nb = f->boundary() ? (f == nbf ? c : nbf->cell()) : f->neighbourCell(); // Estimate the gradient like a finite difference between // cell centers, except that in order to handle periodic // conditions we pass through the face centroid(s). Vector cell_c = c.centroid(); Vector nb_c = nb.centroid(); Vector f_c = f->centroid(); Vector nbf_c = nbf->centroid(); double d0 = (cell_c - f_c).two_norm(); double d1 = (nb_c - nbf_c).two_norm(); int cell = c.index(); int nbcell = nb.index(); double cp0 = cap_pressures_[cell]; double cp1 = cap_pressures_[nbcell]; double val = (cp1 - cp0)/(d0 + d1); Vector res = nb_c - nbf_c + f_c - cell_c; res /= res.two_norm(); res *= val; return res; }
inline void EulerUpstreamImplicit<GI, RP, BC>::initObj(const GI& g, const RP& r, const BC& b) { //residual_computer_.initObj(g, r, b); mygrid_.init(g.grid()); porevol_.resize(mygrid_.numCells()); for (int i = 0; i < mygrid_.numCells(); ++i){ porevol_[i]= mygrid_.cellVolume(i)*r.porosity(i); } // int numf=mygrid_.numFaces(); int num_cells = mygrid_.numCells(); int ngconn = mygrid_.c_grid()->cell_facepos[num_cells]; //std::vector<double> htrans_(ngconn); htrans_.resize(ngconn); const double* perm = &(r.permeability(0)(0,0)); tpfa_htrans_compute(mygrid_.c_grid(), perm, &htrans_[0]); // int count = 0; myrp_= r; typedef typename GI::CellIterator CIt; typedef typename CIt::FaceIterator FIt; std::vector<FIt> bid_to_face; int maxbid = 0; for (CIt c = g.cellbegin(); c != g.cellend(); ++c) { for (FIt f = c->facebegin(); f != c->faceend(); ++f) { int bid = f->boundaryId(); maxbid = std::max(maxbid, bid); } } bid_to_face.resize(maxbid + 1); std::vector<int> egf_cf(mygrid_.numFaces()); int cix=0; for (CIt c = g.cellbegin(); c != g.cellend(); ++c) { int loc_fix=0; for (FIt f = c->facebegin(); f != c->faceend(); ++f) { if (f->boundary() && b.satCond(*f).isPeriodic()) { bid_to_face[f->boundaryId()] = f; } int egf=f->index(); int cf=mygrid_.cellFace(cix,loc_fix); egf_cf[egf]=cf; loc_fix+=1; } cix+=1; } #ifndef NDEBUG const UnstructuredGrid& c_grid=*mygrid_.c_grid(); #endif int hf_ind=0; int bf_ind=0; periodic_cells_.resize(0); periodic_faces_.resize(0); periodic_hfaces_.resize(0); periodic_nbfaces_.resize(0); //cell1 = cell0; direclet_cells_.resize(0); direclet_sat_.resize(0); direclet_sat_.resize(0); direclet_hfaces_.resize(0); assert(periodic_cells_.size()==0); for (CIt c = g.cellbegin(); c != g.cellend(); ++c) { int cell0 = c->index(); for (FIt f = c->facebegin(); f != c->faceend(); ++f) { // Neighbour face, will be changed if on a periodic boundary. // Compute cell[1], cell_sat[1] FIt nbface = f; if (f->boundary()) { bf_ind+=1; if (b.satCond(*f).isPeriodic()) { nbface = bid_to_face[b.getPeriodicPartner(f->boundaryId())]; assert(nbface != f); int cell1 = nbface->cellIndex(); assert(cell0 != cell1); int f_ind=f->index(); int fn_ind=nbface->index(); // mapping face indices f_ind=egf_cf[f_ind]; fn_ind=egf_cf[fn_ind]; assert((c_grid.face_cells[2*f_ind]==-1) || (c_grid.face_cells[2*f_ind+1]==-1)); assert((c_grid.face_cells[2*fn_ind]==-1) || (c_grid.face_cells[2*fn_ind+1]==-1)); assert((c_grid.face_cells[2*f_ind]==cell0) || (c_grid.face_cells[2*f_ind+1]==cell0)); assert((c_grid.face_cells[2*fn_ind]==cell1) || (c_grid.face_cells[2*fn_ind+1]==cell1)); periodic_cells_.push_back(cell0); periodic_cells_.push_back(cell1); periodic_faces_.push_back(f_ind); periodic_hfaces_.push_back(hf_ind); periodic_nbfaces_.push_back(fn_ind); } else if (!( b.flowCond(*f).isNeumann() && b.flowCond(*f).outflux() == 0.0)) { //cell1 = cell0; direclet_cells_.push_back(cell0); direclet_sat_.push_back(b.satCond(*f).saturation()); direclet_sat_.push_back(1-b.satCond(*f).saturation());//only work for 2 phases direclet_hfaces_.push_back(hf_ind); } } hf_ind+=1; } } mygrid_.makeQPeriodic(periodic_hfaces_,periodic_cells_); // use fractional flow instead of saturation as src TwophaseFluid myfluid(myrp_); int num_b=direclet_cells_.size(); for(int i=0; i <num_b; ++i){ std::array<double,2> sat = {{direclet_sat_[2*i] ,direclet_sat_[2*i+1] }}; std::array<double,2> mob; std::array<double,2*2> dmob; myfluid.mobility(direclet_cells_[i], sat, mob, dmob); double fl = mob[0]/(mob[0]+mob[1]); direclet_sat_[2*i] = fl; direclet_sat_[2*i+1] = 1-fl; } }
void operator()(const CIt& c) const { // This is constant for the whole run. const double delta_rho = s.preservoir_properties_->densityDifference(); int cell[2]; double cell_sat[2]; cell[0] = c->index(); cell_sat[0] = saturation[cell[0]]; // Loop over all cell faces. for (FIt f = c->facebegin(); f != c->faceend(); ++f) { // Neighbour face, will be changed if on a periodic boundary. FIt nbface = f; double dS = 0.0; // Compute cell[1], cell_sat[1] if (f->boundary()) { if (s.pboundary_->satCond(*f).isPeriodic()) { nbface = s.bid_to_face_[s.pboundary_->getPeriodicPartner(f->boundaryId())]; assert(nbface != f); cell[1] = nbface->cellIndex(); assert(cell[0] != cell[1]); // Periodic faces will be visited twice, but only once // should they contribute. We make sure that we skip the // periodic faces half the time. if (cell[0] > cell[1]) { // We skip this face. continue; } cell_sat[1] = saturation[cell[1]]; } else { assert(s.pboundary_->satCond(*f).isDirichlet()); cell[1] = cell[0]; cell_sat[1] = s.pboundary_->satCond(*f).saturation(); } } else { cell[1] = f->neighbourCellIndex(); assert(cell[0] != cell[1]); if (cell[0] > cell[1]) { // We skip this face. continue; } cell_sat[1] = saturation[cell[1]]; } // Get some local properties. const double loc_area = f->area(); const double loc_flux = pressure_sol.outflux(f); const Vector loc_normal = f->normal(); // We will now try to establish the upstream directions for each // phase. They may be the same, or different (due to gravity). // Recall the equation for v_w (water phase velocity): // v_w = lambda_w * (lambda_o + lambda_w)^{-1} // * (v + lambda_o * K * grad p_{cow} + lambda_o * K * (rho_w - rho_o) * g) // ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // viscous term capillary term gravity term // // For the purpose of upstream weighting, we only consider the viscous and gravity terms. // The question is, in which direction does v_w and v_o point? That is, what is the sign // of v_w*loc_normal and v_o*loc_normal? // // For the case when the mobilities are scalar, the following analysis applies: // The viscous contribution to v_w is loc_area*loc_normal*f_w*v == f_w*loc_flux. // Then the phase fluxes become // flux_w = f_w*(loc_flux + loc_area*loc_normal*lambda_o*K*(rho_w - rho_o)*g) // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // := lambda_o*G (only scalar case) // flux_o = f_o*(loc_flux - lambda_w*G) // In the above, we must decide where to evaluate K, and for this purpose (deciding // upstream directions) we use a K averaged between the two cells. // Since all mobilities and fractional flow functions are positive, the sign // of one of these cases is trivial. If G >= 0, flux_w is in the same direction as // loc_flux, if G <= 0, flux_o is in the same direction as loc_flux. // The phase k for which flux_k and loc_flux are of same sign, is called the trivial // phase in the code below. // // Assuming for the moment that G >=0, we know the direction of the water flux // (same as loc_flux) and evaluate lambda_w in the upstream cell. Then we may use // that lambda_w to evaluate flux_o using the above formula. Knowing flux_o, we know // the direction of the oil flux, and can evaluate lambda_o in the corresponding // upstream cell. Finally, we can use the equation for flux_w to compute that flux. // The opposite case is similar. // // What about tensorial mobilities? In the following code, we make the assumption // that the directions of vectors are not so changed by the multiplication with // mobility tensors that upstream directions change. In other words, we let all // the upstream logic stand as it is. This assumption may need to be revisited. // A worse problem is that // 1) we do not have v, just loc_area*loc_normal*v, // 2) we cannot define G, since the lambdas do not commute with the dot product. typedef typename UpstreamSolver::RP::Mobility Mob; using Opm::utils::arithmeticAverage; // Doing arithmetic averages. Should we consider harmonic or geometric instead? const MutablePermTensor aver_perm = arithAver(s.preservoir_properties_->permeability(cell[0]), s.preservoir_properties_->permeability(cell[1])); // Computing the raw gravity influence vector = (rho_w - rho_o)Kg Vector grav_influence = prod(aver_perm, gravity); grav_influence *= delta_rho; // Computing G. Note that we do not multiply with the mobility, // so this G is wrong in case of anisotropic relperm. const double G = s.method_gravity_ ? loc_area*inner(loc_normal, grav_influence) : 0.0; const int triv_phase = G >= 0.0 ? 0 : 1; const int ups_cell = loc_flux >= 0.0 ? 0 : 1; // Compute mobility of the trivial phase. Mob m_ups[2]; s.preservoir_properties_->phaseMobility(triv_phase, cell[ups_cell], cell_sat[ups_cell], m_ups[triv_phase].mob); // Compute gravity flow of the nontrivial phase. double sign_G[2] = { -1.0, 1.0 }; double grav_flux_nontriv = sign_G[triv_phase]*loc_area *inner(loc_normal, m_ups[triv_phase].multiply(grav_influence)); // Find flow direction of nontrivial phase. const int ups_cell_nontriv = (loc_flux + grav_flux_nontriv >= 0.0) ? 0 : 1; const int nontriv_phase = (triv_phase + 1) % 2; s.preservoir_properties_->phaseMobility(nontriv_phase, cell[ups_cell_nontriv], cell_sat[ups_cell_nontriv], m_ups[nontriv_phase].mob); // Now we have the upstream phase mobilities in m_ups[]. Mob m_tot; m_tot.setToSum(m_ups[0], m_ups[1]); Mob m_totinv; m_totinv.setToInverse(m_tot); const double aver_sat = Opm::utils::arithmeticAverage<double, double>(cell_sat[0], cell_sat[1]); Mob m1c0, m1c1, m2c0, m2c1; s.preservoir_properties_->phaseMobility(0, cell[0], aver_sat, m1c0.mob); s.preservoir_properties_->phaseMobility(0, cell[1], aver_sat, m1c1.mob); s.preservoir_properties_->phaseMobility(1, cell[0], aver_sat, m2c0.mob); s.preservoir_properties_->phaseMobility(1, cell[1], aver_sat, m2c1.mob); Mob m_aver[2]; m_aver[0].setToAverage(m1c0, m1c1); m_aver[1].setToAverage(m2c0, m2c1); Mob m_aver_tot; m_aver_tot.setToSum(m_aver[0], m_aver[1]); Mob m_aver_totinv; m_aver_totinv.setToInverse(m_aver_tot); // Viscous (pressure driven) term. if (s.method_viscous_) { // v is not correct for anisotropic relperm. Vector v(loc_normal); v *= loc_flux; const double visc_change = inner(loc_normal, m_ups[0].multiply(m_totinv.multiply(v))); // const double visc_change = (m_ups[0].mob/(m_ups[1].mob + m_ups[0].mob))*loc_flux; // std::cout << "New: " << visc_change_2 << " old: " << visc_change << '\n'; dS += visc_change; } // Gravity term. if (s.method_gravity_) { if (cell[0] != cell[1]) { // We only add gravity flux on internal or periodic faces. const double grav_change = loc_area *inner(loc_normal, m_ups[0].multiply(m_totinv.multiply(m_ups[1].multiply(grav_influence)))); // const double grav_change = (lambda_one*lambda_two/(lambda_two+lambda_one))*G; // const double grav_change = (lambda_one*lambda_two/(lambda_two+lambda_one))*loc_gravity_flux; dS += grav_change; } } // Capillary term. if (s.method_capillary_) { // J(s_w) = \frac{p_c(s_w)\sqrt{k/\phi}}{\sigma \cos\theta} // p_c = \frac{J \sigma \cos\theta}{\sqrt{k/\phi}} Vector cap_influence = prod(aver_perm, s.estimateCapPressureGradient(f, nbface, saturation)); const double cap_change = loc_area *inner(loc_normal, m_aver[0].multiply(m_aver_totinv.multiply(m_aver[1].multiply(cap_influence)))); // const double cap_vel = inner(loc_normal, prod(aver_perm, estimateCapPressureGradient(f, nbface, saturation))); // const double loc_cap_flux = cap_vel*loc_area; // // const double cap_change = loc_cap_flux*(m_aver[1].mob*m_aver[0].mob // // /(m_aver[0].mob + m_aver[1].mob)); // const double cap_change = loc_cap_flux*(aver_lambda_two*aver_lambda_one // /(aver_lambda_one + aver_lambda_two)); dS += cap_change; } // Modify saturation. if (cell[0] != cell[1]){ residual[cell[0]] -= dS; residual[cell[1]] += dS; } else { assert(cell[0] == cell[1]); residual[cell[0]] -= dS; } } // Source term. double rate = s.pinjection_rates_->element(cell[0]); if (rate < 0.0) { // For anisotropic relperm, fractionalFlow does not really make sense // as a scalar rate *= s.preservoir_properties_->fractionalFlow(cell[0], cell_sat[0]); } residual[cell[0]] += rate; }