Skip to content

Commit

Permalink
Merge pull request #198 from cdk/patch/mdlpartiy
Browse files Browse the repository at this point in the history
Looks good to me, thanks!
  • Loading branch information
egonw committed Feb 22, 2016
2 parents fa571c3 + d5a6914 commit 0f8d3cb
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 11 deletions.
Expand Up @@ -99,7 +99,9 @@ public interface IAtom extends IAtomType {
* @param stereoParity The stereo parity for this atom
* @see org.openscience.cdk.CDKConstants for predefined values.
* @see #getStereoParity
* @deprecated use {@link IStereoElement}s for storing stereochemistry
*/
@Deprecated
public void setStereoParity(Integer stereoParity);

/**
Expand Down Expand Up @@ -137,7 +139,9 @@ public interface IAtom extends IAtomType {
* @return The stereo parity for this atom
* @see org.openscience.cdk.CDKConstants
* @see #setStereoParity
* @deprecated use {@link IStereoElement}s for storing stereochemistry
*/
@Deprecated
public Integer getStereoParity();

/**
Expand Down
Expand Up @@ -41,6 +41,9 @@
import org.openscience.cdk.interfaces.IIsotope;
import org.openscience.cdk.interfaces.IPseudoAtom;
import org.openscience.cdk.interfaces.ISingleElectron;
import org.openscience.cdk.interfaces.IStereoElement;
import org.openscience.cdk.interfaces.ITetrahedralChirality;
import org.openscience.cdk.interfaces.ITetrahedralChirality.Stereo;
import org.openscience.cdk.io.formats.IResourceFormat;
import org.openscience.cdk.io.formats.MDLV2000Format;
import org.openscience.cdk.io.setting.BooleanIOSetting;
Expand All @@ -52,6 +55,7 @@
import org.openscience.cdk.sgroup.SgroupKey;
import org.openscience.cdk.sgroup.SgroupType;
import org.openscience.cdk.stereo.StereoElementFactory;
import org.openscience.cdk.stereo.TetrahedralChirality;
import org.openscience.cdk.tools.ILoggingTool;
import org.openscience.cdk.tools.LoggingToolFactory;
import org.openscience.cdk.tools.manipulator.AtomContainerManipulator;
Expand All @@ -66,6 +70,7 @@
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
Expand Down Expand Up @@ -307,6 +312,8 @@ private static IChemModel newModel(final IAtomContainer container) {
private IAtomContainer readAtomContainer(IAtomContainer molecule) throws CDKException {

IAtomContainer outputContainer = null;
Map<IAtom,Integer> parities = new HashMap<>();

int linecount = 0;
String title = null;
String remark = null;
Expand Down Expand Up @@ -381,7 +388,7 @@ private IAtomContainer readAtomContainer(IAtomContainer molecule) throws CDKExce
line = input.readLine();
linecount++;

final IAtom atom = readAtomFast(line, molecule.getBuilder(), linecount);
final IAtom atom = readAtomFast(line, molecule.getBuilder(), parities, linecount);

atoms[i] = atom;

Expand Down Expand Up @@ -443,6 +450,42 @@ private IAtomContainer readAtomContainer(IAtomContainer molecule) throws CDKExce
outputContainer.addBond(bond);
}

// create 0D stereochemistry
Parities:
for (Map.Entry<IAtom,Integer> e : parities.entrySet()) {
int parity = e.getValue();
if (parity != 1 && parity != 2)
continue; // 3=unspec
int idx = 0;
IAtom focus = e.getKey();
IAtom[] carriers = new IAtom[4];
int hidx = -1;
for (IAtom nbr : molecule.getConnectedAtomsList(focus)) {
if (idx == 4)
continue Parities; // too many neighbors
if (nbr.getAtomicNumber() == 1) {
if (hidx >= 0)
continue Parities;
hidx = idx;
}
carriers[idx++] = nbr;
}
// to few neighbors, or already have a hydrogen defined
if (idx < 3 || idx < 4 && hidx >= 0)
continue;
if (idx == 3)
carriers[idx++] = focus;

if (idx == 4) {
Stereo winding = parity == 1 ? Stereo.CLOCKWISE : Stereo.ANTI_CLOCKWISE;
// H is always at back, even if explicit! At least this seems to be the case.
// we adjust the winding as needed
if (hidx == 0 || hidx == 2)
winding = winding.invert();
molecule.addStereoElement(new TetrahedralChirality(focus, carriers, winding));
}
}

// read PROPERTY block
readPropertiesFast(input, outputContainer, nAtoms);

Expand Down Expand Up @@ -578,6 +621,10 @@ private String removeNonDigits(String input) {
return sb.toString();
}

IAtom readAtomFast(String line, IChemObjectBuilder builder, int lineNum) throws CDKException, IOException {
return readAtomFast(line, builder, Collections.<IAtom,Integer>emptyMap(), lineNum);
}

/**
* Parse an atom line from the atom block using the format: {@code
* xxxxx.xxxxyyyyy.yyyyzzzzz.zzzz aaaddcccssshhhbbbvvvHHHrrriiimmmnnneee}
Expand All @@ -594,10 +641,11 @@ private String removeNonDigits(String input) {
*
* @param line input line
* @param builder chem object builder to create the atom
* @param parities map of atom parities for creation 0D stereochemistry
* @param lineNum the line number - for printing error messages
* @return a new atom instance
*/
IAtom readAtomFast(String line, IChemObjectBuilder builder, int lineNum) throws CDKException, IOException {
IAtom readAtomFast(String line, IChemObjectBuilder builder, Map<IAtom,Integer> parities, int lineNum) throws CDKException, IOException {

// The line may be truncated and it's checked in reverse at the specified
// lengths:
Expand Down Expand Up @@ -652,6 +700,8 @@ IAtom readAtomFast(String line, IChemObjectBuilder builder, int lineNum) throws
atom.setPoint3d(new Point3d(x, y, z));
atom.setFormalCharge(charge);
atom.setStereoParity(parity);
if (parity != 0)
parities.put(atom, parity);

// if there was a mass difference, set the mass number
if (massDiff != 0 && atom.getAtomicNumber() > 0)
Expand Down
Expand Up @@ -35,6 +35,7 @@
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
Expand All @@ -57,6 +58,8 @@
import org.openscience.cdk.interfaces.IChemObject;
import org.openscience.cdk.interfaces.IChemSequence;
import org.openscience.cdk.interfaces.IPseudoAtom;
import org.openscience.cdk.interfaces.IStereoElement;
import org.openscience.cdk.interfaces.ITetrahedralChirality;
import org.openscience.cdk.io.formats.IResourceFormat;
import org.openscience.cdk.io.formats.MDLFormat;
import org.openscience.cdk.io.setting.BooleanIOSetting;
Expand Down Expand Up @@ -361,6 +364,15 @@ public void writeMolecule(IAtomContainer container) throws Exception {
writer.write(line);
writer.newLine();

// index stereo elements for setting atom parity values
Map<IAtom,ITetrahedralChirality> atomstereo = new HashMap<>();
Map<IAtom,Integer> atomindex = new HashMap<>();
for (IStereoElement element : container.stereoElements())
if (element instanceof ITetrahedralChirality)
atomstereo.put(((ITetrahedralChirality) element).getChiralAtom(), (ITetrahedralChirality) element);
for (IAtom atom : container.atoms())
atomindex.put(atom, atomindex.size());

// write Atom block
for (int f = 0; f < container.getAtomCount(); f++) {
IAtom atom = container.getAtom(f);
Expand Down Expand Up @@ -430,7 +442,42 @@ public void writeMolecule(IAtomContainer container) throws Exception {
} else {
line += formatMDLString(container.getAtom(f).getSymbol(), 3);
}
line += String.format(" 0 0 %d 0 0", atom.getStereoParity() == null ? 0 : atom.getStereoParity());

final ITetrahedralChirality tc = atomstereo.get(atom);
if (tc == null) {
line += " 0 0 0 0 0";
} else {
int parity = tc.getStereo() == ITetrahedralChirality.Stereo.CLOCKWISE ? 1 : 2;
int swaps = 0;
IAtom focus = tc.getChiralAtom();
IAtom[] carriers = tc.getLigands();

int hidx = -1;
for (int i = 0; i < 4; i++) {
// hydrogen position
if (carriers[i] == focus || carriers[i].getAtomicNumber() == 1) {
if (hidx >= 0) parity = 0;
hidx = i;
}
}

if (parity != 0) {
for (int i = 0; i < 4; i++) {
for (int j = i + 1; j < 4; j++) {
int a = atomindex.get(carriers[i]);
int b = atomindex.get(carriers[j]);
if (a == hidx)
a = container.getAtomCount();
if (b == hidx)
b = container.getAtomCount();
if (a > b)
parity ^= 0x3;
}
}
}

line += String.format(" 0 0 %d 0 0", parity);
}

// write valence - this is a bit of pain as the CDK has both
// valence and implied hydrogen counts making life a lot more
Expand Down Expand Up @@ -537,11 +584,11 @@ else if (valence > 0 && valence < 15)
if (bond.getStereo() == IBond.Stereo.UP_INVERTED || bond.getStereo() == IBond.Stereo.DOWN_INVERTED
|| bond.getStereo() == IBond.Stereo.UP_OR_DOWN_INVERTED) {
// turn around atom coding to correct for inv stereo
line = formatMDLInt(container.getAtomNumber(bond.getAtom(1)) + 1, 3);
line += formatMDLInt(container.getAtomNumber(bond.getAtom(0)) + 1, 3);
line = formatMDLInt(atomindex.get(bond.getAtom(1)) + 1, 3);
line += formatMDLInt(atomindex.get(bond.getAtom(0)) + 1, 3);
} else {
line = formatMDLInt(container.getAtomNumber(bond.getAtom(0)) + 1, 3);
line += formatMDLInt(container.getAtomNumber(bond.getAtom(1)) + 1, 3);
line = formatMDLInt(atomindex.get(bond.getAtom(0)) + 1, 3);
line += formatMDLInt(atomindex.get(bond.getAtom(1)) + 1, 3);
}
int bondType;
if (writeAromaticBondTypes.isSet() && bond.getFlag(CDKConstants.ISAROMATIC))
Expand Down Expand Up @@ -710,15 +757,15 @@ else if (Order.QUADRUPLE == bond.getOrder())
}
}

writeSgroups(container, writer);
writeSgroups(container, writer, atomindex);

// close molecule
writer.write("M END");
writer.newLine();
writer.flush();
}

private void writeSgroups(IAtomContainer container, BufferedWriter writer) throws IOException {
private void writeSgroups(IAtomContainer container, BufferedWriter writer, Map<IAtom,Integer> atomidxs) throws IOException {
List<Sgroup> sgroups = container.getProperty(CDKConstants.CTAB_SGROUPS);
if (sgroups == null)
return;
Expand Down Expand Up @@ -757,7 +804,7 @@ private void writeSgroups(IAtomContainer container, BufferedWriter writer) throw
writer.write(formatMDLInt(atoms.size(), 3));
for (IAtom atom : atoms) {
writer.write(' ');
writer.write(formatMDLInt(1+container.getAtomNumber(atom), 3));
writer.write(formatMDLInt(1+atomidxs.get(atom), 3));
}
writer.newLine();
}
Expand Down Expand Up @@ -856,7 +903,7 @@ private void writeSgroups(IAtomContainer container, BufferedWriter writer) throw
writer.write(formatMDLInt(atoms.size(), 3));
for (IAtom atom : atoms) {
writer.write(' ');
writer.write(formatMDLInt(1+container.getAtomNumber(atom), 3));
writer.write(formatMDLInt(1+atomidxs.get(atom), 3));
}
writer.newLine();
}
Expand Down
Expand Up @@ -65,6 +65,8 @@
import org.openscience.cdk.interfaces.IBond.Order;
import org.openscience.cdk.interfaces.IChemFile;
import org.openscience.cdk.interfaces.IPseudoAtom;
import org.openscience.cdk.interfaces.IStereoElement;
import org.openscience.cdk.interfaces.ITetrahedralChirality;
import org.openscience.cdk.io.IChemObjectReader.Mode;
import org.openscience.cdk.io.listener.PropertiesListener;
import org.openscience.cdk.isomorphism.matchers.CTFileQueryBond;
Expand Down Expand Up @@ -1661,4 +1663,33 @@ public void testSgroupBracketStyle() throws Exception {
assertThat((Integer) sgroup.getValue(SgroupKey.CtabBracketStyle), is(1));
}
}

@Test public void testReading0DStereochemistry() throws Exception {
try (MDLV2000Reader mdlr = new MDLV2000Reader(getClass().getResourceAsStream("/data/mdl/tetrahedral-parity-withImplH.mol"))) {
IAtomContainer container = mdlr.read(new AtomContainer());
Iterable<IStereoElement> selements = container.stereoElements();
Iterator<IStereoElement> siter = selements.iterator();
assertTrue(siter.hasNext());
IStereoElement se = siter.next();
assertThat(se, is(instanceOf(ITetrahedralChirality.class)));
assertThat(((ITetrahedralChirality) se).getStereo(), is(ITetrahedralChirality.Stereo.CLOCKWISE));
assertThat(((ITetrahedralChirality) se).getLigands(), is(new IAtom[]{container.getAtom(1), container.getAtom(3), container.getAtom(4), container.getAtom(0)}));
assertFalse(siter.hasNext());
}
}

// explicit Hydrogen can reverse winding
@Test public void testReading0DStereochemistryWithHydrogen() throws Exception {
try (MDLV2000Reader mdlr = new MDLV2000Reader(getClass().getResourceAsStream("/data/mdl/tetrahedral-parity-withExpH.mol"))) {
IAtomContainer container = mdlr.read(new AtomContainer());
Iterable<IStereoElement> selements = container.stereoElements();
Iterator<IStereoElement> siter = selements.iterator();
assertTrue(siter.hasNext());
IStereoElement se = siter.next();
assertThat(se, is(instanceOf(ITetrahedralChirality.class)));
assertThat(((ITetrahedralChirality) se).getStereo(), is(ITetrahedralChirality.Stereo.ANTI_CLOCKWISE));
assertThat(((ITetrahedralChirality) se).getLigands(), is(new IAtom[]{container.getAtom(0), container.getAtom(2), container.getAtom(3), container.getAtom(4)}));
assertFalse(siter.hasNext());
}
}
}
Expand Up @@ -54,6 +54,7 @@
import org.openscience.cdk.interfaces.IChemModel;
import org.openscience.cdk.interfaces.IChemObjectBuilder;
import org.openscience.cdk.interfaces.IPseudoAtom;
import org.openscience.cdk.interfaces.ITetrahedralChirality;
import org.openscience.cdk.io.listener.PropertiesListener;
import org.openscience.cdk.sgroup.Sgroup;
import org.openscience.cdk.templates.TestMoleculeFactory;
Expand Down Expand Up @@ -766,4 +767,40 @@ public void sgroupOrderedMixtureRoundTrip() throws Exception {
assertThat(output, containsString("M SNC 1 2 2"));
}
}

@Test
public void roundtripAtomParityExpH() throws Exception {
StringWriter sw = new StringWriter();
try (MDLV2000Reader mdlr = new MDLV2000Reader(getClass().getResourceAsStream("/data/mdl/tetrahedral-parity-withExpH.mol"));
MDLV2000Writer mdlw = new MDLV2000Writer(sw)) {
mdlw.write(mdlr.read(new AtomContainer()));
String output = sw.toString();
assertThat(output, containsString(" 0.0000 0.0000 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0\n"));
}
}

@Test
public void roundtripAtomParityImplH() throws Exception {
StringWriter sw = new StringWriter();
try (MDLV2000Reader mdlr = new MDLV2000Reader(getClass().getResourceAsStream("/data/mdl/tetrahedral-parity-withImplH.mol"));
MDLV2000Writer mdlw = new MDLV2000Writer(sw)) {
mdlw.write(mdlr.read(new AtomContainer()));
String output = sw.toString();
assertThat(output, containsString(" 0.0000 0.0000 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0\n"));
}
}

@Test
public void roundtripAtomParityImplModified() throws Exception {
StringWriter sw = new StringWriter();
try (MDLV2000Reader mdlr = new MDLV2000Reader(getClass().getResourceAsStream("/data/mdl/tetrahedral-parity-withImplH.mol"));
MDLV2000Writer mdlw = new MDLV2000Writer(sw)) {
AtomContainer mol = mdlr.read(new AtomContainer());
ITetrahedralChirality tc = (ITetrahedralChirality) mol.stereoElements().iterator().next();
tc.setStereo(tc.getStereo().invert());
mdlw.write(mol);
String output = sw.toString();
assertThat(output, containsString(" 0.0000 0.0000 0.0000 C 0 0 2 0 0 0 0 0 0 0 0 0\n"));
}
}
}
@@ -0,0 +1,14 @@



5 4 0 0 0 0 999 V2000
0.0000 0.0000 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
0.0000 0.0000 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0
0.0000 0.0000 0.0000 Cl 0 0 0 0 0 0 0 0 0 0 0 0
0.0000 0.0000 0.0000 Br 0 0 0 0 0 0 0 0 0 0 0 0
0.0000 0.0000 0.0000 F 0 0 0 0 0 0 0 0 0 0 0 0
2 1 1 0 0 0 0
2 3 1 0 0 0 0
2 4 1 0 0 0 0
2 5 1 0 0 0 0
M END
@@ -0,0 +1,14 @@



5 4 0 0 0 0 999 V2000
0.0000 0.0000 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0
0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
0.0000 0.0000 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
1 2 1 0 0 0 0
2 3 1 0 0 0 0
1 4 1 0 0 0 0
1 5 1 0 0 0 0
M END

0 comments on commit 0f8d3cb

Please sign in to comment.