/* 
A* -------------------------------------------------------------------
B* This file contains source code for the PyMOL computer program
C* copyright 1998-2004 by Warren Lyford Delano of DeLano Scientific. 
D* -------------------------------------------------------------------
E* It is unlawful to modify or remove this copyright notice.
F* -------------------------------------------------------------------
G* Please see the accompanying LICENSE file for further information. 
H* -------------------------------------------------------------------
I* Additional authors of this source file include:
-* 
-* 
-*
Z* -------------------------------------------------------------------
*/

#include <vector>

#include"os_python.h"
#include"os_numpy.h"
#include"os_std.h"

#include"Base.h"
#include"Map.h"
#include"Vector.h"
#include"Debug.h"
#include"Err.h"
#include"Word.h"
#include"Util.h"
#include"PConv.h"
#include"P.h"

#include"MemoryDebug.h"
#include"Selector.h"
#include"Executive.h"
#include"ObjectMolecule.h"
#include"CoordSet.h"
#include"DistSet.h"
#include"Word.h"
#include"Scene.h"
#include"CGO.h"
#include"Seq.h"
#include"Editor.h"
#include"Seeker.h"
#include "Lex.h"
#include "Mol2Typing.h"

#include"OVContext.h"
#include"OVLexicon.h"
#include"OVOneToAny.h"
#include"Parse.h"

#include"ListMacros.h"

#include "SelectorDef.h"

#define cSelectorTmpPrefix "_sel_tmp_"
#define cSelectorTmpPrefixLen 9 /* strlen(cSelectorTmpPrefix) */
#define cSelectorTmpPattern "_sel_tmp_*"

#define cDummyOrigin 0
#define cDummyCenter 1


/* special selections, unknown to executive */
#define cColorectionFormat "_!c_%s_%d"

#if 0
// A lot of PyMOL's utility code used to select with lower case atomic
// identifiers, making the code dependent on "ignore_case=1". The default
// for this setting was changed to "0 (off)", which required to fix all
// affected scripts (e.g. mutagenesis wizard, PYMOL-2487).
// This is a debug/helper function to identify use of lower case
// identifiers in selection expressions. Remove before the next official
// release.
void WARN_IF_LOWERCASE(PyMOLGlobals * G, const char * label, const char * text) {
  if (!*text)
    return;
  for (const char * p = text; *p; ++p)
    if (isupper(*p))
      return;
  PRINTFB(G, FB_Selector, FB_Warnings)
    "SELECTOR-LOWER-WARNING: %s '%s'\n", label, text
    ENDFB(G);
}
#else
#define WARN_IF_LOWERCASE(G, a, b)
#endif

// Count how often `c` occurs in `s`
static int strchrcount(const char *s, char c) {
  int i = 0;
  while(*s)
    if(*(s++) == c)
      i++;
  return i;
}

static WordKeyValue rep_names[] = {
  {"spheres", cRepSphereBit},
  {"sticks", cRepCylBit},
  {"surface", cRepSurfaceBit},
  {"labels", cRepLabelBit},
  {"nb_spheres", cRepNonbondedSphereBit},
  {"cartoon", cRepCartoonBit},
  {"ribbon", cRepRibbonBit},
  {"lines", cRepLineBit},
  {"dots", cRepDotBit},
  {"mesh", cRepMeshBit},
  {"nonbonded", cRepNonbondedBit},
  {"ellipsoid", cRepEllipsoidBit},
  {"",},
};

static const char *backbone_names[] = {
  // protein
  "CA", "C", "O", "N", "OXT", "H",
  // nucleic acid
  "P", "OP1", "OP2", "OP3", "C1'", "C2'", "O2'",
  "C3'", "O3'", "C4'", "O4'", "C5'", "O5'",
  "H1'", "H3'", "H4'",
  "H2'", "H2''", "H12'", "H22'",
  "H5'", "H5''", "H15'", "H25'",
  "HO2'", "HO3'", "HO5'",
  ""
};

typedef struct {
  int level, imp_op_level;
  int type;                     /* 0 = value 1 = operation 2 = pre-operation */
  unsigned int code;
  SelectorWordType text;
  int *sele;
} EvalElem;

typedef struct {
  int depth1;
  int depth2;
  int depth3;
  int depth4;
  int sum;
  int frag;
} WalkDepthRec;

static int *SelectorSelect(PyMOLGlobals * G, const char *sele, int state, int domain, int quiet);
static int SelectorGetInterstateVLA(PyMOLGlobals * G, int sele1, int state1, int sele2,
                                    int state2, float cutoff, int **vla);

static int SelectorGetArrayNCSet(PyMOLGlobals * G, int *array, int no_dummies);

static int SelectorModulate1(PyMOLGlobals * G, EvalElem * base, int state);
static int SelectorSelect0(PyMOLGlobals * G, EvalElem * base);
static int SelectorSelect1(PyMOLGlobals * G, EvalElem * base, int quiet);
static int SelectorSelect2(PyMOLGlobals * G, EvalElem * base, int state);
static int SelectorLogic1(PyMOLGlobals * G, EvalElem * base, int state);
static int SelectorLogic2(PyMOLGlobals * G, EvalElem * base);
static int SelectorOperator22(PyMOLGlobals * G, EvalElem * base, int state);
static int *SelectorEvaluate(PyMOLGlobals * G, SelectorWordType * word, int state, int quiet);
static SelectorWordType *SelectorParse(PyMOLGlobals * G, const char *s);
static void SelectorPurgeMembers(PyMOLGlobals * G, int sele);
static int SelectorEmbedSelection(PyMOLGlobals * G, int *atom, const char *name,
                                  ObjectMolecule * obj, int no_dummies, int exec_manage);
static int *SelectorGetIndexVLA(PyMOLGlobals * G, int sele);
static int *SelectorGetIndexVLAImpl(PyMOLGlobals * G, CSelector *I, int sele);
static void SelectorClean(PyMOLGlobals * G);
static void SelectorCleanImpl(PyMOLGlobals * G, CSelector *I);
static int *SelectorApplyMultipick(PyMOLGlobals * G, Multipick * mp);
static int SelectorCheckNeighbors(PyMOLGlobals * G, int maxDepth, ObjectMolecule * obj,
                                  int at1, int at2, int *zero, int *scratch);

static int *SelectorUpdateTableSingleObject(PyMOLGlobals * G, ObjectMolecule * obj,
                                            int req_state,
                                            int no_dummies, int *idx,
                                            int n_idx, int numbered_tags);


/*========================================================================*/
/*
 * Iterator over the selector table (all atoms in universe)
 *
 * Selector table must be up-to-date!
 * Does NOT provide coord or coordset access
 *
 * (Implementation in Selector.cpp)
 */
class SelectorAtomIterator : public AbstractAtomIterator {
  CSelector * selector;

public:
  int a;        // index in selector

  SelectorAtomIterator(CSelector * I) {
    selector = I;

    // no coord set
    cs = NULL;
    idx = -1;

    reset();
  }

  void reset() {
  a = cNDummyAtoms - 1;
}

  bool next();
};

bool SelectorAtomIterator::next() {
  if ((++a) >= selector->NAtom)
    return false;

  TableRec *table_a = selector->Table + a;

  atm = table_a->atom;
  obj = selector->Obj[table_a->model];

  return true;
}


/*========================================================================*/
static int SelectorGetObjAtmOffset(CSelector * I, ObjectMolecule * obj, int offset)
{
  if(I->SeleBaseOffsetsValid) {
    return obj->SeleBase + offset;
  } else {
    ov_diff stop_below = obj->SeleBase;
    ov_diff stop_above = I->NAtom - 1;
    int result = stop_below;
    TableRec *i_table = I->Table;
    ObjectMolecule **i_obj = I->Obj;
    int step = offset;
    int cur;
    int proposed;
    int prior1 = -1, prior2 = -1;

    /* non-linear hunt to find atom */

    result = stop_below;
    cur = i_table[result].atom;
    while(step > 1) {
      if(cur < offset) {
        stop_below = result + 1;
        while(step > 1) {
          proposed = result + step;
          if(proposed <= stop_above) {
            if(i_obj[i_table[proposed].model] == obj) {
              if(proposed == prior1) {
                proposed--;
                step--;         /* guarantee progress (avoid flip flop) */
              }
              result = prior1 = proposed;
              break;
            } else if(stop_above > proposed) {
              stop_above = proposed - 1;
            }
          }
          step = (step >> 1);
        }
      } else if(cur > offset) {
        stop_above = result - 1;
        while(step > 1) {
          proposed = result - step;
          if(proposed >= stop_below) {
            if(i_obj[i_table[proposed].model] == obj) {
              if(proposed == prior2) {
                proposed++;
                step--;         /* guarantee progress (avoid flip flop) */
              }
              result = prior2 = proposed;
              break;
            }
          }
          step = (step >> 1);
        }
      } else
        return result;
      cur = i_table[result].atom;
      if(cur == offset)
        return result;
    }

    {
      /* failsafe / linear search */
      int dir = 1;
      if(cur > offset)
        dir = -1;
      while(1) {                /* TODO: optimize this search algorithm! */
        if(cur == offset)
          return result;
        if(dir > 0) {
          if(result >= stop_above)
            break;
          result++;
        } else {
          if(result <= stop_below)
            break;
          result--;
        }
        if(i_obj[i_table[result].model] != obj)
          break;
        cur = i_table[result].atom;
      }
    }
  }
  return -1;
}

#define STYP_VALU 0
#define STYP_OPR1 1
#define STYP_OPR2 2
#define STYP_SEL0 3
#define STYP_SEL1 4
#define STYP_SEL2 5
#define STYP_LIST 6
#define STYP_PRP1 7
#define STYP_SEL3 8
#define STYP_PVAL 0
#define STYP_OP22 9             /* sele oper arg1 arg2 sele */


/*                  code   |   type    | priority */

#define SELE_NOT1 ( 0x0100 | STYP_OPR1 | 0x70 )
#define SELE_BYR1 ( 0x0200 | STYP_OPR1 | 0x20 )
#define SELE_AND2 ( 0x0300 | STYP_OPR2 | 0x60 )
#define SELE_OR_2 ( 0x0400 | STYP_OPR2 | 0x40 )
#define SELE_IN_2 ( 0x0500 | STYP_OPR2 | 0x40 )
#define SELE_ALLz ( 0x0600 | STYP_SEL0 | 0x90 )
#define SELE_NONz ( 0x0700 | STYP_SEL0 | 0x90 )
#define SELE_HETz ( 0x0800 | STYP_SEL0 | 0x80 )
#define SELE_HYDz ( 0x0900 | STYP_SEL0 | 0x90 )
#define SELE_VISz ( 0x0A00 | STYP_SEL0 | 0x90 )
#define SELE_ARD_ ( 0x0B00 | STYP_PRP1 | 0x30 )
#define SELE_EXP_ ( 0x0C00 | STYP_PRP1 | 0x30 )
#define SELE_NAMs ( 0x0D00 | STYP_SEL1 | 0x80 )
#define SELE_ELEs ( 0x0E00 | STYP_SEL1 | 0x80 )
#define SELE_RSIs ( 0x0F00 | STYP_SEL1 | 0x80 )
#define SELE_CHNs ( 0x1000 | STYP_SEL1 | 0x80 )
#define SELE_SEGs ( 0x1100 | STYP_SEL1 | 0x80 )
#define SELE_MODs ( 0x1200 | STYP_SEL1 | 0x80 )
#define SELE_IDXs ( 0x1300 | STYP_SEL1 | 0x80 )
#define SELE_RSNs ( 0x1400 | STYP_SEL1 | 0x80 )
#define SELE_SELs ( 0x1500 | STYP_SEL1 | 0x80 )
#define SELE_BVLx ( 0x1600 | STYP_SEL2 | 0x80 )
#define SELE_ALTs ( 0x1700 | STYP_SEL1 | 0x80 )
#define SELE_FLGs ( 0x1800 | STYP_SEL1 | 0x80 )
#define SELE_GAP_ ( 0x1900 | STYP_PRP1 | 0x80 )
#define SELE_TTYs ( 0x1A00 | STYP_SEL1 | 0x80 )
#define SELE_NTYs ( 0x1B00 | STYP_SEL1 | 0x80 )
#define SELE_PCHx ( 0x1C00 | STYP_SEL2 | 0x80 )
#define SELE_FCHx ( 0x1D00 | STYP_SEL2 | 0x80 )
#define SELE_ID_s ( 0x1E00 | STYP_SEL1 | 0x80 )
#define SELE_BNDz ( 0x1F00 | STYP_SEL0 | 0x80 )
#define SELE_LIK2 ( 0x2000 | STYP_OPR2 | 0x40 )
#define SELE_NGH1 ( 0x2100 | STYP_OPR1 | 0x20 )
#define SELE_QVLx ( 0x2200 | STYP_SEL2 | 0x80 )
#define SELE_BYO1 ( 0x2300 | STYP_OPR1 | 0x20 )
#define SELE_SSTs ( 0x2400 | STYP_SEL1 | 0x80 )
#define SELE_STAs ( 0x2500 | STYP_SEL1 | 0x80 )
#define SELE_PREz ( 0x2500 | STYP_SEL0 | 0x80 )
#define SELE_WIT_ ( 0x2600 | STYP_OP22 | 0x30 )
#define SELE_ORIz ( 0x2700 | STYP_SEL0 | 0x90 )
#define SELE_CENz ( 0x2800 | STYP_SEL0 | 0x90 )
#define SELE_ENAz ( 0x2900 | STYP_SEL0 | 0x90 )
#define SELE_REPs ( 0x2A00 | STYP_SEL1 | 0x80 )
#define SELE_COLs ( 0x2B00 | STYP_SEL1 | 0x80 )
#define SELE_HBDs ( 0x2C00 | STYP_SEL0 | 0x80 )
#define SELE_HBAs ( 0x2D00 | STYP_SEL0 | 0x80 )
#define SELE_BYC1 ( 0x2E00 | STYP_OPR1 | 0x20 )
#define SELE_BYS1 ( 0x2F00 | STYP_OPR1 | 0x20 )
#define SELE_BYM1 ( 0x3000 | STYP_OPR1 | 0x20 )
#define SELE_BYF1 ( 0x3100 | STYP_OPR1 | 0x20 )
#define SELE_EXT_ ( 0x3200 | STYP_PRP1 | 0x30 )
#define SELE_BON1 ( 0x3300 | STYP_OPR1 | 0x50 )
#define SELE_FST1 ( 0x3400 | STYP_OPR1 | 0x30 )
#define SELE_CAS1 ( 0x3500 | STYP_OPR1 | 0x30 )
#define SELE_BEY_ ( 0x3600 | STYP_OP22 | 0x30 )
#define SELE_POLz ( 0x3700 | STYP_SEL0 | 0x90 )
#define SELE_SOLz ( 0x3800 | STYP_SEL0 | 0x90 )
#define SELE_ORGz ( 0x3900 | STYP_SEL0 | 0x90 )
#define SELE_INOz ( 0x3A00 | STYP_SEL0 | 0x90 )
#define SELE_GIDz ( 0x3B00 | STYP_SEL0 | 0x90 )
#define SELE_RNKs ( 0x3C00 | STYP_SEL1 | 0x80 )
#define SELE_PEPs ( 0x3D00 | STYP_SEL1 | 0x80 )
#define SELE_ACCz ( 0x3E00 | STYP_SEL0 | 0x90 )
#define SELE_DONz ( 0x3F00 | STYP_SEL0 | 0x90 )
#define SELE_LST1 ( 0x4000 | STYP_OPR1 | 0x30 )
#define SELE_NTO_ ( 0x4100 | STYP_OP22 | 0x30 )
#define SELE_CCLs ( 0x4200 | STYP_SEL1 | 0x80 )
#define SELE_RCLs ( 0x4300 | STYP_SEL1 | 0x80 )
#define SELE_PTDz ( 0x4400 | STYP_SEL0 | 0x90 )
#define SELE_MSKz ( 0x4500 | STYP_SEL0 | 0x90 )
#define SELE_IOR2 ( 0x4600 | STYP_OPR2 | 0x10 )
#define SELE_FXDz ( 0x4700 | STYP_SEL0 | 0x90 )
#define SELE_RSTz ( 0x4800 | STYP_SEL0 | 0x90 )
#define SELE_ANT2 ( 0x4900 | STYP_OPR2 | 0x60 )
#define SELE_BYX1 ( 0x4A00 | STYP_OPR1 | 0x20 )
#define SELE_STRO ( 0x4B00 | STYP_SEL1 | 0x80 )
#define SELE_METz ( 0x4C00 | STYP_SEL0 | 0x90 )
#define SELE_BB_z ( 0x4D00 | STYP_SEL0 | 0x90 )
#define SELE_SC_z ( 0x4E00 | STYP_SEL0 | 0x90 )
#define SELE_PROP ( 0x4F00 | STYP_SEL3 | 0x80 )
#define SELE_XVLx ( 0x5000 | STYP_SEL2 | 0x80 )
#define SELE_YVLx ( 0x5100 | STYP_SEL2 | 0x80 )
#define SELE_ZVLx ( 0x5200 | STYP_SEL2 | 0x80 )
#define SELE_CUST ( 0x5300 | STYP_SEL1 | 0x80 )
#define SELE_RING ( 0x5400 | STYP_OPR1 | 0x20 )
#define SELE_LABs ( 0x5500 | STYP_SEL1 | 0x80 )
#define SELE_PROz ( 0x5600 | STYP_SEL0 | 0x90 )
#define SELE_NUCz ( 0x5700 | STYP_SEL0 | 0x90 )

#define SEL_PREMAX 0x8

static WordKeyValue Keyword[] = {
  {"not", SELE_NOT1},
  {"!", SELE_NOT1},

  {"neighbor", SELE_NGH1},
  {"nbr;", SELE_NGH1},          /* deprecated */
  {"nbr.", SELE_NGH1},

  {"byfragment", SELE_BYF1},
  {"byfrag", SELE_BYF1},
  {"bf.", SELE_BYF1},

  {"byresidue", SELE_BYR1},
  {"byresi", SELE_BYR1},        /* unofficial */
  {"byres", SELE_BYR1},
  {"br;", SELE_BYR1},           /* deprecated */
  {"br.", SELE_BYR1},
  {"b;", SELE_BYR1},            /* deprecated */

  {"bychain", SELE_BYC1},
  {"bc.", SELE_BYC1},

  {"byobject", SELE_BYO1},
  {"byobj", SELE_BYO1},
  {"bo;", SELE_BYO1},           /* deprecated */
  {"bo.", SELE_BYO1},

  {"bound_to", SELE_BON1},
  {"bto.", SELE_BON1},

  {"bymolecule", SELE_BYM1},
  {"bymol", SELE_BYM1},
  {"bm.", SELE_BYM1},

  {"bysegment", SELE_BYS1},
  {"byseg", SELE_BYS1},
  {"bysegi", SELE_BYS1},        /* unofficial */
  {"bs.", SELE_BYS1},

  {"bycalpha", SELE_CAS1},
  {"bca.", SELE_CAS1},

  {"first", SELE_FST1},
  {"last", SELE_LST1},

  {"and", SELE_AND2},
  {"&", SELE_AND2},
  {"or", SELE_OR_2},
  {"+", SELE_OR_2},             /* added to mitigate damage caused by the obj1+obj2 parser bug */
  {"-", SELE_ANT2},             /* added to provide natural complement to the above: an AND NOT or SUBTRACT operation */
  {"|", SELE_OR_2},
  {"in", SELE_IN_2},

  {"like", SELE_LIK2},
  {"l;", SELE_LIK2},
  {"l.", SELE_LIK2},

  {cKeywordAll, SELE_ALLz},     /* 0 parameter */
  {"*", SELE_ALLz},             /* 0 parameter */

  {cKeywordNone, SELE_NONz},    /* 0 parameter */
  {"hetatm", SELE_HETz},        /* 0 parameter */
  {"het", SELE_HETz},           /* 0 parameter */

  {"hydrogens", SELE_HYDz},     /* 0 parameter */
  {"hydro", SELE_HYDz},         /* 0 parameter */
  {"h;", SELE_HYDz},            /* deprecated */
  {"h.", SELE_HYDz},            /* 0 parameter */

  {"hba.", SELE_HBAs},
  {"hbd.", SELE_HBDs},

  {"visible", SELE_VISz},       /* 0 parameter */
  {"v;", SELE_VISz},            /* 0 parameter */
  {"v.", SELE_VISz},            /* 0 parameter */

  {"around", SELE_ARD_},        /* 1 parameter */
  {"a;", SELE_ARD_},            /* deprecated */
  {"a.", SELE_ARD_},            /* 1 parameter */

  {"expand", SELE_EXP_},        /* 1 parameter */
  {"x;", SELE_EXP_},            /* 1 parameter */
  {"x.", SELE_EXP_},            /* 1 parameter */

  {"extend", SELE_EXT_},        /* 1 parameter */
  {"xt.", SELE_EXT_},           /* 1 parameter */

  {"name", SELE_NAMs},
  {"n;", SELE_NAMs},            /* deprecated */
  {"n.", SELE_NAMs},

  {"symbol", SELE_ELEs},
  {"element", SELE_ELEs},
  {"elem", SELE_ELEs},
  {"e;", SELE_ELEs},            /* deprecated */
  {"e.", SELE_ELEs},

  {"enabled", SELE_ENAz},

  {"residue", SELE_RSIs},
  {"resi", SELE_RSIs},
  {"resident", SELE_RSIs},
  {"resid", SELE_RSIs},
  {"i;", SELE_RSIs},            /* deprecated */
  {"i.", SELE_RSIs},

  {"rep", SELE_REPs},

  {"color", SELE_COLs},
  {"cartoon_color", SELE_CCLs},
  {"ribbon_color", SELE_RCLs},

  {"altloc", SELE_ALTs},
  {"alt", SELE_ALTs},

  {"flag", SELE_FLGs},
  {"f;", SELE_FLGs},            /* deprecated */
  {"f.", SELE_FLGs},

  {"gap", SELE_GAP_},

  {"partial_charge", SELE_PCHx},
  {"pc;", SELE_PCHx},           /* deprecated */
  {"pc.", SELE_PCHx},

  {"masked", SELE_MSKz},
  {"msk.", SELE_MSKz},

  {"protected", SELE_PTDz},

  {"formal_charge", SELE_FCHx},
  {"fc;", SELE_FCHx},           /* deprecated */
  {"fc.", SELE_FCHx},

  {"numeric_type", SELE_NTYs},
  {"nt;", SELE_NTYs},           /* deprecated */
  {"nt.", SELE_NTYs},

  {"text_type", SELE_TTYs},
  {"custom", SELE_CUST},
  {"tt;", SELE_TTYs},           /* deprecated */
  {"tt.", SELE_TTYs},

  {"chain", SELE_CHNs},
  {"c;", SELE_CHNs},            /* deprecated */
  {"c.", SELE_CHNs},

  {cKeywordCenter, SELE_CENz},
  {"bonded", SELE_BNDz},

  {"segment", SELE_SEGs},
  {"segid", SELE_SEGs},
  {"segi", SELE_SEGs},
  {"s;", SELE_SEGs},            /* deprecated */
  {"s.", SELE_SEGs},

  {"ss", SELE_SSTs},

  {"state", SELE_STAs},

  {"object", SELE_MODs},
  {"o.", SELE_MODs},

  {cKeywordOrigin, SELE_ORIz},

  {"model", SELE_MODs},
  {"m;", SELE_MODs},            /* deprecated */
  {"m.", SELE_MODs},

  {"index", SELE_IDXs},
  {"idx.", SELE_IDXs},

  {"id", SELE_ID_s},
  {"ID", SELE_ID_s},
  {"rank", SELE_RNKs},

  {"within", SELE_WIT_},
  {"w.", SELE_WIT_},

  {"near_to", SELE_NTO_},
  {"nto.", SELE_NTO_},

  {"beyond", SELE_BEY_},
  {"be.", SELE_BEY_},

  {"donors", SELE_DONz},
  {"don.", SELE_DONz},

  {"acceptors", SELE_ACCz},
  {"acc.", SELE_ACCz},

  {"pepseq", SELE_PEPs},
  {"ps.", SELE_PEPs},

  /*
     {  "nucseq",  SELE_NUCs },
     {  "ns.",      SELE_NUCs },
    */

  {"fixed", SELE_FXDz},
  {"fxd.", SELE_FXDz},

  {"restrained", SELE_RSTz},
  {"rst.", SELE_RSTz},

  {"polymer", SELE_POLz},
  {"pol.", SELE_POLz},

  {"polymer.protein", SELE_PROz},
  {"polymer.nucleic", SELE_NUCz},

#if 0
  // User survey winners. Not activated (yet) but ObjectMakeValidName
  // prints a deprecation warning if these names are used to name
  // objects or selections.
  {"protein", SELE_PROz},
  {"nucleic", SELE_NUCz},

  {"pro.", SELE_PROz},
  {"nuc.", SELE_NUCz},
#endif

  {"organic", SELE_ORGz},
  {"org.", SELE_ORGz},

  {"inorganic", SELE_INOz},
  {"ino.", SELE_INOz},

  {"solvent", SELE_SOLz},
  {"sol.", SELE_SOLz},

  {"guide", SELE_GIDz},

  {"present", SELE_PREz},
  {"pr.", SELE_PREz},

  {"resname", SELE_RSNs},
  {"resn", SELE_RSNs},
  {"r;", SELE_RSNs},            /* deprecated */
  {"r.", SELE_RSNs},

  {"%", SELE_SELs},
  {"b", SELE_BVLx},             /* 2 operand selection operator */
  {"q", SELE_QVLx},             /* 2 operand selection operator */

  {"stereo", SELE_STRO},

  {"bycell", SELE_BYX1},

  {"metals", SELE_METz},        /* 0 parameter */

  {"backbone", SELE_BB_z},
  {"bb.", SELE_BB_z},

  {"sidechain", SELE_SC_z},
  {"sc.", SELE_SC_z},

  {"x", SELE_XVLx},
  {"y", SELE_YVLx},
  {"z", SELE_ZVLx},

  {"byring", SELE_RING},
  {"label", SELE_LABs},

  {"", 0}
};

#define SCMP_GTHN 0x01
#define SCMP_LTHN 0x02
#define SCMP_RANG 0x03
#define SCMP_EQAL 0x04

static WordKeyValue AtOper[] = {
  {">", SCMP_GTHN},
  {"<", SCMP_LTHN},
  {"in", SCMP_RANG},
  {"=", SCMP_EQAL},
  {"", 0}
};

static short fcmp(float a, float b, int oper) {
  switch (oper) {
  case SCMP_GTHN:
    return (a > b);
  case SCMP_LTHN:
    return (a < b);
  case SCMP_EQAL:
    return fabs(a - b) < R_SMALL4;
  }
  printf("ERROR: invalid operator %d\n", oper);
  return false;
}

#define cINTER_ENTRIES 11

int SelectorRenameObjectAtoms(PyMOLGlobals * G, ObjectMolecule * obj, int sele, int force,
                              int update_table)
{
  int result = 0;
  int obj_nAtom = obj->NAtom;

  if(update_table) {
    SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);
  }
  if(obj_nAtom) {
    int *flag = Calloc(int, obj_nAtom);
    if(!flag) {
      result = -1;
    } else {
      AtomInfoType *ai = obj->AtomInfo;
      int a;
      for(a = 0; a < obj_nAtom; a++) {
        if(SelectorIsMember(G, ai->selEntry, sele)) {
          flag[a] = true;
          result = true;
        }
        ai++;
      }
      if (!result && !force) {
        // nothing selected, no need to continue
        return 0;
      }
      result = ObjectMoleculeRenameAtoms(obj, flag, force);
    }
    FreeP(flag);
  }
  return result;
}

int SelectorResidueVLAsTo3DMatchScores(PyMOLGlobals * G, CMatch * match,
                                       int *vla1, int n1, int state1,
                                       int *vla2, int n2, int state2,
                                       float seq_wt,
                                       float radius, float scale, float base,
                                       float coord_wt, float rms_exp)
{
  CSelector *I = G->Selector;
  int a, b, *vla;
  int n_max = (n1 > n2) ? n1 : n2;
  float *inter1 = Calloc(float, cINTER_ENTRIES * n1);
  float *inter2 = Calloc(float, cINTER_ENTRIES * n2);
  float *v_ca = Calloc(float, 3 * n_max);
  if(inter1 && inter2 && v_ca) {
    int pass;

    for(pass = 0; pass < 2; pass++) {
      ObjectMolecule *obj;
      CoordSet *cs;
      int *neighbor = NULL;
      AtomInfoType *atomInfo = NULL;
      ObjectMolecule *last_obj = NULL;
      float **dist_mat;
      float *inter;
      int state;
      int n;
      if(!pass) {
        vla = vla1;
        state = state1;
        inter = inter1;
        n = n1;
        dist_mat = match->da;
      } else {
        vla = vla2;
        state = state2;
        inter = inter2;
        n = n2;
        dist_mat = match->db;
      }

      if(state < 0)
        state = 0;
      for(a = 0; a < n; a++) {
        int at_ca1;
        float *vv_ca = v_ca + a * 3;

        obj = I->Obj[vla[0]];
        at_ca1 = vla[1];
        if(obj != last_obj) {
          ObjectMoleculeUpdateNeighbors(obj);
          last_obj = obj;
          neighbor = obj->Neighbor;
          atomInfo = obj->AtomInfo;
        }

        if(state < obj->NCSet)
          cs = obj->CSet[state];
        else
          cs = NULL;
        if(cs && neighbor && atomInfo) {
          int idx_ca1 = -1;
          if(obj->DiscreteFlag) {
            if(cs == obj->DiscreteCSet[at_ca1])
              idx_ca1 = obj->DiscreteAtmToIdx[at_ca1];
            else
              idx_ca1 = -1;
          } else
            idx_ca1 = cs->AtmToIdx[at_ca1];

          if(idx_ca1 >= 0) {
            int mem0, mem1, mem2, mem3, mem4;
            int nbr0, nbr1, nbr2, nbr3;
            float *v_ca1 = cs->Coord + 3 * idx_ca1;
            int idx_cb1 = -1;
            int cnt = 0;

            copy3f(v_ca1, vv_ca);
            copy3f(v_ca1, inter + 8);

            /* find attached CB */

            mem0 = at_ca1;
            nbr0 = neighbor[mem0] + 1;
            while((mem1 = neighbor[nbr0]) >= 0) {
              if((atomInfo[mem1].protons == cAN_C) &&
                 (atomInfo[mem1].name == G->lex_const.CB)) {
                idx_cb1 = cs->atmToIdx(mem1);
                break;
              }
              nbr0 += 2;
            }

            /* find remote CA, CB */

            if(idx_cb1 >= 0) {
              float *v_cb1 = cs->Coord + 3 * idx_cb1;

              mem0 = at_ca1;
              nbr0 = neighbor[mem0] + 1;
              while((mem1 = neighbor[nbr0]) >= 0) {

                nbr1 = neighbor[mem1] + 1;
                while((mem2 = neighbor[nbr1]) >= 0) {
                  if(mem2 != mem0) {
                    int idx_ca2 = -1;

                    nbr2 = neighbor[mem2] + 1;
                    while((mem3 = neighbor[nbr2]) >= 0) {
                      if((mem3 != mem1) && (mem3 != mem0)) {
                        if((atomInfo[mem3].protons == cAN_C) &&
                           (atomInfo[mem3].name == G->lex_const.CA)) {
                          idx_ca2 = cs->atmToIdx(mem3);
                          break;
                        }
                      }
                      nbr2 += 2;
                    }
                    if(idx_ca2 >= 0) {
                      float *v_ca2 = cs->Coord + 3 * idx_ca2;

                      nbr2 = neighbor[mem2] + 1;
                      while((mem3 = neighbor[nbr2]) >= 0) {
                        if((mem3 != mem1) && (mem3 != mem0)) {
                          int idx_cb2 = -1;
                          nbr3 = neighbor[mem3] + 1;
                          while((mem4 = neighbor[nbr3]) >= 0) {
                            if((mem4 != mem2) && (mem4 != mem1) && (mem4 != mem0)) {
                              if((atomInfo[mem4].protons == cAN_C) &&
                                 (atomInfo[mem4].name == G->lex_const.CB)) {
                                idx_cb2 = cs->atmToIdx(mem4);
                                break;
                              }
                            }
                            nbr3 += 2;
                          }

                          if(idx_cb2 >= 0) {
                            float *v_cb2 = NULL;
                            v_cb2 = cs->Coord + 3 * idx_cb2;
                            {
                              float angle = get_dihedral3f(v_cb1, v_ca1, v_ca2, v_cb2);
                              if(idx_cb1 < idx_cb2) {
                                inter[0] = (float) cos(angle);
                                inter[1] = (float) sin(angle);
                              } else {
                                inter[2] = (float) cos(angle);
                                inter[3] = (float) sin(angle);
                              }
                            }
                            cnt++;
                          }
                        }
                        nbr2 += 2;
                      }
                    }
                  }
                  nbr1 += 2;
                }
                nbr0 += 2;
              }
            }
          }
        }
        vla += 3;
        inter += cINTER_ENTRIES;
      }
      if(dist_mat) {
        for(a = 0; a < n; a++) {        /* optimize this later */
          float *vv_ca = v_ca + a * 3;
          for(b = 0; b < n; b++) {
            float *vv_cb = v_ca + b * 3;
            float diff = (float) diff3f(vv_ca, vv_cb);
            dist_mat[a][b] = diff;
            dist_mat[b][a] = diff;
          }
        }
      }
      {
        MapType *map = MapNew(G, radius, v_ca, n, NULL);
        if(!pass) {
          inter = inter1;
        } else {
          inter = inter2;
        }
        if(map) {
          int i, h, k, l;
          MapSetupExpress(map);

          for(a = 0; a < n; a++) {
            float *v_ca1 = v_ca + 3 * a;
            float *i_ca1 = inter + cINTER_ENTRIES * a;
            if(MapExclLocus(map, v_ca1, &h, &k, &l)) {
              i = *(MapEStart(map, h, k, l));
              if(i) {
                b = map->EList[i++];
                while(b >= 0) {
                  float *v_ca2 = v_ca + 3 * b;
                  if(a != b) {
                    if(within3f(v_ca1, v_ca2, radius)) {
                      float *i_ca2 = inter + cINTER_ENTRIES * b;
                      i_ca1[4] += i_ca2[0];     /* add dihedral vectors head-to-tail */
                      i_ca1[5] += i_ca2[1];
                      i_ca1[6] += i_ca2[2];
                      i_ca1[7] += i_ca2[3];
                    }
                  }
                  b = map->EList[i++];
                }
              }
            }
          }
          MapFree(map);
          for(a = 0; a < n; a++) {
            float nf = (float) sqrt(inter[4] * inter[4] + inter[5] * inter[5]);
            if(nf > 0.0001F) {
              inter[4] = inter[4] / nf;
              inter[5] = inter[5] / nf;
            }
            nf = (float) sqrt(inter[6] * inter[6] + inter[7] * inter[7]);
            if(nf > 0.0001F) {

              inter[6] = inter[6] / nf;
              inter[7] = inter[7] / nf;
            }
            inter += cINTER_ENTRIES;
          }
        }
      }
    }
    {
      const float _0F = 0.0F;

      if((scale != 0.0F) || (seq_wt != 0.0F)) {
        for(a = 0; a < n1; a++) {
          float *i1 = inter1 + cINTER_ENTRIES * a;
          for(b = 0; b < n2; b++) {
            float *i2 = inter2 + cINTER_ENTRIES * b;
            float sm[cINTER_ENTRIES], comp1, comp2, comp3 = 1.0F;
            float score;
            int c;
            for(c = 0; c < (cINTER_ENTRIES - 1); c += 2) {
              if(((i1[c] == _0F) && (i1[c + 1] == _0F))
                 || ((i2[c] == _0F) && (i2[c + 1] == _0F))) {
                /* handle glycine case */
                sm[c] = 1.0F;
                sm[c + 1] = 1.0F;
              } else {
                sm[c] = i1[c] + i2[c];
                sm[c + 1] = i1[c + 1] + i2[c + 1];
              }
            }
            comp1 = (float)
              ((sqrt(sm[0] * sm[0] + sm[1] * sm[1]) +
                sqrt(sm[2] * sm[2] + sm[3] * sm[3])) * 0.25);
            comp2 = (float)
              ((sqrt(sm[4] * sm[4] + sm[5] * sm[5]) +
                sqrt(sm[6] * sm[6] + sm[7] * sm[7])) * 0.25);
            score = scale * (comp1 * comp2 - base);
            if(coord_wt != 0.0) {
              float diff = (float) diff3f(i1 + 8, i2 + 8);
              comp3 = (float) -log(diff / rms_exp);
              score = (1 - coord_wt) * score + coord_wt * comp3 * scale;
            }
            match->mat[a][b] = seq_wt * match->mat[a][b] + score;
          }
        }
      }
    }
  }
  FreeP(inter1);
  FreeP(inter2);
  FreeP(v_ca);
  return 1;
}

int SelectorNameIsKeyword(PyMOLGlobals * G, const char *name)
{
  CSelector *I = G->Selector;
  WordType lower_name;
  OVreturn_word result;
  UtilNCopyToLower(lower_name, name, sizeof(WordType));
  if(OVreturn_IS_OK((result = OVLexicon_BorrowFromCString(I->Lex, lower_name)))) {
    if(OVreturn_IS_OK((result = OVOneToAny_GetKey(I->Key, result.word)))) {
      return 1;
    }
  }
  return 0;
}


/*========================================================================*/
static int SelectorIsSelectionDiscrete(PyMOLGlobals * G, int sele, int update_table)
{
  CSelector *I = G->Selector;
  ObjectMolecule **i_obj = I->Obj, *obj;
  AtomInfoType *ai;
  int result = false;
  TableRec *i_table = I->Table, *table_a;
  ov_size a;

  if(update_table) {
    SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);
  }

  table_a = i_table + cNDummyAtoms;
  for(a = cNDummyAtoms; a < I->NAtom; a++) {
    obj = i_obj[table_a->model];
    ai = obj->AtomInfo + table_a->atom;
    if(SelectorIsMember(G, ai->selEntry, sele)) {
      if(obj->DiscreteFlag) {
        result = true;
        break;
      }
    }
    table_a++;
  }
  return (result);
}

static int *SelectorUpdateTableMultiObjectIdxTag(PyMOLGlobals * G,
                                                 ObjectMolecule ** obj_list,
                                                 int no_dummies,
                                                 int **idx_list, int *n_idx_list,
                                                 int n_obj)
{
  int a = 0;
  int b;
  int c = 0;
  int modelCnt;
  int *result = NULL;
  CSelector *I = G->Selector;
  ObjectMolecule *obj = NULL;
  int *idx, n_idx;

  PRINTFD(G, FB_Selector)
    "SelectorUpdateTableMultiObject-Debug: entered ...\n" ENDFD;

  SelectorClean(G);

  I->SeleBaseOffsetsValid = true;       /* all states -> all atoms -> offsets valid */
  I->NCSet = 0;
  if(no_dummies) {
    modelCnt = 0;
    c = 0;
  } else {
    modelCnt = cNDummyModels;
    c = cNDummyAtoms;
  }
  for(b = 0; b < n_obj; b++) {
    obj = obj_list[b];
    c += obj->NAtom;
    if(I->NCSet < obj->NCSet)
      I->NCSet = obj->NCSet;
    modelCnt++;
  }
  result = Calloc(int, c);
  I->Table = Calloc(TableRec, c);
  ErrChkPtr(G, I->Table);
  I->Obj = Calloc(ObjectMolecule *, modelCnt);
  ErrChkPtr(G, I->Obj);
  if(no_dummies) {
    modelCnt = 0;
    c = 0;
  } else {
    c = cNDummyAtoms;
    modelCnt = cNDummyModels;
  }
  for(b = 0; b < n_obj; b++) {
    obj = obj_list[b];
    idx = idx_list[b];
    n_idx = n_idx_list[b];

    I->Obj[modelCnt] = obj;
    obj->SeleBase = c;
    for(a = 0; a < obj->NAtom; a++) {
      I->Table[c].model = modelCnt;
      I->Table[c].atom = a;
      c++;
    }
    if(idx && n_idx) {
      if(n_idx > 0) {
        for(a = 0; a < n_idx; a++) {
          int at = idx[2 * a];  /* index first */
          int pri = idx[2 * a + 1];     /* then priority */
          if((at >= 0) && (at < obj->NAtom)) {
            result[obj->SeleBase + at] = pri;
          }
        }
      }
    }
    modelCnt++;
    I->NModel = modelCnt;
  }
  I->NAtom = c;
  I->Flag1 = Alloc(int, c);
  ErrChkPtr(G, I->Flag1);
  I->Flag2 = Alloc(int, c);
  ErrChkPtr(G, I->Flag2);
  I->Vertex = Alloc(float, c * 3);
  ErrChkPtr(G, I->Vertex);

  PRINTFD(G, FB_Selector)
    "SelectorUpdateTableMultiObject-Debug: leaving...\n" ENDFD;

  return (result);
}

static int IntInOrder(int *list, int a, int b)
{
  return (list[a] <= list[b]);
}

static int SelectorAddName(PyMOLGlobals * G, int index)
{
  CSelector *I = G->Selector;
  int ok = false;
  OVreturn_word result;
  OVstatus status;
  if(OVreturn_IS_OK((result = OVLexicon_GetFromCString(I->Lex, I->Name[index])))) {
    if(OVreturn_IS_OK((status = OVOneToOne_Set(I->NameOffset, result.word, index)))) {
      ok = true;
    }
  }
  return ok;
}

static int SelectorDelName(PyMOLGlobals * G, int index)
{
  CSelector *I = G->Selector;
  int ok = false;
  OVreturn_word result;
  if(OVreturn_IS_OK((result = OVLexicon_BorrowFromCString(I->Lex, I->Name[index])))) {
    if(OVreturn_IS_OK(OVLexicon_DecRef(I->Lex, result.word)) &&
       OVreturn_IS_OK(OVOneToOne_DelForward(I->NameOffset, result.word))) {
      ok = true;
    }
  }
  return ok;
}

int SelectorClassifyAtoms(PyMOLGlobals * G, int sele, int preserve,
                          ObjectMolecule * only_object)
{
  CSelector *I = G->Selector;
  ObjectMolecule *obj, *last_obj = NULL, *obj0, *obj1 = NULL;
  int a, aa, at, a0, a1;
  AtomInfoType *ai, *last_ai = NULL, *ai0, *ai1;
  unsigned int mask;
  int n_dummies = 0;

  int auto_show_classified = SettingGetGlobal_i(G, cSetting_auto_show_classified);
  int auto_show_mask = (auto_show_classified != 2) ? 0 : cRepBitmask;

  int visRep_organic = cRepCylBit | cRepNonbondedSphereBit;
  int visRep_inorganic = cRepSphereBit;
  int visRep_polymer = cRepCartoonBit;

  // detect large systems
  if (auto_show_classified == -1 &&
      only_object &&
      only_object->NAtom * (only_object->DiscreteFlag ?
        1 : only_object->NCSet) > 5e5) {
    auto_show_classified = 3;
  }

  if (auto_show_classified == 3) {
    visRep_organic = visRep_inorganic = cRepLineBit | cRepNonbondedBit;
    visRep_polymer = cRepRibbonBit;
  }

  if(only_object) {
    SelectorUpdateTableSingleObject(G, only_object, cSelectorUpdateTableAllStates,
                                    true, NULL, 0, false);
    n_dummies = 0;
  } else {
    SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);
    n_dummies = cNDummyAtoms;
  }
  a = 0;
  while(a < I->NAtom) {
    obj = I->Obj[I->Table[a].model];
    at = I->Table[a].atom;
    ai = obj->AtomInfo + at;

    if(SelectorIsMember(G, ai->selEntry, sele) &&
       ((!AtomInfoSameResidueP(G, ai, last_ai)))) {

      AtomInfoType *guide_atom = NULL;

      /* delimit residue */

      a0 = a - 1;
      while(a0 >= n_dummies) {
        obj0 = I->Obj[I->Table[a0].model];
        if(obj0 != obj)
          break;
        ai0 = obj0->AtomInfo + I->Table[a0].atom;
        if(!AtomInfoSameResidue(G, ai0, ai))
          break;
        a0--;
      }

      a1 = a + 1;
      while(a1 < I->NAtom) {
        obj1 = I->Obj[I->Table[a1].model];
        if(obj1 != obj)
          break;
        ai1 = obj1->AtomInfo + I->Table[a1].atom;
        if(!AtomInfoSameResidue(G, ai1, ai))
          break;
        a1++;
      }

      a0++;
      a1--;

      mask = 0;
      if(!ai->hetatm && AtomInfoKnownProteinResName(LexStr(G, ai->resn)))
        mask = cAtomFlag_polymer | cAtomFlag_protein;
      else if(!ai->hetatm && AtomInfoKnownNucleicResName(LexStr(G, ai->resn)))
        mask = cAtomFlag_polymer | cAtomFlag_nucleic;
      else if(AtomInfoKnownWaterResName(G, LexStr(G, ai->resn)))
        mask = cAtomFlag_solvent;
      else {

        /* does this residue have a canonical atoms? */

        int found_ca = false;
        int found_n = false;
        int found_c = false;
        int found_o = false;
        int found_oh2 = false;
        int found_carbon = false;
        int found_cn_bond = false;
        int found_nc_bond = false;
        int found_o3_bond = false;
        int found_o3star = false;
        int found_c3star = false;
        int found_c4star = false;
        int found_c5star = false;
        int found_o5star = false;
        int found_p_bond = false;
        if(obj != last_obj) {
          ObjectMoleculeUpdateNeighbors(obj);
          last_obj = obj;
        }

        ai0 = obj->AtomInfo + I->Table[a0].atom;
        for(aa = a0; aa <= a1; aa++) {
          if(ai0->protons == cAN_C) {
            const char *name = LexStr(G, ai0->name);
            found_carbon = true;
            switch (name[0]) {
            case 'C':
              switch (name[1]) {
              case 0:
                found_c = true;
                found_cn_bond =
                  ObjectMoleculeIsAtomBondedToName(obj, I->Table[aa].atom, "N", 0);
                break;
              case 'A':
                switch (name[2]) {
                case 0:
                  found_ca = true;
                  guide_atom = ai0;
                  break;
                }
              case '3':
                switch (name[2]) {
                case '*':
                case '\'':
                  guide_atom = ai0;
                  found_c3star = true;
                  break;
                }
                break;
              case '4':
                switch (name[2]) {
                case '*':
                case '\'':
                  found_c4star = true;
                  break;
                }
                break;
              case '5':
                switch (name[2]) {
                case '*':
                case '\'':
                  found_c5star = true;
                  break;
                }
                break;
              }
            }
          } else if(ai0->protons == cAN_N) {
            const char *name = LexStr(G, ai0->name);
            switch (name[0]) {
            case 'N':
              switch (name[1]) {
              case 0:
                found_n = true;
                found_nc_bond =
                  ObjectMoleculeIsAtomBondedToName(obj, I->Table[aa].atom, "C", 0);
                break;
              }
            }
          } else if(ai0->protons == cAN_O) {
            const char *name = LexStr(G, ai0->name);
            switch (name[0]) {
            case 'O':
              switch (name[1]) {
              case 0:
                found_o = true;
                break;
              case 'H':
                switch (name[2]) {
                case '2':
                  found_oh2 = true;
                  break;
                }
              case '3':
                switch (name[2]) {
                case '*':
                case '\'':
                  found_o3star = true;
                  found_o3_bond =
                    ObjectMoleculeIsAtomBondedToName(obj, I->Table[aa].atom, "P", 0);
                  break;
                }
                break;
              case '5':
                switch (name[2]) {
                case '*':
                case '\'':
                  found_o5star = true;
                  break;
                }
                break;
              }
            }
          } else if(ai0->protons == cAN_P) {

            const char *name = LexStr(G, ai0->name);
            switch (name[0]) {
            case 'P':
              switch (name[1]) {
              case 0:
                found_p_bond =
                  (ObjectMoleculeIsAtomBondedToName(obj, I->Table[aa].atom, "O3*", 0)
                   || ObjectMoleculeIsAtomBondedToName(obj, I->Table[aa].atom, "O3'", 0));
                break;
              }
            }
          }
          ai0++;
        }

        if(found_ca && found_n && found_c && found_o && (found_cn_bond || found_nc_bond)) {
          mask = cAtomFlag_polymer | cAtomFlag_protein;
        } else if (found_o3star && found_c3star && found_c4star && found_c5star
               && found_o5star && (found_o3_bond || found_p_bond)) {
          mask = cAtomFlag_polymer | cAtomFlag_nucleic;
        } else if(found_carbon)
          mask = cAtomFlag_organic;
        else if((found_o || found_oh2) && (a1 == a0))
          mask = cAtomFlag_solvent;
        else
          mask = cAtomFlag_inorganic;
      }

      /* mark which atoms we can write to */

      ai0 = obj->AtomInfo + I->Table[a0].atom;
      if(preserve) {
        printf("NOT IMPLEMENTED\n");
      } else {
        auto visRep_polymer_obj = visRep_polymer;
        if (obj->NAtom < 50) {
          // prevent single residue objects from disappearing
          visRep_polymer_obj |= visRep_organic;
        }

        for(aa = a0; aa <= a1; aa++) {
          if(SelectorIsMember(G, ai0->selEntry, sele))
          {
            // apply styles if atom was unclassified
            if (auto_show_classified && !(ai0->flags & cAtomFlag_class)) {
              if (mask & cAtomFlag_organic) {
                ai0->visRep = (ai0->visRep & auto_show_mask) | visRep_organic;
              } else if (mask & cAtomFlag_inorganic) {
                ai0->visRep = (ai0->visRep & auto_show_mask) | visRep_inorganic;
              } else if (mask & cAtomFlag_polymer) {
                ai0->visRep = (ai0->visRep & auto_show_mask) | visRep_polymer_obj;
              }
            }
            ai0->flags = (ai0->flags & cAtomFlag_class_mask) | mask;
          }
          ai0++;
        }
      }

      if((mask & cAtomFlag_polymer)) {
        ai0 = obj->AtomInfo + I->Table[a0].atom;
        for(aa = a0; !guide_atom && aa <= a1; aa++) {
          if(ai0->protons == cAN_C) {
            const char *name = LexStr(G, ai0->name);
            switch (name[0]) {
            case 'C':
              switch (name[1]) {
              case 'A':
                switch (name[2]) {
                case 0:
                  guide_atom = ai0;
                  break;
                }
                break;
              case '4':
                switch (name[2]) {      /* use C4* as guide atom for nucleic acids */
                case '*':
                case '\'':
                  guide_atom = ai0;
                  break;
                }
                break;
              }
            }
          }
          ai0++;
        }
      }

      if(guide_atom)
        guide_atom->flags |= cAtomFlag_guide;

      if(a1 > (a + 1))
        a = a1;
    }
    a++;
  }
  return true;
}

static void SelectionInfoInit(SelectionInfoRec * rec)
{
  rec->justOneObjectFlag = false;
  rec->justOneAtomFlag = false;
}

void SelectorComputeFragPos(PyMOLGlobals * G, ObjectMolecule * obj, int state, int n_frag,
                            char *prefix, float **vla)
{
  CSelector *I = G->Selector;
  WordType name;
  int *sele;
  int *cnt;
  SelectorUpdateTableSingleObject(G, obj, cSelectorUpdateTableAllStates, true, NULL, 0,
                                  false);
  sele = Alloc(int, n_frag);
  cnt = Calloc(int, n_frag);
  VLACheck(*vla, float, n_frag * 3 + 2);
  {
    int a;
    for(a = 0; a < n_frag; a++) {
      sprintf(name, "%s%d", prefix, a + 1);
      sele[a] = SelectorIndexByName(G, name);
      zero3f((*vla) + 3 * a);
    }
  }
  {
    int at, a, ati;
    AtomInfoType *ai;
    float v[3], *vp;
    int vert_flag;
    for(at = 0; at < I->NAtom; at++) {

      ati = I->Table[at].atom;
      ai = obj->AtomInfo + ati;

      vert_flag = false;
      for(a = 0; a < n_frag; a++) {
        if(SelectorIsMember(G, ai->selEntry, sele[a])) {
          if(!vert_flag) {
            vert_flag = ObjectMoleculeGetAtomVertex(obj, state, ati, v);
          }
          if(vert_flag) {
            vp = (*vla) + 3 * a;
            add3f(v, vp, vp);
            cnt[a]++;
          }
        }
      }
    }
  }

  {
    int a;
    float div, *vp;
    for(a = 0; a < n_frag; a++) {
      if(cnt[a]) {
        vp = (*vla) + 3 * a;
        div = 1.0F / cnt[a];
        scale3f(vp, div, vp);
      }
    }
  }

  FreeP(sele);
  FreeP(cnt);
}

MapType *SelectorGetSpacialMapFromSeleCoord(PyMOLGlobals * G, int sele, int state,
                                            float cutoff, float **coord_vla)
{
  //  register CSelector *I = G->Selector;

  int *index_vla = NULL;
  float *coord = NULL;
  int n, nc = 0;
  MapType *result = NULL;
  if(sele < 0)
    return NULL;
  else {
    CSelector *I = NULL;
    SelectorInitImpl(G, &I, 0);
    SelectorUpdateTableImpl(G, I, state, -1);
    index_vla = SelectorGetIndexVLAImpl(G, I, sele);

    if(index_vla) {
      n = VLAGetSize(index_vla);
      if(n)
        coord = VLAlloc(float, n * 3);
      if(coord) {
        int i, a;
        int st, sta;
        ObjectMolecule *obj;
        CoordSet *cs;
        int at;
        int idx;
        float *src, *dst;
        for(i = 0; i < n; i++) {
          a = index_vla[i];

          obj = I->Obj[I->Table[a].model];
          at = +I->Table[a].atom;
          for(st = 0; st < I->NCSet; st++) {

            if((state < 0) || (st == state)) {

              sta = st;
              if(sta < obj->NCSet)
                cs = obj->CSet[sta];
              else
                cs = NULL;
              if(cs) {
                idx = cs->atmToIdx(at);
              } else {
                idx = -1;
              }
              if(idx >= 0) {
                VLACheck(coord, float, nc * 3 + 2);
                src = cs->Coord + 3 * idx;
                dst = coord + 3 * nc;
                *(dst++) = *(src++);
                *(dst++) = *(src++);
                *(dst++) = *(src++);
                nc++;
              }
            }
          }
        }
        if(nc) {
          result = MapNew(G, cutoff, coord, nc, NULL);
        }
      }
    }
    SelectorFreeImpl(G, I, 0);
  }
  VLAFreeP(index_vla);
  if(coord)
    VLASize(coord, float, nc * 3);
  *(coord_vla) = coord;
  return (result);
}

static ov_diff SelectGetNameOffset(PyMOLGlobals * G, const char *name, ov_size minMatch,
                                   int ignCase)
{

  CSelector *I = G->Selector;
  int result = -1;
  while(name[0] == '?')
    name++;
  {                             /* first try for perfect match using the dictionary */
    OVreturn_word res;
    if(OVreturn_IS_OK((res = OVLexicon_BorrowFromCString(I->Lex, name)))) {
      if(OVreturn_IS_OK((res = OVOneToOne_GetForward(I->NameOffset, res.word)))) {
        result = res.word;
      }
    }
  }
  if(result < 0) {              /* not found, so try partial/ignored-case match */

    int offset, wm, best_match, best_offset;
    SelectorWordType *I_Name = I->Name;
    offset = 0;
    best_offset = -1;
    best_match = -1;

    while(I_Name[offset][0]) {
      wm = WordMatch(G, name, I_Name[offset], ignCase);
      if(wm < 0) {              /* exact match is always good */
        best_offset = offset;
        best_match = wm;
        break;
      }
      if(wm > 0) {
        if(best_match < wm) {
          best_match = wm;
          best_offset = offset;
        } else if(best_match == wm) {   /* uh oh -- ambiguous match */
          best_offset = -1;
        }
      }
      offset++;
    }
    if((best_match < 0) || (best_match > (ov_diff) minMatch))
      result = best_offset;
  }
  return (result);
}

void SelectorSelectByID(PyMOLGlobals * G, const char *name, ObjectMolecule * obj, int *id,
                        int n_id)
{
  CSelector *I = G->Selector;
  int min_id, max_id, range, *lookup = NULL;
  int *atom = NULL;
  /* this routine only works if IDs cover a reasonable range --
     should rewrite using a hash table */

  SelectorUpdateTableSingleObject(G, obj, cSelectorUpdateTableAllStates, true, NULL, 0,
                                  false);
  atom = Calloc(int, I->NAtom);
  if(I->NAtom) {

    /* determine range */

    {
      int a, cur_id;
      cur_id = obj->AtomInfo[0].id;
      min_id = cur_id;
      max_id = cur_id;
      for(a = 1; a < obj->NAtom; a++) {
        cur_id = obj->AtomInfo[a].id;
        if(min_id > cur_id)
          min_id = cur_id;
        if(max_id < cur_id)
          max_id = cur_id;
      }
    }

    /* create cross-reference table */

    {
      int a, offset;

      range = max_id - min_id + 1;
      lookup = Calloc(int, range);
      for(a = 0; a < obj->NAtom; a++) {
        offset = obj->AtomInfo[a].id - min_id;
        if(lookup[offset])
          lookup[offset] = -1;
        else {
          lookup[offset] = a + 1;
        }
      }
    }

    /* iterate through IDs and mark */

    {
      int i, a, offset, lkup;

      for(i = 0; i < n_id; i++) {
        offset = id[i] - min_id;
        if((offset >= 0) && (offset < range)) {
          lkup = lookup[offset];
          if(lkup > 0) {
            atom[lkup - 1] = true;
          } else if(lkup < 0) {
            for(a = 0; a < obj->NAtom; a++) {
              if(obj->AtomInfo[a].id == id[i])
                atom[a] = true;
            }
          }
        }
      }
    }
  }

  SelectorEmbedSelection(G, atom, name, NULL, true, -1);
  FreeP(atom);
  FreeP(lookup);
  SelectorClean(G);
}

void SelectorDefragment(PyMOLGlobals * G)
{
  CSelector *I = G->Selector;
  /* restore new member ordering so that CPU can continue to get good cache hit */

  int n_free = 0;
  int m;
  int *list, *l;
  int a;
  m = I->FreeMember;
  while(m) {
    n_free++;
    m = I->Member[m].next;
  }
  if(n_free) {
    list = Alloc(int, n_free);
    l = list;
    m = I->FreeMember;
    while(m) {
      *(l++) = m;
      m = I->Member[m].next;
    }
    UtilSortInPlace(G, list, n_free, sizeof(int), (UtilOrderFn *) IntInOrder);
    while(n_free > 5000) {      /* compact inactive members when possible */
      if(list[n_free - 1] == I->NMember) {
        I->NMember--;
        n_free--;
      } else
        break;
    }
    for(a = 0; a < (n_free - 1); a++) {
      I->Member[list[a]].next = list[a + 1];
    }
    I->Member[list[n_free - 1]].next = 0;
    I->FreeMember = list[0];
    FreeP(list);
  }
}

int SelectorWalkTree(PyMOLGlobals * G, int *atom, int *comp, int *toDo, int **stk,
                     int stkDepth, ObjectMolecule * obj,
                     int sele1, int sele2, int sele3, int sele4);

typedef struct {
  int color;
  int sele;
} ColorectionRec;

static void SelectorDeleteSeleAtOffset(PyMOLGlobals * G, int n)
{
  CSelector *I = G->Selector;
  int id;
  id = I->Info[n].ID;
  SelectorDelName(G, n);
  SelectorPurgeMembers(G, id);

  I->NActive--;
  {
    OVreturn_word result;
    /* repoint the name index at the relocated entry */
    if(OVreturn_IS_OK(result = OVOneToOne_GetReverse(I->NameOffset, I->NActive))) {
      OVOneToOne_DelForward(I->NameOffset, result.word);
      OVOneToOne_Set(I->NameOffset, result.word, n);
    }
    if (I->Name[n]!=I->Name[I->NActive])
      strcpy(I->Name[n], I->Name[I->NActive]);
    I->Info[n] = I->Info[I->NActive];
    I->Name[I->NActive][0] = 0;
  }
}

char *SelectorGetNameFromIndex(PyMOLGlobals * G, int index)
{
  CSelector *I = G->Selector;
  int a;
  for(a = 1; a < I->NActive; a++) {
    if(I->Info[a].ID == index) {
      return I->Name[a];
    }
  }
  return NULL;
}

#ifndef _PYMOL_NOPY
static void SelectorDeleteIndex(PyMOLGlobals * G, int index)
{
  CSelector *I = G->Selector;
  int n = 0;
  int a;
  for(a = 1; a < I->NActive; a++) {
    if(I->Info[a].ID == index) {
      n = a;
      break;
    }
  }
  if(n)
    SelectorDeleteSeleAtOffset(G, n);
}
#endif

#define cSSMaxHBond 6

#define cSSHelix3HBond          0x0001
#define cSSHelix4HBond          0x0002
#define cSSHelix5HBond          0x0004
#define cSSGotPhiPsi            0x0008
#define cSSPhiPsiHelix          0x0010
#define cSSPhiPsiNotHelix       0x0020
#define cSSPhiPsiStrand         0x0040
#define cSSPhiPsiNotStrand      0x0080
#define cSSAntiStrandSingleHB   0x0100
#define cSSAntiStrandDoubleHB   0x0200
#define cSSAntiStrandBuldgeHB   0x0400
#define cSSAntiStrandSkip       0x0800
#define cSSParaStrandSingleHB   0x1000
#define cSSParaStrandDoubleHB   0x2000
#define cSSParaStrandSkip       0x4000

#define cSSBreakSize 5

typedef struct {
  int real;
  int ca, n, c, o;              /* indices in selection-table space */
  float phi, psi;
  char ss, ss_save;
  int flags;
  int n_acc, n_don;
  int acc[cSSMaxHBond];         /* interactions where this residue is an acceptor */
  int don[cSSMaxHBond];         /* interactions where this residue is a donor */
  ObjectMolecule *obj;
  int preserve;
  int present;
} SSResi;

int SelectorAssignSS(PyMOLGlobals * G, int target, int present,
                     int state_value, int preserve, ObjectMolecule * single_object,
                     int quiet)
{

  /* PyMOL's secondary structure assignment algorithm: 

     General principal -- if it looks like a duck, then it's a duck:

     I. Helices
     - must have reasonably helical geometry within the helical span
     - near-ideal geometry guarantees helix assignment
     - a continuous ladder stre i+3, i+4, or i+5 hydrogen bonding
     with permissible geometry can reinforce marginal cases
     - a minimum helix is three residues with i+3 H-bond

     II. Sheets
     - Hydrogen bonding ladders are the primary guide
     - Out-of-the envelope 
     - 1-residue gaps in sheets are filled unless there
     is a turn.
   */

  CSelector *I = G->Selector;
  SSResi *res;
  int n_res = 0;
  int state_start, state_stop, state;
  int consensus = true;
  int first_last_only = false;
  int first_pass = true;

  if(!single_object) {
    if(state_value < 0) {
      switch (state_value) {
      case -2:                 /* api: state=-1: current global state */
      case -3:                 /* api: state=-2: effective object states TO DO! */
        SelectorUpdateTable(G, state_value, -1);
        break;
      default:
        SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);
        break;
      }
    } else {
      SelectorUpdateTable(G, state_value, -1);
    }
  } else {
    SelectorUpdateTableSingleObject(G, single_object, state_value, false, NULL, 0, 0);
  }

  res = VLACalloc(SSResi, 1000);

  if(state_value < 0) {
    if(state_value == -4)
      consensus = false;
    if(state_value == -5)
      first_last_only = true;
    state_start = 0;
    state_stop = SelectorGetSeleNCSet(G, target);

    if (state_value == -2) {
      /* api: state=-1: current global state */
      StateIterator iter(G, NULL, state_value, state_stop);
      if (iter.next()) {
        state_start = iter.state;
        state_stop = iter.state + 1;
      }
    }
  } else {
    state_start = state_value;
    state_stop = state_value + 1;
  }
  for(state = state_start; state < state_stop; state++) {
    int a;
    ObjectMolecule *obj;
    int aa, a0, a1, at, idx;
    AtomInfoType *ai, *ai0, *ai1;
    CoordSet *cs;
    ObjectMolecule *last_obj = NULL;
    /* first, we need to count the number of residues under consideration */

    if(first_pass) {
      for(a = cNDummyAtoms; a < I->NAtom; a++) {

        obj = I->Obj[I->Table[a].model];
        at = +I->Table[a].atom;
        ai = obj->AtomInfo + at;

        /* see if CA coordinates exist... */

        if(SelectorIsMember(G, ai->selEntry, present)) {

          if((ai->protons == cAN_C) && (WordMatchExact(G, G->lex_const.CA, ai->name, true))) {

            if(last_obj != obj) {
              ObjectMoleculeUpdateNeighbors(obj);
              ObjectMoleculeVerifyChemistry(obj, state_value);
              last_obj = obj;
            }
            /* delimit residue */

            a0 = a - 1;
            while(a0 >= cNDummyAtoms) {
              ai0 = I->Obj[I->Table[a0].model]->AtomInfo + I->Table[a0].atom;
              if(!AtomInfoSameResidue(G, ai0, ai))
                break;
              a0--;
            }

            a1 = a + 1;
            while(a1 < I->NAtom) {
              ai1 = I->Obj[I->Table[a1].model]->AtomInfo + I->Table[a1].atom;
              if(!AtomInfoSameResidue(G, ai1, ai))
                break;
              a1++;
            }

            {
              int found_N = 0;
              int found_O = 0;
              int found_C = 0;

              /* locate key atoms */

              for(aa = a0 + 1; aa < a1; aa++) {
                ai = I->Obj[I->Table[aa].model]->AtomInfo + I->Table[aa].atom;
                if((ai->protons == cAN_C) && (WordMatchExact(G, G->lex_const.C, ai->name, true))) {
                  found_C = aa;
                }
                if((ai->protons == cAN_N) && (WordMatchExact(G, G->lex_const.N, ai->name, true))) {
                  found_N = aa;
                }
                if((ai->protons == cAN_O) && (WordMatchExact(G, G->lex_const.O, ai->name, true))) {
                  found_O = aa;
                }
              }

              if((found_C) && (found_N) && (found_O)) {

                VLACheck(res, SSResi, n_res);
                res[n_res].n = found_N;
                res[n_res].o = found_O;
                res[n_res].c = found_C;
                res[n_res].ca = a;
                res[n_res].obj = I->Obj[I->Table[a].model];
                res[n_res].real = true;

                n_res++;

              } else {
                if(!quiet) {
                  PRINTFB(G, FB_Selector, FB_Warnings)
                    " AssignSS-Warning: Ignoring incomplete residue /%s/%s/%s/%d%c ...\n",
                    obj->Obj.Name, LexStr(G, ai->segi), LexStr(G, ai->chain), ai->resv, ai->getInscode(true) ENDFB(G);
                }
              }
            }
          }
        }
      }                         /* count pass */

      if(preserve) {            /* if we're in preserve mode, then mark which objects don't get changed */
        int a, b;
        char ss;
        ObjectMolecule *p_obj = NULL;
        SSResi *r, *r2;
        for(a = 0; a < n_res; a++) {
          r = res + a;
          if(r->real) {
            if(p_obj != r->obj) {
              ss = r->obj->AtomInfo[I->Table[r->ca].atom].ssType[0];
              if((ss == 'S') || (ss == 'H') || (ss == 's') || (ss == 'h')) {
                p_obj = r->obj;

                b = a;
                while(b >= 0) {
                  r2 = res + b;
                  if(p_obj == r2->obj)
                    r2->preserve = true;
                  b--;
                }
                b = a + 1;
                while(b < n_res) {
                  r2 = res + b;
                  if(p_obj == r2->obj)
                    r2->preserve = true;
                  b++;
                }
              }
            }
          }
        }
      }
      /*  printf("n_res %d\n",n_res); */

      /* now, let's repack res. into discrete chunks so that we can do easy gap & ladder analysis */

      {
        SSResi *res2;
        int a;
        int n_res2 = 0;
        int add_break;
        int at_ca0, at_ca1;

        res2 = VLACalloc(SSResi, n_res * 2);

        for(a = 0; a < n_res; a++) {
          add_break = false;

          if(!a) {
            add_break = true;
          } else if(res[a].obj != res[a - 1].obj) {
            add_break = true;
          } else if(res[a].obj) {
            at_ca0 = I->Table[res[a].ca].atom;
            at_ca1 = I->Table[res[a - 1].ca].atom;
            if(!ObjectMoleculeCheckBondSep(res[a].obj, at_ca0, at_ca1, 3)) {    /* CA->N->C->CA = 3 bonds */
              add_break = true;
            }
          }

          if(add_break) {
            n_res2 += cSSBreakSize;
          }

          VLACheck(res2, SSResi, n_res2);
          res2[n_res2] = res[a];
          n_res2++;
        }

        n_res2 += cSSBreakSize;
        VLACheck(res2, SSResi, n_res2);

        VLAFreeP(res);
        res = res2;
        n_res = n_res2;
      }
      first_pass = false;
    }

    /* okay, the rest of this loop runs for each coordinate set */

    {
      int b;
      for(a = 0; a < n_res; a++) {
        res[a].present = res[a].real;

        if(res[a].present) {
          obj = res[a].obj;
          if(state < obj->NCSet)
            cs = obj->CSet[state];
          else
            cs = NULL;
          for(b = 0; b < 4; b++) {
            if(cs) {
              switch (b) {
              case 0:
                at = I->Table[res[a].n].atom;
                break;
              case 1:
                at = I->Table[res[a].o].atom;
                break;
              case 2:
                at = I->Table[res[a].c].atom;
                break;
              default:
              case 3:
                at = I->Table[res[a].ca].atom;
                break;
              }
              idx = cs->atmToIdx(at);
            } else
              idx = -1;
            if(idx < 0) {
              res[a].present = false;
            }
          }
        }
      }
    }

    /* next, we need to record hydrogen bonding relationships */

    {

      MapType *map;
      float *v0, *v1;
      int n1;
      int i, h, k, l;
      int at;

      int a, aa;
      int a0, a1;               /* SS res space */
      int as0, as1;             /* selection space */
      int at0, at1;             /* object-atom space */
      int exclude;

      ObjectMolecule *obj0, *obj1;

      CoordSet *cs;
      float cutoff;
      HBondCriteria hbcRec, *hbc;
      int *zero = NULL, *scratch = NULL;

      {
        int max_n_atom = I->NAtom;
        ObjectMolecule *lastObj = NULL;
        for(a = cNDummyAtoms; a < I->NAtom; a++) {
          ObjectMolecule *obj = I->Obj[I->Table[a].model];
          if(obj != lastObj) {
            if(max_n_atom < obj->NAtom)
              max_n_atom = obj->NAtom;
            lastObj = obj;
          }
        }
        zero = Calloc(int, max_n_atom);
        scratch = Alloc(int, max_n_atom);
      }

      for(a = 0; a < n_res; a++) {
        res[a].n_acc = 0;
        res[a].n_don = 0;
      }
      hbc = &hbcRec;
      ObjectMoleculeInitHBondCriteria(G, hbc);

      /* use parameters which reflect the spirit of Kabsch and Sander
         ( i.e. long hydrogen-bonds/polar electrostatic interactions ) */

      hbc->maxAngle = 63.0F;
      hbc->maxDistAtMaxAngle = 3.2F;
      hbc->maxDistAtZero = 4.0F;
      hbc->power_a = 1.6F;
      hbc->power_b = 5.0F;
      hbc->cone_dangle = 0.0F;  /* 180 deg. */
      if(hbc->maxDistAtMaxAngle != 0.0F) {
        hbc->factor_a = 0.5F / (float) pow(hbc->maxAngle, hbc->power_a);
        hbc->factor_b = 0.5F / (float) pow(hbc->maxAngle, hbc->power_b);
      }

      cutoff = hbc->maxDistAtMaxAngle;
      if(cutoff < hbc->maxDistAtZero) {
        cutoff = hbc->maxDistAtZero;
      }

      n1 = 0;

      for(aa = 0; aa < I->NAtom; aa++) {        /* first, clear flags */
        I->Flag1[aa] = false;
        I->Flag2[aa] = false;
      }

      for(a = 0; a < n_res; a++) {
        if(res[a].present) {
          obj0 = res[a].obj;

          if(obj0) {
            /* map will contain the h-bond backbone nitrogens */

            aa = res[a].n;
            at = I->Table[aa].atom;
            I->Flag2[aa] = a;   /* so we can find the atom again... */

            if(state < obj0->NCSet)
              cs = obj0->CSet[state];
            else
              cs = NULL;
            if(cs) {
              if(CoordSetGetAtomVertex(cs, at,
                    I->Vertex + 3 * aa /* record coordinate */)) {
                I->Flag1[aa] = true;
                n1++;
              }
            }

            /* also copy O coordinates for usage below */

            aa = res[a].o;
            at = I->Table[aa].atom;

            if(state < obj0->NCSet)
              cs = obj0->CSet[state];
            else
              cs = NULL;
            if(cs) {
              CoordSetGetAtomVertex(cs, at,
                  I->Vertex + 3 * aa /* record coordinate */);
            }
          }
        }
      }

      if(n1) {
        short too_many_atoms = false;
        map = MapNewFlagged(G, -cutoff, I->Vertex, I->NAtom, NULL, I->Flag1);
        if(map) {
          MapSetupExpress(map);

          for(a0 = 0; a0 < n_res; a0++) {

            if(res[a0].obj) {

              /* now iterate through carbonyls */
              obj0 = res[a0].obj;
              as0 = res[a0].o;
              at0 = I->Table[as0].atom;

              v0 = I->Vertex + 3 * as0;
              if(MapExclLocus(map, v0, &h, &k, &l)) {
                i = *(MapEStart(map, h, k, l));
                if(i) {
                  int nat = 0;
                  as1 = map->EList[i++];
                  while(as1 >= 0) {
                    v1 = I->Vertex + 3 * as1;

                    if(within3f(v0, v1, cutoff)) {

                      obj1 = I->Obj[I->Table[as1].model];
                      at1 = I->Table[as1].atom;

                      if(obj0 == obj1) {        /* don't count hbonds between adjacent residues */
                        exclude = SelectorCheckNeighbors(G, 5, obj0, at0, at1,
                                                         zero, scratch);
                      } else {
                        exclude = false;
                      }

                      /*                      if(!exclude) {
                         printf("at1 %s %s vs at0 %s %s\n",
                         obj1->AtomInfo[at1].resi,
                         obj1->AtomInfo[at1].name,
                         obj0->AtomInfo[at0].resi,
                         obj0->AtomInfo[at0].name
                         );
                         }
                       */
                      if((!exclude) && ObjectMoleculeGetCheckHBond(NULL, NULL, obj1,    /* donor first */
                                                                   at1, state, obj0,    /* then acceptor */
                                                                   at0, state, hbc)) {

                        /*                        printf(" found hbond between acceptor resi %s and donor resi %s\n",
                           res[a0].obj->AtomInfo[at0].resi,
                           res[I->Flag2[as1]].obj->AtomInfo[I->Table[as1].atom].resi); */

                        a1 = I->Flag2[as1];     /* index in SS n_res space */

                        /* store acceptor link */

                        n1 = res[a0].n_acc;
                        if(n1 < (cSSMaxHBond - 1)) {
                          res[a0].acc[n1] = a1;
                          res[a0].n_acc = n1 + 1;
                        }

                        /* store donor link */

                        n1 = res[a1].n_don;
                        if(n1 < (cSSMaxHBond - 1)) {
                          res[a1].don[n1] = a0;
                          res[a1].n_don = n1 + 1;
                        }
                      }
                    }
                    as1 = map->EList[i++];
		    nat++;
                  }
                  if (nat > 1000){ // if map returns more than 1000 atoms within 4, should be a dss error
                    too_many_atoms = true;
                    break;
                  }
                }
              }
            }
          }
        }
        MapFree(map);
	if (too_many_atoms){
	  PRINTFB(G, FB_Selector, FB_Errors)
	    " SelectorAssignSS: ERROR: Unreasonable number of neighbors for dss, cannot assign secondary structure.\n" ENDFB(G);
	}
      }
      FreeP(zero);
      FreeP(scratch);
    }

    {                           /* compute phi, psi's */

      SSResi *r;
      int a;

      float helix_psi_delta, helix_phi_delta;
      float strand_psi_delta, strand_phi_delta;

      float helix_psi_target = SettingGet_f(G, NULL, NULL, cSetting_ss_helix_psi_target);
      float helix_psi_include =
        SettingGet_f(G, NULL, NULL, cSetting_ss_helix_psi_include);
      float helix_psi_exclude =
        SettingGet_f(G, NULL, NULL, cSetting_ss_helix_psi_exclude);

      float helix_phi_target = SettingGet_f(G, NULL, NULL, cSetting_ss_helix_phi_target);
      float helix_phi_include =
        SettingGet_f(G, NULL, NULL, cSetting_ss_helix_phi_include);
      float helix_phi_exclude =
        SettingGet_f(G, NULL, NULL, cSetting_ss_helix_phi_exclude);

      float strand_psi_target =
        SettingGet_f(G, NULL, NULL, cSetting_ss_strand_psi_target);
      float strand_psi_include =
        SettingGet_f(G, NULL, NULL, cSetting_ss_strand_psi_include);
      float strand_psi_exclude =
        SettingGet_f(G, NULL, NULL, cSetting_ss_strand_psi_exclude);

      float strand_phi_target =
        SettingGet_f(G, NULL, NULL, cSetting_ss_strand_phi_target);
      float strand_phi_include =
        SettingGet_f(G, NULL, NULL, cSetting_ss_strand_phi_include);
      float strand_phi_exclude =
        SettingGet_f(G, NULL, NULL, cSetting_ss_strand_phi_exclude);

      for(a = 0; a < n_res; a++) {
        r = res + a;
        if(r->real && ((r - 1)->real)) {
          r->flags = 0;

          if(ObjectMoleculeGetPhiPsi
             (r->obj, I->Table[r->ca].atom, &r->phi, &r->psi, state)) {
            r->flags |= cSSGotPhiPsi;

            helix_psi_delta = (float) fabs(r->psi - helix_psi_target);
            strand_psi_delta = (float) fabs(r->psi - strand_psi_target);
            helix_phi_delta = (float) fabs(r->phi - helix_phi_target);
            strand_phi_delta = (float) fabs(r->phi - strand_phi_target);

            if(helix_psi_delta > 180.0F)
              helix_psi_delta = 360.0F - helix_psi_delta;
            if(strand_psi_delta > 180.0F)
              strand_psi_delta = 360.0F - strand_psi_delta;
            if(helix_phi_delta > 180.0F)
              helix_phi_delta = 360.0F - helix_phi_delta;
            if(strand_phi_delta > 180.0F)
              strand_phi_delta = 360.0F - strand_phi_delta;

            /* printf("helix %d strand %d\n",helix_delta,strand_delta); */

            if((helix_psi_delta > helix_psi_exclude) ||
               (helix_phi_delta > helix_phi_exclude)) {
              r->flags |= cSSPhiPsiNotHelix;
            } else if((helix_psi_delta < helix_psi_include) &&
                      (helix_phi_delta < helix_phi_include)) {
              r->flags |= cSSPhiPsiHelix;
            }

            if((strand_psi_delta > strand_psi_exclude) ||
               (strand_phi_delta > strand_phi_exclude)) {
              r->flags |= cSSPhiPsiNotStrand;
            } else if((strand_psi_delta < strand_psi_include) &&
                      (strand_phi_delta < strand_phi_include)) {
              r->flags |= cSSPhiPsiStrand;
            }
          }
        }
      }
    }

    /* by default, tentatively assign everything as loop */

    {
      int a;
      for(a = cSSBreakSize; a < (n_res - cSSBreakSize); a++) {
        if(res[a].present)
          res[a].ss = 'L';
      }
    }

    {
      SSResi *r, *r2;
      int a, b, c;

      for(a = cSSBreakSize; a < (n_res - cSSBreakSize); a++) {
        r = res + a;
        if(r->real) {

          /* look for tell-tale i+3,4,5 hydrogen bonds for helix  */

          /* is residue an acceptor for i+3,4,5 residue? */
          for(b = 0; b < r->n_acc; b++) {
            r->flags |=
              ((r->acc[b] == (a + 3)) ? cSSHelix3HBond : 0) |
              ((r->acc[b] == (a + 4)) ? cSSHelix4HBond : 0) |
              ((r->acc[b] == (a + 5)) ? cSSHelix5HBond : 0);

          }

          /* is residue a donor for i-3,4,5 residue */
          for(b = 0; b < r->n_don; b++) {
            r->flags |=
              ((r->don[b] == (a - 3)) ? cSSHelix3HBond : 0) |
              ((r->don[b] == (a - 4)) ? cSSHelix4HBond : 0) |
              ((r->don[b] == (a - 5)) ? cSSHelix5HBond : 0);

          }

          /*        if(r->flags & (cSSHelix3HBond | cSSHelix4HBond | cSSHelix5HBond)) {
             printf("HelixHB %s \n",
             r->obj->AtomInfo[I->Table[r->ca].atom].resi);
             }
           */

          /* look for double h-bonded antiparallel beta sheet pairs:
           * 
           *  \ /\ /
           *   N  C
           *   #  O
           *   O  #
           *   C  N
           *  / \/ \
           *
           */

          for(b = 0; b < r->n_acc; b++) {       /* iterate through acceptors */
            r2 = (res + r->acc[b]);
            if(r2->real) {
              for(c = 0; c < r2->n_acc; c++) {
                if(r2->acc[c] == a) {   /* found a pair */
                  r->flags |= cSSAntiStrandDoubleHB;
                  r2->flags |= cSSAntiStrandDoubleHB;

                  /*                printf("anti double %s to %s\n",
                     r->obj->AtomInfo[I->Table[r->ca].atom].resi,
                     r2->obj->AtomInfo[I->Table[r2->ca].atom].resi); */

                }
              }
            }
          }

          /* look for antiparallel beta buldges
           * 
           *     CCNC
           *  \ / O  \ /
           *   N      C
           *   #      O
           *    O    #
           *     C  N
           *    / \/ \
           *
           */

          for(b = 0; b < r->n_acc; b++) {       /* iterate through acceptors */
            r2 = (res + r->acc[b]) + 1; /* go forward 1 */
            if(r2->real) {
              for(c = 0; c < r2->n_acc; c++) {
                if(r2->acc[c] == a) {   /* found a buldge */
                  r->flags |= cSSAntiStrandDoubleHB;
                  r2->flags |= cSSAntiStrandBuldgeHB;
                  (r2 - 1)->flags |= cSSAntiStrandBuldgeHB;

                  /*                printf("anti BULDGE %s to %s %s\n",
                     r->obj->AtomInfo[I->Table[r->ca].atom].resi,
                     r2->obj->AtomInfo[I->Table[r2->ca].atom].resi,
                     r2->obj->AtomInfo[I->Table[(r2-1)->ca].atom].resi); */

                }
              }
            }
          }

          /* look for antiparallel beta sheet ladders (single or double)
           *
           *        O
           *     N  C
           *  \ / \/ \ /
           *   C      N
           *   O      #
           *   #      O
           *   N      C
           *  / \ /\ / \
           *     C  N
           *     O
           */

          if((r + 1)->real && (r + 2)->real) {

            for(b = 0; b < r->n_acc; b++) {     /* iterate through acceptors */
              r2 = (res + r->acc[b]) - 2;       /* go back 2 */
              if(r2->real) {

                for(c = 0; c < r2->n_acc; c++) {

                  if(r2->acc[c] == a + 2) {     /* found a ladder */

                    (r)->flags |= cSSAntiStrandSingleHB;
                    (r + 1)->flags |= cSSAntiStrandSkip;
                    (r + 2)->flags |= cSSAntiStrandSingleHB;

                    (r2)->flags |= cSSAntiStrandSingleHB;
                    (r2 + 1)->flags |= cSSAntiStrandSkip;
                    (r2 + 2)->flags |= cSSAntiStrandSingleHB;

                    /*                  printf("anti ladder %s %s to %s %s\n",
                       r->obj->AtomInfo[I->Table[r->ca].atom].resi,
                       r->obj->AtomInfo[I->Table[(r+2)->ca].atom].resi,
                       r2->obj->AtomInfo[I->Table[r2->ca].atom].resi,
                       r2->obj->AtomInfo[I->Table[(r2+2)->ca].atom].resi); */
                  }
                }
              }
            }
          }

          /* look for parallel beta sheet ladders 
           *

           *    \ /\ /
           *     C  N
           *    O    #
           *   #      O
           *   N      C
           *  / \ /\ / \
           *     C  N
           *     O
           */

          if((r + 1)->real && (r + 2)->real) {

            for(b = 0; b < r->n_acc; b++) {     /* iterate through acceptors */
              r2 = (res + r->acc[b]);
              if(r2->real) {

                for(c = 0; c < r2->n_acc; c++) {

                  if(r2->acc[c] == a + 2) {     /* found a ladder */

                    (r)->flags |= cSSParaStrandSingleHB;
                    (r + 1)->flags |= cSSParaStrandSkip;
                    (r + 2)->flags |= cSSParaStrandSingleHB;

                    (r2)->flags |= cSSParaStrandDoubleHB;

                    /*                                    printf("parallel ladder %s %s to %s \n",
                       r->obj->AtomInfo[I->Table[r->ca].atom].resi,
                       r->obj->AtomInfo[I->Table[(r+2)->ca].atom].resi,
                       r2->obj->AtomInfo[I->Table[r2->ca].atom].resi); */
                  }
                }
              }
            }
          }
        }
      }
    }

    {
      int a;
      SSResi *r;
      /* convert flags to assignments */

      /* HELICES FIRST */

      for(a = cSSBreakSize; a < (n_res - cSSBreakSize); a++) {
        r = res + a;

        if(r->real) {
          /* clean internal helical residues are easy to find using H-bonds */

          if(((r - 1)->flags & (cSSHelix3HBond | cSSHelix4HBond | cSSHelix5HBond)) &&
             ((r)->flags & (cSSHelix3HBond | cSSHelix4HBond | cSSHelix5HBond)) &&
             ((r + 1)->flags & (cSSHelix3HBond | cSSHelix4HBond | cSSHelix5HBond))) {
            if(!(r->flags & (cSSPhiPsiNotHelix))) {
              r->ss = 'H';
            }
          }

          /*
             if(((r-1)->flags & (cSSHelix3HBond )) &&
             ((r  )->flags & (cSSHelix3HBond )) &&
             ((r+1)->flags & (cSSHelix3HBond ))) {
             if(!(r->flags & (cSSPhiPsiNotHelix))) {
             r->ss = 'H';
             }
             }

             if(((r-1)->flags & (cSSHelix4HBond)) &&
             ((r  )->flags & (cSSHelix4HBond)) &&
             ((r+1)->flags & (cSSHelix4HBond))) {
             if(!(r->flags & (cSSPhiPsiNotHelix))) {
             r->ss = 'H';
             }
             }

             if(((r-1)->flags & (cSSHelix5HBond)) &&
             ((r  )->flags & (cSSHelix5HBond)) &&
             ((r+1)->flags & (cSSHelix5HBond))) {
             if(!(r->flags & (cSSPhiPsiNotHelix))) {
             r->ss = 'H';
             }
             }
           */

        }
      }

      for(a = cSSBreakSize; a < (n_res - cSSBreakSize); a++) {
        r = res + a;

        if(r->real) {

          /* occasionally they'll be one whacked out residue missing h-bonds... 
             in an otherwise good segment */

          if(((r - 2)->flags & (cSSHelix3HBond | cSSHelix4HBond | cSSHelix5HBond)) &&
             ((r - 1)->flags & (cSSHelix3HBond | cSSHelix4HBond | cSSHelix5HBond)) &&
             ((r - 1)->flags & (cSSPhiPsiHelix)) &&
             ((r)->flags & (cSSPhiPsiHelix)) &&
             ((r + 1)->flags & (cSSHelix3HBond | cSSHelix4HBond | cSSHelix5HBond)) &&
             ((r + 1)->flags & (cSSPhiPsiHelix)) &&
             ((r + 2)->flags & (cSSHelix3HBond | cSSHelix4HBond | cSSHelix5HBond))
            ) {
            r->ss = 'h';
          }
        }
      }

      for(a = cSSBreakSize; a < (n_res - cSSBreakSize); a++) {
        r = res + a;
        if(r->real) {
          if(r->ss == 'h') {
            r->flags |= (cSSHelix3HBond | cSSHelix4HBond | cSSHelix5HBond);
            r->ss = 'H';
          }
        }
      }

      for(a = cSSBreakSize; a < (n_res - cSSBreakSize); a++) {
        r = res + a;

        if(r->real) {

          /* deciding where the helix ends is trickier -- here we use helix geometry */

          if(((r)->flags & (cSSHelix3HBond | cSSHelix4HBond | cSSHelix5HBond)) &&
             ((r)->flags & (cSSPhiPsiHelix)) &&
             ((r + 1)->flags & (cSSHelix3HBond | cSSHelix4HBond | cSSHelix5HBond)) &&
             ((r + 1)->flags & (cSSPhiPsiHelix)) &&
             ((r + 2)->flags & (cSSHelix3HBond | cSSHelix4HBond | cSSHelix5HBond)) &&
             ((r + 2)->flags & (cSSPhiPsiHelix)) && ((r + 1)->ss == 'H')
            ) {
            r->ss = 'H';
          }

          if(((r)->flags & (cSSHelix3HBond | cSSHelix4HBond | cSSHelix5HBond)) &&
             ((r)->flags & (cSSPhiPsiHelix)) &&
             ((r - 1)->flags & (cSSHelix3HBond | cSSHelix4HBond | cSSHelix5HBond)) &&
             ((r - 1)->flags & (cSSPhiPsiHelix)) &&
             ((r - 2)->flags & (cSSHelix3HBond | cSSHelix4HBond | cSSHelix5HBond)) &&
             ((r - 2)->flags & (cSSPhiPsiHelix)) && ((r - 1)->ss == 'H')
            ) {
            r->ss = 'H';
          }

        }
      }

      /* THEN SHEETS/STRANDS */

      for(a = cSSBreakSize; a < (n_res - cSSBreakSize); a++) {
        r = res + a;
        if(r->real) {

          /* Antiparallel Sheets */

          if(((r)->flags & (cSSAntiStrandDoubleHB)) &&
             (!((r->flags & (cSSPhiPsiNotStrand))))) {
            (r)->ss = 'S';
          }

          if(((r)->flags & (cSSAntiStrandBuldgeHB)) &&  /* no strand geometry filtering for buldges.. */
             ((r + 1)->flags & (cSSAntiStrandBuldgeHB))) {
            (r)->ss = 'S';
            (r + 1)->ss = 'S';
          }

          if(((r - 1)->flags & (cSSAntiStrandDoubleHB)) &&
             ((r)->flags & (cSSAntiStrandSkip)) &&
             (!(((r)->flags & (cSSPhiPsiNotStrand)))) &&
             ((r + 1)->flags & (cSSAntiStrandSingleHB | cSSAntiStrandDoubleHB))) {

            (r)->ss = 'S';
          }

          if(((r - 1)->flags & (cSSAntiStrandSingleHB | cSSAntiStrandDoubleHB)) &&
             ((r)->flags & (cSSAntiStrandSkip)) &&
             (!(((r)->flags & (cSSPhiPsiNotStrand)))) &&
             ((r + 1)->flags & (cSSAntiStrandDoubleHB))) {
            (r)->ss = 'S';
          }

          /* include open "ladders" if PHIPSI geometry supports assignment */

          if(((r - 1)->flags & (cSSAntiStrandSingleHB | cSSAntiStrandDoubleHB)) &&
             ((r - 1)->flags & (cSSPhiPsiStrand)) &&
             (!(((r - 1)->flags & (cSSPhiPsiNotStrand)))) &&
             ((r)->flags & (cSSPhiPsiStrand)) &&
             (!(((r - 1)->flags & (cSSPhiPsiNotStrand)))) &&
             ((r + 1)->flags & (cSSAntiStrandSingleHB | cSSAntiStrandDoubleHB)) &&
             ((r + 1)->flags & (cSSPhiPsiStrand))) {

            (r - 1)->ss = 'S';
            (r)->ss = 'S';
            (r + 1)->ss = 'S';
          }

          /* Parallel Sheets */

          if(((r)->flags & (cSSParaStrandDoubleHB)) &&
             (!(((r)->flags & (cSSPhiPsiNotStrand))))) {
            (r)->ss = 'S';
          }

          if(((r - 1)->flags & (cSSParaStrandDoubleHB)) &&
             ((r)->flags & (cSSParaStrandSkip)) &&
             (!(((r)->flags & (cSSPhiPsiNotStrand)))) &&
             ((r + 1)->flags & (cSSParaStrandSingleHB | cSSParaStrandDoubleHB))) {

            (r)->ss = 'S';
          }

          if(((r - 1)->flags & (cSSParaStrandSingleHB | cSSParaStrandDoubleHB)) &&
             ((r)->flags & (cSSParaStrandSkip)) &&
             (!(((r)->flags & (cSSPhiPsiNotStrand)))) &&
             ((r + 1)->flags & (cSSParaStrandDoubleHB))) {
            (r)->ss = 'S';
          }

          /* include open "ladders" if PHIPSI geometry supports assignment */

          if(((r - 1)->flags & (cSSParaStrandSingleHB | cSSParaStrandDoubleHB)) &&
             ((r - 1)->flags & (cSSPhiPsiStrand)) &&
             ((r)->flags & (cSSParaStrandSkip)) &&
             ((r)->flags & (cSSPhiPsiStrand)) &&
             ((r + 1)->flags & (cSSParaStrandSingleHB | cSSParaStrandDoubleHB)) &&
             ((r + 1)->flags & (cSSPhiPsiStrand))) {

            (r - 1)->ss = 'S';
            (r)->ss = 'S';
            (r + 1)->ss = 'S';

          }
        }
      }
    }

    {
      int a, b;
      SSResi *r, *r2;
      int repeat = true;
      int found;

      while(repeat) {
        repeat = false;

        for(a = cSSBreakSize; a < (n_res - cSSBreakSize); a++) {
          r = res + a;
          if(r->real) {

            /* make sure we don't have any 2-residue segments */

            if((r->ss == 'S') && ((r + 1)->ss == 'S') &&
               (((r - 1)->ss != 'S') && ((r + 2)->ss != 'S'))) {
              r->ss = 'L';
              (r + 1)->ss = 'L';
              repeat = true;
            }
            if((r->ss == 'H') && ((r + 1)->ss == 'H') &&
               (((r - 1)->ss != 'H') && ((r + 2)->ss != 'H'))) {
              r->ss = 'L';
              (r + 1)->ss = 'L';
              repeat = true;
            }

            /* make sure we don't have any 1-residue segments */

            if((r->ss == 'S') && (((r - 1)->ss != 'S') && ((r + 1)->ss != 'S'))) {
              r->ss = 'L';
              repeat = true;
            }
            if((r->ss == 'H') && (((r - 1)->ss != 'H') && ((r + 1)->ss != 'H'))) {
              r->ss = 'L';
              repeat = true;
            }

            /* double-check to make sure every terminal strand residue 
               that should have a partner has one */

            if((r->ss == 'S') && (((r - 1)->ss != 'S') || ((r + 1)->ss != 'S'))) {

              found = false;

              for(b = 0; b < r->n_acc; b++) {
                r2 = res + r->acc[b];
                if(r2->ss == r->ss) {
                  found = true;
                  break;
                }
              }

              if(!found) {
                for(b = 0; b < r->n_don; b++) {
                  r2 = res + r->don[b];
                  if(r2->ss == r->ss) {
                    found = true;
                    break;
                  }
                }
              }

              if(!found) {     
                /* allow these strand "skip" residues to persist if a neighbor has hydrogen bonds */
                if(r->flags & (cSSAntiStrandSkip | cSSParaStrandSkip)) {

                  if((r + 1)->ss == r->ss)
                    for(b = 0; b < (r + 1)->n_acc; b++) {
                      r2 = res + (r + 1)->acc[b];
                      if(r2->ss == r->ss) {
                        found = true;
                        break;
                      }
                    }

                  if(!found) {
                    if((r - 1)->ss == r->ss) {
                      for(b = 0; b < (r - 1)->n_don; b++) {
                        r2 = res + (r - 1)->don[b];
                        if(r2->ss == r->ss) {
                          found = true;
                          break;
                        }
                      }
                    }
                  }
                }
              }

              if(!found) {
                r->ss = 'L';
                repeat = true;
              }
            }
          }
        }
      }
    }

    {
      int a;
      for(a = 0; a < n_res; a++) {      /* now apply consensus or union behavior, if appropriate */
        if(res[a].present) {
          if(res[a].ss_save) {
            if(res[a].ss != res[a].ss_save) {
              if(consensus) {
                res[a].ss = res[a].ss_save = 'L';
              } else if(res[a].ss == 'L')
                res[a].ss = res[a].ss_save;
            }
          }
          res[a].ss_save = res[a].ss;
        }
      }
    }

    {
      int a, aa;
      ObjectMolecule *obj = NULL, *last_obj = NULL;
      AtomInfoType *ai;
      int changed_flag = false;

      for(a = 0; a < n_res; a++) {
        if(res[a].present && (!res[a].preserve)) {

          aa = res[a].ca;
          obj = I->Obj[I->Table[aa].model];

          if(obj != last_obj) {
            if(changed_flag && last_obj) {
              ObjectMoleculeInvalidate(last_obj, cRepCartoon, cRepInvRep, -1);
              SceneChanged(G);
              changed_flag = false;
            }
            last_obj = obj;
          }
          ai = obj->AtomInfo + I->Table[aa].atom;

          if(SelectorIsMember(G, ai->selEntry, target)) {
            ai->ssType[0] = res[a].ss;
            ai->cartoon = 0;    /* switch back to auto */
            ai->ssType[1] = 0;
            changed_flag = true;
          }
        }
      }

      if(changed_flag && last_obj) {
        ObjectMoleculeInvalidate(last_obj, cRepCartoon, cRepInvRep, -1);
        SceneChanged(G);
        changed_flag = false;
      }
    }
    if(first_last_only && (state == state_start))
      state = state_stop - 2;
  }

  VLAFreeP(res);
  return 1;
}

PyObject *SelectorColorectionGet(PyMOLGlobals * G, const char *prefix)
{
#ifdef _PYMOL_NOPY
  return NULL;
#else
  CSelector *I = G->Selector;
  PyObject *result = NULL;
  int n_used = 0;
  ColorectionRec *used = NULL, tmp;
  ov_size a, b, n;
  int sele;
  int found;
  int m;
  int color;
  AtomInfoType *ai;
  used = VLAlloc(ColorectionRec, 1000);

  SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);
  for(a = cNDummyAtoms; a < I->NAtom; a++) {
    ai = I->Obj[I->Table[a].model]->AtomInfo + I->Table[a].atom;
    color = ai->color;
    found = false;
    for(b = 0; b < n_used; b++) {
      if(used[b].color == color) {
        tmp = used[0];          /* optimize to minimize N^2 effects */
        used[0] = used[b];
        used[b] = tmp;
        found = true;
        break;
      }
    }
    if(!found) {
      VLACheck(used, ColorectionRec, n_used);
      used[n_used] = used[0];
      used[0].color = color;
      n_used++;
    }
  }
  for(a = 0; a < n_used; a++) {
    /* create selections */

    n = I->NActive;
    VLACheck(I->Name, SelectorWordType, n + 1);
    VLACheck(I->Info, SelectionInfoRec, n + 1);
    sele = I->NSelection++;
    used[a].sele = sele;
    sprintf(I->Name[n], cColorectionFormat, prefix, used[a].color);
    I->Name[n + 1][0] = 0;
    SelectorAddName(G, n);
    SelectionInfoInit(I->Info + n);
    I->Info[n].ID = sele;
    I->NActive++;
  }

  for(a = cNDummyAtoms; a < I->NAtom; a++) {
    ai = I->Obj[I->Table[a].model]->AtomInfo + I->Table[a].atom;
    color = ai->color;
    for(b = 0; b < n_used; b++) {
      if(used[b].color == color) {
        tmp = used[0];          /* optimize to minimize N^2 effects */
        used[0] = used[b];
        used[b] = tmp;

        /* add selection onto atom */
        if(I->FreeMember > 0) {
          m = I->FreeMember;
          I->FreeMember = I->Member[m].next;
        } else {
          I->NMember++;
          m = I->NMember;
          VLACheck(I->Member, MemberType, m);
        }
        I->Member[m].selection = used[0].sele;
        I->Member[m].tag = 1;
        I->Member[m].next = ai->selEntry;
        ai->selEntry = m;
        break;
      }
    }
  }

  VLASize(used, ColorectionRec, n_used * 2);
  result = PConvIntVLAToPyList((int *) used);
  VLAFreeP(used);
  return (result);
#endif
}

int SelectorColorectionApply(PyMOLGlobals * G, PyObject * list, const char *prefix)
{
#ifdef _PYMOL_NOPY
  return 0;
#else

  CSelector *I = G->Selector;
  int ok = true;
  ColorectionRec *used = NULL;
  ov_size n_used = 0;
  int a, b;
  AtomInfoType *ai;
  ObjectMolecule *obj, *last = NULL;
  SelectorWordType name;

  if(ok)
    ok = (list != NULL);
  if(ok)
    ok = PyList_Check(list);
  if(ok)
    n_used = PyList_Size(list) / 2;
  if(ok)
    ok = ((used = VLAlloc(ColorectionRec, n_used)) != NULL);
  if(ok)
    ok = PConvPyListToIntArrayInPlace(list, (int *) used, n_used * 2);
  if(ok) {

    SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);

    for(b = 0; b < n_used; b++) {       /* update selection indices */
      sprintf(name, cColorectionFormat, prefix, used[b].color);
      used[b].sele = SelectorIndexByName(G, name);
    }

    for(a = cNDummyAtoms; a < I->NAtom; a++) {
      obj = I->Obj[I->Table[a].model];
      ai = obj->AtomInfo + I->Table[a].atom;

      for(b = 0; b < n_used; b++) {
        if(SelectorIsMember(G, ai->selEntry, used[b].sele)) {
          ai->color = used[b].color;
          if(obj != last) {
            ObjectMoleculeInvalidate(obj, cRepAll, cRepInvColor, -1);
            last = obj;
          }
          break;
        }
      }
    }
  }
  VLAFreeP(used);
  return (ok);
#endif
}

int SelectorColorectionSetName(PyMOLGlobals * G, PyObject * list, const char *prefix,
                               char *new_prefix)
{
#ifdef _PYMOL_NOPY
  return 0;
#else

  int ok = true;
  ColorectionRec *used = NULL;
  ov_size n_used = 0;
  ov_size b;
  SelectorWordType name;
  SelectorWordType new_name;

  if(ok)
    ok = (list != NULL);
  if(ok)
    ok = PyList_Check(list);
  if(ok)
    n_used = PyList_Size(list) / 2;
  if(ok)
    ok = ((used = VLAlloc(ColorectionRec, n_used)) != NULL);
  if(ok)
    ok = PConvPyListToIntArrayInPlace(list, (int *) used, n_used * 2);
  if(ok) {
    for(b = 0; b < n_used; b++) {       /* update selection indices */
      sprintf(name, cColorectionFormat, prefix, used[b].color);
      sprintf(new_name, cColorectionFormat, new_prefix, used[b].color);
      SelectorSetName(G, new_name, name);
    }
  }
  VLAFreeP(used);
  return (ok);
#endif

}

int SelectorColorectionFree(PyMOLGlobals * G, PyObject * list, const char *prefix)
{
#ifdef _PYMOL_NOPY
  return 0;
#else

  int ok = true;
  ColorectionRec *used = NULL;
  ov_size n_used = 0;
  ov_size b;
  SelectorWordType name;

  if(ok)
    ok = (list != NULL);
  if(ok)
    ok = PyList_Check(list);
  if(ok)
    n_used = PyList_Size(list) / 2;
  if(ok)
    ok = ((used = VLAlloc(ColorectionRec, n_used)) != NULL);
  if(ok)
    ok = PConvPyListToIntArrayInPlace(list, (int *) used, n_used * 2);
  if(ok) {

    for(b = 0; b < n_used; b++) {       /* update selection indices */
      sprintf(name, cColorectionFormat, prefix, used[b].color);
      used[b].sele = SelectorIndexByName(G, name);
    }

    for(b = 0; b < n_used; b++) {
      SelectorDeleteIndex(G, used[b].sele);
    }
  }
  VLAFreeP(used);
  return (ok);
#endif

}

PyObject *SelectorSecretsAsPyList(PyMOLGlobals * G)
{
  CSelector *I = G->Selector;
  int n_secret;
  int a;
  PyObject *result, *list;

  n_secret = 0;
  for(a = 0; a < I->NActive; a++) {
    if((I->Name[a][0] == '_') && (I->Name[a][1] == '!'))
      n_secret++;
  }
  result = PyList_New(n_secret);
  n_secret = 0;
  SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);
  for(a = 0; a < I->NActive; a++) {
    if((I->Name[a][0] == '_') && (I->Name[a][1] == '!')) {
      list = PyList_New(2);
      PyList_SetItem(list, 0, PyString_FromString(I->Name[a]));
      PyList_SetItem(list, 1, SelectorAsPyList(G, I->Info[a].ID));
      PyList_SetItem(result, n_secret, list);
      n_secret++;
    }
  }
  return (result);
}

int SelectorSecretsFromPyList(PyMOLGlobals * G, PyObject * list)
{
  int ok = true;
  ov_size n_secret = 0;
  ov_size a;
  PyObject *entry = NULL;
  SelectorWordType name;
  ov_size ll = 0;
  if(ok)
    ok = (list != NULL);
  if(ok)
    ok = PyList_Check(list);
  if(ok)
    n_secret = PyList_Size(list);
  if(ok) {
    for(a = 0; a < n_secret; a++) {
      if(ok)
        entry = PyList_GetItem(list, a);
      if(ok)
        ok = (entry != NULL);
      if(ok)
        ok = PyList_Check(entry);
      if(ok)
        ll = PyList_Size(entry);
      if(ok & (ll > 1)) {
        if(ok)
          ok = PConvPyStrToStr(PyList_GetItem(entry, 0), name, sizeof(SelectorWordType));
        if(ok)
          ok = SelectorFromPyList(G, name, PyList_GetItem(entry, 1));
      }
      if(!ok)
        break;
    }
  }
  return (ok);
}

typedef struct {
  int atom;
  int tag;
} SelAtomTag;

PyObject *SelectorAsPyList(PyMOLGlobals * G, int sele1)
{                               /* assumes SelectorUpdateTable has been called */
  CSelector *I = G->Selector;
  int a, b;
  int at;
  int s;
  SelAtomTag **vla_list = NULL;
  int n_obj = 0;
  int n_idx = 0;
  int cur = -1;
  ObjectMolecule **obj_list = NULL;
  ObjectMolecule *obj, *cur_obj = NULL;
  PyObject *result = NULL;
  PyObject *obj_pyobj;
  PyObject *idx_pyobj;
  PyObject *tag_pyobj;

  vla_list = VLACalloc(SelAtomTag *, 10);
  obj_list = VLAlloc(ObjectMolecule *, 10);

  n_idx = 0;
  for(a = cNDummyAtoms; a < I->NAtom; a++) {
    int tag;
    at = I->Table[a].atom;
    obj = I->Obj[I->Table[a].model];
    s = obj->AtomInfo[at].selEntry;
    if((tag = SelectorIsMember(G, s, sele1))) {
      if(cur_obj != obj) {
        if(n_idx) {
          VLASize(vla_list[cur], SelAtomTag, n_idx);
        }
        cur++;
        VLACheck(vla_list, SelAtomTag *, n_obj);
        vla_list[cur] = VLAlloc(SelAtomTag, 1000);
        VLACheck(obj_list, ObjectMolecule *, n_obj);
        obj_list[cur] = obj;
        cur_obj = obj;
        n_obj++;
        n_idx = 0;
      }
      VLACheck(vla_list[cur], SelAtomTag, n_idx);
      vla_list[cur][n_idx].atom = at;
      vla_list[cur][n_idx].tag = tag;
      n_idx++;
    }
  }
  if(cur_obj) {
    if(n_idx) {
      VLASize(vla_list[cur], SelAtomTag, n_idx);
    }
  }
  if(n_obj) {
    result = PyList_New(n_obj);
    for(a = 0; a < n_obj; a++) {
      obj_pyobj = PyList_New(3);
      n_idx = VLAGetSize(vla_list[a]);
      idx_pyobj = PyList_New(n_idx);
      tag_pyobj = PyList_New(n_idx);
      for(b = 0; b < n_idx; b++) {
        PyList_SetItem(idx_pyobj, b, PyInt_FromLong(vla_list[a][b].atom));
        PyList_SetItem(tag_pyobj, b, PyInt_FromLong(vla_list[a][b].tag));
      }
      VLAFreeP(vla_list[a]);
      PyList_SetItem(obj_pyobj, 0, PyString_FromString(obj_list[a]->Obj.Name));
      PyList_SetItem(obj_pyobj, 1, idx_pyobj);
      PyList_SetItem(obj_pyobj, 2, tag_pyobj);
      PyList_SetItem(result, a, obj_pyobj);
    }
  } else {
    result = PyList_New(0);
  }
  VLAFreeP(vla_list);
  VLAFreeP(obj_list);
  return (result);
}

int SelectorFromPyList(PyMOLGlobals * G, const char *name, PyObject * list)
{
  int ok = true;
  CSelector *I = G->Selector;
  ov_size a, b;
  ov_diff n;
  int m, sele;
  ov_size ll;
  PyObject *obj_list = NULL;
  PyObject *idx_list = NULL, *tag_list;
  ov_size n_obj = 0, n_idx = 0;
  int idx, tag;
  const char *oname;
  ObjectMolecule *obj;
  int singleAtomFlag = true;
  int singleObjectFlag = true;
  ObjectMolecule *singleObject = NULL;
  int singleAtom = -1;

  AtomInfoType *ai;
  if(ok)
    ok = PyList_Check(list);
  if(ok)
    n_obj = PyList_Size(list);

  /* get rid of existing selection */
  SelectorDelete(G, name);

  n = I->NActive;
  VLACheck(I->Name, SelectorWordType, n + 1);
  VLACheck(I->Info, SelectionInfoRec, n + 1);
  strcpy(I->Name[n], name);
  I->Name[n + 1][0] = 0;
  SelectorAddName(G, n);
  sele = I->NSelection++;
  SelectionInfoInit(I->Info + n);
  I->Info[n].ID = sele;
  I->NActive++;
  if(ok) {
    for(a = 0; a < n_obj; a++) {
      ll = 0;
      if(ok)
        obj_list = PyList_GetItem(list, a);
      if(ok)
        ok = PyList_Check(obj_list);
      if(ok)
        ll = PyList_Size(obj_list);
      if(ok)
        ok = PConvPyStrToStrPtr(PyList_GetItem(obj_list, 0), &oname);
      obj = NULL;
      if(ok)
        obj = ExecutiveFindObjectMoleculeByName(G, oname);
      if(ok && obj) {
        if(ok)
          idx_list = PyList_GetItem(obj_list, 1);
        if(ll > 2)
          tag_list = PyList_GetItem(obj_list, 2);
        else
          tag_list = NULL;
        if(ok)
          ok = PyList_Check(idx_list);
        if(ok)
          n_idx = PyList_Size(idx_list);
        for(b = 0; b < n_idx; b++) {
          if(ok)
            ok = PConvPyIntToInt(PyList_GetItem(idx_list, b), &idx);
          if(tag_list)
            PConvPyIntToInt(PyList_GetItem(tag_list, b), &tag);
          else
            tag = 1;
          if(ok && (idx < obj->NAtom)) {
            ai = obj->AtomInfo + idx;
            if(I->FreeMember > 0) {
              m = I->FreeMember;
              I->FreeMember = I->Member[m].next;
            } else {
              I->NMember++;
              m = I->NMember;
              VLACheck(I->Member, MemberType, m);
            }
            I->Member[m].selection = sele;
            I->Member[m].tag = tag;
            I->Member[m].next = ai->selEntry;
            ai->selEntry = m;

            /* take note of selections which are one atom/one object */
            if(singleObjectFlag) {
              if(singleObject) {
                if(obj != singleObject) {
                  singleObjectFlag = false;
                }
              } else {
                singleObject = obj;
              }
            }

            if(singleAtomFlag) {
              if(singleAtom >= 0) {
                if(idx != singleAtom) {
                  singleAtomFlag = false;
                }
              } else {
                singleAtom = idx;
              }
            }
          }
        }
      }
    }
    {                           /* make note of single atom/object selections */
      SelectionInfoRec *info = I->Info + (I->NActive - 1);
      if(singleObjectFlag && singleObject) {
        info->justOneObjectFlag = true;
        info->theOneObject = singleObject;
        if(singleAtomFlag && (singleAtom >= 0)) {
          info->justOneAtomFlag = true;
          info->theOneAtom = singleAtom;
        }
      }
    }
  }
  return (ok);
}

int SelectorVdwFit(PyMOLGlobals * G, int sele1, int state1, int sele2, int state2,
                   float buffer, int quiet)
{
  int ok = true;
  CSelector *I = G->Selector;
  int *vla = NULL;
  int c;
  float sumVDW = 0.0, dist;
  int a1, a2;
  AtomInfoType *ai1, *ai2;
  int at1, at2;
  CoordSet *cs1, *cs2;
  ObjectMolecule *obj1, *obj2;
  int idx1, idx2;
  float *adj = NULL;
  int a;

  if(state1 < 0)
    state1 = 0;
  if(state2 < 0)
    state2 = 0;

  if(state1 != state2) {
    SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);
  } else {
    SelectorUpdateTable(G, state1, -1);
  }

  c =
    SelectorGetInterstateVLA(G, sele1, state1, sele2, state2, 2 * MAX_VDW + buffer, &vla);
  if(c) {
    adj = Calloc(float, 2 * c);
    for(a = 0; a < c; a++) {
      a1 = vla[a * 2];
      a2 = vla[a * 2 + 1];

      at1 = I->Table[a1].atom;
      at2 = I->Table[a2].atom;

      obj1 = I->Obj[I->Table[a1].model];
      obj2 = I->Obj[I->Table[a2].model];

      if((state1 < obj1->NCSet) && (state2 < obj2->NCSet)) {
        cs1 = obj1->CSet[state1];
        cs2 = obj2->CSet[state2];
        if(cs1 && cs2) {        /* should always be true */

          ai1 = obj1->AtomInfo + at1;
          ai2 = obj2->AtomInfo + at2;

          idx1 = cs1->AtmToIdx[at1];    /* these are also pre-validated */
          idx2 = cs2->AtmToIdx[at2];

          sumVDW = ai1->vdw + ai2->vdw;
          dist = (float) diff3f(cs1->Coord + 3 * idx1, cs2->Coord + 3 * idx2);

          if(dist < (sumVDW + buffer)) {
            float shift = (dist - (sumVDW + buffer)) / 2.0F;
            adj[2 * a] = ai1->vdw + shift;
            adj[2 * a + 1] = ai2->vdw + shift;
          } else {
            adj[2 * a] = ai1->vdw;
            adj[2 * a + 1] = ai2->vdw;
          }

        }
      }
    }

    for(a = 0; a < c; a++) {
      a1 = vla[a * 2];
      a2 = vla[a * 2 + 1];

      at1 = I->Table[a1].atom;
      at2 = I->Table[a2].atom;

      obj1 = I->Obj[I->Table[a1].model];
      obj2 = I->Obj[I->Table[a2].model];

      if((state1 < obj1->NCSet) && (state2 < obj2->NCSet)) {
        cs1 = obj1->CSet[state1];
        cs2 = obj2->CSet[state2];
        if(cs1 && cs2) {        /* should always be true */

          ai1 = obj1->AtomInfo + at1;
          ai2 = obj2->AtomInfo + at2;

          if(adj[2 * a] < ai1->vdw) {
            ai1->vdw = adj[2 * a];
          }

          if(adj[2 * a + 1] < ai2->vdw) {
            ai2->vdw = adj[2 * a + 1];
          }

        }
      }
    }
  }

  VLAFreeP(vla);
  FreeP(adj);
  return ok;
}


/*========================================================================*/

int SelectorGetPairIndices(PyMOLGlobals * G, int sele1, int state1, int sele2, int state2,
                           int mode, float cutoff, float h_angle,
                           int **indexVLA, ObjectMolecule *** objVLA)
{
  CSelector *I = G->Selector;
  int *vla = NULL;
  int c;
  float dist;
  int a1, a2;
  int at1, at2;
  CoordSet *cs1, *cs2;
  ObjectMolecule *obj1, *obj2;
  int idx1, idx2;
  int a;
  int dist_cnt = 0;
  float dir[3];
  float v1[3], v2[3];
  int flag;
  float angle_cutoff = 0.0;

  if(mode == 1) {
    angle_cutoff = (float) cos(PI * h_angle / 180.0);
  }

  if(state1 < 0)
    state1 = 0;
  if(state2 < 0)
    state2 = 0;

  if(state1 != state2) {
    SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);
  } else {
    SelectorUpdateTable(G, state1, -1);
  }
  if(cutoff < 0)
    cutoff = 1000.0;
  c = SelectorGetInterstateVLA(G, sele1, state1, sele2, state2, cutoff, &vla);
  (*indexVLA) = VLAlloc(int, 1000);
  (*objVLA) = VLAlloc(ObjectMolecule *, 1000);

  for(a = 0; a < c; a++) {
    a1 = vla[a * 2];
    a2 = vla[a * 2 + 1];

    if(a1 != a2) {
      at1 = I->Table[a1].atom;
      at2 = I->Table[a2].atom;

      obj1 = I->Obj[I->Table[a1].model];
      obj2 = I->Obj[I->Table[a2].model];

      if(state1 < obj1->NCSet && state2 < obj2->NCSet) {
        cs1 = obj1->CSet[state1];
        cs2 = obj2->CSet[state2];
        if(cs1 && cs2) {
          idx1 = cs1->atmToIdx(at1);
          idx2 = cs2->atmToIdx(at2);

          if((idx1 >= 0) && (idx2 >= 0)) {
            subtract3f(cs1->Coord + 3 * idx1, cs2->Coord + 3 * idx2, dir);
            dist = (float) length3f(dir);
            if(dist > R_SMALL4) {
              float dist_1 = 1.0F / dist;
              scale3f(dir, dist_1, dir);
            }
            if(dist < cutoff) {
              if(mode == 1) {   /* coarse hydrogen bonding assessment */
                flag = false;
                if(ObjectMoleculeGetAvgHBondVector(obj1, at1, state1, v1, NULL) > 0.3)
                  if(dot_product3f(v1, dir) < -angle_cutoff)
                    flag = true;
                if(ObjectMoleculeGetAvgHBondVector(obj2, at2, state2, v2, NULL) > 0.3)
                  if(dot_product3f(v2, dir) > angle_cutoff)
                    flag = true;
              } else
                flag = true;

              if(flag) {
                VLACheck((*objVLA), ObjectMolecule *, dist_cnt + 1);
                VLACheck((*indexVLA), int, dist_cnt + 1);
                (*objVLA)[dist_cnt] = obj1;
                (*indexVLA)[dist_cnt] = at1;
                dist_cnt++;
                (*objVLA)[dist_cnt] = obj2;
                (*indexVLA)[dist_cnt] = at2;
                dist_cnt++;
              }
            }
          }
        }
      }
    }
  }

  VLASize((*objVLA), ObjectMolecule *, dist_cnt);
  VLASize((*indexVLA), int, dist_cnt);
  VLAFreeP(vla);
  dist_cnt = dist_cnt / 2;
  return (dist_cnt);
}


/*========================================================================*/
int SelectorCreateAlignments(PyMOLGlobals * G,
                             int *pair, int sele1, int *vla1, int sele2,
                             int *vla2, const char *name1, const char *name2,
                             int identical, int atomic_input)
{
  CSelector *I = G->Selector;
  int *flag1 = NULL, *flag2 = NULL;
  int *p;
  int i, np;
  int cnt;
  int mod1, mod2;               /* model indexes */
  int at1, at2, at1a, at2a;     /* atoms indexes */
  int vi1, vi2;                 /* vla indexes */
  int index1, index2;           /* indices in the selection array */
  AtomInfoType *ai1, *ai2, *ai1a, *ai2a;        /* atom information pointers */
  ObjectMolecule *obj1, *obj2;
  int cmp;
  PRINTFD(G, FB_Selector)
    " SelectorCreateAlignments-DEBUG: entry.\n" ENDFD cnt = 0;
  /* number of pairs of atoms */
  np = VLAGetSize(pair) / 2;
  if(np) {

    SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);  /* unnecessary? */
    /* flags initialized to false */
    flag1 = Calloc(int, I->NAtom);
    flag2 = Calloc(int, I->NAtom);

    /* we need to create two selection arrays: for the matched 
     * atoms in the original selections */
    p = pair;
    for(i = 0; i < np; i++) {   /* iterate through all pairs of matched residues */
      vi1 = *(p++);
      vi2 = *(p++);

      /* find positions in the selection arrays */
      /* SelectorGetResidueVLA returns a VLA with 3 entries per atom, index0=model,
       * index1=atom#, hence: *3, and *3+1 */
      mod1 = vla1[vi1 * 3];
      at1 = vla1[vi1 * 3 + 1];

      mod2 = vla2[vi2 * 3];
      at2 = vla2[vi2 * 3 + 1];

      PRINTFD(G, FB_Selector)
        " S.C.A.-DEBUG: mod1 %d at1 %d mod2 %d at2 %d\n", mod1, at1, mod2, at2
        ENDFD obj1 = I->Obj[mod1];
      obj2 = I->Obj[mod2];

      ai1 = obj1->AtomInfo + at1;
      ai2 = obj2->AtomInfo + at2;
      at1a = at1;
      at2a = at2;
      ai1a = ai1;
      ai2a = ai2;

      if(atomic_input) {
        index1 = SelectorGetObjAtmOffset(I, obj1, at1a);
        index2 = SelectorGetObjAtmOffset(I, obj2, at2a);
        flag1[index1] = true;
        flag2[index2] = true;
        cnt++;
      } else {

        /* search back to first atom in residue */
        while(at1a > 0 && AtomInfoSameResidue(G, ai1a, ai1a - 1)) { ai1a--; at1a--; }
        while(at2a > 0 && AtomInfoSameResidue(G, ai2a, ai2a - 1)) { ai2a--; at2a--; }

        while(1) {              /* match up all matching atom names in each residue */
          cmp = AtomInfoNameOrder(G, ai1a, ai2a);
          if(cmp == 0) {        /* atoms match */
            index1 = SelectorGetObjAtmOffset(I, obj1, at1a);
            index2 = SelectorGetObjAtmOffset(I, obj2, at2a);

            PRINTFD(G, FB_Selector)
              " S.C.A.-DEBUG: compare %s %s %d\n", LexStr(G, ai1a->name), LexStr(G, ai2a->name), cmp
              ENDFD PRINTFD(G, FB_Selector)
              " S.C.A.-DEBUG: entry %d %d\n",
              ai1a->selEntry, ai2a->selEntry ENDFD if((index1 >= 0) && (index2 >= 0)) {
              if(SelectorIsMember(G, ai1a->selEntry, sele1) &&
                 SelectorIsMember(G, ai2a->selEntry, sele2)) {
                if((!identical) || (ai1a->resn == ai2a->resn)) {
                  flag1[index1] = true;
                  flag2[index2] = true;
                  cnt++;
                }
              }
            }
            at1a++;
            at2a++;
          } else if(cmp < 0) {  /* 1 is before 2 */
            at1a++;
          } else if(cmp > 0) {  /* 1 is after 2 */
            at2a++;
          }
          if(at1a >= obj1->NAtom)
            break;
          if(at2a >= obj2->NAtom)
            break;
          ai1a = obj1->AtomInfo + at1a;
          ai2a = obj2->AtomInfo + at2a;
          /* make sure we're still in the same residue */
          if(!AtomInfoSameResidue(G, ai1a, ai1))
            break;
          if(!AtomInfoSameResidue(G, ai2a, ai2))
            break;
        }
      }
    }
    if(cnt) {
      SelectorEmbedSelection(G, flag1, name1, NULL, false, -1);
      SelectorEmbedSelection(G, flag2, name2, NULL, false, -1);
    }
    FreeP(flag1);
    FreeP(flag2);
  }
  PRINTFD(G, FB_Selector)
    " SelectorCreateAlignments-DEBUG: exit, cnt = %d.\n", cnt ENDFD return cnt;
}


/*========================================================================*/
int SelectorCountStates(PyMOLGlobals * G, int sele)
{
  CSelector *I = G->Selector;
  int a;
  int result = 0;
  int n_frame;
  int at1;
  ObjectMolecule *last = NULL;
  ObjectMolecule *obj;
  SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);
  if(I->NAtom) {
    for(a = cNDummyAtoms; a < I->NAtom; a++) {
      obj = I->Obj[I->Table[a].model];
      if(obj != last) {
        at1 = I->Table[a].atom;
        if(SelectorIsMember(G, obj->AtomInfo[at1].selEntry, sele)) {
          if(obj->Obj.fGetNFrame) {
            n_frame = obj->Obj.fGetNFrame((CObject *) obj);
            if(result < n_frame)
              result = n_frame;
          }
          last = obj;
        }
      }
    }
  }
  return (result);
}


/*========================================================================*/
int SelectorCheckIntersection(PyMOLGlobals * G, int sele1, int sele2)
{
  CSelector *I = G->Selector;
  int a;
  int at1;
  ObjectMolecule *obj;

  SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);
  if(I->NAtom) {
    for(a = cNDummyAtoms; a < I->NAtom; a++) {
      obj = I->Obj[I->Table[a].model];
      at1 = I->Table[a].atom;
      if(SelectorIsMember(G, obj->AtomInfo[at1].selEntry, sele1) &&
         SelectorIsMember(G, obj->AtomInfo[at1].selEntry, sele2))
        return 1;
    }
  }
  return 0;
}


/*========================================================================*/
int SelectorCountAtoms(PyMOLGlobals * G, int sele, int state)
{
  CSelector *I = G->Selector;
  int a;
  int result = 0;
  int at1;
  ObjectMolecule *obj;

  SelectorUpdateTable(G, state, -1);
  if(I->NAtom) {
    for(a = cNDummyAtoms; a < I->NAtom; a++) {
      obj = I->Obj[I->Table[a].model];
      at1 = I->Table[a].atom;
      if(SelectorIsMember(G, obj->AtomInfo[at1].selEntry, sele)) {
        result++;
      }
    }
  }
  return (result);
}

/*========================================================================*/
void SelectorSetDeleteFlagOnSelectionInObject(PyMOLGlobals * G, int sele, ObjectMolecule *obj, signed char val){
  CSelector *I = G->Selector;
  int a,nflags = 0;
  int at1;
  ObjectMolecule *obj0;

  SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);
  if(I->NAtom) {
    for(a = cNDummyAtoms; a < I->NAtom; a++) {
      obj0 = I->Obj[I->Table[a].model];
      if (obj==obj0){
	at1 = I->Table[a].atom;
	if(SelectorIsMember(G, obj->AtomInfo[at1].selEntry, sele)) {
	  obj->AtomInfo[at1].deleteFlag = val;
	  nflags++;
	}
      }
    }
  }
}

/*========================================================================*/
int *SelectorGetResidueVLA(PyMOLGlobals * G, int sele, int ca_only,
                           ObjectMolecule * exclude)
{
  /* returns a VLA containing atom indices followed by residue integers
     (residue names packed as characters into integers)
     The indices are the first and last residue in the selection...
   */
  CSelector *I = G->Selector;
  int *result = NULL, *r;
  AtomInfoType *ai1 = NULL, *ai2;

  /* update the selector's table */
  SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);

  /* make room for model#, at#, rcode, per atom */
  result = VLAlloc(int, I->NAtom * 3);

  r = result;
  PRINTFD(G, FB_Selector)
    " SelectorGetResidueVLA-DEBUG: entry, sele = %d\n", sele ENDFD;

  for(SeleAtomIterator iter(G, sele); iter.next();) {
    if(iter.obj == exclude)
      continue;

    ai2 = iter.getAtomInfo();

    if(ca_only) {
      if(!(ai2->flags & cAtomFlag_guide))
        continue;
    } else if(ai1 && AtomInfoSameResidue(G, ai1, ai2)) {
      continue;
    }

    *(r++) = I->Table[iter.a].model;
    *(r++) = I->Table[iter.a].atom;

    const char * resn = LexStr(G, ai2->resn);
    *(r)    = (resn[0] << (8 * 2));
    if (resn[0] && resn[1]) {
      *(r) |= (resn[1] << (8 * 1));
      *(r) |= (resn[2] << (8 * 0));
    }
    r++;

    ai1 = ai2;
  }
  if(result) {
    VLASize(result, int, (ov_size) (r - result));
  }
  PRINTFD(G, FB_Selector)
    " SelectorGetResidueVLA-DEBUG: exit, result = %p, size = %d\n",
    (void *) result, (unsigned int) VLAGetSize(result)
    ENDFD;

  return (result);
}


/*========================================================================*/
/* bad to make this non-static? */
/* static int *SelectorGetIndexVLA(PyMOLGlobals * G, int sele) */
static int *SelectorGetIndexVLA(PyMOLGlobals * G, int sele)
{
  return (SelectorGetIndexVLAImpl(G, G->Selector, sele));
}
static int *SelectorGetIndexVLAImpl(PyMOLGlobals * G, CSelector *I, int sele)
{                               /* assumes updated tables */
  int a, c = 0;
  int *result = NULL;
  ObjectMolecule *obj;
  int at1;

  result = VLAlloc(int, (I->NAtom / 10) + 1);
  for(a = cNDummyAtoms; a < I->NAtom; a++) {
    obj = I->Obj[I->Table[a].model];
    at1 = I->Table[a].atom;
    if(SelectorIsMember(G, obj->AtomInfo[at1].selEntry, sele)) {
      VLACheck(result, int, c);
      result[c++] = a;
    }
  }
  VLASize(result, int, c);
  return (result);
}


/*========================================================================*/
void SelectorUpdateObjectSele(PyMOLGlobals * G, ObjectMolecule * obj)
{
  if(obj->Obj.Name[0]) {
    SelectorDelete(G, obj->Obj.Name);
    SelectorCreate(G, obj->Obj.Name, NULL, obj, true, NULL);    
    /* create a selection with same name */
    if(SettingGetGlobal_b(G, cSetting_auto_classify_atoms))
    {
      SelectorClassifyAtoms(G, 0, false, obj);

      // for file formats other than PDB
      if (obj->need_hetatm_classification) {
        for (auto ai = obj->AtomInfo, ai_end = ai + obj->NAtom;
            ai != ai_end; ++ai) {
          if (!(ai->flags & cAtomFlag_polymer)) {
            ai->hetatm = true;
            ai->flags |= cAtomFlag_ignore;
          }
        }
        obj->need_hetatm_classification = false;
      }
    }
  }
}


/*========================================================================*/
void SelectorLogSele(PyMOLGlobals * G, const char *name)
{
  CSelector *I = G->Selector;
  int a;
  OrthoLineType line, buf1;
  int cnt = -1;
  int first = 1;
  int append = 0;
  ObjectMolecule *obj;
  int at1;
  int sele;
  int logging;
  int robust;
  logging = SettingGetGlobal_i(G, cSetting_logging);
  robust = SettingGetGlobal_b(G, cSetting_robust_logs);
  if(logging) {
    sele = SelectorIndexByName(G, name);
    if(sele >= 0) {
      SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);
      for(a = cNDummyAtoms; a < I->NAtom; a++) {
        obj = I->Obj[I->Table[a].model];
        at1 = I->Table[a].atom;
        if(SelectorIsMember(G, obj->AtomInfo[at1].selEntry, sele)) {
          if(cnt < 0) {
            if(first) {
              switch (logging) {
              case cPLog_pml:
                sprintf(line, "_ cmd.select(\"%s\",\"(", name);
                break;
              case cPLog_pym:
                sprintf(line, "cmd.select(\"%s\",\"(", name);
                break;
              }
              append = 0;
              cnt = 0;
              first = 0;
            } else {
              switch (logging) {
              case cPLog_pml:
                sprintf(line, "_ cmd.select(\"%s\",\"(%s", name, name);
                break;
              case cPLog_pym:
                sprintf(line, "cmd.select(\"%s\",\"(%s", name, name);
                break;
              }
              append = 1;
              cnt = 0;
            }
          }
          if(append)
            strcat(line, "|");
          if(robust)
            ObjectMoleculeGetAtomSeleFast(obj, at1, buf1);
          else
            sprintf(buf1, "%s`%d", obj->Obj.Name, at1 + 1);
          strcat(line, buf1);
          append = 1;
          cnt++;
          if(strlen(line) > (sizeof(OrthoLineType) / 2)) {
            strcat(line, ")\")\n");
            PLog(G, line, cPLog_no_flush);
            cnt = -1;
          }
        }
      }
      if(cnt > 0) {
        strcat(line, ")\")\n");
        PLog(G, line, cPLog_no_flush);
        PLogFlush(G);
      }
    }
  }
}


/*========================================================================*/
/*
 * This is the most heavily called routine in interactive PyMOL
 *
 * s:    AtomInfoType.selEntry
 * sele: selection index or 0 for "all"
 */
int SelectorIsMember(PyMOLGlobals * G, int s, int sele)
{
  if(sele > 1) {
    const MemberType *mem, *member = G->Selector->Member;
    for(; s; s = mem->next) {
      mem = member + s;
      if(mem->selection == sele)
        return mem->tag;
    }
  } else if(!sele)
    return true;                /* "all" is selection number 0, unordered */
  return false;
}


/*========================================================================*/
int SelectorMoveMember(PyMOLGlobals * G, int s, int sele_old, int sele_new)
{
  CSelector *I = G->Selector;
  int result = false;
  while(s) {
    if(I->Member[s].selection == sele_old) {
      I->Member[s].selection = sele_new;
      result = true;
    }
    s = I->Member[s].next;
  }
  return result;
}


/*========================================================================*/
static int SelectorIndexByID(PyMOLGlobals * G, int id)
{
  CSelector *I = G->Selector;
  int i = 0;
  int result = -1;
  SelectionInfoRec *info = I->Info;
  while(i < I->NActive) {
    if((info++)->ID == id) {
      result = i;
      break;
    }
    i++;
  }
  return result;
}


/*========================================================================*/
ObjectMolecule *SelectorGetFastSingleObjectMolecule(PyMOLGlobals * G, int sele)
{
  CSelector *I = G->Selector;
  ObjectMolecule *result = NULL;
  SelectionInfoRec *info;
  int sele_idx = SelectorIndexByID(G, sele);
  if((sele_idx >= 0) && (sele_idx < I->NActive)) {
    info = I->Info + sele_idx;
    if(info->justOneObjectFlag) {
      if(ExecutiveValidateObjectPtr(G, (CObject *) info->theOneObject, cObjectMolecule))
        result = info->theOneObject;
    } else {
      result = SelectorGetSingleObjectMolecule(G, sele);        /* fallback onto slow approach */
    }
  }
  return (result);
}


/*========================================================================*/
ObjectMolecule *SelectorGetFastSingleAtomObjectIndex(PyMOLGlobals * G, int sele,
                                                     int *index)
{
  CSelector *I = G->Selector;
  ObjectMolecule *result = NULL;
  SelectionInfoRec *info;
  int got_it = false;
  int sele_idx = SelectorIndexByID(G, sele);

  if((sele_idx >= 0) && (sele_idx < I->NActive)) {
    info = I->Info + sele_idx;
    if(info->justOneObjectFlag && info->justOneAtomFlag) {
      ObjectMolecule *obj = info->theOneObject;
      int at = info->theOneAtom;
      if(ExecutiveValidateObjectPtr(G, (CObject *) obj, cObjectMolecule)) {
        if((at < obj->NAtom) && SelectorIsMember(G, obj->AtomInfo[at].selEntry, sele)) {
          result = obj;
          *index = at;
          got_it = true;
        }
      }
    }
    if(!got_it) {               /* fallback onto slow approach */
      if(!SelectorGetSingleAtomObjectIndex(G, sele, &result, index))
        result = NULL;
    }
  }
  return (result);
}


/*========================================================================
 * SelectorGetSingleObjectMolecule -- get a ptr to the molecule indiecated
 *    by the selection parameter
 * PARAMS
 *   (int) selection #
 * RETURNS
 *   (ptr) pts to the ObjectMolecule or NULL if not found
 */
ObjectMolecule *SelectorGetSingleObjectMolecule(PyMOLGlobals * G, int sele)
{
  /* slow way */

  int a;
  ObjectMolecule *result = NULL;
  ObjectMolecule *obj;
  CSelector *I = G->Selector;
  int at1;
  SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);

  for(a = 0; a < I->NAtom; a++) {
    obj = I->Obj[I->Table[a].model];
    at1 = I->Table[a].atom;
    if(SelectorIsMember(G, obj->AtomInfo[at1].selEntry, sele)) {
      if(result) {
        if(result != obj) {
          result = NULL;
          break;
        }
      } else {
        result = obj;
      }
    }
  }
  return (result);
}


/*========================================================================*/
ObjectMolecule *SelectorGetFirstObjectMolecule(PyMOLGlobals * G, int sele)
{
  /* slow way */

  int a;
  ObjectMolecule *result = NULL;
  ObjectMolecule *obj;
  CSelector *I = G->Selector;
  int at1;
  SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);

  for(a = 0; a < I->NAtom; a++) {
    obj = I->Obj[I->Table[a].model];
    at1 = I->Table[a].atom;
    if(SelectorIsMember(G, obj->AtomInfo[at1].selEntry, sele)) {
      result = obj;
      break;
    }
  }
  return (result);
}


/*========================================================================*/
ObjectMolecule **SelectorGetObjectMoleculeVLA(PyMOLGlobals * G, int sele)
{
  int a;
  ObjectMolecule *last = NULL;
  ObjectMolecule *obj, **result = NULL;
  CSelector *I = G->Selector;
  int at1;
  int n = 0;
  SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);

  result = VLAlloc(ObjectMolecule *, 10);
  for(a = cNDummyAtoms; a < I->NAtom; a++) {
    obj = I->Obj[I->Table[a].model];
    at1 = I->Table[a].atom;
    if(SelectorIsMember(G, obj->AtomInfo[at1].selEntry, sele)) {
      if(obj != last) {
        VLACheck(result, ObjectMolecule *, n);
        result[n] = obj;
        last = obj;
        n++;
      }
    }
  }
  VLASize(result, ObjectMolecule *, n);
  return (result);
}


/*========================================================================*/
int SelectorGetSingleAtomObjectIndex(PyMOLGlobals * G, int sele, ObjectMolecule ** in_obj,
                                     int *index)
{
  /* slow way */

  int found_it = false;
  int a;
  void *iterator = NULL;
  ObjectMolecule *obj = NULL;

  while(ExecutiveIterateObjectMolecule(G, &obj, &iterator)) {
    int n_atom = obj->NAtom;
    AtomInfoType *ai = obj->AtomInfo;
    for(a = 0; a < n_atom; a++) {
      int s = (ai++)->selEntry;
      if(SelectorIsMember(G, s, sele)) {
        if(found_it) {
          return false;         /* ADD'L EXIT POINT */
        } else {
          found_it = true;
          (*in_obj) = obj;
          (*index) = a;
        }
      }
    }
  }
  return (found_it);
}


/*========================================================================*/
int SelectorGetSingleAtomVertex(PyMOLGlobals * G, int sele, int state, float *v)
{
  ObjectMolecule *obj = NULL;
  int index = 0;
  int found_it = false;
  if(SelectorGetSingleAtomObjectIndex(G, sele, &obj, &index))
    found_it = ObjectMoleculeGetAtomTxfVertex(obj, state, index, v);
  return (found_it);
}


/*========================================================================*/
void SelectorDeletePrefixSet(PyMOLGlobals * G, const char *pref)
{
  ov_diff a;
  CSelector *I = G->Selector;
  SelectorWordType name_copy;
  int ignore_case = SettingGetGlobal_b(G, cSetting_ignore_case);

  while(1) {
    a = SelectGetNameOffset(G, pref, strlen(pref), ignore_case);
    if(a > 0) {
      strcpy(name_copy, I->Name[a]);
      ExecutiveDelete(G, name_copy);    /* import to use a copy, otherwise 
                                         * you'll delete all objects  */
    } else
      break;
  }
}


/*========================================================================*/
#define MAX_DEPTH 1000

static int SelectorCheckNeighbors(PyMOLGlobals * G, int maxDist, ObjectMolecule * obj,
                                  int at1, int at2, int *zero, int *scratch)
{
  int s;
  int a, a1;
  int stkDepth = 0;
  int si = 0;
  int stk[MAX_DEPTH];
  int dist = 0;

  zero[at1] = dist;
  scratch[si++] = at1;
  stk[stkDepth] = at1;
  stkDepth++;

  while(stkDepth) {             /* this will explore a tree */
    stkDepth--;
    a = stk[stkDepth];
    dist = zero[a] + 1;

    s = obj->Neighbor[a];       /* add neighbors onto the stack */
    s++;                        /* skip count */
    while(1) {
      a1 = obj->Neighbor[s];
      if(a1 == at2) {
        while(si--) {
          zero[scratch[si]] = 0;
        }
        /* EXIT POINT 1 */
        return 1;
      }
      if(a1 >= 0) {
        if((!zero[a1]) && (stkDepth < MAX_DEPTH) && (dist < maxDist)) {
          zero[a1] = dist;
          scratch[si++] = a1;
          stk[stkDepth] = a1;
          stkDepth++;
        }
      } else
        break;
      s += 2;
    }
  }
  while(si--) {
    zero[scratch[si]] = 0;
  }
  /* EXIT POINT 2 */
  return 0;
}


/*========================================================================*/
int SelectorWalkTree(PyMOLGlobals * G, int *atom, int *comp, int *toDo, int **stk,
                     int stkDepth, ObjectMolecule * obj,
                     int sele1, int sele2, int sele3, int sele4)
{
  int s;
  int c = 0;
  int a, a1;
  int seleFlag;
  AtomInfoType *ai;

  while(stkDepth) {             /* this will explore a tree, stopping at protected atoms */
    stkDepth--;
    a = (*stk)[stkDepth];
    toDo[a] = 0;
    seleFlag = false;
    ai = obj->AtomInfo + a;
    s = ai->selEntry;
    seleFlag = SelectorIsMember(G, s, sele1);
    if(!seleFlag)
      seleFlag = SelectorIsMember(G, s, sele2);
    if(!seleFlag)
      seleFlag = SelectorIsMember(G, s, sele3);
    if(!seleFlag)
      seleFlag = SelectorIsMember(G, s, sele4);
    if(!seleFlag) {
      if(!(ai->protekted == 1)) {       /* if not explicitly protected... */
        atom[a] = 1;            /* mark this atom into the selection */
        comp[a] = 1;
      }
      s = obj->Neighbor[a];     /* add neighbors onto the stack */
      s++;                      /* skip count */
      while(1) {
        a1 = obj->Neighbor[s];
        if(a1 >= 0) {
          if(toDo[a1]) {
            VLACheck((*stk), int, stkDepth);
            (*stk)[stkDepth] = a1;
            stkDepth++;
          }
        } else
          break;
        s += 2;
      }
      c++;
    }
  }
  return (c);
}


/*========================================================================*/
static int SelectorWalkTreeDepth(PyMOLGlobals * G, int *atom, int *comp, int *toDo,
                                 int **stk, int stkDepth, ObjectMolecule * obj, int sele1,
                                 int sele2, int sele3, int sele4, int **extraStk,
                                 WalkDepthRec * wd)
{
  int s;
  int c = 0;
  int a, a1;
  int seleFlag;
  int depth;
  AtomInfoType *ai;

  wd->depth1 = -1;
  wd->depth2 = -1;
  wd->depth3 = -1;
  wd->depth4 = -1;
  VLACheck(*extraStk, int, stkDepth);
  UtilZeroMem(*extraStk, sizeof(int) * stkDepth);

  while(stkDepth) {             /* this will explore a tree, stopping at protected atoms */
    stkDepth--;
    a = (*stk)[stkDepth];
    depth = ((*extraStk)[stkDepth] + 1);
    seleFlag = false;
    ai = obj->AtomInfo + a;
    s = ai->selEntry;

    /* record how many cycles it take to reach each & any picked atoms */

    seleFlag = false;
    if(SelectorIsMember(G, s, sele1)) {
      if(((wd->depth1 < 0) || (wd->depth1 > depth))) {
        wd->depth1 = depth;
      }
      seleFlag = true;
    }
    if(SelectorIsMember(G, s, sele2)) {
      if(((wd->depth2 < 0) || (wd->depth2 > depth))) {
        wd->depth2 = depth;
      }
      seleFlag = true;
    }
    if(SelectorIsMember(G, s, sele3)) {
      if(((wd->depth3 < 0) || (wd->depth3 > depth))) {
        wd->depth3 = depth;
      }
      seleFlag = true;
    }
    if(SelectorIsMember(G, s, sele4)) {
      if(((wd->depth4 < 0) || (wd->depth4 > depth))) {
        wd->depth4 = depth;
      }
      seleFlag = true;
    }

    if(!seleFlag) {
      toDo[a] = 0;
      if(!(ai->protekted == 1)) {       /* if not explicitly protected... */
        atom[a] = 1;            /* mark this atom into the selection */
        comp[a] = 1;
      }
      s = obj->Neighbor[a];     /* add neighbors onto the stack */
      s++;                      /* skip count */
      while(1) {
        a1 = obj->Neighbor[s];
        if(a1 >= 0) {
          if(toDo[a1]) {
            VLACheck((*stk), int, stkDepth);
            (*stk)[stkDepth] = a1;
            VLACheck((*extraStk), int, stkDepth);
            (*extraStk)[stkDepth] = depth;
            stkDepth++;
          }
        } else
          break;
        s += 2;
      }
      c++;
    }
  }
  return (c);
}


/*========================================================================*/

int SelectorIsAtomBondedToSele(PyMOLGlobals * G, ObjectMolecule * obj, int sele1atom,
                               int sele2)
{
  int a0, a2, s, ss;
  int bonded = false;
  ObjectMoleculeUpdateNeighbors(obj);

  a0 = ObjectMoleculeGetAtomIndex(obj, sele1atom);

  if(a0 >= 0) {
    s = obj->Neighbor[a0];
    s++;                        /* skip count */
    while(1) {
      a2 = obj->Neighbor[s];
      if(a2 < 0)
        break;
      ss = obj->AtomInfo[a2].selEntry;
      if(SelectorIsMember(G, ss, sele2)) {
        bonded = true;
        break;
      }
      s += 2;
    }
  }
  return bonded;
}

static void update_min_walk_depth(WalkDepthRec * minWD,
                                  int frag, WalkDepthRec * wd,
                                  int sele1, int sele2, int sele3, int sele4)
{
  /* first, does this fragment even qualify ? */
  int qualifies = true;
  int cnt = 0;
  wd->sum = 0;
  if(sele1 >= 0) {
    if(wd->depth1 < 0) {
      qualifies = false;
    } else {
      wd->sum += wd->depth1;
      cnt++;
    }
  }
  if(sele2 >= 0) {
    if(wd->depth2 < 0) {
      qualifies = false;
    } else {
      wd->sum += wd->depth2;
      cnt++;
    }
  }
  if(sele3 >= 0) {
    if(wd->depth3 < 0) {
      qualifies = false;
    } else {
      wd->sum += wd->depth3;
      cnt++;
    }
  }
  if(sele4 >= 0) {
    if(wd->depth4 < 0) {
      qualifies = false;
    } else {
      wd->sum += wd->depth4;
      cnt++;
    }
  }
  if(qualifies && (cnt > 1)) {

    /* is it better than the current min? */

    if((!minWD->frag) || (wd->sum < minWD->sum)) {
      (*minWD) = (*wd);
      minWD->frag = frag;
    }
  }
}


/*========================================================================*/
int SelectorSubdivide(PyMOLGlobals * G, const char *pref, int sele1, int sele2,
                      int sele3, int sele4, const char *fragPref, const char *compName, int *bondMode)
{
  CSelector *I = G->Selector;
  int a0 = 0, a1 = 0, a2;
  int *atom = NULL;
  int *toDo = NULL;
  int *comp = NULL;
  int *pkset = NULL;
  int set_cnt = 0;
  int nFrag = 0;
  int *stk = NULL;
  int stkDepth;
  int c, s;
  int cycFlag = false;
  SelectorWordType name, link_sele = "";
  ObjectMolecule *obj1 = NULL, *obj2 = NULL, *obj3 = NULL, *obj4 = NULL;
  int index1 = 0, index2 = 0, index3 = 0, index4 = 0;

  /* this is seriously getting out of hand -- need to switch over to arrays soon */

  int *atom1_base = NULL, *atom2_base = NULL, *atom3_base = NULL, *atom4_base = NULL;
  int *toDo1_base = NULL, *toDo2_base = NULL, *toDo3_base = NULL, *toDo4_base = NULL;
  int *comp1_base = NULL, *comp2_base = NULL, *comp3_base = NULL, *comp4_base = NULL;
  int *pkset1_base = NULL, *pkset2_base = NULL, *pkset3_base = NULL, *pkset4_base = NULL;

  PRINTFD(G, FB_Selector)
    " SelectorSubdivideObject: entered...\n" ENDFD;
  SelectorDeletePrefixSet(G, pref);
  SelectorDeletePrefixSet(G, fragPref);
  ExecutiveDelete(G, cEditorLink);
  ExecutiveDelete(G, cEditorSet);
  /* delete any existing matches */

  obj1 = SelectorGetFastSingleAtomObjectIndex(G, sele1, &index1);
  obj2 = SelectorGetFastSingleAtomObjectIndex(G, sele2, &index2);
  obj3 = SelectorGetFastSingleAtomObjectIndex(G, sele3, &index3);
  obj4 = SelectorGetFastSingleAtomObjectIndex(G, sele4, &index4);

  if(obj1 || obj2 || obj3 || obj4) {

    if(obj1)
      ObjectMoleculeUpdateNeighbors(obj1);
    if(obj2)
      ObjectMoleculeUpdateNeighbors(obj2);
    if(obj3)
      ObjectMoleculeUpdateNeighbors(obj3);
    if(obj4)
      ObjectMoleculeUpdateNeighbors(obj4);

    SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);

    comp = Calloc(int, I->NAtom);
    atom = Alloc(int, I->NAtom);
    toDo = Alloc(int, I->NAtom);
    pkset = Calloc(int, I->NAtom);

    /* NOTE: SeleBase only safe with cSelectorUpdateTableAllStates!  */

    if(obj1) {
      atom1_base = atom + obj1->SeleBase;
      toDo1_base = toDo + obj1->SeleBase;
      comp1_base = comp + obj1->SeleBase;
      pkset1_base = pkset + obj1->SeleBase;
    }

    if(obj2) {
      atom2_base = atom + obj2->SeleBase;
      toDo2_base = toDo + obj2->SeleBase;
      comp2_base = comp + obj2->SeleBase;
      pkset2_base = pkset + obj2->SeleBase;
    }

    if(obj3) {
      atom3_base = atom + obj3->SeleBase;
      toDo3_base = toDo + obj3->SeleBase;
      comp3_base = comp + obj3->SeleBase;
      pkset3_base = pkset + obj3->SeleBase;
    }

    if(obj4) {
      atom4_base = atom + obj4->SeleBase;
      toDo4_base = toDo + obj4->SeleBase;
      comp4_base = comp + obj4->SeleBase;
      pkset4_base = pkset + obj4->SeleBase;
    }

    stk = VLAlloc(int, 100);

    {
      int a;
      int *p1;
      p1 = toDo;
      for(a = 0; a < I->NAtom; a++)
        *(p1++) = true;
    }

    if(*bondMode) {
      /* verify bond mode, or clear the flag */

      *bondMode = false;

      if((sele1 >= 0) && (sele2 >= 0) && (sele3 < 0) && (sele4 < 0) && (obj1 == obj2)) {
        /* two selections only, in same object... */

        a0 = index1;
        a1 = index2;

        if((a0 >= 0) && (a1 >= 0)) {
          s = obj1->Neighbor[a0];       /* add neighbors onto the stack */
          s++;                  /* skip count */
          while(1) {
            a2 = obj1->Neighbor[s];
            if(a2 < 0)
              break;
            if(a2 == a1) {
              *bondMode = true;
              break;
            }
            s += 2;
          }
        }
      }
    }

    /* ===== BOND MODE ===== (sele0 and sele1 only) */

    if(*bondMode) {
      if(obj1 == obj2) {        /* just to be safe */

        pkset1_base[a0] = 1;
        pkset1_base[a1] = 1;
        SelectorEmbedSelection(G, pkset, cEditorBond, NULL, false, -1);

        a0 = index1;
        if(a0 >= 0) {
          stkDepth = 0;
          s = obj1->Neighbor[a0];       /* add neighbors onto the stack */
          s++;                  /* skip count */
          while(1) {
            a1 = obj1->Neighbor[s];
            if(a1 >= 0) {
              if(toDo1_base[a1]) {
                VLACheck(stk, int, stkDepth);
                stk[stkDepth] = a1;
                stkDepth++;
              }
            } else
              break;
            s += 2;
          }
          UtilZeroMem(atom, sizeof(int) * I->NAtom);
          atom1_base[a0] = 1;   /* create selection for this atom alone as fragment base atom */
          comp1_base[a0] = 1;
          sprintf(name, "%s%1d", fragPref, nFrag + 1);
          SelectorEmbedSelection(G, atom, name, NULL, false, -1);
          c =
            SelectorWalkTree(G, atom1_base, comp1_base, toDo1_base, &stk, stkDepth, obj1,
                             sele1, sele2, -1, -1) + 1;
          sprintf(name, "%s%1d", pref, nFrag + 1);

          /* check for cyclic situation */
          cycFlag = false;
          a2 = index2;
          if(a2 >= 0) {
            stkDepth = 0;
            s = obj1->Neighbor[a2];     /* add neighbors onto the stack */
            s++;                /* skip count */
            while(1) {
              a1 = obj1->Neighbor[s];
              if(a1 < 0)
                break;
              if((a1 >= 0) && (a1 != a0)) {
                if(!toDo1_base[a1]) {
                  cycFlag = true;       /* we have a cycle... */
                  break;
                }
              }
              s += 2;
            }
          }
          if(cycFlag) {         /* cyclic situation is a bit complex... */

            a0 = index2;
            if(a0 >= 0) {
              stkDepth = 0;
              s = obj1->Neighbor[a0];   /* add neighbors onto the stack */
              s++;              /* skip count */
              while(1) {
                a1 = obj1->Neighbor[s];
                if(a1 >= 0) {
                  if(toDo1_base[a1]) {
                    VLACheck(stk, int, stkDepth);
                    stk[stkDepth] = a1;
                    stkDepth++;
                  }
                } else
                  break;
                s += 2;
              }
              atom1_base[a0] = 1;
              comp1_base[a0] = 1;
              c =
                SelectorWalkTree(G, atom1_base, comp1_base, toDo1_base, &stk, stkDepth,
                                 obj1, sele1, sele2, -1, -1) + 1;
            }
          }
          SelectorEmbedSelection(G, atom, name, NULL, false, -1);
          nFrag++;
        }

        if(!cycFlag) {
          a0 = index2;
          if(a0 >= 0) {
            stkDepth = 0;
            s = obj1->Neighbor[a0];     /* add neighbors onto the stack */
            s++;                /* skip count */
            while(1) {
              a1 = obj1->Neighbor[s];
              if(a1 >= 0) {
                if(toDo1_base[a1]) {
                  VLACheck(stk, int, stkDepth);
                  stk[stkDepth] = a1;
                  stkDepth++;
                }
              } else
                break;
              s += 2;
            }

            UtilZeroMem(atom, sizeof(int) * I->NAtom);
            atom1_base[a0] = 1; /* create selection for this atom alone as fragment base atom */
            comp1_base[a0] = 1;
            sprintf(name, "%s%1d", fragPref, nFrag + 1);
            SelectorEmbedSelection(G, atom, name, NULL, false, -1);
            c =
              SelectorWalkTree(G, atom1_base, comp1_base, toDo1_base, &stk, stkDepth,
                               obj1, sele1, sele2, -1, -1) + 1;
            sprintf(name, "%s%1d", pref, nFrag + 1);
            SelectorEmbedSelection(G, atom, name, NULL, false, -1);
            nFrag++;
          }
        }
      }
    } else {
      /* ===== WALK MODE ===== (any combination of sele0, sele1, sele2, sele3 */

      int *extraStk = VLAlloc(int, 50);
      WalkDepthRec curWalk, minWalk;
      minWalk.sum = 0;
      minWalk.frag = 0;

      if(obj1) {
        a0 = index1;
        if(a0 >= 0) {
          pkset1_base[a0] = 1;
          set_cnt++;
          comp1_base[a0] = 1;
          stkDepth = 0;
          s = obj1->Neighbor[a0];       /* add neighbors onto the stack */
          s++;                  /* skip count */
          while(1) {
            a1 = obj1->Neighbor[s];
            if(a1 < 0)
              break;
            if(toDo1_base[a1]) {
              stkDepth = 1;
              stk[0] = a1;
              UtilZeroMem(atom, sizeof(int) * I->NAtom);
              atom1_base[a1] = 1;       /* create selection for this atom alone as fragment base atom */
              comp1_base[a1] = 1;
              sprintf(name, "%s%1d", fragPref, nFrag + 1);
              SelectorEmbedSelection(G, atom, name, NULL, false, -1);
              atom1_base[a1] = 0;
              c = SelectorWalkTreeDepth(G, atom1_base, comp1_base, toDo1_base, &stk,
                                        stkDepth, obj1, sele1, sele2, sele3, sele4,
                                        &extraStk, &curWalk);
              if(c) {
                nFrag++;
                sprintf(name, "%s%1d", pref, nFrag);
                SelectorEmbedSelection(G, atom, name, NULL, false, -1);
                update_min_walk_depth(&minWalk,
                                      nFrag, &curWalk, sele1, sele2, sele3, sele4);
              }
            }
            s += 2;
          }
        }
      }

      if(obj2) {
        a0 = index2;
        if(a0 >= 0) {
          pkset2_base[a0] = 1;
          set_cnt++;
          comp2_base[a0] = 1;
          stkDepth = 0;
          s = obj2->Neighbor[a0];       /* add neighbors onto the stack */
          s++;                  /* skip count */
          while(1) {
            a1 = obj2->Neighbor[s];
            if(a1 < 0)
              break;
            if(toDo2_base[a1]) {
              stkDepth = 1;
              stk[0] = a1;
              UtilZeroMem(atom, sizeof(int) * I->NAtom);
              atom2_base[a1] = 1;       /* create selection for this atom alone as fragment base atom */
              comp2_base[a1] = 1;
              sprintf(name, "%s%1d", fragPref, nFrag + 1);
              SelectorEmbedSelection(G, atom, name, NULL, false, -1);
              atom2_base[a1] = 0;
              c = SelectorWalkTreeDepth(G, atom2_base, comp2_base, toDo2_base, &stk,
                                        stkDepth, obj2, sele1, sele2, sele3, sele4,
                                        &extraStk, &curWalk);
              if(c) {
                nFrag++;
                sprintf(name, "%s%1d", pref, nFrag);
                SelectorEmbedSelection(G, atom, name, NULL, false, -1);
                update_min_walk_depth(&minWalk,
                                      nFrag, &curWalk, sele1, sele2, sele3, sele4);
              }
            }
            s += 2;
          }
        }
      }

      if(obj3) {
        a0 = index3;
        if(a0 >= 0) {
          pkset3_base[a0] = 1;
          set_cnt++;
          comp3_base[a0] = 1;
          stkDepth = 0;
          s = obj3->Neighbor[a0];       /* add neighbors onto the stack */
          s++;                  /* skip count */
          while(1) {
            a1 = obj3->Neighbor[s];
            if(a1 < 0)
              break;
            if(toDo3_base[a1]) {
              stkDepth = 1;
              stk[0] = a1;
              UtilZeroMem(atom, sizeof(int) * I->NAtom);
              atom3_base[a1] = 1;       /* create selection for this atom alone as fragment base atom */
              comp3_base[a1] = 1;
              sprintf(name, "%s%1d", fragPref, nFrag + 1);
              SelectorEmbedSelection(G, atom, name, NULL, false, -1);
              atom3_base[a1] = 0;
              c = SelectorWalkTreeDepth(G, atom3_base, comp3_base, toDo3_base, &stk,
                                        stkDepth, obj3, sele1, sele2, sele3, sele4,
                                        &extraStk, &curWalk);
              if(c) {
                nFrag++;
                sprintf(name, "%s%1d", pref, nFrag);
                SelectorEmbedSelection(G, atom, name, NULL, false, -1);
                update_min_walk_depth(&minWalk,
                                      nFrag, &curWalk, sele1, sele2, sele3, sele4);

              }
            }
            s += 2;
          }
        }
      }

      if(obj4) {
        a0 = index4;
        if(a0 >= 0) {
          pkset4_base[a0] = 1;
          set_cnt++;
          comp4_base[a0] = 1;
          stkDepth = 0;
          s = obj4->Neighbor[a0];       /* add neighbors onto the stack */
          s++;                  /* skip count */
          while(1) {
            a1 = obj4->Neighbor[s];
            if(a1 < 0)
              break;
            if(toDo4_base[a1]) {
              stkDepth = 1;
              stk[0] = a1;
              UtilZeroMem(atom, sizeof(int) * I->NAtom);
              atom4_base[a1] = 1;       /* create selection for this atom alone as fragment base atom */
              comp4_base[a1] = 1;
              sprintf(name, "%s%1d", fragPref, nFrag + 1);
              SelectorEmbedSelection(G, atom, name, NULL, false, -1);
              atom4_base[a1] = 0;
              c = SelectorWalkTreeDepth(G, atom4_base, comp4_base, toDo4_base, &stk,
                                        stkDepth, obj4, sele1, sele2, sele3, sele4,
                                        &extraStk, &curWalk);
              if(c) {
                nFrag++;
                sprintf(name, "%s%1d", pref, nFrag);
                SelectorEmbedSelection(G, atom, name, NULL, false, -1);
                update_min_walk_depth(&minWalk,
                                      nFrag, &curWalk, sele1, sele2, sele3, sele4);
              }
            }
            s += 2;
          }
        }
      }

      if(minWalk.frag) {        /* create the linking selection if one exists */
        sprintf(link_sele, "%s%d|?pk1|?pk2|?pk3|?pk4", pref, minWalk.frag);
      }
      VLAFreeP(extraStk);
    }

    if(set_cnt > 1) {
      SelectorEmbedSelection(G, pkset, cEditorSet, NULL, false, -1);
    }

    if(nFrag) {
      SelectorEmbedSelection(G, comp, compName, NULL, false, -1);
    }

    if(link_sele[0])
      SelectorCreate(G, cEditorLink, link_sele, NULL, true, NULL);

    FreeP(toDo);
    FreeP(atom);
    FreeP(comp);
    FreeP(pkset);
    VLAFreeP(stk);
    SelectorClean(G);
  }
  PRINTFD(G, FB_Selector)
    " SelectorSubdivideObject: leaving...nFrag %d\n", nFrag ENDFD;

  return (nFrag);
}


/*========================================================================*/
int SelectorGetSeleNCSet(PyMOLGlobals * G, int sele)
{
  CSelector *I = G->Selector;

  int a, s, at = 0;
  ObjectMolecule *obj, *last_obj = NULL;
  int result = 0;

  if((obj = SelectorGetFastSingleAtomObjectIndex(G, sele, &at))) {
    int a = obj->NCSet;
    CoordSet *cs;
    int idx;

    while(a--) {
      cs = obj->CSet[a];
      idx = cs->atmToIdx(at);
      if(idx >= 0) {
        result = a + 1;
        break;
      }
    }
  } else {
    for(a = cNDummyAtoms; a < I->NAtom; a++) {
      obj = I->Obj[I->Table[a].model];
      if(obj != last_obj) {
        at = I->Table[a].atom;
        s = obj->AtomInfo[at].selEntry;
        if(SelectorIsMember(G, s, sele)) {
          if(result < obj->NCSet) {
            result = obj->NCSet;
            last_obj = obj;
          }
        }
      }
    }
  }
  return (result);
}


/*========================================================================*/
int SelectorGetArrayNCSet(PyMOLGlobals * G, int *array, int no_dummies)
{
  CSelector *I = G->Selector;
  int a;
  ObjectMolecule *obj;
  int result = 0;
  int start = 0;
  if(no_dummies)
    start = cNDummyAtoms;
  for(a = start; a < I->NAtom; a++) {
    if(*(array++)) {
      if(a >= cNDummyAtoms) {
        obj = I->Obj[I->Table[a].model];
        if(result < obj->NCSet)
          result = obj->NCSet;
      } else {
        if(result < 1)
          result = 1;           /* selected dummy has at least one CSet */
      }

    }
  }
  return (result);
}


/*========================================================================*/
float SelectorSumVDWOverlap(PyMOLGlobals * G, int sele1, int state1, int sele2,
                            int state2, float adjust)
{
  CSelector *I = G->Selector;
  int *vla = NULL;
  int c;
  float result = 0.0;
  float sumVDW = 0.0, dist;
  int a1, a2;
  AtomInfoType *ai1, *ai2;
  int at1, at2;
  CoordSet *cs1, *cs2;
  ObjectMolecule *obj1, *obj2;
  int idx1, idx2;
  int a;

  if(state1 < 0)
    state1 = 0;
  if(state2 < 0)
    state2 = 0;

  if(state1 != state2) {
    SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);
  } else {
    SelectorUpdateTable(G, state1, -1);
  }

  c =
    SelectorGetInterstateVLA(G, sele1, state1, sele2, state2, 2 * MAX_VDW + adjust, &vla);
  for(a = 0; a < c; a++) {
    a1 = vla[a * 2];
    a2 = vla[a * 2 + 1];

    at1 = I->Table[a1].atom;
    at2 = I->Table[a2].atom;

    obj1 = I->Obj[I->Table[a1].model];
    obj2 = I->Obj[I->Table[a2].model];

    if((state1 < obj1->NCSet) && (state2 < obj2->NCSet)) {
      cs1 = obj1->CSet[state1];
      cs2 = obj2->CSet[state2];
      if(cs1 && cs2) {          /* should always be true */

        ai1 = obj1->AtomInfo + at1;
        ai2 = obj2->AtomInfo + at2;

        idx1 = cs1->AtmToIdx[at1];      /* these are also pre-validated */
        idx2 = cs2->AtmToIdx[at2];

        sumVDW = ai1->vdw + ai2->vdw + adjust;
        dist = (float) diff3f(cs1->Coord + 3 * idx1, cs2->Coord + 3 * idx2);

        if(dist < sumVDW) {
          result += ((sumVDW - dist) / 2.0F);
        }
      }
    }
  }
  VLAFreeP(vla);
  return (result);
}


/*========================================================================*/
static int SelectorGetInterstateVLA(PyMOLGlobals * G,
                                    int sele1, int state1,
                                    int sele2, int state2, float cutoff, int **vla)
{                               /* Assumes valid tables */
  CSelector *I = G->Selector;
  MapType *map;
  float *v2;
  int n1, n2;
  int c, i, j, h, k, l;
  int at;
  int a, s, idx;
  ObjectMolecule *obj;
  CoordSet *cs;

  if(!(*vla))
    (*vla) = VLAlloc(int, 1000);

  c = 0;
  n1 = 0;

  for(a = 0; a < I->NAtom; a++) {
    /* foreach atom, grab its atom ID, object ID, selection ID,
     * and current state's coordinate set */
    I->Flag1[a] = false;
    at = I->Table[a].atom;
    obj = I->Obj[I->Table[a].model];
    s = obj->AtomInfo[at].selEntry;
    if(SelectorIsMember(G, s, sele1)) {
      if(state1 < obj->NCSet)
        cs = obj->CSet[state1];
      else
        cs = NULL;
      if(cs) {
        if(CoordSetGetAtomVertex(cs, at, I->Vertex + 3 * a)) {
          I->Flag1[a] = true;
          n1++;
        }
      }
    }
  }
  /* now create and apply voxel map */
  c = 0;
  if(n1) {
    n2 = 0;
    map = MapNewFlagged(G, -cutoff, I->Vertex, I->NAtom, NULL, I->Flag1);
    if(map) {
      MapSetupExpress(map);
      for(a = cNDummyAtoms; a < I->NAtom; a++) {
        at = I->Table[a].atom;
        obj = I->Obj[I->Table[a].model];
        s = obj->AtomInfo[at].selEntry;
        if(SelectorIsMember(G, s, sele2)) {
          if(state2 < obj->NCSet)
            cs = obj->CSet[state2];
          else
            cs = NULL;
          if(cs) {
            idx = cs->atmToIdx(at);
            if(idx >= 0) {
              v2 = cs->Coord + (3 * idx);
              if(MapExclLocus(map, v2, &h, &k, &l)) {
                i = *(MapEStart(map, h, k, l));
                if(i) {
                  j = map->EList[i++];
                  while(j >= 0) {
                    if(within3f(I->Vertex + 3 * j, v2, cutoff)) {
                      VLACheck((*vla), int, c * 2 + 1);
                      *((*vla) + c * 2) = j;
                      *((*vla) + c * 2 + 1) = a;
                      c++;
                    }
                    j = map->EList[i++];
                  }
                }
              }
              n2++;
            }
          }
        }
      }
      MapFree(map);
    }
  }
  return (c);
}


/*========================================================================*/
int SelectorMapMaskVDW(PyMOLGlobals * G, int sele1, ObjectMapState * oMap, float buffer,
                       int state)
{
  CSelector *I = G->Selector;
  MapType *map;
  float *v2;
  int n1;
  int a, b, c, i, j, h, k, l;
  int at;
  int s;
  AtomInfoType *ai;
  ObjectMolecule *obj;
  CoordSet *cs;
  int state1, state2;
  int once_flag;

  c = 0;
  n1 = 0;
  SelectorUpdateTable(G, state, -1);

  for(a = 0; a < I->NAtom; a++) {
    I->Flag1[a] = false;
    at = I->Table[a].atom;
    obj = I->Obj[I->Table[a].model];
    s = obj->AtomInfo[at].selEntry;
    if(SelectorIsMember(G, s, sele1)) {
      once_flag = true;
      for(state2 = 0; state2 < obj->NCSet; state2++) {
        if(state < 0)
          once_flag = false;
        if(!once_flag)
          state1 = state2;
        else
          state1 = state;
        if(state1 < obj->NCSet)
          cs = obj->CSet[state1];
        else
          cs = NULL;
        if(cs) {
          if(CoordSetGetAtomVertex(cs, at, I->Vertex + 3 * a)) {
            I->Flag1[a] = true;
            n1++;
          }
        }
        if(once_flag)
          break;
      }
    }
  }
  /* now create and apply voxel map */
  c = 0;
  if(n1) {
    map = MapNewFlagged(G, -(buffer + MAX_VDW), I->Vertex, I->NAtom, NULL, I->Flag1);
    if(map) {
      MapSetupExpress(map);

      for(a = oMap->Min[0]; a <= oMap->Max[0]; a++) {
        for(b = oMap->Min[1]; b <= oMap->Max[1]; b++) {
          for(c = oMap->Min[2]; c <= oMap->Max[2]; c++) {
            F3(oMap->Field->data, a, b, c) = 0.0;

            v2 = F4Ptr(oMap->Field->points, a, b, c, 0);

            if(MapExclLocus(map, v2, &h, &k, &l)) {
              i = *(MapEStart(map, h, k, l));
              if(i) {
                j = map->EList[i++];
                while(j >= 0) {
                  ai = I->Obj[I->Table[j].model]->AtomInfo + I->Table[j].atom;
                  if(within3f(I->Vertex + 3 * j, v2, ai->vdw + buffer)) {
                    F3(oMap->Field->data, a, b, c) = 1.0;
                  }
                  j = map->EList[i++];
                }
              }
            }
          }
        }
      }
      oMap->Active = true;
      MapFree(map);
    }
  }
  return (c);
}

static double max2d(double a, double b)
{
  if(a > b)
    return a;
  else
    return b;
}

static double max6d(double a, double b, double c, double d, double e, double f)
{

  if(d > a)
    a = d;
  if(e > b)
    b = e;
  if(f > c)
    c = f;
  if(b > a)
    a = b;
  if(c > a)
    a = c;
  return (a);
}

#define D_SMALL10 1e-10

typedef double AtomSF[11];


/*========================================================================*/
int SelectorMapGaussian(PyMOLGlobals * G, int sele1, ObjectMapState * oMap,
                        float buffer, int state, int normalize, int use_max, int quiet,
                        float resolution)
{
  CSelector *I = G->Selector;
  MapType *map;
  float *v2;
  int n1, n2;
  int a, b, c, i, j, h, k, l;
  int at;
  int s, idx;
  AtomInfoType *ai;
  ObjectMolecule *obj;
  CoordSet *cs;
  int state1, state2;
  float *point = NULL, *fp;
  int *sfidx = NULL, *ip;
  float *b_factor = NULL, *bf, bfact;
  float *occup = NULL, *oc;
  int prot;
  int once_flag;
  float d, e_val;
  double sum, sumsq;
  float mean, stdev;
  double sf[256][11], *sfp;
  AtomSF *atom_sf = NULL;
  double b_adjust = (double) SettingGetGlobal_f(G, cSetting_gaussian_b_adjust);
  double elim = 7.0;
  double rcut2;
  float rcut;
  float max_rcut = 0.0F;
  float b_floor = SettingGetGlobal_f(G, cSetting_gaussian_b_floor);
  float blur_factor = 1.0F;

  {
    if(resolution < R_SMALL4)
      resolution = SettingGetGlobal_f(G, cSetting_gaussian_resolution);
    if(resolution < 1.0 ) 
      resolution = 1.0F;
    blur_factor = 2.0F / resolution;    
    /* a gaussian_resolution of 2.0 is considered perfect ? Hmm...where is this from??? */

  }

  if(b_adjust > 500.0)
    b_adjust = 500.0;           /* constrain to be somewhat reasonable */

  for(a = 0; a < 256; a++) {
    sf[a][0] = -1.0;
  }

  sf[cAN_H][0] = 0.493002;
  sf[cAN_H][1] = 10.510900;
  sf[cAN_H][2] = 0.322912;
  sf[cAN_H][3] = 26.125700;
  sf[cAN_H][4] = 0.140191;
  sf[cAN_H][5] = 3.142360;
  sf[cAN_H][6] = 0.040810;
  sf[cAN_H][7] = 57.799698;
  sf[cAN_H][8] = 0.003038;
  sf[cAN_H][9] = 0.0;

  /* LP currently using scattering factors of carbon 
     (Roche Pocket viewer relies upon this behavior) */

  sf[cAN_LP][0] = 2.310000;
  sf[cAN_LP][1] = 20.843899;
  sf[cAN_LP][2] = 1.020000;
  sf[cAN_LP][3] = 10.207500;
  sf[cAN_LP][4] = 1.588600;
  sf[cAN_LP][5] = 0.568700;
  sf[cAN_LP][6] = 0.865000;
  sf[cAN_LP][7] = 51.651199;
  sf[cAN_LP][8] = 0.215600;
  sf[cAN_LP][9] = 0.0;

  sf[cAN_C][0] = 2.310000;
  sf[cAN_C][1] = 20.843899;
  sf[cAN_C][2] = 1.020000;
  sf[cAN_C][3] = 10.207500;
  sf[cAN_C][4] = 1.588600;
  sf[cAN_C][5] = 0.568700;
  sf[cAN_C][6] = 0.865000;
  sf[cAN_C][7] = 51.651199;
  sf[cAN_C][8] = 0.215600;
  sf[cAN_C][9] = 0.0;

  sf[cAN_O][0] = 3.048500;
  sf[cAN_O][1] = 13.277100;
  sf[cAN_O][2] = 2.286800;
  sf[cAN_O][3] = 5.701100;
  sf[cAN_O][4] = 1.546300;
  sf[cAN_O][5] = 0.323900;
  sf[cAN_O][6] = 0.867000;
  sf[cAN_O][7] = 32.908897;
  sf[cAN_O][8] = 0.250800;
  sf[cAN_O][9] = 0.0;

  sf[cAN_N][0] = 12.212600;
  sf[cAN_N][1] = 0.005700;
  sf[cAN_N][2] = 3.132200;
  sf[cAN_N][3] = 9.893300;
  sf[cAN_N][4] = 2.012500;
  sf[cAN_N][5] = 28.997499;
  sf[cAN_N][6] = 1.166300;
  sf[cAN_N][7] = 0.582600;
  sf[cAN_N][8] = -11.528999;
  sf[cAN_N][9] = 0.0;

  sf[cAN_S][0] = 6.905300;
  sf[cAN_S][1] = 1.467900;
  sf[cAN_S][2] = 5.203400;
  sf[cAN_S][3] = 22.215099;
  sf[cAN_S][4] = 1.437900;
  sf[cAN_S][5] = 0.253600;
  sf[cAN_S][6] = 1.586300;
  sf[cAN_S][7] = 56.172001;
  sf[cAN_S][8] = 0.866900;
  sf[cAN_S][9] = 0.0;

  sf[cAN_Cl][0] = 11.460400;
  sf[cAN_Cl][1] = 0.010400;
  sf[cAN_Cl][2] = 7.196400;
  sf[cAN_Cl][3] = 1.166200;
  sf[cAN_Cl][4] = 6.255600;
  sf[cAN_Cl][5] = 18.519400;
  sf[cAN_Cl][6] = 1.645500;
  sf[cAN_Cl][7] = 47.778400;
  sf[cAN_Cl][8] = 0.866900;
  sf[cAN_Cl][9] = 0.0;

  sf[cAN_Br][0] = 17.178900;
  sf[cAN_Br][1] = 2.172300;
  sf[cAN_Br][2] = 5.235800;
  sf[cAN_Br][3] = 16.579599;
  sf[cAN_Br][4] = 5.637700;
  sf[cAN_Br][5] = 0.260900;
  sf[cAN_Br][6] = 3.985100;
  sf[cAN_Br][7] = 41.432800;
  sf[cAN_Br][8] = 2.955700;
  sf[cAN_Br][9] = 0.0;

  sf[cAN_I][0] = 20.147200;
  sf[cAN_I][1] = 4.347000;
  sf[cAN_I][2] = 18.994900;
  sf[cAN_I][3] = 0.381400;
  sf[cAN_I][4] = 7.513800;
  sf[cAN_I][5] = 27.765999;
  sf[cAN_I][6] = 2.273500;
  sf[cAN_I][7] = 66.877602;
  sf[cAN_I][8] = 4.071200;
  sf[cAN_I][9] = 0.0;

  sf[cAN_F][0] = 3.539200;
  sf[cAN_F][1] = 10.282499;
  sf[cAN_F][2] = 2.641200;
  sf[cAN_F][3] = 4.294400;
  sf[cAN_F][4] = 1.517000;
  sf[cAN_F][5] = 0.261500;
  sf[cAN_F][6] = 1.024300;
  sf[cAN_F][7] = 26.147600;
  sf[cAN_F][8] = 0.277600;
  sf[cAN_F][9] = 0.0;

  sf[cAN_K][0] = 8.218599;
  sf[cAN_K][1] = 12.794900;
  sf[cAN_K][2] = 7.439800;
  sf[cAN_K][3] = 0.774800;
  sf[cAN_K][4] = 1.051900;
  sf[cAN_K][5] = 213.186996;
  sf[cAN_K][6] = 0.865900;
  sf[cAN_K][7] = 41.684097;
  sf[cAN_K][8] = 1.422800;
  sf[cAN_K][9] = 0.0;

  sf[cAN_Mg][0] = 5.420400;
  sf[cAN_Mg][1] = 2.827500;
  sf[cAN_Mg][2] = 2.173500;
  sf[cAN_Mg][3] = 79.261101;
  sf[cAN_Mg][4] = 1.226900;
  sf[cAN_Mg][5] = 0.380800;
  sf[cAN_Mg][6] = 2.307300;
  sf[cAN_Mg][7] = 7.193700;
  sf[cAN_Mg][8] = 0.858400;
  sf[cAN_Mg][9] = 0.0;

  sf[cAN_Na][0] = 4.762600;
  sf[cAN_Na][1] = 3.285000;
  sf[cAN_Na][2] = 3.173600;
  sf[cAN_Na][3] = 8.842199;
  sf[cAN_Na][4] = 1.267400;
  sf[cAN_Na][5] = 0.313600;
  sf[cAN_Na][6] = 1.112800;
  sf[cAN_Na][7] = 129.423996;
  sf[cAN_Na][8] = 0.676000;
  sf[cAN_Na][9] = 0.0;

  sf[cAN_P][0] = 6.434500;
  sf[cAN_P][1] = 1.906700;
  sf[cAN_P][2] = 4.179100;
  sf[cAN_P][3] = 27.157000;
  sf[cAN_P][4] = 1.780000;
  sf[cAN_P][5] = 0.526000;
  sf[cAN_P][6] = 1.490800;
  sf[cAN_P][7] = 68.164497;
  sf[cAN_P][8] = 1.114900;
  sf[cAN_P][9] = 0.0;

  sf[cAN_Zn][0] = 14.074300;
  sf[cAN_Zn][1] = 3.265500;
  sf[cAN_Zn][2] = 7.031800;
  sf[cAN_Zn][3] = 0.233300;
  sf[cAN_Zn][4] = 5.162500;
  sf[cAN_Zn][5] = 10.316299;
  sf[cAN_Zn][6] = 2.410000;
  sf[cAN_Zn][7] = 58.709702;
  sf[cAN_Zn][8] = 1.304100;
  sf[cAN_Zn][9] = 0.0;

  sf[cAN_Ca][0] = 8.626600;
  sf[cAN_Ca][1] = 10.442100;
  sf[cAN_Ca][2] = 7.387300;
  sf[cAN_Ca][3] = 0.659900;
  sf[cAN_Ca][4] = 1.589900;
  sf[cAN_Ca][5] = 85.748398;
  sf[cAN_Ca][6] = 1.021100;
  sf[cAN_Ca][7] = 178.436996;
  sf[cAN_Ca][8] = 1.375100;
  sf[cAN_Ca][9] = 0.0;

  sf[cAN_Cu][0] = 13.337999;
  sf[cAN_Cu][1] = 3.582800;
  sf[cAN_Cu][2] = 7.167600;
  sf[cAN_Cu][3] = 0.247000;
  sf[cAN_Cu][4] = 5.615800;
  sf[cAN_Cu][5] = 11.396600;
  sf[cAN_Cu][6] = 1.673500;
  sf[cAN_Cu][7] = 64.812599;
  sf[cAN_Cu][8] = 1.191000;
  sf[cAN_Cu][9] = 0.0;

  sf[cAN_Fe][0] = 11.769500;
  sf[cAN_Fe][1] = 4.761100;
  sf[cAN_Fe][2] = 7.357300;
  sf[cAN_Fe][3] = 0.307200;
  sf[cAN_Fe][4] = 3.522200;
  sf[cAN_Fe][5] = 15.353500;
  sf[cAN_Fe][6] = 2.304500;
  sf[cAN_Fe][7] = 76.880501;
  sf[cAN_Fe][8] = 1.036900;
  sf[cAN_Fe][9] = 0.0;

  sf[cAN_Se][0] = 17.000599;
  sf[cAN_Se][1] = 2.409800;
  sf[cAN_Se][2] = 5.819600;
  sf[cAN_Se][3] = 0.272600;
  sf[cAN_Se][4] = 3.973100;
  sf[cAN_Se][5] = 15.237200;
  sf[cAN_Se][6] = 4.354300;
  sf[cAN_Se][7] = 43.816299;
  sf[cAN_Se][8] = 2.840900;
  sf[cAN_Se][9] = 0.0;

  buffer += MAX_VDW;
  c = 0;
  n1 = 0;
  if(state >= cSelectorUpdateTableEffectiveStates) {
    SelectorUpdateTable(G, state, -1);
  } else {
    SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);
  }
  for(a = 0; a < I->NAtom; a++) {
    at = I->Table[a].atom;
    obj = I->Obj[I->Table[a].model];
    s = obj->AtomInfo[at].selEntry;
    if(SelectorIsMember(G, s, sele1)) {
      once_flag = true;
      for(state1 = 0; state1 < obj->NCSet; state1++) {
        if(state < 0)
          once_flag = false;
        if(!once_flag)
          state2 = state1;
        else
          state2 = state;
        if(state2 < obj->NCSet)
          cs = obj->CSet[state2];
        else
          cs = NULL;
        if(cs) {
          idx = cs->atmToIdx(at);
          if(idx >= 0) {
            n1++;
          }
        }
        if(once_flag)
          break;
      }
    }
  }
  point = Alloc(float, 3 * n1);
  sfidx = Alloc(int, n1);
  b_factor = Alloc(float, n1);
  occup = Alloc(float, n1);
  atom_sf = Alloc(AtomSF, n1);

  if(!quiet) {
    PRINTFB(G, FB_ObjectMap, FB_Details)
      " ObjectMap: Computing Gaussian map for %d atom positions.\n", n1 ENDFB(G);
  }

  n1 = 0;
  fp = point;
  ip = sfidx;
  bf = b_factor;
  oc = occup;
  for(a = 0; a < I->NAtom; a++) {
    at = I->Table[a].atom;
    obj = I->Obj[I->Table[a].model];
    ai = obj->AtomInfo + at;
    s = ai->selEntry;
    if(SelectorIsMember(G, s, sele1)) {
      once_flag = true;
      for(state1 = 0; state1 < obj->NCSet; state1++) {
        if(state < 0)
          once_flag = false;
        if(!once_flag)
          state2 = state1;
        else
          state2 = state;
        if(state2 < obj->NCSet)
          cs = obj->CSet[state2];
        else
          cs = NULL;
        if(cs) {
          if(CoordSetGetAtomVertex(cs, at, fp)) {
            prot = ai->protons;
            if(sf[prot][0] == -1.0F)
              prot = cAN_C;
            bfact = ai->b + (float) b_adjust;
            if(bfact < b_floor)
              bfact = b_floor;
            if((bfact > R_SMALL4) && (ai->q > R_SMALL4)) {
              fp += 3;
              *(ip++) = prot;
              *(bf++) = bfact;
              *(oc++) = ai->q;
              n1++;
            }
          }
        }
        if(once_flag)
          break;
      }
    }
  }

  for(a = 0; a < n1; a++) {
    double *src_sf;

    src_sf = &sf[sfidx[a]][0];
    bfact = b_factor[a];

    for(b = 0; b < 10; b += 2) {
      double sfa, sfb;
      sfa = src_sf[b];
      sfb = src_sf[b + 1];

      atom_sf[a][b] = occup[a] * sfa * pow(sqrt1d(4 * PI / (sfb + bfact)), 3.0);
      atom_sf[a][b + 1] = 4 * PI * PI / (sfb + bfact);

    }

    rcut2 = max6d(0.0,
                  (elim + log(max2d(fabs(atom_sf[a][0]), D_SMALL10))) / atom_sf[a][1],
                  (elim + log(max2d(fabs(atom_sf[a][2]), D_SMALL10))) / atom_sf[a][3],
                  (elim + log(max2d(fabs(atom_sf[a][4]), D_SMALL10))) / atom_sf[a][5],
                  (elim + log(max2d(fabs(atom_sf[a][6]), D_SMALL10))) / atom_sf[a][7],
                  (elim + log(max2d(fabs(atom_sf[a][8]), D_SMALL10))) / atom_sf[a][9]);
    rcut = ((float) sqrt1d(rcut2)) / blur_factor;
    atom_sf[a][10] = rcut;
    if(max_rcut < rcut)
      max_rcut = rcut;
  }

  /* now create and apply voxel map */
  c = 0;
  if(n1) {
    n2 = 0;
    map = MapNew(G, -max_rcut, point, n1, NULL);
    if(map) {
      MapSetupExpress(map);
      sum = 0.0;
      sumsq = 0.0;
      for(a = oMap->Min[0]; a <= oMap->Max[0]; a++) {
        OrthoBusyFast(G, a - oMap->Min[0], oMap->Max[0] - oMap->Min[0] + 1);
        for(b = oMap->Min[1]; b <= oMap->Max[1]; b++) {
          for(c = oMap->Min[2]; c <= oMap->Max[2]; c++) {
            e_val = 0.0;
            v2 = F4Ptr(oMap->Field->points, a, b, c, 0);
            if(MapExclLocus(map, v2, &h, &k, &l)) {
              i = *(MapEStart(map, h, k, l));
              if(i) {
                j = map->EList[i++];
                if(use_max) {
                  float e_partial;
                  while(j >= 0) {
                    d = (float) diff3f(point + 3 * j, v2) * blur_factor;        
                    /* scale up width */
                    sfp = atom_sf[j];
                    if(d < sfp[10]) {
                      d = d * d;
                      if(d < R_SMALL8)
                        d = R_SMALL8;
                      e_partial = (float) ((sfp[0] * exp(-sfp[1] * d))
                                           + (sfp[2] * exp(-sfp[3] * d))
                                           + (sfp[4] * exp(-sfp[5] * d))
                                           + (sfp[6] * exp(-sfp[7] * d))
                                           + (sfp[8] * exp(-sfp[9] * d))) * blur_factor;       
                      /* scale down intensity */
                      if(e_partial > e_val)
                        e_val = e_partial;
                    }
                    j = map->EList[i++];
                  }
                } else {
                  while(j >= 0) {
                    d = (float) diff3f(point + 3 * j, v2) * blur_factor;       
                    /* scale up width */
                    sfp = atom_sf[j];
                    if(d < sfp[10]) {
                      d = d * d;
                      if(d < R_SMALL8)
                        d = R_SMALL8;
                      e_val += (float) ((sfp[0] * exp(-sfp[1] * d))
                                        + (sfp[2] * exp(-sfp[3] * d))
                                        + (sfp[4] * exp(-sfp[5] * d))
                                        + (sfp[6] * exp(-sfp[7] * d))
                                        + (sfp[8] * exp(-sfp[9] * d))) * blur_factor;  
                      /* scale down intensity */
                    }
                    j = map->EList[i++];
                  }
                }
              }
            }
            F3(oMap->Field->data, a, b, c) = e_val;
            sum += e_val;
            sumsq += (e_val * e_val);
            n2++;
          }
        }
      }
      mean = (float) (sum / n2);
      stdev = (float) sqrt1d((sumsq - (sum * sum / n2)) / (n2 - 1));
      if(normalize) {

        if(!quiet) {
          PRINTFB(G, FB_ObjectMap, FB_Details)
            " ObjectMap: Normalizing: mean = %8.6f & stdev = %8.6f.\n", mean, stdev
            ENDFB(G);
        }

        if(stdev < R_SMALL8)
          stdev = R_SMALL8;

        for(a = oMap->Min[0]; a <= oMap->Max[0]; a++) {
          for(b = oMap->Min[1]; b <= oMap->Max[1]; b++) {
            for(c = oMap->Min[2]; c <= oMap->Max[2]; c++) {
              fp = F3Ptr(oMap->Field->data, a, b, c);

              *fp = (*fp - mean) / stdev;
            }
          }
        }
      } else {
        if(!quiet) {
          PRINTFB(G, FB_ObjectMap, FB_Details)
            " ObjectMap: Not normalizing: mean = %8.6f and stdev = %8.6f.\n",
            mean, stdev ENDFB(G);
        }
      }
      oMap->Active = true;
      MapFree(map);
    }
  }
  FreeP(point);
  FreeP(sfidx);
  FreeP(atom_sf);
  FreeP(b_factor);
  FreeP(occup);
  return (c);
}


/*========================================================================*/
int SelectorMapCoulomb(PyMOLGlobals * G, int sele1, ObjectMapState * oMap,
                       float cutoff, int state, int neutral, int shift, float shift_power)
{
  CSelector *I = G->Selector;
  MapType *map;
  float *v2;
  int a, b, c, j, i;
  int h, k, l;
  int at;
  int s, idx;
  AtomInfoType *ai;
  ObjectMolecule *obj;
  CoordSet *cs;
  int state1, state2;
  int once_flag;
  int n_at = 0;
  double tot_charge = 0.0;
  float *point = NULL;
  float *charge = NULL;
  int n_point = 0;
  int n_occur;
  float *v0, *v1;
  float c_factor = 1.0F;
  float cutoff_to_power = 1.0F;
  const float _1 = 1.0F;

  if(shift)
    cutoff_to_power = (float) pow(cutoff, shift_power);

  c_factor = SettingGetGlobal_f(G, cSetting_coulomb_units_factor) /
             SettingGetGlobal_f(G, cSetting_coulomb_dielectric);

  c = 0;
  SelectorUpdateTable(G, state, -1);

  point = VLAlloc(float, I->NAtom * 3);
  charge = VLAlloc(float, I->NAtom);

  /* first count # of times each atom appears */

  for(a = cNDummyAtoms; a < I->NAtom; a++) {
    at = I->Table[a].atom;
    obj = I->Obj[I->Table[a].model];
    s = obj->AtomInfo[at].selEntry;
    ai = obj->AtomInfo + at;
    if(SelectorIsMember(G, s, sele1)) {
      n_occur = 0;
      /* count */
      once_flag = true;
      for(state2 = 0; state2 < obj->NCSet; state2++) {
        if(state < 0)
          once_flag = false;
        if(!once_flag)
          state1 = state2;
        else
          state1 = state;
        if(state1 < obj->NCSet)
          cs = obj->CSet[state1];
        else
          cs = NULL;
        if(cs) {
          idx = cs->atmToIdx(at);
          if(idx >= 0) {
            n_occur++;
            n_at++;
          }
        }
        if(once_flag)
          break;
      }
      /* copy */
      if(n_occur) {
        once_flag = true;
        for(state2 = 0; state2 < obj->NCSet; state2++) {
          if(state < 0)
            once_flag = false;
          if(!once_flag)
            state1 = state2;
          else
            state1 = state;
          if(state1 < obj->NCSet)
            cs = obj->CSet[state1];
          else
            cs = NULL;
          if(cs) {
            idx = cs->atmToIdx(at);
            if(idx >= 0) {
              VLACheck(point, float, 3 * n_point + 2);
              VLACheck(charge, float, n_point);
              v0 = cs->Coord + (3 * idx);
              v1 = point + 3 * n_point;
              copy3f(v0, v1);
              charge[n_point] = ai->partialCharge * ai->q / n_occur;

              tot_charge += charge[n_point];
              n_point++;
            }
          }
          if(once_flag)
            break;
        }
      }
    }
  }

  PRINTFB(G, FB_Selector, FB_Details)
    " SelectorMapCoulomb: Total charge is %0.3f for %d points (%d atoms).\n", tot_charge,
    n_point, n_at ENDFB(G);

  if(neutral && (fabs(tot_charge) > R_SMALL4)) {
    float adjust;

    adjust = (float) (-tot_charge / n_point);

    for(a = 0; a < n_point; a++) {
      charge[a] += adjust;
    }

    PRINTFB(G, FB_Selector, FB_Details)
      " SelectorMapCoulomb: Setting net charge to zero...\n" ENDFB(G);

  }

  for(a = 0; a < n_point; a++) {        /* premultiply c_factor by charges */
    charge[a] *= c_factor;
  }

  /* now create and apply voxel map */
  c = 0;
  if(n_point) {
    int *min = oMap->Min;
    int *max = oMap->Max;
    CField *data = oMap->Field->data;
    CField *points = oMap->Field->points;
    float dist;

    if(cutoff > 0.0F) {         /* we are using a cutoff */
      if(shift) {
        PRINTFB(G, FB_Selector, FB_Details)
          " SelectorMapCoulomb: Evaluating local Coulomb potential for grid (shift=%0.2f)...\n",
          cutoff ENDFB(G);
      } else {
        PRINTFB(G, FB_Selector, FB_Details)
          " SelectorMapCoulomb: Evaluating Coulomb potential for grid (cutoff=%0.2f)...\n",
          cutoff ENDFB(G);
      }

      map = MapNew(G, -(cutoff), point, n_point, NULL);
      if(map) {
        int *elist;
        float dx, dy, dz;
        float cut = cutoff;
        float cut2 = cutoff * cutoff;

        MapSetupExpress(map);
        elist = map->EList;
        for(a = min[0]; a <= max[0]; a++) {
          OrthoBusyFast(G, a - min[0], max[0] - min[0] + 1);
          for(b = min[1]; b <= max[1]; b++) {
            for(c = min[2]; c <= max[2]; c++) {
              F3(data, a, b, c) = 0.0F;
              v2 = F4Ptr(points, a, b, c, 0);

              if(MapExclLocus(map, v2, &h, &k, &l)) {
                i = *(MapEStart(map, h, k, l));
                if(i) {
                  j = elist[i++];
                  while(j >= 0) {
                    v1 = point + 3 * j;
                    while(1) {

                      dx = v1[0] - v2[0];
                      dy = v1[1] - v2[1];
                      dx = (float) fabs(dx);
                      dy = (float) fabs(dy);
                      if(dx > cut)
                        break;
                      dz = v1[2] - v2[2];
                      dx = dx * dx;
                      if(dy > cut)
                        break;
                      dz = (float) fabs(dz);
                      dy = dy * dy;
                      if(dz > cut)
                        break;
                      dx = dx + dy;
                      dz = dz * dz;
                      if(dx > cut2)
                        break;
                      dy = dx + dz;
                      if(dy > cut2)
                        break;
                      dist = (float) sqrt1f(dy);

                      if(dist > R_SMALL4) {
                        if(shift) {
                          if(dist < cutoff) {
                            F3(data, a, b, c) += (charge[j] / dist) *
                              (_1 - (float) pow(dist, shift_power) / cutoff_to_power);
                          }
                        } else {
                          F3(data, a, b, c) += charge[j] / dist;
                        }
                      }

                      break;
                    }
                    j = map->EList[i++];
                  }
                }
              }
            }
          }
        }
        MapFree(map);
      }
    } else {
      float *v1;
      PRINTFB(G, FB_Selector, FB_Details)
        " SelectorMapCoulomb: Evaluating Coulomb potential for grid (no cutoff)...\n"
        ENDFB(G);

      for(a = min[0]; a <= max[0]; a++) {
        OrthoBusyFast(G, a - min[0], max[0] - min[0] + 1);
        for(b = min[1]; b <= max[1]; b++) {
          for(c = min[2]; c <= max[2]; c++) {
            F3(data, a, b, c) = 0.0F;
            v1 = point;
            v2 = F4Ptr(points, a, b, c, 0);
            for(j = 0; j < n_point; j++) {
              dist = (float) diff3f(v1, v2);
              v1 += 3;
              if(dist > R_SMALL4) {
                F3(data, a, b, c) += charge[j] / dist;
              }
            }
          }
        }
      }
    }
    oMap->Active = true;
  }
  VLAFreeP(point);
  VLAFreeP(charge);
  return (1);
}


/*========================================================================*/
int SelectorAssignAtomTypes(PyMOLGlobals * G, int sele, int state, int quiet, int format)
{
#ifndef NO_MMLIBS
  CSelector *I = G->Selector;
  int ok = true;

  SelectorUpdateTable(G, state, -1);

  if(ok) {
    ObjectMolecule *prevobj = NULL;
    int a;
    for(a = cNDummyAtoms; a < I->NAtom; a++) {
      int at = I->Table[a].atom;
      ObjectMolecule *obj = I->Obj[I->Table[a].model];
      int s = obj->AtomInfo[at].selEntry;
      I->Table[a].index = 0;
      if(SelectorIsMember(G, s, sele)) {
	ObjectMoleculeInvalidateAtomType(obj, state);
      }
    }
    for(a = cNDummyAtoms; a < I->NAtom; a++) {
      int at = I->Table[a].atom;
      ObjectMolecule *obj = I->Obj[I->Table[a].model];
      int s = obj->AtomInfo[at].selEntry;
      I->Table[a].index = 0;
      if(obj != prevobj && SelectorIsMember(G, s, sele)) {
	ObjectMoleculeUpdateAtomTypeInfoForState(G, obj, state, 1, format);
          prevobj = obj;
      }
    }
  }
  return 1;
#else
  if (format != 1) {
    PRINTFB(G, FB_Selector, FB_Errors)
      " Error: assign_atom_types only supports format='mol2'\n" ENDFB(G);
    return 0;
  }

  SelectorUpdateTable(G, state, -1);

  ObjectMolecule *obj = NULL;

  for (SeleAtomIterator iter(G, sele); iter.next();) {
    if (obj != iter.obj) {
      obj = iter.obj;
      ObjectMoleculeVerifyChemistry(obj, state);
    }

    LexAssign(G, iter.getAtomInfo()->textType,
        getMOL2Type(obj, iter.getAtm()));
  }
  return 1;
#endif
}

/*========================================================================*/
/*
 * Get selection coordinates as Nx3 numpy array. Equivalent to
 *
 * PyMOL> coords = []
 * PyMOL> cmd.iterate_state(state, sele, 'coords.append([x,y,z])')
 * PyMOL> coords = numpy.array(coords)
 */
PyObject *SelectorGetCoordsAsNumPy(PyMOLGlobals * G, int sele, int state)
{
#ifndef _PYMOL_NUMPY
  printf("No numpy support\n");
  return NULL;
#else

  double matrix[16];
  double *matrix_ptr = NULL;
  float *v_ptr, v_tmp[3], *dataptr;
  int i, nAtom = 0;
  int typenum = -1;
  const int base_size = sizeof(float);
  SeleCoordIterator iter(G, sele, state);
  CoordSet *mat_cs = NULL;
  PyObject *result = NULL;
  npy_intp dims[2] = {0, 3};

  for(iter.reset(); iter.next();)
    nAtom++;

  if(!nAtom)
    return NULL;

  dims[0] = nAtom;

  import_array1(NULL);

  switch(base_size) {
    case 4: typenum = NPY_FLOAT32; break;
    case 8: typenum = NPY_FLOAT64; break;
  }

  if(typenum == -1) {
    printf("error: no typenum for float size %d\n", base_size);
    return NULL;
  }

  result = PyArray_SimpleNew(2, dims, typenum);
  dataptr = (float*) PyArray_DATA((PyArrayObject *)result);

  for(i = 0, iter.reset(); iter.next(); i++) {
    v_ptr = iter.getCoord();

    if(mat_cs != iter.cs) {
      /* compute the effective matrix for output coordinates */
      matrix_ptr = ObjectGetTotalMatrix(&iter.obj->Obj, state, false, matrix) ? matrix : NULL;
      mat_cs = iter.cs;
    }

    if(matrix_ptr) {
      transform44d3f(matrix_ptr, v_ptr, v_tmp);
      v_ptr = v_tmp;
    }

    copy3f(v_ptr, dataptr + i * 3);
  }

  return result;
#endif
}

/*========================================================================*/
/*
 * Load coordinates from a Nx3 sequence into the given selection.
 * Most efficiant with numpy arrays. Equivalent to
 *
 * PyMOL> coords = iter(coords)
 * PyMOL> cmd.alter_state(state, sele, '(x,y,z) = coords.next()')
 */
int SelectorLoadCoords(PyMOLGlobals * G, PyObject * coords, int sele, int state)
{
#ifdef _PYMOL_NOPY
  return false;
#else

  double matrix[16];
  double *matrix_ptr = NULL;
  float v_xyz[3];
  int a, b, nAtom = 0, itemsize;
  SeleCoordIterator iter(G, sele, state);
  CoordSet *mat_cs = NULL;
  PyObject *v, *w;
  bool is_np_array = false;
  void * ptr;

  if(!PySequence_Check(coords)) {
    ErrMessage(G, "LoadCoords", "passed argument is not a sequence");
    ok_raise(1);
  }

  // atom count in selection
  while(iter.next())
    nAtom++;

  // sequence length must match atom count
  if(nAtom != PySequence_Size(coords)) {
    ErrMessage(G, "LoadCoords", "atom count mismatch");
    return false;
  }

  // detect numpy arrays, allows faster data access (see below)
#ifdef _PYMOL_NUMPY
  import_array1(false);

  if(PyArray_Check(coords)) {
    if(PyArray_NDIM((PyArrayObject *)coords) != 2 ||
        PyArray_DIM((PyArrayObject *)coords, 1) != 3) {
      ErrMessage(G, "LoadCoords", "numpy array shape mismatch");
      return false;
    }
    itemsize = PyArray_ITEMSIZE((PyArrayObject *)coords);
    switch(itemsize) {
      case sizeof(double):
      case sizeof(float):
        is_np_array = true;
        break;
      default:
        PRINTFB(G, FB_Selector, FB_Warnings)
          " LoadCoords-Warning: numpy array with unsupported dtype\n" ENDFB(G);
    }
  }
#endif

  for(a = 0, iter.reset(); iter.next(); a++) {
    // get xyz from python
    if (is_np_array) {
      // fast implementation for numpy arrays only
#ifdef _PYMOL_NUMPY
      for(b = 0; b < 3; b++) {
        ptr = PyArray_GETPTR2((PyArrayObject *)coords, a, b);

        switch(itemsize) {
          case sizeof(double):
            v_xyz[b] = (float) *((double*)ptr);
            break;
          default:
            v_xyz[b] = *((float*)ptr);
        }
      }
#endif
    } else {
      // general implementation for any 2d sequence
      v = PySequence_ITEM(coords, a);

      // get xyz from python sequence item
      for(b = 0; b < 3; b++) {
        if(!(w = PySequence_GetItem(v, b)))
          break;

        v_xyz[b] = (float) PyFloat_AsDouble(w);
        Py_DECREF(w);
      }

      Py_DECREF(v);
    }

    ok_assert(2, !PyErr_Occurred());

    // coord set specific stuff
    if(mat_cs != iter.cs) {
      // update matrix
      matrix_ptr = ObjectGetTotalMatrix(&iter.obj->Obj, state, false, matrix) ? matrix : NULL;
      mat_cs = iter.cs;

      // invalidate reps
      iter.cs->invalidateRep(cRepAll, cRepInvRep);
    }

    // handle matrix
    if(matrix_ptr) {
      inverse_transform44d3f(matrix_ptr, v_xyz, v_xyz);
    }

    // copy coordinates
    copy3f(v_xyz, iter.getCoord());
  }

  return true;

  // error handling
ok_except2:
  PyErr_Print();
ok_except1:
  ErrMessage(G, "LoadCoords", "failed");
  return false;
#endif
}

/*========================================================================*/
void SelectorUpdateCmd(PyMOLGlobals * G, int sele0, int sele1, int sta0, int sta1,
                       int matchmaker, int quiet)
{
  CSelector *I = G->Selector;
  int a, b;
  int at0 = 0, at1;
  int *vla0 = NULL;
  int *vla1 = NULL;
  int c0 = 0, c1 = 0;
  int i0 = 0, i1;
  ObjectMolecule *obj0 = NULL, *obj1;
  CoordSet *cs0;
  const CoordSet *cs1;
  int matched_flag;
  int b_start;
  int ccc = 0;

  bool ignore_case = SettingGetGlobal_b(G, cSetting_ignore_case);
  bool ignore_case_chain = SettingGetGlobal_b(G, cSetting_ignore_case_chain);

  PRINTFD(G, FB_Selector)
    " SelectorUpdateCmd-Debug: entered sta0 %d sta1 %d", sta0, sta1 ENDFD;

  // either both or none must be "all states"
  if (sta0 != sta1) {
    if (sta0 == cSelectorUpdateTableAllStates) {
      sta0 = sta1;
    } else if (sta1 == cSelectorUpdateTableAllStates) {
      sta1 = sta0;
    }
  }

  if((sta0 < 0) || (sta1 < 0) || (sta0 != sta1)) {
    SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);
  } else {
    SelectorUpdateTable(G, sta0, -1);
  }

  vla0 = SelectorGetIndexVLA(G, sele0);
  vla1 = SelectorGetIndexVLA(G, sele1);

  if (vla0 && vla1) {
    c0 = VLAGetSize(vla0);
    c1 = VLAGetSize(vla1);
  }

  if (c0 < 1 || c1 < 1)
    ErrMessage(G, "Update", "no coordinates updated.");
  else {

    b = 0;
    for(a = 0; a < c1; a++) {   /* iterate over source atoms */
      /* NOTE, this algorithm is N^2 and slow in the worst case...
         however the best case (N) is quite common, especially when merging 
         files written out of PyMOL */

      i1 = vla1[a];
      at1 = I->Table[i1].atom;
      obj1 = I->Obj[I->Table[i1].model];
      matched_flag = false;

      switch (matchmaker) {
      case 0:                 
        /* simply assume that atoms are stored in PyMOL in the identical order, one for one */
        if(b < c0) {
          i0 = vla0[b];
          at0 = I->Table[i0].atom;
          obj0 = I->Obj[I->Table[i0].model];
          b++;
          matched_flag = true;
        }
        break;
      case 1:                  
        /* match each pair based on atom info */
        b_start = b;
        matched_flag = false;
        while(1) {
          i0 = vla0[b];
          at0 = I->Table[i0].atom;
          obj0 = I->Obj[I->Table[i0].model];
          if(obj0 != obj1) {
            if(AtomInfoMatch(G, obj1->AtomInfo + at1, obj0->AtomInfo + at0, ignore_case, ignore_case_chain)) {
              matched_flag = true;
              break;
            }
          } else if(at0 == at1) {
            matched_flag = true;
            break;
          }
          b++;
          if(b >= c0)
            b = 0;
          if(b == b_start)
            break;
        }
        break;
      case 2:                  /* match based on ID */
        {
          int target = obj1->AtomInfo[at1].id;
          b_start = b;
          matched_flag = false;
          while(1) {
            i0 = vla0[b];
            at0 = I->Table[i0].atom;
            obj0 = I->Obj[I->Table[i0].model];
            if(obj0 != obj1) {
              if(obj0->AtomInfo[at0].id == target) {
                matched_flag = true;
                break;
              }
            } else if(at0 == at1) {
              matched_flag = true;
              break;
            }
            b++;
            if(b >= c0)
              b = 0;
            if(b == b_start)
              break;
          }
        }
        break;
      case 3:                  /* match based on rank */
        {
          int target = obj1->AtomInfo[at1].rank;
          b_start = b;
          matched_flag = false;
          while(1) {
            i0 = vla0[b];
            at0 = I->Table[i0].atom;
            obj0 = I->Obj[I->Table[i0].model];
            if(obj0 != obj1) {
              if(obj0->AtomInfo[at0].rank == target) {
                matched_flag = true;
                break;
              }
            } else if(at0 == at1) {
              matched_flag = true;
            }
            b++;
            if(b >= c0)
              b = 0;
            if(b == b_start)
              break;
          }
        }
        break;
      case 4:                  /* match based on index */
        {
          b_start = b;
          matched_flag = false;
          while(1) {
            i0 = vla0[b];
            at0 = I->Table[i0].atom;
            obj0 = I->Obj[I->Table[i0].model];
            if(obj0 != obj1) {
              if(at0 == at1) {
                matched_flag = true;
                break;
              }
            } else if(at0 == at1) {
              matched_flag = true;
              break;
            }
            b++;
            if(b >= c0)
              b = 0;
            if(b == b_start)
              break;
          }
        }
        break;
      }

      if(matched_flag) {        /* atom matched, so copy coordinates */
        ccc++;

        StateIterator iter0(G, obj0->Obj.Setting, sta0, obj0->NCSet);
        StateIterator iter1(G, obj1->Obj.Setting, sta1, obj1->NCSet);

        while (iter0.next() && iter1.next()) {
          cs0 = obj0->CSet[iter0.state];
          cs1 = obj1->CSet[iter1.state];
          if (cs1 && cs0) {
            int idx0 = cs0->atmToIdx(at0);
            int idx1 = cs1->atmToIdx(at1);
            if (idx0 >= 0 && idx1 >= 0) {
              copy3f(cs1->coordPtr(idx1), cs0->coordPtr(idx0));
            }
          }
        }
      }
    }
    obj0 = NULL;

    {
      ObjectMolecule **objs = SelectorGetObjectMoleculeVLA(G, sele0);
      int sz = VLAGetSize(objs);
      for(b = 0; b < sz; b++) {
	ObjectMoleculeInvalidate(objs[b], cRepAll, cRepInvCoord, -1);
      }
      VLAFree(objs);
    }
    SceneChanged(G);
    if(!quiet) {
      PRINTFB(G, FB_Selector, FB_Actions)
        " Update: coordinates updated for %d atoms.\n", ccc ENDFB(G);

    }
  }
  VLAFreeP(vla0);
  VLAFreeP(vla1);
}


/*========================================================================*/

int SelectorCreateObjectMolecule(PyMOLGlobals * G, int sele, const char *name,
                                 int target, int source, int discrete,
                                 int zoom, int quiet, int singletons, int copy_properties)
{
  CSelector *I = G->Selector;
  int ok = true;
  int a, b, a2, b1, b2, c, d, s, at;
  BondType *ii1, *bond = NULL;
  int nBond = 0;
  int nCSet, nAtom, ts;
  AtomInfoType *atInfo = NULL;
  int isNew;
  CoordSet *cs = NULL;
  CoordSet *cs1, *cs2;
  ObjectMolecule *obj;
  CObject *ob;
  ObjectMolecule *targ = NULL;
  ObjectMolecule *info_src = NULL;
  int static_singletons = SettingGetGlobal_b(G, cSetting_static_singletons);

  if(singletons < 0)
    singletons = static_singletons;

  ob = ExecutiveFindObjectByName(G, name);
  if(ob)
    if(ob->type == cObjectMolecule)
      targ = (ObjectMolecule *) ob;

  c = 0;
    SelectorUpdateTable(G, source, -1);

  if(!targ) {
    isNew = true;
    if(discrete < 0)
      discrete = SelectorIsSelectionDiscrete(G, sele, false);
    targ = ObjectMoleculeNew(G, discrete);
    targ->Bond = VLACalloc(BondType, 1);
    {
      /* copy object color of previous object (if any) */
      ObjectMolecule *singleObj = NULL;
      for(a = cNDummyAtoms; a < I->NAtom; a++) {
        at = I->Table[a].atom;
        I->Table[a].index = -1;
        obj = I->Obj[I->Table[a].model];
        s = obj->AtomInfo[at].selEntry;
        if(SelectorIsMember(G, s, sele)) {
          if(!singleObj)
            singleObj = obj;
          else if(singleObj && (obj != singleObj)) {
            singleObj = NULL;
            break;
          }
        }
      }
      if(singleObj)
        targ->Obj.Color = singleObj->Obj.Color;
      /* should also consider copying lots of other stuff from the source object ... */
    }
  } else {
    isNew = false;
  }

  for(a = cNDummyAtoms; a < I->NAtom; a++) {
    at = I->Table[a].atom;
    I->Table[a].index = -1;
    obj = I->Obj[I->Table[a].model];
    s = obj->AtomInfo[at].selEntry;
    if(SelectorIsMember(G, s, sele)) {
      I->Table[a].index = c;    /* Mark records as to which atom they'll be */
      c++;
      if(!info_src)
        info_src = obj;
    }
  }
  if(isNew && info_src) {       /* copy symmetry information, etc. */
    targ->Symmetry = SymmetryCopy(info_src->Symmetry);
  }
  nAtom = c;

  nBond = 0;
  bond = VLACalloc(BondType, nAtom * 4);
  for(a = cNDummyModels; a < I->NModel; a++) {  /* find bonds wholly contained in the selection */
    obj = I->Obj[a];
    ii1 = obj->Bond;
    for(b = 0; b < obj->NBond; b++) {
      b1 = SelectorGetObjAtmOffset(I, obj, ii1->index[0]);
      b2 = SelectorGetObjAtmOffset(I, obj, ii1->index[1]);
      if((b1 >= 0) && (b2 >= 0)) {
        if((I->Table[b1].index >= 0) && (I->Table[b2].index >= 0)) {
          VLACheck(bond, BondType, nBond);
          {
            BondType *dst_bond = bond + nBond;
            AtomInfoBondCopy(G, ii1, dst_bond);
            dst_bond->index[0] = I->Table[b1].index;    /* store what will be the new index */
            dst_bond->index[1] = I->Table[b2].index;
            /*            printf("Selector-DEBUG %d %d\n",dst_bond->index[0],dst_bond->index[1]); */
            nBond++;
          }
        }
      }
      ii1++;
    }
  }

  atInfo = VLACalloc(AtomInfoType, nAtom);
  /* copy the atom info records and create new zero-based IDs */
  c = 0;
  {
    for(a = cNDummyAtoms; a < I->NAtom; a++) {
      if(I->Table[a].index >= 0) {
        obj = I->Obj[I->Table[a].model];
        at = I->Table[a].atom;
        VLACheck(atInfo, AtomInfoType, c);
        AtomInfoCopy(G, obj->AtomInfo + at, atInfo + c);
        c++;
      }
    }
  }

  cs = CoordSetNew(G);          /* set up a dummy coordinate set for the merge xref */
  cs->NIndex = nAtom;
  cs->enumIndices();
  cs->TmpBond = bond;           /* load up the bonds */
  cs->NTmpBond = nBond;
  bond = NULL;

  /*  printf("Selector-DEBUG nAtom %d\n",nAtom); */
  ObjectMoleculeMerge(targ, atInfo, cs, false, cAIC_AllMask, true);     /* will free atInfo */
  /* cs->IdxToAtm will now have the reverse mapping from the new subset
     to the new merged molecule */

  ObjectMoleculeExtendIndices(targ, -1);
  ObjectMoleculeUpdateIDNumbers(targ);
  ObjectMoleculeUpdateNonbonded(targ);

  if(!isNew) {                  /* recreate selection table */

      SelectorUpdateTable(G, source, -1);

  }

  /* get maximum state index for the selection...note that
     we'll be creating states from 1 up to the maximum required to
     capture atoms in the selection 
   */

  nCSet = 0;

  {
    c = 0;
    for(a = cNDummyAtoms; a < I->NAtom; a++) {
      at = I->Table[a].atom;
      I->Table[a].index = -1;
      obj = I->Obj[I->Table[a].model];
      s = obj->AtomInfo[at].selEntry;
      if(SelectorIsMember(G, s, sele)) {
        I->Table[a].index = c;  /* Mark records  */
        if(nCSet < obj->NCSet)
          nCSet = obj->NCSet;
        c++;
      }
    }
  }

  if(c != nAtom)
    ErrFatal(G, "SelectorCreate", "inconsistent selection.");
  /* cs->IdxToAtm now has the relevant indexes for the coordinate transfer */

  for(StateIterator iter(G, NULL, source, nCSet); iter.next();) {
    d = iter.state;
    {
      cs2 = CoordSetNew(G);
      c = 0;
      cs2->Coord = VLAlloc(float, 3 * nAtom);
      cs2->AtmToIdx = VLACalloc(int, targ->NAtom + 1);
      for(a = 0; a < targ->NAtom; a++)
        cs2->AtmToIdx[a] = -1;
      cs2->NAtIndex = targ->NAtom;
      cs2->IdxToAtm = VLACalloc(int, nAtom);
      for(a = cNDummyAtoms; a < I->NAtom; a++)  /* any selected atoms in this state? */
        if(I->Table[a].index >= 0) {
          at = I->Table[a].atom;
          obj = I->Obj[I->Table[a].model];
          cs1 = NULL;
          if(d < obj->NCSet) {
            cs1 = obj->CSet[d];
          } else if(singletons && (obj->NCSet == 1)) {
            cs1 = obj->CSet[0];
          }
          if(cs1) {
            if((!cs2->Name[0]) && (cs1->Name[0]))       /* copy the molecule name (if any) */
              strcpy(cs2->Name, cs1->Name);

            if(CoordSetGetAtomVertex(cs1, at, cs2->Coord + c * 3)) {
              a2 = cs->IdxToAtm[I->Table[a].index];     /* actual merged atom index */
              cs2->IdxToAtm[c] = a2;
              cs2->AtmToIdx[a2] = c;
              c++;
            }
          }
        }
      VLASize(cs2->IdxToAtm, int, c);
      VLASize(cs2->Coord, float, c * 3);
      cs2->NIndex = c;
      if(target >= 0) {
        if(source == -1)
          ts = target + d;
        else
          ts = target;
      } else {
        ts = d;
      }
      VLACheck(targ->CSet, CoordSet *, ts);
      if(targ->NCSet <= ts)
        targ->NCSet = ts + 1;
      if(targ->CSet[ts])
        targ->CSet[ts]->fFree();
      targ->CSet[ts] = cs2;
      cs2->Obj = targ;
    }
  }
  VLAFreeP(bond);               /* null-safe */
  if(cs)
    cs->fFree();
  if(targ->DiscreteFlag) {      /* if the new object is discrete, then eliminate the AtmToIdx array */
    for(d = 0; d < targ->NCSet; d++) {
      cs = targ->CSet[d];
      if(cs) {
        if(cs->AtmToIdx) {
          for(a = 0; a < cs->NIndex; a++) {
            b = cs->IdxToAtm[a];
            targ->DiscreteAtmToIdx[b] = a;
            targ->DiscreteCSet[b] = cs;
          }
          VLAFree(cs->AtmToIdx);
          cs->AtmToIdx = NULL;
        }
      }
    }
  }
  SceneCountFrames(G);
  if(!quiet) {
    PRINTFB(G, FB_Selector, FB_Details)
      " Selector: found %d atoms.\n", nAtom ENDFB(G);
  }
  if (ok)
    ok &= ObjectMoleculeSort(targ);
  if(isNew) {
    ObjectSetName((CObject *) targ, name);
    ExecutiveManageObject(G, (CObject *) targ, zoom, quiet);
  } else {
    ExecutiveUpdateObjectSelection(G, (CObject *) targ);
  }
  SceneChanged(G);
  return ok;
}


/*========================================================================*/
int SelectorSetName(PyMOLGlobals * G, const char *new_name, const char *old_name)
{
  CSelector *I = G->Selector;
  ov_diff i;
  int ignore_case = SettingGetGlobal_b(G, cSetting_ignore_case);

  i = SelectGetNameOffset(G, old_name, 1, ignore_case);
  if(i >= 0) {
    SelectorDelName(G, i);
    UtilNCopy(I->Name[i], new_name, WordLength);
    SelectorAddName(G, i);
    return true;
  } else {
    return false;
  }
}


/*========================================================================
 * SelectorIndexByName -- fetch the global selector's ID for sname
 * PARAMS
 *  (string) sname, object name
 * RETURNS
 *   (int) index #, or -1 if not found
 */
int SelectorIndexByName(PyMOLGlobals * G, const char *sname, int ignore_case)
{
  CSelector *I = G->Selector;
  ov_diff i = -1;

  if(sname) {
    if (ignore_case < 0)
      ignore_case = SettingGetGlobal_b(G, cSetting_ignore_case);

    while((sname[0] == '%') || (sname[0] == '?'))
      sname++;
    i = SelectGetNameOffset(G, sname, 1, ignore_case);

    if((i >= 0) && (sname[0] != '_')) {  /* don't do checking on internal selections */
      const char *best;
      best = ExecutiveFindBestNameMatch(G, sname);      /* suppress spurious matches
                                                           of selections with non-selections */
      if((best != sname) && (strcmp(best, I->Name[i])))
        i = -1;
    }
    if(i >= 0)
      i = I->Info[i].ID;
  }
  return (i);
}


/*========================================================================*/
static void SelectorPurgeMembers(PyMOLGlobals * G, int sele)
{
  CSelector *I = G->Selector;
  void *iterator = NULL;
  ObjectMolecule *obj = NULL;
  short changed = 0;

  if(I->Member) {
    MemberType *I_Member = I->Member;
    int I_FreeMember = I->FreeMember;

    while(ExecutiveIterateObjectMolecule(G, &obj, &iterator)) {
      if(obj->Obj.type == cObjectMolecule) {
        AtomInfoType *ai = obj->AtomInfo;
        int a, n_atom = obj->NAtom;
        for(a = 0; a < n_atom; a++) {
          int s = (ai++)->selEntry;
          int l = -1;
          while(s) {
            MemberType *i_member_s = I_Member + s;
            int nxt = i_member_s->next;
            if(i_member_s->selection == sele) {
              if(l > 0)
                I_Member[l].next = i_member_s->next;
              else
                ai[-1].selEntry = i_member_s->next;
	      changed = 1;
              i_member_s->next = I_FreeMember;
              I_FreeMember = s;
            }
            l = s;
            s = nxt;
          }
        }
      }
    }
    I->FreeMember = I_FreeMember;
  }
  if (changed){
    // not sure if this is needed since its in SelectorClean()
    ExecutiveInvalidateSelectionIndicatorsCGO(G);
  }
}


/*========================================================================*/
int SelectorPurgeObjectMembers(PyMOLGlobals * G, ObjectMolecule * obj)
{
  int a = 0;
  int s = 0;
  int nxt;
  short changed = 0;

  CSelector *I = G->Selector;
  if(I->Member) {
    for(a = 0; a < obj->NAtom; a++) {
      s = obj->AtomInfo[a].selEntry;
      while(s) {
        nxt = I->Member[s].next;
        I->Member[s].next = I->FreeMember;
        I->FreeMember = s;
        s = nxt;
      }
      obj->AtomInfo[a].selEntry = 0;
      changed = 1;
    }
  }
  if (changed){
    // not sure if this is needed since its in SelectorClean()
    ExecutiveInvalidateSelectionIndicatorsCGO(G);
  }

  return 1;
}


/*========================================================================*/
void SelectorDelete(PyMOLGlobals * G, const char *sele)


/* should (only) be called by Executive or by Selector, unless the

   named selection has never been registered with the executive 

   (i.e., temporary on-the-fly selection) */
{
  ov_diff n;
  n = SelectGetNameOffset(G, sele, 999, SettingGetGlobal_b(G, cSetting_ignore_case));   /* already exist? */
  if(n >= 0) {                  /* get rid of existing selection -- but never selection 0 (all) */
    SelectorDeleteSeleAtOffset(G, n);
  }
}


/*========================================================================*/
void SelectorGetUniqueTmpName(PyMOLGlobals * G, char *name_buffer)
{
  CSelector *I = G->Selector;
  sprintf(name_buffer, "%s%d", cSelectorTmpPrefix, I->TmpCounter++);
}

/*========================================================================*/
/*
 * If `input` is already a name of an object or a valid position keyword
 * (center, origin, all, ...), then simply copy it to `store`. Otherwise
 * process the selection expression and create a temporary named selection.
 *
 * Unlike `SelectorGetTmp`, this will accept names of maps, groups, etc.
 */
int SelectorGetTmp2(PyMOLGlobals * G, const char *input, char *store, bool quiet)
{
  int count = 0;
  /* ASSUMES that store is at least as big as an OrthoLineType */
  CSelector *I = G->Selector;
  PRINTFD(G, FB_Selector)
    " SelectorGetTmp-Debug: entered with \"%s\".\n", input ENDFD;

  store[0] = 0;

  /* skip trivial cases */

  if(input[0] && !((input[0] == '\'') && (input[1] == '\'') && (!input[2]))) {

    /* OKAY, this routine is in flux.  Eventually this routine will...

       (1) fully parse the input recognizing selection keywords, nested
       parens, quotes, escaped strings, etc.

       (2) replace selection blocks with temporary selection names

       (3) return a space-separated list of names for processing

       However, right now, this routine simply handles two cases.

       A. where the input is a selection, in which case store is set to a
       temporary selection name

       B. where the input is simply a list of space-separated name patterns,
       in which case store is simply passed along as a copy of the input

     */

    // make selection if "input" doesn't fit into "store"
    int is_selection = strlen(input) >= OrthoLineLength;

    const char *p = input;
    OrthoLineType word;
    OVreturn_word result;

    if (!is_selection) while(*p) {
      /* copy first word/token of p into "word", remainder of string in p */
      p = ParseWord(word, p, sizeof(OrthoLineType));
      /* see a paren? then this must be a selection */

      if(word[0] == '(') {
        is_selection = true;
        break;
      }

      if(strchr(word, '/')) {
        is_selection = true;
        break;
      }

      /* encounterd a selection keyword? then this must be a selection */

      if(OVreturn_IS_OK((result = OVLexicon_BorrowFromCString(I->Lex, word)))) {
        if(OVreturn_IS_OK((result = OVOneToAny_GetKey(I->Key, result.word)))) {
          if((result.word != SELE_ALLz) &&
             (result.word != SELE_ORIz) && (result.word != SELE_CENz)) {
            is_selection = true;
            break;
          }
        }
      }

      if(!ExecutiveValidName(G, word)) {        /* don't recognize the name? */
        if(!ExecutiveValidNamePattern(G, word)) {       /* don't recognize this as a pattern? */
          is_selection = true;  /* must be a selection */
          break;
        }
      }
    }
    if(is_selection) {          /* incur the computational expense of 
                                   parsing the input as an atom selection */
      WordType name;
      sprintf(name, "%s%d", cSelectorTmpPrefix, I->TmpCounter++);
      count = SelectorCreate(G, name, input, NULL, quiet, NULL);
      if(count >= 0) {
        strcpy(store, name);
      } else {
        store[0] = 0;
      }
    } else {                    /* otherwise, just parse the input as a space-separated list of names */
      /* not a selection */
      strcpy(store, input);
    }
  }
  PRINTFD(G, FB_Selector)
    " SelectorGetTmp-Debug: leaving with \"%s\".\n", store ENDFD;
  return count;

}

/*========================================================================*/
/*
 * Like SelectorGetTmp2, but doesn't accept names from any non-molecular
 * entities like groups or map objects (those will be processed as selection
 * expressions).
 */
int SelectorGetTmp(PyMOLGlobals * G, const char *input, char *store, bool quiet)
{
  int count = 0;
  CSelector *I = G->Selector;

  store[0] = 0;

  // trivial (but valid) case: empty selection string
  ok_assert(1, input[0]);

  // if object molecule or named selection, then don't create a temp selection
  if (ExecutiveIsMoleculeOrSelection(G, input)
      && strncmp(input, cSelectorTmpPrefix, cSelectorTmpPrefixLen) != 0) {
    strcpy(store, input);
    return 0;
  }

  // evaluate expression and create a temp selection
  sprintf(store, "%s%d", cSelectorTmpPrefix, I->TmpCounter++);
  count = SelectorCreate(G, store, input, NULL, quiet, NULL);
  if(count < 0)
    store[0] = 0;

ok_except1:
  return count;
}

int SelectorCheckTmp(PyMOLGlobals * G, const char *name)
{
  if(WordMatch(G, cSelectorTmpPattern, name, false) + 1 ==
     - cSelectorTmpPrefixLen)
    return true;
  else
    return false;
}


/*========================================================================*/
void SelectorFreeTmp(PyMOLGlobals * G, const char *name)
{                               /* remove temporary selections */
  if(name && name[0]) {
    if(strncmp(name, cSelectorTmpPrefix, cSelectorTmpPrefixLen) == 0) {
      ExecutiveDelete(G, name);
    }
  }
}


/*========================================================================*/
static int SelectorEmbedSelection(PyMOLGlobals * G, int *atom, const char *name,
                                  ObjectMolecule * obj, int no_dummies, int exec_managed)
{
  /* either atom or obj should be NULL, not both and not neither */

  CSelector *I = G->Selector;
  int tag;
  int newFlag = true;
  int n, a, m, sele;
  int c = 0;
  int start = 0;
  int singleAtomFlag = true;
  int singleObjectFlag = true;
  ObjectMolecule *singleObject = NULL, *selObj;
  int singleAtom = -1;
  int index;
  AtomInfoType *ai;

  if(exec_managed < 0) {
    if(atom)                    /* automatic behavior: manage selections defined via atom masks */
      exec_managed = true;
    else
      exec_managed = false;
  }

  n = SelectGetNameOffset(G, name, 999, SettingGetGlobal_b(G, cSetting_ignore_case));   /* already exist? */
  if(n == 0)                    /* don't allow redefinition of "all" */
    return 0;
  if(n > 0) {                   /* get rid of existing selection */
    SelectorDeleteSeleAtOffset(G, n);
    newFlag = false;
  }

  /*  printf("I->NMember %d I->FreeMember %d\n",I->NMember,I->FreeMember); */

  n = I->NActive;
  /* make sure there's enough space for n+1 SelectorWordTypes
   * and SelectorInfoRecs in the Selector */
  VLACheck(I->Name, SelectorWordType, n + 1);
  VLACheck(I->Info, SelectionInfoRec, n + 1);
  /* copy the name and null terminate it */
  strcpy(I->Name[n], name);
  I->Name[n + 1][0] = 0;
  /* This name is in Name, so now set it in the OVLexicon hash */
  SelectorAddName(G, n);
  sele = I->NSelection++;
  SelectionInfoInit(I->Info + n);
  I->Info[n].ID = sele;
  I->NActive++;

  if(no_dummies) {
    start = 0;
  } else {
    start = cNDummyAtoms;
  }
  for(a = start; a < I->NAtom; a++) {
    tag = false;
    /* set tag based on passed in atom list or on global atom table */
    if(atom) {
      if(atom[a])
        tag = atom[a];
    } else {
      if(I->Obj[I->Table[a].model] == obj)
        tag = 1;
    }
    if(tag) {
      /* if this this atom is tagged, grab its object
       * index, and info record */
      selObj = I->Obj[I->Table[a].model];
      index = I->Table[a].atom;
      ai = selObj->AtomInfo + index;

      /* update whether or not this is a selection w/only one object */
      if(singleObjectFlag) {
        if(singleObject) {
          if(selObj != singleObject) {
            singleObjectFlag = false;
          }
        } else {
          singleObject = selObj;
        }
      }
      /* update whether or not this is a selection w/only one atom */
      if(singleAtomFlag) {
        if(singleAtom >= 0) {
          if(index != singleAtom) {
            singleAtomFlag = false;
          }
        } else {
          singleAtom = index;
        }
      }

      /* store this is the Selectors->Member table, so make sure there's room */
      c++;
      if(I->FreeMember > 0) {
        m = I->FreeMember;
        I->FreeMember = I->Member[m].next;
      } else {
        I->NMember++;
        m = I->NMember;
        VLACheck(I->Member, MemberType, m);
      }
      I->Member[m].selection = sele;
      I->Member[m].tag = tag;
      /* at runtime, selections can now have transient ordering --
         but these are not yet persistent through session saves & restores */
      I->Member[m].next = ai->selEntry;
      ai->selEntry = m;
    }
  }

  /* after scanning, update whether or not we touched multiple objects/atoms */
  if(c) {                       
    SelectionInfoRec *info = I->Info + (I->NActive - 1);
    if(singleObjectFlag) {
      info->justOneObjectFlag = true;
      info->theOneObject = singleObject;
      if(singleAtomFlag) {
        info->justOneAtomFlag = true;
        info->theOneAtom = singleAtom;
      }
    }
  }

  if(exec_managed) {
    if(newFlag)
      ExecutiveManageSelection(G, name);
  }
  PRINTFD(G, FB_Selector)
    " Selector: Embedded %s, %d atoms.\n", name, c ENDFD;
  return (c);
}


/*========================================================================*/
static int *SelectorApplyMultipick(PyMOLGlobals * G, Multipick * mp)
{
  CSelector *I = G->Selector;
  int *result;
  int a, n;
  Picking *p;
  ObjectMolecule *obj;
  SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);
  result = Alloc(int, I->NAtom);
  n = mp->picked[0].src.index;
  p = mp->picked + 1;
  for(a = 0; a < I->NAtom; a++)
    result[a] = 0;
  while(n--) {                  /* what if this object isn't a molecule object?? */
    obj = (ObjectMolecule *) p->context.object;
    /* NOTE: SeleBase only safe with cSelectorUpdateTableAllStates!  */
    result[obj->SeleBase + p->src.index] = true;
    p++;
  }
  return (result);
}


/*========================================================================*/
static int *SelectorSelectFromTagDict(PyMOLGlobals * G, OVOneToAny * id2tag)
{
  CSelector *I = G->Selector;
  int *result = NULL;
  int a;
  AtomInfoType *ai;
  OVreturn_word ret;

  SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);    /* for now, update the entire table */
  {
    TableRec *i_table = I->Table, *table_a;
    ObjectMolecule **i_obj = I->Obj;

    result = Calloc(int, I->NAtom);
    if(result) {
      table_a = i_table + cNDummyAtoms;
      for(a = cNDummyAtoms; a < I->NAtom; a++) {
        ai = i_obj[table_a->model]->AtomInfo + table_a->atom;
        if(ai->unique_id) {
          if(!OVreturn_IS_ERROR(ret = OVOneToAny_GetKey(id2tag, ai->unique_id)))
            result[a] = ret.word;
        }
        table_a++;
      }
    }
  }
  return (result);
}


/*========================================================================*/

static int _SelectorCreate(PyMOLGlobals * G, const char *sname, const char *sele,
                           ObjectMolecule ** obj, int quiet, Multipick * mp,
                           CSeqRow * rowVLA, int nRow, int **obj_idx, int *n_idx,
                           int n_obj, OVOneToAny * id2tag, int executive_manage,
                           int state, int domain)
{
  int *atom = NULL;
  OrthoLineType name;
  int ok = true;
  int c = 0;
  int ignore_case = SettingGetGlobal_b(G, cSetting_ignore_case);
  ObjectMolecule *embed_obj = NULL;

  PRINTFD(G, FB_Selector)
    "SelectorCreate-Debug: entered...\n" ENDFD;

  /* copy sname into name and check if it's a keyword; abort on 
   * the selection name == keyword: eg. "select all, none" */
  if(sname[0] == '%')
    strcpy(name, &sname[1]);
  else
    strcpy(name, sname);
  if(WordMatchExact(G, cKeywordAll, name, ignore_case)) {
    name[0] = 0;                /* force error */
  }
  UtilCleanStr(name);
  /* name was invalid, output error msg to user */
  if(!name[0]) {
      PRINTFB(G, FB_Selector, FB_Errors)
	"Selector-Error: Invalid selection name \"%s\".\n", sname ENDFB(G);
  }
  if(ok) {
    if(sele) {
      atom = SelectorSelect(G, sele, state, domain, quiet);
      if(!atom)
        ok = false;
    } else if(id2tag) {
      atom = SelectorSelectFromTagDict(G, id2tag);
    } else if(obj && obj[0]) {  /* optimized full-object selection */
      if(n_obj <= 0) {
        embed_obj = *obj;
        if(obj_idx && n_idx) {
          atom =
            SelectorUpdateTableSingleObject(G, embed_obj, cSelectorUpdateTableAllStates,
                                            false, *obj_idx, *n_idx, (n_obj == 0));
        } else {
          atom =
            SelectorUpdateTableSingleObject(G, embed_obj, cSelectorUpdateTableAllStates,
                                            false, NULL, 0, (n_obj == 0));
        }
      } else {
        atom = SelectorUpdateTableMultiObjectIdxTag(G, obj, false, obj_idx, n_idx, n_obj);
      }
    } else if(mp) {
      atom = SelectorApplyMultipick(G, mp);
#if 0
    } else if(rowVLA) {
      atom = SelectorApplySeqRowVLA(G, rowVLA, nRow);
#endif
    } else
      ok = false;
  }
  if(ok)
    c = SelectorEmbedSelection(G, atom, name, embed_obj, false, executive_manage);
  FreeP(atom);
  SelectorClean(G);
  /* ignore reporting on quiet */
  if(!quiet) {
    /* ignore reporting on internal/private names */
    if(name[0] != '_') {
      if(ok) {
        PRINTFB(G, FB_Selector, FB_Actions)
          " Selector: selection \"%s\" defined with %d atoms.\n", name, c ENDFB(G);
      }
    }
  }
  if(ok) {
    PRINTFD(G, FB_Selector)
      " SelectorCreate: \"%s\" created with %d atoms.\n", name, c ENDFD;
  } else {
    PRINTFD(G, FB_Selector)
      " SelectorCreate: \"%s\" not created due to error\n", name ENDFD;
  }
  if(!ok)
    c = -1;
  return (c);
}

int SelectorCreateFromTagDict(PyMOLGlobals * G, const char *sname, OVOneToAny * id2tag,
                              int exec_managed)
{
  return _SelectorCreate(G, sname, NULL, NULL, true, NULL, NULL, 0, NULL, NULL, 0, id2tag,
                         exec_managed, -1, -1);
}

int SelectorCreateEmpty(PyMOLGlobals * G, const char *name, int exec_managed)
{
  return _SelectorCreate(G, name, "none", NULL, 1, NULL, NULL, 0, NULL, 0, 0, NULL,
                         exec_managed, -1, -1);
}

int SelectorCreateSimple(PyMOLGlobals * G, const char *name, const char *sele)
{
  return _SelectorCreate(G, name, sele, NULL, 1, NULL, NULL, 0, NULL, 0, 0, NULL, -1, -1,
                         -1);
}

int SelectorCreateFromObjectIndices(PyMOLGlobals * G, const char *sname, ObjectMolecule * obj,
                                    int *idx, int n_idx)
{
  return _SelectorCreate(G, sname, NULL, &obj, true, NULL, NULL, 0, &idx, &n_idx, -1, NULL, -1, -1, -1); 
  /* n_obj = -1 disables numbered tags */
}

int SelectorCreateOrderedFromObjectIndices(PyMOLGlobals * G, const char *sname,
                                           ObjectMolecule * obj, int *idx, int n_idx)
{
  return _SelectorCreate(G, sname, NULL, &obj, true, NULL, NULL, 0, &idx, &n_idx, 0, NULL, -1, -1, -1);
  /* assigned numbered tags */
}

int SelectorCreateOrderedFromMultiObjectIdxTag(PyMOLGlobals * G, const char *sname,
                                               ObjectMolecule ** obj,
                                               int **idx_tag, int *n_idx, int n_obj)
{
  return _SelectorCreate(G, sname, NULL, obj, true, NULL, NULL, 0, idx_tag, n_idx, n_obj,
                         NULL, -1, -1, -1);
}

int SelectorCreate(PyMOLGlobals * G, const char *sname, const char *sele, ObjectMolecule * obj,
                   int quiet, Multipick * mp)
{
  return _SelectorCreate(G, sname, sele, &obj, quiet, mp, NULL, 0, NULL, 0, 0, NULL, -1,
                         -1, -1);
}

int SelectorCreateWithStateDomain(PyMOLGlobals * G, const char *sname, const char *sele,
                                  ObjectMolecule * obj, int quiet, Multipick * mp,
                                  int state, const char *domain)
{
  int domain_sele = -1;
  ObjectNameType valid_name;

  UtilNCopy(valid_name, sname, sizeof(valid_name));
  if(SettingGetGlobal_b(G, cSetting_validate_object_names)) {
    ObjectMakeValidName(G, valid_name);
    sname = valid_name;
  }

  if(domain && domain[0]) {
    if(!WordMatchExact(G, cKeywordAll, domain, true)) { /* allow domain=all */
      domain_sele = SelectorIndexByName(G, domain);
      if(domain_sele < 0) {

        PRINTFB(G, FB_Selector, FB_Errors)
          "Selector-Error: Invalid domain selection name \"%s\".\n", domain ENDFB(G);
        return -1;
      }
    }
  }
  return _SelectorCreate(G, sname, sele, &obj, quiet, mp, NULL, 0, NULL, 0, 0, NULL, -1,
                         state, domain_sele);
}


/*========================================================================*/
static void SelectorClean(PyMOLGlobals * G)
{
  SelectorCleanImpl(G, G->Selector);
}

static void SelectorCleanImpl(PyMOLGlobals * G, CSelector *I)
{
  FreeP(I->Table);
  I->Table = NULL;
  FreeP(I->Obj);
  I->Obj = NULL;
  FreeP(I->Vertex);
  I->Vertex = NULL;
  FreeP(I->Flag1);
  I->Flag1 = NULL;
  FreeP(I->Flag2);
  I->Flag2 = NULL;
  I->NAtom = 0;
  ExecutiveInvalidateSelectionIndicatorsCGO(G);
}


/*========================================================================*/
static int *SelectorUpdateTableSingleObject(PyMOLGlobals * G, ObjectMolecule * obj,
                                            int req_state,
                                            int no_dummies, int *idx,
                                            int n_idx, int numbered_tags)
{
  int a = 0;
  int c = 0;
  int modelCnt;
  int *result = NULL;
  int tag = true;
  int state = req_state;
  CSelector *I = G->Selector;

  PRINTFD(G, FB_Selector)
    "SelectorUpdateTableSingleObject-Debug: entered for %s...\n", obj->Obj.Name ENDFD;

  SelectorClean(G);

  switch (req_state) {
  case cSelectorUpdateTableAllStates:
    state = req_state;
    break;
  case cSelectorUpdateTableEffectiveStates:
    state = ObjectGetCurrentState(&obj->Obj, true);
    break;
  case cSelectorUpdateTableCurrentState:
    state = SceneGetState(G);
    break;
  default:
    if(req_state < 0)
      state = cSelectorUpdateTableAllStates;    /* fail safe */
    break;
  }

  switch (req_state) {
  case cSelectorUpdateTableAllStates:
    I->SeleBaseOffsetsValid = true;     /* all states -> all atoms -> offsets valid */
    break;
  default:
    I->SeleBaseOffsetsValid = false;    /* not including all atoms, so atom-based offsets are invalid */
    break;
  }

  I->NCSet = 0;
  if(no_dummies) {
    modelCnt = 0;
    c = 0;
  } else {
    modelCnt = cNDummyModels;
    c = cNDummyAtoms;
  }
  c += obj->NAtom;
  if(I->NCSet < obj->NCSet)
    I->NCSet = obj->NCSet;
  modelCnt++;
  I->Table = Calloc(TableRec, c);
  ErrChkPtr(G, I->Table);
  I->Obj = Calloc(ObjectMolecule *, modelCnt);
  ErrChkPtr(G, I->Obj);
  if(no_dummies) {
    modelCnt = 0;
    c = 0;
  } else {
    c = cNDummyAtoms;
    modelCnt = cNDummyModels;
  }
  I->Obj[modelCnt] = obj;

  obj->SeleBase = c;

  if(state < 0) {
    for(a = 0; a < obj->NAtom; a++) {
      I->Table[c].model = modelCnt;
      I->Table[c].atom = a;
      c++;
    }
  } else if(state < obj->NCSet) {
    TableRec *rec = I->Table + c;
    CoordSet *cs = obj->CSet[state];
    if(cs) {
      for(a = 0; a < obj->NAtom; a++) {
        int ix;
        ix = cs->atmToIdx(a);
        if(ix >= 0) {
          rec->model = modelCnt;
          rec->atom = a;
          rec++;
        }
      }
    }
    c = rec - I->Table;
  }

  if(idx && n_idx) {
    result = Calloc(int, c);
    if(n_idx > 0) {
      for(a = 0; a < n_idx; a++) {
        int at = idx[a];
        if(numbered_tags)
          tag = a + SELECTOR_BASE_TAG;
        if((at >= 0) && (at < obj->NAtom)) {
          /* create an ordered selection based on the input order of the object indices */
          result[obj->SeleBase + at] = tag;
        }
      }
    } else {                    /* -1 terminated list */
      int *at_idx = idx;
      int at;
      a = SELECTOR_BASE_TAG + 1;
      while((at = *(at_idx++)) >= 0) {
        if(numbered_tags) {
          tag = a++;
        }
        if((at >= 0) && (at < obj->NAtom)) {
          /* create an ordered selection based on the input order of the object indices */
          result[obj->SeleBase + at] = tag;
        }
      }
    }
  }
  modelCnt++;
  I->NModel = modelCnt;
  I->NAtom = c;
  I->Flag1 = Alloc(int, c);
  ErrChkPtr(G, I->Flag1);
  I->Flag2 = Alloc(int, c);
  ErrChkPtr(G, I->Flag2);
  I->Vertex = Alloc(float, c * 3);
  ErrChkPtr(G, I->Vertex);

  PRINTFD(G, FB_Selector)
    "SelectorUpdateTableSingleObject-Debug: leaving...\n" ENDFD;

  return (result);
}


/*========================================================================*/
int SelectorUpdateTable(PyMOLGlobals * G, int req_state, int domain)
{
  return (SelectorUpdateTableImpl(G, G->Selector, req_state, domain));
}

int SelectorUpdateTableImpl(PyMOLGlobals * G, CSelector *I, int req_state, int domain)
{
  int a = 0;
  ov_size c = 0;
  int modelCnt;
  int state = req_state;
  void *iterator = NULL;
  ObjectMolecule *obj = NULL;

  /* Origin and Center are dummy objects */
  if(!I->Origin)
    I->Origin = ObjectMoleculeDummyNew(G, cObjectMoleculeDummyOrigin);

  if(!I->Center)
    I->Center = ObjectMoleculeDummyNew(G, cObjectMoleculeDummyCenter);

  SelectorClean(G);
  I->NCSet = 0;

  /* take a summary of PyMOL's current state; foreach molecular object
   * sum up the number of atoms, count how many models, states, etc... */
  modelCnt = cNDummyModels;
  c = cNDummyAtoms;
  while(ExecutiveIterateObjectMolecule(G, &obj, &iterator)) {
    c += obj->NAtom;
    if(I->NCSet < obj->NCSet)
      I->NCSet = obj->NCSet;
    modelCnt++;
  }
  /* allocate space for each atom, in the record table */
  I->Table = Calloc(TableRec, c);
  ErrChkPtr(G, I->Table);
  I->Obj = Calloc(ObjectMolecule *, modelCnt);
  ErrChkPtr(G, I->Obj);

  switch (req_state) {
  case cSelectorUpdateTableAllStates:
    I->SeleBaseOffsetsValid = true;     /* all states -> all atoms -> offsets valid */
    break;
  default:
    I->SeleBaseOffsetsValid = false;    /* not including all atoms, so atom-based offsets are invalid */
    break;
  }

  c = 0;
  modelCnt = 0;

  /* update the origin and center dummies */
  obj = I->Origin;
  if(obj) {
    I->Obj[modelCnt] = I->Origin;
    obj->SeleBase = c;          /* make note of where this object starts */
    for(a = 0; a < obj->NAtom; a++) {
      I->Table[c].model = modelCnt;
      I->Table[c].atom = a;
      c++;
    }
    modelCnt++;
  }

  obj = I->Center;
  if(obj) {
    I->Obj[modelCnt] = I->Center;
    obj->SeleBase = c;          /* make note of where this object starts */
    for(a = 0; a < obj->NAtom; a++) {
      I->Table[c].model = modelCnt;
      I->Table[c].atom = a;
      c++;
    }
    modelCnt++;
  }

  if(req_state < cSelectorUpdateTableAllStates) {
    state = SceneGetState(G);   /* just in case... */
  }

  while(ExecutiveIterateObjectMolecule(G, &obj, &iterator)) {
    int skip_flag = false;
    if(req_state < 0) {
      switch (req_state) {
      case cSelectorUpdateTableAllStates:
        state = -1;             /* all states */
        /* proceed... */
        break;
      case cSelectorUpdateTableCurrentState:
        state = SettingGetGlobal_i(G, cSetting_state) - 1;
        break;
      case cSelectorUpdateTableEffectiveStates:
        state = ObjectGetCurrentState(&obj->Obj, true);
        break;
      default:                 /* unknown input -- fail safe (all states) */
        state = -1;
        break;
      }
    } else {
      if(state >= obj->NCSet)
        skip_flag = true;
      else if(!obj->CSet[state])
        skip_flag = true;
    }

    if(!skip_flag) {
      /* fill in the table */
      I->Obj[modelCnt] = obj;
      {
        int n_atom = obj->NAtom;
        TableRec *rec = I->Table + c;
        TableRec *start_rec = rec;
        if(state < 0) {         /* all states */
          if(domain < 0) {      /* domain=all */
            for(a = 0; a < n_atom; a++) {
              rec->model = modelCnt;
              rec->atom = a;
              rec++;
            }
          } else {
            AtomInfoType *ai = obj->AtomInfo;
            int included_one = false;
            int excluded_one = false;
            for(a = 0; a < n_atom; a++) {
              if(SelectorIsMember(G, ai->selEntry, domain)) {
                rec->model = modelCnt;
                rec->atom = a;
                rec++;
                included_one = true;
              } else {
                excluded_one = true;
              }
              ai++;
            }
            if(included_one && excluded_one)
              I->SeleBaseOffsetsValid = false;  /* partial objects in domain, so
                                                   base offsets are invalid */
          }
        } else {                /* specific states */
          CoordSet *cs;
          int idx;
          if(domain < 0) {
            for(a = 0; a < n_atom; a++) {
              /* does coordinate exist for this atom in the requested state? */
              if(state < obj->NCSet)
                cs = obj->CSet[state];
              else
                cs = NULL;
              if(cs) {
                idx = cs->atmToIdx(a);
                if(idx >= 0) {
                  rec->model = modelCnt;
                  rec->atom = a;
                  rec++;
                }
              }
            }
          } else {
            AtomInfoType *ai = obj->AtomInfo;
            for(a = 0; a < n_atom; a++) {
              /* does coordinate exist for this atom in the requested state? */
              if(state < obj->NCSet)
                cs = obj->CSet[state];
              else
                cs = NULL;
              if(cs) {
                idx = cs->atmToIdx(a);
                if(idx >= 0) {
                  if(SelectorIsMember(G, ai->selEntry, domain)) {
                    rec->model = modelCnt;
                    rec->atom = a;
                    rec++;
                  }
                }
              }
              ai++;
            }
          }
        }
        if(rec != start_rec) {  /* skip excluded models */
          modelCnt++;
          obj->SeleBase = c;    /* make note of where this object starts */
          c += (rec - start_rec);
        } else {
          obj->SeleBase = 0;
        }
      }
    }
  }
  I->NModel = modelCnt;
  I->NAtom = c;
  I->Flag1 = Alloc(int, c);
  ErrChkPtr(G, I->Flag1);
  I->Flag2 = Alloc(int, c);
  ErrChkPtr(G, I->Flag2);
  I->Vertex = Alloc(float, c * 3);
  ErrChkPtr(G, I->Vertex);
  /* printf("selector update table state=%d, natom=%d\n",req_state,c); */
  return (true);
}


/*========================================================================*/
static int *SelectorSelect(PyMOLGlobals * G, const char *sele, int state, int domain, int quiet)
{
  SelectorWordType *parsed;
  int *result = NULL;
  PRINTFD(G, FB_Selector)
    "SelectorSelect-DEBUG: sele = \"%s\"\n", sele ENDFD;
  SelectorUpdateTable(G, state, domain);
  parsed = SelectorParse(G, sele);
  if(parsed) {
    if(Feedback(G, FB_Selector, FB_Debugging)) {
      SelectorWordType *a;
      fprintf(stderr, "SelectorSelect-DEBUG: parsed tokens:\n");
      a = parsed;
      while(1) {
        if(!a[0][0])
          break;
        fprintf(stderr, "  \"%s\"\n", (a[0]));
        a++;
      }
      fprintf(stderr, "SelectorSelect-DEBUG: end of tokens.\n");
    }
    result = SelectorEvaluate(G, parsed, state, quiet);
    VLAFreeP(parsed);
  }
  return (result);
}


/*========================================================================*/
static int SelectorModulate1(PyMOLGlobals * G, EvalElem * base, int state)
{
  CSelector *I = G->Selector;
  int a, d, e;
  int c = 0;
  float dist;
  int nbond;
  float *v2;
  CoordSet *cs;
  int ok = true;
  int nCSet;
  MapType *map;
  int i, j, h, k, l;
  int n1, at, idx;
  ObjectMolecule *obj;

  if(state < 0) {
    switch (state) {
    case -2:
    case -3:
      state = SceneGetState(G);
      break;
    }
  }

  base[1].sele = base[0].sele;  /* base1 has the mask */
  base->sele = Calloc(int, I->NAtom);
  for(a = 0; a < I->NAtom; a++)
    base[0].sele[a] = false;
  ErrChkPtr(G, base->sele);
  switch (base[1].code) {
  case SELE_ARD_:
  case SELE_EXP_:
    if(!sscanf(base[2].text, "%f", &dist))
      ok = ErrMessage(G, "Selector", "Invalid distance.");
    if(ok) {
      for(d = 0; d < I->NCSet; d++) {
        if((state < 0) || (d == state)) {
          n1 = 0;
          for(a = 0; a < I->NAtom; a++) {
            I->Flag1[a] = false;
            at = I->Table[a].atom;
            obj = I->Obj[I->Table[a].model];
            if(d < obj->NCSet)
              cs = obj->CSet[d];
            else
              cs = NULL;
            if(cs) {
              if(CoordSetGetAtomVertex(cs, at, I->Vertex + 3 * a)) {
                I->Flag1[a] = true;
                n1++;
              }
            }
          }
          if(n1) {
            map = MapNewFlagged(G, -dist, I->Vertex, I->NAtom, NULL, I->Flag1);
            if(map) {
              ok &= MapSetupExpress(map);
              nCSet = SelectorGetArrayNCSet(G, base[1].sele, false);
              for(e = 0; ok && e < nCSet; e++) {
                if((state < 0) || (e == state)) {
                  for(a = 0; ok && a < I->NAtom; a++) {
                    if(base[1].sele[a]) {
                      at = I->Table[a].atom;
                      obj = I->Obj[I->Table[a].model];
                      if(e < obj->NCSet)
                        cs = obj->CSet[e];
                      else
                        cs = NULL;
                      if(cs) {
                        idx = cs->atmToIdx(at);
                        if(idx >= 0) {
                          v2 = cs->Coord + (3 * idx);
                          MapLocus(map, v2, &h, &k, &l);
                          i = *(MapEStart(map, h, k, l));
                          if(i) {
                            j = map->EList[i++];
                            while(j >= 0) {
                              if((!base[0].sele[j]) && ((base[1].code == SELE_EXP_)
                                                        || (!base[1].sele[j]))) {   
                                /*exclude current selection */
                                if(within3f(I->Vertex + 3 * j, v2, dist))
                                  base[0].sele[j] = true;
                              }
                              j = map->EList[i++];
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
              MapFree(map);
            }
          }
        }
      }
    }
    break;

  case SELE_EXT_:
    if(sscanf(base[2].text, "%d", &nbond) != 1)
      ok = ErrMessage(G, "Selector", "Invalid bond count.");
    if(ok) {
      ObjectMolecule *lastObj = NULL;
      int a, n, a0, a1, a2;
      UtilCopyMem(base[0].sele, base[1].sele, sizeof(int) * I->NAtom);
      while((nbond--) > 0) {
        int *tmp = base[1].sele;
        base[1].sele = base[0].sele;
        base[0].sele = tmp;
        for(a = cNDummyAtoms; a < I->NAtom; a++) {
          if(base[1].sele[a]) {
            if(I->Obj[I->Table[a].model] != lastObj) {
              lastObj = I->Obj[I->Table[a].model];
              ObjectMoleculeUpdateNeighbors(lastObj);
            }
            a0 = I->Table[a].atom;
            n = lastObj->Neighbor[a0];
            n++;
            while(1) {
              a1 = lastObj->Neighbor[n];
              if(a1 < 0)
                break;
              if((a2 = SelectorGetObjAtmOffset(I, lastObj, a1)) >= 0) {
                base[0].sele[a2] = 1;
                n += 2;
              }
            }
          }
        }
      }
      FreeP(base[1].sele);
    }
    break;

  case SELE_GAP_:
    if(!sscanf(base[2].text, "%f", &dist))
      ok = ErrMessage(G, "Selector", "Invalid distance.");
    if(ok) {
      for(a = 0; a < I->NAtom; a++) {
        obj = I->Obj[I->Table[a].model];
        at = I->Table[a].atom;
        I->Table[a].f1 = obj->AtomInfo[at].vdw;
        base[0].sele[a] = true; /* start selected, subtract off */
        c = I->NAtom;
      }
      for(d = 0; d < I->NCSet; d++) {
        if((state < 0) || (d == state)) {
          n1 = 0;
          for(a = 0; a < I->NAtom; a++) {
            obj = I->Obj[I->Table[a].model];
            I->Flag1[a] = false;
            at = I->Table[a].atom;
            if(d < obj->NCSet)
              cs = obj->CSet[d];
            else
              cs = NULL;
            if(cs) {
              if(CoordSetGetAtomVertex(cs, at, I->Vertex + 3 * a)) {
                I->Flag1[a] = true;
                n1++;
              }
            }
          }
          if(n1) {
            map =
              MapNewFlagged(G, -(dist + 2 * MAX_VDW), I->Vertex, I->NAtom, NULL,
                            I->Flag1);
            if(map) {

              ok &= MapSetupExpress(map);
              nCSet = SelectorGetArrayNCSet(G, base[1].sele, false);
              for(e = 0; ok && e < nCSet; e++) {
                if((state < 0) || (e == state)) {
                  for(a = 0; a < I->NAtom; a++) {
                    if(base[1].sele[a]) {
                      at = I->Table[a].atom;
                      obj = I->Obj[I->Table[a].model];
                      if(e < obj->NCSet)
                        cs = obj->CSet[e];
                      else
                        cs = NULL;
                      if(cs) {
                        idx = cs->atmToIdx(at);

                        if(idx >= 0) {
                          v2 = cs->Coord + (3 * idx);
                          MapLocus(map, v2, &h, &k, &l);
                          i = *(MapEStart(map, h, k, l));
                          if(i) {
                            j = map->EList[i++];
                            while(j >= 0) {
                              if((base[0].sele[j]) && (!base[1].sele[j])) {     /*exclude current selection */
                                if(within3f(I->Vertex + 3 * j, v2, dist +       /* eliminate atoms w/o gap */
                                            I->Table[a].f1 + I->Table[j].f1)) {
                                  base[0].sele[j] = false;
                                  c--;
                                }
                              } else if(base[1].sele[j]) {
                                base[0].sele[j] = false;
                                c--;
                              }
                              j = map->EList[i++];
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
              MapFree(map);
            }
          }
        }
      }
    }
    break;
  }
  FreeP(base[1].sele);
  if(Feedback(G, FB_Selector, FB_Debugging)) {
    c = 0;
    for(a = cNDummyAtoms; a < I->NAtom; a++)
      if(base[0].sele[a])
        c++;
    fprintf(stderr, "SelectorModulate0: %d atoms selected.\n", c);
  }
  return (ok);

}


/*========================================================================*/
static int SelectorSelect0(PyMOLGlobals * G, EvalElem * passed_base)
{
  CSelector *I = G->Selector;
  TableRec *i_table = I->Table;
  ObjectMolecule **i_obj = I->Obj;
  int a, b, flag;
  EvalElem *base = passed_base;
  int c = 0;
  int state;
  ObjectMolecule *obj, *cur_obj = NULL;
  CoordSet *cs;

  base->type = STYP_LIST;
  base->sele = Calloc(int, I->NAtom);
  ErrChkPtr(G, base->sele);

  switch (base->code) {
  case SELE_HBAs:
  case SELE_HBDs:
  case SELE_DONz:
  case SELE_ACCz:

    {
      /* first, verify chemistry for all atoms... */
      ObjectMolecule *lastObj = NULL, *obj;
      for(a = cNDummyAtoms; a < I->NAtom; a++) {
        obj = i_obj[i_table[a].model];
        if(obj != lastObj) {
          ObjectMoleculeUpdateNeighbors(obj);
          ObjectMoleculeVerifyChemistry(obj, -1);
          lastObj = obj;
        }
      }
    }
    switch (base->code) {
    case SELE_HBAs:
    case SELE_ACCz:
      for(a = cNDummyAtoms; a < I->NAtom; a++)
        base[0].sele[a] = i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].hb_acceptor;
      break;
    case SELE_HBDs:
    case SELE_DONz:
      for(a = cNDummyAtoms; a < I->NAtom; a++)
        base[0].sele[a] = i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].hb_donor;
      break;

    }
    break;
  case SELE_NONz:
    for(a = cNDummyAtoms; a < I->NAtom; a++)
      base[0].sele[a] = false;
    break;
  case SELE_BNDz:
    for(a = cNDummyAtoms; a < I->NAtom; a++)
      base[0].sele[a] = i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].bonded;
    break;
  case SELE_HETz:
    for(a = cNDummyAtoms; a < I->NAtom; a++)
      base[0].sele[a] = i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].hetatm;
    break;
  case SELE_HYDz:
    for(a = cNDummyAtoms; a < I->NAtom; a++)
      base[0].sele[a] = i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].isHydrogen();
    break;
  case SELE_METz:
    for(a = cNDummyAtoms; a < I->NAtom; a++) {
      base[0].sele[a] = i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].isMetal();
    }
    break;
  case SELE_BB_z:
  case SELE_SC_z:
    {
      AtomInfoType *ai;
      flag = (base->code == SELE_BB_z);
      for(a = cNDummyAtoms; a < I->NAtom; a++) {
        ai = i_obj[i_table[a].model]->AtomInfo + i_table[a].atom;
        if(!(ai->flags & cAtomFlag_polymer)) {
          base[0].sele[a] = 0;
          continue;
        }
        base[0].sele[a] = !flag;
        for(b = 0; backbone_names[b][0]; b++) {
          if(!(strcmp(LexStr(G, ai->name), backbone_names[b]))) {
            base[0].sele[a] = flag;
            break;
          }
        }
      }
    }
    break;
  case SELE_FXDz:
    for(a = cNDummyAtoms; a < I->NAtom; a++)
      base[0].sele[a] =
        i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].flags & cAtomFlag_fix;
    break;
  case SELE_RSTz:
    for(a = cNDummyAtoms; a < I->NAtom; a++)
      base[0].sele[a] =
        i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].flags & cAtomFlag_restrain;
    break;
  case SELE_POLz:
    for(a = cNDummyAtoms; a < I->NAtom; a++)
      base[0].sele[a] =
        i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].flags & cAtomFlag_polymer;
    break;
  case SELE_PROz:
    for(a = cNDummyAtoms; a < I->NAtom; a++)
      base[0].sele[a] =
        i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].flags & cAtomFlag_protein;
    break;
  case SELE_NUCz:
    for(a = cNDummyAtoms; a < I->NAtom; a++)
      base[0].sele[a] =
        i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].flags & cAtomFlag_nucleic;
    break;
  case SELE_SOLz:
    for(a = cNDummyAtoms; a < I->NAtom; a++)
      base[0].sele[a] =
        i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].flags & cAtomFlag_solvent;
    break;
  case SELE_PTDz:
    for(a = cNDummyAtoms; a < I->NAtom; a++)
      base[0].sele[a] = i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].protekted;
    break;
  case SELE_MSKz:
    for(a = cNDummyAtoms; a < I->NAtom; a++)
      base[0].sele[a] = i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].masked;
    break;
  case SELE_ORGz:
    for(a = cNDummyAtoms; a < I->NAtom; a++)
      base[0].sele[a] =
        i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].flags & cAtomFlag_organic;
    break;
  case SELE_INOz:
    for(a = cNDummyAtoms; a < I->NAtom; a++)
      base[0].sele[a] =
        i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].flags & cAtomFlag_inorganic;
    break;
  case SELE_GIDz:
    for(a = cNDummyAtoms; a < I->NAtom; a++)
      base[0].sele[a] =
        i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].flags & cAtomFlag_guide;
    break;

  case SELE_PREz:
    flag = false;
    cs = NULL;
    for(a = cNDummyAtoms; a < I->NAtom; a++) {
      base[0].sele[a] = false;
      obj = i_obj[i_table[a].model];
      if(obj != cur_obj) {      /* different object */
        state = obj->getState();
        if(state >= obj->NCSet)
          flag = false;
        else if(state < 0)
          flag = false;
        else if(!obj->CSet[state])
          flag = false;
        else {
          cs = obj->CSet[state];
          flag = true;          /* valid state */
        }
        cur_obj = obj;
      }
      if(flag && cs) {
        if(cs->atmToIdx(i_table[a].atom) >= 0) {
          base[0].sele[a] = true;
          c++;
        }
      }
    }
    break;
  case SELE_ALLz:
    for(a = cNDummyAtoms; a < I->NAtom; a++) {
      base[0].sele[a] = true;
      c++;
    }
    break;
  case SELE_ORIz:
    for(a = 0; a < I->NAtom; a++) {
      base[0].sele[a] = false;
      c++;
    }
    if(I->Origin)
      ObjectMoleculeDummyUpdate(I->Origin, cObjectMoleculeDummyOrigin);
    base[0].sele[cDummyOrigin] = true;
    break;
  case SELE_CENz:
    for(a = 0; a < I->NAtom; a++) {
      base[0].sele[a] = false;
      c++;
    }
    if(I->Center)
      ObjectMoleculeDummyUpdate(I->Center, cObjectMoleculeDummyCenter);
    base[0].sele[cDummyCenter] = true;
    break;
  case SELE_VISz:
    {
      ObjectMolecule *last_obj = NULL;
      AtomInfoType *ai;
      for(a = cNDummyAtoms; a < I->NAtom; a++) {
        flag = false;
        obj = i_obj[i_table[a].model];
        if(obj->Obj.Enabled) {
          ai = obj->AtomInfo + i_table[a].atom;

          if(last_obj != obj) {
            ObjectMoleculeUpdateNeighbors(obj);
            ObjectMoleculeVerifyChemistry(obj, -1);
            last_obj = obj;
          }

          flag = ai->isVisible();
        }
        base[0].sele[a] = flag;
        if(flag)
          c++;
      }
    }
    break;
  case SELE_ENAz:
    for(a = cNDummyAtoms; a < I->NAtom; a++) {
      flag = (i_obj[i_table[a].model]->Obj.Enabled);
      base[0].sele[a] = flag;
      if(flag)
        c++;
    }
    break;
  }
  PRINTFD(G, FB_Selector)
    " SelectorSelect0: %d atoms selected.\n", c ENDFD;

  return (1);
}


/*========================================================================*/
static int SelectorSelect1(PyMOLGlobals * G, EvalElem * base, int quiet)
{
  CSelector *I = G->Selector;
  CWordMatcher *matcher = NULL;
  int a, b, c = 0, hit_flag;
  ObjectMolecule **i_obj = I->Obj, *obj, *last_obj;
  TableRec *i_table = I->Table, *table_a;
  int ignore_case = SettingGetGlobal_b(G, cSetting_ignore_case);
  int ignore_case_chain = SettingGetGlobal_b(G, cSetting_ignore_case_chain);
  int I_NAtom = I->NAtom;
  int *base_0_sele_a;

  int model, sele, s, col_idx;
  int flag;
  int ok = true;
  int index, state;
  char *np;
  int rep_mask;
  const char *wildcard = SettingGetGlobal_s(G, cSetting_wildcard);

  ObjectMolecule *cur_obj = NULL;
  CoordSet *cs = NULL;

  base->type = STYP_LIST;
  base->sele = Calloc(int, I_NAtom);    /* starting with zeros */
  PRINTFD(G, FB_Selector)
    " SelectorSelect1: base: %p sele: %p\n", (void *) base, (void *) base->sele ENDFD;
  ErrChkPtr(G, base->sele);
  switch (base->code) {
  case SELE_PEPs:
    if(base[1].text[0]) {
      AtomInfoType *last_ai0 = NULL, *ai0;
      for(a = cNDummyAtoms; a < I_NAtom; a++) {
        ai0 = i_obj[i_table[a].model]->AtomInfo + i_table[a].atom;
        if(!AtomInfoSameResidueP(G, ai0, last_ai0)) {   /* new starting residue */
          int match_found = false;
          char *ch = base[1].text;      /* sequence argument */
          AtomInfoType *ai1, *last_ai1 = NULL;
          for(b = a; b < I_NAtom; b++) {
            ai1 = i_obj[i_table[b].model]->AtomInfo + i_table[b].atom;
            if(!AtomInfoSameResidueP(G, ai1, last_ai1)) {
              if(*ch != '-') {  /* if not skipping this residue */
                if(!((*ch == '+') || (SeekerGetAbbr(G, LexStr(G, ai1->resn), 'O', 0) == *ch))) {   /* if a mismatch */
                  break;
                }
              }
              ch++;
              if(!*ch) {        /* end of sequence pattern */
                match_found = true;
                break;
              }
              last_ai1 = ai1;
            }
          }
          if(match_found) {
            char *ch = base[1].text;    /* sequence argument */
            AtomInfoType *ai1, *last_ai1 = NULL, *ai2;
            for(b = a; b < I_NAtom; b++) {
              ai1 = i_obj[i_table[b].model]->AtomInfo + i_table[b].atom;
              if(!AtomInfoSameResidueP(G, ai1, last_ai1)) {
                if(*ch != '-') {        /* if not skipping this residue */
                  if((*ch == '+') || (SeekerGetAbbr(G, LexStr(G, ai1->resn), 'O', 0) == *ch)) {    /* if matched */
                    int d;
                    for(d = b; d < I_NAtom; d++) {
                      ai2 = i_obj[i_table[d].model]->AtomInfo + i_table[d].atom;        /* complete residue */
                      if(AtomInfoSameResidue(G, ai1, ai2)) {
                        c++;
                        base[0].sele[d] = true;
                      }
                    }
                  }
                }
                ch++;
                if(!*ch) {      /* end of sequence pattern */
                  break;
                }
                last_ai1 = ai1;
              }
            }
          }
        }
      }
    }
    break;
  case SELE_IDXs:
    {
      CWordMatchOptions options;

      WordMatchOptionsConfigInteger(&options);

      if((matcher = WordMatcherNew(G, base[1].text, &options, true))) {
        table_a = i_table + cNDummyAtoms;
        base_0_sele_a = &base[0].sele[cNDummyAtoms];

        for(a = cNDummyAtoms; a < I_NAtom; a++) {
          if((*base_0_sele_a = WordMatcherMatchInteger(matcher, table_a->atom + 1)))
            c++;
          table_a++;
          base_0_sele_a++;
        }
        WordMatcherFree(matcher);
      }

    }
    break;
  case SELE_ID_s:
    {
      CWordMatchOptions options;

      WordMatchOptionsConfigInteger(&options);

      if((matcher = WordMatcherNew(G, base[1].text, &options, true))) {
        table_a = i_table + cNDummyAtoms;
        base_0_sele_a = &base[0].sele[cNDummyAtoms];

        for(a = cNDummyAtoms; a < I_NAtom; a++) {
          if((*base_0_sele_a =
              WordMatcherMatchInteger(matcher,
                                      i_obj[table_a->model]->AtomInfo[table_a->atom].id)))
            c++;
          table_a++;
          base_0_sele_a++;
        }
        WordMatcherFree(matcher);
      }
    }
    break;
  case SELE_RNKs:
    {
      CWordMatchOptions options;

      WordMatchOptionsConfigInteger(&options);

      table_a = i_table + cNDummyAtoms;
      base_0_sele_a = &base[0].sele[cNDummyAtoms];

      if((matcher = WordMatcherNew(G, base[1].text, &options, true))) {
        table_a = i_table + cNDummyAtoms;
        base_0_sele_a = &base[0].sele[cNDummyAtoms];

        for(a = cNDummyAtoms; a < I_NAtom; a++) {
          if((*base_0_sele_a =
              WordMatcherMatchInteger(matcher,
                                      i_obj[table_a->model]->AtomInfo[table_a->atom].
                                      rank)))
            c++;
          table_a++;
          base_0_sele_a++;
        }
        WordMatcherFree(matcher);
      }
    }
    break;
  case SELE_NAMs:
    WARN_IF_LOWERCASE(G, "name", base[1].text);
    {
      CWordMatchOptions options;
      const char *atom_name_wildcard = SettingGetGlobal_s(G, cSetting_atom_name_wildcard);

      if(!atom_name_wildcard[0])
        atom_name_wildcard = wildcard;

      WordMatchOptionsConfigAlphaList(&options, atom_name_wildcard[0], ignore_case);

      matcher = WordMatcherNew(G, base[1].text, &options, false);

      table_a = i_table + cNDummyAtoms;
      base_0_sele_a = &base[0].sele[cNDummyAtoms];
      last_obj = NULL;
      for(a = cNDummyAtoms; a < I_NAtom; a++) {
        obj = i_obj[table_a->model];
        if(obj != last_obj) {

          /* allow objects to have their own atom_name_wildcards...this is a tricky workaround
             for handling nucleic acid structures that use "*" in atom names */

          const char *atom_name_wildcard =
            SettingGet_s(G, obj->Obj.Setting, NULL, cSetting_atom_name_wildcard);

          if(!atom_name_wildcard[0])
            atom_name_wildcard = wildcard;

          if(options.wildcard != atom_name_wildcard[0]) {
            options.wildcard = atom_name_wildcard[0];
            if(matcher)
              WordMatcherFree(matcher);
            matcher = WordMatcherNew(G, base[1].text, &options, false);
            if(!matcher)
              WordPrimeCommaMatch(G, base[1].text);
          }
          last_obj = obj;
        }

        const char * name = LexStr(G, obj->AtomInfo[table_a->atom].name);
        if(matcher)
          hit_flag =
            WordMatcherMatchAlpha(matcher,
                                  name);
        else
          hit_flag = (WordMatchCommaExact(G, base[1].text,
                                          name,
                                          ignore_case) < 0);

        if((*base_0_sele_a = hit_flag))
          c++;
        table_a++;
        base_0_sele_a++;
      }
      if(matcher)
        WordMatcherFree(matcher);
    }
    break;
  case SELE_TTYs:
    {
      CWordMatchOptions options;

      WordMatchOptionsConfigAlphaList(&options, wildcard[0], ignore_case);

      table_a = i_table + cNDummyAtoms;
      base_0_sele_a = &base[0].sele[cNDummyAtoms];

      if((matcher = WordMatcherNew(G, base[1].text, &options, true))) {
	AtomInfoType * ai;
        int prevmodel;
        table_a = i_table + cNDummyAtoms;
        base_0_sele_a = &base[0].sele[cNDummyAtoms];

	prevmodel = -1;
        for(a = cNDummyAtoms; a < I_NAtom; a++) {
          ai = i_obj[table_a->model]->AtomInfo + table_a->atom;
#ifndef NO_MMLIBS
#endif
          if((*base_0_sele_a = WordMatcherMatchAlpha(matcher, LexStr(G, ai->textType))))
	    c++;
          table_a++;
          base_0_sele_a++;
        }
        WordMatcherFree(matcher);
      }
    }
    break;
  case SELE_ELEs:
    WARN_IF_LOWERCASE(G, "element", base[1].text);
    {
      CWordMatchOptions options;

      WordMatchOptionsConfigAlphaList(&options, wildcard[0], ignore_case);

      table_a = i_table + cNDummyAtoms;
      base_0_sele_a = &base[0].sele[cNDummyAtoms];

      if((matcher = WordMatcherNew(G, base[1].text, &options, true))) {
        table_a = i_table + cNDummyAtoms;
        base_0_sele_a = &base[0].sele[cNDummyAtoms];

        for(a = cNDummyAtoms; a < I_NAtom; a++) {
          if((*base_0_sele_a =
              WordMatcherMatchAlpha(matcher,
                                    i_obj[table_a->model]->AtomInfo[table_a->atom].elem)))
            c++;
          table_a++;
          base_0_sele_a++;
        }
        WordMatcherFree(matcher);
      }
    }
    break;
  case SELE_STRO:
    {
      CWordMatchOptions options;
      int prevmodel;
      WordMatchOptionsConfigAlphaList(&options, wildcard[0], ignore_case);

      table_a = i_table + cNDummyAtoms;
      base_0_sele_a = &base[0].sele[cNDummyAtoms];


      if((matcher = WordMatcherNew(G, base[1].text, &options, true))) {
        table_a = i_table + cNDummyAtoms;
        base_0_sele_a = &base[0].sele[cNDummyAtoms];
	prevmodel = -1;
        for(a = cNDummyAtoms; a < I_NAtom; a++) {
#ifndef NO_MMLIBS
#endif
          const char * mmstereotype =
            AtomInfoGetStereoAsStr(i_obj[table_a->model]->AtomInfo + table_a->atom);
          if((*base_0_sele_a =
              WordMatcherMatchAlpha(matcher,mmstereotype)))
            c++;
          table_a++;
          base_0_sele_a++;
        }
        WordMatcherFree(matcher);
      }
    }
    break;
  case SELE_REPs:
    rep_mask = 0;
    WordPrimeCommaMatch(G, base[1].text);
    for(a = 0; rep_names[a].word[0]; a++) {
      if(WordMatchComma(G, base[1].text, rep_names[a].word, ignore_case) < 0)
        rep_mask |= rep_names[a].value;
    }
    for(SelectorAtomIterator iter(I); iter.next();) {
      if(iter.getAtomInfo()->visRep & rep_mask) {
        base[0].sele[iter.a] = true;
        c++;
      } else {
        base[0].sele[iter.a] = false;
      }
    }
    break;
  case SELE_COLs:
    col_idx = ColorGetIndex(G, base[1].text);
    for(a = cNDummyAtoms; a < I_NAtom; a++) {
      base[0].sele[a] = false;
      if(i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].color == col_idx) {
        base[0].sele[a] = true;
        c++;
      }
    }
    break;
  case SELE_CCLs:
  case SELE_RCLs:
    // setting index
    index = (base->code == SELE_CCLs) ? cSetting_cartoon_color : cSetting_ribbon_color;
    col_idx = ColorGetIndex(G, base[1].text);
    for(a = cNDummyAtoms; a < I_NAtom; a++) {
      base[0].sele[a] = false;
      {
        AtomInfoType *ai = i_obj[i_table[a].model]->AtomInfo + i_table[a].atom;
        int value;
        if (AtomSettingGetIfDefined(G, ai, index, &value)) {
            if(value == col_idx) {
              base[0].sele[a] = true;
              c++;
            }
        }
      }
    }
    break;
  case SELE_CHNs:
  case SELE_SEGs:
  case SELE_CUST:
  case SELE_LABs:
    {
      CWordMatchOptions options;

      WordMatchOptionsConfigAlphaList(&options, wildcard[0], ignore_case_chain);

      table_a = i_table + cNDummyAtoms;
      base_0_sele_a = &base[0].sele[cNDummyAtoms];

      int offset = 0;
      switch (base->code) {
        case SELE_CHNs:
          offset = offsetof(AtomInfoType, chain);
          break;
        case SELE_SEGs:
          offset = offsetof(AtomInfoType, segi);
          break;
        case SELE_CUST:
          offset = offsetof(AtomInfoType, custom);
          break;
        case SELE_LABs:
          offset = offsetof(AtomInfoType, label);
          break;
        default:
          printf("coding error: missing case\n");
      }

      if((matcher = WordMatcherNew(G, base[1].text, &options, true))) {
        table_a = i_table + cNDummyAtoms;
        base_0_sele_a = &base[0].sele[cNDummyAtoms];

        for(a = cNDummyAtoms; a < I_NAtom; a++) {
          if((*base_0_sele_a =
              WordMatcherMatchAlpha(matcher, LexStr(G,
                  *reinterpret_cast<lexidx_t*>
                  (((char*)(i_obj[table_a->model]->AtomInfo + table_a->atom)) + offset)
                  ))))
            c++;
          table_a++;
          base_0_sele_a++;
        }
        WordMatcherFree(matcher);
      }
    }
    break;
  case SELE_SSTs:
    WARN_IF_LOWERCASE(G, "ss", base[1].text);
    {
      CWordMatchOptions options;

      WordMatchOptionsConfigAlphaList(&options, wildcard[0], ignore_case);

      table_a = i_table + cNDummyAtoms;
      base_0_sele_a = &base[0].sele[cNDummyAtoms];

      if((matcher = WordMatcherNew(G, base[1].text, &options, true))) {
        table_a = i_table + cNDummyAtoms;
        base_0_sele_a = &base[0].sele[cNDummyAtoms];

        for(a = cNDummyAtoms; a < I_NAtom; a++) {
          if((*base_0_sele_a =
              WordMatcherMatchAlpha(matcher,
                                    i_obj[table_a->model]->AtomInfo[table_a->atom].
                                    ssType)))
            c++;
          table_a++;
          base_0_sele_a++;
        }
        WordMatcherFree(matcher);
      }
    }
    break;
  case SELE_STAs:
    sscanf(base[1].text, "%d", &state);
    state = state - 1;
    obj = NULL;

    {
      auto state_arg = state;
      for(a = cNDummyAtoms; a < I_NAtom; a++) {
        base[0].sele[a] = false;
        obj = i_obj[i_table[a].model];
        if(obj != cur_obj) {    /* different object */
          if (state_arg == cSelectorUpdateTableCurrentState)
            state = obj->getState();
          if(state >= obj->NCSet)
            flag = false;
          else if(state < 0)
            flag = false;
          else if(!obj->CSet[state])
            flag = false;
          else {
            cs = obj->CSet[state];
            flag = true;        /* valid state */
          }
          cur_obj = obj;
        }
        if(flag && cs) {
          if(cs->atmToIdx(i_table[a].atom) >= 0) {
            base[0].sele[a] = true;
            c++;
          }
        }
      }
    }
    break;
  case SELE_ALTs:
    WARN_IF_LOWERCASE(G, "alt", base[1].text);
    {
      CWordMatchOptions options;

      WordMatchOptionsConfigAlphaList(&options, wildcard[0], ignore_case);

      table_a = i_table + cNDummyAtoms;
      base_0_sele_a = &base[0].sele[cNDummyAtoms];

      if((matcher = WordMatcherNew(G, base[1].text, &options, true))) {
        table_a = i_table + cNDummyAtoms;
        base_0_sele_a = &base[0].sele[cNDummyAtoms];

        for(a = cNDummyAtoms; a < I_NAtom; a++) {
          if((*base_0_sele_a =
              WordMatcherMatchAlpha(matcher,
                                    i_obj[table_a->model]->AtomInfo[table_a->atom].alt)))
            c++;
          table_a++;
          base_0_sele_a++;
        }
        WordMatcherFree(matcher);
      }
    }
    break;
  case SELE_FLGs:
    sscanf(base[1].text, "%d", &flag);
    flag = (1 << flag);
    for(a = cNDummyAtoms; a < I_NAtom; a++) {
      if(i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].flags & flag) {
        base[0].sele[a] = true;
        c++;
      } else
        base[0].sele[a] = false;
    }
    break;
  case SELE_NTYs:
    {
      CWordMatchOptions options;

      WordMatchOptionsConfigInteger(&options);

      if((matcher = WordMatcherNew(G, base[1].text, &options, true))) {
        table_a = i_table + cNDummyAtoms;
        base_0_sele_a = &base[0].sele[cNDummyAtoms];

        for(a = cNDummyAtoms; a < I_NAtom; a++) {
          if((*base_0_sele_a =
              WordMatcherMatchInteger(matcher,
                                      i_obj[table_a->model]->AtomInfo[table_a->atom].
                                      customType)))
            c++;
          table_a++;
          base_0_sele_a++;
        }
        WordMatcherFree(matcher);
      }
    }
    break;
  case SELE_RSIs:
    {
      CWordMatchOptions options;
      AtomInfoType *ai;

      WordMatchOptionsConfigMixed(&options, wildcard[0], ignore_case);

      table_a = i_table + cNDummyAtoms;
      base_0_sele_a = &base[0].sele[cNDummyAtoms];

      if((matcher = WordMatcherNew(G, base[1].text, &options, true))) {
        table_a = i_table + cNDummyAtoms;
        base_0_sele_a = &base[0].sele[cNDummyAtoms];

        for(a = cNDummyAtoms; a < I_NAtom; a++) {
          ai = i_obj[table_a->model]->AtomInfo + table_a->atom;
          char resi[8];
          AtomResiFromResv(resi, sizeof(resi), ai);
          if((*base_0_sele_a = WordMatcherMatchMixed(matcher, resi, ai->resv)))
            c++;
          table_a++;
          base_0_sele_a++;
        }
        WordMatcherFree(matcher);
      }
    }
    break;
  case SELE_RSNs:
    WARN_IF_LOWERCASE(G, "resn", base[1].text);
    {
      CWordMatchOptions options;

      WordMatchOptionsConfigAlphaList(&options, wildcard[0], ignore_case);

      table_a = i_table + cNDummyAtoms;
      base_0_sele_a = &base[0].sele[cNDummyAtoms];

      if((matcher = WordMatcherNew(G, base[1].text, &options, true))) {
        table_a = i_table + cNDummyAtoms;
        base_0_sele_a = &base[0].sele[cNDummyAtoms];

        for(a = cNDummyAtoms; a < I_NAtom; a++) {
          auto& resn = i_obj[table_a->model]->AtomInfo[table_a->atom].resn;
          if((*base_0_sele_a =
              WordMatcherMatchAlpha(matcher, LexStr(G, resn))))
            c++;
          table_a++;
          base_0_sele_a++;
        }
        WordMatcherFree(matcher);
      }
    }
    break;
  case SELE_SELs:
    {
      char *word = base[1].text;
      int enabled_only = false;
      CWordMatchOptions options;

      if(word[0] == '?') {
        word++;
        if(word[0] == '?') {
          enabled_only = true;
          word++;
        }
      }
      WordMatchOptionsConfigAlpha(&options, wildcard[0], ignore_case);

      if((matcher = WordMatcherNew(G, word, &options, false))) {

        SelectorWordType *list = I->Name;
        int idx = 0;

        for(a = 0; a < I_NAtom; a++)    /* zero out first before iterating through selections */
          base[0].sele[a] = false;

        while(list[idx][0]) {
          if(WordMatcherMatchAlpha(matcher, list[idx])) {
            if((idx >= 0) &&
               ((!enabled_only) ||
                ExecutiveGetActiveSeleName(G, list[idx], false, false))) {
              MemberType *I_Member = I->Member;
              sele = I->Info[idx].ID;
              for(a = cNDummyAtoms; a < I_NAtom; a++) {
                s = i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].selEntry;
                while(s) {
                  if(I_Member[s].selection == sele) {
                    if(!base[0].sele[a]) {
                      base[0].sele[a] = I_Member[s].tag;
                      c++;
                    }
                  }
                  s = I_Member[s].next;
                }
              }
            }
          }
          idx++;
        }
        WordMatcherFree(matcher);

        /* must also allow for group name pattern matches */

        {
          int group_list_id;
          if((group_list_id = ExecutiveGetExpandedGroupListFromPattern(G, word))) {
            int last_was_member = false;
            last_obj = NULL;
            for(a = cNDummyAtoms; a < I_NAtom; a++) {
              if(last_obj != i_obj[i_table[a].model]) {
                last_obj = i_obj[i_table[a].model];
                last_was_member = ExecutiveCheckGroupMembership(G,
                                                                group_list_id,
                                                                (CObject *) last_obj);
              }
              if(last_was_member && !base[0].sele[a]) {
                base[0].sele[a] = true;
                c++;
              }
            }
          }
          ExecutiveFreeGroupList(G, group_list_id);
        }

      } else if((!enabled_only) || ExecutiveGetActiveSeleName(G, word, false, false)) {
        sele = SelectGetNameOffset(G, word, 1, ignore_case);
        if(sele >= 0) {
          MemberType *I_Member = I->Member;
          sele = I->Info[sele].ID;
          for(a = cNDummyAtoms; a < I_NAtom; a++) {
            base[0].sele[a] = false;
            s = i_obj[i_table[a].model]->AtomInfo[i_table[a].atom].selEntry;
            while(s) {
              if(I_Member[s].selection == sele) {
                base[0].sele[a] = I_Member[s].tag;
                c++;
              }
              s = I_Member[s].next;
            }
          }
        } else {
          int group_list_id;
          if((group_list_id = ExecutiveGetExpandedGroupList(G, word))) {
            int last_was_member = false;
            last_obj = NULL;
            for(a = 0; a < I_NAtom; a++)        /* zero out first before iterating through selections */
              base[0].sele[a] = false;
            for(a = cNDummyAtoms; a < I_NAtom; a++) {
              if(last_obj != i_obj[i_table[a].model]) {
                last_obj = i_obj[i_table[a].model];
                last_was_member = ExecutiveCheckGroupMembership(G,
                                                                group_list_id,
                                                                (CObject *) last_obj);
              }
              if((base[0].sele[a] = last_was_member))
                c++;
            }
            ExecutiveFreeGroupList(G, group_list_id);
          } else if(base[1].text[0] == '?') {   /* undefined ?sele allowed */
            for(a = cNDummyAtoms; a < I_NAtom; a++)
              base[0].sele[a] = false;
          } else {
	      PRINTFB(G, FB_Selector, FB_Errors)
		"Selector-Error: Invalid selection name \"%s\".\n", word ENDFB(G);
            ok = false;
          }
        }
      }
    }
    break;
  case SELE_MODs:

    /* need to change this to handle wildcarded model names */

    /* first, trim off and record the atom index if one exists */

    index = -1;
    if((np = strstr(base[1].text, "`"))) {
      *np = 0;
      if(sscanf(np + 1, "%d", &index) != 1)
        index = -1;
      else
        index--;
    }
    model = 0;

    {
      CWordMatchOptions options;
      WordMatchOptionsConfigAlpha(&options, wildcard[0], ignore_case);

      if((matcher = WordMatcherNew(G, base[1].text, &options, false))) {

        int obj_matches = false;

        for(a = 0; a < I_NAtom; a++)    /* zero out first before iterating through selections */
          base[0].sele[a] = false;

        table_a = i_table + cNDummyAtoms;
        base_0_sele_a = &base[0].sele[cNDummyAtoms];
        last_obj = NULL;
        for(a = cNDummyAtoms; a < I_NAtom; a++) {
          obj = i_obj[table_a->model];
          if(obj != last_obj) {

            obj_matches = WordMatcherMatchAlpha(matcher, i_obj[table_a->model]->Obj.Name);
            last_obj = obj;
          }
          if(obj_matches) {
            if((index < 0) || (table_a->atom == index)) {
              *base_0_sele_a = true;
              c++;
            }
          }
          table_a++;
          base_0_sele_a++;
        }
        WordMatcherFree(matcher);
      } else {

        obj = (ObjectMolecule *) ExecutiveFindObjectByName(G, base[1].text);
        if(obj) {
          for(a = cNDummyModels; a < I->NModel; a++)
            if(i_obj[a] == obj) {
              model = a + 1;
              break;
            }
        }
        if(!model)
          if(sscanf(base[1].text, "%i", &model) == 1) {
            if(model <= 0)
              model = 0;
            else if(model > I->NModel)
              model = 0;
            else if(!i_obj[model])
              model = 0;
          }
        if(model) {
          model--;
          if(index >= 0) {
            for(a = cNDummyAtoms; a < I_NAtom; a++) {
              if(i_table[a].model == model)
                if(i_table[a].atom == index) {
                  base[0].sele[a] = true;
                  c++;
                } else {
                  base[0].sele[a] = false;
              } else
                base[0].sele[a] = false;
            }
          } else {
            for(a = cNDummyAtoms; a < I_NAtom; a++) {
              if(i_table[a].model == model) {
                base[0].sele[a] = true;
                c++;
              } else
                base[0].sele[a] = false;
            }
          }
        } else {
          PRINTFB(G, FB_Selector, FB_Errors)
            " Selector-Error: invalid model \"%s\".\n", base[1].text ENDFB(G);
          ok = false;
        }
      }
    }
    break;
  }
  PRINTFD(G, FB_Selector)
    " SelectorSelect1:  %d atoms selected.\n", c ENDFD;
  return (ok);
}


/*========================================================================*/
static int SelectorSelect2(PyMOLGlobals * G, EvalElem * base, int state)
{
  int a;
  int c = 0;
  int ok = true;
  int oper;
  float comp1;
  int exact;
  int ignore_case = SettingGetGlobal_b(G, cSetting_ignore_case);

  AtomInfoType *at1;
  CSelector *I = G->Selector;
  base->type = STYP_LIST;
  base->sele = Calloc(int, I->NAtom);
  ErrChkPtr(G, base->sele);
  switch (base->code) {
  case SELE_XVLx:
  case SELE_YVLx:
  case SELE_ZVLx:
    oper = WordKey(G, AtOper, base[1].text, 4, ignore_case, &exact);
    switch (oper) {
    case SCMP_GTHN:
    case SCMP_LTHN:
    case SCMP_EQAL:
      if(sscanf(base[2].text, "%f", &comp1) != 1)
        ok = ErrMessage(G, "Selector", "Invalid Number");
      break;
    default:
      ok = ErrMessage(G, "Selector", "Invalid Operator.");
      break;
    }
    if(ok) {
      ObjectMolecule *obj;
      CoordSet *cs;
      int at, idx, s, s0 = 0, sN = I->NCSet;

      if(state != -1) {
        s0 = (state < -1) ? SceneGetState(G) : state;
        sN = s0 + 1;
      }

      for(a = cNDummyAtoms; a < I->NAtom; a++)
        base[0].sele[a] = false;

      for(s = s0; s < sN; s++) {
        for(a = cNDummyAtoms; a < I->NAtom; a++) {
          if(base[0].sele[a])
            continue;

          obj = I->Obj[I->Table[a].model];
          if(s >= obj->NCSet)
            continue;

          at = I->Table[a].atom;
          cs = obj->CSet[s];
          idx = cs->atmToIdx(at);
          if(idx < 0)
            continue;

          idx *= 3;
          switch (base->code) {
          case SELE_ZVLx:
            idx++;
          case SELE_YVLx:
            idx++;
          }

          base[0].sele[a] = fcmp(cs->Coord[idx], comp1, oper);
        }
      }
    }
    break;
  case SELE_PCHx:
  case SELE_FCHx:
  case SELE_BVLx:
  case SELE_QVLx:
    oper = WordKey(G, AtOper, base[1].text, 4, ignore_case, &exact);
    if(!oper)
      ok = ErrMessage(G, "Selector", "Invalid Operator.");
    if(ok) {
      switch (oper) {
      case SCMP_GTHN:
      case SCMP_LTHN:
      case SCMP_EQAL:
        if(sscanf(base[2].text, "%f", &comp1) != 1)
          ok = ErrMessage(G, "Selector", "Invalid Number");
        break;
      }
      if(ok) {
        switch (oper) {
        case SCMP_GTHN:
          switch (base->code) {
          case SELE_BVLx:
            for(a = cNDummyAtoms; a < I->NAtom; a++) {
              at1 = &I->Obj[I->Table[a].model]->AtomInfo[I->Table[a].atom];
              if(at1->b > comp1) {
                base[0].sele[a] = true;
                c++;
              } else {
                base[0].sele[a] = false;
              }
            }
            break;
          case SELE_QVLx:
            for(a = cNDummyAtoms; a < I->NAtom; a++) {
              at1 = &I->Obj[I->Table[a].model]->AtomInfo[I->Table[a].atom];
              if(at1->q > comp1) {
                base[0].sele[a] = true;
                c++;
              } else {
                base[0].sele[a] = false;
              }
            }
            break;
          case SELE_PCHx:
            for(a = cNDummyAtoms; a < I->NAtom; a++) {
              at1 = &I->Obj[I->Table[a].model]->AtomInfo[I->Table[a].atom];
              if(at1->partialCharge > comp1) {
                base[0].sele[a] = true;
                c++;
              } else {
                base[0].sele[a] = false;
              }
            }
            break;
          case SELE_FCHx:
            for(a = cNDummyAtoms; a < I->NAtom; a++) {
              at1 = &I->Obj[I->Table[a].model]->AtomInfo[I->Table[a].atom];
              if(at1->formalCharge > comp1) {
                base[0].sele[a] = true;
                c++;
              } else {
                base[0].sele[a] = false;
              }
            }
            break;
          }
          break;
        case SCMP_LTHN:
          switch (base->code) {
          case SELE_BVLx:
            for(a = cNDummyAtoms; a < I->NAtom; a++) {
              at1 = &I->Obj[I->Table[a].model]->AtomInfo[I->Table[a].atom];
              if(at1->b < comp1) {
                base[0].sele[a] = true;
                c++;
              } else {
                base[0].sele[a] = false;
              }
            }
            break;
          case SELE_QVLx:
            for(a = cNDummyAtoms; a < I->NAtom; a++) {
              at1 = &I->Obj[I->Table[a].model]->AtomInfo[I->Table[a].atom];
              if(at1->q < comp1) {
                base[0].sele[a] = true;
                c++;
              } else {
                base[0].sele[a] = false;
              }
            }
            break;
          case SELE_PCHx:
            for(a = cNDummyAtoms; a < I->NAtom; a++) {
              at1 = &I->Obj[I->Table[a].model]->AtomInfo[I->Table[a].atom];
              if(at1->partialCharge < comp1) {
                base[0].sele[a] = true;
                c++;
              } else {
                base[0].sele[a] = false;
              }
            }
            break;
          case SELE_FCHx:
            for(a = cNDummyAtoms; a < I->NAtom; a++) {
              at1 = &I->Obj[I->Table[a].model]->AtomInfo[I->Table[a].atom];
              if(at1->formalCharge < comp1) {
                base[0].sele[a] = true;
                c++;
              } else {
                base[0].sele[a] = false;
              }
            }
            break;
          }
          break;
        case SCMP_EQAL:
          switch (base->code) {
          case SELE_BVLx:
            for(a = cNDummyAtoms; a < I->NAtom; a++) {
              at1 = &I->Obj[I->Table[a].model]->AtomInfo[I->Table[a].atom];
              if(fabs(at1->b - comp1) < R_SMALL4) {
                base[0].sele[a] = true;
                c++;
              } else {
                base[0].sele[a] = false;
              }
            }
            break;
          case SELE_QVLx:
            for(a = cNDummyAtoms; a < I->NAtom; a++) {
              at1 = &I->Obj[I->Table[a].model]->AtomInfo[I->Table[a].atom];
              if(fabs(at1->q - comp1) < R_SMALL4) {
                base[0].sele[a] = true;
                c++;
              } else {
                base[0].sele[a] = false;
              }
            }
            break;
          case SELE_PCHx:
            for(a = cNDummyAtoms; a < I->NAtom; a++) {
              at1 = &I->Obj[I->Table[a].model]->AtomInfo[I->Table[a].atom];
              if(fabs(at1->partialCharge - comp1) < R_SMALL4) {
                base[0].sele[a] = true;
                c++;
              } else {
                base[0].sele[a] = false;
              }
            }
            break;
          case SELE_FCHx:
            for(a = cNDummyAtoms; a < I->NAtom; a++) {
              at1 = &I->Obj[I->Table[a].model]->AtomInfo[I->Table[a].atom];
              if(fabs(at1->formalCharge - comp1) < R_SMALL4) {
                base[0].sele[a] = true;
                c++;
              } else {
                base[0].sele[a] = false;
              }
            }
            break;
          }
          break;
        }
        break;
      }
    }
  }

  PRINTFD(G, FB_Selector)
    " SelectorSelect2: %d atoms selected.\n", c ENDFD;
  return (ok);
}

/*========================================================================*/
static int SelectorSelect3(PyMOLGlobals * G, EvalElem * base, int state)
{
  return false;
}

/*========================================================================*/

/*
 * Ring finder subroutine
 */
class SelectorRingFinder {
  CSelector * I;
  EvalElem * base;
  ObjectMolecule * obj;
  std::vector<int> indices;

  void recursion(int atm, int depth) {
    int atm_neighbor, offset, j;

    indices[depth] = atm;

    ITERNEIGHBORATOMS(obj->Neighbor, atm, atm_neighbor, j) {
      // check bond order
      if (obj->Bond[obj->Neighbor[j + 1]].order < 1)
        continue;

      // check if closing a ring of size >= 3
      if (depth > 1 && atm_neighbor == indices[0]) {
        // found ring, add it to the selection
        for (int i = 0; i <= depth; ++i)
          if ((offset = SelectorGetObjAtmOffset(I, obj, indices[i])) >= 0)
            base->sele[offset] = 1;
      } else if (depth < indices.size() - 1) {
        // check for undesired ring with start != 0
        int i = depth;
        while ((--i) >= 0)
          if (atm_neighbor == indices[i])
            break; // stop recursion
        if (i == -1) {
          recursion(atm_neighbor, depth + 1);
        }
      }
    }
  }

public:
  SelectorRingFinder(CSelector * I, EvalElem * base, int maxringsize=7) :
    I(I), base(base), obj(NULL), indices(maxringsize) {}

  /*
   * Does a depth-first search for all paths of length in range [3, maxringsize],
   * which lead back to `atm` and don't visit any atom twice.
   *
   * Modifies base[0].sele
   */
  void apply(ObjectMolecule * obj_, int atm) {
    if (obj != obj_) {
      obj = obj_;
      ObjectMoleculeUpdateNeighbors(obj);
    }

    recursion(atm, 0);
  }
};

/*========================================================================*/
static int SelectorLogic1(PyMOLGlobals * G, EvalElem * inp_base, int state)
{
  /* some cases in this function still need to be optimized
     for performance (see BYR1 for example) */

  CSelector *I = G->Selector;
  int a, b, tag;
  int c = 0;
  int flag;
  EvalElem *base = inp_base;
  AtomInfoType *at1, *at2;
  TableRec *i_table = I->Table, *table_a;
  ObjectMolecule **i_obj = I->Obj;
  int n_atom = I->NAtom;
  int ignore_case = SettingGetGlobal_b(G, cSetting_ignore_case);
  int n;
  int a0, a1, a2;
  ObjectMolecule *lastObj = NULL;

  base[0].sele = base[1].sele;
  base[1].sele = NULL;
  base[0].type = STYP_LIST;
  switch (base->code) {
  case SELE_NOT1:
    {
      int *base_0_sele_a;

      base_0_sele_a = base[0].sele;
      for(a = 0; a < n_atom; a++) {
        if((*base_0_sele_a = !*base_0_sele_a))
          c++;
        base_0_sele_a++;
      }
    }
    break;
  case SELE_RING:
    {
      std::vector<bool> selemask(base[0].sele, base[0].sele + n_atom);
      SelectorRingFinder ringfinder(I, base);

      memset(base[0].sele, 0, sizeof(int) * n_atom);

      for (SelectorAtomIterator iter(I); iter.next();) {
        if (selemask[iter.a])
          ringfinder.apply(iter.obj, iter.getAtm());
      }
    }
    break;
  case SELE_NGH1:
    base[1].sele = base[0].sele;
    base[0].sele = Calloc(int, n_atom);

    table_a = i_table + cNDummyAtoms;
    for(a = cNDummyAtoms; a < n_atom; a++) {
      if((tag = base[1].sele[a])) {
        if(i_obj[table_a->model] != lastObj) {
          lastObj = i_obj[table_a->model];
          ObjectMoleculeUpdateNeighbors(lastObj);
        }
        a0 = table_a->atom;
        n = lastObj->Neighbor[a0];
        n++;
        while(1) {
          a1 = lastObj->Neighbor[n];
          if(a1 < 0)
            break;
          if((a2 = SelectorGetObjAtmOffset(I, lastObj, a1)) >= 0) {
            if(!base[1].sele[a2])
              base[0].sele[a2] = tag;
          }
          n += 2;
        }
      }
      table_a++;
    }
    FreeP(base[1].sele);
    break;
  case SELE_BON1:
    base[1].sele = base[0].sele;
    base[0].sele = Calloc(int, n_atom);
    table_a = i_table + cNDummyAtoms;
    for(a = cNDummyAtoms; a < n_atom; a++) {
      if((tag = base[1].sele[a])) {
        if(i_obj[table_a->model] != lastObj) {
          lastObj = i_obj[table_a->model];
          ObjectMoleculeUpdateNeighbors(lastObj);
        }
        a0 = table_a->atom;
        n = lastObj->Neighbor[a0];
        n++;
        while(1) {
          a1 = lastObj->Neighbor[n];
          if(a1 < 0)
            break;
          if((a2 = SelectorGetObjAtmOffset(I, lastObj, a1)) >= 0) {
            if(!base[0].sele[a2])
              base[0].sele[a2] = 1;
            n += 2;
          }
        }
      }
      table_a++;
    }
    FreeP(base[1].sele);
    break;
  case SELE_BYO1:
    base[1].sele = base[0].sele;
    base[0].sele = Calloc(int, n_atom);
    for(a = cNDummyAtoms; a < n_atom; a++) {
      if(base[1].sele[a]) {
        if(i_obj[i_table[a].model] != lastObj) {
          lastObj = i_obj[i_table[a].model];
          b = a;
          while(b >= 0) {
            if(i_obj[i_table[b].model] != lastObj)
              break;
            base[0].sele[b] = 1;
            b--;
          }
          b = a + 1;
          while(b < n_atom) {
            if(i_obj[i_table[b].model] != lastObj)
              break;
            base[0].sele[b] = 1;
            b++;
          }
        }
      }
    }
    FreeP(base[1].sele);
    break;
  case SELE_BYR1:              /* ASSUMES atoms are sorted & grouped by residue */
  case SELE_CAS1:
    {
      int *base_0_sele = base[0].sele;
      int break_atom = -1;
      int last_tag = 0;
      table_a = i_table + cNDummyAtoms;
      for(a = cNDummyAtoms; a < n_atom; a++) {
        if((tag = base_0_sele[a]) && ((a >= break_atom) || (base_0_sele[a] != last_tag))) {
          at1 = &i_obj[table_a->model]->AtomInfo[table_a->atom];
          b = a - 1;
          while(b >= 0) {
            if(!base_0_sele[b]) {
              flag = false;
              if(table_a->model == i_table[b].model) {
                at2 = &i_obj[i_table[b].model]->AtomInfo[i_table[b].atom];
                if(AtomInfoSameResidue(G, at1, at2)) {
                            base_0_sele[b] = tag;
                            c++;
                            flag = 1;
                          }
              }
              if(!flag) {
                break;
              }
            }
            b--;
          }
          b = a + 1;
          while(b < n_atom) {
            if(!base_0_sele[b]) {
              flag = false;
              if(table_a->model == i_table[b].model) {
                at2 = &i_obj[i_table[b].model]->AtomInfo[i_table[b].atom];
                if(AtomInfoSameResidue(G, at1, at2)) {
                            base_0_sele[b] = tag;
                            c++;
                            flag = 1;
                          }
              }
              if(!flag) {
                break_atom = b - 1;
                last_tag = tag;
                break;
              }
            }
            b++;
          }
        }
        table_a++;
      }
      if(base->code == SELE_CAS1) {
        c = 0;
        table_a = i_table + cNDummyAtoms;
        for(a = cNDummyAtoms; a < n_atom; a++) {
          if(base_0_sele[a]) {
            base_0_sele[a] = false;

            if(i_obj[table_a->model]->AtomInfo[table_a->atom].protons == cAN_C)
              if(WordMatchExact(G, G->lex_const.CA,
                                     i_obj[table_a->model]->AtomInfo[table_a->atom].name,
                                     ignore_case)) {
                base_0_sele[a] = true;
                c++;
              }
          }
          table_a++;
        }
      }
    }
    break;
  case SELE_BYC1:              /* ASSUMES atoms are sorted & grouped by chain */
    {
      int *base_0_sele = base[0].sele;
      int break_atom_high = -1;
      int break_atom_low = 0;
      int last_tag = 0;
      table_a = i_table + cNDummyAtoms;
      for(a = cNDummyAtoms; a < n_atom; a++) {
        if((tag = base_0_sele[a])
           && ((a >= break_atom_high) || (base_0_sele[a] != last_tag))) {
          if(tag != last_tag)
            break_atom_low = 0;
          at1 = &i_obj[table_a->model]->AtomInfo[table_a->atom];
          b = a - 1;
          while(b >= break_atom_low) {
            if(!base_0_sele[b]) {
              flag = false;
              if(table_a->model == i_table[b].model) {
                at2 = &i_obj[i_table[b].model]->AtomInfo[i_table[b].atom];
                if(at1->chain == at2->chain)
                  if(at1->segi == at2->segi) {
                    base_0_sele[b] = tag;
                    c++;
                    flag = 1;
                  }
              }
              if(!flag) {
                break_atom_low = b + 1;
                break;
              }
            }
            b--;
          }
          if(b < 0)
            break_atom_low = 0;
          b = a + 1;
          while(b < n_atom) {
            if(!base_0_sele[b]) {
              flag = false;
              if(table_a->model == i_table[b].model) {
                at2 = &i_obj[i_table[b].model]->AtomInfo[i_table[b].atom];
                if(at1->chain == at2->chain)
                  if(at1->segi == at2->segi) {
                    base_0_sele[b] = tag;
                    c++;
                    flag = 1;
                  }
              }
              if(!flag) {
                break_atom_high = b - 1;
                last_tag = tag;
                break;
              }
            }
            b++;
          }
        }
        table_a++;
      }
    }
    break;
  case SELE_BYS1:              /* ASSUMES atoms are sorted & grouped by segi */
    {
      int *base_0_sele = base[0].sele;
      int break_atom_high = -1;
      int break_atom_low = 0;
      int last_tag = 0;
      table_a = i_table + cNDummyAtoms;
      for(a = cNDummyAtoms; a < n_atom; a++) {
        if((tag = base_0_sele[a])
           && ((a >= break_atom_high) || (base_0_sele[a] != last_tag))) {
          if(tag != last_tag)
            break_atom_low = 0;
          at1 = &i_obj[table_a->model]->AtomInfo[table_a->atom];
          b = a - 1;
          while(b >= break_atom_low) {
            if(!base_0_sele[b]) {
              flag = false;
              if(table_a->model == i_table[b].model) {
                at2 = &i_obj[i_table[b].model]->AtomInfo[i_table[b].atom];
                if(at1->segi == at2->segi) {
                  base_0_sele[b] = tag;
                  c++;
                  flag = 1;
                }
              }
              if(!flag) {
                break_atom_low = b + 1;
                break;
              }
            }
            b--;
          }
          b = a + 1;
          while(b < n_atom) {
            if(!base_0_sele[b]) {
              flag = false;
              if(table_a->model == i_table[b].model) {
                at2 = &i_obj[i_table[b].model]->AtomInfo[i_table[b].atom];
                if(at1->segi == at2->segi) {
                  base_0_sele[b] = tag;
                  c++;
                  flag = 1;
                }
              }
              if(!flag) {
                break_atom_high = b - 1;
                last_tag = tag;
                break;
              }
            }
            b++;
          }
        }
        table_a++;
      }
    }
    break;
  case SELE_BYF1:              /* first, identify all atom by fragment selection */
    {
      /* NOTE: this algorithm looks incompatible with selection
         tags...need to do some more thinking & work... */

      int n_frag = EditorGetNFrag(G);

      base[1].sele = base[0].sele;
      base[0].sele = Calloc(int, n_atom);

      if(n_frag) {
        int a, f, at, s;
        int *fsele;
        WordType name;
        ObjectMolecule *obj;

        fsele = Alloc(int, n_frag + 1);

        for(f = 0; f < n_frag; f++) {
          sprintf(name, "%s%1d", cEditorFragPref, f + 1);
          fsele[f] = SelectorIndexByName(G, name);
        }

        /* mark atoms by fragment */
        for(a = 0; a < n_atom; a++) {
          at = i_table[a].atom;
          obj = i_obj[i_table[a].model];
          s = obj->AtomInfo[at].selEntry;
          for(f = 0; f < n_frag; f++) {
            if(SelectorIsMember(G, s, fsele[f])) {
              base[0].sele[a] = f + 1;
            }
          }
        }

        /* mark fragments we keep */
        for(f = 0; f <= n_frag; f++) {
          fsele[f] = 0;
        }
        for(a = 0; a < n_atom; a++) {
          int f = base[0].sele[a];
          if(base[1].sele[a] && f)
            fsele[f] = 1;
        }

        /* now set flags */
        for(a = 0; a < n_atom; a++) {
          c += (base[0].sele[a] = fsele[base[0].sele[a]]);
        }

        FreeP(fsele);
      }
      FreeP(base[1].sele);
    }
    break;
  case SELE_BYM1:
    {
      int s;
      int c = 0;
      int a, at, a1, aa;
      ObjectMolecule *obj, *lastObj = NULL;
      int *stk;
      int stkDepth = 0;
      base[1].sele = base[0].sele;
      base[0].sele = Calloc(int, n_atom);

      stk = VLAlloc(int, 50);

      for(a = 0; a < n_atom; a++) {
        if((tag = base[1].sele[a]) && (!base[0].sele[a])) {
          VLACheck(stk, int, stkDepth);
          stk[stkDepth] = a;
          stkDepth++;

          obj = i_obj[i_table[a].model];
          if(obj != lastObj) {
            lastObj = obj;
            ObjectMoleculeUpdateNeighbors(obj);
          }

          while(stkDepth) {     /* this will explore a tree */
            stkDepth--;
            a = stk[stkDepth];
            base[0].sele[a] = tag;
            c++;
            at = i_table[a].atom;       /* start walk from this location */

            /* add neighbors onto the stack */
            ITERNEIGHBORATOMS(obj->Neighbor, at, a1, s) {
              b = obj->Neighbor[s + 1];
              if (obj->Bond[b].order > 0) {
                if((aa = SelectorGetObjAtmOffset(I, obj, a1)) >= 0) {
                  if(!base[0].sele[aa]) {
                    VLACheck(stk, int, stkDepth);
                    stk[stkDepth] = aa; /* add index in selector space */
                    stkDepth++;
                  }
                }
              }
            }
          }
        }
      }
      FreeP(base[1].sele);
      VLAFreeP(stk);
    }
    break;
  case SELE_BYX1:              /* by cell */
    base[1].sele = base[0].sele;
    base[0].sele = Calloc(int, n_atom);
    {
      ObjectMolecule *obj;
      CoordSet *cs;
      int d, n1, at;
      for(d = 0; d < I->NCSet; d++) {
        if((state < 0) || (d == state)) {
          n1 = 0;
          for(a = 0; a < I->NAtom; a++) {
            I->Flag1[a] = false;
            at = I->Table[a].atom;
            obj = I->Obj[I->Table[a].model];
            if(d < obj->NCSet)
              cs = obj->CSet[d];
            else
              cs = NULL;
            if(cs) {
              CCrystal *cryst = cs->PeriodicBox;
              if((!cryst) && (obj->Symmetry))
                cryst = obj->Symmetry->Crystal;
              if(cryst) {
                int idx;
                idx = cs->atmToIdx(at);
                if(idx >= 0) {
                  transform33f3f(cryst->RealToFrac, cs->Coord + (3 * idx),
                                 I->Vertex + 3 * a);
                  I->Flag1[a] = true;
                  n1++;
                }
              }
            }
          }
          if(n1) {
            MapType *map = MapNewFlagged(G, -1.1, I->Vertex, I->NAtom, NULL, I->Flag1);
            if(map) {
              int e, nCSet;
              MapSetupExpress(map);
              nCSet = SelectorGetArrayNCSet(G, base[1].sele, false);
              for(e = 0; e < nCSet; e++) {
                if((state < 0) || (e == state)) {
                  for(a = 0; a < I->NAtom; a++) {
                    if(base[1].sele[a]) {
                      at = I->Table[a].atom;
                      obj = I->Obj[I->Table[a].model];
                      if(e < obj->NCSet)
                        cs = obj->CSet[e];
                      else
                        cs = NULL;
                      if(cs) {
                        CCrystal *cryst = cs->PeriodicBox;
                        if((!cryst) && (obj->Symmetry))
                          cryst = obj->Symmetry->Crystal;
                        if(cryst) {
                          int idx;
                          idx = cs->atmToIdx(at);
                          if(idx >= 0) {
                            float probe[3], probe_i[3];
                            int h, i, j, k, l;

                            transform33f3f(cryst->RealToFrac, cs->Coord + (3 * idx),
                                           probe);
                            MapLocus(map, probe, &h, &k, &l);
                            i = *(MapEStart(map, h, k, l));
                            if(i) {
                              j = map->EList[i++];

                              probe_i[0] = (int) floor(probe[0]);
                              probe_i[1] = (int) floor(probe[1]);
                              probe_i[2] = (int) floor(probe[2]);

                              while(j >= 0) {
                                if(!base[0].sele[j]) {
                                  float *tst = I->Vertex + 3 * j;
                                  base[0].sele[j] = ((probe_i[0] == (int) floor(tst[0]))
                                                     && (probe_i[1] ==
                                                         (int) floor(tst[1]))
                                                     && (probe_i[2] ==
                                                         (int) floor(tst[2])));
                                }
                                j = map->EList[i++];
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
              MapFree(map);
            }
          }
        }
      }
    }
    FreeP(base[1].sele);
    break;
  case SELE_FST1:
    base[1].sele = base[0].sele;
    base[0].sele = Calloc(int, n_atom);
    for(a = cNDummyAtoms; a < n_atom; a++) {
      if(base[1].sele[a]) {
        base[0].sele[a] = base[1].sele[a];      /* preserve tag */
        break;
      }
    }
    FreeP(base[1].sele);
    break;
  case SELE_LST1:
    {
      int last = -1;
      base[1].sele = base[0].sele;
      base[0].sele = Calloc(int, n_atom);
      for(a = cNDummyAtoms; a < n_atom; a++) {
        if(base[1].sele[a]) {
          last = a;
        }
      }
      if(last >= 0)
        base[0].sele[last] = base[1].sele[last];        /* preserve tag */
    }
    FreeP(base[1].sele);
    break;
  }
  PRINTFD(G, FB_Selector)
    " SelectorLogic1: %d atoms selected.\n", c ENDFD;
  return (1);
}


/*========================================================================*/
static int SelectorLogic2(PyMOLGlobals * G, EvalElem * base)
{
  CSelector *I = G->Selector;
  int a, b, tag;
  int c = 0;
  int ignore_case = SettingGetGlobal_b(G, cSetting_ignore_case);
  int ignore_case_chain = SettingGetGlobal_b(G, cSetting_ignore_case_chain);
  int *base_0_sele_a, *base_2_sele_a;
  TableRec *i_table = I->Table, *table_a, *table_b;
  ObjectMolecule **i_obj = I->Obj;
  int n_atom = I->NAtom;

  AtomInfoType *at1, *at2;

  switch (base[1].code) {

  case SELE_OR_2:
  case SELE_IOR2:
    {
      base_0_sele_a = base[0].sele;
      base_2_sele_a = base[2].sele;

      for(a = 0; a < n_atom; a++) {
        if(((*base_0_sele_a) =
            (((*base_0_sele_a) >
              (*base_2_sele_a)) ? (*base_0_sele_a) : (*base_2_sele_a)))) {
          /* use higher tag */
          c++;
        }
        base_0_sele_a++;
        base_2_sele_a++;
      }
    }
    break;
  case SELE_AND2:

    base_0_sele_a = base[0].sele;
    base_2_sele_a = base[2].sele;

    for(a = 0; a < n_atom; a++) {
      if((*base_0_sele_a) && (*base_2_sele_a)) {
        (*base_0_sele_a) =
          (((*base_0_sele_a) > (*base_2_sele_a)) ? (*base_0_sele_a) : (*base_2_sele_a));
        /* use higher tag */
        c++;
      } else {
        (*base_0_sele_a) = 0;
      }
      base_0_sele_a++;
      base_2_sele_a++;
    }
    break;
  case SELE_ANT2:
    base_0_sele_a = base[0].sele;
    base_2_sele_a = base[2].sele;

    for(a = 0; a < n_atom; a++) {
      if((*base_0_sele_a) && !(*base_2_sele_a)) {
        c++;
      } else {
        (*base_0_sele_a) = 0;
      }
      base_0_sele_a++;
      base_2_sele_a++;
    }
    break;
  case SELE_IN_2:
    {
      int *base_2_sele_b;
      base_0_sele_a = &base[0].sele[cNDummyAtoms];
      table_a = i_table + cNDummyAtoms;
      for(a = cNDummyAtoms; a < n_atom; a++) {
        if((tag = *base_0_sele_a)) {
          at1 = &i_obj[table_a->model]->AtomInfo[table_a->atom];
          *base_0_sele_a = 0;
          table_b = i_table + cNDummyAtoms;
          base_2_sele_b = &base[2].sele[cNDummyAtoms];
          for(b = cNDummyAtoms; b < n_atom; b++) {
            if(*base_2_sele_b) {
              at2 = &i_obj[table_b->model]->AtomInfo[table_b->atom];
              if(at1->resv == at2->resv)
                if(WordMatchExact(G, at1->chain, at2->chain, ignore_case_chain))
                  if(WordMatchExact(G, at1->name, at2->name, ignore_case))
                    if(WordMatchExact(G, at1->inscode, at2->inscode, ignore_case))
                      if(WordMatchExact(G, at1->resn, at2->resn, ignore_case))
                        if(WordMatchExact(G, at1->segi, at2->segi, ignore_case_chain)) {
                          *base_0_sele_a = tag;
                          break;
                        }
            }
            base_2_sele_b++;
            table_b++;
          }
        }
        if(*(base_0_sele_a++))
          c++;
        table_a++;
      }
    }
    break;
  case SELE_LIK2:
    {
      int *base_2_sele_b;
      base_0_sele_a = &base[0].sele[cNDummyAtoms];
      table_a = i_table + cNDummyAtoms;
      for(a = cNDummyAtoms; a < n_atom; a++) {
        if((tag = *base_0_sele_a)) {
          at1 = &i_obj[table_a->model]->AtomInfo[table_a->atom];
          *base_0_sele_a = 0;
          table_b = i_table + cNDummyAtoms;
          base_2_sele_b = &base[2].sele[cNDummyAtoms];
          for(b = cNDummyAtoms; b < n_atom; b++) {
            if(*base_2_sele_b) {
              at2 = &i_obj[table_b->model]->AtomInfo[table_b->atom];
              if(at1->resv == at2->resv)
                if(WordMatchExact(G, at1->name, at2->name, ignore_case))
                  if(WordMatchExact(G, at1->inscode, at2->inscode, ignore_case)) {
                    *base_0_sele_a = tag;
                    break;
                  }
            }
            base_2_sele_b++;
            table_b++;
          }
        }
        if(*(base_0_sele_a++))
          c++;
        table_a++;
      }
    }
    break;
  }
  FreeP(base[2].sele);
  PRINTFD(G, FB_Selector)
    " SelectorLogic2: %d atoms selected.\n", c ENDFD;
  return (1);
}


/*========================================================================*/
int SelectorOperator22(PyMOLGlobals * G, EvalElem * base, int state)
{
  int c = 0;
  int a, d, e;
  CSelector *I = G->Selector;
  ObjectMolecule *obj;

  float dist;
  float *v2;
  CoordSet *cs;
  int ok = true;
  int nCSet;
  MapType *map;
  int i, j, h, k, l;
  int n1, at, idx;
  int code = base[1].code;

  if(state < 0) {
    switch (state) {
    case -2:
    case -3:
      state = SceneGetState(G);
      break;
    }
  }

  switch (code) {
  case SELE_WIT_:
  case SELE_BEY_:
  case SELE_NTO_:
    if(!sscanf(base[2].text, "%f", &dist))
      ok = ErrMessage(G, "Selector", "Invalid distance.");
    if(ok) {
      if(dist < 0.0)
        dist = 0.0;

      /* copy starting mask */
      for(a = 0; a < I->NAtom; a++) {
        I->Flag2[a] = base[0].sele[a];
        base[0].sele[a] = false;
      }

      for(d = 0; d < I->NCSet; d++) {
        if((state < 0) || (d == state)) {
          n1 = 0;
          for(a = 0; a < I->NAtom; a++) {
            I->Flag1[a] = false;
            at = I->Table[a].atom;
            obj = I->Obj[I->Table[a].model];
            if(d < obj->NCSet)
              cs = obj->CSet[d];
            else
              cs = NULL;
            if(cs) {
              if(CoordSetGetAtomVertex(cs, at, I->Vertex + 3 * a)) {
                I->Flag1[a] = true;
                n1++;
              }
            }
          }
          if(n1) {
            map = MapNewFlagged(G, -dist, I->Vertex, I->NAtom, NULL, I->Flag1);
            if(map) {
              ok &= MapSetupExpress(map);
              nCSet = SelectorGetArrayNCSet(G, base[4].sele, false);
              for(e = 0; ok && e < nCSet; e++) {
                if((state < 0) || (e == state)) {
                  for(a = 0; a < I->NAtom; a++) {
                    if(base[4].sele[a]) {
                      at = I->Table[a].atom;
                      obj = I->Obj[I->Table[a].model];
                      if(e < obj->NCSet)
                        cs = obj->CSet[e];
                      else
                        cs = NULL;
                      if(cs) {
                        idx = cs->atmToIdx(at);
                        if(idx >= 0) {
                          v2 = cs->Coord + (3 * idx);
                          MapLocus(map, v2, &h, &k, &l);
                          i = *(MapEStart(map, h, k, l));
                          if(i) {
                            j = map->EList[i++];

                            while(j >= 0) {
                              if(!base[0].sele[j])
                                if(I->Flag2[j])
                                  if(within3f(I->Vertex + 3 * j, v2, dist)) {
                                    if((code != SELE_NTO_) || (!base[4].sele[j]))
                                      base[0].sele[j] = true;
                                  }
                              j = map->EList[i++];
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
              MapFree(map);
            }
          }
        }
      }
      if(code == SELE_BEY_) {
        for(a = 0; a < I->NAtom; a++) {
          if(I->Flag2[a])
            base[0].sele[a] = !base[0].sele[a];
        }
      }
      for(a = cNDummyAtoms; a < I->NAtom; a++)
        if(base[0].sele[a])
          c++;
    }
    break;
  }
  FreeP(base[4].sele);
  PRINTFD(G, FB_Selector)
    " SelectorOperator22: %d atoms selected.\n", c ENDFD;
  return (1);
}

static void remove_quotes(char *st)
{
  /* nasty */

  SelectorWordType store;
  char *p, *q;
  char *quote_start = NULL;
  char active_quote = 0;
  p = st;
  q = store;

  while(*p) {
    if(((*p) == 34) || ((*p) == 39)) {
      if(quote_start && (active_quote == *p)) { /* eliminate quotes... */
        while(quote_start < (q - 1)) {
          *(quote_start) = *(quote_start + 1);
          quote_start++;
        }
        q--;
        quote_start = NULL;
        p++;
      } else if(quote_start) {
        *(q++) = *(p++);
      } else {
        if(p == st) {           /* at start => real quote */
          quote_start = q;
          active_quote = *p;
        } else if((*(p - 1) == '+') || (*(p - 1) == ',')) {     /* after separator => real quote */
          quote_start = q;
          active_quote = *p;
        }
        *(q++) = *(p++);
      }
    } else {
      /* UNWORKABLE -- hopelly getting rid of this kludge will not cause major grief 
         if((*p=='+')&&(!quote_start))
         if(!((*(p+1)==0)||(*(p+1)==',')||(*(p+1)=='+')))
         *p=',';
       */
      *(q++) = *(p++);
    }
  }
  *(q++) = 0;
  strcpy(st, store);

}

#define STACK_PUSH_VALUE(value) { \
  depth++; \
  VLACheck(Stack, EvalElem, depth); \
  e = Stack + depth; \
  e->level = (level << 4) + 1; \
  e->imp_op_level = (imp_op_level << 4) + 1; \
  imp_op_level = level; \
  e->type = STYP_VALU; \
  strcpy(e->text, value); \
  remove_quotes(e->text); \
}

#define STACK_PUSH_OPERATION(ocode) { \
  depth++; \
  VLACheck(Stack, EvalElem, depth); \
  e = Stack + depth; \
  e->code = ocode; \
  e->level = (level << 4) + ((e->code & 0xF0) >> 4); \
  e->imp_op_level = (imp_op_level << 4) + 1; \
  imp_op_level = level; \
  e->type = (e->code & 0xF); \
}

/*========================================================================*/
int *SelectorEvaluate(PyMOLGlobals * G, SelectorWordType * word, int state, int quiet)
{
  int level = 0, imp_op_level = 0;
  int depth = 0;
  int a, b, c = 0;
  int ok = true;
  unsigned int code = 0;
  int valueFlag = 0;            /* are we expecting? */
  int *result = NULL;
  int opFlag, maxLevel;
  char *q;
  int totDepth = 0;
  int exact = 0;

  int ignore_case = SettingGetGlobal_b(G, cSetting_ignore_case);
  /* CFGs can efficiently be parsed by stacks; use a clean stack w/space
   * for 100 elements */
  EvalElem *Stack = NULL, *e;
  SelectorWordType tmpKW;
  Stack = VLAlloc(EvalElem, 100);
  CHECKOK(ok, Stack);
  if (!ok)
    return NULL;
  UtilZeroMem(Stack, sizeof(EvalElem)); /* blank first entry */

  /* converts all keywords into code, adds them into a operation list */
  while(ok && word[c][0]) {
    if(word[c][0] == '#') {
      if((!valueFlag) && (!level)) {
        word[c][0] = 0;         /* terminate selection if we encounter a comment */
        word[c + 1][0] = 0;
        break;
      }
    }
    switch (word[c][0]) {
    case 0:
      break;
    case '(':
      /* increase stack depth on open parens: (selection ((and token) blah)) */
      if(valueFlag)
        ok = ErrMessage(G, "Selector", "Misplaced (.");
      if(ok)
        level++;
      break;
    case ')':
      /* decrease stack depth */
      if(valueFlag)
        ok = ErrMessage(G, "Selector", "Misplaced ).");
      if(ok) {
        level--;
        if(level < 0)
          ok = ErrMessage(G, "Selector", "Syntax error.");
        else
          imp_op_level = level;
      }
      if(ok && depth)
        Stack[depth].level--;
      break;
    default:
      if(valueFlag > 0) {       /* standard operand */
        STACK_PUSH_VALUE(word[c]);
        valueFlag--;
      } else if(valueFlag < 0) {        /* operation parameter i.e. around X<-- */
        depth++;
        VLACheck(Stack, EvalElem, depth);
        e = Stack + depth;
        e->level = (level << 4) + 1;
        e->imp_op_level = (imp_op_level << 4) + 1;
        imp_op_level = level;
        e->type = STYP_PVAL;
        strcpy(e->text, word[c]);
        valueFlag++;
      } else {                  /* possible keyword... */
        code = WordKey(G, Keyword, word[c], 4, ignore_case, &exact);
        if(!code) {
          b = strlen(word[c]) - 1;
          if((b > 2) && (word[c][b] == ';')) {
            /* kludge to accomodate unnec. ';' usage */
            word[c][b] = 0;
            code = WordKey(G, Keyword, word[c], 4, ignore_case, &exact);
          }
        }
        PRINTFD(G, FB_Selector)
          " Selector: code %x\n", code ENDFD;
        if((code > 0) && (!exact))
          if(SelectorIndexByName(G, word[c]) >= 0)
            code = 0;           /* favor selections over partial keyword matches */
        if(code) {
          /* this is a known operation */
          STACK_PUSH_OPERATION(code);
          switch (e->type) {
          case STYP_SEL0:
            valueFlag = 0;
            break;
          case STYP_SEL1:
            valueFlag = 1;
            break;
          case STYP_SEL2:
            valueFlag = 2;
            break;
          case STYP_SEL3:
            valueFlag = 3;
            break;
          case STYP_OPR1:
            valueFlag = 0;
            break;
          case STYP_OPR2:
            valueFlag = 0;
            break;
          case STYP_PRP1:
            valueFlag = -1;
            break;
          case STYP_OP22:
            valueFlag = -2;
            break;
          }
        } else {
          strcpy(tmpKW, word[c]);

          if((a = strchrcount(tmpKW, '/'))) { /* handle slash notation */
            if(a > 5) {
              ok = ErrMessage(G, "Selector", "too many slashes in macro");
              break;
            }

            // macro codes (some special cases apply! see code below)
            const int macrocodes[] = {SELE_SELs, SELE_SEGs, SELE_CHNs, SELE_RSNs, SELE_NAMs};

            // two code/value pairs to support resn`resi and name`alt
            int codes[] = {0, 0, 0}; // null-terminated
            char * values[2];

            q = tmpKW;

            // if macro starts with "/" then read from left, otherwise
            // read from right
            if(*q == '/') {
              a = 4;
              q++;
            }

            // loop over macro elements
            for(b = 0; q && *q; a--) {
              values[0] = q;

              // null-terminate current element
              if((q = strchr(q, '/')))
                *(q++) = '\0';

              // skip empty elements
              if(!*values[0])
                continue;

              codes[0] = macrocodes[4 - a];
              codes[1] = 0;

              // resn`resi or name`alt
              if (codes[0] == SELE_RSNs || codes[0] == SELE_NAMs) {
                char * backtick = strchr(values[0], '`');
                if (backtick) {
                  if (codes[0] == SELE_RSNs) {
                    codes[1] = SELE_RSIs;
                  } else {
                    codes[1] = SELE_ALTs;
                  }
                  values[1] = backtick + 1;
                  *backtick = '\0';
                } else if (codes[0] == SELE_RSNs
                    && values[0][0] >= '0'
                    && values[0][0] <= '9') {
                  // numeric -> resi
                  codes[0] = SELE_RSIs;
                }
              }

              for (int i = 0; codes[i]; ++i) {
                // skip empty and "*" values
                if (*values[i] && strcmp(values[i], "*")) {
                  if(b++)
                    STACK_PUSH_OPERATION(SELE_AND2);

                  STACK_PUSH_OPERATION(codes[i]);
                  STACK_PUSH_VALUE(values[i]);
                }
              }
            }

            // all-empty slash macro equals "all"
            if(!b)
              STACK_PUSH_OPERATION(SELE_ALLz);

          } else if(strstr(tmpKW, "`")) { /* handle <object`index> syntax */
            STACK_PUSH_OPERATION(SELE_MODs);
            valueFlag = 1;
            c--;
          } else {              /* handle <selection-name> syntax */
            STACK_PUSH_OPERATION(SELE_SELs);
            valueFlag = 1;
            c--;
          }
        }
      }
      break;
    }
    if(ok)
      c++;                      /* go onto next word */
  }
  if(level > 0){
      ok = ErrMessage(G, "Selector", "Malformed selection.");
  }
  if(ok) {                      /* this is the main operation loop */
    totDepth = depth;
    opFlag = true;
    maxLevel = -1;
    for(a = 1; a <= totDepth; a++) {
      PRINTFD(G, FB_Selector)
        " Selector initial stack %d-%p lv: %x co: %d type: %x sele %p\n",
        a, (void *) (Stack + a), Stack[a].level, Stack[a].code,
        Stack[a].type, (void *) Stack[a].sele ENDFD;

      if(Stack[a].level > maxLevel)
        maxLevel = Stack[a].level;
    }
    level = maxLevel;
    PRINTFD(G, FB_Selector)
      " Selector: maxLevel %d %d\n", maxLevel, totDepth ENDFD;
    if(level >= 0)
      while(ok) {               /* loop until all ops at all levels have been tried */

        /* order & efficiency of this algorithm could be improved... */

        PRINTFD(G, FB_Selector)
          " Selector: new cycle...\n" ENDFD;
        depth = 1;
        opFlag = true;
        while(ok && opFlag) {   /* loop through all entries looking for ops at the current level */
          PRINTFD(G, FB_Selector)
            " Selector: lvl: %d de:%d-%p slv:%d co: %x typ %x sele %p td: %d\n",
            level, depth, (void *) (Stack + depth), Stack[depth].level,
            Stack[depth].code,
            Stack[depth].type, (void *) Stack[depth].sele, totDepth ENDFD;

          opFlag = false;

          if(Stack[depth].level >= level) {
            Stack[depth].level = level; /* trim peaks */
          }
          if(ok)
            if(depth > 0)
              if((!opFlag) && (Stack[depth].type == STYP_SEL0)) {
                opFlag = true;
                ok = SelectorSelect0(G, &Stack[depth]);
              }
          if(ok)
            if(depth > 1)
              if(Stack[depth - 1].level >= Stack[depth].level) {
                if(ok && (!opFlag) && (Stack[depth - 1].type == STYP_SEL1)
                   && (Stack[depth].type == STYP_VALU)) {
                  /* 1 argument selection operator */
                  opFlag = true;
                  ok = SelectorSelect1(G, &Stack[depth - 1], quiet);
                  for(a = depth + 1; a <= totDepth; a++)
                    Stack[a - 1] = Stack[a];
                  totDepth--;
                } else if(ok && (!opFlag) && (Stack[depth - 1].type == STYP_OPR1)
                          && (Stack[depth].type == STYP_LIST)) {
                  /* 1 argument logical operator */
                  opFlag = true;
                  ok = SelectorLogic1(G, &Stack[depth - 1], state);
                  for(a = depth + 1; a <= totDepth; a++)
                    Stack[a - 1] = Stack[a];
                  totDepth--;
                } else if((Stack[depth - 1].type == STYP_LIST) &&
                          (Stack[depth].type == STYP_LIST) &&
                          (!((Stack[depth - 1].level & 0xF) ||
                             (Stack[depth].level & 0xF)))) {
                  /* two adjacent lists at zeroth priority level
                     for the scope (lowest nibble of level is
                     zero) is an implicit OR action */
                  VLACheck(Stack, EvalElem, totDepth);
                  for(a = totDepth; a >= depth; a--)
                    Stack[a + 1] = Stack[a];
                  totDepth++;
                  Stack[depth].type = STYP_OPR2;
                  Stack[depth].code = SELE_IOR2;
                  Stack[depth].level = Stack[depth].imp_op_level;
                  Stack[depth].sele = NULL;
                  Stack[depth].text[0] = 0;
                  if(level < Stack[depth].level)
                    level = Stack[depth].level;
                  opFlag = true;
                }
              }
          if(ok)
            if(depth > 2)
              if((Stack[depth - 1].level >= Stack[depth].level) &&
                 (Stack[depth - 1].level >= Stack[depth - 2].level)) {

                if(ok && (!opFlag) && (Stack[depth - 1].type == STYP_OPR2)
                   && (Stack[depth].type == STYP_LIST)
                   && (Stack[depth - 2].type == STYP_LIST)) {
                  /* 2 argument logical operator */
                  ok = SelectorLogic2(G, &Stack[depth - 2]);
                  opFlag = true;
                  for(a = depth + 1; a <= totDepth; a++)
                    Stack[a - 2] = Stack[a];
                  totDepth -= 2;
                } else if(ok && (!opFlag) && (Stack[depth - 1].type == STYP_PRP1)
                          && (Stack[depth].type == STYP_PVAL)
                          && (Stack[depth - 2].type == STYP_LIST)) {
                  /* 2 argument logical operator */
                  ok = SelectorModulate1(G, &Stack[depth - 2], state);
                  opFlag = true;
                  for(a = depth + 1; a <= totDepth; a++)
                    Stack[a - 2] = Stack[a];
                  totDepth -= 2;
                }
              }
          if(ok)
            if(depth > 2)
              if((Stack[depth - 2].level >= Stack[depth - 1].level) &&
                 (Stack[depth - 2].level >= Stack[depth].level)) {

                if(ok && (!opFlag) && (Stack[depth - 2].type == STYP_SEL2)
                   && (Stack[depth - 1].type == STYP_VALU)
                   && (Stack[depth].type == STYP_VALU)) {
                  /* 2 argument value operator */
                  ok = SelectorSelect2(G, &Stack[depth - 2], state);
                  opFlag = true;
                  for(a = depth + 1; a <= totDepth; a++)
                    Stack[a - 2] = Stack[a];
                  totDepth -= 2;
                }
              }
          if(ok)
            if(depth > 3)
              if((Stack[depth - 3].level >= Stack[depth].level) &&
                 (Stack[depth - 3].level >= Stack[depth - 1].level) &&
                 (Stack[depth - 3].level >= Stack[depth - 2].level)) {

                if(ok && (!opFlag) && (Stack[depth - 3].type == STYP_SEL3)
                   && (Stack[depth].type == STYP_VALU)
                   && (Stack[depth - 1].type == STYP_VALU)
                   && (Stack[depth - 2].type == STYP_VALU)) {
                  /* 2 argument logical operator */
                  ok = SelectorSelect3(G, &Stack[depth-3], state);
                  opFlag = true;
                  for(a = depth + 1; a <= totDepth; a++)
                    Stack[a - 3] = Stack[a];
                  totDepth -= 3;
                }
              }
          if(ok)
            if(depth > 4)
              if((Stack[depth - 3].level >= Stack[depth].level) &&
                 (Stack[depth - 3].level >= Stack[depth - 1].level) &&
                 (Stack[depth - 3].level >= Stack[depth - 2].level) &&
                 (Stack[depth - 3].level >= Stack[depth - 4].level)) {

                if(ok && (!opFlag) && (Stack[depth - 3].type == STYP_OP22)
                   && (Stack[depth - 1].type == STYP_VALU)
                   && (Stack[depth - 2].type == STYP_VALU)
                   && (Stack[depth].type == STYP_LIST)
                   && (Stack[depth - 4].type == STYP_LIST)) {

                  ok = SelectorOperator22(G, &Stack[depth - 4], state);
                  opFlag = true;
                  for(a = depth + 1; a <= totDepth; a++)
                    Stack[a - 4] = Stack[a];
                  totDepth -= 4;
                }

              }
          if(opFlag) {
            depth = 1;          /* start back at the left hand side */
          } else {
            depth = depth + 1;
            opFlag = true;
            if(depth > totDepth)
              break;
          }
        }
        if(level)
          level--;
        else
          break;
      }
    depth = totDepth;
  }
  if(ok) {
    if(depth != 1) {
	ok = ErrMessage(G, "Selector", "Malformed selection.");
    } else if(Stack[depth].type != STYP_LIST)
	ok = ErrMessage(G, "Selector", "Invalid selection.");
    else
      result = Stack[totDepth].sele;    /* return the selection list */
  }
  if(!ok) {
    for(a = 1; a <= depth; a++) {
      PRINTFD(G, FB_Selector)
        " Selector: releasing %d %x %p\n", a, Stack[a].type, (void *) Stack[a].sele ENDFD;
      if(Stack[a].type == STYP_LIST)
        FreeP(Stack[a].sele);
    }
    depth = 0;
    {
      OrthoLineType line;
      for(a = 0; a <= c; a++) {
        q = line;
        if(a && word[a][0])
          q = UtilConcat(q, " ");
        q = UtilConcat(q, word[a]);
	  PRINTFB(G, FB_Selector, FB_Errors)
	    "%s", line ENDFB(G);
      }
      q = line;
      q = UtilConcat(q, "<--");
	PRINTFB(G, FB_Selector, FB_Errors)
	  "%s\n", line ENDFB(G);
    }
  }
  VLAFreeP(Stack);
  if(!ok) {
    FreeP(result);
    result = NULL;
  }
  return (result);
}


/*========================================================================*/
SelectorWordType *SelectorParse(PyMOLGlobals * G, const char *s)
{

  /* break a selection down into its constituent strings and
     return them in a SelectorWordType VLA, null string terminated */

  SelectorWordType *r = NULL;
  int c = 0;
  int w_flag = false;
  int quote_flag = false;
  char quote_char = '"';
  const char *p = s;
  char *q = NULL, *q_base = NULL;
  r = VLAlloc(SelectorWordType, 100);
  while(*p) {
    if(w_flag) {                /* currently in a word, thus q is a valid pointer */
      if(quote_flag) {
        if(*p != quote_char) {
          *q++ = *p;
        } else {
          quote_flag = false;
          *q++ = *p;
        }
      } else
        switch (*p) {
        case ' ':
          *q = 0;
          w_flag = false;
          break;
        case ';':              /* special word terminator */
          *q++ = *p;
          *q = 0;
          w_flag = false;
          break;
        case '!':              /* single words */
        case '&':
        case '|':
        case '(':
        case ')':
        case '>':
        case '<':
        case '=':
        case '%':
          *q = 0;               /* terminate current word */
          c++;
          VLACheck(r, SelectorWordType, c);     /* add new word */
          q = r[c - 1];
          *q++ = *p;
          *q = 0;               /* terminate current word */
          w_flag = false;
          break;
        case '"':
          quote_flag = true;
          *q++ = *p;
          break;
        default:
          *q++ = *p;
          break;
        }
      if(w_flag) {
        if((q - q_base) >= sizeof(SelectorWordType)) {
          q_base[sizeof(SelectorWordType) - 1] = 0;
          w_flag = false;
          PRINTFB(G, FB_Selector, FB_Errors)
            "Selector-Error: Word too long. Truncated:\nSelector-Error: %s...\n", q_base
            ENDFB(G);
        }
      }
    } else {                    /*outside a word -- q is undefined */

      switch (*p) {
      case '!':                /* single words */
      case '&':
      case '|':
      case '(':
      case ')':
      case '>':
      case '<':
      case '=':
      case '%':
        c++;
        VLACheck(r, SelectorWordType, c);
        q = r[c - 1];
        *q++ = (*p);
        *q = 0;
        break;
      case ' ':
        break;
      case '"':
        quote_flag = true;
        quote_char = *p;
        w_flag = true;
        c++;
        VLACheck(r, SelectorWordType, c);
        q = r[c - 1];
        q_base = q;
        *q++ = *p;
        break;
      default:
        w_flag = true;
        c++;
        VLACheck(r, SelectorWordType, c);
        q = r[c - 1];
        q_base = q;
        *q++ = *p;
        break;
      }
    }
    p++;
  }
  /* end current word */
  if(w_flag)
    *q = 0;

  /* null strings terminate the list */
  q = r[c];
  *q = 0;
  if(Feedback(G, FB_Selector, FB_Debugging)) {
    c = 0;
    while(r[c][0]) {
      fprintf(stderr, "word: %s\n", r[c]);
      c++;
    }
  }
  return (r);
}


/*========================================================================*/
void SelectorFree(PyMOLGlobals * G)
{
  SelectorFreeImpl(G, G->Selector, 1);
}

void SelectorFreeImpl(PyMOLGlobals * G, CSelector *I, short init2)
{
  SelectorCleanImpl(G, I);
  if(I->Origin)
    if(I->Origin->Obj.fFree)
      I->Origin->Obj.fFree((CObject *) I->Origin);
  if(I->Center)
    if(I->Center->Obj.fFree)
      I->Center->Obj.fFree((CObject *) I->Center);
  if (init2){
    VLAFreeP(I->Member);
    VLAFreeP(I->Name);
    VLAFreeP(I->Info);

    OVLexicon_DEL_AUTO_NULL(I->Lex);
    OVOneToAny_DEL_AUTO_NULL(I->Key);
    OVOneToOne_DEL_AUTO_NULL(I->NameOffset);
  }
  FreeP(I);
}


/*========================================================================*/

void SelectorMemoryDump(PyMOLGlobals * G)
{
  CSelector *I = G->Selector;
  printf(" SelectorMemory: NSelection %d\n", I->NSelection);
  printf(" SelectorMemory: NActive %d\n", I->NActive);
  printf(" SelectorMemory: TmpCounter %d\n", I->TmpCounter);
  printf(" SelectorMemory: NMember %d\n", I->NMember);
}

static void SelectorInit2(PyMOLGlobals * G, CSelector *I)
{
  I->NSelection = 0;
  I->NActive = 0;
  I->TmpCounter = 0;
  I->NCSet = 0;

  I->Lex = OVLexicon_New(G->Context->heap);
  I->Key = OVOneToAny_New(G->Context->heap);
  I->NameOffset = OVOneToOne_New(G->Context->heap);

  {                             /* create placeholder "all" selection, which is selection 0
                                   and "none" selection, which is selection 1 */
    int n;

    n = I->NActive;
    VLACheck(I->Name, SelectorWordType, n + 1);
    VLACheck(I->Info, SelectionInfoRec, n + 1);
    strcpy(I->Name[n], cKeywordAll);    /* "all" selection = 0 */
    I->Name[n + 1][0] = 0;
    SelectorAddName(G, n);
    SelectionInfoInit(I->Info + n);
    I->Info[n].ID = I->NSelection++;
    I->NActive++;

    n = I->NActive;
    VLACheck(I->Name, SelectorWordType, n + 1);
    VLACheck(I->Info, SelectionInfoRec, n + 1);
    strcpy(I->Name[n], cKeywordNone);   /* "none" selection = 1 */
    I->Name[n + 1][0] = 0;
    SelectorAddName(G, n);
    SelectionInfoInit(I->Info + n);
    I->Info[n].ID = I->NSelection++;
    I->NActive++;
  }

  if(I->Lex && I->Key) {
    int a = 0;
    OVreturn_word result;
    while(1) {
      if(!Keyword[a].word[0])
        break;
      if(OVreturn_IS_OK((result = OVLexicon_GetFromCString(I->Lex, Keyword[a].word)))) {
        OVOneToAny_SetKey(I->Key, result.word, Keyword[a].value);
      }
      a++;
    }

  }
}

void SelectorReinit(PyMOLGlobals * G)
{
  CSelector *I = G->Selector;
  SelectorClean(G);

  OVLexicon_DEL_AUTO_NULL(I->Lex);
  OVOneToAny_DEL_AUTO_NULL(I->Key);
  OVOneToOne_DEL_AUTO_NULL(I->NameOffset);

  SelectorInit2(G, I);
}


/*========================================================================*/
int SelectorInit(PyMOLGlobals * G)
{
  return SelectorInitImpl(G, &G->Selector, 1);
}

int SelectorInitImpl(PyMOLGlobals * G, CSelector **Iarg, short init2){
  CSelector *I = NULL;
  ok_assert(1, I = Calloc(CSelector, 1));

    *Iarg = I;

    I->Vertex = NULL;
    I->Origin = NULL;
    I->Table = NULL;
    I->Obj = NULL;
    I->Flag1 = NULL;
    I->Flag2 = NULL;

    if (init2){
      I->Member = (MemberType *) VLAMalloc(100, sizeof(MemberType), 5, true);
      I->NMember = 0;
      I->FreeMember = 0;
      I->Name = VLAlloc(SelectorWordType, 10);
      I->Info = VLAlloc(SelectionInfoRec, 10);
      SelectorInit2(G, I);
    } else {
      CSelector *GI = G->Selector;
      I->Member = GI->Member;
      I->NMember = GI->NMember;
      I->FreeMember = GI->FreeMember;
      I->NSelection = GI->NSelection;
      I->NActive = GI->NActive ;
      I->TmpCounter = GI->TmpCounter;
      I->NCSet = GI->NCSet;
      I->Lex = GI->Lex;
      I->Key = GI->Key;
      I->NameOffset = GI->NameOffset;
      I->Name = GI->Name;
      I->Info = GI->Info;      
    }
    return 1;
ok_except1:
 return 0;
}
 

/*========================================================================*/

DistSet *SelectorGetDistSet(PyMOLGlobals * G, DistSet * ds,
                            int sele1, int state1, int sele2, int state2,
                            int mode, float cutoff, float *result)
{
  CSelector *I = G->Selector;
  int *vla = NULL;
  int c;
  float dist;
  int a1, a2;
  AtomInfoType *ai1, *ai2;
  int at, at1, at2;
  CoordSet *cs1, *cs2;
  ObjectMolecule *obj, *obj1, *obj2, *lastObj;
  int idx1, idx2;
  int a;
  int nv = 0;
  float *vv = NULL, *vv0, *vv1;
  float dist_sum = 0.0;
  int dist_cnt = 0;
  int s;
  int a_keeper = false;
  int *zero = NULL, *scratch = NULL;
  std::vector<bool> coverage;
  HBondCriteria hbcRec, *hbc;
  int exclusion = 0;
  int bonds_only = 0;
  int from_proton = SettingGetGlobal_b(G, cSetting_h_bond_from_proton);
  AtomInfoType *h_ai;
  CMeasureInfo *atom1Info=NULL;

  /* if we're creating hydrogen bonds, then set some distance cutoffs */
  switch (mode) {
  case 1:
    bonds_only = 1;
    break;
  case 2:
    exclusion = SettingGetGlobal_i(G, cSetting_h_bond_exclusion);
    break;
  case 3:
    exclusion = SettingGetGlobal_i(G, cSetting_distance_exclusion);
    break;
  }

  hbc = &hbcRec;
  *result = 0.0;
  /* if the dist set exists, get info from it, otherwise get a new one */
  if(!ds) {
    ds = DistSetNew(G);
  } else {
    vv = ds->Coord;  /* vertices */
    nv = ds->NIndex; /* number of vertices */
  }
  /* make sure we have memory to hold the vertex info for this distance set */
  if(!vv) {
    vv = VLAlloc(float, 10);
  }

  /* update states: if the two are the same, update that one state, else update all states */
  if((state1 < 0) || (state2 < 0) || (state1 != state2)) {
    SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);
  } else {
    SelectorUpdateTable(G, state1, -1);
  }

  /* find and prepare (neighbortables) in any participating Molecular objects */
  if((mode == 1) || (mode == 2) || (mode == 3)) {       /* fill in all the neighbor tables */
    int max_n_atom = I->NAtom;
    lastObj = NULL;
    for(a = cNDummyAtoms; a < I->NAtom; a++) {
      /* foreach atom in the session, get its identifier and ObjectMolecule to which it belongs */
      at = I->Table[a].atom;  /* grab the atom ID from the Selectors->Table */
      obj = I->Obj[I->Table[a].model];		/* -- JV -- quick way to get an object from an atom */
      s = obj->AtomInfo[at].selEntry;  /* grab the selection entry# from this Atoms Info */
      if(obj != lastObj) {
        if(max_n_atom < obj->NAtom)
          max_n_atom = obj->NAtom;
	/* if the current atom is in sele1 or sele2 then update it's object's neighbor table */
        if(SelectorIsMember(G, s, sele1) || SelectorIsMember(G, s, sele2)) {
          ObjectMoleculeUpdateNeighbors(obj);
	  /* if hbonds (so, more than just distance) */
          if(mode == 2)
            ObjectMoleculeVerifyChemistry(obj, -1);
          lastObj = obj;
        }
      }
    }
    /* prepare these for the next round */
    zero = Calloc(int, max_n_atom);
    scratch = Alloc(int, max_n_atom);
  }

  /* if we're hydrogen bonding, setup the cutoff */
  if(mode == 2) {
    ObjectMoleculeInitHBondCriteria(G, hbc);
    if(cutoff < 0.0F) {
      cutoff = hbc->maxDistAtMaxAngle;
      if(cutoff < hbc->maxDistAtZero) {
        cutoff = hbc->maxDistAtZero;
      }
    }
  }
  if(cutoff < 0)
    cutoff = 1000.0;
	
  if (mode == 4) {
    // centroid distance
    float centroid1[3], centroid2[3];
    ObjectMoleculeOpRec op;

    // get centroid 1
    ObjectMoleculeOpRecInit(&op);
    op.code = OMOP_CSetSumVertices;
    op.cs1 = state1;
    ExecutiveObjMolSeleOp(G, sele1, &op);

    if (op.i1 > 0) {
      scale3f(op.v1, 1.f / op.i1, centroid1);

      // get centroid 2
      ObjectMoleculeOpRecInit(&op);
      op.code = OMOP_CSetSumVertices;
      op.cs1 = state2;
      ExecutiveObjMolSeleOp(G, sele2, &op);

      if (op.i1 > 0) {
        scale3f(op.v1, 1.f / op.i1, centroid2);

        // store positions in measurement object
        VLACheck(vv, float, (nv * 3) + 6);
        vv0 = vv + (nv * 3);
        nv += 2;
        copy3f(centroid1, vv0);
        copy3f(centroid2, vv0 + 3);

        // for the return value
        dist_cnt = 1;
        dist_sum = diff3f(centroid1, centroid2);
      }
    }

    // skip searching for pairwise distances
    c = 0;
  } else {
    /* coverage determines if a given atom appears in sel1 and sel2 */
    coverage.resize(I->NAtom);

    for (SelectorAtomIterator iter(I); iter.next();) {
      s = iter.getAtomInfo()->selEntry;
      if (SelectorIsMember(G, s, sele1) &&
          SelectorIsMember(G, s, sele2))
        coverage[iter.a] = true;
    }

    /* this creates an interleaved list of ints for mapping ids to states within a given neighborhood */
    c = SelectorGetInterstateVLA(G, sele1, state1, sele2, state2, cutoff, &vla);
  }

  /* for each state */
  for(a = 0; a < c; a++) {
    atom1Info = NULL;
    /* get the interstate atom identifier for the two atoms to distance */
    a1 = vla[a * 2];
    a2 = vla[a * 2 + 1];

    /* check their coverage to avoid duplicates */
    if(a1 < a2 || (a1 != a2 && !(coverage[a1] && coverage[a2]))
        || (state1 != state2)) {  /* eliminate reverse duplicates */
      /* get the object-local atom ID */
      at1 = I->Table[a1].atom;
      at2 = I->Table[a2].atom;
      /* get the object for this global atom ID */
      obj1 = I->Obj[I->Table[a1].model];
      obj2 = I->Obj[I->Table[a2].model];

      /* the states are valid for these two atoms */
      if((state1 < obj1->NCSet) && (state2 < obj2->NCSet)) {
	/* get the coordinate sets for both atoms */
        cs1 = obj1->CSet[state1];
        cs2 = obj2->CSet[state2];
        if(cs1 && cs2) {
	  /* for bonding */
          float *don_vv = NULL;
          float *acc_vv = NULL;

	  /* grab the appropriate atom information for this object-local atom */
          ai1 = obj1->AtomInfo + at1;
          ai2 = obj2->AtomInfo + at2;
	  
          idx1 = cs1->atmToIdx(at1);
          idx2 = cs2->atmToIdx(at2);

          if((idx1 >= 0) && (idx2 >= 0)) {
	    /* actual distance calculation from ptA to ptB */
            dist = (float) diff3f(cs1->Coord + 3 * idx1, cs2->Coord + 3 * idx2);

	    /* if we pass the boding cutoff */
            if(dist < cutoff) {
              float h_crd[3];
              h_ai = NULL;

              a_keeper = true;
              if(exclusion && (obj1 == obj2)) {
                a_keeper = !SelectorCheckNeighbors(G, exclusion,
                                                   obj1, at1, at2, zero, scratch);
              } else if(bonds_only) {
                a_keeper = SelectorCheckNeighbors(G, 1, obj1, at1, at2, zero, scratch);
              }
              if(a_keeper && (mode == 2)) {
		/* proton comes from ai1 */
                if(ai1->hb_donor && ai2->hb_acceptor) {
                  a_keeper = ObjectMoleculeGetCheckHBond(&h_ai, h_crd,
							 obj1, at1, state1, 
							 obj2, at2, state2,
							 hbc);
                  if(a_keeper) {
                    if(h_ai && from_proton) {
                      don_vv = h_crd;
                      ai1 = h_ai;
		    }
                    else {
                      don_vv = cs1->Coord + 3 * idx1;
		    }
                    acc_vv = cs2->Coord + 3 * idx2;
                  }
                } else if(ai1->hb_acceptor && ai2->hb_donor) {
		  /* proton comes from ai2 */
                  a_keeper = ObjectMoleculeGetCheckHBond(&h_ai, h_crd,
							 obj2, at2, state2,
							 obj1, at1, state1, 
							 hbc);

                  if(a_keeper) {
                    if(h_ai && from_proton) {
                      don_vv = h_crd;
                      ai2 = h_ai;
		    }
                    else {
                      don_vv = cs2->Coord + 3 * idx2;
		    }
		    acc_vv = cs1->Coord + 3 * idx1;
                  }
                } else {
                  a_keeper = false;
                }
	      }
              if((sele1 == sele2) && (at1 > at2))
                a_keeper = false;

              if(a_keeper) {

		/* Insert DistInfo records for updating distances */
		/* Init/Add the elem to the DistInfo list */
                atom1Info = Alloc(CMeasureInfo, 1);

                // TH
                atom1Info->id[0] = AtomInfoCheckUniqueID(G, ai1);
                atom1Info->id[1] = AtomInfoCheckUniqueID(G, ai2);

		atom1Info->offset = nv;  /* offset into this DSet's Coord */
		atom1Info->state[0] = state1;  /* state1 of sel1 */
		atom1Info->state[1] = state2;
		atom1Info->measureType = cRepDash; /* DISTANCE-dash */
		ListPrepend(ds->MeasureInfo, atom1Info, next);

		/* we have a distance we want to keep */
                dist_cnt++;
                dist_sum += dist;
		/* see if vv has room at another 6 floats */
                VLACheck(vv, float, (nv * 3) + 6);
                vv0 = vv + (nv * 3);
		
                if((mode == 2) && (don_vv) && (acc_vv)) {
                  *(vv0++) = *(don_vv++);
                  *(vv0++) = *(don_vv++);
                  *(vv0++) = *(don_vv++);
                  *(vv0++) = *(acc_vv++);
                  *(vv0++) = *(acc_vv++);
                  *(vv0++) = *(acc_vv++);
                } else {
                  vv1 = cs1->Coord + 3 * idx1;
                  *(vv0++) = *(vv1++);
                  *(vv0++) = *(vv1++);
                  *(vv0++) = *(vv1++);
                  vv1 = cs2->Coord + 3 * idx2;
                  *(vv0++) = *(vv1++);
                  *(vv0++) = *(vv1++);
                  *(vv0++) = *(vv1++);
                }

                nv += 2;
              }
            }
          }
        }
      }
    }
  }
  if(dist_cnt)
    (*result) = dist_sum / dist_cnt;
  VLAFreeP(vla);
  FreeP(zero);
  FreeP(scratch);
  if(vv)
    VLASize(vv, float, (nv + 1) * 3);
  ds->NIndex = nv;
  ds->Coord = vv;
  return (ds);
}

DistSet *SelectorGetAngleSet(PyMOLGlobals * G, DistSet * ds,
                             int sele1, int state1,
                             int sele2, int state2,
                             int sele3, int state3,
                             int mode, float *angle_sum, int *angle_cnt)
{
  CSelector *I = G->Selector;
  float *vv = NULL;
  int nv = 0;
  std::vector<bool> coverage;

  if(!ds) {
    ds = DistSetNew(G);
  } else {
    vv = ds->AngleCoord;
    nv = ds->NAngleIndex;
  }
  if(!vv)
    vv = VLAlloc(float, 10);

  if((state1 < 0) || (state2 < 0) || (state3 < 0) || (state1 != state2)
     || (state1 != state3)) {
    SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);
  } else {
    SelectorUpdateTable(G, state1, -1);
  }

  /* which atoms are involved? */

  {
    int a, s, at;
    ObjectMolecule *obj;

    coverage.resize(I->NAtom);
    for(a = cNDummyAtoms; a < I->NAtom; a++) {
      at = I->Table[a].atom;
      obj = I->Obj[I->Table[a].model];
      s = obj->AtomInfo[at].selEntry;
      if (SelectorIsMember(G, s, sele1) &&
          SelectorIsMember(G, s, sele3))
        coverage[a] = true;
    }
  }

  {                             /* fill in neighbor tables */
    int a, s, at;
    ObjectMolecule *obj, *lastObj = NULL;
    for(a = cNDummyAtoms; a < I->NAtom; a++) {
      at = I->Table[a].atom;
      obj = I->Obj[I->Table[a].model];
      s = obj->AtomInfo[at].selEntry;
      if(obj != lastObj) {
        if(SelectorIsMember(G, s, sele1) ||
           SelectorIsMember(G, s, sele2) || SelectorIsMember(G, s, sele3)) {
          ObjectMoleculeUpdateNeighbors(obj);
          lastObj = obj;
        }
      }
    }
  }

  {
    int a, s, at;
    ObjectMolecule *obj;
    int *list1 = VLAlloc(int, 1000);
    int *list2 = VLAlloc(int, 1000);
    int *list3 = VLAlloc(int, 1000);
    int n1 = 0;
    int n2 = 0;
    int n3 = 0;
    int bonded12, bonded23;

    /* now generate three lists of atoms, one for each selection set */

    if(list1 && list2 && list3) {
      for(a = cNDummyAtoms; a < I->NAtom; a++) {
        at = I->Table[a].atom;
        obj = I->Obj[I->Table[a].model];
        s = obj->AtomInfo[at].selEntry;
        if(SelectorIsMember(G, s, sele1)) {
          VLACheck(list1, int, n1);
          list1[n1++] = a;
        }
        if(SelectorIsMember(G, s, sele2)) {
          VLACheck(list2, int, n2);
          list2[n2++] = a;
        }
        if(SelectorIsMember(G, s, sele3)) {
          VLACheck(list3, int, n3);
          list3[n3++] = a;
        }
      }

      /* for each set of 3 atoms in each selection... */

      {
        int i1, i2, i3;
        int a1, a2, a3;
        int at1, at2, at3;

        /*        AtomInfoType *ai1,*ai2,ai3; */
        CoordSet *cs1, *cs2, *cs3;
        ObjectMolecule *obj1, *obj2, *obj3;

        int idx1, idx2, idx3;
        float angle;
        float d1[3], d2[3];
        float *v1, *v2, *v3, *vv0;

        CMeasureInfo *atom1Info=NULL;

        for(i1 = 0; i1 < n1; i1++) {
          a1 = list1[i1];
          at1 = I->Table[a1].atom;
          obj1 = I->Obj[I->Table[a1].model];

          if(state1 < obj1->NCSet) {
            cs1 = obj1->CSet[state1];

            if(cs1) {
              idx1 = cs1->atmToIdx(at1);

              if(idx1 >= 0) {

                for(i2 = 0; i2 < n2; i2++) {
                  a2 = list2[i2];
                  at2 = I->Table[a2].atom;
                  obj2 = I->Obj[I->Table[a2].model];

                  if(state2 < obj2->NCSet) {

                    cs2 = obj2->CSet[state2];

                    if(cs2) {
                      idx2 = cs2->atmToIdx(at2);

                      if(idx2 >= 0) {
			/* neighbor table like BPRec */
                        bonded12 = ObjectMoleculeAreAtomsBonded2(obj1, at1, obj2, at2);

                        for(i3 = 0; i3 < n3; i3++) {
			  atom1Info = NULL;
                          a3 = list3[i3];

                          if( (a1 != a2 || state1 != state2) &&
                              (a2 != a3 || state2 != state3) &&
                              (a1 != a3 || state1 != state3)) {
                            if(!(coverage[a1] && coverage[a3])
                               || (a1 < a3)
                               || (state1 != state3)) {  /* eliminate alternate-order duplicates */

                              at3 = I->Table[a3].atom;
                              obj3 = I->Obj[I->Table[a3].model];

                              if(state3 < obj3->NCSet) {

                                cs3 = obj3->CSet[state3];

                                if(cs3) {
                                  idx3 = cs3->atmToIdx(at3);

                                  if(idx3 >= 0) {

                                    bonded23 =
                                      ObjectMoleculeAreAtomsBonded2(obj2, at2, obj3, at3);

                                    if(!mode || ((mode == 1) && (bonded12 && bonded23))) {
                                      /* store the 3 coordinates */

                                      v1 = cs1->Coord + 3 * idx1;
                                      v2 = cs2->Coord + 3 * idx2;
                                      v3 = cs3->Coord + 3 * idx3;

                                      subtract3f(v1, v2, d1);
                                      subtract3f(v3, v2, d2);

				      /* Insert DistInfo records for updating distances */
				      /* Init/Add the elem to the DistInfo list */
				      atom1Info = Alloc(CMeasureInfo, 1);

                                      // TH
                                      atom1Info->id[0] = AtomInfoCheckUniqueID(G, obj1->AtomInfo + at1);
                                      atom1Info->id[1] = AtomInfoCheckUniqueID(G, obj2->AtomInfo + at2);
                                      atom1Info->id[2] = AtomInfoCheckUniqueID(G, obj3->AtomInfo + at3);

				      atom1Info->offset = nv;  /* offset into this DSet's Coord */
				      atom1Info->state[0] = state1;  /* state1 of sel1 */
				      atom1Info->state[1] = state2;
				      atom1Info->state[2] = state3;
				      atom1Info->measureType = cRepAngle;
				      ListPrepend(ds->MeasureInfo, atom1Info, next);

                                      angle = get_angle3f(d1, d2);

                                      (*angle_sum) += angle;
                                      (*angle_cnt)++;

                                      VLACheck(vv, float, (nv * 3) + 14);
                                      vv0 = vv + (nv * 3);
                                      *(vv0++) = *(v1++);
                                      *(vv0++) = *(v1++);
                                      *(vv0++) = *(v1++);
                                      *(vv0++) = *(v2++);
                                      *(vv0++) = *(v2++);
                                      *(vv0++) = *(v2++);
                                      *(vv0++) = *(v3++);
                                      *(vv0++) = *(v3++);
                                      *(vv0++) = *(v3++);
                                      *(vv0++) = (float) !bonded12;
                                      /* show line 1 flag */
                                      *(vv0++) = (float) !bonded23;
                                      *(vv0++) = 0.0F;
                                      *(vv0++) = 0.0F;  /* label x relative to v2 */
                                      *(vv0++) = 0.0F;  /* label y relative to v2 */
                                      *(vv0++) = 0.0F;  /* label z relative to v2 */
                                      nv += 5;
                                    }
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
    VLAFreeP(list1);
    VLAFreeP(list2);
    VLAFreeP(list3);
  }

  if(vv)
    VLASize(vv, float, (nv + 1) * 3);
  ds->NAngleIndex = nv;
  ds->AngleCoord = vv;
  return (ds);
}

DistSet *SelectorGetDihedralSet(PyMOLGlobals * G, DistSet * ds,
                                int sele1, int state1,
                                int sele2, int state2,
                                int sele3, int state3,
                                int sele4, int state4,
                                int mode, float *angle_sum, int *angle_cnt)
{
  CSelector *I = G->Selector;
  float *vv = NULL;
  int nv = 0;
  std::vector<bool> coverage14;
  std::vector<bool> coverage23;
  ObjectMolecule *just_one_object = NULL;
  int just_one_atom[4] = { -1, -1, -1, -1 };

  CMeasureInfo *atom1Info;

  if(!ds) {
    ds = DistSetNew(G);
  } else {
    vv = ds->DihedralCoord;
    nv = ds->NDihedralIndex;
  }
  if(!vv)
    vv = VLAlloc(float, 10);

  if((state1 < 0) || (state2 < 0) || (state3 < 0) || (state4 < 0) ||
     (state1 != state2) || (state1 != state3) || (state1 != state4)) {
    SelectorUpdateTable(G, cSelectorUpdateTableAllStates, -1);
  } else {
    SelectorUpdateTable(G, state1, -1);
  }

  /* which atoms are involved? */

  {
    int a, s, at;
    ObjectMolecule *obj;

    coverage14.resize(I->NAtom);
    coverage23.resize(I->NAtom);
    for(a = cNDummyAtoms; a < I->NAtom; a++) {
      bool coverage1 = false;
      bool coverage2 = false;
      at = I->Table[a].atom;
      obj = I->Obj[I->Table[a].model];
      if(!a)
        just_one_object = obj;
      s = obj->AtomInfo[at].selEntry;
      if(SelectorIsMember(G, s, sele1)) {
        if(obj != just_one_object)
          just_one_object = NULL;
        else if(just_one_atom[0] == -1)
          just_one_atom[0] = a;
        else
          just_one_atom[0] = -2;
        coverage1 = true;
      }
      if(SelectorIsMember(G, s, sele2)) {
        if(obj != just_one_object)
          just_one_object = NULL;
        else if(just_one_atom[1] == -1)
          just_one_atom[1] = a;
        else
          just_one_atom[1] = -2;
        coverage2 = true;
      }
      if(SelectorIsMember(G, s, sele3)) {
        if(obj != just_one_object)
          just_one_object = NULL;
        else if(just_one_atom[2] == -1)
          just_one_atom[2] = a;
        else
          just_one_atom[2] = -2;
        coverage23[a] = coverage2;
      }
      if(SelectorIsMember(G, s, sele4)) {
        if(obj != just_one_object)
          just_one_object = NULL;
        else if(just_one_atom[3] == -1)
          just_one_atom[3] = a;
        else
          just_one_atom[3] = -2;
        coverage14[a] = coverage1;
      }
    }
  }

  if(just_one_object) {
    ObjectMoleculeUpdateNeighbors(just_one_object);
  } else {                      /* fill in neighbor tables */
    int a, s, at;
    ObjectMolecule *obj, *lastObj = NULL;
    for(a = cNDummyAtoms; a < I->NAtom; a++) {
      at = I->Table[a].atom;
      obj = I->Obj[I->Table[a].model];
      s = obj->AtomInfo[at].selEntry;
      if(obj != lastObj) {
        if(SelectorIsMember(G, s, sele1) ||
           SelectorIsMember(G, s, sele2) ||
           SelectorIsMember(G, s, sele3) || SelectorIsMember(G, s, sele4)
          ) {
          ObjectMoleculeUpdateNeighbors(obj);
          lastObj = obj;
        }
      }
    }
  }

  {
    int a, s, at;
    ObjectMolecule *obj;
    int *list1 = VLAlloc(int, 1000);
    int *list2 = VLAlloc(int, 1000);
    int *list3 = VLAlloc(int, 1000);
    int *list4 = VLAlloc(int, 1000);
    int n1 = 0;
    int n2 = 0;
    int n3 = 0;
    int n4 = 0;
    int bonded12, bonded23, bonded34;

    /* now generate three lists of atoms, one for each selection set */

    if(list1 && list2 && list3 && list4) {

      if(just_one_object && (just_one_atom[0] >= 0) && (just_one_atom[1] >= 0)
         && (just_one_atom[2] >= 0) && (just_one_atom[3] >= 0)) {
        /* optimal case */

        list1[0] = just_one_atom[0];
        list2[0] = just_one_atom[1];
        list3[0] = just_one_atom[2];
        list4[0] = just_one_atom[3];

        n1 = n2 = n3 = n4 = 1;

      } else {

        for(a = cNDummyAtoms; a < I->NAtom; a++) {
          at = I->Table[a].atom;
          obj = I->Obj[I->Table[a].model];
          s = obj->AtomInfo[at].selEntry;
          if(SelectorIsMember(G, s, sele1)) {
            VLACheck(list1, int, n1);
            list1[n1++] = a;
          }
          if(SelectorIsMember(G, s, sele2)) {
            VLACheck(list2, int, n2);
            list2[n2++] = a;
          }
          if(SelectorIsMember(G, s, sele3)) {
            VLACheck(list3, int, n3);
            list3[n3++] = a;
          }
          if(SelectorIsMember(G, s, sele4)) {
            VLACheck(list4, int, n4);
            list4[n4++] = a;
          }
        }
      }

      /* for each set of 3 atoms in each selection... */

      {
        int i1, i2, i3, i4;
        int a1, a2, a3, a4;
        int at1, at2, at3, at4;

        /*        AtomInfoType *ai1,*ai2,ai3; */
        CoordSet *cs1, *cs2, *cs3, *cs4;
        ObjectMolecule *obj1, *obj2, *obj3, *obj4;

        int idx1, idx2, idx3, idx4;
        float angle;
        float *v1, *v2, *v3, *v4, *vv0;

        for(i1 = 0; i1 < n1; i1++) {
          a1 = list1[i1];
          at1 = I->Table[a1].atom;
          obj1 = I->Obj[I->Table[a1].model];
          if(state1 < obj1->NCSet) {
            cs1 = obj1->CSet[state1];

            if(cs1) {
              idx1 = cs1->atmToIdx(at1);

              if(idx1 >= 0) {

                for(i2 = 0; i2 < n2; i2++) {
                  a2 = list2[i2];
                  at2 = I->Table[a2].atom;
                  obj2 = I->Obj[I->Table[a2].model];

                  if(state2 < obj2->NCSet) {

                    cs2 = obj2->CSet[state2];

                    if(cs2) {
                      idx2 = cs2->atmToIdx(at2);

                      if(idx2 >= 0) {

                        bonded12 = ObjectMoleculeAreAtomsBonded2(obj1, at1, obj2, at2);

                        if(!mode || ((mode == 1) && bonded12))
                          for(i3 = 0; i3 < n3; i3++) {
                            a3 = list3[i3];
                            at3 = I->Table[a3].atom;
                            obj3 = I->Obj[I->Table[a3].model];

                            if(state3 < obj3->NCSet) {

                              cs3 = obj3->CSet[state3];

                              if(cs3) {
                                idx3 = cs3->atmToIdx(at3);

                                if(idx3 >= 0) {

                                  bonded23 =
                                    ObjectMoleculeAreAtomsBonded2(obj2, at2, obj3, at3);
                                  if(!mode || ((mode == 1) && bonded23))
                                    for(i4 = 0; i4 < n4; i4++) {
				      atom1Info = NULL;
                                      a4 = list4[i4];

                                      if((a1 != a2) && (a1 != a3) && (a1 != a4)
                                         && (a2 != a3) && (a2 != a4) && (a3 != a4)) {
                                        if (!(coverage14[a1] &&
                                              coverage14[a4] &&
                                              coverage23[a2] &&
                                              coverage23[a3])
                                           || (a1 < a4)) {
                                          /* eliminate alternate-order duplicates */

                                          at4 = I->Table[a4].atom;
                                          obj4 = I->Obj[I->Table[a4].model];

                                          if(state4 < obj4->NCSet) {

                                            cs4 = obj4->CSet[state4];

                                            if(cs4) {
                                              idx4 = cs3->atmToIdx(at4);

                                              if(idx4 >= 0) {

                                                bonded34 =
                                                  ObjectMoleculeAreAtomsBonded2(obj3, at3,
                                                                                obj4,
                                                                                at4);

                                                if(!mode || ((mode == 1) && bonded34)) {
                                                  /* store the 3 coordinates */

                                                  v1 = cs1->Coord + 3 * idx1;
                                                  v2 = cs2->Coord + 3 * idx2;
                                                  v3 = cs3->Coord + 3 * idx3;
                                                  v4 = cs4->Coord + 3 * idx4;

						  /* Insert DistInfo records for updating distances */
						  /* Init/Add the elem to the DistInfo list */
						  atom1Info = Alloc(CMeasureInfo ,1);

                                                  // TH
                                                  atom1Info->id[0] = AtomInfoCheckUniqueID(G, obj1->AtomInfo + at1);
                                                  atom1Info->id[1] = AtomInfoCheckUniqueID(G, obj2->AtomInfo + at2);
                                                  atom1Info->id[2] = AtomInfoCheckUniqueID(G, obj3->AtomInfo + at3);
                                                  atom1Info->id[3] = AtomInfoCheckUniqueID(G, obj4->AtomInfo + at4);

						  atom1Info->offset = nv;  /* offset into this DSet's Coord */

						  atom1Info->state[0] = state1;  /* state1 of sel1 */
						  atom1Info->state[1] = state2;
						  atom1Info->state[2] = state3;
						  atom1Info->state[3] = state4;

						  atom1Info->measureType = cRepDihedral;

						  ListPrepend(ds->MeasureInfo, atom1Info, next);

                                                  angle = get_dihedral3f(v1, v2, v3, v4);

                                                  (*angle_sum) += angle;
                                                  (*angle_cnt)++;

                                                  VLACheck(vv, float, (nv * 3) + 17);
                                                  vv0 = vv + (nv * 3);
                                                  ObjectMoleculeGetAtomTxfVertex(obj1,
                                                                                 state1,
                                                                                 at1,
                                                                                 vv0);
                                                  ObjectMoleculeGetAtomTxfVertex(obj2,
                                                                                 state2,
                                                                                 at2,
                                                                                 vv0 + 3);
                                                  ObjectMoleculeGetAtomTxfVertex(obj3,
                                                                                 state3,
                                                                                 at3,
                                                                                 vv0 + 6);
                                                  ObjectMoleculeGetAtomTxfVertex(obj4,
                                                                                 state4,
                                                                                 at4,
                                                                                 vv0 + 9);
                                                  vv0 += 12;
                                                  *(vv0++) = (float) !bonded12;
                                                  *(vv0++) = (float) !bonded23;
                                                  *(vv0++) = (float) !bonded34;
                                                  *(vv0++) = 0.0F;      /* label x relative to v2+v3/2 */
                                                  *(vv0++) = 0.0F;      /* label y relative to v2+v3/2 */
                                                  *(vv0++) = 0.0F;      /* label z relative to v2+v3/2 */
                                                  nv += 6;
                                                }
                                              }
                                            }
                                          }
                                        }
                                      }
                                    }
                                }
                              }
                            }
                          }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
    VLAFreeP(list1);
    VLAFreeP(list2);
    VLAFreeP(list3);
    VLAFreeP(list4);
  }

  if(vv)
    VLASize(vv, float, (nv + 1) * 3);
  ds->NDihedralIndex = nv;
  ds->DihedralCoord = vv;
  return (ds);
}


/*========================================================================*/


/*

example selections
cas
backbone
(model 1 and backbone)
(name ca)
(resi 123)
(resi 200:400 and chain A)
(model a)

 */


/* In order for selections to be robust during atom insertions
   deletions, they are stored not as lists of selected atoms, but
   rather in the inverse - as atoms with selection membership
   information */


/* Each atom points into the selection heap, a series of linked
   entries where each member is selection index */


/* Definition of the selection language:

	<sele> = [(] [<not>] <prop> <val-range> [<qual> [<qual-range>] ] [)] { <SEL1> <sele> }

	Example selections:
	
   name ca
	( name ca )
	name ca around 5 {all atoms within 5 angstroms of any ca atom) }
	( resi 10 ) 
	resi 10:50
	resi 10A:50A
	resi 10,50
	chain A
	segi A
	model 1 and segi B
	model a and segi B
	not name ca

*/


/* Selection processing, left to right by default, but with parenthesis for grouping
	stack based functional language processing
	each stack level has a full selection matrix
	logical binary operators (or,and) and negation SEL1ation solely on these matrices.
*/


/*

(not (name ca or name c) and (name s around 5 and (name c) )) around 6

0:
1: not
2: name 
2: ca

0:

1: not
2:<name ca>
not name ca around 5

force compute

0:
1: not

attrib b < 0 

*/
