/*
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 .
*/
#include
#include
#include
#include
namespace Opm
{
/// Construct solver.
/// \param[in] grid A 2d grid.
AnisotropicEikonal2d::AnisotropicEikonal2d(const UnstructuredGrid& grid)
: grid_(grid)
{
if (grid.dimensions != 2) {
OPM_THROW(std::logic_error, "Grid for AnisotropicEikonal2d must be 2d.");
}
cell_neighbours_ = cellNeighboursAcrossVertices(grid);
orderCounterClockwise(grid, cell_neighbours_);
}
/// 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& startcells,
std::vector& solution)
{
// 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, metric)) {
const double value = computeValue(ccell, metric, solution.data());
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 double* metric) const
{
return true;
}
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 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;
}
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::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();
}
} // namespace Opm