/* Copyright 2023 Equinor. This file is part of the Open Porous Media project (OPM). OPM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OPM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OPM. If not, see . */ #include #define BOOST_TEST_MODULE Parallel_WBPn_Calculation #define BOOST_TEST_NO_MAIN #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { #if HAVE_MPI struct MPIError { MPIError(std::string_view errstr, const int ec) : errorstring { errstr } , errorcode { ec } {} std::string errorstring; int errorcode; }; void MPI_err_handler(MPI_Comm*, int* err_code, ...) { std::array err_string_vec{'\0'}; auto err_length = 0; MPI_Error_string(*err_code, err_string_vec.data(), &err_length); auto err_string = std::string_view { err_string_vec.data(), static_cast(err_length) }; std::cerr << "An MPI Error ocurred:\n -> " << err_string << '\n'; throw MPIError { err_string, *err_code }; } #endif // HAVE_MPI double standardGravity() { return Opm::unit::gravity; } std::size_t globIndex(const std::array& ijk, const std::array& dims) { return ijk[0] + dims[0]*(ijk[1] + static_cast(dims[1])*ijk[2]); } std::array cellIJK(int cell, const std::array& dims) { auto ijk = std::array{}; ijk[0] = cell % dims[0]; cell /= dims[0]; ijk[1] = cell % dims[1]; ijk[2] = cell / dims[1]; return ijk; } namespace Rank { namespace Top { int globalToLocal(const std::size_t global) { return (global >= 5 * 5 * 5) ? -1 : static_cast(global); } bool isInRange(const std::array& ijk) { // Well block column in top half covers zero-based index // range // // [ 1..3, 1..3, 2..4 ] // // of index range // // [ 0..4, 0..4 , 0..4 ] return (ijk[0] >= 1) && (ijk[0] <= 3) && (ijk[1] >= 1) && (ijk[1] <= 3) && (ijk[2] >= 2) && (ijk[2] <= 4); } std::size_t fieldIx(const std::array& ijk) { return globIndex({ ijk[0] - 1, ijk[1] - 1, ijk[2] - 2 }, {3, 3, 3}); } double fieldValue(const int cell, std::initializer_list field) { const auto ijk = cellIJK(cell, { 5, 5, 5 }); return isInRange(ijk) ? *(std::data(field) + fieldIx(ijk)) : 0.0; } // Octave: 1234 + fix(100 * rand([3, 3, 6])) -- top half double pressure(const int cell) { return fieldValue(cell, { // K=2 1.302000e+03, 1.308000e+03, 1.279000e+03, 1.242000e+03, 1.256000e+03, 1.325000e+03, 1.249000e+03, 1.316000e+03, 1.287000e+03, // K=3 1.333000e+03, 1.241000e+03, 1.278000e+03, 1.244000e+03, 1.330000e+03, 1.234000e+03, 1.311000e+03, 1.315000e+03, 1.320000e+03, // K=4 1.242000e+03, 1.273000e+03, 1.259000e+03, 1.314000e+03, 1.277000e+03, 1.325000e+03, 1.252000e+03, 1.260000e+03, 1.248000e+03, }); } // Octave: fix(1e6 * (123.4 + 56.7*rand([3, 3, 6]))) / 1e6 -- top half double porevol(const int cell) { return fieldValue(cell, { // K=2 1.301471680e+02, 1.516572410e+02, 1.778174820e+02, 1.426998700e+02, 1.565846810e+02, 1.360901360e+02, 1.659968420e+02, 1.378638930e+02, 1.520877640e+02, // K=3 1.630376500e+02, 1.739142140e+02, 1.777918230e+02, 1.544271200e+02, 1.312600050e+02, 1.318649700e+02, 1.380007180e+02, 1.710686680e+02, 1.378177990e+02, // K=4 1.695699490e+02, 1.372078650e+02, 1.760892470e+02, 1.432440790e+02, 1.345469500e+02, 1.376364540e+02, 1.583297330e+02, 1.502354770e+02, 1.433390940e+02, }); } // Octave: 0.1 + round(0.1 * rand([3, 3, 6]), 2) -- top half double density(const int cell) { return fieldValue(cell, { // K=2 0.120, 0.120, 0.120, 0.140, 0.130, 0.190, 0.140, 0.120, 0.190, // K=3 0.200, 0.140, 0.110, 0.130, 0.140, 0.160, 0.130, 0.160, 0.170, // K=4 0.120, 0.110, 0.130, 0.130, 0.140, 0.150, 0.110, 0.130, 0.180, }); } void cellSource(const int cell, Opm::PAvgDynamicSourceData::SourceDataSpan src) { using Item = Opm::PAvgDynamicSourceData::SourceDataSpan::Item; src .set(Item::Pressure , pressure(cell)) .set(Item::PoreVol , porevol (cell)) .set(Item::MixtureDensity, density (cell)); } std::vector localConnIdx() { auto localIdx = std::vector(6, -1); for (auto perf = 0; perf < 3; ++perf) { localIdx[perf] = perf; } return localIdx; } Opm::ParallelWBPCalculation::EvaluatorFactory connSource() { return []() { auto rho = std::vector { 0.1, 0.12, 0.14, }; return [rho = std::move(rho)] (const int connIx, Opm::PAvgDynamicSourceData::SourceDataSpan src) { using Item = Opm::PAvgDynamicSourceData::SourceDataSpan::Item; src .set(Item::Pressure , 1222.0) .set(Item::PoreVol , 1.25) .set(Item::MixtureDensity, rho[connIx]); }; }; } } // namespace Top namespace Bottom { int globalToLocal(const std::size_t global) { constexpr auto middle = 5 * 5 * 5; return (global < middle) ? -1 : static_cast(global - middle); } bool isInRange(const std::array& ijk) { // Well block column in bottom half covers zero-based index // range // // [ 1..3, 1..3, 0..2 ] // // of index range // // [ 0..4, 0..4 , 0..4 ] return (ijk[0] >= 1) && (ijk[0] <= 3) && (ijk[1] >= 1) && (ijk[1] <= 3) && (ijk[2] >= 0) && (ijk[2] <= 2); } std::size_t fieldIx(const std::array& ijk) { return globIndex({ ijk[0] - 1, ijk[1] - 1, ijk[2] }, {3, 3, 3}); } double fieldValue(const int cell, std::initializer_list field) { const auto ijk = cellIJK(cell, { 5, 5, 5 }); return isInRange(ijk) ? *(std::data(field) + fieldIx(ijk)) : 0.0; } // Octave: 1234 + fix(100 * rand([3, 3, 6])) -- bottom half double pressure(const int cell) { return fieldValue(cell, { // K=5 1.247000e+03, 1.320000e+03, 1.291000e+03, 1.288000e+03, 1.248000e+03, 1.319000e+03, 1.296000e+03, 1.269000e+03, 1.285000e+03, // K=6 1.274000e+03, 1.241000e+03, 1.257000e+03, 1.246000e+03, 1.252000e+03, 1.257000e+03, 1.275000e+03, 1.238000e+03, 1.324000e+03, // K=7 1.328000e+03, 1.283000e+03, 1.282000e+03, 1.267000e+03, 1.324000e+03, 1.270000e+03, 1.245000e+03, 1.312000e+03, 1.272000e+03, }); } // Octave: fix(1e6 * (123.4 + 56.7*rand([3, 3, 6]))) / 1e6 -- bottom half double porevol(const int cell) { return fieldValue(cell, { // K=5 1.705079830e+02, 1.565844730e+02, 1.545693280e+02, 1.754048800e+02, 1.396070720e+02, 1.663332520e+02, 1.661364390e+02, 1.449712790e+02, 1.555954870e+02, // K=6 1.277009380e+02, 1.264589710e+02, 1.534962210e+02, 1.675787810e+02, 1.763584050e+02, 1.307656820e+02, 1.556523010e+02, 1.500144490e+02, 1.240748470e+02, // K=7 1.425148530e+02, 1.325957360e+02, 1.684359330e+02, 1.410458920e+02, 1.533678280e+02, 1.327922820e+02, 1.575323760e+02, 1.383104710e+02, 1.604862840e+02, }); } // Octave: 0.1 + round(0.1 * rand([3, 3, 6]), 2) -- bottom half double density(const int cell) { return fieldValue(cell, { // K=5 0.100, 0.190, 0.170, 0.150, 0.160, 0.120, 0.150, 0.200, 0.150, // K=6 0.150, 0.120, 0.150, 0.160, 0.170, 0.140, 0.140, 0.200, 0.100, // K=7 0.190, 0.190, 0.180, 0.110, 0.130, 0.130, 0.170, 0.110, 0.170, }); } void cellSource(const int cell, Opm::PAvgDynamicSourceData::SourceDataSpan src) { using Item = Opm::PAvgDynamicSourceData::SourceDataSpan::Item; src .set(Item::Pressure , pressure(cell)) .set(Item::PoreVol , porevol (cell)) .set(Item::MixtureDensity, density (cell)); } std::vector localConnIdx() { auto localIdx = std::vector(6, -1); for (auto perf = 0; perf < 3; ++perf) { localIdx[3 + perf] = perf; } return localIdx; } Opm::ParallelWBPCalculation::EvaluatorFactory connSource() { return []() { auto rho = std::vector { 0.16, 0.18, 0.2, }; return [rho = std::move(rho)] (const int connIx, Opm::PAvgDynamicSourceData::SourceDataSpan src) { using Item = Opm::PAvgDynamicSourceData::SourceDataSpan::Item; src .set(Item::Pressure , 1222.0) .set(Item::PoreVol , 1.25) .set(Item::MixtureDensity, rho[connIx]); }; }; } } // namespace Bottom } // namespace Rank std::shared_ptr centreConnections(const int topConn, const int numConns) { auto conns = std::vector{}; const auto dims = std::array { 5, 5, 10 }; const auto i = 2; const auto j = 2; const auto kMax = std::min(dims[2] - 1, topConn + numConns); const auto state = std::array { Opm::Connection::State::OPEN, Opm::Connection::State::SHUT, Opm::Connection::State::OPEN, }; for (auto k = topConn; k < kMax; ++k) { const auto depth = 2000 + (2*k + 1) / static_cast(2); auto ctf_props = Opm::Connection::CTFProperties{}; // 0.03, 0.0, 0.01, 0.02, 0.03, ... ctf_props.CF = ((k + 3 - topConn) % 4) / 100.0; ctf_props.Kh = 1.0; ctf_props.Ke = 1.0; ctf_props.rw = 1.0; ctf_props.r0 = 0.5; ctf_props.re = 0.5; ctf_props.connection_length = 1.0; conns.emplace_back(i, j, k, globIndex({i, j, k}, dims), k - topConn, // Open, Shut, Open, Open, Shut, ... state[(k - topConn) % state.size()], Opm::Connection::Direction::Z, Opm::Connection::CTFKind::DeckValue, 0, depth, ctf_props, k - topConn, false); } return std::make_shared (Opm::Connection::Order::INPUT, i, j, conns); } Opm::Well producerWell() { auto w = Opm::Well { "P", "G", 0, 0, 2, 2, 2000.5, Opm::WellType { true, Opm::Phase::OIL }, // Oil producer Opm::Well::ProducerCMode::ORAT, Opm::Connection::Order::INPUT, Opm::UnitSystem::newMETRIC(), -3.0e+20, // UDQ undefined 0.0, true, true, 0, Opm::Well::GasInflowEquation::STD }; w.updateConnections(centreConnections(2, 6), true); return w; } Opm::ParallelWellInfo parallelWellInfo(const Opm::Parallel::Communication& comm) { auto pwi = Opm::ParallelWellInfo { std::pair { std::string{ "P" }, true }, comm }; pwi.beginReset(); const auto numLocalPerf = 3; const auto perfOffset = comm.rank() * numLocalPerf; auto prev = Opm::ParallelWellInfo::INVALID_ECL_INDEX; for (auto perf = 0; perf < numLocalPerf; ++perf) { const auto curr = perfOffset + perf; pwi.pushBackEclIndex(prev, curr); prev = curr; } pwi.endReset(); pwi.communicateFirstPerforation(comm.rank() == 0); return pwi; } void setCallbacksTop(Opm::ParallelWBPCalculation& wbpCalcService) { wbpCalcService .localCellIndex(&Rank::Top::globalToLocal) .evalCellSource(&Rank::Top::cellSource); } void setCallbacksBottom(Opm::ParallelWBPCalculation& wbpCalcService) { wbpCalcService .localCellIndex(&Rank::Bottom::globalToLocal) .evalCellSource(&Rank::Bottom::cellSource); } void setCallbacks(const int rank, Opm::ParallelWBPCalculation& wbpCalcService) { if (rank == 0) { setCallbacksTop(wbpCalcService); } else { setCallbacksBottom(wbpCalcService); } } Opm::ParallelWBPCalculation::EvaluatorFactory connSource(const int rank) { if (rank == 0) { return Rank::Top::connSource(); } else { return Rank::Bottom::connSource(); } } std::vector localConnIdx(const int rank) { if (rank == 0) { return Rank::Top::localConnIdx(); } else { return Rank::Bottom::localConnIdx(); } } bool init_unit_test_func() { return true; } struct Setup { Setup() : comm { Dune::MPIHelper::getCommunicator() } , cellIndexMap { 5, 5, 10 } , wbpCalcService { cellIndexMap, comm } , pwi { parallelWellInfo(comm) } { setCallbacks(this->comm.rank(), this->wbpCalcService); this->wbpCalcService .createCalculator(producerWell(), this->pwi, localConnIdx(this->comm.rank()), connSource(this->comm.rank())); } Opm::Parallel::Communication comm; Opm::GridDims cellIndexMap; Opm::ParallelWBPCalculation wbpCalcService; Opm::ParallelWellInfo pwi; }; } // Anonymous namespace BOOST_AUTO_TEST_CASE(Create) { auto comm = Opm::Parallel::Communication { Dune::MPIHelper::getCommunicator() }; BOOST_REQUIRE_EQUAL(comm.size(), 2); const Opm::GridDims dims{5, 5, 10}; auto wbpCalcService = Opm::ParallelWBPCalculation { dims, comm }; setCallbacks(comm.rank(), wbpCalcService); const auto pwi = parallelWellInfo(comm); const auto calcIdx = wbpCalcService .createCalculator(producerWell(), pwi, localConnIdx(comm.rank()), connSource(comm.rank())); BOOST_CHECK_EQUAL(calcIdx, std::size_t{0}); } BOOST_AUTO_TEST_CASE(TopOfFormation_Well_OpenConns) { // Producer connected in Z direction in cells (3,3,3), (3,3,4), (3,3,5), // (3,3,6), (3,3,7), and (3,3,8). Connections (3,3,4) and (3,3,7) are // shut. Setup cse {}; cse.wbpCalcService.defineCommunication(); cse.wbpCalcService.collectDynamicValues(); const auto calcIndex = std::size_t{0}; const auto controls = Opm::PAvg{}; const auto gravity = standardGravity(); const auto refDepth = 2002.5; // BHP reference depth. Depth correction in layers 4..8. cse.wbpCalcService.inferBlockAveragePressures(calcIndex, controls, gravity, refDepth); const auto avgPress = cse.wbpCalcService.averagePressures(calcIndex); using WBPMode = Opm::PAvgCalculator::Result::WBPMode; BOOST_CHECK_CLOSE(avgPress.value(WBPMode::WBP) , 1254.806625666667, 1.0e-8); BOOST_CHECK_CLOSE(avgPress.value(WBPMode::WBP4), 1295.348292333333, 1.0e-8); BOOST_CHECK_CLOSE(avgPress.value(WBPMode::WBP5), 1275.077459000000, 1.0e-8); BOOST_CHECK_CLOSE(avgPress.value(WBPMode::WBP9), 1269.379542333333, 1.0e-8); } int main(int argc, char** argv) { Dune::MPIHelper::instance(argc, argv); #if HAVE_MPI // Register a throwing error handler to allow for debugging with // // catch throw // // in GDB. MPI_Errhandler handler{}; MPI_Comm_create_errhandler(MPI_err_handler, &handler); MPI_Comm_set_errhandler(MPI_COMM_WORLD, handler); #endif // HAVE_MPI return boost::unit_test::unit_test_main(&init_unit_test_func, argc, argv); }