From eb08e2e863af33a8960d8abf42555838d0acc3b3 Mon Sep 17 00:00:00 2001 From: Kai Bao Date: Wed, 28 Jun 2023 17:18:47 +0200 Subject: [PATCH] cleaning up implementation related to filter cake --- .../wells/BlackoilWellModel_impl.hpp | 4 +- opm/simulators/wells/PerfData.hpp | 9 +- opm/simulators/wells/WellInterface.hpp | 3 - opm/simulators/wells/WellInterfaceGeneric.cpp | 70 ++++++++++++- opm/simulators/wells/WellInterfaceGeneric.hpp | 8 +- opm/simulators/wells/WellInterface_impl.hpp | 99 ------------------- 6 files changed, 82 insertions(+), 111 deletions(-) diff --git a/opm/simulators/wells/BlackoilWellModel_impl.hpp b/opm/simulators/wells/BlackoilWellModel_impl.hpp index 448ecf291..30a71c95f 100644 --- a/opm/simulators/wells/BlackoilWellModel_impl.hpp +++ b/opm/simulators/wells/BlackoilWellModel_impl.hpp @@ -481,8 +481,8 @@ namespace Opm { well->updateWaterThroughput(dt, this->wellState()); } - if (well->isInjector()) { - well->updateWaterInjectionVolume(dt, this->wellState()); + if (well->isInjector() && Indices::waterEnabled) { + well->updateWaterInjectionVolume(dt, FluidSystem::waterPhaseIdx, this->wellState()); } } diff --git a/opm/simulators/wells/PerfData.hpp b/opm/simulators/wells/PerfData.hpp index 31f6d356a..55d91b15b 100644 --- a/opm/simulators/wells/PerfData.hpp +++ b/opm/simulators/wells/PerfData.hpp @@ -86,9 +86,12 @@ public: std::vector water_velocity; // This is the accumulated water injection volume - // At the moment, it will be used for the filtration cake modeling - // TODO: it might be problematic to handle the well open and shut, connection open and shut - // TODO: let us handle the most straightforward scenarios for now + // At the moment, it will only be used for the filtration cake modeling + // TODO: it might be problematic to handle the well open and shut, connection open and shut, since the + // information in PerfData will disappear if the well is SHUT + // TODO: if the injection concentration change, only the water injection volume will not be enough to + // calculate the formation of the filter cake. + // TODO: will change to track the volume of the solid formed during the injection std::vector water_injection_volume; }; diff --git a/opm/simulators/wells/WellInterface.hpp b/opm/simulators/wells/WellInterface.hpp index 505400631..b2dc6a5ad 100644 --- a/opm/simulators/wells/WellInterface.hpp +++ b/opm/simulators/wells/WellInterface.hpp @@ -303,9 +303,6 @@ public: // update perforation water throughput based on solved water rate virtual void updateWaterThroughput(const double dt, WellState& well_state) const = 0; - void updateWaterInjectionVolume(const double dt, WellState& well_state) const; - void updateInjFCMult(const std::vector& water_inj_volume); - /// Compute well rates based on current reservoir conditions and well variables. /// Used in updateWellStateRates(). virtual std::vector computeCurrentWellRates(const Simulator& ebosSimulator, diff --git a/opm/simulators/wells/WellInterfaceGeneric.cpp b/opm/simulators/wells/WellInterfaceGeneric.cpp index a140d9e09..bc0bc8bd2 100644 --- a/opm/simulators/wells/WellInterfaceGeneric.cpp +++ b/opm/simulators/wells/WellInterfaceGeneric.cpp @@ -24,6 +24,7 @@ #include +#include #include #include #include @@ -99,8 +100,6 @@ WellInterfaceGeneric::WellInterfaceGeneric(const Well& well, } if (this->isInjector()) { inj_fc_multiplier_.resize(number_of_perforations_, 1.0); - // TODO: if the injection concentration changes, the filter cake thickness can be different, need to find a general way - // can apply to the a few different ways of handling the modeling of filter cake } } @@ -719,4 +718,71 @@ checkNegativeWellPotentials(std::vector& well_potentials, } } +void WellInterfaceGeneric:: +updateWaterInjectionVolume(const double dt, const size_t water_index, WellState& well_state) const +{ + if (!this->isInjector()) { + return; + } + + const auto injectorType = this->well_ecl_.injectorType(); + if (injectorType != InjectorType::WATER) { + return; + } + + // it is currently used for the filter cake modeling related to formation damage study + auto& ws = well_state.well(this->index_of_well_); + const auto& connection_rates = ws.perf_data.phase_rates; + // per connection + auto& water_injection_volume = ws.perf_data.water_injection_volume; + + const size_t np = well_state.numPhases(); + for (int perf = 0; perf < this->number_of_perforations_; ++perf) { + // not considering the production water + const double water_rates = std::max(0., connection_rates[perf * np + water_index]); + water_injection_volume[perf] += water_rates * dt; + } +} + +void WellInterfaceGeneric:: +updateInjFCMult(const std::vector& water_inj_volume) { + // the filter injecting concentration, the porosity, the area size + // we also need the permeability of the formation, and rw and some other information + for (int perf = 0; perf < this->number_of_perforations_; ++perf) { + const auto perf_ecl_index = this->perforationData()[perf].ecl_index; + const auto& connections = this->well_ecl_.getConnections(); + const auto& connection = connections[perf_ecl_index]; + if (this->isInjector() && connection.filterCakeActive()) { + const auto& filter_cake = connection.getFilterCake(); + const double conc = this->well_ecl_.getFilterConc(); + const double area = connection.getFilterCakeArea(); + const double poro = filter_cake.poro; + const double thickness = water_inj_volume[perf] * conc / (area*(1.-poro)); + const double perm = filter_cake.perm; + const double rw = connection.getFilterCakeRadius(); + const auto cr0 = connection.r0(); + const auto crw = connection.rw(); + const auto cskinfactor = connection.skinFactor(); + const double K = connection.Kh() / connection.connectionLength(); + const double factor = filter_cake.sf_multiplier; + // compute a multiplier for the transmissibility + if (filter_cake.geometry == FilterCake::FilterCakeGeometry::LINEAR) { + // but we are using this form just for first prototype + const double skin_factor = thickness / rw * K / perm * factor; + const auto denom = std::log(cr0 / std::min(crw, cr0)) + cskinfactor; + const auto denom2 = denom + skin_factor; + this->inj_fc_multiplier_[perf] = denom / denom2; + } else if (filter_cake.geometry == FilterCake::FilterCakeGeometry::RADIAL) { + const double rc = std::sqrt(rw * rw + 2. * rw * thickness ); + const double skin_factor = K / perm * std::log(rc/rw) * factor; + const auto denom = std::log(cr0 / std::min(crw, cr0)) + cskinfactor; + const auto denom2 = denom + skin_factor; + this->inj_fc_multiplier_[perf] = denom / denom2; + } + } else { + this->inj_fc_multiplier_[perf] = 1.0; + } + } +} + } // namespace Opm diff --git a/opm/simulators/wells/WellInterfaceGeneric.hpp b/opm/simulators/wells/WellInterfaceGeneric.hpp index 39f463325..7e91e4dd2 100644 --- a/opm/simulators/wells/WellInterfaceGeneric.hpp +++ b/opm/simulators/wells/WellInterfaceGeneric.hpp @@ -188,6 +188,12 @@ public: // it might change in the future double getInjMult(const int perf, const double bhp, const double perf_pres) const; + // update the water injection volume, it will be used for calculation related to cake filtration due to injection activity + void updateWaterInjectionVolume(const double dt, const size_t water_index, WellState& well_state) const; + + // update the multiplier for well transmissbility due to cake filteration + void updateInjFCMult(const std::vector& water_inj_volume); + // whether a well is specified with a non-zero and valid VFP table number bool isVFPActive(DeferredLogger& deferred_logger) const; @@ -369,12 +375,10 @@ protected: // which intends to keep the fracturing open std::vector prev_inj_multiplier_; - // TODO: remove the mutable // the multiplier due to injection filtration cake mutable std::vector inj_fc_multiplier_; // TODO: currently, the water_injection_volume is in PerfData, maybe we should move it here - double well_efficiency_factor_; const VFPProperties* vfp_properties_; const GuideRate* guide_rate_; diff --git a/opm/simulators/wells/WellInterface_impl.hpp b/opm/simulators/wells/WellInterface_impl.hpp index 438b7c4f9..c9696f9ce 100644 --- a/opm/simulators/wells/WellInterface_impl.hpp +++ b/opm/simulators/wells/WellInterface_impl.hpp @@ -22,9 +22,6 @@ #include #include -#include -#include - #include #include #include @@ -1373,100 +1370,4 @@ namespace Opm connII[phase_pos] = connIICalc(mt * fs.invB(this->flowPhaseToEbosPhaseIdx(phase_pos)).value()); } - - - // TODO: this function does not need to be in the WellInterface class? - template - void WellInterface::updateWaterInjectionVolume(const double dt, WellState& well_state) const - { - if (!this->isInjector()) { - return; - } - // TODO: gonna abuse this function for calculation the skin factors - if (!FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - return; - } - // it is currently used for the filter cake modeling related to formation damage study - auto& ws = well_state.well(this->index_of_well_); - const auto& connection_rates = ws.perf_data.phase_rates; - // per connection - auto& water_injection_volume = ws.perf_data.water_injection_volume; - // which is the index for water - // and also, we should check whether the phase is active - const size_t water_index = FluidSystem::waterPhaseIdx; - const size_t np = well_state.numPhases(); - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - // not considering the production water - const double water_rates = std::max(0., connection_rates[perf * np + water_index]); - water_injection_volume[perf] += water_rates * dt; - std::cout << " well " << this->name() << " perf " << perf << " injection volume " - << water_injection_volume[perf] << std::endl; - } - } - - template - void WellInterface::updateInjFCMult(const std::vector& water_inj_volume) { - // the filter injecting concentration, the porosity, the area size - // we also need the permeability of the formation, and rw and some other information - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const auto perf_ecl_index = this->perforationData()[perf].ecl_index; - const auto& connections = this->well_ecl_.getConnections(); - const auto& connection = connections[perf_ecl_index]; - const auto& filter_cake = connection.getFilterCake(); - if (filter_cake.active()) { - const double conc = this->well_ecl_.getFilterConc(); - const double area = connection.getFilterCakeArea(); - const double poro = filter_cake.poro; - const double thickness = water_inj_volume[perf] * conc / (area*(1.-poro)); - const double perm = filter_cake.perm; - const double rw = connection.getFilterCakeRadius(); - const auto cr0 = connection.r0(); - const auto crw = connection.rw(); - const auto cskinfactor = connection.skinFactor(); - const double K = connection.Kh() / connection.connectionLength(); - const double factor = filter_cake.sf_multiplier; - // we do the work here, the main thing here is to compute a multiplier for the transmissibility - // TODO: the formulation needs to be double checked - if (filter_cake.geometry == FilterCake::FilterCakeGeometry::LINEAR) { - // TODO: do we want to use this rw? - std::cout << " perf " << perf << " water_injection_volume " << water_inj_volume[perf] << " conc " << conc - << " area " << area << " poro " << poro << " thickness " << thickness << std::endl; - // TODO: this formulation might not apply for different situation - // but we are using this form just for first prototype - std::cout << " sf_multiplier " << factor; - const double skin_factor = thickness / rw * K / perm * factor; - std::cout << " K " << K << " skin_factor " << skin_factor << std::endl; - const auto denom = std::log(cr0 / std::min(crw, cr0)) + cskinfactor; - const auto denom2 = denom + skin_factor; - const auto scaling = denom / denom2; - std::cout << " scaling will be " << scaling << std::endl; - this->inj_fc_multiplier_[perf] = scaling; - std::cout << " well " << this->name() << " perf " << perf << " inj_fc_scaling " << this->inj_fc_multiplier_[perf] << std::endl; - // TODO: basically, rescale the well connectivity index with the following formulation - // CF = angle * Kh / (std::log(r0 / std::min(rw, r0)) + skin_factor); - } else if (filter_cake.geometry == FilterCake::FilterCakeGeometry::RADIAL) { - // const double rc = std::sqrt(rw * rw - conc * water_inj_volume[perf]/(3.1415926*(1-poro))); - std::cout << " perf " << perf << " water_injection_volume " << water_inj_volume[perf] << " conc " << conc - << " area " << area << " poro " << poro << " thickness " << thickness << std::endl; - const double rc = std::sqrt(rw * rw + 2. * rw * thickness ); - std::cout << " perf " << perf << " rw " << rw << " rc " << rc; - std::cout << " K " << K << " perm " << perm; - std::cout << " sf_multiplier " << factor; - // const double skin_factor = K / perm * std::log(rw/rc) * factor; - const double skin_factor = K / perm * std::log(rc/rw) * factor; - std::cout << " skin_factor " << skin_factor << std::endl; - const auto denom = std::log(cr0 / std::min(crw, cr0)) + cskinfactor; - const auto denom2 = denom + skin_factor; - const auto scaling = denom / denom2; - std::cout << " scaling will be " << scaling << std::endl; - this->inj_fc_multiplier_[perf] = scaling; - std::cout << " well " << this->name() << " perf " << perf << " inj_fc_scaling " << this->inj_fc_multiplier_[perf] << std::endl; - } - } else { - this->inj_fc_multiplier_[perf] = 1.0; - } - } - } - - } // namespace Opm