Loading...
Searching...
No Matches
AtlasChart.cpp
1/*********************************************************************
2 * Software License Agreement (BSD License)
3 *
4 * Copyright (c) 2014, Rice University
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * * Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
17 * * Neither the name of the Rice University nor the names of its
18 * contributors may be used to endorse or promote products derived
19 * from this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 * POSSIBILITY OF SUCH DAMAGE.
33 *********************************************************************/
34
35/* Author: Caleb Voss */
36
37#include "ompl/base/spaces/constraint/AtlasChart.h"
38#include <boost/math/constants/constants.hpp>
39#include <Eigen/Dense>
40
42
44
45ompl::base::AtlasChart::Halfspace::Halfspace(const AtlasChart *owner, const AtlasChart *neighbor) : owner_(owner)
46{
47 // Project neighbor's chart center onto our chart.
48 Eigen::VectorXd u(owner_->k_);
49 owner_->psiInverse(*neighbor->getOrigin(), u);
50
51 // Compute the halfspace equation, which is the perpendicular bisector
52 // between 0 and u (plus 5% to reduce cracks, see Jaillet et al.).
53 setU(1.05 * u);
54}
55
56bool ompl::base::AtlasChart::Halfspace::contains(const Eigen::Ref<const Eigen::VectorXd> &v) const
57{
58 return v.dot(u_) <= rhs_;
59}
60
61void ompl::base::AtlasChart::Halfspace::checkNear(const Eigen::Ref<const Eigen::VectorXd> &v) const
62{
63 // Threshold is 10% of the distance from the boundary to the origin.
64 if (distanceToPoint(v) < 1.0 / 20)
65 {
66 Eigen::VectorXd x(owner_->n_);
67 owner_->psi(v, x);
68 complement_->expandToInclude(x);
69 }
70}
71
72bool ompl::base::AtlasChart::Halfspace::circleIntersect(const double r, Eigen::Ref<Eigen::VectorXd> v1,
73 Eigen::Ref<Eigen::VectorXd> v2) const
74{
75 if (owner_->getManifoldDimension() != 2)
76 throw ompl::Exception("ompl::base::AtlasChart::Halfspace::circleIntersect() "
77 "Only works on 2D manifolds.");
78
79 // Check if there will be no solutions.
80 double discr = 4 * r * r - usqnorm_;
81 if (discr < 0)
82 return false;
83 discr = std::sqrt(discr);
84
85 // Compute the 2 solutions (possibly 1 repeated solution).
86 double unorm = std::sqrt(usqnorm_);
87 v1[0] = -u_[1] * discr;
88 v1[1] = u_[0] * discr;
89 v2 = -v1;
90 v1 += u_ * unorm;
91 v2 += u_ * unorm;
92 v1 /= 2 * unorm;
93 v2 /= 2 * unorm;
94
95 return true;
96}
97
99
100void ompl::base::AtlasChart::Halfspace::intersect(const Halfspace &l1, const Halfspace &l2,
101 Eigen::Ref<Eigen::VectorXd> out)
102{
103 if (l1.owner_ != l2.owner_)
104 throw ompl::Exception("Cannot intersect linear inequalities on different charts.");
105 if (l1.owner_->getManifoldDimension() != 2)
106 throw ompl::Exception("AtlasChart::Halfspace::intersect() only works on 2D manifolds.");
107
108 // Computer the intersection point of these lines.
109 Eigen::MatrixXd A(2, 2);
110 A.row(0) = l1.u_.transpose();
111 A.row(1) = l2.u_.transpose();
112 out[0] = l1.u_.squaredNorm();
113 out[1] = l2.u_.squaredNorm();
114 out = 0.5 * A.inverse() * out;
115}
116
118
119void ompl::base::AtlasChart::Halfspace::setU(const Eigen::Ref<const Eigen::VectorXd> &u)
120{
121 u_ = u;
122
123 // Precompute the squared norm of u.
124 usqnorm_ = u_.squaredNorm();
125
126 // Precompute the right-hand side of the linear inequality.
127 rhs_ = usqnorm_ / 2;
128}
129
130double ompl::base::AtlasChart::Halfspace::distanceToPoint(const Eigen::Ref<const Eigen::VectorXd> &v) const
131{
132 // Result is a scalar factor of u_.
133 return (0.5 - v.dot(u_)) / usqnorm_;
134}
135
136void ompl::base::AtlasChart::Halfspace::expandToInclude(const Eigen::Ref<const Eigen::VectorXd> &x)
137{
138 // Compute how far v = psiInverse(x) lies past the boundary, if at all.
139 Eigen::VectorXd v(owner_->k_);
140 owner_->psiInverse(x, v);
141 const double t = -distanceToPoint(v);
142
143 // Move u_ further out by twice that much.
144 if (t > 0)
145 setU((1 + 2 * t) * u_);
146}
147
149
151
152ompl::base::AtlasChart::AtlasChart(const AtlasStateSpace *atlas, const AtlasStateSpace::StateType *state)
153 : constraint_(atlas->getConstraint().get())
154 , n_(atlas->getAmbientDimension())
155 , k_(atlas->getManifoldDimension())
156 , state_(state)
157 , bigPhi_(
158 [&]() -> const Eigen::MatrixXd
159 {
160 Eigen::MatrixXd j(n_ - k_, n_);
161 constraint_->jacobian(*state_, j);
162
163 Eigen::FullPivLU<Eigen::MatrixXd> decomp = j.fullPivLu();
164 if (!decomp.isSurjective())
165 throw ompl::Exception("Cannot compute full-rank tangent space.");
166
167 // Compute the null space and orthonormalize, which is a basis for the tangent space.
168 return decomp.kernel().householderQr().householderQ() * Eigen::MatrixXd::Identity(n_, k_);
169 }())
170 , radius_(atlas->getRho_s())
171{
172}
173
178
180{
181 for (auto h : polytope_)
182 delete h;
183
184 polytope_.clear();
185}
186
187void ompl::base::AtlasChart::phi(const Eigen::Ref<const Eigen::VectorXd> &u, Eigen::Ref<Eigen::VectorXd> out) const
188{
189 out = *state_ + bigPhi_ * u;
190}
191
192bool ompl::base::AtlasChart::psi(const Eigen::Ref<const Eigen::VectorXd> &u, Eigen::Ref<Eigen::VectorXd> out) const
193{
194 // Initial guess for Newton's method
195 Eigen::VectorXd x0(n_);
196 phi(u, x0);
197
198 // Newton-Raphson to solve Ax = b
199 unsigned int iter = 0;
200 double norm = 0;
201 Eigen::MatrixXd A(n_, n_);
202 Eigen::VectorXd b(n_);
203
204 const double tolerance = constraint_->getTolerance();
205 const double squaredTolerance = tolerance * tolerance;
206
207 // Initialize output to initial guess
208 out = x0;
209
210 // Initialize A with orthonormal basis (constant)
211 A.block(n_ - k_, 0, k_, n_) = bigPhi_.transpose();
212
213 // Initialize b with initial f(out) = b
214 constraint_->function(out, b.head(n_ - k_));
215 b.tail(k_).setZero();
216
217 while ((norm = b.squaredNorm()) > squaredTolerance && iter++ < constraint_->getMaxIterations())
218 {
219 // Recompute the Jacobian at the new guess.
220 constraint_->jacobian(out, A.block(0, 0, n_ - k_, n_));
221
222 // Move in the direction that decreases F(out) and is perpendicular to
223 // the chart.
224 out -= A.partialPivLu().solve(b);
225
226 // Recompute b with new guess.
227 constraint_->function(out, b.head(n_ - k_));
228 b.tail(k_) = bigPhi_.transpose() * (out - x0);
229 }
230
231 return norm < squaredTolerance;
232}
233
234void ompl::base::AtlasChart::psiInverse(const Eigen::Ref<const Eigen::VectorXd> &x,
235 Eigen::Ref<Eigen::VectorXd> out) const
236{
237 out = bigPhi_.transpose() * (x - *state_);
238}
239
240bool ompl::base::AtlasChart::inPolytope(const Eigen::Ref<const Eigen::VectorXd> &u, const Halfspace *const ignore1,
241 const Halfspace *const ignore2) const
242{
243 if (u.norm() > radius_)
244 return false;
245
246 for (Halfspace *h : polytope_)
247 {
248 if (h == ignore1 || h == ignore2)
249 continue;
250
251 if (!h->contains(u))
252 return false;
253 }
254
255 return true;
256}
257
258void ompl::base::AtlasChart::borderCheck(const Eigen::Ref<const Eigen::VectorXd> &v) const
259{
260 for (Halfspace *h : polytope_)
261 h->checkNear(v);
262}
263
264const ompl::base::AtlasChart *ompl::base::AtlasChart::owningNeighbor(const Eigen::Ref<const Eigen::VectorXd> &x) const
265{
266 Eigen::VectorXd projx(n_), proju(k_);
267 for (Halfspace *h : polytope_)
268 {
269 // Project onto the neighboring chart.
270 const AtlasChart *c = h->getComplement()->getOwner();
271 c->psiInverse(x, proju);
272 c->phi(proju, projx);
273
274 // Check if it's within the validity region and polytope boundary.
275 const bool withinTolerance = (projx - x).norm();
276 const bool inPolytope = c->inPolytope(proju);
277
278 if (withinTolerance && inPolytope)
279 return c;
280 }
281
282 return nullptr;
283}
284
285bool ompl::base::AtlasChart::toPolygon(std::vector<Eigen::VectorXd> &vertices) const
286{
287 if (k_ != 2)
288 throw ompl::Exception("AtlasChart::toPolygon() only works on 2D manifold/charts.");
289
290 // Compile a list of all the vertices in P and all the times the border
291 // intersects the circle.
292 Eigen::VectorXd v(2);
293 Eigen::VectorXd intersection(n_);
294 vertices.clear();
295 for (std::size_t i = 0; i < polytope_.size(); i++)
296 {
297 for (std::size_t j = i + 1; j < polytope_.size(); j++)
298 {
299 // Check if intersection of the lines is a part of the boundary and
300 // within the circle.
301 Halfspace::intersect(*polytope_[i], *polytope_[j], v);
302 phi(v, intersection);
303 if (inPolytope(v, polytope_[i], polytope_[j]))
304 vertices.push_back(intersection);
305 }
306
307 // Check if intersection with circle is part of the boundary.
308 Eigen::VectorXd v1(2), v2(2);
309 if ((polytope_[i])->circleIntersect(radius_, v1, v2))
310 {
311 if (inPolytope(v1, polytope_[i]))
312 {
313 phi(v1, intersection);
314 vertices.push_back(intersection);
315 }
316 if (inPolytope(v2, polytope_[i]))
317 {
318 phi(v2, intersection);
319 vertices.push_back(intersection);
320 }
321 }
322 }
323
324 // Include points approximating the circle, if they're inside the polytope.
325 bool is_frontier = false;
326 Eigen::VectorXd v0(2);
327 v0 << radius_, 0;
328 const double step = boost::math::constants::pi<double>() / 32.;
329 for (double a = 0.; a < 2. * boost::math::constants::pi<double>(); a += step)
330 {
331 const Eigen::VectorXd vn = Eigen::Rotation2Dd(a) * v0;
332
333 if (inPolytope(vn))
334 {
335 is_frontier = true;
336 phi(vn, intersection);
337 vertices.push_back(intersection);
338 }
339 }
340
341 // Put all the points in order.
342 std::sort(vertices.begin(), vertices.end(),
343 [&](const Eigen::Ref<const Eigen::VectorXd> &x1, const Eigen::Ref<const Eigen::VectorXd> &x2) -> bool
344 {
345 // Check the angles to see who should come first.
346 Eigen::VectorXd v1(2), v2(2);
347 psiInverse(x1, v1);
348 psiInverse(x2, v2);
349 return std::atan2(v1[1], v1[0]) < std::atan2(v2[1], v2[0]);
350 });
351
352 return is_frontier;
353}
354
356{
357 RNG rng;
358 Eigen::VectorXd ru(k_);
359 for (int k = 0; k < 1000; k++)
360 {
361 for (int i = 0; i < ru.size(); i++)
362 ru[i] = rng.gaussian01();
363 ru *= radius_ / ru.norm();
364 if (inPolytope(ru))
365 return true;
366 }
367 return false;
368}
369
371
372void ompl::base::AtlasChart::generateHalfspace(AtlasChart *c1, AtlasChart *c2)
373{
374 if (c1 == c2)
375 throw ompl::Exception("ompl::base::AtlasChart::generateHalfspace(): "
376 "Must use two different charts.");
377
378 // c1, c2 will delete l1, l2, respectively, upon destruction.
379 Halfspace *l1, *l2;
380 l1 = new Halfspace(c1, c2);
381 l2 = new Halfspace(c2, c1);
382 l1->setComplement(l2);
383 l2->setComplement(l1);
384 c1->addBoundary(l1);
385 c2->addBoundary(l2);
386}
387
389
390void ompl::base::AtlasChart::addBoundary(Halfspace *halfspace)
391{
392 polytope_.push_back(halfspace);
393}
The exception type for ompl.
Definition Exception.h:47
Random number generation. An instance of this class cannot be used by multiple threads at once (membe...
double gaussian01()
Generate a random real using a normal distribution with mean 0 and variance 1.
Tangent space and bounding polytope approximating some patch of the manifold.
Definition AtlasChart.h:53
bool inPolytope(const Eigen::Ref< const Eigen::VectorXd > &u, const Halfspace *ignore1=nullptr, const Halfspace *ignore2=nullptr) const
Check if a point u on the chart lies within its polytope boundary. Can ignore up to 2 of the halfspac...
std::vector< Halfspace * > polytope_
Set of halfspaces defining the polytope boundary.
Definition AtlasChart.h:242
bool estimateIsFrontier() const
Use sampling to make a quick estimate as to whether this chart's polytope boundary is completely defi...
void addBoundary(Halfspace *halfspace)
Introduce a new halfspace to the chart's bounding polytope. This chart assumes responsibility for del...
void clear()
Forget all acquired information such as the halfspace boundary.
unsigned int getAmbientDimension() const
Returns the dimension of the ambient space.
Definition AtlasChart.h:170
bool psi(const Eigen::Ref< const Eigen::VectorXd > &u, Eigen::Ref< Eigen::VectorXd > out) const
Exponential mapping. Project chart point u onto the manifold and store the result in out,...
unsigned int getManifoldDimension() const
Returns the dimension of the manifold.
Definition AtlasChart.h:176
void borderCheck(const Eigen::Ref< const Eigen::VectorXd > &v) const
Check if chart point v lies very close to any part of the boundary. Wherever it does,...
bool toPolygon(std::vector< Eigen::VectorXd > &vertices) const
For manifolds of dimension 2, return in order in vertices the polygon boundary of this chart,...
const Constraint * constraint_
The constraint function that defines the manifold.
Definition AtlasChart.h:239
static void generateHalfspace(AtlasChart *c1, AtlasChart *c2)
Create two complementary halfspaces dividing the space between charts c1 and c2, and add them to the ...
void psiInverse(const Eigen::Ref< const Eigen::VectorXd > &x, Eigen::Ref< Eigen::VectorXd > out) const
Logarithmic mapping. Project ambient point x onto the chart and store the result in out,...
void phi(const Eigen::Ref< const Eigen::VectorXd > &u, Eigen::Ref< Eigen::VectorXd > out) const
Rewrite a chart point u in ambient space coordinates and store the result in out, which should be all...
const AtlasChart * owningNeighbor(const Eigen::Ref< const Eigen::VectorXd > &x) const
Try to find an owner for ambient point \x from among the neighbors of this chart. Returns nullptr if ...
A state in an atlas represented as a real vector in ambient space and a chart that it belongs to.
ConstrainedStateSpace encapsulating a planner-agnostic atlas algorithm for planning on a constraint m...