Skip to content

Commit

Permalink
Match SMARTS geometric (double-bond) stereo queries.
Browse files Browse the repository at this point in the history
Signed-off-by: Stephan Beisken <sbeisken@gmail.com>
Signed-off-by: Egon Willighagen <egonw@users.sourceforge.net>
  • Loading branch information
johnmay authored and egonw committed Dec 18, 2013
1 parent 01b6173 commit 34f6b9a
Show file tree
Hide file tree
Showing 2 changed files with 263 additions and 2 deletions.
70 changes: 69 additions & 1 deletion src/main/org/openscience/cdk/isomorphism/SmartsStereoMatch.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.openscience.cdk.interfaces.ITetrahedralChirality;
import org.openscience.cdk.isomorphism.matchers.IQueryAtomContainer;
import org.openscience.cdk.isomorphism.matchers.smarts.SMARTSAtom;
import org.openscience.cdk.isomorphism.matchers.smarts.StereoBond;

import java.util.Arrays;
import java.util.EnumSet;
Expand Down Expand Up @@ -116,7 +117,8 @@ public boolean apply(final int[] mapping) {
return false;
break;
case Geometric:
// not yet supported
if (!checkGeometric(u, otherIndex(u), mapping))
return false;
break;
}
}
Expand Down Expand Up @@ -188,6 +190,72 @@ private int[] map(int u, int v, int[] us, int[] mapping) {
return us;
}

/**
* Verify the geometric stereochemistry (cis/trans) of the double bond
* {@code u1=u2} is preserved in the target when the {@code mapping} is
* used.
*
* @param u1 one index of the double bond
* @param u2 other index of the double bond
* @param mapping mapping of vertices
* @return the geometric configuration is preserved
*/
private boolean checkGeometric(int u1, int u2, int[] mapping) {

int v1 = mapping[u1];
int v2 = mapping[u2];

IDoubleBondStereochemistry queryElement = (IDoubleBondStereochemistry) queryElements[u1];
IBond[] queryBonds = queryElement.getBonds();

boolean unspecified = ((StereoBond)queryBonds[0]).unspecified()
|| ((StereoBond)queryBonds[1]).unspecified();

if (unspecified && (targetTypes[v1] == null || targetTypes[v2] == null))
return true;

// no configuration in target
if (targetTypes[v1] != Type.Geometric || targetTypes[v2] != Type.Geometric)
return false;


IDoubleBondStereochemistry targetElement = (IDoubleBondStereochemistry) targetElements[v1];

// bond is undirected so we need to ensure v1 is the first atom in the bond
// we also need to to swap the substituents later
boolean swap = false;
if (targetElement.getStereoBond().getAtom(0) != target.getAtom(v1)) {
int tmp = v1;
v1 = v2;
v2 = tmp;
swap = true;
}


IBond[] targetBonds = targetElement.getBonds();

int p = parity(queryElement.getStereo());
int q = parity(targetElement.getStereo());

int uLeft = queryMap.get(queryBonds[0].getConnectedAtom(query.getAtom(u1)));
int uRight = queryMap.get(queryBonds[1].getConnectedAtom(query.getAtom(u2)));

int vLeft = targetMap.get(targetBonds[0].getConnectedAtom(target.getAtom(v1)));
int vRight = targetMap.get(targetBonds[1].getConnectedAtom(target.getAtom(v2)));

if (swap) {
int tmp = vLeft;
vLeft = vRight;
vRight = tmp;
}
if (mapping[uLeft] != vLeft)
p *= -1;
if (mapping[uRight] != vRight)
p *= -1;

return p == q;
}

/**
* Access the neighbors of {@code element} as their indices.
*
Expand Down
195 changes: 194 additions & 1 deletion src/test/org/openscience/cdk/isomorphism/SmartsStereoMatchTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,200 @@ public class SmartsStereoMatchTest {
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{0, 1, 2, 3, 4}));
}


@Test public void geometric_match_together1() {
IAtomContainer query = sma("C/C=C\\C");
IAtomContainer target = but2ene();
assertFalse(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.TOGETHER));
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
}

@Test public void geometric_match_together2() {
IAtomContainer query = sma("C\\C=C/C");
IAtomContainer target = but2ene();
assertFalse(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.TOGETHER));
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
}

@Test public void geometric_match_opposite1() {
IAtomContainer query = sma("C/C=C/C");
IAtomContainer target = but2ene();
assertFalse(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.OPPOSITE));
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
}

@Test public void geometric_match_opposite2() {
IAtomContainer query = sma("C\\C=C\\C");
IAtomContainer target = but2ene();
assertFalse(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.OPPOSITE));
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
}

@Test public void geometric_mismatch_together1() {
IAtomContainer query = sma("C/C=C\\C");
IAtomContainer target = but2ene();
assertFalse(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.OPPOSITE));
assertFalse(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
}

@Test public void geometric_mismatch_together2() {
IAtomContainer query = sma("C\\C=C/C");
IAtomContainer target = but2ene();
assertFalse(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.OPPOSITE));
assertFalse(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
}

@Test public void geometric_mismatch_opposite1() {
IAtomContainer query = sma("C/C=C/C");
IAtomContainer target = but2ene();
assertFalse(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.TOGETHER));
assertFalse(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
}

@Test public void geometric_mismatch_opposite2() {
IAtomContainer query = sma("C\\C=C\\C");
IAtomContainer target = but2ene();
assertFalse(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.TOGETHER));
assertFalse(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
}

@Test public void geometric_match_together_unspecified1() {
IAtomContainer query = sma("C/C=C\\?C");
IAtomContainer target = but2ene();
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.TOGETHER));
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
}

@Test public void geometric_match_together_unspecified2() {
IAtomContainer query = sma("C/?C=C\\C");
IAtomContainer target = but2ene();
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.TOGETHER));
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.setStereoElements(new ArrayList<IStereoElement>(1));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.OPPOSITE));
assertFalse(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
}

@Test public void geometric_match_together_unspecified3() {
IAtomContainer query = sma("C\\C=C/?C");
IAtomContainer target = but2ene();
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.TOGETHER));
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.setStereoElements(new ArrayList<IStereoElement>(1));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.OPPOSITE));
assertFalse(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
}

@Test public void geometric_match_together_unspecified4() {
IAtomContainer query = sma("C\\?C=C/C");
IAtomContainer target = but2ene();
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.TOGETHER));
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.setStereoElements(new ArrayList<IStereoElement>(1));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.OPPOSITE));
assertFalse(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
}

@Test public void geometric_match_opposite_unspecified1() {
IAtomContainer query = sma("C/C=C/?C");
IAtomContainer target = but2ene();
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.OPPOSITE));
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.setStereoElements(new ArrayList<IStereoElement>(1));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.TOGETHER));
assertFalse(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
}

@Test public void geometric_match_opposite_unspecified2() {
IAtomContainer query = sma("C/?C=C/C");
IAtomContainer target = but2ene();
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.OPPOSITE));
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.setStereoElements(new ArrayList<IStereoElement>(1));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.TOGETHER));
assertFalse(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
}

@Test public void geometric_match_opposite_unspecified3() {
IAtomContainer query = sma("C\\C=C\\?C");
IAtomContainer target = but2ene();
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.OPPOSITE));
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.setStereoElements(new ArrayList<IStereoElement>(1));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.TOGETHER));
assertFalse(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
}

@Test public void geometric_match_opposite_unspecified4() {
IAtomContainer query = sma("C\\?C=C\\C");
IAtomContainer target = but2ene();
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.OPPOSITE));
assertTrue(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
target.setStereoElements(new ArrayList<IStereoElement>(1));
target.addStereoElement(new DoubleBondStereochemistry(target.getBond(0),
new IBond[]{target.getBond(1), target.getBond(2)},
IDoubleBondStereochemistry.Conformation.TOGETHER));
assertFalse(new SmartsStereoMatch(query, target).apply(new int[]{2, 0, 1, 3}));
}

static IAtomContainer dimethylpropane() {
IAtomContainer container = new AtomContainer();
Expand Down

0 comments on commit 34f6b9a

Please sign in to comment.