Add Facility For Identifying Specific Well Segments

The primary use case is identifying UDQ sets when the model defines
UDQs at the segment level (SU*).  The call site will construct a
SegmentMatcher object backed by a specific ScheduleState, and then
segment-level UDQs will match against this to determine the correct
UDQ set on which to define the quantity.
This commit is contained in:
Bård Skaflestad 2022-12-01 17:26:55 +01:00
parent c8daba28ad
commit 1a1ba1f7a1
4 changed files with 2354 additions and 1 deletions

View File

@ -131,6 +131,7 @@ if(ENABLE_ECL_INPUT)
src/opm/input/eclipse/Schedule/MSW/icd.cpp
src/opm/input/eclipse/Schedule/MSW/Compsegs.cpp
src/opm/input/eclipse/Schedule/MSW/Segment.cpp
src/opm/input/eclipse/Schedule/MSW/SegmentMatcher.cpp
src/opm/input/eclipse/Schedule/MSW/WellSegments.cpp
src/opm/input/eclipse/Schedule/MSW/AICD.cpp
src/opm/input/eclipse/Schedule/MSW/SICD.cpp
@ -374,6 +375,7 @@ list (APPEND TEST_SOURCE_FILES
tests/test_param.cpp
tests/test_pengrobinson.cpp
tests/test_RootFinders.cpp
tests/test_SegmentMatcher.cpp
tests/test_sparsevector.cpp
tests/test_spline.cpp
tests/test_tabulation.cpp
@ -1099,7 +1101,7 @@ if(ENABLE_ECL_INPUT)
opm/input/eclipse/Schedule/OilVaporizationProperties.hpp
opm/input/eclipse/Schedule/MSW/icd.hpp
opm/input/eclipse/Schedule/MSW/Segment.hpp
opm/input/eclipse/Schedule/MSW/Segment.hpp
opm/input/eclipse/Schedule/MSW/SegmentMatcher.hpp
opm/input/eclipse/Schedule/MSW/WellSegments.hpp
opm/input/eclipse/Schedule/MSW/AICD.hpp
opm/input/eclipse/Schedule/MSW/SICD.hpp

View File

@ -0,0 +1,354 @@
/*
Copyright 2022 Equinor ASA.
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 <http://www.gnu.org/licenses/>.
*/
#ifndef SEGMENT_MATCHER_HPP
#define SEGMENT_MATCHER_HPP
#include <cstddef>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
/// \file Facility for Identifying Specific Well Segments Matching a UDQ Segment Set.
namespace Opm {
class ScheduleState;
} // namespace Opm
namespace Opm {
/// Encapsulation of Matching Process for MSW Segment Sets
///
/// Primary use case is determining the set of MSW segments used to define
/// segment level UDQs. Typical segment quantities in this context are
///
/// SOFR - Oil flow rate in all segments in all MS wells
/// SOFR 'P' - Oil flow rate in all segments of MS well 'P'
/// SOFR 'P' 1 - Oil flow rate in segment 1 of MS well 'P'
/// SOFR 'P*' - Oil flow rate in all segments of all MS wells whose name
/// begins with 'P'
/// SOFR 'P*' 27 - Oil flow rate in segment 27 of all MS wells whose name
/// begins with 'P'
/// SOFR '*' 27 - Oil flow rate in segment 27 of all MS wells
/// SOFR '*' '*' - Oil flow rate in all segments of all MS wells
///
/// The user initiates the matching process by constructing a SetDescriptor
/// object, filling in the known pieces of information--well name patterns
/// and segment numbers--if applicable. A SetDescriptor with no well name
/// or segment number will match all segments in all MS wells.
///
/// The matching process, \code SegmentMatcher::findSegments() \endcode,
/// forms a \c SegmentSet object which holds a list of matching MS wells and
/// their associate/corresponding matching segment numbers.
class SegmentMatcher
{
public:
/// Result Set From Matching Process
class SegmentSet
{
public:
/// Demarcation of Start/End of Segment Range for Single MS Well
using WellSegmentRangeIterator = std::vector<int>::const_iterator;
/// Segment Range for Single MS Well.
class WellSegmentRange
{
public:
/// Start of Range
WellSegmentRangeIterator begin() const { return this->begin_; }
/// End of Range
WellSegmentRangeIterator end() const { return this->end_; }
/// Name of well to which this segment range is attached
std::string_view well() const { return this->well_; }
friend class SegmentSet;
private:
/// Start of Range
WellSegmentRangeIterator begin_{};
/// End of Range
WellSegmentRangeIterator end_{};
/// Name of well to which this segment range is attached
std::string_view well_{};
/// Default Constructor.
///
/// Empty range.
///
/// For use by SegmentSet only.
WellSegmentRange() = default;
/// Non-Empty Range
///
/// For use by SegmentSet only.
///
/// \param[in] begin Start of range.
/// \param[in] end End of range.
/// \param[in] well Name of well to which this segment range is
/// attached
WellSegmentRange(WellSegmentRangeIterator begin,
WellSegmentRangeIterator end,
std::string_view well)
: begin_{ begin }
, end_ { end }
, well_ { well }
{}
};
/// Default Constructor.
SegmentSet();
/// Predicate for whether or not segment set is empty.
///
/// \return Whether or not segment set is empty.
bool empty() const
{
return this->segments_.empty();
}
/// Predicate for whether or not segment set applies to a single
/// segment in a single MS well.
///
/// \return Whether or not segment set is a single segment in a
/// single MS well. Useful to distinguish whether or not this
/// segment set generates a scalar UDQ or a UDQ set in the context
/// of a segment level UDQ.
bool isScalar() const
{
return this->segments_.size() == std::vector<int>::size_type{1};
}
/// Retrieve list of (MS) well names covered by this result set.
///
/// \return List MS well names covered by this result set.
std::vector<std::string_view> wells() const;
/// Retrieve number of (MS) wells covered by this result set.
///
/// \return Number of MS wells covered by this result set.
std::size_t numWells() const
{
return this->wells_.size();
}
/// Retrive result set's segments for single MS well.
///
/// \param[in] well Named MS well. Should usually be one of the
/// items in the return value from \code wells() \endcode.
///
/// \return range of \c well's segments matching the input request.
/// Empty unless \p well is one of the return values from \code
/// wells() \endcode.
WellSegmentRange segments(std::string_view well) const;
/// Retrive result set's segments for single MS well.
///
/// \param[in] well Named MS well. Should usually be one of the
/// items in the return value from \code wells() \endcode.
///
/// \return range of \c well's segments matching the input request.
/// Empty unless \p well is one of the return values from \code
/// wells() \endcode.
WellSegmentRange segments(const std::size_t well) const;
friend class SegmentMatcher;
private:
/// List of MS wells covered by this result set.
std::vector<std::string> wells_{};
/// Name-to-index lookup table.
///
/// User, i.e., the SegmentMatcher, must call \code
/// establishNameLookupIndex() \endcode to prepare the lookup table.
std::vector<std::vector<std::string>::size_type> wellNameIndex_{};
/// CSR start pointers for MS wells' segments.
std::vector<std::vector<int>::size_type> segmentStart_{};
/// All segments covered by this result set. Structured by \c
/// segmentStart_.
std::vector<int> segments_{};
/// Build well-name to well number lookup index.
///
/// For use by SegmentMatcher only.
void establishNameLookupIndex();
/// Add non-empty range of segments for single MS well to result set.
///
/// For use by SegmentMatcher only.
///
/// \param[in] well Name of MS well.
///
/// \param[in] segments List of segment numbers matching input
/// request for \p well.
void addWellSegments(const std::string& well,
const std::vector<int>& segments);
};
/// Description of Particular Segment Set
///
/// User specified.
class SetDescriptor
{
public:
/// Default Constructor
SetDescriptor() = default;
/// Assign request's segment number.
///
/// Non-positive matches all segments.
///
/// \param[in] segNum Requests's segment number.
///
/// \return \code *this \endcode.
SetDescriptor& segmentNumber(const int segNum);
/// Assign request's segment number.
///
/// String version. Supports both quoted and unquoted strings.
/// Wildcard ('*') and string representation of a negative number
/// (e.g., '-1'), match all segments.
///
/// \param[in] segNum Requests's segment number. Must be a text
/// representation of an integer or one of the recognized options
/// for matching all segments. Throws exception otherwise.
///
/// \return \code *this \endcode.
SetDescriptor& segmentNumber(std::string_view segNum);
/// Retrive request's segment number
///
/// \return Segment number. Unset if request matches all segments.
const std::optional<int>& segmentNumber() const
{
return this->segmentNumber_;
}
/// Assign request's well set.
///
/// \param[in] wellNamePattern Pattern for selecting specific MS
/// wells. Used in normal matching, except well lists are not
/// supported.
SetDescriptor& wellNames(std::string_view wellNamePattern);
/// Retrive request's well name pattern.
///
/// \return Well name pattern. Unset if request matches all
/// multi-segmented wells.
const std::optional<std::string>& wellNames() const
{
return this->wellNamePattern_;
}
private:
/// Request's well name or well name pattern. Unset if request
/// applies to all MS wells.
std::optional<std::string> wellNamePattern_{};
/// Request's segment number. Unset if request applies to all
/// segments of pertinent well set.
std::optional<int> segmentNumber_{};
};
/// Default constructor
///
/// Disabled.
SegmentMatcher() = delete;
/// Constructor
///
/// \param[in] mswInputData Input's notion of existing wells, both
/// regular and multi-segmented. Expected to be the state of a
/// Schedule block at a particular report step.
explicit SegmentMatcher(const ScheduleState& mswInputData);
/// Copy constructor
///
/// Disabled.
///
/// \param[in] rhs Source object.
SegmentMatcher(const SegmentMatcher& rhs) = delete;
/// Move constructor
///
/// \param[in,out] rhs Source object. Left in empty state upon return.
SegmentMatcher(SegmentMatcher&& rhs);
/// Assignment operator
///
/// Disabled.
///
/// \param[in] rhs Source object.
///
/// \return \code *this \endcode.
SegmentMatcher& operator=(const SegmentMatcher& rhs) = delete;
/// Move-assignment operator
///
/// \param[in,out] rhs Source object. Left in empty state upon return.
///
/// \return \code *this \endcode.
SegmentMatcher& operator=(SegmentMatcher&& rhs);
/// Destructor
///
/// Needed to implement "pimpl" idiom.
~SegmentMatcher();
/// Determine set of MS wells and corresponding segments matching an
/// input set description.
///
/// Set is typically derived from description of user defined quantities
/// at the segment level, e.g.,
///
/// \code
/// UDQ
/// DEFINE SU-LPR1 SOFR OP01 + SWPR OP01 /
/// /
/// \endcode
///
/// which represents the surface level liquid production rate for all
/// segments in the multi-segmented well OP01.
///
/// \param[in] segments Segment set selection description.
///
/// \return Set of MS wells and corresponding segments matching input
/// set description.
SegmentSet findSegments(const SetDescriptor& segments) const;
private:
/// Implementation class.
class Impl;
/// Pointer to implementation.
std::unique_ptr<Impl> pImpl_{};
};
} // namespace Opm
#endif // SEGMENT_MATCHER_HPP

View File

@ -0,0 +1,405 @@
/*
Copyright 2022 Equinor ASA.
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 <http://www.gnu.org/licenses/>.
*/
#include <opm/input/eclipse/Schedule/MSW/SegmentMatcher.hpp>
#include <opm/input/eclipse/Schedule/MSW/Segment.hpp>
#include <opm/input/eclipse/Schedule/MSW/WellSegments.hpp>
#include <opm/input/eclipse/Schedule/ScheduleState.hpp>
#include <opm/input/eclipse/Schedule/Well/Well.hpp>
#include <opm/input/eclipse/Schedule/Well/WellMatcher.hpp>
#include <algorithm>
#include <cassert>
#include <charconv>
#include <cstddef>
#include <functional>
#include <memory>
#include <numeric>
#include <optional>
#include <regex>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <utility>
#include <vector>
#include <fmt/format.h>
class Opm::SegmentMatcher::Impl
{
public:
explicit Impl(const ScheduleState& mswInputData)
: mswInputData_{ std::cref(mswInputData) }
{}
template <class AddWellSegments>
SegmentSet findSegments(const SetDescriptor& request,
AddWellSegments addSegments) const;
private:
std::reference_wrapper<const ScheduleState> mswInputData_;
std::vector<std::string>
candidateWells(const std::optional<std::string>& wellNamePattern) const;
std::vector<std::string>
candidateWells(const std::string& wellNamePattern) const;
std::vector<std::string> candidateWells() const;
std::vector<std::string>
candidateWells(const std::vector<std::string>& allWells) const;
std::vector<int>
matchingSegments(const std::string& well,
const std::optional<int>& segmentNumber) const;
std::vector<int>
matchingSegments(const std::string& well,
const int segmentNumber) const;
std::vector<int> matchingSegments(const std::string& well) const;
};
template <class AddWellSegments>
Opm::SegmentMatcher::SegmentSet
Opm::SegmentMatcher::Impl::findSegments(const SetDescriptor& request,
AddWellSegments addSegments) const
{
auto segSet = SegmentSet{};
for (const auto& well : this->candidateWells(request.wellNames())) {
if (const auto& segments = this->matchingSegments(well, request.segmentNumber());
! segments.empty())
{
addSegments(well, segments, segSet);
}
}
return segSet;
}
std::vector<std::string>
Opm::SegmentMatcher::Impl::
candidateWells(const std::optional<std::string>& wellNamePattern) const
{
return wellNamePattern.has_value()
? this->candidateWells(*wellNamePattern)
: this->candidateWells();
}
std::vector<std::string>
Opm::SegmentMatcher::Impl::
candidateWells(const std::string& wellNamePattern) const
{
// Consider all MS wells matching 'wellNamePattern'.
return this->candidateWells(WellMatcher {
this->mswInputData_.get().well_order()
}.wells(wellNamePattern));
}
std::vector<std::string> Opm::SegmentMatcher::Impl::candidateWells() const
{
// No specific wellname pattern => all MS wells match.
return this->candidateWells(this->mswInputData_.get().well_order().names());
}
std::vector<std::string>
Opm::SegmentMatcher::Impl::
candidateWells(const std::vector<std::string>& allWells) const
{
auto candidates = std::vector<std::string>{};
candidates.reserve(allWells.size());
std::copy_if(allWells.begin(), allWells.end(), std::back_inserter(candidates),
[this](const std::string& well)
{
return this->mswInputData_.get().wells(well).isMultiSegment();
});
return candidates;
}
std::vector<int>
Opm::SegmentMatcher::Impl::
matchingSegments(const std::string& well,
const std::optional<int>& segmentNumber) const
{
return segmentNumber.has_value()
? this->matchingSegments(well, *segmentNumber)
: this->matchingSegments(well);
}
std::vector<int>
Opm::SegmentMatcher::Impl::
matchingSegments(const std::string& wellname,
const int segmentNumber) const
{
const auto& well = this->mswInputData_.get().wells(wellname);
assert (well.isMultiSegment());
if (const auto ix = well.getSegments().segmentNumberToIndex(segmentNumber);
ix < 0)
{
// SegmentNumber not among well's segments. Empty result set.
return {};
}
// SegmentNumber is among well's segments. Result set is exactly that segment.
return { segmentNumber };
}
std::vector<int>
Opm::SegmentMatcher::Impl::matchingSegments(const std::string& wellname) const
{
// No specific segment number => All segments match.
auto segments = std::vector<int>{};
const auto& well = this->mswInputData_.get().wells(wellname);
assert (well.isMultiSegment());
const auto& segSet = well.getSegments();
segments.reserve(segSet.size());
std::transform(segSet.begin(), segSet.end(), std::back_inserter(segments),
[](const Segment& segment) { return segment.segmentNumber(); });
return segments;
}
// ===========================================================================
// Public Interface Below Separator
// ===========================================================================
namespace {
std::string_view dequote(std::string_view s)
{
auto b = s.find_first_of("'");
if (b == std::string_view::npos) {
return s;
}
auto x = s.substr(b + 1);
auto e = x.find_first_of("'");
if (e == std::string_view::npos) {
throw std::invalid_argument {
fmt::format("Invalid quoted string |{}|", s)
};
}
return x.substr(0, e);
}
bool is_asterisk(std::string_view s)
{
const auto ast = std::regex { R"(\s*\*\s*)" };
return std::regex_match(s.begin(), s.end(), ast);
}
} // Anonymous namespace
Opm::SegmentMatcher::SetDescriptor&
Opm::SegmentMatcher::SetDescriptor::segmentNumber(const int segNum)
{
if (segNum <= 0) {
// No specific segment number
this->segmentNumber_.reset();
}
else {
this->segmentNumber_ = segNum;
}
return *this;
}
Opm::SegmentMatcher::SetDescriptor&
Opm::SegmentMatcher::SetDescriptor::segmentNumber(std::string_view segNum0)
{
auto segNum = dequote(segNum0);
if (segNum.empty()) {
// Not specified
return this->segmentNumber(0);
}
auto result = 0;
auto [ptr, ec] { std::from_chars(segNum.data(), segNum.data() + segNum.size(), result) };
if ((ec == std::errc{}) && (ptr == segNum.data() + segNum.size())) {
// Segment number is "123", or "'-1'", or something similar.
return this->segmentNumber(result);
}
else if ((ec == std::errc::invalid_argument) && is_asterisk(segNum)) {
// Segment number is '*'. Treat as all segments.
return this->segmentNumber(0);
}
else {
// Segment number is some unrecognized number string other than '*'.
throw std::invalid_argument {
fmt::format("Invalid segment number string |{}|", segNum0)
};
}
}
Opm::SegmentMatcher::SetDescriptor&
Opm::SegmentMatcher::SetDescriptor::wellNames(std::string_view wellNamePattern)
{
if (wellNamePattern.empty()) {
// Match all MS wells.
this->wellNamePattern_.reset();
}
else {
// Match only those MS wells whose names match 'wellNamePattern'.
this->wellNamePattern_ = wellNamePattern;
}
return *this;
}
// ---------------------------------------------------------------------------
Opm::SegmentMatcher::SegmentSet::SegmentSet()
{
this->segmentStart_.push_back(this->segments_.size());
}
std::vector<std::string_view>
Opm::SegmentMatcher::SegmentSet::wells() const
{
auto wellset = std::vector<std::string_view>{};
wellset.reserve(this->wells_.size());
auto ix = std::vector<std::vector<int>::size_type>::size_type{0};
for (const auto& well : this->wells_) {
if (this->segmentStart_[ix] != this->segmentStart_[ix + 1]) {
wellset.emplace_back(well);
}
ix += 1;
}
return wellset;
}
Opm::SegmentMatcher::SegmentSet::WellSegmentRange
Opm::SegmentMatcher::SegmentSet::segments(std::string_view well) const
{
using Ix = std::vector<std::string>::size_type;
// Get 'well's insertion index from list sorted alphabetically on well
// names. Client must call establishNameLookupIndex() prior to calling
// segments(string_view).
auto ixPos =
std::lower_bound(this->wellNameIndex_.begin(), this->wellNameIndex_.end(),
well, [this](const Ix i, std::string_view wellname)
{
return this->wells_[i] < wellname;
});
if ((ixPos == this->wellNameIndex_.end()) || (this->wells_[*ixPos] != well)) {
return this->segments(this->numWells());
}
else {
return this->segments(*ixPos);
}
}
Opm::SegmentMatcher::SegmentSet::WellSegmentRange
Opm::SegmentMatcher::SegmentSet::segments(const std::size_t well) const
{
if (well >= this->numWells()) {
return {};
}
const auto first = this->segmentStart_[well + 0];
const auto last = this->segmentStart_[well + 1];
return {
this->segments_.begin() + first,
this->segments_.begin() + last,
this->wells_[well]
};
}
void Opm::SegmentMatcher::SegmentSet::establishNameLookupIndex()
{
using Ix = std::vector<std::string>::size_type;
// Sort well insertion/order indices alphabetically on well names.
// Enables using std::lobwer_bound() in segments(well).
this->wellNameIndex_.resize(this->wells_.size());
std::iota(this->wellNameIndex_.begin(), this->wellNameIndex_.end(), Ix{0});
std::sort(this->wellNameIndex_.begin(), this->wellNameIndex_.end(),
[this](const Ix i1, const Ix i2)
{
return this->wells_[i1] < this->wells_[i2];
});
}
void Opm::SegmentMatcher::SegmentSet::addWellSegments(const std::string& well,
const std::vector<int>& segments)
{
assert (! segments.empty());
this->wells_.push_back(well);
// Note that segmentStart_.push_back() must be after segments_.insert()
// in order to maintain CSR invariant.
this->segments_.insert(this->segments_.end(), segments.begin(), segments.end());
this->segmentStart_.push_back(this->segments_.size());
}
// ---------------------------------------------------------------------------
Opm::SegmentMatcher::SegmentMatcher(const ScheduleState& mswInputData)
: pImpl_{ new Impl { mswInputData } }
{}
Opm::SegmentMatcher::SegmentMatcher(SegmentMatcher&& rhs)
: pImpl_{ std::move(rhs.pImpl_) }
{}
Opm::SegmentMatcher&
Opm::SegmentMatcher::operator=(SegmentMatcher&& rhs)
{
this->pImpl_ = std::move(rhs.pImpl_);
return *this;
}
Opm::SegmentMatcher::~SegmentMatcher() = default;
Opm::SegmentMatcher::SegmentSet
Opm::SegmentMatcher::findSegments(const SetDescriptor& segments) const
{
auto segSet = this->pImpl_->
findSegments(segments,
[](const std::string& well,
const std::vector<int>& well_segments,
SegmentSet& seg_set)
{
seg_set.addWellSegments(well, well_segments);
});
segSet.establishNameLookupIndex();
return segSet;
}

File diff suppressed because it is too large Load Diff