ADB PolymerPropsAd::effectiveInvWaterVisc(const ADB& c, const double* visc) const { const int nc = c.size(); V inv_mu_w_eff(nc); V dinv_mu_w_eff(nc); for (int i = 0; i < nc; ++i) { double im = 0, dim = 0; polymer_props_.effectiveInvViscWithDer(c.value()(i), visc, im, dim); inv_mu_w_eff(i) = im; dinv_mu_w_eff(i) = dim; } ADB::M dim_diag = spdiag(dinv_mu_w_eff); const int num_blocks = c.numBlocks(); std::vector<ADB::M> jacs(num_blocks); for (int block = 0; block < num_blocks; ++block) { jacs[block] = dim_diag * c.derivative()[block]; } return ADB::function(std::move(inv_mu_w_eff), std::move(jacs)); }
ADB PolymerPropsAd::viscMult(const ADB& c) const { const int nc = c.size(); V visc_mult(nc); V dvisc_mult(nc); for (int i = 0; i < nc; ++i) { double im = 0, dim = 0; im = polymer_props_.viscMultWithDer(c.value()(i), &dim); visc_mult(i) = im; dvisc_mult(i) = dim; } ADB::M dim_diag(dvisc_mult.matrix().asDiagonal()); const int num_blocks = c.numBlocks(); std::vector<ADB::M> jacs(num_blocks); for (int block = 0; block < num_blocks; ++block) { jacs[block] = dim_diag * c.derivative()[block]; } return ADB::function(std::move(visc_mult), std::move(jacs)); }
ADB SolventPropsAdFromDeck::makeADBfromTables(const ADB& X_AD, const Cells& cells, const std::vector<int>& regionIdx, const std::vector<NonuniformTableLinear<double>>& tables) const { const int n = cells.size(); assert(X_AD.value().size() == n); V x(n); V dx(n); for (int i = 0; i < n; ++i) { const double& X_i = X_AD.value()[i]; x[i] = tables[regionIdx[cells[i]]](X_i); dx[i] = tables[regionIdx[cells[i]]].derivative(X_i); } ADB::M dx_diag(dx.matrix().asDiagonal()); const int num_blocks = X_AD.numBlocks(); std::vector<ADB::M> jacs(num_blocks); for (int block = 0; block < num_blocks; ++block) { fastSparseProduct(dx_diag, X_AD.derivative()[block], jacs[block]); } return ADB::function(std::move(x), std::move(jacs)); }
/// Gas formation volume factor. /// \param[in] pg Array of n gas pressure values. /// \param[in] cells Array of n cell indices to be associated with the pressure values. /// \return Array of n formation volume factor values. ADB BlackoilPropsAd::bGas(const ADB& pg, const Cells& cells) const { if (!pu_.phase_used[Gas]) { OPM_THROW(std::runtime_error, "Cannot call muGas(): gas phase not present."); } const int n = cells.size(); assert(pg.value().size() == n); const int np = props_.numPhases(); Block z = Block::Zero(n, np); Block matrix(n, np*np); Block dmatrix(n, np*np); props_.matrix(n, pg.value().data(), z.data(), cells.data(), matrix.data(), dmatrix.data()); const int phase_ind = pu_.phase_pos[Gas]; const int column = phase_ind*np + phase_ind; // Index of our sought diagonal column. ADB::M db_diag = spdiag(dmatrix.col(column)); const int num_blocks = pg.numBlocks(); std::vector<ADB::M> jacs(num_blocks); for (int block = 0; block < num_blocks; ++block) { jacs[block] = db_diag * pg.derivative()[block]; } return ADB::function(matrix.col(column), jacs); }
ADB PolymerPropsAd::effectiveInvPolymerVisc(const ADB& c, const V& mu_w) const { assert(c.size() == mu_w.size()); const int nc = c.size(); V inv_mu_p_eff(nc); V dinv_mu_p_eff(nc); for (int i = 0; i < nc; ++i) { double im = 0; double dim = 0; polymer_props_.effectiveInvPolyViscWithDer(c.value()(i), mu_w(i), im, dim); inv_mu_p_eff(i) = im; dinv_mu_p_eff(i) = dim; } ADB::M dim_diag(dinv_mu_p_eff.matrix().asDiagonal()); const int num_blocks = c.numBlocks(); std::vector<ADB::M> jacs(num_blocks); for (int block = 0; block < num_blocks; ++block) { jacs[block] = dim_diag * c.derivative()[block]; } return ADB::function(std::move(inv_mu_p_eff), std::move(jacs)); }
/// Water formation volume factor. /// \param[in] pw Array of n water pressure values. /// \param[in] cells Array of n cell indices to be associated with the pressure values. /// \return Array of n formation volume factor values. ADB BlackoilPropsAd::bWat(const ADB& pw, const Cells& cells) const { if (!pu_.phase_used[Water]) { THROW("Cannot call muWat(): water phase not present."); } const int n = cells.size(); ASSERT(pw.value().size() == n); const int np = props_.numPhases(); Block z = Block::Zero(n, np); Block matrix(n, np*np); Block dmatrix(n, np*np); props_.matrix(n, pw.value().data(), z.data(), cells.data(), matrix.data(), dmatrix.data()); const int phase_ind = pu_.phase_pos[Water]; const int column = phase_ind*np + phase_ind; // Index of our sought diagonal column. ADB::M db_diag = spdiag(dmatrix.col(column)); const int num_blocks = pw.numBlocks(); std::vector<ADB::M> jacs(num_blocks); for (int block = 0; block < num_blocks; ++block) { jacs[block] = db_diag * pw.derivative()[block]; } return ADB::function(matrix.col(column), jacs); }
/// Water viscosity. /// \param[in] pw Array of n water pressure values. /// \param[in] cells Array of n cell indices to be associated with the pressure values. /// \return Array of n viscosity values. ADB BlackoilPropsAdFromDeck::muWat(const ADB& pw, const Cells& cells) const { if (!phase_usage_.phase_used[Water]) { OPM_THROW(std::runtime_error, "Cannot call muWat(): water phase not present."); } const int n = cells.size(); assert(pw.size() == n); V mu(n); V dmudp(n); V dmudr(n); const double* rs = 0; props_[phase_usage_.phase_pos[Water]]->mu(n, pw.value().data(), rs, mu.data(), dmudp.data(), dmudr.data()); ADB::M dmudp_diag = spdiag(dmudp); const int num_blocks = pw.numBlocks(); std::vector<ADB::M> jacs(num_blocks); for (int block = 0; block < num_blocks; ++block) { jacs[block] = dmudp_diag * pw.derivative()[block]; } return ADB::function(mu, jacs); }
/// Oil viscosity. /// \param[in] po Array of n oil pressure values. /// \param[in] rs Array of n gas solution factor values. /// \param[in] cond Array of n taxonomies classifying fluid condition. /// \param[in] cells Array of n cell indices to be associated with the pressure values. /// \return Array of n viscosity values. ADB BlackoilPropsAd::muOil(const ADB& po, const ADB& rs, const std::vector<PhasePresence>& cond, const Cells& cells) const { #if 1 return ADB::constant(muOil(po.value(), rs.value(), cond, cells), po.blockPattern()); #else if (!pu_.phase_used[Oil]) { OPM_THROW(std::runtime_error, "Cannot call muOil(): oil phase not present."); } const int n = cells.size(); assert(po.value().size() == n); const int np = props_.numPhases(); Block z = Block::Zero(n, np); if (pu_.phase_used[Gas]) { // Faking a z with the right ratio: // rs = zg/zo z.col(pu_.phase_pos[Oil]) = V::Ones(n, 1); z.col(pu_.phase_pos[Gas]) = rs.value(); } Block mu(n, np); Block dmu(n, np); props_.viscosity(n, po.value().data(), z.data(), cells.data(), mu.data(), dmu.data()); ADB::M dmu_diag = spdiag(dmu.col(pu_.phase_pos[Oil])); const int num_blocks = po.numBlocks(); std::vector<ADB::M> jacs(num_blocks); for (int block = 0; block < num_blocks; ++block) { // For now, we deliberately ignore the derivative with respect to rs, // since the BlackoilPropertiesInterface class does not evaluate it. // We would add to the next line: + dmu_drs_diag * rs.derivative()[block] jacs[block] = dmu_diag * po.derivative()[block]; } return ADB::function(mu.col(pu_.phase_pos[Oil]), jacs); #endif }
ADB PolymerPropsAd::adsorption(const ADB& c, const ADB& cmax_cells) const { const int nc = c.value().size(); V ads(nc); V dads(nc); for (int i = 0; i < nc; ++i) { double c_ads = 0; double dc_ads = 0; polymer_props_.adsorptionWithDer(c.value()(i), cmax_cells.value()(i), c_ads, dc_ads); ads(i) = c_ads; dads(i) = dc_ads; } ADB::M dads_diag(dads.matrix().asDiagonal()); int num_blocks = c.numBlocks(); std::vector<ADB::M> jacs(num_blocks); for (int block = 0; block < num_blocks; ++block) { jacs[block] = dads_diag * c.derivative()[block]; } return ADB::function(std::move(ads), std::move(jacs)); }
int main() try { typedef Opm::AutoDiffBlock<double> ADB; typedef ADB::V V; typedef Eigen::SparseMatrix<double> S; Opm::time::StopWatch clock; clock.start(); const Opm::GridManager gm(3,3);//(50, 50, 10); const UnstructuredGrid& grid = *gm.c_grid(); using namespace Opm::unit; using namespace Opm::prefix; // const Opm::IncompPropertiesBasic props(2, Opm::SaturationPropsBasic::Linear, // { 1000.0, 800.0 }, // { 1.0*centi*Poise, 5.0*centi*Poise }, // 0.2, 100*milli*darcy, // grid.dimensions, grid.number_of_cells); // const Opm::IncompPropertiesBasic props(2, Opm::SaturationPropsBasic::Linear, // { 1000.0, 1000.0 }, // { 1.0, 1.0 }, // 1.0, 1.0, // grid.dimensions, grid.number_of_cells); const Opm::IncompPropertiesBasic props(2, Opm::SaturationPropsBasic::Linear, { 1000.0, 1000.0 }, { 1.0, 30.0 }, 1.0, 1.0, grid.dimensions, grid.number_of_cells); V htrans(grid.cell_facepos[grid.number_of_cells]); tpfa_htrans_compute(const_cast<UnstructuredGrid*>(&grid), props.permeability(), htrans.data()); V trans_all(grid.number_of_faces); // tpfa_trans_compute(const_cast<UnstructuredGrid*>(&grid), htrans.data(), trans_all.data()); const int nc = grid.number_of_cells; std::vector<int> allcells(nc); for (int i = 0; i < nc; ++i) { allcells[i] = i; } std::cerr << "Opm core " << clock.secsSinceLast() << std::endl; // Define neighbourhood-derived operator matrices. const Opm::HelperOps ops(grid); const int num_internal = ops.internal_faces.size(); std::cerr << "Topology matrices " << clock.secsSinceLast() << std::endl; typedef Opm::AutoDiffBlock<double> ADB; typedef ADB::V V; // q V q(nc); q.setZero(); q[0] = 1.0; q[nc-1] = -1.0; // s0 - this is explicit now typedef Eigen::Array<double, Eigen::Dynamic, 2, Eigen::RowMajor> TwoCol; TwoCol s0(nc, 2); s0.leftCols<1>().setZero(); s0.rightCols<1>().setOnes(); // totmob - explicit as well TwoCol kr(nc, 2); props.relperm(nc, s0.data(), allcells.data(), kr.data(), 0); const V krw = kr.leftCols<1>(); const V kro = kr.rightCols<1>(); const double* mu = props.viscosity(); const V totmob = krw/mu[0] + kro/mu[1]; // Moved down here because we need total mobility. tpfa_eff_trans_compute(const_cast<UnstructuredGrid*>(&grid), totmob.data(), htrans.data(), trans_all.data()); // Still explicit, and no upwinding! V mobtransf(num_internal); for (int fi = 0; fi < num_internal; ++fi) { mobtransf[fi] = trans_all[ops.internal_faces[fi]]; } std::cerr << "Property arrays " << clock.secsSinceLast() << std::endl; // Initial pressure. V p0(nc,1); p0.fill(200*Opm::unit::barsa); // First actual AD usage: defining pressure variable. const std::vector<int> bpat = { nc }; // Could actually write { nc } instead of bpat below, // but we prefer a named variable since we will repeat it. const ADB p = ADB::variable(0, p0, bpat); const ADB ngradp = ops.ngrad*p; // We want flux = totmob*trans*(p_i - p_j) for the ij-face. const ADB flux = mobtransf*ngradp; const ADB residual = ops.div*flux - q; std::cerr << "Construct AD residual " << clock.secsSinceLast() << std::endl; // It's the residual we want to be zero. We know it's linear in p, // so we just need a single linear solve. Since we have formulated // ourselves with a residual and jacobian we do this with a single // Newton step (hopefully easy to extend later): // p = p0 - J(p0) \ R(p0) // Where R(p0) and J(p0) are contained in residual.value() and // residual.derived()[0]. #if HAVE_SUITESPARSE_UMFPACK_H typedef Eigen::UmfPackLU<S> LinSolver; #else typedef Eigen::BiCGSTAB<S> LinSolver; #endif // HAVE_SUITESPARSE_UMFPACK_H LinSolver solver; S pmatr; residual.derivative()[0].toSparse(pmatr); pmatr.coeffRef(0,0) *= 2.0; pmatr.makeCompressed(); solver.compute(pmatr); if (solver.info() != Eigen::Success) { std::cerr << "Pressure/flow Jacobian decomposition error\n"; return EXIT_FAILURE; } // const Eigen::VectorXd dp = solver.solve(residual.value().matrix()); ADB::V residual_v = residual.value(); const V dp = solver.solve(residual_v.matrix()).array(); if (solver.info() != Eigen::Success) { std::cerr << "Pressure/flow solve failure\n"; return EXIT_FAILURE; } const V p1 = p0 - dp; std::cerr << "Solve " << clock.secsSinceLast() << std::endl; // std::cout << p1 << std::endl; // ------ Transport solve ------ // Now we'll try to do a transport step as well. // Residual formula is // R_w = s_w - s_w^0 + dt/pv * (div v_w) // where // v_w = f_w v // and f_w is (for now) based on averaged mobilities, not upwind. double res_norm = 1e100; const V sw0 = s0.leftCols<1>(); // V sw1 = sw0; V sw1 = 0.5*V::Ones(nc,1); const V ndp = (ops.ngrad * p1.matrix()).array(); const V dflux = mobtransf * ndp; const Opm::UpwindSelector<double> upwind(grid, ops, dflux); const V pv = Eigen::Map<const V>(props.porosity(), nc, 1) * Eigen::Map<const V>(grid.cell_volumes, nc, 1); const double dt = 0.0005; const V dtpv = dt/pv; const V qneg = q.min(V::Zero(nc,1)); const V qpos = q.max(V::Zero(nc,1)); std::cout.setf(std::ios::scientific); std::cout.precision(16); int it = 0; do { const ADB sw = ADB::variable(0, sw1, bpat); const std::vector<ADB> pmobc = phaseMobility<ADB>(props, allcells, sw.value()); const std::vector<ADB> pmobf = upwind.select(pmobc); const ADB fw_cell = fluxFunc(pmobc); const ADB fw_face = fluxFunc(pmobf); const ADB flux1 = fw_face * dflux; const ADB qtr_ad = qpos + fw_cell*qneg; const ADB transport_residual = sw - sw0 + dtpv*(ops.div*flux1 - qtr_ad); res_norm = transport_residual.value().matrix().norm(); std::cout << "res_norm[" << it << "] = " << res_norm << std::endl; S smatr; transport_residual.derivative()[0].toSparse(smatr); smatr.makeCompressed(); solver.compute(smatr); if (solver.info() != Eigen::Success) { std::cerr << "Transport Jacobian decomposition error\n"; return EXIT_FAILURE; } ADB::V transport_residual_v = transport_residual.value(); const V ds = solver.solve(transport_residual_v.matrix()).array(); if (solver.info() != Eigen::Success) { std::cerr << "Transport solve failure\n"; return EXIT_FAILURE; } sw1 = sw.value() - ds; std::cerr << "Solve for s[" << it << "]: " << clock.secsSinceLast() << '\n'; sw1 = sw1.min(V::Ones(nc,1)).max(V::Zero(nc,1)); it += 1; } while (res_norm > 1e-7); std::cout << "Saturation solution:\n" << "function s1 = solution\n" << "s1 = [\n" << sw1 << "\n];\n"; } catch (const std::exception &e) { std::cerr << "Program threw an exception: " << e.what() << "\n"; throw; }
VFPProdProperties::ADB VFPProdProperties::bhp(const std::vector<int>& table_id, const ADB& aqua, const ADB& liquid, const ADB& vapour, const ADB& thp_arg, const ADB& alq) const { const int nw = thp_arg.size(); std::vector<int> block_pattern = detail::commonBlockPattern(aqua, liquid, vapour, thp_arg, alq); assert(static_cast<int>(table_id.size()) == nw); assert(aqua.size() == nw); assert(liquid.size() == nw); assert(vapour.size() == nw); assert(thp_arg.size() == nw); assert(alq.size() == nw); //Allocate data for bhp's and partial derivatives ADB::V value = ADB::V::Zero(nw); ADB::V dthp = ADB::V::Zero(nw); ADB::V dwfr = ADB::V::Zero(nw); ADB::V dgfr = ADB::V::Zero(nw); ADB::V dalq = ADB::V::Zero(nw); ADB::V dflo = ADB::V::Zero(nw); //Get the table for each well std::vector<const VFPProdTable*> well_tables(nw, nullptr); for (int i=0; i<nw; ++i) { if (table_id[i] >= 0) { well_tables[i] = detail::getTable(m_tables, table_id[i]); } } //Get the right FLO/GFR/WFR variable for each well as a single ADB const ADB flo = detail::combineADBVars<VFPProdTable::FLO_TYPE>(well_tables, aqua, liquid, vapour); const ADB wfr = detail::combineADBVars<VFPProdTable::WFR_TYPE>(well_tables, aqua, liquid, vapour); const ADB gfr = detail::combineADBVars<VFPProdTable::GFR_TYPE>(well_tables, aqua, liquid, vapour); //Compute the BHP for each well independently for (int i=0; i<nw; ++i) { const VFPProdTable* table = well_tables[i]; if (table != nullptr) { //First, find the values to interpolate between //Value of FLO is negative in OPM for producers, but positive in VFP table auto flo_i = detail::findInterpData(-flo.value()[i], table->getFloAxis()); auto thp_i = detail::findInterpData( thp_arg.value()[i], table->getTHPAxis()); auto wfr_i = detail::findInterpData( wfr.value()[i], table->getWFRAxis()); auto gfr_i = detail::findInterpData( gfr.value()[i], table->getGFRAxis()); auto alq_i = detail::findInterpData( alq.value()[i], table->getALQAxis()); detail::VFPEvaluation bhp_val = detail::interpolate(table->getTable(), flo_i, thp_i, wfr_i, gfr_i, alq_i); value[i] = bhp_val.value; dthp[i] = bhp_val.dthp; dwfr[i] = bhp_val.dwfr; dgfr[i] = bhp_val.dgfr; dalq[i] = bhp_val.dalq; dflo[i] = bhp_val.dflo; } else { value[i] = -1e100; //Signal that this value has not been calculated properly, due to "missing" table } } //Create diagonal matrices from ADB::Vs ADB::M dthp_diag(dthp.matrix().asDiagonal()); ADB::M dwfr_diag(dwfr.matrix().asDiagonal()); ADB::M dgfr_diag(dgfr.matrix().asDiagonal()); ADB::M dalq_diag(dalq.matrix().asDiagonal()); ADB::M dflo_diag(dflo.matrix().asDiagonal()); //Calculate the Jacobians const int num_blocks = block_pattern.size(); std::vector<ADB::M> jacs(num_blocks); for (int block = 0; block < num_blocks; ++block) { //Could have used fastSparseProduct and temporary variables //but may not save too much on that. jacs[block] = ADB::M(nw, block_pattern[block]); if (!thp_arg.derivative().empty()) { jacs[block] += dthp_diag * thp_arg.derivative()[block]; } if (!wfr.derivative().empty()) { jacs[block] += dwfr_diag * wfr.derivative()[block]; } if (!gfr.derivative().empty()) { jacs[block] += dgfr_diag * gfr.derivative()[block]; } if (!alq.derivative().empty()) { jacs[block] += dalq_diag * alq.derivative()[block]; } if (!flo.derivative().empty()) { jacs[block] -= dflo_diag * flo.derivative()[block]; } } ADB retval = ADB::function(std::move(value), std::move(jacs)); return retval; }