/* $Revision$ $Author$ $Date$ * * Copyright (C) 2006-2007 Sam Adams <sea36@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. * * 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.inchi; import net.sf.jniinchi.*; import org.openscience.cdk.CDKConstants; import org.openscience.cdk.annotations.TestClass; import org.openscience.cdk.annotations.TestMethod; import org.openscience.cdk.config.IsotopeFactory; import org.openscience.cdk.exception.CDKException; import org.openscience.cdk.interfaces.IAtom; import org.openscience.cdk.interfaces.IAtomContainer; import org.openscience.cdk.interfaces.IAtomParity; import org.openscience.cdk.interfaces.IBond; import javax.vecmath.Point2d; import javax.vecmath.Point3d; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; /** * <p>This class generates the IUPAC International Chemical Identifier (InChI) for * a CDK IAtomContainer. It places calls to a JNI wrapper for the InChI C++ library. * * <p>If the atom container has 3D coordinates for all of its atoms then they * will be used, otherwise 2D coordinates will be used if available. * * <p><i>Spin multiplicities and some aspects of stereochemistry are not * currently handled completely.</i> * * <h3>Example usage</h3> * * <code>// Generate factory - throws CDKException if native code does not load</code><br> * <code>InChIGeneratorFactory factory = new InChIGeneratorFactory();</code><br> * <code>// Get InChIGenerator</code><br> * <code>InChIGenerator gen = factory.getInChIGenerator(container);</code><br> * <code></code><br> * <code>INCHI_RET ret = gen.getReturnStatus();</code><br> * <code>if (ret == INCHI_RET.WARNING) {</code><br> * <code> // InChI generated, but with warning message</code><br> * <code> System.out.println("InChI warning: " + gen.getMessage());</code><br> * <code>} else if (ret != INCHI_RET.OKAY) {</code><br> * <code> // InChI generation failed</code><br> * <code> throw new CDKException("InChI failed: " + ret.toString()</code><br> * <code> + " [" + gen.getMessage() + "]");</code><br> * <code>}</code><br> * <code></code><br> * <code>String inchi = gen.getInchi();</code><br> * <code>String auxinfo = gen.getAuxInfo();</code><br> * <p><tt><b> * TODO: distinguish between singlet and undefined spin multiplicity<br/> * TODO: double bond and allene parities<br/> * TODO: problem recognising bond stereochemistry<br/> * </b></tt> * * @author Sam Adams * * @cdk.module inchi * @cdk.githash */ @TestClass("org.openscience.cdk.inchi.InChIGeneratorTest") public class InChIGenerator { protected JniInchiInput input; protected JniInchiOutput output; /** * AtomContainer instance refers to. */ protected IAtomContainer atomContainer; /** * <p>Constructor. Generates InChI from CDK AtomContainer. * * <p>Reads atoms, bonds etc from atom container and converts to format * InChI library requires, then calls the library. * * @param atomContainer AtomContainer to generate InChI for. * @throws org.openscience.cdk.exception.CDKException if there is an * error during InChI generation */ @TestClass("testGetInchiFromChlorineAtom,testGetInchiFromLithiumIontestGetInchiFromChlorine37Atom") protected InChIGenerator(IAtomContainer atomContainer) throws CDKException { try { input = new JniInchiInput(""); generateInchiFromCDKAtomContainer(atomContainer); } catch (JniInchiException jie) { throw new CDKException("InChI generation failed: " + jie.getMessage(), jie); } } /** * <p>Constructor. Generates InChI from CDK AtomContainer. * * <p>Reads atoms, bonds etc from atom container and converts to format * InChI library requires, then calls the library. * * @param atomContainer AtomContainer to generate InChI for. * @param options Space delimited string of options to pass to InChI library. * Each option may optionally be preceded by a command line * switch (/ or -). * @throws CDKException */ protected InChIGenerator(IAtomContainer atomContainer, String options) throws CDKException { try { input = new JniInchiInput(options); generateInchiFromCDKAtomContainer(atomContainer); } catch (JniInchiException jie) { throw new CDKException("InChI generation failed: " + jie.getMessage(), jie); } } /** * <p>Constructor. Generates InChI from CDK AtomContainer. * * <p>Reads atoms, bonds etc from atom container and converts to format * InChI library requires, then calls the library. * * @param atomContainer AtomContainer to generate InChI for. * @param options List of INCHI_OPTION. * @throws CDKException */ protected InChIGenerator(IAtomContainer atomContainer, List options) throws CDKException { try { input = new JniInchiInput(options); generateInchiFromCDKAtomContainer(atomContainer); } catch (JniInchiException jie) { throw new CDKException("InChI generation failed: " + jie.getMessage(), jie); } } /** * <p>Reads atoms, bonds etc from atom container and converts to format * InChI library requires, then places call for the library to generate * the InChI. * * @param atomContainer AtomContainer to generate InChI for. * @throws CDKException */ private void generateInchiFromCDKAtomContainer(IAtomContainer atomContainer) throws CDKException { this.atomContainer = atomContainer; Iterator<IAtom> atoms = atomContainer.atoms().iterator(); // Check for 3d coordinates boolean all3d = true; boolean all2d = true; while (atoms.hasNext()) { IAtom atom = (IAtom)atoms.next(); if (atom.getPoint3d() == null) { all3d = false; } if (atom.getPoint2d() == null) { all2d = false; } } // Process atoms IsotopeFactory ifact = null; try { ifact = IsotopeFactory.getInstance(atomContainer.getBuilder()); } catch (Exception e) { // Do nothing } Map<IAtom, JniInchiAtom> atomMap = new HashMap<IAtom, JniInchiAtom>(); atoms = atomContainer.atoms().iterator(); while (atoms.hasNext()) { IAtom atom = atoms.next(); // Get coordinates // Use 3d if possible, otherwise 2d or none double x, y, z; if (all3d) { Point3d p = atom.getPoint3d(); x = p.x; y = p.y; z = p.z; } else if (all2d) { Point2d p = atom.getPoint2d(); x = p.x; y = p.y; z = 0.0; } else { x = 0.0; y = 0.0; z = 0.0; } // Chemical element symbol String el = atom.getSymbol(); // Generate InChI atom JniInchiAtom iatom = input.addAtom(new JniInchiAtom(x, y, z, el)); atomMap.put(atom, iatom); // Check if charged int charge = atom.getFormalCharge(); if (charge != 0) { iatom.setCharge(charge); } // Check whether isotopic Integer isotopeNumber = atom.getMassNumber(); if (isotopeNumber != CDKConstants.UNSET && ifact != null) { IAtom isotope = atomContainer.getBuilder().newAtom(el); ifact.configure(isotope); if (isotope.getMassNumber().intValue() == isotopeNumber.intValue()) { isotopeNumber = 0; } } if (isotopeNumber != CDKConstants.UNSET) { iatom.setIsotopicMass(isotopeNumber); } // Check for implicit hydrogens // atom.getHydrogenCount() returns number of implict hydrogens, not // total number // Ref: Posting to cdk-devel list by Egon Willighagen 2005-09-17 Integer implicitH = atom.getHydrogenCount(); if (implicitH == CDKConstants.UNSET) implicitH = 0; if (implicitH != 0) { iatom.setImplicitH(implicitH); } // Check if radical int count = atomContainer.getConnectedSingleElectronsCount(atom); if (count == 0) { // TODO - how to check whether singlet or undefined multiplicity } else if (count == 1) { iatom.setRadical(INCHI_RADICAL.DOUBLET); } else if (count == 2) { iatom.setRadical(INCHI_RADICAL.TRIPLET); } else { throw new CDKException("Unrecognised radical type"); } } // Process bonds Iterator<IBond> bonds = atomContainer.bonds().iterator(); while (bonds.hasNext()) { IBond bond = bonds.next(); // Assumes 2 centre bond JniInchiAtom at0 = (JniInchiAtom) atomMap.get(bond.getAtom(0)); JniInchiAtom at1 = (JniInchiAtom) atomMap.get(bond.getAtom(1)); // Get bond order INCHI_BOND_TYPE order; IBond.Order bo = bond.getOrder(); if (bond.getFlag(CDKConstants.ISAROMATIC)) { order = INCHI_BOND_TYPE.ALTERN; } else if (bo == CDKConstants.BONDORDER_SINGLE) { order = INCHI_BOND_TYPE.SINGLE; } else if (bo == CDKConstants.BONDORDER_DOUBLE) { order = INCHI_BOND_TYPE.DOUBLE; } else if (bo == CDKConstants.BONDORDER_TRIPLE) { order = INCHI_BOND_TYPE.TRIPLE; } else { throw new CDKException("Failed to generate InChI: Unsupported bond type"); } // Create InChI bond JniInchiBond ibond = new JniInchiBond(at0, at1, order); input.addBond(ibond); // Check for bond stereo definitions IBond.Stereo stereo = bond.getStereo(); // No stereo definition if (stereo == IBond.Stereo.NONE) { ibond.setStereoDefinition(INCHI_BOND_STEREO.NONE); } // Bond ending (fat end of wedge) below the plane else if (stereo == IBond.Stereo.DOWN) { ibond.setStereoDefinition(INCHI_BOND_STEREO.SINGLE_1DOWN); } // Bond ending (fat end of wedge) above the plane else if (stereo == IBond.Stereo.UP) { ibond.setStereoDefinition(INCHI_BOND_STEREO.SINGLE_1UP); } // Bond starting (pointy end of wedge) below the plane else if (stereo == IBond.Stereo.DOWN_INVERTED) { ibond.setStereoDefinition(INCHI_BOND_STEREO.SINGLE_2DOWN); } // Bond starting (pointy end of wedge) above the plane else if (stereo == IBond.Stereo.UP_INVERTED) { ibond.setStereoDefinition(INCHI_BOND_STEREO.SINGLE_2UP); } // Bond with undefined stereochemistry else if (stereo == CDKConstants.UNSET) { if (order == INCHI_BOND_TYPE.SINGLE) { ibond.setStereoDefinition(INCHI_BOND_STEREO.SINGLE_1EITHER); } else if (order == INCHI_BOND_TYPE.DOUBLE) { ibond.setStereoDefinition(INCHI_BOND_STEREO.DOUBLE_EITHER); } } } // Process atom parities (tetrahedral InChI Stereo0D Parities) atoms = atomContainer.atoms().iterator(); while (atoms.hasNext()) { IAtom atom = atoms.next(); IAtomParity parity = atomContainer.getAtomParity(atom); if (parity != null) { IAtom[] surroundingAtoms = parity.getSurroundingAtoms(); int sign = parity.getParity(); JniInchiAtom atC = (JniInchiAtom) atomMap.get(atom); JniInchiAtom at0 = (JniInchiAtom) atomMap.get(surroundingAtoms[0]); JniInchiAtom at1 = (JniInchiAtom) atomMap.get(surroundingAtoms[1]); JniInchiAtom at2 = (JniInchiAtom) atomMap.get(surroundingAtoms[2]); JniInchiAtom at3 = (JniInchiAtom) atomMap.get(surroundingAtoms[3]); INCHI_PARITY p = INCHI_PARITY.UNKNOWN; if (sign > 0) { p = INCHI_PARITY.EVEN; } else if (sign < 0) { p = INCHI_PARITY.ODD; } else { throw new CDKException("Atom parity of zero"); } input.addStereo0D(new JniInchiStereo0D(atC, at0, at1, at2, at3, INCHI_STEREOTYPE.TETRAHEDRAL, p)); } } try { output = JniInchiWrapper.getInchi(input); } catch (JniInchiException jie) { throw new CDKException("Failed to generate InChI: " + jie.getMessage(), jie); } } /** * Gets return status from InChI process. OKAY and WARNING indicate * InChI has been generated, in all other cases InChI generation * has failed. */ @TestMethod("testGetInchiFromLandDAlanine3D,testGetInchiEandZ12Dichloroethene2D") public INCHI_RET getReturnStatus() { return(output.getReturnStatus()); } /** * Gets generated InChI string. */ @TestMethod("testGetInchiEandZ12Dichloroethene2D,testGetInchiFromEthyne,testGetInchiFromEthene") public String getInchi() { return(output.getInchi()); } /** * Gets generated InChIKey string. */ @TestMethod("testGetInchiFromEthane") public String getInchiKey() throws CDKException { JniInchiOutputKey key; try { key = JniInchiWrapper.getInChIKey(output.getInchi()); if (key.getReturnStatus() == INCHI_KEY.OK) { return key.getKey(); } else { throw new CDKException("Error while creating InChIKey: " + key.getReturnStatus()); } } catch (JniInchiException exception) { throw new CDKException("Error while creating InChIKey: " + exception.getMessage(), exception); } } /** * Gets auxillary information. */ public String getAuxInfo() { return(output.getAuxInfo()); } /** * Gets generated (error/warning) messages. */ public String getMessage() { return(output.getMessage()); } /** * Gets generated log. */ public String getLog() { return(output.getLog()); } }