/* $Revision$ $Author$ $Date$ * * Copyright (C) 2002-2007 Oliver Horlacher * * 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. * All we ask is that proper credit is given for our work, which includes * - but is not limited to - adding the above copyright notice to the beginning * of your source code files, and to any copyright notice that you may distribute * with programs based on this work. * * 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.smiles; import org.openscience.cdk.CDKConstants; import org.openscience.cdk.PseudoAtom; import org.openscience.cdk.annotations.TestClass; import org.openscience.cdk.annotations.TestMethod; import org.openscience.cdk.aromaticity.CDKHueckelAromaticityDetector; import org.openscience.cdk.config.IsotopeFactory; import org.openscience.cdk.exception.CDKException; import org.openscience.cdk.geometry.BondTools; import org.openscience.cdk.graph.ConnectivityChecker; import org.openscience.cdk.graph.invariant.CanonicalLabeler; import org.openscience.cdk.graph.invariant.MorganNumbersTools; import org.openscience.cdk.interfaces.IAtom; import org.openscience.cdk.interfaces.IAtomContainer; import org.openscience.cdk.interfaces.IAtomType; import org.openscience.cdk.interfaces.IBond; import org.openscience.cdk.interfaces.IChemObjectBuilder; import org.openscience.cdk.interfaces.IIsotope; import org.openscience.cdk.interfaces.IMolecule; import org.openscience.cdk.interfaces.IMoleculeSet; import org.openscience.cdk.interfaces.IPseudoAtom; import org.openscience.cdk.interfaces.IReaction; import org.openscience.cdk.interfaces.IRingSet; import org.openscience.cdk.ringsearch.AllRingsFinder; import org.openscience.cdk.ringsearch.RingPartitioner; import org.openscience.cdk.tools.manipulator.AtomContainerManipulator; import org.openscience.cdk.tools.manipulator.RingSetManipulator; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.TreeMap; import java.util.Vector; /** * Generates SMILES strings {@cdk.cite WEI88, WEI89}. It takes into account the * isotope and formal charge information of the atoms. In addition to this it * takes stereochemistry in account for both Bond's and Atom's. Via the flag * useAromaticity it can be set if only SP2-hybridized atoms shall be set to * lower case (default) or atoms, which are SP2 or aromatic. * * <p>Some example code: * <pre> * IMolecule benzene; // single/aromatic bonds between 6 carbons * SmilesGenerator sg = new SmilesGenerator(); * String smiles = sg.createSMILES(benzene); // C1CCCCC1 * sg.setUseAromaticityFlag(true); * smiles = sg.createSMILES(benzene); // c1ccccc1 * IMolecule benzene2; // one of the two kekule structures with explicit double bond orders * String smiles2 = sg.createSMILES(benzene2); // C1=CC=CC=C1 * </pre> * <b>Note</b>Due to the way the initial atom labeling is constructed, ensure * that the input molecule is appropriately configured. * In absence of such configuration it is possible that different forms * of the same molecule will not result in the same canonical SMILES. * * @author Oliver Horlacher * @author Stefan Kuhn (chiral smiles) * @cdk.created 2002-02-26 * @cdk.keyword SMILES, generator * @cdk.module smiles * @cdk.githash * @cdk.bug 1793446 */ @TestClass("org.openscience.cdk.smiles.SmilesGeneratorTest") public class SmilesGenerator { //private final static boolean debug = false; /** * The number of rings that have been opened */ private int ringMarker = 0; /** * Collection of all the bonds that were broken */ private List<BrokenBond> brokenBonds = new ArrayList<BrokenBond>(); /** * The isotope factory which is used to write the mass is needed */ private IsotopeFactory isotopeFactory; AllRingsFinder ringFinder; /** * RingSet that holds all rings of the molecule */ private IRingSet rings = null; /** * The canonical labler */ private CanonicalLabeler canLabler = new CanonicalLabeler(); private final String RING_CONFIG = "stereoconfig"; private final String UP = "up"; private final String DOWN = "down"; private boolean useAromaticityFlag=false; /** * Create the SMILES generator. */ public SmilesGenerator() {} /** * Create the SMILES generator. * @param userAromaticityFlag if false only SP2-hybridized atoms will be lower case (default), true=SP2 or aromaticity trigger lower case (same as using setUseAromaticityFlag later) */ public SmilesGenerator(boolean useAromaticityFlag) { this.useAromaticityFlag=useAromaticityFlag; } /** * Tells if a certain bond is center of a valid double bond configuration. * *@param container The atomcontainer. *@param bond The bond. *@return true=is a potential configuration, false=is not. */ public boolean isValidDoubleBondConfiguration(IAtomContainer container, IBond bond) { IAtom atom0 = bond.getAtom(0); IAtom atom1 = bond.getAtom(1); List<IAtom> connectedAtoms = container.getConnectedAtomsList(atom0); IAtom from = null; for (IAtom connectedAtom : connectedAtoms) { if (connectedAtom != atom1) { from = connectedAtom; } } boolean[] array = new boolean[container.getBondCount()]; for (int i = 0; i < array.length; i++) { array[i] = true; } if (isStartOfDoubleBond(container, atom0, from, array) && isEndOfDoubleBond(container, atom1, atom0, array) && !bond.getFlag(CDKConstants.ISAROMATIC)) { return (true); } else { return (false); } } /** * Provide a reference to a RingSet that holds ALL rings of the molecule.<BR> * During creation of a SMILES the aromaticity of the molecule has to be detected. * This, in turn, requires the dermination of all rings of the molecule. If this * computationally expensive calculation has been done beforehand, a RingSet can * be handed over to the SmilesGenerator to save the effort of another all-rings- * calculation. * * @param rings RingSet that holds ALL rings of the molecule * @return reference to the SmilesGenerator object this method was called for */ public SmilesGenerator setRings(IRingSet rings) { this.rings = rings; return this; } /** * Generate canonical SMILES from the <code>molecule</code>. This method * canonicaly lables the molecule but does not perform any checks on the * chemical validity of the molecule. * IMPORTANT: A precomputed Set of All Rings (SAR) can be passed to this * SmilesGenerator in order to avoid recomputing it. Use setRings() to * assign the SAR. * * @param molecule The molecule to evaluate * @see org.openscience.cdk.graph.invariant.CanonicalLabeler#canonLabel(IAtomContainer) * @return the SMILES representation of the molecule */ @TestMethod("testCisResorcinol,testEthylPropylPhenantren,testAlanin") public synchronized String createSMILES(IMolecule molecule) { try { return (createSMILES(molecule, false, new boolean[molecule.getBondCount()])); } catch (CDKException exception) { // This exception can only happen if a chiral smiles is requested return (""); } } /** * Generate a SMILES for the given <code>Reaction</code>. * @param reaction the reaction in question * @return the SMILES representation of the reaction * @throws org.openscience.cdk.exception.CDKException if there is an error during SMILES generation */ public synchronized String createSMILES(IReaction reaction) throws CDKException { StringBuffer reactionSMILES = new StringBuffer(); IMoleculeSet reactants = reaction.getReactants(); for (int i = 0; i < reactants.getAtomContainerCount(); i++) { reactionSMILES.append(createSMILES(reactants.getMolecule(i))); if (i + 1 < reactants.getAtomContainerCount()) { reactionSMILES.append('.'); } } reactionSMILES.append('>'); IMoleculeSet agents = reaction.getAgents(); for (int i = 0; i < agents.getAtomContainerCount(); i++) { reactionSMILES.append(createSMILES(agents.getMolecule(i))); if (i + 1 < agents.getAtomContainerCount()) { reactionSMILES.append('.'); } } reactionSMILES.append('>'); IMoleculeSet products = reaction.getProducts(); for (int i = 0; i < products.getAtomContainerCount(); i++) { reactionSMILES.append(createSMILES(products.getMolecule(i))); if (i + 1 < products.getAtomContainerCount()) { reactionSMILES.append('.'); } } return reactionSMILES.toString(); } /** * Generate canonical and chiral SMILES from the <code>molecule</code>. This * method canonicaly lables the molecule but dose not perform any checks on * the chemical validity of the molecule. The chiral smiles is done like in * the <a href="http://www.daylight.com/dayhtml/doc/theory/theory.smiles.html"> * daylight theory manual</a> . I did not find rules for canonical and chiral * smiles, therefore there is no guarantee that the smiles complies to any * externeal rules, but it is canonical compared to other smiles produced by * this method. The method checks if there are 2D coordinates but does not * check if coordinates make sense. Invalid stereo configurations are ignored; * if there are no valid stereo configuration the smiles will be the same as * the non-chiral one. Note that often stereo configurations are only complete * and can be converted to a smiles if explicit Hs are given. * IMPORTANT: A precomputed Set of All Rings (SAR) can be passed to this * SmilesGenerator in order to avoid recomputing it. Use setRings() to * assign the SAR. * * @param molecule The molecule to evaluate. * @param doubleBondConfiguration Should E/Z configurations be read at these positions? If the flag at position X is set to true, * an E/Z configuration will be written from coordinates around bond X, if false, it will be ignored. * If flag is true for a bond which does not constitute a valid double bond configuration, it will be * ignored (meaning setting all to true will create E/Z indication will be pu in the smiles wherever * possible, but note the coordinates might be arbitrary). * @exception CDKException At least one atom has no Point2D; * coordinates are needed for creating the chiral smiles. * @see org.openscience.cdk.graph.invariant.CanonicalLabeler#canonLabel(IAtomContainer) * @return the SMILES representation of the molecule */ @TestMethod("testAlaSMILES,testSugarSMILES") public synchronized String createChiralSMILES(IMolecule molecule, boolean[] doubleBondConfiguration) throws CDKException { return (createSMILES(molecule, true, doubleBondConfiguration)); } /** * Generate canonical SMILES from the <code>molecule</code>. This method * canonicaly lables the molecule but dose not perform any checks on the * chemical validity of the molecule. This method also takes care of multiple * molecules. * IMPORTANT: A precomputed Set of All Rings (SAR) can be passed to this * SmilesGenerator in order to avoid recomputing it. Use setRings() to * assign the SAR. * * @param molecule The molecule to evaluate. * @param chiral true=SMILES will be chiral, false=SMILES. * will not be chiral. * @param doubleBondConfiguration Should E/Z configurations be read at these positions? If the flag at position X is set to true, * an E/Z configuration will be written from coordinates around bond X, if false, it will be ignored. * If flag is true for a bond which does not constitute a valid double bond configuration, it will be * ignored (meaning setting all to true will create E/Z indication will be pu in the smiles wherever * possible, but note the coordinates might be arbitrary). * @exception CDKException At least one atom has no Point2D; * coordinates are needed for crating the chiral smiles. This excpetion * can only be thrown if chiral smiles is created, ignore it if you want a * non-chiral smiles (createSMILES(AtomContainer) does not throw an * exception). * @see org.openscience.cdk.graph.invariant.CanonicalLabeler#canonLabel(IAtomContainer) * @return the SMILES representation of the molecule */ public synchronized String createSMILES(IMolecule molecule, boolean chiral, boolean doubleBondConfiguration[]) throws CDKException { IMoleculeSet moleculeSet = ConnectivityChecker.partitionIntoMolecules(molecule); if (moleculeSet.getMoleculeCount() > 1) { StringBuffer fullSMILES = new StringBuffer(); for (int i = 0; i < moleculeSet.getAtomContainerCount(); i++) { IMolecule molPart = moleculeSet.getMolecule(i); fullSMILES.append(createSMILESWithoutCheckForMultipleMolecules( molPart, chiral, doubleBondConfiguration)); if (i < (moleculeSet.getAtomContainerCount() - 1)) { // are there more molecules? fullSMILES.append('.'); } } return fullSMILES.toString(); } else { return (createSMILESWithoutCheckForMultipleMolecules(molecule, chiral, doubleBondConfiguration)); } } /** * Generate canonical SMILES from the <code>molecule</code>. This method * canonicaly lables the molecule but dose not perform any checks on the * chemical validity of the molecule. Does not care about multiple molecules. * IMPORTANT: A precomputed Set of All Rings (SAR) can be passed to this * SmilesGenerator in order to avoid recomputing it. Use setRings() to * assign the SAR. * * @param molecule The molecule to evaluate. * @param chiral true=SMILES will be chiral, false=SMILES * will not be chiral. * @param doubleBondConfiguration Should E/Z configurations be read at these positions? If the flag at position X is set to true, * an E/Z configuration will be written from coordinates around bond X, if false, it will be ignored. * If flag is true for a bond which does not constitute a valid double bond configuration, it will be * ignored (meaning setting all to true will create E/Z indication will be pu in the smiles wherever * possible, but note the coordinates might be arbitrary). * @param detectAromaticity true=an aromaticity detection will be done * (using setRings avoids ring search for that), * false=no aromaticity detection will be done * @exception CDKException At least one atom has no Point2D; * coordinates are needed for creating the chiral smiles. This excpetion * can only be thrown if chiral smiles is created, ignore it if you want a * non-chiral smiles (createSMILES(AtomContainer) does not throw an * exception). *@see org.openscience.cdk.graph.invariant.CanonicalLabeler#canonLabel(IAtomContainer) * @return the SMILES representation of the molecule */ @TestMethod("testCreateSMILESWithoutCheckForMultipleMolecules_withDetectAromaticity,testCreateSMILESWithoutCheckForMultipleMolecules_withoutDetectAromaticity") public synchronized String createSMILESWithoutCheckForMultipleMolecules(IMolecule molecule, boolean chiral, boolean doubleBondConfiguration[]) throws CDKException { if (molecule.getAtomCount() == 0) { return ""; } canLabler.canonLabel(molecule); brokenBonds.clear(); ringMarker = 0; IAtom start = null; for (int i = 0; i < molecule.getAtomCount(); i++) { IAtom atom = molecule.getAtom(i); if (chiral && atom.getPoint2d() == null) { throw new CDKException("Atom number " + i + " has no 2D coordinates, but 2D coordinates are needed for creating chiral smiles"); } //logger.debug("Setting all VISITED flags to false"); atom.setFlag(CDKConstants.VISITED, false); if ((Long) atom.getProperty("CanonicalLable") == 1) { start = atom; } } //detect aromaticity if(useAromaticityFlag || chiral){ if(rings == null){ if (ringFinder == null) { ringFinder = new AllRingsFinder(); } rings = ringFinder.findAllRings(molecule); } AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(molecule); CDKHueckelAromaticityDetector.detectAromaticity(molecule); } if (chiral && rings.getAtomContainerCount() > 0) { List v = RingPartitioner.partitionRings(rings); //logger.debug("RingSystems: " + v.size()); for (int i = 0; i < v.size(); i++) { int counter = 0; Iterator<IAtomContainer> containers = RingSetManipulator.getAllAtomContainers((IRingSet) v.get(i)).iterator(); while (containers.hasNext()) { IAtomContainer allrings = (IAtomContainer) containers.next(); for (int k = 0; k < allrings.getAtomCount(); k++) { if (!BondTools.isStereo(molecule, allrings.getAtom(k)) && hasWedges(molecule, allrings.getAtom(k)) != null) { IBond bond = molecule.getBond(allrings.getAtom(k), hasWedges(molecule, allrings.getAtom(k))); if (bond.getStereo() == IBond.Stereo.UP) { allrings.getAtom(k).setProperty(RING_CONFIG, UP); } else { allrings.getAtom(k).setProperty(RING_CONFIG, DOWN); } counter++; } } if (counter == 1) { for (int k = 0; k < allrings.getAtomCount(); k++) { IBond bond = molecule.getBond(allrings.getAtom(k), hasWedges(molecule, allrings.getAtom(k))); if(bond!=null){ if (bond.getStereo() == IBond.Stereo.UP) { allrings.getAtom(k).setProperty(RING_CONFIG, UP); } else { allrings.getAtom(k).setProperty(RING_CONFIG, DOWN); } } } } } } } StringBuffer l = new StringBuffer(); createSMILES(start, l, molecule, chiral, doubleBondConfiguration,useAromaticityFlag); rings = null; // remove all CanonicalLable/InvariancePair props for (int k = 0; k < molecule.getAtomCount(); k++) { molecule.getAtom(k).removeProperty("CanonicalLable"); molecule.getAtom(k).removeProperty("InvariancePair"); } return l.toString(); } private IAtom hasWedges(IAtomContainer ac, IAtom a) { List<IAtom> atoms = ac.getConnectedAtomsList(a); // for (int i = 0; i < atoms.size(); i++) // { // atomi = (IAtom)atoms.get(i); // if (ac.getBond(a, atomi).getStereo() != IBond.Stereo.NONE && !atomi.getSymbol().equals("H")) // { // return (atomi); // } // } for (IAtom atom : atoms) { if (ac.getBond(a, atom).getStereo() != IBond.Stereo.NONE) { return (atom); } } return (null); } /** * Says if an atom is the end of a double bond configuration * *@param atom The atom which is the end of configuration *@param container The atomContainer the atom is in *@param parent The atom we came from *@param doubleBondConfiguration The array indicating where double bond * configurations are specified (this method ensures that there is * actually the possibility of a double bond configuration) *@return false=is not end of configuration, true=is */ private boolean isEndOfDoubleBond(IAtomContainer container, IAtom atom, IAtom parent, boolean[] doubleBondConfiguration) { if (container.getBondNumber(atom, parent) == -1 || doubleBondConfiguration.length <= container.getBondNumber(atom, parent) || !doubleBondConfiguration[container.getBondNumber(atom, parent)]) { return false; } // TO-DO: We make the silent assumption of unset hydrogen count equals zero hydrogen count here. int lengthAtom = container.getConnectedAtomsCount(atom) + ((atom.getHydrogenCount() == CDKConstants.UNSET) ? 0 : atom.getHydrogenCount()); // TO-DO: We make the silent assumption of unset hydrogen count equals zero hydrogen count here. int lengthParent = container.getConnectedAtomsCount(parent) + ((parent.getHydrogenCount() == CDKConstants.UNSET) ? 0 : parent.getHydrogenCount()); if (container.getBond(atom, parent) != null) { if (container.getBond(atom, parent).getOrder() == CDKConstants.BONDORDER_DOUBLE && (lengthAtom == 3 || (lengthAtom == 2 && atom.getSymbol().equals("N"))) && (lengthParent == 3 || (lengthParent == 2 && parent.getSymbol().equals("N")))) { List<IAtom> atoms = container.getConnectedAtomsList(atom); IAtom one = null; IAtom two = null; IAtom atomi = null; for (int i = 0; i < atoms.size(); i++) { atomi = (IAtom)container.getAtom(i); if (atomi != parent && one == null) { one = atomi; } else if (atomi != parent && one != null) { two = atomi; } } String[] morgannumbers = MorganNumbersTools.getMorganNumbersWithElementSymbol(container); if ((one != null && two == null && atom.getSymbol().equals("N") && Math.abs(BondTools.giveAngleBothMethods(parent, atom, one, true)) > Math.PI / 10) || (!atom.getSymbol().equals("N") && one != null && two != null && !morgannumbers[container.getAtomNumber(one)].equals(morgannumbers[container.getAtomNumber(two)]))) { return (true); } else { return (false); } } } return (false); } /** * Says if an atom is the start of a double bond configuration * *@param a The atom which is the start of configuration *@param container The atomContainer the atom is in *@param parent The atom we came from *@param doubleBondConfiguration The array indicating where double bond * configurations are specified (this method ensures that there is * actually the possibility of a double bond configuration) *@return false=is not start of configuration, true=is */ private boolean isStartOfDoubleBond(IAtomContainer container, IAtom a, IAtom parent, boolean[] doubleBondConfiguration) { // TO-DO: We make the silent assumption of unset hydrogen count equals zero hydrogen count here. int lengthAtom = container.getConnectedAtomsCount(a) + ((a.getHydrogenCount() == CDKConstants.UNSET) ? 0 : a.getHydrogenCount()); if (lengthAtom != 3 && (lengthAtom != 2 && !a.getSymbol().equals("N"))) { return (false); } List<IAtom> atoms = container.getConnectedAtomsList(a); IAtom one = null; IAtom two = null; boolean doubleBond = false; IAtom nextAtom = null; for (IAtom atomi : atoms) { if (atomi != parent && container.getBond(atomi, a).getOrder() == CDKConstants.BONDORDER_DOUBLE && isEndOfDoubleBond(container, atomi, a, doubleBondConfiguration)) { doubleBond = true; nextAtom = atomi; } if (atomi != nextAtom && one == null) { one = atomi; } else if (atomi != nextAtom && one != null) { two = atomi; } } String[] morgannumbers = MorganNumbersTools.getMorganNumbersWithElementSymbol(container); if (one != null && ((!a.getSymbol().equals("N") && two != null && !morgannumbers[container.getAtomNumber(one)].equals(morgannumbers[container.getAtomNumber(two)]) && doubleBond && doubleBondConfiguration[container.getBondNumber(a, nextAtom)]) || (doubleBond && a.getSymbol().equals("N") && Math.abs(BondTools.giveAngleBothMethods(nextAtom, a, parent, true)) > Math.PI / 10))) { return (true); } else { return (false); } } /** * Gets the bondBroken attribute of the SmilesGenerator object */ private boolean isBondBroken(IAtom a1, IAtom a2) { for (BrokenBond bond : brokenBonds) { if ((bond.getA1().equals(a1) || bond.getA1().equals(a2)) && (bond.getA2().equals(a1) || bond.getA2().equals(a2))) { return (true); } } return false; } /** * Determines if the atom <code>a</code> is a atom with a ring marker. * *@param a the atom to test *@return true if the atom participates in a bond that was broken in the * first pass. */ // private boolean isRingOpening(IAtom a) // { // Iterator it = brokenBonds.iterator(); // while (it.hasNext()) // { // BrokenBond bond = (BrokenBond) it.next(); // if (bond.getA1().equals(a) || bond.getA2().equals(a)) // { // return true; // } // } // return false; // } /** * Determines if the atom <code>a</code> is a atom with a ring marker. * *@return true if the atom participates in a bond that was broken in the * first pass. */ private boolean isRingOpening(IAtom a1, List v) { for (BrokenBond bond : brokenBonds) { for (Object aV : v) { if ((bond.getA1().equals(a1) && bond.getA2().equals((IAtom) aV)) || (bond.getA1().equals((IAtom) aV) && bond.getA2().equals(a1))) { return true; } } } return false; } /** * Return the neighbours of atom <code>a</code> in canonical order with the * atoms that have high bond order at the front. * *@param a the atom whose neighbours are to be found. *@param container the AtomContainer that is being parsed. *@return Vector of atoms in canonical oreder. */ private List getCanNeigh(final IAtom a, final IAtomContainer container) { List<IAtom> v = container.getConnectedAtomsList(a); if (v.size() > 1) { Collections.sort(v, new Comparator() { public int compare(Object o1, Object o2) { return (int) ((Long) ((IAtom) o1).getProperty("CanonicalLable") - (Long) ((IAtom) o2).getProperty("CanonicalLable")); } }); } return v; } /** * Gets the ringOpenings attribute of the SmilesGenerator object */ private List getRingOpenings(IAtom a, List vbonds) { Iterator it = brokenBonds.iterator(); List v = new Vector(10); while (it.hasNext()) { BrokenBond bond = (BrokenBond) it.next(); if (bond.getA1().equals(a) || bond.getA2().equals(a)) { v.add(bond.getMarker()); if (vbonds != null) { vbonds.add(bond.getA1().equals(a) ? bond.getA2() : bond.getA1()); } } } Collections.sort(v); return v; } /** * Returns true if the <code>atom</code> in the <code>container</code> has * been marked as a chiral center by the user. */ // private boolean isChiralCenter(IAtom atom, IAtomContainer container) // { // IBond[] bonds = container.getConnectedBonds(atom); // for (int i = 0; i < bonds.length; i++) // { // IBond bond = bonds[i]; // int stereo = bond.getStereo(); // if (stereo == IBond.Stereo.DOWN || // stereo == IBond.Stereo.UP) // { // return true; // } // } // return false; // } /** * Gets the last atom object (not Vector) in a Vector as created by * createDSFTree. * *@param v The Vector *@param result The feature to be added to the Atoms attribute */ private void addAtoms(List v, List result) { for (Object aV : v) { if (aV instanceof IAtom) { result.add((IAtom) aV); } else { addAtoms((List) aV, result); } } } /** * Performes a DFS search on the <code>atomContainer</code>. Then parses the * resulting tree to create the SMILES string. * *@param a the atom to start the search at. *@param line the StringBuffer that the SMILES is to be * appended to. *@param chiral true=SMILES will be chiral, false=SMILES * will not be chiral. *@param doubleBondConfiguration Should E/Z configurations be read at these positions? If the flag at position X is set to true, * an E/Z configuration will be written from coordinates around bond X, if false, it will be ignored. * If flag is true for a bond which does not constitute a valid double bond configuration, it will be * ignored (meaning setting all to true will create E/Z indication will be pu in the smiles wherever * possible, but note the coordinates might be arbitrary). *@param atomContainer the AtomContainer that the SMILES string is * generated for. *@param useAromaticity true=aromaticity or sp2 will trigger lower case letters, wrong=only sp2 */ private void createSMILES(IAtom a, StringBuffer line, IAtomContainer atomContainer, boolean chiral, boolean[] doubleBondConfiguration, boolean useAromaticity) { List tree = new Vector(); // set all ISVISITED labels to FALSE Iterator atoms = atomContainer.atoms().iterator(); while (atoms.hasNext()) ((IAtom)atoms.next()).setFlag(CDKConstants.VISITED, false); createDFSTree(a, tree, null, atomContainer); //logger.debug("Done with tree"); parseChain(tree, line, atomContainer, null, chiral, doubleBondConfiguration, new Vector(), useAromaticity); } /** * Recursively perform a DFS search on the <code>container</code> placing * atoms and branches in the vector <code>tree</code>. * *@param a the atom being visited. *@param tree vector holding the tree. *@param parent the atom we came from. *@param container the AtomContainer that we are parsing. */ private void createDFSTree(IAtom a, List tree, IAtom parent, IAtomContainer container) { tree.add(a); List neighbours = new ArrayList(getCanNeigh(a, container)); neighbours.remove(parent); IAtom next; a.setFlag(CDKConstants.VISITED, true); //logger.debug("Starting with DFSTree and AtomContainer of size " + container.getAtomCount()); //logger.debug("Current Atom has " + neighbours.size() + " neighbours"); Iterator iter = neighbours.iterator(); while (iter.hasNext()) { next = (IAtom)iter.next(); if (!next.getFlag(CDKConstants.VISITED)) { if (!iter.hasNext()) { //Last neighbour therefore in this chain createDFSTree(next, tree, a, container); } else { List branch = new Vector(); tree.add(branch); //logger.debug("adding branch"); createDFSTree(next, branch, a, container); } } else { //Found ring closure between next and a //logger.debug("found ringclosure in DFTTreeCreation"); ringMarker++; BrokenBond bond = new BrokenBond(a, next, ringMarker); if (!brokenBonds.contains(bond)) { brokenBonds.add(bond); } else { ringMarker--; } } } } /** * Parse a branch */ private void parseChain(List v, StringBuffer buffer, IAtomContainer container, IAtom parent, boolean chiral, boolean[] doubleBondConfiguration, List atomsInOrderOfSmiles, boolean useAromaticity) { int positionInVector = 0; IAtom atom; //logger.debug("in parse chain. Size of tree: " + v.size()); for (int h = 0; h < v.size(); h++) { Object o = v.get(h); if (o instanceof IAtom) { atom = (IAtom) o; if (parent != null) { parseBond(buffer, atom, parent, container, useAromaticity); } else { if (chiral && BondTools.isStereo(container, atom)) { parent = (IAtom) ((List) v.get(1)).get(0); } } parseAtom(atom, buffer, container, chiral, doubleBondConfiguration, parent, atomsInOrderOfSmiles, v, useAromaticity); //logger.debug("in parseChain after parseAtom()"); /* * The principle of making chiral smiles is quite simple, although the code is * pretty uggly. The Atoms connected to the chiral center are put in sorted[] in the * order they have to appear in the smiles. Then the Vector v is rearranged according * to sorted[] */ if (chiral && BondTools.isStereo(container, atom) && container.getBond(parent, atom) != null) { //logger.debug("in parseChain in isChiral"); IAtom[] sorted = null; List chiralNeighbours = container.getConnectedAtomsList(atom); if (BondTools.isTetrahedral(container, atom,false) > 0) { sorted = new IAtom[3]; } if (BondTools.isTetrahedral(container, atom,false) == 1) { if (container.getBond(parent, atom).getStereo() == IBond.Stereo.DOWN) { for (int i = 0; i < chiralNeighbours.size(); i++) { if (chiralNeighbours.get(i) != parent) { if ((container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == null || container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.NONE) && BondTools.isLeft(((IAtom) chiralNeighbours.get(i)), parent, atom) && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { sorted[2] = (IAtom) chiralNeighbours.get(i); } if ((container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == null || container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.NONE) && !BondTools.isLeft(((IAtom) chiralNeighbours.get(i)), parent, atom) && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { sorted[1] = (IAtom) chiralNeighbours.get(i); } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.UP && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { sorted[0] = (IAtom) chiralNeighbours.get(i); } } } } if (container.getBond(parent, atom).getStereo() == IBond.Stereo.UP) { for (int i = 0; i < chiralNeighbours.size(); i++) { if (chiralNeighbours.get(i) != parent) { if ((container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == null || container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.NONE) && BondTools.isLeft(((IAtom) chiralNeighbours.get(i)), parent, atom) && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { sorted[1] = (IAtom) chiralNeighbours.get(i); } if ((container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == null || container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.NONE) && !BondTools.isLeft(((IAtom) chiralNeighbours.get(i)), parent, atom) && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { sorted[2] = (IAtom) chiralNeighbours.get(i); } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.DOWN && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { sorted[0] = (IAtom) chiralNeighbours.get(i); } } } } if (container.getBond(parent, atom).getStereo() == CDKConstants.UNSET || container.getBond(parent, atom).getStereo() == IBond.Stereo.NONE) { boolean normalBindingIsLeft = false; for (int i = 0; i < chiralNeighbours.size(); i++) { if (chiralNeighbours.get(i) != parent) { if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == null || container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.NONE) { if (BondTools.isLeft(((IAtom) chiralNeighbours.get(i)), parent, atom)) { normalBindingIsLeft = true; break; } } } } for (int i = 0; i < chiralNeighbours.size(); i++) { if (chiralNeighbours.get(i) != parent) { if (normalBindingIsLeft) { if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == null || container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.NONE) { sorted[0] = (IAtom) chiralNeighbours.get(i); } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.UP) { sorted[2] = (IAtom) chiralNeighbours.get(i); } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.DOWN) { sorted[1] = (IAtom) chiralNeighbours.get(i); } } else { if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.UP) { sorted[1] = (IAtom) chiralNeighbours.get(i); } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == null || container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.NONE) { sorted[0] = (IAtom) chiralNeighbours.get(i); } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.DOWN) { sorted[2] = (IAtom) chiralNeighbours.get(i); } } } } } } if (BondTools.isTetrahedral(container, atom,false) == 2) { if (container.getBond(parent, atom).getStereo() == IBond.Stereo.UP) { for (int i = 0; i < chiralNeighbours.size(); i++) { if (chiralNeighbours.get(i) != parent) { if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.DOWN && BondTools.isLeft(((IAtom) chiralNeighbours.get(i)), parent, atom) && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { sorted[1] = (IAtom) chiralNeighbours.get(i); } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.DOWN && !BondTools.isLeft(((IAtom) chiralNeighbours.get(i)), parent, atom) && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { sorted[2] = (IAtom) chiralNeighbours.get(i); } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.UP && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { sorted[0] = (IAtom) chiralNeighbours.get(i); } } } } if (container.getBond(parent, atom).getStereo() == IBond.Stereo.DOWN) { double angle1 = 0; double angle2 = 0; IAtom atom1 = null; IAtom atom2 = null; for (int i = 0; i < chiralNeighbours.size(); i++) { if (chiralNeighbours.get(i) != parent) { if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.UP && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { if (angle1 == 0) { angle1 = BondTools.giveAngle(atom, parent, (IAtom) chiralNeighbours.get(i)); atom1 = (IAtom) chiralNeighbours.get(i); } else { angle2 = BondTools.giveAngle(atom, parent, (IAtom) chiralNeighbours.get(i)); atom2 = (IAtom) chiralNeighbours.get(i); } } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.DOWN && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { sorted[1] = (IAtom) chiralNeighbours.get(i); } } } if (angle1 < angle2) { sorted[0] = atom2; sorted[2] = atom1; } else { sorted[0] = atom1; sorted[2] = atom2; } } } if (BondTools.isTetrahedral(container, atom,false) == 3) { if (container.getBond(parent, atom).getStereo() == IBond.Stereo.UP) { TreeMap hm = new TreeMap(); for (int i = 0; i < chiralNeighbours.size(); i++) { if (chiralNeighbours.get(i) != parent && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { hm.put(new Double(BondTools.giveAngle(atom, parent, ((IAtom) chiralNeighbours.get(i)))), Integer.valueOf(i)); } } Object[] ohere = hm.values().toArray(); for (int i = ohere.length - 1; i > -1; i--) { sorted[i] = ((IAtom) chiralNeighbours.get(((Integer) ohere[i]).intValue())); } } if (container.getBond(parent, atom).getStereo() == null || container.getBond(parent, atom).getStereo() == IBond.Stereo.NONE) { double angle1 = 0; double angle2 = 0; IAtom atom1 = null; IAtom atom2 = null; for (int i = 0; i < chiralNeighbours.size(); i++) { if (chiralNeighbours.get(i) != parent) { if ((container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == null || container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.NONE) && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { if (angle1 == 0) { angle1 = BondTools.giveAngle(atom, parent, (IAtom) chiralNeighbours.get(i)); atom1 = (IAtom) chiralNeighbours.get(i); } else { angle2 = BondTools.giveAngle(atom, parent, (IAtom) chiralNeighbours.get(i)); atom2 = (IAtom) chiralNeighbours.get(i); } } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.UP && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { sorted[0] = (IAtom) chiralNeighbours.get(i); } } } if (angle1 < angle2) { sorted[1] = atom2; sorted[2] = atom1; } else { sorted[1] = atom1; sorted[2] = atom2; } } } if (BondTools.isTetrahedral(container, atom,false) == 4) { if (container.getBond(parent, atom).getStereo() == IBond.Stereo.DOWN) { TreeMap hm = new TreeMap(); for (int i = 0; i < chiralNeighbours.size(); i++) { if (chiralNeighbours.get(i) != parent && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { hm.put(new Double(BondTools.giveAngle(atom, parent, ((IAtom) chiralNeighbours.get(i)))), Integer.valueOf(i)); } } Object[] ohere = hm.values().toArray(); for (int i = ohere.length - 1; i > -1; i--) { sorted[i] = ((IAtom) chiralNeighbours.get(((Integer) ohere[i]).intValue())); } } if (container.getBond(parent, atom).getStereo() == null || container.getBond(parent, atom).getStereo() == IBond.Stereo.NONE) { double angle1 = 0; double angle2 = 0; IAtom atom1 = null; IAtom atom2 = null; for (int i = 0; i < chiralNeighbours.size(); i++) { if (chiralNeighbours.get(i) != parent) { if ((container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == null || container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.NONE) && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { if (angle1 == 0) { angle1 = BondTools.giveAngle(atom, parent, (IAtom) chiralNeighbours.get(i)); atom1 = (IAtom) chiralNeighbours.get(i); } else { angle2 = BondTools.giveAngle(atom, parent, (IAtom) chiralNeighbours.get(i)); atom2 = (IAtom) chiralNeighbours.get(i); } } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.DOWN && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { sorted[2] = (IAtom) chiralNeighbours.get(i); } } } if (angle1 < angle2) { sorted[1] = atom2; sorted[0] = atom1; } else { sorted[1] = atom1; sorted[0] = atom2; } } } if (BondTools.isTetrahedral(container, atom,false) == 5) { if (container.getBond(parent, atom).getStereo() == IBond.Stereo.DOWN) { for (int i = 0; i < chiralNeighbours.size(); i++) { if (chiralNeighbours.get(i) != parent) { if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.UP) { sorted[0] = (IAtom) chiralNeighbours.get(i); } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == null || container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.NONE) { sorted[2] = (IAtom) chiralNeighbours.get(i); } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.DOWN) { sorted[1] = (IAtom) chiralNeighbours.get(i); } } } } if (container.getBond(parent, atom).getStereo() == IBond.Stereo.UP) { for (int i = 0; i < chiralNeighbours.size(); i++) { if (chiralNeighbours.get(i) != parent) { if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.DOWN && BondTools.isLeft(((IAtom) chiralNeighbours.get(i)), parent, atom) && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { sorted[0] = (IAtom) chiralNeighbours.get(i); } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.DOWN && !BondTools.isLeft(((IAtom) chiralNeighbours.get(i)), parent, atom) && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { sorted[2] = (IAtom) chiralNeighbours.get(i); } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == null || container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.NONE) { sorted[1] = (IAtom) chiralNeighbours.get(i); } } } } if (container.getBond(parent, atom).getStereo() == CDKConstants.UNSET || container.getBond(parent, atom).getStereo() == IBond.Stereo.NONE) { for (int i = 0; i < chiralNeighbours.size(); i++) { if (chiralNeighbours.get(i) != parent) { if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.DOWN && BondTools.isLeft(((IAtom) chiralNeighbours.get(i)), parent, atom) && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { sorted[0] = (IAtom) chiralNeighbours.get(i); } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.DOWN && !BondTools.isLeft(((IAtom) chiralNeighbours.get(i)), parent, atom) && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { sorted[2] = (IAtom) chiralNeighbours.get(i); } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.UP) { sorted[1] = (IAtom) chiralNeighbours.get(i); } } } } } if (BondTools.isTetrahedral(container, atom,false) == 6) { if (container.getBond(parent, atom).getStereo() == IBond.Stereo.UP) { for (int i = 0; i < chiralNeighbours.size(); i++) { if (chiralNeighbours.get(i) != parent) { if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.UP) { sorted[0] = (IAtom) chiralNeighbours.get(i); } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == null || container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.NONE) { sorted[2] = (IAtom) chiralNeighbours.get(i); } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.DOWN) { sorted[1] = (IAtom) chiralNeighbours.get(i); } } } } if (container.getBond(parent, atom).getStereo() == IBond.Stereo.DOWN) { for (int i = 0; i < chiralNeighbours.size(); i++) { if (chiralNeighbours.get(i) != parent) { if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.UP && BondTools.isLeft(((IAtom) chiralNeighbours.get(i)), parent, atom) && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { sorted[2] = (IAtom) chiralNeighbours.get(i); } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.UP && !BondTools.isLeft(((IAtom) chiralNeighbours.get(i)), parent, atom) && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { sorted[0] = (IAtom) chiralNeighbours.get(i); } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == null || container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.NONE) { sorted[1] = (IAtom) chiralNeighbours.get(i); } } } } if (container.getBond(parent, atom).getStereo() == CDKConstants.UNSET || container.getBond(parent, atom).getStereo() == IBond.Stereo.NONE) { for (int i = 0; i < chiralNeighbours.size(); i++) { if (chiralNeighbours.get(i) != parent) { if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.UP && BondTools.isLeft(((IAtom) chiralNeighbours.get(i)), parent, atom) && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { sorted[2] = (IAtom) chiralNeighbours.get(i); } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.UP && !BondTools.isLeft(((IAtom) chiralNeighbours.get(i)), parent, atom) && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { sorted[0] = (IAtom) chiralNeighbours.get(i); } if (container.getBond((IAtom) chiralNeighbours.get(i), atom).getStereo() == IBond.Stereo.DOWN) { sorted[1] = (IAtom) chiralNeighbours.get(i); } } } } } if (BondTools.isSquarePlanar(container, atom)) { sorted = new IAtom[3]; //This produces a U=SP1 order in every case TreeMap hm = new TreeMap(); for (int i = 0; i < chiralNeighbours.size(); i++) { if (chiralNeighbours.get(i) != parent && !isBondBroken((IAtom) chiralNeighbours.get(i), atom)) { hm.put(new Double(BondTools.giveAngle(atom, parent, ((IAtom) chiralNeighbours.get(i)))), Integer.valueOf(i)); } } Object[] ohere = hm.values().toArray(); for (int i = 0; i < ohere.length; i++) { sorted[i] = ((IAtom) chiralNeighbours.get(((Integer) ohere[i]).intValue())); } } if (BondTools.isTrigonalBipyramidalOrOctahedral(container, atom)!=0) { sorted = new IAtom[container.getConnectedAtomsCount(atom) - 1]; TreeMap hm = new TreeMap(); if (container.getBond(parent, atom).getStereo() == IBond.Stereo.UP) { for (int i = 0; i < chiralNeighbours.size(); i++) { if (container.getBond(atom, (IAtom) chiralNeighbours.get(i)).getStereo() == null || container.getBond(atom, (IAtom) chiralNeighbours.get(i)).getStereo() == IBond.Stereo.NONE) { hm.put(new Double(BondTools.giveAngle(atom, parent, ((IAtom) chiralNeighbours.get(i)))), Integer.valueOf(i)); } if (container.getBond(atom, (IAtom) chiralNeighbours.get(i)).getStereo() == IBond.Stereo.DOWN) { sorted[sorted.length - 1] = (IAtom) chiralNeighbours.get(i); } } Object[] ohere = hm.values().toArray(); for (int i = 0; i < ohere.length; i++) { sorted[i] = ((IAtom) chiralNeighbours.get(((Integer) ohere[i]).intValue())); } } if (container.getBond(parent, atom).getStereo() == IBond.Stereo.DOWN) { for (int i = 0; i < chiralNeighbours.size(); i++) { if (container.getBond(atom, (IAtom) chiralNeighbours.get(i)).getStereo() == null || container.getBond(atom, (IAtom) chiralNeighbours.get(i)).getStereo() == IBond.Stereo.NONE) { hm.put(new Double(BondTools.giveAngle(atom, parent, ((IAtom) chiralNeighbours.get(i)))), Integer.valueOf(i)); } if (container.getBond(atom, (IAtom) chiralNeighbours.get(i)).getStereo() == IBond.Stereo.UP) { sorted[sorted.length - 1] = (IAtom) chiralNeighbours.get(i); } } Object[] ohere = hm.values().toArray(); for (int i = 0; i < ohere.length; i++) { sorted[i] = ((IAtom) chiralNeighbours.get(((Integer) ohere[i]).intValue())); } } if (container.getBond(parent, atom).getStereo() == null || container.getBond(parent, atom).getStereo() == IBond.Stereo.NONE) { for (int i = 0; i < chiralNeighbours.size(); i++) { if (chiralNeighbours.get(i) != parent) { if (container.getBond(atom, (IAtom) chiralNeighbours.get(i)).getStereo() == null || container.getBond(atom, (IAtom) chiralNeighbours.get(i)).getStereo() == IBond.Stereo.NONE) { hm.put(new Double((BondTools.giveAngleFromMiddle(atom, parent, ((IAtom) chiralNeighbours.get(i))))), Integer.valueOf(i)); } if (container.getBond(atom, (IAtom) chiralNeighbours.get(i)).getStereo() == IBond.Stereo.UP) { sorted[0] = (IAtom) chiralNeighbours.get(i); } if (container.getBond(atom, (IAtom) chiralNeighbours.get(i)).getStereo() == IBond.Stereo.DOWN) { sorted[sorted.length - 2] = (IAtom) chiralNeighbours.get(i); } } } Object[] ohere = hm.values().toArray(); sorted[sorted.length - 1] = ((IAtom) chiralNeighbours.get(((Integer) ohere[ohere.length - 1]).intValue())); if (ohere.length == 2) { sorted[sorted.length - 3] = ((IAtom) chiralNeighbours.get(((Integer) ohere[0]).intValue())); if (BondTools.giveAngleFromMiddle(atom, parent, ((IAtom) chiralNeighbours.get(((Integer) ohere[1]).intValue()))) < 0) { IAtom dummy = sorted[sorted.length - 2]; sorted[sorted.length - 2] = sorted[0]; sorted[0] = dummy; } } if (ohere.length == 3) { sorted[sorted.length - 3] = sorted[sorted.length - 2]; sorted[sorted.length - 2] = ((IAtom) chiralNeighbours.get(((Integer) ohere[ohere.length - 2]).intValue())); sorted[sorted.length - 4] = ((IAtom) chiralNeighbours.get(((Integer) ohere[ohere.length - 3]).intValue())); } } } //This builds an onew[] containing the objects after the center of the chirality in the order given by sorted[] if (sorted != null) { int numberOfAtoms = 3; if (BondTools.isTrigonalBipyramidalOrOctahedral(container, atom)!=0) { numberOfAtoms = container.getConnectedAtomsCount(atom) - 1; } Object[] omy = new Object[numberOfAtoms]; Object[] onew = new Object[numberOfAtoms]; for (int k = getRingOpenings(atom, null).size(); k < numberOfAtoms; k++) { if (positionInVector + 1 + k - getRingOpenings(atom, null).size() < v.size()) { omy[k] = v.get(positionInVector + 1 + k - getRingOpenings(atom, null).size()); } } for (int k = 0; k < sorted.length; k++) { if (sorted[k] != null) { for (int m = 0; m < omy.length; m++) { if (omy[m] instanceof IAtom) { if (omy[m] == sorted[k]) { onew[k] = omy[m]; } } else { if (omy[m] == null) { onew[k] = null; } else { if (((List) omy[m]).get(0) == sorted[k]) { onew[k] = omy[m]; } } } } } else { onew[k] = null; } } //This is a workaround for 3624.MOL.2 I don't have a better solution currently boolean doubleentry = false; for (int m = 0; m < onew.length; m++) { for (int k = 0; k < onew.length; k++) { if (m != k && onew[k] == onew[m]) { doubleentry = true; } } } if (!doubleentry) { //Make sure that the first atom in onew is the first one in the original smiles order. This is important to have a canonical smiles. if (positionInVector + 1 < v.size()) { Object atomAfterCenterInOriginalSmiles = v.get(positionInVector + 1); int l = 0; while (onew[0] != atomAfterCenterInOriginalSmiles) { Object placeholder = onew[onew.length - 1]; for (int k = onew.length - 2; k > -1; k--) { onew[k + 1] = onew[k]; } onew[0] = placeholder; l++; if (l > onew.length) { break; } } } //This cares about ring openings. Here the ring closure (represendted by a figure) must be the first atom. In onew the closure is null. if (getRingOpenings(atom, null).size() > 0) { int l = 0; while (onew[0] != null) { Object placeholder = onew[0]; for (int k = 1; k < onew.length; k++) { onew[k - 1] = onew[k]; } onew[onew.length - 1] = placeholder; l++; if (l > onew.length) { break; } } } //The last in onew is a vector: This means we need to exchange the rest of the original smiles with the rest of this vector. if (onew[numberOfAtoms - 1] instanceof List) { for (int i = 0; i < numberOfAtoms; i++) { if (onew[i] instanceof IAtom) { List vtemp = new Vector(); vtemp.add(onew[i]); for (int k = positionInVector + 1 + numberOfAtoms; k < v.size(); k++) { vtemp.add(v.get(k)); } onew[i] = vtemp; for (int k = v.size() - 1; k > positionInVector + 1 + numberOfAtoms - 1; k--) { v.remove(k); } for (int k = 1; k < ((List) onew[numberOfAtoms - 1]).size(); k++) { v.add(((List) onew[numberOfAtoms - 1]).get(k)); } onew[numberOfAtoms - 1] = ((List) onew[numberOfAtoms - 1]).get(0); break; } } } //Put the onew objects in the original Vector int k = 0; for (int m = 0; m < onew.length; m++) { if (onew[m] != null) { v.set(positionInVector + 1 + k, onew[m]); k++; } } } } } parent = atom; } else { //Have Vector //logger.debug("in parseChain after else"); boolean brackets = true; List result = new Vector(); addAtoms((List) o, result); if (isRingOpening(parent, result) && container.getConnectedBondsCount(parent) < 4) { brackets = false; } if (brackets) { buffer.append('('); } parseChain((List) o, buffer, container, parent, chiral, doubleBondConfiguration, atomsInOrderOfSmiles, useAromaticity); if (brackets) { buffer.append(')'); } } positionInVector++; //logger.debug("in parseChain after positionVector++"); } } /** * Append the symbol for the bond order between <code>a1</code> and <code>a2</code> * to the <code>line</code>. * *@param line the StringBuffer that the bond symbol is appended to. *@param a1 Atom participating in the bond. *@param a2 Atom participating in the bond. *@param atomContainer the AtomContainer that the SMILES string is generated * for. *@param useAromaticity true=aromaticity or sp2 will trigger lower case letters, wrong=only sp2 */ private void parseBond(StringBuffer line, IAtom a1, IAtom a2, IAtomContainer atomContainer, boolean useAromaticity) { //logger.debug("in parseBond()"); if (useAromaticity && a1.getFlag(CDKConstants.ISAROMATIC) && a2.getFlag(CDKConstants.ISAROMATIC)) { return; } if (atomContainer.getBond(a1, a2) == null) { return; } IBond.Order type = atomContainer.getBond(a1, a2).getOrder(); if (type == IBond.Order.SINGLE) { } else if (type == IBond.Order.DOUBLE) { line.append("="); } else if (type == IBond.Order.TRIPLE) { line.append("#"); } else { // //logger.debug("Unknown bond type"); } } /** * Generates the SMILES string for the atom * *@param a the atom to generate the SMILES for. *@param buffer the string buffer that the atom is to be * apended to. *@param container the AtomContainer to analyze. *@param chiral is a chiral smiles wished? *@param parent the atom we came from. *@param atomsInOrderOfSmiles a vector containing the atoms in the order * they are in the smiles. *@param currentChain The chain we currently deal with. *@param useAromaticity true=aromaticity or sp2 will trigger lower case letters, wrong=only sp2 */ private void parseAtom(IAtom a, StringBuffer buffer, IAtomContainer container, boolean chiral, boolean[] doubleBondConfiguration, IAtom parent, List atomsInOrderOfSmiles, List currentChain, boolean useAromaticity) { String symbol = a.getSymbol(); if (a instanceof PseudoAtom) symbol = "*"; boolean stereo = false; if(chiral) stereo = BondTools.isStereo(container, a); boolean brackets = symbol.equals("B") || symbol.equals("C") || symbol.equals("N") || symbol.equals("O") || symbol.equals("P") || symbol.equals("S") || symbol.equals("F") || symbol.equals("Br") || symbol.equals("I") || symbol.equals("Cl"); brackets = !brackets; //logger.debug("in parseAtom()"); //Deal with the start of a double bond configuration if (chiral && isStartOfDoubleBond(container, a, parent, doubleBondConfiguration)) { buffer.append('/'); } String mass = generateMassString(a); brackets = brackets | !mass.equals(""); String charge = generateChargeString(a); brackets = brackets | !charge.equals(""); if (chiral && stereo && (BondTools.isTrigonalBipyramidalOrOctahedral(container, a)!=0 || BondTools.isSquarePlanar(container, a) || BondTools.isTetrahedral(container, a,false) != 0 || BondTools.isSquarePlanar(container, a))) { brackets = true; } if (brackets) { buffer.append('['); } buffer.append(mass); if ((useAromaticity && a.getFlag(CDKConstants.ISAROMATIC))) { // we put in a special check for N.planar3 cases such // as for indole and pyrrole, which require an explicit // H on the nitrogen. However this only makes sense when // the connectivity is not 3 - so for a case such as n1ncn(c1)CC // the PLANAR3 N already has 3 bonds, so don't add a H for this case if (a.getSymbol().equals("N") && a.getHybridization() == IAtomType.Hybridization.PLANAR3 && container.getConnectedAtomsList(a).size() != 3) { buffer.append("[").append(a.getSymbol().toLowerCase()).append("H]"); } else buffer.append(a.getSymbol().toLowerCase()); } else { buffer.append(symbol); if (symbol.equals("*") && a.getHydrogenCount() != null && a.getHydrogenCount() > 0) buffer.append("H").append(a.getHydrogenCount()); } if (a.getProperty(RING_CONFIG) != null && a.getProperty(RING_CONFIG).equals(UP)) { buffer.append('/'); } if (a.getProperty(RING_CONFIG) != null && a.getProperty(RING_CONFIG).equals(DOWN)) { buffer.append('\\'); } if (chiral && stereo && (BondTools.isTrigonalBipyramidalOrOctahedral(container, a)!=0 || BondTools.isSquarePlanar(container, a) || BondTools.isTetrahedral(container, a,false) != 0)) { buffer.append('@'); } if (chiral && stereo && BondTools.isSquarePlanar(container, a)) { buffer.append("SP1"); } //chiral //hcount buffer.append(charge); if (brackets) { buffer.append(']'); } //logger.debug("in parseAtom() after dealing with Pseudoatom or not"); //Deal with the end of a double bond configuration if (chiral && isEndOfDoubleBond(container, a, parent, doubleBondConfiguration)) { IAtom viewFrom = null; for (int i = 0; i < currentChain.size(); i++) { if (currentChain.get(i) == parent) { int k = i - 1; while (k > -1) { if (currentChain.get(k) instanceof IAtom) { viewFrom = (IAtom) currentChain.get(k); break; } k--; } } } if (viewFrom == null) { for (int i = 0; i < atomsInOrderOfSmiles.size(); i++) { if (atomsInOrderOfSmiles.get(i) == parent) { viewFrom = (IAtom) atomsInOrderOfSmiles.get(i - 1); } } } boolean afterThisAtom = false; IAtom viewTo = null; for (int i = 0; i < currentChain.size(); i++) { if (afterThisAtom && currentChain.get(i) instanceof IAtom) { viewTo = (IAtom) currentChain.get(i); break; } if (afterThisAtom && currentChain.get(i) instanceof List) { viewTo = (IAtom) ((List) currentChain.get(i)).get(0); break; } if (a == currentChain.get(i)) { afterThisAtom = true; } } try{ if (BondTools.isCisTrans(viewFrom,a,parent,viewTo,container)) { buffer.append('\\'); } else { buffer.append('/'); } }catch(CDKException ex){ //If the user wants a double bond configuration, where there is none, we ignore this. } } List v = new Vector(); Iterator it = getRingOpenings(a, v).iterator(); Iterator it2 = v.iterator(); //logger.debug("in parseAtom() after checking for Ring openings"); while (it.hasNext()) { Integer integer = (Integer) it.next(); IAtom a2=(IAtom) it2.next(); IBond b = container.getBond(a2, a); IBond.Order type = b.getOrder(); if (!(useAromaticity && a.getFlag(CDKConstants.ISAROMATIC) && a2.getFlag(CDKConstants.ISAROMATIC))){ if (type == IBond.Order.DOUBLE) { buffer.append("="); } else if (type == IBond.Order.TRIPLE) { buffer.append("#"); } } if (integer >= 10) buffer.append("%"+integer); else buffer.append(integer); } atomsInOrderOfSmiles.add(a); //logger.debug("End of parseAtom()"); } /** * Creates a string for the charge of atom <code>a</code>. If the charge is 1 * + is returned if it is -1 - is returned. The positive values all have + in * front of them. * *@return string representing the charge on <code>a</code> */ private String generateChargeString(IAtom a) { int charge = a.getFormalCharge() == CDKConstants.UNSET ? 0 : a.getFormalCharge().intValue(); StringBuffer buffer = new StringBuffer(3); if (charge > 0) { //Positive buffer.append('+'); if (charge > 1) { buffer.append(charge); } } else if (charge < 0) { //Negative if (charge == -1) { buffer.append('-'); } else { buffer.append(charge); } } return buffer.toString(); } /** * Creates a string containing the mass of the atom <code>a</code>. If the * mass is the same as the majour isotope an empty string is returned. * *@param a the atom to create the mass */ private String generateMassString(IAtom a) { if (isotopeFactory == null) setupIsotopeFactory(a.getBuilder()); if (a instanceof IPseudoAtom) { if (a.getMassNumber() != null) return Integer.toString(a.getMassNumber()); else return ""; } IIsotope majorIsotope = isotopeFactory.getMajorIsotope(a.getSymbol()); if (majorIsotope.getMassNumber() == a.getMassNumber()) { return ""; } else if (a.getMassNumber() == null) { return ""; } else { return Integer.toString(a.getMassNumber()); } } private void setupIsotopeFactory(IChemObjectBuilder builder) { try { isotopeFactory = IsotopeFactory.getInstance(builder); } catch (IOException e) { e.printStackTrace(); } } class BrokenBond { /** * The atoms which close the ring */ private IAtom a1, a2; /** * The number of the marker */ private int marker; /** * Construct a BrokenBond between <code>a1</code> and <code>a2</code> with * the marker <code>marker</code>. * *@param marker the ring closure marker. (Great comment!) */ BrokenBond(IAtom a1, IAtom a2, int marker) { this.a1 = a1; this.a2 = a2; this.marker = marker; } /** * Getter method for a1 property * *@return The a1 value */ public IAtom getA1() { return a1; } /** * Getter method for a2 property * *@return The a2 value */ public IAtom getA2() { return a2; } /** * Getter method for marker property * *@return The marker value */ public int getMarker() { return marker; } public String toString() { return Integer.toString(marker); } public boolean equals(Object o) { if (!(o instanceof BrokenBond)) { return false; } BrokenBond bond = (BrokenBond) o; return (a1.equals(bond.getA1()) && a2.equals(bond.getA2())) || (a1.equals(bond.getA2()) && a2.equals(bond.getA1())); } } /** * Returns the current AllRingsFinder instance * *@return the current AllRingsFinder instance */ public AllRingsFinder getRingFinder() { return ringFinder; } /** * Sets the current AllRingsFinder instance * Use this if you want to customize the timeout for * the AllRingsFinder. AllRingsFinder is stopping its * quest to find all rings after a default of 5 seconds. * * @see org.openscience.cdk.ringsearch.AllRingsFinder * * @param ringFinder The value to assign ringFinder. */ public void setRingFinder(AllRingsFinder ringFinder) { this.ringFinder = ringFinder; } /** * Indicates whether output should be an aromatic SMILES. * * @param useAromaticityFlag if false only SP2-hybridized atoms will be lower case (default), * true=SP2 or aromaticity trigger lower case */ @TestMethod("testSFBug956923") public void setUseAromaticityFlag(boolean useAromaticityFlag) { this.useAromaticityFlag = useAromaticityFlag; } }