OSDN Git Service

Start implementing JANPA integration with Psi4
[molby/Molby.git] / MolLib / Ruby_bind / ruby_bind.c
index 84936f1..9d7452e 100644 (file)
 #include <ctype.h>
 #include <limits.h>
 
-#include <version.h>  /*  for Ruby version  */
-#include <node.h>     /*  for rb_add_event_hook()  */
+#include "version.h"       /*  for Ruby version  */
+#include "ruby/version.h"  /*  for RUBY_BIRTH_YEAR etc.  */
+#include "ruby/encoding.h" /*  for rb_str_encode() etc. */
+/*#include <node.h>     *//*  for rb_add_event_hook()  */
 
 #if defined(__WXMAC__) || defined(__CMDMAC__)
 #define USE_PTHREAD_FOR_TIMER 1
@@ -51,6 +53,9 @@ VALUE rb_cMolecule, rb_cMolEnumerable, rb_cAtomRef;
 VALUE rb_cParameter, rb_cParEnumerable, rb_cParameterRef;
 
 VALUE gMolbyBacktrace;
+VALUE gMolbyErrorHistory;
+VALUE gScriptMenuCommands;
+VALUE gScriptMenuEnablers;
 
 int gMolbyRunLevel = 0;
 int gMolbyIsCheckingInterrupt = 0;
@@ -71,9 +76,9 @@ static VALUE
        s_FractRSym, s_FractXSym, s_FractYSym, s_FractZSym,
        s_SigmaSym, s_SigmaXSym, s_SigmaYSym, s_SigmaZSym,
        s_VSym, s_FSym, s_OccupancySym, s_TempFactorSym,
-       s_AnisoSym, s_SymopSym, s_IntChargeSym, s_FixForceSym,
-       s_FixPosSym, s_ExclusionSym, s_MMExcludeSym, s_PeriodicExcludeSym,
-       s_HiddenSym, s_AnchorListSym, s_UFFTypeSym;
+       s_AnisoSym, s_AnisoEigvalSym, s_SymopSym, s_IntChargeSym,
+    s_FixForceSym, s_FixPosSym, s_ExclusionSym, s_MMExcludeSym,
+    s_PeriodicExcludeSym, s_HiddenSym, s_AnchorListSym, s_UFFTypeSym;
 
 /*  Symbols for parameter attributes  */
 static VALUE
@@ -83,9 +88,13 @@ static VALUE
        s_ReqSym, s_EpsSym,
        /* s_A14Sym, s_B14Sym, */
        s_Req14Sym, s_Eps14Sym,
-       s_CutoffSym, s_RadiusSym, s_ColorSym, s_FullNameSym,
+       s_CutoffSym, s_RadiusSym, s_ColorSym, s_FullNameSym, s_VdwRadiusSym,
        s_CommentSym, s_SourceSym;
 
+/*  Symbols for graphics  */
+static VALUE
+       s_LineSym, s_PolySym, s_CylinderSym, s_ConeSym, s_EllipsoidSym;
+
 /*
  *  Utility function
  *  Get ary[i] by calling "[]" method
@@ -111,7 +120,7 @@ Ruby_FileStringValuePtr(VALUE *valp)
 #if __WXMSW__
        char *p = strdup(StringValuePtr(*valp));
        translate_char(p, '/', '\\');
-       *valp = rb_str_new2(p);
+       *valp = Ruby_NewEncodedStringValue2(p);
        free(p);
        return StringValuePtr(*valp);
 #else
@@ -126,14 +135,36 @@ Ruby_NewFileStringValue(const char *fstr)
        VALUE retval;
        char *p = strdup(fstr);
        translate_char(p, '\\', '/');
-       retval = rb_str_new2(p);
+       retval = Ruby_NewEncodedStringValue2(p);
        free(p);
        return retval;
 #else
-       return rb_str_new2(fstr);
+       return Ruby_NewEncodedStringValue2(fstr);
 #endif
 }
 
+char *
+Ruby_EncodedStringValuePtr(VALUE *valp)
+{
+       rb_string_value(valp);
+       *valp = rb_str_encode(*valp, rb_enc_from_encoding(rb_default_external_encoding()), 0, Qnil);
+       return RSTRING_PTR(*valp);
+}
+
+VALUE
+Ruby_NewEncodedStringValue(const char *str, int len)
+{
+       if (len <= 0)
+               len = strlen(str);
+       return rb_enc_str_new(str, len, rb_default_external_encoding());
+}
+
+VALUE
+Ruby_NewEncodedStringValue2(const char *str)
+{
+    return Ruby_NewEncodedStringValue(str, -1);
+}
+
 VALUE
 Ruby_ObjToStringObj(VALUE val)
 {
@@ -225,7 +256,7 @@ s_Kernel_Ask(int argc, VALUE *argv, VALUE self)
        } else buf[0] = 0;
        retval = MyAppCallback_getTextWithPrompt(StringValuePtr(prompt), buf, sizeof buf);
        if (retval)
-               return rb_str_new2(buf);
+               return Ruby_NewEncodedStringValue2(buf);
        else
                return Qnil;    
 }
@@ -307,6 +338,134 @@ s_Kernel_StopSound(VALUE self)
 
 /*
  *  call-seq:
+ *     export_to_clipboard(str)
+ *
+ *  Export the given string to clipboard.
+ */
+static VALUE
+s_Kernel_ExportToClipboard(VALUE self, VALUE sval)
+{
+#if !defined(__CMDMAC__)
+    const char *s;
+       char *ns;
+    if (!gUseGUI)
+        return Qnil;
+    s = StringValuePtr(sval);
+#if __WXMSW__
+       /*  Convert the end-of-line characters  */
+       {       const char *p; int nc; char *np;
+               nc = 0;
+               for (p = s; *p != 0; p++) {
+                       if (*p == '\n')
+                               nc++;
+               }       
+               ns = (char *)malloc(strlen(s) + nc + 1);
+               for (np = ns, p = s; *p != 0; p++, np++) {
+                       if (*p == '\n')
+                               *np++ = '\r';
+                       *np = *p;
+               }
+               *np = 0;
+       }
+#else
+       ns = (char *)malloc(strlen(s) + 1);
+       strcpy(ns, s);
+#if __WXMAC__
+       {       char *np;
+               /*  wxMac still has Carbon code. Oops.  */
+               for (np = ns; *np != 0; np++) {
+                       if (*np == '\n')
+                               *np = '\r';
+               }
+       }
+#endif
+#endif
+       if (MoleculeCallback_writeToPasteboard("TEXT", ns, strlen(ns) + 1))
+               rb_raise(rb_eMolbyError, "Cannot export string to clipboard");
+#endif
+       return Qnil;
+}
+
+/*
+ *  call-seq:
+ *     hartree_to_kcal(val)
+ *
+ *  Convert hartree to kcal/mol
+ */
+static VALUE
+s_Kernel_HartreeToKcal(VALUE self, VALUE fval)
+{
+    double d = NUM2DBL(rb_Float(fval));
+    return rb_float_new(d * 627.5094740630557);
+}
+
+/*
+ *  call-seq:
+ *     kcal_to_hartree(val)
+ *
+ *  Convert kcal/mol to hartree
+ */
+static VALUE
+s_Kernel_KcalToHartree(VALUE self, VALUE fval)
+{
+    double d = NUM2DBL(rb_Float(fval));
+    return rb_float_new(d / 627.5094740630557);
+}
+
+/*
+ *  call-seq:
+ *     hartree_to_kj(val)
+ *
+ *  Convert hartree to kJ/mol
+ */
+static VALUE
+s_Kernel_HartreeToKJ(VALUE self, VALUE fval)
+{
+    double d = NUM2DBL(rb_Float(fval));
+    return rb_float_new(d * 2625.4996394798253);
+}
+
+/*
+ *  call-seq:
+ *     kj_to_hartree(val)
+ *
+ *  Convert kJ/mol to hartree
+ */
+static VALUE
+s_Kernel_KJToHartree(VALUE self, VALUE fval)
+{
+    double d = NUM2DBL(rb_Float(fval));
+    return rb_float_new(d / 2625.4996394798253);
+}
+
+/*
+ *  call-seq:
+ *     bohr_to_angstrom(val)
+ *
+ *  Convert bohr to angstrom
+ */
+static VALUE
+s_Kernel_BohrToAngstrom(VALUE self, VALUE fval)
+{
+    double d = NUM2DBL(rb_Float(fval));
+    return rb_float_new(d * 0.529177210903);
+}
+
+/*
+ *  call-seq:
+ *     angstrom_to_bohr(val)
+ *
+ *  Convert angstrom to bohr
+ */
+static VALUE
+s_Kernel_AngstromToBohr(VALUE self, VALUE fval)
+{
+    double d = NUM2DBL(rb_Float(fval));
+    return rb_float_new(d / 0.529177210903);
+}
+
+/*
+ *  call-seq:
  *     stdout.write(str)
  *
  *  Put the message in the main text view in black color.
@@ -338,6 +497,19 @@ s_StandardErrorOutput(VALUE self, VALUE str)
 
 /*
  *  call-seq:
+ *     stdout.flush
+ *     stderr.flush
+ *
+ *  Flush the standard (error) output. Actually do nothing.
+ */
+static VALUE
+s_FlushConsoleOutput(VALUE self)
+{
+       return self;
+}
+
+/*
+ *  call-seq:
  *     stdin.gets(rs = $/)
  *
  *  Read one line message via dialog box.
@@ -346,7 +518,7 @@ static VALUE
 s_StandardInputGets(int argc, VALUE *argv, VALUE self)
 {
        VALUE pval, rval;
-       pval = rb_str_new2("Enter a line:");
+       pval = Ruby_NewEncodedStringValue2("Enter a line:");
        rval = s_Kernel_Ask(1, &pval, self);
        if (rval == Qnil)
                rb_interrupt();
@@ -434,9 +606,6 @@ s_SetInterruptFlag(VALUE self, VALUE val)
        oldval = s_interrupt_flag;
        if (val != Qundef) {
                s_interrupt_flag = val;
-               if (val == Qfalse) {
-                       s_HideProgressPanel(self);
-               }
        }
        return oldval;
 }
@@ -447,47 +616,6 @@ s_GetInterruptFlag(VALUE self)
        return s_SetInterruptFlag(self, Qundef);
 }
 
-#if 0
-static VALUE
-s_Ruby_CallMethod(VALUE val)
-{
-       void **ptr = (void **)val;
-       VALUE receiver = (VALUE)ptr[0];
-       ID method_id = (ID)ptr[1];
-       VALUE args = (VALUE)ptr[2];
-       VALUE retval;
-       if (method_id == 0) {
-               /*  args should be a string, which is evaluated  */
-               if (receiver == Qnil) {
-                       retval = rb_eval_string(StringValuePtr(args));
-               } else {
-                       retval = rb_obj_instance_eval(1, &args, receiver);
-               }
-       } else {
-               /*  args should be an array of arguments  */
-               retval = rb_apply(receiver, method_id, args);
-       }
-       return retval;
-}
-
-VALUE
-Ruby_CallMethodWithInterrupt(VALUE receiver, ID method_id, VALUE args, int *status)
-{
-       VALUE retval, save_interrupt_flag;
-       void *ptr[3];
-       save_interrupt_flag = s_SetInterruptFlag(Qnil, Qtrue);
-       ptr[0] = (void *)receiver;
-       ptr[1] = (void *)method_id;
-       ptr[2] = (void *)args;
-       MyAppCallback_beginUndoGrouping();
-       retval = rb_protect(s_Ruby_CallMethod, (VALUE)ptr, status);
-       MyAppCallback_endUndoGrouping();
-       s_SetInterruptFlag(Qnil, save_interrupt_flag);
-       MyAppCallback_hideProgressPanel();  /*  In case when the progress panel is still onscreen */\v
-       return retval;
-}
-#endif
-
 VALUE
 Ruby_SetInterruptFlag(VALUE val)
 {
@@ -628,7 +756,8 @@ s_GetTimerCount(void)
 }
 
 static void
-s_Event_Callback(rb_event_t event, NODE *node, VALUE self, ID rid, VALUE klass)
+//s_Event_Callback(rb_event_t event, NODE *node, VALUE self, ID rid, VALUE klass)
+s_Event_Callback(rb_event_flag_t evflag, VALUE data, VALUE self, ID mid, VALUE klass)
 {
        if (s_interrupt_flag != Qfalse) {
                static unsigned long sLastTime = 0;
@@ -652,24 +781,79 @@ s_Event_Callback(rb_event_t event, NODE *node, VALUE self, ID rid, VALUE klass)
 
 /*
  *  call-seq:
- *     register_menu(title, method)
+ *     register_menu(title, method, enable_proc = nil)
  *
  *  Register the method (specified as a symbol) in the script menu.
  *  The method must be either an instance method of Molecule with no argument,
- *  or a class method of Molecule with one argument (the current molecule).
+ *  or a class method of Molecule with one argument (the current molecule),
+ *  or a proc object with one argument (the current molecule).
  *  The menu associated with the class method can be invoked even when no document
  *  is open (the argument is set to Qnil in this case). On the other hand, the
  *  menu associated with the instance method can only be invoked when at least one 
  *  document is active.
- */
-static VALUE
-s_Kernel_RegisterMenu(VALUE self, VALUE title, VALUE method)
-{
-       if (TYPE(method) == T_SYMBOL) {
-               method = rb_funcall(method, rb_intern("to_s"), 0);
+ *  If enable_proc is non-nil, then it is called whenever the availability of
+ *  the menu command is tested. It is usually a proc object with one argument
+ *  (the current molecule or nil). As a special case, the following symbols can
+ *  be given; :mol (enabled when any molecule is open), :non_empty (enabled when
+ *  the top-level molecule has at least one atom), :selection (enabled when
+ *  the top-level molecule has one or more selected atoms).
+ */
+static VALUE
+s_Kernel_RegisterMenu(int argc, VALUE *argv, VALUE self)
+{
+       int n, mtype = 0;
+       VALUE tval, mval, pval;
+       static VALUE sMolSym, sNonEmptySym, sSelectionSym;
+       static VALUE sMolProc, sNonEmptyProc, sSelectionProc, sTrueProc;
+       rb_scan_args(argc, argv, "21", &tval, &mval, &pval);
+       tval = rb_str_to_str(tval);
+       n = MyAppCallback_registerScriptMenu(StringValuePtr(tval));
+       if (n < 0)
+               return Qnil;
+       if (TYPE(mval) == T_SYMBOL) {
+               /*  Create an appropriate proc object  */
+               const char *name = rb_id2name(SYM2ID(mval));
+               char *s;
+               if (rb_funcall(rb_cMolecule, rb_intern("method_defined?"), 1, mval) != Qfalse) {
+                       /*  Defined as a Molecule method  */
+                       asprintf(&s, "lambda { |m| m.%s }", name);
+                       mtype = 1;
+               } else if (rb_respond_to(rb_cMolecule, SYM2ID(mval))) {
+                       /*  Defined as a Molecule class method  */
+                       asprintf(&s, "lambda { |m| Molecule.%s(m) }", name);
+                       mtype = 2;
+               } else rb_raise(rb_eMolbyError, "The method %s is not defined in Molecule", name);
+               mval = rb_eval_string(s);
+               free(s);
+       }
+       if (sMolSym == Qfalse) {
+               sMolSym = ID2SYM(rb_intern("mol"));
+               sNonEmptySym = ID2SYM(rb_intern("non_empty"));
+               sSelectionSym = ID2SYM(rb_intern("selection"));
+               sMolProc = rb_eval_string("lambda { |m| m != nil }");
+        rb_define_variable("$is_a_molecule_proc", &sMolProc);
+               sNonEmptyProc = rb_eval_string("lambda { |m| m.is_a?(Molecule) && m.natoms > 0 }");
+        rb_define_variable("$is_molecule_not_empty_proc", &sNonEmptyProc);
+               sSelectionProc = rb_eval_string("lambda { |m| m.is_a?(Molecule) && m.selection.count > 0 }");
+        rb_define_variable("$has_molecule_selection_proc", &sSelectionProc);
+               sTrueProc = rb_eval_string("lambda { |m| true }");
+        rb_define_variable("$always_true_proc", &sTrueProc);
        }
-       MyAppCallback_registerScriptMenu(StringValuePtr(method), StringValuePtr(title));
-       return self;
+       
+       if (pval == Qnil) {
+               if (mtype == 1)
+                       pval = sMolProc;
+               else
+                       pval = sTrueProc;
+       } else if (pval == sMolSym)
+               pval = sMolProc;
+       else if (pval == sNonEmptySym)
+               pval = sNonEmptyProc;
+       else if (pval == sSelectionSym)
+               pval = sSelectionProc;
+       rb_ary_store(gScriptMenuCommands, n, mval);
+       rb_ary_store(gScriptMenuEnablers, n, pval);
+       return INT2NUM(n);
 }
 
 static VALUE
@@ -680,6 +864,54 @@ s_Kernel_LookupMenu(VALUE self, VALUE title)
 }
 
 static VALUE
+s_Ruby_UpdateUI_handler(VALUE data)
+{
+       void **p = (void **)data;
+       int index = (intptr_t)p[0];
+       Molecule *mol = (Molecule *)p[1];
+       int *outChecked = (int *)p[2];
+       char **outTitle = (char **)p[3];
+       VALUE mval = ValueFromMolecule(mol);
+       VALUE pval = rb_ary_entry(gScriptMenuEnablers, index);
+       static ID call_id = 0;
+       if (call_id == 0)
+               call_id = rb_intern("call");
+       if (pval == Qnil)
+               return Qnil;
+       pval = rb_funcall(pval, call_id, 1, mval);
+       if (rb_obj_is_kind_of(pval, rb_cArray)) {
+               VALUE val;
+               if (outChecked != NULL) {
+                       val = rb_ary_entry(pval, 1);  /*  Checked or not  */
+                       *outChecked = (RTEST(val) ? 1 : 0);
+               }
+               if (outTitle != NULL) {
+                       val = rb_ary_entry(pval, 2);  /*  Text  */
+                       if (TYPE(val) == T_STRING) {
+                               *outTitle = strdup(StringValuePtr(val));
+                       }
+               }
+               pval = rb_ary_entry(pval, 0);
+       }
+       return pval;
+}
+
+int
+Ruby_UpdateUI(int index, Molecule *mol, int *outChecked, char **outTitle)
+{
+       int status;
+       void *p[4];
+       VALUE retval;
+       p[0] = (void *)(intptr_t)index;
+       p[1] = mol;
+       p[2] = outChecked;
+       p[3] = outTitle;
+       retval = rb_protect(s_Ruby_UpdateUI_handler, (VALUE)p, &status);
+       return (RTEST(retval) ? 1 : 0);
+}
+
+/*
+static VALUE
 s_Ruby_methodType_sub(VALUE data)
 {
        const char **p = (const char **)data;
@@ -693,10 +925,10 @@ s_Ruby_methodType_sub(VALUE data)
        else ival = 0;
        return INT2FIX(ival);
 }
-       
+*/     
 /*  Returns 1 if the class defines the instance method with the given name, 2 if the class
     has the singleton method (class method) with the given name, 0 otherwise.  */
-int
+/*int
 Ruby_methodType(const char *className, const char *methodName)
 {
        int status;
@@ -709,6 +941,7 @@ Ruby_methodType(const char *className, const char *methodName)
                return FIX2INT(retval);
        else return 0;
 }
+*/
 
 /*
  *  call-seq:
@@ -776,8 +1009,10 @@ static VALUE
 s_Kernel_CallSubProcess(int argc, VALUE *argv, VALUE self)
 {
        VALUE cmd, procname, cproc, stdout_val, stderr_val;
-       int n;
+    VALUE save_interruptFlag;
+       int n, exitstatus, pid;
        char *sout, *serr;
+    const char *pnamestr;
        FILE *fpout, *fperr;
 
        rb_scan_args(argc, argv, "23", &cmd, &procname, &cproc, &stdout_val, &stderr_val);
@@ -820,9 +1055,14 @@ s_Kernel_CallSubProcess(int argc, VALUE *argv, VALUE self)
                                rb_raise(rb_eMolbyError, "Cannot open file for standard output (%s)", serr);
                }
        }
-
-       n = MyAppCallback_callSubProcess(StringValuePtr(cmd), StringValuePtr(procname), (cproc == Qnil ? NULL : s_Kernel_CallSubProcess_Callback), (cproc == Qnil ? NULL : (void *)cproc), fpout, fperr);
-       
+    
+    save_interruptFlag = s_SetInterruptFlag(self, Qnil);
+    if (procname != Qnil)
+        pnamestr = StringValuePtr(procname);
+    else pnamestr = NULL;
+       n = MyAppCallback_callSubProcess(StringValuePtr(cmd), pnamestr, (cproc == Qnil ? NULL : s_Kernel_CallSubProcess_Callback), (cproc == Qnil ? NULL : (void *)cproc), fpout, fperr, &exitstatus, &pid);
+    s_SetInterruptFlag(self, save_interruptFlag);
+    
        if (fpout != NULL && fpout != (FILE *)1)
                fclose(fpout);
        if (fperr != NULL && fperr != (FILE *)1)
@@ -843,17 +1083,17 @@ static VALUE
 s_Kernel_Backquote(VALUE self, VALUE cmd)
 {
        char *buf;
-       int n;
+       int n, exitstatus, pid;
        VALUE val;
-       n = MyAppCallback_callSubProcess(StringValuePtr(cmd), NULL, DUMMY_CALLBACK, &buf, NULL, NULL);
-       if (n != 0)
-               rb_raise(rb_eMolbyError, "Cannot invoke command '%s'", StringValuePtr(cmd));
-       if (buf != NULL) {
-               val = rb_str_new2(buf);
+       n = MyAppCallback_callSubProcess(StringValuePtr(cmd), NULL, DUMMY_CALLBACK, &buf, NULL, NULL, &exitstatus, &pid);
+/*     fprintf(stderr, "n = %d, exitstatus = %d, pid = %d\n", n, exitstatus, pid); */
+       if (n >= 0 && buf != NULL) {
+               val = Ruby_NewEncodedStringValue(buf, 0);
                free(buf);
        } else {
-               val = rb_str_new2("");
+               val = Ruby_NewEncodedStringValue("", 0);
        }
+       rb_last_status_set(exitstatus, pid);
        return val;
 }
 
@@ -890,6 +1130,75 @@ s_Kernel_SetGlobalSettings(VALUE self, VALUE key, VALUE value)
        return value;
 }
 
+#pragma mark ====== IO extension ======
+
+static VALUE
+s_Ruby_str_encode_protected(VALUE val)
+{
+       return rb_str_encode(val, rb_enc_from_encoding(rb_default_external_encoding()),
+                                 ECONV_INVALID_REPLACE | ECONV_UNDEF_REPLACE, Qnil);
+}
+
+/*
+ *  call-seq:
+ *     gets_any_eol
+ *
+ *  A gets variant that works for CR, LF, and CRLF end-of-line characters. 
+ */
+static VALUE
+s_IO_gets_any_eol(VALUE self)
+{
+       VALUE val, val2, cval;
+       char buf[1024];
+       int i, c, status;
+       static ID id_getbyte = 0, id_ungetbyte;
+       if (id_getbyte == 0) {
+               id_getbyte = rb_intern("getbyte");
+               id_ungetbyte = rb_intern("ungetbyte");
+       }
+       i = 0;
+       val = Qnil;
+       while ((cval = rb_funcall(self, id_getbyte, 0)) != Qnil) {
+               c = NUM2INT(rb_Integer(cval));
+               if (c == 0x0d) {
+                       cval = rb_funcall(self, id_getbyte, 0);
+                       if (cval != Qnil) {
+                               c = NUM2INT(rb_Integer(cval));
+                               if (c != 0x0a)
+                                       rb_funcall(self, id_ungetbyte, 1, cval);
+                       }
+                       break;
+               } else if (c != 0x0a) {
+                       buf[i++] = c;
+                       if (i >= 1020) {
+                               buf[i] = 0;
+                               if (val == Qnil)
+                                       val = rb_str_new(buf, i);
+                               else
+                                       rb_str_append(val, rb_str_new(buf, i));
+                               i = 0;
+                       }
+               } else break;
+       }
+       if (cval == Qnil && i == 0 && val == Qnil)
+               return Qnil;  /*  End of file  */
+       buf[i] = 0;
+       if (val == Qnil)
+               val = rb_str_new(buf, i);
+       else if (i > 0)
+               rb_str_append(val, rb_str_new(buf, i));
+       val2 = rb_protect(s_Ruby_str_encode_protected, val, &status); /*  Ignore exception  */
+       if (status == 0)
+               val = val2;
+       if (cval != Qnil) {
+               /*  Needs a end-of-line mark  */
+               cval = rb_gv_get("$/");
+               rb_str_append(val, cval);
+       }
+       rb_gv_set("$_", val);
+       return val;
+}
+
 #pragma mark ====== Utility functions (protected funcall) ======
 
 struct Ruby_funcall2_record {
@@ -1035,10 +1344,10 @@ static VALUE s_ParameterRef_GetParType(VALUE self) {
        Int tp;
        s_UnionParFromValue(self, &tp, 0);
        if (tp == kElementParType)
-               return rb_str_new2("element");
+               return Ruby_NewEncodedStringValue2("element");
        tp -= kFirstParType;
        if (tp >= 0 && tp < sizeof(s_ParameterTypeNames) / sizeof(s_ParameterTypeNames[0]))
-               return rb_str_new2(s_ParameterTypeNames[tp]);
+               return Ruby_NewEncodedStringValue2(s_ParameterTypeNames[tp]);
        else rb_raise(rb_eMolbyError, "Internal error: parameter type tag is out of range (%d)", tp);
 }
 
@@ -1399,7 +1708,7 @@ static VALUE s_ParameterRef_GetCutoff(VALUE self) {
  *  call-seq:
  *     radius -> Float
  *
- *  Get the atomic radius for the atom display parameter.
+ *  Get the atomic (covalent) radius for the element parameter.
  */
 static VALUE s_ParameterRef_GetRadius(VALUE self) {
        UnionPar *up;
@@ -1412,16 +1721,31 @@ static VALUE s_ParameterRef_GetRadius(VALUE self) {
 
 /*
  *  call-seq:
+ *     vdw_radius -> Float
+ *
+ *  Get the van der Waals radius for the element parameter. (0 if not given)
+ */
+static VALUE s_ParameterRef_GetVdwRadius(VALUE self) {
+       UnionPar *up;
+       Int tp;
+       up = s_UnionParFromValue(self, &tp, 0);
+       if (tp == kElementParType)
+               return rb_float_new(up->atom.vdw_radius);
+       else rb_raise(rb_eMolbyError, "invalid member vdw_radius");
+}
+
+/*
+ *  call-seq:
  *     color -> [Float, Float, Float]
  *
- *  Get the rgb color for the atom display parameter.
+ *  Get the rgb color for the element parameter.
  */
 static VALUE s_ParameterRef_GetColor(VALUE self) {
        UnionPar *up;
        Int tp;
        up = s_UnionParFromValue(self, &tp, 0);
        if (tp == kElementParType)
-               return rb_ary_new3(3, rb_float_new(up->atom.r), rb_float_new(up->atom.g), rb_float_new(up->atom.b));
+               return rb_ary_new3(3, rb_float_new(up->atom.red / 65535.0), rb_float_new(up->atom.green / 65535.0), rb_float_new(up->atom.blue / 65535.0));
        else rb_raise(rb_eMolbyError, "invalid member color");
 }
 
@@ -1429,7 +1753,7 @@ static VALUE s_ParameterRef_GetColor(VALUE self) {
  *  call-seq:
  *     atomic_number -> Integer
  *
- *  Get the atomic number for the vdw or atom parameter.
+ *  Get the atomic number for the vdw or element parameter.
  */
 static VALUE s_ParameterRef_GetAtomicNumber(VALUE self) {
        UnionPar *up;
@@ -1446,7 +1770,7 @@ static VALUE s_ParameterRef_GetAtomicNumber(VALUE self) {
  *  call-seq:
  *     name -> String
  *
- *  Get the name for the atom display parameter.
+ *  Get the name for the element parameter.
  */
 static VALUE s_ParameterRef_GetName(VALUE self) {
        UnionPar *up;
@@ -1456,7 +1780,7 @@ static VALUE s_ParameterRef_GetName(VALUE self) {
                char name[5];
                strncpy(name, up->atom.name, 4);
                name[4] = 0;
-               return rb_str_new2(name);
+               return Ruby_NewEncodedStringValue2(name);
        } else rb_raise(rb_eMolbyError, "invalid member name");
 }
 
@@ -1464,7 +1788,7 @@ static VALUE s_ParameterRef_GetName(VALUE self) {
  *  call-seq:
  *     weight -> Float
  *
- *  Get the atomic weight for the atom display parameter.
+ *  Get the atomic weight for the element parameter.
  */
 static VALUE s_ParameterRef_GetWeight(VALUE self) {
        UnionPar *up;
@@ -1481,7 +1805,7 @@ static VALUE s_ParameterRef_GetWeight(VALUE self) {
  *  call-seq:
  *     fullname -> String
  *
- *  Get the full name for the atom display parameter.
+ *  Get the full name for the element parameter.
  */
 static VALUE s_ParameterRef_GetFullName(VALUE self) {
        UnionPar *up;
@@ -1491,7 +1815,7 @@ static VALUE s_ParameterRef_GetFullName(VALUE self) {
                char fullname[16];
                strncpy(fullname, up->atom.fullname, 15);
                fullname[15] = 0;
-               return rb_str_new2(fullname);
+               return Ruby_NewEncodedStringValue2(fullname);
        } else rb_raise(rb_eMolbyError, "invalid member fullname");
 }
 
@@ -1508,7 +1832,7 @@ static VALUE s_ParameterRef_GetComment(VALUE self) {
        com = up->bond.com;
        if (com == 0)
                return Qnil;
-       else return rb_str_new2(ParameterGetComment(com));
+       else return Ruby_NewEncodedStringValue2(ParameterGetComment(com));
 }
 
 /*
@@ -1527,7 +1851,7 @@ static VALUE s_ParameterRef_GetSource(VALUE self) {
                return Qfalse;  /* undefined */
        else if (src == 0)
                return Qnil;  /*  local  */
-       else return rb_str_new2(ParameterGetComment(src));
+       else return Ruby_NewEncodedStringValue2(ParameterGetComment(src));
 }
 
 static void
@@ -1988,6 +2312,21 @@ static VALUE s_ParameterRef_SetRadius(VALUE self, VALUE val) {
        return val;
 }
 
+static VALUE s_ParameterRef_SetVdwRadius(VALUE self, VALUE val) {
+       UnionPar *up;
+       Int tp, oldsrc;
+       VALUE oldval;
+       up = s_UnionParFromValue(self, &tp, 1);
+       oldval = s_ParameterRef_GetVdwRadius(self);
+       oldsrc = up->bond.src;
+       val = rb_Float(val);
+       if (tp == kElementParType) {
+               up->atom.vdw_radius = NUM2DBL(val);
+       } else rb_raise(rb_eMolbyError, "invalid member vdw_radius");
+       s_RegisterUndoForParameterAttrChange(self, s_VdwRadiusSym, val, oldval, oldsrc);        
+       return val;
+}
+
 static VALUE s_ParameterRef_SetColor(VALUE self, VALUE val) {
        UnionPar *up;
        Int tp, oldsrc;
@@ -2000,9 +2339,9 @@ static VALUE s_ParameterRef_SetColor(VALUE self, VALUE val) {
                rb_raise(rb_eMolbyError, "value should be an array of three floats (r, g, b)");
        valp = RARRAY_PTR(val);
        if (tp == kElementParType) {
-               up->atom.r = NUM2DBL(rb_Float(valp[0]));
-               up->atom.g = NUM2DBL(rb_Float(valp[1]));
-               up->atom.b = NUM2DBL(rb_Float(valp[2]));
+               up->atom.red = (unsigned short)(NUM2DBL(rb_Float(valp[0])) * 65535.0);
+               up->atom.green = (unsigned short)(NUM2DBL(rb_Float(valp[1])) * 65535.0);
+               up->atom.blue = (unsigned short)(NUM2DBL(rb_Float(valp[2])) * 65535.0);
        } else rb_raise(rb_eMolbyError, "invalid member color");
        s_RegisterUndoForParameterAttrChange(self, s_ColorSym, val, oldval, oldsrc);    
        return val;
@@ -2133,6 +2472,7 @@ static struct s_ParameterAttrDef {
        {"eps14",        &s_Eps14Sym,        0, s_ParameterRef_GetEps14,        s_ParameterRef_SetEps14},
        {"cutoff",       &s_CutoffSym,       0, s_ParameterRef_GetCutoff,       s_ParameterRef_SetCutoff},
        {"radius",       &s_RadiusSym,       0, s_ParameterRef_GetRadius,       s_ParameterRef_SetRadius},
+       {"vdw_radius",   &s_VdwRadiusSym,    0, s_ParameterRef_GetVdwRadius,    s_ParameterRef_SetVdwRadius},
        {"color",        &s_ColorSym,        0, s_ParameterRef_GetColor,        s_ParameterRef_SetColor},
        {"atomic_number",&s_AtomicNumberSym, 0, s_ParameterRef_GetAtomicNumber, s_ParameterRef_SetAtomicNumber},
        {"name",         &s_NameSym,         0, s_ParameterRef_GetName,         s_ParameterRef_SetName},
@@ -2198,7 +2538,7 @@ s_ParameterRef_Keys(VALUE self)
                case kVdwCutoffParType:
                        return rb_ary_new3(6, s_IndexSym, s_ParTypeSym, s_AtomTypesSym, s_CutoffSym, s_CommentSym, s_SourceSym);
                case kElementParType:
-                       return rb_ary_new3(10, s_IndexSym, s_ParTypeSym, s_AtomicNumberSym, s_NameSym, s_FullNameSym, s_RadiusSym, s_ColorSym, s_WeightSym, s_CommentSym, s_SourceSym);
+                       return rb_ary_new3(10, s_IndexSym, s_ParTypeSym, s_AtomicNumberSym, s_NameSym, s_FullNameSym, s_RadiusSym, s_ColorSym, s_WeightSym, s_VdwRadiusSym, s_CommentSym, s_SourceSym);
                default:
                        rb_raise(rb_eMolbyError, "internal error: invalid parameter type");
        }
@@ -2266,10 +2606,10 @@ s_ParameterRef_ToString(VALUE self)
                        snprintf(buf, sizeof buf, "vdwcutoff %4.6s %4.6s %8.4f", AtomTypeDecodeToString(up->vdwcutoff.type1, types[0]), AtomTypeDecodeToString(up->vdwcutoff.type2, types[1]), up->vdwcutoff.cutoff);
                        break;
                case kElementParType:
-                       snprintf(buf, sizeof buf, "element %2.2s %3d %6.3f %6.3f %6.3f %6.3f %8.4f %s", up->atom.name, up->atom.number, up->atom.radius, up->atom.r, up->atom.g, up->atom.b, up->atom.weight, up->atom.fullname);
+                       snprintf(buf, sizeof buf, "element %2.2s %3d %6.3f %6.3f %6.3f %6.3f %8.4f %s %6.3f", up->atom.name, up->atom.number, up->atom.radius, up->atom.red / 65535.0, up->atom.green / 65535.0, up->atom.blue / 65535.0, up->atom.weight, up->atom.fullname, up->atom.vdw_radius);
                        break;
        }
-       return rb_str_new2(buf);
+       return Ruby_NewEncodedStringValue2(buf);
 }
 
 /*
@@ -3155,10 +3495,10 @@ s_ParEnumerable_ParType(VALUE self) {
     Data_Get_Struct(self, ParEnumerable, pen);
        tp = pen->parType;
        if (tp == kElementParType)
-               return rb_str_new2("element");
+               return Ruby_NewEncodedStringValue2("element");
        tp -= kFirstParType;
        if (tp >= 0 && tp < sizeof(s_ParameterTypeNames) / sizeof(s_ParameterTypeNames[0]))
-               return rb_str_new2(s_ParameterTypeNames[tp]);
+               return Ruby_NewEncodedStringValue2(s_ParameterTypeNames[tp]);
        else rb_raise(rb_eMolbyError, "Internal error: parameter type tag is out of range (%d)", tp);
 }
 
@@ -3520,7 +3860,7 @@ static VALUE s_AtomRef_GetSegSeq(VALUE self) {
 
 static VALUE s_AtomRef_GetSegName(VALUE self) {
        char *p = s_AtomFromValue(self)->segName;
-       return rb_str_new(p, strlen_limit(p, 4));
+       return Ruby_NewEncodedStringValue(p, strlen_limit(p, 4));
 }
 
 static VALUE s_AtomRef_GetResSeq(VALUE self) {
@@ -3529,18 +3869,18 @@ static VALUE s_AtomRef_GetResSeq(VALUE self) {
 
 static VALUE s_AtomRef_GetResName(VALUE self) {
        char *p = s_AtomFromValue(self)->resName;
-       return rb_str_new(p, strlen_limit(p, 4));
+       return Ruby_NewEncodedStringValue(p, strlen_limit(p, 4));
 }
 
 static VALUE s_AtomRef_GetName(VALUE self) {
        char *p = s_AtomFromValue(self)->aname;
-       return rb_str_new(p, strlen_limit(p, 4));
+       return Ruby_NewEncodedStringValue(p, strlen_limit(p, 4));
 }
 
 static VALUE s_AtomRef_GetAtomType(VALUE self) {
        int type = s_AtomFromValue(self)->type;
        char *p = (type == 0 ? "" : AtomTypeDecodeToString(type, NULL));
-       return rb_str_new(p, strlen_limit(p, 6));
+       return Ruby_NewEncodedStringValue(p, strlen_limit(p, 6));
 }
 
 static VALUE s_AtomRef_GetCharge(VALUE self) {
@@ -3553,7 +3893,7 @@ static VALUE s_AtomRef_GetWeight(VALUE self) {
 
 static VALUE s_AtomRef_GetElement(VALUE self) {
        char *p = s_AtomFromValue(self)->element;
-       return rb_str_new(p, strlen_limit(p, 4));
+       return Ruby_NewEncodedStringValue(p, strlen_limit(p, 4));
 }
 
 static VALUE s_AtomRef_GetAtomicNumber(VALUE self) {
@@ -3666,6 +4006,18 @@ static VALUE s_AtomRef_GetAniso(VALUE self) {
        return retval;
 }
 
+static VALUE s_AtomRef_GetAnisoEigenValues(VALUE self) {
+    VALUE retval;
+    int i;
+    Atom *ap = s_AtomFromValue(self);
+    if (ap->aniso == NULL)
+        return Qnil;
+    retval = rb_ary_new();
+    for (i = 0; i < 3; i++)
+        rb_ary_push(retval, rb_float_new(ap->aniso->eigval[i]));
+    return retval;
+}
+
 static VALUE s_AtomRef_GetSymop(VALUE self) {
        VALUE retval;
        Atom *ap = s_AtomFromValue(self);
@@ -3754,7 +4106,7 @@ static VALUE s_AtomRef_GetAnchorList(VALUE self) {
 
 static VALUE s_AtomRef_GetUFFType(VALUE self) {
        char *p = s_AtomFromValue(self)->uff_type;
-       return rb_str_new(p, strlen_limit(p, 5));
+       return Ruby_NewEncodedStringValue(p, strlen_limit(p, 5));
 }
 
 static VALUE s_AtomRef_SetIndex(VALUE self, VALUE val) {
@@ -4105,6 +4457,11 @@ static VALUE s_AtomRef_SetAniso(VALUE self, VALUE val) {
        return val;
 }
 
+static VALUE s_AtomRef_SetAnisoEigenValues(VALUE self, VALUE val) {
+    rb_raise(rb_eMolbyError, "Eigenvalues of anisotropic factors are read-only.");
+    return val; /* Not reached */
+}
+
 static VALUE s_AtomRef_SetSymop(VALUE self, VALUE val) {
        Molecule *mol;
        Atom *ap;
@@ -4339,6 +4696,7 @@ static struct s_AtomAttrDef {
        {"occupancy",    &s_OccupancySym,    0, s_AtomRef_GetOccupancy,    s_AtomRef_SetOccupancy},
        {"temp_factor",  &s_TempFactorSym,   0, s_AtomRef_GetTempFactor,   s_AtomRef_SetTempFactor},
        {"aniso",        &s_AnisoSym,        0, s_AtomRef_GetAniso,        s_AtomRef_SetAniso},
+    {"aniso_eigenvalues", &s_AnisoEigvalSym, 0, s_AtomRef_GetAnisoEigenValues,        s_AtomRef_SetAnisoEigenValues},
        {"symop",        &s_SymopSym,        0, s_AtomRef_GetSymop,        s_AtomRef_SetSymop},
        {"int_charge",   &s_IntChargeSym,    0, s_AtomRef_GetIntCharge,    s_AtomRef_SetIntCharge},
        {"fix_force",    &s_FixForceSym,     0, s_AtomRef_GetFixForce,     s_AtomRef_SetFixForce},
@@ -4448,7 +4806,7 @@ s_MolEnumerable_Aref(VALUE self, VALUE arg1)
                        if (idx2 < 0 || idx2 >= mol->nresidues)
                                rb_raise(rb_eIndexError, "residue index out of range (%d; should be %d..%d)", idx1, -mol->nresidues, mol->nresidues - 1);
                        p = mol->residues[idx2];
-                       return rb_str_new(p, strlen_limit(p, 4));
+                       return Ruby_NewEncodedStringValue(p, strlen_limit(p, 4));
                }
        }
        return Qnil;
@@ -4536,7 +4894,9 @@ s_MolEnumerable_Equal(VALUE self, VALUE val)
 
 #pragma mark ====== Molecule Class ======
 
-/*  An malloc'ed string buffer. Retains the error/warning message from the last ***load/***save method.  */
+#pragma mark ------ Allocate/Release/Accessor ------
+
+/*  An malloc'ed string buffer. Retains the error/warning message from the last ***load / ***save method.  */
 /*  Accessible from Ruby as Molecule#error_message and Molecule#error_message=.  */
 char *gLoadSaveErrorMessage = NULL;
 
@@ -4653,26 +5013,17 @@ static IntGroup *
 s_Molecule_AtomGroupFromValue(VALUE self, VALUE val)
 {
        IntGroup *ig;
+    Molecule *mp1;
+    Data_Get_Struct(self, Molecule, mp1);
        val = rb_funcall(self, rb_intern("atom_group"), 1, val);
        if (!rb_obj_is_kind_of(val, rb_cIntGroup))
                rb_raise(rb_eMolbyError, "IntGroup instance is expected");
        Data_Get_Struct(val, IntGroup, ig);
+    IntGroupRemove(ig, mp1->natoms, ATOMS_MAX_NUMBER); /* Limit the group member to existing atoms */
        IntGroupRetain(ig);
        return ig;
 }
 
-static void
-s_Molecule_RaiseOnLoadSave(int status, const char *msg, const char *fname)
-{
-       if (gLoadSaveErrorMessage != NULL) {
-               MyAppCallback_setConsoleColor(1);
-               MyAppCallback_showScriptMessage("On loading %s:\n%s\n", fname, gLoadSaveErrorMessage);
-               MyAppCallback_setConsoleColor(0);
-       }
-       if (status != 0)
-               rb_raise(rb_eMolbyError, "%s %s", msg, fname);
-}
-
 /*
  *  call-seq:
  *     dup          -> Molecule
@@ -4693,6 +5044,56 @@ s_Molecule_InitCopy(VALUE self, VALUE arg)
 
 /*
  *  call-seq:
+ *     atom_index(val)       -> Integer
+ *
+ *  Returns the atom index represented by val. val can be either a non-negative integer
+ *  (directly representing the atom index), a negative integer (representing <code>natoms - val</code>),
+ *  a string of type "resname.resid:name" or "resname:name" or "resid:name" or "name", 
+ *  where resname, resid, name are the residue name, residue id, and atom name respectively.
+ *  If val is a string and multiple atoms match the description, the atom with the lowest index
+ *  is returned.
+ */
+static VALUE
+s_Molecule_AtomIndex(VALUE self, VALUE val)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       return INT2NUM(s_Molecule_AtomIndexFromValue(mol, val));
+}
+
+/*
+ *  call-seq:
+ *     self == Molecule -> boolean
+ *
+ *  True if the two arguments point to the same molecule.
+ */
+static VALUE
+s_Molecule_Equal(VALUE self, VALUE val)
+{
+       if (rb_obj_is_kind_of(val, rb_cMolecule)) {
+               Molecule *mol1, *mol2;
+               Data_Get_Struct(self, Molecule, mol1);
+               Data_Get_Struct(val, Molecule, mol2);
+               return (mol1 == mol2 ? Qtrue : Qfalse);
+       } else return Qfalse;
+}
+
+#pragma mark ------ Load/Save ------
+
+static void
+s_Molecule_RaiseOnLoadSave(int status, int isloading, const char *msg, const char *fname)
+{
+       if (gLoadSaveErrorMessage != NULL) {
+               MyAppCallback_setConsoleColor(1);
+               MyAppCallback_showScriptMessage("On %s %s:\n%s\n", (isloading ? "loading" : "saving"), fname, gLoadSaveErrorMessage);
+               MyAppCallback_setConsoleColor(0);
+       }
+       if (status != 0)
+               rb_raise(rb_eMolbyError, "%s %s", msg, fname);
+}
+
+/*
+ *  call-seq:
  *     loadmbsf(file)       -> bool
  *
  *  Read a structure from a mbsf file.
@@ -4710,7 +5111,7 @@ s_Molecule_Loadmbsf(int argc, VALUE *argv, VALUE self)
        rb_scan_args(argc, argv, "1", &fname);
        fstr = FileStringValuePtr(fname);
        retval = MoleculeLoadMbsfFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to load mbsf", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load mbsf", fstr);
        return Qtrue;   
 }
 
@@ -4737,13 +5138,13 @@ s_Molecule_Loadpsf(int argc, VALUE *argv, VALUE self)
        rb_scan_args(argc, argv, "11", &fname, &pdbname);
        fstr = FileStringValuePtr(fname);
        retval = MoleculeLoadPsfFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to load psf", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load psf", fstr);
        pdbstr = NULL;
        if (!NIL_P(pdbname)) {
                pdbstr = strdup(FileStringValuePtr(pdbname));
                retval = MoleculeReadCoordinatesFromPdbFile(mol, pdbstr, &gLoadSaveErrorMessage);
                free(pdbstr);
-               s_Molecule_RaiseOnLoadSave(retval, "Failed to load coordinates from pdb", pdbstr);
+               s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load coordinates from pdb", pdbstr);
        }
        return Qtrue;
 }
@@ -4768,7 +5169,7 @@ s_Molecule_Loadpdb(int argc, VALUE *argv, VALUE self)
        MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
        retval = MoleculeReadCoordinatesFromPdbFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to load pdb", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load pdb", fstr);
        return Qtrue;   
 }
 
@@ -4791,7 +5192,7 @@ s_Molecule_Loaddcd(int argc, VALUE *argv, VALUE self)
        MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
        retval = MoleculeReadCoordinatesFromDcdFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to load dcd", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load dcd", fstr);
        return Qtrue;   
 }
 
@@ -4814,7 +5215,7 @@ s_Molecule_Loadtep(int argc, VALUE *argv, VALUE self)
        MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
        retval = MoleculeLoadTepFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to load ORTEP file", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load ORTEP file", fstr);
        return Qtrue;   
 }
 
@@ -4837,7 +5238,7 @@ s_Molecule_Loadres(int argc, VALUE *argv, VALUE self)
        MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
        retval = MoleculeLoadShelxFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to load SHELX res file", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load SHELX res file", fstr);
        return Qtrue;   
 }
 
@@ -4860,7 +5261,7 @@ s_Molecule_Loadfchk(int argc, VALUE *argv, VALUE self)
        MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
        retval = MoleculeLoadGaussianFchkFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to load Gaussian fchk", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load Gaussian fchk", fstr);
        return Qtrue;   
 }
 
@@ -4885,7 +5286,7 @@ s_Molecule_Loaddat(int argc, VALUE *argv, VALUE self)
        MyAppCallback_showProgressPanel("Loading GAMESS dat file...");
        retval = MoleculeLoadGamessDatFile(mol, fstr, &gLoadSaveErrorMessage);
        MyAppCallback_hideProgressPanel();
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to load GAMESS dat", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load GAMESS dat", fstr);
        return Qtrue;   
 }
 
@@ -4905,7 +5306,7 @@ s_Molecule_Savembsf(VALUE self, VALUE fname)
        MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
        retval = MoleculeWriteToMbsfFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to save mbsf", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 0, "Failed to save mbsf", fstr);
        return Qtrue;
 }
 
@@ -4925,7 +5326,7 @@ s_Molecule_Savepsf(VALUE self, VALUE fname)
        MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
        retval = MoleculeWriteToPsfFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to save psf", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 0, "Failed to save psf", fstr);
        return Qtrue;
 }
 
@@ -4945,7 +5346,7 @@ s_Molecule_Savepdb(VALUE self, VALUE fname)
        MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
        retval = MoleculeWriteToPdbFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to save pdb", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 0, "Failed to save pdb", fstr);
        return Qtrue;
 }
 
@@ -4965,32 +5366,11 @@ s_Molecule_Savedcd(VALUE self, VALUE fname)
        MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
        retval = MoleculeWriteToDcdFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to save dcd", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 0, "Failed to save dcd", fstr);
        return Qtrue;
 }
 
-/*
- *  call-seq:
- *     savetep(file)       -> bool
- *
- *  Write coordinates as an ORTEP file. Returns true if successful.
- */
-/*
-static VALUE
-s_Molecule_Savetep(VALUE self, VALUE fname)
-{
-       char *fstr;
-    Molecule *mol;
-       char errbuf[128];
-    Data_Get_Struct(self, Molecule, mol);
-       fstr = FileStringValuePtr(fname);
-       if (MoleculeWriteToTepFile(mol, fstr, errbuf, sizeof errbuf) != 0)
-               rb_raise(rb_eMolbyError, errbuf);
-       return Qtrue;
-}
-*/
-
-/*  load([ftype, ] fname, ...)  */
+/*  load([ftype, ] fname, ...)  */
 static VALUE
 s_Molecule_LoadSave(int argc, VALUE *argv, VALUE self, int loadFlag)
 {
@@ -5061,7 +5441,7 @@ s_Molecule_LoadSave(int argc, VALUE *argv, VALUE self, int loadFlag)
 failure:
        rval = rb_str_to_str(argv[0]);
        asprintf(&p, "Failed to %s file %s", (loadFlag ? "load" : "save"), type);
-       s_Molecule_RaiseOnLoadSave(1, p, StringValuePtr(rval));
+       s_Molecule_RaiseOnLoadSave(1, loadFlag, p, StringValuePtr(rval));
        return Qnil;  /*  Does not reach here  */
 
 success:
@@ -5115,6 +5495,112 @@ s_Molecule_Save(int argc, VALUE *argv, VALUE self)
 
 /*
  *  call-seq:
+ *     open        -> Molecule
+ *     open(file)  -> Molecule
+ *
+ *  Create a new molecule from file as a document. If file is not given, an untitled document is created.
+ */
+static VALUE
+s_Molecule_Open(int argc, VALUE *argv, VALUE self)
+{
+       VALUE fname;
+       const char *p;
+       Molecule *mp;
+       VALUE iflag;
+    
+    if (!gUseGUI) {
+        rb_raise(rb_eMolbyError, "Molecule.open is not usable in non-GUI mode. Use Molecule.new instead.");
+    }
+    
+       rb_scan_args(argc, argv, "01", &fname);
+       if (NIL_P(fname))
+               p = NULL;
+       else
+               p = FileStringValuePtr(fname);
+       iflag = Ruby_SetInterruptFlag(Qfalse);
+       mp = MoleculeCallback_openNewMolecule(p);
+       Ruby_SetInterruptFlag(iflag);
+       if (mp == NULL) {
+               if (p == NULL)
+                       rb_raise(rb_eMolbyError, "Cannot create untitled document");
+               else
+                       rb_raise(rb_eMolbyError, "Cannot open the file %s", p);
+       }
+       return ValueFromMolecule(mp);
+}
+
+/*
+ *  call-seq:
+ *     new  -> Molecule
+ *     new(file, *args)  -> Molecule
+ *
+ *  Create a new molecule and call "load" method with the same arguments.
+ */
+static VALUE
+s_Molecule_Initialize(int argc, VALUE *argv, VALUE self)
+{
+       if (argc > 0)
+               return s_Molecule_Load(argc, argv, self);
+       else return Qnil;  /*  An empty molecule (which is prepared in s_Molecule_Alloc()) is returned  */
+}
+
+/*
+ *  call-seq:
+ *     error_message       -> String
+ *
+ *  Get the error_message from the last load/save method. If no error, returns nil.
+ */
+static VALUE
+s_Molecule_ErrorMessage(VALUE klass)
+{
+       if (gLoadSaveErrorMessage == NULL)
+               return Qnil;
+       else return Ruby_NewEncodedStringValue2(gLoadSaveErrorMessage);
+}
+
+/*
+ *  call-seq:
+ *     set_error_message(String)
+ *     Molecule.error_message = String
+ *
+ *  Set the error_message for the present load/save method.
+ */
+static VALUE
+s_Molecule_SetErrorMessage(VALUE klass, VALUE sval)
+{
+       if (gLoadSaveErrorMessage != NULL) {
+               free(gLoadSaveErrorMessage);
+               gLoadSaveErrorMessage = NULL;
+       }
+       if (sval != Qnil) {
+               sval = rb_str_to_str(sval);
+               gLoadSaveErrorMessage = strdup(StringValuePtr(sval));
+       }
+       return sval;
+}
+
+/*
+ *  call-seq:
+ *     set_molecule(Molecule)
+ *
+ *  Duplicate the given molecule and set to self. The present molecule must be empty.
+ *  This method is exclusively used for associating a new document with an existing molecule.
+ */
+static VALUE
+s_Molecule_SetMolecule(VALUE self, VALUE mval)
+{
+       Molecule *mp1, *mp2;
+       Data_Get_Struct(self, Molecule, mp1);
+       mp2 = MoleculeFromValue(mval);
+       MoleculeInitWithMolecule(mp1, mp2);
+       MoleculeCallback_notifyModification(mp1, 1);
+       return self;
+}
+
+#pragma mark ------ Name attributes ------
+
+/*
+ *  call-seq:
  *     name       -> String
  *
  *  Returns the display name of the molecule. If the molecule has no associated
@@ -5130,7 +5616,7 @@ s_Molecule_Name(VALUE self)
        if (buf[0] == 0)
                return Qnil;
        else
-               return rb_str_new2(buf);
+               return Ruby_NewEncodedStringValue2(buf);
 }
 
 /*
@@ -5194,7 +5680,7 @@ s_Molecule_Dir(VALUE self)
                p = strrchr(buf, '/');
                if (p != NULL)
                        *p = 0;
-               return rb_str_new2(buf);
+               return Ruby_NewEncodedStringValue2(buf);
        }
 }
 
@@ -5216,7 +5702,7 @@ s_Molecule_Inspect(VALUE self)
        if (buf[0] == 0) {
                /*  No associated document  */
                snprintf(buf, sizeof buf, "#<Molecule:0x%lx>", self);
-               return rb_str_new2(buf);
+               return Ruby_NewEncodedStringValue2(buf);
        } else {
                /*  Check whether the document name is duplicate  */
                char buf2[256];
@@ -5235,55 +5721,11 @@ s_Molecule_Inspect(VALUE self)
                } else {
                        snprintf(buf2, sizeof buf2, "Molecule[\"%s\"]", buf);
                }
-               return rb_str_new2(buf2);
-       }
-}
-
-/*
- *  call-seq:
- *     open        -> Molecule
- *     open(file)  -> Molecule
- *
- *  Create a new molecule from file as a document. If file is not given, an untitled document is created.
- */
-static VALUE
-s_Molecule_Open(int argc, VALUE *argv, VALUE self)
-{
-       VALUE fname;
-       const char *p;
-       Molecule *mp;
-       VALUE iflag;
-       rb_scan_args(argc, argv, "01", &fname);
-       if (NIL_P(fname))
-               p = NULL;
-       else
-               p = FileStringValuePtr(fname);
-       iflag = Ruby_SetInterruptFlag(Qfalse);
-       mp = MoleculeCallback_openNewMolecule(p);
-       Ruby_SetInterruptFlag(iflag);
-       if (mp == NULL) {
-               if (p == NULL)
-                       rb_raise(rb_eMolbyError, "Cannot create untitled document");
-               else
-                       rb_raise(rb_eMolbyError, "Cannot open the file %s", p);
+               return Ruby_NewEncodedStringValue2(buf2);
        }
-       return ValueFromMolecule(mp);
 }
 
-/*
- *  call-seq:
- *     new  -> Molecule
- *     new(file, *args)  -> Molecule
- *
- *  Create a new molecule and call "load" method with the same arguments.
- */
-static VALUE
-s_Molecule_Initialize(int argc, VALUE *argv, VALUE self)
-{
-       if (argc > 0)
-               return s_Molecule_Load(argc, argv, self);
-       else return Qnil;  /*  An empty molecule (which is prepared in s_Molecule_Alloc()) is returned  */
-}
+#pragma mark ------ MolEnumerables ------
 
 static VALUE
 s_Molecule_MolEnumerable(VALUE self, int kind)
@@ -5455,2359 +5897,1892 @@ s_Molecule_Nresidues(VALUE self)
        return INT2NUM(mol->nresidues);
 }
 
-static VALUE
-s_Molecule_BondParIsObsolete(VALUE self, VALUE val)
-{
-       rb_raise(rb_eMolbyError, "Molecule#bond_par, angle_par, dihedral_par, improper_par, vdw_par are now obsolete. You can use MDArena#bond_par, angle_par, dihedral_par, improper_par, vdw_par instead, and probably these are what you really want.");
-}
-
 /*
  *  call-seq:
- *     bond_par(idx)    -> ParameterRef
+ *     nresidues = Integer
  *
- *  Returns the MD parameter for the idx-th bond.
+ *  Change the number of residues.
  */
-/*
 static VALUE
-s_Molecule_BondPar(VALUE self, VALUE val)
+s_Molecule_ChangeNresidues(VALUE self, VALUE val)
 {
     Molecule *mol;
-       BondPar *bp;
-       UInt t1, t2;
-       Int i1, i2;
-       Int ival;
+       int ival = NUM2INT(val);
     Data_Get_Struct(self, Molecule, mol);
-       ival = NUM2INT(rb_Integer(val));
-       if (ival < -mol->nbonds || ival >= mol->nbonds)
-               rb_raise(rb_eMolbyError, "bond index (%d) out of range", ival);
-       if (ival < 0)
-               ival += mol->nbonds;
-       s_RebuildMDParameterIfNecessary(self, Qtrue);
-       i1 = mol->bonds[ival * 2];
-       i2 = mol->bonds[ival * 2 + 1];
-       t1 = ATOM_AT_INDEX(mol->atoms, i1)->type;
-       t2 = ATOM_AT_INDEX(mol->atoms, i2)->type;
-       bp = ParameterLookupBondPar(mol->par, t1, t2, i1, i2, 0);
-       if (bp == NULL)
-               return Qnil;
-       return ValueFromMoleculeWithParameterTypeAndIndex(mol, kBondParType, bp - mol->par->bondPars);
+       MolActionCreateAndPerform(mol, gMolActionChangeNumberOfResidues, ival);
+       if (ival != mol->nresidues)
+               rb_raise(rb_eMolbyError, "Cannot set the number of residues to %d (set to %d)", ival, mol->nresidues);
+       return val;
 }
-*/
 
 /*
  *  call-seq:
- *     angle_par(idx)    -> ParameterRef
+ *     max_residue_number(atom_group = nil)     -> Integer
  *
- *  Returns the MD parameter for the idx-th angle.
+ *  Returns the maximum residue number actually used. If an atom group is given, only
+ *  these atoms are examined. If no atom is present, nil is returned.
  */
-/*
 static VALUE
-s_Molecule_AnglePar(VALUE self, VALUE val)
+s_Molecule_MaxResSeq(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       AnglePar *ap;
-       UInt t1, t2, t3;
-       Int i1, i2, i3;
-       Int ival;
+       VALUE gval;
+       int maxSeq;
+       IntGroup *ig;
     Data_Get_Struct(self, Molecule, mol);
-       ival = NUM2INT(rb_Integer(val));
-       if (ival < -mol->nangles || ival >= mol->nangles)
-               rb_raise(rb_eMolbyError, "angle index (%d) out of range", ival);
-       if (ival < 0)
-               ival += mol->nangles;
-       s_RebuildMDParameterIfNecessary(self, Qtrue);
-       i1 = mol->angles[ival * 3];
-       i2 = mol->angles[ival * 3 + 1];
-       i3 = mol->angles[ival * 3 + 2];
-       t1 = ATOM_AT_INDEX(mol->atoms, i1)->type;
-       t2 = ATOM_AT_INDEX(mol->atoms, i2)->type;
-       t3 = ATOM_AT_INDEX(mol->atoms, i3)->type;
-       ap = ParameterLookupAnglePar(mol->par, t1, t2, t3, i1, i2, i3, 0);
-       if (ap == NULL)
-               return Qnil;
-       return ValueFromMoleculeWithParameterTypeAndIndex(mol, kAngleParType, ap - mol->par->anglePars);
+       rb_scan_args(argc, argv, "01", &gval);
+       ig = (gval == Qnil ? NULL : s_Molecule_AtomGroupFromValue(self, gval));
+       maxSeq = MoleculeMaximumResidueNumber(mol, ig);
+       return (maxSeq >= 0 ? INT2NUM(maxSeq) : Qnil);
 }
-*/
+
 /*
  *  call-seq:
- *     dihedral_par(idx)    -> ParameterRef
+ *     min_residue_number(atom_group = nil)     -> Integer
  *
- *  Returns the MD parameter for the idx-th dihedral.
+ *  Returns the minimum residue number actually used. If an atom group is given, only
+ *  these atoms are examined. If no atom is present, nil is returned.
  */
-/*
 static VALUE
-s_Molecule_DihedralPar(VALUE self, VALUE val)
+s_Molecule_MinResSeq(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       Int ival;
-       TorsionPar *tp;
-       UInt t1, t2, t3, t4;
-       Int i1, i2, i3, i4;
+       VALUE gval;
+       int minSeq;
+       IntGroup *ig;
     Data_Get_Struct(self, Molecule, mol);
-       ival = NUM2INT(rb_Integer(val));
-       if (ival < -mol->ndihedrals || ival >= mol->ndihedrals)
-               rb_raise(rb_eMolbyError, "dihedral index (%d) out of range", ival);
-       if (ival < 0)
-               ival += mol->ndihedrals;
-       s_RebuildMDParameterIfNecessary(self, Qtrue);
-       i1 = mol->dihedrals[ival * 4];
-       i2 = mol->dihedrals[ival * 4 + 1];
-       i3 = mol->dihedrals[ival * 4 + 2];
-       i4 = mol->dihedrals[ival * 4 + 3];
-       t1 = ATOM_AT_INDEX(mol->atoms, i1)->type;
-       t2 = ATOM_AT_INDEX(mol->atoms, i2)->type;
-       t3 = ATOM_AT_INDEX(mol->atoms, i3)->type;
-       t4 = ATOM_AT_INDEX(mol->atoms, i4)->type;
-       tp = ParameterLookupDihedralPar(mol->par, t1, t2, t3, t4, i1, i2, i3, i4, 0);
-       if (tp == NULL)
-               return Qnil;
-       return ValueFromMoleculeWithParameterTypeAndIndex(mol, kDihedralParType, tp - mol->par->dihedralPars);
+       rb_scan_args(argc, argv, "01", &gval);
+       ig = (gval == Qnil ? NULL : s_Molecule_AtomGroupFromValue(self, gval));
+       minSeq = MoleculeMinimumResidueNumber(mol, ig);
+       return (minSeq >= 0 ? INT2NUM(minSeq) : Qnil);
 }
-*/
+
 /*
  *  call-seq:
- *     improper_par(idx)    -> ParameterRef
+ *     each_atom(atom_group = nil) {|aref| ...}
  *
- *  Returns the MD parameter for the idx-th improper.
+ *  Execute the block, with the AtomRef object for each atom as the argument. If an atom
+ *  group is given, only these atoms are processed.
+ *  If atom_group is nil, this is equivalent to self.atoms.each, except that the return value 
+ *  is self (a Molecule object).
  */
-/*
 static VALUE
-s_Molecule_ImproperPar(VALUE self, VALUE val)
+s_Molecule_EachAtom(int argc, VALUE *argv, VALUE self)
 {
+       int i;
     Molecule *mol;
-       Int ival;
-       TorsionPar *tp;
-       UInt t1, t2, t3, t4;
-       Int i1, i2, i3, i4;
+       AtomRef *aref;
+       VALUE arval;
+       VALUE gval;
+       IntGroup *ig;
     Data_Get_Struct(self, Molecule, mol);
-       ival = NUM2INT(rb_Integer(val));
-       if (ival < -mol->nimpropers || ival >= mol->nimpropers)
-               rb_raise(rb_eMolbyError, "improper index (%d) out of range", ival);
-       if (ival < 0)
-               ival += mol->nimpropers;
-       s_RebuildMDParameterIfNecessary(self, Qtrue);
-       i1 = mol->impropers[ival * 4];
-       i2 = mol->impropers[ival * 4 + 1];
-       i3 = mol->impropers[ival * 4 + 2];
-       i4 = mol->impropers[ival * 4 + 3];
-       t1 = ATOM_AT_INDEX(mol->atoms, i1)->type;
-       t2 = ATOM_AT_INDEX(mol->atoms, i2)->type;
-       t3 = ATOM_AT_INDEX(mol->atoms, i3)->type;
-       t4 = ATOM_AT_INDEX(mol->atoms, i4)->type;
-       tp = ParameterLookupImproperPar(mol->par, t1, t2, t3, t4, i1, i2, i3, i4, 0);
-       if (tp == NULL)
-               return Qnil;
-       return ValueFromMoleculeWithParameterTypeAndIndex(mol, kImproperParType, tp - mol->par->improperPars);
+       rb_scan_args(argc, argv, "01", &gval);
+       ig = (gval == Qnil ? NULL : s_Molecule_AtomGroupFromValue(self, gval));
+       arval = ValueFromMoleculeAndIndex(mol, 0);
+       Data_Get_Struct(arval, AtomRef, aref);
+       for (i = 0; i < mol->natoms; i++) {
+               aref->idx = i;
+               if (ig == NULL || IntGroupLookup(ig, i, NULL))
+                       rb_yield(arval);
+       }
+       if (ig != NULL)
+               IntGroupRelease(ig);
+    return self;
 }
-*/
 
-/*
- *  call-seq:
- *     start_step       -> Integer
- *
- *  Returns the start step (defined by dcd format).
- */
+#pragma mark ------ Atom Group ------
+
 static VALUE
-s_Molecule_StartStep(VALUE self)
+s_Molecule_AtomGroup_i(VALUE arg, VALUE values)
 {
-    Molecule *mol;
-    Data_Get_Struct(self, Molecule, mol);
-       return INT2NUM(mol->startStep);
+       Molecule *mol = (Molecule *)(((VALUE *)values)[0]);
+       IntGroup *ig1 = (IntGroup *)(((VALUE *)values)[1]);
+       int idx = s_Molecule_AtomIndexFromValue(mol, arg);
+       IntGroup_RaiseIfError(IntGroupAdd(ig1, idx, 1));
+       return Qnil;
 }
 
 /*
  *  call-seq:
- *     start_step = Integer
+ *     atom_group
+ *     atom_group {|aref| ...}
+ *     atom_group(arg1, arg2, ...)
+ *     atom_group(arg1, arg2, ...) {|aref| ...}
+ *
+ *  Specify a group of atoms. If no arguments are given, IntGroup\[0...natoms] is the result.
+ *  If arguments are given, then the atoms reprensented by the arguments are added to the
+ *  group. For a conversion of a string to an atom index, see the description
+ *  of Molecule#atom_index.
+ *  If a block is given, it is evaluated with an AtomRef (not atom index integers)
+ *  representing each atom, and the atoms are removed from the result if the block returns false.
  *
- *  Set the start step (defined by dcd format).
  */
 static VALUE
-s_Molecule_SetStartStep(VALUE self, VALUE val)
+s_Molecule_AtomGroup(int argc, VALUE *argv, VALUE self)
 {
+       IntGroup *ig1, *ig2;
     Molecule *mol;
-    Data_Get_Struct(self, Molecule, mol);
-       mol->startStep = NUM2INT(rb_Integer(val));
-       return val;
+       Int i, startPt, interval;
+       VALUE retval = IntGroup_Alloc(rb_cIntGroup);
+       Data_Get_Struct(retval, IntGroup, ig1);
+    Data_Get_Struct(self, Molecule, mol);
+       if (argc == 0) {
+               IntGroup_RaiseIfError(IntGroupAdd(ig1, 0, mol->natoms));
+       } else {
+               while (argc > 0) {
+                       if (FIXNUM_P(*argv) || TYPE(*argv) == T_STRING) {
+                               i = s_Molecule_AtomIndexFromValue(mol, *argv);
+                               IntGroup_RaiseIfError(IntGroupAdd(ig1, i, 1));
+                       } else if (rb_obj_is_kind_of(*argv, rb_cIntGroup)) {
+                               ig2 = IntGroupFromValue(*argv);
+                               for (i = 0; (startPt = IntGroupGetStartPoint(ig2, i)) >= 0; i++) {
+                                       interval = IntGroupGetInterval(ig2, i);
+                                       IntGroup_RaiseIfError(IntGroupAdd(ig1, startPt, interval));
+                               }
+                               IntGroupRelease(ig2);
+                       } else if (rb_respond_to(*argv, rb_intern("each"))) {
+                               VALUE values[2];
+                               values[0] = (VALUE)mol;
+                               values[1] = (VALUE)ig1;
+                               rb_iterate(rb_each, *argv, s_Molecule_AtomGroup_i, (VALUE)values);
+                       } else
+                               IntGroup_RaiseIfError(IntGroupAdd(ig1, NUM2INT(*argv), 1));
+                       argc--;
+                       argv++;
+               }
+       }
+       if (rb_block_given_p()) {
+               /*  Evaluate the given block with an AtomRef as the argument, and delete
+                       the index if the block returns false  */
+               AtomRef *aref = AtomRefNew(mol, 0);
+               VALUE arval = Data_Wrap_Struct(rb_cAtomRef, 0, (void (*)(void *))AtomRefRelease, aref);
+               ig2 = IntGroupNew();
+               IntGroupCopy(ig2, ig1);
+               for (i = 0; (startPt = IntGroupGetNthPoint(ig2, i)) >= 0; i++) {
+                       VALUE resval;
+                       if (startPt >= mol->natoms)
+                               break;
+                       aref->idx = startPt;
+                       resval = rb_yield(arval);
+                       if (!RTEST(resval))
+                               IntGroupRemove(ig1, startPt, 1);
+               }
+               IntGroupRelease(ig2);
+       }
+       
+       /*  Remove points that are out of bounds */
+       IntGroup_RaiseIfError(IntGroupRemove(ig1, mol->natoms, INT_MAX));
+
+       return retval;                  
 }
 
 /*
  *  call-seq:
- *     steps_per_frame       -> Integer
+ *     selection       -> IntGroup
  *
- *  Returns the number of steps between frames (defined by dcd format).
+ *  Returns the current selection.
  */
 static VALUE
-s_Molecule_StepsPerFrame(VALUE self)
+s_Molecule_Selection(VALUE self)
 {
     Molecule *mol;
+       IntGroup *ig;
+       VALUE val;
     Data_Get_Struct(self, Molecule, mol);
-       return INT2NUM(mol->stepsPerFrame);
+       if (mol != NULL && (ig = MoleculeGetSelection(mol)) != NULL) {
+               ig = IntGroupNewFromIntGroup(ig);  /*  Duplicate, so that the change from GUI does not affect the value  */
+               val = ValueFromIntGroup(ig);
+               IntGroupRelease(ig);
+       } else {
+               val = IntGroup_Alloc(rb_cIntGroup);
+       }
+       return val;
 }
 
-/*
- *  call-seq:
- *     steps_per_frame = Integer
- *
- *  Set the number of steps between frames (defined by dcd format).
- */
 static VALUE
-s_Molecule_SetStepsPerFrame(VALUE self, VALUE val)
+s_Molecule_SetSelectionSub(VALUE self, VALUE val, int undoable)
 {
     Molecule *mol;
+       IntGroup *ig;
     Data_Get_Struct(self, Molecule, mol);
-       mol->stepsPerFrame = NUM2INT(rb_Integer(val));
+       if (val == Qnil)
+               ig = NULL;
+       else
+               ig = s_Molecule_AtomGroupFromValue(self, val);
+       if (undoable)
+               MolActionCreateAndPerform(mol, gMolActionSetSelection, ig);
+       else
+               MoleculeSetSelection(mol, ig);
+       if (ig != NULL)
+               IntGroupRelease(ig);
        return val;
 }
 
 /*
  *  call-seq:
- *     ps_per_step       -> Float
+ *     selection = IntGroup
  *
- *  Returns the time increment (in picoseconds) for one step (defined by dcd format).
+ *  Set the current selection. The right-hand operand may be nil.
+ *  This operation is _not_ undoable. If you need undo, use set_undoable_selection instead.
  */
 static VALUE
-s_Molecule_PsPerStep(VALUE self)
+s_Molecule_SetSelection(VALUE self, VALUE val)
 {
-    Molecule *mol;
-    Data_Get_Struct(self, Molecule, mol);
-       return rb_float_new(mol->psPerStep);
+       return s_Molecule_SetSelectionSub(self, val, 0);
 }
 
 /*
  *  call-seq:
- *     ps_per_step = Float
+ *     set_undoable_selection(IntGroup)
  *
- *  Set the time increment (in picoseconds) for one step (defined by dcd format).
+ *  Set the current selection with undo registration. The right-hand operand may be nil.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_SetPsPerStep(VALUE self, VALUE val)
+s_Molecule_SetUndoableSelection(VALUE self, VALUE val)
 {
-    Molecule *mol;
-    Data_Get_Struct(self, Molecule, mol);
-       mol->psPerStep = NUM2DBL(rb_Float(val));
-       return val;
+       return s_Molecule_SetSelectionSub(self, val, 1);
 }
 
+#pragma mark ------ Editing ------
+
 /*
  *  call-seq:
- *     find_angles     -> Integer
+ *     extract(group, dummy_flag = nil)       -> Molecule
  *
- *  Find the angles from the bonds. Returns the number of angles newly created.
+ *  Extract the atoms given by group and return as a new molecule object.
+ *  If dummy_flag is true, then the atoms that are not included in the group but are connected
+ *  to any atoms in the group are converted to "dummy" atoms (i.e. with element "Du" and 
+ *  names beginning with an underscore) and included in the new molecule object.
  */
-/*
 static VALUE
-s_Molecule_FindAngles(VALUE self)
+s_Molecule_Extract(int argc, VALUE *argv, VALUE self)
 {
-    Molecule *mol;
-       Atom *ap;
-       int n1, i, j, nc;
-       Int *ip, nip, n[3];
-    Data_Get_Struct(self, Molecule, mol);
-       if (mol == NULL || mol->natoms == 0)
-               return INT2NUM(0);
-       ip = NULL;
-       nip = 0;
-       for (n1 = 0, ap = mol->atoms; n1 < mol->natoms; n1++, ap = ATOM_NEXT(ap)) {
-               nc = ap->connect.count;
-               n[1] = n1;
-               for (i = 0; i < nc; i++) {
-                       n[0] = ap->connects[i];
-                       for (j = i + 1; j < nc; j++) {
-                               n[2] = ap->connects[j];
-                               if (MoleculeLookupAngle(mol, n[0], n[1], n[2]) < 0)
-                                       AssignArray(&ip, &nip, sizeof(Int) * 3, nip, n);
-                       }
-               }
-       }
-       if (nip > 0) {
-               MolActionCreateAndPerform(mol, gMolActionAddAngles, nip * 3, ip, NULL);         
-               free(ip);
+    Molecule *mol1, *mol2;
+       IntGroup *ig;
+       VALUE group, dummy_flag, retval;
+    Data_Get_Struct(self, Molecule, mol1);
+       rb_scan_args(argc, argv, "11", &group, &dummy_flag);
+       ig = s_Molecule_AtomGroupFromValue(self, group);
+       if (MoleculeExtract(mol1, &mol2, ig, (dummy_flag != Qnil && dummy_flag != Qfalse)) != 0) {
+               retval = Qnil;
+       } else {
+               retval = ValueFromMolecule(mol2);
        }
-       return INT2NUM(nip);
+       IntGroupRelease(ig);
+       return retval;
 }
-*/
+
 /*
  *  call-seq:
- *     find_dihedrals     -> Integer
+ *     add(molecule2)       -> self
  *
- *  Find the dihedrals from the bonds. Returns the number of dihedrals newly created.
+ *  Combine two molecules. The residue numbers of the newly added atoms may be renumbered to avoid
+    conflicts.
+    This operation is undoable.
  */
+static VALUE
+s_Molecule_Add(VALUE self, VALUE val)
+{
+    Molecule *mol1, *mol2;
+    Data_Get_Struct(self, Molecule, mol1);
+       mol2 = MoleculeFromValue(val);
+       MolActionCreateAndPerform(mol1, gMolActionMergeMolecule, mol2, NULL);
+       return self; 
+}
+
 /*
+ *  call-seq:
+ *     remove(group)       -> Molecule
+ *
+ *  The atoms designated by the given group are removed from the molecule.
+ *  This operation is undoable.
+ */
 static VALUE
-s_Molecule_FindDihedrals(VALUE self)
+s_Molecule_Remove(VALUE self, VALUE group)
 {
-    Molecule *mol;
-       Atom *ap1, *ap2;
-       int n1, i, j, k, nc1, nc2;
-       Int *ip, nip, n[4];
-    Data_Get_Struct(self, Molecule, mol);
-       if (mol == NULL || mol->natoms == 0)
-               return INT2NUM(0);
-       ip = NULL;
-       nip = 0;
-       for (n1 = 0, ap1 = mol->atoms; n1 < mol->natoms; n1++, ap1 = ATOM_NEXT(ap1)) {
-               nc1 = ap1->connect.count;
-               n[1] = n1;
-               for (i = 0; i < nc1; i++) {
-                       n[2] = ap1->connects[i];
-                       if (n[1] > n[2])
-                               continue;
-                       ap2 = ATOM_AT_INDEX(mol->atoms, n[2]);
-                       nc2 = ap2->connect.count;
-                       for (j = 0; j < nc1; j++) {
-                               n[0] = ap1->connects[j];
-                               if (n[0] == n[2])
-                                       continue;
-                               for (k = 0; k < nc2; k++) {
-                                       n[3] = ap2->connects[k];
-                                       if (n[3] == n1 || n[3] == n[0])
-                                               continue;
-                                       if (MoleculeLookupDihedral(mol, n[0], n[1], n[2], n[3]) < 0)
-                                               AssignArray(&ip, &nip, sizeof(Int) * 4, nip, n);
+    Molecule *mol1;
+       IntGroup *ig, *bg;
+       Int i;
+       IntGroupIterator iter;
+
+    ig = s_Molecule_AtomGroupFromValue(self, group);
+/*    Data_Get_Struct(self, Molecule, mol1);
+       group = rb_funcall(self, rb_intern("atom_group"), 1, group);
+       if (!rb_obj_is_kind_of(group, rb_cIntGroup))
+               rb_raise(rb_eMolbyError, "IntGroup instance is expected");
+       Data_Get_Struct(group, IntGroup, ig); */
+    Data_Get_Struct(self, Molecule, mol1);
+    
+       /*  Remove the bonds between the two fragments  */
+       /*  (This is necessary for undo to work correctly)  */
+       IntGroupIteratorInit(ig, &iter);
+       bg = NULL;
+       while ((i = IntGroupIteratorNext(&iter)) >= 0) {
+               Atom *ap = ATOM_AT_INDEX(mol1->atoms, i);
+               Int j, *cp;
+               cp = AtomConnectData(&ap->connect);
+               for (j = 0; j < ap->connect.count; j++) {
+                       int n = cp[j];
+                       if (!IntGroupLookup(ig, n, NULL)) {
+                               /*  bond i-n, i is in ig and n is not  */
+                               int k = MoleculeLookupBond(mol1, i, n);
+                               if (k >= 0) {
+                                       if (bg == NULL)
+                                               bg = IntGroupNew();
+                                       IntGroupAdd(bg, k, 1);
                                }
                        }
                }
        }
-       if (nip > 0) {
-               MolActionCreateAndPerform(mol, gMolActionAddDihedrals, nip * 4, ip, NULL);
-               free(ip);
+       IntGroupIteratorRelease(&iter);
+       if (bg != NULL) {
+               /*  Remove bonds  */
+               MolActionCreateAndPerform(mol1, gMolActionDeleteBonds, bg);
+               IntGroupRelease(bg);
        }
-       return INT2NUM(nip);
+       /*  Remove atoms  */
+       if (MolActionCreateAndPerform(mol1, gMolActionUnmergeMolecule, ig) == 0)
+               return Qnil;
+       return self;
 }
-*/
 
 /*
  *  call-seq:
- *     nresidues = Integer
+ *     create_atom(name, pos = -1)  -> AtomRef
  *
- *  Change the number of residues.
+ *  Create a new atom with the specified name (may contain residue 
+ *  information) and position (if position is out of range, the atom is appended at
+ *  the end). Returns the reference to the new atom.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_ChangeNresidues(VALUE self, VALUE val)
+s_Molecule_CreateAnAtom(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       int ival = NUM2INT(val);
+    Int i, pos;
+       VALUE name, ival;
+    Atom arec;
+    AtomRef *aref;
+       char *p, resName[6], atomName[6];
+       int resSeq;
     Data_Get_Struct(self, Molecule, mol);
-       MolActionCreateAndPerform(mol, gMolActionChangeNumberOfResidues, ival);
-       if (ival != mol->nresidues)
-               rb_raise(rb_eMolbyError, "Cannot set the number of residues to %d (set to %d)", ival, mol->nresidues);
-       return val;
+       rb_scan_args(argc, argv, "02", &name, &ival);
+       if (ival != Qnil)
+               pos = NUM2INT(rb_Integer(ival));
+       else pos = -1;
+       if (name != Qnil) {
+               p = StringValuePtr(name);
+               if (p[0] != 0) {
+                       i = MoleculeAnalyzeAtomName(p, resName, &resSeq, atomName);
+                       if (atomName[0] == 0)
+                         rb_raise(rb_eMolbyError, "bad atom name specification: %s", p);
+               }
+       } else p = NULL;
+       if (p == NULL || p[0] == 0) {
+               memset(atomName, 0, 4);
+               resSeq = -1;
+       }
+    memset(&arec, 0, sizeof(arec));
+    strncpy(arec.aname, atomName, 4);
+    if (resSeq >= 0) {
+      strncpy(arec.resName, resName, 4);
+      arec.resSeq = resSeq;
+    }
+       arec.occupancy = 1.0;
+//    i = MoleculeCreateAnAtom(mol, &arec);
+       if (MolActionCreateAndPerform(mol, gMolActionAddAnAtom, &arec, pos, &pos) != 0)
+               return Qnil;
+    aref = AtomRefNew(mol, pos);
+    return Data_Wrap_Struct(rb_cAtomRef, 0, (void (*)(void *))AtomRefRelease, aref);
 }
 
 /*
  *  call-seq:
- *     max_residue_number(atom_group = nil)     -> Integer
+ *     duplicate_atom(atomref, pos = -1)  -> AtomRef
  *
- *  Returns the maximum residue number actually used. If an atom group is given, only
- *  these atoms are examined. If no atom is present, nil is returned.
- */
+ *  Create a new atom with the same attributes (but no bonding information)
+ *  with the specified atom. Returns the reference to the new atom.
+ *  This operation is undoable.
+ */
 static VALUE
-s_Molecule_MaxResSeq(int argc, VALUE *argv, VALUE self)
+s_Molecule_DuplicateAnAtom(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       VALUE gval;
-       int maxSeq;
-       IntGroup *ig;
+       const Atom *apsrc;
+    Atom arec;
+       AtomRef *aref;
+       VALUE retval, aval, ival;
+       Int pos;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "01", &gval);
-       ig = (gval == Qnil ? NULL : s_Molecule_AtomGroupFromValue(self, gval));
-       maxSeq = MoleculeMaximumResidueNumber(mol, ig);
-       return (maxSeq >= 0 ? INT2NUM(maxSeq) : Qnil);
+       rb_scan_args(argc, argv, "11", &aval, &ival);
+       if (FIXNUM_P(aval)) {
+               int idx = NUM2INT(aval);
+               if (idx < 0 || idx >= mol->natoms)
+                       rb_raise(rb_eMolbyError, "atom index out of range: %d", idx);
+               apsrc = ATOM_AT_INDEX(mol->atoms, idx);
+       } else {
+               apsrc = s_AtomFromValue(aval);
+       }
+       if (apsrc == NULL)
+               rb_raise(rb_eMolbyError, "bad atom specification");
+       if (ival != Qnil)
+               pos = NUM2INT(rb_Integer(ival));
+       else pos = -1;
+       AtomDuplicate(&arec, apsrc);
+       arec.connect.count = 0;
+       if (MolActionCreateAndPerform(mol, gMolActionAddAnAtom, &arec, pos, &pos) != 0)
+               retval = Qnil;
+       else {
+               aref = AtomRefNew(mol, pos);
+               retval = Data_Wrap_Struct(rb_cAtomRef, 0, (void (*)(void *))AtomRefRelease, aref);
+       }
+       AtomClean(&arec);
+       return retval;
 }
 
 /*
  *  call-seq:
- *     min_residue_number(atom_group = nil)     -> Integer
+ *     create_bond(n1, n2, ...)       -> Integer
  *
- *  Returns the minimum residue number actually used. If an atom group is given, only
- *  these atoms are examined. If no atom is present, nil is returned.
+ *  Create bonds between atoms n1 and n2, n3 and n4, and so on. If the corresponding bond is already present for a particular pair,
+ *  do nothing for that pair. Returns the number of bonds actually created.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_MinResSeq(int argc, VALUE *argv, VALUE self)
+s_Molecule_CreateBond(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       VALUE gval;
-       int minSeq;
-       IntGroup *ig;
+       Int i, j, k, *ip, old_nbonds;
+       if (argc == 0)
+               rb_raise(rb_eMolbyError, "missing arguments");
+       if (argc % 2 != 0)
+               rb_raise(rb_eMolbyError, "bonds should be specified by pairs of atom indices");
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "01", &gval);
-       ig = (gval == Qnil ? NULL : s_Molecule_AtomGroupFromValue(self, gval));
-       minSeq = MoleculeMinimumResidueNumber(mol, ig);
-       return (minSeq >= 0 ? INT2NUM(minSeq) : Qnil);
+       ip = ALLOC_N(Int, argc + 1);
+       for (i = j = 0; i < argc; i++, j++) {
+               ip[j] = s_Molecule_AtomIndexFromValue(mol, argv[i]);
+               if (i % 2 == 1) {
+                       if (MoleculeLookupBond(mol, ip[j - 1], ip[j]) >= 0)
+                               j -= 2;  /*  This bond is already present: skip it  */
+                       else {
+                               for (k = 0; k < j - 1; k += 2) {
+                                       if ((ip[k] == ip[j - 1] && ip[k + 1] == ip[j]) || (ip[k + 1] == ip[j - 1] && ip[k] == ip[j])) {
+                                               j -= 2;   /*  The same entry is already in the argument  */
+                                               break;
+                                       }
+                               }
+                       }
+               }
+       }
+       old_nbonds = mol->nbonds;
+       if (j > 0) {
+               ip[j] = kInvalidIndex;
+               i = MolActionCreateAndPerform(mol, gMolActionAddBonds, j, ip, NULL);
+       } else i = 0;
+       xfree(ip);
+       if (i == -1)
+               rb_raise(rb_eMolbyError, "atom index out of range");
+       else if (i == -2)
+               rb_raise(rb_eMolbyError, "too many bonds");
+       else if (i == -3)
+               rb_raise(rb_eMolbyError, "duplicate bonds");
+       else if (i == -5)
+               rb_raise(rb_eMolbyError, "cannot create bond to itself");
+       else if (i != 0)
+               rb_raise(rb_eMolbyError, "error in creating bonds");
+       return INT2NUM(mol->nbonds - old_nbonds);
 }
 
 /*
  *  call-seq:
- *     each_atom(atom_group = nil) {|aref| ...}
+ *     molecule.remove_bonds(n1, n2, ...)       -> Integer
  *
- *  Execute the block, with the AtomRef object for each atom as the argument. If an atom
- *  group is given, only these atoms are processed.
- *  If atom_group is nil, this is equivalent to self.atoms.each, except that the return value 
- *  is self (a Molecule object).
+ *  Remove bonds between atoms n1 and n2, n3 and n4, and so on. If the corresponding bond is not present for
+ *  a particular pair, do nothing for that pair. Returns the number of bonds actually removed.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_EachAtom(int argc, VALUE *argv, VALUE self)
+s_Molecule_RemoveBond(int argc, VALUE *argv, VALUE self)
 {
-       int i;
     Molecule *mol;
-       AtomRef *aref;
-       VALUE arval;
-       VALUE gval;
-       IntGroup *ig;
+       Int i, j, n[2];
+       IntGroup *bg;
+       if (argc == 0)
+               rb_raise(rb_eMolbyError, "missing arguments");
+       if (argc % 2 != 0)
+               rb_raise(rb_eMolbyError, "bonds should be specified by pairs of atom indices");
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "01", &gval);
-       ig = (gval == Qnil ? NULL : s_Molecule_AtomGroupFromValue(self, gval));
-       arval = ValueFromMoleculeAndIndex(mol, 0);
-       Data_Get_Struct(arval, AtomRef, aref);
-       for (i = 0; i < mol->natoms; i++) {
-               aref->idx = i;
-               if (ig == NULL || IntGroupLookup(ig, i, NULL))
-                       rb_yield(arval);
+       bg = NULL;
+       for (i = j = 0; i < argc; i++, j = 1 - j) {
+               n[j] = s_Molecule_AtomIndexFromValue(mol, argv[i]);
+               if (j == 1) {
+                       Int k = MoleculeLookupBond(mol, n[0], n[1]);
+                       if (k >= 0) {
+                               if (bg == NULL)
+                                       bg = IntGroupNew();
+                               IntGroupAdd(bg, k, 1);
+                       }
+               }
        }
-       if (ig != NULL)
-               IntGroupRelease(ig);
-    return self;
+       if (bg != NULL) {
+               MolActionCreateAndPerform(mol, gMolActionDeleteBonds, bg);
+               i = IntGroupGetCount(bg);
+               IntGroupRelease(bg);
+       } else i = 0;
+       return INT2NUM(i);
 }
 
 /*
  *  call-seq:
- *     cell     -> [a, b, c, alpha, beta, gamma [, sig_a, sig_b, sig_c, sig_alpha, sig_beta, sig_gamma]]
+ *     assign_bond_order(idx, d1)
+ *     assign_bond_orders(group, [d1, d2, ...])
  *
- *  Returns the unit cell parameters. If cell is not set, returns nil.
+ *  Assign bond order. In the first form, the bond order of the idx-th bond is set to d1 (a Float value).
+ *  In the second form, the bond orders at the indices in the group are set to d1, d2, etc.
+ *  At present, the bond orders are only used in UFF parameter guess, and not in the MM/MD calculations.
+ *  (This may change in the future)
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_Cell(VALUE self)
+s_Molecule_AssignBondOrder(VALUE self, VALUE idxval, VALUE dval)
 {
     Molecule *mol;
-       int i;
-       VALUE val;
+       IntGroup *ig;
     Data_Get_Struct(self, Molecule, mol);
-       if (mol->cell == NULL)
-               return Qnil;
-       val = rb_ary_new2(6);
-       for (i = 0; i < 6; i++)
-               rb_ary_push(val, rb_float_new(mol->cell->cell[i]));
-       if (mol->cell->has_sigma) {
-               for (i = 0; i < 6; i++) {
-                       rb_ary_push(val, rb_float_new(mol->cell->cellsigma[i]));
+       if (rb_obj_is_kind_of(idxval, rb_cNumeric)) {
+               /*  The first form  */
+               Int idx = NUM2INT(rb_Integer(idxval));
+               Double d1 = NUM2DBL(rb_Float(dval));
+               if (idx < 0 || idx >= mol->nbonds)
+                       rb_raise(rb_eMolbyError, "the bond index (%d) is out of bounds", idx);
+               ig = IntGroupNewWithPoints(idx, 1, -1);
+               MolActionCreateAndPerform(mol, gMolActionAssignBondOrders, 1, &d1, ig);
+               IntGroupRelease(ig);
+       } else {
+               Int i, n;
+               Double *dp;
+               ig = IntGroupFromValue(idxval);
+               n = IntGroupGetCount(ig);
+               if (n == 0)
+                       rb_raise(rb_eMolbyError, "the bond index is empty");
+               dval = rb_ary_to_ary(dval);
+               dp = (Double *)calloc(sizeof(Double), n);
+               for (i = 0; i < RARRAY_LEN(dval) && i < n; i++) {
+                       dp[i] = NUM2DBL(rb_Float(RARRAY_PTR(dval)[i]));
                }
+               MolActionCreateAndPerform(mol, gMolActionAssignBondOrders, n, dp, ig);
+               free(dp);
+               IntGroupRelease(ig);
        }
-       return val;
+       return self;
 }
 
 /*
  *  call-seq:
- *     cell = [a, b, c, alpha, beta, gamma [, sig_a, sig_b, sig_c, sig_alpha, sig_beta, sig_gamma]]
- *     set_cell([a, b, c, alpha, beta, gamma[, sig_a, sig_b, sig_c, sig_alpha, sig_beta, sig_gamma]], convert_coord = nil)
+ *     get_bond_order(idx) -> Float
+ *     get_bond_orders(group) -> Array
  *
- *  Set the unit cell parameters. If the cell value is nil, then clear the current cell.
   If the given argument has 12 or more members, then the second half of the parameters represents the sigma values.
-    This operation is undoable.
   Convert_coord is a flag to specify that the coordinates should be transformed so that the fractional coordinates remain the same.
+ *  Get the bond order. In the first form, the bond order of the idx-th bond is returned.
*  In the second form, the bond orders at the indices in the group are returned as an array.
+ *  If no bond order information have been assigned, returns nil (the first form)
*  or an empty array (the second form).
  */
 static VALUE
-s_Molecule_SetCell(int argc, VALUE *argv, VALUE self)
+s_Molecule_GetBondOrder(VALUE self, VALUE idxval)
 {
     Molecule *mol;
-       VALUE val, cval;
-       int i, convert_coord, n;
-       double d[12];
+       IntGroup *ig;
+       Double *dp;
+       VALUE retval;
+       Int i, n, numericArg;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "11", &val, &cval);
-       if (val == Qnil) {
-               n = 0;
+       if (rb_obj_is_kind_of(idxval, rb_cNumeric)) {
+               /*  The first form  */
+               Int idx = NUM2INT(rb_Integer(idxval));
+               if (idx < 0 || idx >= mol->nbonds)
+                       rb_raise(rb_eMolbyError, "the bond index (%d) is out of bounds", idx);
+               if (mol->bondOrders == NULL)
+                       return Qnil;
+               ig = IntGroupNewWithPoints(idx, 1, -1);
+               n = 1;
+               numericArg = 1;
        } else {
-               int len;
-               val = rb_ary_to_ary(val);
-               len = RARRAY_LEN(val);
-               if (len >= 12) {
-                       n = 12;
-               } else if (len >= 6) {
-                       n = 6;
-               } else rb_raise(rb_eMolbyError, "too few members for cell parameters (6 or 12 required)");
+               if (mol->bondOrders == NULL)
+                       return rb_ary_new();
+               ig = IntGroupFromValue(idxval);
+               n = IntGroupGetCount(ig);
+               if (n == 0)
+                       rb_raise(rb_eMolbyError, "the bond index is empty");
+               numericArg = 0;
+       }
+       dp = (Double *)calloc(sizeof(Double), n);
+       MoleculeGetBondOrders(mol, dp, ig);
+       if (numericArg)
+               retval = rb_float_new(dp[0]);
+       else {
+               retval = rb_ary_new();
                for (i = 0; i < n; i++)
-                       d[i] = NUM2DBL(rb_Float((RARRAY_PTR(val))[i]));
+                       rb_ary_push(retval, rb_float_new(dp[i]));
        }
-       convert_coord = (RTEST(cval) ? 1 : 0);
-       MolActionCreateAndPerform(mol, gMolActionSetCell, n, d, convert_coord);
-       return val;
+       free(dp);
+       IntGroupRelease(ig);
+       return retval;
 }
 
 /*
  *  call-seq:
- *     box -> [avec, bvec, cvec, origin, flags]
+ *     bond_exist?(idx1, idx2) -> bool
  *
- *  Get the unit cell information in the form of a periodic bounding box.
- *  Avec, bvec, cvec, origin are Vector3D objects, and flags is a 3-member array of 
- *  Integers which define whether the system is periodic along the axis.
- *  If no unit cell is defined, nil is returned.
+ *  Returns true if bond exists between atoms idx1 and idx2, otherwise returns false.
+ *  Imaginary bonds between a pi-anchor and member atoms are not considered.
  */
 static VALUE
-s_Molecule_Box(VALUE self)
+s_Molecule_BondExist(VALUE self, VALUE ival1, VALUE ival2)
 {
-    Molecule *mol;
-       VALUE v[5], val;
+       Molecule *mol;
+       Int idx1, idx2, i;
+       Atom *ap;
+       Int *cp;
     Data_Get_Struct(self, Molecule, mol);
-       if (mol == NULL || mol->cell == NULL)
-               return Qnil;
-       v[0] = ValueFromVector(&(mol->cell->axes[0]));
-       v[1] = ValueFromVector(&(mol->cell->axes[1]));
-       v[2] = ValueFromVector(&(mol->cell->axes[2]));
-       v[3] = ValueFromVector(&(mol->cell->origin));
-       v[4] = rb_ary_new3(3, INT2NUM(mol->cell->flags[0]), INT2NUM(mol->cell->flags[1]), INT2NUM(mol->cell->flags[2]));
-       val = rb_ary_new4(5, v);
-       return val;
+       idx1 = NUM2INT(rb_Integer(ival1));
+       idx2 = NUM2INT(rb_Integer(ival2));
+       if (idx1 < 0 || idx1 >= mol->natoms || idx2 < 0 || idx2 >= mol->natoms)
+               rb_raise(rb_eMolbyError, "Atom index (%d or %d) out of range", idx1, idx2);
+       ap = ATOM_AT_INDEX(mol->atoms, idx1);
+       cp = AtomConnectData(&ap->connect);
+       for (i = 0; i < ap->connect.count; i++) {
+               if (cp[i] == idx2)
+                       return Qtrue;
+       }
+       return Qfalse;
 }
 
 /*
  *  call-seq:
- *     set_box(avec, bvec, cvec, origin = [0, 0, 0], flags = [1, 1, 1], convert_coordinates = nil)
- *     set_box(d, origin = [0, 0, 0])
- *     set_box
+ *     add_angle(n1, n2, n3)       -> Molecule
  *
- *  Set the unit cell parameters. Avec, bvec, and cvec can be either a Vector3D or a number.
- If it is a number, the x/y/z axis vector is multiplied with the given number and used
- as the box vector.
- Flags, if present, is a 3-member array of Integers defining whether the system is
- periodic along the axis.
- If convert_coordinates is true, then the coordinates are converted so that the fractional coordinates remain the same.
- In the second form, an isotropic box with cell-length d is set.
- In the third form, the existing box is cleared.
- Note: the sigma of the cell parameters is not cleared unless the periodic box itself is cleared.
+ *  Add angle n1-n2-n3. Returns self. Usually, angles are automatically added
+ *  when a bond is created, so it is rarely necessary to use this method explicitly.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_SetBox(VALUE self, VALUE aval)
+s_Molecule_AddAngle(VALUE self, VALUE v1, VALUE v2, VALUE v3)
 {
+       Int n[4];
     Molecule *mol;
-       VALUE v[6];
-       static Vector ax[3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
-       Vector vv[3];
-       Vector origin = {0, 0, 0};
-       char flags[3];
-       Double d;
-       int i, convertCoordinates = 0;
     Data_Get_Struct(self, Molecule, mol);
-       if (aval == Qnil) {
-               MolActionCreateAndPerform(mol, gMolActionClearBox);
-               return self;
-       }
-       aval = rb_ary_to_ary(aval);
-       for (i = 0; i < 6; i++) {
-               if (i < RARRAY_LEN(aval))
-                       v[i] = (RARRAY_PTR(aval))[i];
-               else v[i] = Qnil;
-       }
-       if (v[0] == Qnil) {
-               MolActionCreateAndPerform(mol, gMolActionClearBox);
-               return self;
-       }
-       if ((v[1] == Qnil || v[2] == Qnil) && rb_obj_is_kind_of(v[0], rb_cNumeric)) {
-               d = NUM2DBL(rb_Float(v[0]));
-               for (i = 0; i < 3; i++)
-                       VecScale(vv[i], ax[i], d);
-               if (v[1] != Qnil)
-                       VectorFromValue(v[1], &origin);
-               flags[0] = flags[1] = flags[2] = 1;
-       } else {
-               for (i = 0; i < 3; i++) {
-                       if (v[i] == Qnil) {
-                               VecZero(vv[i]);
-                       } else if (rb_obj_is_kind_of(v[i], rb_cNumeric)) {
-                               d = NUM2DBL(rb_Float(v[i]));
-                               VecScale(vv[i], ax[i], d);
-                       } else {
-                               VectorFromValue(v[i], &vv[i]);
-                       }
-                       flags[i] = (VecLength2(vv[i]) > 0.0);
-               }
-               if (v[3] != Qnil)
-                       VectorFromValue(v[3], &origin);
-               if (v[4] != Qnil) {
-                       for (i = 0; i < 3; i++) {
-                               VALUE val = Ruby_ObjectAtIndex(v[4], i);
-                               flags[i] = (NUM2INT(rb_Integer(val)) != 0);
-                       }
-               }
-               if (RTEST(v[5]))
-                       convertCoordinates = 1;
-       }
-       MolActionCreateAndPerform(mol, gMolActionSetBox, &(vv[0]), &(vv[1]), &(vv[2]), &origin, (flags[0] * 4 + flags[1] * 2 + flags[2]), convertCoordinates);
+       n[0] = s_Molecule_AtomIndexFromValue(mol, v1);
+       n[1] = s_Molecule_AtomIndexFromValue(mol, v2);
+       n[2] = s_Molecule_AtomIndexFromValue(mol, v3);
+       if (MoleculeLookupAngle(mol, n[0], n[1], n[2]) >= 0)
+               rb_raise(rb_eMolbyError, "angle %d-%d-%d is already present", n[0], n[1], n[2]);
+       n[3] = kInvalidIndex;
+       MolActionCreateAndPerform(mol, gMolActionAddAngles, 3, n, NULL);
        return self;
 }
 
 /*
  *  call-seq:
- *     cell_periodicity -> [n1, n2, n3]
+ *     remove_angle(n1, n2, n3)       -> Molecule
  *
- *  Get flags denoting whether the cell is periodic along the a/b/c axes. If the cell is not defined
- *  nil is returned.
+ *  Remove angle n1-n2-n3. Returns self. Usually, angles are automatically removed
+ *  when a bond is removed, so it is rarely necessary to use this method explicitly.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_CellPeriodicity(VALUE self)
+s_Molecule_RemoveAngle(VALUE self, VALUE v1, VALUE v2, VALUE v3)
 {
+       Int n[4];
     Molecule *mol;
+       IntGroup *ig;
     Data_Get_Struct(self, Molecule, mol);
-       if (mol->cell == NULL)
-               return Qnil;
-       return rb_ary_new3(3, INT2FIX((int)mol->cell->flags[0]), INT2FIX((int)mol->cell->flags[1]), INT2FIX((int)mol->cell->flags[2]));
+       n[0] = s_Molecule_AtomIndexFromValue(mol, v1);
+       n[1] = s_Molecule_AtomIndexFromValue(mol, v2);
+       n[2] = s_Molecule_AtomIndexFromValue(mol, v3);
+       if ((n[3] = MoleculeLookupAngle(mol, n[0], n[1], n[2])) < 0)
+               rb_raise(rb_eMolbyError, "angle %d-%d-%d is not present", n[0], n[1], n[2]);
+       ig = IntGroupNewWithPoints(n[3], 1, -1);
+       MolActionCreateAndPerform(mol, gMolActionDeleteAngles, ig);
+       IntGroupRelease(ig);
+       return self;
 }
 
 /*
  *  call-seq:
- *     self.cell_periodicity = [n1, n2, n3] or Integer or nil
- *     set_cell_periodicity = [n1, n2, n3] or Integer or nil
+ *     add_dihedral(n1, n2, n3, n4)       -> Molecule
  *
- *  Set whether the cell is periodic along the a/b/c axes. If an integer is given as an argument,
- *  its bits 2/1/0 (from the lowest) correspond to the a/b/c axes. Nil is equivalent to [0, 0, 0].
- *  If cell is not defined, exception is raised.
+ *  Add dihedral n1-n2-n3-n4. Returns self. Usually, dihedrals are automatically added
+ *  when a bond is created, so it is rarely necessary to use this method explicitly.
  *  This operation is undoable.
  */
 static VALUE
-s_Molecule_SetCellPeriodicity(VALUE self, VALUE arg)
+s_Molecule_AddDihedral(VALUE self, VALUE v1, VALUE v2, VALUE v3, VALUE v4)
 {
+       Int n[5];
     Molecule *mol;
-       Int flag;
     Data_Get_Struct(self, Molecule, mol);
-       if (mol->cell == NULL)
-               rb_raise(rb_eMolbyError, "periodic cell is not defined");
-       if (arg == Qnil)
-               flag = 0;
-       else if (rb_obj_is_kind_of(arg, rb_cNumeric))
-               flag = NUM2INT(rb_Integer(arg));
-       else {
-               Int i;
-               VALUE arg0;
-               arg = rb_ary_to_ary(arg);
-               flag = 0;
-               for (i = 0; i < 3 && i < RARRAY_LEN(arg); i++) {
-                       arg0 = RARRAY_PTR(arg)[i];
-                       if (arg0 != Qnil && arg0 != Qfalse && arg0 != INT2FIX(0))
-                               flag |= (1 << (2 - i));
-               }
-       }
-       MolActionCreateAndPerform(mol, gMolActionSetCellPeriodicity, flag);
-       return arg;
+       n[0] = s_Molecule_AtomIndexFromValue(mol, v1);
+       n[1] = s_Molecule_AtomIndexFromValue(mol, v2);
+       n[2] = s_Molecule_AtomIndexFromValue(mol, v3);
+       n[3] = s_Molecule_AtomIndexFromValue(mol, v4);
+       if (MoleculeLookupDihedral(mol, n[0], n[1], n[2], n[3]) >= 0)
+               rb_raise(rb_eMolbyError, "dihedral %d-%d-%d-%d is already present", n[0], n[1], n[2], n[3]);
+       n[4] = kInvalidIndex;
+       MolActionCreateAndPerform(mol, gMolActionAddDihedrals, 4, n, NULL);
+       return self;
 }
 
 /*
  *  call-seq:
- *     cell_flexibility -> bool
+ *     remove_dihedral(n1, n2, n3, n4)       -> Molecule
  *
- *  Returns the unit cell is flexible or not
+ *  Remove dihedral n1-n2-n3-n4. Returns self. Usually, dihedrals are automatically removed
+ *  when a bond is removed, so it is rarely necessary to use this method explicitly.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_CellFlexibility(VALUE self)
+s_Molecule_RemoveDihedral(VALUE self, VALUE v1, VALUE v2, VALUE v3, VALUE v4)
 {
-       rb_warn("cell_flexibility is obsolete (unit cell is always frame dependent)");
-       return Qtrue;
-/*    Molecule *mol;
+       Int n[5];
+    Molecule *mol;
+       IntGroup *ig;
     Data_Get_Struct(self, Molecule, mol);
-       if (mol->cell == NULL)
-               return Qfalse;
-       if (mol->useFlexibleCell)
-               return Qtrue;
-       else return Qfalse; */
+       n[0] = s_Molecule_AtomIndexFromValue(mol, v1);
+       n[1] = s_Molecule_AtomIndexFromValue(mol, v2);
+       n[2] = s_Molecule_AtomIndexFromValue(mol, v3);
+       n[3] = s_Molecule_AtomIndexFromValue(mol, v4);
+       if ((n[4] = MoleculeLookupDihedral(mol, n[0], n[1], n[2], n[3])) < 0)
+               rb_raise(rb_eMolbyError, "dihedral %d-%d-%d-%d is not present", n[0], n[1], n[2], n[3]);
+       ig = IntGroupNewWithPoints(n[4], 1, -1);
+       MolActionCreateAndPerform(mol, gMolActionDeleteDihedrals, ig);
+       IntGroupRelease(ig);
+       return self;
 }
 
 /*
  *  call-seq:
- *     self.cell_flexibility = bool
- *     set_cell_flexibility(bool)
+ *     add_improper(n1, n2, n3, n4)       -> Molecule
  *
- *  Change the unit cell is flexible or not
+ *  Add dihedral n1-n2-n3-n4. Returns self. Unlike angles and dihedrals, impropers are
+ *  not automatically added when a new bond is created, so this method is more useful than
+ *  the angle/dihedral counterpart.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_SetCellFlexibility(VALUE self, VALUE arg)
+s_Molecule_AddImproper(VALUE self, VALUE v1, VALUE v2, VALUE v3, VALUE v4)
 {
-       rb_warn("set_cell_flexibility is obsolete (unit cell is always frame dependent)");
-       return self;
-/*    Molecule *mol;
+       Int n[5];
+    Molecule *mol;
     Data_Get_Struct(self, Molecule, mol);
-       MolActionCreateAndPerform(mol, gMolActionSetCellFlexibility, RTEST(arg) != 0);
-       return self; */
+       n[0] = s_Molecule_AtomIndexFromValue(mol, v1);
+       n[1] = s_Molecule_AtomIndexFromValue(mol, v2);
+       n[2] = s_Molecule_AtomIndexFromValue(mol, v3);
+       n[3] = s_Molecule_AtomIndexFromValue(mol, v4);
+       if (MoleculeLookupImproper(mol, n[0], n[1], n[2], n[3]) >= 0)
+               rb_raise(rb_eMolbyError, "improper %d-%d-%d-%d is already present", n[0], n[1], n[2], n[3]);
+       n[4] = kInvalidIndex;
+       MolActionCreateAndPerform(mol, gMolActionAddImpropers, 4, n, NULL);
+       return self;
 }
 
 /*
  *  call-seq:
- *     cell_transform -> Transform
+ *     remove_improper(n1, n2, n3, n4)       -> Molecule
+ *     remove_improper(intgroup)             -> Molecule
  *
- *  Get the transform matrix that converts internal coordinates to cartesian coordinates.
- *  If cell is not defined, nil is returned.
+ *  Remove improper n1-n2-n3-n4, or the specified impropers (in indices) in IntGroup.
+ *  Returns self. Unlike angles and dihedrals, impropers are
+ *  not automatically added when a new bond is created, so this method is more useful than
+ *  the angle/dihedral counterpart.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_CellTransform(VALUE self)
+s_Molecule_RemoveImproper(int argc, VALUE *argv, VALUE self)
 {
+       Int n[5];
+       VALUE v1, v2, v3, v4;
     Molecule *mol;
+       IntGroup *ig;
     Data_Get_Struct(self, Molecule, mol);
-       if (mol == NULL || mol->cell == NULL)
-               return Qnil;
-       return ValueFromTransform(&(mol->cell->tr));
+       if (argc == 1) {
+               ig = IntGroupFromValue(argv[0]);
+       } else {
+               rb_scan_args(argc, argv, "40", &v1, &v2, &v3, &v4);
+               n[0] = s_Molecule_AtomIndexFromValue(mol, v1);
+               n[1] = s_Molecule_AtomIndexFromValue(mol, v2);
+               n[2] = s_Molecule_AtomIndexFromValue(mol, v3);
+               n[3] = s_Molecule_AtomIndexFromValue(mol, v4);
+               if ((n[4] = MoleculeLookupImproper(mol, n[0], n[1], n[2], n[3])) < 0)
+                       rb_raise(rb_eMolbyError, "improper %d-%d-%d-%d is not present", n[0], n[1], n[2], n[3]);
+               ig = IntGroupNewWithPoints(n[4], 1, -1);
+       }
+       MolActionCreateAndPerform(mol, gMolActionDeleteImpropers, ig);
+       IntGroupRelease(ig);
+       return self;
 }
 
 /*
  *  call-seq:
- *     symmetry -> Array of Transforms
- *     symmetries -> Array of Transforms
+ *     assign_residue(group, res)       -> Molecule
  *
- *  Get the currently defined symmetry operations. If no symmetry operation is defined,
- *  returns an empty array.
+ *  Assign the specified atoms as the given residue. res can either be an integer, "resname"
+ *  or "resname.resno". When the residue number is not specified, the residue number of
+ *  the first atom in the group is used.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_Symmetry(VALUE self)
+s_Molecule_AssignResidue(VALUE self, VALUE range, VALUE res)
 {
     Molecule *mol;
-       VALUE val;
-       int i;
-    Data_Get_Struct(self, Molecule, mol);
-       if (mol->nsyms <= 0)
-               return rb_ary_new();
-       val = rb_ary_new2(mol->nsyms);
-       for (i = 0; i < mol->nsyms; i++) {
-               rb_ary_push(val, ValueFromTransform(&mol->syms[i]));
+       IntGroup *ig;
+       char *p, *pp, buf[16];
+       Int resid, n;
+       Atom *ap;
+    Data_Get_Struct(self, Molecule, mol);
+       
+       /*  Parse the argument res  */
+       if (FIXNUM_P(res)) {
+               /*  We can assume Fixnum here because Bignum is non-realistic as residue numbers  */
+               resid = NUM2INT(res);
+               buf[0] = 0;
+       } else {
+               p = StringValuePtr(res);
+               pp = strchr(p, '.');
+               if (pp != NULL) {
+                       resid = atoi(pp + 1);
+                       n = pp - p;
+               } else {
+                       resid = -1;
+                       n = strlen(p);
+               }
+               if (n > sizeof buf - 1)
+                       n = sizeof buf - 1;
+               strncpy(buf, p, n);
+               buf[n] = 0;
        }
-       return val;
+       ig = s_Molecule_AtomGroupFromValue(self, range);
+       if (ig == NULL || IntGroupGetCount(ig) == 0)
+               return Qnil;
+
+       if (resid < 0) {
+               /*  Use the residue number of the first specified atom  */
+               n = IntGroupGetNthPoint(ig, 0);
+               if (n >= mol->natoms)
+                       rb_raise(rb_eMolbyError, "Atom index (%d) out of range", n);
+               ap = ATOM_AT_INDEX(mol->atoms, n);
+               resid = ap->resSeq;
+       }
+       /*  Change the residue number  */
+       MolActionCreateAndPerform(mol, gMolActionChangeResidueNumber, ig, resid);
+       /*  Change the residue name if necessary  */
+       if (buf[0] != 0) {
+       /*      Int seqs[2];
+               seqs[0] = resid;
+               seqs[1] = kInvalidIndex; */
+               MolActionCreateAndPerform(mol, gMolActionChangeResidueNames, 1, &resid, 4, buf);
+       }
+       IntGroupRelease(ig);
+       return self;
 }
 
 /*
  *  call-seq:
- *     nsymmetries -> Integer
+ *     offset_residue(group, offset)       -> Molecule
  *
- *  Get the number of currently defined symmetry operations.
+ *  Offset the residue number of the specified atoms. If any of the residue number gets
+ *  negative, then exception is thrown.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_Nsymmetries(VALUE self)
+s_Molecule_OffsetResidue(VALUE self, VALUE range, VALUE offset)
 {
     Molecule *mol;
+       IntGroup *ig;
+       int ofs, result;
     Data_Get_Struct(self, Molecule, mol);
-       return INT2NUM(mol->nsyms);
+       ig = s_Molecule_AtomGroupFromValue(self, range);
+       ofs = NUM2INT(offset);
+       result = MolActionCreateAndPerform(mol, gMolActionOffsetResidueNumbers, ig, ofs, -1);
+       if (result > 0)
+               rb_raise(rb_eMolbyError, "residue number of atom %d becomes negative", result - 1);
+       IntGroupRelease(ig);
+       return self;
 }
 
 /*
  *  call-seq:
- *     add_symmetry(Transform) -> Integer
+ *     renumber_atoms(array)       -> IntGroup
  *
- *  Add a new symmetry operation. If no symmetry operation is defined and the
- *  given argument is not an identity transform, then also add an identity
- *  transform at the index 0.
- *  Returns the total number of symmetries after operation.
+ *  Change the order of atoms so that the atoms specified in the array argument appear
+ *  in this order from the top of the molecule. The atoms that are not included in array
+ *  are placed after these atoms, and these atoms are returned as an intGroup.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_AddSymmetry(VALUE self, VALUE trans)
+s_Molecule_RenumberAtoms(VALUE self, VALUE array)
 {
     Molecule *mol;
-       Transform tr;
+       Int *new2old;
+       IntGroup *ig;
+       int i, n;
+       VALUE *valp, retval;
     Data_Get_Struct(self, Molecule, mol);
-       TransformFromValue(trans, &tr);
-       MolActionCreateAndPerform(mol, gMolActionAddSymmetryOperation, &tr);
-       return INT2NUM(mol->nsyms);
+       if (TYPE(array) != T_ARRAY)
+               array = rb_funcall(array, rb_intern("to_a"), 0);
+       n = RARRAY_LEN(array);
+       valp = RARRAY_PTR(array);
+       new2old = ALLOC_N(Int, n + 1);
+       for (i = 0; i < n; i++)
+               new2old[i] = s_Molecule_AtomIndexFromValue(mol, valp[i]);
+       new2old[i] = kInvalidIndex;
+       i = MolActionCreateAndPerform(mol, gMolActionRenumberAtoms, i, new2old);
+       if (i == 1)
+               rb_raise(rb_eMolbyError, "Atom index out of range");
+       else if (i == 2)
+               rb_raise(rb_eMolbyError, "Duplicate entry");
+       else if (i == 3)
+               rb_raise(rb_eMolbyError, "Internal inconsistency during atom renumbering");
+       retval = IntGroup_Alloc(rb_cIntGroup);
+       Data_Get_Struct(retval, IntGroup, ig);
+       if (mol->natoms > n)
+               IntGroup_RaiseIfError(IntGroupAdd(ig, n, mol->natoms - n));
+       xfree(new2old);
+       return retval;
 }
 
 /*
  *  call-seq:
- *     remove_symmetry(count = nil) -> Integer
- *     remove_symmetries(count = nil) -> Integer
+ *     set_atom_attr(index, key, value)
  *
- *  Remove the specified number of symmetry operations. The last added ones are removed
- *  first. If count is nil, then all symmetry operations are removed. Returns the
- *  number of leftover symmetries.
+ *  Set the atom attribute for the specified atom.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_RemoveSymmetry(int argc, VALUE *argv, VALUE self)
+s_Molecule_SetAtomAttr(VALUE self, VALUE idx, VALUE key, VALUE val)
 {
-    Molecule *mol;
-       VALUE cval;
-       int i, n;
+       Molecule *mol;
+       VALUE aref, oldval;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "01", &cval);
-       if (cval == Qnil)
-               n = mol->nsyms - 1;
-       else {
-               n = NUM2INT(rb_Integer(cval));
-               if (n < 0 || n > mol->nsyms)
-                       rb_raise(rb_eMolbyError, "the given count of symops is out of range");
-               if (n == mol->nsyms)
-                       n = mol->nsyms - 1;
-       }
-       for (i = 0; i < n; i++)
-               MolActionCreateAndPerform(mol, gMolActionDeleteSymmetryOperation);
-       return INT2NUM(mol->nsyms);
+       aref = ValueFromMoleculeAndIndex(mol, s_Molecule_AtomIndexFromValue(mol, idx));
+       oldval = s_AtomRef_GetAttr(aref, key);
+       if (val == Qundef)
+               return oldval;
+       s_AtomRef_SetAttr(aref, key, val);
+       return val;
 }
 
+/*
+ *  call-seq:
+ *     get_atom_attr(index, key)
+ *
+ *  Get the atom attribute for the specified atom.
+ */
 static VALUE
-s_Molecule_AtomGroup_i(VALUE arg, VALUE values)
+s_Molecule_GetAtomAttr(VALUE self, VALUE idx, VALUE key)
 {
-       Molecule *mol = (Molecule *)(((VALUE *)values)[0]);
-       IntGroup *ig1 = (IntGroup *)(((VALUE *)values)[1]);
-       int idx = s_Molecule_AtomIndexFromValue(mol, arg);
-       IntGroup_RaiseIfError(IntGroupAdd(ig1, idx, 1));
-       return Qnil;
+       return s_Molecule_SetAtomAttr(self, idx, key, Qundef);
 }
 
+#pragma mark ------ Undo Support ------
+
 /*
  *  call-seq:
- *     atom_group
- *     atom_group {|aref| ...}
- *     atom_group(arg1, arg2, ...)
- *     atom_group(arg1, arg2, ...) {|aref| ...}
- *
- *  Specify a group of atoms. If no arguments are given, IntGroup\[0...natoms] is the result.
- *  If arguments are given, then the atoms reprensented by the arguments are added to the
- *  group. For a conversion of a string to an atom index, see the description
- *  of Molecule#atom_index.
- *  If a block is given, it is evaluated with an AtomRef (not atom index integers)
- *  representing each atom, and the atoms are removed from the result if the block returns false.
+ *     register_undo(script, *args)
  *
+ *  Register an undo operation with the current molecule.
  */
 static VALUE
-s_Molecule_AtomGroup(int argc, VALUE *argv, VALUE self)
+s_Molecule_RegisterUndo(int argc, VALUE *argv, VALUE self)
 {
-       IntGroup *ig1, *ig2;
-    Molecule *mol;
-       Int i, startPt, interval;
-       VALUE retval = IntGroup_Alloc(rb_cIntGroup);
-       Data_Get_Struct(retval, IntGroup, ig1);
+       Molecule *mol;
+       VALUE script, args;
+       MolAction *act;
     Data_Get_Struct(self, Molecule, mol);
-       if (argc == 0) {
-               IntGroup_RaiseIfError(IntGroupAdd(ig1, 0, mol->natoms));
-       } else {
-               while (argc > 0) {
-                       if (FIXNUM_P(*argv) || TYPE(*argv) == T_STRING) {
-                               i = s_Molecule_AtomIndexFromValue(mol, *argv);
-                               IntGroup_RaiseIfError(IntGroupAdd(ig1, i, 1));
-                       } else if (rb_obj_is_kind_of(*argv, rb_cIntGroup)) {
-                               ig2 = IntGroupFromValue(*argv);
-                               for (i = 0; (startPt = IntGroupGetStartPoint(ig2, i)) >= 0; i++) {
-                                       interval = IntGroupGetInterval(ig2, i);
-                                       IntGroup_RaiseIfError(IntGroupAdd(ig1, startPt, interval));
-                               }
-                               IntGroupRelease(ig2);
-                       } else if (rb_respond_to(*argv, rb_intern("each"))) {
-                               VALUE values[2];
-                               values[0] = (VALUE)mol;
-                               values[1] = (VALUE)ig1;
-                               rb_iterate(rb_each, *argv, s_Molecule_AtomGroup_i, (VALUE)values);
-                       } else
-                               IntGroup_RaiseIfError(IntGroupAdd(ig1, NUM2INT(*argv), 1));
-                       argc--;
-                       argv++;
-               }
-       }
-       if (rb_block_given_p()) {
-               /*  Evaluate the given block with an AtomRef as the argument, and delete
-                       the index if the block returns false  */
-               AtomRef *aref = AtomRefNew(mol, 0);
-               VALUE arval = Data_Wrap_Struct(rb_cAtomRef, 0, (void (*)(void *))AtomRefRelease, aref);
-               ig2 = IntGroupNew();
-               IntGroupCopy(ig2, ig1);
-               for (i = 0; (startPt = IntGroupGetNthPoint(ig2, i)) >= 0; i++) {
-                       VALUE resval;
-                       if (startPt >= mol->natoms)
-                               break;
-                       aref->idx = startPt;
-                       resval = rb_yield(arval);
-                       if (!RTEST(resval))
-                               IntGroupRemove(ig1, startPt, 1);
-               }
-               IntGroupRelease(ig2);
-       }
-       
-       /*  Remove points that are out of bounds */
-       IntGroup_RaiseIfError(IntGroupRemove(ig1, mol->natoms, INT_MAX));
-
-       return retval;                  
+       rb_scan_args(argc, argv, "1*", &script, &args);
+       act = MolActionNew(SCRIPT_ACTION("R"), StringValuePtr(script), args);
+       MolActionCallback_registerUndo(mol, act);
+       return script;
 }
 
 /*
  *  call-seq:
- *     atom_index(val)       -> Integer
+ *     undo_enabled? -> bool
  *
- *  Returns the atom index represented by val. val can be either a non-negative integer
- *  (directly representing the atom index), a negative integer (representing <code>natoms - val</code>),
- *  a string of type "resname.resid:name" or "resname:name" or "resid:name" or "name", 
- *  where resname, resid, name are the residue name, residue id, and atom name respectively.
- *  If val is a string and multiple atoms match the description, the atom with the lowest index
- *  is returned.
+ *  Returns true if undo is enabled for this molecule; otherwise no.
  */
 static VALUE
-s_Molecule_AtomIndex(VALUE self, VALUE val)
+s_Molecule_UndoEnabled(VALUE self)
 {
     Molecule *mol;
     Data_Get_Struct(self, Molecule, mol);
-       return INT2NUM(s_Molecule_AtomIndexFromValue(mol, val));
+       if (MolActionCallback_isUndoRegistrationEnabled(mol))
+               return Qtrue;
+       else return Qfalse;
 }
 
 /*
  *  call-seq:
- *     extract(group, dummy_flag = nil)       -> Molecule
+ *     undo_enabled = bool
  *
- *  Extract the atoms given by group and return as a new molecule object.
- *  If dummy_flag is true, then the atoms that are not included in the group but are connected
- *  to any atoms in the group are converted to "dummy" atoms (i.e. with element "Du" and 
- *  names beginning with an underscore) and included in the new molecule object.
+ *  Enable or disable undo.
  */
 static VALUE
-s_Molecule_Extract(int argc, VALUE *argv, VALUE self)
+s_Molecule_SetUndoEnabled(VALUE self, VALUE val)
 {
-    Molecule *mol1, *mol2;
-       IntGroup *ig;
-       VALUE group, dummy_flag, retval;
-    Data_Get_Struct(self, Molecule, mol1);
-       rb_scan_args(argc, argv, "11", &group, &dummy_flag);
-       ig = s_Molecule_AtomGroupFromValue(self, group);
-       if (MoleculeExtract(mol1, &mol2, ig, (dummy_flag != Qnil && dummy_flag != Qfalse)) != 0) {
-               retval = Qnil;
-       } else {
-               retval = ValueFromMolecule(mol2);
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       MolActionCallback_setUndoRegistrationEnabled(mol, (val != Qfalse && val != Qnil));
+       return val;
+}
+
+#pragma mark ------ Measure ------
+
+static void
+s_Molecule_DoCenterOfMass(Molecule *mol, Vector *outv, IntGroup *ig)
+{
+       switch (MoleculeCenterOfMass(mol, outv, ig)) {
+               case 2: rb_raise(rb_eMolbyError, "atom group is empty"); break;
+               case 3: rb_raise(rb_eMolbyError, "weight is zero --- atomic weights are not defined?"); break;
+               case 0: break;
+               default: rb_raise(rb_eMolbyError, "cannot calculate center of mass"); break;
        }
-       IntGroupRelease(ig);
-       return retval;
 }
 
 /*
  *  call-seq:
- *     add(molecule2)       -> self
+ *     center_of_mass(group = nil)       -> Vector3D
  *
- *  Combine two molecules. The residue numbers of the newly added atoms may be renumbered to avoid
-    conflicts.
-    This operation is undoable.
+ *  Calculate the center of mass for the given set of atoms. The argument
+ *  group is null, then all atoms are considered.
  */
 static VALUE
-s_Molecule_Add(VALUE self, VALUE val)
+s_Molecule_CenterOfMass(int argc, VALUE *argv, VALUE self)
 {
-    Molecule *mol1, *mol2;
-    Data_Get_Struct(self, Molecule, mol1);
-       mol2 = MoleculeFromValue(val);
-       MolActionCreateAndPerform(mol1, gMolActionMergeMolecule, mol2, NULL);
-       return self; 
+    Molecule *mol;
+       VALUE group;
+       IntGroup *ig;
+       Vector v;
+    Data_Get_Struct(self, Molecule, mol);
+       rb_scan_args(argc, argv, "01", &group);
+       ig = (NIL_P(group) ? NULL : s_Molecule_AtomGroupFromValue(self, group));
+       s_Molecule_DoCenterOfMass(mol, &v, ig);
+       if (ig != NULL)
+               IntGroupRelease(ig);
+       return ValueFromVector(&v);
 }
 
 /*
  *  call-seq:
- *     remove(group)       -> Molecule
+ *     centralize(group = nil)       -> self
  *
- *  The atoms designated by the given group are removed from the molecule.
- *  This operation is undoable.
+ *  Translate the molecule so that the center of mass of the given group is located
+ *  at (0, 0, 0). Equivalent to molecule.translate(molecule.center_of_mass(group) * -1).
  */
 static VALUE
-s_Molecule_Remove(VALUE self, VALUE group)
+s_Molecule_Centralize(int argc, VALUE *argv, VALUE self)
 {
-    Molecule *mol1;
-       IntGroup *ig, *bg;
-       Int i;
-       IntGroupIterator iter;
-
-    Data_Get_Struct(self, Molecule, mol1);
-       group = rb_funcall(self, rb_intern("atom_group"), 1, group);
-       if (!rb_obj_is_kind_of(group, rb_cIntGroup))
-               rb_raise(rb_eMolbyError, "IntGroup instance is expected");
-       Data_Get_Struct(group, IntGroup, ig);
-
-       /*  Remove the bonds between the two fragments  */
-       /*  (This is necessary for undo to work correctly)  */
-       IntGroupIteratorInit(ig, &iter);
-       bg = NULL;
-       while ((i = IntGroupIteratorNext(&iter)) >= 0) {
-               Atom *ap = ATOM_AT_INDEX(mol1->atoms, i);
-               Int j, *cp;
-               cp = AtomConnectData(&ap->connect);
-               for (j = 0; j < ap->connect.count; j++) {
-                       int n = cp[j];
-                       if (!IntGroupLookup(ig, n, NULL)) {
-                               /*  bond i-n, i is in ig and n is not  */
-                               int k = MoleculeLookupBond(mol1, i, n);
-                               if (k >= 0) {
-                                       if (bg == NULL)
-                                               bg = IntGroupNew();
-                                       IntGroupAdd(bg, k, 1);
-                               }
-                       }
-               }
-       }
-       IntGroupIteratorRelease(&iter);
-       if (bg != NULL) {
-               /*  Remove bonds  */
-               MolActionCreateAndPerform(mol1, gMolActionDeleteBonds, bg);
-               IntGroupRelease(bg);
-       }
-       /*  Remove atoms  */
-       if (MolActionCreateAndPerform(mol1, gMolActionUnmergeMolecule, ig) == 0)
-               return Qnil;
+    Molecule *mol;
+       VALUE group;
+       IntGroup *ig;
+       Vector v;
+    Data_Get_Struct(self, Molecule, mol);
+       rb_scan_args(argc, argv, "01", &group);
+       ig = (NIL_P(group) ? NULL : s_Molecule_AtomGroupFromValue(self, group));
+       s_Molecule_DoCenterOfMass(mol, &v, ig);
+       if (ig != NULL)
+               IntGroupRelease(ig);
+       v.x = -v.x;
+       v.y = -v.y;
+       v.z = -v.z;
+       MolActionCreateAndPerform(mol, gMolActionTranslateAtoms, &v, NULL);
        return self;
 }
 
 /*
  *  call-seq:
- *     create_atom(name, pos = -1)  -> AtomRef
+ *     bounds(group = nil)       -> [min, max]
  *
- *  Create a new atom with the specified name (may contain residue 
- *  information) and position (if position is out of range, the atom is appended at
- *  the end). Returns the reference to the new atom.
- *  This operation is undoable.
+ *  Calculate the boundary. The return value is an array of two Vector3D objects.
  */
 static VALUE
-s_Molecule_CreateAnAtom(int argc, VALUE *argv, VALUE self)
+s_Molecule_Bounds(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-    Int i, pos;
-       VALUE name, ival;
-    Atom arec;
-    AtomRef *aref;
-       char *p, resName[6], atomName[6];
-       int resSeq;
+       VALUE group;
+       IntGroup *ig;
+       Vector vmin, vmax;
+       int n;
+       Atom *ap;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "02", &name, &ival);
-       if (ival != Qnil)
-               pos = NUM2INT(rb_Integer(ival));
-       else pos = -1;
-       if (name != Qnil) {
-               p = StringValuePtr(name);
-               if (p[0] != 0) {
-                       i = MoleculeAnalyzeAtomName(p, resName, &resSeq, atomName);
-                       if (atomName[0] == 0)
-                         rb_raise(rb_eMolbyError, "bad atom name specification: %s", p);
-               }
-       } else p = NULL;
-       if (p == NULL || p[0] == 0) {
-               memset(atomName, 0, 4);
-               resSeq = -1;
+       rb_scan_args(argc, argv, "01", &group);
+       ig = (NIL_P(group) ? NULL : s_Molecule_AtomGroupFromValue(self, group));
+       if (ig != NULL && IntGroupGetCount(ig) == 0)
+               rb_raise(rb_eMolbyError, "atom group is empty");
+       vmin.x = vmin.y = vmin.z = 1e30;
+       vmax.x = vmax.y = vmax.z = -1e30;
+       for (n = 0, ap = mol->atoms; n < mol->natoms; n++, ap = ATOM_NEXT(ap)) {
+               Vector r;
+               if (ig != NULL && IntGroupLookup(ig, n, NULL) == 0)
+                       continue;
+               r = ap->r;
+               if (r.x < vmin.x)
+                       vmin.x = r.x;
+               if (r.y < vmin.y)
+                       vmin.y = r.y;
+               if (r.z < vmin.z)
+                       vmin.z = r.z;
+               if (r.x > vmax.x)
+                       vmax.x = r.x;
+               if (r.y > vmax.y)
+                       vmax.y = r.y;
+               if (r.z > vmax.z)
+                       vmax.z = r.z;
+       }
+       return rb_ary_new3(2, ValueFromVector(&vmin), ValueFromVector(&vmax));
+}
+
+/*  Get atom position or a vector  */
+static void
+s_Molecule_GetVectorFromArg(Molecule *mol, VALUE val, Vector *vp)
+{
+       if (rb_obj_is_kind_of(val, rb_cInteger) || rb_obj_is_kind_of(val, rb_cString)) {
+               int n1 = s_Molecule_AtomIndexFromValue(mol, val);
+               *vp = ATOM_AT_INDEX(mol->atoms, n1)->r;
+       } else {
+               VectorFromValue(val, vp);
        }
-    memset(&arec, 0, sizeof(arec));
-    strncpy(arec.aname, atomName, 4);
-    if (resSeq >= 0) {
-      strncpy(arec.resName, resName, 4);
-      arec.resSeq = resSeq;
-    }
-       arec.occupancy = 1.0;
-//    i = MoleculeCreateAnAtom(mol, &arec);
-       if (MolActionCreateAndPerform(mol, gMolActionAddAnAtom, &arec, pos, &pos) != 0)
-               return Qnil;
-    aref = AtomRefNew(mol, pos);
-    return Data_Wrap_Struct(rb_cAtomRef, 0, (void (*)(void *))AtomRefRelease, aref);
 }
 
 /*
  *  call-seq:
- *     duplicate_atom(atomref, pos = -1)  -> AtomRef
+ *     measure_bond(n1, n2)       -> Float
  *
- *  Create a new atom with the same attributes (but no bonding information)
- *  with the specified atom. Returns the reference to the new atom.
- *  This operation is undoable.
+ *  Calculate the bond length. The arguments can either be atom indices, the "residue:name" representation, 
+ *  or Vector3D values.
+ *  If the crystallographic cell is defined, then the internal coordinates are convereted to the cartesian.
  */
 static VALUE
-s_Molecule_DuplicateAnAtom(int argc, VALUE *argv, VALUE self)
+s_Molecule_MeasureBond(VALUE self, VALUE nval1, VALUE nval2)
 {
     Molecule *mol;
-       const Atom *apsrc;
-    Atom arec;
-       AtomRef *aref;
-       VALUE retval, aval, ival;
-       Int pos;
+       Vector v1, v2;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "11", &aval, &ival);
-       if (FIXNUM_P(aval)) {
-               int idx = NUM2INT(aval);
-               if (idx < 0 || idx >= mol->natoms)
-                       rb_raise(rb_eMolbyError, "atom index out of range: %d", idx);
-               apsrc = ATOM_AT_INDEX(mol->atoms, idx);
-       } else {
-               apsrc = s_AtomFromValue(aval);
-       }
-       if (apsrc == NULL)
-               rb_raise(rb_eMolbyError, "bad atom specification");
-       if (ival != Qnil)
-               pos = NUM2INT(rb_Integer(ival));
-       else pos = -1;
-       AtomDuplicate(&arec, apsrc);
-       arec.connect.count = 0;
-       if (MolActionCreateAndPerform(mol, gMolActionAddAnAtom, &arec, pos, &pos) != 0)
-               retval = Qnil;
-       else {
-               aref = AtomRefNew(mol, pos);
-               retval = Data_Wrap_Struct(rb_cAtomRef, 0, (void (*)(void *))AtomRefRelease, aref);
-       }
-       AtomClean(&arec);
-       return retval;
+       s_Molecule_GetVectorFromArg(mol, nval1, &v1);
+       s_Molecule_GetVectorFromArg(mol, nval2, &v2);
+       return rb_float_new(MoleculeMeasureBond(mol, &v1, &v2));
 }
 
 /*
  *  call-seq:
- *     create_bond(n1, n2, ...)       -> Integer
+ *     measure_angle(n1, n2, n3)       -> Float
  *
- *  Create bonds between atoms n1 and n2, n3 and n4, and so on. If the corresponding bond is already present for a particular pair,
- *  do nothing for that pair. Returns the number of bonds actually created.
- *  This operation is undoable.
+ *  Calculate the bond angle. The arguments can either be atom indices, the "residue:name" representation, 
+ *  or Vector3D values. The return value is in degree.
+ *  If the crystallographic cell is defined, then the internal coordinates are convereted to the cartesian.
  */
 static VALUE
-s_Molecule_CreateBond(int argc, VALUE *argv, VALUE self)
+s_Molecule_MeasureAngle(VALUE self, VALUE nval1, VALUE nval2, VALUE nval3)
 {
     Molecule *mol;
-       Int i, j, k, *ip, old_nbonds;
-       if (argc == 0)
-               rb_raise(rb_eMolbyError, "missing arguments");
-       if (argc % 2 != 0)
-               rb_raise(rb_eMolbyError, "bonds should be specified by pairs of atom indices");
-    Data_Get_Struct(self, Molecule, mol);
-       ip = ALLOC_N(Int, argc + 1);
-       for (i = j = 0; i < argc; i++, j++) {
-               ip[j] = s_Molecule_AtomIndexFromValue(mol, argv[i]);
-               if (i % 2 == 1) {
-                       if (MoleculeLookupBond(mol, ip[j - 1], ip[j]) >= 0)
-                               j -= 2;  /*  This bond is already present: skip it  */
-                       else {
-                               for (k = 0; k < j - 1; k += 2) {
-                                       if ((ip[k] == ip[j - 1] && ip[k + 1] == ip[j]) || (ip[k + 1] == ip[j - 1] && ip[k] == ip[j])) {
-                                               j -= 2;   /*  The same entry is already in the argument  */
-                                               break;
-                                       }
-                               }
-                       }
-               }
-       }
-       old_nbonds = mol->nbonds;
-       if (j > 0) {
-               ip[j] = kInvalidIndex;
-               i = MolActionCreateAndPerform(mol, gMolActionAddBonds, j, ip, NULL);
-       } else i = 0;
-       xfree(ip);
-       if (i == -1)
-               rb_raise(rb_eMolbyError, "atom index out of range");
-       else if (i == -2)
-               rb_raise(rb_eMolbyError, "too many bonds");
-       else if (i == -3)
-               rb_raise(rb_eMolbyError, "duplicate bonds");
-       else if (i == -5)
-               rb_raise(rb_eMolbyError, "cannot create bond to itself");
-       else if (i != 0)
-               rb_raise(rb_eMolbyError, "error in creating bonds");
-       return INT2NUM(mol->nbonds - old_nbonds);
-}
-
-/*
- *  call-seq:
- *     molecule.remove_bonds(n1, n2, ...)       -> Integer
- *
- *  Remove bonds between atoms n1 and n2, n3 and n4, and so on. If the corresponding bond is not present for
- *  a particular pair, do nothing for that pair. Returns the number of bonds actually removed.
- *  This operation is undoable.
- */
-static VALUE
-s_Molecule_RemoveBond(int argc, VALUE *argv, VALUE self)
-{
-    Molecule *mol;
-       Int i, j, n[2];
-       IntGroup *bg;
-       if (argc == 0)
-               rb_raise(rb_eMolbyError, "missing arguments");
-       if (argc % 2 != 0)
-               rb_raise(rb_eMolbyError, "bonds should be specified by pairs of atom indices");
-    Data_Get_Struct(self, Molecule, mol);
-       bg = NULL;
-       for (i = j = 0; i < argc; i++, j = 1 - j) {
-               n[j] = s_Molecule_AtomIndexFromValue(mol, argv[i]);
-               if (j == 1) {
-                       Int k = MoleculeLookupBond(mol, n[0], n[1]);
-                       if (k >= 0) {
-                               if (bg == NULL)
-                                       bg = IntGroupNew();
-                               IntGroupAdd(bg, k, 1);
-                       }
-               }
-       }
-       if (bg != NULL) {
-               MolActionCreateAndPerform(mol, gMolActionDeleteBonds, bg);
-               i = IntGroupGetCount(bg);
-               IntGroupRelease(bg);
-       } else i = 0;
-       return INT2NUM(i);
-}
-
-/*
- *  call-seq:
- *     assign_bond_order(idx, d1)
- *     assign_bond_orders(group, [d1, d2, ...])
- *
- *  Assign bond order. In the first form, the bond order of the idx-th bond is set to d1 (a Float value).
- *  In the second form, the bond orders at the indices in the group are set to d1, d2, etc.
- *  At present, the bond orders are only used in UFF parameter guess, and not in the MM/MD calculations.
- *  (This may change in the future)
- *  This operation is undoable.
- */
-static VALUE
-s_Molecule_AssignBondOrder(VALUE self, VALUE idxval, VALUE dval)
-{
-    Molecule *mol;
-       IntGroup *ig;
-    Data_Get_Struct(self, Molecule, mol);
-       if (rb_obj_is_kind_of(idxval, rb_cNumeric)) {
-               /*  The first form  */
-               Int idx = NUM2INT(rb_Integer(idxval));
-               Double d1 = NUM2DBL(rb_Float(dval));
-               if (idx < 0 || idx >= mol->nbonds)
-                       rb_raise(rb_eMolbyError, "the bond index (%d) is out of bounds", idx);
-               ig = IntGroupNewWithPoints(idx, 1, -1);
-               MolActionCreateAndPerform(mol, gMolActionAssignBondOrders, 1, &d1, ig);
-               IntGroupRelease(ig);
-       } else {
-               Int i, n;
-               Double *dp;
-               ig = IntGroupFromValue(idxval);
-               n = IntGroupGetCount(ig);
-               if (n == 0)
-                       rb_raise(rb_eMolbyError, "the bond index is empty");
-               dval = rb_ary_to_ary(dval);
-               dp = (Double *)calloc(sizeof(Double), n);
-               for (i = 0; i < RARRAY_LEN(dval) && i < n; i++) {
-                       dp[i] = NUM2DBL(rb_Float(RARRAY_PTR(dval)[i]));
-               }
-               MolActionCreateAndPerform(mol, gMolActionAssignBondOrders, n, dp, ig);
-               free(dp);
-               IntGroupRelease(ig);
-       }
-       return self;
-}
-
-/*
- *  call-seq:
- *     get_bond_order(idx) -> Float
- *     get_bond_orders(group) -> Array
- *
- *  Get the bond order. In the first form, the bond order of the idx-th bond is returned.
- *  In the second form, the bond orders at the indices in the group are returned as an array.
- *  If no bond order information have been assigned, returns nil (the first form)
- *  or an empty array (the second form).
- */
-static VALUE
-s_Molecule_GetBondOrder(VALUE self, VALUE idxval)
-{
-    Molecule *mol;
-       IntGroup *ig;
-       Double *dp;
-       VALUE retval;
-       Int i, n, numericArg;
-    Data_Get_Struct(self, Molecule, mol);
-       if (rb_obj_is_kind_of(idxval, rb_cNumeric)) {
-               /*  The first form  */
-               Int idx = NUM2INT(rb_Integer(idxval));
-               if (idx < 0 || idx >= mol->nbonds)
-                       rb_raise(rb_eMolbyError, "the bond index (%d) is out of bounds", idx);
-               if (mol->bondOrders == NULL)
-                       return Qnil;
-               ig = IntGroupNewWithPoints(idx, 1, -1);
-               n = 1;
-               numericArg = 1;
-       } else {
-               if (mol->bondOrders == NULL)
-                       return rb_ary_new();
-               ig = IntGroupFromValue(idxval);
-               n = IntGroupGetCount(ig);
-               if (n == 0)
-                       rb_raise(rb_eMolbyError, "the bond index is empty");
-               numericArg = 0;
-       }
-       dp = (Double *)calloc(sizeof(Double), n);
-       MoleculeGetBondOrders(mol, dp, ig);
-       if (numericArg)
-               retval = rb_float_new(dp[0]);
-       else {
-               retval = rb_ary_new();
-               for (i = 0; i < n; i++)
-                       rb_ary_push(retval, rb_float_new(dp[i]));
-       }
-       free(dp);
-       IntGroupRelease(ig);
-       return retval;
-}
-
-/*
- *  call-seq:
- *     add_angle(n1, n2, n3)       -> Molecule
- *
- *  Add angle n1-n2-n3. Returns self. Usually, angles are automatically added
- *  when a bond is created, so it is rarely necessary to use this method explicitly.
- *  This operation is undoable.
- */
-static VALUE
-s_Molecule_AddAngle(VALUE self, VALUE v1, VALUE v2, VALUE v3)
-{
-       Int n[4];
-    Molecule *mol;
-    Data_Get_Struct(self, Molecule, mol);
-       n[0] = s_Molecule_AtomIndexFromValue(mol, v1);
-       n[1] = s_Molecule_AtomIndexFromValue(mol, v2);
-       n[2] = s_Molecule_AtomIndexFromValue(mol, v3);
-       if (MoleculeLookupAngle(mol, n[0], n[1], n[2]) >= 0)
-               rb_raise(rb_eMolbyError, "angle %d-%d-%d is already present", n[0], n[1], n[2]);
-       n[3] = kInvalidIndex;
-       MolActionCreateAndPerform(mol, gMolActionAddAngles, 3, n, NULL);
-       return self;
-}
-
-/*
- *  call-seq:
- *     remove_angle(n1, n2, n3)       -> Molecule
- *
- *  Remove angle n1-n2-n3. Returns self. Usually, angles are automatically removed
- *  when a bond is removed, so it is rarely necessary to use this method explicitly.
- *  This operation is undoable.
- */
-static VALUE
-s_Molecule_RemoveAngle(VALUE self, VALUE v1, VALUE v2, VALUE v3)
-{
-       Int n[4];
-    Molecule *mol;
-       IntGroup *ig;
-    Data_Get_Struct(self, Molecule, mol);
-       n[0] = s_Molecule_AtomIndexFromValue(mol, v1);
-       n[1] = s_Molecule_AtomIndexFromValue(mol, v2);
-       n[2] = s_Molecule_AtomIndexFromValue(mol, v3);
-       if ((n[3] = MoleculeLookupAngle(mol, n[0], n[1], n[2])) < 0)
-               rb_raise(rb_eMolbyError, "angle %d-%d-%d is not present", n[0], n[1], n[2]);
-       ig = IntGroupNewWithPoints(n[3], 1, -1);
-       MolActionCreateAndPerform(mol, gMolActionDeleteAngles, ig);
-       IntGroupRelease(ig);
-       return self;
-}
-
-/*
- *  call-seq:
- *     add_dihedral(n1, n2, n3, n4)       -> Molecule
- *
- *  Add dihedral n1-n2-n3-n4. Returns self. Usually, dihedrals are automatically added
- *  when a bond is created, so it is rarely necessary to use this method explicitly.
- *  This operation is undoable.
- */
-static VALUE
-s_Molecule_AddDihedral(VALUE self, VALUE v1, VALUE v2, VALUE v3, VALUE v4)
-{
-       Int n[5];
-    Molecule *mol;
-    Data_Get_Struct(self, Molecule, mol);
-       n[0] = s_Molecule_AtomIndexFromValue(mol, v1);
-       n[1] = s_Molecule_AtomIndexFromValue(mol, v2);
-       n[2] = s_Molecule_AtomIndexFromValue(mol, v3);
-       n[3] = s_Molecule_AtomIndexFromValue(mol, v4);
-       if (MoleculeLookupDihedral(mol, n[0], n[1], n[2], n[3]) >= 0)
-               rb_raise(rb_eMolbyError, "dihedral %d-%d-%d-%d is already present", n[0], n[1], n[2], n[3]);
-       n[4] = kInvalidIndex;
-       MolActionCreateAndPerform(mol, gMolActionAddDihedrals, 4, n, NULL);
-       return self;
-}
-
-/*
- *  call-seq:
- *     remove_dihedral(n1, n2, n3, n4)       -> Molecule
- *
- *  Remove dihedral n1-n2-n3-n4. Returns self. Usually, dihedrals are automatically removed
- *  when a bond is removed, so it is rarely necessary to use this method explicitly.
- *  This operation is undoable.
- */
-static VALUE
-s_Molecule_RemoveDihedral(VALUE self, VALUE v1, VALUE v2, VALUE v3, VALUE v4)
-{
-       Int n[5];
-    Molecule *mol;
-       IntGroup *ig;
-    Data_Get_Struct(self, Molecule, mol);
-       n[0] = s_Molecule_AtomIndexFromValue(mol, v1);
-       n[1] = s_Molecule_AtomIndexFromValue(mol, v2);
-       n[2] = s_Molecule_AtomIndexFromValue(mol, v3);
-       n[3] = s_Molecule_AtomIndexFromValue(mol, v4);
-       if ((n[4] = MoleculeLookupDihedral(mol, n[0], n[1], n[2], n[3])) < 0)
-               rb_raise(rb_eMolbyError, "dihedral %d-%d-%d-%d is not present", n[0], n[1], n[2], n[3]);
-       ig = IntGroupNewWithPoints(n[4], 1, -1);
-       MolActionCreateAndPerform(mol, gMolActionDeleteDihedrals, ig);
-       IntGroupRelease(ig);
-       return self;
-}
-
-/*
- *  call-seq:
- *     add_improper(n1, n2, n3, n4)       -> Molecule
- *
- *  Add dihedral n1-n2-n3-n4. Returns self. Unlike angles and dihedrals, impropers are
- *  not automatically added when a new bond is created, so this method is more useful than
- *  the angle/dihedral counterpart.
- *  This operation is undoable.
- */
-static VALUE
-s_Molecule_AddImproper(VALUE self, VALUE v1, VALUE v2, VALUE v3, VALUE v4)
-{
-       Int n[5];
-    Molecule *mol;
-    Data_Get_Struct(self, Molecule, mol);
-       n[0] = s_Molecule_AtomIndexFromValue(mol, v1);
-       n[1] = s_Molecule_AtomIndexFromValue(mol, v2);
-       n[2] = s_Molecule_AtomIndexFromValue(mol, v3);
-       n[3] = s_Molecule_AtomIndexFromValue(mol, v4);
-       if (MoleculeLookupImproper(mol, n[0], n[1], n[2], n[3]) >= 0)
-               rb_raise(rb_eMolbyError, "improper %d-%d-%d-%d is already present", n[0], n[1], n[2], n[3]);
-       n[4] = kInvalidIndex;
-       MolActionCreateAndPerform(mol, gMolActionAddImpropers, 4, n, NULL);
-       return self;
-}
-
-/*
- *  call-seq:
- *     remove_improper(n1, n2, n3, n4)       -> Molecule
- *     remove_improper(intgroup)             -> Molecule
- *
- *  Remove improper n1-n2-n3-n4, or the specified impropers (in indices) in IntGroup.
- *  Returns self. Unlike angles and dihedrals, impropers are
- *  not automatically added when a new bond is created, so this method is more useful than
- *  the angle/dihedral counterpart.
- *  This operation is undoable.
- */
-static VALUE
-s_Molecule_RemoveImproper(int argc, VALUE *argv, VALUE self)
-{
-       Int n[5];
-       VALUE v1, v2, v3, v4;
-    Molecule *mol;
-       IntGroup *ig;
-    Data_Get_Struct(self, Molecule, mol);
-       if (argc == 1) {
-               ig = IntGroupFromValue(argv[0]);
-       } else {
-               rb_scan_args(argc, argv, "40", &v1, &v2, &v3, &v4);
-               n[0] = s_Molecule_AtomIndexFromValue(mol, v1);
-               n[1] = s_Molecule_AtomIndexFromValue(mol, v2);
-               n[2] = s_Molecule_AtomIndexFromValue(mol, v3);
-               n[3] = s_Molecule_AtomIndexFromValue(mol, v4);
-               if ((n[4] = MoleculeLookupImproper(mol, n[0], n[1], n[2], n[3])) < 0)
-                       rb_raise(rb_eMolbyError, "improper %d-%d-%d-%d is not present", n[0], n[1], n[2], n[3]);
-               ig = IntGroupNewWithPoints(n[4], 1, -1);
-       }
-       MolActionCreateAndPerform(mol, gMolActionDeleteImpropers, ig);
-       IntGroupRelease(ig);
-       return self;
-}
-
-/*
- *  call-seq:
- *     assign_residue(group, res)       -> Molecule
- *
- *  Assign the specified atoms as the given residue. res can either be an integer, "resname"
- *  or "resname.resno". When the residue number is not specified, the residue number of
- *  the first atom in the group is used.
- *  This operation is undoable.
- */
-static VALUE
-s_Molecule_AssignResidue(VALUE self, VALUE range, VALUE res)
-{
-    Molecule *mol;
-       IntGroup *ig;
-       char *p, *pp, buf[16];
-       Int resid, n;
-       Atom *ap;
-    Data_Get_Struct(self, Molecule, mol);
-       
-       /*  Parse the argument res  */
-       if (FIXNUM_P(res)) {
-               /*  We can assume Fixnum here because Bignum is non-realistic as residue numbers  */
-               resid = NUM2INT(res);
-               buf[0] = 0;
-       } else {
-               p = StringValuePtr(res);
-               pp = strchr(p, '.');
-               if (pp != NULL) {
-                       resid = atoi(pp + 1);
-                       n = pp - p;
-               } else {
-                       resid = -1;
-                       n = strlen(p);
-               }
-               if (n > sizeof buf - 1)
-                       n = sizeof buf - 1;
-               strncpy(buf, p, n);
-               buf[n] = 0;
-       }
-       ig = s_Molecule_AtomGroupFromValue(self, range);
-       if (ig == NULL || IntGroupGetCount(ig) == 0)
-               return Qnil;
-
-       if (resid < 0) {
-               /*  Use the residue number of the first specified atom  */
-               n = IntGroupGetNthPoint(ig, 0);
-               if (n >= mol->natoms)
-                       rb_raise(rb_eMolbyError, "Atom index (%d) out of range", n);
-               ap = ATOM_AT_INDEX(mol->atoms, n);
-               resid = ap->resSeq;
-       }
-       /*  Change the residue number  */
-       MolActionCreateAndPerform(mol, gMolActionChangeResidueNumber, ig, resid);
-       /*  Change the residue name if necessary  */
-       if (buf[0] != 0) {
-       /*      Int seqs[2];
-               seqs[0] = resid;
-               seqs[1] = kInvalidIndex; */
-               MolActionCreateAndPerform(mol, gMolActionChangeResidueNames, 1, &resid, 4, buf);
-       }
-       IntGroupRelease(ig);
-       return self;
-}
-
-/*
- *  call-seq:
- *     offset_residue(group, offset)       -> Molecule
- *
- *  Offset the residue number of the specified atoms. If any of the residue number gets
- *  negative, then exception is thrown.
- *  This operation is undoable.
- */
-static VALUE
-s_Molecule_OffsetResidue(VALUE self, VALUE range, VALUE offset)
-{
-    Molecule *mol;
-       IntGroup *ig;
-       int ofs, result;
-    Data_Get_Struct(self, Molecule, mol);
-       ig = s_Molecule_AtomGroupFromValue(self, range);
-       ofs = NUM2INT(offset);
-       result = MolActionCreateAndPerform(mol, gMolActionOffsetResidueNumbers, ig, ofs, -1);
-       if (result > 0)
-               rb_raise(rb_eMolbyError, "residue number of atom %d becomes negative", result - 1);
-       IntGroupRelease(ig);
-       return self;
-}
-
-/*
- *  call-seq:
- *     renumber_atoms(array)       -> IntGroup
- *
- *  Change the order of atoms so that the atoms specified in the array argument appear
- *  in this order from the top of the molecule. The atoms that are not included in array
- *  are placed after these atoms, and these atoms are returned as an intGroup.
- *  This operation is undoable.
- */
-static VALUE
-s_Molecule_RenumberAtoms(VALUE self, VALUE array)
-{
-    Molecule *mol;
-       Int *new2old;
-       IntGroup *ig;
-       int i, n;
-       VALUE *valp, retval;
-    Data_Get_Struct(self, Molecule, mol);
-       if (TYPE(array) != T_ARRAY)
-               array = rb_funcall(array, rb_intern("to_a"), 0);
-       n = RARRAY_LEN(array);
-       valp = RARRAY_PTR(array);
-       new2old = ALLOC_N(Int, n + 1);
-       for (i = 0; i < n; i++)
-               new2old[i] = s_Molecule_AtomIndexFromValue(mol, valp[i]);
-       new2old[i] = kInvalidIndex;
-       i = MolActionCreateAndPerform(mol, gMolActionRenumberAtoms, i, new2old);
-       if (i == 1)
-               rb_raise(rb_eMolbyError, "Atom index out of range");
-       else if (i == 2)
-               rb_raise(rb_eMolbyError, "Duplicate entry");
-       else if (i == 3)
-               rb_raise(rb_eMolbyError, "Internal inconsistency during atom renumbering");
-       retval = IntGroup_Alloc(rb_cIntGroup);
-       Data_Get_Struct(retval, IntGroup, ig);
-       if (mol->natoms > n)
-               IntGroup_RaiseIfError(IntGroupAdd(ig, n, mol->natoms - n));
-       xfree(new2old);
-       return retval;
+       Vector v1, v2, v3;
+       Double d;
+    Data_Get_Struct(self, Molecule, mol);
+       s_Molecule_GetVectorFromArg(mol, nval1, &v1);
+       s_Molecule_GetVectorFromArg(mol, nval2, &v2);
+       s_Molecule_GetVectorFromArg(mol, nval3, &v3);   
+       d = MoleculeMeasureAngle(mol, &v1, &v2, &v3);
+       if (isnan(d))
+               return Qnil;  /*  Cannot define  */
+       else return rb_float_new(d);
 }
 
 /*
  *  call-seq:
- *     find_close_atoms(atom, limit = 1.2)       -> array of Integers (atom indices)
+ *     measure_dihedral(n1, n2, n3, n4)       -> Float
  *
- *  Find atoms that are within the threshold distance from the given atom.
- *  If limit is a positive number, the threshold distance is the sum of the vdw radii times limit.
- *  If limit is a negative number, its absolute value is used for the threshold distance in angstrom.
- *  If limit is not given, a default value of 1.2 is used.
- *  An array of atom indices is returned. If no atoms are found, an empty array is returned.
+ *  Calculate the dihedral angle. The arguments can either be atom indices, the "residue:name" representation, 
+ *  or Vector3D values. The return value is in degree.
+ *  If the crystallographic cell is defined, then the internal coordinates are convereted to the cartesian.
  */
 static VALUE
-s_Molecule_FindCloseAtoms(int argc, VALUE *argv, VALUE self)
+s_Molecule_MeasureDihedral(VALUE self, VALUE nval1, VALUE nval2, VALUE nval3, VALUE nval4)
 {
     Molecule *mol;
-       VALUE aval, limval;
-       double limit;
-       Int n1, nbonds, *bonds;
+       Vector v1, v2, v3, v4;
+       Double d;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "11", &aval, &limval);
-       n1 = s_Molecule_AtomIndexFromValue(mol, aval);
-       if (limval == Qnil)
-               limit = 1.2;
-       else
-               limit = NUM2DBL(rb_Float(limval));
-       nbonds = 0;  /*  This initialization is necessary: see comments in MoleculeFindCloseAtoms()  */
-       bonds = NULL;
-       MoleculeFindCloseAtoms(mol, n1, limit, &nbonds, &bonds, 0);
-       aval = rb_ary_new();
-       if (nbonds > 0) {
-               for (n1 = 0; n1 < nbonds; n1++)
-                       rb_ary_push(aval, INT2NUM(bonds[n1 * 2 + 1]));
-               free(bonds);
-       }
-       return aval;
+       s_Molecule_GetVectorFromArg(mol, nval1, &v1);
+       s_Molecule_GetVectorFromArg(mol, nval2, &v2);
+       s_Molecule_GetVectorFromArg(mol, nval3, &v3);   
+       s_Molecule_GetVectorFromArg(mol, nval4, &v4);   
+       d = MoleculeMeasureDihedral(mol, &v1, &v2, &v3, &v4);
+       if (isnan(d))
+               return Qnil;  /*  Cannot define  */
+       else return rb_float_new(d);
 }
 
 /*
  *  call-seq:
- *     guess_bonds(limit = 1.2)       -> Integer
+ *     find_conflicts(limit[, group1[, group2 [, ignore_exclusion]]]) -> [[n1, n2], [n3, n4], ...]
  *
- *  Create bonds between atoms that are within the threshold distance.
- *  If limit is a positive number, the threshold distance is the sum of the vdw radii times limit.
- *  If limit is a negative number, its absolute value is used for the threshold distance in angstrom.
- *  If limit is not given, a default value of 1.2 is used.
- *  The number of the newly created bonds is returned.
- *  This operation is undoable.
+ *  Find pairs of atoms that are within the limit distance. If group1 and group2 are given, the
+ *  first and second atom in the pair should belong to group1 and group2, respectively.
+ *  If ignore_exclusion is true, then 1-2 (bonded), 1-3, 1-4 pairs are also considered.
  */
 static VALUE
-s_Molecule_GuessBonds(int argc, VALUE *argv, VALUE self)
+s_Molecule_FindConflicts(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       VALUE limval;
-       double limit;
-       Int nbonds, *bonds;
+       VALUE limval, gval1, gval2, rval, igval;
+       IntGroup *ig1, *ig2;
+       IntGroupIterator iter1, iter2;
+       Int npairs, *pairs;
+       Int n[2], i;
+       Double lim;
+       Vector r1;
+       Atom *ap1, *ap2;
+       MDExclusion *exinfo;
+       Int *exlist;
+       
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "01", &limval);
-       if (limval == Qnil)
-               limit = 1.2;
+       rb_scan_args(argc, argv, "13", &limval, &gval1, &gval2, &igval);
+       lim = NUM2DBL(rb_Float(limval));
+       if (lim <= 0.0)
+               rb_raise(rb_eMolbyError, "the limit (%g) should be positive", lim);
+       if (gval1 != Qnil)
+               ig1 = s_Molecule_AtomGroupFromValue(self, gval1);
        else
-               limit = NUM2DBL(rb_Float(limval));
-       MoleculeGuessBonds(mol, limit, &nbonds, &bonds);
-       if (nbonds > 0) {
-               MolActionCreateAndPerform(mol, gMolActionAddBonds, nbonds * 2, bonds, NULL);
-               free(bonds);
-       }
-       return INT2NUM(nbonds);
-}
+               ig1 = IntGroupNewWithPoints(0, mol->natoms, -1);
+       if (gval2 != Qnil)
+               ig2 = s_Molecule_AtomGroupFromValue(self, gval2);
+       else
+               ig2 = IntGroupNewWithPoints(0, mol->natoms, -1);
        
-/*
- *  call-seq:
- *     register_undo(script, *args)
- *
- *  Register an undo operation with the current molecule.
- */
-static VALUE
-s_Molecule_RegisterUndo(int argc, VALUE *argv, VALUE self)
-{
-       Molecule *mol;
-       VALUE script, args;
-       MolAction *act;
-    Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "1*", &script, &args);
-       act = MolActionNew(SCRIPT_ACTION("R"), StringValuePtr(script), args);
-       MolActionCallback_registerUndo(mol, act);
-       return script;
-}
-
-/*
- *  call-seq:
- *     undo_enabled? -> bool
- *
- *  Returns true if undo is enabled for this molecule; otherwise no.
- */
-static VALUE
-s_Molecule_UndoEnabled(VALUE self)
-{
-    Molecule *mol;
-    Data_Get_Struct(self, Molecule, mol);
-       if (MolActionCallback_isUndoRegistrationEnabled(mol))
-               return Qtrue;
-       else return Qfalse;
-}
-
-/*
- *  call-seq:
- *     undo_enabled = bool
- *
- *  Enable or disable undo.
- */
-static VALUE
-s_Molecule_SetUndoEnabled(VALUE self, VALUE val)
-{
-    Molecule *mol;
-    Data_Get_Struct(self, Molecule, mol);
-       MolActionCallback_setUndoRegistrationEnabled(mol, (val != Qfalse && val != Qnil));
-       return val;
-}
-
-/*
- *  call-seq:
- *     selection       -> IntGroup
- *
- *  Returns the current selection.
- */
-static VALUE
-s_Molecule_Selection(VALUE self)
-{
-    Molecule *mol;
-       IntGroup *ig;
-       VALUE val;
-    Data_Get_Struct(self, Molecule, mol);
-       if (mol != NULL && (ig = MoleculeGetSelection(mol)) != NULL) {
-               ig = IntGroupNewFromIntGroup(ig);  /*  Duplicate, so that the change from GUI does not affect the value  */
-               val = ValueFromIntGroup(ig);
-               IntGroupRelease(ig);
+       if (!RTEST(igval)) {
+               /*  Use the exclusion table in MDArena  */
+               if (mol->par == NULL || mol->arena == NULL || mol->arena->is_initialized == 0 || mol->needsMDRebuild) {
+                       VALUE mval = ValueFromMolecule(mol);
+                       s_RebuildMDParameterIfNecessary(mval, Qnil);
+               }
+               exinfo = mol->arena->exinfo;  /*  May be NULL  */
+               exlist = mol->arena->exlist;    
        } else {
-               val = IntGroup_Alloc(rb_cIntGroup);
+               exinfo = NULL;
+               exlist = NULL;
        }
-       return val;
-}
-
-static VALUE
-s_Molecule_SetSelectionSub(VALUE self, VALUE val, int undoable)
-{
-    Molecule *mol;
-       IntGroup *ig;
-    Data_Get_Struct(self, Molecule, mol);
-       if (val == Qnil)
-               ig = NULL;
-       else
-               ig = s_Molecule_AtomGroupFromValue(self, val);
-       if (undoable)
-               MolActionCreateAndPerform(mol, gMolActionSetSelection, ig);
-       else
-               MoleculeSetSelection(mol, ig);
-       if (ig != NULL)
-               IntGroupRelease(ig);
-       return val;
-}
-
-/*
- *  call-seq:
- *     selection = IntGroup
- *
- *  Set the current selection. The right-hand operand may be nil.
- *  This operation is _not_ undoable. If you need undo, use set_undoable_selection instead.
- */
-static VALUE
-s_Molecule_SetSelection(VALUE self, VALUE val)
-{
-       return s_Molecule_SetSelectionSub(self, val, 0);
-}
-
-/*
- *  call-seq:
- *     set_undoable_selection(IntGroup)
- *
- *  Set the current selection with undo registration. The right-hand operand may be nil.
- *  This operation is undoable.
- */
-static VALUE
-s_Molecule_SetUndoableSelection(VALUE self, VALUE val)
-{
-       return s_Molecule_SetSelectionSub(self, val, 1);
+       IntGroupIteratorInit(ig1, &iter1);
+       IntGroupIteratorInit(ig2, &iter2);
+       npairs = 0;
+       pairs = NULL;
+       while ((n[0] = IntGroupIteratorNext(&iter1)) >= 0) {
+               Int exn1, exn2;
+               ap1 = ATOM_AT_INDEX(mol->atoms, n[0]);
+               r1 = ap1->r;
+               if (exinfo != NULL) {
+                       exn1 = exinfo[n[0]].index1;
+                       exn2 = exinfo[n[0] + 1].index1;
+               } else exn1 = exn2 = -1;
+               IntGroupIteratorReset(&iter2);
+               while ((n[1] = IntGroupIteratorNext(&iter2)) >= 0) {
+                       ap2 = ATOM_AT_INDEX(mol->atoms, n[1]);
+                       if (n[0] == n[1])
+                               continue;  /*  Same atom  */
+                       if (exinfo != NULL) {
+                               /*  Look up exclusion table to exclude 1-2, 1-3, and 1-4 pairs  */
+                               for (i = exn1; i < exn2; i++) {
+                                       if (exlist[i] == n[1])
+                                               break;
+                               }
+                               if (i < exn2)
+                                       continue;  /*  Should be excluded  */
+                       }
+                       if (MoleculeMeasureBond(mol, &r1, &(ap2->r)) < lim) {
+                               /*  Is this pair already registered?  */
+                               Int *ip;
+                               for (i = 0, ip = pairs; i < npairs; i++, ip += 2) {
+                                       if ((ip[0] == n[0] && ip[1] == n[1]) || (ip[0] == n[1] && ip[1] == n[0]))
+                                               break;
+                               }
+                               if (i >= npairs) {
+                                       /*  Not registered yet  */
+                                       AssignArray(&pairs, &npairs, sizeof(Int) * 2, npairs, n);
+                               }
+                       }
+               }
+       }
+       IntGroupIteratorRelease(&iter2);
+       IntGroupIteratorRelease(&iter1);
+       IntGroupRelease(ig2);
+       IntGroupRelease(ig1);
+       rval = rb_ary_new2(npairs);
+       if (pairs != NULL) {
+               for (i = 0; i < npairs; i++) {
+                       rb_ary_push(rval, rb_ary_new3(2, INT2NUM(pairs[i * 2]), INT2NUM(pairs[i * 2 + 1])));
+               }
+               free(pairs);
+       }
+       return rval;
 }
 
 /*
  *  call-seq:
- *     hidden_atoms       -> IntGroup
+ *     find_close_atoms(atom, limit = 1.2, radius = 0.77)   -> array of Integers (atom indices)
  *
- *  Returns the currently hidden atoms.
+ *  Find atoms that are within the threshold distance from the given atom.
+ *  (The atom argument can also be a vector, representing a cartesian coordinate. In that case, the van der Waals of the atom can also be specified.)
+ *  If limit is a positive number, the threshold distance is the sum of the vdw radii times limit.
+ *  If limit is a negative number, its absolute value is used for the threshold distance in angstrom.
+ *  If limit is not given, a default value of 1.2 is used.
+ *  An array of atom indices is returned. If no atoms are found, an empty array is returned.
  */
 static VALUE
-s_Molecule_HiddenAtoms(VALUE self)
+s_Molecule_FindCloseAtoms(int argc, VALUE *argv, VALUE self)
 {
-       rb_raise(rb_eMolbyError, "set_hidden_atoms is now obsolete. Try using Molecule#is_atom_visible or AtomRef#hidden.");
-       return Qnil;  /*  Not reached  */
-/*    Molecule *mol;
-       IntGroup *ig;
-       VALUE val;
+    Molecule *mol;
+       VALUE aval, limval, radval;
+       double limit, radius;
+       Int n1, nbonds, *bonds, an;
+       Vector v;
     Data_Get_Struct(self, Molecule, mol);
-       if (mol != NULL) {
-               Atom *ap;
-               int i;
-               ig = IntGroupNew();
-               for (i = 0, ap = mol->atoms; i < mol->natoms; i++, ap = ATOM_NEXT(ap)) {
-                       if (ap->exflags & kAtomHiddenFlag)
-                               IntGroupAdd(ig, i, 1);
-               }
-               val = ValueFromIntGroup(ig);
-               IntGroupRelease(ig);
-               rb_obj_freeze(val);
-               return val;
-       } else return Qnil; */
+       rb_scan_args(argc, argv, "12", &aval, &limval, &radval);
+       if (rb_obj_is_kind_of(aval, rb_cVector3D) || rb_obj_is_kind_of(aval, rb_cLAMatrix) || rb_obj_is_kind_of(aval, rb_mEnumerable)) {
+               VectorFromValue(aval, &v);
+               if (radval == Qnil)
+                       radius = gElementParameters[6].radius;
+               else
+                       radius = NUM2DBL(rb_Float(radval));
+               n1 = mol->natoms;
+       } else {
+               n1 = s_Molecule_AtomIndexFromValue(mol, aval);
+               v = ATOM_AT_INDEX(mol->atoms, n1)->r;
+               an = ATOM_AT_INDEX(mol->atoms, n1)->atomicNumber;
+               if (an >= 0 && an < gCountElementParameters)
+                       radius = gElementParameters[an].radius;
+               else radius = gElementParameters[6].radius;
+       }
+       if (limval == Qnil)
+               limit = 1.2;
+       else
+               limit = NUM2DBL(rb_Float(limval));
+       nbonds = 0;  /*  This initialization is necessary: see comments in MoleculeFindCloseAtoms()  */
+       bonds = NULL;
+       MoleculeFindCloseAtoms(mol, &v, radius, limit, &nbonds, &bonds, n1);
+       aval = rb_ary_new();
+       if (nbonds > 0) {
+               for (n1 = 0; n1 < nbonds; n1++)
+                       rb_ary_push(aval, INT2NUM(bonds[n1 * 2 + 1]));
+               free(bonds);
+       }
+       return aval;
 }
 
 /*
  *  call-seq:
- *     set_hidden_atoms(IntGroup)
- *     self.hidden_atoms = IntGroup
+ *     guess_bonds(limit = 1.2)       -> Integer
  *
- *  Hide the specified atoms. This operation is _not_ undoable.
+ *  Create bonds between atoms that are within the threshold distance.
+ *  If limit is a positive number, the threshold distance is the sum of the vdw radii times limit.
+ *  If limit is a negative number, its absolute value is used for the threshold distance in angstrom.
+ *  If limit is not given, a default value of 1.2 is used.
+ *  The number of the newly created bonds is returned.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_SetHiddenAtoms(VALUE self, VALUE val)
+s_Molecule_GuessBonds(int argc, VALUE *argv, VALUE self)
 {
-       rb_raise(rb_eMolbyError, "set_hidden_atoms is now obsolete. Try using Molecule#is_atom_visible or AtomRef#hidden.");
-       return Qnil;  /*  Not reached  */
-/*
-       Molecule *mol;
+    Molecule *mol;
+       VALUE limval;
+       double limit;
+       Int nbonds, *bonds;
     Data_Get_Struct(self, Molecule, mol);
-       if (mol != NULL) {
-               Atom *ap;
-               int i;
-               IntGroup *ig;
-               if (val == Qnil)
-                       ig = NULL;
-               else
-                       ig = s_Molecule_AtomGroupFromValue(self, val);
-               for (i = 0, ap = mol->atoms; i < mol->natoms; i++, ap = ATOM_NEXT(ap)) {
-                       if (ig != NULL && IntGroupLookup(ig, i, NULL)) {
-                               ap->exflags |= kAtomHiddenFlag;
-                       } else {
-                               ap->exflags &= kAtomHiddenFlag;
-                       }
-               }
-               if (ig != NULL)
-                       IntGroupRelease(ig);
-               MoleculeCallback_notifyModification(mol, 0);
+       rb_scan_args(argc, argv, "01", &limval);
+       if (limval == Qnil)
+               limit = 1.2;
+       else
+               limit = NUM2DBL(rb_Float(limval));
+       MoleculeGuessBonds(mol, limit, &nbonds, &bonds);
+       if (nbonds > 0) {
+               MolActionCreateAndPerform(mol, gMolActionAddBonds, nbonds * 2, bonds, NULL);
+               free(bonds);
        }
-       return val; */
+       return INT2NUM(nbonds);
 }
 
+#pragma mark ------ Cell and Symmetry ------
+
 /*
  *  call-seq:
- *     select_frame(index)
- *     frame = index
+ *     cell     -> [a, b, c, alpha, beta, gamma [, sig_a, sig_b, sig_c, sig_alpha, sig_beta, sig_gamma]]
  *
- *  Select the specified frame. If successful, returns true, otherwise returns false.
+ *  Returns the unit cell parameters. If cell is not set, returns nil.
  */
 static VALUE
-s_Molecule_SelectFrame(VALUE self, VALUE val)
+s_Molecule_Cell(VALUE self)
 {
     Molecule *mol;
-       int ival = NUM2INT(val);
+       int i;
+       VALUE val;
     Data_Get_Struct(self, Molecule, mol);
-       ival = MoleculeSelectFrame(mol, ival, 1);
-       if (ival >= 0)
-               return Qtrue;
-       else return Qfalse;
+       if (mol->cell == NULL)
+               return Qnil;
+       val = rb_ary_new2(6);
+       for (i = 0; i < 6; i++)
+               rb_ary_push(val, rb_float_new(mol->cell->cell[i]));
+       if (mol->cell->has_sigma) {
+               for (i = 0; i < 6; i++) {
+                       rb_ary_push(val, rb_float_new(mol->cell->cellsigma[i]));
+               }
+       }
+       return val;
 }
 
 /*
  *  call-seq:
- *     frame -> Integer
+ *     cell = [a, b, c, alpha, beta, gamma [, sig_a, sig_b, sig_c, sig_alpha, sig_beta, sig_gamma]]
+ *     set_cell([a, b, c, alpha, beta, gamma[, sig_a, sig_b, sig_c, sig_alpha, sig_beta, sig_gamma]], convert_coord = nil)
  *
- *  Get the current frame.
+ *  Set the unit cell parameters. If the cell value is nil, then clear the current cell.
+ If the given argument has 12 or more members, then the second half of the parameters represents the sigma values.
+ This operation is undoable.
+ Convert_coord is a flag to specify that the coordinates should be transformed so that the fractional coordinates remain the same.
  */
 static VALUE
-s_Molecule_Frame(VALUE self)
+s_Molecule_SetCell(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
+       VALUE val, cval;
+       int i, convert_coord, n;
+       double d[12];
     Data_Get_Struct(self, Molecule, mol);
-       return INT2NUM(mol->cframe);
+       rb_scan_args(argc, argv, "11", &val, &cval);
+       if (val == Qnil) {
+               n = 0;
+       } else {
+               int len;
+               val = rb_ary_to_ary(val);
+               len = RARRAY_LEN(val);
+               if (len >= 12) {
+                       n = 12;
+               } else if (len >= 6) {
+                       n = 6;
+               } else rb_raise(rb_eMolbyError, "too few members for cell parameters (6 or 12 required)");
+               for (i = 0; i < n; i++)
+                       d[i] = NUM2DBL(rb_Float((RARRAY_PTR(val))[i]));
+       }
+       convert_coord = (RTEST(cval) ? 1 : 0);
+       MolActionCreateAndPerform(mol, gMolActionSetCell, n, d, convert_coord);
+       return val;
 }
 
 /*
  *  call-seq:
- *     nframes -> Integer
+ *     box -> [avec, bvec, cvec, origin, flags]
  *
- *  Get the number of frames.
+ *  Get the unit cell information in the form of a periodic bounding box.
+ *  Avec, bvec, cvec, origin are Vector3D objects, and flags is a 3-member array of 
+ *  Integers which define whether the system is periodic along the axis.
+ *  If no unit cell is defined, nil is returned.
  */
 static VALUE
-s_Molecule_Nframes(VALUE self)
+s_Molecule_Box(VALUE self)
 {
     Molecule *mol;
+       VALUE v[5], val;
     Data_Get_Struct(self, Molecule, mol);
-       return INT2NUM(MoleculeGetNumberOfFrames(mol));
+       if (mol == NULL || mol->cell == NULL)
+               return Qnil;
+       v[0] = ValueFromVector(&(mol->cell->axes[0]));
+       v[1] = ValueFromVector(&(mol->cell->axes[1]));
+       v[2] = ValueFromVector(&(mol->cell->axes[2]));
+       v[3] = ValueFromVector(&(mol->cell->origin));
+       v[4] = rb_ary_new3(3, INT2NUM(mol->cell->flags[0]), INT2NUM(mol->cell->flags[1]), INT2NUM(mol->cell->flags[2]));
+       val = rb_ary_new4(5, v);
+       return val;
 }
 
 /*
  *  call-seq:
- *     insert_frame(integer, coordinates = nil, cell_axes = nil) -> bool
- *     insert_frames(intGroup = nil, coordinates = nil, cell_axes = nil) -> bool
+ *     set_box(avec, bvec, cvec, origin = [0, 0, 0], flags = [1, 1, 1], convert_coordinates = nil)
+ *     set_box(d, origin = [0, 0, 0])
+ *     set_box
  *
- *  Insert new frames at the indices specified by the intGroup. If the first argument is
- *  an integer, a single new frame is inserted at that index. If the first argument is 
- *  nil, a new frame is inserted at the last. If non-nil coordinates is given, it
- *  should be an array of arrays of Vector3Ds, then those coordinates are set 
- *  to the new frame. Otherwise, the coordinates of current molecule are copied 
- *  to the new frame.
- *  Returns an intGroup representing the inserted frames if successful, nil if not.
+ *  Set the unit cell parameters. Avec, bvec, and cvec can be either a Vector3D or a number.
+ If it is a number, the x/y/z axis vector is multiplied with the given number and used
+ as the box vector.
+ Flags, if present, is a 3-member array of Integers defining whether the system is
+ periodic along the axis.
+ If convert_coordinates is true, then the coordinates are converted so that the fractional coordinates remain the same.
+ In the second form, an isotropic box with cell-length d is set.
+ In the third form, the existing box is cleared.
+ Note: the sigma of the cell parameters is not cleared unless the periodic box itself is cleared.
  */
 static VALUE
-s_Molecule_InsertFrames(int argc, VALUE *argv, VALUE self)
+s_Molecule_SetBox(VALUE self, VALUE aval)
 {
-       VALUE val, coords, cells;
     Molecule *mol;
-       IntGroup *ig;
-       int count, ival, i, j, len, len_c, len2, nframes;
-       VALUE *ptr, *ptr2;
-       Vector *vp, *vp2;
+       VALUE v[6];
+       static Vector ax[3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
+       Vector vv[3];
+       Vector origin = {0, 0, 0};
+       char flags[3];
+       Double d;
+       int i, convertCoordinates = 0;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "12", &val, &coords, &cells);
-       if (coords != Qnil) {
-               if (TYPE(coords) != T_ARRAY)
-                       rb_raise(rb_eTypeError, "the coordinates should be given as an array of Vector3D");
-               len = RARRAY_LEN(coords);
-       } else len = 0;
-       if (cells != Qnil) {
-               if (mol->cell == NULL)
-                       rb_raise(rb_eTypeError, "the unit cell is not defined but the cell axes are given");
-               if (TYPE(cells) != T_ARRAY)
-                       rb_raise(rb_eTypeError, "the cell axes should be given as an array of Vector3D");
-               len_c = RARRAY_LEN(cells);
-       } else len_c = 0;
-       count = (len > len_c ? len : len_c);  /*  May be zero; will be updated later  */
-       nframes = MoleculeGetNumberOfFrames(mol);
-       if (val == Qnil) {
-               ig = IntGroupNewWithPoints(nframes, (count > 0 ? count : 1), -1);
-               val = ValueFromIntGroup(ig);
-       } else {
-               ig = IntGroupFromValue(val);
-       }
-       count = IntGroupGetCount(ig);  /*  Count is updated here  */
-       vp = ALLOC_N(Vector, mol->natoms * count);
-       if (cells != Qnil)
-               vp2 = ALLOC_N(Vector, 4 * count);
-       else vp2 = NULL;
-       if (len > 0) {
-               if (len < count)
-                       rb_raise(rb_eMolbyError, "the coordinates should contain no less than %d arrays (for frames)", count);
-               ptr = RARRAY_PTR(coords);
-               for (i = 0; i < count; i++) {
-                       if (TYPE(ptr[i]) != T_ARRAY)
-                               rb_raise(rb_eTypeError, "the coordinate array contains non-array object at index %d", i);
-                       len2 = RARRAY_LEN(ptr[i]);
-                       if (len2 < mol->natoms)
-                               rb_raise(rb_eMolbyError, "the array at index %d contains less than %d elements", i, mol->natoms);
-                       ptr2 = RARRAY_PTR(ptr[i]);
-                       for (j = 0; j < mol->natoms; j++)
-                               VectorFromValue(ptr2[j], &vp[i * mol->natoms + j]);
-               }
+       if (aval == Qnil) {
+               MolActionCreateAndPerform(mol, gMolActionClearBox);
+               return self;
+       }
+       aval = rb_ary_to_ary(aval);
+       for (i = 0; i < 6; i++) {
+               if (i < RARRAY_LEN(aval))
+                       v[i] = (RARRAY_PTR(aval))[i];
+               else v[i] = Qnil;
+       }
+       if (v[0] == Qnil) {
+               MolActionCreateAndPerform(mol, gMolActionClearBox);
+               return self;
+       }
+       if ((v[1] == Qnil || v[2] == Qnil) && rb_obj_is_kind_of(v[0], rb_cNumeric)) {
+               d = NUM2DBL(rb_Float(v[0]));
+               for (i = 0; i < 3; i++)
+                       VecScale(vv[i], ax[i], d);
+               if (v[1] != Qnil)
+                       VectorFromValue(v[1], &origin);
+               flags[0] = flags[1] = flags[2] = 1;
        } else {
-               Atom *ap;
-               for (i = 0; i < count; i++) {
-                       for (j = 0, ap = mol->atoms; j < mol->natoms; j++, ap = ATOM_NEXT(ap)) {
-                               vp[i * mol->natoms + j] = ap->r;
+               for (i = 0; i < 3; i++) {
+                       if (v[i] == Qnil) {
+                               VecZero(vv[i]);
+                       } else if (rb_obj_is_kind_of(v[i], rb_cNumeric)) {
+                               d = NUM2DBL(rb_Float(v[i]));
+                               VecScale(vv[i], ax[i], d);
+                       } else {
+                               VectorFromValue(v[i], &vv[i]);
                        }
+                       flags[i] = (VecLength2(vv[i]) > 0.0);
                }
-       }
-       if (len_c > 0) {
-               if (len_c < count)
-                       rb_raise(rb_eMolbyError, "the cell vectors should contain no less than %d arrays (for frames)", count);
-               ptr = RARRAY_PTR(cells);
-               for (i = 0; i < count; i++) {
-                       if (TYPE(ptr[i]) != T_ARRAY)
-                               rb_raise(rb_eTypeError, "the cell parameter array contains non-array object at index %d", i);
-                       len2 = RARRAY_LEN(ptr[i]);
-                       if (len2 < 4)
-                               rb_raise(rb_eMolbyError, "the cell parameter should contain 4 vectors");
-                       ptr2 = RARRAY_PTR(ptr[i]);
-                       for (j = 0; j < 4; j++)
-                               VectorFromValue(ptr2[j], &vp2[i * 4 + j]);
+               if (v[3] != Qnil)
+                       VectorFromValue(v[3], &origin);
+               if (v[4] != Qnil) {
+                       for (i = 0; i < 3; i++) {
+                               VALUE val = Ruby_ObjectAtIndex(v[4], i);
+                               flags[i] = (NUM2INT(rb_Integer(val)) != 0);
+                       }
                }
+               if (RTEST(v[5]))
+                       convertCoordinates = 1;
        }
-       ival = MolActionCreateAndPerform(mol, gMolActionInsertFrames, ig, mol->natoms * count, vp, (vp2 != NULL ? 4 * count : 0), vp2);
-       IntGroupRelease(ig);
-       xfree(vp);
-       if (vp2 != NULL)
-               xfree(vp2);
-       return (ival >= 0 ? val : Qnil);
+       MolActionCreateAndPerform(mol, gMolActionSetBox, &(vv[0]), &(vv[1]), &(vv[2]), &origin, (flags[0] * 4 + flags[1] * 2 + flags[2]), convertCoordinates);
+       return self;
 }
 
 /*
  *  call-seq:
- *     create_frame(coordinates = nil) -> Integer
- *     create_frames(coordinates = nil) -> Integer
+ *     cell_periodicity -> [n1, n2, n3]
  *
- *  Same as molecule.insert_frames(nil, coordinates).
+ *  Get flags denoting whether the cell is periodic along the a/b/c axes. If the cell is not defined
+ *  nil is returned.
  */
 static VALUE
-s_Molecule_CreateFrames(int argc, VALUE *argv, VALUE self)
+s_Molecule_CellPeriodicity(VALUE self)
 {
-       VALUE vals[3];
-       rb_scan_args(argc, argv, "02", &vals[1], &vals[2]);
-       vals[0] = Qnil;
-       return s_Molecule_InsertFrames(3, vals, self);
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->cell == NULL)
+               return Qnil;
+       return rb_ary_new3(3, INT2FIX((int)mol->cell->flags[0]), INT2FIX((int)mol->cell->flags[1]), INT2FIX((int)mol->cell->flags[2]));
 }
 
 /*
  *  call-seq:
- *     remove_frames(IntGroup, wantCoordinates = false)
+ *     self.cell_periodicity = [n1, n2, n3] or Integer or nil
+ *     set_cell_periodicity = [n1, n2, n3] or Integer or nil
  *
- *  Remove the frames at group. If wantsCoordinates is false (default), returns true if successful
- *  and nil otherwise. If wantsCoordinates is true, an array of arrays of the coordinates in the
- *  removed frames is returned if operation is successful.
+ *  Set whether the cell is periodic along the a/b/c axes. If an integer is given as an argument,
+ *  its bits 2/1/0 (from the lowest) correspond to the a/b/c axes. Nil is equivalent to [0, 0, 0].
+ *  If cell is not defined, exception is raised.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_RemoveFrames(int argc, VALUE *argv, VALUE self)
+s_Molecule_SetCellPeriodicity(VALUE self, VALUE arg)
 {
-       VALUE val, flag;
-       VALUE retval;
     Molecule *mol;
-       IntGroup *ig;
-       int count;
+       Int flag;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "11", &val, &flag);
-       ig = IntGroupFromValue(val);
-       count = IntGroupGetCount(ig);
-       if (RTEST(flag)) {
-               /*  Create return value before removing frames  */
-               VALUE coords;
-               int i, j, n;
-               Atom *ap;
-               Vector v;
-               retval = rb_ary_new2(count);
-               for (i = 0; i < count; i++) {
-                       n = IntGroupGetNthPoint(ig, i);
-                       coords = rb_ary_new2(mol->natoms);
-                       for (j = 0, ap = mol->atoms; j < mol->natoms; j++, ap = ATOM_NEXT(ap)) {
-                               if (n < ap->nframes && n != mol->cframe)
-                                       v = ap->frames[n];
-                               else v = ap->r;
-                               rb_ary_push(coords, ValueFromVector(&v));
-                       }
-                       rb_ary_push(retval, coords);
+       if (mol->cell == NULL)
+               rb_raise(rb_eMolbyError, "periodic cell is not defined");
+       if (arg == Qnil)
+               flag = 0;
+       else if (rb_obj_is_kind_of(arg, rb_cNumeric))
+               flag = NUM2INT(rb_Integer(arg));
+       else {
+               Int i;
+               VALUE arg0;
+               arg = rb_ary_to_ary(arg);
+               flag = 0;
+               for (i = 0; i < 3 && i < RARRAY_LEN(arg); i++) {
+                       arg0 = RARRAY_PTR(arg)[i];
+                       if (arg0 != Qnil && arg0 != Qfalse && arg0 != INT2FIX(0))
+                               flag |= (1 << (2 - i));
                }
-       } else retval = Qtrue;
-       if (MolActionCreateAndPerform(mol, gMolActionRemoveFrames, ig) >= 0)
-               return retval;
-       else return Qnil;
+       }
+       MolActionCreateAndPerform(mol, gMolActionSetCellPeriodicity, flag);
+       return arg;
 }
 
 /*
  *  call-seq:
- *     each_frame {|n| ...}
+ *     cell_flexibility -> bool
  *
- *  Set the frame number from 0 to nframes-1 and execute the block. The block argument is
- *  the frame number. After completion, the original frame number is restored.
+ *  Returns the unit cell is flexible or not
  */
 static VALUE
-s_Molecule_EachFrame(VALUE self)
+s_Molecule_CellFlexibility(VALUE self)
 {
-       int i, cframe, nframes;
-    Molecule *mol;
-    Data_Get_Struct(self, Molecule, mol);
-       cframe = mol->cframe;
-       nframes = MoleculeGetNumberOfFrames(mol);
-       if (nframes > 0) {
-               for (i = 0; i < nframes; i++) {
-                       MoleculeSelectFrame(mol, i, 1);
-                       rb_yield(INT2NUM(i));
-               }
-               MoleculeSelectFrame(mol, cframe, 1);
-       }
-    return self;
+       rb_warn("cell_flexibility is obsolete (unit cell is always frame dependent)");
+       return Qtrue;
+       /*    Molecule *mol;
+        Data_Get_Struct(self, Molecule, mol);
+        if (mol->cell == NULL)
+        return Qfalse;
+        if (mol->useFlexibleCell)
+        return Qtrue;
+        else return Qfalse; */
 }
 
 /*
  *  call-seq:
- *     set_atom_attr(index, key, value)
+ *     self.cell_flexibility = bool
+ *     set_cell_flexibility(bool)
  *
- *  Set the atom attribute for the specified atom.
- *  This operation is undoable.
+ *  Change the unit cell is flexible or not
  */
 static VALUE
-s_Molecule_SetAtomAttr(VALUE self, VALUE idx, VALUE key, VALUE val)
+s_Molecule_SetCellFlexibility(VALUE self, VALUE arg)
 {
-       Molecule *mol;
-       VALUE aref, oldval;
-    Data_Get_Struct(self, Molecule, mol);
-       aref = ValueFromMoleculeAndIndex(mol, s_Molecule_AtomIndexFromValue(mol, idx));
-       oldval = s_AtomRef_GetAttr(aref, key);
-       if (val == Qundef)
-               return oldval;
-       s_AtomRef_SetAttr(aref, key, val);
-       return val;
+       rb_warn("set_cell_flexibility is obsolete (unit cell is always frame dependent)");
+       return self;
+       /*    Molecule *mol;
+        Data_Get_Struct(self, Molecule, mol);
+        MolActionCreateAndPerform(mol, gMolActionSetCellFlexibility, RTEST(arg) != 0);
+        return self; */
 }
 
 /*
  *  call-seq:
- *     get_atom_attr(index, key)
+ *     cell_transform -> Transform
  *
- *  Get the atom attribute for the specified atom.
+ *  Get the transform matrix that converts internal coordinates to cartesian coordinates.
+ *  If cell is not defined, nil is returned.
  */
 static VALUE
-s_Molecule_GetAtomAttr(VALUE self, VALUE idx, VALUE key)
+s_Molecule_CellTransform(VALUE self)
 {
-       return s_Molecule_SetAtomAttr(self, idx, key, Qundef);
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol == NULL || mol->cell == NULL)
+               return Qnil;
+       return ValueFromTransform(&(mol->cell->tr));
 }
 
 /*
  *  call-seq:
- *     get_coord_from_frame(index, group = nil)
+ *     symmetry -> Array of Transforms
+ *     symmetries -> Array of Transforms
  *
- *  Copy the coordinates from the indicated frame. If group is specified, only the specified atoms
- *  are modified. Third argument (cflag) is now obsolete (it used to specify whether the cell parameters are to be
- *  copied; now they are always copied)
+ *  Get the currently defined symmetry operations. If no symmetry operation is defined,
+ *  returns an empty array.
  */
 static VALUE
-s_Molecule_GetCoordFromFrame(int argc, VALUE *argv, VALUE self)
+s_Molecule_Symmetry(VALUE self)
 {
-       Molecule *mol;
-       VALUE ival, gval, cval;
-       Int index, i, j, n, nn;
-       IntGroup *ig;
-       IntGroupIterator iter;
-       Atom *ap;
-       Vector *vp;
+    Molecule *mol;
+       VALUE val;
+       int i;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "12", &ival, &gval, &cval);
-       if (argc == 3)
-               rb_warn("The 3rd argument to get_coord_from_frame() is now obsolete");
-       index = NUM2INT(rb_Integer(ival));
-       if (index < 0 || index >= (n = MoleculeGetNumberOfFrames(mol))) {
-               if (n == 0)
-                       rb_raise(rb_eMolbyError, "No frame is present");
-               else
-                       rb_raise(rb_eMolbyError, "Frame index (%d) out of range (should be 0..%d)", index, n - 1);
-       }
-       if (gval == Qnil) {
-               ig = IntGroupNewWithPoints(0, mol->natoms, -1);
-       } else {
-               ig = s_Molecule_AtomGroupFromValue(self, gval);
-       }
-       n = IntGroupGetCount(ig);
-       if (n > 0) {
-               vp = (Vector *)calloc(sizeof(Vector), n);
-               IntGroupIteratorInit(ig, &iter);
-               j = 0;
-               nn = 0;
-               while ((i = IntGroupIteratorNext(&iter)) >= 0) {
-                       ap = ATOM_AT_INDEX(mol->atoms, i);
-                       if (index < ap->nframes) {
-                               vp[j] = ap->frames[index];
-                               nn++;
-                       } else {
-                               vp[j] = ap->r;
-                       }
-                       j++;
-               }
-               if (nn > 0)
-                       MolActionCreateAndPerform(mol, gMolActionSetAtomPositions, ig, n, vp);
-               free(vp);
-               if (mol->cell != NULL && mol->frame_cells != NULL && index < mol->nframe_cells) {
-                       vp = mol->frame_cells + index * 4;
-                       MolActionCreateAndPerform(mol, gMolActionSetBox, vp, vp + 1, vp + 2, vp + 3, -1, 0);
-               }
-               IntGroupIteratorRelease(&iter);
+       if (mol->nsyms <= 0)
+               return rb_ary_new();
+       val = rb_ary_new2(mol->nsyms);
+       for (i = 0; i < mol->nsyms; i++) {
+               rb_ary_push(val, ValueFromTransform(&mol->syms[i]));
        }
-       IntGroupRelease(ig);
-       return self;
+       return val;
 }
 
 /*
  *  call-seq:
- *     fragment(n1, *exatoms)  -> IntGroup
- *     fragment(group, *exatoms)  -> IntGroup
+ *     nsymmetries -> Integer
  *
- *  Get the fragment including the atom n1 or the atom group. If additional arguments are given,
- *  those atoms will not be counted during the search.
+ *  Get the number of currently defined symmetry operations.
  */
 static VALUE
-s_Molecule_Fragment(int argc, VALUE *argv, VALUE self)
+s_Molecule_Nsymmetries(VALUE self)
 {
     Molecule *mol;
-       IntGroup *baseg, *ig, *exatoms;
-       int n;
-       volatile VALUE nval, exval;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "1*", &nval, &exval);
-       if (rb_obj_is_kind_of(nval, rb_cNumeric) || rb_obj_is_kind_of(nval, rb_cString)) {
-               baseg = NULL;
-               n = NUM2INT(s_Molecule_AtomIndex(self, nval));
-       } else {
-               baseg = s_Molecule_AtomGroupFromValue(self, nval);
-       }
-       if (RARRAY_LEN(exval) == 0) {
-               exatoms = NULL;
-       } else {
-               exval = s_Molecule_AtomGroup(RARRAY_LEN(exval), RARRAY_PTR(exval), self);
-               Data_Get_Struct(exval, IntGroup, exatoms);
-       }
-       if (baseg == NULL) {
-               ig = MoleculeFragmentExcludingAtomGroup(mol, n, exatoms);
-       } else {
-               IntGroupIterator iter;
-               IntGroupIteratorInit(baseg, &iter);
-               if ((n = IntGroupIteratorNext(&iter)) < 0) {
-                       ig = IntGroupNew();
-               } else {
-                       ig = MoleculeFragmentExcludingAtomGroup(mol, n, exatoms);
-                       if (ig != NULL) {
-                               while ((n = IntGroupIteratorNext(&iter)) >= 0) {
-                                       IntGroup *subg;
-                                       subg = MoleculeFragmentExcludingAtomGroup(mol, n, exatoms);
-                                       if (subg != NULL) {
-                                               IntGroupAddIntGroup(ig, subg);
-                                               IntGroupRelease(subg);
-                                       }
-                               }
-                       }
-               }
-               IntGroupIteratorRelease(&iter);
-               IntGroupRelease(baseg);
-       }
-       if (ig == NULL)
-               rb_raise(rb_eMolbyError, "invalid specification of molecular fragment");
-       nval = ValueFromIntGroup(ig);
-       IntGroupRelease(ig);
-       return nval;
+       return INT2NUM(mol->nsyms);
 }
 
 /*
  *  call-seq:
- *     fragments(exclude = nil)
+ *     add_symmetry(Transform) -> Integer
  *
- *  Returns the fragments as an array of IntGroups. 
- *  If exclude is given (as an array or an IntGroup), then those atoms are excluded
- *  in defining the fragment.
+ *  Add a new symmetry operation. If no symmetry operation is defined and the
+ *  given argument is not an identity transform, then also add an identity
+ *  transform at the index 0.
+ *  Returns the total number of symmetries after operation.
  */
 static VALUE
-s_Molecule_Fragments(int argc, VALUE *argv, VALUE self)
+s_Molecule_AddSymmetry(VALUE self, VALUE trans)
 {
     Molecule *mol;
-       IntGroup *ag, *fg, *eg;
-       VALUE gval, exval, retval;
+       Transform tr;
     Data_Get_Struct(self, Molecule, mol);
-       if (mol == NULL)
-               return Qnil;
-       if (mol->natoms == 0)
-               return rb_ary_new();
-       rb_scan_args(argc, argv, "01", &exval);
-       if (exval == Qnil)
-               eg = NULL;
-       else
-               eg = IntGroupFromValue(exval);
-       ag = IntGroupNewWithPoints(0, mol->natoms, -1);
-       if (eg != NULL)
-               IntGroupRemoveIntGroup(ag, eg);
-       retval = rb_ary_new();
-       while (IntGroupGetCount(ag) > 0) {
-               int n = IntGroupGetNthPoint(ag, 0);
-               fg = MoleculeFragmentExcludingAtomGroup(mol, n, eg);
-               if (fg == NULL)
-                       rb_raise(rb_eMolbyError, "internal error during each_fragment");
-               gval = ValueFromIntGroup(fg);
-               rb_ary_push(retval, gval);
-               IntGroupRemoveIntGroup(ag, fg);
-               IntGroupRelease(fg);
-       }
-       IntGroupRelease(ag);
-       if (eg != NULL)
-               IntGroupRelease(eg);
-       return retval;
+       TransformFromValue(trans, &tr);
+       MolActionCreateAndPerform(mol, gMolActionAddSymmetryOperation, &tr);
+       return INT2NUM(mol->nsyms);
 }
 
 /*
  *  call-seq:
- *     each_fragment(exclude = nil) {|group| ...}
+ *     remove_symmetry(count = nil) -> Integer
+ *     remove_symmetries(count = nil) -> Integer
  *
- *  Execute the block, with the IntGroup object for each fragment as the argument.
- *  Atoms or bonds should not be added or removed during the execution of the block.
- *  If exclude is given (as an array or an IntGroup), then those atoms are excluded
- *  in defining the fragment.
+ *  Remove the specified number of symmetry operations. The last added ones are removed
+ *  first. If count is nil, then all symmetry operations are removed. Returns the
+ *  number of leftover symmetries.
  */
 static VALUE
-s_Molecule_EachFragment(int argc, VALUE *argv, VALUE self)
+s_Molecule_RemoveSymmetry(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       IntGroup *ag, *fg, *eg;
-       VALUE gval, exval;
+       VALUE cval;
+       int i, n;
     Data_Get_Struct(self, Molecule, mol);
-       if (mol == NULL || mol->natoms == 0)
-               return self;
-       rb_scan_args(argc, argv, "01", &exval);
-       if (exval == Qnil)
-               eg = NULL;
-       else
-               eg = IntGroupFromValue(exval);
-       ag = IntGroupNewWithPoints(0, mol->natoms, -1);
-       if (eg != NULL)
-               IntGroupRemoveIntGroup(ag, eg);
-       while (IntGroupGetCount(ag) > 0) {
-               int n = IntGroupGetNthPoint(ag, 0);
-               fg = MoleculeFragmentExcludingAtomGroup(mol, n, eg);
-               if (fg == NULL)
-                       rb_raise(rb_eMolbyError, "internal error during each_fragment");
-               gval = ValueFromIntGroup(fg);
-               rb_yield(gval);
-               IntGroupRemoveIntGroup(ag, fg);
-               IntGroupRelease(fg);
+       rb_scan_args(argc, argv, "01", &cval);
+       if (cval == Qnil)
+               n = mol->nsyms - 1;
+       else {
+               n = NUM2INT(rb_Integer(cval));
+               if (n < 0 || n > mol->nsyms)
+                       rb_raise(rb_eMolbyError, "the given count of symops is out of range");
+               if (n == mol->nsyms)
+                       n = mol->nsyms - 1;
        }
-       IntGroupRelease(ag);
-       if (eg != NULL)
-               IntGroupRelease(eg);
-       return self;
+       for (i = 0; i < n; i++)
+               MolActionCreateAndPerform(mol, gMolActionDeleteSymmetryOperation);
+       return INT2NUM(mol->nsyms);
 }
 
 /*
  *  call-seq:
- *     detachable?(group)  -> [n1, n2]
+ *     wrap_unit_cell(group) -> Vector3D
  *
- *  Check whether the group is 'detachable', i.e. the group is bound to the rest 
- *  of the molecule via only one bond. If it is, then the indices of the atoms
- *  belonging to the bond is returned, the first element being the atom included
- *  in the fragment. Otherwise, Qnil is returned.
+ *  Move the specified group so that the center of mass of the group is within the
+ *  unit cell. The offset vector is returned. If no periodic box is defined, 
+ *  exception is raised.
  */
 static VALUE
-s_Molecule_Detachable_P(VALUE self, VALUE gval)
+s_Molecule_WrapUnitCell(VALUE self, VALUE gval)
 {
-       Molecule *mol;
+    Molecule *mol;
        IntGroup *ig;
-       int n1, n2;
-       VALUE retval;
+       Vector v, cv, dv;
     Data_Get_Struct(self, Molecule, mol);
+       if (mol->cell == NULL)
+               rb_raise(rb_eMolbyError, "no unit cell is defined");
        ig = s_Molecule_AtomGroupFromValue(self, gval);
-       if (MoleculeIsFragmentDetachable(mol, ig, &n1, &n2)) {
-               retval = rb_ary_new3(2, INT2NUM(n1), INT2NUM(n2));
-       } else retval = Qnil;
+       s_Molecule_DoCenterOfMass(mol, &cv, ig);
+       TransformVec(&v, mol->cell->rtr, &cv);
+       if (mol->cell->flags[0])
+               v.x -= floor(v.x);
+       if (mol->cell->flags[1])
+               v.y -= floor(v.y);
+       if (mol->cell->flags[2])
+               v.z -= floor(v.z);
+       TransformVec(&dv, mol->cell->tr, &v);
+       VecDec(dv, cv);
+       MolActionCreateAndPerform(mol, gMolActionTranslateAtoms, &dv, ig);
        IntGroupRelease(ig);
-       return retval;
+       return ValueFromVector(&dv);
 }
 
 /*
  *  call-seq:
- *     bonds_on_border(group = selection)  -> Array of Array of two Integers
+ *     expand_by_symmetry(group, sym, dx=0, dy=0, dz=0, allow_overlap = false) -> Array
  *
- *  Returns an array of bonds that connect an atom in the group and an atom out
- *  of the group. The first atom in the bond always belongs to the group. If no
- *  such bonds are present, an empty array is returned.
+ *  Expand the specified part of the molecule by the given symmetry operation.
+ *  Returns the array of atom indices corresponding to the expanded atoms.
+ *  If allow_overlap is true, then new atoms are created even when the
+ *  coordinates coincide with the some other atom (special position) of the
+ *  same element; otherwise, such atom will not be created and the index of the
+ *  existing atom is given in the returned array.
  */
 static VALUE
-s_Molecule_BondsOnBorder(int argc, VALUE *argv, VALUE self)
+s_Molecule_ExpandBySymmetry(int argc, VALUE *argv, VALUE self)
 {
-       Molecule *mol;
-       IntGroup *ig, *bg;
-       VALUE gval, retval;
+    Molecule *mol;
+       VALUE gval, sval, xval, yval, zval, rval, oval;
+       IntGroup *ig;
+       Int n[4], allow_overlap;
+       Int natoms;
+       Int nidx, *idx;
+       
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "01", &gval);
-       if (gval == Qnil) {
-               ig = MoleculeGetSelection(mol);
-               if (ig != NULL)
-                       IntGroupRetain(ig);
-       } else {
-               ig = s_Molecule_AtomGroupFromValue(self, gval);
-       }
-       retval = rb_ary_new();
-       if (ig == NULL)
-               return retval;
-       bg = MoleculeSearchBondsAcrossAtomGroup(mol, ig);
-       if (bg != NULL) {
-               IntGroupIterator iter;
-               Int i;
-               IntGroupIteratorInit(bg, &iter);
-               while ((i = IntGroupIteratorNext(&iter)) >= 0) {
-                       /*  The atoms at the border  */
-                       Int n1, n2;
-                       n1 = mol->bonds[i * 2];
-                       n2 = mol->bonds[i * 2 + 1];
-                       if (IntGroupLookupPoint(ig, n1) < 0) {
-                               int w = n1;
-                               n1 = n2;
-                               n2 = w;
-                               if (IntGroupLookupPoint(ig, n1) < 0)
-                                       continue;  /*  Actually this is an internal error  */
-                       }
-                       rb_ary_push(retval, rb_ary_new3(2, INT2NUM(n1), INT2NUM(n2)));
-               }
-               IntGroupIteratorRelease(&iter);
+       rb_scan_args(argc, argv, "24", &gval, &sval, &xval, &yval, &zval, &oval);
+       n[0] = NUM2INT(rb_Integer(sval));
+       n[1] = (xval == Qnil ? 0 : NUM2INT(rb_Integer(xval)));
+       n[2] = (yval == Qnil ? 0 : NUM2INT(rb_Integer(yval)));
+       n[3] = (zval == Qnil ? 0 : NUM2INT(rb_Integer(zval)));
+       allow_overlap = (RTEST(oval) ? 1 : 0);
+       ig = s_Molecule_AtomGroupFromValue(self, gval);
+       if (n[0] < 0 || (n[0] > 0 && n[0] >= mol->nsyms))
+               rb_raise(rb_eMolbyError, "symmetry index is out of bounds");
+       natoms = mol->natoms;
+       
+       MolActionCreateAndPerform(mol, gMolActionExpandBySymmetry, ig, n[1], n[2], n[3], n[0], allow_overlap, &nidx, &idx);
+       
+       rval = rb_ary_new2(nidx);
+       while (--nidx >= 0) {
+               rb_ary_store(rval, nidx, INT2NUM(idx[nidx]));
        }
-       IntGroupRelease(bg);
-       IntGroupRelease(ig);
-       return retval;
+       /*      if (natoms == mol->natoms)
+        rval = Qnil;
+        else {
+        rval = IntGroup_Alloc(rb_cIntGroup);
+        Data_Get_Struct(rval, IntGroup, ig);
+        IntGroup_RaiseIfError(IntGroupAdd(ig, natoms, mol->natoms - natoms));
+        } */
+       return rval;
+}
+
+/*
+ *  call-seq:
+ *     amend_by_symmetry(group = nil) -> IntGroup
+ *
+ *  Expand the specified part of the molecule by the given symmetry operation.
+ *  Returns an IntGroup containing the added atoms.
+ */
+static VALUE
+s_Molecule_AmendBySymmetry(int argc, VALUE *argv, VALUE self)
+{
+    Molecule *mol;
+       IntGroup *ig, *ig2;
+       VALUE rval, gval;
+    Data_Get_Struct(self, Molecule, mol);
+       rb_scan_args(argc, argv, "01", &gval);
+       if (gval != Qnil)
+               ig = s_Molecule_AtomGroupFromValue(self, gval);
+       else ig = NULL;
+       MolActionCreateAndPerform(mol, gMolActionAmendBySymmetry, ig, &ig2);
+       rval = ValueFromIntGroup(ig2);
+       IntGroupRelease(ig2);
+       return rval;
 }
 
+#pragma mark ------ Transforms ------
+
 /*
  *  call-seq:
  *     translate(vec, group = nil)       -> Molecule
@@ -7826,7 +7801,7 @@ s_Molecule_Translate(int argc, VALUE *argv, VALUE self)
        rb_scan_args(argc, argv, "11", &vec, &group);
        ig = (NIL_P(group) ? NULL : s_Molecule_AtomGroupFromValue(self, group));
        VectorFromValue(vec, &v);
-//     MoleculeTranslate(mol, &v, ig);
+       //      MoleculeTranslate(mol, &v, ig);
        MolActionCreateAndPerform(mol, gMolActionTranslateAtoms, &v, ig);
        if (ig != NULL)
                IntGroupRelease(ig);
@@ -7949,472 +7924,650 @@ s_Molecule_Transform(int argc, VALUE *argv, VALUE self)
        rb_scan_args(argc, argv, "11", &trans, &group);
        ig = (NIL_P(group) ? NULL : s_Molecule_AtomGroupFromValue(self, group));
        TransformFromValue(trans, &tr);
-/*     MoleculeTransform(mol, tr, ig); */
+       /*      MoleculeTransform(mol, tr, ig); */
        MolActionCreateAndPerform(mol, gMolActionTransformAtoms, &tr, ig);
        if (ig != NULL)
                IntGroupRelease(ig);
        return self;
 }
 
-static void
-s_Molecule_DoCenterOfMass(Molecule *mol, Vector *outv, IntGroup *ig)
+/*
+ *  call-seq:
+ *     transform_for_symop(symop, is_cartesian = nil) -> Transform
+ *
+ *  Get the transform corresponding to the symmetry operation. The symop can either be
+ *  an integer (index of symmetry operation) or [sym, dx, dy, dz].
+ *  If is_cartesian is true, the returned transform is for cartesian coordinates.
+ *  Otherwise, the returned transform is for fractional coordinates.
+ *  Raises exception when no cell or no transform are defined.
+ */
+static VALUE
+s_Molecule_TransformForSymop(int argc, VALUE *argv, VALUE self)
 {
-       switch (MoleculeCenterOfMass(mol, outv, ig)) {
-               case 2: rb_raise(rb_eMolbyError, "atom group is empty"); break;
-               case 3: rb_raise(rb_eMolbyError, "weight is zero --- atomic weights are not defined?"); break;
-               case 0: break;
-               default: rb_raise(rb_eMolbyError, "cannot calculate center of mass"); break;
+    Molecule *mol;
+       VALUE sval, fval;
+       Symop symop;
+       Transform tr;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->cell == NULL)
+               rb_raise(rb_eMolbyError, "no unit cell is defined");
+       if (mol->nsyms == 0)
+               rb_raise(rb_eMolbyError, "no symmetry operation is defined");
+       rb_scan_args(argc, argv, "11", &sval, &fval);
+       if (rb_obj_is_kind_of(sval, rb_cNumeric)) {
+               symop.sym = NUM2INT(rb_Integer(sval));
+               symop.dx = symop.dy = symop.dz = 0;
+       } else {
+               sval = rb_ary_to_ary(sval);
+               if (RARRAY_LEN(sval) < 4)
+                       rb_raise(rb_eMolbyError, "missing arguments as symop; at least four integers should be given");
+               symop.sym = NUM2INT(rb_Integer(RARRAY_PTR(sval)[0]));
+               symop.dx = NUM2INT(rb_Integer(RARRAY_PTR(sval)[1]));
+               symop.dy = NUM2INT(rb_Integer(RARRAY_PTR(sval)[2]));
+               symop.dz = NUM2INT(rb_Integer(RARRAY_PTR(sval)[3]));
        }
+       if (symop.sym >= mol->nsyms)
+               rb_raise(rb_eMolbyError, "index of symmetry operation (%d) is out of range", symop.sym);
+       MoleculeGetTransformForSymop(mol, symop, &tr, (RTEST(fval) != 0));
+       return ValueFromTransform(&tr);
 }
 
 /*
  *  call-seq:
- *     center_of_mass(group = nil)       -> Vector3D
+ *     symop_for_transform(transform, is_cartesian = nil) -> [sym, dx, dy, dz]
  *
- *  Calculate the center of mass for the given set of atoms. The argument
- *  group is null, then all atoms are considered.
+ *  Get the symmetry operation corresponding to the given transform.
+ *  If is_cartesian is true, the given transform is for cartesian coordinates.
+ *  Otherwise, the given transform is for fractional coordinates.
+ *  Raises exception when no cell or no transform are defined.
  */
 static VALUE
-s_Molecule_CenterOfMass(int argc, VALUE *argv, VALUE self)
+s_Molecule_SymopForTransform(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       VALUE group;
-       IntGroup *ig;
-       Vector v;
+       VALUE tval, fval;
+       Symop symop;
+       Transform tr;
+       int n;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "01", &group);
-       ig = (NIL_P(group) ? NULL : s_Molecule_AtomGroupFromValue(self, group));
-       s_Molecule_DoCenterOfMass(mol, &v, ig);
-       if (ig != NULL)
-               IntGroupRelease(ig);
-       return ValueFromVector(&v);
+       if (mol->cell == NULL)
+               rb_raise(rb_eMolbyError, "no unit cell is defined");
+       if (mol->nsyms == 0)
+               rb_raise(rb_eMolbyError, "no symmetry operation is defined");
+       rb_scan_args(argc, argv, "11", &tval, &fval);
+       TransformFromValue(tval, &tr);
+       n = MoleculeGetSymopForTransform(mol, tr, &symop, (RTEST(fval) != 0));
+       if (n == 0) {
+               return rb_ary_new3(4, INT2NUM(symop.sym), INT2NUM(symop.dx), INT2NUM(symop.dy), INT2NUM(symop.dz));
+       } else {
+               return Qnil;  /*  Not found  */
+       }
 }
 
+#pragma mark ------ Frames ------
+
 /*
  *  call-seq:
- *     centralize(group = nil)       -> self
+ *     select_frame(index)
+ *     frame = index
  *
- *  Translate the molecule so that the center of mass of the given group is located
- *  at (0, 0, 0). Equivalent to molecule.translate(molecule.center_of_mass(group) * -1).
+ *  Select the specified frame. If successful, returns true, otherwise returns false.
  */
 static VALUE
-s_Molecule_Centralize(int argc, VALUE *argv, VALUE self)
+s_Molecule_SelectFrame(VALUE self, VALUE val)
 {
     Molecule *mol;
-       VALUE group;
-       IntGroup *ig;
-       Vector v;
+       int ival = NUM2INT(val);
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "01", &group);
-       ig = (NIL_P(group) ? NULL : s_Molecule_AtomGroupFromValue(self, group));
-       s_Molecule_DoCenterOfMass(mol, &v, ig);
-       if (ig != NULL)
-               IntGroupRelease(ig);
-       v.x = -v.x;
-       v.y = -v.y;
-       v.z = -v.z;
-       MolActionCreateAndPerform(mol, gMolActionTranslateAtoms, &v, NULL);
-       return self;
+       ival = MoleculeSelectFrame(mol, ival, 1);
+       if (ival >= 0)
+               return Qtrue;
+       else return Qfalse;
 }
 
 /*
  *  call-seq:
- *     bounds(group = nil)       -> [min, max]
+ *     frame -> Integer
  *
- *  Calculate the boundary. The return value is an array of two Vector3D objects.
+ *  Get the current frame.
  */
 static VALUE
-s_Molecule_Bounds(int argc, VALUE *argv, VALUE self)
+s_Molecule_Frame(VALUE self)
 {
     Molecule *mol;
-       VALUE group;
+    Data_Get_Struct(self, Molecule, mol);
+       return INT2NUM(mol->cframe);
+}
+
+/*
+ *  call-seq:
+ *     nframes -> Integer
+ *
+ *  Get the number of frames.
+ */
+static VALUE
+s_Molecule_Nframes(VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       return INT2NUM(MoleculeGetNumberOfFrames(mol));
+}
+
+/*
+ *  call-seq:
+ *     insert_frame(integer, coordinates = nil, cell_axes = nil) -> bool
+ *     insert_frames(intGroup = nil, coordinates = nil, cell_axes = nil) -> bool
+ *
+ *  Insert new frames at the indices specified by the intGroup. If the first argument is
+ *  an integer, a single new frame is inserted at that index. If the first argument is 
+ *  nil, a new frame is inserted at the last. If non-nil coordinates is given, it
+ *  should be an array of arrays of Vector3Ds, then those coordinates are set 
+ *  to the new frame. Otherwise, the coordinates of current molecule are copied 
+ *  to the new frame.
+ *  Returns an intGroup representing the inserted frames if successful, nil if not.
+ */
+static VALUE
+s_Molecule_InsertFrames(int argc, VALUE *argv, VALUE self)
+{
+       VALUE val, coords, cells;
+    Molecule *mol;
        IntGroup *ig;
-       Vector vmin, vmax;
-       int n;
-       Atom *ap;
+       int count, ival, i, j, len, len_c, len2, nframes;
+       VALUE *ptr, *ptr2;
+       Vector *vp, *vp2;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "01", &group);
-       ig = (NIL_P(group) ? NULL : s_Molecule_AtomGroupFromValue(self, group));
-       if (ig != NULL && IntGroupGetCount(ig) == 0)
-               rb_raise(rb_eMolbyError, "atom group is empty");
-       vmin.x = vmin.y = vmin.z = 1e30;
-       vmax.x = vmax.y = vmax.z = -1e30;
-       for (n = 0, ap = mol->atoms; n < mol->natoms; n++, ap = ATOM_NEXT(ap)) {
-               Vector r;
-               if (ig != NULL && IntGroupLookup(ig, n, NULL) == 0)
-                       continue;
-               r = ap->r;
-               if (r.x < vmin.x)
-                       vmin.x = r.x;
-               if (r.y < vmin.y)
-                       vmin.y = r.y;
-               if (r.z < vmin.z)
-                       vmin.z = r.z;
-               if (r.x > vmax.x)
-                       vmax.x = r.x;
-               if (r.y > vmax.y)
-                       vmax.y = r.y;
-               if (r.z > vmax.z)
-                       vmax.z = r.z;
+       rb_scan_args(argc, argv, "12", &val, &coords, &cells);
+       if (coords != Qnil) {
+               if (TYPE(coords) != T_ARRAY)
+                       rb_raise(rb_eTypeError, "the coordinates should be given as an array of Vector3D");
+               len = RARRAY_LEN(coords);
+       } else len = 0;
+       if (cells != Qnil) {
+               if (mol->cell == NULL)
+                       rb_raise(rb_eTypeError, "the unit cell is not defined but the cell axes are given");
+               if (TYPE(cells) != T_ARRAY)
+                       rb_raise(rb_eTypeError, "the cell axes should be given as an array of Vector3D");
+               len_c = RARRAY_LEN(cells);
+       } else len_c = 0;
+       count = (len > len_c ? len : len_c);  /*  May be zero; will be updated later  */
+       nframes = MoleculeGetNumberOfFrames(mol);
+       if (val == Qnil) {
+               ig = IntGroupNewWithPoints(nframes, (count > 0 ? count : 1), -1);
+               val = ValueFromIntGroup(ig);
+       } else {
+               ig = IntGroupFromValue(val);
+       }
+       count = IntGroupGetCount(ig);  /*  Count is updated here  */
+       vp = ALLOC_N(Vector, mol->natoms * count);
+       if (cells != Qnil)
+               vp2 = ALLOC_N(Vector, 4 * count);
+       else vp2 = NULL;
+       if (len > 0) {
+               if (len < count)
+                       rb_raise(rb_eMolbyError, "the coordinates should contain no less than %d arrays (for frames)", count);
+               ptr = RARRAY_PTR(coords);
+               for (i = 0; i < count; i++) {
+                       if (TYPE(ptr[i]) != T_ARRAY)
+                               rb_raise(rb_eTypeError, "the coordinate array contains non-array object at index %d", i);
+                       len2 = RARRAY_LEN(ptr[i]);
+                       if (len2 < mol->natoms)
+                               rb_raise(rb_eMolbyError, "the array at index %d contains less than %d elements", i, mol->natoms);
+                       ptr2 = RARRAY_PTR(ptr[i]);
+                       for (j = 0; j < mol->natoms; j++)
+                               VectorFromValue(ptr2[j], &vp[i * mol->natoms + j]);
+               }
+       } else {
+               Atom *ap;
+               for (i = 0; i < count; i++) {
+                       for (j = 0, ap = mol->atoms; j < mol->natoms; j++, ap = ATOM_NEXT(ap)) {
+                               vp[i * mol->natoms + j] = ap->r;
+                       }
+               }
+       }
+       if (len_c > 0) {
+               if (len_c < count)
+                       rb_raise(rb_eMolbyError, "the cell vectors should contain no less than %d arrays (for frames)", count);
+               ptr = RARRAY_PTR(cells);
+               for (i = 0; i < count; i++) {
+                       if (TYPE(ptr[i]) != T_ARRAY)
+                               rb_raise(rb_eTypeError, "the cell parameter array contains non-array object at index %d", i);
+                       len2 = RARRAY_LEN(ptr[i]);
+                       if (len2 < 4)
+                               rb_raise(rb_eMolbyError, "the cell parameter should contain 4 vectors");
+                       ptr2 = RARRAY_PTR(ptr[i]);
+                       for (j = 0; j < 4; j++)
+                               VectorFromValue(ptr2[j], &vp2[i * 4 + j]);
+               }
        }
-       return rb_ary_new3(2, ValueFromVector(&vmin), ValueFromVector(&vmax));
+       ival = MolActionCreateAndPerform(mol, gMolActionInsertFrames, ig, mol->natoms * count, vp, (vp2 != NULL ? 4 * count : 0), vp2);
+       IntGroupRelease(ig);
+       xfree(vp);
+       if (vp2 != NULL)
+               xfree(vp2);
+       return (ival >= 0 ? val : Qnil);
 }
 
-/*  Get atom position or a vector  */
-static void
-s_Molecule_GetVectorFromArg(Molecule *mol, VALUE val, Vector *vp)
+/*
+ *  call-seq:
+ *     create_frame(coordinates = nil) -> Integer
+ *     create_frames(coordinates = nil) -> Integer
+ *
+ *  Same as molecule.insert_frames(nil, coordinates).
+ */
+static VALUE
+s_Molecule_CreateFrames(int argc, VALUE *argv, VALUE self)
 {
-       if (rb_obj_is_kind_of(val, rb_cInteger) || rb_obj_is_kind_of(val, rb_cString)) {
-               int n1 = s_Molecule_AtomIndexFromValue(mol, val);
-               *vp = ATOM_AT_INDEX(mol->atoms, n1)->r;
-       } else {
-               VectorFromValue(val, vp);
-       }
+       VALUE vals[3];
+       rb_scan_args(argc, argv, "02", &vals[1], &vals[2]);
+       vals[0] = Qnil;
+       return s_Molecule_InsertFrames(3, vals, self);
 }
 
 /*
  *  call-seq:
- *     measure_bond(n1, n2)       -> Float
+ *     remove_frames(IntGroup, wantCoordinates = false)
  *
- *  Calculate the bond length. The arguments can either be atom indices, the "residue:name" representation, 
- *  or Vector3D values.
- *  If the crystallographic cell is defined, then the internal coordinates are convereted to the cartesian.
+ *  Remove the frames at group. If wantsCoordinates is false (default), returns true if successful
+ *  and nil otherwise. If wantsCoordinates is true, an array of arrays of the coordinates in the
+ *  removed frames is returned if operation is successful.
  */
 static VALUE
-s_Molecule_MeasureBond(VALUE self, VALUE nval1, VALUE nval2)
+s_Molecule_RemoveFrames(int argc, VALUE *argv, VALUE self)
 {
+       VALUE val, flag;
+       VALUE retval;
     Molecule *mol;
-       Vector v1, v2;
+       IntGroup *ig;
+       int count;
     Data_Get_Struct(self, Molecule, mol);
-       s_Molecule_GetVectorFromArg(mol, nval1, &v1);
-       s_Molecule_GetVectorFromArg(mol, nval2, &v2);
-       return rb_float_new(MoleculeMeasureBond(mol, &v1, &v2));
+       rb_scan_args(argc, argv, "11", &val, &flag);
+       ig = IntGroupFromValue(val);
+       count = IntGroupGetCount(ig);
+       if (RTEST(flag)) {
+               /*  Create return value before removing frames  */
+               VALUE coords;
+               int i, j, n;
+               Atom *ap;
+               Vector v;
+               retval = rb_ary_new2(count);
+               for (i = 0; i < count; i++) {
+                       n = IntGroupGetNthPoint(ig, i);
+                       coords = rb_ary_new2(mol->natoms);
+                       for (j = 0, ap = mol->atoms; j < mol->natoms; j++, ap = ATOM_NEXT(ap)) {
+                               if (n < ap->nframes && n != mol->cframe)
+                                       v = ap->frames[n];
+                               else v = ap->r;
+                               rb_ary_push(coords, ValueFromVector(&v));
+                       }
+                       rb_ary_push(retval, coords);
+               }
+       } else retval = Qtrue;
+       if (MolActionCreateAndPerform(mol, gMolActionRemoveFrames, ig) >= 0)
+               return retval;
+       else return Qnil;
 }
 
 /*
  *  call-seq:
- *     measure_angle(n1, n2, n3)       -> Float
+ *     each_frame {|n| ...}
  *
- *  Calculate the bond angle. The arguments can either be atom indices, the "residue:name" representation, 
- *  or Vector3D values. The return value is in degree.
- *  If the crystallographic cell is defined, then the internal coordinates are convereted to the cartesian.
+ *  Set the frame number from 0 to nframes-1 and execute the block. The block argument is
+ *  the frame number. After completion, the original frame number is restored.
  */
 static VALUE
-s_Molecule_MeasureAngle(VALUE self, VALUE nval1, VALUE nval2, VALUE nval3)
+s_Molecule_EachFrame(VALUE self)
 {
+       int i, cframe, nframes;
     Molecule *mol;
-       Vector v1, v2, v3;
-       Double d;
     Data_Get_Struct(self, Molecule, mol);
-       s_Molecule_GetVectorFromArg(mol, nval1, &v1);
-       s_Molecule_GetVectorFromArg(mol, nval2, &v2);
-       s_Molecule_GetVectorFromArg(mol, nval3, &v3);   
-       d = MoleculeMeasureAngle(mol, &v1, &v2, &v3);
-       if (isnan(d))
-               return Qnil;  /*  Cannot define  */
-       else return rb_float_new(d);
+       cframe = mol->cframe;
+       nframes = MoleculeGetNumberOfFrames(mol);
+       if (nframes > 0) {
+               for (i = 0; i < nframes; i++) {
+                       MoleculeSelectFrame(mol, i, 1);
+                       rb_yield(INT2NUM(i));
+               }
+               MoleculeSelectFrame(mol, cframe, 1);
+       }
+    return self;
 }
 
 /*
  *  call-seq:
- *     measure_dihedral(n1, n2, n3, n4)       -> Float
+ *     get_coord_from_frame(index, group = nil)
  *
- *  Calculate the dihedral angle. The arguments can either be atom indices, the "residue:name" representation, 
- *  or Vector3D values. The return value is in degree.
- *  If the crystallographic cell is defined, then the internal coordinates are convereted to the cartesian.
+ *  Copy the coordinates from the indicated frame. If group is specified, only the specified atoms
+ *  are modified. Third argument (cflag) is now obsolete (it used to specify whether the cell parameters are to be
+ *  copied; now they are always copied)
  */
 static VALUE
-s_Molecule_MeasureDihedral(VALUE self, VALUE nval1, VALUE nval2, VALUE nval3, VALUE nval4)
+s_Molecule_GetCoordFromFrame(int argc, VALUE *argv, VALUE self)
 {
-    Molecule *mol;
-       Vector v1, v2, v3, v4;
-       Double d;
+       Molecule *mol;
+       VALUE ival, gval, cval;
+       Int index, i, j, n, nn;
+       IntGroup *ig;
+       IntGroupIterator iter;
+       Atom *ap;
+       Vector *vp;
     Data_Get_Struct(self, Molecule, mol);
-       s_Molecule_GetVectorFromArg(mol, nval1, &v1);
-       s_Molecule_GetVectorFromArg(mol, nval2, &v2);
-       s_Molecule_GetVectorFromArg(mol, nval3, &v3);   
-       s_Molecule_GetVectorFromArg(mol, nval4, &v4);   
-       d = MoleculeMeasureDihedral(mol, &v1, &v2, &v3, &v4);
-       if (isnan(d))
-               return Qnil;  /*  Cannot define  */
-       else return rb_float_new(d);
+       rb_scan_args(argc, argv, "12", &ival, &gval, &cval);
+       if (argc == 3)
+               rb_warn("The 3rd argument to get_coord_from_frame() is now obsolete");
+       index = NUM2INT(rb_Integer(ival));
+       if (index < 0 || index >= (n = MoleculeGetNumberOfFrames(mol))) {
+               if (n == 0)
+                       rb_raise(rb_eMolbyError, "No frame is present");
+               else
+                       rb_raise(rb_eMolbyError, "Frame index (%d) out of range (should be 0..%d)", index, n - 1);
+       }
+       if (gval == Qnil) {
+               ig = IntGroupNewWithPoints(0, mol->natoms, -1);
+       } else {
+               ig = s_Molecule_AtomGroupFromValue(self, gval);
+       }
+       n = IntGroupGetCount(ig);
+       if (n > 0) {
+               vp = (Vector *)calloc(sizeof(Vector), n);
+               IntGroupIteratorInit(ig, &iter);
+               j = 0;
+               nn = 0;
+               while ((i = IntGroupIteratorNext(&iter)) >= 0) {
+                       ap = ATOM_AT_INDEX(mol->atoms, i);
+                       if (index < ap->nframes) {
+                               vp[j] = ap->frames[index];
+                               nn++;
+                       } else {
+                               vp[j] = ap->r;
+                       }
+                       j++;
+               }
+               if (nn > 0)
+                       MolActionCreateAndPerform(mol, gMolActionSetAtomPositions, ig, n, vp);
+               free(vp);
+               if (mol->cell != NULL && mol->frame_cells != NULL && index < mol->nframe_cells) {
+                       vp = mol->frame_cells + index * 4;
+                       MolActionCreateAndPerform(mol, gMolActionSetBox, vp, vp + 1, vp + 2, vp + 3, -1, 0);
+               }
+               IntGroupIteratorRelease(&iter);
+       }
+       /*  Copy the extra properties  */
+       IntGroupRelease(ig);
+       for (i = 0; i < mol->nmolprops; i++) {
+               Double *dp = (Double *)malloc(sizeof(Double));
+               ig = IntGroupNew();
+               IntGroupAdd(ig, mol->cframe, 1);
+               *dp = mol->molprops[i].propvals[index];
+               MolActionCreateAndPerform(mol, gMolActionSetProperty, i, ig, 1, dp);
+               free(dp);
+               IntGroupRelease(ig);
+       }
+       
+       return self;
 }
 
 /*
  *  call-seq:
- *     expand_by_symmetry(group, sym, dx=0, dy=0, dz=0) -> Array
+ *     reorder_frames(old_indices)
  *
- *  Expand the specified part of the molecule by the given symmetry operation.
- *  Returns the array of atom indices corresponding to the expanded atoms.
+ *  Reorder the frames. The argument is an array of integers that specify the 'old' 
+ *  frame numbers. Thus, if the argument is [2,0,1], then the new frames 0/1/2 are the
+ *  same as the old frames 2/0/1, respectively.
+ *  The argument must have the same number of integers as the number of frames.
  */
 static VALUE
-s_Molecule_ExpandBySymmetry(int argc, VALUE *argv, VALUE self)
+s_Molecule_ReorderFrames(VALUE self, VALUE aval)
 {
-    Molecule *mol;
-       VALUE gval, sval, xval, yval, zval, rval;
-       IntGroup *ig;
-       Int n[4];
-       Int natoms;
-       Int nidx, *idx;
-
+       Molecule *mol;
+       Int *ip, *ip2, i, n, nframes;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "23", &gval, &sval, &xval, &yval, &zval);
-       n[0] = NUM2INT(rb_Integer(sval));
-       n[1] = (xval == Qnil ? 0 : NUM2INT(rb_Integer(xval)));
-       n[2] = (yval == Qnil ? 0 : NUM2INT(rb_Integer(yval)));
-       n[3] = (zval == Qnil ? 0 : NUM2INT(rb_Integer(zval)));
-       ig = s_Molecule_AtomGroupFromValue(self, gval);
-       if (n[0] < 0 || (n[0] > 0 && n[0] >= mol->nsyms))
-               rb_raise(rb_eMolbyError, "symmetry index is out of bounds");
-       natoms = mol->natoms;
-       
-       MolActionCreateAndPerform(mol, gMolActionExpandBySymmetry, ig, n[1], n[2], n[3], n[0], &nidx, &idx);
-
-       rval = rb_ary_new2(nidx);
-       while (--nidx >= 0) {
-               rb_ary_store(rval, nidx, INT2NUM(idx[nidx]));
+       aval = rb_ary_to_ary(aval);
+       nframes = MoleculeGetNumberOfFrames(mol);
+       if (RARRAY_LEN(aval) != nframes)
+               rb_raise(rb_eMolbyError, "The argument must have the same number of integers as the number of frames");
+       ip2 = (Int *)calloc(sizeof(Int), nframes);
+       ip = (Int *)calloc(sizeof(Int), nframes);
+       for (i = 0; i < nframes; i++) {
+               n = NUM2INT(rb_Integer(RARRAY_PTR(aval)[i]));
+               if (n < 0 || n >= nframes || ip2[n] != 0) {
+                       free(ip2);
+                       free(ip);
+                       if (n < 0 || n >= nframes)
+                               rb_raise(rb_eMolbyError, "The argument (%d) is out of range", n);
+                       else
+                               rb_raise(rb_eMolbyError, "The argument has duplicated entry (%d)", n);
+               }
+               ip2[n] = 1;
+               ip[i] = n;
        }
-/*     if (natoms == mol->natoms)
-               rval = Qnil;
-       else {
-               rval = IntGroup_Alloc(rb_cIntGroup);
-               Data_Get_Struct(rval, IntGroup, ig);
-               IntGroup_RaiseIfError(IntGroupAdd(ig, natoms, mol->natoms - natoms));
-       } */
-       return rval;
+       free(ip2);
+       MolActionCreateAndPerform(mol, gMolActionReorderFrames, nframes, ip);
+       free(ip);
+       return self;
 }
 
+#pragma mark ------ Fragments ------
+
 /*
  *  call-seq:
- *     amend_by_symmetry(group = nil) -> IntGroup
+ *     fragment(n1, *exatoms)  -> IntGroup
+ *     fragment(group, *exatoms)  -> IntGroup
  *
- *  Expand the specified part of the molecule by the given symmetry operation.
- *  Returns an IntGroup containing the added atoms.
+ *  Get the fragment including the atom n1 or the atom group. If additional arguments are given,
+ *  those atoms will not be counted during the search.
  */
 static VALUE
-s_Molecule_AmendBySymmetry(int argc, VALUE *argv, VALUE self)
+s_Molecule_Fragment(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       IntGroup *ig, *ig2;
-       VALUE rval, gval;
+       IntGroup *baseg, *ig, *exatoms;
+       int n;
+       volatile VALUE nval, exval;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "01", &gval);
-       if (gval != Qnil)
-               ig = s_Molecule_AtomGroupFromValue(self, gval);
-       else ig = NULL;
-       MolActionCreateAndPerform(mol, gMolActionAmendBySymmetry, ig, &ig2);
-       rval = ValueFromIntGroup(ig2);
-       IntGroupRelease(ig2);
-       return rval;
+       rb_scan_args(argc, argv, "1*", &nval, &exval);
+       if (rb_obj_is_kind_of(nval, rb_cNumeric) || rb_obj_is_kind_of(nval, rb_cString)) {
+               baseg = NULL;
+               n = NUM2INT(s_Molecule_AtomIndex(self, nval));
+       } else {
+               baseg = s_Molecule_AtomGroupFromValue(self, nval);
+       }
+       if (RARRAY_LEN(exval) == 0) {
+               exatoms = NULL;
+       } else {
+               exval = s_Molecule_AtomGroup(RARRAY_LEN(exval), RARRAY_PTR(exval), self);
+               Data_Get_Struct(exval, IntGroup, exatoms);
+       }
+       if (baseg == NULL) {
+               ig = MoleculeFragmentExcludingAtomGroup(mol, n, exatoms);
+       } else {
+               IntGroupIterator iter;
+               IntGroupIteratorInit(baseg, &iter);
+               if ((n = IntGroupIteratorNext(&iter)) < 0) {
+                       ig = IntGroupNew();
+               } else {
+                       ig = MoleculeFragmentExcludingAtomGroup(mol, n, exatoms);
+                       if (ig != NULL) {
+                               while ((n = IntGroupIteratorNext(&iter)) >= 0) {
+                                       IntGroup *subg;
+                                       subg = MoleculeFragmentExcludingAtomGroup(mol, n, exatoms);
+                                       if (subg != NULL) {
+                                               IntGroupAddIntGroup(ig, subg);
+                                               IntGroupRelease(subg);
+                                       }
+                               }
+                       }
+               }
+               IntGroupIteratorRelease(&iter);
+               IntGroupRelease(baseg);
+       }
+       if (ig == NULL)
+               rb_raise(rb_eMolbyError, "invalid specification of molecular fragment");
+       nval = ValueFromIntGroup(ig);
+       IntGroupRelease(ig);
+       return nval;
 }
 
 /*
  *  call-seq:
- *     transform_for_symop(symop, is_cartesian = nil) -> Transform
+ *     fragments(exclude = nil)
  *
- *  Get the transform corresponding to the symmetry operation. The symop can either be
- *  an integer (index of symmetry operation) or [sym, dx, dy, dz].
- *  If is_cartesian is true, the returned transform is for cartesian coordinates.
- *  Otherwise, the returned transform is for fractional coordinates.
- *  Raises exception when no cell or no transform are defined.
+ *  Returns the fragments as an array of IntGroups. 
+ *  If exclude is given (as an array or an IntGroup), then those atoms are excluded
+ *  in defining the fragment.
  */
 static VALUE
-s_Molecule_TransformForSymop(int argc, VALUE *argv, VALUE self)
+s_Molecule_Fragments(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       VALUE sval, fval;
-       Symop symop;
-       Transform tr;
+       IntGroup *ag, *fg, *eg;
+       VALUE gval, exval, retval;
     Data_Get_Struct(self, Molecule, mol);
-       if (mol->cell == NULL)
-               rb_raise(rb_eMolbyError, "no unit cell is defined");
-       if (mol->nsyms == 0)
-               rb_raise(rb_eMolbyError, "no symmetry operation is defined");
-       rb_scan_args(argc, argv, "11", &sval, &fval);
-       if (rb_obj_is_kind_of(sval, rb_cNumeric)) {
-               symop.sym = NUM2INT(rb_Integer(sval));
-               symop.dx = symop.dy = symop.dz = 0;
-       } else {
-               sval = rb_ary_to_ary(sval);
-               if (RARRAY_LEN(sval) < 4)
-                       rb_raise(rb_eMolbyError, "missing arguments as symop; at least four integers should be given");
-               symop.sym = NUM2INT(rb_Integer(RARRAY_PTR(sval)[0]));
-               symop.dx = NUM2INT(rb_Integer(RARRAY_PTR(sval)[1]));
-               symop.dy = NUM2INT(rb_Integer(RARRAY_PTR(sval)[2]));
-               symop.dz = NUM2INT(rb_Integer(RARRAY_PTR(sval)[3]));
+       if (mol == NULL)
+               return Qnil;
+       if (mol->natoms == 0)
+               return rb_ary_new();
+       rb_scan_args(argc, argv, "01", &exval);
+       if (exval == Qnil)
+               eg = NULL;
+       else
+               eg = IntGroupFromValue(exval);
+       ag = IntGroupNewWithPoints(0, mol->natoms, -1);
+       if (eg != NULL)
+               IntGroupRemoveIntGroup(ag, eg);
+       retval = rb_ary_new();
+       while (IntGroupGetCount(ag) > 0) {
+               int n = IntGroupGetNthPoint(ag, 0);
+               fg = MoleculeFragmentExcludingAtomGroup(mol, n, eg);
+               if (fg == NULL)
+                       rb_raise(rb_eMolbyError, "internal error during each_fragment");
+               gval = ValueFromIntGroup(fg);
+               rb_ary_push(retval, gval);
+               IntGroupRemoveIntGroup(ag, fg);
+               IntGroupRelease(fg);
        }
-       if (symop.sym >= mol->nsyms)
-               rb_raise(rb_eMolbyError, "index of symmetry operation (%d) is out of range", symop.sym);
-       MoleculeGetTransformForSymop(mol, symop, &tr, (RTEST(fval) != 0));
-       return ValueFromTransform(&tr);
+       IntGroupRelease(ag);
+       if (eg != NULL)
+               IntGroupRelease(eg);
+       return retval;
 }
-       
+
 /*
  *  call-seq:
- *     symop_for_transform(transform, is_cartesian = nil) -> [sym, dx, dy, dz]
+ *     each_fragment(exclude = nil) {|group| ...}
  *
- *  Get the symmetry operation corresponding to the given transform.
- *  If is_cartesian is true, the given transform is for cartesian coordinates.
- *  Otherwise, the given transform is for fractional coordinates.
- *  Raises exception when no cell or no transform are defined.
+ *  Execute the block, with the IntGroup object for each fragment as the argument.
+ *  Atoms or bonds should not be added or removed during the execution of the block.
+ *  If exclude is given (as an array or an IntGroup), then those atoms are excluded
+ *  in defining the fragment.
  */
 static VALUE
-s_Molecule_SymopForTransform(int argc, VALUE *argv, VALUE self)
+s_Molecule_EachFragment(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       VALUE tval, fval;
-       Symop symop;
-       Transform tr;
-       int n;
+       IntGroup *ag, *fg, *eg;
+       VALUE gval, exval;
     Data_Get_Struct(self, Molecule, mol);
-       if (mol->cell == NULL)
-               rb_raise(rb_eMolbyError, "no unit cell is defined");
-       if (mol->nsyms == 0)
-               rb_raise(rb_eMolbyError, "no symmetry operation is defined");
-       rb_scan_args(argc, argv, "11", &tval, &fval);
-       TransformFromValue(tval, &tr);
-       n = MoleculeGetSymopForTransform(mol, tr, &symop, (RTEST(fval) != 0));
-       if (n == 0) {
-               return rb_ary_new3(4, INT2NUM(symop.sym), INT2NUM(symop.dx), INT2NUM(symop.dy), INT2NUM(symop.dz));
-       } else {
-               return Qnil;  /*  Not found  */
+       if (mol == NULL || mol->natoms == 0)
+               return self;
+       rb_scan_args(argc, argv, "01", &exval);
+       if (exval == Qnil)
+               eg = NULL;
+       else
+               eg = IntGroupFromValue(exval);
+       ag = IntGroupNewWithPoints(0, mol->natoms, -1);
+       if (eg != NULL)
+               IntGroupRemoveIntGroup(ag, eg);
+       while (IntGroupGetCount(ag) > 0) {
+               int n = IntGroupGetNthPoint(ag, 0);
+               fg = MoleculeFragmentExcludingAtomGroup(mol, n, eg);
+               if (fg == NULL)
+                       rb_raise(rb_eMolbyError, "internal error during each_fragment");
+               gval = ValueFromIntGroup(fg);
+               rb_yield(gval);
+               IntGroupRemoveIntGroup(ag, fg);
+               IntGroupRelease(fg);
        }
+       IntGroupRelease(ag);
+       if (eg != NULL)
+               IntGroupRelease(eg);
+       return self;
 }
 
 /*
  *  call-seq:
- *     wrap_unit_cell(group) -> Vector3D
+ *     detachable?(group)  -> [n1, n2]
  *
- *  Move the specified group so that the center of mass of the group is within the
- *  unit cell. The offset vector is returned. If no periodic box is defined, 
- *  exception is raised.
+ *  Check whether the group is 'detachable', i.e. the group is bound to the rest 
+ *  of the molecule via only one bond. If it is, then the indices of the atoms
+ *  belonging to the bond is returned, the first element being the atom included
+ *  in the fragment. Otherwise, Qnil is returned.
  */
 static VALUE
-s_Molecule_WrapUnitCell(VALUE self, VALUE gval)
+s_Molecule_Detachable_P(VALUE self, VALUE gval)
 {
-    Molecule *mol;
+       Molecule *mol;
        IntGroup *ig;
-       Vector v, cv, dv;
+       int n1, n2;
+       VALUE retval;
     Data_Get_Struct(self, Molecule, mol);
-       if (mol->cell == NULL)
-               rb_raise(rb_eMolbyError, "no unit cell is defined");
        ig = s_Molecule_AtomGroupFromValue(self, gval);
-       s_Molecule_DoCenterOfMass(mol, &cv, ig);
-       TransformVec(&v, mol->cell->rtr, &cv);
-       if (mol->cell->flags[0])
-               v.x -= floor(v.x);
-       if (mol->cell->flags[1])
-               v.y -= floor(v.y);
-       if (mol->cell->flags[2])
-               v.z -= floor(v.z);
-       TransformVec(&dv, mol->cell->tr, &v);
-       VecDec(dv, cv);
-       MolActionCreateAndPerform(mol, gMolActionTranslateAtoms, &dv, ig);
+       if (MoleculeIsFragmentDetachable(mol, ig, &n1, &n2)) {
+               retval = rb_ary_new3(2, INT2NUM(n1), INT2NUM(n2));
+       } else retval = Qnil;
        IntGroupRelease(ig);
-       return ValueFromVector(&dv);
+       return retval;
 }
 
 /*
  *  call-seq:
- *     find_conflicts(limit[, group1[, group2 [, ignore_exclusion]]]) -> [[n1, n2], [n3, n4], ...]
- *
- *  Find pairs of atoms that are within the limit distance. If group1 and group2 are given, the
- *  first and second atom in the pair should belong to group1 and group2, respectively.
- *  If ignore_exclusion is true, then 1-2 (bonded), 1-3, 1-4 pairs are also considered.
- */
-static VALUE
-s_Molecule_FindConflicts(int argc, VALUE *argv, VALUE self)
-{
-    Molecule *mol;
-       VALUE limval, gval1, gval2, rval, igval;
-       IntGroup *ig1, *ig2;
-       IntGroupIterator iter1, iter2;
-       Int npairs, *pairs;
-       Int n[2], i;
-       Double lim;
-       Vector r1;
-       Atom *ap1, *ap2;
-       MDExclusion *exinfo;
-       Int *exlist;
-
-    Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "13", &limval, &gval1, &gval2, &igval);
-       lim = NUM2DBL(rb_Float(limval));
-       if (lim <= 0.0)
-               rb_raise(rb_eMolbyError, "the limit (%g) should be positive", lim);
-       if (gval1 != Qnil)
-               ig1 = s_Molecule_AtomGroupFromValue(self, gval1);
-       else
-               ig1 = IntGroupNewWithPoints(0, mol->natoms, -1);
-       if (gval2 != Qnil)
-               ig2 = s_Molecule_AtomGroupFromValue(self, gval2);
-       else
-               ig2 = IntGroupNewWithPoints(0, mol->natoms, -1);
-       
-       if (!RTEST(igval)) {
-               /*  Use the exclusion table in MDArena  */
-               if (mol->par == NULL || mol->arena == NULL || mol->arena->is_initialized == 0 || mol->needsMDRebuild) {
-                       VALUE mval = ValueFromMolecule(mol);
-                       s_RebuildMDParameterIfNecessary(mval, Qnil);
-               }
-               exinfo = mol->arena->exinfo;  /*  May be NULL  */
-               exlist = mol->arena->exlist;    
-       } else {
-               exinfo = NULL;
-               exlist = NULL;
-       }
-       IntGroupIteratorInit(ig1, &iter1);
-       IntGroupIteratorInit(ig2, &iter2);
-       npairs = 0;
-       pairs = NULL;
-       while ((n[0] = IntGroupIteratorNext(&iter1)) >= 0) {
-               Int exn1, exn2;
-               ap1 = ATOM_AT_INDEX(mol->atoms, n[0]);
-               r1 = ap1->r;
-               if (exinfo != NULL) {
-                       exn1 = exinfo[n[0]].index1;
-                       exn2 = exinfo[n[0] + 1].index1;
-               } else exn1 = exn2 = -1;
-               IntGroupIteratorReset(&iter2);
-               while ((n[1] = IntGroupIteratorNext(&iter2)) >= 0) {
-                       ap2 = ATOM_AT_INDEX(mol->atoms, n[1]);
-                       if (n[0] == n[1])
-                               continue;  /*  Same atom  */
-                       if (exinfo != NULL) {
-                               /*  Look up exclusion table to exclude 1-2, 1-3, and 1-4 pairs  */
-                               for (i = exn1; i < exn2; i++) {
-                                       if (exlist[i] == n[1])
-                                               break;
-                               }
-                               if (i < exn2)
-                                       continue;  /*  Should be excluded  */
-                       }
-                       if (MoleculeMeasureBond(mol, &r1, &(ap2->r)) < lim) {
-                               /*  Is this pair already registered?  */
-                               Int *ip;
-                               for (i = 0, ip = pairs; i < npairs; i++, ip += 2) {
-                                       if ((ip[0] == n[0] && ip[1] == n[1]) || (ip[0] == n[1] && ip[1] == n[0]))
-                                               break;
-                               }
-                               if (i >= npairs) {
-                                       /*  Not registered yet  */
-                                       AssignArray(&pairs, &npairs, sizeof(Int) * 2, npairs, n);
-                               }
-                       }
-               }
+ *     bonds_on_border(group = selection)  -> Array of Array of two Integers
+ *
+ *  Returns an array of bonds that connect an atom in the group and an atom out
+ *  of the group. The first atom in the bond always belongs to the group. If no
+ *  such bonds are present, an empty array is returned.
+ */
+static VALUE
+s_Molecule_BondsOnBorder(int argc, VALUE *argv, VALUE self)
+{
+       Molecule *mol;
+       IntGroup *ig, *bg;
+       VALUE gval, retval;
+    Data_Get_Struct(self, Molecule, mol);
+       rb_scan_args(argc, argv, "01", &gval);
+       if (gval == Qnil) {
+               ig = MoleculeGetSelection(mol);
+               if (ig != NULL)
+                       IntGroupRetain(ig);
+       } else {
+               ig = s_Molecule_AtomGroupFromValue(self, gval);
        }
-       IntGroupIteratorRelease(&iter2);
-       IntGroupIteratorRelease(&iter1);
-       IntGroupRelease(ig2);
-       IntGroupRelease(ig1);
-       rval = rb_ary_new2(npairs);
-       if (pairs != NULL) {
-               for (i = 0; i < npairs; i++) {
-                       rb_ary_push(rval, rb_ary_new3(2, INT2NUM(pairs[i * 2]), INT2NUM(pairs[i * 2 + 1])));
+       retval = rb_ary_new();
+       if (ig == NULL)
+               return retval;
+       bg = MoleculeSearchBondsAcrossAtomGroup(mol, ig);
+       if (bg != NULL) {
+               IntGroupIterator iter;
+               Int i;
+               IntGroupIteratorInit(bg, &iter);
+               while ((i = IntGroupIteratorNext(&iter)) >= 0) {
+                       /*  The atoms at the border  */
+                       Int n1, n2;
+                       n1 = mol->bonds[i * 2];
+                       n2 = mol->bonds[i * 2 + 1];
+                       if (IntGroupLookupPoint(ig, n1) < 0) {
+                               int w = n1;
+                               n1 = n2;
+                               n2 = w;
+                               if (IntGroupLookupPoint(ig, n1) < 0)
+                                       continue;  /*  Actually this is an internal error  */
+                       }
+                       rb_ary_push(retval, rb_ary_new3(2, INT2NUM(n1), INT2NUM(n2)));
                }
-               free(pairs);
+               IntGroupIteratorRelease(&iter);
        }
-       return rval;
+       IntGroupRelease(bg);
+       IntGroupRelease(ig);
+       return retval;
 }
 
 /*  Calculate the transform that moves the current coordinates to the reference
@@ -8555,7 +8708,7 @@ s_Molecule_FitCoordinates(int argc, VALUE *argv, VALUE self)
        VALUE gval, rval, wval;
        IntGroup *ig;
        IntGroupIterator iter;
-       int nn, errno, i, j, in, status;
+       int nn, errnum, i, j, in, status;
        Vector *ref;
        Double *weights, dval[3];
        Transform tr;
@@ -8565,7 +8718,7 @@ s_Molecule_FitCoordinates(int argc, VALUE *argv, VALUE self)
        if (gval == Qnil)
                ig = IntGroupNewWithPoints(0, mol->natoms, -1);
        else
-               ig = IntGroupFromValue(gval);
+               ig = s_Molecule_AtomGroupFromValue(self, gval);
        if (ig == NULL || (nn = IntGroupGetCount(ig)) == 0) {
                IntGroupRelease(ig);
                rb_raise(rb_eMolbyError, "atom group is not given correctly");
@@ -8576,7 +8729,7 @@ s_Molecule_FitCoordinates(int argc, VALUE *argv, VALUE self)
        if (rb_obj_is_kind_of(rval, rb_cNumeric)) {
                int fn = NUM2INT(rb_Integer(rval));
                if (fn < 0 || fn >= MoleculeGetNumberOfFrames(mol)) {
-                       errno = 1;
+                       errnum = 1;
                        status = fn;
                        goto err;
                }
@@ -8589,7 +8742,7 @@ s_Molecule_FitCoordinates(int argc, VALUE *argv, VALUE self)
        } else if (rb_obj_is_kind_of(rval, rb_cLAMatrix)) {
                LAMatrix *m = LAMatrixFromValue(rval, NULL, 0, 0);
                if (m->row * m->column < nn * 3) {
-                       errno = 2;
+                       errnum = 2;
                        goto err;
                }
                for (i = 0; i < nn; i++) {
@@ -8601,24 +8754,24 @@ s_Molecule_FitCoordinates(int argc, VALUE *argv, VALUE self)
                VALUE aval;
                rval = rb_protect(rb_ary_to_ary, rval, &status);
                if (status != 0) {
-                       errno = 3;
+                       errnum = 3;
                        goto err;
                }
                if (RARRAY_LEN(rval) < nn) {
-                       errno = 2;
+                       errnum = 2;
                        goto err;
                }
                if (rb_obj_is_kind_of((RARRAY_PTR(rval))[0], rb_cNumeric)) {
                        /*  Array of 3*nn numbers  */
                        if (RARRAY_LEN(rval) < nn * 3) {
-                               errno = 2;
+                               errnum = 2;
                                goto err;
                        }
                        for (i = 0; i < nn; i++) {
                                for (j = 0; j < 3; j++) {
                                        aval = rb_protect(rb_Float, (RARRAY_PTR(rval))[i * 3 + j], &status);
                                        if (status != 0) {
-                                               errno = 3;
+                                               errnum = 3;
                                                goto err;
                                        }
                                        dval[j] = NUM2DBL(aval);
@@ -8636,18 +8789,18 @@ s_Molecule_FitCoordinates(int argc, VALUE *argv, VALUE self)
                                } else {
                                        aval = rb_protect(rb_ary_to_ary, aval, &status);
                                        if (status != 0) {
-                                               errno = 3;
+                                               errnum = 3;
                                                goto err;
                                        }
                                        if (RARRAY_LEN(aval) < 3) {
-                                               errno = 4;
+                                               errnum = 4;
                                                status = i;
                                                goto err;
                                        }
                                        for (j = 0; j < 3; j++) {
                                                VALUE aaval = rb_protect(rb_Float, (RARRAY_PTR(aval))[j], &status);
                                                if (status != 0) {
-                                                       errno = 3;
+                                                       errnum = 3;
                                                        goto err;
                                                }
                                                dval[j] = NUM2DBL(aaval);
@@ -8669,17 +8822,17 @@ s_Molecule_FitCoordinates(int argc, VALUE *argv, VALUE self)
        } else {
                wval = rb_protect(rb_ary_to_ary, wval, &status);
                if (status != 0) {
-                       errno = 3;
+                       errnum = 3;
                        goto err;
                }
                if (RARRAY_LEN(wval) < nn) {
-                       errno = 5;
+                       errnum = 5;
                        goto err;
                }
                for (i = 0; i < nn; i++) {
                        VALUE wwval = rb_protect(rb_Float, (RARRAY_PTR(wval))[i], &status);
                        if (status != 0) {
-                               errno = 3;
+                               errnum = 3;
                                goto err;
                        }
                        weights[i] = NUM2DBL(wwval);
@@ -8687,32 +8840,34 @@ s_Molecule_FitCoordinates(int argc, VALUE *argv, VALUE self)
        }
        dval[0] = s_Molecule_FitCoordinates_Sub(mol, ig, ref, weights, tr);
        if (dval[0] < 0) {
-               errno = 6;
+               errnum = 6;
                goto err;
        }
-       errno = 0;
+       errnum = 0;
 err:
        IntGroupIteratorRelease(&iter);
        free(ref);
        free(weights);
-       if (errno == 0) {
+       if (errnum == 0) {
                return rb_ary_new3(2, ValueFromTransform(&tr), rb_float_new(dval[0]));
-       } else if (errno == 1) {
+       } else if (errnum == 1) {
                rb_raise(rb_eMolbyError, "frame index (%d) is out of range", status);
-       } else if (errno == 2) {
+       } else if (errnum == 2) {
                rb_raise(rb_eMolbyError, "insufficient number of reference coordinates");
-       } else if (errno == 3) {
+       } else if (errnum == 3) {
                rb_jump_tag(status);
-       } else if (errno == 4) {
+       } else if (errnum == 4) {
                rb_raise(rb_eMolbyError, "less than 3 elements for index %d of reference coordinates", status);
-       } else if (errno == 5) {
+       } else if (errnum == 5) {
                rb_raise(rb_eMolbyError, "insufficient number of weight values");
-       } else if (errno == 6) {
+       } else if (errnum == 6) {
                rb_raise(rb_eMolbyError, "matrix calculation failed during coordinate fitting");
        }
        return Qnil;  /*  Not reached  */
 }
 
+#pragma mark ------ Screen Display ------
+
 /*
  *  call-seq:
  *     display
@@ -8926,6 +9081,33 @@ s_Molecule_IsAtomVisible(VALUE self, VALUE ival)
 
 /*
  *  call-seq:
+ *     hidden_atoms       -> IntGroup
+ *
+ *  Returns the currently hidden atoms.
+ */
+static VALUE
+s_Molecule_HiddenAtoms(VALUE self)
+{
+       rb_raise(rb_eMolbyError, "set_hidden_atoms is now obsolete. Try using Molecule#is_atom_visible or AtomRef#hidden.");
+       return Qnil;  /*  Not reached  */
+}
+
+/*
+ *  call-seq:
+ *     set_hidden_atoms(IntGroup)
+ *     self.hidden_atoms = IntGroup
+ *
+ *  Hide the specified atoms. This operation is _not_ undoable.
+ */
+static VALUE
+s_Molecule_SetHiddenAtoms(VALUE self, VALUE val)
+{
+       rb_raise(rb_eMolbyError, "set_hidden_atoms is now obsolete. Try using Molecule#is_atom_visible or AtomRef#hidden.");
+       return Qnil;  /*  Not reached  */
+}
+
+/*
+ *  call-seq:
  *     show_graphite -> Integer
  *     show_graphite = Integer
  *     show_graphite = boolean
@@ -9038,6 +9220,29 @@ s_Molecule_ShowPeriodicImageFlag(VALUE self)
 
 /*
  *  call-seq:
+ *     show_rotation_center -> [amin, amax, bmin, bmax, cmin, cmax]
+ *     show_rotation_center = [amin, amax, bmin, bmax, cmin, cmax]
+ *     show_rotation_center = boolean
+ *
+ *  Set to show the rotation center of the screen.
+ *  If the argument is boolean, only the show/hide flag is modified.
+ */
+static VALUE
+s_Molecule_ShowRotationCenter(int argc, VALUE *argv, VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mview == NULL)
+               return Qnil;
+       if (argc > 0) {
+               mol->mview->showRotationCenter = (RTEST(argv[0]) != 0);
+               MainViewCallback_setNeedsDisplay(mol->mview, 1);
+       }
+       return (mol->mview->showRotationCenter ? Qtrue : Qfalse);
+}
+
+/*
+ *  call-seq:
  *     line_mode
  *     line_mode(bool)
  *     line_mode = bool
@@ -9061,6 +9266,113 @@ s_Molecule_LineMode(int argc, VALUE *argv, VALUE self)
 
 /*
  *  call-seq:
+ *     atom_radius = float
+ *     atom_radius
+ *
+ *  Set the atom radius (multiple of the vdw radius) used in drawing the model in normal (non-line) mode.
+ *  (Default = 0.4)
+ *  If no argument is given, the current value is returned.
+ */
+static VALUE
+s_Molecule_AtomRadius(int argc, VALUE *argv, VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mview == NULL)
+               return Qnil;
+       if (argc > 0) {
+               double rad = NUM2DBL(rb_Float(argv[0]));
+               if (rad > 0.0) {
+                       mol->mview->atomRadius = rad;
+                       MainViewCallback_setNeedsDisplay(mol->mview, 1);
+               }
+               return argv[0];
+       }
+       return rb_float_new(mol->mview->atomRadius);
+}
+
+/*
+ *  call-seq:
+ *     bond_radius = float
+ *     bond_radius
+ *
+ *  Set the bond radius (in angstrom) used in drawing the model in normal (non-line) mode. (Default = 0.1)
+ *  If no argument is given, the current value is returned.
+ */
+static VALUE
+s_Molecule_BondRadius(int argc, VALUE *argv, VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mview == NULL)
+               return Qnil;
+       if (argc > 0) {
+               double rad = NUM2DBL(rb_Float(argv[0]));
+               if (rad > 0.0) {
+                       mol->mview->bondRadius = rad;
+                       MainViewCallback_setNeedsDisplay(mol->mview, 1);
+               }
+               return argv[0];
+       }
+       return rb_float_new(mol->mview->bondRadius);
+}
+
+/*
+ *  call-seq:
+ *     atom_resolution = integer
+ *     atom_resolution
+ *
+ *  Set the atom resolution used in drawing the model in normal (non-line) mode.
+ *  (Default = 12; minimum = 6)
+ *  If no argument is given, the current value is returned.
+ */
+static VALUE
+s_Molecule_AtomResolution(int argc, VALUE *argv, VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mview == NULL)
+               return Qnil;
+       if (argc > 0) {
+               int res = NUM2INT(rb_Integer(argv[0]));
+               if (res < 6)
+                       rb_raise(rb_eRangeError, "The atom resolution (%d) less than 6 is not acceptable", res);
+               mol->mview->atomResolution = res;
+               MainViewCallback_setNeedsDisplay(mol->mview, 1);
+               return INT2NUM(res);
+       }
+       return INT2NUM(mol->mview->atomResolution);
+}
+
+/*
+ *  call-seq:
+ *     bond_resolution = integer
+ *     bond_resolution
+ *
+ *  Set the bond resolution used in drawing the model in normal (non-line) mode.
+ *  (Default = 8; minimum = 4)
+ *  If no argument is given, the current value is returned.
+ */
+static VALUE
+s_Molecule_BondResolution(int argc, VALUE *argv, VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mview == NULL)
+               return Qnil;
+       if (argc > 0) {
+               int res = NUM2INT(rb_Integer(argv[0]));
+               if (res < 4)
+                       rb_raise(rb_eRangeError, "The bond resolution (%d) less than 4 is not acceptable", res);
+               mol->mview->bondResolution = res;
+               MainViewCallback_setNeedsDisplay(mol->mview, 1);
+               return INT2NUM(res);
+       }
+       return INT2NUM(mol->mview->bondResolution);
+}
+
+/*
+ *  call-seq:
  *     resize_to_fit
  *
  *  Resize the model drawing to fit in the window.
@@ -9085,7 +9397,7 @@ static VALUE
 s_Molecule_GetViewRotation(VALUE self)
 {
     Molecule *mol;
-       float f[4];
+       double f[4];
        Vector v;
     Data_Get_Struct(self, Molecule, mol);
        if (mol->mview == NULL)
@@ -9124,7 +9436,7 @@ static VALUE
 s_Molecule_GetViewCenter(VALUE self)
 {
     Molecule *mol;
-       float f[4];
+       double f[4];
        Vector v;
     Data_Get_Struct(self, Molecule, mol);
        if (mol->mview == NULL)
@@ -9146,7 +9458,7 @@ static VALUE
 s_Molecule_SetViewRotation(VALUE self, VALUE aval, VALUE angval)
 {
     Molecule *mol;
-       float f[4];
+       double f[4];
        Vector v;
     Data_Get_Struct(self, Molecule, mol);
        if (mol->mview == NULL)
@@ -9192,7 +9504,7 @@ s_Molecule_SetViewCenter(VALUE self, VALUE aval)
 {
     Molecule *mol;
        Vector v;
-       float f[4];
+       double f[4];
     Data_Get_Struct(self, Molecule, mol);
        if (mol->mview == NULL)
                return Qnil;
@@ -9226,41 +9538,150 @@ s_Molecule_SetBackgroundColor(int argc, VALUE *argv, VALUE self)
 
 /*
  *  call-seq:
- *     create_graphic(kind, color, points, fill = nil) -> integer
+ *     export_graphic(fname, scale = 1.0, bg_color = -1, width = 0, height = 0)
+ *
+ *  Export the current graphic to a PNG or TIF file (determined by the extension).
+ *  bg_color: -1, same as screen; 0, transparent; 1, black; 2, white.
+ *  If either width or height is not specified, then the screen width/height is used instead.
+ */
+static VALUE
+s_Molecule_ExportGraphic(int argc, VALUE *argv, VALUE self)
+{
+       Molecule *mol;
+       VALUE fval, sval, bval, wval, hval;
+       char *fname;
+       float scale;
+       int bg_color, width, height;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mview == NULL)
+               rb_raise(rb_eMolbyError, "The molecule has no associated graphic view");
+       rb_scan_args(argc, argv, "14", &fval, &sval, &bval, &wval, &hval);
+       fname = FileStringValuePtr(fval);
+       if (sval == Qnil)
+               scale = 1.0;
+       else scale = NUM2DBL(rb_Float(sval));
+       if (bval == Qnil)
+               bg_color = -1;
+       else bg_color = NUM2INT(rb_Integer(bval));
+       if (wval == Qnil)
+               width = 0;
+       else width = NUM2INT(rb_Integer(wval));
+       if (hval == Qnil)
+               height = 0;
+       else height = NUM2INT(rb_Integer(hval));
+       if (MainViewCallback_exportGraphic(mol->mview, fname, scale, bg_color, width, height) == 0)
+               return fval;
+       else return Qnil;
+}
+
+#pragma mark ------ Graphics ------
+
+static void
+s_CalculateGraphicNormals(MainViewGraphic *gp)
+{
+       int i;
+       Vector v1, v2, v3;
+       if (gp == NULL || gp->npoints < 3)
+               return;
+       AssignArray(&gp->normals, &gp->nnormals, sizeof(GLfloat) * 3, gp->npoints - 1, NULL);
+       v1.x = gp->points[3] - gp->points[0];
+       v1.y = gp->points[4] - gp->points[1];
+       v1.z = gp->points[5] - gp->points[2];
+       /*  nv[i] = (v[i-1]-v[0]).cross(v[i]-v[0]) (i=2..n-1)  */
+       for (i = 2; i < gp->npoints; i++) {
+               v2.x = gp->points[i * 3] - gp->points[0];
+               v2.y = gp->points[i * 3 + 1] - gp->points[1];
+               v2.z = gp->points[i * 3 + 2] - gp->points[2];
+               VecCross(v3, v1, v2);
+               NormalizeVec(&v3, &v3);
+               gp->normals[i * 3] = v3.x;
+               gp->normals[i * 3 + 1] = v3.y;
+               gp->normals[i * 3 + 2] = v3.z;
+               v1 = v2;
+       }
+       /*  normals[0] = average of all nv[i] (i=2..n-1)  */
+       VecZero(v1);
+       for (i = 2; i < gp->npoints; i++) {
+               v1.x += gp->normals[i * 3];
+               v1.y += gp->normals[i * 3 + 1];
+               v1.z += gp->normals[i * 3 + 2];
+       }
+       NormalizeVec(&v1, &v1);
+       gp->normals[0] = v1.x;
+       gp->normals[1] = v1.y;
+       gp->normals[2] = v1.z;
+       /*  normals[1] = nv[2].normalize  */
+       v2.x = gp->normals[6];
+       v2.y = gp->normals[7];
+       v2.z = gp->normals[8];
+       NormalizeVec(&v1, &v2);
+       gp->normals[3] = v1.x;
+       gp->normals[4] = v1.y;
+       gp->normals[5] = v1.z;
+       /*  normals[i] = (nv[i] + nv[i+1]).normalize (i=2..n-2)  */
+       for (i = 2; i < gp->npoints; i++) {
+               if (i == gp->npoints - 1)
+                       VecZero(v3);
+               else {
+                       v3.x = gp->normals[i * 3 + 3];
+                       v3.y = gp->normals[i * 3 + 4];
+                       v3.z = gp->normals[i * 3 + 5];
+               }
+               VecInc(v2, v3);
+               NormalizeVec(&v1, &v2);
+               gp->normals[i * 3] = v1.x;
+               gp->normals[i * 3 + 1] = v1.y;
+               gp->normals[i * 3 + 2] = v1.z;
+               v2 = v3;
+       }
+}
+
+/*
+ *  call-seq:
+ *     insert_graphic(index, kind, color, points, fill = nil) -> integer
  *
- *  Create a new graphic object.
+ *  Create a new graphic object and insert at the given graphic index (if -1, then append at the last).
  *   kind: a symbol representing the kind of the graphic. :line, :poly, :cylinder, :cone, :ellipsoid
  *   color: an array of 3 (rgb) or 4 (rgba) floating numbers
  *   points: an array of Vectors
  *   
  */
 static VALUE
-s_Molecule_CreateGraphic(int argc, VALUE *argv, VALUE self)
+s_Molecule_InsertGraphic(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
        MainViewGraphic g;
-       int i, n, ni;
+       int i, n, ni, idx;
        const char *p;
-       VALUE kval, cval, pval, fval;
+       VALUE kval, cval, pval, fval, ival;
     Data_Get_Struct(self, Molecule, mol);
        if (mol->mview == NULL)
                rb_raise(rb_eMolbyError, "this molecule has no associated graphic view");
-       rb_scan_args(argc, argv, "31", &kval, &cval, &pval, &fval);
-       kval = rb_obj_as_string(kval);
+       rb_scan_args(argc, argv, "41", &ival, &kval, &cval, &pval, &fval);
+       idx = NUM2INT(rb_Integer(ival));
+       if (idx == -1)
+               idx = mol->mview->ngraphics;
+       else if (idx < 0 || idx > mol->mview->ngraphics)
+               rb_raise(rb_eMolbyError, "the graphic index (%d) out of range", idx);
        memset(&g, 0, sizeof(g));
        g.visible = 1;
-       p = RSTRING_PTR(kval);
-       if (strcmp(p, "line") == 0)
-               g.kind = kMainViewGraphicLine;
-       else if (strcmp(p, "poly") == 0)
-               g.kind = kMainViewGraphicPoly;
-       else if (strcmp(p, "cylinder") == 0)
-               g.kind = kMainViewGraphicCylinder;
-       else if (strcmp(p, "cone") == 0)
-               g.kind = kMainViewGraphicCone;
-       else if (strcmp(p, "ellipsoid") == 0)
-               g.kind = kMainViewGraphicEllipsoid;
-       else rb_raise(rb_eMolbyError, "unknown graphic object type: %s", p);
+       if (rb_obj_is_kind_of(kval, rb_cInteger)) {
+               g.kind = NUM2INT(kval);  /*  Direct assign (for undo registration)  */
+       } else {
+               kval = rb_obj_as_string(kval);
+               p = StringValuePtr(kval);
+               if (strcmp(p, "line") == 0)
+                       g.kind = kMainViewGraphicLine;
+               else if (strcmp(p, "poly") == 0)
+                       g.kind = kMainViewGraphicPoly;
+               else if (strcmp(p, "cylinder") == 0)
+                       g.kind = kMainViewGraphicCylinder;
+               else if (strcmp(p, "cone") == 0)
+                       g.kind = kMainViewGraphicCone;
+               else if (strcmp(p, "ellipsoid") == 0)
+                       g.kind = kMainViewGraphicEllipsoid;
+               else rb_raise(rb_eMolbyError, "unknown graphic object type: %s", p);
+       }
        g.closed = (RTEST(fval) ? 1 : 0);
        cval = rb_ary_to_ary(cval);
        n = RARRAY_LEN(cval);
@@ -9300,11 +9721,17 @@ s_Molecule_CreateGraphic(int argc, VALUE *argv, VALUE self)
        NewArray(&g.points, &g.npoints, sizeof(GLfloat) * 3, n);
        for (i = 0; i < n; i++) {
                Vector v;
+               VALUE rval = RARRAY_PTR(pval)[i];
                if (i == ni) {
-                       v.x = NUM2DBL(rb_Float(RARRAY_PTR(pval)[i]));
+                       if (rb_obj_is_kind_of(rval, rb_cVector3D)) {
+                               /*  The float argument can also be given as a vector (for simplify undo registration)  */
+                               VectorFromValue(rval, &v);
+                       } else {
+                               v.x = NUM2DBL(rb_Float(rval));
+                       }
                        v.y = v.z = 0;
                } else {
-                       VectorFromValue(RARRAY_PTR(pval)[i], &v);
+                       VectorFromValue(rval, &v);
                }
                g.points[i * 3] = v.x;
                g.points[i * 3 + 1] = v.y;
@@ -9316,8 +9743,36 @@ s_Molecule_CreateGraphic(int argc, VALUE *argv, VALUE self)
                g.points[6] = g.points[8] = g.points[9] = g.points[10] = 0;
                g.points[7] = g.points[11] = g.points[3];
        }
-       MainView_insertGraphic(mol->mview, -1, &g);
-       return INT2NUM(mol->mview->ngraphics - 1);      
+       if (g.kind == kMainViewGraphicPoly) {
+               /*  Calculate normals  */
+               s_CalculateGraphicNormals(&g);
+       }
+       MainView_insertGraphic(mol->mview, idx, &g);
+       
+       {
+               /*  Register undo  */
+               MolAction *act;
+               act = MolActionNew(SCRIPT_ACTION("i"), "remove_graphic", idx);
+               MolActionCallback_registerUndo(mol, act);
+               MolActionRelease(act);
+       }
+
+       return INT2NUM(idx);    
+}
+
+/*
+ *  call-seq:
+ *     create_graphic(kind, color, points, fill = nil) -> integer
+ *
+ *  Create a new graphic object. The arguments are similar as insert_graphic.
+ */
+static VALUE
+s_Molecule_CreateGraphic(int argc, VALUE *argv, VALUE self)
+{
+       VALUE args[5];
+       rb_scan_args(argc, argv, "31", args + 1, args + 2, args + 3, args + 4);
+       args[0] = INT2NUM(-1);
+       return s_Molecule_InsertGraphic(argc + 1, args, self);
 }
 
 /*
@@ -9337,6 +9792,34 @@ s_Molecule_RemoveGraphic(VALUE self, VALUE ival)
        i = NUM2INT(rb_Integer(ival));
        if (i < 0 || i >= mol->mview->ngraphics)
                rb_raise(rb_eArgError, "graphic index is out of range");
+       {
+               /*  Prepare data for undo  */
+               MainViewGraphic *gp;
+               Vector *vp;
+               MolAction *act;
+               double col[4];
+               int n;
+               gp = mol->mview->graphics + i;
+               vp = (Vector *)malloc(sizeof(Vector) * gp->npoints);
+               for (n = 0; n < gp->npoints; n++) {
+                       vp[n].x = gp->points[n * 3];
+                       vp[n].y = gp->points[n * 3 + 1];
+                       vp[n].z = gp->points[n * 3 + 2];
+               }
+               col[0] = gp->rgba[0];
+               col[1] = gp->rgba[1];
+               col[2] = gp->rgba[2];
+               col[3] = gp->rgba[3];
+               if (gp->visible == 0) {
+                       act = MolActionNew(SCRIPT_ACTION("i"), "hide_graphic", i);
+                       MolActionCallback_registerUndo(mol, act);
+                       MolActionRelease(act);
+               }
+               act = MolActionNew(SCRIPT_ACTION("iiDVb"), "insert_graphic", i, gp->kind, 4, col, gp->npoints, vp, gp->closed);
+               MolActionCallback_registerUndo(mol, act);
+               free(vp);
+               MolActionRelease(act);
+       }
        MainView_removeGraphic(mol->mview, i);
        return ival;
 }
@@ -9354,23 +9837,179 @@ s_Molecule_NGraphics(VALUE self)
     Data_Get_Struct(self, Molecule, mol);
        if (mol->mview == NULL)
                rb_raise(rb_eMolbyError, "this molecule has no associated graphic view");
-       return INT2NUM(mol->mview->ngraphics);
+       return INT2NUM(mol->mview->ngraphics);
+}
+
+/*
+ *  call-seq:
+ *     get_graphic_point(graphic_index, point_index) -> value
+ *     get_graphic_points(graphic_index) -> values
+ *
+ *  Get the point_index-th control point of graphic_index-th graphic object.
+ *  Get an array of all control points with the given values.
+ *   
+ */
+static VALUE
+s_Molecule_GetGraphicPoint(int argc, VALUE *argv, VALUE self)
+{
+       MainViewGraphic *gp;
+    Molecule *mol;
+       int index, pindex;
+       Vector v;
+       VALUE gval, pval;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mview == NULL)
+               rb_raise(rb_eMolbyError, "this molecule has no associated graphic view");
+       rb_scan_args(argc, argv, "11", &gval, &pval);
+       index = NUM2INT(rb_Integer(gval));
+       if (index < 0 || index >= mol->mview->ngraphics)
+               rb_raise(rb_eArgError, "the graphic index is out of range");
+       gp = mol->mview->graphics + index;
+       if (pval != Qnil) {
+               pindex = NUM2INT(rb_Integer(pval));
+               if (pindex < 0 || pindex >= gp->npoints)
+                       rb_raise(rb_eArgError, "the point index is out of range");
+               v.x = gp->points[pindex * 3];
+               v.y = gp->points[pindex * 3 + 1];
+               v.z = gp->points[pindex * 3 + 2];
+               if ((gp->kind == kMainViewGraphicCylinder || gp->kind == kMainViewGraphicCone) && pindex == 2) {
+                       return rb_float_new(v.x);
+               } else {
+                       return ValueFromVector(&v);
+               }
+       } else {
+               pval = rb_ary_new();
+               for (pindex = 0; pindex < gp->npoints; pindex++) {
+                       v.x = gp->points[pindex * 3];
+                       v.y = gp->points[pindex * 3 + 1];
+                       v.z = gp->points[pindex * 3 + 2];
+                       rb_ary_push(pval, ValueFromVector(&v));
+               }
+               return pval;
+       }
+}
+
+/*
+ *  call-seq:
+ *     set_graphic_point(graphic_index, point_index, new_value) -> new_value
+ *     set_graphic_points(graphic_index, new_values) -> new_values
+ *
+ *  Change the point_index-th control point of graphic_index-th graphic object.
+ *  Replace the control points with the given values.
+ *   
+ */
+static VALUE
+s_Molecule_SetGraphicPoint(int argc, VALUE *argv, VALUE self)
+{
+       MainViewGraphic *gp;
+    Molecule *mol;
+       int index, pindex;
+       Vector v, v0;
+       VALUE gval, pval, nval;
+       MolAction *act;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mview == NULL)
+               rb_raise(rb_eMolbyError, "this molecule has no associated graphic view");
+       rb_scan_args(argc, argv, "21", &gval, &pval, &nval);
+       index = NUM2INT(rb_Integer(gval));
+       if (index < 0 || index >= mol->mview->ngraphics)
+               rb_raise(rb_eArgError, "the graphic index is out of range");
+       gp = mol->mview->graphics + index;
+       if (nval != Qnil) {
+               pindex = NUM2INT(rb_Integer(pval));
+               if (pindex < 0 || pindex >= gp->npoints)
+                       rb_raise(rb_eArgError, "the point index is out of range");
+               v0.x = gp->points[pindex * 3];
+               v0.y = gp->points[pindex * 3 + 1];
+               v0.z = gp->points[pindex * 3 + 2];
+               if (rb_obj_is_kind_of(nval, rb_cNumeric)) {
+                       if ((gp->kind == kMainViewGraphicCylinder || gp->kind == kMainViewGraphicCone) && pindex == 2) {
+                               v.x = NUM2DBL(rb_Float(nval));
+                               v.y = v.z = 0;
+                       } else if (gp->kind == kMainViewGraphicEllipsoid && pindex == 1) {
+                               v.x = NUM2DBL(rb_Float(nval));
+                               v.y = v.z = 0;
+                               gp->points[7] = gp->points[11] = v.x;
+                               gp->points[6] = gp->points[8] = gp->points[9] = gp->points[10] = 0;
+                       } else rb_raise(rb_eArgError, "the argument must be an array-like object");
+               } else {
+                       if (nval == Qnil) {
+                               v.x = kInvalidFloat;
+                               v.y = v.z = 0.0;
+                       } else VectorFromValue(nval, &v);
+               }
+               gp->points[pindex * 3] = v.x;
+               gp->points[pindex * 3 + 1] = v.y;
+               gp->points[pindex * 3 + 2] = v.z;
+               act = MolActionNew(SCRIPT_ACTION("iiv"), "set_graphic_point", index, pindex, &v0);
+       } else {
+               VALUE aval;
+               int len;
+               Vector *vp = (Vector *)malloc(sizeof(Vector) * gp->npoints);
+               for (pindex = 0; pindex < gp->npoints; pindex++) {
+                       vp[pindex].x = gp->points[pindex * 3];
+                       vp[pindex].y = gp->points[pindex * 3 + 1];
+                       vp[pindex].z = gp->points[pindex * 3 + 2];
+               }
+               act = MolActionNew(SCRIPT_ACTION("iV"), "set_graphic_points", index, gp->npoints, vp);
+               free(vp);
+               pval = rb_ary_to_ary(pval);
+               len = RARRAY_LEN(pval);
+               if (gp->npoints < len) {
+                       gp->points = (GLfloat *)realloc(gp->points, sizeof(GLfloat) * 3 * len);
+                       gp->npoints = len;
+               } else if (gp->npoints > len) {
+                       int len2 = 3;
+                       switch (gp->kind) {
+                               case kMainViewGraphicLine: len2 = 2; break;
+                               case kMainViewGraphicPoly: len2 = 3; break;
+                               case kMainViewGraphicCylinder: len2 = 3; break;
+                               case kMainViewGraphicCone: len2 = 3; break;
+                               case kMainViewGraphicEllipsoid: len2 = 4; break;
+                       }
+                       if (len2 < len)
+                               len2 = len;
+                       gp->npoints = len2;
+               }
+               for (pindex = 0; pindex < len && pindex < gp->npoints; pindex++) {
+                       aval = RARRAY_PTR(pval)[pindex];
+                       if ((gp->kind == kMainViewGraphicCylinder || gp->kind == kMainViewGraphicCone) && pindex == 2) {
+                               v.x = NUM2DBL(rb_Float(aval));
+                               v.y = v.z = 0;
+                       } else if (gp->kind == kMainViewGraphicEllipsoid && pindex == 1 && len == 2) {
+                               v.x = NUM2DBL(rb_Float(aval));
+                               v.y = v.z = 0;
+                               gp->points[7] = gp->points[11] = v.x;
+                               gp->points[6] = gp->points[8] = gp->points[9] = gp->points[10] = 0;
+                               break;
+                       } else VectorFromValue(aval, &v);
+                       gp->points[pindex * 3] = v.x;
+                       gp->points[pindex * 3 + 1] = v.y;
+                       gp->points[pindex * 3 + 2] = v.z;
+               }
+       }
+       if (gp->kind == kMainViewGraphicPoly) {
+               /*  Calculate normals  */
+               s_CalculateGraphicNormals(gp);
+       }
+       MolActionCallback_registerUndo(mol, act);
+       MolActionRelease(act);          
+       MoleculeCallback_notifyModification(mol, 0);
+       return nval;
 }
-       
+
 /*
  *  call-seq:
- *     set_graphic_point(graphic_index, point_index, new_value) -> new_value
+ *     get_graphic_color(graphic_index) -> value
  *
- *  Change the point_index-th control point of graphic_index-th graphic object
- *   
+ *  Get the color of graphic_index-th graphic object
  */
 static VALUE
-s_Molecule_SetGraphicPoint(VALUE self, VALUE gval, VALUE pval, VALUE nval)
+s_Molecule_GetGraphicColor(VALUE self, VALUE gval)
 {
        MainViewGraphic *gp;
     Molecule *mol;
        int index;
-       Vector v;
     Data_Get_Struct(self, Molecule, mol);
        if (mol->mview == NULL)
                rb_raise(rb_eMolbyError, "this molecule has no associated graphic view");
@@ -9378,29 +10017,7 @@ s_Molecule_SetGraphicPoint(VALUE self, VALUE gval, VALUE pval, VALUE nval)
        if (index < 0 || index >= mol->mview->ngraphics)
                rb_raise(rb_eArgError, "the graphic index is out of range");
        gp = mol->mview->graphics + index;
-       index = NUM2INT(rb_Integer(pval));
-       if (index < 0 || index >= gp->npoints)
-               rb_raise(rb_eArgError, "the point index is out of range");
-       if (rb_obj_is_kind_of(nval, rb_cNumeric)) {
-               if ((gp->kind == kMainViewGraphicCylinder || gp->kind == kMainViewGraphicCone) && index == 2) {
-                       v.x = NUM2DBL(rb_Float(nval));
-                       v.y = v.z = 0;
-               } else if (gp->kind == kMainViewGraphicEllipsoid && index == 1) {
-                       gp->points[3] = gp->points[7] = gp->points[11] = NUM2DBL(rb_Float(nval));
-                       gp->points[4] = gp->points[5] = gp->points[6] = gp->points[8] = gp->points[9] = gp->points[10] = 0;
-                       return nval;
-               } else rb_raise(rb_eArgError, "the argument must be an array-like object");
-       } else {
-               if (nval == Qnil) {
-                       v.x = kInvalidFloat;
-                       v.y = v.z = 0.0;
-               } else VectorFromValue(nval, &v);
-       }
-       gp->points[index * 3] = v.x;
-       gp->points[index * 3 + 1] = v.y;
-       gp->points[index * 3 + 2] = v.z;
-       MoleculeCallback_notifyModification(mol, 0);
-       return nval;
+       return rb_ary_new3(4, rb_float_new(gp->rgba[0]), rb_float_new(gp->rgba[1]), rb_float_new(gp->rgba[2]), rb_float_new(gp->rgba[3]));
 }
 
 /*
@@ -9415,7 +10032,9 @@ s_Molecule_SetGraphicColor(VALUE self, VALUE gval, VALUE cval)
 {
        MainViewGraphic *gp;
     Molecule *mol;
-       int index, n;
+       MolAction *act;
+       double c[4];
+       int index, i, n;
     Data_Get_Struct(self, Molecule, mol);
        if (mol->mview == NULL)
                rb_raise(rb_eMolbyError, "this molecule has no associated graphic view");
@@ -9423,15 +10042,21 @@ s_Molecule_SetGraphicColor(VALUE self, VALUE gval, VALUE cval)
        if (index < 0 || index >= mol->mview->ngraphics)
                rb_raise(rb_eArgError, "the graphic index is out of range");
        gp = mol->mview->graphics + index;
+       for (i = 0; i < 4; i++)
+               c[i] = gp->rgba[i];
        cval = rb_ary_to_ary(cval);
        n = RARRAY_LEN(cval);
        if (n != 3 && n != 4)
                rb_raise(rb_eArgError, "the color argument must have 3 or 4 numbers");
-       for (index = 0; index < n; index++) {
-               gp->rgba[index] = NUM2DBL(rb_Float(RARRAY_PTR(cval)[index]));
+
+       for (i = 0; i < n; i++) {
+               gp->rgba[i] = NUM2DBL(rb_Float(RARRAY_PTR(cval)[i]));
        }
        if (n == 3)
                gp->rgba[3] = 1.0;
+       act = MolActionNew(SCRIPT_ACTION("iD"), "set_graphic_color", index, 4, c);
+       MolActionCallback_registerUndo(mol, act);
+       MolActionRelease(act);          
        MoleculeCallback_notifyModification(mol, 0);
        return cval;
 }
@@ -9502,6 +10127,8 @@ s_Molecule_ShowText(VALUE self, VALUE arg)
        return Qnil;
 }
 
+#pragma mark ------ MD Support ------
+
 /*
  *  call-seq:
  *     md_arena -> MDArena
@@ -9566,6 +10193,101 @@ s_Molecule_Parameter(VALUE self)
 
 /*
  *  call-seq:
+ *     start_step       -> Integer
+ *
+ *  Returns the start step (defined by dcd format).
+ */
+static VALUE
+s_Molecule_StartStep(VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       return INT2NUM(mol->startStep);
+}
+
+/*
+ *  call-seq:
+ *     start_step = Integer
+ *
+ *  Set the start step (defined by dcd format).
+ */
+static VALUE
+s_Molecule_SetStartStep(VALUE self, VALUE val)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       mol->startStep = NUM2INT(rb_Integer(val));
+       return val;
+}
+
+/*
+ *  call-seq:
+ *     steps_per_frame       -> Integer
+ *
+ *  Returns the number of steps between frames (defined by dcd format).
+ */
+static VALUE
+s_Molecule_StepsPerFrame(VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       return INT2NUM(mol->stepsPerFrame);
+}
+
+/*
+ *  call-seq:
+ *     steps_per_frame = Integer
+ *
+ *  Set the number of steps between frames (defined by dcd format).
+ */
+static VALUE
+s_Molecule_SetStepsPerFrame(VALUE self, VALUE val)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       mol->stepsPerFrame = NUM2INT(rb_Integer(val));
+       return val;
+}
+
+/*
+ *  call-seq:
+ *     ps_per_step       -> Float
+ *
+ *  Returns the time increment (in picoseconds) for one step (defined by dcd format).
+ */
+static VALUE
+s_Molecule_PsPerStep(VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       return rb_float_new(mol->psPerStep);
+}
+
+/*
+ *  call-seq:
+ *     ps_per_step = Float
+ *
+ *  Set the time increment (in picoseconds) for one step (defined by dcd format).
+ */
+static VALUE
+s_Molecule_SetPsPerStep(VALUE self, VALUE val)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       mol->psPerStep = NUM2DBL(rb_Float(val));
+       return val;
+}
+
+static VALUE
+s_Molecule_BondParIsObsolete(VALUE self, VALUE val)
+{
+       rb_raise(rb_eMolbyError, "Molecule#bond_par, angle_par, dihedral_par, improper_par, vdw_par are now obsolete. You can use MDArena#bond_par, angle_par, dihedral_par, improper_par, vdw_par instead, and probably these are what you really want.");
+}
+
+#pragma mark ------ MO Handling ------
+
+/*
+ *  call-seq:
  *     selectedMO -> IntGroup
  *
  *  Returns a group of selected mo in the "MO Info" table. If the MO info table
@@ -9654,8 +10376,8 @@ s_Molecule_Cubegen(int argc, VALUE *argv, VALUE self)
        
        /*  Set up parameters  */
        mono = NUM2INT(rb_Integer(mval));
-       if (mono <= 0 || mono > mol->bset->ncomps)
-               rb_raise(rb_eMolbyError, "The MO number (%d) is out of range (should be 1..%d)", mono, mol->bset->ncomps);
+       if (mono < 0 || mono > mol->bset->ncomps)
+               rb_raise(rb_eMolbyError, "The MO number (%d) is out of range (should be 1..%d, or 0 as 'arbitrary vector')", mono, mol->bset->ncomps);
        if (RTEST(bval)) {
                if (mol->bset->rflag != 0)
                        rb_raise(rb_eMolbyError, "Beta MO is requested but not present");
@@ -9724,6 +10446,169 @@ s_Molecule_Cubegen(int argc, VALUE *argv, VALUE self)
 
 /*
  *  call-seq:
+ *     clear_surface
+ *
+ *  Clear the MO surface if present.
+ */
+static VALUE
+s_Molecule_ClearSurface(VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mcube != NULL)
+               MoleculeClearMCube(mol, 0, 0, 0, NULL, 0.0, 0.0, 0.0);
+       return self;
+}
+
+/*
+ *  call-seq:
+ *     hide_surface
+ *
+ *  Hide the MO surface if present.
+ */
+static VALUE
+s_Molecule_HideSurface(VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mcube != NULL) {
+               mol->mcube->hidden = 1;
+               MoleculeCallback_notifyModification(mol, 0);
+       }
+       return self;
+}
+
+/*
+ *  call-seq:
+ *     show_surface
+ *
+ *  Show the MO surface if present.
+ */
+static VALUE
+s_Molecule_ShowSurface(VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mcube != NULL) {
+               mol->mcube->hidden = 0;
+               MoleculeCallback_notifyModification(mol, 0);
+       }
+       return self;
+}
+
+/*
+ *  call-seq:
+ *     create_surface(mo, attr = nil)
+ *
+ *  Create a MO surface. The argument mo is the MO index (1-based); if mo is negative,
+ *  then it denotes the beta orbital.
+ *  If mo is nil, then the attributes of the current surface are modified.
+ *  Attributes:
+ *    :npoints : the approximate number of grid points
+ *    :expand  : the scale factor to expand/shrink the display box size for each atom,
+ *    :thres   : the threshold for the isovalue surface
+ *  If the molecule does not contain MO information, raises exception.
+ */
+static VALUE
+s_Molecule_CreateSurface(int argc, VALUE *argv, VALUE self)
+{
+    Molecule *mol;
+       Vector o, dx, dy, dz;
+       Int nmo, nx, ny, nz, i;
+       Int need_recalc = 0;
+       VALUE nval, hval, aval;
+       Int npoints;
+       Double expand;
+       Double thres;
+       Double d[4];
+    Data_Get_Struct(self, Molecule, mol);
+       rb_scan_args(argc, argv, "11", &nval, &hval);
+       if (mol->bset == NULL)
+               rb_raise(rb_eMolbyError, "No MO information is given");
+       if (nval == Qnil) {
+               nmo = -1;
+       } else if (nval == ID2SYM(rb_intern("total_density"))) {
+               nmo = mol->bset->nmos + 1;
+       } else {
+               nmo = NUM2INT(rb_Integer(nval));
+               if (nmo > mol->bset->nmos || nmo < -mol->bset->ncomps)
+                       rb_raise(rb_eMolbyError, "MO index (%d) is out of range; should be 1..%d (or -1..-%d for beta orbitals); (0 is acceptable as arbitrary vector)", nmo, mol->bset->nmos, mol->bset->ncomps);
+               if (nmo < 0)
+                       nmo = -nmo + mol->bset->ncomps;
+       }
+       if (hval != Qnil && (aval = rb_hash_aref(hval, ID2SYM(rb_intern("npoints")))) != Qnil) {
+               npoints = NUM2INT(rb_Integer(aval));
+               need_recalc = 1;
+       } else if (mol->mcube != NULL) {
+               npoints = mol->mcube->nx * mol->mcube->ny * mol->mcube->nz;
+       } else npoints = 80 * 80 * 80;
+       if (hval != Qnil && (aval = rb_hash_aref(hval, ID2SYM(rb_intern("expand")))) != Qnil) {
+               expand = NUM2DBL(rb_Float(aval));
+       } else if (mol->mcube != NULL) {
+               expand = mol->mcube->expand;
+       } else expand = 1.0;
+       if (hval != Qnil && (aval = rb_hash_aref(hval, ID2SYM(rb_intern("thres")))) != Qnil) {
+               thres = NUM2DBL(rb_Float(aval));
+       } else if (mol->mcube != NULL) {
+               thres = mol->mcube->thres;
+       } else thres = 0.05;
+       if (mol->mcube == NULL || npoints != mol->mcube->nx * mol->mcube->ny * mol->mcube->nz) {
+               if (MoleculeGetDefaultMOGrid(mol, npoints, &o, &dx, &dy, &dz, &nx, &ny, &nz) != 0)
+                       rb_raise(rb_eMolbyError, "Cannot get default grid size (internal error?)");
+               if (MoleculeClearMCube(mol, nx, ny, nz, &o, dx.x, dy.y, dz.z) == NULL)
+                       rb_raise(rb_eMolbyError, "Cannot allocate memory for MO surface calculation");
+       }
+       for (nx = 0; nx < 2; nx++) {
+               aval = ID2SYM(rb_intern(nx == 0 ? "color0" : "color"));
+               if (hval != Qnil && (aval = rb_hash_aref(hval, aval)) != Qnil) {
+                       aval = rb_ary_to_ary(aval);
+                       if (RARRAY_LEN(aval) < 3) {
+                       raise:
+                               rb_raise(rb_eMolbyError, "The color%s value must be an array with at least 3 float numbers", (nx == 0 ? "0" : ""));
+                       }
+                       for (i = 0; i < 4; i++)
+                               d[i] = mol->mcube->c[nx].rgba[i];
+                       for (i = 0; i < 4 && i < RARRAY_LEN(aval); i++) {
+                               d[i] = NUM2DBL(rb_Float(RARRAY_PTR(aval)[i]));
+                               if (d[i] < 0.0 && d[i] > 1.0)
+                                       goto raise;
+                       }
+                       for (i = 0; i < 4; i++)
+                               mol->mcube->c[nx].rgba[i] = d[i];
+               }
+       }
+       if (mol->mcube->expand != expand)
+               need_recalc = 1;
+       mol->mcube->thres = thres;
+       mol->mcube->expand = expand;
+       if (nmo < 0) {
+               if (mol->mcube->idn < 0)
+                       return self;  /*  Only set attributes for now  */
+               if (need_recalc)
+                       nmo = mol->mcube->idn;  /*  Force recalculation  */
+       }
+       if (MoleculeUpdateMCube(mol, nmo) != 0)
+               rb_raise(rb_eMolbyError, "Cannot complete MO surface calculation");
+       return self;
+}
+
+/*
+ *  call-seq:
+ *     set_surface_attr(attr = nil)
+ *
+ *  Set the drawing attributes of the surface. Attr is a hash containing the attributes.
+ */
+static VALUE
+s_Molecule_SetSurfaceAttr(VALUE self, VALUE hval)
+{
+       VALUE args[2];
+       args[0] = Qnil;
+       args[1] = hval;
+       return s_Molecule_CreateSurface(2, args, self);
+}
+
+/*
+ *  call-seq:
  *     nelpots
  *
  *  Get the number of electrostatic potential info.
@@ -9758,22 +10643,47 @@ s_Molecule_Elpot(VALUE self, VALUE ival)
 
 /*
  *  call-seq:
- *     add_gaussian_orbital_shell(sym, nprims, atom_index)
+ *     clear_basis_set
+ *
+ *  Clear the existing basis set info. All gaussian coefficients, MO energies and coefficients,
+ *  cube and marching cube information are discarded. This operation is _not_ undoable!
+ */
+static VALUE
+s_Molecule_ClearBasisSet(VALUE self)
+{
+       Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol != NULL) {
+               if (mol->bset != NULL) {
+                       BasisSetRelease(mol->bset);
+                       mol->bset = NULL;
+               }
+               if (mol->mcube != NULL) {
+                       MoleculeDeallocateMCube(mol->mcube);
+                       mol->mcube = NULL;
+               }
+       }
+       return self;
+}
+
+/*
+ *  call-seq:
+ *     add_gaussian_orbital_shell(atom_index, sym, no_of_primitives)
  *
- *  To be used internally. Add a gaussian orbital shell with symmetry code, number of primitives,
- *  and the corresponding atom index. Symmetry code: 0, S-type; 1, P-type; -1, SP-type; 2, D-type;
+ *  To be used internally. Add a gaussian orbital shell with the atom index, symmetry code,
+ *  and the number of primitives. Symmetry code: 0, S-type; 1, P-type; -1, SP-type; 2, D-type;
  *  -2, D5-type.
  */
 static VALUE
-s_Molecule_AddGaussianOrbitalShell(VALUE self, VALUE symval, VALUE npval, VALUE aval)
+s_Molecule_AddGaussianOrbitalShell(VALUE self, VALUE aval, VALUE symval, VALUE npval)
 {
        Molecule *mol;
        int sym, nprims, a_idx, n;
     Data_Get_Struct(self, Molecule, mol);
+       a_idx = NUM2INT(rb_Integer(aval));
        sym = NUM2INT(rb_Integer(symval));
        nprims = NUM2INT(rb_Integer(npval));
-       a_idx = NUM2INT(rb_Integer(aval));
-       n = MoleculeAddGaussianOrbitalShell(mol, sym, nprims, a_idx);
+       n = MoleculeAddGaussianOrbitalShell(mol, a_idx, sym, nprims);
        if (n == -1)
                rb_raise(rb_eMolbyError, "Molecule is emptry");
        else if (n == -2)
@@ -9787,59 +10697,161 @@ s_Molecule_AddGaussianOrbitalShell(VALUE self, VALUE symval, VALUE npval, VALUE
 
 /*
  *  call-seq:
- *     add_gaussian_primitive_coefficients(exponent, contraction, contraction_sp)
+ *     add_gaussian_primitive_coefficients(exponent, contraction, contraction_sp)
+ *
+ *  To be used internally. Add a gaussian primitive coefficients.
+ */
+static VALUE
+s_Molecule_AddGaussianPrimitiveCoefficients(VALUE self, VALUE expval, VALUE cval, VALUE cspval)
+{
+       Molecule *mol;
+       Int n;
+       Double exponent, contraction, contraction_sp;
+    Data_Get_Struct(self, Molecule, mol);
+       exponent = NUM2DBL(rb_Float(expval));
+       contraction = NUM2DBL(rb_Float(cval));
+       contraction_sp = NUM2DBL(rb_Float(cspval));
+       n = MoleculeAddGaussianPrimitiveCoefficients(mol, exponent, contraction, contraction_sp);
+       if (n == -1)
+               rb_raise(rb_eMolbyError, "Molecule is emptry");
+       else if (n == -2)
+               rb_raise(rb_eMolbyError, "Low memory");
+       else if (n != 0)
+               rb_raise(rb_eMolbyError, "Unknown error");
+       return self;
+}
+
+/*
+ *  call-seq:
+ *     get_gaussian_shell_info(shell_index) -> [atom_index, sym, no_of_primitives, comp_index, no_of_components]
+ *
+ *  Get the Gaussian shell information for the given MO coefficient index.
+ *  The symmetry code is the same as in add_gaussian_orbital_shell.
+ *  The comp_index is the index of the first MO component belonging to this shell, and no_of_components
+ *  is the number of MO component belonging to this shell.
+ */
+static VALUE
+s_Molecule_GetGaussianShellInfo(VALUE self, VALUE sval)
+{
+       Molecule *mol;
+       ShellInfo *sp;
+       int s_idx, sym;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->bset == NULL)
+               rb_raise(rb_eMolbyError, "No basis set information is defined");
+       s_idx = NUM2INT(rb_Integer(sval));
+       if (s_idx < 0 || s_idx >= mol->bset->nshells)
+               return Qnil;
+       sp = mol->bset->shells + s_idx;
+       sym = sp->sym;
+       switch (sym) {
+               case kGTOType_S:  sym = 0;  break;
+               case kGTOType_SP: sym = -1; break;
+               case kGTOType_P:  sym = 1;  break;
+               case kGTOType_D:  sym = 2;  break;
+               case kGTOType_D5: sym = -2; break;
+               case kGTOType_F:  sym = 3;  break;
+               case kGTOType_F7: sym = -3; break;
+               case kGTOType_G:  sym = 4;  break;
+               case kGTOType_G9: sym = -4; break;
+               default:
+                       rb_raise(rb_eMolbyError, "The Gaussian shell type (%d) is unknown (internal error?)", sym);
+       }
+       return rb_ary_new3(5, INT2NUM(sp->a_idx), INT2NUM(sym), INT2NUM(sp->nprim), INT2NUM(sp->m_idx), INT2NUM(sp->ncomp));
+}
+
+/*
+ *  call-seq:
+ *     get_gaussian_primitive_coefficients(shell_index) -> [[exp1, con1 [, con_sp1]], [exp2, con2 [, con_sp2]],...]
+ *
+ *  Get the Gaussian primitive coefficients for the given MO component.
+ */
+static VALUE
+s_Molecule_GetGaussianPrimitiveCoefficients(VALUE self, VALUE sval)
+{
+       Molecule *mol;
+       ShellInfo *sp;
+       PrimInfo *pp;
+       int s_idx, i;
+       VALUE retval, aval;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->bset == NULL)
+               rb_raise(rb_eMolbyError, "No basis set information is defined");
+       s_idx = NUM2INT(rb_Integer(sval));
+       if (s_idx < 0 || s_idx >= mol->bset->nshells)
+               return Qnil;
+       sp = mol->bset->shells + s_idx;
+       pp = mol->bset->priminfos + sp->p_idx;
+       retval = rb_ary_new2(sp->nprim);
+       for (i = 0; i < sp->nprim; i++) {
+               if (sp->sym == kGTOType_SP) {
+                       /*  With P contraction coefficient  */
+                       aval = rb_ary_new3(3, rb_float_new(pp[i].A), rb_float_new(pp[i].C), rb_float_new(pp[i].Csp));
+               } else {
+                       /*  Without P contraction coefficient  */
+                       aval = rb_ary_new3(2, rb_float_new(pp[i].A), rb_float_new(pp[i].C));
+               }
+               rb_ary_store(retval, i, aval);
+       }
+       return retval;
+}
+
+/*
+ *  call-seq:
+ *     get_gaussian_component_info(comp_index) -> [atom_index, shell_index, orbital_description]
  *
- *  To be used internally. Add a gaussian primitive coefficients.
+ *  Get the Gaussian shell information for the given MO coefficient index.
  */
 static VALUE
-s_Molecule_AddGaussianPrimitiveCoefficients(VALUE self, VALUE expval, VALUE cval, VALUE cspval)
+s_Molecule_GetGaussianComponentInfo(VALUE self, VALUE cval)
 {
        Molecule *mol;
-       Int n;
-       Double exponent, contraction, contraction_sp;
+       Int n, c, atom_idx, shell_idx;
+       char label[32];
     Data_Get_Struct(self, Molecule, mol);
-       exponent = NUM2DBL(rb_Float(expval));
-       contraction = NUM2DBL(rb_Float(cval));
-       contraction_sp = NUM2DBL(rb_Float(cspval));
-       n = MoleculeAddGaussianPrimitiveCoefficients(mol, exponent, contraction, contraction_sp);
-       if (n == -1)
-               rb_raise(rb_eMolbyError, "Molecule is emptry");
-       else if (n == -2)
-               rb_raise(rb_eMolbyError, "Low memory");
-       else if (n != 0)
-               rb_raise(rb_eMolbyError, "Unknown error");
-       return self;
+       if (mol->bset == NULL)
+               rb_raise(rb_eMolbyError, "No basis set information is defined");
+       c = NUM2INT(rb_Integer(cval));
+       if (c < 0 || c >= mol->bset->ncomps)
+               return Qnil;
+       n = MoleculeGetGaussianComponentInfo(mol, c, &atom_idx, label, &shell_idx);
+       if (n != 0)
+               rb_raise(rb_eMolbyError, "Cannot get the shell info for component index (%d)", c);
+       return rb_ary_new3(3, INT2NUM(atom_idx), INT2NUM(shell_idx), Ruby_NewEncodedStringValue2(label));
 }
 
 /*
  *  call-seq:
- *     mo_type
+ *     clear_mo_coefficients
  *
- *  Returns either "RHF", "UHF", or "ROHF". If no MO info is present, returns nil.
+ *  Clear the existing MO coefficients.
  */
 static VALUE
-s_Molecule_MOType(VALUE self)
+s_Molecule_ClearMOCoefficients(VALUE self)
 {
        Molecule *mol;
-    Data_Get_Struct(self, Molecule, mol);
-       if (mol != NULL && mol->bset != NULL) {
-               const char *s;
-               int rflag = mol->bset->rflag;
-               if (rflag == 0)
-                       s = "UHF";
-               else if (rflag == 2)
-                       s = "ROHF";
-               else s = "RHF";
-               return rb_str_new2(s);
-       } else return Qnil;
+       Data_Get_Struct(self, Molecule, mol);
+       if (mol->bset != NULL) {
+               if (mol->bset->moenergies != NULL) {
+                       free(mol->bset->moenergies);
+                       mol->bset->moenergies = NULL;
+               }
+               if (mol->bset->mo != NULL) {
+                       free(mol->bset->mo);
+                       mol->bset->mo = NULL;
+               }
+               mol->bset->nmos = 0;
+       }
+       return self;
 }
 
 /*
  *  call-seq:
  *     set_mo_coefficients(idx, energy, coefficients)
  *
- *  To be used internally. Add a MO coefficients. Idx is the MO index (for open shell system, 
- *  beta MOs comes after all alpha MOs), energy is the MO energy, coefficients is an array
+ *  To be used internally. Add a MO coefficients. Idx is the MO index (1-based; for open shell system, 
+ *  beta MOs comes after all alpha MOs; alternatively, the beta MO can be specified as a negative MO number)
+ *  Energy is the MO energy, and coefficients is an array
  *  of MO coefficients.
  */
 static VALUE
@@ -9861,7 +10873,7 @@ s_Molecule_SetMOCoefficients(VALUE self, VALUE ival, VALUE eval, VALUE aval)
        }
        for (i = 0; i < ncomps; i++)
                coeffs[i] = NUM2DBL(rb_Float(RARRAY_PTR(aval)[i]));
-       i = MoleculeSetMOCoefficients(mol, idx, energy, ncomps, coeffs);
+       i = MoleculeSetMOCoefficients(mol, idx, energy, ncomps, coeffs); /* Negative (beta orbitals) or zero (arbitrary vector) idx is allowed */
 end:
        if (i == -1)
                rb_raise(rb_eMolbyError, "Molecule is emptry");
@@ -9870,7 +10882,7 @@ end:
        else if (i == -3)
                rb_raise(rb_eMolbyError, "Bad or inconsistent number of MOs");
        else if (i == -4)
-               rb_raise(rb_eMolbyError, "Bad MO index");
+               rb_raise(rb_eMolbyError, "Bad MO index (%d)", idx);
        else if (i == -5)
                rb_raise(rb_eMolbyError, "Insufficient number of coefficients are given");
        else if (i != 0)
@@ -9880,32 +10892,175 @@ end:
 
 /*
  *  call-seq:
- *     allocate_basis_set_record(rflag, ne_alpha, ne_beta)
+ *     get_mo_coefficients(idx)
  *
- *  To be used internally. Allocate a basis set record. rflag: 0, unrestricted; 1, restricted.
- *  ne_alpha, ne_beta: number of alpha/beta electrons.
+ *  To be used internally. Get an array of MO coefficients for the given MO index (1-based).
  */
 static VALUE
-s_Molecule_AllocateBasisSetRecord(VALUE self, VALUE rval, VALUE naval, VALUE nbval)
+s_Molecule_GetMOCoefficients(VALUE self, VALUE ival)
 {
        Molecule *mol;
-       Int rflag, na, nb, n;
+       Int idx, ncomps, n;
+       Double energy;
+       Double *coeffs;
+       VALUE retval;
     Data_Get_Struct(self, Molecule, mol);
-       rflag = NUM2INT(rb_Integer(rval));
-       na = NUM2INT(rb_Integer(naval));
-       nb = NUM2INT(rb_Integer(nbval));
-       n = MoleculeAllocateBasisSetRecord(mol, rflag, na, nb);
+       idx = NUM2INT(rb_Integer(ival));
+       ncomps = 0;
+       coeffs = NULL;
+       n = MoleculeGetMOCoefficients(mol, idx, &energy, &ncomps, &coeffs);
        if (n == -1)
                rb_raise(rb_eMolbyError, "Molecule is emptry");
        else if (n == -2)
-               rb_raise(rb_eMolbyError, "Low memory");
-       else if (n != 0)
-               rb_raise(rb_eMolbyError, "Unknown error");
+               rb_raise(rb_eMolbyError, "No basis set information is present");
+       else if (n == -3)
+               return Qnil;  /*  Silently returns nil  */
+       retval = rb_ary_new2(ncomps);
+       for (n = 0; n < ncomps; n++)
+               rb_ary_store(retval, n, rb_float_new(coeffs[n]));
+       free(coeffs);
+       return retval;
+}
+
+/*
+ *  call-seq:
+ *     get_mo_energy(idx)
+ *
+ *  To be used internally. Get the MO energy for the given MO index (1-based).
+ */
+static VALUE
+s_Molecule_GetMOEnergy(VALUE self, VALUE ival)
+{
+       Molecule *mol;
+       Int idx, n;
+       Double energy;
+    Data_Get_Struct(self, Molecule, mol);
+       idx = NUM2INT(rb_Integer(ival));
+       n = MoleculeGetMOCoefficients(mol, idx, &energy, NULL, NULL);
+       if (n == -1)
+               rb_raise(rb_eMolbyError, "Molecule is emptry");
+       else if (n == -2)
+               rb_raise(rb_eMolbyError, "No basis set information is present");
+       else if (n == -3)
+               return Qnil;
+       return rb_float_new(energy);
+}
+
+static VALUE sTypeSym, sAlphaSym, sBetaSym, sNcompsSym, sNshellsSym;
+
+static inline void
+s_InitMOInfoKeys(void)
+{
+       if (sTypeSym == 0) {
+               sTypeSym = ID2SYM(rb_intern("type"));
+               sAlphaSym = ID2SYM(rb_intern("alpha"));
+               sBetaSym = ID2SYM(rb_intern("beta"));
+               sNcompsSym = ID2SYM(rb_intern("ncomps"));
+               sNshellsSym = ID2SYM(rb_intern("nshells"));
+       }
+}
+
+/*
+ *  call-seq:
+ *     set_mo_info(hash)
+ *
+ *  Set the MO info. hash keys: :type=>"RHF"|"UHF"|"ROHF",
+ *  :alpha=>integer, :beta=>integer
+ */
+static VALUE
+s_Molecule_SetMOInfo(VALUE self, VALUE hval)
+{
+       Molecule *mol;
+       VALUE aval;
+       Int rflag, na, nb, n;
+       char *s;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->bset != NULL) {
+               rflag = mol->bset->rflag;
+               na = mol->bset->ne_alpha;
+               nb = mol->bset->ne_beta;
+       } else {
+               rflag = 1;
+               na = 0;
+               nb = 0;
+       }
+       if (hval != Qnil) {
+               if ((aval = rb_hash_aref(hval, sTypeSym)) != Qnil) {
+                       s = StringValuePtr(aval);
+                       if (strcasecmp(s, "RHF") == 0)
+                               rflag = 1;
+                       else if (strcasecmp(s, "UHF") == 0)
+                               rflag = 0;
+                       else if (strcasecmp(s, "ROHF") == 0)
+                               rflag = 2;
+               }
+               if ((aval = rb_hash_aref(hval, sAlphaSym)) != Qnil) {
+                       n = NUM2INT(rb_Integer(aval));
+                       if (n >= 0)
+                               na = n;
+               }
+               if ((aval = rb_hash_aref(hval, sBetaSym)) != Qnil) {
+                       n = NUM2INT(rb_Integer(aval));
+                       if (n >= 0)
+                               nb = n;
+               }
+               MoleculeSetMOInfo(mol, rflag, na, nb);
+       }
        return self;
 }
 
 /*
  *  call-seq:
+ *     get_mo_info(key)
+ *
+ *  Get the MO info. The key is as described in set_mo_info.
+ *  Read-only: :ncomps the number of components (and the number of MOs), :nshells the number of shells
+ */
+static VALUE
+s_Molecule_GetMOInfo(VALUE self, VALUE kval)
+{
+       Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->bset == NULL)
+               return Qnil;
+       if (kval == sTypeSym) {
+               switch (mol->bset->rflag) {
+                       case 0: return Ruby_NewEncodedStringValue2("UHF");
+                       case 1: return Ruby_NewEncodedStringValue2("RHF");
+                       case 2: return Ruby_NewEncodedStringValue2("ROHF");
+                       default: return rb_str_to_str(INT2NUM(mol->bset->rflag));
+               }
+       } else if (kval == sAlphaSym) {
+               return INT2NUM(mol->bset->ne_alpha);
+       } else if (kval == sBetaSym) {
+               return INT2NUM(mol->bset->ne_beta);
+       } else if (kval == sNcompsSym) {
+               return INT2NUM(mol->bset->ncomps);
+       } else if (kval == sNshellsSym) {
+               return INT2NUM(mol->bset->nshells);
+       } else {
+               kval = rb_inspect(kval);
+               rb_raise(rb_eMolbyError, "Unknown MO info key: %s", StringValuePtr(kval));
+               return Qnil;  /*  Does not reach here  */
+       }
+}
+
+/*
+ *  call-seq:
+ *     mo_type
+ *
+ *  Returns either "RHF", "UHF", or "ROHF". If no MO info is present, returns nil.
+ */
+static VALUE
+s_Molecule_MOType(VALUE self)
+{
+       return s_Molecule_GetMOInfo(self, sTypeSym);
+}
+
+#pragma mark ------ Molecular Topology ------
+
+/*
+ *  call-seq:
  *     search_equivalent_atoms(ig = nil)
  *
  *  Search equivalent atoms (within the atom group if given). Returns an array of integers.
@@ -9922,7 +11077,7 @@ s_Molecule_SearchEquivalentAtoms(int argc, VALUE *argv, VALUE self)
                return Qnil;
        rb_scan_args(argc, argv, "01", &val);
        if (val != Qnil)
-               ig = IntGroupFromValue(val);
+               ig = s_Molecule_AtomGroupFromValue(self, val);
        else ig = NULL;
        result = MoleculeSearchEquivalentAtoms(mol, ig);
        if (result == NULL)
@@ -9969,7 +11124,12 @@ s_Molecule_CreatePiAnchor(int argc, VALUE *argv, VALUE self)
        gval = *argv++;
        argc -= 2;
     Data_Get_Struct(self, Molecule, mol);
-       ig = IntGroupFromValue(gval);
+    if (gval == Qnil)
+        ig = NULL;
+    else
+        ig = s_Molecule_AtomGroupFromValue(self, gval);
+    if (ig == NULL || IntGroupGetCount(ig) == 0)
+    rb_raise(rb_eMolbyError, "atom group is not given correctly");
        memset(&a, 0, sizeof(a));
        memset(&an, 0, sizeof(an));
        strncpy(a.aname, StringValuePtr(nval), 4);
@@ -10037,6 +11197,164 @@ s_Molecule_CreatePiAnchor(int argc, VALUE *argv, VALUE self)
     return Data_Wrap_Struct(rb_cAtomRef, 0, (void (*)(void *))AtomRefRelease, aref);
 }
 
+#pragma mark ------ Molecular Properties ------
+
+/*
+ *  call-seq:
+ *     set_property(name, value[, index]) -> value
+ *     set_property(name, values, group) -> values
+ *
+ *  Set molecular property. A property is a floating-point number with a specified name,
+ *  and can be set for each frame separately. The name of the property is given as a String.
+ *  The value can be a single floating point number, which is set to the current frame.
+ *  
+ */
+static VALUE
+s_Molecule_SetProperty(int argc, VALUE *argv, VALUE self)
+{
+       Molecule *mol;
+       VALUE nval, vval, ival;
+       char *name;
+       IntGroup *ig;
+       Int i, n, idx, fidx;
+       Double *dp;
+       rb_scan_args(argc, argv, "21", &nval, &vval, &ival);
+    Data_Get_Struct(self, Molecule, mol);
+       if (rb_obj_is_kind_of(nval, rb_cNumeric)) {
+               idx = NUM2INT(rb_Integer(nval));
+               if (idx < 0 || idx >= mol->nmolprops)
+                       rb_raise(rb_eMolbyError, "The property index (%d) is out of range; should be 0..%d", idx, mol->nmolprops - 1);
+       } else {
+               name = StringValuePtr(nval);
+               idx = MoleculeLookUpProperty(mol, name);
+               if (idx < 0) {
+                       idx = MoleculeCreateProperty(mol, name);
+                       if (idx < 0)
+                               rb_raise(rb_eMolbyError, "Cannot create molecular property %s", name);
+               }
+       }
+       if (rb_obj_is_kind_of(vval, rb_cNumeric)) {
+               if (ival == Qnil)
+                       fidx = mol->cframe;
+               else {
+                       fidx = NUM2INT(rb_Integer(ival));
+                       n = MoleculeGetNumberOfFrames(mol);
+                       if (fidx < 0 || fidx >= n)
+                               rb_raise(rb_eMolbyError, "The frame index (%d) is out of range; should be 0..%d", fidx, n - 1);
+               }
+               ig = IntGroupNewWithPoints(fidx, 1, -1);
+               dp = (Double *)malloc(sizeof(Double));
+               *dp = NUM2DBL(rb_Float(vval));
+               n = 1;
+       } else {
+               vval = rb_ary_to_ary(vval);
+               ig = IntGroupFromValue(ival);
+               n = IntGroupGetCount(ig);
+               if (n == 0)
+                       rb_raise(rb_eMolbyError, "No frames are specified");
+               if (RARRAY_LEN(vval) < n)
+                       rb_raise(rb_eMolbyError, "Values are missing; at least %d values should be given", n);
+               dp = (Double *)calloc(sizeof(Double), n);
+               for (i = 0; i < n; i++)
+                       dp[i] = NUM2DBL(rb_Float(RARRAY_PTR(vval)[i]));
+       }
+       
+       MolActionCreateAndPerform(mol, gMolActionSetProperty, idx, ig, n, dp);
+       free(dp);
+       IntGroupRelease(ig);
+       return self;
+}
+
+/*
+ *  call-seq:
+ *     get_property(name[, index]) -> value
+ *     get_property(name, group) -> values
+ *
+ *  Get molecular property. In the first form, a property value for a single frame is returned.
+ *  (If index is omitted, then the value for the current frame is given)
+ *  In the second form, an array of property values for the given frames is returned.
+ *  If name is not one of known properties or a valid index integer, exception is raised.
+ */
+static VALUE
+s_Molecule_GetProperty(int argc, VALUE *argv, VALUE self)
+{
+       Molecule *mol;
+       VALUE nval, ival;
+       char *name;
+       IntGroup *ig;
+       Int i, n, idx, fidx;
+       Double *dp;
+       rb_scan_args(argc, argv, "11", &nval, &ival);
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->nmolprops == 0)
+               rb_raise(rb_eMolbyError, "The molecule has no properties");
+       if (rb_obj_is_kind_of(nval, rb_cNumeric)) {
+               idx = NUM2INT(rb_Integer(nval));
+               if (idx < 0 || idx >= mol->nmolprops)
+                       rb_raise(rb_eMolbyError, "The property index (%d) is out of range; should be 0..%d", idx, mol->nmolprops - 1);
+       } else {
+               name = StringValuePtr(nval);
+               idx = MoleculeLookUpProperty(mol, name);
+               if (idx < 0)
+                       rb_raise(rb_eMolbyError, "The molecule has no property '%s'", name);
+       }
+       if (rb_obj_is_kind_of(ival, rb_cNumeric) || ival == Qnil) {
+               if (ival == Qnil)
+                       fidx = mol->cframe;
+               else {
+                       fidx = NUM2INT(rb_Integer(ival));
+                       n = MoleculeGetNumberOfFrames(mol);
+                       if (fidx < 0 || fidx >= n)
+                               rb_raise(rb_eMolbyError, "The frame index (%d) is out of range; should be 0..%d", fidx, n - 1);
+               }
+               ig = IntGroupNewWithPoints(fidx, 1, -1);
+               ival = INT2FIX(fidx);
+               n = 1;
+       } else {
+               ig = IntGroupFromValue(ival);
+               n = IntGroupGetCount(ig);
+               if (n == 0)
+                       return rb_ary_new();
+       }
+       dp = (Double *)calloc(sizeof(Double), n);
+       MoleculeGetProperty(mol, idx, ig, dp);  
+       if (FIXNUM_P(ival))
+               ival = rb_float_new(dp[0]);
+       else {
+               ival = rb_ary_new();
+               for (i = n - 1; i >= 0; i--) {
+                       nval = rb_float_new(dp[i]);
+                       rb_ary_store(ival, i, nval);
+               }
+       }
+       free(dp);
+       IntGroupRelease(ig);
+       return ival;
+}
+
+/*
+ *  call-seq:
+ *     property_names -> Array
+ *
+ *  Get an array of property names.
+ */
+static VALUE
+s_Molecule_PropertyNames(VALUE self)
+{
+       Molecule *mol;
+       VALUE rval, nval;
+       int i;
+    Data_Get_Struct(self, Molecule, mol);
+       rval = rb_ary_new();
+       for (i = mol->nmolprops - 1; i >= 0; i--) {
+               nval = Ruby_NewEncodedStringValue2(mol->molprops[i].propname);
+               rb_ary_store(rval, i, nval);
+       }
+       return rval;
+}
+
+#pragma mark ------ Class methods ------
+
 /*
  *  call-seq:
  *     current       -> Molecule
@@ -10093,7 +11411,7 @@ s_Molecule_MoleculeAtIndex(int argc, VALUE *argv, VALUE klass)
                for (idx = 0; (mol = MoleculeCallback_moleculeAtIndex(idx)) != NULL; idx++) {
                        VALUE name;
                        MoleculeCallback_displayName(mol, buf, sizeof buf);
-                       name = rb_str_new2(buf);
+                       name = Ruby_NewEncodedStringValue2(buf);
                        if (rb_reg_match(val, name) != Qnil && --k == 0)
                                break;
                }       
@@ -10139,67 +11457,17 @@ s_Molecule_OrderedList(VALUE klass)
        Molecule *mol;
        int i;
        VALUE ary;
-       i = 0;
-       ary = rb_ary_new();
-       while ((mol = MoleculeCallback_moleculeAtOrderedIndex(i)) != NULL) {
-               rb_ary_push(ary, ValueFromMolecule(mol));
-               i++;
-       }
-       return ary;
-}
-
-/*
- *  call-seq:
- *     error_message       -> String
- *
- *  Get the error_message from the last load/save method. If no error, returns nil.
- */
-static VALUE
-s_Molecule_ErrorMessage(VALUE klass)
-{
-       if (gLoadSaveErrorMessage == NULL)
-               return Qnil;
-       else return rb_str_new2(gLoadSaveErrorMessage);
-}
-
-/*
- *  call-seq:
- *     set_error_message(String)
- *     Molecule.error_message = String
- *
- *  Get the error_message from the last load/save method. If no error, returns nil.
- */
-static VALUE
-s_Molecule_SetErrorMessage(VALUE klass, VALUE sval)
-{
-       if (gLoadSaveErrorMessage != NULL) {
-               free(gLoadSaveErrorMessage);
-               gLoadSaveErrorMessage = NULL;
-       }
-       if (sval != Qnil) {
-               sval = rb_str_to_str(sval);
-               gLoadSaveErrorMessage = strdup(StringValuePtr(sval));
-       }
-       return sval;
-}
-
-/*
- *  call-seq:
- *     self == Molecule -> boolean
- *
- *  True if the two arguments point to the same molecule.
- */
-static VALUE
-s_Molecule_Equal(VALUE self, VALUE val)
-{
-       if (rb_obj_is_kind_of(val, rb_cMolecule)) {
-               Molecule *mol1, *mol2;
-               Data_Get_Struct(self, Molecule, mol1);
-               Data_Get_Struct(val, Molecule, mol2);
-               return (mol1 == mol2 ? Qtrue : Qfalse);
-       } else return Qfalse;
+       i = 0;
+       ary = rb_ary_new();
+       while ((mol = MoleculeCallback_moleculeAtOrderedIndex(i)) != NULL) {
+               rb_ary_push(ary, ValueFromMolecule(mol));
+               i++;
+       }
+       return ary;
 }
 
+#pragma mark ------ Call Subprocess ------
+
 /*  The callback functions for call_subprocess_async  */
 static int
 s_Molecule_CallSubProcessAsync_EndCallback(Molecule *mol, int status)
@@ -10309,6 +11577,8 @@ s_Molecule_CallSubProcessAsync(int argc, VALUE *argv, VALUE self)
        return INT2NUM(n);
 }
 
+#pragma mark ====== Define Molby Classes ======
+
 void
 Init_Molby(void)
 {
@@ -10325,9 +11595,13 @@ Init_Molby(void)
 
        /*  class Molecule  */
        rb_cMolecule = rb_define_class_under(rb_mMolby, "Molecule", rb_cObject);
+
        rb_define_alloc_func(rb_cMolecule, s_Molecule_Alloc);
     rb_define_private_method(rb_cMolecule, "initialize", s_Molecule_Initialize, -1);
     rb_define_private_method(rb_cMolecule, "initialize_copy", s_Molecule_InitCopy, 1);
+       rb_define_method(rb_cMolecule, "atom_index", s_Molecule_AtomIndex, 1);
+       rb_define_method(rb_cMolecule, "==", s_Molecule_Equal, 1);
+
     rb_define_method(rb_cMolecule, "loadmbsf", s_Molecule_Loadmbsf, -1);
     rb_define_method(rb_cMolecule, "loadpsf", s_Molecule_Loadpsf, -1);
     rb_define_alias(rb_cMolecule, "loadpsfx", "loadpsf");
@@ -10338,19 +11612,25 @@ Init_Molby(void)
     rb_define_method(rb_cMolecule, "loadfchk", s_Molecule_Loadfchk, -1);
     rb_define_alias(rb_cMolecule, "loadfch", "loadfchk");
     rb_define_method(rb_cMolecule, "loaddat", s_Molecule_Loaddat, -1);
-    rb_define_method(rb_cMolecule, "molload", s_Molecule_Load, -1);
-    rb_define_method(rb_cMolecule, "molsave", s_Molecule_Save, -1);
        rb_define_method(rb_cMolecule, "savembsf", s_Molecule_Savembsf, 1);
     rb_define_method(rb_cMolecule, "savepsf", s_Molecule_Savepsf, 1);
     rb_define_alias(rb_cMolecule, "savepsfx", "savepsf");
     rb_define_method(rb_cMolecule, "savepdb", s_Molecule_Savepdb, 1);
     rb_define_method(rb_cMolecule, "savedcd", s_Molecule_Savedcd, 1);
-/*    rb_define_method(rb_cMolecule, "savetep", s_Molecule_Savetep, 1); */
+    rb_define_method(rb_cMolecule, "molload", s_Molecule_Load, -1);
+    rb_define_method(rb_cMolecule, "molsave", s_Molecule_Save, -1);
+    rb_define_method(rb_cMolecule, "set_molecule", s_Molecule_SetMolecule, 1);
+       rb_define_singleton_method(rb_cMolecule, "open", s_Molecule_Open, -1);
+       rb_define_singleton_method(rb_cMolecule, "error_message", s_Molecule_ErrorMessage, 0);
+       rb_define_singleton_method(rb_cMolecule, "set_error_message", s_Molecule_SetErrorMessage, 1);
+       rb_define_singleton_method(rb_cMolecule, "error_message=", s_Molecule_SetErrorMessage, 1);
+       
     rb_define_method(rb_cMolecule, "name", s_Molecule_Name, 0);
        rb_define_method(rb_cMolecule, "set_name", s_Molecule_SetName, 1);
     rb_define_method(rb_cMolecule, "path", s_Molecule_Path, 0);
     rb_define_method(rb_cMolecule, "dir", s_Molecule_Dir, 0);
     rb_define_method(rb_cMolecule, "inspect", s_Molecule_Inspect, 0);
+
     rb_define_method(rb_cMolecule, "atoms", s_Molecule_Atoms, 0);
     rb_define_method(rb_cMolecule, "bonds", s_Molecule_Bonds, 0);
     rb_define_method(rb_cMolecule, "angles", s_Molecule_Angles, 0);
@@ -10362,50 +11642,21 @@ Init_Molby(void)
        rb_define_method(rb_cMolecule, "nangles", s_Molecule_Nangles, 0);
        rb_define_method(rb_cMolecule, "ndihedrals", s_Molecule_Ndihedrals, 0);
        rb_define_method(rb_cMolecule, "nimpropers", s_Molecule_Nimpropers, 0);
-
-       rb_define_method(rb_cMolecule, "bond_par", s_Molecule_BondParIsObsolete, 1);
-       rb_define_method(rb_cMolecule, "angle_par", s_Molecule_BondParIsObsolete, 1);
-       rb_define_method(rb_cMolecule, "dihedral_par", s_Molecule_BondParIsObsolete, 1);
-       rb_define_method(rb_cMolecule, "improper_par", s_Molecule_BondParIsObsolete, 1);
-       rb_define_method(rb_cMolecule, "vdw_par", s_Molecule_BondParIsObsolete, 1);
-
-       rb_define_method(rb_cMolecule, "start_step", s_Molecule_StartStep, 0);
-       rb_define_method(rb_cMolecule, "start_step=", s_Molecule_SetStartStep, 1);
-       rb_define_method(rb_cMolecule, "steps_per_frame", s_Molecule_StepsPerFrame, 0);
-       rb_define_method(rb_cMolecule, "steps_per_frame=", s_Molecule_SetStepsPerFrame, 1);
-       rb_define_method(rb_cMolecule, "ps_per_step", s_Molecule_PsPerStep, 0);
-       rb_define_method(rb_cMolecule, "ps_per_step=", s_Molecule_SetPsPerStep, 1);
-       
        rb_define_method(rb_cMolecule, "nresidues", s_Molecule_Nresidues, 0);
        rb_define_method(rb_cMolecule, "nresidues=", s_Molecule_ChangeNresidues, 1);
        rb_define_method(rb_cMolecule, "max_residue_number", s_Molecule_MaxResSeq, -1);
        rb_define_method(rb_cMolecule, "min_residue_number", s_Molecule_MinResSeq, -1);
+       
        rb_define_method(rb_cMolecule, "each_atom", s_Molecule_EachAtom, -1);
-       rb_define_method(rb_cMolecule, "cell", s_Molecule_Cell, 0);
-       rb_define_method(rb_cMolecule, "cell=", s_Molecule_SetCell, -1);
-       rb_define_alias(rb_cMolecule, "set_cell", "cell=");
-       rb_define_method(rb_cMolecule, "box", s_Molecule_Box, 0);
-       rb_define_method(rb_cMolecule, "box=", s_Molecule_SetBox, 1);
-       rb_define_method(rb_cMolecule, "set_box", s_Molecule_SetBox, -2);
-       rb_define_method(rb_cMolecule, "cell_transform", s_Molecule_CellTransform, 0);
-       rb_define_method(rb_cMolecule, "cell_periodicity", s_Molecule_CellPeriodicity, 0);
-       rb_define_method(rb_cMolecule, "cell_periodicity=", s_Molecule_SetCellPeriodicity, 1);
-       rb_define_alias(rb_cMolecule, "set_cell_periodicity", "cell_periodicity=");
-       rb_define_method(rb_cMolecule, "cell_flexibility", s_Molecule_CellFlexibility, 0);
-       rb_define_method(rb_cMolecule, "cell_flexibility=", s_Molecule_SetCellFlexibility, 1);
-       rb_define_alias(rb_cMolecule, "set_cell_flexibility", "cell_flexibility=");
-       rb_define_method(rb_cMolecule, "symmetry", s_Molecule_Symmetry, 0);
-       rb_define_alias(rb_cMolecule, "symmetries", "symmetry");
-       rb_define_method(rb_cMolecule, "nsymmetries", s_Molecule_Nsymmetries, 0);
-       rb_define_method(rb_cMolecule, "add_symmetry", s_Molecule_AddSymmetry, 1);
-       rb_define_method(rb_cMolecule, "remove_symmetry", s_Molecule_RemoveSymmetry, -1);
-       rb_define_alias(rb_cMolecule, "remove_symmetries", "remove_symmetry");
+       rb_define_method(rb_cMolecule, "atom_group", s_Molecule_AtomGroup, -1);
+       rb_define_method(rb_cMolecule, "selection", s_Molecule_Selection, 0);
+       rb_define_method(rb_cMolecule, "selection=", s_Molecule_SetSelection, 1);
+       rb_define_method(rb_cMolecule, "set_undoable_selection", s_Molecule_SetUndoableSelection, 1);
+       
        rb_define_method(rb_cMolecule, "extract", s_Molecule_Extract, -1);
     rb_define_method(rb_cMolecule, "add", s_Molecule_Add, 1);
        rb_define_alias(rb_cMolecule, "+", "add");
     rb_define_method(rb_cMolecule, "remove", s_Molecule_Remove, 1);
-       rb_define_method(rb_cMolecule, "atom_group", s_Molecule_AtomGroup, -1);
-       rb_define_method(rb_cMolecule, "atom_index", s_Molecule_AtomIndex, 1);
        rb_define_method(rb_cMolecule, "create_atom", s_Molecule_CreateAnAtom, -1);
        rb_define_method(rb_cMolecule, "duplicate_atom", s_Molecule_DuplicateAnAtom, -1);
        rb_define_method(rb_cMolecule, "create_bond", s_Molecule_CreateBond, -1);
@@ -10416,6 +11667,7 @@ Init_Molby(void)
        rb_define_alias(rb_cMolecule, "assign_bond_orders", "assign_bond_order");
        rb_define_method(rb_cMolecule, "get_bond_order", s_Molecule_GetBondOrder, 1);
        rb_define_alias(rb_cMolecule, "get_bond_orders", "get_bond_order");
+       rb_define_method(rb_cMolecule, "bond_exist?", s_Molecule_BondExist, 2);
        rb_define_method(rb_cMolecule, "add_angle", s_Molecule_AddAngle, 3);
        rb_define_method(rb_cMolecule, "remove_angle", s_Molecule_RemoveAngle, 3);
        rb_define_method(rb_cMolecule, "add_dihedral", s_Molecule_AddDihedral, 4);
@@ -10425,54 +11677,75 @@ Init_Molby(void)
        rb_define_method(rb_cMolecule, "assign_residue", s_Molecule_AssignResidue, 2);
        rb_define_method(rb_cMolecule, "offset_residue", s_Molecule_OffsetResidue, 2);
        rb_define_method(rb_cMolecule, "renumber_atoms", s_Molecule_RenumberAtoms, 1);
+
+       rb_define_method(rb_cMolecule, "set_atom_attr", s_Molecule_SetAtomAttr, 3);
+       rb_define_method(rb_cMolecule, "get_atom_attr", s_Molecule_GetAtomAttr, 2);
+       rb_define_method(rb_cMolecule, "register_undo", s_Molecule_RegisterUndo, -1);
+       rb_define_method(rb_cMolecule, "undo_enabled?", s_Molecule_UndoEnabled, 0);
+       rb_define_method(rb_cMolecule, "undo_enabled=", s_Molecule_SetUndoEnabled, 1);
+
+       rb_define_method(rb_cMolecule, "center_of_mass", s_Molecule_CenterOfMass, -1);
+       rb_define_method(rb_cMolecule, "centralize", s_Molecule_Centralize, -1);
+       rb_define_method(rb_cMolecule, "bounds", s_Molecule_Bounds, -1);
+       rb_define_method(rb_cMolecule, "measure_bond", s_Molecule_MeasureBond, 2);
+       rb_define_method(rb_cMolecule, "measure_angle", s_Molecule_MeasureAngle, 3);
+       rb_define_method(rb_cMolecule, "measure_dihedral", s_Molecule_MeasureDihedral, 4);
+       rb_define_method(rb_cMolecule, "find_conflicts", s_Molecule_FindConflicts, -1);
        rb_define_method(rb_cMolecule, "find_close_atoms", s_Molecule_FindCloseAtoms, -1);
        rb_define_method(rb_cMolecule, "guess_bonds", s_Molecule_GuessBonds, -1);
-       rb_define_method(rb_cMolecule, "selection", s_Molecule_Selection, 0);
-       rb_define_method(rb_cMolecule, "selection=", s_Molecule_SetSelection, 1);
-       rb_define_method(rb_cMolecule, "set_undoable_selection", s_Molecule_SetUndoableSelection, 1);
-       rb_define_method(rb_cMolecule, "hidden_atoms", s_Molecule_HiddenAtoms, 0);  /*  obsolete  */
-       rb_define_method(rb_cMolecule, "hidden_atoms=", s_Molecule_SetHiddenAtoms, 1);  /*  obsolete  */
-       rb_define_alias(rb_cMolecule, "set_hidden_atoms", "hidden_atoms=");  /*  obsolete  */
-       rb_define_method(rb_cMolecule, "frame", s_Molecule_Frame, 0);
+
+       rb_define_method(rb_cMolecule, "cell", s_Molecule_Cell, 0);
+       rb_define_method(rb_cMolecule, "cell=", s_Molecule_SetCell, -1);
+       rb_define_alias(rb_cMolecule, "set_cell", "cell=");
+       rb_define_method(rb_cMolecule, "box", s_Molecule_Box, 0);
+       rb_define_method(rb_cMolecule, "box=", s_Molecule_SetBox, 1);
+       rb_define_method(rb_cMolecule, "set_box", s_Molecule_SetBox, -2);
+       rb_define_method(rb_cMolecule, "cell_periodicity", s_Molecule_CellPeriodicity, 0);
+       rb_define_method(rb_cMolecule, "cell_periodicity=", s_Molecule_SetCellPeriodicity, 1);
+       rb_define_alias(rb_cMolecule, "set_cell_periodicity", "cell_periodicity=");
+       rb_define_method(rb_cMolecule, "cell_flexibility", s_Molecule_CellFlexibility, 0);
+       rb_define_method(rb_cMolecule, "cell_flexibility=", s_Molecule_SetCellFlexibility, 1);
+       rb_define_alias(rb_cMolecule, "set_cell_flexibility", "cell_flexibility=");
+       rb_define_method(rb_cMolecule, "cell_transform", s_Molecule_CellTransform, 0);
+       rb_define_method(rb_cMolecule, "symmetry", s_Molecule_Symmetry, 0);
+       rb_define_alias(rb_cMolecule, "symmetries", "symmetry");
+       rb_define_method(rb_cMolecule, "nsymmetries", s_Molecule_Nsymmetries, 0);
+       rb_define_method(rb_cMolecule, "add_symmetry", s_Molecule_AddSymmetry, 1);
+       rb_define_method(rb_cMolecule, "remove_symmetry", s_Molecule_RemoveSymmetry, -1);
+       rb_define_alias(rb_cMolecule, "remove_symmetries", "remove_symmetry");
+       rb_define_method(rb_cMolecule, "wrap_unit_cell", s_Molecule_WrapUnitCell, 1);
+       rb_define_method(rb_cMolecule, "expand_by_symmetry", s_Molecule_ExpandBySymmetry, -1);
+       rb_define_method(rb_cMolecule, "amend_by_symmetry", s_Molecule_AmendBySymmetry, -1);
+
+       rb_define_method(rb_cMolecule, "translate", s_Molecule_Translate, -1);
+       rb_define_method(rb_cMolecule, "rotate", s_Molecule_Rotate, -1);
+       rb_define_method(rb_cMolecule, "reflect", s_Molecule_Reflect, -1);
+       rb_define_method(rb_cMolecule, "invert", s_Molecule_Invert, -1);
+       rb_define_method(rb_cMolecule, "transform", s_Molecule_Transform, -1);
+       rb_define_method(rb_cMolecule, "transform_for_symop", s_Molecule_TransformForSymop, -1);
+       rb_define_method(rb_cMolecule, "symop_for_transform", s_Molecule_SymopForTransform, -1);
+
        rb_define_method(rb_cMolecule, "frame=", s_Molecule_SelectFrame, 1);
        rb_define_alias(rb_cMolecule, "select_frame", "frame=");
+       rb_define_method(rb_cMolecule, "frame", s_Molecule_Frame, 0);
        rb_define_method(rb_cMolecule, "nframes", s_Molecule_Nframes, 0);
-       rb_define_method(rb_cMolecule, "create_frame", s_Molecule_CreateFrames, -1);
        rb_define_method(rb_cMolecule, "insert_frame", s_Molecule_InsertFrames, -1);
+       rb_define_method(rb_cMolecule, "create_frame", s_Molecule_CreateFrames, -1);
        rb_define_method(rb_cMolecule, "remove_frame", s_Molecule_RemoveFrames, -1);
        rb_define_alias(rb_cMolecule, "create_frames", "create_frame");
        rb_define_alias(rb_cMolecule, "insert_frames", "insert_frame");
        rb_define_alias(rb_cMolecule, "remove_frames", "remove_frame");
        rb_define_method(rb_cMolecule, "each_frame", s_Molecule_EachFrame, 0);
        rb_define_method(rb_cMolecule, "get_coord_from_frame", s_Molecule_GetCoordFromFrame, -1);
-       rb_define_method(rb_cMolecule, "register_undo", s_Molecule_RegisterUndo, -1);
-       rb_define_method(rb_cMolecule, "undo_enabled?", s_Molecule_UndoEnabled, 0);
-       rb_define_method(rb_cMolecule, "undo_enabled=", s_Molecule_SetUndoEnabled, 1);
-       rb_define_method(rb_cMolecule, "set_atom_attr", s_Molecule_SetAtomAttr, 3);
-       rb_define_method(rb_cMolecule, "get_atom_attr", s_Molecule_GetAtomAttr, 2);
+       rb_define_method(rb_cMolecule, "reorder_frames", s_Molecule_ReorderFrames, 1);
+
        rb_define_method(rb_cMolecule, "fragment", s_Molecule_Fragment, -1);
        rb_define_method(rb_cMolecule, "fragments", s_Molecule_Fragments, -1);
        rb_define_method(rb_cMolecule, "each_fragment", s_Molecule_EachFragment, -1);
        rb_define_method(rb_cMolecule, "detachable?", s_Molecule_Detachable_P, 1);
        rb_define_method(rb_cMolecule, "bonds_on_border", s_Molecule_BondsOnBorder, -1);
-       rb_define_method(rb_cMolecule, "translate", s_Molecule_Translate, -1);
-       rb_define_method(rb_cMolecule, "rotate", s_Molecule_Rotate, -1);
-       rb_define_method(rb_cMolecule, "reflect", s_Molecule_Reflect, -1);
-       rb_define_method(rb_cMolecule, "invert", s_Molecule_Invert, -1);
-       rb_define_method(rb_cMolecule, "transform", s_Molecule_Transform, -1);
-       rb_define_method(rb_cMolecule, "center_of_mass", s_Molecule_CenterOfMass, -1);
-       rb_define_method(rb_cMolecule, "centralize", s_Molecule_Centralize, -1);
-       rb_define_method(rb_cMolecule, "bounds", s_Molecule_Bounds, -1);
-       rb_define_method(rb_cMolecule, "measure_bond", s_Molecule_MeasureBond, 2);
-       rb_define_method(rb_cMolecule, "measure_angle", s_Molecule_MeasureAngle, 3);
-       rb_define_method(rb_cMolecule, "measure_dihedral", s_Molecule_MeasureDihedral, 4);
-       rb_define_method(rb_cMolecule, "expand_by_symmetry", s_Molecule_ExpandBySymmetry, -1);
-       rb_define_method(rb_cMolecule, "amend_by_symmetry", s_Molecule_AmendBySymmetry, -1);
-       rb_define_method(rb_cMolecule, "transform_for_symop", s_Molecule_TransformForSymop, -1);
-       rb_define_method(rb_cMolecule, "symop_for_transform", s_Molecule_SymopForTransform, -1);
-       rb_define_method(rb_cMolecule, "wrap_unit_cell", s_Molecule_WrapUnitCell, 1);
-       rb_define_method(rb_cMolecule, "find_conflicts", s_Molecule_FindConflicts, -1);
        rb_define_method(rb_cMolecule, "fit_coordinates", s_Molecule_FitCoordinates, -1);
+
        rb_define_method(rb_cMolecule, "display", s_Molecule_Display, 0);
        rb_define_method(rb_cMolecule, "make_front", s_Molecule_MakeFront, 0);
        rb_define_method(rb_cMolecule, "update_enabled?", s_Molecule_UpdateEnabled, 0);
@@ -10484,15 +11757,20 @@ Init_Molby(void)
        rb_define_method(rb_cMolecule, "show_dummy_atoms=", s_Molecule_ShowDummyAtoms, -1);
        rb_define_method(rb_cMolecule, "show_expanded", s_Molecule_ShowExpanded, -1);
        rb_define_method(rb_cMolecule, "show_expanded=", s_Molecule_ShowExpanded, -1);
-       rb_define_method(rb_cMolecule, "is_atom_visible", s_Molecule_IsAtomVisible, 1);
        rb_define_method(rb_cMolecule, "show_ellipsoids", s_Molecule_ShowEllipsoids, -1);
        rb_define_method(rb_cMolecule, "show_ellipsoids=", s_Molecule_ShowEllipsoids, -1);
+       rb_define_method(rb_cMolecule, "is_atom_visible", s_Molecule_IsAtomVisible, 1);
+       rb_define_method(rb_cMolecule, "hidden_atoms", s_Molecule_HiddenAtoms, 0);  /*  obsolete  */
+       rb_define_method(rb_cMolecule, "hidden_atoms=", s_Molecule_SetHiddenAtoms, 1);  /*  obsolete  */
+       rb_define_alias(rb_cMolecule, "set_hidden_atoms", "hidden_atoms=");  /*  obsolete  */
        rb_define_method(rb_cMolecule, "show_graphite", s_Molecule_ShowGraphite, -1);
        rb_define_method(rb_cMolecule, "show_graphite=", s_Molecule_ShowGraphite, -1);
        rb_define_method(rb_cMolecule, "show_graphite?", s_Molecule_ShowGraphiteFlag, 0);
        rb_define_method(rb_cMolecule, "show_periodic_image", s_Molecule_ShowPeriodicImage, -1);
        rb_define_method(rb_cMolecule, "show_periodic_image=", s_Molecule_ShowPeriodicImage, -1);
        rb_define_method(rb_cMolecule, "show_periodic_image?", s_Molecule_ShowPeriodicImageFlag, 0);
+       rb_define_method(rb_cMolecule, "show_rotation_center", s_Molecule_ShowRotationCenter, -1);
+       rb_define_method(rb_cMolecule, "show_rotation_center=", s_Molecule_ShowRotationCenter, -1);
        rb_define_alias(rb_cMolecule, "show_unitcell=", "show_unitcell");
        rb_define_alias(rb_cMolecule, "show_unit_cell", "show_unitcell");
        rb_define_alias(rb_cMolecule, "show_unit_cell=", "show_unitcell");
@@ -10502,10 +11780,18 @@ Init_Molby(void)
        rb_define_alias(rb_cMolecule, "show_ellipsoids=", "show_ellipsoids");
        rb_define_alias(rb_cMolecule, "show_graphite=", "show_graphite");
        rb_define_alias(rb_cMolecule, "show_periodic_image=", "show_periodic_image");
+       rb_define_alias(rb_cMolecule, "show_rotation_center=", "show_rotation_center");
        rb_define_method(rb_cMolecule, "line_mode", s_Molecule_LineMode, -1);
        rb_define_alias(rb_cMolecule, "line_mode=", "line_mode");
+       rb_define_method(rb_cMolecule, "atom_radius", s_Molecule_AtomRadius, -1);
+       rb_define_alias(rb_cMolecule, "atom_radius=", "atom_radius");
+       rb_define_method(rb_cMolecule, "bond_radius", s_Molecule_BondRadius, -1);
+       rb_define_alias(rb_cMolecule, "bond_radius=", "bond_radius");
+       rb_define_method(rb_cMolecule, "atom_resolution", s_Molecule_AtomResolution, -1);
+       rb_define_alias(rb_cMolecule, "atom_resolution=", "atom_resolution");
+       rb_define_method(rb_cMolecule, "bond_resolution", s_Molecule_BondResolution, -1);
+       rb_define_alias(rb_cMolecule, "bond_resolution=", "bond_resolution");
        rb_define_method(rb_cMolecule, "resize_to_fit", s_Molecule_ResizeToFit, 0);
-#if 1 || !defined(__CMDMAC__)
        rb_define_method(rb_cMolecule, "get_view_rotation", s_Molecule_GetViewRotation, 0);
        rb_define_method(rb_cMolecule, "get_view_scale", s_Molecule_GetViewScale, 0);
        rb_define_method(rb_cMolecule, "get_view_center", s_Molecule_GetViewCenter, 0);
@@ -10513,42 +11799,74 @@ Init_Molby(void)
        rb_define_method(rb_cMolecule, "set_view_scale", s_Molecule_SetViewScale, 1);
        rb_define_method(rb_cMolecule, "set_view_center", s_Molecule_SetViewCenter, 1);
        rb_define_method(rb_cMolecule, "set_background_color", s_Molecule_SetBackgroundColor, -1);
+       rb_define_method(rb_cMolecule, "export_graphic", s_Molecule_ExportGraphic, -1);
+       
+       rb_define_method(rb_cMolecule, "insert_graphic", s_Molecule_InsertGraphic, -1);
        rb_define_method(rb_cMolecule, "create_graphic", s_Molecule_CreateGraphic, -1);
        rb_define_method(rb_cMolecule, "remove_graphic", s_Molecule_RemoveGraphic, 1);
        rb_define_method(rb_cMolecule, "ngraphics", s_Molecule_NGraphics, 0);
-       rb_define_method(rb_cMolecule, "set_graphic_point", s_Molecule_SetGraphicPoint, 3);
+       rb_define_method(rb_cMolecule, "get_graphic_point", s_Molecule_GetGraphicPoint, -1);
+       rb_define_method(rb_cMolecule, "set_graphic_point", s_Molecule_SetGraphicPoint, -1);
+       rb_define_alias(rb_cMolecule, "get_graphic_points", "get_graphic_point");
+       rb_define_alias(rb_cMolecule, "set_graphic_points", "set_graphic_point");
+       rb_define_method(rb_cMolecule, "get_graphic_color", s_Molecule_SetGraphicColor, 1);
        rb_define_method(rb_cMolecule, "set_graphic_color", s_Molecule_SetGraphicColor, 2);
        rb_define_method(rb_cMolecule, "show_graphic", s_Molecule_ShowGraphic, 1);
        rb_define_method(rb_cMolecule, "hide_graphic", s_Molecule_HideGraphic, 1);
-#endif
        rb_define_method(rb_cMolecule, "show_text", s_Molecule_ShowText, 1);
+
        rb_define_method(rb_cMolecule, "md_arena", s_Molecule_MDArena, 0);
        rb_define_method(rb_cMolecule, "set_parameter_attr", s_Molecule_SetParameterAttr, 5);
        rb_define_method(rb_cMolecule, "parameter", s_Molecule_Parameter, 0);
+       rb_define_method(rb_cMolecule, "start_step", s_Molecule_StartStep, 0);
+       rb_define_method(rb_cMolecule, "start_step=", s_Molecule_SetStartStep, 1);
+       rb_define_method(rb_cMolecule, "steps_per_frame", s_Molecule_StepsPerFrame, 0);
+       rb_define_method(rb_cMolecule, "steps_per_frame=", s_Molecule_SetStepsPerFrame, 1);
+       rb_define_method(rb_cMolecule, "ps_per_step", s_Molecule_PsPerStep, 0);
+       rb_define_method(rb_cMolecule, "ps_per_step=", s_Molecule_SetPsPerStep, 1);
+       rb_define_method(rb_cMolecule, "bond_par", s_Molecule_BondParIsObsolete, 1); /* Obsolete */
+       rb_define_method(rb_cMolecule, "angle_par", s_Molecule_BondParIsObsolete, 1); /* Obsolete */
+       rb_define_method(rb_cMolecule, "dihedral_par", s_Molecule_BondParIsObsolete, 1); /* Obsolete */
+       rb_define_method(rb_cMolecule, "improper_par", s_Molecule_BondParIsObsolete, 1); /* Obsolete */
+       rb_define_method(rb_cMolecule, "vdw_par", s_Molecule_BondParIsObsolete, 1); /* Obsolete */
+               
        rb_define_method(rb_cMolecule, "selected_MO", s_Molecule_SelectedMO, 0);
        rb_define_method(rb_cMolecule, "default_MO_grid", s_Molecule_GetDefaultMOGrid, -1);
        rb_define_method(rb_cMolecule, "cubegen", s_Molecule_Cubegen, -1);
+       rb_define_method(rb_cMolecule, "clear_surface", s_Molecule_ClearSurface, 0);
+       rb_define_method(rb_cMolecule, "show_surface", s_Molecule_ShowSurface, 0);
+       rb_define_method(rb_cMolecule, "hide_surface", s_Molecule_HideSurface, 0);
+       rb_define_method(rb_cMolecule, "create_surface", s_Molecule_CreateSurface, -1);
+       rb_define_method(rb_cMolecule, "set_surface_attr", s_Molecule_SetSurfaceAttr, 1);
        rb_define_method(rb_cMolecule, "nelpots", s_Molecule_NElpots, 0);
        rb_define_method(rb_cMolecule, "elpot", s_Molecule_Elpot, 1);
+       rb_define_method(rb_cMolecule, "clear_basis_set", s_Molecule_ClearBasisSet, 0);
        rb_define_method(rb_cMolecule, "add_gaussian_orbital_shell", s_Molecule_AddGaussianOrbitalShell, 3);
        rb_define_method(rb_cMolecule, "add_gaussian_primitive_coefficients", s_Molecule_AddGaussianPrimitiveCoefficients, 3);
-       rb_define_method(rb_cMolecule, "mo_type", s_Molecule_MOType, 0);
+       rb_define_method(rb_cMolecule, "get_gaussian_shell_info", s_Molecule_GetGaussianShellInfo, 1);
+       rb_define_method(rb_cMolecule, "get_gaussian_primitive_coefficients", s_Molecule_GetGaussianPrimitiveCoefficients, 1);
+       rb_define_method(rb_cMolecule, "get_gaussian_component_info", s_Molecule_GetGaussianComponentInfo, 1);
+       rb_define_method(rb_cMolecule, "clear_mo_coefficients", s_Molecule_ClearMOCoefficients, 0);
        rb_define_method(rb_cMolecule, "set_mo_coefficients", s_Molecule_SetMOCoefficients, 3);
-       rb_define_method(rb_cMolecule, "allocate_basis_set_record", s_Molecule_AllocateBasisSetRecord, 3);
+       rb_define_method(rb_cMolecule, "get_mo_coefficients", s_Molecule_GetMOCoefficients, 1);
+       rb_define_method(rb_cMolecule, "get_mo_energy", s_Molecule_GetMOEnergy, 1);
+       rb_define_method(rb_cMolecule, "set_mo_info", s_Molecule_SetMOInfo, 1);
+       rb_define_method(rb_cMolecule, "get_mo_info", s_Molecule_GetMOInfo, 1);
+       rb_define_method(rb_cMolecule, "mo_type", s_Molecule_MOType, 0);
+
        rb_define_method(rb_cMolecule, "search_equivalent_atoms", s_Molecule_SearchEquivalentAtoms, -1);
-       
        rb_define_method(rb_cMolecule, "create_pi_anchor", s_Molecule_CreatePiAnchor, -1);
-       rb_define_method(rb_cMolecule, "==", s_Molecule_Equal, 1);
-       rb_define_method(rb_cMolecule, "call_subprocess_async", s_Molecule_CallSubProcessAsync, -1);
-
+       
+       rb_define_method(rb_cMolecule, "set_property", s_Molecule_SetProperty, -1);
+       rb_define_method(rb_cMolecule, "get_property", s_Molecule_GetProperty, -1);
+       rb_define_method(rb_cMolecule, "property_names", s_Molecule_PropertyNames, 0);
+               
        rb_define_singleton_method(rb_cMolecule, "current", s_Molecule_Current, 0);
        rb_define_singleton_method(rb_cMolecule, "[]", s_Molecule_MoleculeAtIndex, -1);
-       rb_define_singleton_method(rb_cMolecule, "open", s_Molecule_Open, -1);
        rb_define_singleton_method(rb_cMolecule, "list", s_Molecule_List, 0);
        rb_define_singleton_method(rb_cMolecule, "ordered_list", s_Molecule_OrderedList, 0);
-       rb_define_singleton_method(rb_cMolecule, "error_message", s_Molecule_ErrorMessage, 0);
-       rb_define_singleton_method(rb_cMolecule, "set_error_message", s_Molecule_SetErrorMessage, 1);
-       rb_define_singleton_method(rb_cMolecule, "error_message=", s_Molecule_SetErrorMessage, 1);
+       
+       rb_define_method(rb_cMolecule, "call_subprocess_async", s_Molecule_CallSubProcessAsync, -1);
        
        /*  class MolEnumerable  */
        rb_cMolEnumerable = rb_define_class_under(rb_mMolby, "MolEnumerable", rb_cObject);
@@ -10574,7 +11892,7 @@ Init_Molby(void)
        rb_define_alias(rb_cAtomRef, "set_attr", "[]=");
        rb_define_method(rb_cAtomRef, "[]", s_AtomRef_GetAttr, 1);
        rb_define_alias(rb_cAtomRef, "get_attr", "[]");
-       s_SetAtomAttrString = rb_str_new2("set_atom_attr");
+       s_SetAtomAttrString = Ruby_NewEncodedStringValue2("set_atom_attr");
        rb_global_variable(&s_SetAtomAttrString);
        rb_define_method(rb_cAtomRef, "molecule", s_AtomRef_GetMolecule, 0);
        rb_define_method(rb_cAtomRef, "==", s_AtomRef_Equal, 1);
@@ -10683,7 +12001,7 @@ Init_Molby(void)
        rb_define_method(rb_mKernel, "set_progress_value", s_SetProgressValue, 1);
        rb_define_method(rb_mKernel, "set_progress_message", s_SetProgressMessage, 1);
        rb_define_method(rb_mKernel, "ask", s_Kernel_Ask, -1);
-       rb_define_method(rb_mKernel, "register_menu", s_Kernel_RegisterMenu, 2);
+       rb_define_method(rb_mKernel, "register_menu", s_Kernel_RegisterMenu, -1);
        rb_define_method(rb_mKernel, "lookup_menu", s_Kernel_LookupMenu, 1);
        rb_define_method(rb_mKernel, "get_global_settings", s_Kernel_GetGlobalSettings, 1);
        rb_define_method(rb_mKernel, "set_global_settings", s_Kernel_SetGlobalSettings, 2);
@@ -10698,41 +12016,114 @@ Init_Molby(void)
        rb_define_method(rb_mKernel, "bell", s_Kernel_Bell, 0);
        rb_define_method(rb_mKernel, "play_sound", s_Kernel_PlaySound, -1);
        rb_define_method(rb_mKernel, "stop_sound", s_Kernel_StopSound, 0);
-
+       rb_define_method(rb_mKernel, "export_to_clipboard", s_Kernel_ExportToClipboard, 1);
+    rb_define_method(rb_mKernel, "hartree_to_kcal", s_Kernel_HartreeToKcal, 1);
+    rb_define_method(rb_mKernel, "hartree_to_kj", s_Kernel_HartreeToKJ, 1);
+    rb_define_method(rb_mKernel, "kcal_to_hartree", s_Kernel_KcalToHartree, 1);
+    rb_define_method(rb_mKernel, "kj_to_hartree", s_Kernel_KJToHartree, 1);
+    rb_define_method(rb_mKernel, "bohr_to_angstrom", s_Kernel_BohrToAngstrom, 1);
+    rb_define_method(rb_mKernel, "angstrom_to_bohr", s_Kernel_AngstromToBohr, 1);
+
+       /*  class IO  */
+       rb_define_method(rb_cIO, "gets_any_eol", s_IO_gets_any_eol, 0);
+       
        s_ID_equal = rb_intern("==");
        g_RubyID_call = rb_intern("call");
+       
+       s_InitMOInfoKeys();
+       
+       /*  Symbols for graphics  */
+       s_LineSym = ID2SYM(rb_intern("line"));
+       s_PolySym = ID2SYM(rb_intern("poly"));
+       s_CylinderSym = ID2SYM(rb_intern("cylinder"));
+       s_ConeSym = ID2SYM(rb_intern("cone"));
+       s_EllipsoidSym = ID2SYM(rb_intern("ellipsoid"));
+}
+
+#pragma mark ====== Interface with RubyDialog class ======
+
+RubyValue
+RubyDialogCallback_parentModule(void)
+{
+       return (RubyValue)rb_mMolby;
 }
 
 #pragma mark ====== External functions ======
 
 static VALUE s_ruby_top_self = Qfalse;
+static VALUE s_ruby_get_binding_for_molecule = Qfalse;
+static VALUE s_ruby_export_local_variables = Qfalse;
 
 static VALUE
 s_evalRubyScriptOnMoleculeSub(VALUE val)
 {
        void **ptr = (void **)val;
        Molecule *mol = (Molecule *)ptr[1];
-       VALUE sval = rb_str_new2((char *)ptr[0]);
-       VALUE fnval;
+       VALUE sval, fnval, lnval, retval;
+       VALUE binding;
+
+       /*  Clear the error information (store in the history array if necessary)  */
+       sval = rb_errinfo();
+       if (sval != Qnil) {
+               rb_eval_string("$error_history.push([$!.to_s, $!.backtrace])");
+               rb_set_errinfo(Qnil);
+       }
+
        if (s_ruby_top_self == Qfalse) {
                s_ruby_top_self = rb_eval_string("eval(\"self\",TOPLEVEL_BINDING)");
        }
+       if (s_ruby_get_binding_for_molecule == Qfalse) {
+               const char *s1 =
+                "lambda { |_mol_, _bind_| \n"
+                "  _proc_ = eval(\"lambda { |__mol__| __mol__.instance_eval { binding } } \", _bind_) \n"
+                "  _proc_.call(_mol_) } ";
+               s_ruby_get_binding_for_molecule = rb_eval_string(s1);
+               rb_define_variable("_get_binding_for_molecule", &s_ruby_get_binding_for_molecule);
+       }
+       if (s_ruby_export_local_variables == Qfalse) {
+               const char *s2 =
+               "lambda { |_bind_| \n"
+               "   # find local variables newly defined in _bind_ \n"
+               " _a_ = _bind_.eval(\"local_variables\") - TOPLEVEL_BINDING.eval(\"local_variables\"); \n"
+               " _a_.each { |_vsym_| \n"
+               "   _vname_ = _vsym_.to_s \n"
+               "   _vval_ = _bind_.eval(_vname_) \n"
+               "   #  Define local variable \n"
+               "   TOPLEVEL_BINDING.eval(_vname_ + \" = nil\") \n"
+               "   #  Then set value  \n"
+               "   TOPLEVEL_BINDING.eval(\"lambda { |_m_| \" + _vname_ + \" = _m_ }\").call(_vval_) \n"
+               " } \n"
+               "}";
+               s_ruby_export_local_variables = rb_eval_string(s2);
+               rb_define_variable("_export_local_variables", &s_ruby_export_local_variables);
+       }
        if (ptr[2] == NULL) {
-               fnval = Qnil;
+               char *scr;
+               /*  String literal: we need to specify string encoding  */
+#if defined(__WXMSW__)
+               asprintf(&scr, "#coding:shift_jis\n%s", (char *)ptr[0]);
+#else
+               asprintf(&scr, "#coding:utf-8\n%s", (char *)ptr[0]);
+#endif
+               sval = Ruby_NewEncodedStringValue2(scr);
+               free(scr);
+               fnval = Ruby_NewEncodedStringValue2("(eval)");
+               lnval = INT2FIX(0);
        } else {
+               sval = Ruby_NewEncodedStringValue2((char *)ptr[0]);
                fnval = Ruby_NewFileStringValue((char *)ptr[2]);
+               lnval = INT2FIX(1);
        }
-       if (mol == NULL) {
-               if (fnval == Qnil)
-                       return rb_funcall(s_ruby_top_self, rb_intern("eval"), 1, sval);
-               else
-                       return rb_funcall(s_ruby_top_self, rb_intern("eval"), 4, sval, Qnil, fnval, INT2FIX(1));
-       } else {
+       binding = rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING"));
+       if (mol != NULL) {
                VALUE mval = ValueFromMolecule(mol);
-               if (fnval == Qnil)
-                       return rb_funcall(mval, rb_intern("instance_eval"), 1, sval);
-               else return rb_funcall(mval, rb_intern("instance_eval"), 3, sval, fnval, INT2FIX(1));
+               binding = rb_funcall(s_ruby_get_binding_for_molecule, rb_intern("call"), 2, mval, binding);
        }
+       retval = rb_funcall(binding, rb_intern("eval"), 3, sval, fnval, lnval);
+       if (mol != NULL) {
+               rb_funcall(s_ruby_export_local_variables, rb_intern("call"), 1, binding);
+       }
+       return retval;
 }
 
 RubyValue
@@ -10741,8 +12132,8 @@ Molby_evalRubyScriptOnMolecule(const char *script, Molecule *mol, const char *fn
        RubyValue retval;
        void *args[3];
        VALUE save_interrupt_flag;
-       char *save_ruby_sourcefile;
-       int save_ruby_sourceline;
+/*     char *save_ruby_sourcefile;
+       int save_ruby_sourceline; */
        if (gMolbyIsCheckingInterrupt) {
                MolActionAlertRubyIsRunning();
                *status = -1;
@@ -10753,8 +12144,8 @@ Molby_evalRubyScriptOnMolecule(const char *script, Molecule *mol, const char *fn
        args[1] = (void *)mol;
        args[2] = (void *)fname;
        save_interrupt_flag = s_SetInterruptFlag(Qnil, Qtrue);
-       save_ruby_sourcefile = ruby_sourcefile;
-       save_ruby_sourceline = ruby_sourceline;
+/*     save_ruby_sourcefile = ruby_sourcefile;
+       save_ruby_sourceline = ruby_sourceline; */
        retval = (RubyValue)rb_protect(s_evalRubyScriptOnMoleculeSub, (VALUE)args, status);
        if (*status != 0) {
                /*  Is this 'exit' exception?  */
@@ -10763,22 +12154,43 @@ Molby_evalRubyScriptOnMolecule(const char *script, Molecule *mol, const char *fn
                        /*  Capture exit and return the status value  */
                        retval = (RubyValue)rb_funcall(last_exception, rb_intern("status"), 0);
                        *status = 0;
+                       rb_set_errinfo(Qnil);
                }
        }
        s_SetInterruptFlag(Qnil, save_interrupt_flag);
-       ruby_sourcefile = save_ruby_sourcefile;
-       ruby_sourceline = save_ruby_sourceline;
+/*     ruby_sourcefile = save_ruby_sourcefile;
+       ruby_sourceline = save_ruby_sourceline; */
        gMolbyRunLevel--;
        return retval;
 }
 
-void
-Molby_showRubyValue(RubyValue value, char **outValueString)
+/*  For debug  */
+char *
+Ruby_inspectValue(RubyValue value)
+{
+    int status;
+    static char buf[256];
+    VALUE val = (VALUE)value;
+    gMolbyRunLevel++;
+    val = rb_protect(rb_inspect, val, &status);
+    gMolbyRunLevel--;
+    if (status == 0) {
+        char *str = StringValuePtr(val);
+        strncpy(buf, str, sizeof(buf) - 1);
+        buf[sizeof(buf) - 1] = 0;
+    } else {
+        snprintf(buf, sizeof(buf), "Error status = %d", status);
+    }
+    return buf;
+}
+
+int
+Ruby_showValue(RubyValue value, char **outValueString)
 {
        VALUE val = (VALUE)value;
        if (gMolbyIsCheckingInterrupt) {
                MolActionAlertRubyIsRunning();
-               return;
+               return 0;
        }
        if (val != Qnil) {
                int status;
@@ -10786,73 +12198,136 @@ Molby_showRubyValue(RubyValue value, char **outValueString)
                gMolbyRunLevel++;
                val = rb_protect(rb_inspect, val, &status);
                gMolbyRunLevel--;
+               if (status != 0)
+                       return status;
                str = StringValuePtr(val);
                if (outValueString != NULL)
                        *outValueString = strdup(str);
                MyAppCallback_showScriptMessage("%s", str);
+       } else {
+               if (outValueString != NULL)
+                       *outValueString = NULL;
        }
+       return 0;
 }
 
 void
-Molby_showError(int status)
+Ruby_showError(int status)
 {
        static const int tag_raise = 6;
+    char *main_message = "Molby script error";
        char *msg = NULL, *msg2;
        VALUE val, backtrace;
        int interrupted = 0;
+    int exit_status = -1;
        if (status == tag_raise) {
-               VALUE eclass = CLASS_OF(ruby_errinfo);
+               VALUE errinfo = rb_errinfo();
+               VALUE eclass = CLASS_OF(errinfo);
                if (eclass == rb_eInterrupt) {
-                       msg = "Interrupt";
+            main_message = "Molby script interrupted";
+            msg = "Interrupt";
                        interrupted = 1;
-               }
+        } else if (eclass == rb_eSystemExit) {
+            main_message = "Molby script exit";
+            interrupted = 2;
+            val = rb_eval_string_protect("$!.status", &status);
+            if (status == 0) {
+                exit_status = NUM2INT(rb_Integer(val));
+                asprintf(&msg, "Molby script exit with status %d", exit_status);
+            } else {
+                asprintf(&msg, "Molby script exit with unknown status");
+            }
+        }
        }
        gMolbyRunLevel++;
-       backtrace = rb_eval_string_protect("$backtrace = $!.backtrace.join(\"\\n\")", &status);
-       if (msg == NULL) {
-               val = rb_eval_string_protect("$!.to_s", &status);
-               if (status == 0)
-                       msg = RSTRING_PTR(val);
-               else msg = "(message not available)";
-       }
-       asprintf(&msg2, "%s\n%s", msg, RSTRING_PTR(backtrace));
-       MyAppCallback_messageBox(msg2, (interrupted == 0 ? "Molby script error" : "Molby script interrupted"), 0, 3);
+    if (exit_status != 0) {
+        backtrace = rb_eval_string_protect("$backtrace = $!.backtrace.join(\"\\n\")", &status);
+        if (msg == NULL) {
+            val = rb_eval_string_protect("$!.to_s", &status);
+            if (status == 0)
+                msg = RSTRING_PTR(val);
+            else
+                msg = "(message not available)";
+        }
+        asprintf(&msg2, "%s\n%s", msg, RSTRING_PTR(backtrace));
+    } else {
+        msg2 = strdup(msg);
+    }
+       MyAppCallback_messageBox(msg2, main_message, 0, 3);
        free(msg2);
+    if (interrupted == 2) {
+        free(msg);
+        if (!gUseGUI && exit_status == 0)
+            exit(0);  // Capture exit(0) here and force exit
+    }
        gMolbyRunLevel--;
 }
 
-char *
-Molby_getDescription(void)
+/*  Wrapper function for rb_load_protect or rb_eval_string_protect. Used only in non-GUI mode.  */
+int
+Molby_loadScript(const char *script, int from_file)
+{
+    int status;
+    gMolbyRunLevel++;
+    if (from_file)
+        rb_load_protect(Ruby_NewEncodedStringValue2(script), 0, &status);
+    else
+        rb_eval_string_protect(script, &status);
+    gMolbyRunLevel--;
+    return status;
+}
+
+void
+Molby_getDescription(char **versionString, char **auxString)
 {
        extern const char *gVersionString, *gCopyrightString;
        extern int gRevisionNumber;
        extern char *gLastBuildString;
-       char *s;
+    char *s1, *s2;
        char *revisionString;
        if (gRevisionNumber > 0) {
                asprintf(&revisionString, ", revision %d", gRevisionNumber);
        } else revisionString = "";
-       asprintf(&s, 
-                        "Molby %s%s\n%s\nLast compile: %s\n"
-#if !defined(__CMDMAC__)
-                        "\nIncluding:\n"
-                        "%s"
+
+    asprintf(&s1, "%s %s%s\n%s\nLast compile: %s\n",
+#if defined(__WXMSW__)
+    #if TARGET_ARCH == 64
+             "Molby (64 bit)",
+    #else
+             "Molby (32 bit)",
+    #endif
 #else
-                        "Including "
+             "Molby",
 #endif
-                        "ruby %s, http://www.ruby-lang.org/\n"
-                        "%s\n"
-                        "FFTW 3.3.2, http://www.fftw.org/\n"
-                        "  Copyright (C) 2003, 2007-11 Matteo Frigo\n"
-                        "  and Massachusetts Institute of Technology",
-                        gVersionString, revisionString, gCopyrightString, gLastBuildString,
-#if !defined(__CMDMAC__)
-                        MyAppCallback_getGUIDescriptionString(),
-#endif
-                        gRubyVersion, gRubyCopyright);
+             gVersionString, revisionString, gCopyrightString, gLastBuildString);
+    if (gUseGUI) {
+        asprintf(&s2,
+                 "\nIncluding:\n"
+                 "%s"
+                 "ruby %s, http://www.ruby-lang.org/\n"
+                 "%s\n"
+                 "FFTW 3.3.2, http://www.fftw.org/\n"
+                 "  Copyright (C) 2003, 2007-11 Matteo Frigo"
+                 "  and Massachusetts Institute of Technology",
+                 MyAppCallback_getGUIDescriptionString(),
+                 gRubyVersion, gRubyCopyright);
+    } else {
+        asprintf(&s2,
+                 "Including "
+                 "ruby %s, http://www.ruby-lang.org/\n"
+                 "%s\n"
+                 "FFTW 3.3.2, http://www.fftw.org/\n"
+                 "  Copyright (C) 2003, 2007-11 Matteo Frigo"
+                 "  and Massachusetts Institute of Technology",
+                 gRubyVersion, gRubyCopyright);
+
+    }
        if (revisionString[0] != 0)
                free(revisionString);
-       return s;
+       if (versionString != NULL)
+        *versionString = s1;
+    if (auxString != NULL)
+        *auxString = s2;
 }
 
 void
@@ -10867,11 +12342,7 @@ Molby_startup(const char *script, const char *dir)
        {
                gRubyVersion = strdup(ruby_version);
                asprintf(&gRubyCopyright, "%sCopyright (C) %d-%d %s",
-#if defined(__CMDMAC__)
-                                "",
-#else
                                 "  ",  /*  Indent for displaying in About dialog  */
-#endif
                                 RUBY_BIRTH_YEAR, RUBY_RELEASE_YEAR, RUBY_AUTHOR);
        }
        
@@ -10904,40 +12375,71 @@ Molby_startup(const char *script, const char *dir)
                }
     } */
 
-#if defined(__CMDMAC__)
-       wbuf = Molby_getDescription();
-       printf("%s\n", wbuf);
-       free(wbuf);
-#endif
+    if (!gUseGUI) {
+        char *wbuf2;
+        Molby_getDescription(&wbuf, &wbuf2);
+        MyAppCallback_showScriptMessage("%s\n%s\n", wbuf, wbuf2);
+        free(wbuf);
+        free(wbuf2);
+    }
        
        /*  Read atom display parameters  */
        if (ElementParameterInitialize("element.par", &wbuf) != 0) {
-#if defined(__CMDMAC__)
-               fprintf(stderr, "%s\n", wbuf);
-#else
-               MyAppCallback_setConsoleColor(1);
-               MyAppCallback_showScriptMessage("%s", wbuf);
-               MyAppCallback_setConsoleColor(0);
-#endif
+        MyAppCallback_setConsoleColor(1);
+        MyAppCallback_showScriptMessage("%s", wbuf);
+        MyAppCallback_setConsoleColor(0);
                free(wbuf);
        }
        
        /*  Read default parameters  */
        ParameterReadFromFile(gBuiltinParameters, "default.par", &wbuf, NULL);
        if (wbuf != NULL) {
-#if defined(__CMDMAC__)
-               fprintf(stderr, "%s\n", wbuf);
-#else
-               MyAppCallback_setConsoleColor(1);
-               MyAppCallback_showScriptMessage("%s", wbuf);
-               MyAppCallback_setConsoleColor(0);
-#endif
+        MyAppCallback_setConsoleColor(1);
+        MyAppCallback_showScriptMessage("%s", wbuf);
+        MyAppCallback_setConsoleColor(0);
                free(wbuf);
        }
                
        /*  Initialize Ruby interpreter  */
+#if __WXMSW__
+       if (gUseGUI) {
+               /*  On Windows, fileno(stdin|stdout|stderr) returns -2 and
+                   it causes rb_bug() (= fatal error) during ruby_init().
+                   As a workaround, these standard streams are reopend as
+                   NUL stream.  */
+               freopen("NUL", "r", stdin);
+               freopen("NUL", "w", stdout);
+               freopen("NUL", "w", stderr);
+       }
+#endif
        ruby_init();
-       
+
+       {
+        /*  Initialize CP932/Windows-31J encodings  */
+               extern void Init_shift_jis(void), Init_windows_31j(void),  Init_trans_japanese_sjis(void);
+        extern int rb_enc_alias(const char *, const char *);
+        Init_shift_jis();
+        Init_windows_31j();
+        Init_trans_japanese_sjis();
+        rb_enc_alias("CP932", "Windows-31J");
+    }
+    
+#if defined(__WXMSW__)
+    {
+        /*  Set default external encoding  */
+        /*  The following snippet is taken from encoding.c  */
+        extern void rb_enc_set_default_external(VALUE encoding);
+        char cp[sizeof(int) * 8 / 3 + 22];
+        int status;
+        VALUE enc;
+        snprintf(cp, sizeof cp, "Encoding.find('CP%d')", AreFileApisANSI() ? GetACP() : GetOEMCP());
+        enc = rb_eval_string_protect(cp, &status);
+        if (status == 0 && !NIL_P(enc)) {
+            rb_enc_set_default_external(enc);
+        }
+       }
+#endif
+
        /*  Initialize loadpath; the specified directory, "lib" subdirectory, and "."  */
        ruby_incpush(".");
        asprintf(&libpath, "%s%clib", dir, PATH_SEPARATOR);
@@ -10960,7 +12462,8 @@ Molby_startup(const char *script, const char *dir)
 
        /*  Define Molby classes  */
        Init_Molby();
-       RubyDialogInitClass();
+    if (gUseGUI)
+        RubyDialogInitClass();
 
        rb_define_const(rb_mMolby, "ResourcePath", val);
        val = Ruby_NewFileStringValue(dir);
@@ -10970,52 +12473,65 @@ Molby_startup(const char *script, const char *dir)
        rb_define_const(rb_mMolby, "MbsfPath", val);    
        free(p);
        
-#if defined(__CMDMAC__)
-       rb_define_const(rb_mMolby, "HasGUI", Qfalse);
-#else
-       rb_define_const(rb_mMolby, "HasGUI", Qtrue);
-#endif
-
-#if !__CMDMAC__
-       
-       /*  Create objects for stdout and stderr  */
-       val = rb_funcall(rb_cObject, rb_intern("new"), 0);
-       rb_define_singleton_method(val, "write", s_StandardOutput, 1);
-       rb_gv_set("$stdout", val);
-       val = rb_funcall(rb_cObject, rb_intern("new"), 0);
-       rb_define_singleton_method(val, "write", s_StandardErrorOutput, 1);
-       rb_gv_set("$stderr", val);
-
-       /*  Create objects for stdin  */
-       val = rb_funcall(rb_cObject, rb_intern("new"), 0);
-       rb_define_singleton_method(val, "gets", s_StandardInputGets, -1);
-       rb_define_singleton_method(val, "readline", s_StandardInputGets, -1);
-       rb_define_singleton_method(val, "method_missing", s_StandardInputMethodMissing, -1);
-       rb_gv_set("$stdin", val);
+       p = MyAppCallback_getHomeDir();
+       val = (p == NULL ? Qnil : Ruby_NewFileStringValue(p));
+       rb_define_const(rb_mMolby, "HomeDirectory", val);
+       free(p);
+       p = MyAppCallback_getDocumentHomeDir();
+       val = (p == NULL ? Qnil : Ruby_NewFileStringValue(p));
+       rb_define_const(rb_mMolby, "DocumentDirectory", val);
+       free(p);
        
-#endif
+    if (gUseGUI)
+        rb_define_const(rb_mMolby, "HasGUI", Qtrue);
+    else
+        rb_define_const(rb_mMolby, "HasGUI", Qfalse);
+
+    {
+        /*  Create objects for stdout and stderr  */
+        val = rb_funcall(rb_cObject, rb_intern("new"), 0);
+        rb_define_singleton_method(val, "write", s_StandardOutput, 1);
+        rb_define_singleton_method(val, "flush", s_FlushConsoleOutput, 0);
+        rb_gv_set("$stdout", val);
+        val = rb_funcall(rb_cObject, rb_intern("new"), 0);
+        rb_define_singleton_method(val, "write", s_StandardErrorOutput, 1);
+        rb_define_singleton_method(val, "flush", s_FlushConsoleOutput, 0);
+        rb_gv_set("$stderr", val);
+
+        /*  Create objects for stdin  */
+        val = rb_funcall(rb_cObject, rb_intern("new"), 0);
+        rb_define_singleton_method(val, "gets", s_StandardInputGets, -1);
+        rb_define_singleton_method(val, "readline", s_StandardInputGets, -1);
+        rb_define_singleton_method(val, "method_missing", s_StandardInputMethodMissing, -1);
+        rb_gv_set("$stdin", val);
+    }
        
-       /*  Global variable to hold backtrace  */
+       /*  Global variable to hold error information  */
        rb_define_variable("$backtrace", &gMolbyBacktrace);
-
-#if !__CMDMAC__
-       /*  Register interrupt check code  */
-       rb_add_event_hook(s_Event_Callback, RUBY_EVENT_ALL);
-#endif
+       rb_define_variable("$error_history", &gMolbyErrorHistory);
+       gMolbyErrorHistory = rb_ary_new();
        
-#if !__CMDMAC__
-       /*  Start interval timer (for periodic polling of interrupt); firing every 50 msec  */
-       s_SetIntervalTimer(0, 50);
-#endif
+       /*  Global variables for script menus  */
+       rb_define_variable("$script_menu_commands", &gScriptMenuCommands);
+       rb_define_variable("$script_menu_enablers", &gScriptMenuEnablers);
+       gScriptMenuCommands = rb_ary_new();
+       gScriptMenuEnablers = rb_ary_new();
+       
+    if (gUseGUI) {
+        /*  Register interrupt check code  */
+        rb_add_event_hook(s_Event_Callback, RUBY_EVENT_ALL, Qnil);
+        /*  Start interval timer (for periodic polling of interrupt); firing every 50 msec  */
+        s_SetIntervalTimer(0, 50);
+    }
        
        /*  Read the startup script  */
        if (script != NULL && script[0] != 0) {
                MyAppCallback_showScriptMessage("Evaluating %s...\n", script);
                gMolbyRunLevel++;
-               rb_load_protect(rb_str_new2(script), 0, &status);
+               rb_load_protect(Ruby_NewEncodedStringValue2(script), 0, &status);
                gMolbyRunLevel--;
                if (status != 0)
-                       Molby_showError(status);
+                       Ruby_showError(status);
                else
                        MyAppCallback_showScriptMessage("Done.\n");
        }