OSDN Git Service

Start implementing 'additional exponent' for JANPA output
[molby/Molby.git] / MolLib / Ruby_bind / ruby_bind.c
index 2a0ad55..2c09218 100644 (file)
 #include "ruby_dialog.h"
 #include "../MD/MDCore.h"
 
+#include <stdio.h>
 #include <errno.h>
 #include <string.h>
 #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
@@ -50,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;
@@ -57,7 +63,9 @@ int gMolbyIsCheckingInterrupt = 0;
 char *gRubyVersion, *gRubyCopyright;
 
 /*  For convenience  */
-static VALUE s_ID_equal;  /*  rb_intern("==")  */
+static ID s_ID_equal;  /*  rb_intern("==")  */
+
+int g_RubyID_call;
 
 /*  Symbols for atom attributes  */
 static VALUE
@@ -68,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_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
@@ -80,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
@@ -108,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
@@ -123,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)
 {
@@ -158,7 +192,7 @@ static VALUE
 s_Kernel_MessageBox(int argc, VALUE *argv, VALUE self)
 {
        char *str, *title, *s;
-       int buttons, icon;
+    int buttons, icon, retval;
        VALUE sval, tval, bval, ival;
        rb_scan_args(argc, argv, "22", &sval, &tval, &bval, &ival);
        str = StringValuePtr(sval);
@@ -185,8 +219,8 @@ s_Kernel_MessageBox(int argc, VALUE *argv, VALUE self)
                else
                        rb_raise(rb_eMolbyError, "the icon specification should be either :info, :warning or :error");
        } else icon = 1;
-       MyAppCallback_messageBox(str, title, buttons, icon);
-       return Qnil;
+       retval = MyAppCallback_messageBox(str, title, buttons, icon);
+    return (retval ? Qtrue : Qfalse);
 }
 
 /*
@@ -222,13 +256,216 @@ 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;    
 }
 
 /*
  *  call-seq:
+ *     show_console_window
+ *
+ *  Show the console window and bring to the front.
+ */
+static VALUE
+s_Kernel_ShowConsoleWindow(VALUE self)
+{
+       MyAppCallback_showConsoleWindow();
+       return Qnil;
+}
+
+/*
+ *  call-seq:
+ *     hide_console_window
+ *
+ *  Hide the console window.
+ */
+static VALUE
+s_Kernel_HideConsoleWindow(VALUE self)
+{
+       MyAppCallback_hideConsoleWindow();
+       return Qnil;
+}
+
+/*
+ *  call-seq:
+ *     bell
+ *
+ *  Ring the system bell.
+ */
+static VALUE
+s_Kernel_Bell(VALUE self)
+{
+       MyAppCallback_bell();
+       return Qnil;
+}
+
+/*
+ *  call-seq:
+ *     play_sound(filename, flag = 0)
+ *
+ *  Play the sound (a WAV file) in the file. Flag: 0, pause until sound ends;
+ *  1, play the sound asynchronously; 3, play the sound with loop asynchronously
+ */
+static VALUE
+s_Kernel_PlaySound(int argc, VALUE *argv, VALUE self)
+{
+       VALUE fnval, flval;
+       int flag, retval;
+       char *fname;
+       rb_scan_args(argc, argv, "11", &fnval, &flval);
+       if (flval == Qnil)
+               flag = 0;
+       else flag = NUM2INT(rb_Integer(flval));
+       fnval = rb_funcall(rb_cFile, rb_intern("expand_path"), 1, fnval);
+       fname = StringValuePtr(fnval);
+       retval = MyAppCallback_playSound(fname, flag);
+       return (retval ? Qtrue : Qnil);
+}
+
+/*
+ *  call-seq:
+ *     stop_sound
+ *
+ *  Stop the sound if playing.
+ */
+static VALUE
+s_Kernel_StopSound(VALUE self)
+{
+       MyAppCallback_stopSound();
+       return Qnil;
+}
+
+/*
+ *  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.
@@ -260,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.
@@ -268,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();
@@ -356,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;
 }
@@ -369,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)
 {
@@ -550,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;
@@ -574,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
@@ -602,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;
@@ -615,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;
@@ -631,6 +941,7 @@ Ruby_methodType(const char *className, const char *methodName)
                return FIX2INT(retval);
        else return 0;
 }
+*/
 
 /*
  *  call-seq:
@@ -670,18 +981,142 @@ s_Kernel_DocumentHome(VALUE self)
        return retval;
 }
 
+/*  The callback function for call_subprocess  */
+static int
+s_Kernel_CallSubProcess_Callback(void *data)
+{
+       int status;
+       VALUE retval = Ruby_funcall2_protect((VALUE)data, rb_intern("call"), 0, NULL, &status);
+       if (status != 0 || retval == Qnil || retval == Qfalse)
+               return 1;
+       else return 0;
+}
+
 /*
  *  call-seq:
- *     call_subprocess(cmd, process_name)
+ *     call_subprocess(cmd, process_name [, callback_proc [, stdout_file [, stderr_file]]])
  *
  *  Call subprocess. A progress dialog window is displayed, with a message
  *  "Running #{process_name}...".
+ *  cmd is either a single string of an array of string. If it is a single string, then
+ *  it will be given to wxExecute as a single argument. In this case, the string can be
+ *  split into arguments by whitespace. If this behavior is not intended, then use an array
+ *  containing a single string.
+ *  A callback proc can be given, which is called periodically during execution. If the proc returns
+ *  nil or false, then the execution will be interrupted.
+ *  If stdout_file or stderr_file is a filename, then the message will be sent to the file; if the
+ *  filename begins with ">>", then the message will be appended to the file.
+ *  If the filename is "/dev/null" or "NUL", then the message will be lost.
+ *  If the argument is nil, then the message will be sent to the Ruby console.
+ */
+static VALUE
+s_Kernel_CallSubProcess(int argc, VALUE *argv, VALUE self)
+{
+       VALUE cmd, procname, cproc, stdout_val, stderr_val;
+    VALUE save_interruptFlag;
+       int n, exitstatus, pid;
+       char *sout, *serr;
+    const char *pnamestr, **cmdargv;
+       FILE *fpout, *fperr;
+
+       rb_scan_args(argc, argv, "23", &cmd, &procname, &cproc, &stdout_val, &stderr_val);
+
+       if (stdout_val == Qnil) {
+               fpout = (FILE *)1;
+       } else {
+               sout = StringValuePtr(stdout_val);
+               if (strcmp(sout, "/dev/null") == 0 || strcmp(sout, "NUL") == 0)
+                       fpout = NULL;
+               else {
+                       if (strncmp(sout, ">>", 2) == 0) {
+                               sout += 2;
+                               fpout = fopen(sout, "a");
+                       } else {
+                               if (*sout == '>')
+                                       sout++;
+                               fpout = fopen(sout, "w");
+                       }
+                       if (fpout == NULL)
+                               rb_raise(rb_eMolbyError, "Cannot open file for standard output (%s)", sout);
+               }
+       }
+       if (stderr_val == Qnil) {
+               fperr = (FILE *)1;
+       } else {
+               serr = StringValuePtr(stderr_val);
+               if (strcmp(serr, "/dev/null") == 0 || strcmp(serr, "NUL") == 0)
+                       fperr = NULL;
+               else {
+                       if (strncmp(serr, ">>", 2) == 0) {
+                               serr += 2;
+                               fpout = fopen(serr, "a");
+                       } else {
+                               if (*serr == '>')
+                                       serr++;
+                               fperr = fopen(serr, "w");
+                       }
+                       if (fperr == NULL)
+                               rb_raise(rb_eMolbyError, "Cannot open file for standard output (%s)", serr);
+               }
+       }
+    
+    save_interruptFlag = s_SetInterruptFlag(self, Qnil);
+    if (procname != Qnil)
+        pnamestr = StringValuePtr(procname);
+    else pnamestr = NULL;
+    if (rb_obj_is_kind_of(cmd, rb_cString)) {
+        cmdargv = calloc(sizeof(cmdargv[0]), 3);
+        cmdargv[0] = StringValuePtr(cmd);
+        cmdargv[1] = "";
+        cmdargv[2] = NULL;
+    } else {
+        cmd = rb_ary_to_ary(cmd);
+        cmdargv = calloc(sizeof(cmdargv[0]), RARRAY_LEN(cmd) + 1);
+        for (n = 0; n < RARRAY_LEN(cmd); n++) {
+            cmdargv[n] = StringValuePtr(RARRAY_PTR(cmd)[n]);
+        }
+        cmdargv[n] = NULL;
+    }
+       n = MyAppCallback_callSubProcess(cmdargv, pnamestr, (cproc == Qnil ? NULL : s_Kernel_CallSubProcess_Callback), (cproc == Qnil ? NULL : (void *)cproc), fpout, fperr, &exitstatus, &pid);
+    s_SetInterruptFlag(self, save_interruptFlag);
+    free(cmdargv);
+
+       if (fpout != NULL && fpout != (FILE *)1)
+               fclose(fpout);
+       if (fperr != NULL && fperr != (FILE *)1)
+               fclose(fperr);
+
+       return INT2NUM(n);
+
+       
+}
+
+/*
+ *  call-seq:
+ *     backquote(cmd)
+ *
+ *  Same as the builtin backquote, except that, under Windows, no console window gets opened.
  */
 static VALUE
-s_Kernel_CallSubProcess(VALUE self, VALUE cmd, VALUE procname)
+s_Kernel_Backquote(VALUE self, VALUE cmd)
 {
-       int n = MyAppCallback_callSubProcess(StringValuePtr(cmd), StringValuePtr(procname));
-       return INT2NUM(n);
+       char *buf;
+       int n, exitstatus, pid;
+       VALUE val;
+    const char *cmdargv[3];
+    cmdargv[0] = StringValuePtr(cmd);
+    cmdargv[1] = "";
+    cmdargv[2] = NULL;
+       n = MyAppCallback_callSubProcess(cmdargv, 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 = Ruby_NewEncodedStringValue("", 0);
+       }
+       rb_last_status_set(exitstatus, pid);
+       return val;
 }
 
 #pragma mark ====== User defaults ======
@@ -717,6 +1152,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 {
@@ -744,6 +1248,12 @@ Ruby_funcall2_protect(VALUE recv, ID mid, int argc, VALUE *argv, int *status)
        return rb_protect(s_Ruby_funcall2_sub, (VALUE)&rec, status);
 }
 
+RubyValue
+Ruby_funcall2_protect_extern(RubyValue recv, int mid, int argc, RubyValue *argv, int *status)
+{
+       return (RubyValue)Ruby_funcall2_protect((VALUE)recv, mid, argc, (VALUE *)argv, status);
+}
+
 #pragma mark ====== ParameterRef Class ======
 
 static UnionPar *
@@ -856,10 +1366,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);
 }
 
@@ -1190,11 +1700,11 @@ static VALUE s_ParameterRef_GetEps14(VALUE self) {
        if (tp == kVdwParType) {
        /*      a = up->vdw.A14;
                b = up->vdw.B14;  */
-               eps = up->vdw.eps;
+               eps = up->vdw.eps14;
        } else if (tp == kVdwPairParType) {
        /*      a = up->vdwp.A14;
                b = up->vdwp.B14; */
-               eps = up->vdwp.eps;
+               eps = up->vdwp.eps14;
        } else rb_raise(rb_eMolbyError, "invalid member eps14");
 /*     if (a == 0.0 || b == 0.0) */
        return rb_float_new(eps * INTERNAL2KCAL);
@@ -1220,7 +1730,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;
@@ -1233,24 +1743,39 @@ static VALUE s_ParameterRef_GetRadius(VALUE self) {
 
 /*
  *  call-seq:
- *     color -> [Float, Float, Float]
+ *     vdw_radius -> Float
  *
- *  Get the rgb color for the atom display parameter.
+ *  Get the van der Waals radius for the element parameter. (0 if not given)
  */
-static VALUE s_ParameterRef_GetColor(VALUE self) {
+static VALUE s_ParameterRef_GetVdwRadius(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));
-       else rb_raise(rb_eMolbyError, "invalid member color");
+               return rb_float_new(up->atom.vdw_radius);
+       else rb_raise(rb_eMolbyError, "invalid member vdw_radius");
 }
 
 /*
  *  call-seq:
- *     atomic_number -> Integer
+ *     color -> [Float, Float, Float]
+ *
+ *  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.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");
+}
+
+/*
+ *  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;
@@ -1267,7 +1792,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;
@@ -1277,7 +1802,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");
 }
 
@@ -1285,7 +1810,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;
@@ -1302,7 +1827,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;
@@ -1312,7 +1837,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");
 }
 
@@ -1329,7 +1854,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));
 }
 
 /*
@@ -1348,7 +1873,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
@@ -1809,6 +2334,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;
@@ -1821,9 +2361,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;
@@ -1954,6 +2494,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},
@@ -1977,6 +2518,8 @@ s_ParameterRef_SetAttr(VALUE self, VALUE key, VALUE value)
                if (s_ParameterAttrDefTable[i].id == kid) {
                        if (value == Qundef)
                                return (*(s_ParameterAttrDefTable[i].getter))(self);
+                       else if (s_ParameterAttrDefTable[i].setter == NULL)
+                               rb_raise(rb_eMolbyError, "the attribute \"%s\" is read-only", rb_id2name(kid));
                        else
                                return (*(s_ParameterAttrDefTable[i].setter))(self, value);
                }
@@ -2017,7 +2560,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");
        }
@@ -2085,12 +2628,27 @@ 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);
 }
 
+/*
+ *  call-seq:
+ *     self == parameterRef -> boolean
+ *  
+ *  True if the parameters point to the same parameter record.
+ */
+static VALUE
+s_ParameterRef_Equal(VALUE self, VALUE val)
+{
+       Int tp1, tp2;
+       if (rb_obj_is_kind_of(val, rb_cParameterRef)) {
+               return (s_UnionParFromValue(self, &tp1, 0) == s_UnionParFromValue(val, &tp2, 0) ? Qtrue : Qfalse);
+       } else return Qfalse;
+}
+       
 #pragma mark ====== Parameter Class ======
 
 /*  The Parameter class actually encapsulate Molecule record. If the record pointer
@@ -2153,7 +2711,9 @@ static Molecule *s_MoleculeFromParEnumerableValue(VALUE val);
 static Molecule *
 s_MoleculeFromParameterOrParEnumerableValue(VALUE val)
 {
-       if (rb_obj_is_kind_of(val, rb_cParameter)) {
+       if (val == rb_cParameter) {
+               return NULL;  /*  Parameter class method: builtin parameters  */
+       } else if (rb_obj_is_kind_of(val, rb_cParameter)) {
                return s_MoleculeFromParameterValue(val);
        } else if (rb_obj_is_kind_of(val, rb_cParEnumerable)) {
                return s_MoleculeFromParEnumerableValue(val);
@@ -2379,7 +2939,7 @@ s_Parameter_Element(VALUE self, VALUE ival)
                int i;
                strncpy(name, StringValuePtr(ival), 4);
                name[4] = 0;
-               for (i = gCountElementParameters - 1, ep = gElementParameters + i; i >= 0; i--) {
+               for (i = gCountElementParameters - 1, ep = gElementParameters + i; i >= 0; i--, ep--) {
                        if (strncmp(ep->name, name, 4) == 0)
                                return ValueFromMoleculeWithParameterTypeAndIndex(NULL, kElementParType, i);
                }
@@ -2657,6 +3217,7 @@ s_Parameter_Lookup_sub(int argc, VALUE *argv, int parType, Molecule *mol)
 {
        VALUE atval, optval;
        UInt t[4];
+       Int ii[4];
        int i, n, idx, flags, is_global;
 
        rb_scan_args(argc, argv, "1*", &atval, &optval);
@@ -2672,6 +3233,17 @@ s_Parameter_Lookup_sub(int argc, VALUE *argv, int parType, Molecule *mol)
                default: return Qnil;
        }
        s_ScanAtomTypes(atval, n, t);
+       for (i = 0; i < n; i++) {
+               if (t[i] < kAtomTypeMinimum) {
+                       /*  Explicit atom index  */
+                       if (mol == NULL)
+                               rb_raise(rb_eMolbyError, "Explicit atom index (%d) is invalid for global parameters", t[i]);
+                       if (t[i] >= mol->natoms)
+                               rb_raise(rb_eMolbyError, "Atom index (%d) out of range", t[i]);
+                       ii[i] = t[i];
+                       t[i] = ATOM_AT_INDEX(mol->atoms, t[i])->type;
+               } else ii[i] = -1;
+       }
        
        /*  Analyze options  */
        flags = 0;
@@ -2700,7 +3272,7 @@ s_Parameter_Lookup_sub(int argc, VALUE *argv, int parType, Molecule *mol)
                case kBondParType: {
                        BondPar *bp;
                        if (mol != NULL) {
-                               bp = ParameterLookupBondPar(mol->par, t[0], t[1], -1, -1, flags);
+                               bp = ParameterLookupBondPar(mol->par, t[0], t[1], ii[0], ii[1], flags);
                                if (bp != NULL) {
                                        idx = bp - mol->par->bondPars;
                                        break;
@@ -2716,7 +3288,7 @@ s_Parameter_Lookup_sub(int argc, VALUE *argv, int parType, Molecule *mol)
                case kAngleParType: {
                        AnglePar *ap;
                        if (mol != NULL) {
-                               ap = ParameterLookupAnglePar(mol->par, t[0], t[1], t[2], -1, -1, -1, flags);
+                               ap = ParameterLookupAnglePar(mol->par, t[0], t[1], t[2], ii[0], ii[1], ii[2], flags);
                                if (ap != NULL) {
                                        idx = ap - mol->par->anglePars;
                                        break;
@@ -2732,7 +3304,7 @@ s_Parameter_Lookup_sub(int argc, VALUE *argv, int parType, Molecule *mol)
                case kDihedralParType: {
                        TorsionPar *tp;
                        if (mol != NULL) {
-                               tp = ParameterLookupDihedralPar(mol->par, t[0], t[1], t[2], t[3], -1, -1, -1, -1, flags);
+                               tp = ParameterLookupDihedralPar(mol->par, t[0], t[1], t[2], t[3], ii[0], ii[1], ii[2], ii[3], flags);
                                if (tp != NULL) {
                                        idx = tp - mol->par->dihedralPars;
                                        break;
@@ -2748,7 +3320,7 @@ s_Parameter_Lookup_sub(int argc, VALUE *argv, int parType, Molecule *mol)
                case kImproperParType: {
                        TorsionPar *tp;
                        if (mol != NULL) {
-                               tp = ParameterLookupImproperPar(mol->par, t[0], t[1], t[2], t[3], -1, -1, -1, -1, flags);
+                               tp = ParameterLookupImproperPar(mol->par, t[0], t[1], t[2], t[3], ii[0], ii[1], ii[2], ii[3], flags);
                                if (tp != NULL) {
                                        idx = tp - mol->par->improperPars;
                                        break;
@@ -2764,13 +3336,13 @@ s_Parameter_Lookup_sub(int argc, VALUE *argv, int parType, Molecule *mol)
                case kVdwParType: {
                        VdwPar *vp;
                        if (mol != NULL) {
-                               vp = ParameterLookupVdwPar(mol->par, t[0], flags);
+                               vp = ParameterLookupVdwPar(mol->par, t[0], ii[0], flags);
                                if (vp != NULL) {
                                        idx = vp - mol->par->vdwPars;
                                        break;
                                }
                        }
-                       vp = ParameterLookupVdwPar(gBuiltinParameters, t[0], flags);
+                       vp = ParameterLookupVdwPar(gBuiltinParameters, t[0], -1, flags);
                        if (vp != NULL) {
                                idx = vp - gBuiltinParameters->vdwPars;
                                is_global = 1;
@@ -2780,13 +3352,13 @@ s_Parameter_Lookup_sub(int argc, VALUE *argv, int parType, Molecule *mol)
                case kVdwPairParType: {
                        VdwPairPar *vp;
                        if (mol != NULL) {
-                               vp = ParameterLookupVdwPairPar(mol->par, t[0], t[1], flags);
+                               vp = ParameterLookupVdwPairPar(mol->par, t[0], t[1], ii[0], ii[1], flags);
                                if (vp != NULL) {
                                        idx = vp - mol->par->vdwpPars;
                                        break;
                                }
                        }
-                       vp = ParameterLookupVdwPairPar(gBuiltinParameters, t[0], t[1], flags);
+                       vp = ParameterLookupVdwPairPar(gBuiltinParameters, t[0], t[1], -1, -1, flags);
                        if (vp != NULL) {
                                idx = vp - gBuiltinParameters->vdwpPars;
                                is_global = 1;
@@ -2866,6 +3438,20 @@ s_Parameter_LookUp(int argc, VALUE *argv, VALUE self)
        return s_Parameter_Lookup_sub(argc - 1, argv + 1, parType, mol);
 }
 
+/*
+ *  call-seq:
+ *     self == parameter -> boolean
+ *  
+ *  True if the parameters point to the same parameter table.
+ */
+static VALUE
+s_Parameter_Equal(VALUE self, VALUE val)
+{
+       if (rb_obj_is_kind_of(val, rb_cParameter)) {
+               return (s_MoleculeFromParameterOrParEnumerableValue(self) == s_MoleculeFromParameterOrParEnumerableValue(val) ? Qtrue : Qfalse);
+       } else return Qfalse;
+}
+
 #pragma mark ====== ParEnumerable Class ======
 
 /*  The ParEnumerable class encapsulates the Molecule (not Parameter) pointer
@@ -2931,10 +3517,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);
 }
 
@@ -3122,7 +3708,7 @@ s_ParEnumerable_Insert(int argc, VALUE *argv, VALUE self)
                u.bond.src = 0;
        } else {
                memset(&u, 0, sizeof(u));
-               u.bond.src = -1;
+               u.bond.src = 0;
        }
        ig = IntGroupNewWithPoints(n, 1, -1);
        ParameterInsert(pen->mol->par, pen->parType, &u, ig);
@@ -3188,6 +3774,23 @@ s_ParEnumerable_LookUp(int argc, VALUE *argv, VALUE self)
        return s_Parameter_Lookup_sub(argc, argv, pen->parType, pen->mol);
 }
 
+/*
+ *  call-seq:
+ *     self == parEnumerable -> boolean
+ *  
+ *  True if the arguments point to the same parameter table and type.
+ */
+static VALUE
+s_ParEnumerable_Equal(VALUE self, VALUE val)
+{
+       if (rb_obj_is_kind_of(val, rb_cParEnumerable)) {
+               ParEnumerable *pen1, *pen2;
+               Data_Get_Struct(self, ParEnumerable, pen1);
+               Data_Get_Struct(val, ParEnumerable, pen2);
+               return (pen1->mol == pen2->mol && pen1->parType == pen2->parType) ? Qtrue : Qfalse;
+       } else return Qfalse;
+}
+
 #pragma mark ====== AtomRef Class ======
 
 /*  Forward declaration for register undo  */
@@ -3279,7 +3882,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) {
@@ -3288,18 +3891,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) {
@@ -3312,7 +3915,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) {
@@ -3395,7 +3998,9 @@ static VALUE s_AtomRef_GetV(VALUE self) {
 }
 
 static VALUE s_AtomRef_GetF(VALUE self) {
-       return ValueFromVector(&(s_AtomFromValue(self)->f));
+       Vector v = s_AtomFromValue(self)->f;
+       VecScaleSelf(v, INTERNAL2KCAL);
+       return ValueFromVector(&v);
 }
 
 static VALUE s_AtomRef_GetOccupancy(VALUE self) {
@@ -3423,6 +4028,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);
@@ -3493,6 +4110,27 @@ static VALUE s_AtomRef_GetHidden(VALUE self) {
        return ((s_AtomFromValue(self)->exflags & kAtomHiddenFlag) != 0 ? Qtrue : Qfalse);
 }
 
+static VALUE s_AtomRef_GetAnchorList(VALUE self) {
+       VALUE retval;
+       Int i, count, *cp;
+       Atom *ap = s_AtomFromValue(self);
+       if (ap->anchor == NULL)
+               return Qnil;
+       count = ap->anchor->connect.count;
+       retval = rb_ary_new2(count * 2);
+       cp = AtomConnectData(&ap->anchor->connect);
+       for (i = 0; i < count; i++) {
+               rb_ary_store(retval, i, INT2NUM(cp[i]));
+               rb_ary_store(retval, i + count, rb_float_new(ap->anchor->coeffs[i]));
+       }
+       return retval;
+}
+
+static VALUE s_AtomRef_GetUFFType(VALUE self) {
+       char *p = s_AtomFromValue(self)->uff_type;
+       return Ruby_NewEncodedStringValue(p, strlen_limit(p, 5));
+}
+
 static VALUE s_AtomRef_SetIndex(VALUE self, VALUE val) {
        rb_raise(rb_eMolbyError, "index cannot be directly set");
        return Qnil;
@@ -3520,9 +4158,12 @@ static VALUE s_AtomRef_SetResSeqOrResName(VALUE self, VALUE val) {
 }
 
 static VALUE s_AtomRef_SetName(VALUE self, VALUE val) {
+       Atom *ap = s_AtomFromValue(self);
        char *p = StringValuePtr(val);
        VALUE oval = s_AtomRef_GetName(self);
-       strncpy(s_AtomFromValue(self)->aname, p, 4);
+       if (ap->anchor != NULL && p[0] == '_')
+               rb_raise(rb_eMolbyError, "please avoid a name beginning with '_' for a pi anchor, because it causes some internal confusion.");
+       strncpy(ap->aname, p, 4);
        s_RegisterUndoForAtomAttrChange(self, s_NameSym, val, oval);
        return val;
 }
@@ -3782,6 +4423,7 @@ static VALUE s_AtomRef_SetF(VALUE self, VALUE val) {
        Molecule *mp;
        VALUE oval = s_AtomRef_GetF(self);
        VectorFromValue(val, &v);
+       VecScaleSelf(v, KCAL2INTERNAL);
        s_AtomAndMoleculeFromValue(self, &mp)->f = v;
        s_RegisterUndoForAtomAttrChange(self, s_FSym, val, oval);
        mp->needsMDCopyCoordinates = 1;
@@ -3837,6 +4479,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;
@@ -3860,8 +4507,8 @@ static VALUE s_AtomRef_SetSymop(VALUE self, VALUE val) {
                        } else ival[i] = -100000;
                }
        }
-       if (ival[0] != -100000 && (ival[0] < 0 || ival[0] >= mol->nsyms))
-               rb_raise(rb_eMolbyError, "index of symmetry (%d) is out of range (should be 0..%d)", ival[0], mol->nsyms - 1);
+       if (ival[0] != -100000 && (ival[0] < 0 || (ival[0] != 0 && ival[0] >= mol->nsyms)))
+               rb_raise(rb_eMolbyError, "index of symmetry (%d) is out of range (should be 0..%d)", ival[0], (mol->nsyms == 0 ? 0 : mol->nsyms - 1));
        if (ival[4] != -100000 && (ival[4] < 0 || ival[4] >= mol->natoms))
                rb_raise(rb_eMolbyError, "atom index number (%d) is out of range (should be 0..%d)", ival[4], mol->natoms - 1);
        if (ap->symop.sym == ival[0] && ap->symop.dx == ival[1] && ap->symop.dy == ival[2] && ap->symop.dz == ival[3])
@@ -3949,6 +4596,92 @@ static VALUE s_AtomRef_SetHidden(VALUE self, VALUE val) {
        return val;
 }
 
+static VALUE s_AtomRef_SetAnchorList(VALUE self, VALUE val) {
+       Int idx, i, j, k, n, *ip;
+       Double *dp;
+       Atom *ap;
+       Molecule *mol;
+       VALUE oval, v;
+       AtomConnect ac;
+       Int nUndoActions;
+       MolAction **undoActions;
+       memset(&ac, 0, sizeof(ac));
+       idx = s_AtomIndexFromValue(self, &ap, &mol);
+       oval = s_AtomRef_GetAnchorList(self);
+       if (val != Qnil) {
+               val = rb_ary_to_ary(val);
+               n = RARRAY_LEN(val);
+       } else n = 0;
+       if (n == 0) {
+               if (ap->anchor != NULL) {
+                       AtomConnectResize(&ap->anchor->connect, 0);
+                       free(ap->anchor->coeffs);
+                       free(ap->anchor);
+                       ap->anchor = NULL;
+                       s_RegisterUndoForAtomAttrChange(self, s_AnchorListSym, val, oval);
+               }
+               return val;
+       }
+       if (n < 2)
+               rb_raise(rb_eMolbyError, "set_anchor_list: the argument should have at least two atom indices");
+       if (ap->aname[0] == '_')
+               rb_raise(rb_eMolbyError, "please avoid a name beginning with '_' for a pi anchor, because it causes some internal confusion.");
+       ip = (Int *)malloc(sizeof(Int) * n);
+       dp = NULL;
+       for (i = 0; i < n; i++) {
+               v = RARRAY_PTR(val)[i];
+               if (rb_obj_is_kind_of(v, rb_cFloat))
+                       break;
+               j = NUM2INT(rb_Integer(v));
+               if (j < 0 || j >= mol->natoms)
+                       rb_raise(rb_eMolbyError, "set_anchor_list: atom index (%d) out of range", j);
+               for (k = 0; k < i; k++) {
+                       if (ip[k] == j)
+                               rb_raise(rb_eMolbyError, "set_anchor_list: duplicate atom index (%d)", j);
+               }
+               ip[i] = j;
+       }
+       if (i < n) {
+               if (i < 2)
+                       rb_raise(rb_eMolbyError, "set_anchor_list: the argument should have at least two atom indices");
+               else if (i * 2 != n)
+                       rb_raise(rb_eMolbyError, "set_anchor_list: the weights should be given in the same number as the atom indices");
+               dp = (Double *)malloc(sizeof(Double) * n / 2);
+               for (i = 0; i < n / 2; i++) {
+                       dp[i] = NUM2DBL(rb_Float(RARRAY_PTR(val)[i + n / 2]));
+                       if (dp[i] <= 0.0)
+                               rb_raise(rb_eMolbyError, "set_anchor_list: the weights should be positive Floats");
+               }
+               n /= 2;
+       }
+       nUndoActions = 0;
+       undoActions = NULL;
+       i = MoleculeSetPiAnchorList(mol, idx, n, ip, dp, &nUndoActions, &undoActions);
+       free(dp);
+       free(ip);
+       if (i != 0)
+               rb_raise(rb_eMolbyError, "invalid argument");
+       if (nUndoActions > 0) {
+               for (i = 0; i < nUndoActions; i++) {
+                       MolActionCallback_registerUndo(mol, undoActions[i]);
+                       MolActionRelease(undoActions[i]);
+               }
+               free(undoActions);
+       }
+       s_RegisterUndoForAtomAttrChange(self, s_AnchorListSym, val, oval);
+       return val;
+}
+
+static VALUE s_AtomRef_SetUFFType(VALUE self, VALUE val) {
+       Atom *ap = s_AtomFromValue(self);
+       char *p = StringValuePtr(val);
+       VALUE oval = s_AtomRef_GetUFFType(self);
+       strncpy(ap->uff_type, p, 5);
+       ap->uff_type[5] = 0;
+       s_RegisterUndoForAtomAttrChange(self, s_UFFTypeSym, val, oval);
+       return val;
+}
+
 static struct s_AtomAttrDef {
        char *name;
        VALUE *symref;  /*  Address of s_IndexSymbol etc. */
@@ -3985,6 +4718,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},
@@ -3993,6 +4727,8 @@ static struct s_AtomAttrDef {
        {"mm_exclude",   &s_MMExcludeSym,    0, s_AtomRef_GetMMExclude,    s_AtomRef_SetMMExclude},
        {"periodic_exclude", &s_PeriodicExcludeSym, 0, s_AtomRef_GetPeriodicExclude, s_AtomRef_SetPeriodicExclude},
        {"hidden",       &s_HiddenSym,       0, s_AtomRef_GetHidden,       s_AtomRef_SetHidden},
+       {"anchor_list",  &s_AnchorListSym,   0, s_AtomRef_GetAnchorList,   s_AtomRef_SetAnchorList},
+       {"uff_type",     &s_UFFTypeSym,      0, s_AtomRef_GetUFFType,      s_AtomRef_SetUFFType},
        {NULL} /* Sentinel */
 };
 
@@ -4023,6 +4759,20 @@ s_AtomRef_GetAttr(VALUE self, VALUE key)
        return s_AtomRef_SetAttr(self, key, Qundef);
 }
 
+/*
+ *  call-seq:
+ *     self == atomRef -> boolean
+ *
+ *  True if the two references point to the same atom.
+ */
+static VALUE
+s_AtomRef_Equal(VALUE self, VALUE val)
+{
+       if (rb_obj_is_kind_of(val, rb_cAtomRef)) {
+               return (s_AtomFromValue(self) == s_AtomFromValue(val) ? Qtrue : Qfalse);
+       } else return Qfalse;
+}
+
 #pragma mark ====== MolEnumerable Class ======
 
 static int s_Molecule_AtomIndexFromValue(Molecule *, VALUE);
@@ -4078,7 +4828,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;
@@ -4146,8 +4896,34 @@ s_MolEnumerable_Each(VALUE self)
     return self;
 }
 
+/*
+ *  call-seq:
+ *     self == molEnumerable -> boolean
+ *
+ *  True if the two arguments point to the same molecule and enumerable type.
+ */
+static VALUE
+s_MolEnumerable_Equal(VALUE self, VALUE val)
+{
+       if (rb_obj_is_kind_of(val, rb_cMolEnumerable)) {
+               MolEnumerable *mseq1, *mseq2;
+               Data_Get_Struct(self, MolEnumerable, mseq1);
+               Data_Get_Struct(val, MolEnumerable, mseq2);
+               return ((mseq1->mol == mseq2->mol && mseq1->kind == mseq2->kind) ? Qtrue : Qfalse);
+       } else return Qfalse;
+}
+
+
 #pragma mark ====== Molecule Class ======
 
+#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;
+
+#define MoleculeClearLoadSaveErrorMessage() (gLoadSaveErrorMessage != NULL ? (free(gLoadSaveErrorMessage), gLoadSaveErrorMessage = NULL) : NULL)
+
 Molecule *
 MoleculeFromValue(VALUE val)
 {
@@ -4230,16 +5006,14 @@ s_Molecule_Alloc(VALUE klass)
 }
 
 static int
-s_Molecule_AtomOrPiAtomIndexFromValue(Molecule *mol, VALUE val)
+s_Molecule_AtomIndexFromValue(Molecule *mol, VALUE val)
 {
-       int n, i;
-       char *p;
+       int n;
+       char *p = "";
        if (FIXNUM_P(val)) {
                n = FIX2INT(val);
                if (n >= 0 && n < mol->natoms)
                        return n;
-               else if (n < 0 && -n - 1 < mol->npiatoms)
-                       return ATOMS_MAX_NUMBER + (-n - 1);  /*  PiAtom  */
                n = -1; /*  No such atom  */
                val = rb_inspect(val);
        } else {
@@ -4248,10 +5022,6 @@ s_Molecule_AtomOrPiAtomIndexFromValue(Molecule *mol, VALUE val)
        if (n >= 0 && n < mol->natoms)
                return n;
        p = StringValuePtr(val);
-       for (i = 0; i < mol->npiatoms; i++) {
-               if (strcmp(p, mol->piatoms->aname) == 0)
-                       return ATOMS_MAX_NUMBER + i;  /*  PiAtom; should be looked up after normal atoms  */
-       }
        if (n == -1)
                rb_raise(rb_eMolbyError, "no such atom: %s", p);
        else if (n == -2)
@@ -4261,25 +5031,17 @@ s_Molecule_AtomOrPiAtomIndexFromValue(Molecule *mol, VALUE val)
        return 0; /* Not reached */
 }
 
-static int
-s_Molecule_AtomIndexFromValue(Molecule *mol, VALUE val)
-{
-       int n = s_Molecule_AtomOrPiAtomIndexFromValue(mol, val);
-       if (n >= ATOMS_MAX_NUMBER) {
-               val = rb_inspect(val);
-               rb_raise(rb_eMolbyError, "no such atom: %s", StringValuePtr(val));
-       }
-       return n;
-}
-
 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;
 }
@@ -4304,6 +5066,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.
@@ -4315,17 +5127,13 @@ s_Molecule_Loadmbsf(int argc, VALUE *argv, VALUE self)
        VALUE fname;
        char *fstr;
        Molecule *mol;
-       char errbuf[128];
        int retval;
+       MoleculeClearLoadSaveErrorMessage();
        Data_Get_Struct(self, Molecule, mol);
        rb_scan_args(argc, argv, "1", &fname);
        fstr = FileStringValuePtr(fname);
-       retval = MoleculeLoadMbsfFile(mol, fstr, errbuf, sizeof errbuf);
-       if (retval != 0) {
-       /*      if (retval == -1)
-                       return Qnil; */
-               rb_raise(rb_eMolbyError, errbuf);
-       }
+       retval = MoleculeLoadMbsfFile(mol, fstr, &gLoadSaveErrorMessage);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load mbsf", fstr);
        return Qtrue;   
 }
 
@@ -4344,42 +5152,21 @@ s_Molecule_Loadpsf(int argc, VALUE *argv, VALUE self)
        VALUE fname, pdbname;
        char *fstr, *pdbstr;
        Molecule *mol;
-       char errbuf[128];
        int retval;
        Data_Get_Struct(self, Molecule, mol);
        if (mol->natoms > 0)
                return Qnil;  /*  Must be a new molecule  */
+       MoleculeClearLoadSaveErrorMessage();
        rb_scan_args(argc, argv, "11", &fname, &pdbname);
        fstr = FileStringValuePtr(fname);
-       retval = MoleculeLoadPsfFile(mol, fstr, errbuf, sizeof errbuf);
-       if (retval != 0) {
-       /*      if (retval == -1)
-                       return Qnil; */
-               rb_raise(rb_eMolbyError, errbuf);
-       }
+       retval = MoleculeLoadPsfFile(mol, fstr, &gLoadSaveErrorMessage);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load psf", fstr);
        pdbstr = NULL;
        if (!NIL_P(pdbname)) {
                pdbstr = strdup(FileStringValuePtr(pdbname));
-/*
-       2008.7.19. The pdbfile should be explicitly given
-       } else {
-               int len = strlen(fstr);
-               if (len > 4 && strcmp(fstr + len - 4, ".psf") == 0) {
-                       pdbstr = ALLOC_N(char, len + 1);
-                       strcpy(pdbstr, fstr);
-                       strcpy(pdbstr + len - 4, ".pdb");
-               }
-       }
-       if (pdbstr != NULL) {
-*/
-               retval = MoleculeReadCoordinatesFromPdbFile(mol, pdbstr, errbuf, sizeof errbuf);
+               retval = MoleculeReadCoordinatesFromPdbFile(mol, pdbstr, &gLoadSaveErrorMessage);
                free(pdbstr);
-       /*      if (retval == -1 && NIL_P(pdbname))
-                       return Qtrue;
-               else 
-       */
-               if (retval != 0)
-                       rb_raise(rb_eMolbyError, errbuf);
+               s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load coordinates from pdb", pdbstr);
        }
        return Qtrue;
 }
@@ -4398,17 +5185,13 @@ s_Molecule_Loadpdb(int argc, VALUE *argv, VALUE self)
        VALUE fname;
        char *fstr;
        Molecule *mol;
-       char errbuf[128];
        int retval;
        Data_Get_Struct(self, Molecule, mol);
        rb_scan_args(argc, argv, "1", &fname);
+       MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
-       retval = MoleculeReadCoordinatesFromPdbFile(mol, fstr, errbuf, sizeof errbuf);
-       if (retval != 0) {
-       /*      if (retval == -1)
-                       return Qnil; */
-               rb_raise(rb_eMolbyError, errbuf);
-       }
+       retval = MoleculeReadCoordinatesFromPdbFile(mol, fstr, &gLoadSaveErrorMessage);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load pdb", fstr);
        return Qtrue;   
 }
 
@@ -4425,17 +5208,13 @@ s_Molecule_Loaddcd(int argc, VALUE *argv, VALUE self)
        VALUE fname;
        char *fstr;
        Molecule *mol;
-       char errbuf[128];
        int retval;
        Data_Get_Struct(self, Molecule, mol);
        rb_scan_args(argc, argv, "1", &fname);
+       MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
-       retval = MoleculeReadCoordinatesFromDcdFile(mol, fstr, errbuf, sizeof errbuf);
-       if (retval != 0) {
-       /*      if (retval == -1)
-                       return Qnil; */
-               rb_raise(rb_eMolbyError, errbuf);
-       }
+       retval = MoleculeReadCoordinatesFromDcdFile(mol, fstr, &gLoadSaveErrorMessage);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load dcd", fstr);
        return Qtrue;   
 }
 
@@ -4452,17 +5231,13 @@ s_Molecule_Loadtep(int argc, VALUE *argv, VALUE self)
        VALUE fname;
        char *fstr;
        Molecule *mol;
-       char errbuf[128];
        int retval;
        Data_Get_Struct(self, Molecule, mol);
        rb_scan_args(argc, argv, "1", &fname);
+       MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
-       retval = MoleculeLoadTepFile(mol, fstr, errbuf, sizeof errbuf);
-       if (retval != 0) {
-       /*      if (retval == -1)
-                       return Qnil; */
-               rb_raise(rb_eMolbyError, errbuf);
-       }
+       retval = MoleculeLoadTepFile(mol, fstr, &gLoadSaveErrorMessage);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load ORTEP file", fstr);
        return Qtrue;   
 }
 
@@ -4479,17 +5254,13 @@ s_Molecule_Loadres(int argc, VALUE *argv, VALUE self)
        VALUE fname;
        char *fstr;
        Molecule *mol;
-       char errbuf[128];
        int retval;
        Data_Get_Struct(self, Molecule, mol);
        rb_scan_args(argc, argv, "1", &fname);
+       MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
-       retval = MoleculeLoadShelxFile(mol, fstr, errbuf, sizeof errbuf);
-       if (retval != 0) {
-       /*      if (retval == -1)
-                       return Qnil; */
-               rb_raise(rb_eMolbyError, errbuf);
-       }
+       retval = MoleculeLoadShelxFile(mol, fstr, &gLoadSaveErrorMessage);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load SHELX res file", fstr);
        return Qtrue;   
 }
 
@@ -4506,17 +5277,13 @@ s_Molecule_Loadfchk(int argc, VALUE *argv, VALUE self)
        VALUE fname;
        char *fstr;
        Molecule *mol;
-       char errbuf[128];
        int retval;
        Data_Get_Struct(self, Molecule, mol);
        rb_scan_args(argc, argv, "1", &fname);
+       MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
-       retval = MoleculeLoadGaussianFchkFile(mol, fstr, errbuf, sizeof errbuf);
-       if (retval != 0) {
-       /*      if (retval == -1)
-                       return Qnil; */
-               rb_raise(rb_eMolbyError, errbuf);
-       }
+       retval = MoleculeLoadGaussianFchkFile(mol, fstr, &gLoadSaveErrorMessage);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load Gaussian fchk", fstr);
        return Qtrue;   
 }
 
@@ -4533,19 +5300,15 @@ s_Molecule_Loaddat(int argc, VALUE *argv, VALUE self)
        VALUE fname;
        char *fstr;
        Molecule *mol;
-       char errbuf[128];
        int retval;
        Data_Get_Struct(self, Molecule, mol);
        rb_scan_args(argc, argv, "1", &fname);
+       MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
        MyAppCallback_showProgressPanel("Loading GAMESS dat file...");
-       retval = MoleculeLoadGamessDatFile(mol, fstr, errbuf, sizeof errbuf);
+       retval = MoleculeLoadGamessDatFile(mol, fstr, &gLoadSaveErrorMessage);
        MyAppCallback_hideProgressPanel();
-       if (retval != 0) {
-       /*      if (retval == -1)
-                       return Qnil; */
-               rb_raise(rb_eMolbyError, errbuf);
-       }
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load GAMESS dat", fstr);
        return Qtrue;   
 }
 
@@ -4560,11 +5323,12 @@ s_Molecule_Savembsf(VALUE self, VALUE fname)
 {
        char *fstr;
     Molecule *mol;
-       char errbuf[128];
+       int retval;
     Data_Get_Struct(self, Molecule, mol);
+       MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
-       if (MoleculeWriteToMbsfFile(mol, fstr, errbuf, sizeof errbuf) != 0)
-               rb_raise(rb_eMolbyError, errbuf);
+       retval = MoleculeWriteToMbsfFile(mol, fstr, &gLoadSaveErrorMessage);
+       s_Molecule_RaiseOnLoadSave(retval, 0, "Failed to save mbsf", fstr);
        return Qtrue;
 }
 
@@ -4579,11 +5343,12 @@ s_Molecule_Savepsf(VALUE self, VALUE fname)
 {
        char *fstr;
     Molecule *mol;
-       char errbuf[128];
+       int retval;
     Data_Get_Struct(self, Molecule, mol);
+       MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
-       if (MoleculeWriteToPsfFile(mol, fstr, errbuf, sizeof errbuf) != 0)
-               rb_raise(rb_eMolbyError, errbuf);
+       retval = MoleculeWriteToPsfFile(mol, fstr, &gLoadSaveErrorMessage);
+       s_Molecule_RaiseOnLoadSave(retval, 0, "Failed to save psf", fstr);
        return Qtrue;
 }
 
@@ -4598,11 +5363,12 @@ s_Molecule_Savepdb(VALUE self, VALUE fname)
 {
        char *fstr;
     Molecule *mol;
-       char errbuf[128];
+       int retval;
     Data_Get_Struct(self, Molecule, mol);
+       MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
-       if (MoleculeWriteToPdbFile(mol, fstr, errbuf, sizeof errbuf) != 0)
-               rb_raise(rb_eMolbyError, errbuf);
+       retval = MoleculeWriteToPdbFile(mol, fstr, &gLoadSaveErrorMessage);
+       s_Molecule_RaiseOnLoadSave(retval, 0, "Failed to save pdb", fstr);
        return Qtrue;
 }
 
@@ -4617,41 +5383,21 @@ s_Molecule_Savedcd(VALUE self, VALUE fname)
 {
        char *fstr;
     Molecule *mol;
-       char errbuf[128];
-    Data_Get_Struct(self, Molecule, mol);
-       fstr = FileStringValuePtr(fname);
-       if (MoleculeWriteToDcdFile(mol, fstr, errbuf, sizeof errbuf) != 0)
-               rb_raise(rb_eMolbyError, errbuf);
-       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];
+       int retval;
     Data_Get_Struct(self, Molecule, mol);
+       MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
-       if (MoleculeWriteToTepFile(mol, fstr, errbuf, sizeof errbuf) != 0)
-               rb_raise(rb_eMolbyError, errbuf);
+       retval = MoleculeWriteToDcdFile(mol, fstr, &gLoadSaveErrorMessage);
+       s_Molecule_RaiseOnLoadSave(retval, 0, "Failed to save dcd", fstr);
        return Qtrue;
 }
-*/
 
 /*  load([ftype, ] fname, ...)  */
 static VALUE
 s_Molecule_LoadSave(int argc, VALUE *argv, VALUE self, int loadFlag)
 {
        VALUE rval;
-       char *argstr, *methname, *p;
+       char *argstr, *methname, *p, *type = "";
        ID mid = 0;
        int i;
        const char *ls = (loadFlag ? "load" : "save");
@@ -4667,6 +5413,7 @@ s_Molecule_LoadSave(int argc, VALUE *argv, VALUE self, int loadFlag)
                methname = ALLOC_N(char, lslen + strlen(argstr));
                strcpy(methname, ls);
                strcat(methname, argstr + 1);
+               type = argstr + 1;
                for (i = lslen; methname[i] != 0; i++)
                        methname[i] = tolower(methname[i]);
                mid = rb_intern(methname);
@@ -4675,7 +5422,7 @@ s_Molecule_LoadSave(int argc, VALUE *argv, VALUE self, int loadFlag)
                argv++;
                rval = rb_funcall2(self, mid, argc, argv);
                if (rval == Qnil)
-                       rb_raise(rb_eMolbyError, "the format specification \'%s\' seems to be wrong", argstr);
+                       goto failure;
                else
                        goto success;
        }
@@ -4683,6 +5430,7 @@ s_Molecule_LoadSave(int argc, VALUE *argv, VALUE self, int loadFlag)
        p = strrchr(argstr, '.');
        if (p != NULL) {
                p++;
+               type = p;
                for (methname = p; *methname != 0; methname++) {
                        if (!isalpha(*methname))
                                break;
@@ -4712,8 +5460,12 @@ s_Molecule_LoadSave(int argc, VALUE *argv, VALUE self, int loadFlag)
                        }
                }
        }
-       rb_raise(rb_eMolbyError, "the file %s cannot be %s", argstr, (loadFlag ? "loaded" : "saved"));
-       
+failure:
+       rval = rb_str_to_str(argv[0]);
+       asprintf(&p, "Failed to %s file %s", (loadFlag ? "load" : "save"), type);
+       s_Molecule_RaiseOnLoadSave(1, loadFlag, p, StringValuePtr(rval));
+       return Qnil;  /*  Does not reach here  */
+
 success:
        {
                /*  Register the path  */
@@ -4765,54 +5517,160 @@ s_Molecule_Save(int argc, VALUE *argv, VALUE self)
 
 /*
  *  call-seq:
- *     name       -> String
+ *     open        -> Molecule
+ *     open(file)  -> Molecule
  *
- *  Returns the display name of the molecule. If the molecule has no associated
- *  document, then returns nil.
+ *  Create a new molecule from file as a document. If file is not given, an untitled document is created.
  */
 static VALUE
-s_Molecule_Name(VALUE self)
+s_Molecule_Open(int argc, VALUE *argv, VALUE self)
 {
-    Molecule *mol;
-       char buf[1024];
-    Data_Get_Struct(self, Molecule, mol);
-       MoleculeCallback_displayName(mol, buf, sizeof buf);
-       if (buf[0] == 0)
-               return Qnil;
+       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
-               return rb_str_new2(buf);
+               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:
- *     set_name(string) -> self
+ *     new  -> Molecule
+ *     new(file, *args)  -> Molecule
  *
- *  Set the name of an untitled molecule. If the molecule is not associated with window
- *  or it already has an associated file, then exception is thrown.
+ *  Create a new molecule and call "load" method with the same arguments.
  */
 static VALUE
-s_Molecule_SetName(VALUE self, VALUE nval)
+s_Molecule_Initialize(int argc, VALUE *argv, VALUE self)
 {
-    Molecule *mol;
-    Data_Get_Struct(self, Molecule, mol);
-       if (MoleculeCallback_setDisplayName(mol, StringValuePtr(nval)))
-               rb_raise(rb_eMolbyError, "Cannot change the window title");
-       return 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:
- *     path       -> String
+ *     error_message       -> String
  *
- *  Returns the full path name of the molecule, if it is associated with a file.
- *  If the molecule has no associated file, then returns nil.
+ *  Get the error_message from the last load/save method. If no error, returns nil.
  */
 static VALUE
-s_Molecule_Path(VALUE self)
+s_Molecule_ErrorMessage(VALUE klass)
 {
-    Molecule *mol;
-       char buf[1024];
+       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
+ *  document, then returns nil.
+ */
+static VALUE
+s_Molecule_Name(VALUE self)
+{
+    Molecule *mol;
+       char buf[1024];
+    Data_Get_Struct(self, Molecule, mol);
+       MoleculeCallback_displayName(mol, buf, sizeof buf);
+       if (buf[0] == 0)
+               return Qnil;
+       else
+               return Ruby_NewEncodedStringValue2(buf);
+}
+
+/*
+ *  call-seq:
+ *     set_name(string) -> self
+ *
+ *  Set the name of an untitled molecule. If the molecule is not associated with window
+ *  or it already has an associated file, then exception is thrown.
+ */
+static VALUE
+s_Molecule_SetName(VALUE self, VALUE nval)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (MoleculeCallback_setDisplayName(mol, StringValuePtr(nval)))
+               rb_raise(rb_eMolbyError, "Cannot change the window title");
+       return self;
+}
+
+
+/*
+ *  call-seq:
+ *     path       -> String
+ *
+ *  Returns the full path name of the molecule, if it is associated with a file.
+ *  If the molecule has no associated file, then returns nil.
+ */
+static VALUE
+s_Molecule_Path(VALUE self)
+{
+    Molecule *mol;
+       char buf[1024];
     Data_Get_Struct(self, Molecule, mol);
        MoleculeCallback_pathName(mol, buf, sizeof buf);
        if (buf[0] == 0)
@@ -4844,7 +5702,7 @@ s_Molecule_Dir(VALUE self)
                p = strrchr(buf, '/');
                if (p != NULL)
                        *p = 0;
-               return rb_str_new2(buf);
+               return Ruby_NewEncodedStringValue2(buf);
        }
 }
 
@@ -4866,7 +5724,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];
@@ -4885,55 +5743,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)
@@ -5105,1969 +5919,2339 @@ 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
- *
- *  Returns the MD parameter for the idx-th angle.
- */
-/*
-static VALUE
-s_Molecule_AnglePar(VALUE self, VALUE val)
-{
-    Molecule *mol;
-       AnglePar *ap;
-       UInt t1, t2, t3;
-       Int i1, i2, i3;
-       Int ival;
-    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);
-}
-*/
-/*
- *  call-seq:
- *     dihedral_par(idx)    -> ParameterRef
- *
- *  Returns the MD parameter for the idx-th dihedral.
- */
-/*
-static VALUE
-s_Molecule_DihedralPar(VALUE self, VALUE val)
-{
-    Molecule *mol;
-       Int ival;
-       TorsionPar *tp;
-       UInt t1, t2, t3, t4;
-       Int i1, i2, i3, i4;
-    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);
-}
-*/
-/*
- *  call-seq:
- *     improper_par(idx)    -> ParameterRef
+ *     max_residue_number(atom_group = nil)     -> Integer
  *
- *  Returns the MD parameter for the idx-th improper.
+ *  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_ImproperPar(VALUE self, VALUE val)
+s_Molecule_MaxResSeq(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 maxSeq;
+       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));
+       maxSeq = MoleculeMaximumResidueNumber(mol, ig);
+       return (maxSeq >= 0 ? INT2NUM(maxSeq) : Qnil);
 }
-*/
+
 /*
  *  call-seq:
- *     vdw_par(idx)    -> ParameterRef
+ *     min_residue_number(atom_group = nil)     -> Integer
  *
- *  Returns the vdw parameter for the idx-th atom.
+ *  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_VdwPar(VALUE self, VALUE val)
+s_Molecule_MinResSeq(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       Int ival;
-       VdwPar *vp;
-       UInt t1;
+       VALUE gval;
+       int minSeq;
+       IntGroup *ig;
     Data_Get_Struct(self, Molecule, mol);
-       ival = NUM2INT(rb_Integer(val));
-       if (ival < -mol->natoms || ival >= mol->natoms)
-               rb_raise(rb_eMolbyError, "atom index (%d) out of range", ival);
-       if (ival < 0)
-               ival += mol->natoms;
-       s_RebuildMDParameterIfNecessary(self, Qtrue);
-       t1 = ATOM_AT_INDEX(mol->atoms, ival)->type;
-       vp = ParameterLookupVdwPar(mol->par, t1, 0);
-       if (vp == NULL)
-               return Qnil;
-       return ValueFromMoleculeWithParameterTypeAndIndex(mol, kVdwParType, vp - mol->par->vdwPars);
+       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:
- *     start_step       -> Integer
+ *     each_atom(atom_group = nil) {|aref| ...}
  *
- *  Returns the start step (defined by dcd format).
+ *  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_StartStep(VALUE self)
+s_Molecule_EachAtom(int argc, VALUE *argv, VALUE self)
 {
+       int i;
     Molecule *mol;
+       AtomRef *aref;
+       VALUE arval;
+       VALUE gval;
+       IntGroup *ig;
     Data_Get_Struct(self, Molecule, mol);
-       return INT2NUM(mol->startStep);
+       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
- *
- *  Set the start step (defined by dcd format).
- */
+#pragma mark ------ Atom Group ------
+
 static VALUE
-s_Molecule_SetStartStep(VALUE self, VALUE val)
+s_Molecule_AtomGroup_i(VALUE arg, VALUE values)
 {
-    Molecule *mol;
-    Data_Get_Struct(self, Molecule, mol);
-       mol->startStep = NUM2INT(rb_Integer(val));
-       return val;
+       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:
- *     steps_per_frame       -> 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.
  *
- *  Returns the number of steps between frames (defined by dcd format).
  */
 static VALUE
-s_Molecule_StepsPerFrame(VALUE self)
+s_Molecule_AtomGroup(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);
     Data_Get_Struct(self, Molecule, mol);
-       return INT2NUM(mol->stepsPerFrame);
-}
-
+       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
  *
- *  Set the number of steps between frames (defined by dcd format).
+ *  Returns the current selection.
  */
 static VALUE
-s_Molecule_SetStepsPerFrame(VALUE self, VALUE val)
+s_Molecule_Selection(VALUE self)
 {
     Molecule *mol;
+       IntGroup *ig;
+       VALUE val;
     Data_Get_Struct(self, Molecule, mol);
-       mol->stepsPerFrame = NUM2INT(rb_Integer(val));
+       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;
+}
+
+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:
- *     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];
-    Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "11", &val, &cval);
-       if (val == Qnil) {
-               n = 0;
+       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 {
-               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);
-               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;
+       IntGroup *ig;
+       char *p, *pp, buf[16];
+       Int resid, n;
+       Atom *ap;
     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]));
+       
+       /*  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;
+    Molecule *mol;
+       VALUE group;
        IntGroup *ig;
-       Int *bonds, nbonds, i, temp[2];
-       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)  */
-       nbonds = 0;
-       bonds = NULL;
-       IntGroupIteratorInit(ig, &iter);
-       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  */
-                               temp[0] = i;
-                               temp[1] = n;
-                               AssignArray(&bonds, &nbonds, sizeof(Int) * 2, nbonds, temp);
-                       }
-               }
-       }
-       IntGroupIteratorRelease(&iter);
-       if (nbonds > 0) {
-               /*  Remove bonds  */
-       /*      temp[0] = kInvalidIndex;
-               AssignArray(&bonds, &nbonds, sizeof(Int) * 2, nbonds, temp); */
-               MolActionCreateAndPerform(mol1, gMolActionDeleteBonds, nbonds * 2, bonds);
-               free(bonds);
-       }
-       /*  Remove atoms  */
-       if (MolActionCreateAndPerform(mol1, gMolActionUnmergeMolecule, ig) == 0)
-               return Qnil;
+       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;
        }
-    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);
+       return rb_ary_new3(2, ValueFromVector(&vmin), ValueFromVector(&vmax));
 }
 
-/*
- *  call-seq:
- *     duplicate_atom(atomref, pos = -1)  -> AtomRef
- *
- *  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_DuplicateAnAtom(int argc, VALUE *argv, VALUE self)
+/*  Get atom position or a vector  */
+static void
+s_Molecule_GetVectorFromArg(Molecule *mol, VALUE val, Vector *vp)
 {
-    Molecule *mol;
-       const Atom *apsrc;
-    Atom arec;
-       AtomRef *aref;
-       VALUE retval, aval, ival;
-       Int pos;
-    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);
+       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 {
-               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:
- *     create_bond(n1, n2, ...)       -> Integer
- *
- *  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_CreateBond(int argc, VALUE *argv, VALUE self)
-{
-    Molecule *mol;
-       Int i, *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 = 0; i < argc; i++) {
-               ip[i] = s_Molecule_AtomIndexFromValue(mol, argv[i]);
+               VectorFromValue(val, vp);
        }
-       ip[argc] = kInvalidIndex;
-       old_nbonds = mol->nbonds;
-       i = MolActionCreateAndPerform(mol, gMolActionAddBonds, argc, ip);
-       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
+ *     measure_bond(n1, n2)       -> Float
  *
- *  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.
+ *  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_RemoveBond(int argc, VALUE *argv, VALUE self)
+s_Molecule_MeasureBond(VALUE self, VALUE nval1, VALUE nval2)
 {
     Molecule *mol;
-       Int i, *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");
+       Vector v1, v2;
     Data_Get_Struct(self, Molecule, mol);
-       ip = ALLOC_N(Int, argc + 1);
-       for (i = 0; i < argc; i++)
-               ip[i] = s_Molecule_AtomIndexFromValue(mol, argv[i]);
-       ip[argc] = kInvalidIndex;
-       old_nbonds = mol->nbonds;
-       MolActionCreateAndPerform(mol, gMolActionDeleteBonds, argc, ip);
-       xfree(ip);
-       return INT2NUM(old_nbonds - mol->nbonds);
+       s_Molecule_GetVectorFromArg(mol, nval1, &v1);
+       s_Molecule_GetVectorFromArg(mol, nval2, &v2);
+       return rb_float_new(MoleculeMeasureBond(mol, &v1, &v2));
 }
 
 /*
  *  call-seq:
- *     add_angle(n1, n2, n3)       -> Molecule
+ *     measure_angle(n1, n2, n3)       -> Float
  *
- *  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.
+ *  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_AddAngle(VALUE self, VALUE v1, VALUE v2, VALUE v3)
+s_Molecule_MeasureAngle(VALUE self, VALUE nval1, VALUE nval2, VALUE nval3)
 {
-       Int n[4];
     Molecule *mol;
+       Vector v1, v2, v3;
+       Double d;
     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;
+       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:
- *     remove_angle(n1, n2, n3)       -> Molecule
+ *     measure_dihedral(n1, n2, n3, n4)       -> Float
  *
- *  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.
+ *  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_RemoveAngle(VALUE self, VALUE v1, VALUE v2, VALUE v3)
+s_Molecule_MeasureDihedral(VALUE self, VALUE nval1, VALUE nval2, VALUE nval3, VALUE nval4)
 {
-       Int n[4];
     Molecule *mol;
-       IntGroup *ig;
+       Vector v1, v2, v3, v4;
+       Double d;
     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;
+       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:
- *     add_dihedral(n1, n2, n3, n4)       -> Molecule
+ *     find_conflicts(limit[, group1[, group2 [, ignore_exclusion]]]) -> [[n1, n2], [n3, n4], ...]
  *
- *  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.
+ *  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_AddDihedral(VALUE self, VALUE v1, VALUE v2, VALUE v3, VALUE v4)
+s_Molecule_FindConflicts(int argc, VALUE *argv, VALUE self)
 {
-       Int n[5];
     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);
-       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;
+       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);
+                               }
+                       }
+               }
+       }
+       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:
- *     add_improper(n1, n2, n3, n4)       -> Molecule
+ *     find_close_atoms(atom, limit = 1.2, radius = 0.77)   -> array of Integers (atom indices)
  *
- *  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.
+ *  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_AddImproper(VALUE self, VALUE v1, VALUE v2, VALUE v3, VALUE v4)
+s_Molecule_FindCloseAtoms(int argc, VALUE *argv, VALUE self)
 {
-       Int n[5];
     Molecule *mol;
+       VALUE aval, limval, radval;
+       double limit, radius;
+       Int n1, nbonds, *bonds, an;
+       Vector v;
     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;
+       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:
- *     remove_improper(n1, n2, n3, n4)       -> Molecule
+ *     guess_bonds(limit = 1.2)       -> Integer
  *
- *  Remove improper 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.
+ *  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_RemoveImproper(VALUE self, VALUE v1, VALUE v2, VALUE v3, VALUE v4)
+s_Molecule_GuessBonds(int argc, VALUE *argv, VALUE self)
 {
-       Int n[5];
     Molecule *mol;
-       IntGroup *ig;
+       VALUE limval;
+       double limit;
+       Int nbonds, *bonds;
     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] = 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;
+       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 INT2NUM(nbonds);
 }
 
+#pragma mark ------ Cell and Symmetry ------
+
 /*
  *  call-seq:
- *     assign_residue(group, res)       -> Molecule
+ *     cell     -> [a, b, c, alpha, beta, gamma [, sig_a, sig_b, sig_c, sig_alpha, sig_beta, sig_gamma]]
  *
- *  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.
+ *  Returns the unit cell parameters. If cell is not set, returns nil.
  */
 static VALUE
-s_Molecule_AssignResidue(VALUE self, VALUE range, VALUE res)
+s_Molecule_Cell(VALUE self)
 {
     Molecule *mol;
-       IntGroup *ig;
-       char *p, *pp, buf[16];
-       Int resid, n;
-       Atom *ap;
+       int i;
+       VALUE val;
     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)
+       if (mol->cell == NULL)
                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);
+       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]));
+               }
        }
-       IntGroupRelease(ig);
-       return self;
+       return val;
 }
 
 /*
  *  call-seq:
- *     offset_residue(group, offset)       -> Molecule
+ *     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)
  *
- *  Offset the residue number of the specified atoms. If any of the residue number gets
- *  negative, then exception is thrown.
- *  This operation is undoable.
+ *  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_OffsetResidue(VALUE self, VALUE range, VALUE offset)
+s_Molecule_SetCell(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       IntGroup *ig;
-       int ofs, result;
+       VALUE val, cval;
+       int i, convert_coord, n;
+       double d[12];
     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;
+       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:
- *     renumber_atoms(array)       -> IntGroup
+ *     box -> [avec, bvec, cvec, origin, flags]
  *
- *  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.
+ *  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_RenumberAtoms(VALUE self, VALUE array)
+s_Molecule_Box(VALUE self)
 {
     Molecule *mol;
-       Int *new2old;
-       IntGroup *ig;
-       int i, n;
-       VALUE *valp, retval;
+       VALUE v[5], val;
     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;
+       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:
- *     find_close_atoms(atom, limit = 1.2)       -> array of Integers (atom indices)
+ *     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
  *
- *  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.
+ *  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_FindCloseAtoms(int argc, VALUE *argv, VALUE self)
+s_Molecule_SetBox(VALUE self, VALUE aval)
 {
     Molecule *mol;
-       VALUE aval, limval;
-       double limit;
-       Int n1, nbonds, *bonds;
+       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, "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);
+       if (aval == Qnil) {
+               MolActionCreateAndPerform(mol, gMolActionClearBox);
+               return self;
        }
-       return aval;
+       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);
+       return self;
 }
 
 /*
  *  call-seq:
- *     guess_bonds(limit = 1.2)       -> Integer
+ *     cell_periodicity -> [n1, n2, n3]
  *
- *  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.
+ *  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_GuessBonds(int argc, VALUE *argv, VALUE self)
+s_Molecule_CellPeriodicity(VALUE self)
 {
     Molecule *mol;
-       VALUE limval;
-       double limit;
-       Int nbonds, *bonds;
     Data_Get_Struct(self, Molecule, mol);
-       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);
-               free(bonds);
-       }
-       return INT2NUM(nbonds);
+       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:
- *     register_undo(script, *args)
+ *     self.cell_periodicity = [n1, n2, n3] or Integer or nil
+ *     set_cell_periodicity = [n1, n2, n3] or Integer or nil
  *
- *  Register an undo operation with the current 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.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_RegisterUndo(int argc, VALUE *argv, VALUE self)
+s_Molecule_SetCellPeriodicity(VALUE self, VALUE arg)
 {
-       Molecule *mol;
-       VALUE script, args;
-       MolAction *act;
+    Molecule *mol;
+       Int flag;
     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;
+       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;
 }
 
 /*
  *  call-seq:
- *     undo_enabled? -> bool
+ *     cell_flexibility -> bool
  *
- *  Returns true if undo is enabled for this molecule; otherwise no.
+ *  Returns the unit cell is flexible or not
  */
 static VALUE
-s_Molecule_UndoEnabled(VALUE self)
+s_Molecule_CellFlexibility(VALUE self)
 {
-    Molecule *mol;
-    Data_Get_Struct(self, Molecule, mol);
-       if (MolActionCallback_isUndoRegistrationEnabled(mol))
-               return Qtrue;
-       else return Qfalse;
+       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:
- *     undo_enabled = bool
+ *     self.cell_flexibility = bool
+ *     set_cell_flexibility(bool)
  *
- *  Enable or disable undo.
+ *  Change the unit cell is flexible or not
  */
 static VALUE
-s_Molecule_SetUndoEnabled(VALUE self, VALUE val)
+s_Molecule_SetCellFlexibility(VALUE self, VALUE arg)
 {
-    Molecule *mol;
-    Data_Get_Struct(self, Molecule, mol);
-       MolActionCallback_setUndoRegistrationEnabled(mol, (val != Qfalse && val != Qnil));
-       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:
- *     selection       -> IntGroup
+ *     cell_transform -> Transform
  *
- *  Returns the current selection.
+ *  Get the transform matrix that converts internal coordinates to cartesian coordinates.
+ *  If cell is not defined, nil is returned.
  */
 static VALUE
-s_Molecule_Selection(VALUE self)
+s_Molecule_CellTransform(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);
-       } else {
-               val = IntGroup_Alloc(rb_cIntGroup);
-       }
-       return val;
+       if (mol == NULL || mol->cell == NULL)
+               return Qnil;
+       return ValueFromTransform(&(mol->cell->tr));
 }
 
+/*
+ *  call-seq:
+ *     symmetry -> Array of Transforms
+ *     symmetries -> Array of Transforms
+ *
+ *  Get the currently defined symmetry operations. If no symmetry operation is defined,
+ *  returns an empty array.
+ */
 static VALUE
-s_Molecule_SetSelectionSub(VALUE self, VALUE val, int undoable)
+s_Molecule_Symmetry(VALUE self)
 {
     Molecule *mol;
-       IntGroup *ig;
+       VALUE val;
+       int i;
     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);
+       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]));
+       }
        return val;
 }
 
 /*
  *  call-seq:
- *     selection = IntGroup
+ *     nsymmetries -> Integer
  *
- *  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.
+ *  Get the number of currently defined symmetry operations.
  */
 static VALUE
-s_Molecule_SetSelection(VALUE self, VALUE val)
+s_Molecule_Nsymmetries(VALUE self)
 {
-       return s_Molecule_SetSelectionSub(self, val, 0);
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       return INT2NUM(mol->nsyms);
 }
 
 /*
  *  call-seq:
- *     set_undoable_selection(IntGroup)
+ *     add_symmetry(Transform) -> Integer
  *
- *  Set the current selection with undo registration. The right-hand operand may be nil.
- *  This operation is undoable.
+ *  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_SetUndoableSelection(VALUE self, VALUE val)
+s_Molecule_AddSymmetry(VALUE self, VALUE trans)
 {
-       return s_Molecule_SetSelectionSub(self, val, 1);
+    Molecule *mol;
+       Transform tr;
+    Data_Get_Struct(self, Molecule, mol);
+       TransformFromValue(trans, &tr);
+       MolActionCreateAndPerform(mol, gMolActionAddSymmetryOperation, &tr);
+       return INT2NUM(mol->nsyms);
 }
 
 /*
  *  call-seq:
- *     hidden_atoms       -> IntGroup
+ *     remove_symmetry(count = nil) -> Integer
+ *     remove_symmetries(count = nil) -> Integer
  *
- *  Returns the currently hidden atoms.
+ *  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_HiddenAtoms(VALUE self)
+s_Molecule_RemoveSymmetry(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 cval;
+       int i, n;
     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, "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);
 }
 
 /*
  *  call-seq:
- *     set_hidden_atoms(IntGroup)
- *     self.hidden_atoms = IntGroup
+ *     wrap_unit_cell(group) -> Vector3D
  *
- *  Hide the specified atoms. This operation is _not_ undoable.
+ *  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_SetHiddenAtoms(VALUE self, VALUE val)
+s_Molecule_WrapUnitCell(VALUE self, VALUE gval)
 {
-       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;
+       IntGroup *ig;
+       Vector v, cv, dv;
     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);
-       }
-       return val; */
+       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);
+       IntGroupRelease(ig);
+       return ValueFromVector(&dv);
 }
 
 /*
  *  call-seq:
- *     select_frame(index)
- *     frame = index
+ *     expand_by_symmetry(group, sym, dx=0, dy=0, dz=0, allow_overlap = false) -> Array
  *
- *  Select the specified frame. If successful, returns true, otherwise returns false.
+ *  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_SelectFrame(VALUE self, VALUE val)
+s_Molecule_ExpandBySymmetry(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       int ival = NUM2INT(val);
+       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);
-       ival = MoleculeSelectFrame(mol, ival, 1);
-       if (ival >= 0)
-               return Qtrue;
-       else return Qfalse;
+       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]));
+       }
+       /*      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:
- *     frame -> Integer
+ *     amend_by_symmetry(group = nil) -> IntGroup
  *
- *  Get the current frame.
+ *  Expand the specified part of the molecule by the given symmetry operation.
+ *  Returns an IntGroup containing the added atoms.
  */
 static VALUE
-s_Molecule_Frame(VALUE self)
+s_Molecule_AmendBySymmetry(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
+       IntGroup *ig, *ig2;
+       VALUE rval, gval;
     Data_Get_Struct(self, Molecule, mol);
-       return INT2NUM(mol->cframe);
+       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:
- *     nframes -> Integer
+ *     translate(vec, group = nil)       -> Molecule
  *
- *  Get the number of frames.
+ *  Translate the molecule by vec. If group is given, only atoms in the group are moved.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_Nframes(VALUE self)
+s_Molecule_Translate(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
+       VALUE vec, group;
+       Vector v;
+       IntGroup *ig;
     Data_Get_Struct(self, Molecule, mol);
-       return INT2NUM(MoleculeGetNumberOfFrames(mol));
+       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);
+       MolActionCreateAndPerform(mol, gMolActionTranslateAtoms, &v, ig);
+       if (ig != NULL)
+               IntGroupRelease(ig);
+       return self;
 }
 
 /*
  *  call-seq:
- *     insert_frame(integer, coordinates = nil, cell_axes = nil) -> bool
- *     insert_frames(intGroup = nil, coordinates = nil, cell_axes = nil) -> bool
+ *     rotate(axis, angle, center = [0,0,0], group = nil)       -> Molecule
  *
- *  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.
+ *  Rotate the molecule. The axis must not a zero vector. angle is given in degree.
+ *  If group is given, only atoms in the group are moved.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_InsertFrames(int argc, VALUE *argv, VALUE self)
+s_Molecule_Rotate(int argc, VALUE *argv, VALUE self)
 {
-       VALUE val, coords, cells;
     Molecule *mol;
+       volatile VALUE aval, anval, cval, gval;
+       Double angle;
+       Vector av, cv;
+       Transform tr;
        IntGroup *ig;
-       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, "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]);
-               }
-       }
-       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);
+       rb_scan_args(argc, argv, "22", &aval, &anval, &cval, &gval);
+       ig = (NIL_P(gval) ? NULL : s_Molecule_AtomGroupFromValue(self, gval));
+       angle = NUM2DBL(rb_Float(anval)) * kDeg2Rad;
+       VectorFromValue(aval, &av);
+       if (NIL_P(cval))
+               cv.x = cv.y = cv.z = 0.0;
+       else
+               VectorFromValue(cval, &cv);
+       if (TransformForRotation(tr, &av, angle, &cv))
+               rb_raise(rb_eMolbyError, "rotation axis cannot be a zero vector");
+       MolActionCreateAndPerform(mol, gMolActionTransformAtoms, &tr, ig);
+       if (ig != NULL)
+               IntGroupRelease(ig);
+       return self;
 }
 
 /*
  *  call-seq:
- *     create_frame(coordinates = nil) -> Integer
- *     create_frames(coordinates = nil) -> Integer
+ *     reflect(axis, center = [0,0,0], group = nil)       -> Molecule
  *
- *  Same as molecule.insert_frames(nil, coordinates).
+ *  Reflect the molecule by the plane which is perpendicular to axis and including center. 
+ *  axis must not be a zero vector.
+ *  If group is given, only atoms in the group are moved.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_CreateFrames(int argc, VALUE *argv, VALUE self)
+s_Molecule_Reflect(int argc, VALUE *argv, 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;
+       volatile VALUE aval, cval, gval;
+       Vector av, cv;
+       Transform tr;
+       IntGroup *ig;
+    Data_Get_Struct(self, Molecule, mol);
+       rb_scan_args(argc, argv, "12", &aval, &cval, &gval);
+       ig = (NIL_P(gval) ? NULL : s_Molecule_AtomGroupFromValue(self, gval));
+       VectorFromValue(aval, &av);
+       if (NIL_P(cval))
+               cv.x = cv.y = cv.z = 0.0;
+       else
+               VectorFromValue(cval, &cv);
+       if (TransformForReflection(tr, &av, &cv))
+               rb_raise(rb_eMolbyError, "reflection axis cannot be a zero vector");
+       MolActionCreateAndPerform(mol, gMolActionTransformAtoms, &tr, ig);
+       if (ig != NULL)
+               IntGroupRelease(ig);
+       return self;
 }
 
 /*
  *  call-seq:
- *     remove_frames(IntGroup, wantCoordinates = false)
+ *     invert(center = [0,0,0], group = nil)       -> Molecule
  *
- *  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.
+ *  Invert the molecule with the given center.
+ *  If group is given, only atoms in the group are moved.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_RemoveFrames(int argc, VALUE *argv, VALUE self)
+s_Molecule_Invert(int argc, VALUE *argv, VALUE self)
 {
-       VALUE val, flag;
-       VALUE retval;
-    Molecule *mol;
+       Molecule *mol;
+       volatile VALUE cval, gval;
+       Vector cv;
+       Transform tr;
        IntGroup *ig;
-       int count;
     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);
-               }
-       } else retval = Qtrue;
-       if (MolActionCreateAndPerform(mol, gMolActionRemoveFrames, ig) >= 0)
-               return retval;
-       else return Qnil;
+       rb_scan_args(argc, argv, "02", &cval, &gval);
+       ig = (NIL_P(gval) ? NULL : s_Molecule_AtomGroupFromValue(self, gval));
+       if (NIL_P(cval))
+               cv.x = cv.y = cv.z = 0.0;
+       else
+               VectorFromValue(cval, &cv);
+       TransformForInversion(tr, &cv);
+       MolActionCreateAndPerform(mol, gMolActionTransformAtoms, &tr, ig);
+       if (ig != NULL)
+               IntGroupRelease(ig);
+       return self;
 }
 
 /*
  *  call-seq:
- *     each_frame {|n| ...}
+ *     transform(transform, group = nil)       -> Molecule
  *
- *  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.
+ *  Transform the molecule by the given Transform object.
+ *  If group is given, only atoms in the group are moved.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_EachFrame(VALUE self)
+s_Molecule_Transform(int argc, VALUE *argv, VALUE self)
 {
-       int i, cframe, nframes;
     Molecule *mol;
+       VALUE trans, group;
+       Transform tr;
+       IntGroup *ig;
     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_scan_args(argc, argv, "11", &trans, &group);
+       ig = (NIL_P(group) ? NULL : s_Molecule_AtomGroupFromValue(self, group));
+       TransformFromValue(trans, &tr);
+       /*      MoleculeTransform(mol, tr, ig); */
+       MolActionCreateAndPerform(mol, gMolActionTransformAtoms, &tr, ig);
+       if (ig != NULL)
+               IntGroupRelease(ig);
+       return self;
 }
 
 /*
  *  call-seq:
- *     set_atom_attr(index, key, value)
+ *     transform_for_symop(symop, is_cartesian = nil) -> Transform
  *
- *  Set the atom attribute for the specified atom.
- *  This operation is undoable.
+ *  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_SetAtomAttr(VALUE self, VALUE idx, VALUE key, VALUE val)
+s_Molecule_TransformForSymop(int argc, VALUE *argv, VALUE self)
 {
-       Molecule *mol;
-       VALUE aref, oldval;
+    Molecule *mol;
+       VALUE sval, fval;
+       Symop symop;
+       Transform tr;
     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;
+       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:
- *     get_atom_attr(index, key)
+ *     symop_for_transform(transform, is_cartesian = nil) -> [sym, dx, dy, dz]
  *
- *  Get the atom attribute for the specified atom.
+ *  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_GetAtomAttr(VALUE self, VALUE idx, VALUE key)
+s_Molecule_SymopForTransform(int argc, VALUE *argv, VALUE self)
 {
-       return s_Molecule_SetAtomAttr(self, idx, key, Qundef);
+    Molecule *mol;
+       VALUE tval, fval;
+       Symop symop;
+       Transform tr;
+       int n;
+    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  */
+       }
+}
+
+#pragma mark ------ Frames ------
+
+/*
+ *  call-seq:
+ *     select_frame(index)
+ *     frame = index
+ *
+ *  Select the specified frame. If successful, returns true, otherwise returns false.
+ */
+static VALUE
+s_Molecule_SelectFrame(VALUE self, VALUE val)
+{
+    Molecule *mol;
+       int ival = NUM2INT(val);
+    Data_Get_Struct(self, Molecule, mol);
+       ival = MoleculeSelectFrame(mol, ival, 1);
+       if (ival >= 0)
+               return Qtrue;
+       else return Qfalse;
+}
+
+/*
+ *  call-seq:
+ *     frame -> Integer
+ *
+ *  Get the current frame.
+ */
+static VALUE
+s_Molecule_Frame(VALUE self)
+{
+    Molecule *mol;
+    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;
+       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, "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]);
+               }
+       }
+       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);
+}
+
+/*
+ *  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)
+{
+       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:
+ *     remove_frames(IntGroup, wantCoordinates = false)
+ *
+ *  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_RemoveFrames(int argc, VALUE *argv, VALUE self)
+{
+       VALUE val, flag;
+       VALUE retval;
+    Molecule *mol;
+       IntGroup *ig;
+       int count;
+    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);
+               }
+       } else retval = Qtrue;
+       if (MolActionCreateAndPerform(mol, gMolActionRemoveFrames, ig) >= 0)
+               return retval;
+       else return Qnil;
+}
+
+/*
+ *  call-seq:
+ *     each_frame {|n| ...}
+ *
+ *  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_EachFrame(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;
 }
 
 /*
@@ -7082,14 +8266,16 @@ static VALUE
 s_Molecule_GetCoordFromFrame(int argc, VALUE *argv, VALUE self)
 {
        Molecule *mol;
-       VALUE ival, gval;
+       VALUE ival, gval, cval;
        Int index, i, j, n, nn;
        IntGroup *ig;
        IntGroupIterator iter;
        Atom *ap;
        Vector *vp;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "11", &ival, &gval);
+       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)
@@ -7127,10 +8313,63 @@ s_Molecule_GetCoordFromFrame(int argc, VALUE *argv, VALUE self)
                }
                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:
+ *     reorder_frames(old_indices)
+ *
+ *  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_ReorderFrames(VALUE self, VALUE aval)
+{
+       Molecule *mol;
+       Int *ip, *ip2, i, n, nframes;
+    Data_Get_Struct(self, Molecule, mol);
+       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;
+       }
+       free(ip2);
+       MolActionCreateAndPerform(mol, gMolActionReorderFrames, nframes, ip);
+       free(ip);
        return self;
 }
 
+#pragma mark ------ Fragments ------
+
 /*
  *  call-seq:
  *     fragment(n1, *exatoms)  -> IntGroup
@@ -7192,24 +8431,77 @@ s_Molecule_Fragment(int argc, VALUE *argv, VALUE self)
 
 /*
  *  call-seq:
- *     each_fragment {|group| ...}
+ *     fragments(exclude = nil)
  *
- *  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.
+ *  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_EachFragment(VALUE self)
+s_Molecule_Fragments(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       IntGroup *ag, *fg;
-       VALUE gval;
+       IntGroup *ag, *fg, *eg;
+       VALUE gval, exval, retval;
     Data_Get_Struct(self, Molecule, mol);
-       if (mol == NULL || mol->natoms == 0)
-               return self;
-       ag = IntGroupNewWithPoints(0, mol->natoms, -1);
+       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;
+}
+
+/*
+ *  call-seq:
+ *     each_fragment(exclude = nil) {|group| ...}
+ *
+ *  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_EachFragment(int argc, VALUE *argv, VALUE self)
+{
+    Molecule *mol;
+       IntGroup *ag, *fg, *eg;
+       VALUE gval, exval;
+    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, NULL);
+               fg = MoleculeFragmentExcludingAtomGroup(mol, n, eg);
                if (fg == NULL)
                        rb_raise(rb_eMolbyError, "internal error during each_fragment");
                gval = ValueFromIntGroup(fg);
@@ -7218,6 +8510,8 @@ s_Molecule_EachFragment(VALUE self)
                IntGroupRelease(fg);
        }
        IntGroupRelease(ag);
+       if (eg != NULL)
+               IntGroupRelease(eg);
        return self;
 }
 
@@ -7256,655 +8550,46 @@ s_Molecule_Detachable_P(VALUE self, VALUE gval)
  */
 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);
-       }
-       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);
-       }
-       IntGroupRelease(bg);
-       IntGroupRelease(ig);
-       return retval;
-}
-
-/*
- *  call-seq:
- *     translate(vec, group = nil)       -> Molecule
- *
- *  Translate the molecule by vec. If group is given, only atoms in the group are moved.
- *  This operation is undoable.
- */
-static VALUE
-s_Molecule_Translate(int argc, VALUE *argv, VALUE self)
-{
-    Molecule *mol;
-       VALUE vec, group;
-       Vector v;
-       IntGroup *ig;
-    Data_Get_Struct(self, Molecule, mol);
-       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);
-       MolActionCreateAndPerform(mol, gMolActionTranslateAtoms, &v, ig);
-       if (ig != NULL)
-               IntGroupRelease(ig);
-       return self;
-}
-
-/*
- *  call-seq:
- *     rotate(axis, angle, center = [0,0,0], group = nil)       -> Molecule
- *
- *  Rotate the molecule. The axis must not a zero vector. angle is given in degree.
- *  If group is given, only atoms in the group are moved.
- *  This operation is undoable.
- */
-static VALUE
-s_Molecule_Rotate(int argc, VALUE *argv, VALUE self)
-{
-    Molecule *mol;
-       volatile VALUE aval, anval, cval, gval;
-       Double angle;
-       Vector av, cv;
-       Transform tr;
-       IntGroup *ig;
-    Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "22", &aval, &anval, &cval, &gval);
-       ig = (NIL_P(gval) ? NULL : s_Molecule_AtomGroupFromValue(self, gval));
-       angle = NUM2DBL(rb_Float(anval)) * kDeg2Rad;
-       VectorFromValue(aval, &av);
-       if (NIL_P(cval))
-               cv.x = cv.y = cv.z = 0.0;
-       else
-               VectorFromValue(cval, &cv);
-       if (TransformForRotation(tr, &av, angle, &cv))
-               rb_raise(rb_eMolbyError, "rotation axis cannot be a zero vector");
-       MolActionCreateAndPerform(mol, gMolActionTransformAtoms, &tr, ig);
-       if (ig != NULL)
-               IntGroupRelease(ig);
-       return self;
-}
-
-/*
- *  call-seq:
- *     reflect(axis, center = [0,0,0], group = nil)       -> Molecule
- *
- *  Reflect the molecule by the plane which is perpendicular to axis and including center. 
- *  axis must not be a zero vector.
- *  If group is given, only atoms in the group are moved.
- *  This operation is undoable.
- */
-static VALUE
-s_Molecule_Reflect(int argc, VALUE *argv, VALUE self)
-{
-    Molecule *mol;
-       volatile VALUE aval, cval, gval;
-       Vector av, cv;
-       Transform tr;
-       IntGroup *ig;
-    Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "12", &aval, &cval, &gval);
-       ig = (NIL_P(gval) ? NULL : s_Molecule_AtomGroupFromValue(self, gval));
-       VectorFromValue(aval, &av);
-       if (NIL_P(cval))
-               cv.x = cv.y = cv.z = 0.0;
-       else
-               VectorFromValue(cval, &cv);
-       if (TransformForReflection(tr, &av, &cv))
-               rb_raise(rb_eMolbyError, "reflection axis cannot be a zero vector");
-       MolActionCreateAndPerform(mol, gMolActionTransformAtoms, &tr, ig);
-       if (ig != NULL)
-               IntGroupRelease(ig);
-       return self;
-}
-
-/*
- *  call-seq:
- *     invert(center = [0,0,0], group = nil)       -> Molecule
- *
- *  Invert the molecule with the given center.
- *  If group is given, only atoms in the group are moved.
- *  This operation is undoable.
- */
-static VALUE
-s_Molecule_Invert(int argc, VALUE *argv, VALUE self)
-{
-       Molecule *mol;
-       volatile VALUE cval, gval;
-       Vector cv;
-       Transform tr;
-       IntGroup *ig;
-    Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "02", &cval, &gval);
-       ig = (NIL_P(gval) ? NULL : s_Molecule_AtomGroupFromValue(self, gval));
-       if (NIL_P(cval))
-               cv.x = cv.y = cv.z = 0.0;
-       else
-               VectorFromValue(cval, &cv);
-       TransformForInversion(tr, &cv);
-       MolActionCreateAndPerform(mol, gMolActionTransformAtoms, &tr, ig);
-       if (ig != NULL)
-               IntGroupRelease(ig);
-       return self;
-}
-
-/*
- *  call-seq:
- *     transform(transform, group = nil)       -> Molecule
- *
- *  Transform the molecule by the given Transform object.
- *  If group is given, only atoms in the group are moved.
- *  This operation is undoable.
- */
-static VALUE
-s_Molecule_Transform(int argc, VALUE *argv, VALUE self)
-{
-    Molecule *mol;
-       VALUE trans, group;
-       Transform tr;
-       IntGroup *ig;
-    Data_Get_Struct(self, Molecule, mol);
-       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); */
-       MolActionCreateAndPerform(mol, gMolActionTransformAtoms, &tr, ig);
-       if (ig != NULL)
-               IntGroupRelease(ig);
-       return self;
-}
-
-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;
-       }
-}
-
-/*
- *  call-seq:
- *     center_of_mass(group = nil)       -> Vector3D
- *
- *  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_CenterOfMass(int argc, VALUE *argv, VALUE 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:
- *     centralize(group = nil)       -> self
- *
- *  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_Centralize(int argc, VALUE *argv, VALUE 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);
-       v.x = -v.x;
-       v.y = -v.y;
-       v.z = -v.z;
-       MolActionCreateAndPerform(mol, gMolActionTranslateAtoms, &v, NULL);
-       return self;
-}
-
-/*
- *  call-seq:
- *     bounds(group = nil)       -> [min, max]
- *
- *  Calculate the boundary. The return value is an array of two Vector3D objects.
- */
-static VALUE
-s_Molecule_Bounds(int argc, VALUE *argv, VALUE self)
-{
-    Molecule *mol;
-       VALUE group;
-       IntGroup *ig;
-       Vector vmin, vmax;
-       int n;
-       Atom *ap;
-    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;
-       }
-       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);
-       }
-}
-
-/*
- *  call-seq:
- *     measure_bond(n1, n2)       -> Float
- *
- *  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_MeasureBond(VALUE self, VALUE nval1, VALUE nval2)
-{
-    Molecule *mol;
-       Vector v1, v2;
-    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));
-}
-
-/*
- *  call-seq:
- *     measure_angle(n1, n2, n3)       -> Float
- *
- *  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_MeasureAngle(VALUE self, VALUE nval1, VALUE nval2, VALUE nval3)
-{
-    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);
-}
-
-/*
- *  call-seq:
- *     measure_dihedral(n1, n2, n3, n4)       -> Float
- *
- *  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_MeasureDihedral(VALUE self, VALUE nval1, VALUE nval2, VALUE nval3, VALUE nval4)
-{
-    Molecule *mol;
-       Vector v1, v2, v3, v4;
-       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);   
-       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:
- *     expand_by_symmetry(group, sym, dx=0, dy=0, dz=0) -> Array
- *
- *  Expand the specified part of the molecule by the given symmetry operation.
- *  Returns the array of atom indices corresponding to the expanded atoms.
- */
-static VALUE
-s_Molecule_ExpandBySymmetry(int argc, VALUE *argv, VALUE self)
-{
-    Molecule *mol;
-       VALUE gval, sval, xval, yval, zval, rval;
-       IntGroup *ig;
-       Int n[4];
-       Int natoms;
-       Int nidx, *idx;
-
-    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] >= 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]));
-       }
-/*     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;
-}
-
-/*
- *  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)
-{
-    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:
- *     symop_for_transform(transform, is_cartesian = nil) -> [sym, dx, dy, dz]
- *
- *  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_SymopForTransform(int argc, VALUE *argv, VALUE self)
-{
-    Molecule *mol;
-       VALUE tval, fval;
-       Symop symop;
-       Transform tr;
-       int n;
-    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  */
-       }
-}
-
-/*
- *  call-seq:
- *     wrap_unit_cell(group) -> Vector3D
- *
- *  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_WrapUnitCell(VALUE self, VALUE gval)
-{
-    Molecule *mol;
-       IntGroup *ig;
-       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);
-       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 ValueFromVector(&dv);
-}
-
-/*
- *  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);
-                               }
-                       }
-               }
+{
+       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
@@ -8045,7 +8730,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;
@@ -8055,7 +8740,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");
@@ -8066,7 +8751,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;
                }
@@ -8079,7 +8764,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++) {
@@ -8091,24 +8776,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);
@@ -8126,18 +8811,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);
@@ -8159,17 +8844,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);
@@ -8177,32 +8862,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
@@ -8416,6 +9103,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
@@ -8528,6 +9242,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
@@ -8551,6 +9288,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.
@@ -8575,7 +9419,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)
@@ -8614,7 +9458,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)
@@ -8636,7 +9480,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)
@@ -8682,7 +9526,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;
@@ -8716,41 +9560,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);
@@ -8790,11 +9743,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;
@@ -8806,8 +9765,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);
 }
 
 /*
@@ -8827,6 +9814,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;
 }
@@ -8846,21 +9861,177 @@ s_Molecule_NGraphics(VALUE self)
                rb_raise(rb_eMolbyError, "this molecule has no associated graphic view");
        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");
@@ -8868,29 +10039,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]));
 }
 
 /*
@@ -8905,7 +10054,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");
@@ -8913,15 +10064,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;
 }
@@ -8992,6 +10149,8 @@ s_Molecule_ShowText(VALUE self, VALUE arg)
        return Qnil;
 }
 
+#pragma mark ------ MD Support ------
+
 /*
  *  call-seq:
  *     md_arena -> MDArena
@@ -9056,6 +10215,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
@@ -9144,8 +10398,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");
@@ -9214,6 +10468,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.
@@ -9248,22 +10665,81 @@ s_Molecule_Elpot(VALUE self, VALUE ival)
 
 /*
  *  call-seq:
- *     add_gaussian_orbital_shell(sym, nprims, atom_index)
+ *     clear_basis_set
  *
- *  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;
- *  -2, D5-type.
+ *  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_AddGaussianOrbitalShell(VALUE self, VALUE symval, VALUE npval, VALUE aval)
+s_Molecule_ClearBasisSet(VALUE self)
 {
        Molecule *mol;
-       int sym, nprims, a_idx, n;
     Data_Get_Struct(self, Molecule, mol);
-       sym = NUM2INT(rb_Integer(symval));
-       nprims = NUM2INT(rb_Integer(npval));
+       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[, additional_exponent])
+ *
+ *  To be used internally. Add a gaussian orbital shell with the atom index, symmetry code,
+ *  and the number of primitives.
+ *  Additional exponent is for JANPA only; implements an additinal r^N component that
+ *  appears in cartesian->spherical conversion.
+ *  Symmetry code: 0, S-type; 1, P-type; -1, SP-type; 2, D-type; -2, D5-type;
+ *                 3, F-type; -3, F7-type; 4, G-type; -4, G9-type.
+ *  Or: "s", S-type; "p", P-type; "sp", SP-type; "d", D-type; "d5", D5-type;
+ *      "f", F-type; "f7", F7-type; "g", G-type; "g9", G9-type
+ */
+static VALUE
+s_Molecule_AddGaussianOrbitalShell(int argc, VALUE *argv, VALUE self)
+{
+       Molecule *mol;
+    int sym, nprims, a_idx, n, add_exp;
+    VALUE aval, symval, npval, addval;
+    Data_Get_Struct(self, Molecule, mol);
+    rb_scan_args(argc, argv, "31", &aval, &symval, &npval, &addval);
+    if (rb_obj_is_kind_of(symval, rb_cString)) {
+        const char *p = StringValuePtr(symval);
+        if (strcasecmp(p, "s") == 0)
+            sym = 0;
+        else if (strcasecmp(p, "p") == 0)
+            sym = 1;
+        else if (strcasecmp(p, "sp") == 0)
+            sym = -1;
+        else if (strcasecmp(p, "d") == 0)
+            sym = 2;
+        else if (strcasecmp(p, "d5") == 0)
+            sym = -2;
+        else if (strcasecmp(p, "f") == 0)
+            sym = 3;
+        else if (strcasecmp(p, "f7") == 0)
+            sym = -3;
+        else if (strcasecmp(p, "g") == 0)
+            sym = 4;
+        else if (strcasecmp(p, "g9") == 0)
+            sym = -4;
+        else
+            rb_raise(rb_eArgError, "Unknown orbital type '%s'", p);
+    } else {
+        sym = NUM2INT(rb_Integer(symval));
+    }
        a_idx = NUM2INT(rb_Integer(aval));
-       n = MoleculeAddGaussianOrbitalShell(mol, sym, nprims, a_idx);
+       nprims = NUM2INT(rb_Integer(npval));
+    if (addval != Qnil)
+        add_exp = NUM2INT(rb_Integer(addval));
+    else add_exp = 0;
+       n = MoleculeAddGaussianOrbitalShell(mol, a_idx, sym, nprims, add_exp);
        if (n == -1)
                rb_raise(rb_eMolbyError, "Molecule is emptry");
        else if (n == -2)
@@ -9277,59 +10753,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
@@ -9351,7 +10929,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");
@@ -9360,7 +10938,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)
@@ -9370,359 +10948,469 @@ 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");
-       return self;
+               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:
- *     search_equivalent_atoms(ig = nil)
+ *     get_mo_energy(idx)
  *
- *  Search equivalent atoms (within the atom group if given). Returns an array of integers.
+ *  To be used internally. Get the MO energy for the given MO index (1-based).
  */
 static VALUE
-s_Molecule_SearchEquivalentAtoms(int argc, VALUE *argv, VALUE self)
+s_Molecule_GetMOEnergy(VALUE self, VALUE ival)
 {
        Molecule *mol;
-       Int *result, i;
-       VALUE val;
-       IntGroup *ig;
+       Int idx, n;
+       Double energy;
     Data_Get_Struct(self, Molecule, mol);
-       if (mol->natoms == 0)
+       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;
-       rb_scan_args(argc, argv, "01", &val);
-       if (val != Qnil)
-               ig = IntGroupFromValue(val);
-       else ig = NULL;
-       result = MoleculeSearchEquivalentAtoms(mol, ig);
-       if (result == NULL)
-               rb_raise(rb_eMolbyError, "Failed to search equivalent atoms (out of memory?)");
-       if (ig != NULL)
-               IntGroupRelease(ig);
-       val = rb_ary_new2(mol->natoms);
-       for (i = 0; i < mol->natoms; i++)
-               rb_ary_push(val, INT2NUM(result[i]));
-       free(result);
-       return val;
+       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:
- *     create_pi_anchor(name, type, group [, weights]) -> index
- *     insert_pi_anchor(index, name, type, group [, weights]) -> index
- *     replace_pi_anchor(index, name, type, group [, weights]) -> index
+ *     set_mo_info(hash)
  *
- *  Create or replace a "pi anchor", which is an anchor point to define a metal-pi bond.
- *  If index is negative or no less than the current number of pi anchors, create
- *  a new anchor. Otherwise, replace the existing anchor.
- *  Name and type are Strings, and are similar to those in atoms.
- *  Group is a group of atoms to define a pi system to be bound to the metal.
- *  Weights (optional) is an Array of Floats, which determine the significance
- *  of the component atoms. If not given, then 1.0/N (N is the number of atoms
- *  in the group) is assumed for all atoms.
+ *  Set the MO info. hash keys: :type=>"RHF"|"UHF"|"ROHF",
+ *  :alpha=>integer, :beta=>integer
  */
 static VALUE
-s_Molecule_CreateOrReplacePiAnchor(int func, int argc, VALUE *argv, VALUE self)
+s_Molecule_SetMOInfo(VALUE self, VALUE hval)
 {
        Molecule *mol;
-       Int idx, i, j, atype, *ip;
-       VALUE ival, nval, tval, gval, wval;
-       char *np, *tp, aname[6];
-       Double *dp;
-       IntGroup *ig;
+       VALUE aval;
+       Int rflag, na, nb, n;
+       char *s;
     Data_Get_Struct(self, Molecule, mol);
-       if (func == 0) { /*  create  */
-               rb_scan_args(argc, argv, "31", &nval, &tval, &gval, &wval);
-               idx = mol->npiatoms;
+       if (mol->bset != NULL) {
+               rflag = mol->bset->rflag;
+               na = mol->bset->ne_alpha;
+               nb = mol->bset->ne_beta;
        } else {
-               rb_scan_args(argc, argv, "41", &ival, &nval, &tval, &gval, &wval);
-               idx = NUM2INT(rb_Integer(ival));
-               if (idx < 0)
-                       idx = mol->npiatoms;
-               if (idx > mol->npiatoms || (func == 2 && idx == mol->npiatoms))
-                       rb_raise(rb_eMolbyError, "pi anchor index out of range");
-       }
-       np = StringValuePtr(nval);
-       strncpy(aname, np, 4);
-       tp = StringValuePtr(tval);
-       atype = AtomTypeEncodeToUInt(tp);
-       ig = IntGroupFromValue(gval);
-       if ((i = IntGroupGetCount(ig)) < 2)
-               rb_raise(rb_eMolbyError, "too few atoms are given (at least 2 are needed)");
-       ip = (Int *)malloc(sizeof(Int) * i);
-       for (i = 0; (j = IntGroupGetNthPoint(ig, i)) >= 0; i++) {
-               ip[i] = j;
-               if (j < 0 || j >= mol->natoms) {
-                       free(ip);
-                       rb_raise(rb_eMolbyError, "the atom index (%d) is out of range", j);
-               }
-       }
-       dp = (Double *)malloc(sizeof(Double) * i);
-       if (wval != Qnil) {
-               wval = rb_ary_to_ary(wval);
-               if (RARRAY_LEN(wval) < i) {
-                       free(ip);
-                       free(dp);
-                       rb_raise(rb_eMolbyError, "the number of floats is not sufficient (%d needed but only %d given)", i, RARRAY_LEN(wval));
+               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;
                }
-               for (j = 0; j < i; j++) {
-                       dp[j] = NUM2DBL(rb_Float(RARRAY_PTR(wval)[j]));
+               if ((aval = rb_hash_aref(hval, sAlphaSym)) != Qnil) {
+                       n = NUM2INT(rb_Integer(aval));
+                       if (n >= 0)
+                               na = n;
                }
-       } else {
-               for (j = 0; j < i; j++) {
-                       dp[j] = 1.0 / i;
+               if ((aval = rb_hash_aref(hval, sBetaSym)) != Qnil) {
+                       n = NUM2INT(rb_Integer(aval));
+                       if (n >= 0)
+                               nb = n;
                }
+               MoleculeSetMOInfo(mol, rflag, na, nb);
        }
-       if (func == 0 || func == 1)  /*  Create or insert  */
-               MolActionCreateAndPerform(mol, gMolActionInsertOnePiAtom, idx, 4, aname, atype, i, ip, i, dp);
-       else  /*  Replace  */
-               MolActionCreateAndPerform(mol, gMolActionReplaceOnePiAtom, idx, 4, aname, atype, i, ip, i, dp);
-       free(ip);
-       free(dp);
-       return INT2NUM(idx);
-}
-
-static VALUE
-s_Molecule_CreatePiAnchor(int argc, VALUE *argv, VALUE self)
-{
-       return s_Molecule_CreateOrReplacePiAnchor(0, argc, argv, self);
-}
-
-static VALUE
-s_Molecule_InsertPiAnchor(int argc, VALUE *argv, VALUE self)
-{
-       return s_Molecule_CreateOrReplacePiAnchor(1, argc, argv, self);
-}
-
-static VALUE
-s_Molecule_ReplacePiAnchor(int argc, VALUE *argv, VALUE self)
-{
-       return s_Molecule_CreateOrReplacePiAnchor(2, argc, argv, self);
-}
-
-/*
- *  call-seq:
- *     remove_pi_anchor(idx)       -> Molecule
- *
- *  Remove the pi-anchor at the given index.
- */
-static VALUE
-s_Molecule_RemovePiAnchor(VALUE self, VALUE ival)
-{
-       Molecule *mol;
-       Int idx = NUM2INT(rb_Integer(ival));
-       Data_Get_Struct(self, Molecule, mol);
-       if (idx < 0 || idx >= mol->npiatoms)
-               rb_raise(rb_eMolbyError, "pi-anchor index (%d) is out of range (should be 0 <= index < %d)", idx, mol->npiatoms);
-       MolActionCreateAndPerform(mol, gMolActionRemoveOnePiAtom, idx);
        return self;
 }
 
 /*
  *  call-seq:
- *     pi_anchor(idx) -> nil or [name, type, group, weights]
+ *     get_mo_info(key)
  *
- *  Return the attributes of the idx-th pi anchor if present, otherwise nil.
+ *  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_PiAnchorAtIndex(VALUE self, VALUE ival)
+s_Molecule_GetMOInfo(VALUE self, VALUE kval)
 {
        Molecule *mol;
-       PiAtom *pp;
-       char aname[6], atype[8];
-       Int i, *connects;
-       VALUE cval, wval, rval;
-       Int idx = NUM2INT(rb_Integer(ival));
     Data_Get_Struct(self, Molecule, mol);
-       if (idx < 0 || idx >= mol->npiatoms)
+       if (mol->bset == NULL)
                return Qnil;
-       pp = mol->piatoms + idx;
-       strncpy(aname, pp->aname, 4);
-       aname[4] = 0;
-       AtomTypeDecodeToString(pp->type, atype);
-       cval = rb_ary_new2(pp->connect.count);
-       connects = AtomConnectData(&pp->connect);
-       for (i = 0; i < pp->connect.count; i++)
-               rb_ary_store(cval, i, INT2NUM(connects[i]));
-       wval = rb_ary_new2(pp->ncoeffs);
-       for (i = 0; i < pp->ncoeffs; i++)
-               rb_ary_store(wval, i, rb_float_new(pp->coeffs[i]));
-       rval = rb_ary_new3(4, rb_str_new2(aname), rb_str_new2(atype), cval, wval);
-       return rval;
+       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:
- *     pi_anchor_r(idx) -> Vector3D
+ *     mo_type
  *
- *  Calculate the cartesian coordinate of the idx-th pi anchor if present.
+ *  Returns either "RHF", "UHF", or "ROHF". If no MO info is present, returns nil.
  */
 static VALUE
-s_Molecule_PiAnchorR(VALUE self, VALUE ival)
+s_Molecule_MOType(VALUE self)
 {
-       Molecule *mol;
-       Vector v;
-       Int idx = NUM2INT(rb_Integer(ival));
-    Data_Get_Struct(self, Molecule, mol);
-       if (idx < 0 || idx >= mol->npiatoms)
-               rb_raise(rb_eMolbyError, "no pi anchor present: %d", idx);
-       MoleculeCalculatePiAtomPosition(mol, idx, &v);
-       return ValueFromVector(&v);
+       return s_Molecule_GetMOInfo(self, sTypeSym);
 }
 
-/*
- *  call-seq:
- *     pi_anchor_fract_r(idx) -> Vector3D
- *
- *  Calculate the fractional coordinate of the idx-th pi anchor if present.
- */
-static VALUE
-s_Molecule_PiAnchorFractR(VALUE self, VALUE ival)
-{
-       Molecule *mol;
-       Vector v;
-       Int idx = NUM2INT(rb_Integer(ival));
-    Data_Get_Struct(self, Molecule, mol);
-       if (idx < 0 || idx >= mol->npiatoms)
-               rb_raise(rb_eMolbyError, "no pi anchor present: %d", idx);
-       MoleculeCalculatePiAtomPosition(mol, idx, &v);
-       if (mol->cell != NULL)
-               TransformVec(&v, mol->cell->rtr, &v);
-       return ValueFromVector(&v);
-}
+#pragma mark ------ Molecular Topology ------
 
 /*
  *  call-seq:
- *     count_pi_anchors -> Integer
+ *     search_equivalent_atoms(ig = nil)
  *
- *  Return the number of defined pi anchors.
+ *  Search equivalent atoms (within the atom group if given). Returns an array of integers.
  */
 static VALUE
-s_Molecule_CountPiAnchors(VALUE self)
+s_Molecule_SearchEquivalentAtoms(int argc, VALUE *argv, VALUE self)
 {
        Molecule *mol;
+       Int *result, i;
+       VALUE val;
+       IntGroup *ig;
     Data_Get_Struct(self, Molecule, mol);
-       return INT2NUM(mol->npiatoms);
+       if (mol->natoms == 0)
+               return Qnil;
+       rb_scan_args(argc, argv, "01", &val);
+       if (val != Qnil)
+               ig = s_Molecule_AtomGroupFromValue(self, val);
+       else ig = NULL;
+       result = MoleculeSearchEquivalentAtoms(mol, ig);
+       if (result == NULL)
+               rb_raise(rb_eMolbyError, "Failed to search equivalent atoms (out of memory?)");
+       if (ig != NULL)
+               IntGroupRelease(ig);
+       val = rb_ary_new2(mol->natoms);
+       for (i = 0; i < mol->natoms; i++)
+               rb_ary_push(val, INT2NUM(result[i]));
+       free(result);
+       return val;
 }
 
 /*
  *  call-seq:
- *     create_pi_anchor_construct(n1, n2, n3 = nil, n4 = nil)       -> Integer
+ *     create_pi_anchor(name, group [, type] [, weights] [, index]) -> AtomRef
  *
- *  Create a bond, angle, or dihedral including one or more pi anchor points.
- *  The arguments can either be an atom representation (atom index, atom name, res_seq:name)
- *  or a pi-anchor representation (pi-anchor index, pi-anchor name).
- *  The pi-anchor index is represented by a negative integer -N, which corresponds to
- *  the (N-1)-th pi anchor.
- *  Returns the index for the newly created construct.
- *  This operation is undoable.
+ *  Create a pi anchor, which is a "dummy" atom to represent pi-metal bonds.
+ *  Name is the name of the new pi anchor, and group is the atoms that define
+ *  the pi system. Type (a String) is an atom type for MM implementation.
+ *  Weights represent the relative significance of the component atoms; if omitted, then
+ *  1.0/n (n is the number of component atoms) is assumed for all atoms.
+ *  The weight values will be normalized so that the sum of the weights is 1.0.
+ *  The weight values must be positive.
+ *  Index is the atom index where the created pi-anchor is inserted in the 
+ *  atoms array; if omitted, the pi-anchor is inserted after the component atom
+ *  having the largest index.
+ *  Pi anchors are appear in the atom list along with other ordinary atoms. The list
+ *  of component atoms (and their weights) can be retrived by AtomRef#anchor_list.
  */
 static VALUE
-s_Molecule_CreatePiAnchorConstruct(int argc, VALUE *argv, VALUE self)
+s_Molecule_CreatePiAnchor(int argc, VALUE *argv, VALUE self)
 {
-    Molecule *mol;
-       VALUE vals[4];
-       Int ivals[4], i;
+       Molecule *mol;
+       VALUE nval, gval;
        IntGroup *ig;
-    Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "22", vals, vals + 1, vals + 2, vals + 3);
-       for (i = 0; i < 4; i++) {
-               if (vals[i] == Qnil)
-                       ivals[i] = -1;
-               else ivals[i] = s_Molecule_AtomOrPiAtomIndexFromValue(mol, vals[i]);
+       Int i, n, idx, last_component;
+       Atom a, *ap;
+       PiAnchor an;
+       AtomRef *aref;
+       if (argc < 2 || argc >= 6)
+               rb_raise(rb_eMolbyError, "too %s arguments (should be 2..5)", (argc < 2 ? "few" : "many"));
+       nval = *argv++;
+       gval = *argv++;
+       argc -= 2;
+    Data_Get_Struct(self, Molecule, mol);
+    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);
+       if (a.aname[0] == '_')
+               rb_raise(rb_eMolbyError, "please avoid a name beginning with '_' for a pi anchor, because it causes some internal confusion.");
+       a.type = AtomTypeEncodeToUInt("##");  /*  Default atom type for pi_anchor  */
+       for (i = 0; (n = IntGroupGetNthPoint(ig, i)) >= 0; i++) {
+               if (n >= mol->natoms) {
+                       AtomConnectResize(&an.connect, 0);
+                       rb_raise(rb_eMolbyError, "atom index (%d) out of range", n);
+               }
+               AtomConnectInsertEntry(&an.connect, an.connect.count, n);
+               last_component = n;
+       }
+       if (an.connect.count == 0)
+               rb_raise(rb_eMolbyError, "no atoms are specified");
+       NewArray(&an.coeffs, &an.ncoeffs, sizeof(Double), an.connect.count);
+       for (i = 0; i < an.connect.count; i++) {
+               an.coeffs[i] = 1.0 / an.connect.count;
+       }
+       if (argc > 0 && (argv[0] == Qnil || rb_obj_is_kind_of(argv[0], rb_cString))) {
+               /*  Atom type  */
+               if (argv[0] != Qnil)
+                       a.type = AtomTypeEncodeToUInt(StringValuePtr(argv[0]));
+               argc--;
+               argv++;
        }
-       for (i = 0; i < 4; i++) {
-               if (ivals[i] >= ATOMS_MAX_NUMBER)
-                       break;
+       if (argc > 0 && (argv[0] == Qnil || rb_obj_is_kind_of(argv[0], rb_mEnumerable))) {
+               if (argv[0] != Qnil) {
+                       VALUE aval = rb_ary_to_ary(argv[0]);
+                       Double d, sum;
+                       if (RARRAY_LEN(aval) != an.connect.count)
+                               rb_raise(rb_eMolbyError, "the number of weight values does not match the number of atoms");
+                       for (i = 0, sum = 0.0; i < an.connect.count; i++) {
+                               d = NUM2DBL(rb_Float(RARRAY_PTR(aval)[i]));
+                               if (d <= 0.0)
+                                       rb_raise(rb_eMolbyError, "the weight value must be positive");
+                               sum += d;
+                               an.coeffs[i] = d;
+                       }
+                       for (i = 0; i < an.connect.count; i++)
+                               an.coeffs[i] /= sum;
+               }
+               argc--;
+               argv++;
        }
-       if (i == 4)
-               rb_raise(rb_eMolbyError, "No pi anchor is specified");
-       ig = IntGroupNewWithPoints(mol->npibonds, 1, -1);
-       MolActionCreateAndPerform(mol, gMolActionInsertPiBonds, ig, 4, ivals);
-       IntGroupRelease(ig);
-       return INT2NUM(mol->npibonds);
+       if (argc > 0 && argv[0] != Qnil) {
+               /*  Index  */
+               idx = NUM2INT(rb_Integer(argv[0]));
+       } else idx = -1;
+       if (idx < 0 || idx > mol->natoms) {
+               /*  Immediately after the last specified atom  */
+               idx = last_component + 1;
+       }
+       a.anchor = (PiAnchor *)malloc(sizeof(PiAnchor));
+       memmove(a.anchor, &an, sizeof(PiAnchor));
+       /*  Use residue information of the last specified atom  */
+       ap = ATOM_AT_INDEX(mol->atoms, last_component);
+       a.resSeq = ap->resSeq;
+       strncpy(a.resName, ap->resName, 4);
+       if (MolActionCreateAndPerform(mol, gMolActionAddAnAtom, &a, idx, &idx) != 0)
+               return Qnil;
+       MoleculeCalculatePiAnchorPosition(mol, idx);
+    aref = AtomRefNew(mol, idx);
+    return Data_Wrap_Struct(rb_cAtomRef, 0, (void (*)(void *))AtomRefRelease, aref);
 }
 
+#pragma mark ------ Molecular Properties ------
+
 /*
  *  call-seq:
- *     remove_pi_anchor_constructs(IntGroup)       -> self
+ *     set_property(name, value[, index]) -> value
+ *     set_property(name, values, group) -> values
  *
- *  Remove pi anchor constructs (bond, angle, dihedral) with indices specified in IntGroup.
- *  This operation is undoable.
+ *  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_RemovePiAnchorConstructs(VALUE self, VALUE igval)
+s_Molecule_SetProperty(int argc, VALUE *argv, VALUE self)
 {
-    Molecule *mol;
+       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);
-       ig = IntGroupFromValue(igval);
-       MolActionCreateAndPerform(mol, gMolActionRemovePiBonds, ig);
+       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:
- *     count_pi_anchor_constructs       -> Integer
+ *     get_property(name[, index]) -> value
+ *     get_property(name, group) -> values
  *
- *  Returns the number of pi anchor constructs.
+ *  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_CountPiAnchorConstructs(VALUE self)
+s_Molecule_GetProperty(int argc, VALUE *argv, VALUE self)
 {
-    Molecule *mol;
+       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);
-       return INT2NUM(mol->npibonds);
+       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:
- *     pi_anchor_construct(index)       -> Array of Integers
+ *     property_names -> Array
  *
- *  Returns the elements representing the index-th pi anchor constructs.
+ *  Get an array of property names.
  */
 static VALUE
-s_Molecule_PiAnchorConstructAtIndex(VALUE self, VALUE ival)
+s_Molecule_PropertyNames(VALUE self)
 {
-    Molecule *mol;
-       Int idx, i, n;
-       VALUE vals[4];
+       Molecule *mol;
+       VALUE rval, nval;
+       int i;
     Data_Get_Struct(self, Molecule, mol);
-       idx = NUM2INT(rb_Integer(ival));
-       if (idx < 0 || idx >= mol->npibonds)
-               rb_raise(rb_eMolbyError, "index of pi anchor constructs out of range (%d)", idx);
-       for (i = 0; i < 4; i++) {
-               n = mol->pibonds[idx * 4 + i];
-               if (n < 0)
-                       break;
-               if (n >= ATOMS_MAX_NUMBER)
-                       n = -(n - ATOMS_MAX_NUMBER) - 1;
-               vals[i] = INT2NUM(n);
+       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 rb_ary_new4(i, vals);
+       return rval;
 }
 
+#pragma mark ------ Class methods ------
+
 /*
  *  call-seq:
  *     current       -> Molecule
@@ -9779,7 +11467,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;
                }       
@@ -9831,9 +11519,141 @@ s_Molecule_OrderedList(VALUE klass)
                rb_ary_push(ary, ValueFromMolecule(mol));
                i++;
        }
-       return ary;
+       return ary;
+}
+
+#pragma mark ------ Call Subprocess ------
+
+/*  The callback functions for call_subprocess_async  */
+static int
+s_Molecule_CallSubProcessAsync_EndCallback(Molecule *mol, int status)
+{
+       int ruby_status;
+       VALUE procval, retval, args[2];
+       args[0] = ValueFromMolecule(mol);
+       args[1] = INT2NUM(status);
+       procval = rb_ivar_get(args[0], rb_intern("end_proc"));
+       if (procval != Qnil) {
+               retval = Ruby_funcall2_protect(procval, rb_intern("call"), 2, args, &ruby_status);
+               if (ruby_status != 0 || retval == Qnil || retval == Qfalse)
+                       return 1;
+       }
+       return 0;
+}
+
+static int
+s_Molecule_CallSubProcessAsync_TimerCallback(Molecule *mol, int tcount)
+{
+       int ruby_status;
+       VALUE procval, retval, args[2];
+       args[0] = ValueFromMolecule(mol);
+       args[1] = INT2NUM(tcount);
+       procval = rb_ivar_get(args[0], rb_intern("timer_proc"));
+       if (procval != Qnil) {
+               retval = Ruby_funcall2_protect(procval, rb_intern("call"), 2, args, &ruby_status);
+               if (ruby_status != 0 || retval == Qnil || retval == Qfalse)
+                       return 1;
+       }
+       return 0;
+}
+
+/*
+ *  call-seq:
+ *     call_subprocess_async(cmd [, end_callback [, timer_callback [, standard_output_file [, error_output_file]]]])
+ *
+ *  Call subprocess asynchronically.
+ *  cmd is either a single string of an array of string. If it is a single string, then
+ *  it will be given to wxExecute as a single argument. In this case, the string can be
+ *  split into arguments by whitespace. If this behavior is not intended, then use an array
+ *  containing a single string.
+ *  If end_callback is given, it will be called (with two arguments self and termination status)
+ *  when the subprocess terminated.
+ *  If timer_callback is given, it will be called (also with two arguments, self and timer count).
+ *  If timer_callback returns nil or false, then the subprocess will be interrupted.
+ *  If stdout_file or stderr_file is a filename, then the message will be sent to the file; if the
+ *  filename begins with ">>", then the message will be appended to the file.
+ *  If the filename is "/dev/null" or "NUL", then the message will be lost.
+ *  If the argument is nil, then the message will be sent to the Ruby console.
+ *  Returns the process ID as an integer.
+ */
+static VALUE
+s_Molecule_CallSubProcessAsync(int argc, VALUE *argv, VALUE self)
+{
+       VALUE cmd, end_proc, timer_proc, stdout_val, stderr_val;
+       Molecule *mol;
+       char *sout, *serr;
+    const char **cmdargv;
+       int n;
+       FILE *fpout, *fperr;
+       rb_scan_args(argc, argv, "14", &cmd, &end_proc, &timer_proc, &stdout_val, &stderr_val);
+       Data_Get_Struct(self, Molecule, mol);
+
+       if (stdout_val == Qnil) {
+               fpout = (FILE *)1;
+       } else {
+               sout = StringValuePtr(stdout_val);
+               if (strcmp(sout, "/dev/null") == 0 || strcmp(sout, "NUL") == 0)
+                       fpout = NULL;
+               else {
+                       if (strncmp(sout, ">>", 2) == 0) {
+                               sout += 2;
+                               fpout = fopen(sout, "a");
+                       } else {
+                               if (*sout == '>')
+                                       sout++;
+                               fpout = fopen(sout, "w");
+                       }
+                       if (fpout == NULL)
+                               rb_raise(rb_eMolbyError, "Cannot open file for standard output (%s)", sout);
+               }
+       }
+       if (stderr_val == Qnil) {
+               fperr = (FILE *)1;
+       } else {
+               serr = StringValuePtr(stderr_val);
+               if (strcmp(serr, "/dev/null") == 0 || strcmp(serr, "NUL") == 0)
+                       fperr = NULL;
+               else {
+                       if (strncmp(serr, ">>", 2) == 0) {
+                               serr += 2;
+                               fpout = fopen(serr, "a");
+                       } else {
+                               if (*serr == '>')
+                                       serr++;
+                               fperr = fopen(serr, "w");
+                       }
+                       if (fperr == NULL)
+                               rb_raise(rb_eMolbyError, "Cannot open file for standard output (%s)", serr);
+               }
+       }
+       
+       /*  Register procs as instance variables  */
+       rb_ivar_set(self, rb_intern("end_proc"), end_proc);
+       rb_ivar_set(self, rb_intern("timer_proc"), timer_proc);
+    
+    if (rb_obj_is_kind_of(cmd, rb_cString)) {
+        cmdargv = calloc(sizeof(cmdargv[0]), 3);
+        cmdargv[0] = StringValuePtr(cmd);
+        cmdargv[1] = "";
+        cmdargv[2] = NULL;
+    } else {
+        cmd = rb_ary_to_ary(cmd);
+        cmdargv = calloc(sizeof(cmdargv[0]), RARRAY_LEN(cmd) + 1);
+        for (n = 0; n < RARRAY_LEN(cmd); n++) {
+            cmdargv[n] = StringValuePtr(RARRAY_PTR(cmd)[n]);
+        }
+        cmdargv[n] = NULL;
+    }
+       n = MoleculeCallback_callSubProcessAsync(mol, cmdargv, s_Molecule_CallSubProcessAsync_EndCallback, (timer_proc == Qnil ? NULL : s_Molecule_CallSubProcessAsync_TimerCallback), fpout, fperr);
+       if (fpout != NULL && fpout != (FILE *)1)
+               fclose(fpout);
+       if (fperr != NULL && fperr != (FILE *)1)
+               fclose(fperr);
+       return INT2NUM(n);
 }
 
+#pragma mark ====== Define Molby Classes ======
+
 void
 Init_Molby(void)
 {
@@ -9850,9 +11670,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");
@@ -9863,19 +11687,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);
@@ -9887,112 +11717,110 @@ 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);
        rb_define_alias(rb_cMolecule, "create_bonds", "create_bond");
        rb_define_method(rb_cMolecule, "remove_bond", s_Molecule_RemoveBond, -1);
        rb_define_alias(rb_cMolecule, "remove_bonds", "remove_bond");
+       rb_define_method(rb_cMolecule, "assign_bond_order", s_Molecule_AssignBondOrder, 2);
+       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);
        rb_define_method(rb_cMolecule, "remove_dihedral", s_Molecule_RemoveDihedral, 4);
        rb_define_method(rb_cMolecule, "add_improper", s_Molecule_AddImproper, 4);
-       rb_define_method(rb_cMolecule, "remove_improper", s_Molecule_RemoveImproper, 4);
+       rb_define_method(rb_cMolecule, "remove_improper", s_Molecule_RemoveImproper, -1);
        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, "each_fragment", s_Molecule_EachFragment, 0);
+       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);
@@ -10004,15 +11832,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");
@@ -10022,10 +11855,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);
@@ -10033,50 +11874,75 @@ 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, "add_gaussian_orbital_shell", s_Molecule_AddGaussianOrbitalShell, 3);
+       rb_define_method(rb_cMolecule, "clear_basis_set", s_Molecule_ClearBasisSet, 0);
+       rb_define_method(rb_cMolecule, "add_gaussian_orbital_shell", s_Molecule_AddGaussianOrbitalShell, -1);
        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, "replace_pi_anchor", s_Molecule_ReplacePiAnchor, -1);
-       rb_define_method(rb_cMolecule, "insert_pi_anchor", s_Molecule_InsertPiAnchor, -1);
-       rb_define_method(rb_cMolecule, "remove_pi_anchor", s_Molecule_RemovePiAnchor, 1);
-       rb_define_method(rb_cMolecule, "pi_anchor", s_Molecule_PiAnchorAtIndex, 1);
-       rb_define_method(rb_cMolecule, "pi_anchor_r", s_Molecule_PiAnchorR, 1);
-       rb_define_method(rb_cMolecule, "pi_anchor_fract_r", s_Molecule_PiAnchorFractR, 1);
-       rb_define_method(rb_cMolecule, "count_pi_anchors", s_Molecule_CountPiAnchors, 0);
-       rb_define_method(rb_cMolecule, "create_pi_anchor_construct", s_Molecule_CreatePiAnchorConstruct, -1);
-       rb_define_method(rb_cMolecule, "remove_pi_anchor_constructs", s_Molecule_RemovePiAnchorConstructs, 1);
-       rb_define_alias(rb_cMolecule, "remove_pi_anchor_construct", "remove_pi_anchor_constructs");
-       rb_define_method(rb_cMolecule, "count_pi_anchor_constructs", s_Molecule_CountPiAnchorConstructs, 0);
-       rb_define_method(rb_cMolecule, "pi_anchor_construct", s_Molecule_PiAnchorConstructAtIndex, 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_method(rb_cMolecule, "call_subprocess_async", s_Molecule_CallSubProcessAsync, -1);
+       
        /*  class MolEnumerable  */
        rb_cMolEnumerable = rb_define_class_under(rb_mMolby, "MolEnumerable", rb_cObject);
     rb_include_module(rb_cMolEnumerable, rb_mEnumerable);
@@ -10084,7 +11950,8 @@ Init_Molby(void)
        rb_define_method(rb_cMolEnumerable, "length", s_MolEnumerable_Length, 0);
     rb_define_alias(rb_cMolEnumerable, "size", "length");
        rb_define_method(rb_cMolEnumerable, "each", s_MolEnumerable_Each, 0);
-       
+       rb_define_method(rb_cMolEnumerable, "==", s_MolEnumerable_Equal, 1);
+
        /*  class AtomRef  */
        rb_cAtomRef = rb_define_class_under(rb_mMolby, "AtomRef", rb_cObject);
        for (i = 0; s_AtomAttrDefTable[i].name != NULL; i++) {
@@ -10100,13 +11967,13 @@ 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);
+
        /*  class Parameter  */
        rb_cParameter = rb_define_class_under(rb_mMolby, "Parameter", rb_cObject);
-       rb_define_singleton_method(rb_cParameter, "builtin", s_Parameter_Builtin, 0);
        rb_define_method(rb_cParameter, "bond", s_Parameter_Bond, 1);
        rb_define_method(rb_cParameter, "angle", s_Parameter_Angle, 1);
        rb_define_method(rb_cParameter, "dihedral", s_Parameter_Dihedral, 1);
@@ -10132,6 +11999,33 @@ Init_Molby(void)
        rb_define_method(rb_cParameter, "vdw_cutoffs", s_Parameter_VdwCutoffs, 0);
        rb_define_method(rb_cParameter, "elements", s_Parameter_Elements, 0);
        rb_define_method(rb_cParameter, "lookup", s_Parameter_LookUp, -1);
+       rb_define_method(rb_cParameter, "==", s_Parameter_Equal, 1);
+       rb_define_singleton_method(rb_cParameter, "builtin", s_Parameter_Builtin, 0);
+       rb_define_singleton_method(rb_cParameter, "bond", s_Parameter_Bond, 1);
+       rb_define_singleton_method(rb_cParameter, "angle", s_Parameter_Angle, 1);
+       rb_define_singleton_method(rb_cParameter, "dihedral", s_Parameter_Dihedral, 1);
+       rb_define_singleton_method(rb_cParameter, "improper", s_Parameter_Improper, 1);
+       rb_define_singleton_method(rb_cParameter, "vdw", s_Parameter_Vdw, 1);
+       rb_define_singleton_method(rb_cParameter, "vdw_pair", s_Parameter_VdwPair, 1);
+       rb_define_singleton_method(rb_cParameter, "vdw_cutoff", s_Parameter_VdwCutoff, 1);
+       rb_define_singleton_method(rb_cParameter, "element", s_Parameter_Element, 1);
+       rb_define_singleton_method(rb_cParameter, "nbonds", s_Parameter_Nbonds, 0);
+       rb_define_singleton_method(rb_cParameter, "nangles", s_Parameter_Nangles, 0);
+       rb_define_singleton_method(rb_cParameter, "ndihedrals", s_Parameter_Ndihedrals, 0);
+       rb_define_singleton_method(rb_cParameter, "nimpropers", s_Parameter_Nimpropers, 0);
+       rb_define_singleton_method(rb_cParameter, "nvdws", s_Parameter_Nvdws, 0);
+       rb_define_singleton_method(rb_cParameter, "nvdw_pairs", s_Parameter_NvdwPairs, 0);
+       rb_define_singleton_method(rb_cParameter, "nvdw_cutoffs", s_Parameter_NvdwCutoffs, 0);
+       rb_define_singleton_method(rb_cParameter, "nelements", s_Parameter_Nelements, 0);
+       rb_define_singleton_method(rb_cParameter, "bonds", s_Parameter_Bonds, 0);
+       rb_define_singleton_method(rb_cParameter, "angles", s_Parameter_Angles, 0);
+       rb_define_singleton_method(rb_cParameter, "dihedrals", s_Parameter_Dihedrals, 0);
+       rb_define_singleton_method(rb_cParameter, "impropers", s_Parameter_Impropers, 0);
+       rb_define_singleton_method(rb_cParameter, "vdws", s_Parameter_Vdws, 0);
+       rb_define_singleton_method(rb_cParameter, "vdw_pairs", s_Parameter_VdwPairs, 0);
+       rb_define_singleton_method(rb_cParameter, "vdw_cutoffs", s_Parameter_VdwCutoffs, 0);
+       rb_define_singleton_method(rb_cParameter, "elements", s_Parameter_Elements, 0);
+       rb_define_singleton_method(rb_cParameter, "lookup", s_Parameter_LookUp, -1);
        rb_define_const(rb_cParameter, "Builtin", Data_Wrap_Struct(rb_cParameter, 0, NULL, NULL));
 
        /*  class ParEnumerable  */
@@ -10145,6 +12039,7 @@ Init_Molby(void)
        rb_define_method(rb_cParEnumerable, "insert", s_ParEnumerable_Insert, -1);
        rb_define_method(rb_cParEnumerable, "delete", s_ParEnumerable_Delete, 1);
        rb_define_method(rb_cParEnumerable, "lookup", s_ParEnumerable_LookUp, -1);
+       rb_define_method(rb_cParEnumerable, "==", s_ParEnumerable_Equal, 1);
        
        /*  class ParameterRef  */
        rb_cParameterRef = rb_define_class_under(rb_mMolby, "ParameterRef", rb_cObject);
@@ -10167,7 +12062,8 @@ Init_Molby(void)
        rb_define_method(rb_cParameterRef, "to_hash", s_ParameterRef_ToHash, 0);
        rb_define_method(rb_cParameterRef, "to_s", s_ParameterRef_ToString, 0);
        rb_define_method(rb_cParameterRef, "keys", s_ParameterRef_Keys, 0);
-       
+       rb_define_method(rb_cParameterRef, "==", s_ParameterRef_Equal, 1);
+
        /*  class MolbyError  */
        rb_eMolbyError = rb_define_class_under(rb_mMolby, "MolbyError", rb_eStandardError);
 
@@ -10180,55 +12076,129 @@ 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);
        rb_define_method(rb_mKernel, "execute_script", s_Kernel_ExecuteScript, 1);
        rb_define_method(rb_mKernel, "document_home", s_Kernel_DocumentHome, 0);
-       rb_define_method(rb_mKernel, "call_subprocess", s_Kernel_CallSubProcess, 2);
+       rb_define_method(rb_mKernel, "call_subprocess", s_Kernel_CallSubProcess, -1);
+       rb_define_method(rb_mKernel, "backquote", s_Kernel_Backquote, 1);
        rb_define_method(rb_mKernel, "message_box", s_Kernel_MessageBox, -1);
        rb_define_method(rb_mKernel, "error_message_box", s_Kernel_ErrorMessageBox, 1);
+       rb_define_method(rb_mKernel, "show_console_window", s_Kernel_ShowConsoleWindow, 0);
+       rb_define_method(rb_mKernel, "hide_console_window", s_Kernel_HideConsoleWindow, 0);
+       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_evalRubyScriptOnMoleculeSubSub(VALUE tagval, VALUE val)
+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);
        }
-}
-
-static VALUE
-s_evalRubyScriptOnMoleculeSub(VALUE val)
-{
-       return rb_catch("molby_top", s_evalRubyScriptOnMoleculeSubSub, val);
+       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
@@ -10237,8 +12207,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;
@@ -10249,89 +12219,190 @@ 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?  */
+               VALUE last_exception = rb_gv_get("$!");
+               if (rb_obj_is_kind_of(last_exception, rb_eSystemExit)) {
+                       /*  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)
+/*  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;
+               char *str;
                gMolbyRunLevel++;
                val = rb_protect(rb_inspect, val, &status);
                gMolbyRunLevel--;
-               MyAppCallback_showScriptMessage("%s", StringValuePtr(val));
+               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\n%s",
-                        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
@@ -10346,11 +12417,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);
        }
        
@@ -10383,40 +12450,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);
@@ -10439,7 +12537,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);
@@ -10449,52 +12548,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");
        }