Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Introduced a new utility for computing the first shortest path and al…

…l shortest paths from a start to an end atom. This implementation replaces an existing static method in PathTools which has now been deprecated. Another helper class was added that simplifies the computation of all-vs-all shortest paths.
  • Loading branch information...
commit c84d22ba32fe44fabecc8a20a55568d604aa29d5 1 parent 22d2490
@johnmay authored
View
553 src/main/org/openscience/cdk/graph/AllShortestPaths.java
@@ -0,0 +1,553 @@
+/*
+ * Copyright (C) 2012 John May <jwmay@users.sf.net>
+ *
+ * Contact: cdk-devel@lists.sourceforge.net
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ * All we ask is that proper credit is given for our work, which includes
+ * - but is not limited to - adding the above copyright notice to the beginning
+ * of your source code files, and to any copyright notice that you may distribute
+ * with programs based on this work.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+package org.openscience.cdk.graph;
+
+import org.openscience.cdk.annotations.TestClass;
+import org.openscience.cdk.annotations.TestMethod;
+import org.openscience.cdk.interfaces.IAtom;
+import org.openscience.cdk.interfaces.IAtomContainer;
+import org.openscience.cdk.interfaces.IBond;
+import org.openscience.cdk.interfaces.IChemObjectBuilder;
+import org.openscience.cdk.interfaces.IChemObjectChangeEvent;
+import org.openscience.cdk.interfaces.IChemObjectListener;
+import org.openscience.cdk.interfaces.IElectronContainer;
+import org.openscience.cdk.interfaces.ILonePair;
+import org.openscience.cdk.interfaces.ISingleElectron;
+import org.openscience.cdk.interfaces.IStereoElement;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Determine the shortest paths from all atoms in a molecule.
+ *
+ *
+ * <pre>{@code
+ * IAtomContainer benzene = MoleculeFactory.makeBenzene();
+ * AllShortestPaths shortestPaths = new AllShortestPaths(benzene);
+ *
+ * for (int i = 0; i < benzene.getAtomCount(); i++) {
+ *
+ * // only to half the comparisons, we can reverse the
+ * // path[] to get all j to i
+ * for (int j = i + 1; j < benzene.getAtomCount(); j++) {
+ *
+ * // compute shortest path from i to j
+ * int[] path = shortestPaths.from(i).pathTo(j);
+ *
+ * // compute all shortest paths from i to j
+ * int[][] paths = shortestPaths.from(i).pathsTo(j);
+ *
+ * // compute the atoms in the path from i to j
+ * IAtom[] atoms = shortestPaths.from(i).atomsTo(j);
+ *
+ * // access the number of paths from i to j
+ * int nPaths = shortestPaths.from(i).nPathsTo(j);
+ *
+ * // access the distance from i to j
+ * int distance = shortestPaths.from(i).nPathsTo(j);
+ *
+ * }
+ * }
+ * }</pre>
+ *
+ * @author John May
+ * @cdk.module core
@egonw
egonw added a note
  • @cdk.githash
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ * @see ShortestPaths
+ */
+@TestClass("org.openscience.cdk.graph.AllShortestPathsTest")
+public final class AllShortestPaths {
+
+ private final IAtomContainer container;
+ private final ShortestPaths[] shortestPaths;
+
+
+ @TestMethod("testConstruction_Null,testConstruction_Empty")
+ public AllShortestPaths(IAtomContainer container) {
+
+ // toAdjList performs null check
+ int[][] adjacent = ShortestPaths.toAdjList(container);
+
+ int n = container.getAtomCount();
+
+ this.container = container;
+ this.shortestPaths = new ShortestPaths[n];
+
+ // for each atom construct the ShortestPaths object
+ for (int i = 0; i < n; i++) {
+ shortestPaths[i] = new ShortestPaths(adjacent, container, i);
+ }
+
+ }
+
+ /**
+ * Access the shortest paths object for provided start vertex.
+ *
+ * <pre>{@code
+ * AllShortestPaths asp = ...;
+ *
+ * // access explicitly
+ * ShortestPaths sp = asp.from(0);
+ *
+ * // or chain method calls
+ * int[] path = asp.from(0).pathTo(5);
+ * }</pre>
+ *
+ * @param start the start vertex of the path
+ * @return The shortest paths from the given state vertex
+ * @see ShortestPaths
+ */
+ @TestMethod("testFrom_Int_Benzene")
+ public ShortestPaths from(int start) {
+ return (start < 0 || start >= shortestPaths.length) ? EMPTY_SHORTEST_PATHS : shortestPaths[start];
+ }
+
+ /**
+ * Access the shortest paths object for provided start atom.
+ *
+ * <pre>{@code
+ * AllShortestPaths asp = ...;
+ * IAtom start, end = ...;
+ *
+ * // access explicitly
+ * ShortestPaths sp = asp.from(start);
+ *
+ * // or chain the method calls together
+ *
+ * // first path from start to end atom
+ * int[] path = asp.from(start).pathTo(end);
+ *
+ * // first atom path from start to end atom
+ * IAtom[] atoms = asp.from(start).atomTo(end);
+ *
+ * }</pre>
+ *
+ * @param start the start atom of the path
+ * @return The shortest paths from the given state vertex
+ * @see ShortestPaths
+ */
+ @TestMethod("testFrom_Atom_Benzene")
+ public ShortestPaths from(IAtom start) {
+ // currently container.getAtomNumber() return -1 when null
+ return from(container.getAtomNumber(start));
+ }
+
+
+ /**
+ * an empty atom container so we can handle invalid vertices/atoms better. Note
@johnmay Owner
johnmay added a note

typo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ * very pretty but we can't access the domain model from cdk-core.
+ */
+ private static final IAtomContainer EMPTY_CONTAINER = new IAtomContainer() {
+
+ public void addStereoElement(IStereoElement element) {
+ }
+
+ public void setStereoElements(List<IStereoElement> elements) {
+ }
+
+ public Iterable<IStereoElement> stereoElements() {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public void setAtoms(IAtom[] atoms) {
+ }
+
+ public void setBonds(IBond[] bonds) {
+ }
+
+ public void setAtom(int number, IAtom atom) {
+ }
+
+ public IAtom getAtom(int number) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public IBond getBond(int number) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public ILonePair getLonePair(int number) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public ISingleElectron getSingleElectron(int number) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public Iterable<IAtom> atoms() {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public Iterable<IBond> bonds() {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+
+ public Iterable<ILonePair> lonePairs() {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public Iterable<ISingleElectron> singleElectrons() {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public Iterable<IElectronContainer> electronContainers() {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public IAtom getFirstAtom() {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public IAtom getLastAtom() {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public int getAtomNumber(IAtom atom) {
+ return -1;
+ }
+
+ public int getBondNumber(IAtom atom1, IAtom atom2) {
+ return -1;
+ }
+
+ public int getBondNumber(IBond bond) {
+ return -1;
+ }
+
+ public int getLonePairNumber(ILonePair lonePair) {
+ return -1;
+ }
+
+ public int getSingleElectronNumber(ISingleElectron singleElectron) {
+ return -1;
+ }
+
+ public IElectronContainer getElectronContainer(int number) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public IBond getBond(IAtom atom1, IAtom atom2) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public int getAtomCount() {
+ return 0;
+ }
+
+ public int getBondCount() {
+ return 0;
+ }
+
+ public int getLonePairCount() {
+ return 0;
+ }
+
+ public int getSingleElectronCount() {
+ return 0;
+ }
+
+ public int getElectronContainerCount() {
+ return 0;
+ }
+
+ public List<IAtom> getConnectedAtomsList(IAtom atom) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public List<IBond> getConnectedBondsList(IAtom atom) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public List<ILonePair> getConnectedLonePairsList(IAtom atom) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public List<ISingleElectron> getConnectedSingleElectronsList(IAtom atom) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public List<IElectronContainer> getConnectedElectronContainersList(IAtom atom) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public int getConnectedAtomsCount(IAtom atom) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public int getConnectedBondsCount(IAtom atom) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public int getConnectedBondsCount(int atomnumber) {
+ return 0;
+ }
+
+ public int getConnectedLonePairsCount(IAtom atom) {
+ return 0;
+ }
+
+ public int getConnectedSingleElectronsCount(IAtom atom) {
+ return 0;
+ }
+
+ public double getBondOrderSum(IAtom atom) {
+ return 0;
+ }
+
+ public IBond.Order getMaximumBondOrder(IAtom atom) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public IBond.Order getMinimumBondOrder(IAtom atom) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public void add(IAtomContainer atomContainer) {
+
+ }
+
+ public void addAtom(IAtom atom) {
+
+ }
+
+ public void addBond(IBond bond) {
+
+ }
+
+ public void addLonePair(ILonePair lonePair) {
+
+ }
+
+ public void addSingleElectron(ISingleElectron singleElectron) {
+
+ }
+
+ public void addElectronContainer(IElectronContainer electronContainer) {
+
+ }
+
+ public void remove(IAtomContainer atomContainer) {
+
+ }
+
+ public void removeAtom(int position) {
+
+ }
+
+ public void removeAtom(IAtom atom) {
+
+ }
+
+ public IBond removeBond(int position) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public IBond removeBond(IAtom atom1, IAtom atom2) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public void removeBond(IBond bond) {
+
+ }
+
+ public ILonePair removeLonePair(int position) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public void removeLonePair(ILonePair lonePair) {
+
+ }
+
+
+ public ISingleElectron removeSingleElectron(int position) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public void removeSingleElectron(ISingleElectron singleElectron) {
+
+ }
+
+ public IElectronContainer removeElectronContainer(int position) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public void removeElectronContainer(IElectronContainer electronContainer) {
+
+ }
+
+ public void removeAtomAndConnectedElectronContainers(IAtom atom) {
+
+ }
+
+ public void removeAllElements() {
+
+ }
+
+ public void removeAllElectronContainers() {
+
+ }
+
+ public void removeAllBonds() {
+
+ }
+
+ public void addBond(int atom1, int atom2, IBond.Order order, IBond.Stereo stereo) {
+
+ }
+
+ public void addBond(int atom1, int atom2, IBond.Order order) {
+
+ }
+
+ public void addLonePair(int atomID) {
+
+ }
+
+ public void addSingleElectron(int atomID) {
+
+ }
+
+ public boolean contains(IAtom atom) {
+ return false;
+ }
+
+ public boolean contains(IBond bond) {
+ return false;
+ }
+
+ public boolean contains(ILonePair lonePair) {
+ return false;
+ }
+
+ public boolean contains(ISingleElectron singleElectron) {
+ return false;
+ }
+
+ public boolean contains(IElectronContainer electronContainer) {
+ return false;
+ }
+
+ public boolean isEmpty() {
+ return true;
+ }
+
+ public IAtomContainer clone() throws CloneNotSupportedException {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public void addListener(IChemObjectListener col) {
+
+ }
+
+ public int getListenerCount() {
+ return 0;
+ }
+
+ public void removeListener(IChemObjectListener col) {
+
+ }
+
+ public void setNotification(boolean bool) {
+
+ }
+
+ public boolean getNotification() {
+ return false;
+ }
+
+ public void notifyChanged() {
+
+ }
+
+ public void notifyChanged(IChemObjectChangeEvent evt) {
+
+ }
+
+ public void setProperty(Object description, Object property) {
+
+ }
+
+ public void removeProperty(Object description) {
+
+ }
+
+ public <T> T getProperty(Object description) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public <T> T getProperty(Object description, Class<T> c) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public Map<Object, Object> getProperties() {
+ return null;
+ }
+
+ public String getID() {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public void setID(String identifier) {
+
+ }
+
+ public void setFlag(int mask, boolean value) {
+
+ }
+
+ public boolean getFlag(int mask) {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public void setProperties(Map<Object, Object> properties) {
+ }
+
+ public void setFlags(boolean[] newFlags) {
+ }
+
+ public boolean[] getFlags() {
+ return new boolean[0];
+ }
+
+ public Number getFlagValue() {
+ return 0;
+ }
+
+ public IChemObjectBuilder getBuilder() {
+ throw new UnsupportedOperationException("not supported");
+ }
+
+ public void stateChanged(IChemObjectChangeEvent event) {
+
+ }
+ };
+
+ /**
+ * pseudo shortest-paths - when an invalid atom is given. this will always
+ * return 0 length paths and distances.
+ */
+ private static final ShortestPaths EMPTY_SHORTEST_PATHS = new ShortestPaths(new int[0][0], EMPTY_CONTAINER, 0);
+
+}
View
7 src/main/org/openscience/cdk/graph/PathTools.java
@@ -450,8 +450,15 @@ public static int getVertexCountAtDistance(IAtomContainer atomContainer, int dis
* @param end The ending atom
* @return A <code>List</code> containing the atoms in the shortest path between <code>start</code> and
* <code>end</code> inclusive
+ * @see ShortestPaths
+ * @see ShortestPaths#atomsTo(IAtom)
+ * @see AllShortestPaths
+ * @deprecated This implementation recalculates all shortest paths from the start atom
+ * for each method call and does not indicate if there are equally short paths
+ * from the start to the end. Replaced by {@link ShortestPaths#atomsTo(IAtom)}
*/
@TestMethod("testGetShortestPath_IAtomContainer_IAtom_IAtom")
+ @Deprecated
public static List<IAtom> getShortestPath(IAtomContainer atomContainer, IAtom start, IAtom end) {
int natom = atomContainer.getAtomCount();
int endNumber = atomContainer.getAtomNumber(end);
View
812 src/main/org/openscience/cdk/graph/ShortestPaths.java
@@ -0,0 +1,812 @@
+/*
+ * Copyright (C) 2012 John May <jwmay@users.sf.net>
+ *
+ * Contact: cdk-devel@lists.sourceforge.net
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ * All we ask is that proper credit is given for our work, which includes
+ * - but is not limited to - adding the above copyright notice to the beginning
+ * of your source code files, and to any copyright notice that you may distribute
+ * with programs based on this work.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+package org.openscience.cdk.graph;
+
+import org.openscience.cdk.annotations.TestClass;
+import org.openscience.cdk.annotations.TestMethod;
+import org.openscience.cdk.interfaces.IAtom;
+import org.openscience.cdk.interfaces.IAtomContainer;
+import org.openscience.cdk.interfaces.IBond;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Determine all the shortest paths from a given start atom to any other
+ * connected atom. <p/>
+ *
+ * The number of shortest paths ({@link #nPathsTo(int)}) and the distance
+ * ({@link #distanceTo(int)}) can be accessed before building all the paths.
+ * When no path is found (i.e. not-connected) an empty path is returned. <p/>
+ *
+ * <pre>{@code
+ * IAtomContainer benzene = MoleculeFactory.makeBenzene();
+ *
+ * IAtom c1 = benzene.getAtom(0);
+ * IAtom c4 = benzene.getAtom(3);
+ *
+ * // shortest paths from c1
+ * ShortestPaths sp = new ShortestPaths(benzene, c1);
+ *
+ * // count the number of paths from C1 to C4
+ * int nPaths = sp.nPathsTo(c4);
+ *
+ * // check the distance between C1 to C4
+ * int distance = sp.distanceTo(c4);
+ *
+ * // access the first path to the C4
+ * // note: first path is determined by storage order
+ * int[] path = sp.pathTo(c4);
+ *
+ * // access both paths
+ * int[][] paths = sp.pathsTo(c4);
+ * int[] org = paths[0]; // paths[0] == path
+ * int[] alt = paths[1];
+ * }</pre>
+ *
+ * <p/> If shortest paths from multiple start atoms are required {@link
+ * AllShortestPaths} will have a small performance advantage. Please use {@link
+ * org.openscience.cdk.graph.matrix.TopologicalMatrix} if only the shortest
+ * distances between atoms is required.
+ *
+ * @author John May
+ * @cdk.module core
@egonw
egonw added a note
  • @cdk.githash
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ * @see AllShortestPaths
+ * @see org.openscience.cdk.graph.matrix.TopologicalMatrix
+ */
+@TestClass("org.openscience.cdk.graph.ShortestPathsTest")
+public final class ShortestPaths {
+
+ /* empty path when no valid path was found */
+ private static final int[] EMPTY_PATH = new int[0];
+
+ /* empty path when no valid path was found */
+ private static final int[][] EMPTY_PATHS = new int[0][];
+
+ /* route to each vertex */
+ private final Route[] routeTo;
+
+ /* distance to each vertex */
+ private final int[] distTo;
+
+ /* number of paths to each vertex */
+ private final int[] nPathsTo;
+
+
+ private final int start;
+ private final IAtomContainer container;
+
+ /**
+ * Construct a new shortest paths tool for a single start atom. If shortest
+ * paths from multiple start atoms are required {@link AllShortestPaths}
+ * will have a small performance advantage.
+ *
+ * @param container an atom container to build
+ * @param start the start atom to which all shortest paths will be
+ * computed
+ * @see AllShortestPaths
+ */
+ @TestMethod("testConstructor_Container_Empty,testConstructor_Container_Null,testConstructor_Container_MissingAtom")
+ public ShortestPaths(IAtomContainer container, IAtom start) {
+ this(toAdjList(container), container, container.getAtomNumber(start));
+ }
+
+
+ /**
+ * Internal constructor for use by {@link AllShortestPaths}. This
+ * constructor allows the passing of adjacency list directly so the
+ * representation does not need to be rebuilt for a different start atom.
+ *
+ * @param adjacent adjacency list representation - built from {@link
+ * #toAdjList(org.openscience.cdk.interfaces.IAtomContainer)}
+ * @param container container used to access atoms and their indices
+ * @param start the start atom index of the shortest paths
+ */
+ ShortestPaths(int[][] adjacent, IAtomContainer container, int start) {
+
+ int n = adjacent.length;
+
+ this.container = container;
+ this.start = start;
+
+ this.distTo = new int[n];
+ this.routeTo = new Route[n];
+ this.nPathsTo = new int[n];
+
+ // skip computation for empty molecules
+ if (container.isEmpty())
+ return;
+ if (start == -1)
+ throw new IllegalArgumentException("invalid vertex start - atom not found container");
+
+ for (int i = 0; i < n; i++) {
+ distTo[i] = Integer.MAX_VALUE;
+ }
+
+ // initialise source vertex
+ distTo[start] = 0;
+ routeTo[start] = new Source(start);
+ nPathsTo[start] = 1;
+
+ compute(adjacent);
+
+ }
+
+
+ /**
+ * Perform a breath-first-search (BFS) from the start atom. The distanceTo[]
+ * is updated on each iteration. The routeTo[] keeps track of our route back
+ * to the source. The method has aspects similar to Dijkstra's shortest path
+ * but we are working with vertices and thus our edges are unweighted and is
+ * more similar to a simple BFS.
+ */
+ private void compute(int[][] adjacent) {
+
+ // compute from our start vertex
+ List<Integer> queue = new ArrayList<Integer>(adjacent.length);
+ queue.add(start);
+
+ while (!queue.isEmpty()) {
+
+ Integer v = queue.remove(0);
+ int dist = distTo[v] + 1;
+
+ for (int w : adjacent[v]) {
+
+ // distance is less then the current closest distance
+ if (dist < distTo[w]) {
+ distTo[w] = dist;
+ routeTo[w] = new SequentialRoute(routeTo[v], w); // append w to the route to v
+ nPathsTo[w] = nPathsTo[v];
+ queue.add(w);
+ } else if (distTo[w] == dist) {
+ // found path of equal distance, mark as a branch with a new sequential route
+ routeTo[w] = new Branch(routeTo[w],
+ new SequentialRoute(routeTo[v], w));
+ nPathsTo[w] += nPathsTo[v];
+ }
+ }
+
+ }
+
+
+ }
+
+
+ /**
+ * Compute the first shortest path from the <i>start</i> vertex to the
+ * provided <i>end</i> vertex. The path is an inclusive fixed size array in
+ * order of the vertices to visit. If multiple shortest paths are available
+ * the first shortest path is determined by storage order of adjacent
+ * vertices.
+ *
+ * If there is no path to the <i>end</i> atom an empty array is returned. It
+ * is considered there to be no valid path if a) the vertex belongs to the
+ * same container but is a member of a different fragment, or b) The atom
+ * not present in the container at all.
+ *
+ * <pre>{@code
+ * ShortestPaths sp = ...;
+ *
+ * // get first path
+ * int[] path = sp.pathTo(5);
+ *
+ * // it can be more robust to check if there is only one path
+ * if(sp.nPathsTo(5) == 1){
+ * int[] path = sp.pathTo(5); // get the first path
+ * }
+ * }</pre>
+ *
+ * @param end the <i>end</i> vertex to find a path to
+ * @return path from the <i>start</i> to the <i>end</i> vertex
+ * @see #pathTo(org.openscience.cdk.interfaces.IAtom)
+ * @see #atomsTo(int)
+ * @see #atomsTo(org.openscience.cdk.interfaces.IAtom)
+ */
+ @TestMethod("testPathTo_Int_Simple,testPathTo_Int_Benzene,testPathTo_Int_Norbornane," +
+ "testPathTo_Int_Spiroundecane,testPathTo_Int_Pentadecaspiro," +
+ "testPathTo_Int_OutOfBoundsIndex,testPathTo_Int_NegativeIndex," +
+ "testPathTo_Int_Disconnected")
+ public int[] pathTo(int end) {
+
+ if (end < 0 || end >= routeTo.length)
+ return EMPTY_PATH;
+
+ return routeTo[end] != null ? routeTo[end].toPath(distTo[end] + 1) : EMPTY_PATH;
+
+ }
+
+
+ /**
+ * Compute the first shortest path from the <i>start</i> atom to the
+ * provided <i>end</i> atom. The path is an inclusive fixed size array in
+ * order of the vertices to visit. If multiple shortest paths are available
+ * the first shortest path is determined by storage order of adjacent
+ * vertices.
+ *
+ * If there is no path to the <i>end</i> atom an empty array is returned. It
+ * is considered there to be no valid path if a) the vertex belongs to the
+ * same container but is a member of a different fragment, or b) The atom
+ * not present in the container at all.
+ *
+ * <pre>{@code
+ * ShortestPaths sp = ...;
+ * IAtom end = ...;
+ *
+ * // get first path
+ * int[] path = sp.pathTo(end);
+ *
+ * // it can be more robust to check if there is only one path
+ * if(sp.nPathsTo(end) == 1){
+ * int[] path = sp.pathTo(end); // get the first path
+ * }
+ * }</pre>
+ *
+ * @param end the <i>end</i> vertex to find a path to
+ * @return path from the <i>start</i> to the <i>end</i> vertex
+ * @see #atomsTo(org.openscience.cdk.interfaces.IAtom)
+ * @see #atomsTo(int)
+ * @see #pathTo(int)
+ */
+ @TestMethod("testPathTo_Atom_Simple,testPathTo_Atom_Benzene,testPathTo_Atom_Norbornane," +
+ "testPathTo_Atom_Spiroundecane,testPathTo_Atom_Pentadecaspiro," +
+ "testPathTo_Atom_MissingAtom,testPathTo_Atom_Null," +
+ "testPathTo_Atom_Disconnected")
+ public int[] pathTo(IAtom end) {
+ return pathTo(container.getAtomNumber(end));
+ }
+
+
+ /**
+ * Compute all shortest paths from the given <i>start</i> vertex to the
+ * provided <i>end</i> vertex. The path is an inclusive fixed size array in
+ * order of the vertices to visit. <p/>
+ *
+ * <b>Important:</b> for every possible branch the number of possible paths
+ * doubles. This method will happily generate tens of thousands of possible
+ * paths and although the chance of finding such a molecule is highly
+ * unlikely (e.g. C60 fullerene has at maximum six shortest paths to every
+ * other atom). It is advisable to check how many paths there are first with
+ * {@link #nPathsTo(int)}.
+ *
+ * <pre>{@code
+ * int threshold = 20;
+ * ShortestPaths sp = ...;
+ *
+ * // get shortest paths
+ * int[][] paths = sp.pathsTo(5);
+ *
+ * // only get shortest paths if there are below a given threshold
@johnmay Owner
johnmay added a note

typo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ * if(sp.nPathsTo(5) < threshold){
+ * int[][] path = sp.pathTo(5); // get shortest paths
+ * }
+ * }</pre>
+ *
+ * @param end the end atom
+ * @return all shortest paths from the start to the end vertex
+ */
+ @TestMethod("testPathsTo_Int_Simple,testPathsTo_Int_Benzene,testPathsTo_Int_Spiroundecane," +
+ "testPathsTo_Int_Norbornane,testPathsTo_Int_OutOfBoundsIndex," +
+ "testPathsTo_Int_NegativeIndex,testPathsTo_Int_Disconnected")
+ public int[][] pathsTo(int end) {
+
+ if (end < 0 || end >= routeTo.length)
+ return EMPTY_PATHS;
+
+ return routeTo[end] != null ? routeTo[end].toPaths(distTo[end] + 1) : EMPTY_PATHS;
+ }
+
+
+ /**
+ * Compute all shortest paths from the given <i>start</i> atom to the
+ * provided <i>end</i> atom. The path is an inclusive fixed size array in
+ * order of the vertices to visit. <p/>
+ *
+ * <b>Important:</b> for every possible branch the number of possible paths
+ * doubles. This method will happily generate tens of thousands of possible
+ * paths and although the chance of finding such a molecule is highly
+ * unlikely (e.g. C60 fullerene has at maximum 6 shortest paths to every
+ * other atom). It is advisable to check how many paths there are first with
+ * {@link #nPathsTo(int)}.
+ *
+ * <pre>{@code
+ * int threshold = 20;
+ * ShortestPaths sp = ...;
+ * IAtom end = ...;
+ *
+ * // get shortest paths
+ * int[][] paths = sp.pathsTo(end);
+ *
+ * // only get shortest paths if there are below a given threshold
@johnmay Owner
johnmay added a note

typo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ * if(sp.nPathsTo(end) < threshold){
+ * int[][] path = sp.pathTo(end); // get shortest paths
+ * }
+ * }</pre>
+ *
+ * @param end the end atom
+ * @return all shortest paths from the start to the end vertex
+ */
+ @TestMethod("testPathsTo_Atom_Simple,testPathsTo_Atom_Benzene,testPathsTo_Atom_Spiroundecane," +
+ "testPathsTo_Atom_Norbornane,testPathsTo_Atom_Pentadecaspiro," +
+ "testPathsTo_Atom_MissingAtom,testPathsTo_Atom_Null," +
+ "testPathsTo_Atom_Disconnected")
+ public int[][] pathsTo(IAtom end) {
+ return pathsTo(container.getAtomNumber(end));
+ }
+
+
+ /**
+ * Compute the first shortest path from the <i>start</i> vertex to the
+ * provided <i>end</i> vertex. The path is an inclusive fixed size array in
+ * order of the atoms to visit. If multiple shortest paths are available the
+ * first shortest path of atoms is determined by storage order of adjacent
+ * vertices.
+ *
+ * If there is no path to the <i>end</i> atom an empty array of atoms is
+ * returned. It is considered there to be no valid path if a) the vertex
+ * belongs to the same container but is a member of a different fragment, or
+ * b) The vertex not present in the container at all.
+ *
+ * <pre>{@code
+ * ShortestPaths sp = ...;
+ *
+ * // get first path to vertex 5
+ * IAtom[] path = sp.atomsTo(5);
+ *
+ * // it can be more robust to check if there is only one path
+ * if(sp.nPathsTo(5) == 1){
+ * IAtom[] path = sp.atomsTo(5); // get the first path to vertex 5
+ * }
+ * }</pre>
+ *
+ * @param end the <i>end</i> vertex to find a path to
+ * @return path from the <i>start</i> to the <i>end</i> atoms as fixed size
+ * array of {@link org.openscience.cdk.interfaces.IAtom}s
+ * @see #atomsTo(int)
+ * @see #pathTo(int)
+ * @see #pathTo(org.openscience.cdk.interfaces.IAtom)
+ */
+ @TestMethod("testAtomsTo_Int_Simple,testAtomsTo_Int_Benzene,testAtomsTo_Int_Disconnected," +
+ "testAtomsTo_Int_OutOfBoundsIndex,testAtomsTo_Int_NegativeIndex")
+ public IAtom[] atomsTo(int end) {
+
+ int[] path = pathTo(end);
+ IAtom[] atoms = new IAtom[path.length];
+
+ // copy the atoms from the path indices to the array of atoms
+ for (int i = 0, n = path.length; i < n; i++)
+ atoms[i] = container.getAtom(path[i]);
+
+ return atoms;
+
+ }
+
+
+ /**
+ * Compute the first shortest path from the <i>start</i> atom to the
+ * provided <i>end</i> atom. The path is an inclusive fixed size array in
+ * order of the atoms to visit. If multiple shortest paths are available the
+ * first shortest path of atoms is determined by storage order of adjacent
+ * vertices.
+ *
+ * If there is no path to the <i>end</i> atom an empty array of atoms is
+ * returned. It is considered there to be no valid path if a) the atom
+ * belongs to the same container but is a member of a different fragment, or
+ * b) The atom not present in the container at all.
+ *
+ * <pre>{@code
+ * ShortestPaths sp = ...;
+ * IAtom end = ...;
+ *
+ * // get first path
+ * IAtom[] path = sp.atomsTo(end);
+ *
+ * // it can be more robust to check if there is only one path
+ * if(sp.nPathsTo(end) == 1){
+ * IAtom[] path = sp.atomsTo(end); // get the first path
+ * }
+ * }</pre>
+ *
+ * @param end the <i>end</i> atom to find a path to
+ * @return path from the <i>start</i> to the <i>end</i> atoms as fixed size
+ * array of {@link org.openscience.cdk.interfaces.IAtom}s.
+ * @see #atomsTo(int)
+ * @see #pathTo(int)
+ * @see #pathTo(org.openscience.cdk.interfaces.IAtom)
+ */
+ @TestMethod("testAtomsTo_Atom_Simple,testAtomsTo_Atom_Benzene,testAtomsTo_Atom_Disconnected," +
+ "testAtomsTo_Atom_MissingAtom,testAtomsTo_Atom_Null")
+ public IAtom[] atomsTo(IAtom end) {
+ return atomsTo(container.getAtomNumber(end));
+ }
+
+
+ /**
+ * Access the number of possible paths from the <i>start</i> to the
+ * <i>end</i> vertex. If there is no valid path to the <i>end</i> the number
+ * of paths is 0. It is considered there to be no valid path if a) the
+ * vertex belongs to the same container but is a member of a different
+ * fragment, or b) The vertex not present in the container at all.
+ *
+ * <pre>
+ * {@code
+ * ShortestPaths sp = ...;
+ *
+ * sp.nPathsTo(5); // number of paths to vertex 5
+ *
+ * sp.nPathsTo(-1); // returns 0 - there are no paths
+ * }
+ * </pre>
+ *
+ * @param end the <i>end</i> vertex to which the number of paths will be
+ * returned
+ * @return the number of paths to the end vertex
+ */
+ @TestMethod("testNPathsTo_Int_Simple,testNPathsTo_Int_Benzene,testNPathsTo_Int_Norbornane," +
+ "testNPathsTo_Int_Spiroundecane,testNPathsTo_Int_Pentadecaspiro," +
+ "testNPathsTo_Int_Disconnected," +
+ "testNPathsTo_Int_OutOfBoundIndex,testNPathsTo_Int_NegativeIndex")
+ public int nPathsTo(int end) {
+ return (end < 0 || end >= nPathsTo.length) ? 0 : nPathsTo[end];
+ }
+
+
+ /**
+ * Access the number of possible paths from the <i>start</i> to the
+ * <i>end</i> atom. If there is no valid path to the <i>end</i> the number
+ * of paths is 0. It is considered there to be no valid path if a) the atom
+ * belongs to the same container but is a member of a different fragment, or
+ * b) The atom not present in the container at all.
+ *
+ * <pre>
+ * {@code
+ * ShortestPaths sp = ...;
+ * IAtom end = ...l
+ *
+ * sp.nPathsTo(end); // number of paths to vertex end
+ *
+ * sp.nPathsTo(null); // returns 0 - there are no paths
+ * sp.nPathsTo(new Atom("C")); // returns 0 - there are no paths
+ * }
+ * </pre>
+ *
+ * @param end the <i>end</i> vertex to which the number of paths will be
+ * returned
+ * @return the number of paths to the end vertex
+ */
+ @TestMethod("testNPathsTo_Atom_Simple,testNPathsTo_Atom_Benzene,testNPathsTo_Atom_Norbornane," +
+ "testNPathsTo_Atom_Spiroundecane,testNPathsTo_Atom_Pentadecaspiro," +
+ "testNPathsTo_Atom_Disconnected," +
+ "testNPathsTo_Atom_MissingAtom,testNPathsTo_Atom_Null")
+ public int nPathsTo(IAtom end) {
+ return nPathsTo(container.getAtomNumber(end));
+ }
+
+
+ /**
+ * Access the distance from the <i>start</i> to the given <i>end</i> vertex.
+ * If the two are not connected the distance is returned as {@link
+ * Integer#MAX_VALUE}. Formally, there is a path if the distance is less
+ * then the number of atoms.
+ *
+ * <pre>{@code
+ * IAtomContainer container = ...;
+ * ShortestPaths sp = ...; // start = 0
+ *
+ * int n = container.getAtomCount();
+ *
+ * if( sp.distanceTo(5) < n ) {
+ * // these is a path from 0 to 5
+ * }
+ *
+ * }</pre>
+ *
+ * Conveniently the distance is also the index of the last vertex in the
+ * path.
+ *
+ * <pre>{@code
+ * IAtomContainer container = ...;
+ * ShortestPaths sp = ...; // start = 0
+ *
+ * int path = sp.pathTo(5);
+ *
+ * int start = path[0];
+ * int end = path[sp.distanceTo(5)];
+ *
+ * }</pre>
+ *
+ * @param end vertex to measure the distance to
+ * @return distance to this vertex
+ * @see #distanceTo(org.openscience.cdk.interfaces.IAtom)
+ */
+ @TestMethod("testDistanceTo_Int_Simple,testDistanceTo_Int_OutOfBoundIndex," +
+ "testDistanceTo_Int_NegativeIndex,testDistanceTo_Int_Disconnected," +
+ "testDistanceTo_Int_Benzene,testDistanceTo_Int_Spiroundecane," +
+ "testDistanceTo_Int_Pentadecaspiro")
+ public int distanceTo(int end) {
+ return (end < 0 || end >= nPathsTo.length) ? Integer.MAX_VALUE : distTo[end];
+ }
+
+
+ /**
+ * Access the distance from the <i>start</i> to the given <i>end</i> atom.
+ * If the two are not connected the distance is returned as {@link
+ * Integer#MAX_VALUE}. Formally, there is a path if the distance is less
+ * then the number of atoms.
+ *
+ * <pre>{@code
+ * IAtomContainer container = ...;
+ * ShortestPaths sp = ...; // start atom
+ * IAtom end = ...;
+ *
+ * int n = container.getAtomCount();
+ *
+ * if( sp.distanceTo(end) < n ) {
+ * // these is a path from start to end
+ * }
+ *
+ * }</pre>
+ *
+ * Conveniently the distance is also the index of the last vertex in the
+ * path.
+ *
+ * <pre>{@code
+ * IAtomContainer container = ...;
+ * ShortestPaths sp = ...; // start atom
+ * IAtom end = ...;
+ *
+ * int atoms = sp.atomsTo(end);
+ * // end == atoms[sp.distanceTo(end)];
+ *
+ * }</pre>
+ *
+ * @param end atom to measure the distance to
+ * @return distance to the given atom
+ * @see #distanceTo(int)
+ */
+ @TestMethod("testDistanceTo_Atom_Simple,testDistanceTo_Atom_MissingAtom,testDistanceTo_Atom_Null," +
+ "testDistanceTo_Atom_Disconnected,testDistanceTo_Atom_Benzene," +
+ "testDistanceTo_Atom_Spiroundecane,testDistanceTo_Atom_Pentadecaspiro")
+ public int distanceTo(IAtom end) {
+ return distanceTo(container.getAtomNumber(end));
+ }
+
+
+ /**
+ * Helper class for building a route to the shortest path
+ */
+ private static interface Route {
+
+ /**
+ * Recursively convert this route to all possible shortest paths. The
+ * length is passed down the methods until the source is reached and the
+ * first path created
+ *
+ * @param n length of the path
+ * @return 2D array of all shortest paths
+ */
+ int[][] toPaths(int n);
+
+ /**
+ * Recursively convert this route to the first shortest path. The length
+ * is passed down the methods until the source is reached and the first
+ * path created
+ *
+ * @param n length of the path
+ * @return first shortest path
+ */
+ int[] toPath(int n);
+ }
+
+ /**
+ * The source of a route, the source is always the start atom.
+ */
+ private static class Source implements Route {
@egonw
egonw added a note

Is it worth defining separate classes here, rather than, e.g., using classes from JGraphT? Why not make the outer classes? Then they can be individually tested too...?

@johnmay Owner
johnmay added a note

Using classes from JGraphT? / Why not make the outer classes?
Encapsulation. They are an implementation detail and (currently) of no use outside this class. You can just as well do without them or easily change them without the client knowing.

Then they can be individually tested too...?
Nope - they're inner instance classes and depend on the state of the parent. Also as they're private we control the input into these (i.e. will never pass it a null).

@egonw
egonw added a note

OK on both.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ private final int v;
+
+ /**
+ * Create new source with a given vertex.
+ *
+ * @param v start vertex
+ */
+ public Source(int v) {
+ this.v = v;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public int[][] toPaths(int n) {
+ // only every one shortest path at source
+ return new int[][]{toPath(n)};
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public int[] toPath(int n) {
+ // create the path of the given length
+ // and set the vertex at the first index
+ int[] path = new int[n];
+ path[0] = v;
+ return path;
+ }
+
+ }
+
+ /**
+ * A sequential route is vertex appended to a parent route.
+ */
+ private class SequentialRoute implements Route {
+
+ private final int v;
+ private final Route parent;
+
+ /**
+ * Create a new sequential route from the parent and include the new
+ * vertex <i>v</i>.
+ *
+ * @param parent parent route
+ * @param v additional vertex
+ */
+ private SequentialRoute(Route parent, int v) {
+ this.v = v;
+ this.parent = parent;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public int[][] toPaths(int n) {
+
+ int[][] paths = parent.toPaths(n);
+ int i = distTo[v];
+
+ // for all paths from the parent set the vertex at the given index
+ for (int[] path : paths)
+ path[i] = v;
+
+ return paths;
+
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public int[] toPath(int n) {
+ int[] path = parent.toPath(n);
+ // for all paths from the parent set vertex at the correct index (given by distance)
+ path[distTo[v]] = v;
+ return path;
+ }
+
+ }
+
+ /**
+ * A more complex route which represents a branch in our path. A branch is
+ * composed of a left and a right route. A n-way branches can be constructed
+ * by simply nesting a branch within a branch.
+ */
+ private class Branch implements Route {
+
+ private final Route left, right;
+
+ /**
+ * Create a branch with a left and right
+ *
+ * @param left route to the left
+ * @param right route to the right
+ */
+ private Branch(Route left, Route right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public int[][] toPaths(int n) {
+
+ // get all shortest paths from the left and right
+ int[][] leftPaths = left.toPaths(n);
+ int[][] rightPaths = right.toPaths(n);
+
+ // expand the left paths to a capacity which can also accommodate the right paths also
@johnmay Owner
johnmay added a note

typo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ int[][] paths = Arrays.copyOf(leftPaths, leftPaths.length + rightPaths.length);
+
+ // copy the right paths in to the expanded left paths
+ System.arraycopy(rightPaths, 0, paths, leftPaths.length, rightPaths.length);
+
+ return paths;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public int[] toPath(int n) {
+ // use the left as the first path
+ return left.toPath(n);
+ }
+
+ }
+
+
+ /**
+ * Create an adjacency list representation of the atom container.
+ *
+ * Temporary method - until the adjacency list can be added as an actual
+ * graph type.
+ *
+ * @param container container to convert
+ * @return adjacency list representation
+ */
+ @TestMethod("testToAdjList,testToAdjList_resize,testToAdjList_missingAtom," +
+ "testToAdjList_Empty,testToAdjList_Null")
+ static int[][] toAdjList(IAtomContainer container) {
+
+ if (container == null)
+ throw new IllegalArgumentException("atom container was null");
+
+ int n = container.getAtomCount();
+
+ int[][] graph = new int[n][16];
+ int[] degree = new int[n];
+
+ for (IBond bond : container.bonds()) {
+
+ int v = container.getAtomNumber(bond.getAtom(0));
+ int w = container.getAtomNumber(bond.getAtom(1));
+
+ if (v < 0 || w < 0)
+ throw new IllegalArgumentException("bond at index " + container.getBondNumber(bond)
+ + " contained an atom not pressent in molecule");
+
+ graph[v][degree[v]++] = w;
+ graph[w][degree[w]++] = v;
+
+ // if the vertex degree of v or w reaches capacity, double the size
+ if (degree[v] == graph[v].length)
+ graph[v] = Arrays.copyOf(graph[v], degree[v] * 2);
+ if (degree[w] == graph[w].length)
+ graph[w] = Arrays.copyOf(graph[w], degree[w] * 2);
+ }
+
+ for (int v = 0; v < n; v++) {
+ graph[v] = Arrays.copyOf(graph[v], degree[v]);
+ }
+
+ return graph;
+
+ }
+
+
+}
View
169 src/test/org/openscience/cdk/graph/AllShortestPathsTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2012 John May <jwmay@users.sf.net>
+ *
+ * Contact: cdk-devel@lists.sourceforge.net
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ * All we ask is that proper credit is given for our work, which includes
+ * - but is not limited to - adding the above copyright notice to the beginning
+ * of your source code files, and to any copyright notice that you may distribute
+ * with programs based on this work.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+package org.openscience.cdk.graph;
+
+import org.junit.Test;
+import org.openscience.cdk.interfaces.IAtom;
+import org.openscience.cdk.interfaces.IAtomContainer;
+import org.openscience.cdk.silent.AtomContainer;
+import org.openscience.cdk.templates.MoleculeFactory;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author John May
+ * @cdk.module test-core
+ */
+public class AllShortestPathsTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstruction_Null() {
+ new AllShortestPaths(null);
+ }
+
+ @Test
+ public void testConstruction_Empty() {
+
+ AllShortestPaths asp = new AllShortestPaths(new AtomContainer());
+
+ // all vs all fro -10 -> 10
+ for (int i = -10; i < 10; i++) {
+ for (int j = -10; j < 10; j++) {
+
+ assertArrayEquals(new int[0][0], asp.from(i).pathsTo(0));
+ assertArrayEquals(new int[0], asp.from(i).pathTo(0));
+ assertArrayEquals(new IAtom[0], asp.from(i).atomsTo(0));
+
+ assertThat(asp.from(i).nPathsTo(j), is(0));
+ assertThat(asp.from(i).distanceTo(j), is(Integer.MAX_VALUE));
+
+ }
+ }
+
+
+ }
+
+
+ @Test
+ public void testFrom_Atom_Benzene() throws Exception {
+
+ IAtomContainer benzene = MoleculeFactory.makeBenzene();
+ AllShortestPaths asp = new AllShortestPaths(benzene);
+
+ IAtom c1 = benzene.getAtom(0);
+ IAtom c2 = benzene.getAtom(1);
+ IAtom c3 = benzene.getAtom(2);
+ IAtom c4 = benzene.getAtom(3);
+ IAtom c5 = benzene.getAtom(4);
+ IAtom c6 = benzene.getAtom(5);
+
+ // c2 - c3
+ // / \
+ // c1 c4
+ // \ /
+ // c6 - c5
+
+ assertNotNull(asp.from(c1));
+ assertNotNull(asp.from(c2));
+ assertNotNull(asp.from(c3));
+ assertNotNull(asp.from(c4));
+ assertNotNull(asp.from(c5));
+ assertNotNull(asp.from(c6));
+
+
+ {
+ IAtom[] expected = new IAtom[]{c1, c2, c3};
+ IAtom[] actual = asp.from(c1).atomsTo(c3);
+ assertArrayEquals(expected, actual);
+ }
+
+ {
+ IAtom[] expected = new IAtom[]{c3, c2, c1};
+ IAtom[] actual = asp.from(c3).atomsTo(c1);
+ assertArrayEquals(expected, actual);
+ }
+
+ {
+ IAtom[] expected = new IAtom[]{c1, c6, c5};
+ IAtom[] actual = asp.from(c1).atomsTo(c5);
+ assertArrayEquals(expected, actual);
+ }
+
+ {
+ IAtom[] expected = new IAtom[]{c5, c6, c1};
+ IAtom[] actual = asp.from(c5).atomsTo(c1);
+ assertArrayEquals(expected, actual);
+ }
+
+ }
+
+ @Test
+ public void testFrom_Int_Benzene() throws Exception {
+
+ IAtomContainer benzene = MoleculeFactory.makeBenzene();
+ AllShortestPaths asp = new AllShortestPaths(benzene);
+
+ // 1 - 2
+ // / \
+ // 0 3
+ // \ /
+ // 5 - 4
+
+ assertNotNull(asp.from(0));
+ assertNotNull(asp.from(1));
+ assertNotNull(asp.from(2));
+ assertNotNull(asp.from(3));
+ assertNotNull(asp.from(4));
+ assertNotNull(asp.from(5));
+
+
+ {
+ int[] expected = new int[]{0, 1, 2};
+ int[] actual = asp.from(0).pathTo(2);
+ assertArrayEquals(expected, actual);
+ }
+
+ {
+ int[] expected = new int[]{2, 1, 0};
+ int[] actual = asp.from(2).pathTo(0);
+ assertArrayEquals(expected, actual);
+ }
+
+ {
+ int[] expected = new int[]{0, 5, 4};
+ int[] actual = asp.from(0).pathTo(4);
+ assertArrayEquals(expected, actual);
+ }
+
+ {
+ int[] expected = new int[]{4, 5, 0};
+ int[] actual = asp.from(4).pathTo(0);
+ assertArrayEquals(expected, actual);
+ }
+
+ }
+}
View
1,799 src/test/org/openscience/cdk/graph/ShortestPathsTest.java
@@ -0,0 +1,1799 @@
+/*
+ * Copyright (C) 2012 John May <jwmay@users.sf.net>
+ *
+ * Contact: cdk-devel@lists.sourceforge.net
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ * All we ask is that proper credit is given for our work, which includes
+ * - but is not limited to - adding the above copyright notice to the beginning
+ * of your source code files, and to any copyright notice that you may distribute
+ * with programs based on this work.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+package org.openscience.cdk.graph;
+
+import org.junit.Test;
+import org.openscience.cdk.interfaces.IAtom;
+import org.openscience.cdk.interfaces.IAtomContainer;
+import org.openscience.cdk.interfaces.IBond;
+import org.openscience.cdk.interfaces.IChemObjectBuilder;
+import org.openscience.cdk.silent.Atom;
+import org.openscience.cdk.silent.AtomContainer;
+import org.openscience.cdk.silent.Bond;
+import org.openscience.cdk.silent.SilentChemObjectBuilder;
+import org.openscience.cdk.templates.MoleculeFactory;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertThat;
+
+/**
+ * unit tests for ShortestPaths.
+ * @author John May
+ * @cdk.module test-core
+ */
+public class ShortestPathsTest {
+
+ @Test
+ public void testConstructor_Container_Empty() {
+
+ ShortestPaths sp = new ShortestPaths(new AtomContainer(), new Atom());
+
+ assertArrayEquals(new int[0], sp.pathTo(1));
+ assertArrayEquals(new int[0][0], sp.pathsTo(1));
+ assertThat(sp.nPathsTo(1), is(0));
+ assertThat(sp.distanceTo(1), is(Integer.MAX_VALUE));
+
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructor_Container_Null() {
+ ShortestPaths sp = new ShortestPaths(null, new Atom());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructor_Container_MissingAtom() {
+ ShortestPaths sp = new ShortestPaths(MoleculeFactory.makeBenzene(), new Atom());
+ }
+
+ @Test
+ public void testPathTo_Atom_Simple() {
+
+ IAtomContainer simple = simple();
+
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+
+ assertArrayEquals(new int[]{0, 1}, paths.pathTo(simple.getAtom(1)));
+ assertArrayEquals(new int[]{0, 1, 2}, paths.pathTo(simple.getAtom(2)));
+ assertArrayEquals(new int[]{0, 1, 2, 3}, paths.pathTo(simple.getAtom(3)));
+ assertArrayEquals(new int[]{0, 1, 4}, paths.pathTo(simple.getAtom(4)));
+
+ }
+
+ @Test
+ public void testPathTo_Int_Simple() {
+
+ IAtomContainer simple = simple();
+
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+
+ assertArrayEquals(new int[]{0, 1}, paths.pathTo(1));
+ assertArrayEquals(new int[]{0, 1, 2}, paths.pathTo(2));
+ assertArrayEquals(new int[]{0, 1, 2, 3}, paths.pathTo(3));
+ assertArrayEquals(new int[]{0, 1, 4}, paths.pathTo(4));
+
+ }
+
+ /**
+ * ensures that when multiple paths are available, one path is still
+ * returned via {@link ShortestPaths#pathTo(org.openscience.cdk.interfaces.IAtom)}
+ */
+ @Test
+ public void testPathTo_Atom_Benzene() {
+
+ IAtomContainer simple = MoleculeFactory.makeBenzene();
+
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+
+ assertArrayEquals(new int[]{0, 1, 2, 3}, paths.pathTo(simple.getAtom(3)));
+
+ }
+
+ /**
+ * ensures that when multiple paths are available, one path is still
+ * returned via {@link ShortestPaths#pathTo(org.openscience.cdk.interfaces.IAtom)}
+ */
+ @Test
+ public void testPathTo_Int_Benzene() {
+
+ IAtomContainer simple = MoleculeFactory.makeBenzene();
+
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+
+ assertArrayEquals(new int[]{0, 1, 2, 3}, paths.pathTo(3));
+
+ }
+
+ /**
+ * ensures that when multiple paths are available, one path is still
+ * returned via {@link ShortestPaths#pathTo(org.openscience.cdk.interfaces.IAtom)}
+ */
+ @Test
+ public void testPathTo_Atom_Norbornane() {
+ IAtomContainer norbornane = norbornane();
+ ShortestPaths paths = new ShortestPaths(norbornane, norbornane.getAtom(0));
+ assertArrayEquals(new int[]{0, 1, 2, 3}, paths.pathTo(norbornane.getAtom(3)));
+ }
+
+ /**
+ * ensures that when multiple paths are available, one path is still
+ * returned via {@link ShortestPaths#pathTo(org.openscience.cdk.interfaces.IAtom)}
+ */
+ @Test
+ public void testPathTo_Int_Norbornane() {
+ IAtomContainer norbornane = norbornane();
+ ShortestPaths paths = new ShortestPaths(norbornane, norbornane.getAtom(0));
+ assertArrayEquals(new int[]{0, 1, 2, 3}, paths.pathTo(3));
+ }
+
+ /**
+ * ensures that when multiple paths are available, one path is still
+ * returned via {@link ShortestPaths#pathTo(org.openscience.cdk.interfaces.IAtom)}
+ */
+ @Test
+ public void testPathTo_Atom_Spiroundecane() {
+ IAtomContainer spiroundecane = spiroundecane();
+ ShortestPaths paths = new ShortestPaths(spiroundecane, spiroundecane.getAtom(1));
+ assertArrayEquals(new int[]{1, 0, 5, 4, 6, 10, 9}, paths.pathTo(spiroundecane.getAtom(9)));
+ }
+
+ /**
+ * ensures that when multiple paths are available, one path is still
+ * returned via {@link ShortestPaths#pathTo(org.openscience.cdk.interfaces.IAtom)}
+ */
+ @Test
+ public void testPathTo_Int_Spiroundecane() {
+ IAtomContainer spiroundecane = spiroundecane();
+ ShortestPaths paths = new ShortestPaths(spiroundecane, spiroundecane.getAtom(1));
+ assertArrayEquals(new int[]{1, 0, 5, 4, 6, 10, 9}, paths.pathTo(9));
+ }
+
+ /**
+ * ensures that when multiple paths are available, one path is still
+ * returned via {@link ShortestPaths#pathTo(org.openscience.cdk.interfaces.IAtom)}
+ */
+ @Test
+ public void testPathTo_Atom_Pentadecaspiro() {
+
+ // 3 - // ... // - 4
+ // / \ / \
+ // 0 x 1
+ // \ / \ /
+ // 2 - // ... // - 5
+
+ // bridgehead atoms 0, 66, 68, 70, 72, 74, 76, 78, 80, 79, 77, 75, 73, 71, 69, 67, 1
+
+ IAtomContainer pentadecaspiro = pentadecaspiro();
+ ShortestPaths paths = new ShortestPaths(pentadecaspiro, pentadecaspiro.getAtom(0));
+
+ // first path is determined by storage order and will always be the same
+ int[] expected = new int[]{0, 2, 6, 66, 10, 14, 68,
+ 18, 22, 70, 26, 30, 72,
+ 34, 38, 74, 42, 46, 76,
+ 50, 54, 78, 58, 62, 80,
+ 64, 60, 79, 56, 52, 77,
+ 48, 44, 75, 40, 36, 73,
+ 32, 28, 71, 24, 20, 69,
+ 16, 12, 67, 8, 4, 1};
+
+ int[] path = paths.pathTo(pentadecaspiro.getAtom(1));
+ assertArrayEquals(expected, path);
+
+ }
+
+ /**
+ * ensures that when multiple paths are available, one path is still
+ * returned via {@link ShortestPaths#pathTo(org.openscience.cdk.interfaces.IAtom)}
+ */
+ @Test
+ public void testPathTo_Int_Pentadecaspiro() {
+
+ // 3 - // ... // - 4
+ // / \ / \
+ // 0 x 1
+ // \ / \ /
+ // 2 - // ... // - 5
@egonw
egonw added a note

Can you perhaps additionally use @cdk.inchi in the JavaDoc? I am not sure I understand the drawing...

@johnmay Owner
johnmay added a note

I have :-) check the pentadecaspiro() method

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ // bridgehead atoms 0, 66, 68, 70, 72, 74, 76, 78, 80, 79, 77, 75, 73, 71, 69, 67, 1
+
+ IAtomContainer pentadecaspiro = pentadecaspiro();
+ ShortestPaths paths = new ShortestPaths(pentadecaspiro, pentadecaspiro.getAtom(0));
+
+ // first path is determined by storage order and will always be the same
+ int[] expected = new int[]{0, 2, 6, 66, 10, 14, 68,
+ 18, 22, 70, 26, 30, 72,
+ 34, 38, 74, 42, 46, 76,
+ 50, 54, 78, 58, 62, 80,
+ 64, 60, 79, 56, 52, 77,
+ 48, 44, 75, 40, 36, 73,
+ 32, 28, 71, 24, 20, 69,
+ 16, 12, 67, 8, 4, 1};
+
+ int[] path = paths.pathTo(1);
+ assertArrayEquals(expected, path);
+
+ }
+
+ @Test
+ public void testPathTo_Int_OutOfBoundsIndex() {
+ IAtomContainer simple = simple();
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+ assertArrayEquals(new int[0], paths.pathTo(20));
+ }
+
+ @Test
+ public void testPathTo_Int_NegativeIndex() {
+ IAtomContainer simple = simple();
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+ assertArrayEquals(new int[0], paths.pathTo(-1));
+ }
+
+ @Test
+ public void testPathTo_Atom_MissingAtom() {
+ IAtomContainer simple = simple();
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+ assertArrayEquals(new int[0], paths.pathTo(new Atom("C")));
+ }
+
+ @Test
+ public void testPathTo_Atom_Null() {
+ IAtomContainer simple = simple();
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+ assertArrayEquals(new int[0], paths.pathTo(null));
+ }
+
+ @Test
+ public void testPathTo_Atom_Disconnected() {
+
+ IAtomContainer simple = disconnected();
+
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+
+ assertArrayEquals(new int[]{0, 1}, paths.pathTo(simple.getAtom(1)));
+ assertArrayEquals(new int[]{0, 1, 2}, paths.pathTo(simple.getAtom(2)));
+ assertArrayEquals(new int[]{0, 1, 2, 3}, paths.pathTo(simple.getAtom(3)));
+ assertArrayEquals(new int[]{0, 1, 4}, paths.pathTo(simple.getAtom(4)));
+
+ // disconnect fragment should return 0 length path
+ assertArrayEquals(new int[0], paths.pathTo(simple.getAtom(5)));
+ assertArrayEquals(new int[0], paths.pathTo(simple.getAtom(6)));
+ assertArrayEquals(new int[0], paths.pathTo(simple.getAtom(7)));
+ assertArrayEquals(new int[0], paths.pathTo(simple.getAtom(8)));
+ assertArrayEquals(new int[0], paths.pathTo(simple.getAtom(9)));
+
+ }
+
+ @Test
+ public void testPathTo_Int_Disconnected() {
+
+ IAtomContainer simple = disconnected();
+
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+
+ assertArrayEquals(new int[]{0, 1}, paths.pathTo(1));
+ assertArrayEquals(new int[]{0, 1, 2}, paths.pathTo(2));
+ assertArrayEquals(new int[]{0, 1, 2, 3}, paths.pathTo(3));
+ assertArrayEquals(new int[]{0, 1, 4}, paths.pathTo(4));
+
+ // disconnect fragment should return 0 length path
+ assertArrayEquals(new int[0], paths.pathTo(5));
+ assertArrayEquals(new int[0], paths.pathTo(6));
+ assertArrayEquals(new int[0], paths.pathTo(7));
+ assertArrayEquals(new int[0], paths.pathTo(8));
+ assertArrayEquals(new int[0], paths.pathTo(9));
+
+ }
+
+ @Test
+ public void testPathsTo_Atom_Simple() {
+
+ IAtomContainer simple = simple();
+
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+
+ assertArrayEquals(new int[][]{{0, 1}}, paths.pathsTo(simple.getAtom(1)));
+ assertArrayEquals(new int[][]{{0, 1, 2}}, paths.pathsTo(simple.getAtom(2)));
+ assertArrayEquals(new int[][]{{0, 1, 2, 3}}, paths.pathsTo(simple.getAtom(3)));
+ assertArrayEquals(new int[][]{{0, 1, 4}}, paths.pathsTo(simple.getAtom(4)));
+
+ }
+
+ @Test
+ public void testPathsTo_Int_Simple() {
+
+ IAtomContainer simple = simple();
+
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+
+ assertArrayEquals(new int[][]{{0, 1}}, paths.pathsTo(1));
+ assertArrayEquals(new int[][]{{0, 1, 2}}, paths.pathsTo(2));
+ assertArrayEquals(new int[][]{{0, 1, 2, 3}}, paths.pathsTo(3));
+ assertArrayEquals(new int[][]{{0, 1, 4}}, paths.pathsTo(4));
+
+ }
+
+ @Test
+ public void testPathsTo_Atom_Benzene() {
+
+ IAtomContainer benzene = MoleculeFactory.makeBenzene();
+
+ ShortestPaths paths = new ShortestPaths(benzene, benzene.getAtom(0));
+
+ int[][] expected = new int[][]{{0, 1, 2, 3}, {0, 5, 4, 3}};
+ assertArrayEquals(expected, paths.pathsTo(benzene.getAtom(3)));
+
+ }
+
+
+ @Test
+ public void testPathsTo_Atom_Spiroundecane() {
+
+ IAtomContainer spiroundecane = spiroundecane();
+
+ ShortestPaths paths = new ShortestPaths(spiroundecane, spiroundecane.getAtom(1));
+
+ // 2 -- 3 7 -- 8
+ // / \ / \
+ // 1 4 9
+ // \ / \ /
+ // 0 -- 5 6 - 10
+
+ // path order is determined by storage order, given the same input,
+ // the output order will never change
+ int[][] expected = new int[][]{
+ {1, 0, 5, 4, 6, 10, 9},
+ {1, 2, 3, 4, 6, 10, 9},
+ {1, 0, 5, 4, 7, 8, 9},
+ {1, 2, 3, 4, 7, 8, 9}
+ };
+
+ assertArrayEquals(expected, paths.pathsTo(spiroundecane.getAtom(9)));
+
+ }
+
+ @Test
+ public void testPathsTo_Int_Spiroundecane() {
+
+ IAtomContainer spiroundecane = spiroundecane();
+
+ ShortestPaths paths = new ShortestPaths(spiroundecane, spiroundecane.getAtom(1));
+
+ // 2 -- 3 7 -- 8
+ // / \ / \
+ // 1 4 9
+ // \ / \ /
+ // 0 -- 5 6 - 10
+
+ // path order is determined by storage order, given the same input,
+ // the output order will never change
+ int[][] expected = new int[][]{
+ {1, 0, 5, 4, 6, 10, 9},
+ {1, 2, 3, 4, 6, 10, 9},
+ {1, 0, 5, 4, 7, 8, 9},
+ {1, 2, 3, 4, 7, 8, 9}
+ };
+
+ assertArrayEquals(expected, paths.pathsTo(9));
+
+ }
+
+ @Test
+ public void testPathsTo_Int_Benzene() {
+
+ IAtomContainer benzene = MoleculeFactory.makeBenzene();
+
+ ShortestPaths paths = new ShortestPaths(benzene, benzene.getAtom(0));
+
+ int[][] expected = new int[][]{{0, 1, 2, 3}, {0, 5, 4, 3}};
+ assertArrayEquals(expected, paths.pathsTo(3));
+
+ }
+
+
+ @Test
+ public void testPathsTo_Atom_Norbornane() {
+ IAtomContainer norbornane = norbornane();
+ ShortestPaths paths = new ShortestPaths(norbornane, norbornane.getAtom(0));
+ int[][] expected = new int[][]{{0, 1, 2, 3}, {0, 5, 4, 3}, {0, 6, 7, 3}};
+ assertArrayEquals(expected, paths.pathsTo(norbornane.getAtom(3)));
+ }
+
+ @Test
+ public void testPathsTo_Int_Norbornane() {
+ IAtomContainer norbornane = norbornane();
+ ShortestPaths paths = new ShortestPaths(norbornane, norbornane.getAtom(0));
+ int[][] expected = new int[][]{{0, 1, 2, 3}, {0, 5, 4, 3}, {0, 6, 7, 3}};
+ assertArrayEquals(expected, paths.pathsTo(3));
+ }
+
+ @Test
+ public void testPathsTo_Atom_Pentadecaspiro() {
+
+ // 3 - // ... // - 4
+ // / \ / \
+ // 0 x 1
+ // \ / \ /
+ // 2 - // ... // - 5
+
+ // bridgehead atoms 0, 66, 68, 70, 72, 74, 76, 78, 80, 79, 77, 75, 73, 71, 69, 67, 1
+
+ IAtomContainer pentadecaspiro = pentadecaspiro();
+ ShortestPaths paths = new ShortestPaths(pentadecaspiro, pentadecaspiro.getAtom(0));
+
+ int[] bridgeheads = new int[]{66, 68, 70, 72, 74, 76, 78, 80, 79, 77, 75, 73, 71, 69, 67, 1};
+
+ // demonstrates that all paths up and beyond 65,000+ can be retrieved
+ for (int i = 0; i < bridgeheads.length; i++) {
+
+ int bridgehead = bridgeheads[i];
+
+ int[][] path = paths.pathsTo(pentadecaspiro.getAtom(bridgehead));
+
+ int n = (int) Math.pow(2, (i + 1));
+ assertThat(path.length, is(n));
+
+ // test is too long when more then 500 different paths
+ if (n < 500) {
+ for (int j = 0; j < n; j++) {
+
+ // check the first atom is '0' and the last atom is the 'bridgehead'
+ assertThat(path[j][0], is(0));
+ assertThat(path[j][paths.distanceTo(bridgehead)], is(bridgehead));
+
+ // check all paths are unique
+ for (int k = j + 1; k < n; k++) {
+ // hamcrest matcher does array comparison
+ assertThat(path[j], is(not(equalTo(path[k]))));
+ }
+ }
+ }
+
+ }
+
+
+ }
+
+ @Test
+ public void testPathsTo_Int_OutOfBoundsIndex() {
+ IAtomContainer simple = simple();
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+ assertArrayEquals(new int[0][0], paths.pathsTo(20));
+ }
+
+ @Test
+ public void testPathsTo_Int_NegativeIndex() {
+ IAtomContainer simple = simple();
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+ assertArrayEquals(new int[0][0], paths.pathsTo(-1));
+ }
+
+ @Test
+ public void testPathsTo_Atom_MissingAtom() {
+ IAtomContainer simple = simple();
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+ assertArrayEquals(new int[0][0], paths.pathsTo(new Atom("C")));
+ }
+
+ @Test
+ public void testPathsTo_Atom_Null() {
+ IAtomContainer simple = simple();
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+ assertArrayEquals(new int[0][0], paths.pathsTo(null));
+ }
+
+ @Test
+ public void testPathsTo_Atom_Disconnected() {
+
+ IAtomContainer simple = disconnected();
+
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+
+ assertArrayEquals(new int[][]{{0, 1}}, paths.pathsTo(simple.getAtom(1)));
+ assertArrayEquals(new int[][]{{0, 1, 2}}, paths.pathsTo(simple.getAtom(2)));
+ assertArrayEquals(new int[][]{{0, 1, 2, 3}}, paths.pathsTo(simple.getAtom(3)));
+ assertArrayEquals(new int[][]{{0, 1, 4}}, paths.pathsTo(simple.getAtom(4)));
+
+ // disconnect fragment should return 0 length path
+ assertArrayEquals(new int[0][0], paths.pathsTo(simple.getAtom(5)));
+ assertArrayEquals(new int[0][0], paths.pathsTo(simple.getAtom(6)));
+ assertArrayEquals(new int[0][0], paths.pathsTo(simple.getAtom(7)));
+ assertArrayEquals(new int[0][0], paths.pathsTo(simple.getAtom(8)));
+ assertArrayEquals(new int[0][0], paths.pathsTo(simple.getAtom(9)));
+
+ }
+
+ @Test
+ public void testPathsTo_Int_Disconnected() {
+
+ IAtomContainer simple = disconnected();
+
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+
+ assertArrayEquals(new int[][]{{0, 1}}, paths.pathsTo(1));
+ assertArrayEquals(new int[][]{{0, 1, 2}}, paths.pathsTo(2));
+ assertArrayEquals(new int[][]{{0, 1, 2, 3}}, paths.pathsTo(3));
+ assertArrayEquals(new int[][]{{0, 1, 4}}, paths.pathsTo(4));
+
+ // disconnect fragment should return 0 length path
+ assertArrayEquals(new int[0][0], paths.pathsTo(5));
+ assertArrayEquals(new int[0][0], paths.pathsTo(6));
+ assertArrayEquals(new int[0][0], paths.pathsTo(7));
+ assertArrayEquals(new int[0][0], paths.pathsTo(8));
+ assertArrayEquals(new int[0][0], paths.pathsTo(9));
+
+ }
+
+ @Test
+ public void testAtomsTo_Atom_Simple() {
+
+ IAtomContainer simple = simple();
+
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+
+ IAtom a = simple.getAtom(0);
+ IAtom b = simple.getAtom(1);
+ IAtom c = simple.getAtom(2);
+ IAtom d = simple.getAtom(3);
+ IAtom e = simple.getAtom(4);
+
+ assertArrayEquals(new IAtom[]{a, b}, paths.atomsTo(b));
+ assertArrayEquals(new IAtom[]{a, b, c}, paths.atomsTo(c));
+ assertArrayEquals(new IAtom[]{a, b, c, d}, paths.atomsTo(d));
+ assertArrayEquals(new IAtom[]{a, b, e}, paths.atomsTo(e));
+
+ }
+
+ @Test
+ public void testAtomsTo_Int_Simple() {
+
+ IAtomContainer simple = simple();
+
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+
+ IAtom a = simple.getAtom(0);
+ IAtom b = simple.getAtom(1);
+ IAtom c = simple.getAtom(2);
+ IAtom d = simple.getAtom(3);
+ IAtom e = simple.getAtom(4);
+
+ assertArrayEquals(new IAtom[]{a, b}, paths.atomsTo(1));
+ assertArrayEquals(new IAtom[]{a, b, c}, paths.atomsTo(2));
+ assertArrayEquals(new IAtom[]{a, b, c, d}, paths.atomsTo(3));
+ assertArrayEquals(new IAtom[]{a, b, e}, paths.atomsTo(4));
+
+
+ }
+
+ /**
+ * ensures that when multiple paths are available, one path is still
+ * returned via {@link ShortestPaths#pathTo(org.openscience.cdk.interfaces.IAtom)}
+ */
+ @Test
+ public void testAtomsTo_Atom_Benzene() {
+
+ IAtomContainer simple = MoleculeFactory.makeBenzene();
+
+ IAtom c1 = simple.getAtom(0);
+ IAtom c2 = simple.getAtom(1);
+ IAtom c3 = simple.getAtom(2);
+ IAtom c4 = simple.getAtom(3);
+
+ ShortestPaths paths = new ShortestPaths(simple, c1);
+
+ assertArrayEquals(new IAtom[]{c1, c2, c3, c4}, paths.atomsTo(c4));
+
+ }
+
+ /**
+ * ensures that when multiple paths are available, one path is still
+ * returned via {@link ShortestPaths#pathTo(org.openscience.cdk.interfaces.IAtom)}
+ */
+ @Test
+ public void testAtomsTo_Int_Benzene() {
+
+ IAtomContainer simple = MoleculeFactory.makeBenzene();
+
+ IAtom c1 = simple.getAtom(0);
+ IAtom c2 = simple.getAtom(1);
+ IAtom c3 = simple.getAtom(2);
+ IAtom c4 = simple.getAtom(3);
+
+ ShortestPaths paths = new ShortestPaths(simple, c1);
+
+ assertArrayEquals(new IAtom[]{c1, c2, c3, c4}, paths.atomsTo(3));
+
+ }
+
+
+ @Test
+ public void testAtomsTo_Atom_Disconnected() {
+
+ IAtomContainer simple = disconnected();
+
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+
+ IAtom a = simple.getAtom(0);
+ IAtom b = simple.getAtom(1);
+ IAtom c = simple.getAtom(2);
+ IAtom d = simple.getAtom(3);
+ IAtom e = simple.getAtom(4);
+ IAtom f = simple.getAtom(5);
+ IAtom g = simple.getAtom(6);
+ IAtom h = simple.getAtom(7);
+ IAtom i = simple.getAtom(8);
+ IAtom j = simple.getAtom(9);
+
+ assertArrayEquals(new IAtom[]{a, b}, paths.atomsTo(b));
+ assertArrayEquals(new IAtom[]{a, b, c}, paths.atomsTo(c));
+ assertArrayEquals(new IAtom[]{a, b, c, d}, paths.atomsTo(d));
+ assertArrayEquals(new IAtom[]{a, b, e}, paths.atomsTo(e));
+
+ // disconnect fragment should return 0 length path
+ assertArrayEquals(new IAtom[0], paths.atomsTo(f));
+ assertArrayEquals(new IAtom[0], paths.atomsTo(g));
+ assertArrayEquals(new IAtom[0], paths.atomsTo(h));
+ assertArrayEquals(new IAtom[0], paths.atomsTo(i));
+ assertArrayEquals(new IAtom[0], paths.atomsTo(j));
+
+ }
+
+ @Test
+ public void testAtomsTo_Int_Disconnected() {
+
+ IAtomContainer simple = disconnected();
+
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+
+ IAtom a = simple.getAtom(0);
+ IAtom b = simple.getAtom(1);
+ IAtom c = simple.getAtom(2);
+ IAtom d = simple.getAtom(3);
+ IAtom e = simple.getAtom(4);
+
+ assertArrayEquals(new IAtom[]{a, b}, paths.atomsTo(1));
+ assertArrayEquals(new IAtom[]{a, b, c}, paths.atomsTo(2));
+ assertArrayEquals(new IAtom[]{a, b, c, d}, paths.atomsTo(3));
+ assertArrayEquals(new IAtom[]{a, b, e}, paths.atomsTo(4));
+
+ // disconnect fragment should return 0 length path
+ assertArrayEquals(new IAtom[0], paths.atomsTo(5));
+ assertArrayEquals(new IAtom[0], paths.atomsTo(6));
+ assertArrayEquals(new IAtom[0], paths.atomsTo(7));
+ assertArrayEquals(new IAtom[0], paths.atomsTo(8));
+ assertArrayEquals(new IAtom[0], paths.atomsTo(9));
+
+ }
+
+ @Test
+ public void testAtomsTo_Int_OutOfBoundsIndex() {
+ IAtomContainer simple = simple();
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+ assertArrayEquals(new IAtom[0], paths.atomsTo(20));
+ }
+
+ @Test
+ public void testAtomsTo_Int_NegativeIndex() {
+ IAtomContainer simple = simple();
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+ assertArrayEquals(new IAtom[0], paths.atomsTo(-1));
+ }
+
+ @Test
+ public void testAtomsTo_Atom_MissingAtom() {
+ IAtomContainer simple = simple();
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+ assertArrayEquals(new IAtom[0], paths.atomsTo(new Atom("C")));
+ }
+
+ @Test
+ public void testAtomsTo_Atom_Null() {
+ IAtomContainer simple = simple();
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+ assertArrayEquals(new IAtom[0], paths.atomsTo(null));
+ }
+
+ @Test
+ public void testNPathsTo_Atom_Simple() {
+
+ IAtomContainer simple = simple();
+
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+
+ assertThat(paths.nPathsTo(simple.getAtom(0)), is(1));
+ assertThat(paths.nPathsTo(simple.getAtom(1)), is(1));
+ assertThat(paths.nPathsTo(simple.getAtom(2)), is(1));
+ assertThat(paths.nPathsTo(simple.getAtom(3)), is(1));
+ assertThat(paths.nPathsTo(simple.getAtom(4)), is(1));
+
+ }
+
+ @Test
+ public void testNPathsTo_Int_Simple() {
+
+ IAtomContainer simple = simple();
+
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+
+ assertThat(paths.nPathsTo(1), is(1));
+ assertThat(paths.nPathsTo(2), is(1));
+ assertThat(paths.nPathsTo(3), is(1));
+ assertThat(paths.nPathsTo(4), is(1));
+
+ }
+
+ @Test
+ public void testNPathsTo_Atom_MissingAtom() {
+ IAtomContainer simple = simple();
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+ assertThat(paths.nPathsTo(new Atom("C")), is(0));
+ }
+
+ @Test
+ public void testNPathsTo_Atom_Null() {
+ IAtomContainer simple = simple();
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+ assertThat(paths.nPathsTo(null), is(0));
+ }
+
+ @Test
+ public void testNPathsTo_Int_OutOfBoundIndex() {
+ IAtomContainer simple = simple();
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+ assertThat(paths.nPathsTo(20), is(0));
+ }
+
+ @Test
+ public void testNPathsTo_Int_NegativeIndex() {
+ IAtomContainer simple = simple();
+ ShortestPaths paths = new ShortestPaths(simple, simple.getAtom(0));
+ assertThat(paths.nPathsTo(-1), is(0));
+ }
+
+ @Test
+ public void testNPathsTo_Atom_Disconnected() {
+
+ IAtomContainer container = disconnected();
+
+ ShortestPaths paths = new ShortestPaths(container, container.getAtom(0));
+
+ assertThat(paths.nPathsTo(container.getAtom(0)), is(1));
+ assertThat(paths.nPathsTo(container.getAtom(1)), is(1));
+ assertThat(paths.nPathsTo(container.getAtom(2)), is(1));
+ assertThat(paths.nPathsTo(container.getAtom(3)), is(1));
+ assertThat(paths.nPathsTo(container.getAtom(4)), is(1));
+
+ assertThat(paths.nPathsTo(container.getAtom(5)), is(0));
+ assertThat(paths.nPathsTo(container.getAtom(6)), is(0));
+ assertThat(paths.nPathsTo(container.getAtom(7)), is(0));
+ assertThat(paths.nPathsTo(container.getAtom(8)), is(0));
+ assertThat(paths.nPathsTo(container.getAtom(9)), is(0));
+
+ }
+
+ @Test
+ public void testNPathsTo_Int_Disconnected() {
+
+ IAtomContainer container = disconnected();
+
+ ShortestPaths paths = new ShortestPaths(container, container.getAtom(0));
+
+ assertThat(paths.nPathsTo(0), is(1));
+ assertThat(paths.nPathsTo(1), is(1));
+ assertThat(paths.nPathsTo(2), is(1));
+ assertThat(paths.nPathsTo(3), is(1));
+ assertThat(paths.nPathsTo(4), is(1));
+
+ assertThat(paths.nPathsTo(5), is(0));
+ assertThat(paths.nPathsTo(6), is(0));
+ assertThat(paths.nPathsTo(7), is(0));
+ assertThat(paths.nPathsTo(8), is(0));
+ assertThat(paths.nPathsTo(9), is(0));
+
+ }
+
+ @Test
+ public void testNPathsTo_Atom_Benzene() {
+
+ IAtomContainer benzene = MoleculeFactory.makeBenzene();
+
+ ShortestPaths paths = new ShortestPaths(benzene, benzene.getAtom(0));
+
+ assertThat(paths.nPathsTo(benzene.getAtom(0)), is(1));
+ assertThat(paths.nPathsTo(benzene.getAtom(1)), is(1));
+ assertThat(paths.nPathsTo(benzene.getAtom(2)), is(1));
+ assertThat(paths.nPathsTo(benzene.getAtom(3)), is(2));
+ assertThat(paths.nPathsTo(benzene.getAtom(4)), is(1));
+ assertThat(paths.nPathsTo(benzene.getAtom(5)), is(1));
+
+ }
+
+ @Test
+ public void testNPathsTo_Int_Benzene() {
+
+ IAtomContainer benzene = MoleculeFactory.makeBenzene();
+
+ ShortestPaths paths = new ShortestPaths(benzene, benzene.getAtom(0));
+
+ assertThat(paths.nPathsTo(0), is(1));
+ assertThat(paths.nPathsTo(1), is(1));
+ assertThat(paths.nPathsTo(2), is(1));
+ assertThat(paths.nPathsTo(3), is(2));
+ assertThat(paths.nPathsTo(4), is(1));
+ assertThat(paths.nPathsTo(5), is(1));
+
+ }
+
+
+ @Test
+ public void testNPathsTo_Atom_Norbornane() {
+
+ IAtomContainer norbornane = norbornane();
+
+ ShortestPaths paths = new ShortestPaths(norbornane, norbornane.getAtom(0));
+
+ assertThat(paths.nPathsTo(norbornane.getAtom(0)), is(1));
+ assertThat(paths.nPathsTo(norbornane.getAtom(1)), is(1));
+ assertThat(paths.nPathsTo(norbornane.getAtom(2)), is(1));
+ assertThat(paths.nPathsTo(norbornane.getAtom(3)), is(3));
+ assertTha