Add Utility for Region-Based Phase-Filled Averages

The initial use case is calculating the phase-filled pore-volume
weighted average of the fluid mass densities per PVT region.  This
value goes into calculating depth-corrected per-cell phase pressure
values such as the BPPO and BPPG summary vectors.

This class manages a single linear array which separately tracks the
averages' numerators and denominators as running sums per region and
region set.  We pick this data structure to simplify the cross-rank
reduction needed in MPI parallel runs.  Client code is expected to
add individual per-cell and per-phase contributions using the
addCell() member function and then call the accumulateParallel()
member to affect the cross-rank reduction.  The averages will then
be available through the fieldValue() and value() member functions.

As a further view towards the initial use case, we track two
different types of average per phase--one for the phase-filled
volume and one for the pore-volume filled volume.  The latter is the
average we would get for the case of the phase saturation being one
throughout the region.  This alternative value is the fallback
option for the case of the phase saturation being identically zero
throughout the region.
This commit is contained in:
Bård Skaflestad 2024-02-28 10:52:03 +01:00
parent 06dba324d2
commit d1d22668ea
5 changed files with 1685 additions and 0 deletions

View File

@ -476,6 +476,51 @@ opm_add_test(test_parallel_wbp_calculation_well_openconns
2
)
opm_add_test(test_parallel_region_phase_pvaverage_np2
EXE_NAME
test_region_phase_pvaverage
CONDITION
MPI_FOUND AND Boost_UNIT_TEST_FRAMEWORK_FOUND
DRIVER_ARGS
-n 2
-b ${PROJECT_BINARY_DIR}
TEST_ARGS
--run_test=Parallel/*
NO_COMPILE
PROCESSORS
2
)
opm_add_test(test_parallel_region_phase_pvaverage_np3
EXE_NAME
test_region_phase_pvaverage
CONDITION
MPI_FOUND AND Boost_UNIT_TEST_FRAMEWORK_FOUND
DRIVER_ARGS
-n 3
-b ${PROJECT_BINARY_DIR}
TEST_ARGS
--run_test=Parallel/*
NO_COMPILE
PROCESSORS
3
)
opm_add_test(test_parallel_region_phase_pvaverage_np4
EXE_NAME
test_region_phase_pvaverage
CONDITION
MPI_FOUND AND Boost_UNIT_TEST_FRAMEWORK_FOUND
DRIVER_ARGS
-n 4
-b ${PROJECT_BINARY_DIR}
TEST_ARGS
--run_test=Parallel/*
NO_COMPILE
PROCESSORS
4
)
opm_add_test(test_broadcast
DEPENDS "opmsimulators"
LIBRARIES opmsimulators ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}

View File

@ -51,6 +51,7 @@ list (APPEND MAIN_SOURCE_FILES
opm/simulators/flow/Main.cpp
opm/simulators/flow/NonlinearSolver.cpp
opm/simulators/flow/RSTConv.cpp
opm/simulators/flow/RegionPhasePVAverage.cpp
opm/simulators/flow/SimulatorReportBanners.cpp
opm/simulators/flow/SimulatorSerializer.cpp
opm/simulators/flow/ValidationFunctions.cpp
@ -284,6 +285,7 @@ list (APPEND TEST_SOURCE_FILES
tests/test_partitionCells.cpp
tests/test_preconditionerfactory.cpp
tests/test_privarspacking.cpp
tests/test_region_phase_pvaverage.cpp
tests/test_relpermdiagnostics.cpp
tests/test_RestartSerialization.cpp
tests/test_rstconv.cpp
@ -460,6 +462,7 @@ list (APPEND PUBLIC_HEADER_FILES
opm/simulators/flow/partitionCells.hpp
opm/simulators/flow/priVarsPacking.hpp
opm/simulators/flow/RSTConv.hpp
opm/simulators/flow/RegionPhasePVAverage.hpp
opm/simulators/flow/SimulatorFullyImplicitBlackoil.hpp
opm/simulators/flow/SimulatorReportBanners.hpp
opm/simulators/flow/SimulatorSerializer.hpp

View File

@ -0,0 +1,216 @@
// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
// vi: set et ts=4 sw=4 sts=4:
/*
Copyright 2024 Equinor AS
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 <config.h>
#include <opm/simulators/flow/RegionPhasePVAverage.hpp>
#include <opm/input/eclipse/EclipseState/Grid/FieldProps.hpp>
#include <opm/simulators/utils/ParallelCommunication.hpp>
#include <algorithm>
#include <cstddef>
#include <functional>
#include <vector>
namespace {
std::vector<std::string> fipRegionNames(const std::vector<std::string>& regionNames)
{
auto regs = regionNames;
std::sort(regs.begin(), regs.end());
return { regs.begin(), std::unique(regs.begin(), regs.end()) };
}
std::vector<std::vector<double>::size_type>
regionStartPointers(const std::vector<std::string>& regionNames,
const Opm::RegionPhasePoreVolAverage::RegionArrayAccessor& getRegionArray,
const Opm::Parallel::Communication& comm)
{
// All elements get an initial value of 1 to account for the maximum
// region ID. There should be "max ID + 1" elements for each region
// set. Unused IDs--e.g., 0 if IDs start at 1--will effectively be
// wasted in this scheme. If that becomes a problem we might
// consider some kind of renumbering approach.
auto start = std::vector<std::vector<double>::size_type>(regionNames.size() + 1, 1);
const auto nset = regionNames.size();
for (auto rset = 0*nset; rset < nset; ++rset) {
const auto& reg = getRegionArray(regionNames[rset]);
auto m = std::max_element(reg.begin(), reg.end());
if (m == reg.end()) { // reg.empty()
continue;
}
start[rset + 1] += *m;
}
comm.max(start.data(), start.size());
std::partial_sum(start.begin(), start.end(), start.begin());
return start;
}
} // Anonymous namespace
Opm::RegionPhasePoreVolAverage::
RegionPhasePoreVolAverage(const Parallel::Communication& comm,
const std::size_t numPhases,
const std::vector<std::string>& regionNames,
RegionArrayAccessor getRegionArray)
: comm_ { std::cref(comm) }
, np_ { numPhases }
, rsetNames_ { fipRegionNames(regionNames) }
, getRegionArray_ { std::move(getRegionArray) }
, rsStart_ { regionStartPointers(rsetNames_, getRegionArray_, comm_) }
, x_ (rsStart_.back() * numPhases * AvgType::NumTypes * Element::NumElem)
{}
double Opm::RegionPhasePoreVolAverage::fieldValue(const Phase& p) const
{
return this->averageValueWithFallback(this->fieldStartIx(p.ix));
}
double
Opm::RegionPhasePoreVolAverage::
value(std::string_view rset, const Phase& p, const Region& r) const
{
auto rsetPos = std::lower_bound(this->rsetNames_.begin(),
this->rsetNames_.end(), rset);
if ((rsetPos == this->rsetNames_.end()) || (*rsetPos != rset)) {
// rset is not a known region set name.
return 0.0; // Maybe nullopt or throw here...
}
const auto rsetIx = std::distance(this->rsetNames_.begin(), rsetPos);
return this->averageValueWithFallback(this->rsetStartIx(rsetIx, r.ix, p.ix));
}
void Opm::RegionPhasePoreVolAverage::prepareAccumulation()
{
std::fill(this->x_.begin(), this->x_.end(), 0.0);
}
void Opm::RegionPhasePoreVolAverage::
addCell(const std::size_t activeCell,
const Phase& p,
const CellValue& cv)
{
this->add(this->fieldStartIx(p.ix), cv);
for (auto rset = 0*this->rsetNames_.size(); rset < this->rsetNames_.size(); ++rset) {
this->add(this->rsetStartIx(rset, this->regionIndex(rset, activeCell), p.ix), cv);
}
}
void Opm::RegionPhasePoreVolAverage::accumulateParallel()
{
this->comm_.get().sum(this->x_.data(), this->x_.size());
}
double Opm::RegionPhasePoreVolAverage::averageValueWithFallback(const Ix start) const
{
const auto spv_w = this->weight(start, AvgType::SatPV);
return (spv_w > 0.0)
? this->averageValue(start, AvgType::SatPV)
: this->averageValue(start, AvgType::PV);
}
double Opm::RegionPhasePoreVolAverage::averageValue(const Ix start,
const AvgType type) const
{
return this->value(start, type) / this->weight(start, type);
}
Opm::RegionPhasePoreVolAverage::Ix
Opm::RegionPhasePoreVolAverage::fieldStartIx(const unsigned int phase) const
{
return this->startIx(0, phase);
}
Opm::RegionPhasePoreVolAverage::Ix
Opm::RegionPhasePoreVolAverage::rsetStartIx(const std::size_t rset,
const int region,
const unsigned int phase) const
{
return this->startIx(this->rsStart_[rset] + region, phase);
}
Opm::RegionPhasePoreVolAverage::Ix
Opm::RegionPhasePoreVolAverage::startIx(const std::size_t offset,
const unsigned int phase) const
{
return (offset*this->np_ + phase) * AvgType::NumTypes * Element::NumElem;
}
int Opm::RegionPhasePoreVolAverage::regionIndex(const std::size_t rset,
const std::size_t activeCell) const
{
return this->getRegionArray_(this->rsetNames_[rset])[activeCell];
}
void Opm::RegionPhasePoreVolAverage::add(const Ix start, const CellValue& cv)
{
this->add(start, AvgType::SatPV, cv.value, cv.sat * cv.porv);
this->add(start, AvgType::PV , cv.value, cv.porv);
}
void Opm::RegionPhasePoreVolAverage::add(const Ix start,
const AvgType type,
const double x,
const double w)
{
this->value (start, type) += w * x;
this->weight(start, type) += w;
}
double& Opm::RegionPhasePoreVolAverage::value(const Ix start, const AvgType type)
{
return this->x_[ this->valueArrayIndex(start, type, Element::Value) ];
}
double& Opm::RegionPhasePoreVolAverage::weight(const Ix start, const AvgType type)
{
return this->x_[ this->valueArrayIndex(start, type, Element::Weight) ];
}
double Opm::RegionPhasePoreVolAverage::value(const Ix start, const AvgType type) const
{
return this->x_[ this->valueArrayIndex(start, type, Element::Value) ];
}
double Opm::RegionPhasePoreVolAverage::weight(const Ix start, const AvgType type) const
{
return this->x_[ this->valueArrayIndex(start, type, Element::Weight) ];
}
Opm::RegionPhasePoreVolAverage::Ix
Opm::RegionPhasePoreVolAverage::valueArrayIndex(const Ix start,
const AvgType type,
const Element element) const
{
return start + type*Element::NumElem + element;
}

View File

@ -0,0 +1,353 @@
// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
// vi: set et ts=4 sw=4 sts=4:
/*
Copyright 2024 Equinor AS
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 OPM_REGION_PHASE_POREVOL_AVERAGE_MODULE_HPP
#define OPM_REGION_PHASE_POREVOL_AVERAGE_MODULE_HPP
#include <opm/simulators/utils/ParallelCommunication.hpp>
#include <cstddef>
#include <functional>
#include <string>
#include <string_view>
#include <vector>
namespace Opm {
/// Facility for calculating volume-weighted average function values
/// over user-defined regions in parallel. Defaults to phase-filled
/// pore-volume averages, but falls back to simple pore-volume averages
/// if the saturation is identically zero in a region--i.e., calculates
/// what the average value would be if the phase fully occupied the
/// available pore-volume in this case.
class RegionPhasePoreVolAverage
{
public:
/// Minimal characteristics of a cell from a simulation grid.
struct CellValue
{
/// Function value
double value { 0.0 };
/// Phase saturation
double sat { 0.0 };
/// Reservoir condition pore-volume
double porv { 0.0 };
};
/// Compile-time disambiguation type for phases.
struct Phase
{
/// Phase index
unsigned int ix;
};
/// Compile-time disambiguation type for regions.
struct Region
{
/// Region index
unsigned int ix;
};
/// Call-back function type for accessing region arrays--typically the FIP* arrays.
using RegionArrayAccessor = std::function<const std::vector<int>&(const std::string&)>;
/// Constructor.
///
/// \param[in] comm Grid level global communicator.
///
/// \param[in] numPhases Number of phases for which to calculate
/// average values.
///
/// \param[in] regionNames List of region sets. Typically contains
/// one or more of the FIP* array names and, possibly, the PVTNUM
/// region set as well.
///
/// \param[in] getRegionArray Call-back function for accessing
/// region definition from region set names.
explicit RegionPhasePoreVolAverage(const Parallel::Communication& comm,
std::size_t numPhases,
const std::vector<std::string>& regionNames,
RegionArrayAccessor getRegionArray);
/// Retrieve field-level average function value for specific phase.
///
/// \param[in] p Phase for which to retrieve the field-level average
/// function value.
///
/// \return Field-level average function value for phase \p p.
double fieldValue(const Phase& p) const;
/// Retrieve region-level average function value for specific phase
/// in specific region of named region set.
///
/// \param[in] rset Named region set--e.g., "FIPNUM".
///
/// \param[in] p Phase for which to retrieve the region-level average
/// function value.
///
/// \param[in] r Region ID for which to retrieve the region-level
/// average function value.
///
/// \return Region-level average function value.
double value(std::string_view rset, const Phase& p, const Region& r) const;
/// Clear internal arrays in preparation of accumulating
/// region-level averages from per-cell contributions.
void prepareAccumulation();
/// Incorporate contributions from a single cell.
///
/// \param[in] activeCell Per-rank active cell ID--typically one of
/// the rank's interior cells.
///
/// \param[in] p Phase for which to incorporate the per-cell contribution.
///
/// \param[in] cv Single cell function value contribution.
void addCell(std::size_t activeCell, const Phase& p, const CellValue& cv);
/// Accumulate region-level average values across MPI ranks.
///
/// Typically the last step in calculating the region-level average
/// values. It is typically an error to call this function multiple
/// times without an intervening call to \c prepareAccumulation().
void accumulateParallel();
private:
/// Index type for value array.
using Ix = std::vector<double>::size_type;
/// Kinds of weighting functions.
enum AvgType {
SatPV, //< Weighted by phase-filled pore-volume.
PV, //< Weighted by total pore-volume (s=1).
// ---- Must be last enumerator ----
NumTypes, //< Number of averge types.
};
/// Elements/items collected per average type.
enum Element {
Value, //< Running sum of weighted function values.
Weight, //< Running sum of weights.
// ---- Must be last enumerator ----
NumElem, //< Number of items/elements per average type.
};
/// MPI communication object.
std::reference_wrapper<const Parallel::Communication> comm_;
/// Number of phases for which to accumulate function value averages.
std::size_t np_{};
/// Named region sets.
std::vector<std::string> rsetNames_{};
/// Call-back function for accessing region set arrays.
RegionArrayAccessor getRegionArray_;
/// Start pointers for average values of each named region set. In
/// particular, \code rsStart_[i] \endcode identifies the offset
/// into \c x_ of the first average type of the first phase of the
/// first region in region set \code rsetNames_[i] \endcode.
std::vector<Ix> rsStart_{};
/// All elements--i.e., running sums--that go into calculating the
/// field- and region-level weighted function averages per phase.
/// We store all elements in a single linear array, and track
/// numerators and denominators in separate elements, in order to
/// simplify cross-rank reduction in parallel simulation runs. See
/// accumulateParallel() for details on this reduction process.
///
/// There are \c np_ base entries for each region in each region set
/// (+ FIELD). Each base entry has \code AvgType::NumTypes \endcode
/// different average types, and each average type comprises \code
/// Element::NumElem \endcode separate elements in \c x_.
///
/// You should typically access this array through the value() and
/// weight() member functions, and the start index to both of those
/// should be the return value from fieldStartIx() or rsetStartIx().
std::vector<double> x_{};
/// Compute final average value for a single region and phase.
///
/// Prefers the average value weighted by phase-filled pore-volume,
/// but falls back to average value weighted by total pore-volume if
/// saturation is identically zero throughout the region.
///
/// \param[in] start Offset into linearised value array (\c x_)
/// corresponding to the first average value type of a particular
/// phase in a particular region. Usually calculated by
/// fieldStartIx() or rsetStartIx().
///
/// \return Weighted average function value.
double averageValueWithFallback(Ix start) const;
/// Compute average function value for a single region and phase.
///
/// \param[in] start Offset into linearised value array (\c x_)
/// corresponding to the first average value type of a particular
/// phase in a particular region. Usually calculated by
/// fieldStartIx() or rsetStartIx().
///
/// \param[in] type Which kind of average value to compute.
///
/// \return Weighted average function value.
double averageValue(Ix start, AvgType type) const;
/// Compute linearised value array offset for field-level average
/// function values of a single phase.
///
/// \param[in] phase Phase index for which to compute array offset.
///
/// \return Value array offset for field-level averages of \p phase.
Ix fieldStartIx(unsigned int phase) const;
/// Compute linearised value array offset for region-level average
/// function values of a single phase.
///
/// \param[in] rset Enumerated region set.
///
/// \param[in] region Region index within \p rset.
///
/// \param[in] phase Phase index for which to compute array offset.
///
/// \return Value array offset for region-level averages of \p phase.
Ix rsetStartIx(std::size_t rset, int region, unsigned int phase) const;
/// Compute linearised value array offset for average function
/// values of a single phase.
///
/// \param[in] offset Field or region base offset.
///
/// \param[in] phase Phase index for which to compute array offset.
///
/// \return Value array offset for weighted averages of \p phase.
Ix startIx(std::size_t offset, unsigned int phase) const;
/// Compute region ID of single active cell within particular region
/// set.
///
/// \param[in] rset Enumerated region set.
///
/// \param[in] activeCell Per-rank active cell ID--typically one of
/// the rank's interior cells.
///
/// \return Region ID of \p activeCell within \p rset.
int regionIndex(std::size_t rset, std::size_t activeCell) const;
/// Incorporate per-cell contribution into all average function types.
///
/// \param[in] start Offset into linearised value array (\c x_)
/// corresponding to the first average value type of a particular
/// phase in a particular region. Usually calculated by
/// fieldStartIx() or rsetStartIx().
///
/// \param[in] cv Single cell function value contribution.
void add(Ix start, const CellValue& cv);
/// Incorporate per-cell contribution into specific function type.
///
/// \param[in] start Offset into linearised value array (\c x_)
/// corresponding to the first average value type of a particular
/// phase in a particular region. Usually calculated by
/// fieldStartIx() or rsetStartIx().
///
/// \param[in] type Which kind of average value to accumulate.
///
/// \param[in] x Function value.
///
/// \param[in] w Function weight.
void add(Ix start, AvgType type, double x, double w);
/// Mutable access to value item of specific average value type
///
/// \param[in] start Offset into linearised value array (\c x_)
/// corresponding to the first average value type of a particular
/// phase in a particular region. Usually calculated by
/// fieldStartIx() or rsetStartIx().
///
/// \param[in] type Which kind of average value to accumulate.
///
/// \return Reference to mutable element of linearised value array
/// corresponding to the running sum of function values.
double& value(Ix start, AvgType type);
/// Mutable access to weight item of specific average value type
///
/// \param[in] start Offset into linearised value array (\c x_)
/// corresponding to the first average value type of a particular
/// phase in a particular region. Usually calculated by
/// fieldStartIx() or rsetStartIx().
///
/// \param[in] type Which kind of average value to accumulate.
///
/// \return Reference to mutable element of linearised value array
/// corresponding to the running sum of function weights.
double& weight(Ix start, AvgType type);
/// Read-only access to value item of specific average value type
///
/// \param[in] start Offset into linearised value array (\c x_)
/// corresponding to the first average value type of a particular
/// phase in a particular region. Usually calculated by
/// fieldStartIx() or rsetStartIx().
///
/// \param[in] type Which kind of average value to accumulate.
///
/// \return Running sum of function value.
double value(Ix start, AvgType type) const;
/// Read-only access to weight item of specific average value type
///
/// \param[in] start Offset into linearised value array (\c x_)
/// corresponding to the first average value type of a particular
/// phase in a particular region. Usually calculated by
/// fieldStartIx() or rsetStartIx().
///
/// \param[in] type Which kind of average value to accumulate.
///
/// \return Running sum of function weights.
double weight(Ix start, AvgType type) const;
/// Compute value array index of particular item of particular
/// average value
///
/// \param[in] start Offset into linearised value array (\c x_)
/// corresponding to the first average value type of a particular
/// phase in a particular region. Usually calculated by
/// fieldStartIx() or rsetStartIx().
///
/// \param[in] type Which kind of average value to reference.
///
/// \param[in] element Which running sum element to reference for
/// this particular average value.
///
/// \return Index into linearised value array \c x_ corresponding to
/// this particular element of this particular average function
/// type.
Ix valueArrayIndex(Ix start, AvgType type, Element element) const;
};
} // namespace Opm
#endif // OPM_REGION_PHASE_POREVOL_AVERAGE_MODULE_HPP

File diff suppressed because it is too large Load Diff