Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
John May authored
553 src/main/org/openscience/cdk/graph/AllShortestPaths.java
... ... @@ -0,0 +1,553 @@
  1 +/*
  2 + * Copyright (C) 2012 John May <jwmay@users.sf.net>
  3 + *
  4 + * Contact: cdk-devel@lists.sourceforge.net
  5 + *
  6 + * This program is free software; you can redistribute it and/or
  7 + * modify it under the terms of the GNU Lesser General Public License
  8 + * as published by the Free Software Foundation; either version 2.1
  9 + * of the License, or (at your option) any later version.
  10 + * All we ask is that proper credit is given for our work, which includes
  11 + * - but is not limited to - adding the above copyright notice to the beginning
  12 + * of your source code files, and to any copyright notice that you may distribute
  13 + * with programs based on this work.
  14 + *
  15 + * This program is distributed in the hope that it will be useful,
  16 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18 + * GNU Lesser General Public License for more details.
  19 + *
  20 + * You should have received a copy of the GNU Lesser General Public License
  21 + * along with this program; if not, write to the Free Software
  22 + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  23 + */
  24 +package org.openscience.cdk.graph;
  25 +
  26 +import org.openscience.cdk.annotations.TestClass;
  27 +import org.openscience.cdk.annotations.TestMethod;
  28 +import org.openscience.cdk.interfaces.IAtom;
  29 +import org.openscience.cdk.interfaces.IAtomContainer;
  30 +import org.openscience.cdk.interfaces.IBond;
  31 +import org.openscience.cdk.interfaces.IChemObjectBuilder;
  32 +import org.openscience.cdk.interfaces.IChemObjectChangeEvent;
  33 +import org.openscience.cdk.interfaces.IChemObjectListener;
  34 +import org.openscience.cdk.interfaces.IElectronContainer;
  35 +import org.openscience.cdk.interfaces.ILonePair;
  36 +import org.openscience.cdk.interfaces.ISingleElectron;
  37 +import org.openscience.cdk.interfaces.IStereoElement;
  38 +
  39 +import java.util.List;
  40 +import java.util.Map;
  41 +
  42 +/**
  43 + * Determine the shortest paths from all atoms in a molecule.
  44 + *
  45 + *
  46 + * <pre>{@code
  47 + * IAtomContainer benzene = MoleculeFactory.makeBenzene();
  48 + * AllShortestPaths shortestPaths = new AllShortestPaths(benzene);
  49 + *
  50 + * for (int i = 0; i < benzene.getAtomCount(); i++) {
  51 + *
  52 + * // only to half the comparisons, we can reverse the
  53 + * // path[] to get all j to i
  54 + * for (int j = i + 1; j < benzene.getAtomCount(); j++) {
  55 + *
  56 + * // compute shortest path from i to j
  57 + * int[] path = shortestPaths.from(i).pathTo(j);
  58 + *
  59 + * // compute all shortest paths from i to j
  60 + * int[][] paths = shortestPaths.from(i).pathsTo(j);
  61 + *
  62 + * // compute the atoms in the path from i to j
  63 + * IAtom[] atoms = shortestPaths.from(i).atomsTo(j);
  64 + *
  65 + * // access the number of paths from i to j
  66 + * int nPaths = shortestPaths.from(i).nPathsTo(j);
  67 + *
  68 + * // access the distance from i to j
  69 + * int distance = shortestPaths.from(i).nPathsTo(j);
  70 + *
  71 + * }
  72 + * }
  73 + * }</pre>
  74 + *
  75 + * @author John May
  76 + * @cdk.module core
  77 + * @see ShortestPaths
  78 + */
  79 +@TestClass("org.openscience.cdk.graph.AllShortestPathsTest")
  80 +public final class AllShortestPaths {
  81 +
  82 + private final IAtomContainer container;
  83 + private final ShortestPaths[] shortestPaths;
  84 +
  85 +
  86 + @TestMethod("testConstruction_Null,testConstruction_Empty")
  87 + public AllShortestPaths(IAtomContainer container) {
  88 +
  89 + // toAdjList performs null check
  90 + int[][] adjacent = ShortestPaths.toAdjList(container);
  91 +
  92 + int n = container.getAtomCount();
  93 +
  94 + this.container = container;
  95 + this.shortestPaths = new ShortestPaths[n];
  96 +
  97 + // for each atom construct the ShortestPaths object
  98 + for (int i = 0; i < n; i++) {
  99 + shortestPaths[i] = new ShortestPaths(adjacent, container, i);
  100 + }
  101 +
  102 + }
  103 +
  104 + /**
  105 + * Access the shortest paths object for provided start vertex.
  106 + *
  107 + * <pre>{@code
  108 + * AllShortestPaths asp = ...;
  109 + *
  110 + * // access explicitly
  111 + * ShortestPaths sp = asp.from(0);
  112 + *
  113 + * // or chain method calls
  114 + * int[] path = asp.from(0).pathTo(5);
  115 + * }</pre>
  116 + *
  117 + * @param start the start vertex of the path
  118 + * @return The shortest paths from the given state vertex
  119 + * @see ShortestPaths
  120 + */
  121 + @TestMethod("testFrom_Int_Benzene")
  122 + public ShortestPaths from(int start) {
  123 + return (start < 0 || start >= shortestPaths.length) ? EMPTY_SHORTEST_PATHS : shortestPaths[start];
  124 + }
  125 +
  126 + /**
  127 + * Access the shortest paths object for provided start atom.
  128 + *
  129 + * <pre>{@code
  130 + * AllShortestPaths asp = ...;
  131 + * IAtom start, end = ...;
  132 + *
  133 + * // access explicitly
  134 + * ShortestPaths sp = asp.from(start);
  135 + *
  136 + * // or chain the method calls together
  137 + *
  138 + * // first path from start to end atom
  139 + * int[] path = asp.from(start).pathTo(end);
  140 + *
  141 + * // first atom path from start to end atom
  142 + * IAtom[] atoms = asp.from(start).atomTo(end);
  143 + *
  144 + * }</pre>
  145 + *
  146 + * @param start the start atom of the path
  147 + * @return The shortest paths from the given state vertex
  148 + * @see ShortestPaths
  149 + */
  150 + @TestMethod("testFrom_Atom_Benzene")
  151 + public ShortestPaths from(IAtom start) {
  152 + // currently container.getAtomNumber() return -1 when null
  153 + return from(container.getAtomNumber(start));
  154 + }
  155 +
  156 +
  157 + /**
  158 + * an empty atom container so we can handle invalid vertices/atoms better. Note
  159 + * very pretty but we can't access the domain model from cdk-core.
  160 + */
  161 + private static final IAtomContainer EMPTY_CONTAINER = new IAtomContainer() {
  162 +
  163 + public void addStereoElement(IStereoElement element) {
  164 + }
  165 +
  166 + public void setStereoElements(List<IStereoElement> elements) {
  167 + }
  168 +
  169 + public Iterable<IStereoElement> stereoElements() {
  170 + throw new UnsupportedOperationException("not supported");
  171 + }
  172 +
  173 + public void setAtoms(IAtom[] atoms) {
  174 + }
  175 +
  176 + public void setBonds(IBond[] bonds) {
  177 + }
  178 +
  179 + public void setAtom(int number, IAtom atom) {
  180 + }
  181 +
  182 + public IAtom getAtom(int number) {
  183 + throw new UnsupportedOperationException("not supported");
  184 + }
  185 +
  186 + public IBond getBond(int number) {
  187 + throw new UnsupportedOperationException("not supported");
  188 + }
  189 +
  190 + public ILonePair getLonePair(int number) {
  191 + throw new UnsupportedOperationException("not supported");
  192 + }
  193 +
  194 + public ISingleElectron getSingleElectron(int number) {
  195 + throw new UnsupportedOperationException("not supported");
  196 + }
  197 +
  198 + public Iterable<IAtom> atoms() {
  199 + throw new UnsupportedOperationException("not supported");
  200 + }
  201 +
  202 + public Iterable<IBond> bonds() {
  203 + throw new UnsupportedOperationException("not supported");
  204 + }
  205 +
  206 +
  207 + public Iterable<ILonePair> lonePairs() {
  208 + throw new UnsupportedOperationException("not supported");
  209 + }
  210 +
  211 + public Iterable<ISingleElectron> singleElectrons() {
  212 + throw new UnsupportedOperationException("not supported");
  213 + }
  214 +
  215 + public Iterable<IElectronContainer> electronContainers() {
  216 + throw new UnsupportedOperationException("not supported");
  217 + }
  218 +
  219 + public IAtom getFirstAtom() {
  220 + throw new UnsupportedOperationException("not supported");
  221 + }
  222 +
  223 + public IAtom getLastAtom() {
  224 + throw new UnsupportedOperationException("not supported");
  225 + }
  226 +
  227 + public int getAtomNumber(IAtom atom) {
  228 + return -1;
  229 + }
  230 +
  231 + public int getBondNumber(IAtom atom1, IAtom atom2) {
  232 + return -1;
  233 + }
  234 +
  235 + public int getBondNumber(IBond bond) {
  236 + return -1;
  237 + }
  238 +
  239 + public int getLonePairNumber(ILonePair lonePair) {
  240 + return -1;
  241 + }
  242 +
  243 + public int getSingleElectronNumber(ISingleElectron singleElectron) {
  244 + return -1;
  245 + }
  246 +
  247 + public IElectronContainer getElectronContainer(int number) {
  248 + throw new UnsupportedOperationException("not supported");
  249 + }
  250 +
  251 + public IBond getBond(IAtom atom1, IAtom atom2) {
  252 + throw new UnsupportedOperationException("not supported");
  253 + }
  254 +
  255 + public int getAtomCount() {
  256 + return 0;
  257 + }
  258 +
  259 + public int getBondCount() {
  260 + return 0;
  261 + }
  262 +
  263 + public int getLonePairCount() {
  264 + return 0;
  265 + }
  266 +
  267 + public int getSingleElectronCount() {
  268 + return 0;
  269 + }
  270 +
  271 + public int getElectronContainerCount() {
  272 + return 0;
  273 + }
  274 +
  275 + public List<IAtom> getConnectedAtomsList(IAtom atom) {
  276 + throw new UnsupportedOperationException("not supported");
  277 + }
  278 +
  279 + public List<IBond> getConnectedBondsList(IAtom atom) {
  280 + throw new UnsupportedOperationException("not supported");
  281 + }
  282 +
  283 + public List<ILonePair> getConnectedLonePairsList(IAtom atom) {
  284 + throw new UnsupportedOperationException("not supported");
  285 + }
  286 +
  287 + public List<ISingleElectron> getConnectedSingleElectronsList(IAtom atom) {
  288 + throw new UnsupportedOperationException("not supported");
  289 + }
  290 +
  291 + public List<IElectronContainer> getConnectedElectronContainersList(IAtom atom) {
  292 + throw new UnsupportedOperationException("not supported");
  293 + }
  294 +
  295 + public int getConnectedAtomsCount(IAtom atom) {
  296 + throw new UnsupportedOperationException("not supported");
  297 + }
  298 +
  299 + public int getConnectedBondsCount(IAtom atom) {
  300 + throw new UnsupportedOperationException("not supported");
  301 + }
  302 +
  303 + public int getConnectedBondsCount(int atomnumber) {
  304 + return 0;
  305 + }
  306 +
  307 + public int getConnectedLonePairsCount(IAtom atom) {
  308 + return 0;
  309 + }
  310 +
  311 + public int getConnectedSingleElectronsCount(IAtom atom) {
  312 + return 0;
  313 + }
  314 +
  315 + public double getBondOrderSum(IAtom atom) {
  316 + return 0;
  317 + }
  318 +
  319 + public IBond.Order getMaximumBondOrder(IAtom atom) {
  320 + throw new UnsupportedOperationException("not supported");
  321 + }
  322 +
  323 + public IBond.Order getMinimumBondOrder(IAtom atom) {
  324 + throw new UnsupportedOperationException("not supported");
  325 + }
  326 +
  327 + public void add(IAtomContainer atomContainer) {
  328 +
  329 + }
  330 +
  331 + public void addAtom(IAtom atom) {
  332 +
  333 + }
  334 +
  335 + public void addBond(IBond bond) {
  336 +
  337 + }
  338 +
  339 + public void addLonePair(ILonePair lonePair) {
  340 +
  341 + }
  342 +
  343 + public void addSingleElectron(ISingleElectron singleElectron) {
  344 +
  345 + }
  346 +
  347 + public void addElectronContainer(IElectronContainer electronContainer) {
  348 +
  349 + }
  350 +
  351 + public void remove(IAtomContainer atomContainer) {
  352 +
  353 + }
  354 +
  355 + public void removeAtom(int position) {
  356 +
  357 + }
  358 +
  359 + public void removeAtom(IAtom atom) {
  360 +
  361 + }
  362 +
  363 + public IBond removeBond(int position) {
  364 + throw new UnsupportedOperationException("not supported");
  365 + }
  366 +
  367 + public IBond removeBond(IAtom atom1, IAtom atom2) {
  368 + throw new UnsupportedOperationException("not supported");
  369 + }
  370 +
  371 + public void removeBond(IBond bond) {
  372 +
  373 + }
  374 +
  375 + public ILonePair removeLonePair(int position) {
  376 + throw new UnsupportedOperationException("not supported");
  377 + }
  378 +
  379 + public void removeLonePair(ILonePair lonePair) {
  380 +
  381 + }
  382 +
  383 +
  384 + public ISingleElectron removeSingleElectron(int position) {
  385 + throw new UnsupportedOperationException("not supported");
  386 + }
  387 +
  388 + public void removeSingleElectron(ISingleElectron singleElectron) {
  389 +
  390 + }
  391 +
  392 + public IElectronContainer removeElectronContainer(int position) {
  393 + throw new UnsupportedOperationException("not supported");
  394 + }
  395 +
  396 + public void removeElectronContainer(IElectronContainer electronContainer) {
  397 +
  398 + }
  399 +
  400 + public void removeAtomAndConnectedElectronContainers(IAtom atom) {
  401 +
  402 + }
  403 +
  404 + public void removeAllElements() {
  405 +
  406 + }
  407 +
  408 + public void removeAllElectronContainers() {
  409 +
  410 + }
  411 +
  412 + public void removeAllBonds() {
  413 +
  414 + }
  415 +
  416 + public void addBond(int atom1, int atom2, IBond.Order order, IBond.Stereo stereo) {
  417 +
  418 + }
  419 +
  420 + public void addBond(int atom1, int atom2, IBond.Order order) {
  421 +
  422 + }
  423 +
  424 + public void addLonePair(int atomID) {
  425 +
  426 + }
  427 +
  428 + public void addSingleElectron(int atomID) {
  429 +
  430 + }
  431 +
  432 + public boolean contains(IAtom atom) {
  433 + return false;
  434 + }
  435 +
  436 + public boolean contains(IBond bond) {
  437 + return false;
  438 + }
  439 +
  440 + public boolean contains(ILonePair lonePair) {
  441 + return false;
  442 + }
  443 +
  444 + public boolean contains(ISingleElectron singleElectron) {
  445 + return false;
  446 + }
  447 +
  448 + public boolean contains(IElectronContainer electronContainer) {
  449 + return false;
  450 + }
  451 +
  452 + public boolean isEmpty() {
  453 + return true;
  454 + }
  455 +
  456 + public IAtomContainer clone() throws CloneNotSupportedException {
  457 + throw new UnsupportedOperationException("not supported");
  458 + }
  459 +
  460 + public void addListener(IChemObjectListener col) {
  461 +
  462 + }
  463 +
  464 + public int getListenerCount() {
  465 + return 0;
  466 + }
  467 +
  468 + public void removeListener(IChemObjectListener col) {
  469 +
  470 + }
  471 +
  472 + public void setNotification(boolean bool) {
  473 +
  474 + }
  475 +
  476 + public boolean getNotification() {
  477 + return false;
  478 + }
  479 +
  480 + public void notifyChanged() {
  481 +
  482 + }
  483 +
  484 + public void notifyChanged(IChemObjectChangeEvent evt) {
  485 +
  486 + }
  487 +
  488 + public void setProperty(Object description, Object property) {
  489 +
  490 + }
  491 +
  492 + public void removeProperty(Object description) {
  493 +
  494 + }
  495 +
  496 + public <T> T getProperty(Object description) {
  497 + throw new UnsupportedOperationException("not supported");
  498 + }
  499 +
  500 + public <T> T getProperty(Object description, Class<T> c) {
  501 + throw new UnsupportedOperationException("not supported");
  502 + }
  503 +
  504 + public Map<Object, Object> getProperties() {
  505 + return null;
  506 + }
  507 +
  508 + public String getID() {
  509 + throw new UnsupportedOperationException("not supported");
  510 + }
  511 +
  512 + public void setID(String identifier) {
  513 +
  514 + }
  515 +
  516 + public void setFlag(int mask, boolean value) {
  517 +
  518 + }
  519 +
  520 + public boolean getFlag(int mask) {
  521 + throw new UnsupportedOperationException("not supported");
  522 + }
  523 +
  524 + public void setProperties(Map<Object, Object> properties) {
  525 + }
  526 +
  527 + public void setFlags(boolean[] newFlags) {
  528 + }
  529 +
  530 + public boolean[] getFlags() {
  531 + return new boolean[0];
  532 + }
  533 +
  534 + public Number getFlagValue() {
  535 + return 0;
  536 + }
  537 +
  538 + public IChemObjectBuilder getBuilder() {
  539 + throw new UnsupportedOperationException("not supported");
  540 + }
  541 +
  542 + public void stateChanged(IChemObjectChangeEvent event) {
  543 +
  544 + }
  545 + };
  546 +
  547 + /**
  548 + * pseudo shortest-paths - when an invalid atom is given. this will always
  549 + * return 0 length paths and distances.
  550 + */
  551 + private static final ShortestPaths EMPTY_SHORTEST_PATHS = new ShortestPaths(new int[0][0], EMPTY_CONTAINER, 0);
  552 +
  553 +}
7 src/main/org/openscience/cdk/graph/PathTools.java
@@ -450,8 +450,15 @@ public static int getVertexCountAtDistance(IAtomContainer atomContainer, int dis
450 450 * @param end The ending atom
451 451 * @return A <code>List</code> containing the atoms in the shortest path between <code>start</code> and
452 452 * <code>end</code> inclusive
  453 + * @see ShortestPaths
  454 + * @see ShortestPaths#atomsTo(IAtom)
  455 + * @see AllShortestPaths
  456 + * @deprecated This implementation recalculates all shortest paths from the start atom
  457 + * for each method call and does not indicate if there are equally short paths
  458 + * from the start to the end. Replaced by {@link ShortestPaths#atomsTo(IAtom)}
453 459 */
454 460 @TestMethod("testGetShortestPath_IAtomContainer_IAtom_IAtom")
  461 + @Deprecated
455 462 public static List<IAtom> getShortestPath(IAtomContainer atomContainer, IAtom start, IAtom end) {
456 463 int natom = atomContainer.getAtomCount();
457 464 int endNumber = atomContainer.getAtomNumber(end);
812 src/main/org/openscience/cdk/graph/ShortestPaths.java
... ... @@ -0,0 +1,812 @@
  1 +/*
  2 + * Copyright (C) 2012 John May <jwmay@users.sf.net>
  3 + *
  4 + * Contact: cdk-devel@lists.sourceforge.net
  5 + *
  6 + * This program is free software; you can redistribute it and/or
  7 + * modify it under the terms of the GNU Lesser General Public License
  8 + * as published by the Free Software Foundation; either version 2.1
  9 + * of the License, or (at your option) any later version.
  10 + * All we ask is that proper credit is given for our work, which includes
  11 + * - but is not limited to - adding the above copyright notice to the beginning
  12 + * of your source code files, and to any copyright notice that you may distribute
  13 + * with programs based on this work.
  14 + *
  15 + * This program is distributed in the hope that it will be useful,
  16 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18 + * GNU Lesser General Public License for more details.
  19 + *
  20 + * You should have received a copy of the GNU Lesser General Public License
  21 + * along with this program; if not, write to the Free Software
  22 + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  23 + */
  24 +package org.openscience.cdk.graph;
  25 +
  26 +import org.openscience.cdk.annotations.TestClass;
  27 +import org.openscience.cdk.annotations.TestMethod;
  28 +import org.openscience.cdk.interfaces.IAtom;
  29 +import org.openscience.cdk.interfaces.IAtomContainer;
  30 +import org.openscience.cdk.interfaces.IBond;
  31 +
  32 +import java.util.ArrayList;
  33 +import java.util.Arrays;
  34 +import java.util.List;
  35 +
  36 +/**
  37 + * Determine all the shortest paths from a given start atom to any other
  38 + * connected atom. <p/>
  39 + *
  40 + * The number of shortest paths ({@link #nPathsTo(int)}) and the distance
  41 + * ({@link #distanceTo(int)}) can be accessed before building all the paths.
  42 + * When no path is found (i.e. not-connected) an empty path is returned. <p/>
  43 + *
  44 + * <pre>{@code
  45 + * IAtomContainer benzene = MoleculeFactory.makeBenzene();
  46 + *
  47 + * IAtom c1 = benzene.getAtom(0);
  48 + * IAtom c4 = benzene.getAtom(3);
  49 + *
  50 + * // shortest paths from c1
  51 + * ShortestPaths sp = new ShortestPaths(benzene, c1);
  52 + *
  53 + * // count the number of paths from C1 to C4
  54 + * int nPaths = sp.nPathsTo(c4);
  55 + *
  56 + * // check the distance between C1 to C4
  57 + * int distance = sp.distanceTo(c4);
  58 + *
  59 + * // access the first path to the C4
  60 + * // note: first path is determined by storage order
  61 + * int[] path = sp.pathTo(c4);
  62 + *
  63 + * // access both paths
  64 + * int[][] paths = sp.pathsTo(c4);
  65 + * int[] org = paths[0]; // paths[0] == path
  66 + * int[] alt = paths[1];
  67 + * }</pre>
  68 + *
  69 + * <p/> If shortest paths from multiple start atoms are required {@link
  70 + * AllShortestPaths} will have a small performance advantage. Please use {@link
  71 + * org.openscience.cdk.graph.matrix.TopologicalMatrix} if only the shortest
  72 + * distances between atoms is required.
  73 + *
  74 + * @author John May
  75 + * @cdk.module core
  76 + * @see AllShortestPaths
  77 + * @see org.openscience.cdk.graph.matrix.TopologicalMatrix
  78 + */
  79 +@TestClass("org.openscience.cdk.graph.ShortestPathsTest")
  80 +public final class ShortestPaths {
  81 +
  82 + /* empty path when no valid path was found */
  83 + private static final int[] EMPTY_PATH = new int[0];
  84 +
  85 + /* empty path when no valid path was found */
  86 + private static final int[][] EMPTY_PATHS = new int[0][];
  87 +
  88 + /* route to each vertex */
  89 + private final Route[] routeTo;
  90 +
  91 + /* distance to each vertex */
  92 + private final int[] distTo;
  93 +
  94 + /* number of paths to each vertex */
  95 + private final int[] nPathsTo;
  96 +
  97 +
  98 + private final int start;
  99 + private final IAtomContainer container;
  100 +
  101 + /**
  102 + * Construct a new shortest paths tool for a single start atom. If shortest
  103 + * paths from multiple start atoms are required {@link AllShortestPaths}
  104 + * will have a small performance advantage.
  105 + *
  106 + * @param container an atom container to build
  107 + * @param start the start atom to which all shortest paths will be
  108 + * computed
  109 + * @see AllShortestPaths
  110 + */
  111 + @TestMethod("testConstructor_Container_Empty,testConstructor_Container_Null,testConstructor_Container_MissingAtom")
  112 + public ShortestPaths(IAtomContainer container, IAtom start) {
  113 + this(toAdjList(container), container, container.getAtomNumber(start));
  114 + }
  115 +
  116 +
  117 + /**
  118 + * Internal constructor for use by {@link AllShortestPaths}. This
  119 + * constructor allows the passing of adjacency list directly so the
  120 + * representation does not need to be rebuilt for a different start atom.
  121 + *
  122 + * @param adjacent adjacency list representation - built from {@link
  123 + * #toAdjList(org.openscience.cdk.interfaces.IAtomContainer)}
  124 + * @param container container used to access atoms and their indices
  125 + * @param start the start atom index of the shortest paths
  126 + */
  127 + ShortestPaths(int[][] adjacent, IAtomContainer container, int start) {
  128 +
  129 + int n = adjacent.length;
  130 +
  131 + this.container = container;
  132 + this.start = start;
  133 +
  134 + this.distTo = new int[n];
  135 + this.routeTo = new Route[n];
  136 + this.nPathsTo = new int[n];
  137 +
  138 + // skip computation for empty molecules
  139 + if (container.isEmpty())
  140 + return;
  141 + if (start == -1)
  142 + throw new IllegalArgumentException("invalid vertex start - atom not found container");
  143 +
  144 + for (int i = 0; i < n; i++) {
  145 + distTo[i] = Integer.MAX_VALUE;
  146 + }
  147 +
  148 + // initialise source vertex
  149 + distTo[start] = 0;
  150 + routeTo[start] = new Source(start);
  151 + nPathsTo[start] = 1;
  152 +
  153 + compute(adjacent);
  154 +
  155 + }
  156 +
  157 +
  158 + /**
  159 + * Perform a breath-first-search (BFS) from the start atom. The distanceTo[]
  160 + * is updated on each iteration. The routeTo[] keeps track of our route back
  161 + * to the source. The method has aspects similar to Dijkstra's shortest path
  162 + * but we are working with vertices and thus our edges are unweighted and is
  163 + * more similar to a simple BFS.
  164 + */
  165 + private void compute(int[][] adjacent) {
  166 +
  167 + // compute from our start vertex
  168 + List<Integer> queue = new ArrayList<Integer>(adjacent.length);
  169 + queue.add(start);
  170 +
  171 + while (!queue.isEmpty()) {
  172 +
  173 + Integer v = queue.remove(0);
  174 + int dist = distTo[v] + 1;
  175 +
  176 + for (int w : adjacent[v]) {
  177 +
  178 + // distance is less then the current closest distance
  179 + if (dist < distTo[w]) {
  180 + distTo[w] = dist;
  181 + routeTo[w] = new SequentialRoute(routeTo[v], w); // append w to the route to v
  182 + nPathsTo[w] = nPathsTo[v];
  183 + queue.add(w);
  184 + } else if (distTo[w] == dist) {
  185 + // found path of equal distance, mark as a branch with a new sequential route
  186 + routeTo[w] = new Branch(routeTo[w],
  187 + new SequentialRoute(routeTo[v], w));
  188 + nPathsTo[w] += nPathsTo[v];
  189 + }
  190 + }
  191 +
  192 + }
  193 +
  194 +
  195 + }
  196 +
  197 +
  198 + /**
  199 + * Compute the first shortest path from the <i>start</i> vertex to the
  200 + * provided <i>end</i> vertex. The path is an inclusive fixed size array in
  201 + * order of the vertices to visit. If multiple shortest paths are available
  202 + * the first shortest path is determined by storage order of adjacent
  203 + * vertices.
  204 + *
  205 + * If there is no path to the <i>end</i> atom an empty array is returned. It
  206 + * is considered there to be no valid path if a) the vertex belongs to the
  207 + * same container but is a member of a different fragment, or b) The atom
  208 + * not present in the container at all.
  209 + *
  210 + * <pre>{@code
  211 + * ShortestPaths sp = ...;
  212 + *
  213 + * // get first path
  214 + * int[] path = sp.pathTo(5);
  215 + *
  216 + * // it can be more robust to check if there is only one path
  217 + * if(sp.nPathsTo(5) == 1){
  218 + * int[] path = sp.pathTo(5); // get the first path
  219 + * }
  220 + * }</pre>
  221 + *
  222 + * @param end the <i>end</i> vertex to find a path to
  223 + * @return path from the <i>start</i> to the <i>end</i> vertex
  224 + * @see #pathTo(org.openscience.cdk.interfaces.IAtom)
  225 + * @see #atomsTo(int)
  226 + * @see #atomsTo(org.openscience.cdk.interfaces.IAtom)
  227 + */
  228 + @TestMethod("testPathTo_Int_Simple,testPathTo_Int_Benzene,testPathTo_Int_Norbornane," +
  229 + "testPathTo_Int_Spiroundecane,testPathTo_Int_Pentadecaspiro," +
  230 + "testPathTo_Int_OutOfBoundsIndex,testPathTo_Int_NegativeIndex," +
  231 + "testPathTo_Int_Disconnected")
  232 + public int[] pathTo(int end) {
  233 +
  234 + if (end < 0 || end >= routeTo.length)
  235 + return EMPTY_PATH;
  236 +
  237 + return routeTo[end] != null ? routeTo[end].toPath(distTo[end] + 1) : EMPTY_PATH;
  238 +
  239 + }
  240 +
  241 +
  242 + /**
  243 + * Compute the first shortest path from the <i>start</i> atom to the
  244 + * provided <i>end</i> atom. The path is an inclusive fixed size array in
  245 + * order of the vertices to visit. If multiple shortest paths are available
  246 + * the first shortest path is determined by storage order of adjacent
  247 + * vertices.
  248 + *
  249 + * If there is no path to the <i>end</i> atom an empty array is returned. It
  250 + * is considered there to be no valid path if a) the vertex belongs to the
  251 + * same container but is a member of a different fragment, or b) The atom
  252 + * not present in the container at all.
  253 + *
  254 + * <pre>{@code
  255 + * ShortestPaths sp = ...;
  256 + * IAtom end = ...;
  257 + *
  258 + * // get first path
  259 + * int[] path = sp.pathTo(end);
  260 + *
  261 + * // it can be more robust to check if there is only one path
  262 + * if(sp.nPathsTo(end) == 1){
  263 + * int[] path = sp.pathTo(end); // get the first path
  264 + * }
  265 + * }</pre>
  266 + *
  267 + * @param end the <i>end</i> vertex to find a path to
  268 + * @return path from the <i>start</i> to the <i>end</i> vertex
  269 + * @see #atomsTo(org.openscience.cdk.interfaces.IAtom)
  270 + * @see #atomsTo(int)
  271 + * @see #pathTo(int)
  272 + */
  273 + @TestMethod("testPathTo_Atom_Simple,testPathTo_Atom_Benzene,testPathTo_Atom_Norbornane," +
  274 + "testPathTo_Atom_Spiroundecane,testPathTo_Atom_Pentadecaspiro," +
  275 + "testPathTo_Atom_MissingAtom,testPathTo_Atom_Null," +
  276 + "testPathTo_Atom_Disconnected")
  277 + public int[] pathTo(IAtom end) {
  278 + return pathTo(container.getAtomNumber(end));
  279 + }
  280 +
  281 +
  282 + /**
  283 + * Compute all shortest paths from the given <i>start</i> vertex to the
  284 + * provided <i>end</i> vertex. The path is an inclusive fixed size array in
  285 + * order of the vertices to visit. <p/>
  286 + *
  287 + * <b>Important:</b> for every possible branch the number of possible paths
  288 + * doubles. This method will happily generate tens of thousands of possible
  289 + * paths and although the chance of finding such a molecule is highly
  290 + * unlikely (e.g. C60 fullerene has at maximum six shortest paths to every
  291 + * other atom). It is advisable to check how many paths there are first with
  292 + * {@link #nPathsTo(int)}.
  293 + *
  294 + * <pre>{@code
  295 + * int threshold = 20;
  296 + * ShortestPaths sp = ...;
  297 + *
  298 + * // get shortest paths
  299 + * int[][] paths = sp.pathsTo(5);
  300 + *
  301 + * // only get shortest paths if there are below a given threshold
  302 + * if(sp.nPathsTo(5) < threshold){
  303 + * int[][] path = sp.pathTo(5); // get shortest paths
  304 + * }
  305 + * }</pre>
  306 + *
  307 + * @param end the end atom
  308 + * @return all shortest paths from the start to the end vertex
  309 + */
  310 + @TestMethod("testPathsTo_Int_Simple,testPathsTo_Int_Benzene,testPathsTo_Int_Spiroundecane," +
  311 + "testPathsTo_Int_Norbornane,testPathsTo_Int_OutOfBoundsIndex," +
  312 + "testPathsTo_Int_NegativeIndex,testPathsTo_Int_Disconnected")
  313 + public int[][] pathsTo(int end) {
  314 +
  315 + if (end < 0 || end >= routeTo.length)
  316 + return EMPTY_PATHS;
  317 +
  318 + return routeTo[end] != null ? routeTo[end].toPaths(distTo[end] + 1) : EMPTY_PATHS;
  319 + }
  320 +
  321 +
  322 + /**
  323 + * Compute all shortest paths from the given <i>start</i> atom to the
  324 + * provided <i>end</i> atom. The path is an inclusive fixed size array in
  325 + * order of the vertices to visit. <p/>
  326 + *
  327 + * <b>Important:</b> for every possible branch the number of possible paths
  328 + * doubles. This method will happily generate tens of thousands of possible
  329 + * paths and although the chance of finding such a molecule is highly
  330 + * unlikely (e.g. C60 fullerene has at maximum 6 shortest paths to every
  331 + * other atom). It is advisable to check how many paths there are first with
  332 + * {@link #nPathsTo(int)}.
  333 + *
  334 + * <pre>{@code
  335 + * int threshold = 20;
  336 + * ShortestPaths sp = ...;
  337 + * IAtom end = ...;
  338 + *
  339 + * // get shortest paths
  340 + * int[][] paths = sp.pathsTo(end);
  341 + *
  342 + * // only get shortest paths if there are below a given threshold
  343 + * if(sp.nPathsTo(end) < threshold){
  344 + * int[][] path = sp.pathTo(end); // get shortest paths
  345 + * }
  346 + * }</pre>
  347 + *
  348 + * @param end the end atom
  349 + * @return all shortest paths from the start to the end vertex
  350 + */
  351 + @TestMethod("testPathsTo_Atom_Simple,testPathsTo_Atom_Benzene,testPathsTo_Atom_Spiroundecane," +
  352 + "testPathsTo_Atom_Norbornane,testPathsTo_Atom_Pentadecaspiro," +
  353 + "testPathsTo_Atom_MissingAtom,testPathsTo_Atom_Null," +
  354 + "testPathsTo_Atom_Disconnected")
  355 + public int[][] pathsTo(IAtom end) {
  356 + return pathsTo(container.getAtomNumber(end));
  357 + }
  358 +
  359 +
  360 + /**
  361 + * Compute the first shortest path from the <i>start</i> vertex to the
  362 + * provided <i>end</i> vertex. The path is an inclusive fixed size array in
  363 + * order of the atoms to visit. If multiple shortest paths are available the
  364 + * first shortest path of atoms is determined by storage order of adjacent
  365 + * vertices.
  366 + *
  367 + * If there is no path to the <i>end</i> atom an empty array of atoms is
  368 + * returned. It is considered there to be no valid path if a) the vertex
  369 + * belongs to the same container but is a member of a different fragment, or
  370 + * b) The vertex not present in the container at all.
  371 + *
  372 + * <pre>{@code
  373 + * ShortestPaths sp = ...;
  374 + *
  375 + * // get first path to vertex 5
  376 + * IAtom[] path = sp.atomsTo(5);
  377 + *
  378 + * // it can be more robust to check if there is only one path
  379 + * if(sp.nPathsTo(5) == 1){
  380 + * IAtom[] path = sp.atomsTo(5); // get the first path to vertex 5
  381 + * }
  382 + * }</pre>
  383 + *
  384 + * @param end the <i>end</i> vertex to find a path to
  385 + * @return path from the <i>start</i> to the <i>end</i> atoms as fixed size
  386 + * array of {@link org.openscience.cdk.interfaces.IAtom}s
  387 + * @see #atomsTo(int)
  388 + * @see #pathTo(int)
  389 + * @see #pathTo(org.openscience.cdk.interfaces.IAtom)
  390 + */
  391 + @TestMethod("testAtomsTo_Int_Simple,testAtomsTo_Int_Benzene,testAtomsTo_Int_Disconnected," +
  392 + "testAtomsTo_Int_OutOfBoundsIndex,testAtomsTo_Int_NegativeIndex")
  393 + public IAtom[] atomsTo(int end) {
  394 +
  395 + int[] path = pathTo(end);
  396 + IAtom[] atoms = new IAtom[path.length];
  397 +
  398 + // copy the atoms from the path indices to the array of atoms
  399 + for (int i = 0, n = path.length; i < n; i++)
  400 + atoms[i] = container.getAtom(path[i]);
  401 +
  402 + return atoms;
  403 +
  404 + }
  405 +
  406 +
  407 + /**
  408 + * Compute the first shortest path from the <i>start</i> atom to the
  409 + * provided <i>end</i> atom. The path is an inclusive fixed size array in
  410 + * order of the atoms to visit. If multiple shortest paths are available the
  411 + * first shortest path of atoms is determined by storage order of adjacent
  412 + * vertices.
  413 + *
  414 + * If there is no path to the <i>end</i> atom an empty array of atoms is
  415 + * returned. It is considered there to be no valid path if a) the atom
  416 + * belongs to the same container but is a member of a different fragment, or
  417 + * b) The atom not present in the container at all.
  418 + *
  419 + * <pre>{@code
  420 + * ShortestPaths sp = ...;
  421 + * IAtom end = ...;
  422 + *
  423 + * // get first path
  424 + * IAtom[] path = sp.atomsTo(end);
  425 + *
  426 + * // it can be more robust to check if there is only one path
  427 + * if(sp.nPathsTo(end) == 1){
  428 + * IAtom[] path = sp.atomsTo(end); // get the first path
  429 + * }
  430 + * }</pre>
  431 + *
  432 + * @param end the <i>end</i> atom to find a path to
  433 + * @return path from the <i>start</i> to the <i>end</i> atoms as fixed size
  434 + * array of {@link org.openscience.cdk.interfaces.IAtom}s.
  435 + * @see #atomsTo(int)
  436 + * @see #pathTo(int)
  437 + * @see #pathTo(org.openscience.cdk.interfaces.IAtom)
  438 + */
  439 + @TestMethod("testAtomsTo_Atom_Simple,testAtomsTo_Atom_Benzene,testAtomsTo_Atom_Disconnected," +
  440 + "testAtomsTo_Atom_MissingAtom,testAtomsTo_Atom_Null")
  441 + public IAtom[] atomsTo(IAtom end) {
  442 + return atomsTo(container.getAtomNumber(end));
  443 + }
  444 +
  445 +
  446 + /**
  447 + * Access the number of possible paths from the <i>start</i> to the
  448 + * <i>end</i> vertex. If there is no valid path to the <i>end</i> the number
  449 + * of paths is 0. It is considered there to be no valid path if a) the
  450 + * vertex belongs to the same container but is a member of a different
  451 + * fragment, or b) The vertex not present in the container at all.
  452 + *
  453 + * <pre>
  454 + * {@code
  455 + * ShortestPaths sp = ...;
  456 + *
  457 + * sp.nPathsTo(5); // number of paths to vertex 5
  458 + *
  459 + * sp.nPathsTo(-1); // returns 0 - there are no paths
  460 + * }
  461 + * </pre>
  462 + *
  463 + * @param end the <i>end</i> vertex to which the number of paths will be
  464 + * returned
  465 + * @return the number of paths to the end vertex
  466 + */
  467 + @TestMethod("testNPathsTo_Int_Simple,testNPathsTo_Int_Benzene,testNPathsTo_Int_Norbornane," +
  468 + "testNPathsTo_Int_Spiroundecane,testNPathsTo_Int_Pentadecaspiro," +
  469 + "testNPathsTo_Int_Disconnected," +
  470 + "testNPathsTo_Int_OutOfBoundIndex,testNPathsTo_Int_NegativeIndex")
  471 + public int nPathsTo(int end) {
  472 + return (end < 0 || end >= nPathsTo.length) ? 0 : nPathsTo[end];
  473 + }
  474 +
  475 +
  476 + /**
  477 + * Access the number of possible paths from the <i>start</i> to the
  478 + * <i>end</i> atom. If there is no valid path to the <i>end</i> the number
  479 + * of paths is 0. It is considered there to be no valid path if a) the atom
  480 + * belongs to the same container but is a member of a different fragment, or
  481 + * b) The atom not present in the container at all.
  482 + *
  483 + * <pre>
  484 + * {@code
  485 + * ShortestPaths sp = ...;
  486 + * IAtom end = ...l
  487 + *
  488 + * sp.nPathsTo(end); // number of paths to vertex end
  489 + *
  490 + * sp.nPathsTo(null); // returns 0 - there are no paths
  491 + * sp.nPathsTo(new Atom("C")); // returns 0 - there are no paths
  492 + * }
  493 + * </pre>
  494 + *
  495 + * @param end the <i>end</i> vertex to which the number of paths will be
  496 + * returned
  497 + * @return the number of paths to the end vertex
  498 + */
  499 + @TestMethod("testNPathsTo_Atom_Simple,testNPathsTo_Atom_Benzene,testNPathsTo_Atom_Norbornane," +
  500 + "testNPathsTo_Atom_Spiroundecane,testNPathsTo_Atom_Pentadecaspiro," +
  501 + "testNPathsTo_Atom_Disconnected," +
  502 + "testNPathsTo_Atom_MissingAtom,testNPathsTo_Atom_Null")
  503 + public int nPathsTo(IAtom end) {
  504 + return nPathsTo(container.getAtomNumber(end));
  505 + }
  506 +
  507 +
  508 + /**
  509 + * Access the distance from the <i>start</i> to the given <i>end</i> vertex.
  510 + * If the two are not connected the distance is returned as {@link
  511 + * Integer#MAX_VALUE}. Formally, there is a path if the distance is less
  512 + * then the number of atoms.
  513 + *
  514 + * <pre>{@code
  515 + * IAtomContainer container = ...;
  516 + * ShortestPaths sp = ...; // start = 0
  517 + *
  518 + * int n = container.getAtomCount();
  519 + *
  520 + * if( sp.distanceTo(5) < n ) {
  521 + * // these is a path from 0 to 5