Rename directory opm/core/tof -> opm/core/flowdiagnostics.
This commit is contained in:
1 parent
55b2320ded
commit
9f4da3ad03
17 files changed
+24
-24
No files matched your search
@@ -0,0 +1,461 @@
|
||||
/*
|
||||
Copyright 2014 SINTEF ICT, Applied Mathematics.
|
||||
|
||||
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/core/flowdiagnostics/AnisotropicEikonal.hpp>
|
||||
#include <opm/core/grid/GridUtilities.hpp>
|
||||
#include <opm/core/grid.h>
|
||||
#include <opm/core/utility/RootFinders.hpp>
|
||||
|
||||
#if BOOST_HEAP_AVAILABLE
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
/// Euclidean (isotropic) distance.
|
||||
double distanceIso(const double v1[2],
|
||||
const double v2[2])
|
||||
{
|
||||
const double d[2] = { v2[0] - v1[0], v2[1] - v1[1] };
|
||||
const double dist = std::sqrt(d[0]*d[0] + d[1]*d[1]);
|
||||
return dist;
|
||||
}
|
||||
|
||||
/// Anisotropic distance with respect to a metric g.
|
||||
/// If d = v2 - v1, the distance is sqrt(d^T g d).
|
||||
double distanceAniso(const double v1[2],
|
||||
const double v2[2],
|
||||
const double g[4])
|
||||
{
|
||||
const double d[2] = { v2[0] - v1[0], v2[1] - v1[1] };
|
||||
const double dist = std::sqrt(+ g[0] * d[0] * d[0]
|
||||
+ g[1] * d[0] * d[1]
|
||||
+ g[2] * d[1] * d[0]
|
||||
+ g[3] * d[1] * d[1]);
|
||||
return dist;
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Construct solver.
|
||||
/// \param[in] grid A 2d grid.
|
||||
AnisotropicEikonal2d::AnisotropicEikonal2d(const UnstructuredGrid& grid)
|
||||
: grid_(grid),
|
||||
safety_factor_(1.2)
|
||||
{
|
||||
if (grid.dimensions != 2) {
|
||||
OPM_THROW(std::logic_error, "Grid for AnisotropicEikonal2d must be 2d.");
|
||||
}
|
||||
cell_neighbours_ = cellNeighboursAcrossVertices(grid);
|
||||
orderCounterClockwise(grid, cell_neighbours_);
|
||||
computeGridRadius();
|
||||
}
|
||||
|
||||
/// Solve the eikonal equation.
|
||||
/// \param[in] metric Array of metric tensors, M, for each cell.
|
||||
/// \param[in] startcells Array of cells where u = 0 at the centroid.
|
||||
/// \param[out] solution Array of solution to the eikonal equation.
|
||||
void AnisotropicEikonal2d::solve(const double* metric,
|
||||
const std::vector<int>& startcells,
|
||||
std::vector<double>& solution)
|
||||
{
|
||||
// Compute anisotropy ratios to be used by isClose().
|
||||
computeAnisoRatio(metric);
|
||||
|
||||
// The algorithm used is described in J.A. Sethian and A. Vladimirsky,
|
||||
// "Ordered Upwind Methods for Static Hamilton-Jacobi Equations".
|
||||
// Notation in comments is as used in that paper: U is the solution,
|
||||
// and q is the boundary condition. One difference is that we talk about
|
||||
// grid cells instead of mesh points.
|
||||
//
|
||||
// Algorithm summary:
|
||||
// 1. Put all cells in Far. U_i = \inf.
|
||||
// 2. Move the startcells to Accepted. U_i = q(x_i)
|
||||
// 3. Move cells adjacent to startcells to Considered, evaluate
|
||||
// U_i = min_{(x_j,x_k) \in NF(x_i)} G_{j,k}
|
||||
// 4. Find the Considered cell with the smallest value: r.
|
||||
// 5. Move cell r to Accepted. Update AcceptedFront.
|
||||
// 6. Recompute the value for all Considered cells within
|
||||
// distance h * F_2/F1 from x_r. Use min of previous and new.
|
||||
// 7. Move cells adjacent to r from Far to Considered.
|
||||
// 8. If Considered is not empty, go to step 4.
|
||||
|
||||
// 1. Put all cells in Far. U_i = \inf.
|
||||
const int num_cells = grid_.number_of_cells;
|
||||
const double inf = 1e100;
|
||||
solution.clear();
|
||||
solution.resize(num_cells, inf);
|
||||
is_accepted_.clear();
|
||||
is_accepted_.resize(num_cells, false);
|
||||
accepted_front_.clear();
|
||||
considered_.clear();
|
||||
considered_handles_.clear();
|
||||
is_considered_.clear();
|
||||
is_considered_.resize(num_cells, false);
|
||||
|
||||
// 2. Move the startcells to Accepted. U_i = q(x_i)
|
||||
const int num_startcells = startcells.size();
|
||||
for (int ii = 0; ii < num_startcells; ++ii) {
|
||||
is_accepted_[startcells[ii]] = true;
|
||||
solution[startcells[ii]] = 0.0;
|
||||
}
|
||||
accepted_front_.insert(startcells.begin(), startcells.end());
|
||||
|
||||
// 3. Move cells adjacent to startcells to Considered, evaluate
|
||||
// U_i = min_{(x_j,x_k) \in NF(x_i)} G_{j,k}
|
||||
for (int ii = 0; ii < num_startcells; ++ii) {
|
||||
const int scell = startcells[ii];
|
||||
const int num_nb = cell_neighbours_[scell].size();
|
||||
for (int nb = 0; nb < num_nb; ++nb) {
|
||||
const int nb_cell = cell_neighbours_[scell][nb];
|
||||
if (!is_accepted_[nb_cell] && !is_considered_[nb_cell]) {
|
||||
const double value = computeValue(nb_cell, metric, solution.data());
|
||||
pushConsidered(std::make_pair(value, nb_cell));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (!considered_.empty()) {
|
||||
// 4. Find the Considered cell with the smallest value: r.
|
||||
const ValueAndCell r = topConsidered();
|
||||
// std::cout << "Accepting cell " << r.second << std::endl;
|
||||
|
||||
// 5. Move cell r to Accepted. Update AcceptedFront.
|
||||
const int rcell = r.second;
|
||||
is_accepted_[rcell] = true;
|
||||
solution[rcell] = r.first;
|
||||
popConsidered();
|
||||
accepted_front_.insert(rcell);
|
||||
for (auto it = accepted_front_.begin(); it != accepted_front_.end();) {
|
||||
// Note that loop increment happens in the body of this loop.
|
||||
const int cell = *it;
|
||||
bool on_front = false;
|
||||
for (auto it2 = cell_neighbours_[cell].begin(); it2 != cell_neighbours_[cell].end(); ++it2) {
|
||||
if (!is_accepted_[*it2]) {
|
||||
on_front = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!on_front) {
|
||||
accepted_front_.erase(it++);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Recompute the value for all Considered cells within
|
||||
// distance h * F_2/F1 from x_r. Use min of previous and new.
|
||||
for (auto it = considered_.begin(); it != considered_.end(); ++it) {
|
||||
const int ccell = it->second;
|
||||
if (isClose(rcell, ccell)) {
|
||||
const double value = computeValueUpdate(ccell, metric, solution.data(), rcell);
|
||||
if (value < it->first) {
|
||||
// Update value for considered cell.
|
||||
// Note that as solution values decrease, their
|
||||
// goodness w.r.t. the heap comparator increase,
|
||||
// therefore we may safely call the increase()
|
||||
// modificator below.
|
||||
considered_.increase(considered_handles_[ccell], std::make_pair(value, ccell));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Move cells adjacent to r from Far to Considered.
|
||||
for (auto it = cell_neighbours_[rcell].begin(); it != cell_neighbours_[rcell].end(); ++it) {
|
||||
const int nb_cell = *it;
|
||||
if (!is_accepted_[nb_cell] && !is_considered_[nb_cell]) {
|
||||
assert(solution[nb_cell] == inf);
|
||||
const double value = computeValue(nb_cell, metric, solution.data());
|
||||
pushConsidered(std::make_pair(value, nb_cell));
|
||||
}
|
||||
}
|
||||
|
||||
// 8. If Considered is not empty, go to step 4.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool AnisotropicEikonal2d::isClose(const int c1,
|
||||
const int c2) const
|
||||
{
|
||||
const double* v[] = { grid_.cell_centroids + 2*c1,
|
||||
grid_.cell_centroids + 2*c2 };
|
||||
return distanceIso(v[0], v[1]) < safety_factor_ * aniso_ratio_[c1] * grid_radius_[c1];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
double AnisotropicEikonal2d::computeValue(const int cell,
|
||||
const double* metric,
|
||||
const double* solution) const
|
||||
{
|
||||
// std::cout << "++++ computeValue(), cell = " << cell << std::endl;
|
||||
const auto& nbs = cell_neighbours_[cell];
|
||||
const int num_nbs = nbs.size();
|
||||
const double inf = 1e100;
|
||||
double val = inf;
|
||||
for (int ii = 0; ii < num_nbs; ++ii) {
|
||||
const int n[2] = { nbs[ii], nbs[(ii+1) % num_nbs] };
|
||||
if (accepted_front_.count(n[0]) && accepted_front_.count(n[1])) {
|
||||
const double cand_val = computeFromTri(cell, n[0], n[1], metric, solution);
|
||||
val = std::min(val, cand_val);
|
||||
}
|
||||
}
|
||||
if (val == inf) {
|
||||
// Failed to find two accepted front nodes adjacent to this,
|
||||
// so we go for a single-neighbour update.
|
||||
for (int ii = 0; ii < num_nbs; ++ii) {
|
||||
if (accepted_front_.count(nbs[ii])) {
|
||||
const double cand_val = computeFromLine(cell, nbs[ii], metric, solution);
|
||||
val = std::min(val, cand_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(val != inf);
|
||||
// std::cout << "---> " << val << std::endl;
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
double AnisotropicEikonal2d::computeValueUpdate(const int cell,
|
||||
const double* metric,
|
||||
const double* solution,
|
||||
const int new_cell) const
|
||||
{
|
||||
// std::cout << "++++ computeValueUpdate(), cell = " << cell << std::endl;
|
||||
const auto& nbs = cell_neighbours_[cell];
|
||||
const int num_nbs = nbs.size();
|
||||
const double inf = 1e100;
|
||||
double val = inf;
|
||||
for (int ii = 0; ii < num_nbs; ++ii) {
|
||||
const int n[2] = { nbs[ii], nbs[(ii+1) % num_nbs] };
|
||||
if ((n[0] == new_cell || n[1] == new_cell)
|
||||
&& accepted_front_.count(n[0]) && accepted_front_.count(n[1])) {
|
||||
const double cand_val = computeFromTri(cell, n[0], n[1], metric, solution);
|
||||
val = std::min(val, cand_val);
|
||||
}
|
||||
}
|
||||
if (val == inf) {
|
||||
// Failed to find two accepted front nodes adjacent to this,
|
||||
// so we go for a single-neighbour update.
|
||||
for (int ii = 0; ii < num_nbs; ++ii) {
|
||||
if (nbs[ii] == new_cell && accepted_front_.count(nbs[ii])) {
|
||||
const double cand_val = computeFromLine(cell, nbs[ii], metric, solution);
|
||||
val = std::min(val, cand_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
// std::cout << "---> " << val << std::endl;
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
double AnisotropicEikonal2d::computeFromLine(const int cell,
|
||||
const int from,
|
||||
const double* metric,
|
||||
const double* solution) const
|
||||
{
|
||||
assert(!is_accepted_[cell]);
|
||||
assert(is_accepted_[from]);
|
||||
// Applying the first fundamental form to compute geodesic distance.
|
||||
// Using the metric of 'cell', not 'from'.
|
||||
const double dist = distanceAniso(grid_.cell_centroids + 2 * cell,
|
||||
grid_.cell_centroids + 2 * from,
|
||||
metric + 4 * cell);
|
||||
return solution[from] + dist;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
struct DistanceDerivative
|
||||
{
|
||||
const double* x1;
|
||||
const double* x2;
|
||||
const double* x;
|
||||
double u1;
|
||||
double u2;
|
||||
const double* g;
|
||||
double operator()(const double theta) const
|
||||
{
|
||||
const double xt[2] = { (1-theta)*x1[0] + theta*x2[0], (1-theta)*x1[1] + theta*x2[1] };
|
||||
const double a[2] = { x[0] - xt[0], x[1] - xt[1] };
|
||||
const double b[2] = { x1[0] - x2[0], x1[1] - x2[1] };
|
||||
const double dQdtheta = 2*(a[0]*b[0]*g[0] + a[0]*b[1]*g[1] + a[1]*b[0]*g[2] + a[1]*b[1]*g[3]);
|
||||
const double val = u2 - u1 + dQdtheta/(2*distanceAniso(x, xt, g));
|
||||
// std::cout << theta << " " << val << std::endl;
|
||||
return val;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
double AnisotropicEikonal2d::computeFromTri(const int cell,
|
||||
const int n0,
|
||||
const int n1,
|
||||
const double* metric,
|
||||
const double* solution) const
|
||||
{
|
||||
// std::cout << "==== cell = " << cell << " n0 = " << n0 << " n1 = " << n1 << std::endl;
|
||||
assert(!is_accepted_[cell]);
|
||||
assert(is_accepted_[n0]);
|
||||
assert(is_accepted_[n1]);
|
||||
DistanceDerivative dd;
|
||||
dd.x1 = grid_.cell_centroids + 2 * n0;
|
||||
dd.x2 = grid_.cell_centroids + 2 * n1;
|
||||
dd.x = grid_.cell_centroids + 2 * cell;
|
||||
dd.u1 = solution[n0];
|
||||
dd.u2 = solution[n1];
|
||||
dd.g = metric + 4 * cell;
|
||||
int iter = 0;
|
||||
const double theta = RegulaFalsi<ContinueOnError>::solve(dd, 0.0, 1.0, 15, 1e-8, iter);
|
||||
const double xt[2] = { (1-theta)*dd.x1[0] + theta*dd.x2[0],
|
||||
(1-theta)*dd.x1[1] + theta*dd.x2[1] };
|
||||
const double d1 = distanceAniso(dd.x1, dd.x, dd.g) + solution[n0];
|
||||
const double d2 = distanceAniso(dd.x2, dd.x, dd.g) + solution[n1];
|
||||
const double dt = distanceAniso(xt, dd.x, dd.g) + (1-theta)*solution[n0] + theta*solution[n1];
|
||||
return std::min(d1, std::min(d2, dt));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const AnisotropicEikonal2d::ValueAndCell& AnisotropicEikonal2d::topConsidered() const
|
||||
{
|
||||
return considered_.top();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void AnisotropicEikonal2d::pushConsidered(const ValueAndCell& vc)
|
||||
{
|
||||
HeapHandle h = considered_.push(vc);
|
||||
considered_handles_[vc.second] = h;
|
||||
is_considered_[vc.second] = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void AnisotropicEikonal2d::popConsidered()
|
||||
{
|
||||
is_considered_[considered_.top().second] = false;
|
||||
considered_handles_.erase(considered_.top().second);
|
||||
considered_.pop();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AnisotropicEikonal2d::computeGridRadius()
|
||||
{
|
||||
const int num_cells = cell_neighbours_.size();
|
||||
grid_radius_.resize(num_cells);
|
||||
for (int cell = 0; cell < num_cells; ++cell) {
|
||||
double radius = 0.0;
|
||||
const double* v1 = grid_.cell_centroids + 2*cell;
|
||||
const auto& nb = cell_neighbours_[cell];
|
||||
for (auto it = nb.begin(); it != nb.end(); ++it) {
|
||||
const double* v2 = grid_.cell_centroids + 2*(*it);
|
||||
radius = std::max(radius, distanceIso(v1, v2));
|
||||
}
|
||||
grid_radius_[cell] = radius;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void AnisotropicEikonal2d::computeAnisoRatio(const double* metric)
|
||||
{
|
||||
const int num_cells = cell_neighbours_.size();
|
||||
aniso_ratio_.resize(num_cells);
|
||||
for (int cell = 0; cell < num_cells; ++cell) {
|
||||
const double* m = metric + 4*cell;
|
||||
// Find the two eigenvalues from trace and determinant.
|
||||
const double t = m[0] + m[3];
|
||||
const double d = m[0]*m[3] - m[1]*m[2];
|
||||
const double sd = std::sqrt(t*t/4.0 - d);
|
||||
const double eig[2] = { t/2.0 - sd, t/2.0 + sd };
|
||||
// Anisotropy ratio is the max ratio of the eigenvalues.
|
||||
aniso_ratio_[cell] = std::max(eig[0]/eig[1], eig[1]/eig[0]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
|
||||
#else // BOOST_HEAP_AVAILABLE is false
|
||||
|
||||
namespace {
|
||||
const char* AnisotropicEikonal2derrmsg =
|
||||
"\n********************************************************************************\n"
|
||||
"This library has not been compiled with support for the AnisotropicEikonal2d\n"
|
||||
"class, due to too old version of the boost libraries (Boost.Heap from boost\n"
|
||||
"version 1.49 or newer is required.\n"
|
||||
"To use this class you must recompile opm-core on a system with sufficiently new\n"
|
||||
"version of the boost libraries."
|
||||
"\n********************************************************************************\n";
|
||||
}
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
AnisotropicEikonal2d::AnisotropicEikonal2d(const UnstructuredGrid&)
|
||||
{
|
||||
OPM_THROW(std::logic_error, AnisotropicEikonal2derrmsg);
|
||||
}
|
||||
|
||||
void AnisotropicEikonal2d::solve(const double*,
|
||||
const std::vector<int>&,
|
||||
std::vector<double>&)
|
||||
{
|
||||
OPM_THROW(std::logic_error, AnisotropicEikonal2derrmsg);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // BOOST_HEAP_AVAILABLE
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
Copyright 2014 SINTEF ICT, Applied Mathematics.
|
||||
|
||||
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_ANISOTROPICEIKONAL_HEADER_INCLUDED
|
||||
#define OPM_ANISOTROPICEIKONAL_HEADER_INCLUDED
|
||||
|
||||
#include <opm/core/utility/SparseTable.hpp>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <map>
|
||||
|
||||
#include <boost/version.hpp>
|
||||
|
||||
#define BOOST_HEAP_AVAILABLE ((BOOST_VERSION / 100 % 1000) >= 49)
|
||||
|
||||
#if BOOST_HEAP_AVAILABLE
|
||||
#include <boost/heap/fibonacci_heap.hpp>
|
||||
#endif
|
||||
|
||||
struct UnstructuredGrid;
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
/// A solver for the anisotropic eikonal equation:
|
||||
/// \f[ || \nabla u^T M^{-1}(x) \nabla u || = 1 \qquad x \in \Omega \f]
|
||||
/// where M(x) is a symmetric positive definite matrix.
|
||||
/// The boundary conditions are assumed to be
|
||||
/// \f[ u(x) = 0 \qquad x \in \partial\Omega \f].
|
||||
class AnisotropicEikonal2d
|
||||
{
|
||||
public:
|
||||
/// Construct solver.
|
||||
/// \param[in] grid A 2d grid.
|
||||
explicit AnisotropicEikonal2d(const UnstructuredGrid& grid);
|
||||
|
||||
/// Solve the eikonal equation.
|
||||
/// \param[in] metric Array of metric tensors, M, for each cell.
|
||||
/// \param[in] startcells Array of cells where u = 0 at the centroid.
|
||||
/// \param[out] solution Array of solution to the eikonal equation.
|
||||
void solve(const double* metric,
|
||||
const std::vector<int>& startcells,
|
||||
std::vector<double>& solution);
|
||||
private:
|
||||
#if BOOST_HEAP_AVAILABLE
|
||||
// Grid and topology.
|
||||
const UnstructuredGrid& grid_;
|
||||
SparseTable<int> cell_neighbours_;
|
||||
|
||||
// Keep track of accepted cells.
|
||||
std::vector<char> is_accepted_;
|
||||
std::set<int> accepted_front_;
|
||||
|
||||
// Quantities relating to anisotropy.
|
||||
std::vector<double> grid_radius_;
|
||||
std::vector<double> aniso_ratio_;
|
||||
const double safety_factor_;
|
||||
|
||||
// Keep track of considered cells.
|
||||
typedef std::pair<double, int> ValueAndCell;
|
||||
typedef boost::heap::compare<std::greater<ValueAndCell>> Comparator;
|
||||
typedef boost::heap::fibonacci_heap<ValueAndCell, Comparator> Heap;
|
||||
Heap considered_;
|
||||
typedef Heap::handle_type HeapHandle;
|
||||
std::map<int, HeapHandle> considered_handles_;
|
||||
std::vector<char> is_considered_;
|
||||
|
||||
bool isClose(const int c1, const int c2) const;
|
||||
double computeValue(const int cell, const double* metric, const double* solution) const;
|
||||
double computeValueUpdate(const int cell, const double* metric, const double* solution, const int new_cell) const;
|
||||
double computeFromLine(const int cell, const int from, const double* metric, const double* solution) const;
|
||||
double computeFromTri(const int cell, const int n0, const int n1, const double* metric, const double* solution) const;
|
||||
|
||||
const ValueAndCell& topConsidered() const;
|
||||
void pushConsidered(const ValueAndCell& vc);
|
||||
void popConsidered();
|
||||
|
||||
void computeGridRadius();
|
||||
void computeAnisoRatio(const double* metric);
|
||||
#endif // BOOST_HEAP_AVAILABLE
|
||||
};
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
|
||||
#endif // OPM_ANISOTROPICEIKONAL_HEADER_INCLUDED
|
||||
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
Copyright 2013 SINTEF ICT, Applied Mathematics.
|
||||
|
||||
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/core/flowdiagnostics/DGBasis.hpp>
|
||||
#include <opm/core/grid.h>
|
||||
#include <opm/core/utility/ErrorMacros.hpp>
|
||||
#include <numeric>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
// ---------------- Methods for class DGBasisInterface ----------------
|
||||
|
||||
|
||||
/// Virtual destructor.
|
||||
DGBasisInterface::~DGBasisInterface()
|
||||
{
|
||||
}
|
||||
|
||||
/// Evaluate function f = sum_i c_i b_i at the point x.
|
||||
/// Note that this function is not virtual, but implemented in
|
||||
/// terms of the virtual functions of the class.
|
||||
/// \param[in] cell Cell index
|
||||
/// \param[in] coefficients Coefficients {c_i} for a single cell.
|
||||
/// \param[in] x Point at which to compute f(x).
|
||||
double DGBasisInterface::evalFunc(const int cell,
|
||||
const double* coefficients,
|
||||
const double* x) const
|
||||
{
|
||||
bvals_.resize(numBasisFunc());
|
||||
eval(cell, x, &bvals_[0]);
|
||||
return std::inner_product(bvals_.begin(), bvals_.end(), coefficients, 0.0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ---------------- Methods for class DGBasisBoundedTotalDegree ----------------
|
||||
|
||||
|
||||
/// Constructor.
|
||||
/// \param[in] grid grid on which basis is used (cell-wise)
|
||||
/// \param[in] degree polynomial degree of basis
|
||||
DGBasisBoundedTotalDegree::DGBasisBoundedTotalDegree(const UnstructuredGrid& grid,
|
||||
const int degree_arg)
|
||||
: grid_(grid),
|
||||
degree_(degree_arg)
|
||||
{
|
||||
if (grid_.dimensions > 3) {
|
||||
OPM_THROW(std::runtime_error, "Grid dimension must be 1, 2 or 3.");
|
||||
}
|
||||
if (degree_ > 1 || degree_ < 0) {
|
||||
OPM_THROW(std::runtime_error, "Degree must be 0 or 1.");
|
||||
}
|
||||
}
|
||||
|
||||
/// Destructor.
|
||||
DGBasisBoundedTotalDegree::~DGBasisBoundedTotalDegree()
|
||||
{
|
||||
}
|
||||
|
||||
/// The number of basis functions per cell.
|
||||
int DGBasisBoundedTotalDegree::numBasisFunc() const
|
||||
{
|
||||
switch (dimensions()) {
|
||||
case 1:
|
||||
return degree_ + 1;
|
||||
case 2:
|
||||
return (degree_ + 2)*(degree_ + 1)/2;
|
||||
case 3:
|
||||
return (degree_ + 3)*(degree_ + 2)*(degree_ + 1)/6;
|
||||
default:
|
||||
OPM_THROW(std::runtime_error, "Dimensions must be 1, 2 or 3.");
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of space dimensions.
|
||||
int DGBasisBoundedTotalDegree::dimensions() const
|
||||
{
|
||||
return grid_.dimensions;
|
||||
}
|
||||
|
||||
/// The polynomial degree of the basis functions.
|
||||
int DGBasisBoundedTotalDegree::degree() const
|
||||
{
|
||||
return degree_;
|
||||
}
|
||||
|
||||
/// Evaluate all basis functions associated with cell at x,
|
||||
/// writing to f_x. The array f_x must have size equal to
|
||||
/// numBasisFunc().
|
||||
void DGBasisBoundedTotalDegree::eval(const int cell,
|
||||
const double* x,
|
||||
double* f_x) const
|
||||
{
|
||||
const int dim = dimensions();
|
||||
const double* cc = grid_.cell_centroids + dim*cell;
|
||||
// Note intentional fallthrough in this switch statement!
|
||||
switch (degree_) {
|
||||
case 1:
|
||||
for (int ix = 0; ix < dim; ++ix) {
|
||||
f_x[1 + ix] = x[ix] - cc[ix];
|
||||
}
|
||||
case 0:
|
||||
f_x[0] = 1;
|
||||
break;
|
||||
default:
|
||||
OPM_THROW(std::runtime_error, "Maximum degree is 1 for now.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Evaluate gradients of all basis functions associated with
|
||||
/// cell at x, writing to grad_f_x. The array grad_f_x must
|
||||
/// have size numBasisFunc() * dimensions(). The dimensions()
|
||||
/// components of the first basis function gradient come
|
||||
/// before the components of the second etc.
|
||||
void DGBasisBoundedTotalDegree::evalGrad(const int /*cell*/,
|
||||
const double* /*x*/,
|
||||
double* grad_f_x) const
|
||||
{
|
||||
const int dim = dimensions();
|
||||
const int num_basis = numBasisFunc();
|
||||
std::fill(grad_f_x, grad_f_x + num_basis*dim, 0.0);
|
||||
if (degree_ == 1) {
|
||||
for (int ix = 0; ix < dim; ++ix) {
|
||||
grad_f_x[dim*(ix + 1) + ix] = 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Modify basis coefficients to add to the function value.
|
||||
/// A function f = sum_i c_i b_i is assumed, and we change
|
||||
/// it to (f + increment) by modifying the c_i. This is done without
|
||||
/// modifying its gradient.
|
||||
/// \param[in] increment Add this value to the function.
|
||||
/// \param[out] coefficients Coefficients {c_i} for a single cell.
|
||||
void DGBasisBoundedTotalDegree::addConstant(const double increment,
|
||||
double* coefficients) const
|
||||
{
|
||||
coefficients[0] += increment;
|
||||
}
|
||||
|
||||
/// Modify basis coefficients to change the function's slope.
|
||||
/// A function f = sum_i c_i b_i is assumed, and we change
|
||||
/// it to a function g with the property that grad g = factor * grad f
|
||||
/// by modifying the c_i. This is done without modifying the average,
|
||||
/// i.e. the integrals of g and f over the cell are the same.
|
||||
/// \param[in] factor Multiply gradient by this factor.
|
||||
/// \param[out] coefficients Coefficients {c_i} for a single cell.
|
||||
void DGBasisBoundedTotalDegree::multiplyGradient(const double factor,
|
||||
double* coefficients) const
|
||||
{
|
||||
const int nb = numBasisFunc();
|
||||
for (int ix = 1; ix < nb; ++ix) {
|
||||
coefficients[ix] *= factor;
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the average of the function f = sum_i c_i b_i.
|
||||
/// \param[in] coefficients Coefficients {c_i} for a single cell.
|
||||
double DGBasisBoundedTotalDegree::functionAverage(const double* coefficients) const
|
||||
{
|
||||
return coefficients[0];
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ---------------- Methods for class DGBasisMultilin ----------------
|
||||
|
||||
|
||||
/// Constructor.
|
||||
/// \param[in] grid grid on which basis is used (cell-wise)
|
||||
/// \param[in] degree polynomial degree of basis
|
||||
DGBasisMultilin::DGBasisMultilin(const UnstructuredGrid& grid,
|
||||
const int degree_arg)
|
||||
: grid_(grid),
|
||||
degree_(degree_arg)
|
||||
{
|
||||
if (grid_.dimensions > 3) {
|
||||
OPM_THROW(std::runtime_error, "Grid dimension must be 1, 2 or 3.");
|
||||
}
|
||||
if (degree_ > 1 || degree_ < 0) {
|
||||
OPM_THROW(std::runtime_error, "Degree must be 0 or 1.");
|
||||
}
|
||||
}
|
||||
|
||||
/// Destructor.
|
||||
DGBasisMultilin::~DGBasisMultilin()
|
||||
{
|
||||
}
|
||||
|
||||
/// The number of basis functions per cell.
|
||||
int DGBasisMultilin::numBasisFunc() const
|
||||
{
|
||||
switch (dimensions()) {
|
||||
case 1:
|
||||
return degree_ + 1;
|
||||
case 2:
|
||||
return (degree_ + 1)*(degree_ + 1);
|
||||
case 3:
|
||||
return (degree_ + 1)*(degree_ + 1)*(degree_ + 1);
|
||||
default:
|
||||
OPM_THROW(std::runtime_error, "Dimensions must be 1, 2 or 3.");
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of space dimensions.
|
||||
int DGBasisMultilin::dimensions() const
|
||||
{
|
||||
return grid_.dimensions;
|
||||
}
|
||||
|
||||
/// The polynomial degree of the basis functions.
|
||||
int DGBasisMultilin::degree() const
|
||||
{
|
||||
return degree_;
|
||||
}
|
||||
|
||||
/// Evaluate all basis functions associated with cell at x,
|
||||
/// writing to f_x. The array f_x must have size equal to
|
||||
/// numBasisFunc().
|
||||
void DGBasisMultilin::eval(const int cell,
|
||||
const double* x,
|
||||
double* f_x) const
|
||||
{
|
||||
const int dim = dimensions();
|
||||
const int num_basis = numBasisFunc();
|
||||
const double* cc = grid_.cell_centroids + dim*cell;
|
||||
switch (degree_) {
|
||||
case 0:
|
||||
f_x[0] = 1;
|
||||
break;
|
||||
case 1:
|
||||
std::fill(f_x, f_x + num_basis, 1.0);
|
||||
for (int dd = 0; dd < dim; ++dd) {
|
||||
const double f[2] = { 0.5 - x[dd] + cc[dd], 0.5 + x[dd] - cc[dd] };
|
||||
const int divi = 1 << (dim - dd - 1); // { 4, 2, 1 } for 3d, for example.
|
||||
for (int ix = 0; ix < num_basis; ++ix) {
|
||||
f_x[ix] *= f[(ix/divi) % 2];
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
OPM_THROW(std::runtime_error, "Maximum degree is 1 for now.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Evaluate gradients of all basis functions associated with
|
||||
/// cell at x, writing to grad_f_x. The array grad_f_x must
|
||||
/// have size numBasisFunc() * dimensions(). The dimensions()
|
||||
/// components of the first basis function gradient come
|
||||
/// before the components of the second etc.
|
||||
void DGBasisMultilin::evalGrad(const int cell,
|
||||
const double* x,
|
||||
double* grad_f_x) const
|
||||
{
|
||||
const int dim = dimensions();
|
||||
const int num_basis = numBasisFunc();
|
||||
const double* cc = grid_.cell_centroids + dim*cell;
|
||||
switch (degree_) {
|
||||
case 0:
|
||||
std::fill(grad_f_x, grad_f_x + num_basis*dim, 0.0);
|
||||
break;
|
||||
case 1:
|
||||
std::fill(grad_f_x, grad_f_x + num_basis*dim, 1.0);
|
||||
for (int dd = 0; dd < dim; ++dd) {
|
||||
const double f[2] = { 0.5 - x[dd] + cc[dd], 0.5 + x[dd] - cc[dd] };
|
||||
const double fder[2] = { -1.0, 1.0 };
|
||||
const int divi = 1 << (dim - dd - 1); // { 4, 2, 1 } for 3d, for example.
|
||||
for (int ix = 0; ix < num_basis; ++ix) {
|
||||
const int ind = (ix/divi) % 2;
|
||||
for (int dder = 0; dder < dim; ++dder) {
|
||||
grad_f_x[ix*dim + dder] *= (dder == dd ? fder[ind] : f[ind]);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
OPM_THROW(std::runtime_error, "Maximum degree is 1 for now.");
|
||||
}
|
||||
}
|
||||
|
||||
/// Modify basis coefficients to add to the function value.
|
||||
/// A function f = sum_i c_i b_i is assumed, and we change
|
||||
/// it to (f + increment) by modifying the c_i. This is done without
|
||||
/// modifying its gradient.
|
||||
/// \param[in] increment Add this value to the function.
|
||||
/// \param[out] coefficients Coefficients {c_i} for a single cell.
|
||||
void DGBasisMultilin::addConstant(const double increment,
|
||||
double* coefficients) const
|
||||
{
|
||||
const int nb = numBasisFunc();
|
||||
const double term = increment/double(nb);
|
||||
for (int ix = 0; ix < nb; ++ix) {
|
||||
coefficients[ix] += term;
|
||||
}
|
||||
}
|
||||
|
||||
/// Modify basis coefficients to change the function's slope.
|
||||
/// A function f = sum_i c_i b_i is assumed, and we change
|
||||
/// it to a function g with the property that grad g = factor * grad f
|
||||
/// by modifying the c_i. This is done without modifying the average,
|
||||
/// i.e. the integrals of g and f over the cell are the same.
|
||||
/// \param[in] factor Multiply gradient by this factor.
|
||||
/// \param[out] coefficients Coefficients {c_i} for a single cell.
|
||||
void DGBasisMultilin::multiplyGradient(const double factor,
|
||||
double* coefficients) const
|
||||
{
|
||||
const int nb = numBasisFunc();
|
||||
const double aver = functionAverage(coefficients);
|
||||
for (int ix = 0; ix < nb; ++ix) {
|
||||
coefficients[ix] = factor*(coefficients[ix] - aver) + aver;
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the average of the function f = sum_i c_i b_i.
|
||||
/// \param[in] coefficients Coefficients {c_i} for a single cell.
|
||||
double DGBasisMultilin::functionAverage(const double* coefficients) const
|
||||
{
|
||||
const int nb = numBasisFunc();
|
||||
return std::accumulate(coefficients, coefficients + nb, 0.0)/double(nb);
|
||||
}
|
||||
|
||||
} // namespace Opm
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
Copyright 2013 SINTEF ICT, Applied Mathematics.
|
||||
|
||||
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_DGBASIS_HEADER_INCLUDED
|
||||
#define OPM_DGBASIS_HEADER_INCLUDED
|
||||
|
||||
#include <vector>
|
||||
|
||||
struct UnstructuredGrid;
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
/// Base class for Discontinuous Galerkin bases, intended for time-of-flight computations.
|
||||
class DGBasisInterface
|
||||
{
|
||||
public:
|
||||
/// Virtual destructor.
|
||||
virtual ~DGBasisInterface();
|
||||
|
||||
/// The number of basis functions per cell.
|
||||
virtual int numBasisFunc() const = 0;
|
||||
|
||||
/// The number of space dimensions.
|
||||
virtual int dimensions() const = 0;
|
||||
|
||||
/// The polynomial degree of the basis functions.
|
||||
virtual int degree() const = 0;
|
||||
|
||||
/// Evaluate all basis functions associated with cell at x,
|
||||
/// writing to f_x. The array f_x must have size equal to
|
||||
/// numBasisFunc().
|
||||
virtual void eval(const int cell,
|
||||
const double* x,
|
||||
double* f_x) const = 0;
|
||||
|
||||
/// Evaluate gradients of all basis functions associated with
|
||||
/// cell at x, writing to grad_f_x. The array grad_f_x must
|
||||
/// have size numBasisFunc() * dimensions(). The dimensions()
|
||||
/// components of the first basis function gradient come
|
||||
/// before the components of the second etc.
|
||||
virtual void evalGrad(const int cell,
|
||||
const double* x,
|
||||
double* grad_f_x) const = 0;
|
||||
|
||||
/// Modify basis coefficients to add to the function value.
|
||||
/// A function f = sum_i c_i b_i is assumed, and we change
|
||||
/// it to (f + increment) by modifying the c_i. This is done without
|
||||
/// modifying its gradient.
|
||||
/// \param[in] increment Add this value to the function.
|
||||
/// \param[out] coefficients Coefficients {c_i} for a single cell.
|
||||
virtual void addConstant(const double increment,
|
||||
double* coefficients) const = 0;
|
||||
|
||||
/// Modify basis coefficients to change the function's slope.
|
||||
/// A function f = sum_i c_i b_i is assumed, and we change
|
||||
/// it to a function g with the property that grad g = factor * grad f
|
||||
/// by modifying the c_i. This is done without modifying the average,
|
||||
/// i.e. the integrals of g and f over the cell are the same.
|
||||
/// \param[in] factor Multiply gradient by this factor.
|
||||
/// \param[out] coefficients Coefficients {c_i} for a single cell.
|
||||
virtual void multiplyGradient(const double factor,
|
||||
double* coefficients) const = 0;
|
||||
|
||||
/// Evaluate function f = sum_i c_i b_i at the point x.
|
||||
/// Note that this function is not virtual, but implemented in
|
||||
/// terms of the virtual functions of the class.
|
||||
/// \param[in] cell Cell index
|
||||
/// \param[in] coefficients Coefficients {c_i} for a single cell.
|
||||
/// \param[in] x Point at which to compute f(x).
|
||||
double evalFunc(const int cell,
|
||||
const double* coefficients,
|
||||
const double* x) const;
|
||||
|
||||
/// Compute the average of the function f = sum_i c_i b_i.
|
||||
/// \param[in] coefficients Coefficients {c_i} for a single cell.
|
||||
virtual double functionAverage(const double* coefficients) const = 0;
|
||||
|
||||
private:
|
||||
mutable std::vector<double> bvals_; // For evalFunc().
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// A class providing discontinuous Galerkin basis functions
|
||||
/// of bounded total degree.
|
||||
///
|
||||
/// The basis functions are the following for each cell (example for 3d):
|
||||
/// Degree 0: 1.
|
||||
/// Degree 1: 1, x - xc, y - yc, z - zc
|
||||
/// where (xc, yc, zc) are the coordinates of the cell centroid.
|
||||
/// Further degrees await development.
|
||||
class DGBasisBoundedTotalDegree : public DGBasisInterface
|
||||
{
|
||||
public:
|
||||
/// Constructor.
|
||||
/// \param[in] grid grid on which basis is used (cell-wise)
|
||||
/// \param[in] degree polynomial degree of basis
|
||||
DGBasisBoundedTotalDegree(const UnstructuredGrid& grid, const int degree);
|
||||
|
||||
/// Destructor.
|
||||
virtual ~DGBasisBoundedTotalDegree();
|
||||
|
||||
/// The number of basis functions per cell.
|
||||
virtual int numBasisFunc() const;
|
||||
|
||||
/// The number of space dimensions.
|
||||
virtual int dimensions() const;
|
||||
|
||||
/// The polynomial degree of the basis functions.
|
||||
virtual int degree() const;
|
||||
|
||||
/// Evaluate all basis functions associated with cell at x,
|
||||
/// writing to f_x. The array f_x must have size equal to
|
||||
/// numBasisFunc().
|
||||
virtual void eval(const int cell,
|
||||
const double* x,
|
||||
double* f_x) const;
|
||||
|
||||
/// Evaluate gradients of all basis functions associated with
|
||||
/// cell at x, writing to grad_f_x. The array grad_f_x must
|
||||
/// have size numBasisFunc() * dimensions(). The dimensions()
|
||||
/// components of the first basis function gradient come
|
||||
/// before the components of the second etc.
|
||||
virtual void evalGrad(const int cell,
|
||||
const double* x,
|
||||
double* grad_f_x) const;
|
||||
|
||||
/// Modify basis coefficients to add to the function value.
|
||||
/// A function f = sum_i c_i b_i is assumed, and we change
|
||||
/// it to (f + increment) by modifying the c_i. This is done without
|
||||
/// modifying its gradient.
|
||||
/// \param[in] increment Add this value to the function.
|
||||
/// \param[out] coefficients Coefficients {c_i} for a single cell.
|
||||
virtual void addConstant(const double increment,
|
||||
double* coefficients) const;
|
||||
|
||||
/// Modify basis coefficients to change the function's slope.
|
||||
/// A function f = sum_i c_i b_i is assumed, and we change
|
||||
/// it to a function g with the property that grad g = factor * grad f
|
||||
/// by modifying the c_i. This is done without modifying the average,
|
||||
/// i.e. the integrals of g and f over the cell are the same.
|
||||
/// \param[in] factor Multiply gradient by this factor.
|
||||
/// \param[out] coefficients Coefficients {c_i} for a single cell.
|
||||
virtual void multiplyGradient(const double factor,
|
||||
double* coefficients) const;
|
||||
|
||||
/// Compute the average of the function f = sum_i c_i b_i.
|
||||
/// \param[in] coefficients Coefficients {c_i} for a single cell.
|
||||
virtual double functionAverage(const double* coefficients) const;
|
||||
|
||||
private:
|
||||
const UnstructuredGrid& grid_;
|
||||
const int degree_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/// A class providing discontinuous Galerkin basis functions of
|
||||
/// multi-degree 1 (bilinear or trilinear functions).
|
||||
///
|
||||
/// The basis functions for a cell are the following
|
||||
/// Degree 0: 1.
|
||||
/// (for 2 dims:)
|
||||
/// (Bi)degree 1: (x-)(y-), (x-)(y+), (x+)(y-), (x+)(y+)
|
||||
/// where (x-) = (1/2 - x + xc), (x+) = (1/2 + x - xc)
|
||||
/// and xc is the x-coordinate of the cell centroid.
|
||||
/// Similar for (y-), (y+).
|
||||
class DGBasisMultilin : public DGBasisInterface
|
||||
{
|
||||
public:
|
||||
/// Constructor.
|
||||
/// \param[in] grid grid on which basis is used (cell-wise)
|
||||
/// \param[in] degree polynomial degree of basis (in each coordinate)
|
||||
DGBasisMultilin(const UnstructuredGrid& grid, const int degree);
|
||||
|
||||
/// Destructor.
|
||||
virtual ~DGBasisMultilin();
|
||||
|
||||
/// The number of basis functions per cell.
|
||||
virtual int numBasisFunc() const;
|
||||
|
||||
/// The number of space dimensions.
|
||||
virtual int dimensions() const;
|
||||
|
||||
/// The polynomial degree of the basis functions.
|
||||
virtual int degree() const;
|
||||
|
||||
/// Evaluate all basis functions associated with cell at x,
|
||||
/// writing to f_x. The array f_x must have size equal to
|
||||
/// numBasisFunc().
|
||||
virtual void eval(const int cell,
|
||||
const double* x,
|
||||
double* f_x) const;
|
||||
|
||||
/// Evaluate gradients of all basis functions associated with
|
||||
/// cell at x, writing to grad_f_x. The array grad_f_x must
|
||||
/// have size numBasisFunc() * dimensions(). The dimensions()
|
||||
/// components of the first basis function gradient come
|
||||
/// before the components of the second etc.
|
||||
virtual void evalGrad(const int cell,
|
||||
const double* x,
|
||||
double* grad_f_x) const;
|
||||
|
||||
/// Modify basis coefficients to add to the function value.
|
||||
/// A function f = sum_i c_i b_i is assumed, and we change
|
||||
/// it to (f + increment) by modifying the c_i. This is done without
|
||||
/// modifying its gradient.
|
||||
/// \param[in] increment Add this value to the function.
|
||||
/// \param[out] coefficients Coefficients {c_i} for a single cell.
|
||||
virtual void addConstant(const double increment,
|
||||
double* coefficients) const;
|
||||
|
||||
/// Modify basis coefficients to change the function's slope.
|
||||
/// A function f = sum_i c_i b_i is assumed, and we change
|
||||
/// it to a function g with the property that grad g = factor * grad f
|
||||
/// by modifying the c_i. This is done without modifying the average,
|
||||
/// i.e. the integrals of g and f over the cell are the same.
|
||||
/// \param[in] factor Multiply gradient by this factor.
|
||||
/// \param[out] coefficients Coefficients {c_i} for a single cell.
|
||||
virtual void multiplyGradient(const double factor,
|
||||
double* coefficients) const;
|
||||
|
||||
/// Compute the average of the function f = sum_i c_i b_i.
|
||||
/// \param[in] coefficients Coefficients {c_i} for a single cell.
|
||||
virtual double functionAverage(const double* coefficients) const;
|
||||
|
||||
private:
|
||||
const UnstructuredGrid& grid_;
|
||||
const int degree_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
|
||||
#endif // OPM_DGBASIS_HEADER_INCLUDED
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
Copyright 2015 SINTEF ICT, Applied Mathematics.
|
||||
|
||||
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/core/flowdiagnostics/FlowDiagnostics.hpp>
|
||||
|
||||
#include <opm/core/utility/ErrorMacros.hpp>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
|
||||
/// \brief Compute flow-capacity/storage-capacity based on time-of-flight.
|
||||
///
|
||||
/// The F-Phi curve is an analogue to the fractional flow curve in a 1D
|
||||
/// displacement. It can be used to compute other interesting diagnostic
|
||||
/// quantities such as the Lorenz coefficient. For a technical description
|
||||
/// see Shavali et al. (SPE 146446), Shook and Mitchell (SPE 124625).
|
||||
///
|
||||
/// \param[in] pv pore volumes of each cell
|
||||
/// \param[in] ftof forward (time from injector) time-of-flight values for each cell
|
||||
/// \param[in] rtof reverse (time to producer) time-of-flight values for each cell
|
||||
/// \return a pair of vectors, the first containing F (flow capacity) the second
|
||||
/// containing Phi (storage capacity).
|
||||
std::pair<std::vector<double>, std::vector<double>> computeFandPhi(const std::vector<double>& pv,
|
||||
const std::vector<double>& ftof,
|
||||
const std::vector<double>& rtof)
|
||||
{
|
||||
if (pv.size() != ftof.size() || pv.size() != rtof.size()) {
|
||||
OPM_THROW(std::runtime_error, "computeFandPhi(): Input vectors must have same size.");
|
||||
}
|
||||
|
||||
// Sort according to total travel time.
|
||||
const int n = pv.size();
|
||||
typedef std::pair<double, double> D2;
|
||||
std::vector<D2> time_and_pv(n);
|
||||
for (int ii = 0; ii < n; ++ii) {
|
||||
time_and_pv[ii].first = ftof[ii] + rtof[ii]; // Total travel time.
|
||||
time_and_pv[ii].second = pv[ii];
|
||||
}
|
||||
std::sort(time_and_pv.begin(), time_and_pv.end());
|
||||
|
||||
// Compute Phi.
|
||||
std::vector<double> Phi(n + 1);
|
||||
Phi[0] = 0.0;
|
||||
for (int ii = 0; ii < n; ++ii) {
|
||||
Phi[ii+1] = time_and_pv[ii].second;
|
||||
}
|
||||
std::partial_sum(Phi.begin(), Phi.end(), Phi.begin());
|
||||
const double vt = Phi.back(); // Total pore volume.
|
||||
for (int ii = 1; ii < n+1; ++ii) { // Note limits of loop.
|
||||
Phi[ii] /= vt; // Normalize Phi.
|
||||
}
|
||||
|
||||
// Compute F.
|
||||
std::vector<double> F(n + 1);
|
||||
F[0] = 0.0;
|
||||
for (int ii = 0; ii < n; ++ii) {
|
||||
F[ii+1] = time_and_pv[ii].second / time_and_pv[ii].first;
|
||||
}
|
||||
std::partial_sum(F.begin(), F.end(), F.begin());
|
||||
const double ft = F.back(); // Total flux.
|
||||
for (int ii = 1; ii < n+1; ++ii) { // Note limits of loop.
|
||||
F[ii] /= ft; // Normalize Phi.
|
||||
}
|
||||
|
||||
return std::make_pair(F, Phi);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// \brief Compute the Lorenz coefficient based on the F-Phi curve.
|
||||
///
|
||||
/// The Lorenz coefficient is a measure of heterogeneity. It is equal
|
||||
/// to twice the area between the F-Phi curve and the F = Phi line.
|
||||
/// The coefficient can vary from zero to one. If the coefficient is
|
||||
/// zero (so the F-Phi curve is a straight line) we have perfect
|
||||
/// piston-like displacement while a coefficient of one indicates
|
||||
/// infinitely heterogenous displacement (essentially no sweep).
|
||||
///
|
||||
/// Note: The coefficient is analogous to the Gini coefficient of
|
||||
/// economic theory, where the name Lorenz curve is applied to
|
||||
/// what we call the F-Phi curve.
|
||||
///
|
||||
/// \param[in] flowcap flow capacity (F) as from computeFandPhi()
|
||||
/// \param[in] storagecap storage capacity (Phi) as from computeFandPhi()
|
||||
/// \return the Lorenz coefficient
|
||||
double computeLorenz(const std::vector<double>& flowcap,
|
||||
const std::vector<double>& storagecap)
|
||||
{
|
||||
if (flowcap.size() != storagecap.size()) {
|
||||
OPM_THROW(std::runtime_error, "computeLorenz(): Input vectors must have same size.");
|
||||
}
|
||||
double integral = 0.0;
|
||||
// Trapezoid quadrature of the curve F(Phi).
|
||||
const int num_intervals = flowcap.size() - 1;
|
||||
for (int ii = 0; ii < num_intervals; ++ii) {
|
||||
const double len = storagecap[ii+1] - storagecap[ii];
|
||||
integral += (flowcap[ii] + flowcap[ii+1]) * len / 2.0;
|
||||
}
|
||||
return 2.0 * (integral - 0.5);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// \brief Compute sweep efficiency versus dimensionless time (PVI).
|
||||
///
|
||||
/// The sweep efficiency is analogue to 1D displacement using the
|
||||
/// F-Phi curve as flux function.
|
||||
///
|
||||
/// \param[in] flowcap flow capacity (F) as from computeFandPhi()
|
||||
/// \param[in] storagecap storage capacity (Phi) as from computeFandPhi()
|
||||
/// \return a pair of vectors, the first containing Ev (sweep efficiency)
|
||||
/// the second containing tD (dimensionless time).
|
||||
std::pair<std::vector<double>, std::vector<double>> computeSweep(const std::vector<double>& flowcap,
|
||||
const std::vector<double>& storagecap)
|
||||
{
|
||||
if (flowcap.size() != storagecap.size()) {
|
||||
OPM_THROW(std::runtime_error, "computeSweep(): Input vectors must have same size.");
|
||||
}
|
||||
|
||||
// Compute tD and Ev simultaneously,
|
||||
// skipping identical Phi data points.
|
||||
const int n = flowcap.size();
|
||||
std::vector<double> Ev;
|
||||
std::vector<double> tD;
|
||||
tD.reserve(n);
|
||||
Ev.reserve(n);
|
||||
tD.push_back(0.0);
|
||||
Ev.push_back(0.0);
|
||||
for (int ii = 1; ii < n; ++ii) { // Note loop limits.
|
||||
const double fd = flowcap[ii] - flowcap[ii-1];
|
||||
const double sd = storagecap[ii] - storagecap[ii-1];
|
||||
if (fd != 0.0) {
|
||||
tD.push_back(sd/fd);
|
||||
Ev.push_back(storagecap[ii] + (1.0 - flowcap[ii]) * tD.back());
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(Ev, tD);
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace Opm
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
Copyright 2015 SINTEF ICT, Applied Mathematics.
|
||||
|
||||
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_FLOWDIAGNOSTICS_HEADER_INCLUDED
|
||||
#define OPM_FLOWDIAGNOSTICS_HEADER_INCLUDED
|
||||
|
||||
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
/// \brief Compute flow-capacity/storage-capacity based on time-of-flight.
|
||||
///
|
||||
/// The F-Phi curve is an analogue to the fractional flow curve in a 1D
|
||||
/// displacement. It can be used to compute other interesting diagnostic
|
||||
/// quantities such as the Lorenz coefficient. For a technical description
|
||||
/// see Shavali et al. (SPE 146446), Shook and Mitchell (SPE 124625).
|
||||
///
|
||||
/// \param[in] pv pore volumes of each cell
|
||||
/// \param[in] ftof forward (time from injector) time-of-flight values for each cell
|
||||
/// \param[in] rtof reverse (time to producer) time-of-flight values for each cell
|
||||
/// \return a pair of vectors, the first containing F (flow capacity) the second
|
||||
/// containing Phi (storage capacity).
|
||||
std::pair<std::vector<double>, std::vector<double>>
|
||||
computeFandPhi(const std::vector<double>& pv,
|
||||
const std::vector<double>& ftof,
|
||||
const std::vector<double>& rtof);
|
||||
|
||||
|
||||
/// \brief Compute the Lorenz coefficient based on the F-Phi curve.
|
||||
///
|
||||
/// The Lorenz coefficient is a measure of heterogeneity. It is equal
|
||||
/// to twice the area between the F-Phi curve and the F = Phi line.
|
||||
/// The coefficient can vary from zero to one. If the coefficient is
|
||||
/// zero (so the F-Phi curve is a straight line) we have perfect
|
||||
/// piston-like displacement while a coefficient of one indicates
|
||||
/// infinitely heterogenous displacement (essentially no sweep).
|
||||
///
|
||||
/// Note: The coefficient is analogous to the Gini coefficient of
|
||||
/// economic theory, where the name Lorenz curve is applied to
|
||||
/// what we call the F-Phi curve.
|
||||
///
|
||||
/// \param[in] flowcap flow capacity (F) as from computeFandPhi()
|
||||
/// \param[in] storagecap storage capacity (Phi) as from computeFandPhi()
|
||||
/// \return the Lorenz coefficient
|
||||
double computeLorenz(const std::vector<double>& flowcap,
|
||||
const std::vector<double>& storagecap);
|
||||
|
||||
|
||||
/// \brief Compute sweep efficiency versus dimensionless time (PVI).
|
||||
///
|
||||
/// The sweep efficiency is analogue to 1D displacement using the
|
||||
/// F-Phi curve as flux function.
|
||||
///
|
||||
/// \param[in] flowcap flow capacity (F) as from computeFandPhi()
|
||||
/// \param[in] storagecap storage capacity (Phi) as from computeFandPhi()
|
||||
/// \return a pair of vectors, the first containing Ev (sweep efficiency)
|
||||
/// the second containing tD (dimensionless time).
|
||||
std::pair<std::vector<double>, std::vector<double>>
|
||||
computeSweep(const std::vector<double>& flowcap,
|
||||
const std::vector<double>& storagecap);
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
#endif // OPM_FLOWDIAGNOSTICS_HEADER_INCLUDED
|
||||
@@ -0,0 +1,805 @@
|
||||
/*
|
||||
Copyright 2012 SINTEF ICT, Applied Mathematics.
|
||||
|
||||
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/core/grid/CellQuadrature.hpp>
|
||||
#include <opm/core/grid/FaceQuadrature.hpp>
|
||||
#include <opm/core/flowdiagnostics/TofDiscGalReorder.hpp>
|
||||
#include <opm/core/flowdiagnostics/DGBasis.hpp>
|
||||
#include <opm/core/grid.h>
|
||||
#include <opm/core/utility/ErrorMacros.hpp>
|
||||
#include <opm/core/utility/SparseTable.hpp>
|
||||
#include <opm/core/utility/VelocityInterpolation.hpp>
|
||||
#include <opm/core/utility/parameters/ParameterGroup.hpp>
|
||||
#include <opm/core/linalg/blas_lapack.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
#include <iostream>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
|
||||
/// Construct solver.
|
||||
TofDiscGalReorder::TofDiscGalReorder(const UnstructuredGrid& grid,
|
||||
const parameter::ParameterGroup& param)
|
||||
: grid_(grid),
|
||||
use_cvi_(false),
|
||||
use_limiter_(false),
|
||||
limiter_relative_flux_threshold_(1e-3),
|
||||
limiter_method_(MinUpwindAverage),
|
||||
limiter_usage_(DuringComputations),
|
||||
coord_(grid.dimensions),
|
||||
velocity_(grid.dimensions),
|
||||
gauss_seidel_tol_(1e-3)
|
||||
{
|
||||
const int dg_degree = param.getDefault("dg_degree", 0);
|
||||
const bool use_tensorial_basis = param.getDefault("use_tensorial_basis", false);
|
||||
if (use_tensorial_basis) {
|
||||
basis_func_.reset(new DGBasisMultilin(grid_, dg_degree));
|
||||
} else {
|
||||
basis_func_.reset(new DGBasisBoundedTotalDegree(grid_, dg_degree));
|
||||
}
|
||||
|
||||
tracers_ensure_unity_ = param.getDefault("tracers_ensure_unity", true);
|
||||
|
||||
use_cvi_ = param.getDefault("use_cvi", use_cvi_);
|
||||
use_limiter_ = param.getDefault("use_limiter", use_limiter_);
|
||||
if (use_limiter_) {
|
||||
limiter_relative_flux_threshold_ = param.getDefault("limiter_relative_flux_threshold",
|
||||
limiter_relative_flux_threshold_);
|
||||
const std::string limiter_method_str = param.getDefault<std::string>("limiter_method", "MinUpwindAverage");
|
||||
if (limiter_method_str == "MinUpwindFace") {
|
||||
limiter_method_ = MinUpwindFace;
|
||||
} else if (limiter_method_str == "MinUpwindAverage") {
|
||||
limiter_method_ = MinUpwindAverage;
|
||||
} else {
|
||||
OPM_THROW(std::runtime_error, "Unknown limiter method: " << limiter_method_str);
|
||||
}
|
||||
const std::string limiter_usage_str = param.getDefault<std::string>("limiter_usage", "DuringComputations");
|
||||
if (limiter_usage_str == "DuringComputations") {
|
||||
limiter_usage_ = DuringComputations;
|
||||
} else if (limiter_usage_str == "AsPostProcess") {
|
||||
limiter_usage_ = AsPostProcess;
|
||||
} else if (limiter_usage_str == "AsSimultaneousPostProcess") {
|
||||
limiter_usage_ = AsSimultaneousPostProcess;
|
||||
} else {
|
||||
OPM_THROW(std::runtime_error, "Unknown limiter usage spec: " << limiter_usage_str);
|
||||
}
|
||||
}
|
||||
// A note about the use_cvi_ member variable:
|
||||
// In principle, we should not need it, since the choice of velocity
|
||||
// interpolation is made below, but we may need to use higher order
|
||||
// quadrature to exploit CVI, so we store the choice.
|
||||
// An alternative would be to add a virtual method isConstant() to
|
||||
// the VelocityInterpolationInterface.
|
||||
if (use_cvi_) {
|
||||
velocity_interpolation_.reset(new VelocityInterpolationECVI(grid_));
|
||||
} else {
|
||||
velocity_interpolation_.reset(new VelocityInterpolationConstant(grid_));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Solve for time-of-flight.
|
||||
void TofDiscGalReorder::solveTof(const double* darcyflux,
|
||||
const double* porevolume,
|
||||
const double* source,
|
||||
std::vector<double>& tof_coeff)
|
||||
{
|
||||
darcyflux_ = darcyflux;
|
||||
porevolume_ = porevolume;
|
||||
source_ = source;
|
||||
#ifndef NDEBUG
|
||||
// Sanity check for sources.
|
||||
const double cum_src = std::accumulate(source, source + grid_.number_of_cells, 0.0);
|
||||
if (std::fabs(cum_src) > *std::max_element(source, source + grid_.number_of_cells)*1e-2) {
|
||||
// OPM_THROW(std::runtime_error, "Sources do not sum to zero: " << cum_src);
|
||||
OPM_MESSAGE("Warning: sources do not sum to zero: " << cum_src);
|
||||
}
|
||||
#endif
|
||||
const int num_basis = basis_func_->numBasisFunc();
|
||||
tof_coeff.resize(num_basis*grid_.number_of_cells);
|
||||
std::fill(tof_coeff.begin(), tof_coeff.end(), 0.0);
|
||||
tof_coeff_ = &tof_coeff[0];
|
||||
rhs_.resize(num_basis);
|
||||
jac_.resize(num_basis*num_basis);
|
||||
orig_jac_.resize(num_basis*num_basis);
|
||||
basis_.resize(num_basis);
|
||||
basis_nb_.resize(num_basis);
|
||||
grad_basis_.resize(num_basis*grid_.dimensions);
|
||||
velocity_interpolation_->setupFluxes(darcyflux);
|
||||
num_tracers_ = 0;
|
||||
num_multicell_ = 0;
|
||||
max_size_multicell_ = 0;
|
||||
max_iter_multicell_ = 0;
|
||||
num_singlesolves_ = 0;
|
||||
reorderAndTransport(grid_, darcyflux);
|
||||
switch (limiter_usage_) {
|
||||
case AsPostProcess:
|
||||
applyLimiterAsPostProcess();
|
||||
break;
|
||||
case AsSimultaneousPostProcess:
|
||||
applyLimiterAsSimultaneousPostProcess();
|
||||
break;
|
||||
case DuringComputations:
|
||||
// Do nothing.
|
||||
break;
|
||||
default:
|
||||
OPM_THROW(std::runtime_error, "Unknown limiter usage choice: " << limiter_usage_);
|
||||
}
|
||||
if (num_multicell_ > 0) {
|
||||
std::cout << num_multicell_ << " multicell blocks with max size "
|
||||
<< max_size_multicell_ << " cells in upto "
|
||||
<< max_iter_multicell_ << " iterations." << std::endl;
|
||||
std::cout << "Average solves per cell (for all cells) was "
|
||||
<< double(num_singlesolves_)/double(grid_.number_of_cells) << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Solve for time-of-flight and a number of tracers.
|
||||
/// \param[in] darcyflux Array of signed face fluxes.
|
||||
/// \param[in] porevolume Array of pore volumes.
|
||||
/// \param[in] source Source term. Sign convention is:
|
||||
/// (+) inflow flux,
|
||||
/// (-) outflow flux.
|
||||
/// \param[in] tracerheads Table containing one row per tracer, and each
|
||||
/// row contains the source cells for that tracer.
|
||||
/// \param[out] tof_coeff Array of time-of-flight solution coefficients.
|
||||
/// The values are ordered by cell, meaning that
|
||||
/// the K coefficients corresponding to the first
|
||||
/// cell comes before the K coefficients corresponding
|
||||
/// to the second cell etc.
|
||||
/// K depends on degree and grid dimension.
|
||||
/// \param[out] tracer_coeff Array of tracer solution coefficients. N*K per cell,
|
||||
/// where N is equal to tracerheads.size(). All K coefs
|
||||
/// for a tracer are consecutive, and all tracers' coefs
|
||||
/// for a cell come before those for the next cell.
|
||||
void TofDiscGalReorder::solveTofTracer(const double* darcyflux,
|
||||
const double* porevolume,
|
||||
const double* source,
|
||||
const SparseTable<int>& tracerheads,
|
||||
std::vector<double>& tof_coeff,
|
||||
std::vector<double>& tracer_coeff)
|
||||
{
|
||||
darcyflux_ = darcyflux;
|
||||
porevolume_ = porevolume;
|
||||
source_ = source;
|
||||
#ifndef NDEBUG
|
||||
// Sanity check for sources.
|
||||
const double cum_src = std::accumulate(source, source + grid_.number_of_cells, 0.0);
|
||||
if (std::fabs(cum_src) > *std::max_element(source, source + grid_.number_of_cells)*1e-2) {
|
||||
// OPM_THROW(std::runtime_error, "Sources do not sum to zero: " << cum_src);
|
||||
OPM_MESSAGE("Warning: sources do not sum to zero: " << cum_src);
|
||||
}
|
||||
#endif
|
||||
const int num_basis = basis_func_->numBasisFunc();
|
||||
num_tracers_ = tracerheads.size();
|
||||
tof_coeff.resize(num_basis*grid_.number_of_cells);
|
||||
std::fill(tof_coeff.begin(), tof_coeff.end(), 0.0);
|
||||
tof_coeff_ = &tof_coeff[0];
|
||||
rhs_.resize(num_basis*(num_tracers_ + 1));
|
||||
jac_.resize(num_basis*num_basis);
|
||||
orig_jac_.resize(num_basis*num_basis);
|
||||
basis_.resize(num_basis);
|
||||
basis_nb_.resize(num_basis);
|
||||
grad_basis_.resize(num_basis*grid_.dimensions);
|
||||
velocity_interpolation_->setupFluxes(darcyflux);
|
||||
|
||||
// Set up tracer
|
||||
tracer_coeff.resize(grid_.number_of_cells*num_tracers_*num_basis);
|
||||
std::fill(tracer_coeff.begin(), tracer_coeff.end(), 0.0);
|
||||
if (num_tracers_ > 0) {
|
||||
tracerhead_by_cell_.clear();
|
||||
tracerhead_by_cell_.resize(grid_.number_of_cells, NoTracerHead);
|
||||
}
|
||||
for (int tr = 0; tr < num_tracers_; ++tr) {
|
||||
for (int i = 0; i < tracerheads[tr].size(); ++i) {
|
||||
const int cell = tracerheads[tr][i];
|
||||
basis_func_->addConstant(1.0, &tracer_coeff[cell*num_tracers_*num_basis + tr*num_basis]);
|
||||
tracer_coeff[cell*num_tracers_ + tr] = 1.0;
|
||||
tracerhead_by_cell_[cell] = tr;
|
||||
}
|
||||
}
|
||||
|
||||
tracer_coeff_ = &tracer_coeff[0];
|
||||
num_multicell_ = 0;
|
||||
max_size_multicell_ = 0;
|
||||
max_iter_multicell_ = 0;
|
||||
num_singlesolves_ = 0;
|
||||
reorderAndTransport(grid_, darcyflux);
|
||||
switch (limiter_usage_) {
|
||||
case AsPostProcess:
|
||||
applyLimiterAsPostProcess();
|
||||
break;
|
||||
case AsSimultaneousPostProcess:
|
||||
applyLimiterAsSimultaneousPostProcess();
|
||||
break;
|
||||
case DuringComputations:
|
||||
// Do nothing.
|
||||
break;
|
||||
default:
|
||||
OPM_THROW(std::runtime_error, "Unknown limiter usage choice: " << limiter_usage_);
|
||||
}
|
||||
if (num_multicell_ > 0) {
|
||||
std::cout << num_multicell_ << " multicell blocks with max size "
|
||||
<< max_size_multicell_ << " cells in upto "
|
||||
<< max_iter_multicell_ << " iterations." << std::endl;
|
||||
std::cout << "Average solves per cell (for all cells) was "
|
||||
<< double(num_singlesolves_)/double(grid_.number_of_cells) << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TofDiscGalReorder::solveSingleCell(const int cell)
|
||||
{
|
||||
// Residual:
|
||||
// For each cell K, basis function b_j (spanning V_h),
|
||||
// writing the solution u_h|K = \sum_i c_i b_i
|
||||
// Res = - \int_K \sum_i c_i b_i v(x) \cdot \grad b_j dx
|
||||
// + \int_{\partial K} F(u_h, u_h^{ext}, v(x) \cdot n) b_j ds
|
||||
// - \int_K \phi b_j
|
||||
// This is linear in c_i, so we do not need any nonlinear iterations.
|
||||
// We assemble the jacobian and the right-hand side. The residual is
|
||||
// equal to Res = Jac*c - rhs, and we compute rhs directly.
|
||||
//
|
||||
// For tracers, the equation is the same, except for the last
|
||||
// term being zero (the one with \phi).
|
||||
//
|
||||
// The rhs_ vector contains a (Fortran ordering) matrix of all
|
||||
// right-hand-sides, first for tof and then (optionally) for
|
||||
// all tracers.
|
||||
|
||||
const int num_basis = basis_func_->numBasisFunc();
|
||||
++num_singlesolves_;
|
||||
|
||||
std::fill(rhs_.begin(), rhs_.end(), 0.0);
|
||||
std::fill(jac_.begin(), jac_.end(), 0.0);
|
||||
|
||||
// Add cell contributions to res_ and jac_.
|
||||
cellContribs(cell);
|
||||
|
||||
// Add face contributions to res_ and jac_.
|
||||
faceContribs(cell);
|
||||
|
||||
// Solve linear equation.
|
||||
solveLinearSystem(cell);
|
||||
|
||||
// The solution ends up in rhs_, so we must copy it.
|
||||
std::copy(rhs_.begin(), rhs_.begin() + num_basis, tof_coeff_ + num_basis*cell);
|
||||
if (num_tracers_ && tracerhead_by_cell_[cell] == NoTracerHead) {
|
||||
std::copy(rhs_.begin() + num_basis, rhs_.end(), tracer_coeff_ + num_tracers_*num_basis*cell);
|
||||
}
|
||||
|
||||
// Apply limiter.
|
||||
if (basis_func_->degree() > 0 && use_limiter_ && limiter_usage_ == DuringComputations) {
|
||||
applyLimiter(cell, tof_coeff_);
|
||||
if (num_tracers_ && tracerhead_by_cell_[cell] == NoTracerHead) {
|
||||
for (int tr = 0; tr < num_tracers_; ++tr) {
|
||||
applyTracerLimiter(cell, tracer_coeff_ + cell*num_tracers_*num_basis + tr*num_basis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that tracer averages sum to 1.
|
||||
if (num_tracers_ && tracers_ensure_unity_ && tracerhead_by_cell_[cell] == NoTracerHead) {
|
||||
std::vector<double> tr_aver(num_tracers_);
|
||||
double tr_sum = 0.0;
|
||||
for (int tr = 0; tr < num_tracers_; ++tr) {
|
||||
const double* local_basis = tracer_coeff_ + cell*num_tracers_*num_basis + tr*num_basis;
|
||||
tr_aver[tr] = basis_func_->functionAverage(local_basis);
|
||||
tr_sum += tr_aver[tr];
|
||||
}
|
||||
if (tr_sum == 0.0) {
|
||||
std::cout << "Tracer sum is zero in cell " << cell << std::endl;
|
||||
} else {
|
||||
for (int tr = 0; tr < num_tracers_; ++tr) {
|
||||
const double increment = tr_aver[tr]/tr_sum - tr_aver[tr];
|
||||
double* local_basis = tracer_coeff_ + cell*num_tracers_*num_basis + tr*num_basis;
|
||||
basis_func_->addConstant(increment, local_basis);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TofDiscGalReorder::cellContribs(const int cell)
|
||||
{
|
||||
const int num_basis = basis_func_->numBasisFunc();
|
||||
const int dim = grid_.dimensions;
|
||||
|
||||
// Compute cell residual contribution.
|
||||
{
|
||||
const int deg_needed = basis_func_->degree();
|
||||
CellQuadrature quad(grid_, cell, deg_needed);
|
||||
for (int quad_pt = 0; quad_pt < quad.numQuadPts(); ++quad_pt) {
|
||||
// Integral of: b_i \phi
|
||||
quad.quadPtCoord(quad_pt, &coord_[0]);
|
||||
basis_func_->eval(cell, &coord_[0], &basis_[0]);
|
||||
const double w = quad.quadPtWeight(quad_pt);
|
||||
for (int j = 0; j < num_basis; ++j) {
|
||||
// Only adding to the tof rhs.
|
||||
rhs_[j] += w * basis_[j] * porevolume_[cell] / grid_.cell_volumes[cell];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute cell jacobian contribution. We use Fortran ordering
|
||||
// for jac_, i.e. rows cycling fastest.
|
||||
{
|
||||
// Even with ECVI velocity interpolation, degree of precision 1
|
||||
// is sufficient for optimal convergence order for DG1 when we
|
||||
// use linear (total degree 1) basis functions.
|
||||
// With bi(tri)-linear basis functions, it still seems sufficient
|
||||
// for convergence order 2, but the solution looks much better and
|
||||
// has significantly lower error with degree of precision 2.
|
||||
// For now, we err on the side of caution, and use 2*degree, even
|
||||
// though this is wasteful for the pure linear basis functions.
|
||||
// const int deg_needed = 2*basis_func_->degree() - 1;
|
||||
const int deg_needed = 2*basis_func_->degree();
|
||||
CellQuadrature quad(grid_, cell, deg_needed);
|
||||
for (int quad_pt = 0; quad_pt < quad.numQuadPts(); ++quad_pt) {
|
||||
// b_i (v \cdot \grad b_j)
|
||||
quad.quadPtCoord(quad_pt, &coord_[0]);
|
||||
basis_func_->eval(cell, &coord_[0], &basis_[0]);
|
||||
basis_func_->evalGrad(cell, &coord_[0], &grad_basis_[0]);
|
||||
velocity_interpolation_->interpolate(cell, &coord_[0], &velocity_[0]);
|
||||
const double w = quad.quadPtWeight(quad_pt);
|
||||
for (int j = 0; j < num_basis; ++j) {
|
||||
for (int i = 0; i < num_basis; ++i) {
|
||||
for (int dd = 0; dd < dim; ++dd) {
|
||||
jac_[j*num_basis + i] -= w * basis_[j] * grad_basis_[dim*i + dd] * velocity_[dd];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute downstream jacobian contribution from sink terms.
|
||||
// Contribution from inflow sources would be
|
||||
// similar to the contribution from upstream faces, but
|
||||
// it is zero since we let all external inflow be associated
|
||||
// with a zero tof.
|
||||
if (source_[cell] < 0.0) {
|
||||
// A sink.
|
||||
const double flux = -source_[cell]; // Sign convention for flux: outflux > 0.
|
||||
const double flux_density = flux / grid_.cell_volumes[cell];
|
||||
// Do quadrature over the cell to compute
|
||||
// \int_{K} b_i flux b_j dx
|
||||
CellQuadrature quad(grid_, cell, 2*basis_func_->degree());
|
||||
for (int quad_pt = 0; quad_pt < quad.numQuadPts(); ++quad_pt) {
|
||||
quad.quadPtCoord(quad_pt, &coord_[0]);
|
||||
basis_func_->eval(cell, &coord_[0], &basis_[0]);
|
||||
const double w = quad.quadPtWeight(quad_pt);
|
||||
for (int j = 0; j < num_basis; ++j) {
|
||||
for (int i = 0; i < num_basis; ++i) {
|
||||
jac_[j*num_basis + i] += w * basis_[i] * flux_density * basis_[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TofDiscGalReorder::faceContribs(const int cell)
|
||||
{
|
||||
const int num_basis = basis_func_->numBasisFunc();
|
||||
|
||||
// Compute upstream residual contribution from faces.
|
||||
for (int hface = grid_.cell_facepos[cell]; hface < grid_.cell_facepos[cell+1]; ++hface) {
|
||||
const int face = grid_.cell_faces[hface];
|
||||
double flux = 0.0;
|
||||
int upstream_cell = -1;
|
||||
if (cell == grid_.face_cells[2*face]) {
|
||||
flux = darcyflux_[face];
|
||||
upstream_cell = grid_.face_cells[2*face+1];
|
||||
} else {
|
||||
flux = -darcyflux_[face];
|
||||
upstream_cell = grid_.face_cells[2*face];
|
||||
}
|
||||
if (flux >= 0.0) {
|
||||
// This is an outflow boundary.
|
||||
continue;
|
||||
}
|
||||
if (upstream_cell < 0) {
|
||||
// This is an outer boundary. Assumed tof = 0 on inflow, so no contribution.
|
||||
// For tracers, a cell with inflow should be marked as a tracer head cell,
|
||||
// and not be modified.
|
||||
continue;
|
||||
}
|
||||
// Do quadrature over the face to compute
|
||||
// \int_{\partial K} u_h^{ext} (v(x) \cdot n) b_j ds
|
||||
// (where u_h^{ext} is the upstream unknown (tof)).
|
||||
// Quadrature degree set to 2*D, since u_h^{ext} varies
|
||||
// with degree D, and b_j too. We assume that the normal
|
||||
// velocity is constant (this assumption may have to go
|
||||
// for higher order than DG1).
|
||||
const double normal_velocity = flux / grid_.face_areas[face];
|
||||
const int deg_needed = 2*basis_func_->degree();
|
||||
FaceQuadrature quad(grid_, face, deg_needed);
|
||||
for (int quad_pt = 0; quad_pt < quad.numQuadPts(); ++quad_pt) {
|
||||
quad.quadPtCoord(quad_pt, &coord_[0]);
|
||||
basis_func_->eval(cell, &coord_[0], &basis_[0]);
|
||||
basis_func_->eval(upstream_cell, &coord_[0], &basis_nb_[0]);
|
||||
const double w = quad.quadPtWeight(quad_pt);
|
||||
// Modify tof rhs
|
||||
const double tof_upstream = std::inner_product(basis_nb_.begin(), basis_nb_.end(),
|
||||
tof_coeff_ + num_basis*upstream_cell, 0.0);
|
||||
for (int j = 0; j < num_basis; ++j) {
|
||||
rhs_[j] -= w * tof_upstream * normal_velocity * basis_[j];
|
||||
}
|
||||
// Modify tracer rhs
|
||||
if (num_tracers_ && tracerhead_by_cell_[cell] == NoTracerHead) {
|
||||
for (int tr = 0; tr < num_tracers_; ++tr) {
|
||||
const double* up_tr_co = tracer_coeff_ + num_tracers_*num_basis*upstream_cell + num_basis*tr;
|
||||
const double tracer_up = std::inner_product(basis_nb_.begin(), basis_nb_.end(), up_tr_co, 0.0);
|
||||
for (int j = 0; j < num_basis; ++j) {
|
||||
rhs_[num_basis*(tr + 1) + j] -= w * tracer_up * normal_velocity * basis_[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute downstream jacobian contribution from faces.
|
||||
for (int hface = grid_.cell_facepos[cell]; hface < grid_.cell_facepos[cell+1]; ++hface) {
|
||||
const int face = grid_.cell_faces[hface];
|
||||
double flux = 0.0;
|
||||
if (cell == grid_.face_cells[2*face]) {
|
||||
flux = darcyflux_[face];
|
||||
} else {
|
||||
flux = -darcyflux_[face];
|
||||
}
|
||||
if (flux <= 0.0) {
|
||||
// This is an inflow boundary.
|
||||
continue;
|
||||
}
|
||||
// Do quadrature over the face to compute
|
||||
// \int_{\partial K} b_i (v(x) \cdot n) b_j ds
|
||||
const double normal_velocity = flux / grid_.face_areas[face];
|
||||
FaceQuadrature quad(grid_, face, 2*basis_func_->degree());
|
||||
for (int quad_pt = 0; quad_pt < quad.numQuadPts(); ++quad_pt) {
|
||||
// u^ext flux B (B = {b_j})
|
||||
quad.quadPtCoord(quad_pt, &coord_[0]);
|
||||
basis_func_->eval(cell, &coord_[0], &basis_[0]);
|
||||
const double w = quad.quadPtWeight(quad_pt);
|
||||
for (int j = 0; j < num_basis; ++j) {
|
||||
for (int i = 0; i < num_basis; ++i) {
|
||||
jac_[j*num_basis + i] += w * basis_[i] * normal_velocity * basis_[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// This function assumes that jac_ and rhs_ contain the
|
||||
// linear system to be solved. They are stored in orig_jac_
|
||||
// and orig_rhs_, then the system is solved via LAPACK,
|
||||
// overwriting the input data (jac_ and rhs_).
|
||||
void TofDiscGalReorder::solveLinearSystem(const int cell)
|
||||
{
|
||||
MAT_SIZE_T n = basis_func_->numBasisFunc();
|
||||
int num_tracer_to_compute = num_tracers_;
|
||||
if (num_tracers_) {
|
||||
if (tracerhead_by_cell_[cell] != NoTracerHead) {
|
||||
num_tracer_to_compute = 0;
|
||||
}
|
||||
}
|
||||
MAT_SIZE_T nrhs = 1 + num_tracer_to_compute;
|
||||
MAT_SIZE_T lda = n;
|
||||
std::vector<MAT_SIZE_T> piv(n);
|
||||
MAT_SIZE_T ldb = n;
|
||||
MAT_SIZE_T info = 0;
|
||||
orig_jac_ = jac_;
|
||||
orig_rhs_ = rhs_;
|
||||
dgesv_(&n, &nrhs, &jac_[0], &lda, &piv[0], &rhs_[0], &ldb, &info);
|
||||
if (info != 0) {
|
||||
// Print the local matrix and rhs.
|
||||
std::cerr << "Failed solving single-cell system Ax = b in cell " << cell
|
||||
<< " with A = \n";
|
||||
for (int row = 0; row < n; ++row) {
|
||||
for (int col = 0; col < n; ++col) {
|
||||
std::cerr << " " << orig_jac_[row + n*col];
|
||||
}
|
||||
std::cerr << '\n';
|
||||
}
|
||||
std::cerr << "and b = \n";
|
||||
for (int row = 0; row < n; ++row) {
|
||||
std::cerr << " " << orig_rhs_[row] << '\n';
|
||||
}
|
||||
OPM_THROW(std::runtime_error, "Lapack error: " << info << " encountered in cell " << cell);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TofDiscGalReorder::solveMultiCell(const int num_cells, const int* cells)
|
||||
{
|
||||
++num_multicell_;
|
||||
max_size_multicell_ = std::max(max_size_multicell_, num_cells);
|
||||
// std::cout << "Multiblock solve with " << num_cells << " cells." << std::endl;
|
||||
|
||||
// Using a Gauss-Seidel approach.
|
||||
const int nb = basis_func_->numBasisFunc();
|
||||
double max_delta = 1e100;
|
||||
int num_iter = 0;
|
||||
while (max_delta > gauss_seidel_tol_) {
|
||||
max_delta = 0.0;
|
||||
++num_iter;
|
||||
for (int ci = 0; ci < num_cells; ++ci) {
|
||||
const int cell = cells[ci];
|
||||
const double tof_before = basis_func_->functionAverage(&tof_coeff_[nb*cell]);
|
||||
solveSingleCell(cell);
|
||||
const double tof_after = basis_func_->functionAverage(&tof_coeff_[nb*cell]);
|
||||
max_delta = std::max(max_delta, std::fabs(tof_after - tof_before));
|
||||
}
|
||||
// std::cout << "Max delta = " << max_delta << std::endl;
|
||||
}
|
||||
max_iter_multicell_ = std::max(max_iter_multicell_, num_iter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TofDiscGalReorder::applyLimiter(const int cell, double* tof)
|
||||
{
|
||||
switch (limiter_method_) {
|
||||
case MinUpwindFace:
|
||||
applyMinUpwindLimiter(cell, true, tof);
|
||||
break;
|
||||
case MinUpwindAverage:
|
||||
applyMinUpwindLimiter(cell, false, tof);
|
||||
break;
|
||||
default:
|
||||
OPM_THROW(std::runtime_error, "Limiter type not implemented: " << limiter_method_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TofDiscGalReorder::applyMinUpwindLimiter(const int cell, const bool face_min, double* tof)
|
||||
{
|
||||
if (basis_func_->degree() != 1) {
|
||||
OPM_THROW(std::runtime_error, "This limiter only makes sense for our DG1 implementation.");
|
||||
}
|
||||
|
||||
// Limiter principles:
|
||||
// 1. Let M be either:
|
||||
// - the minimum TOF value of all upstream faces,
|
||||
// evaluated in the upstream cells
|
||||
// (chosen if face_min is true).
|
||||
// or:
|
||||
// - the minimum average TOF value of all upstream cells
|
||||
// (chosen if face_min is false).
|
||||
// Then the value at all points in this cell shall be at
|
||||
// least M. Upstream faces whose flux does not exceed the
|
||||
// relative flux threshold are not considered for this
|
||||
// minimum.
|
||||
// 2. The TOF shall not be below zero in any point.
|
||||
|
||||
// Find minimum tof on upstream faces/cells and for this cell.
|
||||
const int num_basis = basis_func_->numBasisFunc();
|
||||
double min_upstream_tof = 1e100;
|
||||
double min_here_tof = 1e100;
|
||||
int num_upstream_faces = 0;
|
||||
const double total_flux = totalFlux(cell);
|
||||
for (int hface = grid_.cell_facepos[cell]; hface < grid_.cell_facepos[cell+1]; ++hface) {
|
||||
const int face = grid_.cell_faces[hface];
|
||||
double flux = 0.0;
|
||||
int upstream_cell = -1;
|
||||
if (cell == grid_.face_cells[2*face]) {
|
||||
flux = darcyflux_[face];
|
||||
upstream_cell = grid_.face_cells[2*face+1];
|
||||
} else {
|
||||
flux = -darcyflux_[face];
|
||||
upstream_cell = grid_.face_cells[2*face];
|
||||
}
|
||||
const bool upstream = (flux < -total_flux*limiter_relative_flux_threshold_);
|
||||
const bool interior = (upstream_cell >= 0);
|
||||
|
||||
// Find minimum tof in this cell and upstream.
|
||||
// The meaning of minimum upstream tof depends on method.
|
||||
min_here_tof = std::min(min_here_tof, minCornerVal(cell, face));
|
||||
if (upstream) {
|
||||
++num_upstream_faces;
|
||||
double upstream_tof = 0.0;
|
||||
if (interior) {
|
||||
if (face_min) {
|
||||
upstream_tof = minCornerVal(upstream_cell, face);
|
||||
} else {
|
||||
upstream_tof = basis_func_->functionAverage(tof_coeff_ + num_basis*upstream_cell);
|
||||
}
|
||||
}
|
||||
min_upstream_tof = std::min(min_upstream_tof, upstream_tof);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute slope multiplier (limiter).
|
||||
if (num_upstream_faces == 0) {
|
||||
min_upstream_tof = 0.0;
|
||||
min_here_tof = 0.0;
|
||||
}
|
||||
if (min_upstream_tof < 0.0) {
|
||||
min_upstream_tof = 0.0;
|
||||
}
|
||||
const double tof_c = basis_func_->functionAverage(tof_coeff_ + num_basis*cell);
|
||||
double limiter = (tof_c - min_upstream_tof)/(tof_c - min_here_tof);
|
||||
if (tof_c < min_upstream_tof) {
|
||||
// Handle by setting a flat solution.
|
||||
// std::cout << "Trouble in cell " << cell << std::endl;
|
||||
limiter = 0.0;
|
||||
basis_func_->addConstant(min_upstream_tof - tof_c, tof + num_basis*cell);
|
||||
}
|
||||
assert(limiter >= 0.0);
|
||||
|
||||
// Actually do the limiting (if applicable).
|
||||
if (limiter < 1.0) {
|
||||
// std::cout << "Applying limiter in cell " << cell << ", limiter = " << limiter << std::endl;
|
||||
basis_func_->multiplyGradient(limiter, tof + num_basis*cell);
|
||||
} else {
|
||||
// std::cout << "Not applying limiter in cell " << cell << "!" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void TofDiscGalReorder::applyLimiterAsPostProcess()
|
||||
{
|
||||
// Apply the limiter sequentially to all cells.
|
||||
// This means that a cell's limiting behaviour may be affected by
|
||||
// any limiting applied to its upstream cells.
|
||||
const std::vector<int>& seq = ReorderSolverInterface::sequence();
|
||||
const int nc = seq.size();
|
||||
assert(nc == grid_.number_of_cells);
|
||||
for (int i = 0; i < nc; ++i) {
|
||||
const int cell = seq[i];
|
||||
applyLimiter(cell, tof_coeff_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TofDiscGalReorder::applyLimiterAsSimultaneousPostProcess()
|
||||
{
|
||||
// Apply the limiter simultaneously to all cells.
|
||||
// This means that each cell is limited independently from all other cells,
|
||||
// we write the resulting dofs to a new array instead of writing to tof_coeff_.
|
||||
// Afterwards we copy the results back to tof_coeff_.
|
||||
const int num_basis = basis_func_->numBasisFunc();
|
||||
std::vector<double> tof_coeffs_new(tof_coeff_, tof_coeff_ + num_basis*grid_.number_of_cells);
|
||||
for (int c = 0; c < grid_.number_of_cells; ++c) {
|
||||
applyLimiter(c, &tof_coeffs_new[0]);
|
||||
}
|
||||
std::copy(tof_coeffs_new.begin(), tof_coeffs_new.end(), tof_coeff_);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
double TofDiscGalReorder::totalFlux(const int cell) const
|
||||
{
|
||||
// Find total upstream/downstream fluxes.
|
||||
double upstream_flux = 0.0;
|
||||
double downstream_flux = 0.0;
|
||||
for (int hface = grid_.cell_facepos[cell]; hface < grid_.cell_facepos[cell+1]; ++hface) {
|
||||
const int face = grid_.cell_faces[hface];
|
||||
double flux = 0.0;
|
||||
if (cell == grid_.face_cells[2*face]) {
|
||||
flux = darcyflux_[face];
|
||||
} else {
|
||||
flux = -darcyflux_[face];
|
||||
}
|
||||
if (flux < 0.0) {
|
||||
upstream_flux += flux;
|
||||
} else {
|
||||
downstream_flux += flux;
|
||||
}
|
||||
}
|
||||
// In the presence of sources, significant fluxes may be missing from the computed fluxes,
|
||||
// setting the total flux to the (positive) maximum avoids this: since source is either
|
||||
// inflow or outflow, not both, either upstream_flux or downstream_flux must be correct.
|
||||
return std::max(-upstream_flux, downstream_flux);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
double TofDiscGalReorder::minCornerVal(const int cell, const int face) const
|
||||
{
|
||||
// Evaluate the solution in all corners.
|
||||
const int dim = grid_.dimensions;
|
||||
const int num_basis = basis_func_->numBasisFunc();
|
||||
double min_cornerval = 1e100;
|
||||
for (int fnode = grid_.face_nodepos[face]; fnode < grid_.face_nodepos[face+1]; ++fnode) {
|
||||
const double* nc = grid_.node_coordinates + dim*grid_.face_nodes[fnode];
|
||||
basis_func_->eval(cell, nc, &basis_[0]);
|
||||
const double tof_corner = std::inner_product(basis_.begin(), basis_.end(),
|
||||
tof_coeff_ + num_basis*cell, 0.0);
|
||||
min_cornerval = std::min(min_cornerval, tof_corner);
|
||||
}
|
||||
return min_cornerval;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TofDiscGalReorder::applyTracerLimiter(const int cell, double* local_coeff)
|
||||
{
|
||||
// Evaluate the solution in all corners of all faces. Extract max and min.
|
||||
const int dim = grid_.dimensions;
|
||||
const int num_basis = basis_func_->numBasisFunc();
|
||||
double min_cornerval = 1e100;
|
||||
double max_cornerval = -1e100;
|
||||
for (int hface = grid_.cell_facepos[cell]; hface < grid_.cell_facepos[cell+1]; ++hface) {
|
||||
const int face = grid_.cell_faces[hface];
|
||||
for (int fnode = grid_.face_nodepos[face]; fnode < grid_.face_nodepos[face+1]; ++fnode) {
|
||||
const double* nc = grid_.node_coordinates + dim*grid_.face_nodes[fnode];
|
||||
basis_func_->eval(cell, nc, &basis_[0]);
|
||||
const double tracer_corner = std::inner_product(basis_.begin(), basis_.end(),
|
||||
local_coeff, 0.0);
|
||||
min_cornerval = std::min(min_cornerval, tracer_corner);
|
||||
max_cornerval = std::max(min_cornerval, tracer_corner);
|
||||
}
|
||||
}
|
||||
const double average = basis_func_->functionAverage(local_coeff);
|
||||
if (average < 0.0 || average > 1.0) {
|
||||
// Adjust average. Flatten gradient.
|
||||
std::fill(local_coeff, local_coeff + num_basis, 0.0);
|
||||
if (average > 1.0) {
|
||||
basis_func_->addConstant(1.0, local_coeff);
|
||||
}
|
||||
} else {
|
||||
// Possibly adjust gradient.
|
||||
double factor = 1.0;
|
||||
if (min_cornerval < 0.0) {
|
||||
factor = average/(average - min_cornerval);
|
||||
}
|
||||
if (max_cornerval > 1.0) {
|
||||
factor = std::min(factor, (1.0 - average)/(max_cornerval - average));
|
||||
}
|
||||
if (factor != 1.0) {
|
||||
basis_func_->multiplyGradient(factor, local_coeff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace Opm
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
Copyright 2012 SINTEF ICT, Applied Mathematics.
|
||||
|
||||
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_TOFDISCGALREORDER_HEADER_INCLUDED
|
||||
#define OPM_TOFDISCGALREORDER_HEADER_INCLUDED
|
||||
|
||||
#include <opm/core/transport/reorder/ReorderSolverInterface.hpp>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <ostream>
|
||||
|
||||
struct UnstructuredGrid;
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
class IncompPropertiesInterface;
|
||||
class VelocityInterpolationInterface;
|
||||
class DGBasisInterface;
|
||||
namespace parameter { class ParameterGroup; }
|
||||
template <typename T> class SparseTable;
|
||||
|
||||
/// Implements a discontinuous Galerkin solver for
|
||||
/// (single-phase) time-of-flight using reordering.
|
||||
/// The equation solved is:
|
||||
/// \f[v \cdot \nabla\tau = \phi\f]
|
||||
/// in which \f$ v \f$ is the fluid velocity, \f$ \tau \f$ is time-of-flight and
|
||||
/// \f$ \phi \f$ is the porosity. This is a boundary value problem, and
|
||||
/// \f$ \tau \f$ is specified to be zero on all inflow boundaries.
|
||||
/// The user may specify the polynomial degree of the basis function space
|
||||
/// used, but only degrees 0 and 1 are supported so far.
|
||||
class TofDiscGalReorder : public ReorderSolverInterface
|
||||
{
|
||||
public:
|
||||
/// Construct solver.
|
||||
/// \param[in] grid A 2d or 3d grid.
|
||||
/// \param[in] param Parameters for the solver.
|
||||
/// The following parameters are accepted (defaults):\n
|
||||
/// - \c dg_degree (0) -- Polynomial degree of basis functions.
|
||||
/// - \c use_tensorial_basis (false) -- Use tensor-product basis, interpreting dg_degree as
|
||||
/// bi/tri-degree not total degree.
|
||||
/// - \c use_cvi (false) -- Use ECVI velocity interpolation.
|
||||
/// - \c use_limiter (false) -- Use a slope limiter. If true, the next three parameters are used.
|
||||
/// - \c limiter_relative_flux_threshold (1e-3) -- Ignore upstream fluxes below this threshold,
|
||||
/// relative to total cell flux.
|
||||
/// - \c limiter_method ("MinUpwindFace") -- Limiter method used. Accepted methods are:
|
||||
/// - MinUpwindFace -- Limit cell tof to >= inflow face tofs.
|
||||
/// - MinUpwindAverage -- Limit cell tof to >= inflow cell average tofs.
|
||||
/// - \c limiter_usage ("DuringComputations") -- Usage pattern for limiter. Accepted choices are:
|
||||
/// - DuringComputations -- Apply limiter to cells as they are computed,
|
||||
/// so downstream cells' solutions may be affected
|
||||
/// by limiting in upstream cells.
|
||||
/// - AsPostProcess -- Apply in dependency order, but only after
|
||||
/// computing (unlimited) solution.
|
||||
/// - AsSimultaneousPostProcess -- Apply to each cell independently, using un-
|
||||
/// limited solution in neighbouring cells.
|
||||
TofDiscGalReorder(const UnstructuredGrid& grid,
|
||||
const parameter::ParameterGroup& param);
|
||||
|
||||
|
||||
/// Solve for time-of-flight.
|
||||
/// \param[in] darcyflux Array of signed face fluxes.
|
||||
/// \param[in] porevolume Array of pore volumes.
|
||||
/// \param[in] source Source term. Sign convention is:
|
||||
/// (+) inflow flux,
|
||||
/// (-) outflow flux.
|
||||
/// \param[out] tof_coeff Array of time-of-flight solution coefficients.
|
||||
/// The values are ordered by cell, meaning that
|
||||
/// the K coefficients corresponding to the first
|
||||
/// cell come before the K coefficients corresponding
|
||||
/// to the second cell etc.
|
||||
/// K depends on degree and grid dimension.
|
||||
void solveTof(const double* darcyflux,
|
||||
const double* porevolume,
|
||||
const double* source,
|
||||
std::vector<double>& tof_coeff);
|
||||
|
||||
/// Solve for time-of-flight and a number of tracers.
|
||||
/// \param[in] darcyflux Array of signed face fluxes.
|
||||
/// \param[in] porevolume Array of pore volumes.
|
||||
/// \param[in] source Source term. Sign convention is:
|
||||
/// (+) inflow flux,
|
||||
/// (-) outflow flux.
|
||||
/// \param[in] tracerheads Table containing one row per tracer, and each
|
||||
/// row contains the source cells for that tracer.
|
||||
/// \param[out] tof_coeff Array of time-of-flight solution coefficients.
|
||||
/// The values are ordered by cell, meaning that
|
||||
/// the K coefficients corresponding to the first
|
||||
/// cell comes before the K coefficients corresponding
|
||||
/// to the second cell etc.
|
||||
/// K depends on degree and grid dimension.
|
||||
/// \param[out] tracer_coeff Array of tracer solution coefficients. N*K per cell,
|
||||
/// where N is equal to tracerheads.size(). All K coefs
|
||||
/// for a tracer are consecutive, and all tracers' coefs
|
||||
/// for a cell come before those for the next cell.
|
||||
void solveTofTracer(const double* darcyflux,
|
||||
const double* porevolume,
|
||||
const double* source,
|
||||
const SparseTable<int>& tracerheads,
|
||||
std::vector<double>& tof_coeff,
|
||||
std::vector<double>& tracer_coeff);
|
||||
|
||||
private:
|
||||
virtual void solveSingleCell(const int cell);
|
||||
virtual void solveMultiCell(const int num_cells, const int* cells);
|
||||
|
||||
void cellContribs(const int cell);
|
||||
void faceContribs(const int cell);
|
||||
void solveLinearSystem(const int cell);
|
||||
|
||||
private:
|
||||
// Disable copying and assignment.
|
||||
TofDiscGalReorder(const TofDiscGalReorder&);
|
||||
TofDiscGalReorder& operator=(const TofDiscGalReorder&);
|
||||
|
||||
// Data members
|
||||
const UnstructuredGrid& grid_;
|
||||
std::shared_ptr<VelocityInterpolationInterface> velocity_interpolation_;
|
||||
bool use_cvi_;
|
||||
bool use_limiter_;
|
||||
double limiter_relative_flux_threshold_;
|
||||
enum LimiterMethod { MinUpwindFace, MinUpwindAverage };
|
||||
LimiterMethod limiter_method_;
|
||||
enum LimiterUsage { DuringComputations, AsPostProcess, AsSimultaneousPostProcess };
|
||||
LimiterUsage limiter_usage_;
|
||||
const double* darcyflux_; // one flux per grid face
|
||||
const double* porevolume_; // one volume per cell
|
||||
const double* source_; // one volumetric source term per cell
|
||||
std::shared_ptr<DGBasisInterface> basis_func_;
|
||||
double* tof_coeff_;
|
||||
// For tracers.
|
||||
double* tracer_coeff_;
|
||||
int num_tracers_;
|
||||
enum { NoTracerHead = -1 };
|
||||
std::vector<int> tracerhead_by_cell_;
|
||||
bool tracers_ensure_unity_;
|
||||
// Used by solveSingleCell().
|
||||
std::vector<double> rhs_; // single-cell right-hand-sides
|
||||
std::vector<double> jac_; // single-cell jacobian
|
||||
std::vector<double> orig_rhs_; // single-cell right-hand-sides (copy)
|
||||
std::vector<double> orig_jac_; // single-cell jacobian (copy)
|
||||
std::vector<double> coord_;
|
||||
mutable std::vector<double> basis_;
|
||||
mutable std::vector<double> basis_nb_;
|
||||
std::vector<double> grad_basis_;
|
||||
std::vector<double> velocity_;
|
||||
int num_singlesolves_;
|
||||
// Used by solveMultiCell():
|
||||
double gauss_seidel_tol_;
|
||||
int num_multicell_;
|
||||
int max_size_multicell_;
|
||||
int max_iter_multicell_;
|
||||
|
||||
// Private methods
|
||||
|
||||
// Apply some limiter, writing to array tof
|
||||
// (will read data from tof_coeff_, it is ok to call
|
||||
// with tof_coeff as tof argument.
|
||||
void applyLimiter(const int cell, double* tof);
|
||||
void applyMinUpwindLimiter(const int cell, const bool face_min, double* tof);
|
||||
void applyLimiterAsPostProcess();
|
||||
void applyLimiterAsSimultaneousPostProcess();
|
||||
double totalFlux(const int cell) const;
|
||||
double minCornerVal(const int cell, const int face) const;
|
||||
|
||||
// Apply a simple (restrict to [0,1]) limiter.
|
||||
// Intended for tracers.
|
||||
void applyTracerLimiter(const int cell, double* local_coeff);
|
||||
};
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
#endif // OPM_TRANSPORTMODELTRACERTOFDISCGAL_HEADER_INCLUDED
|
||||
@@ -0,0 +1,452 @@
|
||||
/*
|
||||
Copyright 2012 SINTEF ICT, Applied Mathematics.
|
||||
|
||||
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/core/flowdiagnostics/TofReorder.hpp>
|
||||
#include <opm/core/grid.h>
|
||||
#include <opm/core/utility/ErrorMacros.hpp>
|
||||
#include <opm/core/utility/SparseTable.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
|
||||
/// Construct solver.
|
||||
/// \param[in] grid A 2d or 3d grid.
|
||||
/// \param[in] use_multidim_upwind If true, use multidimensional tof upwinding.
|
||||
TofReorder::TofReorder(const UnstructuredGrid& grid,
|
||||
const bool use_multidim_upwind)
|
||||
: grid_(grid),
|
||||
darcyflux_(0),
|
||||
porevolume_(0),
|
||||
source_(0),
|
||||
tof_(0),
|
||||
gauss_seidel_tol_(1e-3),
|
||||
use_multidim_upwind_(use_multidim_upwind)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Solve for time-of-flight.
|
||||
/// \param[in] darcyflux Array of signed face fluxes.
|
||||
/// \param[in] porevolume Array of pore volumes.
|
||||
/// \param[in] source Source term. Sign convention is:
|
||||
/// (+) inflow flux,
|
||||
/// (-) outflow flux.
|
||||
/// \param[out] tof Array of time-of-flight values.
|
||||
void TofReorder::solveTof(const double* darcyflux,
|
||||
const double* porevolume,
|
||||
const double* source,
|
||||
std::vector<double>& tof)
|
||||
{
|
||||
darcyflux_ = darcyflux;
|
||||
porevolume_ = porevolume;
|
||||
source_ = source;
|
||||
#ifndef NDEBUG
|
||||
// Sanity check for sources.
|
||||
const double cum_src = std::accumulate(source, source + grid_.number_of_cells, 0.0);
|
||||
if (std::fabs(cum_src) > *std::max_element(source, source + grid_.number_of_cells)*1e-2) {
|
||||
// OPM_THROW(std::runtime_error, "Sources do not sum to zero: " << cum_src);
|
||||
OPM_MESSAGE("Warning: sources do not sum to zero: " << cum_src);
|
||||
}
|
||||
#endif
|
||||
tof.resize(grid_.number_of_cells);
|
||||
std::fill(tof.begin(), tof.end(), 0.0);
|
||||
tof_ = &tof[0];
|
||||
if (use_multidim_upwind_) {
|
||||
face_tof_.resize(grid_.number_of_faces);
|
||||
std::fill(face_tof_.begin(), face_tof_.end(), 0.0);
|
||||
face_part_tof_.resize(grid_.face_nodepos[grid_.number_of_faces]);
|
||||
std::fill(face_part_tof_.begin(), face_part_tof_.end(), 0.0);
|
||||
}
|
||||
compute_tracer_ = false;
|
||||
executeSolve();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Solve for time-of-flight and a number of tracers.
|
||||
/// \param[in] darcyflux Array of signed face fluxes.
|
||||
/// \param[in] porevolume Array of pore volumes.
|
||||
/// \param[in] source Source term. Sign convention is:
|
||||
/// (+) inflow flux,
|
||||
/// (-) outflow flux.
|
||||
/// \param[in] tracerheads Table containing one row per tracer, and each
|
||||
/// row contains the source cells for that tracer.
|
||||
/// \param[out] tof Array of time-of-flight values (1 per cell).
|
||||
/// \param[out] tracer Array of tracer values. N per cell, where N is
|
||||
/// equalt to tracerheads.size().
|
||||
void TofReorder::solveTofTracer(const double* darcyflux,
|
||||
const double* porevolume,
|
||||
const double* source,
|
||||
const SparseTable<int>& tracerheads,
|
||||
std::vector<double>& tof,
|
||||
std::vector<double>& tracer)
|
||||
{
|
||||
darcyflux_ = darcyflux;
|
||||
porevolume_ = porevolume;
|
||||
source_ = source;
|
||||
const int num_cells = grid_.number_of_cells;
|
||||
#ifndef NDEBUG
|
||||
// Sanity check for sources.
|
||||
const double cum_src = std::accumulate(source, source + num_cells, 0.0);
|
||||
if (std::fabs(cum_src) > *std::max_element(source, source + num_cells)*1e-2) {
|
||||
OPM_THROW(std::runtime_error, "Sources do not sum to zero: " << cum_src);
|
||||
}
|
||||
#endif
|
||||
tof.resize(num_cells);
|
||||
std::fill(tof.begin(), tof.end(), 0.0);
|
||||
tof_ = &tof[0];
|
||||
|
||||
if (use_multidim_upwind_) {
|
||||
face_tof_.resize(grid_.number_of_faces);
|
||||
std::fill(face_tof_.begin(), face_tof_.end(), 0.0);
|
||||
face_part_tof_.resize(grid_.face_nodepos[grid_.number_of_faces]);
|
||||
std::fill(face_part_tof_.begin(), face_part_tof_.end(), 0.0);
|
||||
}
|
||||
|
||||
// Execute solve for tof
|
||||
compute_tracer_ = false;
|
||||
executeSolve();
|
||||
|
||||
// Find the tracer heads (injectors).
|
||||
const int num_tracers = tracerheads.size();
|
||||
tracer.resize(num_cells*num_tracers);
|
||||
std::fill(tracer.begin(), tracer.end(), 0.0);
|
||||
if (num_tracers > 0) {
|
||||
tracerhead_by_cell_.clear();
|
||||
tracerhead_by_cell_.resize(num_cells, NoTracerHead);
|
||||
}
|
||||
for (int tr = 0; tr < num_tracers; ++tr) {
|
||||
for (int i = 0; i < tracerheads[tr].size(); ++i) {
|
||||
const int cell = tracerheads[tr][i];
|
||||
tracer[num_cells * tr + cell] = 1.0;
|
||||
tracerhead_by_cell_[cell] = tr;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute solve for tracers.
|
||||
std::vector<double> fake_pv(num_cells, 0.0);
|
||||
porevolume_ = fake_pv.data();
|
||||
for (int tr = 0; tr < num_tracers; ++tr) {
|
||||
tof_ = tracer.data() + tr * num_cells;
|
||||
compute_tracer_ = true;
|
||||
executeSolve();
|
||||
}
|
||||
|
||||
// Write output tracer data (transposing the computed data).
|
||||
std::vector<double> computed = tracer;
|
||||
for (int cell = 0; cell < num_cells; ++cell) {
|
||||
for (int tr = 0; tr < num_tracers; ++tr) {
|
||||
tracer[num_tracers * cell + tr] = computed[num_cells * tr + cell];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TofReorder::executeSolve()
|
||||
{
|
||||
num_multicell_ = 0;
|
||||
max_size_multicell_ = 0;
|
||||
max_iter_multicell_ = 0;
|
||||
reorderAndTransport(grid_, darcyflux_);
|
||||
if (num_multicell_ > 0) {
|
||||
std::cout << num_multicell_ << " multicell blocks with max size "
|
||||
<< max_size_multicell_ << " cells in upto "
|
||||
<< max_iter_multicell_ << " iterations." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TofReorder::solveSingleCell(const int cell)
|
||||
{
|
||||
if (use_multidim_upwind_) {
|
||||
solveSingleCellMultidimUpwind(cell);
|
||||
return;
|
||||
}
|
||||
// Compute flux terms.
|
||||
// Sources have zero tof, and therefore do not contribute
|
||||
// to upwind_term. Sinks on the other hand, must be added
|
||||
// to the downwind_flux (note sign change resulting from
|
||||
// different sign conventions: pos. source is injection,
|
||||
// pos. flux is outflow).
|
||||
if (compute_tracer_ && tracerhead_by_cell_[cell] != NoTracerHead) {
|
||||
// This is a tracer head cell, already has solution.
|
||||
return;
|
||||
}
|
||||
double upwind_term = 0.0;
|
||||
double downwind_flux = std::max(-source_[cell], 0.0);
|
||||
for (int i = grid_.cell_facepos[cell]; i < grid_.cell_facepos[cell+1]; ++i) {
|
||||
int f = grid_.cell_faces[i];
|
||||
double flux;
|
||||
int other;
|
||||
// Compute cell flux
|
||||
if (cell == grid_.face_cells[2*f]) {
|
||||
flux = darcyflux_[f];
|
||||
other = grid_.face_cells[2*f+1];
|
||||
} else {
|
||||
flux =-darcyflux_[f];
|
||||
other = grid_.face_cells[2*f];
|
||||
}
|
||||
// Add flux to upwind_term or downwind_flux
|
||||
if (flux < 0.0) {
|
||||
// Using tof == 0 on inflow, so we only add a
|
||||
// nonzero contribution if we are on an internal
|
||||
// face.
|
||||
if (other != -1) {
|
||||
upwind_term += flux*tof_[other];
|
||||
}
|
||||
} else {
|
||||
downwind_flux += flux;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute tof.
|
||||
tof_[cell] = (porevolume_[cell] - upwind_term)/downwind_flux;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TofReorder::solveSingleCellMultidimUpwind(const int cell)
|
||||
{
|
||||
// Compute flux terms.
|
||||
// Sources have zero tof, and therefore do not contribute
|
||||
// to upwind_term. Sinks on the other hand, must be added
|
||||
// to the downwind terms (note sign change resulting from
|
||||
// different sign conventions: pos. source is injection,
|
||||
// pos. flux is outflow).
|
||||
double upwind_term = 0.0;
|
||||
double downwind_term_cell_factor = std::max(-source_[cell], 0.0);
|
||||
double downwind_term_face = 0.0;
|
||||
for (int i = grid_.cell_facepos[cell]; i < grid_.cell_facepos[cell+1]; ++i) {
|
||||
int f = grid_.cell_faces[i];
|
||||
double flux;
|
||||
// Compute cell flux
|
||||
if (cell == grid_.face_cells[2*f]) {
|
||||
flux = darcyflux_[f];
|
||||
} else {
|
||||
flux =-darcyflux_[f];
|
||||
}
|
||||
// Add flux to upwind_term or downwind_term_[face|cell_factor].
|
||||
if (flux < 0.0) {
|
||||
upwind_term += flux*face_tof_[f];
|
||||
} else if (flux > 0.0) {
|
||||
double fterm, cterm_factor;
|
||||
multidimUpwindTerms(f, cell, fterm, cterm_factor);
|
||||
downwind_term_face += fterm*flux;
|
||||
downwind_term_cell_factor += cterm_factor*flux;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute tof for cell.
|
||||
if (compute_tracer_ && tracerhead_by_cell_[cell] != NoTracerHead) {
|
||||
// Do nothing to the value in this cell, since we are at a tracer head.
|
||||
} else {
|
||||
tof_[cell] = (porevolume_[cell] - upwind_term - downwind_term_face)/downwind_term_cell_factor;
|
||||
}
|
||||
|
||||
// Compute tof for downwind faces.
|
||||
for (int i = grid_.cell_facepos[cell]; i < grid_.cell_facepos[cell+1]; ++i) {
|
||||
int f = grid_.cell_faces[i];
|
||||
const double outflux_f = (grid_.face_cells[2*f] == cell) ? darcyflux_[f] : -darcyflux_[f];
|
||||
if (outflux_f > 0.0) {
|
||||
double fterm, cterm_factor;
|
||||
multidimUpwindTerms(f, cell, fterm, cterm_factor);
|
||||
face_tof_[f] = fterm + cterm_factor*tof_[cell];
|
||||
|
||||
// Combine locally computed (for each adjacent vertex) terms, with uniform weighting.
|
||||
const int* face_nodes_beg = grid_.face_nodes + grid_.face_nodepos[f];
|
||||
const int* face_nodes_end = grid_.face_nodes + grid_.face_nodepos[f + 1];
|
||||
assert((face_nodes_end - face_nodes_beg) == 2 || grid_.dimensions != 2);
|
||||
for (const int* fn_iter = face_nodes_beg; fn_iter < face_nodes_end; ++fn_iter) {
|
||||
double loc_face_term = 0.0;
|
||||
double loc_cell_term_factor = 0.0;
|
||||
const int node_pos = fn_iter - grid_.face_nodes;
|
||||
localMultidimUpwindTerms(f, cell, node_pos,
|
||||
loc_face_term, loc_cell_term_factor);
|
||||
face_part_tof_[node_pos] = loc_face_term + loc_cell_term_factor * tof_[cell];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TofReorder::solveMultiCell(const int num_cells, const int* cells)
|
||||
{
|
||||
++num_multicell_;
|
||||
max_size_multicell_ = std::max(max_size_multicell_, num_cells);
|
||||
// std::cout << "Multiblock solve with " << num_cells << " cells." << std::endl;
|
||||
|
||||
// Using a Gauss-Seidel approach.
|
||||
double max_delta = 1e100;
|
||||
int num_iter = 0;
|
||||
while (max_delta > gauss_seidel_tol_) {
|
||||
max_delta = 0.0;
|
||||
++num_iter;
|
||||
for (int ci = 0; ci < num_cells; ++ci) {
|
||||
const int cell = cells[ci];
|
||||
const double tof_before = tof_[cell];
|
||||
solveSingleCell(cell);
|
||||
max_delta = std::max(max_delta, std::fabs(tof_[cell] - tof_before));
|
||||
}
|
||||
// std::cout << "Max delta = " << max_delta << std::endl;
|
||||
}
|
||||
max_iter_multicell_ = std::max(max_iter_multicell_, num_iter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Assumes that face_part_tof_[node_pos] is known for all inflow
|
||||
// faces to 'upwind_cell' sharing vertices with 'face'. The index
|
||||
// 'node_pos' is the same as the one used for the grid face-node
|
||||
// connectivity.
|
||||
// Assumes that darcyflux_[face] is != 0.0.
|
||||
// This function returns factors to compute the tof for 'face':
|
||||
// tof(face) = face_term + cell_term_factor*tof(upwind_cell).
|
||||
// It is not computed here, since these factors are needed to
|
||||
// compute the tof(upwind_cell) itself.
|
||||
void TofReorder::multidimUpwindTerms(const int face,
|
||||
const int upwind_cell,
|
||||
double& face_term,
|
||||
double& cell_term_factor) const
|
||||
{
|
||||
// Implements multidim upwind inspired by
|
||||
// "Multidimensional upstream weighting for multiphase transport on general grids"
|
||||
// by Keilegavlen, Kozdon, Mallison.
|
||||
// However, that article does not give a 3d extension other than noting that using
|
||||
// multidimensional upwinding in the XY-plane and not in the Z-direction may be
|
||||
// a good idea. We have here attempted some generalization, by treating each face-part
|
||||
// (association of a face and a vertex) as possibly influencing all downwind face-parts
|
||||
// of the neighbouring cell that share the same vertex.
|
||||
// The current implementation aims to reproduce 2d results for extruded 3d grids.
|
||||
|
||||
// Combine locally computed (for each adjacent vertex) terms, with uniform weighting.
|
||||
const int* face_nodes_beg = grid_.face_nodes + grid_.face_nodepos[face];
|
||||
const int* face_nodes_end = grid_.face_nodes + grid_.face_nodepos[face + 1];
|
||||
const int num_terms = face_nodes_end - face_nodes_beg;
|
||||
assert(num_terms == 2 || grid_.dimensions != 2);
|
||||
face_term = 0.0;
|
||||
cell_term_factor = 0.0;
|
||||
for (const int* fn_iter = face_nodes_beg; fn_iter < face_nodes_end; ++fn_iter) {
|
||||
double loc_face_term = 0.0;
|
||||
double loc_cell_term_factor = 0.0;
|
||||
localMultidimUpwindTerms(face, upwind_cell, fn_iter - grid_.face_nodes,
|
||||
loc_face_term, loc_cell_term_factor);
|
||||
face_term += loc_face_term;
|
||||
cell_term_factor += loc_cell_term_factor;
|
||||
}
|
||||
face_term /= double(num_terms);
|
||||
cell_term_factor /= double(num_terms);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
namespace {
|
||||
double weightFunc(const double w)
|
||||
{
|
||||
// SPU
|
||||
// return 0.0;
|
||||
// TMU
|
||||
return w > 0.0 ? std::min(w, 1.0) : 0.0;
|
||||
// SMU
|
||||
// return w > 0.0 ? w/(1.0 + w) : 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TofReorder::localMultidimUpwindTerms(const int face,
|
||||
const int upwind_cell,
|
||||
const int node_pos,
|
||||
double& face_term,
|
||||
double& cell_term_factor) const
|
||||
{
|
||||
// Loop over all faces adjacent to the given cell and the
|
||||
// vertex in position node_pos.
|
||||
// If that part's influx is positive, we store it, and also its associated
|
||||
// node position.
|
||||
std::vector<double> influx;
|
||||
std::vector<int> node_pos_influx;
|
||||
influx.reserve(5);
|
||||
node_pos_influx.reserve(5);
|
||||
const int node = grid_.face_nodes[node_pos];
|
||||
for (int hf = grid_.cell_facepos[upwind_cell]; hf < grid_.cell_facepos[upwind_cell + 1]; ++hf) {
|
||||
const int f = grid_.cell_faces[hf];
|
||||
if (f != face) {
|
||||
// Find out if the face 'f' is adjacent to vertex 'node'.
|
||||
const int* f_nodes_beg = grid_.face_nodes + grid_.face_nodepos[f];
|
||||
const int* f_nodes_end = grid_.face_nodes + grid_.face_nodepos[f + 1];
|
||||
const int* pos = std::find(f_nodes_beg, f_nodes_end, node);
|
||||
const int node_pos2 = pos - grid_.face_nodes;
|
||||
const bool is_adj = (pos != f_nodes_end);
|
||||
if (is_adj) {
|
||||
const int num_parts = f_nodes_end - f_nodes_beg;
|
||||
const double influx_sign = (grid_.face_cells[2*f] == upwind_cell) ? -1.0 : 1.0;
|
||||
const double part_influx = influx_sign * darcyflux_[f] / double(num_parts);
|
||||
if (part_influx > 0.0) {
|
||||
influx.push_back(part_influx);
|
||||
node_pos_influx.push_back(node_pos2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now we may compute the weighting of the upwind terms.
|
||||
const int num_parts = grid_.face_nodepos[face + 1] - grid_.face_nodepos[face];
|
||||
const double outflux_sign = (grid_.face_cells[2*face] == upwind_cell) ? 1.0 : -1.0;
|
||||
const double part_outflux = outflux_sign * darcyflux_[face] / double(num_parts);
|
||||
const double sum_influx = std::accumulate(influx.begin(), influx.end(), 0.0);
|
||||
const double w_factor = weightFunc(sum_influx / part_outflux);
|
||||
const int num_influx = influx.size();
|
||||
std::vector<double> w(num_influx);
|
||||
face_term = 0.0;
|
||||
for (int ii = 0; ii < num_influx; ++ii) {
|
||||
w[ii] = (influx[ii] / sum_influx) * w_factor;
|
||||
face_term += w[ii] * face_part_tof_[node_pos_influx[ii]];
|
||||
}
|
||||
const double sum_w = std::accumulate(w.begin(), w.end(), 0.0);
|
||||
cell_term_factor = 1.0 - sum_w;
|
||||
const double tol = 1e-5;
|
||||
if (cell_term_factor < -tol && cell_term_factor > 1.0 + tol) {
|
||||
OPM_THROW(std::logic_error, "cell_term_factor outside [0,1]: " << cell_term_factor);
|
||||
}
|
||||
cell_term_factor = std::min(std::max(cell_term_factor, 0.0), 1.0);
|
||||
assert(cell_term_factor >= 0.0);
|
||||
assert(cell_term_factor <= 1.0);
|
||||
}
|
||||
|
||||
} // namespace Opm
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
Copyright 2012 SINTEF ICT, Applied Mathematics.
|
||||
|
||||
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_TOFREORDER_HEADER_INCLUDED
|
||||
#define OPM_TOFREORDER_HEADER_INCLUDED
|
||||
|
||||
#include <opm/core/transport/reorder/ReorderSolverInterface.hpp>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <ostream>
|
||||
|
||||
struct UnstructuredGrid;
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
class IncompPropertiesInterface;
|
||||
template <typename T> class SparseTable;
|
||||
|
||||
/// Implements a first-order finite volume solver for
|
||||
/// (single-phase) time-of-flight using reordering.
|
||||
/// The equation solved is:
|
||||
/// \f[v \cdot \nabla\tau = \phi\f]
|
||||
/// in which \f$ v \f$ is the fluid velocity, \f$ \tau \f$ is time-of-flight and
|
||||
/// \f$ \phi \f$ is the porosity. This is a boundary value problem, and
|
||||
/// \f$ \tau \f$ is specified to be zero on all inflow boundaries.
|
||||
class TofReorder : public ReorderSolverInterface
|
||||
{
|
||||
public:
|
||||
/// Construct solver.
|
||||
/// \param[in] grid A 2d or 3d grid.
|
||||
/// \param[in] use_multidim_upwind If true, use multidimensional tof upwinding.
|
||||
TofReorder(const UnstructuredGrid& grid,
|
||||
const bool use_multidim_upwind = false);
|
||||
|
||||
/// Solve for time-of-flight.
|
||||
/// \param[in] darcyflux Array of signed face fluxes.
|
||||
/// \param[in] porevolume Array of pore volumes.
|
||||
/// \param[in] source Source term. Sign convention is:
|
||||
/// (+) inflow flux,
|
||||
/// (-) outflow flux.
|
||||
/// \param[out] tof Array of time-of-flight values.
|
||||
void solveTof(const double* darcyflux,
|
||||
const double* porevolume,
|
||||
const double* source,
|
||||
std::vector<double>& tof);
|
||||
|
||||
/// Solve for time-of-flight and a number of tracers.
|
||||
/// \param[in] darcyflux Array of signed face fluxes.
|
||||
/// \param[in] porevolume Array of pore volumes.
|
||||
/// \param[in] source Source term. Sign convention is:
|
||||
/// (+) inflow flux,
|
||||
/// (-) outflow flux.
|
||||
/// \param[in] tracerheads Table containing one row per tracer, and each
|
||||
/// row contains the source cells for that tracer.
|
||||
/// \param[out] tof Array of time-of-flight values (1 per cell).
|
||||
/// \param[out] tracer Array of tracer values. N per cell, where N is
|
||||
/// equalt to tracerheads.size().
|
||||
void solveTofTracer(const double* darcyflux,
|
||||
const double* porevolume,
|
||||
const double* source,
|
||||
const SparseTable<int>& tracerheads,
|
||||
std::vector<double>& tof,
|
||||
std::vector<double>& tracer);
|
||||
|
||||
private:
|
||||
void executeSolve();
|
||||
virtual void solveSingleCell(const int cell);
|
||||
void solveSingleCellMultidimUpwind(const int cell);
|
||||
void assembleSingleCell(const int cell,
|
||||
std::vector<int>& local_column,
|
||||
std::vector<double>& local_coefficient,
|
||||
double& rhs);
|
||||
virtual void solveMultiCell(const int num_cells, const int* cells);
|
||||
|
||||
void multidimUpwindTerms(const int face, const int upwind_cell,
|
||||
double& face_term, double& cell_term_factor) const;
|
||||
void localMultidimUpwindTerms(const int face, const int upwind_cell, const int node_pos,
|
||||
double& face_term, double& cell_term_factor) const;
|
||||
|
||||
private:
|
||||
const UnstructuredGrid& grid_;
|
||||
const double* darcyflux_; // one flux per grid face
|
||||
const double* porevolume_; // one volume per cell
|
||||
const double* source_; // one volumetric source term per cell
|
||||
double* tof_;
|
||||
bool compute_tracer_;
|
||||
enum { NoTracerHead = -1 };
|
||||
std::vector<int> tracerhead_by_cell_;
|
||||
// For solveMultiCell():
|
||||
double gauss_seidel_tol_;
|
||||
int num_multicell_;
|
||||
int max_size_multicell_;
|
||||
int max_iter_multicell_;
|
||||
// For multidim upwinding:
|
||||
bool use_multidim_upwind_;
|
||||
std::vector<double> face_tof_; // For multidim upwind face tofs.
|
||||
std::vector<double> face_part_tof_; // For multidim upwind face tofs.
|
||||
};
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
#endif // OPM_TRANSPORTMODELTRACERTOF_HEADER_INCLUDED
|
||||
Reference in new issue
Block a user