diff --git a/src/branch/NautyLikeCanonicalChecker.java b/src/branch/NautyLikeCanonicalChecker.java new file mode 100644 index 0000000..fcb127b --- /dev/null +++ b/src/branch/NautyLikeCanonicalChecker.java @@ -0,0 +1,121 @@ +package branch; + +import group.BondDiscretePartitionRefiner; +import group.Permutation; +import group.PermutationGroup; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +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 setorbit.BruteForcer; +import setorbit.SetOrbit; + +/** + * Canonical check using the procedure described in the nAUTy User Guide. + * + * For each edge color (bond order), make a layer in a new graph (molecule!) and make edges + * between vertices in these layers according to the colors of edges in the original. + * + * @author maclean + * + */ +public class NautyLikeCanonicalChecker { + + public static boolean isCanonical(IAtomContainer atomContainer, Set augmentedBonds) { + IAtomContainer transformedContainer = transform(atomContainer); + Set transformedAugmentedBonds = transformBonds(atomContainer, transformedContainer, augmentedBonds); + BondDiscretePartitionRefiner bondRefiner = new BondDiscretePartitionRefiner(); + PermutationGroup autGE = bondRefiner.getAutomorphismGroup(transformedContainer); + Permutation p = bondRefiner.getBest(); + Set chosen = chosen(transformedContainer, p); + return inOrbit(transformedAugmentedBonds, chosen, autGE); + } + + private static Set transformBonds( + IAtomContainer atomContainer, IAtomContainer transformedContainer, Set augmentedBonds) { + Set chosen = new HashSet(); + + // bit of a hack! + int numberOfLayers = transformedContainer.getAtomCount() / atomContainer.getAtomCount(); + + for (int bondIndex : augmentedBonds) { + IBond bond = atomContainer.getBond(bondIndex); + int order = bond.getOrder().numeric(); + int a0i = transformedIndex(atomContainer, bond.getAtom(0), numberOfLayers, order); + int a1i = transformedIndex(atomContainer, bond.getAtom(1), numberOfLayers, order); + IAtom tA0 = transformedContainer.getAtom(a0i); + IAtom tA1 = transformedContainer.getAtom(a1i); + chosen.add(transformedContainer.getBondNumber(tA0, tA1)); + } + return chosen; + } + + private static boolean inOrbit(Set augmentedBonds, Set chosen, PermutationGroup g) { + List setList = new ArrayList(); + for (int a : augmentedBonds) { setList.add(a); } + SetOrbit orbit = new BruteForcer().getInOrbit(setList, g); + for (List o : orbit) { + if (o.equals(chosen)) { + return true; + } + } + return false; + } + + private static Set chosen(IAtomContainer ac, Permutation p) { + Set chosen = new HashSet(); + + return chosen; + } + + public static IAtomContainer transform(IAtomContainer atomContainer) { + IChemObjectBuilder builder = atomContainer.getBuilder(); + IAtomContainer transformed = builder.newInstance(IAtomContainer.class); + + int numberOfLayers = 1; + for (IBond bond : atomContainer.bonds()) { + int o = bond.getOrder().numeric(); + if (o > numberOfLayers) { + numberOfLayers = o; + } + } + + for (IAtom atom : atomContainer.atoms()) { + IAtom prev = null; + for (int layerIndex = 0; layerIndex < numberOfLayers; layerIndex++) { + IAtom newAtom = builder.newInstance(IAtom.class, atom.getSymbol()); + transformed.addAtom(newAtom); + if (prev != null) { + transformed.addBond(builder.newInstance(IBond.class, prev, newAtom)); + } + prev = newAtom; + } + } + + for (IBond bond : atomContainer.bonds()) { + int o = bond.getOrder().numeric(); + int a0t = transformedIndex(atomContainer, bond.getAtom(0), numberOfLayers, o); + int a1t = transformedIndex(atomContainer, bond.getAtom(1), numberOfLayers, o); + transformed.addBond(a0t, a1t, IBond.Order.SINGLE); + } + + return transformed; + } + + private static int transformedIndex(IAtomContainer ac, IAtom atom, int numberOfLayers, int order) { + return transformedIndex(ac, ac.getAtomNumber(atom), numberOfLayers, order); + } + + private static int transformedIndex(IAtomContainer ac, int originalIndex, int numberOfLayers, int order) { +// System.out.println(String.format("(%s * %s) + %s)", originalIndex, numberOfLayers, order - 1)); + return (originalIndex * numberOfLayers) + (order - 1); + } + +} diff --git a/src/test/branch/TestNautyLikeCanonicalChecker.java b/src/test/branch/TestNautyLikeCanonicalChecker.java new file mode 100644 index 0000000..4e5f088 --- /dev/null +++ b/src/test/branch/TestNautyLikeCanonicalChecker.java @@ -0,0 +1,96 @@ +package test.branch; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import io.AtomContainerPrinter; + +import java.util.HashSet; +import java.util.Set; + +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.SilentChemObjectBuilder; + +import branch.NautyLikeCanonicalChecker; + +public class TestNautyLikeCanonicalChecker { + + private IChemObjectBuilder builder = SilentChemObjectBuilder.getInstance(); + + @Test + public void testA() { + IAtomContainer ac = AtomContainerPrinter.fromString("C0C1C2 0:1(1),1:2(2)", builder); + IAtomContainer transformed = NautyLikeCanonicalChecker.transform(ac); + assertEquals("Should have 6 vertices", 6, transformed.getAtomCount()); + AtomContainerPrinter.print(transformed); + + // 'backbone' edges + assertEdge(transformed, 0, 1); + assertEdge(transformed, 2, 3); + assertEdge(transformed, 4, 5); + + // bond order edges + assertEdge(transformed, 0, 2); // single bond + assertEdge(transformed, 3, 5); // double bond + } + + @Test + public void testB() { + IAtomContainer ac = AtomContainerPrinter.fromString("C0C1C2C3 0:1(1),0:3(2),1:2(2),2:3(1)", builder); + IAtomContainer transformed = NautyLikeCanonicalChecker.transform(ac); + assertEquals("Should have 8 vertices", 8, transformed.getAtomCount()); + + assertEdge(transformed, 0, 2); + assertEdge(transformed, 1, 7); + assertEdge(transformed, 3, 5); + assertEdge(transformed, 4, 6); + } + + @Test + public void testC() { + IAtomContainer ac = AtomContainerPrinter.fromString("C0C1C2C3 0:1(2),0:2(1),1:2(1),2:3(3)", builder); + IAtomContainer transformed = NautyLikeCanonicalChecker.transform(ac); + assertEquals("Should have 12 vertices", 12, transformed.getAtomCount()); + + assertEdge(transformed, 0, 6); + assertEdge(transformed, 1, 4); + assertEdge(transformed, 3, 6); + assertEdge(transformed, 8, 11); + } + + @Test + public void testBPair() { + IAtomContainer ac1 = AtomContainerPrinter.fromString("C0C1C2C3 0:1(1),0:3(2),1:2(2),2:3(1)", builder); + IAtomContainer transformed1 = NautyLikeCanonicalChecker.transform(ac1); + NautyLikeCanonicalChecker.isCanonical(transformed1, set(0, 2)); + + IAtomContainer ac2 = AtomContainerPrinter.fromString("C0C1C2C3 0:1(1),0:2(2),1:3(2),2:3(1)", builder); + IAtomContainer transformed2 = NautyLikeCanonicalChecker.transform(ac2); + NautyLikeCanonicalChecker.isCanonical(transformed2, set(1, 2)); + } + + private Set set(int... ints) { + Set set = new HashSet(); + for (int i : ints){ + set.add(i); + } + return set; + } + + private void assertEdge(IAtomContainer ac, int a0i, int a1i) { + boolean found = false; + for (IBond bond : ac.bonds()) { + IAtom a0 = bond.getAtom(0); + IAtom a1 = bond.getAtom(1); + int a0j = ac.getAtomNumber(a0); + int a1j = ac.getAtomNumber(a1); + found = (a0i == a0j && a1i == a1j) || (a1i == a0j && a0i == a1j); + if (found) break; + } + assertTrue(String.format("No edge (%s, %s)", a0i, a1i), found); + } + +}