Index: src/Actions/MoleculeAction/StretchBondAction.cpp
===================================================================
--- src/Actions/MoleculeAction/StretchBondAction.cpp	(revision 396aab64f0f8871031d8de5574fb24d4a721c370)
+++ src/Actions/MoleculeAction/StretchBondAction.cpp	(revision 4b4ef4200a217f60fba08ff573d2b3f8a4917230)
@@ -33,4 +33,8 @@
 #endif
 
+#include <boost/graph/adjacency_list.hpp>
+#include <boost/graph/breadth_first_search.hpp>
+#include <boost/graph/subgraph.hpp>
+
 //#include "CodePatterns/MemDebug.hpp"
 
@@ -44,4 +48,5 @@
 #include "Atom/atom.hpp"
 #include "Bond/bond.hpp"
+#include "Descriptors/AtomIdDescriptor.hpp"
 #include "molecule.hpp"
 #include "World.hpp"
@@ -52,4 +57,34 @@
 #include "StretchBondAction.def"
 #include "Action_impl_pre.hpp"
+
+
+/**
+ * I have no idea why this is so complicated with BGL ...
+ *
+ * This is taken from the book "The Boost Graph Library: User Guide and Reference Manual, Portable Documents",
+ * chapter "Basic Graph Algorithms", example on calculating the bacon number.
+ */
+template <typename DistanceMap>
+class distance_recorder : public boost::default_bfs_visitor
+{
+public:
+  distance_recorder(DistanceMap dist) : d(dist) {}
+
+  template <typename Edge, typename Graph>
+  void tree_edge(Edge e, const Graph &g) const {
+    typename boost::graph_traits<Graph>::vertex_descriptor u = source(e,g), v = target(e,g);
+    d[v] = d[u] + 1;
+  }
+
+private:
+  DistanceMap d;
+};
+
+template <typename DistanceMap>
+distance_recorder<DistanceMap> record_distance(DistanceMap d)
+{
+  return distance_recorder<DistanceMap>(d);
+}
+
 /** =========== define the function ====================== */
 ActionState::ptr MoleculeStretchBondAction::performCall()
@@ -61,4 +96,9 @@
     return Action::failure;
   }
+  std::vector<atomId_t> atomids(2);
+  atomids[0] = atoms[0]->getId();
+  atomids[1] = atoms[1]->getId();
+  std::sort(atomids.begin(), atomids.end());
+  LOG(1, "DEBUG: Selected nodes are " << atomids);
   molecule *mol = World::getInstance().
       getMolecule(MoleculeById(atoms[0]->getMolecule()->getId()));
@@ -72,24 +112,105 @@
   LOG(1, "INFO: Old bond distance is " << olddistance << ", stretching to " << newdistance << ".");
 
-  // create the bond plane and mid-distance
+  // Assume the selected bond splits the molecule into two parts, each one on
+  // either side of the bond. We need to perform a BFS from each bond partner
+  // not using the selected bond. Therefrom, we obtain two sets of atoms/nodes.
+  // If both are disjoint, the bond is not contained in a cycle and we simply
+  // shift either set as desired. If not, then we simply shift each atom,
+  // leaving the other positions untouched.
+  typedef boost::adjacency_list < boost::vecS, boost::vecS, boost::undirectedS,
+      boost::no_property, boost::no_property > UndirectedGraph;
+
+  // convert BondGraph into boost::graph
+  UndirectedGraph molgraph(mol->getAtomCount());
+  size_t no_edges = 0;
+  for(molecule::const_iterator iter = const_cast<const molecule * const>(mol)->begin();
+      iter != const_cast<const molecule * const>(mol)->end(); ++iter) {
+    LOG(2, "DEBUG: Looking at node " << (*iter)->getId());
+    const BondList& ListOfBonds = (*iter)->getListOfBonds();
+    for(BondList::const_iterator bonditer = ListOfBonds.begin();
+        bonditer != ListOfBonds.end(); ++bonditer) {
+      const unsigned int &leftid = (*bonditer)->leftatom->getId();
+      const unsigned int &rightid = (*bonditer)->rightatom->getId();
+      // only add each edge once and do not add selected edge
+      if ((leftid == (*iter)->getId())
+          && ((leftid != atomids[0]) || (rightid != atomids[1]))) {
+        LOG(1, "DEBUG: ADDING edge " << leftid << " <-> " << rightid);
+        boost::add_edge(leftid, rightid, molgraph);
+        ++no_edges;
+      } else {
+        LOG(1, "DEBUG: Discarding edge " << leftid << " <-> " << rightid);
+      }
+    }
+  }
+  typedef boost::property_map < boost::adjacency_list <>, boost::vertex_index_t >::type index_map_t;
+  index_map_t index_map = boost::get(boost::vertex_index, molgraph);
+  const size_t num_vertices = boost::num_vertices(molgraph);
+  LOG(2, "DEBUG: We have " << num_vertices << " nodes and " << no_edges
+      << " edges in the molecule graph.");
+
+  std::vector< std::vector<size_t> > distances;
+  for (size_t i=0;i<2;++i) {
+    distances.push_back(std::vector<size_t>(num_vertices, num_vertices+1)); // set distance to num+1
+    distances[i][atomids[i]] = 0;
+    boost::breadth_first_search(
+        molgraph,
+        boost::vertex(atomids[i], molgraph),
+        boost::visitor(record_distance(&(distances[i][0]))));
+    LOG(3, "DEBUG: From atom #" << atomids[i]
+        << " BFS discovered the following distances " << distances[i]);
+  }
+
   const Vector NormalVector = (atoms[0]->getPosition() - atoms[1]->getPosition())* (1./olddistance);
-  const Vector OffsetVector = 0.5*(atoms[0]->getPosition() + atoms[1]->getPosition());
-  Plane bondplane(NormalVector, OffsetVector);
-  // go through the molecule and stretch each atom relative two plane
   const double shift = 0.5*(newdistance - olddistance);
-  const Vector PositiveShift = shift * NormalVector;
-  const Vector NegativeShift = -shift * NormalVector;
+  std::vector<Vector> Shift(2);
+  Shift[0] = shift * NormalVector;
+  Shift[1] = -shift * NormalVector;
   Box &domain = World::getInstance().getDomain();
-  for (molecule::iterator iter = mol->begin(); iter != mol->end(); ++iter) {
-    const Vector &position = (*iter)->getPosition();
-    // for each atom determine on which side of plane it is and shift accordingly
-    if (bondplane.SignedDistance(position) > 0) {
-      (*iter)->setPosition( domain.enforceBoundaryConditions(position+PositiveShift) );
-    } else {
-      (*iter)->setPosition( domain.enforceBoundaryConditions(position+NegativeShift) );
-    }
-  }
-
-  MoleculeStretchBondState *UndoState = new MoleculeStretchBondState(shift, bondplane, mol, params);
+  std::vector< std::vector<size_t> > bondside_sets(2);
+
+  // Check whether there are common nodes in each set of distances
+  for (size_t i=0;i<num_vertices;++i)
+    if ((distances[0][i] != (num_vertices+1)) && (distances[1][i] != (num_vertices+1))) {
+      ELOG(2, "Node #" << i << " is reachable from either side of bond, hence must by cyclic."
+          << " Shifting only bond partners.");
+      for(size_t j=0;j<2;++j) {
+        bondside_sets[j].push_back(atoms[j]->getId());
+        const Vector &position = atoms[j]->getPosition();
+        atoms[j]->setPosition( domain.enforceBoundaryConditions(position+Shift[j]) );
+      }
+      break;
+    }
+
+  // go through the molecule and stretch each atom in either set of nodes
+  if (bondside_sets[0].empty()) {
+    for (molecule::iterator iter = mol->begin(); iter != mol->end(); ++iter) {
+      const Vector &position = (*iter)->getPosition();
+      // for each atom determine in which set of nodes it is and shift accordingly
+      size_t i=0;
+      const size_t nodeindex = boost::get(index_map, boost::vertex((*iter)->getId(), molgraph));
+      for (;i<2;++i) {
+        if (distances[i][nodeindex] != (num_vertices+1)) {
+          (*iter)->setPosition( domain.enforceBoundaryConditions(position+Shift[i]) );
+          bondside_sets[i].push_back((*iter)->getId());
+          break;
+        }
+      }
+      if (i==2) {
+        ELOG(1, "Atom " << *iter << " is not contained on either side of bond? Undoing done shifts");
+        // Have to undo shifts
+        for (i=0;i<2;++i) {
+          for (std::vector<size_t>::const_iterator iter = bondside_sets[i].begin();
+              iter != bondside_sets[i].end(); ++iter) {
+            atom &walker = *World::getInstance().getAtom(AtomById(*iter));
+            const Vector &position = walker.getPosition();
+            walker.setPosition( domain.enforceBoundaryConditions(position-Shift[i]) );
+          }
+        }
+        return Action::failure;
+      }
+    }
+  }
+
+  MoleculeStretchBondState *UndoState = new MoleculeStretchBondState(Shift, bondside_sets, mol, params);
   return ActionState::ptr(UndoState);
 }
@@ -99,17 +220,11 @@
 
   // use given plane to undo
-  const Vector PositiveShift = state->shift * state->bondplane.getNormal();
-  const Vector NegativeShift = -state->shift * state->bondplane.getNormal();
   Box &domain = World::getInstance().getDomain();
-  molecule *mol = World::getInstance().
-      getMolecule(MoleculeById(state->mol->getId()));
-  for (molecule::iterator iter = mol->begin();
-      iter != mol->end(); ++iter) {
-    const Vector &position = (*iter)->getPosition();
-    // for each atom determine on which side of plane it is and shift accordingly
-    if (state->bondplane.SignedDistance(position) < 0) {
-      (*iter)->setPosition( domain.enforceBoundaryConditions(position+PositiveShift) );
-    } else {
-      (*iter)->setPosition( domain.enforceBoundaryConditions(position+NegativeShift) );
+  for (size_t i=0;i<2;++i) {
+    for (std::vector<size_t>::const_iterator iter = state->bondside_sets[i].begin();
+        iter != state->bondside_sets[i].end(); ++iter) {
+      atom &walker = *World::getInstance().getAtom(AtomById(*iter));
+      const Vector &position = walker.getPosition();
+      walker.setPosition( domain.enforceBoundaryConditions(position-state->Shift[i]) );
     }
   }
@@ -121,20 +236,14 @@
   MoleculeStretchBondState *state = assert_cast<MoleculeStretchBondState*>(_state.get());
 
-  // use given plane to undo
-  const Vector PositiveShift = state->shift * state->bondplane.getNormal();
-  const Vector NegativeShift = -state->shift * state->bondplane.getNormal();
   Box &domain = World::getInstance().getDomain();
-  molecule *mol = World::getInstance().
-      getMolecule(MoleculeById(state->mol->getId()));
-  for (molecule::iterator iter = mol->begin();
-      iter != mol->end(); ++iter) {
-    const Vector &position = (*iter)->getPosition();
-    // for each atom determine on which side of plane it is and shift accordingly
-    if (state->bondplane.SignedDistance(position) > 0) {
-      (*iter)->setPosition( domain.enforceBoundaryConditions(position+PositiveShift) );
-    } else {
-      (*iter)->setPosition( domain.enforceBoundaryConditions(position+NegativeShift) );
-    }
-  }
+  for (size_t i=0;i<2;++i) {
+    for (std::vector<size_t>::const_iterator iter = state->bondside_sets[i].begin();
+        iter != state->bondside_sets[i].end(); ++iter) {
+      atom &walker = *World::getInstance().getAtom(AtomById(*iter));
+      const Vector &position = walker.getPosition();
+      walker.setPosition( domain.enforceBoundaryConditions(position+state->Shift[i]) );
+    }
+  }
+
   return ActionState::ptr(_state);
 }
Index: src/Actions/MoleculeAction/StretchBondAction.def
===================================================================
--- src/Actions/MoleculeAction/StretchBondAction.def	(revision 396aab64f0f8871031d8de5574fb24d4a721c370)
+++ src/Actions/MoleculeAction/StretchBondAction.def	(revision 4b4ef4200a217f60fba08ff573d2b3f8a4917230)
@@ -21,6 +21,6 @@
 (RangeValidator< double >(0., 10.))
 
-#define statetypes (const double)(const Plane)(const molecule *)
-#define statereferences (shift)(bondplane)(mol)
+#define statetypes (std::vector<Vector>)(std::vector< std::vector<size_t> >)(const molecule *)
+#define statereferences (Shift)(bondside_sets)(mol)
 
 // some defines for all the names, you may use ACTION, STATE and PARAMS
Index: tests/regression/Makefile.am
===================================================================
--- tests/regression/Makefile.am	(revision 396aab64f0f8871031d8de5574fb24d4a721c370)
+++ tests/regression/Makefile.am	(revision 4b4ef4200a217f60fba08ff573d2b3f8a4917230)
@@ -150,4 +150,5 @@
 	$(srcdir)/Molecules/SaveTemperature/testsuite-molecules-save-temperature.at \
 	$(srcdir)/Molecules/StretchBond/testsuite-molecules-stretch-bond.at \
+	$(srcdir)/Molecules/StretchBond/testsuite-molecules-stretch-bond-cyclic_bond.at \
 	$(srcdir)/Molecules/Translation/testsuite-molecules-translation.at \
 	$(srcdir)/Molecules/Translation-Periodic/testsuite-molecules-translation-periodic.at \
Index: tests/regression/Molecules/StretchBond/post/benzene.pdb
===================================================================
--- tests/regression/Molecules/StretchBond/post/benzene.pdb	(revision 4b4ef4200a217f60fba08ff573d2b3f8a4917230)
+++ tests/regression/Molecules/StretchBond/post/benzene.pdb	(revision 4b4ef4200a217f60fba08ff573d2b3f8a4917230)
@@ -0,0 +1,26 @@
+REMARK created by molecuilder on Mon May 15 12:28:27 2017, time step 0
+ATOM      1 H01 0non 01          5.000   7.145   5.002  0.00  0.00           H 0
+ATOM      2 C01 0non 01          6.080   7.145   5.001  0.00  0.00           C 0
+ATOM      3 C02 0non 01          6.781   8.145   5.004  0.00  0.00           C 0
+ATOM      4 C03 0non 01          6.781   6.144   5.004  0.00  0.00           C 0
+ATOM      5 C04 0non 01          8.161   8.345   5.004  0.00  0.00           C 0
+ATOM      6 C05 0non 01          8.161   5.945   5.004  0.00  0.00           C 0
+ATOM      7 C06 0non 01          8.849   7.145   5.001  0.00  0.00           C 0
+ATOM      8 H02 0non 01          6.245   9.290   5.006  0.00  0.00           H 0
+ATOM      9 H03 0non 01          6.245   5.000   5.007  0.00  0.00           H 0
+ATOM     10 H04 0non 01          8.705   9.278   5.005  0.00  0.00           H 0
+ATOM     11 H05 0non 01          8.704   5.012   5.006  0.00  0.00           H 0
+ATOM     12 H06 0non 01          9.929   7.145   5.000  0.00  0.00           H 0
+CONECT    1    2                                                                
+CONECT    2    1    3    4                                                      
+CONECT    3    2    5    8                                                      
+CONECT    4    2    6    9                                                      
+CONECT    5    3    7   10                                                      
+CONECT    6    4    7   11                                                      
+CONECT    7    5    6   12                                                      
+CONECT    8    3                                                                
+CONECT    9    4                                                                
+CONECT   10    5                                                                
+CONECT   11    6                                                                
+CONECT   12    7                                                                
+END
Index: tests/regression/Molecules/StretchBond/pre/benzene.pdb
===================================================================
--- tests/regression/Molecules/StretchBond/pre/benzene.pdb	(revision 4b4ef4200a217f60fba08ff573d2b3f8a4917230)
+++ tests/regression/Molecules/StretchBond/pre/benzene.pdb	(revision 4b4ef4200a217f60fba08ff573d2b3f8a4917230)
@@ -0,0 +1,26 @@
+REMARK created by molecuilder on Mon May 15 12:23:36 2017, time step 0
+ATOM      1 H01 0non 01          5.000   7.145   5.002  0.00  0.00           H 0
+ATOM      2 C01 0non 01          6.080   7.145   5.001  0.00  0.00           C 0
+ATOM      3 C02 0non 01          6.781   8.352   5.004  0.00  0.00           C 0
+ATOM      4 C03 0non 01          6.781   5.937   5.004  0.00  0.00           C 0
+ATOM      5 C04 0non 01          8.161   8.345   5.004  0.00  0.00           C 0
+ATOM      6 C05 0non 01          8.161   5.945   5.004  0.00  0.00           C 0
+ATOM      7 C06 0non 01          8.849   7.145   5.001  0.00  0.00           C 0
+ATOM      8 H02 0non 01          6.245   9.290   5.006  0.00  0.00           H 0
+ATOM      9 H03 0non 01          6.245   5.000   5.007  0.00  0.00           H 0
+ATOM     10 H04 0non 01          8.705   9.278   5.005  0.00  0.00           H 0
+ATOM     11 H05 0non 01          8.704   5.012   5.006  0.00  0.00           H 0
+ATOM     12 H06 0non 01          9.929   7.145   5.000  0.00  0.00           H 0
+CONECT    1    2                                                                
+CONECT    2    1    3    4                                                      
+CONECT    3    2    5    8                                                      
+CONECT    4    2    6    9                                                      
+CONECT    5    3    7   10                                                      
+CONECT    6    4    7   11                                                      
+CONECT    7    5    6   12                                                      
+CONECT    8    3                                                                
+CONECT    9    4                                                                
+CONECT   10    5                                                                
+CONECT   11    6                                                                
+CONECT   12    7                                                                
+END
Index: tests/regression/Molecules/StretchBond/testsuite-molecules-stretch-bond-cyclic_bond.at
===================================================================
--- tests/regression/Molecules/StretchBond/testsuite-molecules-stretch-bond-cyclic_bond.at	(revision 4b4ef4200a217f60fba08ff573d2b3f8a4917230)
+++ tests/regression/Molecules/StretchBond/testsuite-molecules-stretch-bond-cyclic_bond.at	(revision 4b4ef4200a217f60fba08ff573d2b3f8a4917230)
@@ -0,0 +1,51 @@
+#
+#    MoleCuilder - creates and alters molecular systems
+#    Copyright (C) 2017 Frederik Heber
+#
+#    This program 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.
+#
+#    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+### stretching cyclic bond
+
+AT_SETUP([Molecules - Stretching cyclic bond])
+AT_KEYWORDS([molecules stretch-bond])
+
+file=benzene.pdb
+AT_CHECK([/bin/cp -f ${abs_top_srcdir}/tests/regression/Molecules/StretchBond/pre/$file $file], 0)
+AT_CHECK([chmod +w $file], 0)
+AT_CHECK([../../molecuilder -i $file --select-atom-by-id 2 3 --stretch-bond 2.], 0, [stdout], [stderr])
+AT_CHECK([diff -I '.*reated by molecuilder.*' $file ${abs_top_srcdir}/tests/regression/Molecules/StretchBond/post/$file], 0, [ignore], [ignore])
+
+AT_CLEANUP
+
+AT_SETUP([Molecules - Stretching cyclic bond with Undo])
+AT_KEYWORDS([molecules stretch-bond undo])
+
+file=benzene.pdb
+AT_CHECK([/bin/cp -f ${abs_top_srcdir}/tests/regression/Molecules/StretchBond/pre/$file $file], 0)
+AT_CHECK([chmod +w $file], 0)
+AT_CHECK([../../molecuilder -i $file --select-atom-by-id 2 3 --stretch-bond 2. --undo], 0, [stdout], [stderr])
+AT_CHECK([diff -I '.*reated by molecuilder.*' $file ${abs_top_srcdir}/tests/regression/Molecules/StretchBond/pre/$file], 0, [ignore], [ignore])
+
+AT_CLEANUP
+
+AT_SETUP([Molecules - Stretching cyclic bond with Redo])
+AT_KEYWORDS([molecules stretch-bond redo])
+
+file=benzene.pdb
+AT_CHECK([/bin/cp -f ${abs_top_srcdir}/tests/regression/Molecules/StretchBond/pre/$file $file], 0)
+AT_CHECK([chmod +w $file], 0)
+AT_CHECK([../../molecuilder -i $file --select-atom-by-id 2 3 --stretch-bond 2. --undo --redo], 0, [stdout], [stderr])
+AT_CHECK([diff -I '.*reated by molecuilder.*' $file ${abs_top_srcdir}/tests/regression/Molecules/StretchBond/post/$file], 0, [ignore], [ignore])
+
+AT_CLEANUP
Index: tests/regression/Molecules/testsuite-molecules.at
===================================================================
--- tests/regression/Molecules/testsuite-molecules.at	(revision 396aab64f0f8871031d8de5574fb24d4a721c370)
+++ tests/regression/Molecules/testsuite-molecules.at	(revision 4b4ef4200a217f60fba08ff573d2b3f8a4917230)
@@ -71,4 +71,5 @@
 # stretch one of its bond
 m4_include([Molecules/StretchBond/testsuite-molecules-stretch-bond.at])
+m4_include([Molecules/StretchBond/testsuite-molecules-stretch-bond-cyclic_bond.at])
 
 # anneal forces by one step
