dune-grid-glue  2.4.0
projection_impl.hh
Go to the documentation of this file.
1 #include <dune/common/fmatrix.hh>
2 
3 #include <cmath>
4 
5 namespace Dune {
6 namespace GridGlue {
7 
8 namespace ProjectionImplementation {
9 
20 template<typename Coordinate, typename Field>
21 inline Coordinate
22 corner(unsigned c)
23 {
24  Coordinate x(Field(0));
25  if (c == 0)
26  return x;
27  x[c-1] = Field(1);
28  return x;
29 }
30 
40 inline std::pair<unsigned, unsigned>
41 edgeToCorners(unsigned edge)
42 {
43  switch(edge) {
44  case 0: return {0, 1};
45  case 1: return {0, 2};
46  case 2: return {1, 2};
47  }
48  DUNE_THROW(Dune::Exception, "Unexpected edge number.");
49 }
50 
66 template<typename Coordinate, typename Corners>
67 inline typename Corners::value_type
68 interpolate(const Coordinate& x, const Corners& corners)
69 {
70  auto y = corners[0];
71  for (unsigned i = 0; i < corners.size() - 1; ++i)
72  y.axpy(x[i], corners[i+1] - corners[0]);
73  return y;
74 }
75 
87 template<typename Coordinate, typename Normals>
88 inline typename Normals::value_type
89 interpolate_unit_normals(const Coordinate& x, const Normals& normals)
90 {
91  auto n = interpolate(x, normals);
92  n /= n.two_norm();
93  return n;
94 }
95 
107 template<typename Coordinate, typename Field>
108 inline bool
109 inside(const Coordinate& x, const Field& epsilon)
110 {
111  const unsigned dim = Coordinate::dimension;
112  Field sum(0);
113  for (unsigned i = 0; i < dim-1; ++i) {
114  if (x[i] < -epsilon)
115  return false;
116  sum += x[i];
117  }
118  /* If any xᵢ is NaN, sum will be NaN and this comparison false! */
119  if (sum <= Field(1) + epsilon)
120  return true;
121  return false;
122 }
123 
124 } /* namespace ProjectionImplementation */
125 
126 template<typename Coordinate>
128 ::Projection(const Field overlap, const Field max_normal_product)
129  : m_overlap(overlap)
130  , m_max_normal_product(max_normal_product)
131 {
132  /* Nothing. */
133 }
134 
135 template<typename Coordinate>
136 void
138 ::epsilon(const Field epsilon)
139 {
140  m_epsilon = epsilon;
141 }
142 
143 template<typename Coordinate>
144 template<typename Corners, typename Normals>
145 void
147 ::doProjection(const std::tuple<Corners&, Corners&>& corners, const std::tuple<Normals&, Normals&>& normals)
148 {
149  /* Try to obtain Φ(xᵢ) for each corner xᵢ of the preimage triangle.
150  * This means solving a linear system of equations
151  * Φ(xᵢ) = (1-α-β) y₀ + α y₁ + β y₂ = xᵢ + δ nᵢ
152  * or α (y₁ - y₀) + β (y₂ - y₀) - δ nᵢ = xᵢ - y₀
153  * to obtain the barycentric coordinates (α, β) of Φ(xᵢ) in the image
154  * triangle and the distance δ.
155  *
156  * In the matrix m corresponding to the system, only the third column and the
157  * right-hand side depend on i. The first two columns can be assembled before
158  * and reused.
159  */
160  using namespace ProjectionImplementation;
161  using std::get;
162  typedef Dune::FieldMatrix<Field, dim, dim> Matrix;
163  Matrix m;
164 
165  const auto& origin = get<0>(corners);
166  const auto& origin_normals = get<0>(normals);
167  const auto& target = get<1>(corners);
168  const auto& target_normals = get<1>(normals);
169  auto& images = get<0>(m_images);
170  auto& success = get<0>(m_success);
171 
172  /* directionsᵢ = (yᵢ - y₀) / ||yᵢ - y₀||
173  * These are the first to columns of the system matrix; the rescaling is done
174  * to ensure all columns have a comparable norm (the last has the normal with norm 1.
175  */
176  std::array<Coordinate, dim-1> directions;
177  std::array<Field, dim-1> scales;
178  for (unsigned i = 0; i < dim-1; ++i) {
179  directions[i] = target[i+1] - target[0];
180  scales[i] = directions[i].infinity_norm();
181  directions[i] /= scales[i];
182  }
183 
184  for (unsigned i = 0; i < dim-1; ++i) {
185  for (unsigned j = 0; j < dim; ++j) {
186  m[j][i] = directions[i][j];
187  }
188  }
189 
190  m_projection_valid = true;
191  success.reset();
192 
193  /* Now project xᵢ for each i */
194  for (unsigned i = 0; i < origin.size(); ++i) {
195  for (unsigned j = 0; j < dim; ++j)
196  m[j][dim-1] = origin_normals[i][j];
197 
198  const Coordinate rhs = origin[i] - target[0];
199 
200  try {
201  /* y = (α, β, δ) */
202  auto& y = images[i];
203  m.solve(y, rhs);
204  for (unsigned j = 0; j < dim-1; ++j)
205  y[j] /= scales[j];
206  /* Solving gave us -δ as the term is "-δ nᵢ". */
207  y[dim-1] *= Field(-1);
208 
209  const bool feasible = projectionFeasible(origin[i], origin_normals[i], y, target, target_normals);
210  success.set(i, feasible);
211  }
212  catch (const Dune::FMatrixError&) {
213  success.set(i, false);
214  m_projection_valid = false;
215  }
216  }
217 }
218 
219 template<typename Coordinate>
220 template<typename Corners, typename Normals>
221 void
223 ::doInverseProjection(const std::tuple<Corners&, Corners&>& corners, const std::tuple<Normals&, Normals&>& normals)
224 {
225  /* Try to obtain Φ⁻¹(yᵢ) for each corner yᵢ of the image triangle.
226  * Instead of solving the problem directly (which would lead to
227  * non-linear equations), we make use of the forward projection Φ
228  * which projects the preimage triangle on the plane spanned by the
229  * image triangle. The inverse projection is then given by finding
230  * the barycentric coordinates of yᵢ with respect to the triangle
231  * with the corners Φ(xᵢ). This way we only have to solve linear
232  * equations.
233  */
234 
235  using namespace ProjectionImplementation;
236  using std::get;
237  typedef Dune::FieldMatrix<Field, dim-1, dim-1> Matrix;
238  typedef Dune::FieldVector<Field, dim-1> Vector;
239 
240  /* The inverse projection can only be computed if the forward projection
241  * managed to project all xᵢ on the plane spanned by the yᵢ
242  */
243  if (!m_projection_valid) {
244  get<1>(m_success).reset();
245  return;
246  }
247 
248  const auto& images = get<0>(m_images);
249  const auto& target_corners = get<1>(corners);
250  auto& preimages = get<1>(m_images);
251  auto& success = get<1>(m_success);
252 
253  std::array<Coordinate, dim> v;
254  for (unsigned i = 0; i < dim-1; ++i) {
255  v[i] = interpolate(images[i+1], target_corners);
256  v[i] -= interpolate(images[0], target_corners);
257  }
258 
259  Matrix m;
260  for (unsigned i = 0; i < dim-1; ++i) {
261  for (unsigned j = 0; j < dim-1; ++j) {
262  m[i][j] = v[i]*v[j];
263  }
264  }
265 
266  for (unsigned i = 0; i < dim; ++i) {
267  /* Convert yᵢ to barycentric coordinates with respect to Φ(xⱼ) */
268  v[dim-1] = target_corners[i];
269  v[dim-1] -= interpolate(images[0], target_corners);
270 
271  Vector rhs, z;
272  for (unsigned j = 0; j < dim-1; ++j)
273  rhs[j] = v[dim-1]*v[j];
274  m.solve(z, rhs);
275 
276  for (unsigned j = 0; j < dim-1; ++j)
277  preimages[i][j] = z[j];
278 
279  /* Calculate distance along normal direction */
280  const auto x = interpolate(z, get<0>(corners));
281  preimages[i][dim-1] = (x - target_corners[i]) * get<1>(normals)[i];
282 
283  /* Check y_i lies inside the Φ(xⱼ) */
284  const bool feasible = projectionFeasible(target_corners[i], get<1>(normals)[i], preimages[i], get<0>(corners), get<0>(normals));
285  success.set(i, feasible);
286  }
287 }
288 
289 template<typename Coordinate>
290 template<typename Corners, typename Normals>
291 void
293 ::doEdgeIntersection(const std::tuple<Corners&, Corners&>& corners, const std::tuple<Normals&, Normals&>& normals)
294 {
295  using namespace ProjectionImplementation;
296  using std::get;
297 
298  m_number_of_edge_intersections = 0;
299 
300  /* There are no edge intersections for 2d, only for 3d */
301  if (dim != 3)
302  return;
303 
304  /* There are no edge intersections
305  * - when the projection is invalid,
306  * - when the projected triangle lies fully in the target triangle,
307  * - or when the target triangle lies fully in the projected triangle.
308  */
309  if (!m_projection_valid || get<0>(m_success).all() || get<1>(m_success).all()) {
310  return;
311  }
312 
313  const auto& images = get<0>(m_images);
314  const auto& ys = get<1>(corners);
315 
316  /* Intersect line through Φ(xᵢ), Φ(xⱼ) with line through yₖ, yₗ:
317  We want α, β ∈ ℝ such that
318  Φ(xᵢ) + α (Φ(xⱼ) - Φ(xᵢ)) = yₖ + β (yₗ - yₖ)
319  or
320  α (Φ(xⱼ)-Φ(xᵢ)) + β (yₗ-yₖ) = yₖ-Φ(xᵢ)
321  To get a 2×2 system of equations, multiply with yₘ-y₀ for
322  m ∈ {1,̣̣2} which are linear indep. (and so the system is
323  equivalent to the original 3×2 system)
324  */
325  for (unsigned edgex = 0; edgex < dim; ++edgex) {
326  unsigned i, j;
327  std::tie(i, j) = edgeToCorners(edgex);
328 
329  /* Both sides of edgex lie in the target triangle means no edge intersection */
330  if (get<0>(m_success)[i] && get<0>(m_success)[j])
331  continue;
332 
333  const auto pxi = interpolate(images[i], ys);
334  const auto pxj = interpolate(images[j], ys);
335  const auto pxjpxi = pxj - pxi;
336 
337  typedef Dune::FieldMatrix<Field, dim-1, dim-1> Matrix;
338  typedef Dune::FieldVector<Field, dim-1> Vector;
339 
340  for (unsigned edgey = 0; edgey < dim; ++edgey) {
341  unsigned k, l;
342  std::tie(k, l) = edgeToCorners(edgey);
343 
344  /* Both sides of edgey lie in the projected triangle means no edge intersection */
345  if (get<1>(m_success)[k] && get<1>(m_success)[l])
346  continue;
347 
348  const auto ykyl = ys[k] - ys[l];
349  const auto ykpxi = ys[k] - pxi;
350 
351  Matrix mat;
352  Vector rhs, z;
353 
354  for (unsigned m = 0; m < dim-1; ++m) {
355  const auto ym1y0 = ys[m+1] - ys[0];
356  mat[m][0] = pxjpxi * ym1y0;
357  mat[m][1] = ykyl * ym1y0;
358  rhs[m] = ykpxi * ym1y0;
359  }
360 
361  try {
362  using std::isfinite;
363 
364  mat.solve(z, rhs);
365 
366  /* If solving the system gives a NaN, the edges are probably parallel. */
367  if (!isfinite(z[0]) || !isfinite(z[1]))
368  continue;
369 
370  /* Filter out corner (pre)images. We only want "real" edge-edge intersections here. */
371  if (z[0] < m_epsilon || z[0] > Field(1) - m_epsilon
372  || z[1] < m_epsilon || z[1] > Field(1) - m_epsilon)
373  continue;
374 
375  Coordinate local_x = corner<Coordinate, Field>(i);
376  local_x.axpy(z[0], corner<Coordinate, Field>(j) - corner<Coordinate, Field>(i));
377  Coordinate local_y = corner<Coordinate, Field>(k);
378  local_y.axpy(z[1], corner<Coordinate, Field>(l) - corner<Coordinate, Field>(k));
379 
380  /* Make sure the intersection is in the triangle. */
381  if (!inside(local_x, m_epsilon) || !inside(local_y, m_epsilon))
382  continue;
383 
384  /* Make sure the intersection respects overlap. */
385  auto xy = interpolate(local_x, get<0>(corners));
386  xy -= interpolate(local_y, get<1>(corners));
387  const auto nx = interpolate_unit_normals(local_x, get<0>(normals));
388  const auto ny = interpolate_unit_normals(local_y, get<1>(normals));
389  local_x[dim-1] = -(xy*nx);
390  local_y[dim-1] = xy*ny;
391 
392  if (local_x[dim-1] < -m_overlap-m_epsilon || local_y[dim-1] < -m_overlap-m_epsilon)
393  continue;
394 
395  /* Normals should be opposing. */
396  if (nx*ny > m_max_normal_product + m_epsilon)
397  continue;
398 
399  /* Intersection is feasible. Store it. */
400  auto& intersection = m_edge_intersections[m_number_of_edge_intersections++];
401  intersection = { {{edgex, edgey}}, {{local_x, local_y}} };
402  }
403  catch(const Dune::FMatrixError&) {
404  /* Edges might be parallel, ignore and continue with next edge */
405  }
406  }
407  }
408 }
409 
410 template<typename Coordinate>
411 template<typename Corners, typename Normals>
413 ::projectionFeasible(const Coordinate& x, const Coordinate& nx, const Coordinate& px, const Corners& corners, const Normals& normals) const
414 {
415  using namespace ProjectionImplementation;
416 
417  /* Image must be within simplex. */
418  if (!inside(px, m_epsilon))
419  return false;
420 
421  /* Distance along normal must not be smaller than -overlap. */
422  if (px[dim-1] < -m_overlap-m_epsilon)
423  return false;
424 
425  /* Distance along normal at image must not be smaller than -overlap. */
426  auto xmy = x;
427  xmy -= interpolate(px, corners);
428  const auto n = interpolate_unit_normals(px, normals);
429  const auto d = xmy * n;
430  if (d < -m_overlap-m_epsilon)
431  return false;
432 
433  /* Normals at x and Φ(x) are opposing. */
434  if (nx * n > m_max_normal_product + m_epsilon)
435  return false;
436 
437  /* Okay, projection is feasible. */
438  return true;
439 }
440 
441 template<typename Coordinate>
442 template<typename Corners, typename Normals>
444 ::project(const std::tuple<Corners&, Corners&>& corners, const std::tuple<Normals&, Normals&>& normals)
445 {
446  doProjection(corners, normals);
447  doInverseProjection(corners, normals);
448  doEdgeIntersection(corners, normals);
449 }
450 
451 } /* namespace GridGlue */
452 } /* namespace Dune */
Coordinate corner(unsigned c)
Definition: projection_impl.hh:22
Definition: gridglue.hh:33
void project(const std::tuple< Corners &, Corners & > &corners, const std::tuple< Normals &, Normals & > &normals)
Do the actual projection.
Definition: projection_impl.hh:444
Projection of a line (triangle) on another line (triangle).
Definition: projection.hh:18
void epsilon(const Field epsilon)
Set epsilon used for floating-point comparisons.
Definition: projection_impl.hh:138
std::pair< unsigned, unsigned > edgeToCorners(unsigned edge)
Definition: projection_impl.hh:41
Projection(const Field overlap=Field(0), const Field max_normal_product=Field(-0.1))
Definition: projection_impl.hh:128
Coordinate::field_type Field
Scalar type.
Definition: projection.hh:54
Corners::value_type interpolate(const Coordinate &x, const Corners &corners)
Definition: projection_impl.hh:68
Normals::value_type interpolate_unit_normals(const Coordinate &x, const Normals &normals)
Definition: projection_impl.hh:89
bool inside(const Coordinate &x, const Field &epsilon)
Definition: projection_impl.hh:109