// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
// vi: set et ts=4 sw=4 sts=4:
/*
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 2 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 .
Consult the COPYING file in the top-level source directory of this
module for the precise wording of the license and the list of
copyright holders.
*/
/*!
* \file
*
* \copydoc Opm::EclTransmissibility
*/
#ifndef EWOMS_ECL_TRANSMISSIBILITY_HH
#define EWOMS_ECL_TRANSMISSIBILITY_HH
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
BEGIN_PROPERTIES
NEW_PROP_TAG(Scalar);
NEW_PROP_TAG(Vanguard);
NEW_PROP_TAG(Grid);
NEW_PROP_TAG(GridView);
NEW_PROP_TAG(ElementMapper);
NEW_PROP_TAG(EnableEnergy);
END_PROPERTIES
namespace Opm {
/*!
* \ingroup EclBlackOilSimulator
*
* \brief This class calculates the transmissibilites for grid faces according to the
* Eclipse Technical Description.
*/
template
class EclTransmissibility
{
typedef typename GET_PROP_TYPE(TypeTag, Grid) Grid;
typedef typename GET_PROP_TYPE(TypeTag, GridView) GridView;
typedef typename GET_PROP_TYPE(TypeTag, Scalar) Scalar;
typedef typename GET_PROP_TYPE(TypeTag, Vanguard) Vanguard;
typedef typename GET_PROP_TYPE(TypeTag, ElementMapper) ElementMapper;
typedef typename GridView::Intersection Intersection;
static const bool enableEnergy = GET_PROP_VALUE(TypeTag, EnableEnergy);
// Grid and world dimension
enum { dimWorld = GridView::dimensionworld };
typedef Dune::FieldMatrix DimMatrix;
typedef Dune::FieldVector DimVector;
static const unsigned elemIdxShift = 32; // bits
public:
EclTransmissibility(const Vanguard& vanguard)
: vanguard_(vanguard)
{
const Opm::UnitSystem& unitSystem = vanguard_.eclState().getDeckUnitSystem();
transmissibilityThreshold_ = unitSystem.parse("Transmissibility").getSIScaling() * 1e-6;
}
/*!
* \brief Actually compute the transmissibilty over a face as a pre-compute step.
*
* This code actually uses the direction specific "centroids" of
* each element. These "centroids" are _not_ the identical
* barycenter of the element, but the middle of the centers of the
* faces of the logical Cartesian cells, i.e., the centers of the
* faces of the reference elements. We do things this way because
* the barycenter of the element can be located outside of the
* element for sufficiently "ugly" (i.e., thin and "non-flat")
* elements which in turn leads to quite wrong
* permeabilities. This approach is probably not always correct
* either but at least it seems to be much better.
*/
void finishInit()
{ update(true); }
/*!
* \brief Compute all transmissibilities
*
* \param global If true, update is called on all processes
* Also, this updates the "thermal half transmissibilities" if energy is enabled.
*/
void update(bool global)
{
const auto& gridView = vanguard_.gridView();
const auto& cartMapper = vanguard_.cartesianIndexMapper();
const auto& eclState = vanguard_.eclState();
const auto& cartDims = cartMapper.cartesianDimensions();
auto& transMult = eclState.getTransMult();
const auto& comm = vanguard_.gridView().comm();
ElementMapper elemMapper(gridView, Dune::mcmgElementLayout());
// get the ntg values, the ntg values are modified for the cells merged with minpv
const std::vector& ntg = eclState.fieldProps().get_double("NTG");
unsigned numElements = elemMapper.size();
extractPermeability_();
// calculate the axis specific centroids of all elements
std::array, dimWorld> axisCentroids;
for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx)
axisCentroids[dimIdx].resize(numElements);
std::vector centroids;
#if HAVE_MPI
size_t cells = vanguard_.grid().numCells();
if (global && comm.size() > 1) {
std::vector sizes(comm.size());
if (comm.rank() == 0) {
const auto& eclGrid = eclState.getInputGrid();
comm.gather(&cells, sizes.data(), 1, 0);
for (int i = 1; i < comm.size(); ++i) {
std::vector cell_id(sizes[i]);
MPI_Recv(cell_id.data(), sizes[i], MPI_INT,
i, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
centroids.resize(dimWorld * sizes[i]);
auto cIt = centroids.begin();
for (int idx : cell_id) {
const auto& centroid = eclGrid.getCellCenter(idx);
for (const auto& it : centroid)
*cIt++ = it;
}
MPI_Send(centroids.data(), dimWorld * sizes[i],
MPI_DOUBLE, i, 0, MPI_COMM_WORLD);
}
centroids.clear();
} else {
comm.gather(&cells, sizes.data(), 1, 0);
std::vector cell_ids;
cell_ids.reserve(cells);
auto elemIt = gridView.template begin*codim=*/ 0>();
const auto& elemEndIt = gridView.template end*codim=*/ 0>();
for (; elemIt != elemEndIt; ++elemIt) {
const auto& elem = *elemIt;
cell_ids.push_back(cartMapper.cartesianIndex(elemMapper.index(elem)));
}
MPI_Send(cell_ids.data(), cells, MPI_INT, 0, 0, MPI_COMM_WORLD);
centroids.resize(cells * dimWorld);
MPI_Recv(centroids.data(), dimWorld * cells, MPI_DOUBLE,
0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
}
}
#endif
auto elemIt = gridView.template begin*codim=*/ 0>();
const auto& elemEndIt = gridView.template end*codim=*/ 0>();
size_t centroidIdx = 0;
for (; elemIt != elemEndIt; ++elemIt, ++centroidIdx) {
const auto& elem = *elemIt;
unsigned elemIdx = elemMapper.index(elem);
// compute the axis specific "centroids" used for the transmissibilities. for
// consistency with the flow simulator, we use the element centers as
// computed by opm-parser's Opm::EclipseGrid class for all axes.
const double* centroid;
if (vanguard_.gridView().comm().rank() == 0) {
const auto& eclGrid = eclState.getInputGrid();
unsigned cartesianCellIdx = cartMapper.cartesianIndex(elemIdx);
centroid = &eclGrid.getCellCenter(cartesianCellIdx)[0];
} else
centroid = ¢roids[centroidIdx * dimWorld];
for (unsigned axisIdx = 0; axisIdx < dimWorld; ++axisIdx)
for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx)
axisCentroids[axisIdx][elemIdx][dimIdx] = centroid[dimIdx];
}
// reserving some space in the hashmap upfront saves quite a bit of time because
// resizes are costly for hashmaps and there would be quite a few of them if we
// would not have a rough idea of how large the final map will be (the rough idea
// is a conforming Cartesian grid).
trans_.clear();
trans_.reserve(numElements*3*1.05);
transBoundary_.clear();
// if energy is enabled, let's do the same for the "thermal half transmissibilities"
if (enableEnergy) {
thermalHalfTrans_->clear();
thermalHalfTrans_->reserve(numElements*6*1.05);
thermalHalfTransBoundary_.clear();
}
// The MULTZ needs special case if the option is ALL
// Then the smallest multiplier is applied.
// Default is to apply the top and bottom multiplier
bool useSmallestMultiplier;
if (comm.rank() == 0) {
const auto& eclGrid = eclState.getInputGrid();
useSmallestMultiplier = eclGrid.getMultzOption() == Opm::PinchMode::ModeEnum::ALL;
}
if (global && comm.size() > 1) {
comm.broadcast(&useSmallestMultiplier, 1, 0);
}
// compute the transmissibilities for all intersections
elemIt = gridView.template begin*codim=*/ 0>();
for (; elemIt != elemEndIt; ++elemIt) {
const auto& elem = *elemIt;
unsigned elemIdx = elemMapper.index(elem);
auto isIt = gridView.ibegin(elem);
const auto& isEndIt = gridView.iend(elem);
unsigned boundaryIsIdx = 0;
for (; isIt != isEndIt; ++ isIt) {
// store intersection, this might be costly
const auto& intersection = *isIt;
// deal with grid boundaries
if (intersection.boundary()) {
// compute the transmissibilty for the boundary intersection
const auto& geometry = intersection.geometry();
const auto& faceCenterInside = geometry.center();
auto faceAreaNormal = intersection.centerUnitOuterNormal();
faceAreaNormal *= geometry.volume();
Scalar transBoundaryIs;
computeHalfTrans_(transBoundaryIs,
faceAreaNormal,
intersection.indexInInside(),
distanceVector_(faceCenterInside,
intersection.indexInInside(),
elemIdx,
axisCentroids),
permeability_[elemIdx]);
// normally there would be two half-transmissibilities that would be
// averaged. on the grid boundary there only is the half
// transmissibility of the interior element.
transBoundary_[std::make_pair(elemIdx, boundaryIsIdx)] = transBoundaryIs;
// for boundary intersections we also need to compute the thermal
// half transmissibilities
if (enableEnergy) {
const auto& n = intersection.centerUnitOuterNormal();
const auto& inPos = elem.geometry().center();
const auto& outPos = intersection.geometry().center();
const auto& d = outPos - inPos;
// eWoms expects fluxes to be area specific, i.e. we must *not*
// the transmissibility with the face area here
Scalar thermalHalfTrans = std::abs(n*d)/(d*d);
thermalHalfTransBoundary_[std::make_pair(elemIdx, boundaryIsIdx)] =
thermalHalfTrans;
}
++ boundaryIsIdx;
continue;
}
if (!intersection.neighbor())
// elements can be on process boundaries, i.e. they are not on the
// domain boundary yet they don't have neighbors.
continue;
const auto& outsideElem = intersection.outside();
unsigned outsideElemIdx = elemMapper.index(outsideElem);
// update the "thermal half transmissibility" for the intersection
if (enableEnergy) {
const auto& n = intersection.centerUnitOuterNormal();
Scalar A = intersection.geometry().volume();
const auto& inPos = elem.geometry().center();
const auto& outPos = intersection.geometry().center();
const auto& d = outPos - inPos;
(*thermalHalfTrans_)[directionalIsId_(elemIdx, outsideElemIdx)] =
A * (n*d)/(d*d);
}
// we only need to calculate a face's transmissibility
// once...
if (elemIdx > outsideElemIdx)
continue;
unsigned insideCartElemIdx = cartMapper.cartesianIndex(elemIdx);
unsigned outsideCartElemIdx = cartMapper.cartesianIndex(outsideElemIdx);
// local indices of the faces of the inside and
// outside elements which contain the intersection
int insideFaceIdx = intersection.indexInInside();
int outsideFaceIdx = intersection.indexInOutside();
if (insideFaceIdx == -1) {
// NNC. Set zero transmissibility, as it will be
// *added to* by applyNncToGridTrans_() later.
assert(outsideFaceIdx == -1);
trans_[isId_(elemIdx, outsideElemIdx)] = 0.0;
continue;
}
DimVector faceCenterInside;
DimVector faceCenterOutside;
DimVector faceAreaNormal;
typename std::is_same::type isCpGrid;
computeFaceProperties(intersection,
elemIdx,
insideFaceIdx,
outsideElemIdx,
outsideFaceIdx,
faceCenterInside,
faceCenterOutside,
faceAreaNormal,
isCpGrid);
Scalar halfTrans1;
Scalar halfTrans2;
computeHalfTrans_(halfTrans1,
faceAreaNormal,
insideFaceIdx,
distanceVector_(faceCenterInside,
intersection.indexInInside(),
elemIdx,
axisCentroids),
permeability_[elemIdx]);
computeHalfTrans_(halfTrans2,
faceAreaNormal,
outsideFaceIdx,
distanceVector_(faceCenterOutside,
intersection.indexInOutside(),
outsideElemIdx,
axisCentroids),
permeability_[outsideElemIdx]);
applyNtg_(halfTrans1, insideFaceIdx, elemIdx, ntg);
applyNtg_(halfTrans2, outsideFaceIdx, outsideElemIdx, ntg);
// convert half transmissibilities to full face
// transmissibilities using the harmonic mean
Scalar trans;
if (std::abs(halfTrans1) < 1e-30 || std::abs(halfTrans2) < 1e-30)
// avoid division by zero
trans = 0.0;
else
trans = 1.0 / (1.0/halfTrans1 + 1.0/halfTrans2);
// apply the full face transmissibility multipliers
// for the inside ...
if (useSmallestMultiplier)
applyAllZMultipliers_(trans, insideFaceIdx, insideCartElemIdx, outsideCartElemIdx, transMult, cartDims);
else
applyMultipliers_(trans, insideFaceIdx, insideCartElemIdx, transMult);
// ... and outside elements
applyMultipliers_(trans, outsideFaceIdx, outsideCartElemIdx, transMult);
// apply the region multipliers (cf. the MULTREGT keyword)
Opm::FaceDir::DirEnum faceDir;
switch (insideFaceIdx) {
case 0:
case 1:
faceDir = Opm::FaceDir::XPlus;
break;
case 2:
case 3:
faceDir = Opm::FaceDir::YPlus;
break;
case 4:
case 5:
faceDir = Opm::FaceDir::ZPlus;
break;
default:
throw std::logic_error("Could not determine a face direction");
}
trans *= transMult.getRegionMultiplier(insideCartElemIdx,
outsideCartElemIdx,
faceDir);
trans_[isId_(elemIdx, outsideElemIdx)] = trans;
}
}
// potentially overwrite and/or modify transmissibilities based on input from deck
updateFromEclState_();
// Create mapping from global to local index
const size_t cartesianSize = cartMapper.cartesianSize();
// reserve memory
std::vector globalToLocal(cartesianSize, -1);
// loop over all elements (global grid) and store Cartesian index
elemIt = vanguard_.grid().leafGridView().template begin<0>();
for (; elemIt != elemEndIt; ++elemIt) {
int elemIdx = elemMapper.index(*elemIt);
int cartElemIdx = vanguard_.cartesianIndexMapper().cartesianIndex(elemIdx);
globalToLocal[cartElemIdx] = elemIdx;
}
applyEditNncToGridTrans_(globalToLocal);
applyNncToGridTrans_(globalToLocal);
//remove very small non-neighbouring transmissibilities
removeSmallNonCartesianTransmissibilities_();
}
/*!
* \brief Return the permeability for an element.
*/
const DimMatrix& permeability(unsigned elemIdx) const
{ return permeability_[elemIdx]; }
/*!
* \brief Return the transmissibility for the intersection between two elements.
*/
Scalar transmissibility(unsigned elemIdx1, unsigned elemIdx2) const
{ return trans_.at(isId_(elemIdx1, elemIdx2)); }
/*!
* \brief Return the transmissibility for a given boundary segment.
*/
Scalar transmissibilityBoundary(unsigned elemIdx, unsigned boundaryFaceIdx) const
{ return transBoundary_.at(std::make_pair(elemIdx, boundaryFaceIdx)); }
/*!
* \brief Return the thermal "half transmissibility" for the intersection between two
* elements.
*
* The "half transmissibility" features all sub-expressions of the "thermal
* transmissibility" which can be precomputed, i.e. they are not dependent on the
* current solution:
*
* H_t = A * (n*d)/(d*d);
*
* where A is the area of the intersection between the inside and outside elements, n
* is the outer unit normal, and d is the distance between the center of the inside
* cell and the center of the intersection.
*/
Scalar thermalHalfTrans(unsigned insideElemIdx, unsigned outsideElemIdx) const
{ return thermalHalfTrans_->at(directionalIsId_(insideElemIdx, outsideElemIdx)); }
Scalar thermalHalfTransBoundary(unsigned insideElemIdx, unsigned boundaryFaceIdx) const
{ return thermalHalfTransBoundary_.at(std::make_pair(insideElemIdx, boundaryFaceIdx)); }
private:
void removeSmallNonCartesianTransmissibilities_()
{
const auto& cartMapper = vanguard_.cartesianIndexMapper();
const auto& cartDims = cartMapper.cartesianDimensions();
for (auto&& trans: trans_) {
if (trans.second < transmissibilityThreshold_) {
const auto& id = trans.first;
const auto& elements = isIdReverse_(id);
int gc1 = std::min(cartMapper.cartesianIndex(elements.first), cartMapper.cartesianIndex(elements.second));
int gc2 = std::max(cartMapper.cartesianIndex(elements.first), cartMapper.cartesianIndex(elements.second));
// only adjust the NNCs
if (gc2 - gc1 == 1 || gc2 - gc1 == cartDims[0] || gc2 - gc1 == cartDims[0]*cartDims[1])
continue;
//remove transmissibilities less than the threshold (by default 1e-6 in the deck's unit system)
trans.second = 0.0;
}
}
}
void applyAllZMultipliers_(Scalar& trans,
unsigned insideFaceIdx,
unsigned insideCartElemIdx,
unsigned outsideCartElemIdx,
const Opm::TransMult& transMult,
const std::array& cartDims)
{
if (insideFaceIdx > 3) { // top or or bottom
Scalar mult = 1e20;
unsigned cartElemIdx = insideCartElemIdx;
// pick the smallest multiplier while looking down the pillar untill reaching the other end of the connection
// for the inbetween cells we apply it from both sides
while (cartElemIdx != outsideCartElemIdx) {
if (insideFaceIdx == 4 || cartElemIdx !=insideCartElemIdx )
mult = std::min(mult, transMult.getMultiplier(cartElemIdx, Opm::FaceDir::ZMinus));
if (insideFaceIdx == 5 || cartElemIdx !=insideCartElemIdx)
mult = std::min(mult, transMult.getMultiplier(cartElemIdx, Opm::FaceDir::ZPlus));
cartElemIdx += cartDims[0]*cartDims[1];
}
trans *= mult;
}
else
applyMultipliers_(trans, insideFaceIdx, insideCartElemIdx, transMult);
}
void updateFromEclState_()
{
const auto& gridView = vanguard_.gridView();
const auto& cartMapper = vanguard_.cartesianIndexMapper();
const auto& cartDims = cartMapper.cartesianDimensions();
ElementMapper elemMapper(gridView, Dune::mcmgElementLayout());
const auto& fp = vanguard_.eclState().fieldProps();
const auto& inputTranxData = fp.get_global_double("TRANX");
const auto& inputTranyData = fp.get_global_double("TRANY");
const auto& inputTranzData = fp.get_global_double("TRANZ");
bool tranx_deckAssigned = false; // Ohh my ....
bool trany_deckAssigned = false;
bool tranz_deckAssigned = false;
// compute the transmissibilities for all intersections
auto elemIt = gridView.template begin*codim=*/ 0>();
const auto& elemEndIt = gridView.template end*codim=*/ 0>();
for (; elemIt != elemEndIt; ++elemIt) {
const auto& elem = *elemIt;
auto isIt = gridView.ibegin(elem);
const auto& isEndIt = gridView.iend(elem);
for (; isIt != isEndIt; ++ isIt) {
// store intersection, this might be costly
const auto& intersection = *isIt;
if (!intersection.neighbor())
continue; // intersection is on the domain boundary
unsigned c1 = elemMapper.index(intersection.inside());
unsigned c2 = elemMapper.index(intersection.outside());
if (c1 > c2)
continue; // we only need to handle each connection once, thank you.
auto isId = isId_(c1, c2);
int gc1 = std::min(cartMapper.cartesianIndex(c1), cartMapper.cartesianIndex(c2));
int gc2 = std::max(cartMapper.cartesianIndex(c1), cartMapper.cartesianIndex(c2));
if (gc2 - gc1 == 1) {
if (tranx_deckAssigned)
// set simulator internal transmissibilities to values from inputTranx
trans_[isId] = inputTranxData[gc1];
else
// Scale transmissibilities with scale factor from inputTranx
trans_[isId] *= inputTranxData[gc1];
}
else if (gc2 - gc1 == cartDims[0]) {
if (trany_deckAssigned)
// set simulator internal transmissibilities to values from inputTrany
trans_[isId] = inputTranyData[gc1];
else
// Scale transmissibilities with scale factor from inputTrany
trans_[isId] *= inputTranyData[gc1];
}
else if (gc2 - gc1 == cartDims[0]*cartDims[1]) {
if (tranz_deckAssigned)
// set simulator internal transmissibilities to values from inputTranz
trans_[isId] = inputTranzData[gc1];
else
// Scale transmissibilities with scale factor from inputTranz
trans_[isId] *= inputTranzData[gc1];
}
//else.. We don't support modification of NNC at the moment.
}
}
}
template
void computeFaceProperties(const Intersection& intersection,
const int insideElemIdx OPM_UNUSED,
const int insideFaceIdx OPM_UNUSED,
const int outsideElemIdx OPM_UNUSED,
const int outsideFaceIdx OPM_UNUSED,
DimVector& faceCenterInside,
DimVector& faceCenterOutside,
DimVector& faceAreaNormal,
/*isCpGrid=*/std::false_type) const
{
// default implementation for DUNE grids
const auto& geometry = intersection.geometry();
faceCenterInside = geometry.center();
faceCenterOutside = faceCenterInside;
faceAreaNormal = intersection.centerUnitOuterNormal();
faceAreaNormal *= geometry.volume();
}
template
void computeFaceProperties(const Intersection& intersection,
const int insideElemIdx,
const int insideFaceIdx,
const int outsideElemIdx,
const int outsideFaceIdx,
DimVector& faceCenterInside,
DimVector& faceCenterOutside,
DimVector& faceAreaNormal,
/*isCpGrid=*/std::true_type) const
{
int faceIdx = intersection.id();
faceCenterInside = vanguard_.grid().faceCenterEcl(insideElemIdx, insideFaceIdx);
faceCenterOutside = vanguard_.grid().faceCenterEcl(outsideElemIdx, outsideFaceIdx);
faceAreaNormal = vanguard_.grid().faceAreaNormalEcl(faceIdx);
}
/*
* \brief Applies additional transmissibilities specified via NNC keyword.
*
* Applies only those NNC that are actually represented by the grid. These may
* NNCs due to faults or NNCs that are actually neighbours. In both cases that
* specified transmissibilities (scaled by EDITNNC) will be added to the already
* existing models.
*
* \param cartesianToCompressed Vector containing the compressed index (or -1 for inactive
* cells) as the element at the cartesian index.
* \return Two vector of NNCs (scaled by EDITNNC). The first one are the NNCs that have been applied
* and the second the NNCs not resembled by faces of the grid. NNCs specified for
* inactive cells are omitted in these vectors.
*/
std::tuple, std::vector >
applyNncToGridTrans_(const std::vector& cartesianToCompressed)
{
// First scale NNCs with EDITNNC.
std::vector unprocessedNnc;
std::vector processedNnc;
const auto& nnc = vanguard_.eclState().getInputNNC();
if (!nnc.hasNNC())
return make_tuple(processedNnc, unprocessedNnc);
auto nncData = sortNncAndApplyEditnnc(nnc.data(), vanguard_.eclState().getInputEDITNNC().data());
for (const auto& nncEntry : nncData) {
auto c1 = nncEntry.cell1;
auto c2 = nncEntry.cell2;
auto low = cartesianToCompressed[c1];
auto high = cartesianToCompressed[c2];
if (low > high)
std::swap(low, high);
if (low == -1 && high == -1)
// Silently discard as it is not between active cells
continue;
if (low == -1 || high == -1) {
// Discard the NNC if it is between active cell and inactive cell
std::ostringstream sstr;
sstr << "NNC between active and inactive cells ("
<< low << " -> " << high << ")";
Opm::OpmLog::warning(sstr.str());
continue;
}
auto candidate = trans_.find(isId_(low, high));
if (candidate == trans_.end())
// This NNC is not resembled by the grid. Save it for later
// processing with local cell values
unprocessedNnc.push_back({c1, c2, nncEntry.trans});
else {
// NNC is represented by the grid and might be a neighboring connection
// In this case the transmissibilty is added to the value already
// set or computed.
candidate->second += nncEntry.trans;
processedNnc.push_back({c1, c2, nncEntry.trans});
}
}
return make_tuple(processedNnc, unprocessedNnc);
}
/// \brief Multiplies the grid transmissibilities according to EDITNNC.
void applyEditNncToGridTrans_(const std::vector& globalToLocal)
{
const auto& editNnc = vanguard_.eclState().getInputEDITNNC();
if (editNnc.empty())
return;
// editNnc is supposed to only reference non-neighboring connections and not
// neighboring connections. Use all entries for scaling if there is an NNC.
// variable nnc incremented in loop body.
auto nnc = editNnc.data().begin();
auto end = editNnc.data().end();
while (nnc != end) {
auto c1 = nnc->cell1;
auto c2 = nnc->cell2;
auto low = globalToLocal[c1];
auto high = globalToLocal[c2];
if (low > high)
std::swap(low, high);
auto candidate = trans_.find(isId_(low, high));
if (candidate == trans_.end()) {
std::ostringstream sstr;
sstr << "Cannot edit NNC from " << c1 << " to " << c2
<< " as it does not exist";
Opm::OpmLog::warning(sstr.str());
++nnc;
}
else {
// NNC exists
while (nnc!= end && c1==nnc->cell1 && c2==nnc->cell2) {
candidate->second *= nnc->trans;
++nnc;
}
}
}
}
void extractPermeability_()
{
unsigned numElem = vanguard_.gridView().size(/*codim=*/0);
permeability_.resize(numElem);
// read the intrinsic permeabilities from the eclState. Note that all arrays
// provided by eclState are one-per-cell of "uncompressed" grid, whereas the
// simulation grid might remove a few elements. (e.g. because it is distributed
// over several processes.)
const auto& fp = vanguard_.eclState().fieldProps();
if (fp.has_double("PERMX")) {
const std::vector& permxData = fp.get_global_double("PERMX");
std::vector permyData(permxData);
if (fp.has_double("PERMY"))
permyData = fp.get_global_double("PERMY");
std::vector permzData(permxData);
if (fp.has_double("PERMZ"))
permzData = fp.get_global_double("PERMZ");
for (size_t dofIdx = 0; dofIdx < numElem; ++ dofIdx) {
unsigned cartesianElemIdx = vanguard_.cartesianIndex(dofIdx);
permeability_[dofIdx] = 0.0;
permeability_[dofIdx][0][0] = permxData[cartesianElemIdx];
permeability_[dofIdx][1][1] = permyData[cartesianElemIdx];
permeability_[dofIdx][2][2] = permzData[cartesianElemIdx];
}
// for now we don't care about non-diagonal entries
}
else
throw std::logic_error("Can't read the intrinsic permeability from the ecl state. "
"(The PERM{X,Y,Z} keywords are missing)");
}
std::uint64_t isId_(std::uint32_t elemIdx1, std::uint32_t elemIdx2) const
{
std::uint32_t elemAIdx = std::min(elemIdx1, elemIdx2);
std::uint64_t elemBIdx = std::max(elemIdx1, elemIdx2);
return (elemBIdx< isIdReverse_(const std::uint64_t& id) const
{
// Assigning an unsigned integer to a narrower type discards the most significant bits.
// See "The C programming language", section A.6.2.
// NOTE that the ordering of element A and B may have changed
std::uint32_t elemAIdx = id;
std::uint32_t elemBIdx = (id - elemAIdx) >> elemIdxShift;
return std::make_pair(elemAIdx, elemBIdx);
}
std::uint64_t directionalIsId_(std::uint32_t elemIdx1, std::uint32_t elemIdx2) const
{
return (std::uint64_t(elemIdx1)<= 0);
unsigned dimIdx = faceIdx/2;
assert(dimIdx < dimWorld);
halfTrans = perm[dimIdx][dimIdx];
Scalar val = 0;
for (unsigned i = 0; i < areaNormal.size(); ++i)
val += areaNormal[i]*distance[i];
halfTrans *= std::abs(val);
halfTrans /= distance.two_norm2();
}
DimVector distanceVector_(const DimVector& center,
int faceIdx, // in the reference element that contains the intersection
unsigned elemIdx,
const std::array, dimWorld>& axisCentroids) const
{
assert(faceIdx >= 0);
unsigned dimIdx = faceIdx/2;
assert(dimIdx < dimWorld);
DimVector x = center;
x -= axisCentroids[dimIdx][elemIdx];
return x;
}
void applyMultipliers_(Scalar& trans,
unsigned faceIdx,
unsigned cartElemIdx,
const Opm::TransMult& transMult) const
{
// apply multiplyer for the transmissibility of the face. (the
// face index is the index of the reference-element face which
// contains the intersection of interest.)
switch (faceIdx) {
case 0: // left
trans *= transMult.getMultiplier(cartElemIdx, Opm::FaceDir::XMinus);
break;
case 1: // right
trans *= transMult.getMultiplier(cartElemIdx, Opm::FaceDir::XPlus);
break;
case 2: // front
trans *= transMult.getMultiplier(cartElemIdx, Opm::FaceDir::YMinus);
break;
case 3: // back
trans *= transMult.getMultiplier(cartElemIdx, Opm::FaceDir::YPlus);
break;
case 4: // bottom
trans *= transMult.getMultiplier(cartElemIdx, Opm::FaceDir::ZMinus);
break;
case 5: // top
trans *= transMult.getMultiplier(cartElemIdx, Opm::FaceDir::ZPlus);
break;
}
}
void applyNtg_(Scalar& trans,
unsigned faceIdx,
unsigned elemIdx,
const std::vector& ntg) const
{
// apply multiplyer for the transmissibility of the face. (the
// face index is the index of the reference-element face which
// contains the intersection of interest.)
switch (faceIdx) {
case 0: // left
trans *= ntg[elemIdx];
break;
case 1: // right
trans *= ntg[elemIdx];
break;
case 2: // front
trans *= ntg[elemIdx];
break;
case 3: // back
trans *= ntg[elemIdx];
break;
// NTG does not apply to top and bottom faces
}
}
const Vanguard& vanguard_;
Scalar transmissibilityThreshold_;
std::vector permeability_;
std::unordered_map trans_;
std::map, Scalar> transBoundary_;
std::map, Scalar> thermalHalfTransBoundary_;
Opm::ConditionalStorage > thermalHalfTrans_;
};
} // namespace Opm
#endif