Merge pull request #3681 from svenn-t/cskin

Implementation of CSKIN
This commit is contained in:
Bård Skaflestad
2023-09-18 14:11:38 +02:00
committed by GitHub
8 changed files with 329 additions and 1 deletions

View File

@@ -688,6 +688,7 @@ namespace Opm
void handleCOMPORD (HandlerContext&);
void handleCOMPSEGS (HandlerContext&);
void handleCOMPTRAJ (HandlerContext&);
void handleCSKIN (HandlerContext&);
void handleDRSDT (HandlerContext&);
void handleDRSDTCON (HandlerContext&);
void handleDRSDTR (HandlerContext&);

View File

@@ -116,6 +116,7 @@ namespace RestartIO {
int complnum() const;
int segment() const;
double CF() const;
double wpimult() const;
double Kh() const;
double rw() const;
double r0() const;
@@ -134,6 +135,8 @@ namespace RestartIO {
void setState(State state);
void setComplnum(int compnum);
void setSkinFactor(double skin_factor);
void setCF(double CF);
void scaleWellPi(double wellPi);
bool prepareWellPIScaling();
bool applyWellPIScaling(const double scaleFactor);
@@ -264,6 +267,9 @@ namespace RestartIO {
// Whether or not this Connection is subject to WELPI scaling.
bool m_subject_to_welpi = false;
// For applying last known WPIMULT to when calculating connection transmissibilty factor in CSKIN
double m_wpimult = 1.0;
std::optional<FilterCake> m_filter_cake;
static std::string CTFKindToString(const CTFKind);

View File

@@ -479,6 +479,7 @@ public:
bool handleWELSEGS(const DeckKeyword& keyword);
bool handleCOMPSEGS(const DeckKeyword& keyword, const ScheduleGrid& grid, const ParseContext& parseContext, ErrorGuard& errors);
bool handleWELOPENConnections(const DeckRecord& record, Connection::State status);
bool handleCSKINConnections(const DeckRecord& record);
bool handleCOMPLUMP(const DeckRecord& record);
bool handleWPIMULT(const DeckRecord& record);
bool handleWINJCLN(const DeckRecord& record, const KeywordLocation& location);

View File

@@ -351,6 +351,27 @@ File {} line {}.)", wname, location.keyword, location.filename, location.lineno)
handlerContext.compsegs_handled(wname);
}
void Schedule::handleCSKIN(HandlerContext& handlerContext) {
// Get CSKIN keyword info and current step
const auto& keyword = handlerContext.keyword;
const auto& currentStep = handlerContext.currentStep;
// Loop over records in CSKIN
for (const auto& record: keyword) {
// Get well names
const auto& wellNamePattern = record.getItem( "WELL" ).getTrimmedString(0);
const auto well_names = this->wellNames(wellNamePattern, handlerContext);
// Loop over well(s) in record
for (const auto& wname : well_names) {
// Get well information, modify connection skin factor, and update well
auto well = this->snapshots[currentStep].wells.get(wname);
well.handleCSKINConnections(record);
this->snapshots[currentStep].wells.update( std::move(well) );
}
}
}
void Schedule::handleDRSDT(HandlerContext& handlerContext) {
std::size_t numPvtRegions = this->m_static.m_runspec.tabdims().getNumPVTTables();
std::vector<double> maximums(numPvtRegions);
@@ -2590,6 +2611,7 @@ Well{0} entered with 'FIELD' parent group:
{ "COMPORD" , &Schedule::handleCOMPORD },
{ "COMPSEGS", &Schedule::handleCOMPSEGS },
{ "COMPTRAJ", &Schedule::handleCOMPTRAJ },
{ "CSKIN", &Schedule::handleCSKIN },
{ "DRSDT" , &Schedule::handleDRSDT },
{ "DRSDTCON", &Schedule::handleDRSDTCON },
{ "DRSDTR" , &Schedule::handleDRSDTR },

View File

@@ -210,10 +210,22 @@ const std::optional<std::pair<double, double>>& Connection::perf_range() const {
this->m_complnum = complnum;
}
void Connection::setSkinFactor(double skin_factor) {
this->m_skin_factor = skin_factor;
}
void Connection::setCF(double CF) {
this->m_CF = CF;
}
double Connection::CF() const {
return this->m_CF;
}
double Connection::wpimult() const {
return this->m_wpimult;
}
double Connection::Kh() const {
return this->m_Kh;
}
@@ -263,6 +275,7 @@ const std::optional<std::pair<double, double>>& Connection::perf_range() const {
}
void Connection::scaleWellPi(double wellPi) {
this->m_wpimult *= wellPi;
this->m_CF *= wellPi;
}
@@ -273,6 +286,7 @@ const std::optional<std::pair<double, double>>& Connection::perf_range() const {
return update;
}
bool Connection::applyWellPIScaling(const double scaleFactor) {
if (! this->m_subject_to_welpi)

View File

@@ -1248,9 +1248,46 @@ bool Well::handleWELOPENConnections(const DeckRecord& record, Connection::State
return this->updateConnections(std::move(new_connections), false);
}
bool Well::handleCSKINConnections(const DeckRecord& record) {
// Lambda expression to check if record coordinates match connection coordinates
auto match = [=]( const Connection &c) -> bool {
if (!match_eq(c.getI(), record, "I" , -1)) return false;
if (!match_eq(c.getJ(), record, "J" , -1)) return false;
if (!match_ge(c.getK(), record, "K_UPPER", -1)) return false;
if (!match_le(c.getK(), record, "K_LOWER", -1)) return false;
return true;
};
// Generate a new connection which will be updated with new connection skin factor
auto new_connections = std::make_shared<WellConnections>(this->connections->ordering(), this->headI, this->headJ);
// Update skin factor
double skin_factor = record.getItem("CONNECTION_SKIN_FACTOR").get<double>(0);
const double angle = 6.2831853071795864769252867665590057683943387987502116419498;
for (auto c : *this->connections) {
if (match(c)) {
// Check for potential negative new CF
if ((std::log(c.r0() / std::min(c.rw(), c.r0())) + skin_factor) < 0.0) {
throw std::runtime_error("Negative connection transmissibility factor produced by CSKIN for well "
+ name());
}
// Calculate new connection transmissibility factor
double CF = angle * c.Kh() / (std::log(c.r0() / std::min(c.rw(), c.r0())) + skin_factor);
// Apply last known WPIMULT (defaulted to 1.0)
CF *= c.wpimult();
// Set skin factor and connection factor
c.setSkinFactor(skin_factor);
c.setCF(CF);
}
new_connections->add(c);
}
return this->updateConnections(std::move(new_connections), false);
}
bool Well::handleCOMPLUMP(const DeckRecord& record) {

View File

@@ -443,8 +443,12 @@ namespace Opm {
if (KhItem.defaultApplied(0) || KhItem.getSIDouble(0) < 0) {
Kh = CF * (std::log(r0 / std::min(r0, rw)) + skin_factor) / angle;
} else {
if (Kh < 0)
if (Kh < 0) {
Kh = std::sqrt(K[0] * K[1]) * D[2];
// Compute r0 to be consistent with other parameters
r0 = RestartIO::RstConnection::inverse_peaceman(CF, Kh, rw, skin_factor);
}
}
}
}

View File

@@ -678,6 +678,249 @@ BOOST_AUTO_TEST_CASE(TestCrossFlowHandling) {
BOOST_CHECK(Well::Status::OPEN == schedule.getWell("BAN", 5).getStatus());
}
static std::string createDeckWithWellsAndSkinFactorChanges() {
std::string input = R"(
START -- 0
1 NOV 1979 /
GRID
PORO
1000*0.1 /
PERMX
1000*1 /
PERMY
1000*0.1 /
PERMZ
1000*0.01 /
SCHEDULE
DATES -- 1
1 DES 1979/
/
WELSPECS
'OP_1' 'OP' 9 9 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
'OP_2' 'OP' 8 8 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
'OP_3' 'OP' 7 7 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
/
COMPDAT
'OP_1' 9 9 1 1 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 /
'OP_1' 9 9 2 2 'OPEN' 1* 46.825 0.311 4332.346 1* 1* 'X' 22.123 /
'OP_2' 8 8 1 3 'OPEN' 1* 1.168 0.311 107.872 1* 1* 'Y' 21.925 /
'OP_2' 8 7 3 3 'OPEN' 1* 15.071 0.311 1391.859 1* 1* 'Y' 21.920 /
'OP_2' 8 7 3 6 'OPEN' 1* 6.242 0.311 576.458 1* 1* 'Y' 21.915 /
'OP_3' 7 7 1 1 'OPEN' 1* 27.412 0.311 2445.337 1* 1* 'Y' 18.521 /
'OP_3' 7 7 2 2 'OPEN' 1* 55.195 0.311 4923.842 1* 1* 'Y' 18.524 /
/
DATES -- 2
10 JUL 2007 /
/
CSKIN
'OP_1' 9 9 1 1 1.5 /
'OP_2' 4* -1.0 /
'OP_3' 2* 1 2 10.0 /
'OP_3' 7 7 1 1 -1.15 /
/
)";
return input;
}
BOOST_AUTO_TEST_CASE(CreateScheduleDeckWellsAndSkinFactorChanges) {
Opm::UnitSystem units(Opm::UnitSystem::UnitType::UNIT_TYPE_METRIC);
const auto& schedule = make_schedule(createDeckWithWellsAndSkinFactorChanges());
// OP_1
{
const auto& cs = schedule.getWell("OP_1", 2).getConnections();
BOOST_CHECK_CLOSE(cs.getFromIJK(8, 8, 0).skinFactor(), 1.5, 1e-10);
double CF = 25.290608354096133;
BOOST_CHECK_CLOSE(cs.getFromIJK(8, 8, 0).CF(), units.to_si(Opm::UnitSystem::measure::transmissibility, CF), 1e-5);
}
// OP_2
{
const auto& well = schedule.getWell("OP_2", 2);
const auto& cs = well.getConnections();
for (size_t i = 0; i < cs.size(); i++) {
BOOST_CHECK_CLOSE(cs.get(i).skinFactor(), -1.0, 1e-10);
}
double CF = 7.822338909386947;
BOOST_CHECK_CLOSE(cs.getFromIJK(7, 6, 2).CF(), units.to_si(Opm::UnitSystem::measure::transmissibility, CF), 1e-5);
}
// OP_3
{
const auto& well = schedule.getWell("OP_3", 2);
const auto& cs = well.getConnections();
BOOST_CHECK_CLOSE(cs.getFromIJK(6, 6, 0).skinFactor(), -1.15, 1e-10);
BOOST_CHECK_CLOSE(cs.getFromIJK(6, 6, 1).skinFactor(), 10.0, 1e-10);
double CF1 = 36.09169888375442;
BOOST_CHECK_CLOSE(cs.getFromIJK(6, 6, 0).CF(), units.to_si(Opm::UnitSystem::measure::transmissibility, CF1), 1e-5);
double CF2 = 17.848489977420336;
BOOST_CHECK_CLOSE(cs.getFromIJK(6, 6, 1).CF(), units.to_si(Opm::UnitSystem::measure::transmissibility, CF2), 1e-5);
}
}
static std::string createDeckWithWPIMULTandWELPIandCSKIN() {
std::string input = R"(
START -- 0
1 NOV 1979 /
GRID
PORO
1000*0.1 /
PERMX
1000*1 /
PERMY
1000*0.1 /
PERMZ
1000*0.01 /
SCHEDULE
DATES -- 1
1 DES 1979/
/
WELSPECS
'OP_1' 'OP' 9 9 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* /
/
COMPDAT
'OP_1' 9 9 1 1 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 /
/
DATES -- 2
10 JUL 2007 /
/
CSKIN
'OP_1' 9 9 1 1 1.5 /
/
DATES -- 3
10 AUG 2007 /
/
WPIMULT
OP_1 1.30 /
/
WPIMULT
OP_1 1.30 /
/
DATES -- 4
10 SEP 2007 /
/
CSKIN
'OP_1' 9 9 1 1 0.5 /
/
DATES -- 5
10 OCT 2007 /
/
WPIMULT
OP_1 1.30 /
/
DATES -- 6
10 NOV 2007 /
/
WELPI
OP_1 50 /
/
DATES -- 7
10 DEC 2007 /
/
CSKIN
'OP_1' 9 9 1 1 5.0 /
/
DATES -- 8
10 JAN 2008 /
/
COMPDAT
'OP_1' 9 9 1 1 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 /
/
DATES -- 9
10 FEB 2008 /
/
CSKIN
'OP_1' 9 9 1 1 -1.0 /
/
)";
return input;
}
BOOST_AUTO_TEST_CASE(CreateScheduleDeckWPIMULTandWELPIandCSKIN) {
// Setup
Opm::UnitSystem units(Opm::UnitSystem::UnitType::UNIT_TYPE_METRIC);
auto schedule = make_schedule(createDeckWithWPIMULTandWELPIandCSKIN());
// Report step 2
{
const auto& cs = schedule.getWell("OP_1", 2).getConnections();
BOOST_CHECK_CLOSE(cs.getFromIJK(8, 8, 0).skinFactor(), 1.5, 1e-10);
double CF = 25.290608354096133;
BOOST_CHECK_CLOSE(cs.getFromIJK(8, 8, 0).CF(), units.to_si(Opm::UnitSystem::measure::transmissibility, CF), 1e-5);
BOOST_CHECK_CLOSE(cs.getFromIJK(8, 8, 0).wpimult(), 1.0, 1e-5);
}
// Report step 3
{
const auto& cs_prev = schedule.getWell("OP_1", 2).getConnections();
const auto& cs_curr = schedule.getWell("OP_1", 3).getConnections();
BOOST_CHECK_CLOSE(cs_curr.getFromIJK(8, 8, 0).CF() / cs_prev.getFromIJK(8, 8, 0).CF(), 1.3, 1e-5);
BOOST_CHECK_CLOSE(cs_curr.getFromIJK(8, 8, 0).wpimult(), 1.3, 1e-5);
}
// Report step 4
{
const auto& cs = schedule.getWell("OP_1", 4).getConnections();
BOOST_CHECK_CLOSE(cs.getFromIJK(8, 8, 0).skinFactor(), 0.5, 1e-10);
double CF = 38.90302007377862; // CF from CSKIN multiplied by 1.3 from WPIMULT
BOOST_CHECK_CLOSE(cs.getFromIJK(8, 8, 0).CF(), units.to_si(Opm::UnitSystem::measure::transmissibility, CF), 1e-5);
}
// Report step 5
{
const auto& cs_prev = schedule.getWell("OP_1", 4).getConnections();
const auto& cs_curr = schedule.getWell("OP_1", 5).getConnections();
BOOST_CHECK_CLOSE(cs_curr.getFromIJK(8, 8, 0).CF() / cs_prev.getFromIJK(8, 8, 0).CF(), 1.3, 1e-5);
BOOST_CHECK_CLOSE(cs_curr.getFromIJK(8, 8, 0).wpimult(), 1.3 * 1.3, 1e-5);
}
// Report step 6
{
const auto& cs = schedule.getWell("OP_1", 6).getConnections();
double init_pi = 100.0;
schedule.applyWellProdIndexScaling("OP_1", 6, units.to_si(Opm::UnitSystem::measure::liquid_productivity_index, init_pi));
const auto& target_pi = schedule[6].target_wellpi.at("OP_1");
BOOST_CHECK_CLOSE(target_pi, 50.0, 1e-5);
BOOST_CHECK_CLOSE(cs.getFromIJK(8, 8, 0).wpimult(), 1.3 * 1.3 * (target_pi / init_pi), 1e-5);
}
// Report step 7
{
const auto& cs_prev = schedule.getWell("OP_1", 6).getConnections();
const auto& cs_curr = schedule.getWell("OP_1", 7).getConnections();
BOOST_CHECK_CLOSE(cs_curr.getFromIJK(8, 8, 0).skinFactor(), 5.0, 1e-10);
double CF = 13.858329011932668; // CF from CSKIN multiplied by 0.845 from WPIMULT and WELPI previous
BOOST_CHECK_CLOSE(cs_curr.getFromIJK(8, 8, 0).CF(), units.to_si(Opm::UnitSystem::measure::transmissibility, CF), 1e-5);
BOOST_CHECK_CLOSE(cs_curr.getFromIJK(8, 8, 0).wpimult(), cs_prev.getFromIJK(8, 8, 0).wpimult(), 1e-5);
}
// Report step 8
{
const auto& cs = schedule.getWell("OP_1", 8).getConnections();
BOOST_CHECK_CLOSE(cs.getFromIJK(8, 8, 0).CF(), units.to_si(Opm::UnitSystem::measure::transmissibility, 32.948), 1e-5);
BOOST_CHECK_CLOSE(cs.getFromIJK(8, 8, 0).wpimult(), 1.0, 1e-5);
}
// Report step 9
{
const auto& cs = schedule.getWell("OP_1", 9).getConnections();
BOOST_CHECK_CLOSE(cs.getFromIJK(8, 8, 0).skinFactor(), -1.0, 1e-10);
double CF = 41.27026972084714; // CF from CSKIN with WPIMULT and WELLPI multiplier reset to 1.0
BOOST_CHECK_CLOSE(cs.getFromIJK(8, 8, 0).CF(), units.to_si(Opm::UnitSystem::measure::transmissibility, CF), 1e-5);
BOOST_CHECK_CLOSE(cs.getFromIJK(8, 8, 0).wpimult(), 1.0, 1e-5);
}
}
static std::string createDeckWithWellsAndConnectionDataWithWELOPEN() {
std::string input = R"(
START -- 0