Package org.jmol.smiles

Source Code of org.jmol.smiles.SmilesSearch

/* $RCSfile$
* $Author: hansonr $
* $Date: 2011-02-13 06:52:46 +0100 (dim., 13 févr. 2011) $
* $Revision: 15159 $
*
* 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.Arrays;
import java.util.BitSet;
import java.util.Hashtable;
import java.util.List;
import java.util.ArrayList;

import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;

import org.jmol.api.JmolEdge;
import org.jmol.api.JmolMolecule;
import org.jmol.api.JmolNode;
import org.jmol.util.Logger;

/**
*  -- was SmilesMolecule,
* but this now includes more data than that and the search itself
* so as to keep this thread safe
*
*/
public class SmilesSearch extends JmolMolecule {

  public String toString() {
    StringBuffer sb = new StringBuffer(pattern);
    sb.append("\nmolecular formula: " + getMolecularFormula(true));
    return sb.toString();   
  }
 
  private final static int INITIAL_ATOMS = 16;
  SmilesAtom[] patternAtoms = new SmilesAtom[INITIAL_ATOMS];

  /* ============================================================= */
  /*                             Setup                             */
  /* ============================================================= */

  String pattern;
  JmolNode[] jmolAtoms;
  int jmolAtomCount;
  private BitSet bsSelected;
  void setSelected(BitSet bs) {
    if (bs == null) {
      bs = new BitSet(jmolAtomCount);
      bs.set(0, jmolAtomCount);
    }
    bsSelected = bs;
  }
  BitSet bsRequired;
  boolean firstMatchOnly;
  boolean matchAllAtoms;
  boolean isSmarts;
  boolean isSmilesFind;
  SmilesSearch[] subSearches;
  boolean haveSelected;
  boolean haveBondStereochemistry;
  boolean haveAtomStereochemistry;
  boolean needRingData;
  boolean needAromatic;
  boolean needRingMemberships;
  int ringDataMax = Integer.MIN_VALUE;
  List measures = new ArrayList();
  boolean noAromatic;
  boolean ignoreStereochemistry;
  StringBuffer ringSets;
  BitSet bsAromatic = new BitSet();
  SmilesAtom lastChainAtom;

  boolean asVector;
  boolean getMaps;
  SmilesSearch parent = this;
 
  //  private data
 
  private boolean isSilent;
  private boolean isRingCheck;
  private int selectedAtomCount;
  private BitSet[] ringData;
  private int[] ringCounts;
  private int[] ringConnections;
  private BitSet bsFound = new BitSet();
  private Hashtable htNested;
  private int nNested;
  private SmilesBond nestedBond;

  private List vReturn;
  private BitSet bsReturn = new BitSet();
   

  void setAtomArray() {
    nodes = patternAtoms;
    if (patternAtoms.length > atomCount) {
      SmilesAtom[] tmp = new SmilesAtom[atomCount];
      System.arraycopy(patternAtoms, 0, tmp, 0, atomCount);
      nodes = patternAtoms = tmp;
    }
  }

  SmilesAtom addAtom() {
    if (atomCount >= patternAtoms.length) {
      SmilesAtom[] tmp = new SmilesAtom[patternAtoms.length * 2];
      System.arraycopy(patternAtoms, 0, tmp, 0, patternAtoms.length);
      patternAtoms = tmp;
    }
    SmilesAtom sAtom = new SmilesAtom(atomCount);
    patternAtoms[atomCount] = sAtom;
    atomCount++;
    return sAtom;
  }

  int addNested(String pattern) {
    if (parent.htNested == null)
      parent.htNested = new Hashtable();
    setNested(++parent.nNested, pattern);
    return parent.nNested;
  }
 
  void setNested(int iNested, Object o) {
    parent.htNested.put("_" + iNested, o);
  }

  Object getNested(int iNested) {
    return parent.htNested.get("_" + iNested);
  }
 
  int getMissingHydrogenCount() {
    int n = 0;
    int nH;
    for (int i = 0; i < atomCount; i++)
      if ((nH = patternAtoms[i].missingHydrogenCount) >= 0)
          n += nH;
    return n;
  }

  void setRingData(BitSet bsA) throws InvalidSmilesException {
    needAromatic &= (bsA == null) & !noAromatic;
    // when using "xxx".find("search","....")
    // or $(...), the aromatic set has already been determined
    if (!needAromatic) {
      bsAromatic.clear();
      if (bsA != null)
        bsAromatic.or(bsA);
      if (!needRingMemberships && !needRingData)
        return;
    }
    if (ringDataMax < 0)
      ringDataMax = 8;
    if (needRingData) {
      ringCounts = new int[jmolAtomCount];
      ringConnections = new int[jmolAtomCount];
      ringData = new BitSet[ringDataMax + 1];
    }

    ringSets = new StringBuffer();
    String s = "****";
    while (s.length() < ringDataMax)
      s += s;

    for (int i = 3; i <= ringDataMax; i++) {
      if (i > jmolAtomCount)
        continue;
      String smarts = "*1" + s.substring(0, i - 2) + "*1";
      SmilesSearch search = SmilesParser.getMolecule(smarts, true);
      List v = (List) getBitSets(search, false, true);
      if (needAromatic)
        for (int r = v.size(); --r >= 0;) {
          BitSet bs = (BitSet) v.get(r);
          if (SmilesAromatic.isFlatSp2Ring(
              jmolAtoms, bsSelected, bs, 0.01f))
          for (int j = bs.nextSetBit(0); j >= 0; j = bs.nextSetBit(j + 1))
              bsAromatic.set(j);
        }
      if (needRingData) {
        ringData[i] = new BitSet();
        for (int k = 0; k < v.size(); k++) {
          BitSet r = (BitSet) v.get(k);
          ringData[i].or(r);
          for (int j = r.nextSetBit(0); j >= 0; j = r.nextSetBit(j + 1))
            ringCounts[j]++;
        }
      }
    }
    if (needRingData) {
      for (int i = bsSelected.nextSetBit(0); i >= 0; i = bsSelected.nextSetBit(i + 1)) {
        JmolNode atom = jmolAtoms[i];
        JmolEdge[] bonds = atom.getEdges();
        if (bonds != null)
          for (int k = bonds.length; --k >= 0;)
            if (ringCounts[atom.getBondedAtomIndex(k)] > 0)
              ringConnections[i]++;
      }
    }
  }

  private Object getBitSets(SmilesSearch search,
                            boolean firstAtomOnly,
                            boolean isRingCheck) throws InvalidSmilesException {
    search.ringSets = ringSets;
    search.jmolAtoms = jmolAtoms;
    search.jmolAtomCount = jmolAtomCount;
    search.bsSelected = bsSelected;
    search.htNested = htNested;
    search.isSmilesFind = isSmilesFind;
    search.isSmarts = true;
    //search.measures = measures;
    search.bsAromatic = bsAromatic;
    search.ringData = ringData;
    search.ringCounts = ringCounts;
    search.ringConnections = ringConnections;
    if (firstAtomOnly) {
      search.bsRequired = null;
      search.firstMatchOnly = false;
      search.matchAllAtoms = false;
      search.bsFound = bsFound;
    } else if (isRingCheck) {
      search.bsRequired = null;
      search.isSilent = true;
      search.isRingCheck = true;
      search.asVector = true;
      search.matchAllAtoms = false;
    } else {
      // processing ||
      search.haveSelected = haveSelected;
      search.bsRequired = bsRequired;
      search.firstMatchOnly = firstMatchOnly;
      search.matchAllAtoms = matchAllAtoms;
      search.getMaps = getMaps;
      search.asVector = asVector;
      search.vReturn = vReturn;
      search.bsReturn = bsReturn;
    }
    return search.search(firstAtomOnly);
  }
 
  /* ============================================================= */
  /*                             Search                            */
  /* ============================================================= */

  /**
   * the start of the search. ret will be either a List or a BitSet
   * @param firstAtomOnly TODO
   * @return BitSet or Vector
   * @throws InvalidSmilesException
   *
   */
  Object search(boolean firstAtomOnly) throws InvalidSmilesException {

    /*
     * The essence of the search process is as follows:
     *
     * 1) From the pattern, create an ordered set of atoms connected by bonds.
     *   
     * 2) Try all model set atoms for position 0.
     *
     * 3) For each atom that matches position N
     *    we move to position N+1 and run through all
     *    of the pattern bonds TO this atom (atom in position 2).
     *    Those bonds will be to atoms that have already
     *    been assigned. There may be more than one of these
     *    if the atom is associated with a ring junction.
     *   
     *    We check that previously assigned model atom,
     *    looking at all of its bonded atoms to check for
     *    a match for our N+1 atom. This works because if
     *    this atom is going to work in this position, then
     *    it must be bound to the atom assigned to position N
     *   
     *    There is no need to check more than one route to this
     *    atom in this position - if it is found to be good once,
     *    that is all we need, and if it is found to be bad once,
     *    that is all we need as well.
     *   
     */

    if (Logger.debugging && !isSilent)
      Logger.debug("SmilesSearch processing " + pattern);

    if (vReturn == null && (asVector || getMaps))
      vReturn = new ArrayList();
    if (bsSelected == null) {
      bsSelected = new BitSet(jmolAtomCount);
      bsSelected.set(0, jmolAtomCount);
    }
    selectedAtomCount = bsSelected.cardinality();
    if (subSearches != null) {
      for (int i = 0; i < subSearches.length; i++) {
        if (subSearches[i] == null)
          continue;
        getBitSets(subSearches[i], false, false);
        if (firstMatchOnly) {
          if (vReturn == null ? bsReturn.nextSetBit(0) >= 0 : vReturn.size() > 0)
            break;
        }
      }
    } else if (atomCount > 0) {
      checkMatch(null, -1, -1, firstAtomOnly);
    }
    return (asVector || getMaps ? (Object) vReturn : bsReturn);
  }

  /**
   * Check for a specific match of a model set atom with a pattern position
   *
   * @param patternAtom
   *          Atom of the pattern that is currently tested.
   * @param atomNum
   *          Current atom of the pattern.
   * @param iAtom
   *          Atom number of the Jmol atom that is currently tested to match
   *          <code>patternAtom</code>.
   * @param firstAtomOnly
   *          TODO
   * @return true to continue or false if oneOnly
   * @throws InvalidSmilesException
   */
  private final boolean checkMatch(SmilesAtom patternAtom, int atomNum,
                                   int iAtom, boolean firstAtomOnly)
      throws InvalidSmilesException {

    JmolNode jmolAtom;
    JmolEdge[] jmolBonds;
    if (patternAtom == null) {
      // first atom in pattern
      if (nestedBond == null)
        bsFound.clear();
      else
        bsReturn.clear();
    } else {
      // check for requested selection or not-selection

      if (bsFound.get(iAtom) || !bsSelected.get(iAtom))
        return true;

      jmolAtom = jmolAtoms[iAtom];

      if (!isRingCheck) {
        // check atoms
        if (patternAtom.atomsOr != null) {
          for (int ii = 0; ii < patternAtom.nAtomsOr; ii++)
            if (!checkMatch(patternAtom.atomsOr[ii], atomNum, iAtom,
                firstAtomOnly))
              return false;
          return true;
        }

        if (patternAtom.primitives == null) {
          if (!checkPrimitiveAtom(patternAtom, iAtom))
            return true;
        } else {
          for (int i = 0; i < patternAtom.nPrimitives; i++)
            if (!checkPrimitiveAtom(patternAtom.primitives[i], iAtom))
              return true;
        }
      }
      // Check bonds

      jmolBonds = jmolAtom.getEdges();
      for (int i = patternAtom.getBondCount(); --i >= 0;) {
        SmilesBond patternBond = patternAtom.getBond(i);
        // Check only if the current atom is the second atom of the bond
        if (patternBond.getAtomIndex2() != patternAtom.index)
          continue;

        // note that there might be more than one of these.
        // in EACH case we need to ensure that the actual
        // bonds to the previously assigned atoms matches

        SmilesAtom atom1 = patternBond.getAtom1();
        int matchingAtom = atom1.getMatchingAtom();

        // BIOSMILES/BIOSMARTS check is by group

        switch (patternBond.bondType) {
        case SmilesBond.TYPE_BIO_SEQUENCE:
        case SmilesBond.TYPE_BIO_PAIR:
          if (!checkMatchBond(patternAtom, atom1, patternBond, iAtom,
              matchingAtom, null))
            return true;
          break;
        default:

          // regular SMILES/SMARTS check
          // is to find the bond and test it against the pattern

          int k = 0;
          for (; k < jmolBonds.length; k++)
            if ((jmolBonds[k].getAtomIndex1() == matchingAtom || jmolBonds[k]
                .getAtomIndex2() == matchingAtom)
                && jmolBonds[k].isCovalent())
              break;
          if (k == jmolBonds.length)
            return true; // probably wasn't a covalent bond

          if (!checkMatchBond(patternAtom, atom1, patternBond, iAtom,
              matchingAtom, jmolBonds[k]))
            return true;
        }
      }

      // Note that we explicitly do a reference using
      // index because this could be a SEARCH [x,x] "sub" atom.

      patternAtoms[patternAtom.index].setMatchingAtom(iAtom);

      // The atom has passed both the atom and the bond test.
      // Add this atom to the growing list.

      if (Logger.debugging && !isSilent)
        Logger.debug("pattern atom " + atomNum + " " + patternAtom);
      bsFound.set(iAtom);

    }
    if (!continueMatch(atomNum, iAtom, firstAtomOnly))
      return false;
    if (iAtom >= 0)
      bsFound.clear(iAtom);
    return true;
  }

  private boolean continueMatch(int atomNum, int iAtom, boolean firstAtomOnly)
      throws InvalidSmilesException {

    JmolNode jmolAtom;
    JmolEdge[] jmolBonds;

    if (++atomNum < atomCount) {

      // so far, so good... not done yet... on to the next position...

      SmilesAtom newPatternAtom = patternAtoms[atomNum];
      // For all the pattern bonds for this atom...
      // find the bond to atoms already assigned.
      // If it is not there, then it means this is a
      // new component.

      // the nestedBond may be set to previous search
      SmilesBond newPatternBond = (iAtom >= 0 ? newPatternAtom.getBondTo(null)
          : atomNum == 0 ? nestedBond : null);
      if (newPatternBond == null) {

        // Option 1: we are processing "."
        // run through all unmatched and unbonded-to-match
        // selected Jmol atoms to see if there is a match.

        BitSet bs = (BitSet) bsFound.clone();
        if (newPatternAtom.notBondedIndex >= 0) {
          SmilesAtom pa = patternAtoms[newPatternAtom.notBondedIndex];
          JmolNode a = jmolAtoms[pa.getMatchingAtom()];
          if (pa.bioType == '\0') {
            // clear out all atoms connected to the last atom only
            jmolBonds = a.getEdges();
            for (int k = 0; k < jmolBonds.length; k++)
              bs.set(jmolBonds[k].getOtherAtom(a).getIndex());
          } else {
            // clear out adjacent residues
            int ii = a.getOffsetResidueAtom("0", 1);
            if (ii >= 0)
              bs.set(ii);
            ii = a.getOffsetResidueAtom("0", -1);
            if (ii >= 0)
              bs.set(ii);
          }
        }
        boolean skipGroup = (iAtom >= 0 && newPatternAtom.isBioAtom && (newPatternAtom.atomName == null || newPatternAtom.residueChar != null));
        for (int j = bsSelected.nextSetBit(0); j >= 0; j = bsSelected
            .nextSetBit(j + 1)) {
          if (!bs.get(j)
              && !checkMatch(newPatternAtom, atomNum, j, firstAtomOnly))
            return false;
          if (skipGroup) {
            int j1 = jmolAtoms[j].getOffsetResidueAtom(newPatternAtom.atomName,
                1);
            if (j1 >= 0)
              j = j1 - 1;
          }
        }
        bsFound = bs;
        return true;
      }

      // The new atom is connected to the old one in the pattern.
      // It doesn't so much matter WHICH connection we found --
      // there may be several -- but whatever we have, we must
      // have a connection in the real molecule between these two
      // particular atoms. So we just follow that connection.

      jmolAtom = jmolAtoms[newPatternBond.getAtom1().getMatchingAtom()];

      // Option 2: The connecting bond is a bio sequence or
      // from ~GGC(T)C:ATTC...
      // For sequences, we go to the next GROUP, either via
      // the standard sequence or via basepair/cysteine pairing.

      switch (newPatternBond.bondType) {
      case SmilesBond.TYPE_BIO_SEQUENCE:
        int nextGroupAtom = jmolAtom.getOffsetResidueAtom(
            newPatternAtom.atomName, 1);
        if (nextGroupAtom >= 0) {
          BitSet bs = (BitSet) bsFound.clone();
          jmolAtom.setGroupBits(bsFound);

          // working here

          if (!checkMatch(newPatternAtom, atomNum, nextGroupAtom, firstAtomOnly))
            return false;
          bsFound = bs;
        }
        return true;
      case SmilesBond.TYPE_BIO_PAIR:
        List vLinks = new ArrayList();
        jmolAtom.getCrossLinkLeadAtomIndexes(vLinks);
        BitSet bs = (BitSet) bsFound.clone();
        jmolAtom.setGroupBits(bsFound);
        for (int j = 0; j < vLinks.size(); j++)
          if (!checkMatch(newPatternAtom, atomNum, ((Integer) vLinks.get(j))
              .intValue(), firstAtomOnly))
            return false;
        bsFound = bs;
        return true;
      }

      // Option 3: Standard practice

      // We looked at the next pattern atom position and
      // found at least one bond to it from a previous
      // pattern atom. The only valid possibilities for this
      // pattern atom position, then, is a Jmol atom that is
      // bonded to that previous connection. So we only have
      // to check a handful of atoms. We do this so
      // that we don't have to check EVERY atom in the model.

      // Run through the bonds of that assigned atom
      // to see if any match this new connection.

      jmolBonds = jmolAtom.getEdges();
      if (jmolBonds != null)
        for (int j = 0; j < jmolBonds.length; j++)
          if (!checkMatch(newPatternAtom, atomNum, jmolAtom
              .getBondedAtomIndex(j), firstAtomOnly))
            return false;

      // Done checking this atom from any one of the places
      // higher in this stack. Clear the atom and keep going...

      bsFound.clear(iAtom);
      return true;
    }

    // the pattern is complete

    // check stereochemistry

    if (!ignoreStereochemistry && !checkStereochemistry())
      return true;

    // set up the return BitSet and Vector, if requested

    // bioSequences only return the "lead" atom
    // If the search is SMILES, we add the missing hydrogens

    BitSet bs = new BitSet();
    int nMatch = 0;
    for (int j = 0; j < atomCount; j++) {
      int i = patternAtoms[j].getMatchingAtom();
      if (!firstAtomOnly && haveSelected && !patternAtoms[j].selected)
        continue;
      nMatch++;
      bs.set(i);
      if (patternAtoms[j].isBioAtom && patternAtoms[j].atomName == null)
        jmolAtoms[i].setGroupBits(bs);
      if (firstAtomOnly)
        break;
      if (!isSmarts && patternAtoms[j].missingHydrogenCount > 0)
        getHydrogens(jmolAtoms[i], bs);
    }
    if (bsRequired != null && !bsRequired.intersects(bs))
      return true;
    if (matchAllAtoms && bs.cardinality() != selectedAtomCount)
      return true;
    bsReturn.or(bs);

    if (getMaps) {
      // every map is important always
      int[] map = new int[nMatch];
      for (int j = 0, nn = 0; j < atomCount; j++) {
        if (!firstAtomOnly && haveSelected && !patternAtoms[j].selected)
          continue;
        map[nn++] = patternAtoms[j].getMatchingAtom();
      }
      vReturn.add(map);
      return !firstMatchOnly;
    }

    if (asVector) {
      boolean isOK = true;
      for (int j = vReturn.size(); --j >= 0 && isOK;)
        isOK = !(((BitSet) vReturn.get(j)).equals(bs));
      if (!isOK)
        return true;
      vReturn.add(bs);
    }

    if (isRingCheck) {
      ringSets.append(" ");
      for (int k = atomNum * 3 + 2; --k > atomNum;)
        ringSets.append("-").append(
            patternAtoms[(k <= atomNum * 2 ? atomNum * 2 - k + 1 : k - 1)
                % atomNum].getMatchingAtom());
      ringSets.append("- ");
      return true;
    }

    // requested return is a BitSet or vector of BitSets

    // TRUE means "continue searching"

    if (firstMatchOnly)
      return false;

    // only continue if we have not found all the atoms already

    return (bs.cardinality() != selectedAtomCount);

  }

  private JmolNode getHydrogens(JmolNode atom, BitSet bsHydrogens) {
    JmolEdge[] b = atom.getEdges();
    int k = -1;
    for (int i = 0; i < b.length; i++)
      if (jmolAtoms[atom.getBondedAtomIndex(i)].getElementNumber() == 1) {
        k = atom.getBondedAtomIndex(i);
        if (bsHydrogens == null)
          break;
        bsHydrogens.set(k);
      }
    return (k >= 0 ? jmolAtoms[k] : null);
  }

  private boolean checkPrimitiveAtom(SmilesAtom patternAtom, int iAtom) throws InvalidSmilesException {
    JmolNode atom = jmolAtoms[iAtom];
    boolean foundAtom = patternAtom.not;
    while (true) {

      int n;

      // _ <n> apply "recursive" SEARCH -- for example, [C&$(C[$(aaaO);$(aaC)])]"
      if (patternAtom.iNested > 0) {
        Object o = getNested(patternAtom.iNested);
        if (o instanceof SmilesSearch) {
          SmilesSearch search = (SmilesSearch) o;             
          if (patternAtom.isBioAtom)
            search.nestedBond = patternAtom.getBondTo(null);
          o = getBitSets(search, true, false);
          if (o == null)
            o = new BitSet();
          if (!patternAtom.isBioAtom)
            setNested(patternAtom.iNested, o);
        }
        foundAtom = (patternAtom.not != (((BitSet) o).get(iAtom)));
        break;
      }
      if (patternAtom.isBioAtom) {

        // BIOSMARTS
        if (patternAtom.atomName != null
            && (patternAtom.isLeadAtom() ? !atom.isLeadAtom()
                : !patternAtom.atomName
                    .equals(atom.getAtomName().toUpperCase())))
          break;

        if (patternAtom.residueName != null
            && !patternAtom.residueName.equals(atom.getGroup3(false)
                .toUpperCase()))
          break;
        if (patternAtom.residueChar != null) {
          if (patternAtom.isDna() && !atom.isDna()
              || patternAtom.isRna() && !atom.isRna()
              || patternAtom.isProtein() && !atom.isProtein()
              || patternAtom.isNucleic() && !atom.isNucleic())
            break;
          String s = atom.getGroup1('\0').toUpperCase();
          boolean isOK = patternAtom.residueChar.equals(s);
          switch (patternAtom.residueChar.charAt(0)) {
          case 'N':
            isOK = patternAtom.isNucleic() ? atom.isNucleic() : isOK;
            break;
          case 'R': // arginine purine
            isOK = patternAtom.isNucleic() ? atom.isPurine() : isOK;
            break;
          case 'Y': // tyrosine or pyrimidine
            isOK = patternAtom.isNucleic() ? atom.isPyrimidine() : isOK;
            break;
          }
          if (!isOK)
            break;
        }

        // # <n> or Symbol Check atomic number
        if (patternAtom.elementNumber >= 0
            && patternAtom.elementNumber != atom.getElementNumber())
          break;

        if (patternAtom.notCrossLinked
            && atom.getCrossLinkLeadAtomIndexes(null))
          break;

      } else {

        // "=" <n>  Jmol index

        if (patternAtom.jmolIndex >= 0
            && atom.getIndex() != patternAtom.jmolIndex)
          break;

        // # <n> or Symbol Check atomic number
        if (patternAtom.elementNumber >= 0
            && patternAtom.elementNumber != atom.getElementNumber())
          break;

        // Check aromatic
        boolean isAromatic = patternAtom.isAromatic();
        if (!noAromatic && patternAtom.elementNumber != -2
            && isAromatic != bsAromatic.get(iAtom))
          break;

        // <n> Check isotope
        if ((n = patternAtom.getAtomicMass()) != Short.MIN_VALUE) {
          int isotope = atom.getIsotopeNumber();
          if (n >= 0 && n != isotope || n < 0 && isotope != 0 && -n != isotope) {
            // smiles indicates [13C] or [12C]
            // must match perfectly -- [12C] matches only explicit C-12, not
            // "unlabeled" C
            break;
          }
        }

        // +/- Check charge
        if (patternAtom.getCharge() != atom.getFormalCharge())
          break;

        // H explicit H count
        //n = patternAtom.missingHydrogenCount;
        //problem here is that you can have C[H]
        n = patternAtom.getCovalentHydrogenCount() + patternAtom.missingHydrogenCount;
        if (n >= 0 && n != atom.getCovalentHydrogenCount())
          break;

        // h implicit H count
        n = patternAtom.implicitHydrogenCount;
        if (n != Integer.MIN_VALUE) {
          int nH = atom.getImplicitHydrogenCount();
          if (n == -1 ? nH == 0 : n != nH)
            break;
        }

        // D <n> degree
        if (patternAtom.degree > 0
            && patternAtom.degree != atom.getCovalentBondCount())
          break;

        // d <n> degree
        if (patternAtom.nonhydrogenDegree > 0
            && patternAtom.nonhydrogenDegree != atom.getCovalentBondCount()
                - atom.getCovalentHydrogenCount())
          break;

        // v <n> valence
        if (patternAtom.valence > 0 && patternAtom.valence != atom.getValence())
          break;

        // X <n> connectivity ?
        if (patternAtom.connectivity > 0
            && patternAtom.connectivity != atom.getCovalentBondCount()
                + atom.getImplicitHydrogenCount())
          break;

        // r <n> ring of a given size
        if (ringData != null && patternAtom.ringSize >= -1) {
          if (patternAtom.ringSize <= 0) {
            if ((ringCounts[iAtom] == 0) != (patternAtom.ringSize == 0))
              break;
          } else if (ringData[patternAtom.ringSize] == null
              || !ringData[patternAtom.ringSize].get(iAtom)) {
            break;
          }
        }
        // R <n> a certain number of rings
        if (ringData != null && patternAtom.ringMembership >= -1) {
          //  R --> -1 implies "!R0"
          if (patternAtom.ringMembership == -1 ? ringCounts[iAtom] == 0
              : ringCounts[iAtom] != patternAtom.ringMembership)
            break;
        }
        // x <n>
        if (patternAtom.ringConnectivity >= 0) {
          // default > 0
          n = ringConnections[iAtom];
          if (patternAtom.ringConnectivity == -1 && n == 0
              || patternAtom.ringConnectivity != -1
              && n != patternAtom.ringConnectivity)
            break;
        }
      }
      foundAtom = !foundAtom;
      break;
    }

    return foundAtom;
  }

  private boolean checkMatchBond(SmilesAtom patternAtom, SmilesAtom atom1,
                                 SmilesBond patternBond, int iAtom,
                                 int matchingAtom, JmolEdge bond) {

    // apply SEARCH [ , , & ; ] logic

    if (patternBond.bondsOr != null) {
      for (int ii = 0; ii < patternBond.nBondsOr; ii++)
        if (checkMatchBond(patternAtom, atom1, patternBond.bondsOr[ii], iAtom,
            matchingAtom, bond))
          return true;
      return false;
    }

    if (patternBond.primitives == null) {
      if (!checkPrimitiveBond(patternAtom, atom1, patternBond, iAtom, matchingAtom, bond))
        return false;
    } else {
      for (int i = 0; i < patternBond.nPrimitives; i++)
        if (!checkPrimitiveBond(patternAtom, atom1, patternBond.primitives[i], iAtom, matchingAtom, bond))
          return false;
    }
    patternBond.matchingBond = bond;
    return true;
  }

  private boolean checkPrimitiveBond(SmilesAtom patternAtom, SmilesAtom atom1,
                                     SmilesBond patternBond, int iAtom,
                                     int matchingAtom, JmolEdge bond) {
    boolean bondFound = false;
   
    switch (patternBond.bondType) {
    case SmilesBond.TYPE_BIO_SEQUENCE:
      return (patternBond.isNot != (jmolAtoms[matchingAtom].getOffsetResidueAtom("0", 1)
          == jmolAtoms[iAtom].getOffsetResidueAtom("0", 0)));
    case SmilesBond.TYPE_BIO_PAIR:
      return (patternBond.isNot != jmolAtoms[iAtom].isCrossLinked(jmolAtoms[matchingAtom]));
    }
   
    boolean isAromatic = !noAromatic && patternAtom.isAromatic();

   
    int order = bond.getCovalentOrder();
    if (isAromatic && atom1.isAromatic()) {
      switch (patternBond.bondType) {
      case SmilesBond.TYPE_AROMATIC: // :
      case SmilesBond.TYPE_DOUBLE:
      case SmilesBond.TYPE_RING:
        bondFound = isRingBond(ringSets, iAtom, matchingAtom);
        break;
      case SmilesBond.TYPE_SINGLE:
        // for SMARTS, single bond in aromatic means
        // TO ANOTHER RING
        bondFound = !isSmarts || !isRingBond(ringSets, iAtom, matchingAtom);
        break;
      case SmilesBond.TYPE_ATROPISOMER_1:
      case SmilesBond.TYPE_ATROPISOMER_2:
      case SmilesBond.TYPE_ANY:
      case SmilesBond.TYPE_UNKNOWN:
        bondFound = true;
        break;
      }
    } else {
      switch (patternBond.bondType) {
      case SmilesBond.TYPE_ANY:
      case SmilesBond.TYPE_UNKNOWN:
        bondFound = true;
        break;
      case SmilesBond.TYPE_SINGLE:
      case SmilesBond.TYPE_DIRECTIONAL_1:
      case SmilesBond.TYPE_DIRECTIONAL_2:
        // STEREO_NEAR and _FAR are stand-ins for find()
        bondFound = (order == JmolEdge.BOND_COVALENT_SINGLE
            || order == JmolEdge.BOND_STEREO_FAR
            || order == JmolEdge.BOND_STEREO_NEAR);
        break;
      case SmilesBond.TYPE_ATROPISOMER_1:
        bondFound = (order == (isSmilesFind ? JmolEdge.BOND_PARTIAL01 : JmolEdge.BOND_COVALENT_SINGLE));
        break;
      case SmilesBond.TYPE_ATROPISOMER_2:
        bondFound = (order == (isSmilesFind ? JmolEdge.BOND_PARTIAL23 : JmolEdge.BOND_COVALENT_SINGLE));
        break;
      case SmilesBond.TYPE_DOUBLE:
        bondFound = (order == JmolEdge.BOND_COVALENT_DOUBLE);
        break;
      case SmilesBond.TYPE_TRIPLE:
        bondFound = (order == JmolEdge.BOND_COVALENT_TRIPLE);
        break;
      case SmilesBond.TYPE_RING:
        bondFound = isRingBond(ringSets, iAtom, matchingAtom);
        break;
      }
    }
    return bondFound != patternBond.isNot;
  }

  static boolean isRingBond(StringBuffer ringSets, int i, int j) {
    return (ringSets != null && ringSets.indexOf("-" + i + "-" + j + "-") >= 0);
  }
 
  /* ============================================================= */
  /*                          Stereochemistry                      */
  /* ============================================================= */

  private boolean checkStereochemistry() {

    // first, @ stereochemistry

    for (int i = 0; i < measures.size(); i++)
      if (!((SmilesMeasure) measures.get(i)).check())
        return false;
    if (haveAtomStereochemistry) {

      if (Logger.debugging)
        Logger.debug("checking stereochemistry...");

      JmolNode atom1 = null, atom2 = null, atom3 = null, atom4 = null, atom5 = null, atom6 = null;
      SmilesAtom sAtom1 = null, sAtom2 = null;
      JmolNode[] jn;
      for (int i = 0; i < atomCount; i++) {
        SmilesAtom sAtom = patternAtoms[i];
        JmolNode atom0 = jmolAtoms[sAtom.getMatchingAtom()];
        int nH = sAtom.missingHydrogenCount;
        if (nH < 0)
          nH = 0;
        int chiralClass = sAtom.getChiralClass();
        if (chiralClass == Integer.MIN_VALUE)
          continue;
        int order = sAtom.getChiralOrder();
        if (isSmilesFind && (atom0.getAtomSite() >> 8) != chiralClass)
          return false;
        atom4 = null;
        if (Logger.debugging)
          Logger.debug("...type " + chiralClass);
        switch (chiralClass) {
        //        case SmilesAtom.STEREOCHEMISTRY_DOUBLE_BOND:
        case SmilesAtom.STEREOCHEMISTRY_ALLENE:
          boolean isAllene = true;//(chiralClass == SmilesAtom.STEREOCHEMISTRY_ALLENE);
          if (isAllene) {
            sAtom1 = sAtom.getBond(0).getOtherAtom(sAtom);
            sAtom2 = sAtom.getBond(1).getOtherAtom(sAtom);
            if (sAtom1 == null || sAtom2 == null)
              continue; // "OK - stereochemistry is desgnated for something like C=C=O
            // cumulenes
            SmilesAtom sAtom1a = sAtom;
            SmilesAtom sAtom2a = sAtom;
            while (sAtom1.getBondCount() == 2
                && sAtom2.getBondCount() == 2
                && sAtom1.getValence() == 4 && sAtom2.getValence() == 4) {
              SmilesBond b = sAtom1.getBondNotTo(sAtom1a, true);
              sAtom1a = sAtom1;
              sAtom1 = b.getOtherAtom(sAtom1);
              b = sAtom2.getBondNotTo(sAtom2a, true);
              sAtom2a = sAtom2;
              sAtom2 = b.getOtherAtom(sAtom2);
            }
            sAtom = sAtom1;
          }
          jn = new JmolNode[6];
          jn[4] = new SmilesAtom(604);
          int nBonds = sAtom.getBondCount();
          for (int k = 0; k < nBonds; k++) {
            sAtom1 = sAtom.bonds[k].getOtherAtom(sAtom);
            if (sAtom.bonds[k].matchingBond.getCovalentOrder() == 2) {
              if (sAtom2 == null)
                sAtom2 = sAtom1;
            } else if (jn[0] == null) {
              jn[0] = getJmolAtom(sAtom1.getMatchingAtom());
            } else {
              jn[1] = getJmolAtom(sAtom1.getMatchingAtom());
            }
          }
          if (sAtom2 == null)
            continue;
          nBonds = sAtom2.getBondCount();
          if (nBonds < 2 || nBonds > 3)
            continue; // [C@]=O always matches
          for (int k = 0; k < nBonds; k++) {
            sAtom1 = sAtom2.bonds[k].getOtherAtom(sAtom2);
            if (sAtom2.bonds[k].matchingBond.getCovalentOrder() == 2) {
            } else if (jn[2] == null) {
              jn[2] = getJmolAtom(sAtom1.getMatchingAtom());
            } else {
              jn[3] = getJmolAtom(sAtom1.getMatchingAtom());
            }
          }

          if (isSmilesFind) {
            if (jn[1] == null)
              getX(sAtom, sAtom2, jn, 1, false, isAllene);
            if (jn[3] == null)
              getX(sAtom2, sAtom, jn, 3, false, false);
            if (!setSmilesCoordinates(atom0, sAtom, sAtom2, jn))
              return false;
          }
          if (jn[1] == null)
            getX(sAtom, sAtom2, jn, 1, true, false);
          if (jn[3] == null)
            getX(sAtom2, sAtom, jn, 3, true, false);
          if (!checkStereochemistry(sAtom.not, atom0, chiralClass, order,
              jn[0], jn[1], jn[2], jn[3], null, null, v))
            return false;
          continue;
        case SmilesAtom.STEREOCHEMISTRY_TETRAHEDRAL:
        case SmilesAtom.STEREOCHEMISTRY_SQUARE_PLANAR:
        case SmilesAtom.STEREOCHEMISTRY_TRIGONAL_BIPYRAMIDAL:
        case SmilesAtom.STEREOCHEMISTRY_OCTAHEDRAL:
          atom1 = getJmolAtom(sAtom.getMatchingBondedAtom(0));
          switch (nH) {
          case 0:
            atom2 = getJmolAtom(sAtom.getMatchingBondedAtom(1));
            break;
          case 1:
            atom2 = getHydrogens(getJmolAtom(sAtom.getMatchingAtom()), null);
            if (sAtom.isFirst) {
              JmolNode a = atom2;
              atom2 = atom1;
              atom1 = a;
            }
            break;
          default:
            continue;
          }
          atom3 = getJmolAtom(sAtom.getMatchingBondedAtom(2 - nH));
          atom4 = getJmolAtom(sAtom.getMatchingBondedAtom(3 - nH));
          atom5 = getJmolAtom(sAtom.getMatchingBondedAtom(4 - nH));
          atom6 = getJmolAtom(sAtom.getMatchingBondedAtom(5 - nH));

          // in all the checks below, we use Measure utilities to
          // three given atoms -- the normal, in particular. We
          // then use dot products to check the directions of normals
          // to see if the rotation is in the direction required.

          // we only use TP1, TP2, OH1, OH2 here.
          // so we must also check that the two bookend atoms are axial

          if (isSmilesFind
              && !setSmilesCoordinates(atom0, sAtom, sAtom2, new JmolNode[] {
                  atom1, atom2, atom3, atom4, atom5, atom6 }))
            return false;

          if (!checkStereochemistry(sAtom.not, atom0, chiralClass, order,
              atom1, atom2, atom3, atom4, atom5, atom6, v))
            return false;
          continue;
        }
      }
    }
    // next, /C=C/ double bond stereochemistry

    if (haveBondStereochemistry) {
      for (int k = 0; k < atomCount; k++) {
        SmilesAtom sAtom1 = patternAtoms[k];
        SmilesAtom sAtom2 = null;
        SmilesAtom sAtomDirected1 = null;
        SmilesAtom sAtomDirected2 = null;
        int dir1 = 0;
        int dir2 = 0;
        int bondType = 0;
        SmilesBond b;
        int nBonds = sAtom1.getBondCount();
        boolean isAtropisomer = false;
        for (int j = 0; j < nBonds; j++) {
          b = sAtom1.getBond(j);
          boolean isAtom2 = (b.getAtom2() == sAtom1);
          int type = b.bondType;
          switch (type) {
          case SmilesBond.TYPE_ATROPISOMER_1:
          case SmilesBond.TYPE_ATROPISOMER_2:
          case SmilesBond.TYPE_DOUBLE:
            if (isAtom2)
              continue;
            sAtom2 = b.getAtom2();
            bondType = type;
            isAtropisomer = (type != SmilesBond.TYPE_DOUBLE);
            if (isAtropisomer)
              dir1 = (b.isNot ? -1 : 1);
            break;
          case SmilesBond.TYPE_DIRECTIONAL_1:
          case SmilesBond.TYPE_DIRECTIONAL_2:
            sAtomDirected1 = (isAtom2 ? b.getAtom1() : b.getAtom2());
            dir1 = (isAtom2 != (type == SmilesBond.TYPE_DIRECTIONAL_1) ? 1 : -1);
            break;
          }
        }
        if (isAtropisomer) {
          //System.out.println(sAtom1 + " " + sAtom2);
          b = sAtom1.getBondNotTo(sAtom2, false);
          if (b == null)
            return false;
          sAtomDirected1 = b.getOtherAtom(sAtom1);
          b = sAtom2.getBondNotTo(sAtom1, false);
          if (b == null)
            return false;
          sAtomDirected2 = b.getOtherAtom(sAtom2);
        } else {
          if (sAtom2 == null || dir1 == 0)
            continue;
          nBonds = sAtom2.getBondCount();
          for (int j = 0; j < nBonds && dir2 == 0; j++) {
            b = sAtom2.getBond(j);
            boolean isAtom2 = (b.getAtom2() == sAtom2);
            int type = b.bondType;
            switch (type) {
            case SmilesBond.TYPE_DIRECTIONAL_1:
            case SmilesBond.TYPE_DIRECTIONAL_2:
              sAtomDirected2 = (isAtom2 ? b.getAtom1() : b.getAtom2());
              dir2 = (isAtom2 != (type == SmilesBond.TYPE_DIRECTIONAL_1) ? 1
                  : -1);
              break;
            }
          }
          if (dir2 == 0)
            continue;
        }
        if (isSmilesFind)
          setSmilesBondCoordinates(sAtom1, sAtom2, bondType);
        JmolNode dbAtom1 = getJmolAtom(sAtom1.getMatchingAtom());
        JmolNode dbAtom2 = getJmolAtom(sAtom2.getMatchingAtom());
        JmolNode dbAtom1a = getJmolAtom(sAtomDirected1.getMatchingAtom());
        JmolNode dbAtom2a = getJmolAtom(sAtomDirected2.getMatchingAtom());
        if (dbAtom1a == null || dbAtom2a == null)
          return false;
        SmilesMeasure.setTorsionData((Point3f) dbAtom1a, (Point3f) dbAtom1,
            (Point3f) dbAtom2, (Point3f) dbAtom2a, v, isAtropisomer);
        if (isAtropisomer) {
          // Ranges here involve
          // acos(0.05) and acos(0.95) to exclude
          // conformations very close to 90o and 0o
          dir2 = (bondType == SmilesBond.TYPE_ATROPISOMER_1 ? 1 : -1);
          float f = v.vTemp1.dot(v.vTemp2);
          if (f < 0.05f || f > 0.95f
              || v.vNorm1.dot(v.vNorm2) * dir1 * dir2 > 0) // sign of dihedral < or > here
            return false;
        } else {
          // for \C=C\, (dir1*dir2 == -1), dot product should be negative
          // because the bonds are oppositely directed
          // for \C=C/, (dir1*dir2 == 1), dot product should be positive
          // because the bonds are only about 60 degrees apart
          if (v.vTemp1.dot(v.vTemp2) * dir1 * dir2 < 0)
            return false;
        }
      }
    }
    return true;

  }

  private void getX(SmilesAtom sAtom, SmilesAtom sAtom2, JmolNode[] jn, int pt,
                    boolean haveCoordinates, boolean needHSwitch) {
    JmolNode atom = getJmolAtom(sAtom.getMatchingAtom());
    boolean doSwitch = sAtom.isFirst || pt == 3;
    if (haveCoordinates) {
      if (isSmarts) {
        JmolEdge[] b = atom.getEdges();
        for (int i = 0; i < b.length; i++) {
          if (b[i].getCovalentOrder() == 2)
            continue;
          JmolNode a = jmolAtoms[atom.getBondedAtomIndex(i)];
          if (a == jn[pt - 1])
            continue;
          jn[pt] = a;
          break;
        }
      }
      if (jn[pt] == null) {
        // add a dummy point for stereochemical reference
        // imines and diazines only
        Vector3f v = new Vector3f();
        int n = 0;
        for (int i = 0; i < 4; i++) {
          if (jn[i] == null)
            continue;
          n++;
          v.sub((Point3f) jn[i]);
        }
        if (v.length() == 0) {
          v.set(((Point3f)jn[4]));
          doSwitch = false;
        } else {
          v.scaleAdd(n + 1,(Point3f) getJmolAtom(sAtom.getMatchingAtom()), v);
          doSwitch = isSmilesFind || doSwitch ;
        }
        jn[pt] = new SmilesAtom(-1);
        ((Point3f) jn[pt]).set(v);
      }
    }
    if (jn[pt] == null) {
      jn[pt] = getHydrogens(atom, null);
      if (needHSwitch)
        doSwitch = true;
    }
    if (jn[pt] != null && doSwitch) {
      // a missing substituent on the SECOND atom
      // should be placed in position 2, not 3
      // also check for the VERY first atom in a set
      // attached H is first in that case
      // so we have to switch it, since we have
      // assigned already the first atom to be
      // the first pattern atom
      JmolNode a = jn[pt];
      jn[pt] = jn[pt - 1];
      jn[pt - 1] = a;
    }
  }
 
  static boolean checkStereochemistry(boolean isNot, JmolNode atom0, int chiralClass, int order,
                                JmolNode atom1, JmolNode atom2, JmolNode atom3, JmolNode atom4, JmolNode atom5, JmolNode atom6, VTemp v) {
   
    switch (chiralClass) {
    default:
    case SmilesAtom.STEREOCHEMISTRY_ALLENE:
    case SmilesAtom.STEREOCHEMISTRY_TETRAHEDRAL:
      return (isNot == (getHandedness(atom2, atom3, atom4, atom1, v) != order));
    case SmilesAtom.STEREOCHEMISTRY_TRIGONAL_BIPYRAMIDAL:
      // check for axial-axial'
      return (isNot == (!isDiaxial(atom0, atom0, atom5, atom1, v, -0.95f)
          || getHandedness(atom2, atom3, atom4, atom1, v) != order));
    case SmilesAtom.STEREOCHEMISTRY_OCTAHEDRAL:
      if (isNot != (!isDiaxial(atom0, atom0, atom6, atom1, v, -0.95f)))
        return false;
      // check for CW or CCW set
      getPlaneNormals(atom2, atom3, atom4, atom5, v);
      if (isNot != (v.vNorm1.dot(v.vNorm2) < 0
          || v.vNorm2.dot(v.vNorm3) < 0))
        return false;
      // now check rotation in relation to the first atom
      v.vNorm2.set((Point3f) atom0);
      v.vNorm2.sub((Point3f) atom1);
      return (isNot == ((v.vNorm1.dot(v.vNorm2) < 0 ? 2 : 1) == order));
    //case SmilesAtom.STEREOCHEMISTRY_DOUBLE_BOND:
      //System.out.println("draw p1 @{point" + new Point3f((Point3f)atom1)+"} color red");
      //System.out.println("draw p2 @{point" + new Point3f((Point3f)atom2)+"} color yellow");
      //System.out.println("draw p3 @{point" + new Point3f((Point3f)atom3)+"} color green");
      //System.out.println("draw p4 @{point" + new Point3f((Point3f)atom4)+"} color blue");

      //getPlaneNormals(atom1, atom2, atom3, atom4, v);
      //System.out.println(order + " "+ atom1.getAtomName() + "-" + atom2.getAtomName() + "-"  + atom3.getAtomName() + "-" + atom4.getAtomName());
      //return (isNot == ((v.vNorm1.dot(v.vNorm2) < 0 ? 2 : 1) == order));
    case SmilesAtom.STEREOCHEMISTRY_SQUARE_PLANAR:
      getPlaneNormals(atom1, atom2, atom3, atom4, v);
      // vNorm1 vNorm2 vNorm3 are right-hand normals for the given
      // triangles
      // 1-2-3, 2-3-4, 3-4-1
      // sp1 up up up U-shaped
      // sp2 up up DOWN 4-shaped
      // sp3 up DOWN DOWN Z-shaped
      return (v.vNorm1.dot(v.vNorm2) < 0 ? isNot == (order != 3)
          : v.vNorm2.dot(v.vNorm3) < 0 ? isNot == (order != 2)
          : isNot == (order != 1));
    }
  }
 
  private JmolNode getJmolAtom(int i) {
    return (i < 0 || i >= jmolAtoms.length ? null : jmolAtoms[i]);
  }

  private void setSmilesBondCoordinates(SmilesAtom sAtom1, SmilesAtom sAtom2,
                                        int bondType) {
    JmolNode dbAtom1 = jmolAtoms[sAtom1.getMatchingAtom()];
    JmolNode dbAtom2 = jmolAtoms[sAtom2.getMatchingAtom()];
    // Note that the directionality of the bond depends upon whether
    // the alkene C is the first or the second atom in the bond.
    // if it is the first -- C(/X)= or C/1= -- then the X is UP
    // but if it is the second: -- X/C= or X/1... C1= -- then the X is DOWN
    //
    //                         C C       C     C
    //                        / /         \   /
    //      C(/C)=C/C  ==    C=C     ==    C=C     ==   C\C=C/C  
    //
    // because what we are doing is translating the / or \ vertically
    // to match the atoms it is connected to. Same with rings:
    //
    //                       CCC C     CCC     C
    //                        / /         \   /
    //  C1CC.C/1=C/C  ==     C=C    ==     C=C     ==   CCC\C=C/C  
    //
    // If the branch ALSO has a double bond,
    // then for THAT double bond we will have it the normal way:
    //
    //                              Br
    //                             /    BR
    //                          C=C      \
    //                         / C        C=C     C
    //                        / /            \   /
    //  C(/C=C/Br)=C/C  ==   C=C     ==       C=C     ==  Br\C=C\C=C/C  
    //
    // interesting case for ring connections:
    //
    // Br/C=C\1OCCC.C/1=C/C=C/CCS/C=C\2CCCC.NN/2
    //
    // Note that that directionality of the matching ring bonds must be OPPOSITE.
    // Better is to not show it both places:
    //
    // Br/C=C\1OCCC.C/1=C/C=C/CCS/C=C\2CCCC.NN/2
    //
    dbAtom1.set(-1, 0, 0);
    dbAtom2.set(1, 0, 0);
    if (bondType == SmilesBond.TYPE_DOUBLE) {
      int nBonds = 0;
      int dir1 = 0;
      JmolEdge[] bonds = dbAtom1.getEdges();
      for (int k = bonds.length; --k >= 0;) {
        JmolEdge bond = bonds[k];
        JmolNode atom = bond.getOtherAtom(dbAtom1);
        if (atom == dbAtom2)
          continue;
        atom.set(-1, (nBonds++ == 0) ? -1 : 1, 0);
        int mode = (bond.getAtomIndex2() == dbAtom1.getIndex() ? nBonds
            : -nBonds);
        switch (bond.getOrder()) {
        case JmolEdge.BOND_STEREO_NEAR:
          dir1 = mode;
          break;
        case JmolEdge.BOND_STEREO_FAR:
          dir1 = -mode;
        }
      }
      int dir2 = 0;
      nBonds = 0;
      JmolNode[] atoms = new JmolNode[2];
      bonds = dbAtom2.getEdges();
      for (int k = bonds.length; --k >= 0;) {
        JmolEdge bond = bonds[k];
        JmolNode atom = bond.getOtherAtom(dbAtom2);
        if (atom == dbAtom1)
          continue;
        atoms[nBonds] = atom;
        atom.set(1, (nBonds++ == 0) ? 1 : -1, 0);
        int mode = (bond.getAtomIndex2() == dbAtom2.getIndex() ? nBonds
            : -nBonds);
        switch (bond.getOrder()) {
        case JmolEdge.BOND_STEREO_NEAR:
          dir2 = mode;
          break;
        case JmolEdge.BOND_STEREO_FAR:
          dir2 = -mode;
        }

      }
      //     2     3
      //      \   /
      //       C=C
      //      /   \
      //     1     4
      //
      // check for overall directionality matching even/oddness of bond order
      // and switch Y positions of 3 and 4 if necessary
      // 
      if ((dir1 * dir2 > 0) == (Math.abs(dir1) % 2 == Math.abs(dir2) % 2)) {
        float y = ((Point3f) atoms[0]).y;
        ((Point3f) atoms[0]).y = ((Point3f) atoms[1]).y;
        ((Point3f) atoms[1]).y = y;
      }
    } else {
      // just set ALL the attached bonds to the given dihedral setting
      JmolEdge[] bonds = dbAtom1.getEdges();
      int dir = 0;
      for (int k = bonds.length; --k >= 0;) {
        JmolEdge bond = bonds[k];
        if (bond.getOtherAtom(dbAtom1) == dbAtom2) {
          dir = (bond.getOrder() == JmolEdge.BOND_PARTIAL01 ? 1 : -1);
          break;
        }
      }
      for (int k = bonds.length; --k >= 0;) {
        JmolEdge bond = bonds[k];
        JmolNode atom = bond.getOtherAtom(dbAtom1);
        if (atom != dbAtom2)
          atom.set(-1, 1, 0);
      }   
      bonds = dbAtom2.getEdges();
      for (int k = bonds.length; --k >= 0;) {
        JmolEdge bond = bonds[k];
        JmolNode atom = bond.getOtherAtom(dbAtom2);
        if (atom != dbAtom1)
          atom.set(1, 1, -dir/2.0f);
      }
    }
  }

  private boolean setSmilesCoordinates(JmolNode atom, SmilesAtom sAtom, SmilesAtom sAtom2, JmolNode[] cAtoms) {

    // When testing equality of two SMILES strings in terms of stereochemistry,
    // we need to set the atom positions based on the ORIGINAL SMILES order,
    // which, except for the H atom, will be the same as the "matchedAtom"
    // index.
    // all the necessary information is passed via the atomSite field of Atom

    // atomSite is used by smilesMatch.find to encode chiralClass and chiralOrder
    int atomSite = atom.getAtomSite();
    if (atomSite == Integer.MIN_VALUE)
      return false;
    int chiralClass = atomSite >> 8;
    int chiralOrder = atomSite & 0xFF;
    JmolNode a2 = (chiralClass == 2 || chiralClass == 3 ?
      a2 = jmolAtoms[sAtom2.getMatchingAtom()] : null);
       
    // set the chirality center at the origin
    atom.set(0, 0, 0);
    atom = jmolAtoms[sAtom.getMatchingAtom()];
    atom.set(0, 0, 0);

    int[] map = getMappedAtoms(atom, a2, cAtoms);
    switch (chiralClass) {
    case SmilesAtom.STEREOCHEMISTRY_ALLENE:
    case SmilesAtom.STEREOCHEMISTRY_TETRAHEDRAL:
      if (chiralOrder == 2) {
        int i = map[0];
        map[0] = map[1];
        map[1] = i;
      }
      cAtoms[map[0]].set(0, 0, 1);
      cAtoms[map[1]].set(1, 0, -1);
      cAtoms[map[2]].set(0, 1, -1);
      cAtoms[map[3]].set(-1, -1, -1);
      break;
/*     
    case SmilesAtom.STEREOCHEMISTRY_DOUBLE_BOND:
      switch (chiralOrder) {
      case 1: // U-shaped 0 3 2 1
        cAtoms[map[0]].set(1, 0, 0);
        cAtoms[map[1]].set(0, 1, 0);
        cAtoms[map[2]].set(-1, 0, 0);
        cAtoms[map[3]].set(0, -1, 0);
        break;
      case 2: // 4-shaped
        cAtoms[map[0]].set(1, 0, 0);
        cAtoms[map[1]].set(-1, 0, 0);
        cAtoms[map[2]].set(0, 1, 0);
        cAtoms[map[3]].set(0, -1, 0);
        break;
      }
      break;
*/     
    case SmilesAtom.STEREOCHEMISTRY_SQUARE_PLANAR:
      switch (chiralOrder) {
      case 1: // U-shaped
        cAtoms[map[0]].set(1, 0, 0);
        cAtoms[map[1]].set(0, 1, 0);
        cAtoms[map[2]].set(-1, 0, 0);
        cAtoms[map[3]].set(0, -1, 0);
        break;
      case 2: // 4-shaped
        cAtoms[map[0]].set(1, 0, 0);
        cAtoms[map[1]].set(-1, 0, 0);
        cAtoms[map[2]].set(0, 1, 0);
        cAtoms[map[3]].set(0, -1, 0);
        break;
      case 3: // Z-shaped
        cAtoms[map[0]].set(1, 0, 0);
        cAtoms[map[1]].set(0, 1, 0);
        cAtoms[map[2]].set(0, -1, 0);
        cAtoms[map[3]].set(-1, 0, 0);
        break;
      }
      break;
    case SmilesAtom.STEREOCHEMISTRY_TRIGONAL_BIPYRAMIDAL:
    case SmilesAtom.STEREOCHEMISTRY_OCTAHEDRAL:
      int n = map.length;
      if (chiralOrder == 2) {
        int i = map[0];
        map[0] = map[n - 1];
        map[n - 1] = i;
      }
      cAtoms[map[0]].set(0, 0, 1);
      cAtoms[map[n - 1]].set(0, 0, -1);
      cAtoms[map[1]].set(1, 0, 0);
      cAtoms[map[2]].set(0, 1, 0);
      cAtoms[map[3]].set(-1, 0, 0);
      if (n == 6)
        cAtoms[map[4]].set(0, -1, 0);
      break;
    }
    return true;
  }

  int[] getMappedAtoms(JmolNode atom, JmolNode a2, JmolNode[] cAtoms) {
  
    // Here is the secret:
    // Sort the atoms by the origintal order of bonds
    // in the SMILES string that generated the
    // atom set.
    int[] map = new int[cAtoms[4] == null ? 4 : cAtoms[5] == null ? 5 : 6];
    for (int i = 0; i < map.length; i++)
      map[i] = (cAtoms[i] == null ? 104 + i * 100: cAtoms[i].getIndex());
    int k;
    JmolEdge[] bonds = atom.getEdges();
    JmolEdge[] b2 = (a2 == null ? null : a2.getEdges());
    for (int i = 0; i < map.length; i++) {
      for (k = 0; k < bonds.length; k++)
        if (bonds[k].getOtherAtom(atom) == cAtoms[i])
          break;
      if (k < bonds.length) {
        map[i] = (k * 10 + 100) + i;
      } else if (a2 != null) {
        for (k = 0; k < b2.length; k++)
          if (b2[k].getOtherAtom(a2) == cAtoms[i])
            break;
        if (k < b2.length)
          map[i] = (k * 10 + 300) + i;
      }
    }
    Arrays.sort(map);
    for (int i = 0; i < map.length; i++) {
      map[i] = map[i] % 10;
      // System.out.println("i=" + i + "; map[i]=" + map[i] + " a=" +
      // cAtoms[map[i]].getIndex());
    }
    return map;
  }
  static class VTemp {
    final Vector3f vTemp = new Vector3f();
    final Vector3f vA = new Vector3f();
    final Vector3f vB = new Vector3f();
    final Vector3f vTemp1 = new Vector3f();
    final Vector3f vTemp2 = new Vector3f();
    final Vector3f vNorm1 = new Vector3f();
    final Vector3f vNorm2 = new Vector3f();
    final Vector3f vNorm3 = new Vector3f();
  }
 
  VTemp v = new VTemp();
 
  static boolean isDiaxial(JmolNode atomA, JmolNode atomB, JmolNode atom1, JmolNode atom2, VTemp v, float f) {
    v.vA.set((Point3f) atomA);
    v.vB.set((Point3f) atomB);
    v.vA.sub((Point3f) atom1);
    v.vB.sub((Point3f) atom2);
    // -0.95f about 172 degrees
    return (v.vA.dot(v.vB) < f);
  }

  /**
   * compares the
   * @param a
   * @param b
   * @param c
   * @param pt
   * @param v
   * @return   1 for "@", 2 for "@@" 
   */
  private static int getHandedness(JmolNode a, JmolNode b, JmolNode c, JmolNode pt, VTemp v) {
    float d = SmilesAromatic.getNormalThroughPoints(a, b, c, v.vTemp, v.vA, v.vB);
    //System.out.println("draw p1 @{point" + new Point3f((Point3f)a) +"} color red");
    //System.out.println("draw p2 @{point" + new Point3f((Point3f)b)+"} color green");
    //System.out.println("draw p3 @{point" + new Point3f((Point3f)c)+"} color blue");
    //System.out.println("draw p plane @{point" + new Point3f((Point3f)a) +"} @{point" + new Point3f((Point3f)b)+"} @{point" + new Point3f((Point3f)c)+"}");
    //System.out.println("draw v vector {0 0 0} @{point" + v.vTemp+"}");
    return (distanceToPlane(v.vTemp, d, (Point3f) pt) > 0 ? 1 : 2);
  }

  private static void getPlaneNormals(JmolNode atom1, JmolNode atom2, JmolNode atom3, JmolNode atom4, VTemp v) {
    SmilesAromatic.getNormalThroughPoints(atom1, atom2, atom3, v.vNorm1, v.vTemp1,
        v.vTemp2);
    SmilesAromatic.getNormalThroughPoints(atom2, atom3, atom4, v.vNorm2, v.vTemp1,
        v.vTemp2);
    SmilesAromatic.getNormalThroughPoints(atom3, atom4, atom1, v.vNorm3, v.vTemp1,
        v.vTemp2);
  }

  static float distanceToPlane(Vector3f norm, float w, Point3f pt) {
    return (norm == null ? Float.NaN
        : (norm.x * pt.x + norm.y * pt.y + norm.z * pt.z + w)
        / (float) Math.sqrt(norm.x * norm.x + norm.y * norm.y + norm.z
            * norm.z));
  }

/*
  static String getDoubleBondStereoFlag(JmolNode[] atoms, VTemp v) {
    JmolNode atom1 = atoms[0];
    JmolNode atom2 = atoms[1];
    JmolNode atom3 = atoms[2];
    JmolNode atom4 = atoms[3];
    getPlaneNormals(atom1, atom2, atom3, atom4, v);
    return (v.vNorm1.dot(v.vNorm2) < 0 ? "@" : "@@");
  }
*/
 
  void createTopoMap(BitSet bsAromatic) {
    if (bsAromatic == null)
      bsAromatic = new BitSet();
    int nAtomsMissing = getMissingHydrogenCount();
    SmilesAtom[] atoms = new SmilesAtom[atomCount + nAtomsMissing];
    jmolAtoms = atoms;
    int ptAtom = 0;
    BitSet bsFixH = new BitSet();
    for (int i = 0; i < atomCount; i++) {
      SmilesAtom sAtom = patternAtoms[i];
      int cclass = sAtom.getChiralClass();
      int n = sAtom.missingHydrogenCount;
      if (n < 0)
        n = 0;
      // create a Jmol atom for this pattern atom
      // we co-opt atom.matchingAtom here
      // because this search will never actually be run
      SmilesAtom atom = atoms[ptAtom] = new SmilesAtom(0, ptAtom,
          cclass == Integer.MIN_VALUE ? cclass : (cclass << 8)
              + sAtom.getChiralOrder(), sAtom.elementNumber, sAtom.getCharge());
      atom.atomName = sAtom.atomName;
      atom.residueName = sAtom.residueName;
      atom.residueChar = sAtom.residueChar;
      atom.isBioAtom = sAtom.isBioAtom;
      atom.isLeadAtom = sAtom.isLeadAtom;
      atom.setAtomicMass(sAtom.getAtomicMass());
      //System.out.println(atom);
      // we pass on the aromatic flag because
      // we don't want SmilesSearch to calculate
      // that for us
      if (sAtom.isAromatic())
        bsAromatic.set(ptAtom);
      // set up the bonds array and fill with H atoms
      // when there is only 1 H and the atom is NOT FIRST, then it will
      // be important to designate the bonds in order -- with the
      // H SECOND not first
      // this is still not satisfactory for allenes or the second atom of
      // imines and possibly double bonds. We handle that later.

      if (!sAtom.isFirst && n == 1 && cclass > 0)
        bsFixH.set(ptAtom);

      sAtom.setMatchingAtom(ptAtom++);
      SmilesBond[] bonds = new SmilesBond[sAtom.getBondCount() + n];
      atom.setBonds(bonds);
      while (--n >= 0) {
        SmilesAtom atomH = atoms[ptAtom] = new SmilesAtom(0, ptAtom, 0,
            (short) 1, 0);
        //System.out.println(atomH);
        ptAtom++;
        atomH.setBonds(new SmilesBond[1]);
        SmilesBond b = new SmilesBond(atom, atomH,
            JmolEdge.BOND_COVALENT_SINGLE, false);
        Logger.info("" + b);
      }
    }

    // set up bonds
    for (int i = 0; i < atomCount; i++) {
      SmilesAtom sAtom = patternAtoms[i];
      int i1 = sAtom.getMatchingAtom();
      SmilesAtom atom1 = atoms[i1];
      int n = sAtom.getBondCount();
      for (int j = 0; j < n; j++) {
        SmilesBond sBond = sAtom.getBond(j);
        boolean firstAtom = (sBond.getAtom1() == sAtom);
        //SmilesBond b;
        if (firstAtom) {
          int order = 1;
          switch (sBond.bondType) {
          // these first two are for cis/trans alkene
          // stereochemistry; we co-opt stereo near/far here
          case SmilesBond.TYPE_ATROPISOMER_1:
            order = JmolEdge.BOND_PARTIAL01;
            break;
          case SmilesBond.TYPE_ATROPISOMER_2:
            order = JmolEdge.BOND_PARTIAL23;
            break;
          case SmilesBond.TYPE_DIRECTIONAL_1:
            order = JmolEdge.BOND_STEREO_NEAR;
            break;
          case SmilesBond.TYPE_DIRECTIONAL_2:
            order = JmolEdge.BOND_STEREO_FAR;
            break;
          case SmilesBond.TYPE_BIO_PAIR:
          case SmilesBond.TYPE_BIO_SEQUENCE:
            order = sBond.bondType;
            break;
          case SmilesBond.TYPE_SINGLE:
            order = JmolEdge.BOND_COVALENT_SINGLE;
            break;
          case SmilesBond.TYPE_AROMATIC:
            order = JmolEdge.BOND_AROMATIC_DOUBLE;
            break;
          case SmilesBond.TYPE_DOUBLE:
            order = JmolEdge.BOND_COVALENT_DOUBLE;
            break;
          case SmilesBond.TYPE_TRIPLE:
            order = JmolEdge.BOND_COVALENT_TRIPLE;
            break;
          }
          SmilesAtom atom2 = atoms[sBond.getAtom2().getMatchingAtom()];
          SmilesBond b = new SmilesBond(atom1, atom2, order, false);
          // do NOT add this bond to the second atom -- we will do that later;
          atom2.bondCount--;
          Logger.info("" + b);
        } else {
          SmilesAtom atom2 = atoms[sBond.getAtom1().getMatchingAtom()];
          SmilesBond b = atom2.getBondTo(atom1);
          // NOW we can add this bond
          atom1.addBond(b);
         
        }
      }
    }
    // fix H atoms
    for (int i = bsFixH.nextSetBit(0); i >= 0; i = bsFixH.nextSetBit(i + 1)) {
      JmolEdge[] bonds = atoms[i].getEdges();
      JmolEdge b = bonds[0];
      bonds[0] = bonds[1];
      bonds[1] = b;
    }
  }

  public void setParent(SmilesSearch parent) {
    if (parent == null)
      this.parent = this;
    else
      this.parent = parent.getParent();
  }

  SmilesSearch getParent() {
    return (parent == this ? this : parent.getParent());
  }
 
}
TOP

Related Classes of org.jmol.smiles.SmilesSearch

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.