static PyObject *setbonds(PyObject *self, PyObject *args) {
  int molid;
  PyObject *atomlist, *bondlist; 

  if (!PyArg_ParseTuple(args, (char *)"iO!O!:setbonds", &molid, 
                        &PyTuple_Type, &atomlist, &PyList_Type, &bondlist)) 
    return NULL;  // bad args
 
  Molecule *mol = get_vmdapp()->moleculeList->mol_from_id(molid);
  if (!mol) {
    PyErr_SetString(PyExc_ValueError, "molecule no longer exists");
    return NULL;
  }
  int num_atoms = mol->nAtoms;
  int num_selected = PyTuple_Size(atomlist);
  if (PyList_Size(bondlist) != num_selected) {
    PyErr_SetString(PyExc_ValueError, 
      (char *)"setbonds: atomlist and bondlist must have the same size");
    return NULL;
  }
  mol->force_recalc(DrawMolItem::MOL_REGEN); // many reps ignore bonds
  for (int i=0; i<num_selected; i++) {
    int id = PyInt_AsLong(PyTuple_GET_ITEM(atomlist, i));
    if (PyErr_Occurred()) {
      return NULL;
    }
    if (id < 0 || id >= num_atoms) {
      PyErr_SetString(PyExc_ValueError, (char *)"invalid atom id found");
      return NULL;
    }
    MolAtom *atom = mol->atom(id);
   
    PyObject *atomids = PyList_GET_ITEM(bondlist, i);
    if (!PyList_Check(atomids)) {
      PyErr_SetString(PyExc_TypeError, 
        (char *)"bondlist must contain lists");
      return NULL;
    }
    int numbonds = PyList_Size(atomids);
    int k=0;
    for (int j=0; j<numbonds; j++) {
      int bond = PyInt_AsLong(PyList_GET_ITEM(atomids, j));
      if (PyErr_Occurred())
        return NULL;
      if (bond >= 0 && bond < mol->nAtoms) {
        atom->bondTo[k++] = bond;
      } else {
        char buf[40];
        sprintf(buf, "Invalid atom id in bondlist: %d", bond);
        PyErr_SetString(PyExc_ValueError, buf);
        return NULL;
      }
    }
    atom->bonds = k;
  }
  Py_INCREF(Py_None);
  return Py_None;
}
static PyObject *py_align(PyObject *self, PyObject *args) {
  int selmol, selframe, refmol, refframe, movemol, moveframe;
  PyObject *selobj, *refobj, *moveobj, *weightobj = NULL;
  if (!PyArg_ParseTuple(args, (char *)"iiO!iiO!iiO!O:atomselection.align",
        &selmol, &selframe, &PyTuple_Type, &selobj,
        &refmol, &refframe, &PyTuple_Type, &refobj,
        &movemol, &moveframe, &PyTuple_Type, &moveobj,
        &weightobj))
    return NULL;

  // check if movemol is -1.  If so, use the sel molecule and timestep instead
  if (movemol == -1) {
    movemol = selmol;
    moveobj = NULL;
  }
  VMDApp *app = get_vmdapp();
  AtomSel *sel=NULL, *ref=NULL, *move=NULL;
  if (!(sel = sel_from_py(selmol, selframe, selobj, app)) ||
      !(ref = sel_from_py(refmol, refframe, refobj, app)) ||
      !(move = sel_from_py(movemol, moveframe, moveobj, app))) {
    delete sel;
    delete ref;
    delete move;
    return NULL;
  }
  const float *selts, *refts;
  float *movets;
  if (!(selts = sel->coordinates(app->moleculeList)) ||
      !(refts = ref->coordinates(app->moleculeList)) || 
      !(movets = move->coordinates(app->moleculeList))) {
    delete sel;
    delete ref;
    delete move;
    PyErr_SetString(PyExc_ValueError, "No coordinates in selection");
    return NULL;
  }
  float *weight = parse_weight(sel, weightobj);
  if (!weight) {
    delete sel;
    delete ref;
    delete move;
    return NULL;
  }
  // Find the matrix that aligns sel with ref.  Apply the transformation to
  // the atoms in move.
  // XXX need to add support for the "order" parameter as in Tcl.
  Matrix4 mat;
  int rc = measure_fit(sel, ref, selts, refts, weight, NULL, &mat);
  delete [] weight;
  delete sel;
  delete ref;
  if (rc < 0) {
    delete move;
    PyErr_SetString(PyExc_ValueError, (char *)measure_error(rc));
    return NULL;
  }
  for (int i=0; i<move->num_atoms; i++) {
    if (move->on[i]) {
      float *pos = movets+3*i;
      mat.multpoint3d(pos, pos);
    }
  }
  Molecule *mol = app->moleculeList->mol_from_id(move->molid());
  mol->force_recalc(DrawMolItem::MOL_REGEN);
  delete move;
  Py_INCREF(Py_None);
  return Py_None;
}
static PyObject *set(PyObject *self, PyObject *args) {
  int i, molid, frame;
  PyObject *selected, *val;
  char *attr = 0;

  //
  // get molid, frame, list, attribute, and value
  //
  if (!PyArg_ParseTuple(args, (char *)"iiO!sO!", &molid, &frame,
                        &PyTuple_Type, &selected, 
                        &attr, &PyTuple_Type, &val ))
    return NULL;  // bad args

  // 
  // check that we have been given either one value or one for each selected
  // atom
  //
  int num_selected = PyTuple_Size(selected);
  int tuplesize = PyTuple_Size(val);
  if (tuplesize != 1 && tuplesize != num_selected) {
    PyErr_SetString(PyExc_ValueError, "wrong number of items");
    return NULL; 
  }
 
  //
  // check molecule
  //
  VMDApp *app = get_vmdapp();
  Molecule *mol = app->moleculeList->mol_from_id(molid);
  if (!mol) {
    PyErr_SetString(PyExc_ValueError, "molecule no longer exists");
    return NULL;
  }
  const int num_atoms = mol->nAtoms;

  //
  // Check for a valid attribute
  //
  SymbolTable *table = app->atomSelParser;
  int attrib_index = table->find_attribute(attr);
  if (attrib_index == -1) {
    PyErr_SetString(PyExc_ValueError, "unknown atom attribute");
    return NULL;
  }
  SymbolTableElement *elem = table->fctns.data(attrib_index);
  if (elem->is_a != SymbolTableElement::KEYWORD &&
      elem->is_a != SymbolTableElement::SINGLEWORD) {
    PyErr_SetString(PyExc_ValueError, "attribute is not a keyword or singleword");
    return NULL;
  }
  if (!table->is_changeable(attrib_index)) {
    PyErr_SetString(PyExc_ValueError, "attribute is not modifiable");
    return NULL; 
  }

  // 
  // convert the list of selected atoms into an array of integer flags
  //
  // XXX should check that selected contains valid indices
  int *flgs = new int[num_atoms];
  memset(flgs,0,num_atoms*sizeof(int));
  for (i=0; i<num_selected; i++)
    flgs[PyInt_AsLong(PyTuple_GET_ITEM(selected,i))] = 1;
 
  //  
  // set the data
  //

  // singlewords can never be set, so macro is NULL.
  atomsel_ctxt context(table, mol, frame, NULL);
  if (elem->returns_a == SymbolTableElement::IS_INT) {
    int *list = new int[num_atoms];
    if (tuplesize > 1) {
      int j=0;
      for (int i=0; i<num_atoms; i++) {
        if (flgs[i])
          list[i] = PyInt_AsLong(PyTuple_GET_ITEM(val, j++));
      }
    } else {
      for (int i=0; i<num_atoms; i++) {
        if (flgs[i])
          list[i] = PyInt_AsLong(PyTuple_GET_ITEM(val, 0));
      }
    }
    elem->set_keyword_int(&context, num_atoms, list, flgs);
    delete [] list;

  } else if (elem->returns_a == SymbolTableElement::IS_FLOAT) {
    double *list = new double[num_atoms];
    if (tuplesize > 1) { 
      int j=0;
      for (int i=0; i<num_atoms; i++) { 
        if (flgs[i])
          list[i] = PyFloat_AsDouble(PyTuple_GET_ITEM(val, j++));
      }
    } else {
      for (int i=0; i<num_atoms; i++) {
        if (flgs[i])
          list[i] = PyFloat_AsDouble(PyTuple_GET_ITEM(val, 0));
      }
    }
    elem->set_keyword_double(&context, num_atoms, list, flgs);
    delete [] list;


  } else if (elem->returns_a == SymbolTableElement::IS_STRING) {

    const char **list = new const char *[num_atoms];
    if (tuplesize > 1) { 
      int j=0;
      for (int i=0; i<num_atoms; i++) { 
        if (flgs[i])
          list[i] = PyString_AsString(PyTuple_GET_ITEM(val, j++));
      }
    } else {
      for (int i=0; i<num_atoms; i++) {
        if (flgs[i])
          list[i] = PyString_AsString(PyTuple_GET_ITEM(val, 0));
      }
    }
    elem->set_keyword_string(&context, num_atoms, list, flgs);
    delete [] list;
  }

  // Recompute the color assignments if certain atom attributes are changed.
  if (!strcmp(attr, "name") ||
      !strcmp(attr, "type") ||
      !strcmp(attr, "resname") ||
      !strcmp(attr, "chain") ||
      !strcmp(attr, "segid") ||
      !strcmp(attr, "segname")) 
    app->moleculeList->add_color_names(molid);

  mol->force_recalc(DrawMolItem::SEL_REGEN | DrawMolItem::COL_REGEN); 
  delete [] flgs;
  Py_INCREF(Py_None);
  return Py_None;
}