Package org.jmol.smiles

Source Code of org.jmol.smiles.SmilesGenerator

/* $RCSfile$
* $Author: hansonr $
* $Date: 2010-06-05 21:50:17 -0500 (Sat, 05 Jun 2010) $
* $Revision: 13295 $
*
* Copyright (C) 2005  The Jmol Development Team
*
* Contact: jmol-developers@lists.sf.net
*
*  This library 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 library 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 library; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*/

package org.jmol.smiles;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.Hashtable;
import java.util.Enumeration;

import javax.vecmath.Point3f;

import org.jmol.api.JmolMolecule;
import org.jmol.api.JmolNode;
import org.jmol.api.JmolEdge;
import org.jmol.util.Elements;
import org.jmol.util.Logger;
import org.jmol.smiles.SmilesSearch.VTemp;

/**
* Double bond, allene, square planar and tetrahedral stereochemistry only
* not octahedral or trigonal bipyramidal.
*
* No attempt at canonicalization -- unnecessary for model searching.
*
* see SmilesMatcher and package.html for details
*
* Bob Hanson, Jmol 12.0.RC17 2010.06.5
*
*/
public class SmilesGenerator {

  // inputs:
  private JmolNode[] atoms;
  private int atomCount;
  private BitSet bsSelected;
  private BitSet bsAromatic;
  private StringBuffer ringSets;

  // data

  private VTemp vTemp = new VTemp();
  private int nPairs;
  private BitSet bsBondsUp = new BitSet();
  private BitSet bsBondsDn = new BitSet();
  private BitSet bsToDo;
  private JmolNode prevAtom;
  private JmolNode[] prevSp2Atoms;
 
  // outputs

  private Hashtable htRingsSequence = new Hashtable();
  private Hashtable htRings = new Hashtable();
  private BitSet bsIncludingH;

  // generation of SMILES strings

  String getSmiles(JmolNode[] atoms, int atomCount, BitSet bsSelected)
      throws InvalidSmilesException {
    int i = bsSelected.nextSetBit(0);
    if (i < 0)
      return "";
    this.atoms = atoms;
    this.atomCount = atomCount;
    this.bsSelected = bsSelected = (BitSet) bsSelected.clone();
    return getSmilesComponent(atoms[i], bsSelected, false);
  }

  String getBioSmiles(JmolNode[] atoms, int atomCount, BitSet bsSelected,
                      boolean allowUnmatchedRings, boolean addCrossLinks, String comment)
      throws InvalidSmilesException {
    this.atoms = atoms;
    this.atomCount = atomCount;
    StringBuffer sb = new StringBuffer();
    BitSet bs = (BitSet) bsSelected.clone();
    if (comment != null)
      sb.append("//* Jmol bioSMILES ").append(comment.replace('*', '_')).append(
        " *//");
    String end = "\n";
    BitSet bsIgnore = new BitSet();
    String lastComponent = null;
    String s;
    List vLinks = new ArrayList();
    try {
      int len = 0;
      for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
        JmolNode a = atoms[i];
        String ch = a.getGroup1('?');
        String groupType = a.getGroupType();
        boolean unknown = (ch.equals("?"));
        if (end != null) {
          if (sb.length() > 0)
            sb.append(end);
          end = null;
          len = 0;
          if (groupType.length() > 0) {
            char id = a.getChainID();
            if (id != '\0') {
              s = "//* chain " + id + " " + groupType + " " + a.getResno() + " *// ";
              len = s.length();
              sb.append(s);
            }
            sb.append("~").append(groupType.charAt(0)).append("~");
            len++;
          } else {
            s = getSmilesComponent(a, bs, true);
            if (s.equals(lastComponent)) {
              end = "";
            } else {
              lastComponent = s;
              groupType = a.getGroup3(true);
              if (groupType != null)
                sb.append("//* ").append(groupType).append(" *//");
              sb.append(s);
              end = ".\n";
            }
            continue;
          }
        }
        if (len >= 75) {
          sb.append("\n  ");
          len = 2;
        }
        if (unknown) {
          addBracketedBioName(sb, a, groupType.length() > 0 ? ".0" : null);
        } else {
          sb.append(ch);
        }
        len++;
        int i0 = a.getOffsetResidueAtom("0", 0);
        if (addCrossLinks) {
          a.getCrossLinkLeadAtomIndexes(vLinks);
          for (int j = 0; j < vLinks.size(); j++) {
            sb.append(":");
            s = getRingCache(i0, ((Integer) vLinks.get(j)).intValue(),
                htRingsSequence);
            sb.append(s);
            len += 1 + s.length();
          }
          vLinks.clear();
        }
        a.setGroupBits(bsIgnore);
        bs.andNot(bsIgnore);
        int i2 = a.getOffsetResidueAtom("0", 1);
        if (i2 < 0 || !bs.get(i2)) {
          sb.append(" //* ").append(a.getResno()).append(" *//");      
          if (i2 < 0 && (i2 = bs.nextSetBit(i + 1)) < 0)
            break;
          if (len > 0)
            end = ".\n";
        }
        i = i2 - 1;
      }
    } catch (Exception e) {
      e.printStackTrace();
      return "";
    }
    if (!allowUnmatchedRings && !htRingsSequence.isEmpty()) {
      dumpRingKeys(sb, htRingsSequence);
      throw new InvalidSmilesException("//* ?ring error? *//");
    }
    s = sb.toString();
    if (s.endsWith(".\n"))
      s = s.substring(0, s.length() - 2);
    return s;
  }

  private void addBracketedBioName(StringBuffer sb, JmolNode a, String atomName) {
    sb.append("[");
    if (atomName != null) {
      char chChain = a.getChainID();
      sb.append(a.getGroup3(false));
      if (!atomName.equals(".0"))
        sb.append(atomName).append("#").append(a.getElementNumber());
      sb.append("//* ").append(
          a.getResno());
      if (chChain != '\0')
        sb.append(":").append(chChain);
      sb.append(" *//");
    } else {
      sb.append(Elements.elementNameFromNumber(a.getElementNumber()));
    }
    sb.append("]");
  }

  /**
   *
   * creates a valid SMILES string from a model.
   * TODO: stereochemistry other than square planar and tetrahedral
   *
   * @param atom
   * @param bs
   * @param allowConnectionsToOutsideWorld
   * @return        SMILES
   * @throws InvalidSmilesException
   */
  private String getSmilesComponent(JmolNode atom, BitSet bs, boolean allowConnectionsToOutsideWorld)
      throws InvalidSmilesException {

    if (atom.getElementNumber() == 1 && atom.getEdges().length > 0)
      atom = atoms[atom.getBondedAtomIndex(0)]; // don't start with H
    bsSelected = JmolMolecule.getBranchBitSet(atoms, atom.getIndex(),
        (BitSet) bs.clone(), null, -1, true, false);
    bs.andNot(bsSelected);
    bsIncludingH = (BitSet) bsSelected.clone();
    for (int j = bsSelected.nextSetBit(0); j >= 0; j = bsSelected
        .nextSetBit(j + 1)) {
      JmolNode a = atoms[j];
      if (a.getElementNumber() == 1 && a.getIsotopeNumber() == 0)
        bsSelected.clear(j);
    }
    if (bsSelected.cardinality() > 2) {
      SmilesSearch search = null;
      search = SmilesParser.getMolecule("A[=&@]A", true);
      search.jmolAtoms = atoms;
      search.setSelected(bsSelected);
      search.jmolAtomCount = atomCount;
      search.ringDataMax = 7;
      search.setRingData(null);
      bsAromatic = search.bsAromatic;
      ringSets = search.ringSets;
      setBondDirections();
    } else {
      bsAromatic = new BitSet();
    }
    bsToDo = (BitSet) bsSelected.clone();
    StringBuffer sb = new StringBuffer();
   
    for (int i = bsToDo.nextSetBit(0); i >= 0 ; i = bsToDo.nextSetBit(i + 1))
      if (atoms[i].getCovalentBondCount() > 4) {
        getSmiles(sb, atoms[i], allowConnectionsToOutsideWorld, false);
        atom = null;      
      }
    if (atom != null)
      while ((atom = getSmiles(sb, atom, allowConnectionsToOutsideWorld, true)) != null) {
      }
    while (bsToDo.cardinality() > 0 || !htRings.isEmpty()) {
      Enumeration e = htRings.keys();
      if (e.hasMoreElements()) {
        atom = atoms[((Integer) ((Object[]) htRings.get(e.nextElement()))[1])
            .intValue()];
        if (!bsToDo.get(atom.getIndex()))
          break;
      } else {
        atom = atoms[bsToDo.nextSetBit(0)];
      }
      sb.append(".");
      prevSp2Atoms = null;
      prevAtom = null;
      while ((atom = getSmiles(sb, atom, allowConnectionsToOutsideWorld, true)) != null) {
      }
    }
    if (!htRings.isEmpty()) {
      dumpRingKeys(sb, htRings);
      throw new InvalidSmilesException("//* ?ring error? *//\n" + sb);
    }
    return sb.toString();
  }

  /**
   * Retrieves the saved character based on the index of the bond.
   * bsBondsUp and bsBondsDown are global fields.
   *
   * @param bond
   * @param atomFrom
   * @return   the correct character '/', '\\', '\0' (meaning "no stereochemistry")
   */
  private char getBondStereochemistry(JmolEdge bond, JmolNode atomFrom) {
    if (bond == null)
      return '\0';
    int i = bond.getIndex();
    boolean isFirst = (atomFrom == null || bond.getAtomIndex1() == atomFrom
        .getIndex());
    return (bsBondsUp.get(i) ? (isFirst ? '/' : '\\')
        : bsBondsDn.get(i) ? (isFirst ? '\\' : '/') : '\0');
  }

  /**
   * Creates global BitSets bsBondsUp and bsBondsDown. Noniterative.
   *
   */
  private void setBondDirections() {
    BitSet bsDone = new BitSet();
    JmolEdge[][] edges = new JmolEdge[2][3];
   
    // We don't assume a bond list, just an atom list, so we
    // loop through all the bonds of all the atoms, flagging them
    // as having been done already so as not to do twice.
    // The bonds we are marking will be bits in bsBondsUp or bsBondsDn
   
    for (int i = bsSelected.nextSetBit(0); i >= 0; i = bsSelected
        .nextSetBit(i + 1)) {
      JmolNode atom1 = atoms[i];
      JmolEdge[] bonds = atom1.getEdges();
      for (int k = 0; k < bonds.length; k++) {
        JmolEdge bond = bonds[k];
        int index = bond.getIndex();
        if (bsDone.get(index))
          continue;
        JmolNode atom2 = bond.getOtherAtom(atom1);
        if (bond.getCovalentOrder() != 2
            || SmilesSearch.isRingBond(ringSets, i, atom2.getIndex()))
          continue;
        bsDone.set(index);
        JmolEdge b0 = null;
        JmolNode a0 = null;
        int i0 = 0;
        JmolNode[] atom12 = new JmolNode[] { atom1, atom2 };
        if (Logger.debugging)
          Logger.debug(atom1 + " == " + atom2);
        int edgeCount = 1;
       
        // OK, so we have a double bond. Only looking at single bonds around it.
       
        // First pass: just see if there is an already-assigned bond direction
        // and collect the edges in an array.
       
        for (int j = 0; j < 2 && edgeCount > 0 && edgeCount < 3; j++) {
          edgeCount = 0;
          JmolNode atomA = atom12[j];
          JmolEdge[] bb = atomA.getEdges();
          for (int b = 0; b < bb.length; b++) {
            if (bb[b].getCovalentOrder() != 1)
              continue;
            edges[j][edgeCount++] = bb[b];
            if (getBondStereochemistry(bb[b], atomA) != '\0') {
              b0 = bb[b];
              i0 = j;
            }
          }
        }
        if (edgeCount == 3 || edgeCount == 0)
          continue;
       
        // If no bond around this double bond is already marked, we assign it UP.
       
        if (b0 == null) {
          i0 = 0;
          b0 = edges[i0][0];
          bsBondsUp.set(b0.getIndex());
        }
       
        // The character '/' or '\\' is assigned based on a
        // geometric reference to the reference bond. Initially
        // this comes in in reference to the double bond, but
        // when we save the bond, we are saving the correct
        // character for the bond itself -- based on its
        // "direction" from atom 1 to atom 2. Then, when
        // creating the SMILES string, we use the atom on the
        // left as the reference to get the correct character
        // for the string itself. The only tricky part, I think.
        // SmilesSearch.isDiaxial is just a simple method that
        // does the dot products to determine direction. In this
        // case we are looking simply for vA.vB < 0,meaning
        // "more than 90 degrees apart" (ab, and cd)
        // Parity errors would be caught here, but I doubt you
        // could ever get that with a real molecule.
       
        char c0 = getBondStereochemistry(b0, atom12[i0]);
        a0 = b0.getOtherAtom(atom12[i0]);
        for (int j = 0; j < 2; j++)
          for (int jj = 0; jj < 2; jj++) {
            JmolEdge b1 = edges[j][jj];
            if (b1 == null || b1 == b0)
              continue;
            int bi = b1.getIndex();
            JmolNode a1 = b1.getOtherAtom(atom12[j]);
            char c1 = getBondStereochemistry(b1, atom12[j]);

            //   c1 is FROM the double bond:
            //   
            //    a     b
            //     \   /
            //      C=C       /a /b  \c \d
            //     /   \
            //    c     d

            boolean isOpposite = SmilesSearch.isDiaxial(atom12[i0], atom12[j],
                a0, a1, vTemp, 0);
            if (c1 == '\0' || (c1 != c0) == isOpposite) {
              boolean isUp = (c0 == '\\' && isOpposite || c0 == '/'
                  && !isOpposite);
              if (isUp == (b1.getAtomIndex1() != a1.getIndex()))
                bsBondsUp.set(bi);
              else
                bsBondsDn.set(bi);
            } else {
              Logger.error("BOND STEREOCHEMISTRY ERROR");
            }
            if (Logger.debugging)
              Logger.debug(getBondStereochemistry(b0, atom12[0]) + " "
                  + a0.getIndex() + " " + a1.getIndex() + " "
                  + getBondStereochemistry(b1, atom12[j]));
          }
      }
    }
  }

  private JmolNode getSmiles(StringBuffer sb, JmolNode atom,
                             boolean allowConnectionsToOutsideWorld,
                             boolean allowBranches) {
    int atomIndex = atom.getIndex();

    if (!bsToDo.get(atomIndex))
      return null;
    bsToDo.clear(atomIndex);
    boolean isExtension = (!bsSelected.get(atomIndex));
    int prevIndex = (prevAtom == null ? -1 : prevAtom.getIndex());
    boolean isAromatic = bsAromatic.get(atomIndex);
    // prevSp2Atoms is for allene ABC=C=CDE
    boolean havePreviousSp2Atoms = (prevSp2Atoms != null);
    JmolNode[] sp2Atoms = prevSp2Atoms;
    int nSp2Atoms = 0;
    int atomicNumber = atom.getElementNumber();
    int nH = 0;
    List v = new ArrayList();
    JmolEdge bond0 = null;
    JmolEdge bondPrev = null;
    JmolEdge[] bonds = atom.getEdges();
    JmolNode aH = null;
    int stereoFlag = (isAromatic ? 10 : 0);
    JmolNode[] stereo = new JmolNode[7];
    if (Logger.debugging)
      Logger.debug(sb.toString());

    // first look through the bonds for the best
    // continuation -- bond0 -- and count hydrogens
    // and create a list of bonds to process.

    if (bonds != null)
      for (int i = bonds.length; --i >= 0;) {
        JmolEdge bond = bonds[i];
        if (!bond.isCovalent())
          continue;
        JmolNode atom1 = bonds[i].getOtherAtom(atom);
        int index1 = atom1.getIndex();
        if (index1 == prevIndex) {
          bondPrev = bonds[i];
          continue;
        }
        boolean isH = (atom1.getElementNumber() == 1 && atom1
            .getIsotopeNumber() == 0);
        if (!bsIncludingH.get(index1)) {
          if (!isH && allowConnectionsToOutsideWorld
              && bsSelected.get(atomIndex))
            bsToDo.set(index1);
          else
            continue;
        }
        if (isH) {
          aH = atom1;
          nH++;
          if (nH > 1)
            stereoFlag = 10;
        } else {
          v.add(bonds[i]);
        }
      }

    // order of listing is critical for stereochemistry:
    //
    // 1) previous atom
    // 2) bond to previous atom
    // 3) atom symbol
    // 4) hydrogen atoms
    // 5) branches
    // 6) rings

    // add the bond to the previous atom

    String strBond = null;
    if (sp2Atoms == null)
      sp2Atoms = new JmolNode[5];
    if (bondPrev == null) {
    } else {
      strBond = SmilesBond.getBondOrderString(bondPrev.getCovalentOrder());
      if (prevSp2Atoms == null)
        sp2Atoms[nSp2Atoms++] = prevAtom;
      else
        nSp2Atoms = 2;
    }
    nSp2Atoms += nH;

    // get bond0
    int nMax = 0;
    BitSet bsBranches = new BitSet();
    if (allowBranches)
      for (int i = 0; i < v.size(); i++) {
        JmolEdge bond = (JmolEdge) v.get(i);
        JmolNode a = bond.getOtherAtom(atom);
        int n = a.getCovalentBondCount() - a.getCovalentHydrogenCount();
        int order = bond.getCovalentOrder();
        if (order == 1 && n == 1 && i < v.size() - (bond0 == null ? 1 : 0)) {
          bsBranches.set(bond.getIndex());
        } else if ((order > 1 || n > nMax)
            && !htRings.containsKey(getRingKey(a.getIndex(), atomIndex))) {
          nMax = (order > 1 ? 1000 + order : n);
          bond0 = bond;
        }
      }
    JmolNode atomNext = (bond0 == null ? null : bond0.getOtherAtom(atom));
    int orderNext = (bond0 == null ? 0 : bond0.getCovalentOrder());

    if (stereoFlag < 7 && bondPrev != null) {
      if (bondPrev.getCovalentOrder() == 2 && orderNext == 2
          && prevSp2Atoms != null && prevSp2Atoms[1] != null) {
        // allene continuation
        stereo[stereoFlag++] = prevSp2Atoms[0];
        stereo[stereoFlag++] = prevSp2Atoms[1];
      } else {
        stereo[stereoFlag++] = prevAtom;
      }
    }

    if (stereoFlag < 7 && nH == 1)
      stereo[stereoFlag++] = aH;

    boolean deferStereo = (orderNext == 1 && prevSp2Atoms == null);
    char chBond = getBondStereochemistry(bondPrev, prevAtom);

    // now construct the branches part

    StringBuffer sMore = new StringBuffer();
    for (int i = 0; i < v.size(); i++) {
      JmolEdge bond = (JmolEdge) v.get(i);
      if (!bsBranches.get(bond.getIndex()))
        continue;
      JmolNode a = bond.getOtherAtom(atom);
      StringBuffer s2 = new StringBuffer();
      s2.append("(");
      prevAtom = atom;
      prevSp2Atoms = null;
      JmolEdge bond0t = bond0;
      getSmiles(s2, a, allowConnectionsToOutsideWorld, allowBranches);
      bond0 = bond0t;
      s2.append(")");
      if (sMore.indexOf(s2.toString()) >= 0)
        stereoFlag = 10;
      sMore.append(s2);
      v.remove(i--);
      if (stereoFlag < 7)
        stereo[stereoFlag++] = a;
      if (nSp2Atoms < 5)
        sp2Atoms[nSp2Atoms++] = a;
    }

    // from here on, prevBondAtoms and prevAtom must not be used.   

    // process the bond to the next atom
    // and cancel any double bond stereochemistry if nec.

    int index2 = (orderNext == 2 ? atomNext.getIndex() : -1);
    if (nH > 1 || isAromatic || index2 < 0
        || SmilesSearch.isRingBond(ringSets, atomIndex, index2)) {
      nSp2Atoms = -1;
    }
    if (nSp2Atoms < 0) {
      sp2Atoms = null;
    }

    // output section

    if (strBond != null || chBond != '\0') {
      if (chBond != '\0') {
        strBond = "" + chBond;
      }
      sb.append(strBond);
    }

    // now process any rings

    String atat = null;
    if (!allowBranches && (v.size() == 5 || v.size() == 6))
      atat = sortInorganic(atom, v);
    for (int i = 0; i < v.size(); i++) {
      JmolEdge bond = (JmolEdge) v.get(i);
      if (bond == bond0)
        continue;
      JmolNode a = bond.getOtherAtom(atom);
      String s = getRingCache(atomIndex, a.getIndex(), htRings);
      strBond = SmilesBond.getBondOrderString(bond.getOrder());
      if (!deferStereo) {
        chBond = getBondStereochemistry(bond, atom);
        if (chBond != '\0') {
          strBond = "" + chBond;
        }
      }

      sMore.append(strBond);
      sMore.append(s);
      if (stereoFlag < 7)
        stereo[stereoFlag++] = a;
      if (sp2Atoms != null && nSp2Atoms < 5)
        sp2Atoms[nSp2Atoms++] = a;
    }

    // now the atom symbol or bracketed expression
    // we allow for charge, hydrogen count, isotope number,
    // and stereochemistry

    if (havePreviousSp2Atoms && stereoFlag == 2 && orderNext == 2
        && atomNext.getCovalentBondCount() == 3) {
      // this is for allenes only, not cumulenes
      bonds = atomNext.getEdges();
      for (int k = 0; k < bonds.length; k++) {
        if (bonds[k].isCovalent()
            && atomNext.getBondedAtomIndex(k) != atomIndex)
          stereo[stereoFlag++] = atoms[atomNext.getBondedAtomIndex(k)];
      }
      nSp2Atoms = 0;
    } else if (atomNext != null && stereoFlag < 7) {
      stereo[stereoFlag++] = atomNext;
    }
    int valence = atom.getValence();
    int charge = atom.getFormalCharge();
    int isotope = atom.getIsotopeNumber();
    String atomName = atom.getAtomName();
    String groupType = atom.getGroupType();
    // for bioSMARTS we provide the connecting atom if
    // present. For example, in 1BLU we have
    // .[CYS.SG#16] could match either the atom number or the element number
    if (isExtension && groupType.length() != 0 && atomName.length() != 0)
      addBracketedBioName(sb, atom, "." + atomName);
    else
      sb.append(SmilesAtom.getAtomLabel(atomicNumber, isotope, valence, charge,
          nH, isAromatic, atat != null ? atat : checkStereoPairs(atom,
              atomIndex, stereo, stereoFlag)));
    sb.append(sMore);

    // check the next bond

    if (bond0 == null)
      return null;

    if (orderNext == 2 && (nSp2Atoms == 1 || nSp2Atoms == 2)) {
      if (sp2Atoms[0] == null)
        sp2Atoms[0] = atom; // CN=C= , for example. close enough!
      if (sp2Atoms[1] == null)
        sp2Atoms[1] = atom; // .C3=C=
    } else {
      sp2Atoms = null;
      nSp2Atoms = 0;
    }

    // prevSp2Atoms is only so that we can track
    // ABC=C=CDE  systems

    prevSp2Atoms = sp2Atoms;
    prevAtom = atom;
    return atomNext;
  }

  /**
   * We must sort the bond vector such that a diaxial pair is
   * first and last. Then we assign stereochemistry based on what
   * is left. The assignment is not made if there are no diaxial groups
   * or with octahedral if there are fewer than three.
   *
   * @param atom
   * @param v
   * @return  "@" or "@@" or ""
   */
  private String sortInorganic(JmolNode atom, List v) {
    int atomIndex = atom.getIndex();
    int n = v.size();
    List axialPairs = new ArrayList();
    List bonds = new ArrayList();
    JmolNode a1, a2;
    JmolEdge bond1, bond2;
    BitSet bsDone = new BitSet();
    JmolEdge[] pair0 = null;
    JmolNode[] stereo = new JmolNode[6];
    boolean isOK = true; // AX6 or AX5
    String s = "";
    for (int i = 0; i < n; i++) {
      bond1 = (JmolEdge) v.get(i);
      stereo[0] = a1 = bond1.getOtherAtom(atom);
      if (i == 0)
        s = addStereoCheck(atomIndex, stereo, 0, "");
      else if (isOK && addStereoCheck(atomIndex, stereo, 0, s) != null)
        isOK = false;
      if (bsDone.get(i))
        continue;
      bsDone.set(i);
      boolean isAxial = false;
      for (int j = i + 1; j < n; j++) {
        if (bsDone.get(j))
          continue;
        bond2 = (JmolEdge) v.get(j);
        a2 = bond2.getOtherAtom(atom);
        if (SmilesSearch.isDiaxial(atom, atom, a1, a2, vTemp, -0.95f)) {
          axialPairs.add(new JmolEdge[] { bond1, bond2 });
          isAxial = true;
          bsDone.set(j);
          break;
        }
      }
      if (!isAxial)
        bonds.add(bond1);
    }
    int nPairs = axialPairs.size();

    // AX6 or AX5 are fine as is
    // can't proceed if octahedral and not all axial pairs
    // or trigonal bipyramidal and no axial pair.

    if (isOK || n == 6 && nPairs != 3 || n == 5 && nPairs == 0)
      return "";

    // now sort them into the ligand vector in the proper order
    pair0 = (JmolEdge[]) axialPairs.get(0);
    bond1 = pair0[0];
    stereo[0] = bond1.getOtherAtom(atom);
    v.clear();
    v.add(bond1);
    if (nPairs > 1)
      bonds.add(((JmolEdge[]) axialPairs.get(1))[0]);
    if (nPairs == 3)
      bonds.add(((JmolEdge[]) axialPairs.get(2))[0]);
    if (nPairs > 1)
      bonds.add(((JmolEdge[]) axialPairs.get(1))[1]);
    if (nPairs == 3)
      bonds.add(((JmolEdge[]) axialPairs.get(2))[1]);
    for (int i = 0; i < bonds.size(); i++) {
      bond1 = (JmolEdge) bonds.get(i);
      v.add(bond1);
      stereo[i + 1] = bond1.getOtherAtom(atom);
    }
    v.add(pair0[1]);
    return (isOK ? "" : getStereoFlag(atom, stereo, n, vTemp));
  }

  private String checkStereoPairs(JmolNode atom, int atomIndex,
                                  JmolNode[] stereo, int stereoFlag) {
    if (stereoFlag < 4)
      return "";
    if (stereoFlag == 4 && (atom.getElementNumber()) == 6) {
      // do a quick check for two of the same group.
      String s = "";
      for (int i = 0; i < 4; i++)
        if ((s = addStereoCheck(atomIndex, stereo, i, s)) == null) {
          stereoFlag = 10;
          break;
        }
    }
    return (stereoFlag > 6 ? "" : getStereoFlag(atom, stereo,
        stereoFlag, vTemp));
  }

  /**
   *
   * @param atom0
   * @param atoms
   * @param nAtoms
   * @param v
   * @return        String
   */
  private static String getStereoFlag(JmolNode atom0, JmolNode[] atoms, int nAtoms, VTemp v) {
    JmolNode atom1 = atoms[0];
    JmolNode atom2 = atoms[1];
    JmolNode atom3 = atoms[2];
    JmolNode atom4 = atoms[3];
    JmolNode atom5 = atoms[4];
    JmolNode atom6 = atoms[5];
    int chiralClass = SmilesAtom.STEREOCHEMISTRY_TETRAHEDRAL;
    switch (nAtoms) {
    default:
    case 5:
    case 6:
      // like tetrahedral
      return (SmilesSearch.checkStereochemistry(false, atom0, chiralClass, 1, atom1, atom2, atom3, atom4, atom5, atom6, v)? "@" : "@@");
    case 2: // allene
    case 4: // tetrahedral, square planar
      if (atom3 == null || atom4 == null)
        return "";
      float d = SmilesAromatic.getNormalThroughPoints(atom1, atom2, atom3, v.vTemp, v.vA, v.vB);
      if (Math.abs(SmilesSearch.distanceToPlane(v.vTemp, d, (Point3f) atom4)) < 0.2f) {
        chiralClass = SmilesAtom.STEREOCHEMISTRY_SQUARE_PLANAR;
        if (SmilesSearch.checkStereochemistry(false, atom0, chiralClass, 1, atom1, atom2, atom3, atom4, atom5, atom6, v))
          return "@SP1";
        if (SmilesSearch.checkStereochemistry(false, atom0, chiralClass, 2, atom1, atom2, atom3, atom4, atom5, atom6, v))
          return "@SP2";
        if (SmilesSearch.checkStereochemistry(false, atom0, chiralClass, 3, atom1, atom2, atom3, atom4, atom5, atom6, v))
          return "@SP3";      
      } else {
        return (SmilesSearch.checkStereochemistry(false, atom0, chiralClass, 1, atom1, atom2, atom3, atom4, atom5, atom6, v)? "@" : "@@");
      }      
    }
    return "";
  }

  /**
   * checks a group and either adds a new group to the growing
   * check string or returns null
   * @param atomIndex
   * @param stereo
   * @param i
   * @param s
   * @return   null if duplicate
   */
  private String addStereoCheck(int atomIndex, JmolNode[] stereo, int i, String s) {
    int n = stereo[i].getAtomicAndIsotopeNumber();
    int nx = stereo[i].getCovalentBondCount();
    int nh = (n == 6 ? stereo[i].getCovalentHydrogenCount() : 0);
    // only carbon or singly-connected atoms are checked
    // for C we use nh -- CH3, for example.
    // for other atoms, we use number of bonds.
    // just checking for tetrahedral CH3)
    if (n == 6 ? nx != 4 || nh != 3 : nx > 1)
      return s;
    String sa = ";" + n + "/" + nh + "/" + nx + ",";
    if (s.indexOf(sa) >= 0) {
      if (nh == 3) {
        // must check isotopes for CH3
        int ndt = 0;
        for (int j = 0; j < nx && ndt < 3; j++) {
          int ia = stereo[i].getBondedAtomIndex(j);
          if (ia == atomIndex)
            continue;
          ndt += atoms[ia].getAtomicAndIsotopeNumber();
        }
        if (ndt > 3)
          return s;
      }
      return null;
    }
    return s + sa;
  }

  private String getRingCache(int i0, int i1, Hashtable ht) {
    String key = getRingKey(i0, i1);
    Object[] o = (Object[]) ht.get(key);
    String s = (o == null ? null : (String) o[0]);
    if (s == null) {
      ht.put(key, new Object[] {
          s = SmilesParser.getRingPointer(++nPairs), new Integer(i1) });
      if (Logger.debugging)
        Logger.info("adding for " + i0 + " ring key " + nPairs + ": " + key);
    } else {
      ht.remove(key);
      if (Logger.debugging)
        Logger.info("using ring key " + key);
    }
    return s;//  + " _" + key + "_ \n";
  }

  private void dumpRingKeys(StringBuffer sb, Hashtable ht) {
    Logger.info(sb.toString() + "\n\n");
    Enumeration e = ht.keys();
    while (e.hasMoreElements()) {
      Logger.info("unmatched ring key: " + e.nextElement());
    }
  }

  protected static String getRingKey(int i0, int i1) {
    return Math.min(i0, i1) + "_" + Math.max(i0, i1);
  }

}
TOP

Related Classes of org.jmol.smiles.SmilesGenerator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.