00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031 #include <laser_slam/scan_match_constraints.h>
00032 #include <pose_graph/graph_search.h>
00033 #include <boost/graph/filtered_graph.hpp>
00034 #include <boost/graph/connected_components.hpp>
00035 #include <graph_mapping_utils/utils.h>
00036 #include <visualization_msgs/Marker.h>
00037 #include <pose_graph/exception.h>
00038 #include <geometry_msgs/PoseStamped.h>
00039
00040 namespace laser_slam
00041 {
00042
00043 namespace pg=pose_graph;
00044 namespace util=graph_mapping_utils;
00045 namespace msg=graph_mapping_msgs;
00046 namespace ksm=karto_scan_matcher;
00047 namespace gm=geometry_msgs;
00048 namespace vm=visualization_msgs;
00049 namespace gs=graph_slam;
00050 namespace wh=warehouse;
00051
00052 using std::string;
00053 using util::toString;
00054
00055 typedef boost::mutex::scoped_lock Lock;
00056 typedef std::map<unsigned, unsigned> NodeMap;
00057 typedef boost::optional<unsigned> MaybeNode;
00058
00059 const unsigned LOCALIZATION_BUFFER_SIZE=50;
00060 const double LOOP_MATCH_FINE_MULTIPLIER=0.2;
00061 const double POLL_INC=0.1;
00062 const double POLL_MAX=1;
00063 const double MAX_EXTRAPOLATION=5.0;
00064 const double EXPECTED_NODE_DISTANCE=0.3;
00065 const double DISTANCE_UPDATE_INC=0.5;
00066 const double MAX_SPEED=1.0;
00067
00068 ScanMatchConstraints::ScanMatchConstraints (ros::NodeHandle p_nh, TfPtr tf) :
00069 fixed_frame_(util::getParam<string>(p_nh, "fixed_frame")),
00070 base_frame_(util::getParam<string>(p_nh, "base_frame")),
00071 laser_frame_(util::getParam<string>(p_nh, "laser_frame")),
00072 opt_frame_(util::searchParam<string>(p_nh, "optimization_frame")),
00073 local_window_size_(util::getParam<double>(p_nh, "local_window_size", 10.0)),
00074 running_scan_match_size_(util::getParam<double>(p_nh, "running_scan_match_size", 0.3)),
00075 running_scan_match_res_(util::getParam<double>(p_nh, "running_scan_match_resolution", 0.01)),
00076 near_link_min_chain_size_(util::getParam<int>(p_nh, "near_link_min_chain_size", 8)),
00077 chain_distance_threshold_(util::getParam<double>(p_nh, "chain_distance_threshold", 2.0)),
00078 loop_scan_match_size_(util::getParam<double>(p_nh, "loop_scan_match_size", 5.0)),
00079 loop_scan_match_res_(util::getParam<double>(p_nh, "loop_scan_match_resolution", 0.05)),
00080 loop_match_window_size_(util::getParam<double>(p_nh, "loop_closure_window_size", 5.0)),
00081 loop_match_min_chain_size_(util::getParam<int>(p_nh, "loop_closure_min_chain_size", 10)),
00082 min_loop_response_(util::getParam<double>(p_nh, "min_loop_closure_response", 0.7)),
00083 max_loop_match_variance_(util::getParam<double>(p_nh, "max_loop_match_variance", 0.16)),
00084 min_sequential_response_(util::getParam<double>(p_nh, "min_sequential_response", 0.7)),
00085 max_nbd_size_(util::getParam<double>(p_nh, "scan_match_nbd_size", 20)),
00086 running_buffer_size_(util::getParam<int>(p_nh, "running_buffer_size", 50)),
00087 initialized_(false), distance_since_last_added_node_(0), running_nodes_(running_buffer_size_),
00088 tf_(tf), db_("graph_mapping"), before_(&db_, PREV_NODE_TOPIC), after_(&db_, NEXT_NODE_TOPIC),
00089 scans_(&db_, "scans"), ff_poses_(&db_, "fixed_frame_poses"),
00090 loc_buf_(LOCALIZATION_BUFFER_SIZE, fixed_frame_, base_frame_, tf, MAX_EXTRAPOLATION),
00091 diff_sub_(boost::bind(&ScanMatchConstraints::addConstraints, this, _1, _2)),
00092 loc_sub_(nh_.subscribe("graph_localization", 5, &ScanMatchConstraints::locCallback, this)),
00093 constraint_pub_(nh_.advertise<msg::GraphConstraint>("graph_constraints", 10)),
00094 marker_pub_(nh_.advertise<vm::Marker>("visualization_marker", 1)),
00095 pose_pub_(nh_.advertise<gm::PoseStamped>("opt_pose_estimate", 1)),
00096 get_poses_client_(nh_.serviceClient<msg::GetPoses>("get_node_poses")),
00097 distance_update_timer_(nh_.createTimer(ros::Duration(DISTANCE_UPDATE_INC),
00098 &ScanMatchConstraints::updateDistanceTravelled, this))
00099 {
00100 Lock l(mutex_);
00101
00102
00103 while (ros::ok()) {
00104 ros::Time t = ros::Time::now();
00105 if (tf_->waitForTransform(base_frame_, laser_frame_, t, ros::Duration(5.0))) {
00106 gm::PoseStamped id, laser_pose;
00107 id.pose.orientation.w = 1.0;
00108 id.header.stamp = t;
00109 id.header.frame_id = laser_frame_;
00110 tf_->transformPose(base_frame_, id, laser_pose);
00111 laser_offset_ = util::toPose(laser_pose.pose);
00112 break;
00113 }
00114 ROS_INFO_STREAM ("Waiting for transform between " << base_frame_ << " and " << laser_frame_);
00115 }
00116
00117 initialized_ = ros::ok();
00118 ROS_DEBUG_NAMED ("init", "Initialized scan match constraint generator");
00119 }
00120
00121 void ScanMatchConstraints::locCallback (msg::LocalizationDistribution::ConstPtr m)
00122 {
00123 Lock lock(mutex_);
00124 ROS_ASSERT (m->samples.size()==1);
00125 const gm::PoseStamped& l = m->samples[0];
00126 const unsigned ref = util::refNode(l);
00127 if (!(running_nodes_.size() > 0 && *(--running_nodes_.end()) == ref))
00128 running_nodes_.push_back(ref);
00129 const btVector3 p = fixedFramePose(ref).getOrigin();
00130 const double max_dist = running_buffer_size_ * EXPECTED_NODE_DISTANCE;
00131 while (running_nodes_.size() > 3 && p.distance(fixedFramePose(running_nodes_[0]).getOrigin()) > max_dist)
00132 running_nodes_.pop_front();
00133
00134 }
00135
00136
00137
00138 void ScanMatchConstraints::addConstraints
00139 (OptionalDiff diff, const pose_graph::ConstraintGraph& orig)
00140 {
00141
00142 pg::ConstraintGraph g = orig;
00143 ROS_ASSERT(0 < LOOP_MATCH_FINE_MULTIPLIER && LOOP_MATCH_FINE_MULTIPLIER < 1);
00144 if (diff && diff->new_nodes.size() == 1 && g.allNodes().size() > 1) {
00145
00146
00147 const unsigned n(diff->new_nodes[0].id);
00148
00149 ros::Time t;
00150 while (ros::ok()) {
00151 try
00152 {
00153 t = scans_.get(n)->scan.header.stamp;
00154 break;
00155 }
00156 catch (pg::DataNotFoundException& e)
00157 {
00158 ros::Duration(POLL_INC).sleep();
00159 }
00160 }
00161
00162
00163 unsigned j=0;
00164 gm::PoseStamped graph_loc;
00165 while (ros::ok()) {
00166 if (loc_buf_.hasLocalization(t)) {
00167 graph_loc = loc_buf_.localizationAt(t);
00168 break;
00169 }
00170 if (++j>5) {
00171 ROS_WARN_STREAM_NAMED ("scan_match_constraints", "Not generating constraints to " << n <<
00172 " because localization for time " << t << " unavailable.");
00173 return;
00174 }
00175 ros::Duration(POLL_INC).sleep();
00176 }
00177
00178 ROS_DEBUG_STREAM_NAMED ("scan_match_constraints", "Generating scan match constraints given new node "
00179 << n << " and ref node " << util::refNode(graph_loc));
00180 Lock l(mutex_);
00181
00182
00183 if (!sequential_matcher_) {
00184 sequential_matcher_.reset(new ksm::KartoScanMatcher(scans_.get(n)->scan, util::projectToPose2D(laser_offset_),
00185 running_scan_match_size_,
00186 running_scan_match_res_));
00187 ksm::DoubleVector resolutions, sizes;
00188 sizes.push_back(loop_scan_match_size_);
00189 resolutions.push_back(loop_scan_match_res_);
00190
00191
00192 sizes.push_back(loop_scan_match_size_*LOOP_MATCH_FINE_MULTIPLIER);
00193 resolutions.push_back(loop_scan_match_res_*LOOP_MATCH_FINE_MULTIPLIER);
00194 loop_matcher_.reset(new ksm::KartoScanMatcher(scans_.get(n)->scan, util::projectToPose2D(laser_offset_),
00195 sizes, resolutions));
00196
00197 ROS_DEBUG_NAMED ("scan_match_constraints", "Matchers initialized");
00198 }
00199 ROS_ASSERT(loop_matcher_);
00200
00201 {
00202 const unsigned ref = util::refNode(graph_loc);
00203 pg::NodeSet comp = pg::componentContaining(g, ref);
00204
00205 optimizeGraph(&g, &optimized_poses_, comp, get_poses_client_);
00206 ROS_ASSERT (!util::contains(optimized_poses_, n));
00207 }
00208
00209 pg::NodeSet running_nodes = getRunningNodes(g, n, graph_loc);
00210 ROS_DEBUG_STREAM_NAMED ("scan_match_constraints", "Running nodes are "
00211 << util::toString(running_nodes));
00212 ConstraintVec constraints = getRunningConstraints(n, g, graph_loc,
00213 running_nodes);
00214
00215
00216
00217
00218 const ConstraintVec loop_constraints = getLoopConstraints(n, g, graph_loc, running_nodes);
00219 constraints.insert(constraints.end(), loop_constraints.begin(), loop_constraints.end());
00220
00221
00222 BOOST_FOREACH (const msg::GraphConstraint& c, constraints)
00223 constraint_pub_.publish(c);
00224
00225 if (last_added_node_ && distance_since_last_added_node_ < chain_distance_threshold_)
00226 addSequenceLink(*last_added_node_, n);
00227
00228 last_added_node_ = n;
00229 distance_since_last_added_node_ = 0.0;
00230 ROS_DEBUG_NAMED ("scan_match_constraints", "Added %zu constraints",
00231 constraints.size());
00232 }
00233 }
00234
00235 pg::NodeSet ScanMatchConstraints::getRunningNodes (const pg::ConstraintGraph& g, const unsigned new_node,
00236 const gm::PoseStamped& graph_loc) const
00237 {
00238
00239 LocalizedScan::ConstPtr scan = scans_.get(new_node);
00240 const unsigned ref = util::refNode(graph_loc);
00241 const tf::Pose opt_pose_estimate = g.getOptimizedPose(ref)*util::toPose(graph_loc.pose);
00242
00243 const gm::Point barycenter = util::transformPoint(opt_pose_estimate, scan->barycenter);
00244 BarycenterDistancePredicate pred(g, scans_, barycenter, local_window_size_);
00245 const pg::NodeSet nearby = pg::filterNearbyNodes(g, ref, pred);
00246
00247 Chain chain = getChain(ref, nearby, pg::NodeSet());
00248 pg::NodeSet running(chain.begin(), chain.end());
00249 return running;
00250 }
00251
00252 tf::Pose ScanMatchConstraints::fixedFramePose (const unsigned n) const
00253 {
00254 return util::toPose(*ff_poses_.get(n));
00255 }
00256
00257 void ScanMatchConstraints::updateDistanceTravelled (const ros::TimerEvent& e)
00258 {
00259 Lock l(mutex_);
00260 const ros::Time t = ros::Time::now();
00261 const ros::Duration inc(DISTANCE_UPDATE_INC);
00262 const ros::Time prev = t - inc;
00263 util::MaybeTransform tr = util::getTransform(*tf_, base_frame_, prev,
00264 base_frame_, t, fixed_frame_,
00265 inc);
00266 if (tr)
00267 distance_since_last_added_node_ += tr->getOrigin().length();
00268 else
00269 {
00270 ROS_WARN_STREAM ("Couldn't get transform between times " << t << " and " << prev <<
00271 " in distance update.");
00272 distance_since_last_added_node_ += DISTANCE_UPDATE_INC * MAX_SPEED;
00273 }
00274 }
00275
00276 ConstraintVec ScanMatchConstraints::getNearLinkConstraints (const unsigned new_node, const pg::ConstraintGraph& g,
00277 const gm::PoseStamped& graph_loc,
00278 const pg::NodeSet& running_nodes) const
00279 {
00280 ConstraintVec constraints;
00281 LocalizedScan::ConstPtr scan = scans_.get(new_node);
00282 const unsigned ref = util::refNode(graph_loc);
00283 const tf::Pose opt_pose_estimate = g.getOptimizedPose(ref)*util::toPose(graph_loc.pose);
00284
00285 const gm::Point barycenter = util::transformPoint(opt_pose_estimate, scan->barycenter);
00286 BarycenterDistancePredicate pred(g, scans_, barycenter, local_window_size_);
00287 const pg::NodeSet nearby = pg::filterNearbyNodes(g, ref, pred);
00288
00289 pg::NodeSet processed(running_nodes.begin(), running_nodes.end());
00290
00291 BOOST_FOREACH (const unsigned n, nearby) {
00292 const Chain chain = getChain(n, nearby, processed);
00293 if (chain.size() >= near_link_min_chain_size_) {
00294 const pg::NodeSet nodes(chain.begin(), chain.end());
00295 const pg::NodeSet node_subset = util::sampleSubset(nodes, max_nbd_size_);
00296 const ksm::ScanMatchResult res =
00297 scanMatchNodes(g, sequential_matcher_, node_subset, scans_,
00298 scan->scan, opt_pose_estimate, laser_offset_);
00299 if (res.response > min_sequential_response_) {
00300 const msg::GraphConstraint constraint = makeConstraint(g, new_node, res, nodes, scan);
00301 constraints.push_back(constraint);
00302 ROS_DEBUG_STREAM_NAMED ("scan_match_constraints", "Adding near chain constraint to " << constraint.src);
00303 }
00304 processed.insert(chain.begin(), chain.end());
00305 }
00306 }
00307 return constraints;
00308 }
00309
00310 ConstraintVec ScanMatchConstraints::getLoopConstraints (const unsigned n, const pg::ConstraintGraph& g,
00311 const gm::PoseStamped& graph_loc,
00312 const pg::NodeSet& running) const
00313 {
00314 ConstraintVec constraints;
00315 LocalizedScan::ConstPtr scan = scans_.get(n);
00316 const unsigned ref = util::refNode(graph_loc);
00317 const tf::Pose opt_pose_estimate = g.getOptimizedPose(ref)*util::toPose(graph_loc.pose);
00318
00319 const gm::Point barycenter = util::transformPoint(opt_pose_estimate, scan->barycenter);
00320 BarycenterDistancePredicate pred(g, scans_, barycenter, loop_match_window_size_);
00321 typedef boost::filtered_graph<pg::Graph, pg::AllEdges, BarycenterDistancePredicate> FilteredGraph;
00322 const FilteredGraph filtered(g.graph(), pg::AllEdges(), pred);
00323
00324
00325
00326
00327 typedef std::map<pg::GraphVertex, boost::default_color_type> ColorMap;
00328 ColorMap cmap;
00329 boost::associative_property_map<ColorMap> color_pmap(cmap);
00330
00331
00332 typedef std::map<pg::GraphVertex, unsigned> ComponentMap;
00333 ComponentMap comp_map;
00334 boost::associative_property_map<ComponentMap> comp_pmap(comp_map);
00335
00336
00337 const unsigned num_comps = connected_components(filtered, comp_pmap, color_map(color_pmap));
00338 std::vector<pg::NodeSet> comps(num_comps);
00339 BOOST_FOREACH (const ComponentMap::value_type& e, comp_map) {
00340 comps[e.second].insert(filtered[e.first].id);
00341 }
00342
00343
00344
00345 std::set<unsigned> not_loop_matchable;
00346 {
00347 ComponentMap::const_iterator pos = comp_map.find(g.idVertex(n));
00348 if (pos!=comp_map.end())
00349 not_loop_matchable.insert(pos->second);
00350 BOOST_FOREACH (const unsigned running_node, running) {
00351 pos = comp_map.find(g.idVertex(running_node));
00352 if (pos!=comp_map.end())
00353 not_loop_matchable.insert(pos->second);
00354 }
00355 }
00356
00357 ROS_DEBUG_STREAM_COND_NAMED
00358 (num_comps>1, "loop_closure", "There were " << num_comps <<
00359 " components, with nonmatchable components " <<
00360 util::toString(not_loop_matchable));
00361
00362
00363
00364 for (unsigned comp=0; comp<num_comps; comp++)
00365 {
00366 if (util::contains(not_loop_matchable, comp))
00367 continue;
00368 if (comps[comp].size() < (unsigned)loop_match_min_chain_size_)
00369 {
00370 ROS_DEBUG_STREAM_NAMED ("loop_closure", "Skipping component of size "
00371 << util::toString(comps[comp]));
00372 continue;
00373 }
00374 const pg::NodeSet node_subset =
00375 util::sampleSubset(comps[comp], max_nbd_size_);
00376 ksm::ScanMatchResult res =
00377 scanMatchNodes(g, loop_matcher_, node_subset, scans_, scan->scan,
00378 opt_pose_estimate, laser_offset_);
00379 ROS_DEBUG_STREAM_NAMED ("loop_closure", "Potential loop match " << comp
00380 << " of size " << loop_match_min_chain_size_ <<
00381 " has response " << res.response <<
00382 " and covariances " << res.cov(0,0) << ", " <<
00383 res.cov(1,1));
00384 if (res.response > min_loop_response_ &&
00385 res.cov(0,0) < max_loop_match_variance_ &&
00386 res.cov(1,1) < max_loop_match_variance_) {
00387 ROS_DEBUG_STREAM_NAMED ("loop_closure",
00388 "Adding loop constraint to component " << comp);
00389 constraints.push_back(makeConstraint(g, n, res, comps[comp], scan));
00390 }
00391 }
00392 return constraints;
00393 }
00394
00395 ConstraintVec ScanMatchConstraints::getRunningConstraints
00396 (const unsigned n, const pg::ConstraintGraph& g,
00397 const gm::PoseStamped& graph_loc, const pg::NodeSet& running) const
00398 {
00399 ConstraintVec constraints;
00400 const unsigned ref = util::refNode(graph_loc);
00401 LocalizedScan::ConstPtr scan = scans_.get(n);
00402
00403
00404 const tf::Pose opt_pose_estimate =
00405 g.getOptimizedPose(ref)*util::toPose(graph_loc.pose);
00406
00407
00408 const pg::NodeSet nodes = util::sampleSubset(running, max_nbd_size_);
00409 ksm::ScanMatchResult res =
00410 scanMatchNodes(g, sequential_matcher_, nodes, scans_, scan->scan,
00411 opt_pose_estimate, laser_offset_);
00412
00413
00414 const msg::GraphConstraint running_constraint = makeConstraint(g, n, res,
00415 running, scan);
00416 constraints.push_back(running_constraint);
00417 ROS_DEBUG_STREAM_NAMED ("scan_match_constraints", "Added running constraint "
00418 "to " << running_constraint.src);
00419
00420
00421 if (last_added_node_ &&
00422 distance_since_last_added_node_ < chain_distance_threshold_*0.5 &&
00423 constraints[0].src != *last_added_node_ &&
00424 g.hasOptimizedPose(*last_added_node_)) {
00425 const tf::Pose last_pose = g.getOptimizedPose(*last_added_node_);
00426 if (last_pose.getOrigin().distance(opt_pose_estimate.getOrigin()) <
00427 local_window_size_) {
00428 constraints.push_back(makeConstraint(g, n, *last_added_node_, res, scan));
00429 ROS_DEBUG_STREAM_NAMED ("scan_match_constraints", "Also adding constraint"
00430 " to last added node " << *last_added_node_);
00431 }
00432 }
00433 return constraints;
00434 }
00435
00436
00437
00438
00439 msg::GraphConstraint ScanMatchConstraints::makeConstraint (const pg::ConstraintGraph& g, const unsigned n,
00440 const ksm::ScanMatchResult& res, const pg::NodeSet& nodes,
00441 LocalizedScan::ConstPtr scan) const
00442 {
00443 const tf::Pose base_pose = util::toPose(res.pose);
00444 const gm::Point new_barycenter = util::transformPoint(base_pose, scans_.get(n)->barycenter);
00445 const unsigned closest = nodeWithNearestBarycenter(g, nodes, new_barycenter);
00446
00447
00448
00449 return makeConstraint(g, n, closest, res, scan);
00450 }
00451
00452
00453 msg::GraphConstraint ScanMatchConstraints::makeConstraint (const pg::ConstraintGraph& g, const unsigned n,
00454 const unsigned ref, const ksm::ScanMatchResult& res,
00455 LocalizedScan::ConstPtr scan) const
00456 {
00457 const tf::Pose base_pose = util::toPose(res.pose);
00458
00459
00460
00461 const tf::Pose node_in_laser_frame = util::toPose(scan->sensor_pose).inverse();
00462 const tf::Pose node_pose = base_pose*laser_offset_*node_in_laser_frame;
00463
00464 msg::GraphConstraint constraint;
00465 constraint.src = ref;
00466 constraint.dest = n;
00467 constraint.constraint.pose = util::toPose(util::relativePose(node_pose, g.getOptimizedPose(ref)));
00468
00470 const Eigen::Matrix3f prec2D = res.cov.inverse();
00471 constraint.constraint.precision = util::makePrecisionMatrix(prec2D(0,0), prec2D(1,1), prec2D(0,1), prec2D(2,2));
00472
00473 return constraint;
00474 }
00475
00476
00477
00478 unsigned ScanMatchConstraints::nodeWithNearestBarycenter (const pg::ConstraintGraph& g,
00479 const pg::NodeSet& nodes, const gm::Point& p) const
00480 {
00481 boost::optional<unsigned> closest;
00482 double dist = -42.42;
00483 BOOST_FOREACH (const unsigned n2, nodes) {
00484 const tf::Pose node_pose = g.getOptimizedPose(n2);
00485 const gm::Point& b = util::transformPoint(node_pose, scans_.get(n2)->barycenter);
00486 const double dx=b.x-p.x;
00487 const double dy=b.y-p.y;
00488 const double new_dist = dx*dx + dy*dy;
00489 if (!closest || new_dist < dist) {
00490 dist = new_dist;
00491 closest = n2;
00492 }
00493 }
00494 ROS_ASSERT(closest);
00495 return *closest;
00496 }
00497
00498
00499
00500
00501
00502
00503
00504
00505
00506 MaybeNode ScanMatchConstraints::previousNode (const unsigned n) const
00507 {
00508 MaybeNode prev;
00509 if (before_.hasData(n))
00510 prev = before_.get(n)->data;
00511 return prev;
00512 }
00513
00514 MaybeNode ScanMatchConstraints::nextNode (const unsigned n) const
00515 {
00516 MaybeNode next;
00517 if (after_.hasData(n))
00518 next = after_.get(n)->data;
00519 return next;
00520 }
00521
00522 void ScanMatchConstraints::addSequenceLink (const unsigned n1, const unsigned n2)
00523 {
00524 std_msgs::UInt64 m1, m2;
00525 m1.data = n1;
00526 m2.data = n2;
00527 before_.set(n2, m1);
00528 after_.set(n1, m2);
00529 ROS_DEBUG_STREAM_NAMED ("scan_chain_constraints", "Adding sequence link from " << n1 << " to " << n2);
00530 }
00531
00532 Chain ScanMatchConstraints::getChain (const unsigned n, const pg::NodeSet& nodes,
00533 const pg::NodeSet& forbidden) const
00534 {
00535 using util::contains;
00536 if (contains(forbidden, n) || !contains(nodes, n))
00537 return Chain();
00538
00539 Chain before, after;
00540 {
00541 unsigned current = n;
00542 while (true) {
00543 MaybeNode prev = previousNode(current);
00544 if (prev && contains(nodes, *prev) && !contains(forbidden, *prev)) {
00545 before.push_back(*prev);
00546 current = *prev;
00547 }
00548 else
00549 break;
00550 }
00551 }
00552
00553 {
00554 unsigned current = n;
00555 while (true) {
00556 MaybeNode next = nextNode(current);
00557 if (next && contains(nodes, *next) && !contains(forbidden, *next)) {
00558 after.push_back(*next);
00559 current = *next;
00560 }
00561 else
00562 break;
00563 }
00564 }
00565
00566 Chain chain(before.size() + after.size() + 1);
00567 copy(before.rbegin(), before.rend(), chain.begin());
00568 chain[before.size()] = n;
00569 copy(after.begin(), after.end(), chain.begin() + (before.size() + 1));
00570 return chain;
00571 }
00572
00573
00574 }
00575
00576
00577
00578
00579
00580