LehrFEM++ 1.0.0
A simple Finite Element Library for teaching
quad_o1.cc
1#include "quad_o1.h"
2
3#include <Eigen/Eigen>
4#include <cmath>
5
6#include "point.h"
7#include "segment_o1.h"
8#include "tria_o1.h"
9
10namespace lf::geometry {
11
13 const Eigen::Matrix<double, Eigen::Dynamic, 4>& coords, double tol) {
14 // World dimension
15 const Geometry::dim_t wd = coords.rows();
16 // Length tests
17 double e0lensq = (coords.col(1) - coords.col(0)).squaredNorm();
18 double e1lensq = (coords.col(2) - coords.col(1)).squaredNorm();
19 double e2lensq = (coords.col(3) - coords.col(2)).squaredNorm();
20 double e3lensq = (coords.col(0) - coords.col(3)).squaredNorm();
21 // Test lengths of edges versus circumference.
22 double circum = e0lensq + e1lensq + e2lensq + e3lensq;
23 LF_VERIFY_MSG(e0lensq > tol * circum, "Collapsed edge 0");
24 LF_VERIFY_MSG(e1lensq > tol * circum, "Collapsed edge 1");
25 LF_VERIFY_MSG(e2lensq > tol * circum, "Collapsed edge 2");
26 LF_VERIFY_MSG(e3lensq > tol * circum, "Collapsed edge 3");
27 // Area test
28 switch (wd) {
29 case 2: {
30 double ar1 =
31 ((coords(0, 1) - coords(0, 0)) * (coords(1, 2) - coords(1, 0)) -
32 (coords(1, 1) - coords(1, 0)) * (coords(0, 2) - coords(0, 0)));
33 double ar2 =
34 ((coords(0, 3) - coords(0, 0)) * (coords(1, 2) - coords(1, 0)) -
35 (coords(1, 3) - coords(1, 0)) * (coords(0, 2) - coords(0, 0)));
36 double area = std::fabs(ar1) + std::fabs(ar2);
37 LF_VERIFY_MSG(area > tol * circum, "Degenerate 2D quad");
38 return true;
39 break;
40 }
41 case 3: {
42 const Eigen::Matrix<double, 3, 4> c3d(coords.block<3, 4>(0, 0));
43 double ar1 =
44 ((c3d.col(1) - c3d.col(0)).cross(c3d.col(2) - c3d.col(0))).norm();
45 double ar2 =
46 ((c3d.col(3) - c3d.col(0)).cross(c3d.col(2) - c3d.col(0))).norm();
47 double area = ar1 + ar2;
48 LF_VERIFY_MSG(area > tol * circum, "Degenerate 3D quad");
49 return true;
50 break;
51 }
52 default: {
53 LF_ASSERT_MSG(false, "Illegal world dimension" << wd);
54 break;
55 }
56 }
57 return false;
58}
59
60QuadO1::QuadO1(Eigen::Matrix<double, Eigen::Dynamic, 4> coords)
61 : coords_(std::move(coords)) {
62 // Check validity of geometry (non-zero area)
64}
65
66/* SAM_LISTING_BEGIN_1 */
67Eigen::MatrixXd QuadO1::Global(const Eigen::MatrixXd& local) const {
68 LF_ASSERT_MSG(local.rows() == 2, "reference coords must be 2-vectors");
69 // Componentwise bilinear transformation from unit square in a
70 // vectorized fashion.
71 // Note the use of array and matrix views offered by Eigen.
72 return coords_.col(0) *
73 ((1 - local.array().row(0)) * (1 - local.array().row(1)))
74 .matrix() +
75 coords_.col(1) *
76 (local.array().row(0) * (1 - local.array().row(1))).matrix() +
77 coords_.col(2) *
78 (local.array().row(0) * local.array().row(1)).matrix() +
79 coords_.col(3) *
80 ((1 - local.array().row(0)) * local.array().row(1)).matrix();
81}
82/* SAM_LISTING_END_1 */
83
84/* SAM_LISTING_BEGIN_2 */
85Eigen::MatrixXd QuadO1::Jacobian(const Eigen::MatrixXd& local) const {
86 Eigen::MatrixXd result(DimGlobal(), local.cols() * 2);
87
88 // Note that coords\_ stores the coordinates of the vertices of
89 // the quadrilateral in its columns.
90 for (Eigen::Index i = 0; i < local.cols(); ++i) {
91 // Partial derivative of componentwise bilinear mapping
92 // w.r.t. to first reference coordinate
93 result.col(2 * i) = (coords_.col(1) - coords_.col(0)) * (1 - local(1, i)) +
94 (coords_.col(2) - coords_.col(3)) * local(1, i);
95 // Partial derivative of componentwise bilinear mapping
96 // w.r.t. to second reference coordinate
97 result.col(2 * i + 1) =
98 (coords_.col(3) - coords_.col(0)) * (1 - local(0, i)) +
99 (coords_.col(2) - coords_.col(1)) * local(0, i);
100 }
101 return result;
102}
103/* SAM_LISTING_END_2 */
104
105/* SAM_LISTING_BEGIN_3 */
107 const Eigen::MatrixXd& local) const {
108 Eigen::MatrixXd result(DimGlobal(), local.cols() * 2);
109 Eigen::MatrixXd jacobian(DimGlobal(), 2);
110
111 // Loop over all evaluatin points
112 for (Eigen::Index i = 0; i < local.cols(); ++i) {
113 // Compute Jacobian matrix in one evaluation point.
114 jacobian.col(0) = (coords_.col(1) - coords_.col(0)) * (1 - local(1, i)) +
115 (coords_.col(2) - coords_.col(3)) * local(1, i);
116 jacobian.col(1) = (coords_.col(3) - coords_.col(0)) * (1 - local(0, i)) +
117 (coords_.col(2) - coords_.col(1)) * local(0, i);
118 // Distinguish whether the cell lies in a planar mesh or a surface mesh
119 if (DimGlobal() == 2) {
120 // Standard case: 2D planar mesh
121 // Eigen has a built-in function for computing the inverse of a small
122 // matrix
123 result.block(0, 2 * i, DimGlobal(), 2) = jacobian.transpose().inverse();
124 } else {
125 result.block(0, 2 * i, DimGlobal(), 2) = (jacobian.transpose() * jacobian)
126 .colPivHouseholderQr()
127 .solve(jacobian.transpose())
128 .transpose();
129 }
130 }
131 return result;
132}
133/* SAM_LISTING_END_3 */
134
135/* SAM_LISTING_BEGIN_4 */
136Eigen::VectorXd QuadO1::IntegrationElement(const Eigen::MatrixXd& local) const {
137 Eigen::VectorXd result(local.cols());
138 Eigen::MatrixXd jacobian(DimGlobal(), 2);
139
140 // Loop over all evaluatin points
141 for (Eigen::Index i = 0; i < local.cols(); ++i) {
142 // Compute Jacobian matrix in one evaluation point.
143 jacobian.col(0) = (coords_.col(1) - coords_.col(0)) * (1 - local(1, i)) +
144 (coords_.col(2) - coords_.col(3)) * local(1, i);
145 jacobian.col(1) = (coords_.col(3) - coords_.col(0)) * (1 - local(0, i)) +
146 (coords_.col(2) - coords_.col(1)) * local(0, i);
147 // For planar cell, simply the determinant, for a cell in 3D space, the
148 // volume form
149 if (DimGlobal() == 2) {
150 result(i) = std::abs(jacobian.determinant());
151 } else {
152 result(i) = std::sqrt((jacobian.transpose() * jacobian).determinant());
153 }
154 }
155 return result;
156}
157/* SAM_LISTING_END_4 */
158
159std::unique_ptr<Geometry> QuadO1::SubGeometry(dim_t codim, dim_t i) const {
160 using std::make_unique;
161 switch (codim) {
162 case 0:
163 LF_ASSERT_MSG(i == 0, "i is out of bounds.");
164 return std::make_unique<QuadO1>(coords_);
165 case 1:
166 LF_ASSERT_MSG(i >= 0 && i < 4, "i is out of bounds.");
167 return make_unique<SegmentO1>(
168 (Eigen::Matrix<double, Eigen::Dynamic, 2>(DimGlobal(), 2)
169 << coords_.col(RefEl().SubSubEntity2SubEntity(1, i, 1, 0)),
170 coords_.col(RefEl().SubSubEntity2SubEntity(1, i, 1, 1)))
171 .finished());
172 case 2:
173 LF_ASSERT_MSG(i >= 0 && i < 4, "i is out of bounds.");
174 return make_unique<Point>(coords_.col(i));
175 default:
176 LF_VERIFY_MSG(false, "codim is out of bounds.");
177 }
178}
179
180std::vector<std::unique_ptr<Geometry>> QuadO1::ChildGeometry(
181 const RefinementPattern& ref_pat, base::dim_t codim) const {
182 // The refinement pattern must be for a quadrilateral
183 LF_VERIFY_MSG(ref_pat.RefEl() == lf::base::RefEl::kQuad(),
184 "Refinement pattern for " << ref_pat.RefEl().ToString());
185 // Allowed condimensions:
186 // 0 -> child cells, 1-> child edges, 2 -> child points
187 LF_VERIFY_MSG(codim < 3, "Illegal codim " << codim);
188
189 // Lattice meshwidth
190 const double h_lattice = 1.0 / static_cast<double>(ref_pat.LatticeConst());
191 // Obtain geometry of children as lattice polygons
192 std::vector<Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic>>
193 child_polygons(ref_pat.ChildPolygons(codim));
194 // Number of child segments
195 const int no_children = child_polygons.size();
196 // Check consistency of data
197 LF_ASSERT_MSG(
198 no_children == ref_pat.NumChildren(codim),
199 "no_children = " << no_children << " <-> " << ref_pat.NumChildren(codim));
200
201 // Variable for returning (unique pointers to) child geometries
202 std::vector<std::unique_ptr<Geometry>> child_geo_uptrs{};
203 // For each child entity create a geometry object and a unique pointer to it.
204 for (int l = 0; l < no_children; l++) {
205 // A single child entity is described by a lattice polygon with
206 // a certain number of corners
207 LF_VERIFY_MSG(
208 child_polygons[l].rows() == 2,
209 "child_polygons[" << l << "].rows() = " << child_polygons[l].rows());
210 // Obtain physical (world) coordinates of the vertices of
211 // of the child entities after normalizing lattice coordinates
212 const Eigen::MatrixXd child_geo(
213 Global(h_lattice * child_polygons[l].cast<double>()));
214 // Treat child entities of different dimension
215 switch (codim) {
216 case 0: { // Child cells
217 if (child_polygons[l].cols() == 3) {
218 // Child cell is a triangle
219 child_geo_uptrs.push_back(std::make_unique<TriaO1>(child_geo));
220 } else if (child_polygons[l].cols() == 4) {
221 // Child cell is a quadrilateral
222 child_geo_uptrs.push_back(std::make_unique<QuadO1>(child_geo));
223 } else {
224 LF_VERIFY_MSG(false, "child_polygons[" << l << "].cols() = "
225 << child_polygons[l].cols());
226 }
227 break;
228 }
229 case 1: {
230 // Child is an edge
231 LF_VERIFY_MSG(
232 child_polygons[l].cols() == 2,
233 "child_polygons[l].cols() = " << child_polygons[l].cols());
234 child_geo_uptrs.push_back(std::make_unique<SegmentO1>(child_geo));
235 break;
236 }
237 case 2: {
238 LF_VERIFY_MSG(
239 child_polygons[l].cols() == 1,
240 "child_polygons[l].cols() = " << child_polygons[l].cols());
241 child_geo_uptrs.push_back(std::make_unique<Point>(child_geo));
242 break;
243 }
244 default: {
245 LF_VERIFY_MSG(false, "Illegal co-dimension");
246 break;
247 }
248 } // end switch codim
249 } // end loop over children
250 return (child_geo_uptrs);
251} // end ChildGeometry()
252
254// Implementation class Parallelogram
256
257Parallelogram::Parallelogram(Eigen::Matrix<double, Eigen::Dynamic, 4> coords)
258 : coords_(std::move(coords)),
259 jacobian_(coords_.rows(), 2),
260 jacobian_inverse_gramian_(coords_.rows(), 2),
261 integrationElement_(0) {
262 // Check validity of geometry (non-zero area)
264 // Check parallelogram property
265 LF_ASSERT_MSG(
266 (coords_.col(3) - coords_.col(2) + coords_.col(1) - coords_.col(0))
267 .norm() < 1.0E-8 * (coords_.col(2) - coords_.col(0)).norm(),
268 "No parallelogram!");
269 init();
270}
271
272Parallelogram::Parallelogram(const Eigen::VectorXd& p0,
273 const Eigen::VectorXd& p1,
274 const Eigen::VectorXd& p2)
275 : coords_(p0.rows(), 4),
276 jacobian_(p0.rows(), 2),
277 jacobian_inverse_gramian_(p0.rows(), 2),
278 integrationElement_(0) {
279 LF_ASSERT_MSG(p0.size() == p1.size(), "Vector length mismatch p0 <-> p1");
280 LF_ASSERT_MSG(p0.size() == p2.size(), "Vector length mismatch p0 <-> p2");
281 coords_.col(0) = p0;
282 coords_.col(1) = p1;
283 coords_.col(2) = p1 + (p2 - p0);
284 coords_.col(3) = p2;
286 init();
287}
288
290 jacobian_ << coords_.col(1) - coords_.col(0), coords_.col(3) - coords_.col(0);
291 // Distinguish between different world dimensions
292 if (coords_.rows() == 2) {
293 // 2D case: Simpler formula!
294 jacobian_inverse_gramian_ = jacobian_.transpose().inverse();
295 integrationElement_ = std::abs(jacobian_.determinant());
296 } else {
297 // 3D case: complicated formula
298 jacobian_inverse_gramian_ = Eigen::MatrixXd(
299 jacobian_ * (jacobian_.transpose() * jacobian_).inverse());
301 std::sqrt((jacobian_.transpose() * jacobian_).determinant());
302 }
303} // end InitDofHandler()
304
305Eigen::MatrixXd Parallelogram::Global(const Eigen::MatrixXd& local) const {
306 return coords_.col(0) *
307 (1 - local.array().row(0) - local.array().row(1)).matrix() +
308 coords_.col(1) * local.row(0) + coords_.col(3) * local.row(1);
309}
310
311Eigen::MatrixXd Parallelogram::Jacobian(const Eigen::MatrixXd& local) const {
312 return jacobian_.replicate(1, local.cols());
313}
314
316 const Eigen::MatrixXd& local) const {
317 return jacobian_inverse_gramian_.replicate(1, local.cols());
318}
319
321 const Eigen::MatrixXd& local) const {
322 return Eigen::VectorXd::Constant(local.cols(), integrationElement_);
323}
324
325// essentially a copy of the same method for QuadO1
326std::unique_ptr<Geometry> Parallelogram::SubGeometry(dim_t codim,
327 dim_t i) const {
328 using std::make_unique;
329 switch (codim) {
330 case 0:
331 LF_ASSERT_MSG(i == 0, "i is out of bounds.");
332 return std::make_unique<Parallelogram>(coords_);
333 case 1:
334 LF_ASSERT_MSG(i >= 0 && i < 4, "i is out of bounds.");
335 return make_unique<SegmentO1>(
336 (Eigen::Matrix<double, Eigen::Dynamic, 2>(DimGlobal(), 2)
337 << coords_.col(RefEl().SubSubEntity2SubEntity(1, i, 1, 0)),
338 coords_.col(RefEl().SubSubEntity2SubEntity(1, i, 1, 1)))
339 .finished());
340 case 2:
341 LF_ASSERT_MSG(i >= 0 && i < 4, "i is out of bounds.");
342 return make_unique<Point>(coords_.col(i));
343 default:
344 LF_VERIFY_MSG(false, "codim is out of bounds.");
345 }
346} // end SubGeometry
347
348// Essentially a copy of the same code for QuadO1
349std::vector<std::unique_ptr<Geometry>> Parallelogram::ChildGeometry(
350 const RefinementPattern& ref_pat, lf::base::dim_t codim) const {
351 // The refinement pattern must be for a quadrilateral
352 LF_VERIFY_MSG(ref_pat.RefEl() == lf::base::RefEl::kQuad(),
353 "Refinement pattern for " << ref_pat.RefEl().ToString());
354 // Allowed condimensions:
355 // 0 -> child cells, 1-> child edges, 2 -> child points
356 LF_VERIFY_MSG(codim < 3, "Illegal codim " << codim);
357
358 // Lattice meshwidth
359 const double h_lattice = 1.0 / static_cast<double>(ref_pat.LatticeConst());
360 // Obtain geometry of children as lattice polygons
361 std::vector<Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic>>
362 child_polygons(ref_pat.ChildPolygons(codim));
363 // Number of child segments
364 const int no_children = child_polygons.size();
365 // Check consistency of data
366 LF_ASSERT_MSG(
367 no_children == ref_pat.NumChildren(codim),
368 "no_children = " << no_children << " <-> " << ref_pat.NumChildren(codim));
369
370 // Variable for returning (unique pointers to) child geometries
371 std::vector<std::unique_ptr<Geometry>> child_geo_uptrs{};
372 // For each child entity create a geometry object and a unique pointer to it.
373 for (int l = 0; l < no_children; l++) {
374 // A single child entity is described by a lattice polygon with
375 // a certain number of corners
376 LF_VERIFY_MSG(
377 child_polygons[l].rows() == 2,
378 "child_polygons[" << l << "].rows() = " << child_polygons[l].rows());
379 // Obtain physical (world) coordinates of the vertices of
380 // of the child entities after normalizing lattice coordinates
381 const Eigen::MatrixXd child_geo(
382 Global(h_lattice * child_polygons[l].cast<double>()));
383 // Treat child entities of different dimension
384 switch (codim) {
385 case 0: { // Child cells
386 if (child_polygons[l].cols() == 3) {
387 // Child cell is a triangle
388 child_geo_uptrs.push_back(std::make_unique<TriaO1>(child_geo));
389 } else if (child_polygons[l].cols() == 4) {
390 // Child cell is a quadrilateral
392 // Code specific for a parallleogram
393 // If the lattice polygon is a parallelogram
394 // then create another parallelogram, otherwise
395 // a general quadrilateral
396 if (isParallelogram(child_polygons[l])) {
397 // Child cell is parallelogram as well
398 child_geo_uptrs.push_back(
399 std::make_unique<Parallelogram>(child_geo));
400 } else {
401 // General quadrilateral
402 child_geo_uptrs.push_back(std::make_unique<QuadO1>(child_geo));
403 }
404 } else {
405 LF_VERIFY_MSG(false, "child_polygons[" << l << "].cols() = "
406 << child_polygons[l].cols());
407 }
408 break;
409 }
410 case 1: {
411 // Child is an edge
412 LF_VERIFY_MSG(
413 child_polygons[l].cols() == 2,
414 "child_polygons[l].cols() = " << child_polygons[l].cols());
415 child_geo_uptrs.push_back(std::make_unique<SegmentO1>(child_geo));
416 break;
417 }
418 case 2: {
419 LF_VERIFY_MSG(
420 child_polygons[l].cols() == 1,
421 "child_polygons[l].cols() = " << child_polygons[l].cols());
422 child_geo_uptrs.push_back(std::make_unique<Point>(child_geo));
423 break;
424 }
425 default: {
426 LF_VERIFY_MSG(false, "Illegal co-dimension");
427 break;
428 }
429 } // end switch codim
430 } // end loop over children
431 return (child_geo_uptrs);
432} // end ChildGeometry()
433
434} // namespace lf::geometry
static constexpr RefEl kQuad()
Returns the reference quadrilateral.
Definition: ref_el.h:166
std::string ToString() const
Return a string representation of this Reference element.
Definition: ref_el.h:455
base::RefEl::dim_t dim_t
std::unique_ptr< Geometry > SubGeometry(dim_t codim, dim_t i) const override
Construct a new Geometry() object that describes the geometry of the i-th sub-entity with codimension...
Definition: quad_o1.cc:326
double integrationElement_
constant Gramian determinant
Definition: quad_o1.h:174
Eigen::Matrix< double, Eigen::Dynamic, 2 > jacobian_inverse_gramian_
Constant matrix ( )
Definition: quad_o1.h:172
Eigen::MatrixXd Global(const Eigen::MatrixXd &local) const override
Map a number of points in local coordinates into the global coordinate system.
Definition: quad_o1.cc:305
Eigen::MatrixXd JacobianInverseGramian(const Eigen::MatrixXd &local) const override
Evaluate the Jacobian * Inverse Gramian ( ) simultaneously at numPoints.
Definition: quad_o1.cc:315
Eigen::Matrix< double, Eigen::Dynamic, 2 > jacobian_
Matrix of affine mapping generating the parallelogram, agrees with constant Jacobian.
Definition: quad_o1.h:170
dim_t DimGlobal() const override
Dimension of the image of this mapping.
Definition: quad_o1.h:123
Eigen::VectorXd IntegrationElement(const Eigen::MatrixXd &local) const override
The integration element (factor appearing in integral transformation formula, see below) at number of...
Definition: quad_o1.cc:320
Eigen::Matrix< double, Eigen::Dynamic, 4 > coords_
Coordinates of the a four vertices, stored in matrix columns.
Definition: quad_o1.h:167
Parallelogram(Eigen::Matrix< double, Eigen::Dynamic, 4 > coords)
Constructor building a parallelogram from four vertex coordinates.
Definition: quad_o1.cc:257
Eigen::MatrixXd Jacobian(const Eigen::MatrixXd &local) const override
Evaluate the jacobian of the mapping simultaneously at numPoints points.
Definition: quad_o1.cc:311
base::RefEl RefEl() const override
The Reference element that defines the domain of this mapping.
Definition: quad_o1.h:124
std::vector< std::unique_ptr< Geometry > > ChildGeometry(const RefinementPattern &ref_pat, lf::base::dim_t codim) const override
Generate geometry objects for child entities created in the course of refinement.
Definition: quad_o1.cc:349
void init()
performs initialization of data members
Definition: quad_o1.cc:289
Eigen::MatrixXd Global(const Eigen::MatrixXd &local) const override
Map a number of points in local coordinates into the global coordinate system.
Definition: quad_o1.cc:67
std::unique_ptr< Geometry > SubGeometry(dim_t codim, dim_t i) const override
Construct a new Geometry() object that describes the geometry of the i-th sub-entity with codimension...
Definition: quad_o1.cc:159
Eigen::VectorXd IntegrationElement(const Eigen::MatrixXd &local) const override
The integration element (factor appearing in integral transformation formula, see below) at number of...
Definition: quad_o1.cc:136
QuadO1(Eigen::Matrix< double, Eigen::Dynamic, 4 > coords)
Constructor building quadrilateral from vertex coordinates.
Definition: quad_o1.cc:60
Eigen::MatrixXd JacobianInverseGramian(const Eigen::MatrixXd &local) const override
Evaluate the Jacobian * Inverse Gramian ( ) simultaneously at numPoints.
Definition: quad_o1.cc:106
Eigen::MatrixXd Jacobian(const Eigen::MatrixXd &local) const override
Evaluate the jacobian of the mapping simultaneously at numPoints points.
Definition: quad_o1.cc:85
dim_t DimGlobal() const override
Dimension of the image of this mapping.
Definition: quad_o1.h:41
std::vector< std::unique_ptr< Geometry > > ChildGeometry(const RefinementPattern &ref_pat, lf::base::dim_t codim) const override
Generate geometry objects for child entities created in the course of refinement.
Definition: quad_o1.cc:180
base::RefEl RefEl() const override
The Reference element that defines the domain of this mapping.
Definition: quad_o1.h:42
Eigen::Matrix< double, Eigen::Dynamic, 4 > coords_
Coordinates of the a four vertices, stored in matrix columns.
Definition: quad_o1.h:89
Abstract interface class for encoding topological local refinement
lf::base::size_type LatticeConst() const
Provides information about lattice constant used.
virtual lf::base::size_type NumChildren(lf::base::dim_t codim) const =0
provide number of child entities of a given co-dimension to be created by refinement
virtual std::vector< Eigen::Matrix< int, Eigen::Dynamic, Eigen::Dynamic > > ChildPolygons(lf::base::dim_t codim) const =0
provide lattice reference coordinates of vertices of child polygons
lf::base::RefEl RefEl() const
Returns topological type of entity for which the current object is set up.
unsigned int dim_t
type for dimensions and co-dimensions and numbers derived from them
Definition: base.h:36
Defines the Geometry interface and provides a number of classes that implement this interface + addit...
Definition: compose.h:13
bool assertNonDegenerateQuad(const Eigen::Matrix< double, Eigen::Dynamic, 4 > &coords, double tol)
Asserting a non-degenerate bilinear quadrilateral.
Definition: quad_o1.cc:12
bool isParallelogram(const Eigen::Matrix< int, Eigen::Dynamic, Eigen::Dynamic > &polygon)
Test whether a lattice polygon describes a logical parallelogram.