opm-simulators/opm/simulators/flow/RegionPhasePVAverage.cpp
Bård Skaflestad d1d22668ea 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.
2024-02-28 12:08:24 +01:00

217 lines
7.5 KiB
C++

// -*- 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;
}