OSDN Git Service

Start implementing JANPA integration with Psi4
[molby/Molby.git] / MolLib / Ruby_bind / ruby_bind.c
index 593b0e6..9d7452e 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_AnchorListSym;
+       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)
 {
@@ -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,120 @@ 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}...".
+ *  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(VALUE self, VALUE cmd, VALUE procname)
+s_Kernel_CallSubProcess(int argc, VALUE *argv, VALUE self)
 {
-       int n = MyAppCallback_callSubProcess(StringValuePtr(cmd), StringValuePtr(procname));
+       VALUE cmd, procname, cproc, stdout_val, stderr_val;
+    VALUE save_interruptFlag;
+       int n, exitstatus, pid;
+       char *sout, *serr;
+    const char *pnamestr;
+       FILE *fpout, *fperr;
+
+       rb_scan_args(argc, argv, "23", &cmd, &procname, &cproc, &stdout_val, &stderr_val);
+
+       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;
+       n = MyAppCallback_callSubProcess(StringValuePtr(cmd), pnamestr, (cproc == Qnil ? NULL : s_Kernel_CallSubProcess_Callback), (cproc == Qnil ? NULL : (void *)cproc), fpout, fperr, &exitstatus, &pid);
+    s_SetInterruptFlag(self, save_interruptFlag);
+    
+       if (fpout != NULL && fpout != (FILE *)1)
+               fclose(fpout);
+       if (fperr != NULL && fperr != (FILE *)1)
+               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_Backquote(VALUE self, VALUE cmd)
+{
+       char *buf;
+       int n, exitstatus, pid;
+       VALUE val;
+       n = MyAppCallback_callSubProcess(StringValuePtr(cmd), NULL, DUMMY_CALLBACK, &buf, NULL, NULL, &exitstatus, &pid);
+/*     fprintf(stderr, "n = %d, exitstatus = %d, pid = %d\n", n, exitstatus, pid); */
+       if (n >= 0 && buf != NULL) {
+               val = Ruby_NewEncodedStringValue(buf, 0);
+               free(buf);
+       } else {
+               val = Ruby_NewEncodedStringValue("", 0);
+       }
+       rb_last_status_set(exitstatus, pid);
+       return val;
 }
 
 #pragma mark ====== User defaults ======
@@ -717,6 +1130,75 @@ s_Kernel_SetGlobalSettings(VALUE self, VALUE key, VALUE value)
        return value;
 }
 
+#pragma mark ====== IO extension ======
+
+static VALUE
+s_Ruby_str_encode_protected(VALUE val)
+{
+       return rb_str_encode(val, rb_enc_from_encoding(rb_default_external_encoding()),
+                                 ECONV_INVALID_REPLACE | ECONV_UNDEF_REPLACE, Qnil);
+}
+
+/*
+ *  call-seq:
+ *     gets_any_eol
+ *
+ *  A gets variant that works for CR, LF, and CRLF end-of-line characters. 
+ */
+static VALUE
+s_IO_gets_any_eol(VALUE self)
+{
+       VALUE val, val2, cval;
+       char buf[1024];
+       int i, c, status;
+       static ID id_getbyte = 0, id_ungetbyte;
+       if (id_getbyte == 0) {
+               id_getbyte = rb_intern("getbyte");
+               id_ungetbyte = rb_intern("ungetbyte");
+       }
+       i = 0;
+       val = Qnil;
+       while ((cval = rb_funcall(self, id_getbyte, 0)) != Qnil) {
+               c = NUM2INT(rb_Integer(cval));
+               if (c == 0x0d) {
+                       cval = rb_funcall(self, id_getbyte, 0);
+                       if (cval != Qnil) {
+                               c = NUM2INT(rb_Integer(cval));
+                               if (c != 0x0a)
+                                       rb_funcall(self, id_ungetbyte, 1, cval);
+                       }
+                       break;
+               } else if (c != 0x0a) {
+                       buf[i++] = c;
+                       if (i >= 1020) {
+                               buf[i] = 0;
+                               if (val == Qnil)
+                                       val = rb_str_new(buf, i);
+                               else
+                                       rb_str_append(val, rb_str_new(buf, i));
+                               i = 0;
+                       }
+               } else break;
+       }
+       if (cval == Qnil && i == 0 && val == Qnil)
+               return Qnil;  /*  End of file  */
+       buf[i] = 0;
+       if (val == Qnil)
+               val = rb_str_new(buf, i);
+       else if (i > 0)
+               rb_str_append(val, rb_str_new(buf, i));
+       val2 = rb_protect(s_Ruby_str_encode_protected, val, &status); /*  Ignore exception  */
+       if (status == 0)
+               val = val2;
+       if (cval != Qnil) {
+               /*  Needs a end-of-line mark  */
+               cval = rb_gv_get("$/");
+               rb_str_append(val, cval);
+       }
+       rb_gv_set("$_", val);
+       return val;
+}
+
 #pragma mark ====== Utility functions (protected funcall) ======
 
 struct Ruby_funcall2_record {
@@ -744,6 +1226,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 +1344,10 @@ static VALUE s_ParameterRef_GetParType(VALUE self) {
        Int tp;
        s_UnionParFromValue(self, &tp, 0);
        if (tp == kElementParType)
-               return rb_str_new2("element");
+               return Ruby_NewEncodedStringValue2("element");
        tp -= kFirstParType;
        if (tp >= 0 && tp < sizeof(s_ParameterTypeNames) / sizeof(s_ParameterTypeNames[0]))
-               return rb_str_new2(s_ParameterTypeNames[tp]);
+               return Ruby_NewEncodedStringValue2(s_ParameterTypeNames[tp]);
        else rb_raise(rb_eMolbyError, "Internal error: parameter type tag is out of range (%d)", tp);
 }
 
@@ -1190,11 +1678,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 +1708,7 @@ static VALUE s_ParameterRef_GetCutoff(VALUE self) {
  *  call-seq:
  *     radius -> Float
  *
- *  Get the atomic radius for the atom display parameter.
+ *  Get the atomic (covalent) radius for the element parameter.
  */
 static VALUE s_ParameterRef_GetRadius(VALUE self) {
        UnionPar *up;
@@ -1233,24 +1721,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 atomic number for the vdw or atom parameter.
+ *  Get the rgb color for the element parameter.
+ */
+static VALUE s_ParameterRef_GetColor(VALUE self) {
+       UnionPar *up;
+       Int tp;
+       up = s_UnionParFromValue(self, &tp, 0);
+       if (tp == kElementParType)
+               return rb_ary_new3(3, rb_float_new(up->atom.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 element parameter.
  */
 static VALUE s_ParameterRef_GetAtomicNumber(VALUE self) {
        UnionPar *up;
@@ -1267,7 +1770,7 @@ static VALUE s_ParameterRef_GetAtomicNumber(VALUE self) {
  *  call-seq:
  *     name -> String
  *
- *  Get the name for the atom display parameter.
+ *  Get the name for the element parameter.
  */
 static VALUE s_ParameterRef_GetName(VALUE self) {
        UnionPar *up;
@@ -1277,7 +1780,7 @@ static VALUE s_ParameterRef_GetName(VALUE self) {
                char name[5];
                strncpy(name, up->atom.name, 4);
                name[4] = 0;
-               return rb_str_new2(name);
+               return Ruby_NewEncodedStringValue2(name);
        } else rb_raise(rb_eMolbyError, "invalid member name");
 }
 
@@ -1285,7 +1788,7 @@ static VALUE s_ParameterRef_GetName(VALUE self) {
  *  call-seq:
  *     weight -> Float
  *
- *  Get the atomic weight for the atom display parameter.
+ *  Get the atomic weight for the element parameter.
  */
 static VALUE s_ParameterRef_GetWeight(VALUE self) {
        UnionPar *up;
@@ -1302,7 +1805,7 @@ static VALUE s_ParameterRef_GetWeight(VALUE self) {
  *  call-seq:
  *     fullname -> String
  *
- *  Get the full name for the atom display parameter.
+ *  Get the full name for the element parameter.
  */
 static VALUE s_ParameterRef_GetFullName(VALUE self) {
        UnionPar *up;
@@ -1312,7 +1815,7 @@ static VALUE s_ParameterRef_GetFullName(VALUE self) {
                char fullname[16];
                strncpy(fullname, up->atom.fullname, 15);
                fullname[15] = 0;
-               return rb_str_new2(fullname);
+               return Ruby_NewEncodedStringValue2(fullname);
        } else rb_raise(rb_eMolbyError, "invalid member fullname");
 }
 
@@ -1329,7 +1832,7 @@ static VALUE s_ParameterRef_GetComment(VALUE self) {
        com = up->bond.com;
        if (com == 0)
                return Qnil;
-       else return rb_str_new2(ParameterGetComment(com));
+       else return Ruby_NewEncodedStringValue2(ParameterGetComment(com));
 }
 
 /*
@@ -1348,7 +1851,7 @@ static VALUE s_ParameterRef_GetSource(VALUE self) {
                return Qfalse;  /* undefined */
        else if (src == 0)
                return Qnil;  /*  local  */
-       else return rb_str_new2(ParameterGetComment(src));
+       else return Ruby_NewEncodedStringValue2(ParameterGetComment(src));
 }
 
 static void
@@ -1809,6 +2312,21 @@ static VALUE s_ParameterRef_SetRadius(VALUE self, VALUE val) {
        return val;
 }
 
+static VALUE s_ParameterRef_SetVdwRadius(VALUE self, VALUE val) {
+       UnionPar *up;
+       Int tp, oldsrc;
+       VALUE oldval;
+       up = s_UnionParFromValue(self, &tp, 1);
+       oldval = s_ParameterRef_GetVdwRadius(self);
+       oldsrc = up->bond.src;
+       val = rb_Float(val);
+       if (tp == kElementParType) {
+               up->atom.vdw_radius = NUM2DBL(val);
+       } else rb_raise(rb_eMolbyError, "invalid member vdw_radius");
+       s_RegisterUndoForParameterAttrChange(self, s_VdwRadiusSym, val, oldval, oldsrc);        
+       return val;
+}
+
 static VALUE s_ParameterRef_SetColor(VALUE self, VALUE val) {
        UnionPar *up;
        Int tp, oldsrc;
@@ -1821,9 +2339,9 @@ static VALUE s_ParameterRef_SetColor(VALUE self, VALUE val) {
                rb_raise(rb_eMolbyError, "value should be an array of three floats (r, g, b)");
        valp = RARRAY_PTR(val);
        if (tp == kElementParType) {
-               up->atom.r = NUM2DBL(rb_Float(valp[0]));
-               up->atom.g = NUM2DBL(rb_Float(valp[1]));
-               up->atom.b = NUM2DBL(rb_Float(valp[2]));
+               up->atom.red = (unsigned short)(NUM2DBL(rb_Float(valp[0])) * 65535.0);
+               up->atom.green = (unsigned short)(NUM2DBL(rb_Float(valp[1])) * 65535.0);
+               up->atom.blue = (unsigned short)(NUM2DBL(rb_Float(valp[2])) * 65535.0);
        } else rb_raise(rb_eMolbyError, "invalid member color");
        s_RegisterUndoForParameterAttrChange(self, s_ColorSym, val, oldval, oldsrc);    
        return val;
@@ -1954,6 +2472,7 @@ static struct s_ParameterAttrDef {
        {"eps14",        &s_Eps14Sym,        0, s_ParameterRef_GetEps14,        s_ParameterRef_SetEps14},
        {"cutoff",       &s_CutoffSym,       0, s_ParameterRef_GetCutoff,       s_ParameterRef_SetCutoff},
        {"radius",       &s_RadiusSym,       0, s_ParameterRef_GetRadius,       s_ParameterRef_SetRadius},
+       {"vdw_radius",   &s_VdwRadiusSym,    0, s_ParameterRef_GetVdwRadius,    s_ParameterRef_SetVdwRadius},
        {"color",        &s_ColorSym,        0, s_ParameterRef_GetColor,        s_ParameterRef_SetColor},
        {"atomic_number",&s_AtomicNumberSym, 0, s_ParameterRef_GetAtomicNumber, s_ParameterRef_SetAtomicNumber},
        {"name",         &s_NameSym,         0, s_ParameterRef_GetName,         s_ParameterRef_SetName},
@@ -1977,6 +2496,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 +2538,7 @@ s_ParameterRef_Keys(VALUE self)
                case kVdwCutoffParType:
                        return rb_ary_new3(6, s_IndexSym, s_ParTypeSym, s_AtomTypesSym, s_CutoffSym, s_CommentSym, s_SourceSym);
                case kElementParType:
-                       return rb_ary_new3(10, s_IndexSym, s_ParTypeSym, s_AtomicNumberSym, s_NameSym, s_FullNameSym, s_RadiusSym, s_ColorSym, s_WeightSym, s_CommentSym, s_SourceSym);
+                       return rb_ary_new3(10, s_IndexSym, s_ParTypeSym, s_AtomicNumberSym, s_NameSym, s_FullNameSym, s_RadiusSym, s_ColorSym, s_WeightSym, s_VdwRadiusSym, s_CommentSym, s_SourceSym);
                default:
                        rb_raise(rb_eMolbyError, "internal error: invalid parameter type");
        }
@@ -2085,10 +2606,10 @@ s_ParameterRef_ToString(VALUE self)
                        snprintf(buf, sizeof buf, "vdwcutoff %4.6s %4.6s %8.4f", AtomTypeDecodeToString(up->vdwcutoff.type1, types[0]), AtomTypeDecodeToString(up->vdwcutoff.type2, types[1]), up->vdwcutoff.cutoff);
                        break;
                case kElementParType:
-                       snprintf(buf, sizeof buf, "element %2.2s %3d %6.3f %6.3f %6.3f %6.3f %8.4f %s", up->atom.name, up->atom.number, up->atom.radius, up->atom.r, up->atom.g, up->atom.b, up->atom.weight, up->atom.fullname);
+                       snprintf(buf, sizeof buf, "element %2.2s %3d %6.3f %6.3f %6.3f %6.3f %8.4f %s %6.3f", up->atom.name, up->atom.number, up->atom.radius, up->atom.red / 65535.0, up->atom.green / 65535.0, up->atom.blue / 65535.0, up->atom.weight, up->atom.fullname, up->atom.vdw_radius);
                        break;
        }
-       return rb_str_new2(buf);
+       return Ruby_NewEncodedStringValue2(buf);
 }
 
 /*
@@ -2793,13 +3314,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;
@@ -2809,13 +3330,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;
@@ -2974,10 +3495,10 @@ s_ParEnumerable_ParType(VALUE self) {
     Data_Get_Struct(self, ParEnumerable, pen);
        tp = pen->parType;
        if (tp == kElementParType)
-               return rb_str_new2("element");
+               return Ruby_NewEncodedStringValue2("element");
        tp -= kFirstParType;
        if (tp >= 0 && tp < sizeof(s_ParameterTypeNames) / sizeof(s_ParameterTypeNames[0]))
-               return rb_str_new2(s_ParameterTypeNames[tp]);
+               return Ruby_NewEncodedStringValue2(s_ParameterTypeNames[tp]);
        else rb_raise(rb_eMolbyError, "Internal error: parameter type tag is out of range (%d)", tp);
 }
 
@@ -3339,7 +3860,7 @@ static VALUE s_AtomRef_GetSegSeq(VALUE self) {
 
 static VALUE s_AtomRef_GetSegName(VALUE self) {
        char *p = s_AtomFromValue(self)->segName;
-       return rb_str_new(p, strlen_limit(p, 4));
+       return Ruby_NewEncodedStringValue(p, strlen_limit(p, 4));
 }
 
 static VALUE s_AtomRef_GetResSeq(VALUE self) {
@@ -3348,18 +3869,18 @@ static VALUE s_AtomRef_GetResSeq(VALUE self) {
 
 static VALUE s_AtomRef_GetResName(VALUE self) {
        char *p = s_AtomFromValue(self)->resName;
-       return rb_str_new(p, strlen_limit(p, 4));
+       return Ruby_NewEncodedStringValue(p, strlen_limit(p, 4));
 }
 
 static VALUE s_AtomRef_GetName(VALUE self) {
        char *p = s_AtomFromValue(self)->aname;
-       return rb_str_new(p, strlen_limit(p, 4));
+       return Ruby_NewEncodedStringValue(p, strlen_limit(p, 4));
 }
 
 static VALUE s_AtomRef_GetAtomType(VALUE self) {
        int type = s_AtomFromValue(self)->type;
        char *p = (type == 0 ? "" : AtomTypeDecodeToString(type, NULL));
-       return rb_str_new(p, strlen_limit(p, 6));
+       return Ruby_NewEncodedStringValue(p, strlen_limit(p, 6));
 }
 
 static VALUE s_AtomRef_GetCharge(VALUE self) {
@@ -3372,7 +3893,7 @@ static VALUE s_AtomRef_GetWeight(VALUE self) {
 
 static VALUE s_AtomRef_GetElement(VALUE self) {
        char *p = s_AtomFromValue(self)->element;
-       return rb_str_new(p, strlen_limit(p, 4));
+       return Ruby_NewEncodedStringValue(p, strlen_limit(p, 4));
 }
 
 static VALUE s_AtomRef_GetAtomicNumber(VALUE self) {
@@ -3485,6 +4006,18 @@ static VALUE s_AtomRef_GetAniso(VALUE self) {
        return retval;
 }
 
+static VALUE s_AtomRef_GetAnisoEigenValues(VALUE self) {
+    VALUE retval;
+    int i;
+    Atom *ap = s_AtomFromValue(self);
+    if (ap->aniso == NULL)
+        return Qnil;
+    retval = rb_ary_new();
+    for (i = 0; i < 3; i++)
+        rb_ary_push(retval, rb_float_new(ap->aniso->eigval[i]));
+    return retval;
+}
+
 static VALUE s_AtomRef_GetSymop(VALUE self) {
        VALUE retval;
        Atom *ap = s_AtomFromValue(self);
@@ -3571,6 +4104,11 @@ static VALUE s_AtomRef_GetAnchorList(VALUE self) {
        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;
@@ -3919,6 +4457,11 @@ static VALUE s_AtomRef_SetAniso(VALUE self, VALUE val) {
        return val;
 }
 
+static VALUE s_AtomRef_SetAnisoEigenValues(VALUE self, VALUE val) {
+    rb_raise(rb_eMolbyError, "Eigenvalues of anisotropic factors are read-only.");
+    return val; /* Not reached */
+}
+
 static VALUE s_AtomRef_SetSymop(VALUE self, VALUE val) {
        Molecule *mol;
        Atom *ap;
@@ -4107,6 +4650,16 @@ static VALUE s_AtomRef_SetAnchorList(VALUE self, VALUE val) {
        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. */
@@ -4143,6 +4696,7 @@ static struct s_AtomAttrDef {
        {"occupancy",    &s_OccupancySym,    0, s_AtomRef_GetOccupancy,    s_AtomRef_SetOccupancy},
        {"temp_factor",  &s_TempFactorSym,   0, s_AtomRef_GetTempFactor,   s_AtomRef_SetTempFactor},
        {"aniso",        &s_AnisoSym,        0, s_AtomRef_GetAniso,        s_AtomRef_SetAniso},
+    {"aniso_eigenvalues", &s_AnisoEigvalSym, 0, s_AtomRef_GetAnisoEigenValues,        s_AtomRef_SetAnisoEigenValues},
        {"symop",        &s_SymopSym,        0, s_AtomRef_GetSymop,        s_AtomRef_SetSymop},
        {"int_charge",   &s_IntChargeSym,    0, s_AtomRef_GetIntCharge,    s_AtomRef_SetIntCharge},
        {"fix_force",    &s_FixForceSym,     0, s_AtomRef_GetFixForce,     s_AtomRef_SetFixForce},
@@ -4152,6 +4706,7 @@ static struct s_AtomAttrDef {
        {"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 */
 };
 
@@ -4251,7 +4806,7 @@ s_MolEnumerable_Aref(VALUE self, VALUE arg1)
                        if (idx2 < 0 || idx2 >= mol->nresidues)
                                rb_raise(rb_eIndexError, "residue index out of range (%d; should be %d..%d)", idx1, -mol->nresidues, mol->nresidues - 1);
                        p = mol->residues[idx2];
-                       return rb_str_new(p, strlen_limit(p, 4));
+                       return Ruby_NewEncodedStringValue(p, strlen_limit(p, 4));
                }
        }
        return Qnil;
@@ -4339,7 +4894,9 @@ s_MolEnumerable_Equal(VALUE self, VALUE val)
 
 #pragma mark ====== Molecule Class ======
 
-/*  An malloc'ed string buffer. Retains the error/warning message from the last ***load/***save method.  */
+#pragma mark ------ Allocate/Release/Accessor ------
+
+/*  An malloc'ed string buffer. Retains the error/warning message from the last ***load / ***save method.  */
 /*  Accessible from Ruby as Molecule#error_message and Molecule#error_message=.  */
 char *gLoadSaveErrorMessage = NULL;
 
@@ -4456,26 +5013,17 @@ static IntGroup *
 s_Molecule_AtomGroupFromValue(VALUE self, VALUE val)
 {
        IntGroup *ig;
+    Molecule *mp1;
+    Data_Get_Struct(self, Molecule, mp1);
        val = rb_funcall(self, rb_intern("atom_group"), 1, val);
        if (!rb_obj_is_kind_of(val, rb_cIntGroup))
                rb_raise(rb_eMolbyError, "IntGroup instance is expected");
        Data_Get_Struct(val, IntGroup, ig);
+    IntGroupRemove(ig, mp1->natoms, ATOMS_MAX_NUMBER); /* Limit the group member to existing atoms */
        IntGroupRetain(ig);
        return ig;
 }
 
-static void
-s_Molecule_RaiseOnLoadSave(int status, const char *msg, const char *fname)
-{
-       if (gLoadSaveErrorMessage != NULL) {
-               MyAppCallback_setConsoleColor(1);
-               MyAppCallback_showScriptMessage("On loading %s:\n%s\n", fname, gLoadSaveErrorMessage);
-               MyAppCallback_setConsoleColor(0);
-       }
-       if (status != 0)
-               rb_raise(rb_eMolbyError, "%s %s", msg, fname);
-}
-
 /*
  *  call-seq:
  *     dup          -> Molecule
@@ -4496,6 +5044,56 @@ s_Molecule_InitCopy(VALUE self, VALUE arg)
 
 /*
  *  call-seq:
+ *     atom_index(val)       -> Integer
+ *
+ *  Returns the atom index represented by val. val can be either a non-negative integer
+ *  (directly representing the atom index), a negative integer (representing <code>natoms - val</code>),
+ *  a string of type "resname.resid:name" or "resname:name" or "resid:name" or "name", 
+ *  where resname, resid, name are the residue name, residue id, and atom name respectively.
+ *  If val is a string and multiple atoms match the description, the atom with the lowest index
+ *  is returned.
+ */
+static VALUE
+s_Molecule_AtomIndex(VALUE self, VALUE val)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       return INT2NUM(s_Molecule_AtomIndexFromValue(mol, val));
+}
+
+/*
+ *  call-seq:
+ *     self == Molecule -> boolean
+ *
+ *  True if the two arguments point to the same molecule.
+ */
+static VALUE
+s_Molecule_Equal(VALUE self, VALUE val)
+{
+       if (rb_obj_is_kind_of(val, rb_cMolecule)) {
+               Molecule *mol1, *mol2;
+               Data_Get_Struct(self, Molecule, mol1);
+               Data_Get_Struct(val, Molecule, mol2);
+               return (mol1 == mol2 ? Qtrue : Qfalse);
+       } else return Qfalse;
+}
+
+#pragma mark ------ Load/Save ------
+
+static void
+s_Molecule_RaiseOnLoadSave(int status, int isloading, const char *msg, const char *fname)
+{
+       if (gLoadSaveErrorMessage != NULL) {
+               MyAppCallback_setConsoleColor(1);
+               MyAppCallback_showScriptMessage("On %s %s:\n%s\n", (isloading ? "loading" : "saving"), fname, gLoadSaveErrorMessage);
+               MyAppCallback_setConsoleColor(0);
+       }
+       if (status != 0)
+               rb_raise(rb_eMolbyError, "%s %s", msg, fname);
+}
+
+/*
+ *  call-seq:
  *     loadmbsf(file)       -> bool
  *
  *  Read a structure from a mbsf file.
@@ -4513,7 +5111,7 @@ s_Molecule_Loadmbsf(int argc, VALUE *argv, VALUE self)
        rb_scan_args(argc, argv, "1", &fname);
        fstr = FileStringValuePtr(fname);
        retval = MoleculeLoadMbsfFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to load mbsf", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load mbsf", fstr);
        return Qtrue;   
 }
 
@@ -4540,13 +5138,13 @@ s_Molecule_Loadpsf(int argc, VALUE *argv, VALUE self)
        rb_scan_args(argc, argv, "11", &fname, &pdbname);
        fstr = FileStringValuePtr(fname);
        retval = MoleculeLoadPsfFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to load psf", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load psf", fstr);
        pdbstr = NULL;
        if (!NIL_P(pdbname)) {
                pdbstr = strdup(FileStringValuePtr(pdbname));
                retval = MoleculeReadCoordinatesFromPdbFile(mol, pdbstr, &gLoadSaveErrorMessage);
                free(pdbstr);
-               s_Molecule_RaiseOnLoadSave(retval, "Failed to load coordinates from pdb", pdbstr);
+               s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load coordinates from pdb", pdbstr);
        }
        return Qtrue;
 }
@@ -4571,7 +5169,7 @@ s_Molecule_Loadpdb(int argc, VALUE *argv, VALUE self)
        MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
        retval = MoleculeReadCoordinatesFromPdbFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to load pdb", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load pdb", fstr);
        return Qtrue;   
 }
 
@@ -4594,7 +5192,7 @@ s_Molecule_Loaddcd(int argc, VALUE *argv, VALUE self)
        MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
        retval = MoleculeReadCoordinatesFromDcdFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to load dcd", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load dcd", fstr);
        return Qtrue;   
 }
 
@@ -4617,7 +5215,7 @@ s_Molecule_Loadtep(int argc, VALUE *argv, VALUE self)
        MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
        retval = MoleculeLoadTepFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to load ORTEP file", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load ORTEP file", fstr);
        return Qtrue;   
 }
 
@@ -4640,7 +5238,7 @@ s_Molecule_Loadres(int argc, VALUE *argv, VALUE self)
        MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
        retval = MoleculeLoadShelxFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to load SHELX res file", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load SHELX res file", fstr);
        return Qtrue;   
 }
 
@@ -4663,7 +5261,7 @@ s_Molecule_Loadfchk(int argc, VALUE *argv, VALUE self)
        MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
        retval = MoleculeLoadGaussianFchkFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to load Gaussian fchk", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load Gaussian fchk", fstr);
        return Qtrue;   
 }
 
@@ -4688,7 +5286,7 @@ s_Molecule_Loaddat(int argc, VALUE *argv, VALUE self)
        MyAppCallback_showProgressPanel("Loading GAMESS dat file...");
        retval = MoleculeLoadGamessDatFile(mol, fstr, &gLoadSaveErrorMessage);
        MyAppCallback_hideProgressPanel();
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to load GAMESS dat", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 1, "Failed to load GAMESS dat", fstr);
        return Qtrue;   
 }
 
@@ -4708,7 +5306,7 @@ s_Molecule_Savembsf(VALUE self, VALUE fname)
        MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
        retval = MoleculeWriteToMbsfFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to save mbsf", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 0, "Failed to save mbsf", fstr);
        return Qtrue;
 }
 
@@ -4728,7 +5326,7 @@ s_Molecule_Savepsf(VALUE self, VALUE fname)
        MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
        retval = MoleculeWriteToPsfFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to save psf", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 0, "Failed to save psf", fstr);
        return Qtrue;
 }
 
@@ -4748,7 +5346,7 @@ s_Molecule_Savepdb(VALUE self, VALUE fname)
        MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
        retval = MoleculeWriteToPdbFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to save pdb", fstr);
+       s_Molecule_RaiseOnLoadSave(retval, 0, "Failed to save pdb", fstr);
        return Qtrue;
 }
 
@@ -4768,30 +5366,9 @@ s_Molecule_Savedcd(VALUE self, VALUE fname)
        MoleculeClearLoadSaveErrorMessage();
        fstr = FileStringValuePtr(fname);
        retval = MoleculeWriteToDcdFile(mol, fstr, &gLoadSaveErrorMessage);
-       s_Molecule_RaiseOnLoadSave(retval, "Failed to save dcd", fstr);
-       return Qtrue;
-}
-
-/*
- *  call-seq:
- *     savetep(file)       -> bool
- *
- *  Write coordinates as an ORTEP file. Returns true if successful.
- */
-/*
-static VALUE
-s_Molecule_Savetep(VALUE self, VALUE fname)
-{
-       char *fstr;
-    Molecule *mol;
-       char errbuf[128];
-    Data_Get_Struct(self, Molecule, mol);
-       fstr = FileStringValuePtr(fname);
-       if (MoleculeWriteToTepFile(mol, fstr, errbuf, sizeof errbuf) != 0)
-               rb_raise(rb_eMolbyError, errbuf);
+       s_Molecule_RaiseOnLoadSave(retval, 0, "Failed to save dcd", fstr);
        return Qtrue;
 }
-*/
 
 /*  load([ftype, ] fname, ...)  */
 static VALUE
@@ -4864,7 +5441,7 @@ s_Molecule_LoadSave(int argc, VALUE *argv, VALUE self, int loadFlag)
 failure:
        rval = rb_str_to_str(argv[0]);
        asprintf(&p, "Failed to %s file %s", (loadFlag ? "load" : "save"), type);
-       s_Molecule_RaiseOnLoadSave(1, p, StringValuePtr(rval));
+       s_Molecule_RaiseOnLoadSave(1, loadFlag, p, StringValuePtr(rval));
        return Qnil;  /*  Does not reach here  */
 
 success:
@@ -4918,6 +5495,112 @@ s_Molecule_Save(int argc, VALUE *argv, VALUE self)
 
 /*
  *  call-seq:
+ *     open        -> Molecule
+ *     open(file)  -> Molecule
+ *
+ *  Create a new molecule from file as a document. If file is not given, an untitled document is created.
+ */
+static VALUE
+s_Molecule_Open(int argc, VALUE *argv, VALUE self)
+{
+       VALUE fname;
+       const char *p;
+       Molecule *mp;
+       VALUE iflag;
+    
+    if (!gUseGUI) {
+        rb_raise(rb_eMolbyError, "Molecule.open is not usable in non-GUI mode. Use Molecule.new instead.");
+    }
+    
+       rb_scan_args(argc, argv, "01", &fname);
+       if (NIL_P(fname))
+               p = NULL;
+       else
+               p = FileStringValuePtr(fname);
+       iflag = Ruby_SetInterruptFlag(Qfalse);
+       mp = MoleculeCallback_openNewMolecule(p);
+       Ruby_SetInterruptFlag(iflag);
+       if (mp == NULL) {
+               if (p == NULL)
+                       rb_raise(rb_eMolbyError, "Cannot create untitled document");
+               else
+                       rb_raise(rb_eMolbyError, "Cannot open the file %s", p);
+       }
+       return ValueFromMolecule(mp);
+}
+
+/*
+ *  call-seq:
+ *     new  -> Molecule
+ *     new(file, *args)  -> Molecule
+ *
+ *  Create a new molecule and call "load" method with the same arguments.
+ */
+static VALUE
+s_Molecule_Initialize(int argc, VALUE *argv, VALUE self)
+{
+       if (argc > 0)
+               return s_Molecule_Load(argc, argv, self);
+       else return Qnil;  /*  An empty molecule (which is prepared in s_Molecule_Alloc()) is returned  */
+}
+
+/*
+ *  call-seq:
+ *     error_message       -> String
+ *
+ *  Get the error_message from the last load/save method. If no error, returns nil.
+ */
+static VALUE
+s_Molecule_ErrorMessage(VALUE klass)
+{
+       if (gLoadSaveErrorMessage == NULL)
+               return Qnil;
+       else return Ruby_NewEncodedStringValue2(gLoadSaveErrorMessage);
+}
+
+/*
+ *  call-seq:
+ *     set_error_message(String)
+ *     Molecule.error_message = String
+ *
+ *  Set the error_message for the present load/save method.
+ */
+static VALUE
+s_Molecule_SetErrorMessage(VALUE klass, VALUE sval)
+{
+       if (gLoadSaveErrorMessage != NULL) {
+               free(gLoadSaveErrorMessage);
+               gLoadSaveErrorMessage = NULL;
+       }
+       if (sval != Qnil) {
+               sval = rb_str_to_str(sval);
+               gLoadSaveErrorMessage = strdup(StringValuePtr(sval));
+       }
+       return sval;
+}
+
+/*
+ *  call-seq:
+ *     set_molecule(Molecule)
+ *
+ *  Duplicate the given molecule and set to self. The present molecule must be empty.
+ *  This method is exclusively used for associating a new document with an existing molecule.
+ */
+static VALUE
+s_Molecule_SetMolecule(VALUE self, VALUE mval)
+{
+       Molecule *mp1, *mp2;
+       Data_Get_Struct(self, Molecule, mp1);
+       mp2 = MoleculeFromValue(mval);
+       MoleculeInitWithMolecule(mp1, mp2);
+       MoleculeCallback_notifyModification(mp1, 1);
+       return self;
+}
+
+#pragma mark ------ Name attributes ------
+
+/*
+ *  call-seq:
  *     name       -> String
  *
  *  Returns the display name of the molecule. If the molecule has no associated
@@ -4933,7 +5616,7 @@ s_Molecule_Name(VALUE self)
        if (buf[0] == 0)
                return Qnil;
        else
-               return rb_str_new2(buf);
+               return Ruby_NewEncodedStringValue2(buf);
 }
 
 /*
@@ -4997,7 +5680,7 @@ s_Molecule_Dir(VALUE self)
                p = strrchr(buf, '/');
                if (p != NULL)
                        *p = 0;
-               return rb_str_new2(buf);
+               return Ruby_NewEncodedStringValue2(buf);
        }
 }
 
@@ -5019,7 +5702,7 @@ s_Molecule_Inspect(VALUE self)
        if (buf[0] == 0) {
                /*  No associated document  */
                snprintf(buf, sizeof buf, "#<Molecule:0x%lx>", self);
-               return rb_str_new2(buf);
+               return Ruby_NewEncodedStringValue2(buf);
        } else {
                /*  Check whether the document name is duplicate  */
                char buf2[256];
@@ -5038,55 +5721,11 @@ s_Molecule_Inspect(VALUE self)
                } else {
                        snprintf(buf2, sizeof buf2, "Molecule[\"%s\"]", buf);
                }
-               return rb_str_new2(buf2);
-       }
-}
-
-/*
- *  call-seq:
- *     open        -> Molecule
- *     open(file)  -> Molecule
- *
- *  Create a new molecule from file as a document. If file is not given, an untitled document is created.
- */
-static VALUE
-s_Molecule_Open(int argc, VALUE *argv, VALUE self)
-{
-       VALUE fname;
-       const char *p;
-       Molecule *mp;
-       VALUE iflag;
-       rb_scan_args(argc, argv, "01", &fname);
-       if (NIL_P(fname))
-               p = NULL;
-       else
-               p = FileStringValuePtr(fname);
-       iflag = Ruby_SetInterruptFlag(Qfalse);
-       mp = MoleculeCallback_openNewMolecule(p);
-       Ruby_SetInterruptFlag(iflag);
-       if (mp == NULL) {
-               if (p == NULL)
-                       rb_raise(rb_eMolbyError, "Cannot create untitled document");
-               else
-                       rb_raise(rb_eMolbyError, "Cannot open the file %s", p);
+               return Ruby_NewEncodedStringValue2(buf2);
        }
-       return ValueFromMolecule(mp);
 }
 
-/*
- *  call-seq:
- *     new  -> Molecule
- *     new(file, *args)  -> Molecule
- *
- *  Create a new molecule and call "load" method with the same arguments.
- */
-static VALUE
-s_Molecule_Initialize(int argc, VALUE *argv, VALUE self)
-{
-       if (argc > 0)
-               return s_Molecule_Load(argc, argv, self);
-       else return Qnil;  /*  An empty molecule (which is prepared in s_Molecule_Alloc()) is returned  */
-}
+#pragma mark ------ MolEnumerables ------
 
 static VALUE
 s_Molecule_MolEnumerable(VALUE self, int kind)
@@ -5258,366 +5897,17 @@ 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;
-    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);
-}
-*/
-
-/*
- *  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
- *
- *  Returns the MD parameter for the idx-th improper.
- */
-/*
-static VALUE
-s_Molecule_ImproperPar(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->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);
-}
-*/
-/*
- *  call-seq:
- *     vdw_par(idx)    -> ParameterRef
- *
- *  Returns the vdw parameter for the idx-th atom.
- */
-/*
-static VALUE
-s_Molecule_VdwPar(VALUE self, VALUE val)
-{
-    Molecule *mol;
-       Int ival;
-       VdwPar *vp;
-       UInt t1;
-    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);
-}
-*/
-
-/*
- *  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;
-}
-
-/*
- *  call-seq:
- *     find_angles     -> Integer
- *
- *  Find the angles from the bonds. Returns the number of angles newly created.
- */
-/*
-static VALUE
-s_Molecule_FindAngles(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);
-       }
-       return INT2NUM(nip);
-}
-*/
-/*
- *  call-seq:
- *     find_dihedrals     -> Integer
- *
- *  Find the dihedrals from the bonds. Returns the number of dihedrals newly created.
- */
-/*
-static VALUE
-s_Molecule_FindDihedrals(VALUE self)
-{
-    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);
-                               }
-                       }
-               }
-       }
-       if (nip > 0) {
-               MolActionCreateAndPerform(mol, gMolActionAddDihedrals, nip * 4, ip, NULL);
-               free(ip);
-       }
-       return INT2NUM(nip);
-}
-*/
-
-/*
- *  call-seq:
- *     nresidues = Integer
- *
- *  Change the number of residues.
- */
-static VALUE
-s_Molecule_ChangeNresidues(VALUE self, VALUE val)
-{
-    Molecule *mol;
-       int ival = NUM2INT(val);
+       int ival = NUM2INT(val);
     Data_Get_Struct(self, Molecule, mol);
        MolActionCreateAndPerform(mol, gMolActionChangeNumberOfResidues, ival);
        if (ival != mol->nresidues)
@@ -5700,524 +5990,213 @@ s_Molecule_EachAtom(int argc, VALUE *argv, VALUE self)
     return self;
 }
 
-/*
- *  call-seq:
- *     cell     -> [a, b, c, alpha, beta, gamma [, sig_a, sig_b, sig_c, sig_alpha, sig_beta, sig_gamma]]
- *
- *  Returns the unit cell parameters. If cell is not set, returns nil.
- */
+#pragma mark ------ Atom Group ------
+
 static VALUE
-s_Molecule_Cell(VALUE self)
+s_Molecule_AtomGroup_i(VALUE arg, VALUE values)
 {
-    Molecule *mol;
-       int i;
-       VALUE val;
-    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]));
-               }
-       }
-       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:
- *     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)
+ *     atom_group
+ *     atom_group {|aref| ...}
+ *     atom_group(arg1, arg2, ...)
+ *     atom_group(arg1, arg2, ...) {|aref| ...}
+ *
+ *  Specify a group of atoms. If no arguments are given, IntGroup\[0...natoms] is the result.
+ *  If arguments are given, then the atoms reprensented by the arguments are added to the
+ *  group. For a conversion of a string to an atom index, see the description
+ *  of Molecule#atom_index.
+ *  If a block is given, it is evaluated with an AtomRef (not atom index integers)
+ *  representing each atom, and the atoms are removed from the result if the block returns false.
  *
- *  Set the 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_SetCell(int argc, VALUE *argv, VALUE self)
+s_Molecule_AtomGroup(int argc, VALUE *argv, VALUE self)
 {
+       IntGroup *ig1, *ig2;
     Molecule *mol;
-       VALUE val, cval;
-       int i, convert_coord, n;
-       double d[12];
+       Int i, startPt, interval;
+       VALUE retval = IntGroup_Alloc(rb_cIntGroup);
+       Data_Get_Struct(retval, IntGroup, ig1);
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "11", &val, &cval);
-       if (val == Qnil) {
-               n = 0;
+       if (argc == 0) {
+               IntGroup_RaiseIfError(IntGroupAdd(ig1, 0, mol->natoms));
        } 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;
+               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:
- *     box -> [avec, bvec, cvec, origin, flags]
+ *     selection       -> IntGroup
  *
- *  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 the current selection.
  */
 static VALUE
-s_Molecule_Box(VALUE self)
+s_Molecule_Selection(VALUE self)
 {
     Molecule *mol;
-       VALUE v[5], val;
+       IntGroup *ig;
+       VALUE val;
     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);
+       if (mol != NULL && (ig = MoleculeGetSelection(mol)) != NULL) {
+               ig = IntGroupNewFromIntGroup(ig);  /*  Duplicate, so that the change from GUI does not affect the value  */
+               val = ValueFromIntGroup(ig);
+               IntGroupRelease(ig);
+       } else {
+               val = IntGroup_Alloc(rb_cIntGroup);
+       }
        return val;
 }
 
-/*
- *  call-seq:
- *     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
- *
- *  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_SetBox(VALUE self, VALUE aval)
+s_Molecule_SetSelectionSub(VALUE self, VALUE val, int undoable)
 {
     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;
+       IntGroup *ig;
     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);
-       return self;
+       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:
- *     cell_periodicity -> [n1, n2, n3]
+ *     selection = IntGroup
  *
- *  Get flags denoting whether the cell is periodic along the a/b/c axes. If the cell is not defined
- *  nil is returned.
+ *  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_CellPeriodicity(VALUE self)
+s_Molecule_SetSelection(VALUE self, VALUE val)
 {
-    Molecule *mol;
-    Data_Get_Struct(self, Molecule, mol);
-       if (mol->cell == NULL)
-               return Qnil;
-       return rb_ary_new3(3, INT2FIX((int)mol->cell->flags[0]), INT2FIX((int)mol->cell->flags[1]), INT2FIX((int)mol->cell->flags[2]));
+       return s_Molecule_SetSelectionSub(self, val, 0);
 }
 
 /*
  *  call-seq:
- *     self.cell_periodicity = [n1, n2, n3] or Integer or nil
- *     set_cell_periodicity = [n1, n2, n3] or Integer or nil
+ *     set_undoable_selection(IntGroup)
  *
- *  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.
+ *  Set the current selection with undo registration. The right-hand operand may be nil.
  *  This operation is undoable.
  */
 static VALUE
-s_Molecule_SetCellPeriodicity(VALUE self, VALUE arg)
+s_Molecule_SetUndoableSelection(VALUE self, VALUE val)
 {
-    Molecule *mol;
-       Int flag;
-    Data_Get_Struct(self, Molecule, mol);
-       if (mol->cell == NULL)
-               rb_raise(rb_eMolbyError, "periodic cell is not defined");
-       if (arg == Qnil)
-               flag = 0;
-       else if (rb_obj_is_kind_of(arg, rb_cNumeric))
-               flag = NUM2INT(rb_Integer(arg));
-       else {
-               Int i;
-               VALUE arg0;
-               arg = rb_ary_to_ary(arg);
-               flag = 0;
-               for (i = 0; i < 3 && i < RARRAY_LEN(arg); i++) {
-                       arg0 = RARRAY_PTR(arg)[i];
-                       if (arg0 != Qnil && arg0 != Qfalse && arg0 != INT2FIX(0))
-                               flag |= (1 << (2 - i));
-               }
-       }
-       MolActionCreateAndPerform(mol, gMolActionSetCellPeriodicity, flag);
-       return arg;
+       return s_Molecule_SetSelectionSub(self, val, 1);
 }
 
+#pragma mark ------ Editing ------
+
 /*
  *  call-seq:
- *     cell_flexibility -> bool
+ *     extract(group, dummy_flag = nil)       -> Molecule
  *
- *  Returns the unit cell is flexible or not
+ *  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_CellFlexibility(VALUE self)
+s_Molecule_Extract(int argc, VALUE *argv, VALUE self)
 {
-       rb_warn("cell_flexibility is obsolete (unit cell is always frame dependent)");
-       return Qtrue;
-/*    Molecule *mol;
-    Data_Get_Struct(self, Molecule, mol);
-       if (mol->cell == NULL)
-               return Qfalse;
-       if (mol->useFlexibleCell)
-               return Qtrue;
-       else return Qfalse; */
+    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);
+       }
+       IntGroupRelease(ig);
+       return retval;
 }
 
 /*
  *  call-seq:
- *     self.cell_flexibility = bool
- *     set_cell_flexibility(bool)
+ *     add(molecule2)       -> self
  *
- *  Change the unit cell is flexible or not
+ *  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_SetCellFlexibility(VALUE self, VALUE arg)
+s_Molecule_Add(VALUE self, VALUE 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; */
+    Molecule *mol1, *mol2;
+    Data_Get_Struct(self, Molecule, mol1);
+       mol2 = MoleculeFromValue(val);
+       MolActionCreateAndPerform(mol1, gMolActionMergeMolecule, mol2, NULL);
+       return self; 
 }
 
 /*
  *  call-seq:
- *     cell_transform -> Transform
+ *     remove(group)       -> Molecule
  *
- *  Get the transform matrix that converts internal coordinates to cartesian coordinates.
- *  If cell is not defined, nil is returned.
- */
-static VALUE
-s_Molecule_CellTransform(VALUE self)
-{
-    Molecule *mol;
-    Data_Get_Struct(self, Molecule, mol);
-       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_Symmetry(VALUE self)
-{
-    Molecule *mol;
-       VALUE val;
-       int i;
-    Data_Get_Struct(self, Molecule, mol);
-       if (mol->nsyms <= 0)
-               return rb_ary_new();
-       val = rb_ary_new2(mol->nsyms);
-       for (i = 0; i < mol->nsyms; i++) {
-               rb_ary_push(val, ValueFromTransform(&mol->syms[i]));
-       }
-       return val;
-}
-
-/*
- *  call-seq:
- *     nsymmetries -> Integer
- *
- *  Get the number of currently defined symmetry operations.
- */
-static VALUE
-s_Molecule_Nsymmetries(VALUE self)
-{
-    Molecule *mol;
-    Data_Get_Struct(self, Molecule, mol);
-       return INT2NUM(mol->nsyms);
-}
-
-/*
- *  call-seq:
- *     add_symmetry(Transform) -> Integer
- *
- *  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_AddSymmetry(VALUE self, VALUE trans)
-{
-    Molecule *mol;
-       Transform tr;
-    Data_Get_Struct(self, Molecule, mol);
-       TransformFromValue(trans, &tr);
-       MolActionCreateAndPerform(mol, gMolActionAddSymmetryOperation, &tr);
-       return INT2NUM(mol->nsyms);
-}
-
-/*
- *  call-seq:
- *     remove_symmetry(count = nil) -> Integer
- *     remove_symmetries(count = nil) -> Integer
- *
- *  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_RemoveSymmetry(int argc, VALUE *argv, VALUE self)
-{
-    Molecule *mol;
-       VALUE cval;
-       int i, n;
-    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);
-}
-
-static VALUE
-s_Molecule_AtomGroup_i(VALUE arg, VALUE values)
-{
-       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:
- *     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.
- *
- */
-static VALUE
-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);
-       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:
- *     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:
- *     extract(group, dummy_flag = nil)       -> Molecule
- *
- *  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_Extract(int argc, VALUE *argv, VALUE self)
-{
-    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);
-       }
-       IntGroupRelease(ig);
-       return retval;
-}
-
-/*
- *  call-seq:
- *     add(molecule2)       -> self
- *
- *  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.
+ *  The atoms designated by the given group are removed from the molecule.
+ *  This operation is undoable.
  */
 static VALUE
 s_Molecule_Remove(VALUE self, VALUE group)
@@ -6227,12 +6206,14 @@ s_Molecule_Remove(VALUE self, VALUE group)
        Int i;
        IntGroupIterator iter;
 
-    Data_Get_Struct(self, Molecule, mol1);
+    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(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);
@@ -6372,7 +6353,7 @@ static VALUE
 s_Molecule_CreateBond(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       Int i, j, *ip, old_nbonds;
+       Int i, j, k, *ip, old_nbonds;
        if (argc == 0)
                rb_raise(rb_eMolbyError, "missing arguments");
        if (argc % 2 != 0)
@@ -6384,6 +6365,14 @@ s_Molecule_CreateBond(int argc, VALUE *argv, VALUE self)
                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;
@@ -6446,6 +6435,131 @@ s_Molecule_RemoveBond(int argc, VALUE *argv, VALUE self)
 
 /*
  *  call-seq:
+ *     assign_bond_order(idx, d1)
+ *     assign_bond_orders(group, [d1, d2, ...])
+ *
+ *  Assign bond order. In the first form, the bond order of the idx-th bond is set to d1 (a Float value).
+ *  In the second form, the bond orders at the indices in the group are set to d1, d2, etc.
+ *  At present, the bond orders are only used in UFF parameter guess, and not in the MM/MD calculations.
+ *  (This may change in the future)
+ *  This operation is undoable.
+ */
+static VALUE
+s_Molecule_AssignBondOrder(VALUE self, VALUE idxval, VALUE dval)
+{
+    Molecule *mol;
+       IntGroup *ig;
+    Data_Get_Struct(self, Molecule, mol);
+       if (rb_obj_is_kind_of(idxval, rb_cNumeric)) {
+               /*  The first form  */
+               Int idx = NUM2INT(rb_Integer(idxval));
+               Double d1 = NUM2DBL(rb_Float(dval));
+               if (idx < 0 || idx >= mol->nbonds)
+                       rb_raise(rb_eMolbyError, "the bond index (%d) is out of bounds", idx);
+               ig = IntGroupNewWithPoints(idx, 1, -1);
+               MolActionCreateAndPerform(mol, gMolActionAssignBondOrders, 1, &d1, ig);
+               IntGroupRelease(ig);
+       } else {
+               Int i, n;
+               Double *dp;
+               ig = IntGroupFromValue(idxval);
+               n = IntGroupGetCount(ig);
+               if (n == 0)
+                       rb_raise(rb_eMolbyError, "the bond index is empty");
+               dval = rb_ary_to_ary(dval);
+               dp = (Double *)calloc(sizeof(Double), n);
+               for (i = 0; i < RARRAY_LEN(dval) && i < n; i++) {
+                       dp[i] = NUM2DBL(rb_Float(RARRAY_PTR(dval)[i]));
+               }
+               MolActionCreateAndPerform(mol, gMolActionAssignBondOrders, n, dp, ig);
+               free(dp);
+               IntGroupRelease(ig);
+       }
+       return self;
+}
+
+/*
+ *  call-seq:
+ *     get_bond_order(idx) -> Float
+ *     get_bond_orders(group) -> Array
+ *
+ *  Get the bond order. In the first form, the bond order of the idx-th bond is returned.
+ *  In the second form, the bond orders at the indices in the group are returned as an array.
+ *  If no bond order information have been assigned, returns nil (the first form)
+ *  or an empty array (the second form).
+ */
+static VALUE
+s_Molecule_GetBondOrder(VALUE self, VALUE idxval)
+{
+    Molecule *mol;
+       IntGroup *ig;
+       Double *dp;
+       VALUE retval;
+       Int i, n, numericArg;
+    Data_Get_Struct(self, Molecule, mol);
+       if (rb_obj_is_kind_of(idxval, rb_cNumeric)) {
+               /*  The first form  */
+               Int idx = NUM2INT(rb_Integer(idxval));
+               if (idx < 0 || idx >= mol->nbonds)
+                       rb_raise(rb_eMolbyError, "the bond index (%d) is out of bounds", idx);
+               if (mol->bondOrders == NULL)
+                       return Qnil;
+               ig = IntGroupNewWithPoints(idx, 1, -1);
+               n = 1;
+               numericArg = 1;
+       } else {
+               if (mol->bondOrders == NULL)
+                       return rb_ary_new();
+               ig = IntGroupFromValue(idxval);
+               n = IntGroupGetCount(ig);
+               if (n == 0)
+                       rb_raise(rb_eMolbyError, "the bond index is empty");
+               numericArg = 0;
+       }
+       dp = (Double *)calloc(sizeof(Double), n);
+       MoleculeGetBondOrders(mol, dp, ig);
+       if (numericArg)
+               retval = rb_float_new(dp[0]);
+       else {
+               retval = rb_ary_new();
+               for (i = 0; i < n; i++)
+                       rb_ary_push(retval, rb_float_new(dp[i]));
+       }
+       free(dp);
+       IntGroupRelease(ig);
+       return retval;
+}
+
+/*
+ *  call-seq:
+ *     bond_exist?(idx1, idx2) -> bool
+ *
+ *  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_BondExist(VALUE self, VALUE ival1, VALUE ival2)
+{
+       Molecule *mol;
+       Int idx1, idx2, i;
+       Atom *ap;
+       Int *cp;
+    Data_Get_Struct(self, Molecule, mol);
+       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:
  *     add_angle(n1, n2, n3)       -> Molecule
  *
  *  Add angle n1-n2-n3. Returns self. Usually, angles are automatically added
@@ -6575,26 +6689,34 @@ s_Molecule_AddImproper(VALUE self, VALUE v1, VALUE v2, VALUE v3, VALUE v4)
 /*
  *  call-seq:
  *     remove_improper(n1, n2, n3, n4)       -> Molecule
+ *     remove_improper(intgroup)             -> Molecule
  *
- *  Remove improper n1-n2-n3-n4. Returns self. Unlike angles and dihedrals, impropers are
+ *  Remove improper n1-n2-n3-n4, or the specified impropers (in indices) in IntGroup.
+ *  Returns self. Unlike angles and dihedrals, impropers are
  *  not automatically added when a new bond is created, so this method is more useful than
  *  the angle/dihedral counterpart.
  *  This operation is undoable.
  */
 static VALUE
-s_Molecule_RemoveImproper(VALUE self, VALUE v1, VALUE v2, VALUE v3, VALUE v4)
+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);
-       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);
+       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;
@@ -6731,72 +6853,39 @@ s_Molecule_RenumberAtoms(VALUE self, VALUE array)
 
 /*
  *  call-seq:
- *     find_close_atoms(atom, limit = 1.2)       -> array of Integers (atom indices)
+ *     set_atom_attr(index, key, value)
  *
- *  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 atom attribute for the specified atom.
+ *  This operation is undoable.
  */
 static VALUE
-s_Molecule_FindCloseAtoms(int argc, VALUE *argv, VALUE self)
+s_Molecule_SetAtomAttr(VALUE self, VALUE idx, VALUE key, VALUE val)
 {
-    Molecule *mol;
-       VALUE aval, limval;
-       double limit;
-       Int n1, nbonds, *bonds;
+       Molecule *mol;
+       VALUE aref, oldval;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "11", &aval, &limval);
-       n1 = s_Molecule_AtomIndexFromValue(mol, aval);
-       if (limval == Qnil)
-               limit = 1.2;
-       else
-               limit = NUM2DBL(rb_Float(limval));
-       nbonds = 0;  /*  This initialization is necessary: see comments in MoleculeFindCloseAtoms()  */
-       bonds = NULL;
-       MoleculeFindCloseAtoms(mol, n1, limit, &nbonds, &bonds, 0);
-       aval = rb_ary_new();
-       if (nbonds > 0) {
-               for (n1 = 0; n1 < nbonds; n1++)
-                       rb_ary_push(aval, INT2NUM(bonds[n1 * 2 + 1]));
-               free(bonds);
-       }
-       return aval;
+       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:
- *     guess_bonds(limit = 1.2)       -> Integer
+ *     get_atom_attr(index, key)
  *
- *  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 the atom attribute for the specified atom.
  */
 static VALUE
-s_Molecule_GuessBonds(int argc, VALUE *argv, VALUE self)
+s_Molecule_GetAtomAttr(VALUE self, VALUE idx, VALUE key)
 {
-    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, NULL);
-               free(bonds);
-       }
-       return INT2NUM(nbonds);
+       return s_Molecule_SetAtomAttr(self, idx, key, Qundef);
 }
-       
+
+#pragma mark ------ Undo Support ------
+
 /*
  *  call-seq:
  *     register_undo(script, *args)
@@ -6847,660 +6936,883 @@ s_Molecule_SetUndoEnabled(VALUE self, VALUE val)
        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;
+       }
+}
+
 /*
  *  call-seq:
- *     selection       -> IntGroup
+ *     center_of_mass(group = nil)       -> Vector3D
  *
- *  Returns the current selection.
+ *  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_Selection(VALUE self)
+s_Molecule_CenterOfMass(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
+       VALUE group;
        IntGroup *ig;
-       VALUE val;
+       Vector v;
     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);
+       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);
-       } else {
-               val = IntGroup_Alloc(rb_cIntGroup);
-       }
-       return val;
+       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_SetSelectionSub(VALUE self, VALUE val, int undoable)
+s_Molecule_Centralize(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
+       VALUE group;
        IntGroup *ig;
+       Vector v;
     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);
+       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 val;
+       v.x = -v.x;
+       v.y = -v.y;
+       v.z = -v.z;
+       MolActionCreateAndPerform(mol, gMolActionTranslateAtoms, &v, NULL);
+       return self;
 }
 
 /*
  *  call-seq:
- *     selection = IntGroup
+ *     bounds(group = nil)       -> [min, max]
  *
- *  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.
+ *  Calculate the boundary. The return value is an array of two Vector3D objects.
  */
 static VALUE
-s_Molecule_SetSelection(VALUE self, VALUE val)
+s_Molecule_Bounds(int argc, VALUE *argv, VALUE self)
 {
-       return s_Molecule_SetSelectionSub(self, val, 0);
+    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));
 }
 
-/*
- *  call-seq:
- *     set_undoable_selection(IntGroup)
- *
- *  Set the current selection with undo registration. The right-hand operand may be nil.
- *  This operation is undoable.
- */
-static VALUE
-s_Molecule_SetUndoableSelection(VALUE self, VALUE val)
+/*  Get atom position or a vector  */
+static void
+s_Molecule_GetVectorFromArg(Molecule *mol, VALUE val, Vector *vp)
 {
-       return s_Molecule_SetSelectionSub(self, val, 1);
+       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:
- *     hidden_atoms       -> IntGroup
+ *     measure_bond(n1, n2)       -> Float
  *
- *  Returns the currently hidden atoms.
+ *  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_HiddenAtoms(VALUE self)
+s_Molecule_MeasureBond(VALUE self, VALUE nval1, VALUE nval2)
 {
-       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;
+       Vector v1, v2;
     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; */
+       s_Molecule_GetVectorFromArg(mol, nval1, &v1);
+       s_Molecule_GetVectorFromArg(mol, nval2, &v2);
+       return rb_float_new(MoleculeMeasureBond(mol, &v1, &v2));
 }
 
 /*
  *  call-seq:
- *     set_hidden_atoms(IntGroup)
- *     self.hidden_atoms = IntGroup
+ *     measure_angle(n1, n2, n3)       -> Float
  *
- *  Hide the specified atoms. This operation is _not_ 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_SetHiddenAtoms(VALUE self, VALUE val)
+s_Molecule_MeasureAngle(VALUE self, VALUE nval1, VALUE nval2, VALUE nval3)
 {
-       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;
+       Vector v1, v2, v3;
+       Double d;
     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; */
+       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:
- *     select_frame(index)
- *     frame = index
+ *     measure_dihedral(n1, n2, n3, n4)       -> Float
  *
- *  Select the specified frame. If successful, returns true, otherwise returns false.
+ *  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_SelectFrame(VALUE self, VALUE val)
+s_Molecule_MeasureDihedral(VALUE self, VALUE nval1, VALUE nval2, VALUE nval3, VALUE nval4)
 {
     Molecule *mol;
-       int ival = NUM2INT(val);
+       Vector v1, v2, v3, v4;
+       Double d;
     Data_Get_Struct(self, Molecule, mol);
-       ival = MoleculeSelectFrame(mol, ival, 1);
-       if (ival >= 0)
-               return Qtrue;
-       else return Qfalse;
+       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:
- *     frame -> Integer
+ *     find_conflicts(limit[, group1[, group2 [, ignore_exclusion]]]) -> [[n1, n2], [n3, n4], ...]
  *
- *  Get the current frame.
+ *  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_Frame(VALUE self)
+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);
-       return INT2NUM(mol->cframe);
+       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:
- *     nframes -> Integer
+ *     find_close_atoms(atom, limit = 1.2, radius = 0.77)   -> array of Integers (atom indices)
  *
- *  Get the number of frames.
+ *  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_Nframes(VALUE self)
+s_Molecule_FindCloseAtoms(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
+       VALUE aval, limval, radval;
+       double limit, radius;
+       Int n1, nbonds, *bonds, an;
+       Vector v;
     Data_Get_Struct(self, Molecule, mol);
-       return INT2NUM(MoleculeGetNumberOfFrames(mol));
+       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:
- *     insert_frame(integer, coordinates = nil, cell_axes = nil) -> bool
- *     insert_frames(intGroup = nil, coordinates = nil, cell_axes = nil) -> bool
+ *     guess_bonds(limit = 1.2)       -> Integer
  *
- *  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.
+ *  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_InsertFrames(int argc, VALUE *argv, VALUE self)
+s_Molecule_GuessBonds(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;
+       VALUE limval;
+       double limit;
+       Int nbonds, *bonds;
     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);
+       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);
        }
-       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);
+       return INT2NUM(nbonds);
 }
 
-/*
- *  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);
-}
+#pragma mark ------ Cell and Symmetry ------
 
 /*
  *  call-seq:
- *     remove_frames(IntGroup, wantCoordinates = false)
+ *     cell     -> [a, b, c, alpha, beta, gamma [, sig_a, sig_b, sig_c, sig_alpha, sig_beta, sig_gamma]]
  *
- *  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.
+ *  Returns the unit cell parameters. If cell is not set, returns nil.
  */
 static VALUE
-s_Molecule_RemoveFrames(int argc, VALUE *argv, VALUE self)
+s_Molecule_Cell(VALUE self)
 {
-       VALUE val, flag;
-       VALUE retval;
     Molecule *mol;
-       IntGroup *ig;
-       int count;
+       int i;
+       VALUE val;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "11", &val, &flag);
-       ig = IntGroupFromValue(val);
-       count = IntGroupGetCount(ig);
-       if (RTEST(flag)) {
-               /*  Create return value before removing frames  */
-               VALUE coords;
-               int i, j, n;
-               Atom *ap;
-               Vector v;
-               retval = rb_ary_new2(count);
-               for (i = 0; i < count; i++) {
-                       n = IntGroupGetNthPoint(ig, i);
-                       coords = rb_ary_new2(mol->natoms);
-                       for (j = 0, ap = mol->atoms; j < mol->natoms; j++, ap = ATOM_NEXT(ap)) {
-                               if (n < ap->nframes && n != mol->cframe)
-                                       v = ap->frames[n];
-                               else v = ap->r;
-                               rb_ary_push(coords, ValueFromVector(&v));
-                       }
-                       rb_ary_push(retval, coords);
+       if (mol->cell == NULL)
+               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]));
                }
-       } else retval = Qtrue;
-       if (MolActionCreateAndPerform(mol, gMolActionRemoveFrames, ig) >= 0)
-               return retval;
-       else return Qnil;
+       }
+       return val;
 }
 
 /*
  *  call-seq:
- *     each_frame {|n| ...}
+ *     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)
  *
- *  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.
+ *  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_EachFrame(VALUE self)
+s_Molecule_SetCell(int argc, VALUE *argv, VALUE self)
 {
-       int i, cframe, nframes;
     Molecule *mol;
+       VALUE val, cval;
+       int i, convert_coord, n;
+       double d[12];
     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);
+       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]));
        }
-    return self;
+       convert_coord = (RTEST(cval) ? 1 : 0);
+       MolActionCreateAndPerform(mol, gMolActionSetCell, n, d, convert_coord);
+       return val;
 }
 
 /*
  *  call-seq:
- *     set_atom_attr(index, key, value)
+ *     box -> [avec, bvec, cvec, origin, flags]
  *
- *  Set the atom attribute for the specified atom.
- *  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_SetAtomAttr(VALUE self, VALUE idx, VALUE key, VALUE val)
+s_Molecule_Box(VALUE self)
 {
-       Molecule *mol;
-       VALUE aref, oldval;
+    Molecule *mol;
+       VALUE v[5], val;
     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);
+       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:
- *     get_atom_attr(index, key)
+ *     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
  *
- *  Get the atom attribute for the specified atom.
+ *  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_GetAtomAttr(VALUE self, VALUE idx, VALUE key)
+s_Molecule_SetBox(VALUE self, VALUE aval)
 {
-       return s_Molecule_SetAtomAttr(self, idx, key, Qundef);
+    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);
+       return self;
 }
 
 /*
  *  call-seq:
- *     get_coord_from_frame(index, group = nil)
+ *     cell_periodicity -> [n1, n2, n3]
  *
- *  Copy the coordinates from the indicated frame. If group is specified, only the specified atoms
- *  are modified. Third argument (cflag) is now obsolete (it used to specify whether the cell parameters are to be
- *  copied; now they are always copied)
+ *  Get 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_GetCoordFromFrame(int argc, VALUE *argv, VALUE self)
+s_Molecule_CellPeriodicity(VALUE self)
 {
-       Molecule *mol;
-       VALUE ival, gval, cval;
-       Int index, i, j, n, nn;
-       IntGroup *ig;
-       IntGroupIterator iter;
-       Atom *ap;
-       Vector *vp;
+    Molecule *mol;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "12", &ival, &gval, &cval);
-       if (argc == 3)
-               rb_warn("The 3rd argument to get_coord_from_frame() is now obsolete");
-       index = NUM2INT(rb_Integer(ival));
-       if (index < 0 || index >= (n = MoleculeGetNumberOfFrames(mol))) {
-               if (n == 0)
-                       rb_raise(rb_eMolbyError, "No frame is present");
-               else
-                       rb_raise(rb_eMolbyError, "Frame index (%d) out of range (should be 0..%d)", index, n - 1);
-       }
-       if (gval == Qnil) {
-               ig = IntGroupNewWithPoints(0, mol->natoms, -1);
-       } else {
-               ig = s_Molecule_AtomGroupFromValue(self, gval);
-       }
-       n = IntGroupGetCount(ig);
-       if (n > 0) {
-               vp = (Vector *)calloc(sizeof(Vector), n);
-               IntGroupIteratorInit(ig, &iter);
-               j = 0;
-               nn = 0;
-               while ((i = IntGroupIteratorNext(&iter)) >= 0) {
-                       ap = ATOM_AT_INDEX(mol->atoms, i);
-                       if (index < ap->nframes) {
-                               vp[j] = ap->frames[index];
-                               nn++;
-                       } else {
-                               vp[j] = ap->r;
-                       }
-                       j++;
-               }
-               if (nn > 0)
-                       MolActionCreateAndPerform(mol, gMolActionSetAtomPositions, ig, n, vp);
-               free(vp);
-               if (mol->cell != NULL && mol->frame_cells != NULL && index < mol->nframe_cells) {
-                       vp = mol->frame_cells + index * 4;
-                       MolActionCreateAndPerform(mol, gMolActionSetBox, vp, vp + 1, vp + 2, vp + 3, -1, 0);
-               }
-               IntGroupIteratorRelease(&iter);
-       }
-       IntGroupRelease(ig);
-       return self;
+       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:
- *     fragment(n1, *exatoms)  -> IntGroup
- *     fragment(group, *exatoms)  -> IntGroup
+ *     self.cell_periodicity = [n1, n2, n3] or Integer or nil
+ *     set_cell_periodicity = [n1, n2, n3] or Integer or nil
  *
- *  Get the fragment including the atom n1 or the atom group. If additional arguments are given,
- *  those atoms will not be counted during the search.
+ *  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_Fragment(int argc, VALUE *argv, VALUE self)
+s_Molecule_SetCellPeriodicity(VALUE self, VALUE arg)
 {
     Molecule *mol;
-       IntGroup *baseg, *ig, *exatoms;
-       int n;
-       volatile VALUE nval, exval;
+       Int flag;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "1*", &nval, &exval);
-       if (rb_obj_is_kind_of(nval, rb_cNumeric) || rb_obj_is_kind_of(nval, rb_cString)) {
-               baseg = NULL;
-               n = NUM2INT(s_Molecule_AtomIndex(self, nval));
-       } else {
-               baseg = s_Molecule_AtomGroupFromValue(self, nval);
-       }
-       if (RARRAY_LEN(exval) == 0) {
-               exatoms = NULL;
-       } else {
-               exval = s_Molecule_AtomGroup(RARRAY_LEN(exval), RARRAY_PTR(exval), self);
-               Data_Get_Struct(exval, IntGroup, exatoms);
-       }
-       if (baseg == NULL) {
-               ig = MoleculeFragmentExcludingAtomGroup(mol, n, exatoms);
-       } else {
-               IntGroupIterator iter;
-               IntGroupIteratorInit(baseg, &iter);
-               if ((n = IntGroupIteratorNext(&iter)) < 0) {
-                       ig = IntGroupNew();
-               } else {
-                       ig = MoleculeFragmentExcludingAtomGroup(mol, n, exatoms);
-                       if (ig != NULL) {
-                               while ((n = IntGroupIteratorNext(&iter)) >= 0) {
-                                       IntGroup *subg;
-                                       subg = MoleculeFragmentExcludingAtomGroup(mol, n, exatoms);
-                                       if (subg != NULL) {
-                                               IntGroupAddIntGroup(ig, subg);
-                                               IntGroupRelease(subg);
-                                       }
-                               }
-                       }
+       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));
                }
-               IntGroupIteratorRelease(&iter);
-               IntGroupRelease(baseg);
        }
-       if (ig == NULL)
-               rb_raise(rb_eMolbyError, "invalid specification of molecular fragment");
-       nval = ValueFromIntGroup(ig);
-       IntGroupRelease(ig);
-       return nval;
+       MolActionCreateAndPerform(mol, gMolActionSetCellPeriodicity, flag);
+       return arg;
 }
 
 /*
  *  call-seq:
- *     each_fragment {|group| ...}
+ *     cell_flexibility -> bool
  *
- *  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 unit cell is flexible or not
  */
 static VALUE
-s_Molecule_EachFragment(VALUE self)
+s_Molecule_CellFlexibility(VALUE self)
 {
-    Molecule *mol;
-       IntGroup *ag, *fg;
-       VALUE gval;
-    Data_Get_Struct(self, Molecule, mol);
-       if (mol == NULL || mol->natoms == 0)
-               return self;
-       ag = IntGroupNewWithPoints(0, mol->natoms, -1);
-       while (IntGroupGetCount(ag) > 0) {
-               int n = IntGroupGetNthPoint(ag, 0);
-               fg = MoleculeFragmentExcludingAtomGroup(mol, n, NULL);
-               if (fg == NULL)
-                       rb_raise(rb_eMolbyError, "internal error during each_fragment");
-               gval = ValueFromIntGroup(fg);
-               rb_yield(gval);
-               IntGroupRemoveIntGroup(ag, fg);
-               IntGroupRelease(fg);
-       }
-       IntGroupRelease(ag);
-       return self;
+       rb_warn("cell_flexibility is obsolete (unit cell is always frame dependent)");
+       return Qtrue;
+       /*    Molecule *mol;
+        Data_Get_Struct(self, Molecule, mol);
+        if (mol->cell == NULL)
+        return Qfalse;
+        if (mol->useFlexibleCell)
+        return Qtrue;
+        else return Qfalse; */
 }
 
 /*
  *  call-seq:
- *     detachable?(group)  -> [n1, n2]
+ *     self.cell_flexibility = bool
+ *     set_cell_flexibility(bool)
  *
- *  Check whether the group is 'detachable', i.e. the group is bound to the rest 
- *  of the molecule via only one bond. If it is, then the indices of the atoms
- *  belonging to the bond is returned, the first element being the atom included
- *  in the fragment. Otherwise, Qnil is returned.
+ *  Change the unit cell is flexible or not
  */
 static VALUE
-s_Molecule_Detachable_P(VALUE self, VALUE gval)
+s_Molecule_SetCellFlexibility(VALUE self, VALUE arg)
 {
-       Molecule *mol;
-       IntGroup *ig;
-       int n1, n2;
-       VALUE retval;
-    Data_Get_Struct(self, Molecule, mol);
-       ig = s_Molecule_AtomGroupFromValue(self, gval);
-       if (MoleculeIsFragmentDetachable(mol, ig, &n1, &n2)) {
-               retval = rb_ary_new3(2, INT2NUM(n1), INT2NUM(n2));
-       } else retval = Qnil;
-       IntGroupRelease(ig);
-       return retval;
+       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:
- *     bonds_on_border(group = selection)  -> Array of Array of two Integers
+ *     cell_transform -> Transform
  *
- *  Returns an array of bonds that connect an atom in the group and an atom out
- *  of the group. The first atom in the bond always belongs to the group. If no
- *  such bonds are present, an empty array is returned.
+ *  Get the transform matrix that converts internal coordinates to cartesian coordinates.
+ *  If cell is not defined, nil is returned.
  */
 static VALUE
-s_Molecule_BondsOnBorder(int argc, VALUE *argv, VALUE self)
+s_Molecule_CellTransform(VALUE self)
 {
-       Molecule *mol;
-       IntGroup *ig, *bg;
-       VALUE gval, retval;
+    Molecule *mol;
     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;
+       if (mol == NULL || mol->cell == NULL)
+               return Qnil;
+       return ValueFromTransform(&(mol->cell->tr));
 }
 
 /*
  *  call-seq:
- *     translate(vec, group = nil)       -> Molecule
+ *     symmetry -> Array of Transforms
+ *     symmetries -> Array of Transforms
  *
- *  Translate the molecule by vec. If group is given, only atoms in the group are moved.
- *  This operation is undoable.
+ *  Get the currently defined symmetry operations. If no symmetry operation is defined,
+ *  returns an empty array.
  */
 static VALUE
-s_Molecule_Translate(int argc, VALUE *argv, VALUE self)
+s_Molecule_Symmetry(VALUE self)
 {
     Molecule *mol;
-       VALUE vec, group;
-       Vector v;
-       IntGroup *ig;
+       VALUE val;
+       int i;
     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;
+       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:
- *     rotate(axis, angle, center = [0,0,0], group = nil)       -> Molecule
+ *     nsymmetries -> Integer
  *
- *  Rotate the molecule. The axis must not a zero vector. angle is given in degree.
+ *  Get the number of currently defined symmetry operations.
+ */
+static VALUE
+s_Molecule_Nsymmetries(VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       return INT2NUM(mol->nsyms);
+}
+
+/*
+ *  call-seq:
+ *     add_symmetry(Transform) -> Integer
+ *
+ *  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_AddSymmetry(VALUE self, VALUE trans)
+{
+    Molecule *mol;
+       Transform tr;
+    Data_Get_Struct(self, Molecule, mol);
+       TransformFromValue(trans, &tr);
+       MolActionCreateAndPerform(mol, gMolActionAddSymmetryOperation, &tr);
+       return INT2NUM(mol->nsyms);
+}
+
+/*
+ *  call-seq:
+ *     remove_symmetry(count = nil) -> Integer
+ *     remove_symmetries(count = nil) -> Integer
+ *
+ *  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_RemoveSymmetry(int argc, VALUE *argv, VALUE self)
+{
+    Molecule *mol;
+       VALUE cval;
+       int i, n;
+    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);
+}
+
+/*
+ *  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:
+ *     expand_by_symmetry(group, sym, dx=0, dy=0, dz=0, allow_overlap = false) -> Array
+ *
+ *  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_ExpandBySymmetry(int argc, VALUE *argv, VALUE self)
+{
+    Molecule *mol;
+       VALUE gval, sval, xval, yval, zval, rval, oval;
+       IntGroup *ig;
+       Int n[4], allow_overlap;
+       Int natoms;
+       Int nidx, *idx;
+       
+    Data_Get_Struct(self, Molecule, mol);
+       rb_scan_args(argc, argv, "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:
+ *     amend_by_symmetry(group = nil) -> IntGroup
+ *
+ *  Expand the specified part of the molecule by the given symmetry operation.
+ *  Returns an IntGroup containing the added atoms.
+ */
+static VALUE
+s_Molecule_AmendBySymmetry(int argc, VALUE *argv, VALUE self)
+{
+    Molecule *mol;
+       IntGroup *ig, *ig2;
+       VALUE rval, gval;
+    Data_Get_Struct(self, Molecule, mol);
+       rb_scan_args(argc, argv, "01", &gval);
+       if (gval != Qnil)
+               ig = s_Molecule_AtomGroupFromValue(self, gval);
+       else ig = NULL;
+       MolActionCreateAndPerform(mol, gMolActionAmendBySymmetry, ig, &ig2);
+       rval = ValueFromIntGroup(ig2);
+       IntGroupRelease(ig2);
+       return rval;
+}
+
+#pragma mark ------ Transforms ------
+
+/*
+ *  call-seq:
+ *     translate(vec, group = nil)       -> Molecule
+ *
+ *  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.
  */
@@ -7612,472 +7924,650 @@ s_Molecule_Transform(int argc, VALUE *argv, VALUE self)
        rb_scan_args(argc, argv, "11", &trans, &group);
        ig = (NIL_P(group) ? NULL : s_Molecule_AtomGroupFromValue(self, group));
        TransformFromValue(trans, &tr);
-/*     MoleculeTransform(mol, tr, ig); */
+       /*      MoleculeTransform(mol, tr, ig); */
        MolActionCreateAndPerform(mol, gMolActionTransformAtoms, &tr, ig);
        if (ig != NULL)
                IntGroupRelease(ig);
        return self;
 }
 
-static void
-s_Molecule_DoCenterOfMass(Molecule *mol, Vector *outv, IntGroup *ig)
-{
-       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
+ *     transform_for_symop(symop, is_cartesian = nil) -> Transform
  *
- *  Calculate the center of mass for the given set of atoms. The argument
- *  group is null, then all atoms are considered.
+ *  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_CenterOfMass(int argc, VALUE *argv, VALUE self)
+s_Molecule_TransformForSymop(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       VALUE group;
-       IntGroup *ig;
-       Vector v;
+       VALUE sval, fval;
+       Symop symop;
+       Transform tr;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "01", &group);
-       ig = (NIL_P(group) ? NULL : s_Molecule_AtomGroupFromValue(self, group));
-       s_Molecule_DoCenterOfMass(mol, &v, ig);
-       if (ig != NULL)
-               IntGroupRelease(ig);
-       return ValueFromVector(&v);
+       if (mol->cell == NULL)
+               rb_raise(rb_eMolbyError, "no unit cell is defined");
+       if (mol->nsyms == 0)
+               rb_raise(rb_eMolbyError, "no symmetry operation is defined");
+       rb_scan_args(argc, argv, "11", &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:
- *     centralize(group = nil)       -> self
+ *     symop_for_transform(transform, is_cartesian = nil) -> [sym, dx, dy, dz]
  *
- *  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).
+ *  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_Centralize(int argc, VALUE *argv, VALUE self)
+s_Molecule_SymopForTransform(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       VALUE group;
-       IntGroup *ig;
-       Vector v;
+       VALUE tval, fval;
+       Symop symop;
+       Transform tr;
+       int n;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "01", &group);
-       ig = (NIL_P(group) ? NULL : s_Molecule_AtomGroupFromValue(self, group));
-       s_Molecule_DoCenterOfMass(mol, &v, ig);
-       if (ig != NULL)
-               IntGroupRelease(ig);
-       v.x = -v.x;
-       v.y = -v.y;
-       v.z = -v.z;
-       MolActionCreateAndPerform(mol, gMolActionTranslateAtoms, &v, NULL);
-       return self;
+       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:
- *     bounds(group = nil)       -> [min, max]
+ *     select_frame(index)
+ *     frame = index
  *
- *  Calculate the boundary. The return value is an array of two Vector3D objects.
+ *  Select the specified frame. If successful, returns true, otherwise returns false.
  */
 static VALUE
-s_Molecule_Bounds(int argc, VALUE *argv, VALUE self)
+s_Molecule_SelectFrame(VALUE self, VALUE val)
 {
     Molecule *mol;
-       VALUE group;
-       IntGroup *ig;
-       Vector vmin, vmax;
-       int n;
-       Atom *ap;
+       int ival = NUM2INT(val);
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "01", &group);
-       ig = (NIL_P(group) ? NULL : s_Molecule_AtomGroupFromValue(self, group));
-       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));
+       ival = MoleculeSelectFrame(mol, ival, 1);
+       if (ival >= 0)
+               return Qtrue;
+       else return Qfalse;
 }
 
-/*  Get atom position or a vector  */
-static void
-s_Molecule_GetVectorFromArg(Molecule *mol, VALUE val, Vector *vp)
+/*
+ *  call-seq:
+ *     frame -> Integer
+ *
+ *  Get the current frame.
+ */
+static VALUE
+s_Molecule_Frame(VALUE self)
 {
-       if (rb_obj_is_kind_of(val, rb_cInteger) || rb_obj_is_kind_of(val, rb_cString)) {
-               int n1 = s_Molecule_AtomIndexFromValue(mol, val);
-               *vp = ATOM_AT_INDEX(mol->atoms, n1)->r;
+    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 {
-               VectorFromValue(val, vp);
+               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:
- *     measure_bond(n1, n2)       -> Float
+ *     create_frame(coordinates = nil) -> Integer
+ *     create_frames(coordinates = nil) -> Integer
  *
- *  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.
+ *  Same as molecule.insert_frames(nil, coordinates).
  */
 static VALUE
-s_Molecule_MeasureBond(VALUE self, VALUE nval1, VALUE nval2)
+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;
-       Vector v1, v2;
+       IntGroup *ig;
+       int count;
     Data_Get_Struct(self, Molecule, mol);
-       s_Molecule_GetVectorFromArg(mol, nval1, &v1);
-       s_Molecule_GetVectorFromArg(mol, nval2, &v2);
-       return rb_float_new(MoleculeMeasureBond(mol, &v1, &v2));
+       rb_scan_args(argc, argv, "11", &val, &flag);
+       ig = IntGroupFromValue(val);
+       count = IntGroupGetCount(ig);
+       if (RTEST(flag)) {
+               /*  Create return value before removing frames  */
+               VALUE coords;
+               int i, j, n;
+               Atom *ap;
+               Vector v;
+               retval = rb_ary_new2(count);
+               for (i = 0; i < count; i++) {
+                       n = IntGroupGetNthPoint(ig, i);
+                       coords = rb_ary_new2(mol->natoms);
+                       for (j = 0, ap = mol->atoms; j < mol->natoms; j++, ap = ATOM_NEXT(ap)) {
+                               if (n < ap->nframes && n != mol->cframe)
+                                       v = ap->frames[n];
+                               else v = ap->r;
+                               rb_ary_push(coords, ValueFromVector(&v));
+                       }
+                       rb_ary_push(retval, coords);
+               }
+       } else retval = Qtrue;
+       if (MolActionCreateAndPerform(mol, gMolActionRemoveFrames, ig) >= 0)
+               return retval;
+       else return Qnil;
 }
 
 /*
  *  call-seq:
- *     measure_angle(n1, n2, n3)       -> Float
+ *     each_frame {|n| ...}
  *
- *  Calculate the bond angle. The arguments can either be atom indices, the "residue:name" representation, 
- *  or Vector3D values. The return value is in degree.
- *  If the crystallographic cell is defined, then the internal coordinates are convereted to the cartesian.
+ *  Set the frame number from 0 to nframes-1 and execute the block. The block argument is
+ *  the frame number. After completion, the original frame number is restored.
  */
 static VALUE
-s_Molecule_MeasureAngle(VALUE self, VALUE nval1, VALUE nval2, VALUE nval3)
+s_Molecule_EachFrame(VALUE self)
 {
+       int i, cframe, nframes;
     Molecule *mol;
-       Vector v1, v2, v3;
-       Double d;
     Data_Get_Struct(self, Molecule, mol);
-       s_Molecule_GetVectorFromArg(mol, nval1, &v1);
-       s_Molecule_GetVectorFromArg(mol, nval2, &v2);
-       s_Molecule_GetVectorFromArg(mol, nval3, &v3);   
-       d = MoleculeMeasureAngle(mol, &v1, &v2, &v3);
-       if (isnan(d))
-               return Qnil;  /*  Cannot define  */
-       else return rb_float_new(d);
+       cframe = mol->cframe;
+       nframes = MoleculeGetNumberOfFrames(mol);
+       if (nframes > 0) {
+               for (i = 0; i < nframes; i++) {
+                       MoleculeSelectFrame(mol, i, 1);
+                       rb_yield(INT2NUM(i));
+               }
+               MoleculeSelectFrame(mol, cframe, 1);
+       }
+    return self;
 }
 
 /*
  *  call-seq:
- *     measure_dihedral(n1, n2, n3, n4)       -> Float
+ *     get_coord_from_frame(index, group = nil)
  *
- *  Calculate the dihedral angle. The arguments can either be atom indices, the "residue:name" representation, 
- *  or Vector3D values. The return value is in degree.
- *  If the crystallographic cell is defined, then the internal coordinates are convereted to the cartesian.
+ *  Copy the coordinates from the indicated frame. If group is specified, only the specified atoms
+ *  are modified. Third argument (cflag) is now obsolete (it used to specify whether the cell parameters are to be
+ *  copied; now they are always copied)
  */
 static VALUE
-s_Molecule_MeasureDihedral(VALUE self, VALUE nval1, VALUE nval2, VALUE nval3, VALUE nval4)
+s_Molecule_GetCoordFromFrame(int argc, VALUE *argv, VALUE self)
 {
-    Molecule *mol;
-       Vector v1, v2, v3, v4;
-       Double d;
+       Molecule *mol;
+       VALUE ival, gval, cval;
+       Int index, i, j, n, nn;
+       IntGroup *ig;
+       IntGroupIterator iter;
+       Atom *ap;
+       Vector *vp;
     Data_Get_Struct(self, Molecule, mol);
-       s_Molecule_GetVectorFromArg(mol, nval1, &v1);
-       s_Molecule_GetVectorFromArg(mol, nval2, &v2);
-       s_Molecule_GetVectorFromArg(mol, nval3, &v3);   
-       s_Molecule_GetVectorFromArg(mol, nval4, &v4);   
-       d = MoleculeMeasureDihedral(mol, &v1, &v2, &v3, &v4);
-       if (isnan(d))
-               return Qnil;  /*  Cannot define  */
-       else return rb_float_new(d);
+       rb_scan_args(argc, argv, "12", &ival, &gval, &cval);
+       if (argc == 3)
+               rb_warn("The 3rd argument to get_coord_from_frame() is now obsolete");
+       index = NUM2INT(rb_Integer(ival));
+       if (index < 0 || index >= (n = MoleculeGetNumberOfFrames(mol))) {
+               if (n == 0)
+                       rb_raise(rb_eMolbyError, "No frame is present");
+               else
+                       rb_raise(rb_eMolbyError, "Frame index (%d) out of range (should be 0..%d)", index, n - 1);
+       }
+       if (gval == Qnil) {
+               ig = IntGroupNewWithPoints(0, mol->natoms, -1);
+       } else {
+               ig = s_Molecule_AtomGroupFromValue(self, gval);
+       }
+       n = IntGroupGetCount(ig);
+       if (n > 0) {
+               vp = (Vector *)calloc(sizeof(Vector), n);
+               IntGroupIteratorInit(ig, &iter);
+               j = 0;
+               nn = 0;
+               while ((i = IntGroupIteratorNext(&iter)) >= 0) {
+                       ap = ATOM_AT_INDEX(mol->atoms, i);
+                       if (index < ap->nframes) {
+                               vp[j] = ap->frames[index];
+                               nn++;
+                       } else {
+                               vp[j] = ap->r;
+                       }
+                       j++;
+               }
+               if (nn > 0)
+                       MolActionCreateAndPerform(mol, gMolActionSetAtomPositions, ig, n, vp);
+               free(vp);
+               if (mol->cell != NULL && mol->frame_cells != NULL && index < mol->nframe_cells) {
+                       vp = mol->frame_cells + index * 4;
+                       MolActionCreateAndPerform(mol, gMolActionSetBox, vp, vp + 1, vp + 2, vp + 3, -1, 0);
+               }
+               IntGroupIteratorRelease(&iter);
+       }
+       /*  Copy the extra properties  */
+       IntGroupRelease(ig);
+       for (i = 0; i < mol->nmolprops; i++) {
+               Double *dp = (Double *)malloc(sizeof(Double));
+               ig = IntGroupNew();
+               IntGroupAdd(ig, mol->cframe, 1);
+               *dp = mol->molprops[i].propvals[index];
+               MolActionCreateAndPerform(mol, gMolActionSetProperty, i, ig, 1, dp);
+               free(dp);
+               IntGroupRelease(ig);
+       }
+       
+       return self;
 }
 
 /*
  *  call-seq:
- *     expand_by_symmetry(group, sym, dx=0, dy=0, dz=0) -> Array
+ *     reorder_frames(old_indices)
  *
- *  Expand the specified part of the molecule by the given symmetry operation.
- *  Returns the array of atom indices corresponding to the expanded atoms.
+ *  Reorder the frames. The argument is an array of integers that specify the 'old' 
+ *  frame numbers. Thus, if the argument is [2,0,1], then the new frames 0/1/2 are the
+ *  same as the old frames 2/0/1, respectively.
+ *  The argument must have the same number of integers as the number of frames.
  */
 static VALUE
-s_Molecule_ExpandBySymmetry(int argc, VALUE *argv, VALUE self)
+s_Molecule_ReorderFrames(VALUE self, VALUE aval)
 {
-    Molecule *mol;
-       VALUE gval, sval, xval, yval, zval, rval;
-       IntGroup *ig;
-       Int n[4];
-       Int natoms;
-       Int nidx, *idx;
-
+       Molecule *mol;
+       Int *ip, *ip2, i, n, nframes;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "23", &gval, &sval, &xval, &yval, &zval);
-       n[0] = NUM2INT(rb_Integer(sval));
-       n[1] = (xval == Qnil ? 0 : NUM2INT(rb_Integer(xval)));
-       n[2] = (yval == Qnil ? 0 : NUM2INT(rb_Integer(yval)));
-       n[3] = (zval == Qnil ? 0 : NUM2INT(rb_Integer(zval)));
-       ig = s_Molecule_AtomGroupFromValue(self, gval);
-       if (n[0] < 0 || (n[0] > 0 && n[0] >= mol->nsyms))
-               rb_raise(rb_eMolbyError, "symmetry index is out of bounds");
-       natoms = mol->natoms;
-       
-       MolActionCreateAndPerform(mol, gMolActionExpandBySymmetry, ig, n[1], n[2], n[3], n[0], &nidx, &idx);
-
-       rval = rb_ary_new2(nidx);
-       while (--nidx >= 0) {
-               rb_ary_store(rval, nidx, INT2NUM(idx[nidx]));
+       aval = rb_ary_to_ary(aval);
+       nframes = MoleculeGetNumberOfFrames(mol);
+       if (RARRAY_LEN(aval) != nframes)
+               rb_raise(rb_eMolbyError, "The argument must have the same number of integers as the number of frames");
+       ip2 = (Int *)calloc(sizeof(Int), nframes);
+       ip = (Int *)calloc(sizeof(Int), nframes);
+       for (i = 0; i < nframes; i++) {
+               n = NUM2INT(rb_Integer(RARRAY_PTR(aval)[i]));
+               if (n < 0 || n >= nframes || ip2[n] != 0) {
+                       free(ip2);
+                       free(ip);
+                       if (n < 0 || n >= nframes)
+                               rb_raise(rb_eMolbyError, "The argument (%d) is out of range", n);
+                       else
+                               rb_raise(rb_eMolbyError, "The argument has duplicated entry (%d)", n);
+               }
+               ip2[n] = 1;
+               ip[i] = n;
        }
-/*     if (natoms == mol->natoms)
-               rval = Qnil;
-       else {
-               rval = IntGroup_Alloc(rb_cIntGroup);
-               Data_Get_Struct(rval, IntGroup, ig);
-               IntGroup_RaiseIfError(IntGroupAdd(ig, natoms, mol->natoms - natoms));
-       } */
-       return rval;
+       free(ip2);
+       MolActionCreateAndPerform(mol, gMolActionReorderFrames, nframes, ip);
+       free(ip);
+       return self;
 }
 
+#pragma mark ------ Fragments ------
+
 /*
  *  call-seq:
- *     amend_by_symmetry(group = nil) -> IntGroup
+ *     fragment(n1, *exatoms)  -> IntGroup
+ *     fragment(group, *exatoms)  -> IntGroup
  *
- *  Expand the specified part of the molecule by the given symmetry operation.
- *  Returns an IntGroup containing the added atoms.
+ *  Get the fragment including the atom n1 or the atom group. If additional arguments are given,
+ *  those atoms will not be counted during the search.
  */
 static VALUE
-s_Molecule_AmendBySymmetry(int argc, VALUE *argv, VALUE self)
+s_Molecule_Fragment(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       IntGroup *ig, *ig2;
-       VALUE rval, gval;
+       IntGroup *baseg, *ig, *exatoms;
+       int n;
+       volatile VALUE nval, exval;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "01", &gval);
-       if (gval != Qnil)
-               ig = s_Molecule_AtomGroupFromValue(self, gval);
-       else ig = NULL;
-       MolActionCreateAndPerform(mol, gMolActionAmendBySymmetry, ig, &ig2);
-       rval = ValueFromIntGroup(ig2);
-       IntGroupRelease(ig2);
-       return rval;
+       rb_scan_args(argc, argv, "1*", &nval, &exval);
+       if (rb_obj_is_kind_of(nval, rb_cNumeric) || rb_obj_is_kind_of(nval, rb_cString)) {
+               baseg = NULL;
+               n = NUM2INT(s_Molecule_AtomIndex(self, nval));
+       } else {
+               baseg = s_Molecule_AtomGroupFromValue(self, nval);
+       }
+       if (RARRAY_LEN(exval) == 0) {
+               exatoms = NULL;
+       } else {
+               exval = s_Molecule_AtomGroup(RARRAY_LEN(exval), RARRAY_PTR(exval), self);
+               Data_Get_Struct(exval, IntGroup, exatoms);
+       }
+       if (baseg == NULL) {
+               ig = MoleculeFragmentExcludingAtomGroup(mol, n, exatoms);
+       } else {
+               IntGroupIterator iter;
+               IntGroupIteratorInit(baseg, &iter);
+               if ((n = IntGroupIteratorNext(&iter)) < 0) {
+                       ig = IntGroupNew();
+               } else {
+                       ig = MoleculeFragmentExcludingAtomGroup(mol, n, exatoms);
+                       if (ig != NULL) {
+                               while ((n = IntGroupIteratorNext(&iter)) >= 0) {
+                                       IntGroup *subg;
+                                       subg = MoleculeFragmentExcludingAtomGroup(mol, n, exatoms);
+                                       if (subg != NULL) {
+                                               IntGroupAddIntGroup(ig, subg);
+                                               IntGroupRelease(subg);
+                                       }
+                               }
+                       }
+               }
+               IntGroupIteratorRelease(&iter);
+               IntGroupRelease(baseg);
+       }
+       if (ig == NULL)
+               rb_raise(rb_eMolbyError, "invalid specification of molecular fragment");
+       nval = ValueFromIntGroup(ig);
+       IntGroupRelease(ig);
+       return nval;
 }
 
 /*
  *  call-seq:
- *     transform_for_symop(symop, is_cartesian = nil) -> Transform
+ *     fragments(exclude = nil)
  *
- *  Get the transform corresponding to the symmetry operation. The symop can either be
- *  an integer (index of symmetry operation) or [sym, dx, dy, dz].
- *  If is_cartesian is true, the returned transform is for cartesian coordinates.
- *  Otherwise, the returned transform is for fractional coordinates.
- *  Raises exception when no cell or no transform are defined.
+ *  Returns the fragments as an array of IntGroups. 
+ *  If exclude is given (as an array or an IntGroup), then those atoms are excluded
+ *  in defining the fragment.
  */
 static VALUE
-s_Molecule_TransformForSymop(int argc, VALUE *argv, VALUE self)
+s_Molecule_Fragments(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       VALUE sval, fval;
-       Symop symop;
-       Transform tr;
+       IntGroup *ag, *fg, *eg;
+       VALUE gval, exval, retval;
     Data_Get_Struct(self, Molecule, mol);
-       if (mol->cell == NULL)
-               rb_raise(rb_eMolbyError, "no unit cell is defined");
-       if (mol->nsyms == 0)
-               rb_raise(rb_eMolbyError, "no symmetry operation is defined");
-       rb_scan_args(argc, argv, "11", &sval, &fval);
-       if (rb_obj_is_kind_of(sval, rb_cNumeric)) {
-               symop.sym = NUM2INT(rb_Integer(sval));
-               symop.dx = symop.dy = symop.dz = 0;
-       } else {
-               sval = rb_ary_to_ary(sval);
-               if (RARRAY_LEN(sval) < 4)
-                       rb_raise(rb_eMolbyError, "missing arguments as symop; at least four integers should be given");
-               symop.sym = NUM2INT(rb_Integer(RARRAY_PTR(sval)[0]));
-               symop.dx = NUM2INT(rb_Integer(RARRAY_PTR(sval)[1]));
-               symop.dy = NUM2INT(rb_Integer(RARRAY_PTR(sval)[2]));
-               symop.dz = NUM2INT(rb_Integer(RARRAY_PTR(sval)[3]));
+       if (mol == NULL)
+               return Qnil;
+       if (mol->natoms == 0)
+               return rb_ary_new();
+       rb_scan_args(argc, argv, "01", &exval);
+       if (exval == Qnil)
+               eg = NULL;
+       else
+               eg = IntGroupFromValue(exval);
+       ag = IntGroupNewWithPoints(0, mol->natoms, -1);
+       if (eg != NULL)
+               IntGroupRemoveIntGroup(ag, eg);
+       retval = rb_ary_new();
+       while (IntGroupGetCount(ag) > 0) {
+               int n = IntGroupGetNthPoint(ag, 0);
+               fg = MoleculeFragmentExcludingAtomGroup(mol, n, eg);
+               if (fg == NULL)
+                       rb_raise(rb_eMolbyError, "internal error during each_fragment");
+               gval = ValueFromIntGroup(fg);
+               rb_ary_push(retval, gval);
+               IntGroupRemoveIntGroup(ag, fg);
+               IntGroupRelease(fg);
        }
-       if (symop.sym >= mol->nsyms)
-               rb_raise(rb_eMolbyError, "index of symmetry operation (%d) is out of range", symop.sym);
-       MoleculeGetTransformForSymop(mol, symop, &tr, (RTEST(fval) != 0));
-       return ValueFromTransform(&tr);
+       IntGroupRelease(ag);
+       if (eg != NULL)
+               IntGroupRelease(eg);
+       return retval;
 }
-       
+
 /*
  *  call-seq:
- *     symop_for_transform(transform, is_cartesian = nil) -> [sym, dx, dy, dz]
+ *     each_fragment(exclude = nil) {|group| ...}
  *
- *  Get the symmetry operation corresponding to the given transform.
- *  If is_cartesian is true, the given transform is for cartesian coordinates.
- *  Otherwise, the given transform is for fractional coordinates.
- *  Raises exception when no cell or no transform are defined.
+ *  Execute the block, with the IntGroup object for each fragment as the argument.
+ *  Atoms or bonds should not be added or removed during the execution of the block.
+ *  If exclude is given (as an array or an IntGroup), then those atoms are excluded
+ *  in defining the fragment.
  */
 static VALUE
-s_Molecule_SymopForTransform(int argc, VALUE *argv, VALUE self)
+s_Molecule_EachFragment(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
-       VALUE tval, fval;
-       Symop symop;
-       Transform tr;
-       int n;
+       IntGroup *ag, *fg, *eg;
+       VALUE gval, exval;
     Data_Get_Struct(self, Molecule, mol);
-       if (mol->cell == NULL)
-               rb_raise(rb_eMolbyError, "no unit cell is defined");
-       if (mol->nsyms == 0)
-               rb_raise(rb_eMolbyError, "no symmetry operation is defined");
-       rb_scan_args(argc, argv, "11", &tval, &fval);
-       TransformFromValue(tval, &tr);
-       n = MoleculeGetSymopForTransform(mol, tr, &symop, (RTEST(fval) != 0));
-       if (n == 0) {
-               return rb_ary_new3(4, INT2NUM(symop.sym), INT2NUM(symop.dx), INT2NUM(symop.dy), INT2NUM(symop.dz));
-       } else {
-               return Qnil;  /*  Not found  */
+       if (mol == NULL || mol->natoms == 0)
+               return self;
+       rb_scan_args(argc, argv, "01", &exval);
+       if (exval == Qnil)
+               eg = NULL;
+       else
+               eg = IntGroupFromValue(exval);
+       ag = IntGroupNewWithPoints(0, mol->natoms, -1);
+       if (eg != NULL)
+               IntGroupRemoveIntGroup(ag, eg);
+       while (IntGroupGetCount(ag) > 0) {
+               int n = IntGroupGetNthPoint(ag, 0);
+               fg = MoleculeFragmentExcludingAtomGroup(mol, n, eg);
+               if (fg == NULL)
+                       rb_raise(rb_eMolbyError, "internal error during each_fragment");
+               gval = ValueFromIntGroup(fg);
+               rb_yield(gval);
+               IntGroupRemoveIntGroup(ag, fg);
+               IntGroupRelease(fg);
        }
+       IntGroupRelease(ag);
+       if (eg != NULL)
+               IntGroupRelease(eg);
+       return self;
 }
 
 /*
  *  call-seq:
- *     wrap_unit_cell(group) -> Vector3D
+ *     detachable?(group)  -> [n1, n2]
  *
- *  Move the specified group so that the center of mass of the group is within the
- *  unit cell. The offset vector is returned. If no periodic box is defined, 
- *  exception is raised.
+ *  Check whether the group is 'detachable', i.e. the group is bound to the rest 
+ *  of the molecule via only one bond. If it is, then the indices of the atoms
+ *  belonging to the bond is returned, the first element being the atom included
+ *  in the fragment. Otherwise, Qnil is returned.
  */
 static VALUE
-s_Molecule_WrapUnitCell(VALUE self, VALUE gval)
+s_Molecule_Detachable_P(VALUE self, VALUE gval)
 {
-    Molecule *mol;
+       Molecule *mol;
        IntGroup *ig;
-       Vector v, cv, dv;
+       int n1, n2;
+       VALUE retval;
     Data_Get_Struct(self, Molecule, mol);
-       if (mol->cell == NULL)
-               rb_raise(rb_eMolbyError, "no unit cell is defined");
        ig = s_Molecule_AtomGroupFromValue(self, gval);
-       s_Molecule_DoCenterOfMass(mol, &cv, ig);
-       TransformVec(&v, mol->cell->rtr, &cv);
-       if (mol->cell->flags[0])
-               v.x -= floor(v.x);
-       if (mol->cell->flags[1])
-               v.y -= floor(v.y);
-       if (mol->cell->flags[2])
-               v.z -= floor(v.z);
-       TransformVec(&dv, mol->cell->tr, &v);
-       VecDec(dv, cv);
-       MolActionCreateAndPerform(mol, gMolActionTranslateAtoms, &dv, ig);
+       if (MoleculeIsFragmentDetachable(mol, ig, &n1, &n2)) {
+               retval = rb_ary_new3(2, INT2NUM(n1), INT2NUM(n2));
+       } else retval = Qnil;
        IntGroupRelease(ig);
-       return ValueFromVector(&dv);
+       return retval;
 }
 
 /*
  *  call-seq:
- *     find_conflicts(limit[, group1[, group2 [, ignore_exclusion]]]) -> [[n1, n2], [n3, n4], ...]
- *
- *  Find pairs of atoms that are within the limit distance. If group1 and group2 are given, the
- *  first and second atom in the pair should belong to group1 and group2, respectively.
- *  If ignore_exclusion is true, then 1-2 (bonded), 1-3, 1-4 pairs are also considered.
- */
-static VALUE
-s_Molecule_FindConflicts(int argc, VALUE *argv, VALUE self)
-{
-    Molecule *mol;
-       VALUE limval, gval1, gval2, rval, igval;
-       IntGroup *ig1, *ig2;
-       IntGroupIterator iter1, iter2;
-       Int npairs, *pairs;
-       Int n[2], i;
-       Double lim;
-       Vector r1;
-       Atom *ap1, *ap2;
-       MDExclusion *exinfo;
-       Int *exlist;
-
+ *     bonds_on_border(group = selection)  -> Array of Array of two Integers
+ *
+ *  Returns an array of bonds that connect an atom in the group and an atom out
+ *  of the group. The first atom in the bond always belongs to the group. If no
+ *  such bonds are present, an empty array is returned.
+ */
+static VALUE
+s_Molecule_BondsOnBorder(int argc, VALUE *argv, VALUE self)
+{
+       Molecule *mol;
+       IntGroup *ig, *bg;
+       VALUE gval, retval;
     Data_Get_Struct(self, Molecule, mol);
-       rb_scan_args(argc, argv, "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;    
+       rb_scan_args(argc, argv, "01", &gval);
+       if (gval == Qnil) {
+               ig = MoleculeGetSelection(mol);
+               if (ig != NULL)
+                       IntGroupRetain(ig);
        } else {
-               exinfo = NULL;
-               exlist = NULL;
+               ig = s_Molecule_AtomGroupFromValue(self, gval);
        }
-       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);
-                               }
+       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);
        }
-       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;
+       IntGroupRelease(bg);
+       IntGroupRelease(ig);
+       return retval;
 }
 
 /*  Calculate the transform that moves the current coordinates to the reference
@@ -8218,7 +8708,7 @@ s_Molecule_FitCoordinates(int argc, VALUE *argv, VALUE self)
        VALUE gval, rval, wval;
        IntGroup *ig;
        IntGroupIterator iter;
-       int nn, errno, i, j, in, status;
+       int nn, errnum, i, j, in, status;
        Vector *ref;
        Double *weights, dval[3];
        Transform tr;
@@ -8228,7 +8718,7 @@ s_Molecule_FitCoordinates(int argc, VALUE *argv, VALUE self)
        if (gval == Qnil)
                ig = IntGroupNewWithPoints(0, mol->natoms, -1);
        else
-               ig = IntGroupFromValue(gval);
+               ig = s_Molecule_AtomGroupFromValue(self, gval);
        if (ig == NULL || (nn = IntGroupGetCount(ig)) == 0) {
                IntGroupRelease(ig);
                rb_raise(rb_eMolbyError, "atom group is not given correctly");
@@ -8239,7 +8729,7 @@ s_Molecule_FitCoordinates(int argc, VALUE *argv, VALUE self)
        if (rb_obj_is_kind_of(rval, rb_cNumeric)) {
                int fn = NUM2INT(rb_Integer(rval));
                if (fn < 0 || fn >= MoleculeGetNumberOfFrames(mol)) {
-                       errno = 1;
+                       errnum = 1;
                        status = fn;
                        goto err;
                }
@@ -8252,7 +8742,7 @@ s_Molecule_FitCoordinates(int argc, VALUE *argv, VALUE self)
        } else if (rb_obj_is_kind_of(rval, rb_cLAMatrix)) {
                LAMatrix *m = LAMatrixFromValue(rval, NULL, 0, 0);
                if (m->row * m->column < nn * 3) {
-                       errno = 2;
+                       errnum = 2;
                        goto err;
                }
                for (i = 0; i < nn; i++) {
@@ -8264,24 +8754,24 @@ s_Molecule_FitCoordinates(int argc, VALUE *argv, VALUE self)
                VALUE aval;
                rval = rb_protect(rb_ary_to_ary, rval, &status);
                if (status != 0) {
-                       errno = 3;
+                       errnum = 3;
                        goto err;
                }
                if (RARRAY_LEN(rval) < nn) {
-                       errno = 2;
+                       errnum = 2;
                        goto err;
                }
                if (rb_obj_is_kind_of((RARRAY_PTR(rval))[0], rb_cNumeric)) {
                        /*  Array of 3*nn numbers  */
                        if (RARRAY_LEN(rval) < nn * 3) {
-                               errno = 2;
+                               errnum = 2;
                                goto err;
                        }
                        for (i = 0; i < nn; i++) {
                                for (j = 0; j < 3; j++) {
                                        aval = rb_protect(rb_Float, (RARRAY_PTR(rval))[i * 3 + j], &status);
                                        if (status != 0) {
-                                               errno = 3;
+                                               errnum = 3;
                                                goto err;
                                        }
                                        dval[j] = NUM2DBL(aval);
@@ -8299,18 +8789,18 @@ s_Molecule_FitCoordinates(int argc, VALUE *argv, VALUE self)
                                } else {
                                        aval = rb_protect(rb_ary_to_ary, aval, &status);
                                        if (status != 0) {
-                                               errno = 3;
+                                               errnum = 3;
                                                goto err;
                                        }
                                        if (RARRAY_LEN(aval) < 3) {
-                                               errno = 4;
+                                               errnum = 4;
                                                status = i;
                                                goto err;
                                        }
                                        for (j = 0; j < 3; j++) {
                                                VALUE aaval = rb_protect(rb_Float, (RARRAY_PTR(aval))[j], &status);
                                                if (status != 0) {
-                                                       errno = 3;
+                                                       errnum = 3;
                                                        goto err;
                                                }
                                                dval[j] = NUM2DBL(aaval);
@@ -8332,17 +8822,17 @@ s_Molecule_FitCoordinates(int argc, VALUE *argv, VALUE self)
        } else {
                wval = rb_protect(rb_ary_to_ary, wval, &status);
                if (status != 0) {
-                       errno = 3;
+                       errnum = 3;
                        goto err;
                }
                if (RARRAY_LEN(wval) < nn) {
-                       errno = 5;
+                       errnum = 5;
                        goto err;
                }
                for (i = 0; i < nn; i++) {
                        VALUE wwval = rb_protect(rb_Float, (RARRAY_PTR(wval))[i], &status);
                        if (status != 0) {
-                               errno = 3;
+                               errnum = 3;
                                goto err;
                        }
                        weights[i] = NUM2DBL(wwval);
@@ -8350,32 +8840,34 @@ s_Molecule_FitCoordinates(int argc, VALUE *argv, VALUE self)
        }
        dval[0] = s_Molecule_FitCoordinates_Sub(mol, ig, ref, weights, tr);
        if (dval[0] < 0) {
-               errno = 6;
+               errnum = 6;
                goto err;
        }
-       errno = 0;
+       errnum = 0;
 err:
        IntGroupIteratorRelease(&iter);
        free(ref);
        free(weights);
-       if (errno == 0) {
+       if (errnum == 0) {
                return rb_ary_new3(2, ValueFromTransform(&tr), rb_float_new(dval[0]));
-       } else if (errno == 1) {
+       } else if (errnum == 1) {
                rb_raise(rb_eMolbyError, "frame index (%d) is out of range", status);
-       } else if (errno == 2) {
+       } else if (errnum == 2) {
                rb_raise(rb_eMolbyError, "insufficient number of reference coordinates");
-       } else if (errno == 3) {
+       } else if (errnum == 3) {
                rb_jump_tag(status);
-       } else if (errno == 4) {
+       } else if (errnum == 4) {
                rb_raise(rb_eMolbyError, "less than 3 elements for index %d of reference coordinates", status);
-       } else if (errno == 5) {
+       } else if (errnum == 5) {
                rb_raise(rb_eMolbyError, "insufficient number of weight values");
-       } else if (errno == 6) {
+       } else if (errnum == 6) {
                rb_raise(rb_eMolbyError, "matrix calculation failed during coordinate fitting");
        }
        return Qnil;  /*  Not reached  */
 }
 
+#pragma mark ------ Screen Display ------
+
 /*
  *  call-seq:
  *     display
@@ -8589,6 +9081,33 @@ s_Molecule_IsAtomVisible(VALUE self, VALUE ival)
 
 /*
  *  call-seq:
+ *     hidden_atoms       -> IntGroup
+ *
+ *  Returns the currently hidden atoms.
+ */
+static VALUE
+s_Molecule_HiddenAtoms(VALUE self)
+{
+       rb_raise(rb_eMolbyError, "set_hidden_atoms is now obsolete. Try using Molecule#is_atom_visible or AtomRef#hidden.");
+       return Qnil;  /*  Not reached  */
+}
+
+/*
+ *  call-seq:
+ *     set_hidden_atoms(IntGroup)
+ *     self.hidden_atoms = IntGroup
+ *
+ *  Hide the specified atoms. This operation is _not_ undoable.
+ */
+static VALUE
+s_Molecule_SetHiddenAtoms(VALUE self, VALUE val)
+{
+       rb_raise(rb_eMolbyError, "set_hidden_atoms is now obsolete. Try using Molecule#is_atom_visible or AtomRef#hidden.");
+       return Qnil;  /*  Not reached  */
+}
+
+/*
+ *  call-seq:
  *     show_graphite -> Integer
  *     show_graphite = Integer
  *     show_graphite = boolean
@@ -8701,6 +9220,29 @@ s_Molecule_ShowPeriodicImageFlag(VALUE self)
 
 /*
  *  call-seq:
+ *     show_rotation_center -> [amin, amax, bmin, bmax, cmin, cmax]
+ *     show_rotation_center = [amin, amax, bmin, bmax, cmin, cmax]
+ *     show_rotation_center = boolean
+ *
+ *  Set to show the rotation center of the screen.
+ *  If the argument is boolean, only the show/hide flag is modified.
+ */
+static VALUE
+s_Molecule_ShowRotationCenter(int argc, VALUE *argv, VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mview == NULL)
+               return Qnil;
+       if (argc > 0) {
+               mol->mview->showRotationCenter = (RTEST(argv[0]) != 0);
+               MainViewCallback_setNeedsDisplay(mol->mview, 1);
+       }
+       return (mol->mview->showRotationCenter ? Qtrue : Qfalse);
+}
+
+/*
+ *  call-seq:
  *     line_mode
  *     line_mode(bool)
  *     line_mode = bool
@@ -8724,6 +9266,113 @@ s_Molecule_LineMode(int argc, VALUE *argv, VALUE self)
 
 /*
  *  call-seq:
+ *     atom_radius = float
+ *     atom_radius
+ *
+ *  Set the atom radius (multiple of the vdw radius) used in drawing the model in normal (non-line) mode.
+ *  (Default = 0.4)
+ *  If no argument is given, the current value is returned.
+ */
+static VALUE
+s_Molecule_AtomRadius(int argc, VALUE *argv, VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mview == NULL)
+               return Qnil;
+       if (argc > 0) {
+               double rad = NUM2DBL(rb_Float(argv[0]));
+               if (rad > 0.0) {
+                       mol->mview->atomRadius = rad;
+                       MainViewCallback_setNeedsDisplay(mol->mview, 1);
+               }
+               return argv[0];
+       }
+       return rb_float_new(mol->mview->atomRadius);
+}
+
+/*
+ *  call-seq:
+ *     bond_radius = float
+ *     bond_radius
+ *
+ *  Set the bond radius (in angstrom) used in drawing the model in normal (non-line) mode. (Default = 0.1)
+ *  If no argument is given, the current value is returned.
+ */
+static VALUE
+s_Molecule_BondRadius(int argc, VALUE *argv, VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mview == NULL)
+               return Qnil;
+       if (argc > 0) {
+               double rad = NUM2DBL(rb_Float(argv[0]));
+               if (rad > 0.0) {
+                       mol->mview->bondRadius = rad;
+                       MainViewCallback_setNeedsDisplay(mol->mview, 1);
+               }
+               return argv[0];
+       }
+       return rb_float_new(mol->mview->bondRadius);
+}
+
+/*
+ *  call-seq:
+ *     atom_resolution = integer
+ *     atom_resolution
+ *
+ *  Set the atom resolution used in drawing the model in normal (non-line) mode.
+ *  (Default = 12; minimum = 6)
+ *  If no argument is given, the current value is returned.
+ */
+static VALUE
+s_Molecule_AtomResolution(int argc, VALUE *argv, VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mview == NULL)
+               return Qnil;
+       if (argc > 0) {
+               int res = NUM2INT(rb_Integer(argv[0]));
+               if (res < 6)
+                       rb_raise(rb_eRangeError, "The atom resolution (%d) less than 6 is not acceptable", res);
+               mol->mview->atomResolution = res;
+               MainViewCallback_setNeedsDisplay(mol->mview, 1);
+               return INT2NUM(res);
+       }
+       return INT2NUM(mol->mview->atomResolution);
+}
+
+/*
+ *  call-seq:
+ *     bond_resolution = integer
+ *     bond_resolution
+ *
+ *  Set the bond resolution used in drawing the model in normal (non-line) mode.
+ *  (Default = 8; minimum = 4)
+ *  If no argument is given, the current value is returned.
+ */
+static VALUE
+s_Molecule_BondResolution(int argc, VALUE *argv, VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mview == NULL)
+               return Qnil;
+       if (argc > 0) {
+               int res = NUM2INT(rb_Integer(argv[0]));
+               if (res < 4)
+                       rb_raise(rb_eRangeError, "The bond resolution (%d) less than 4 is not acceptable", res);
+               mol->mview->bondResolution = res;
+               MainViewCallback_setNeedsDisplay(mol->mview, 1);
+               return INT2NUM(res);
+       }
+       return INT2NUM(mol->mview->bondResolution);
+}
+
+/*
+ *  call-seq:
  *     resize_to_fit
  *
  *  Resize the model drawing to fit in the window.
@@ -8748,7 +9397,7 @@ static VALUE
 s_Molecule_GetViewRotation(VALUE self)
 {
     Molecule *mol;
-       float f[4];
+       double f[4];
        Vector v;
     Data_Get_Struct(self, Molecule, mol);
        if (mol->mview == NULL)
@@ -8787,7 +9436,7 @@ static VALUE
 s_Molecule_GetViewCenter(VALUE self)
 {
     Molecule *mol;
-       float f[4];
+       double f[4];
        Vector v;
     Data_Get_Struct(self, Molecule, mol);
        if (mol->mview == NULL)
@@ -8809,7 +9458,7 @@ static VALUE
 s_Molecule_SetViewRotation(VALUE self, VALUE aval, VALUE angval)
 {
     Molecule *mol;
-       float f[4];
+       double f[4];
        Vector v;
     Data_Get_Struct(self, Molecule, mol);
        if (mol->mview == NULL)
@@ -8855,7 +9504,7 @@ s_Molecule_SetViewCenter(VALUE self, VALUE aval)
 {
     Molecule *mol;
        Vector v;
-       float f[4];
+       double f[4];
     Data_Get_Struct(self, Molecule, mol);
        if (mol->mview == NULL)
                return Qnil;
@@ -8889,41 +9538,150 @@ s_Molecule_SetBackgroundColor(int argc, VALUE *argv, VALUE self)
 
 /*
  *  call-seq:
- *     create_graphic(kind, color, points, fill = nil) -> integer
+ *     export_graphic(fname, scale = 1.0, bg_color = -1, width = 0, height = 0)
+ *
+ *  Export the current graphic to a PNG or TIF file (determined by the extension).
+ *  bg_color: -1, same as screen; 0, transparent; 1, black; 2, white.
+ *  If either width or height is not specified, then the screen width/height is used instead.
+ */
+static VALUE
+s_Molecule_ExportGraphic(int argc, VALUE *argv, VALUE self)
+{
+       Molecule *mol;
+       VALUE fval, sval, bval, wval, hval;
+       char *fname;
+       float scale;
+       int bg_color, width, height;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mview == NULL)
+               rb_raise(rb_eMolbyError, "The molecule has no associated graphic view");
+       rb_scan_args(argc, argv, "14", &fval, &sval, &bval, &wval, &hval);
+       fname = FileStringValuePtr(fval);
+       if (sval == Qnil)
+               scale = 1.0;
+       else scale = NUM2DBL(rb_Float(sval));
+       if (bval == Qnil)
+               bg_color = -1;
+       else bg_color = NUM2INT(rb_Integer(bval));
+       if (wval == Qnil)
+               width = 0;
+       else width = NUM2INT(rb_Integer(wval));
+       if (hval == Qnil)
+               height = 0;
+       else height = NUM2INT(rb_Integer(hval));
+       if (MainViewCallback_exportGraphic(mol->mview, fname, scale, bg_color, width, height) == 0)
+               return fval;
+       else return Qnil;
+}
+
+#pragma mark ------ Graphics ------
+
+static void
+s_CalculateGraphicNormals(MainViewGraphic *gp)
+{
+       int i;
+       Vector v1, v2, v3;
+       if (gp == NULL || gp->npoints < 3)
+               return;
+       AssignArray(&gp->normals, &gp->nnormals, sizeof(GLfloat) * 3, gp->npoints - 1, NULL);
+       v1.x = gp->points[3] - gp->points[0];
+       v1.y = gp->points[4] - gp->points[1];
+       v1.z = gp->points[5] - gp->points[2];
+       /*  nv[i] = (v[i-1]-v[0]).cross(v[i]-v[0]) (i=2..n-1)  */
+       for (i = 2; i < gp->npoints; i++) {
+               v2.x = gp->points[i * 3] - gp->points[0];
+               v2.y = gp->points[i * 3 + 1] - gp->points[1];
+               v2.z = gp->points[i * 3 + 2] - gp->points[2];
+               VecCross(v3, v1, v2);
+               NormalizeVec(&v3, &v3);
+               gp->normals[i * 3] = v3.x;
+               gp->normals[i * 3 + 1] = v3.y;
+               gp->normals[i * 3 + 2] = v3.z;
+               v1 = v2;
+       }
+       /*  normals[0] = average of all nv[i] (i=2..n-1)  */
+       VecZero(v1);
+       for (i = 2; i < gp->npoints; i++) {
+               v1.x += gp->normals[i * 3];
+               v1.y += gp->normals[i * 3 + 1];
+               v1.z += gp->normals[i * 3 + 2];
+       }
+       NormalizeVec(&v1, &v1);
+       gp->normals[0] = v1.x;
+       gp->normals[1] = v1.y;
+       gp->normals[2] = v1.z;
+       /*  normals[1] = nv[2].normalize  */
+       v2.x = gp->normals[6];
+       v2.y = gp->normals[7];
+       v2.z = gp->normals[8];
+       NormalizeVec(&v1, &v2);
+       gp->normals[3] = v1.x;
+       gp->normals[4] = v1.y;
+       gp->normals[5] = v1.z;
+       /*  normals[i] = (nv[i] + nv[i+1]).normalize (i=2..n-2)  */
+       for (i = 2; i < gp->npoints; i++) {
+               if (i == gp->npoints - 1)
+                       VecZero(v3);
+               else {
+                       v3.x = gp->normals[i * 3 + 3];
+                       v3.y = gp->normals[i * 3 + 4];
+                       v3.z = gp->normals[i * 3 + 5];
+               }
+               VecInc(v2, v3);
+               NormalizeVec(&v1, &v2);
+               gp->normals[i * 3] = v1.x;
+               gp->normals[i * 3 + 1] = v1.y;
+               gp->normals[i * 3 + 2] = v1.z;
+               v2 = v3;
+       }
+}
+
+/*
+ *  call-seq:
+ *     insert_graphic(index, kind, color, points, fill = nil) -> integer
  *
- *  Create a new graphic object.
+ *  Create a new graphic object and insert at the given graphic index (if -1, then append at the last).
  *   kind: a symbol representing the kind of the graphic. :line, :poly, :cylinder, :cone, :ellipsoid
  *   color: an array of 3 (rgb) or 4 (rgba) floating numbers
  *   points: an array of Vectors
  *   
  */
 static VALUE
-s_Molecule_CreateGraphic(int argc, VALUE *argv, VALUE self)
+s_Molecule_InsertGraphic(int argc, VALUE *argv, VALUE self)
 {
     Molecule *mol;
        MainViewGraphic g;
-       int i, n, ni;
+       int i, n, ni, idx;
        const char *p;
-       VALUE kval, cval, pval, fval;
+       VALUE kval, cval, pval, fval, ival;
     Data_Get_Struct(self, Molecule, mol);
        if (mol->mview == NULL)
                rb_raise(rb_eMolbyError, "this molecule has no associated graphic view");
-       rb_scan_args(argc, argv, "31", &kval, &cval, &pval, &fval);
-       kval = rb_obj_as_string(kval);
+       rb_scan_args(argc, argv, "41", &ival, &kval, &cval, &pval, &fval);
+       idx = NUM2INT(rb_Integer(ival));
+       if (idx == -1)
+               idx = mol->mview->ngraphics;
+       else if (idx < 0 || idx > mol->mview->ngraphics)
+               rb_raise(rb_eMolbyError, "the graphic index (%d) out of range", idx);
        memset(&g, 0, sizeof(g));
        g.visible = 1;
-       p = RSTRING_PTR(kval);
-       if (strcmp(p, "line") == 0)
-               g.kind = kMainViewGraphicLine;
-       else if (strcmp(p, "poly") == 0)
-               g.kind = kMainViewGraphicPoly;
-       else if (strcmp(p, "cylinder") == 0)
-               g.kind = kMainViewGraphicCylinder;
-       else if (strcmp(p, "cone") == 0)
-               g.kind = kMainViewGraphicCone;
-       else if (strcmp(p, "ellipsoid") == 0)
-               g.kind = kMainViewGraphicEllipsoid;
-       else rb_raise(rb_eMolbyError, "unknown graphic object type: %s", p);
+       if (rb_obj_is_kind_of(kval, rb_cInteger)) {
+               g.kind = NUM2INT(kval);  /*  Direct assign (for undo registration)  */
+       } else {
+               kval = rb_obj_as_string(kval);
+               p = StringValuePtr(kval);
+               if (strcmp(p, "line") == 0)
+                       g.kind = kMainViewGraphicLine;
+               else if (strcmp(p, "poly") == 0)
+                       g.kind = kMainViewGraphicPoly;
+               else if (strcmp(p, "cylinder") == 0)
+                       g.kind = kMainViewGraphicCylinder;
+               else if (strcmp(p, "cone") == 0)
+                       g.kind = kMainViewGraphicCone;
+               else if (strcmp(p, "ellipsoid") == 0)
+                       g.kind = kMainViewGraphicEllipsoid;
+               else rb_raise(rb_eMolbyError, "unknown graphic object type: %s", p);
+       }
        g.closed = (RTEST(fval) ? 1 : 0);
        cval = rb_ary_to_ary(cval);
        n = RARRAY_LEN(cval);
@@ -8963,11 +9721,17 @@ s_Molecule_CreateGraphic(int argc, VALUE *argv, VALUE self)
        NewArray(&g.points, &g.npoints, sizeof(GLfloat) * 3, n);
        for (i = 0; i < n; i++) {
                Vector v;
+               VALUE rval = RARRAY_PTR(pval)[i];
                if (i == ni) {
-                       v.x = NUM2DBL(rb_Float(RARRAY_PTR(pval)[i]));
+                       if (rb_obj_is_kind_of(rval, rb_cVector3D)) {
+                               /*  The float argument can also be given as a vector (for simplify undo registration)  */
+                               VectorFromValue(rval, &v);
+                       } else {
+                               v.x = NUM2DBL(rb_Float(rval));
+                       }
                        v.y = v.z = 0;
                } else {
-                       VectorFromValue(RARRAY_PTR(pval)[i], &v);
+                       VectorFromValue(rval, &v);
                }
                g.points[i * 3] = v.x;
                g.points[i * 3 + 1] = v.y;
@@ -8979,8 +9743,36 @@ s_Molecule_CreateGraphic(int argc, VALUE *argv, VALUE self)
                g.points[6] = g.points[8] = g.points[9] = g.points[10] = 0;
                g.points[7] = g.points[11] = g.points[3];
        }
-       MainView_insertGraphic(mol->mview, -1, &g);
-       return INT2NUM(mol->mview->ngraphics - 1);      
+       if (g.kind == kMainViewGraphicPoly) {
+               /*  Calculate normals  */
+               s_CalculateGraphicNormals(&g);
+       }
+       MainView_insertGraphic(mol->mview, idx, &g);
+       
+       {
+               /*  Register undo  */
+               MolAction *act;
+               act = MolActionNew(SCRIPT_ACTION("i"), "remove_graphic", idx);
+               MolActionCallback_registerUndo(mol, act);
+               MolActionRelease(act);
+       }
+
+       return INT2NUM(idx);    
+}
+
+/*
+ *  call-seq:
+ *     create_graphic(kind, color, points, fill = nil) -> integer
+ *
+ *  Create a new graphic object. The arguments are similar as insert_graphic.
+ */
+static VALUE
+s_Molecule_CreateGraphic(int argc, VALUE *argv, VALUE self)
+{
+       VALUE args[5];
+       rb_scan_args(argc, argv, "31", args + 1, args + 2, args + 3, args + 4);
+       args[0] = INT2NUM(-1);
+       return s_Molecule_InsertGraphic(argc + 1, args, self);
 }
 
 /*
@@ -9000,6 +9792,34 @@ s_Molecule_RemoveGraphic(VALUE self, VALUE ival)
        i = NUM2INT(rb_Integer(ival));
        if (i < 0 || i >= mol->mview->ngraphics)
                rb_raise(rb_eArgError, "graphic index is out of range");
+       {
+               /*  Prepare data for undo  */
+               MainViewGraphic *gp;
+               Vector *vp;
+               MolAction *act;
+               double col[4];
+               int n;
+               gp = mol->mview->graphics + i;
+               vp = (Vector *)malloc(sizeof(Vector) * gp->npoints);
+               for (n = 0; n < gp->npoints; n++) {
+                       vp[n].x = gp->points[n * 3];
+                       vp[n].y = gp->points[n * 3 + 1];
+                       vp[n].z = gp->points[n * 3 + 2];
+               }
+               col[0] = gp->rgba[0];
+               col[1] = gp->rgba[1];
+               col[2] = gp->rgba[2];
+               col[3] = gp->rgba[3];
+               if (gp->visible == 0) {
+                       act = MolActionNew(SCRIPT_ACTION("i"), "hide_graphic", i);
+                       MolActionCallback_registerUndo(mol, act);
+                       MolActionRelease(act);
+               }
+               act = MolActionNew(SCRIPT_ACTION("iiDVb"), "insert_graphic", i, gp->kind, 4, col, gp->npoints, vp, gp->closed);
+               MolActionCallback_registerUndo(mol, act);
+               free(vp);
+               MolActionRelease(act);
+       }
        MainView_removeGraphic(mol->mview, i);
        return ival;
 }
@@ -9019,55 +9839,189 @@ s_Molecule_NGraphics(VALUE self)
                rb_raise(rb_eMolbyError, "this molecule has no associated graphic view");
        return INT2NUM(mol->mview->ngraphics);
 }
-       
+
 /*
  *  call-seq:
- *     set_graphic_point(graphic_index, point_index, new_value) -> new_value
+ *     get_graphic_point(graphic_index, point_index) -> value
+ *     get_graphic_points(graphic_index) -> values
  *
- *  Change the point_index-th control point of graphic_index-th graphic object
+ *  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_SetGraphicPoint(VALUE self, VALUE gval, VALUE pval, VALUE nval)
+s_Molecule_GetGraphicPoint(int argc, VALUE *argv, VALUE self)
 {
        MainViewGraphic *gp;
     Molecule *mol;
-       int index;
+       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;
-       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");
+       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 {
-               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;
+               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:
+ *     get_graphic_color(graphic_index) -> value
+ *
+ *  Get the color of graphic_index-th graphic object
+ */
+static VALUE
+s_Molecule_GetGraphicColor(VALUE self, VALUE gval)
+{
+       MainViewGraphic *gp;
+    Molecule *mol;
+       int index;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mview == NULL)
+               rb_raise(rb_eMolbyError, "this molecule has no associated graphic view");
+       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;
+       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]));
+}
+
+/*
+ *  call-seq:
  *     set_graphic_color(graphic_index, new_value) -> new_value
  *
  *  Change the color of graphic_index-th graphic object
@@ -9078,7 +10032,9 @@ s_Molecule_SetGraphicColor(VALUE self, VALUE gval, VALUE cval)
 {
        MainViewGraphic *gp;
     Molecule *mol;
-       int index, n;
+       MolAction *act;
+       double c[4];
+       int index, i, n;
     Data_Get_Struct(self, Molecule, mol);
        if (mol->mview == NULL)
                rb_raise(rb_eMolbyError, "this molecule has no associated graphic view");
@@ -9086,15 +10042,21 @@ s_Molecule_SetGraphicColor(VALUE self, VALUE gval, VALUE cval)
        if (index < 0 || index >= mol->mview->ngraphics)
                rb_raise(rb_eArgError, "the graphic index is out of range");
        gp = mol->mview->graphics + index;
+       for (i = 0; i < 4; i++)
+               c[i] = gp->rgba[i];
        cval = rb_ary_to_ary(cval);
        n = RARRAY_LEN(cval);
        if (n != 3 && n != 4)
                rb_raise(rb_eArgError, "the color argument must have 3 or 4 numbers");
-       for (index = 0; index < n; index++) {
-               gp->rgba[index] = NUM2DBL(rb_Float(RARRAY_PTR(cval)[index]));
+
+       for (i = 0; i < n; i++) {
+               gp->rgba[i] = NUM2DBL(rb_Float(RARRAY_PTR(cval)[i]));
        }
        if (n == 3)
                gp->rgba[3] = 1.0;
+       act = MolActionNew(SCRIPT_ACTION("iD"), "set_graphic_color", index, 4, c);
+       MolActionCallback_registerUndo(mol, act);
+       MolActionRelease(act);          
        MoleculeCallback_notifyModification(mol, 0);
        return cval;
 }
@@ -9165,6 +10127,8 @@ s_Molecule_ShowText(VALUE self, VALUE arg)
        return Qnil;
 }
 
+#pragma mark ------ MD Support ------
+
 /*
  *  call-seq:
  *     md_arena -> MDArena
@@ -9229,6 +10193,101 @@ s_Molecule_Parameter(VALUE self)
 
 /*
  *  call-seq:
+ *     start_step       -> Integer
+ *
+ *  Returns the start step (defined by dcd format).
+ */
+static VALUE
+s_Molecule_StartStep(VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       return INT2NUM(mol->startStep);
+}
+
+/*
+ *  call-seq:
+ *     start_step = Integer
+ *
+ *  Set the start step (defined by dcd format).
+ */
+static VALUE
+s_Molecule_SetStartStep(VALUE self, VALUE val)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       mol->startStep = NUM2INT(rb_Integer(val));
+       return val;
+}
+
+/*
+ *  call-seq:
+ *     steps_per_frame       -> Integer
+ *
+ *  Returns the number of steps between frames (defined by dcd format).
+ */
+static VALUE
+s_Molecule_StepsPerFrame(VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       return INT2NUM(mol->stepsPerFrame);
+}
+
+/*
+ *  call-seq:
+ *     steps_per_frame = Integer
+ *
+ *  Set the number of steps between frames (defined by dcd format).
+ */
+static VALUE
+s_Molecule_SetStepsPerFrame(VALUE self, VALUE val)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       mol->stepsPerFrame = NUM2INT(rb_Integer(val));
+       return val;
+}
+
+/*
+ *  call-seq:
+ *     ps_per_step       -> Float
+ *
+ *  Returns the time increment (in picoseconds) for one step (defined by dcd format).
+ */
+static VALUE
+s_Molecule_PsPerStep(VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       return rb_float_new(mol->psPerStep);
+}
+
+/*
+ *  call-seq:
+ *     ps_per_step = Float
+ *
+ *  Set the time increment (in picoseconds) for one step (defined by dcd format).
+ */
+static VALUE
+s_Molecule_SetPsPerStep(VALUE self, VALUE val)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       mol->psPerStep = NUM2DBL(rb_Float(val));
+       return val;
+}
+
+static VALUE
+s_Molecule_BondParIsObsolete(VALUE self, VALUE val)
+{
+       rb_raise(rb_eMolbyError, "Molecule#bond_par, angle_par, dihedral_par, improper_par, vdw_par are now obsolete. You can use MDArena#bond_par, angle_par, dihedral_par, improper_par, vdw_par instead, and probably these are what you really want.");
+}
+
+#pragma mark ------ MO Handling ------
+
+/*
+ *  call-seq:
  *     selectedMO -> IntGroup
  *
  *  Returns a group of selected mo in the "MO Info" table. If the MO info table
@@ -9317,8 +10376,8 @@ s_Molecule_Cubegen(int argc, VALUE *argv, VALUE self)
        
        /*  Set up parameters  */
        mono = NUM2INT(rb_Integer(mval));
-       if (mono <= 0 || mono > mol->bset->ncomps)
-               rb_raise(rb_eMolbyError, "The MO number (%d) is out of range (should be 1..%d)", mono, mol->bset->ncomps);
+       if (mono < 0 || mono > mol->bset->ncomps)
+               rb_raise(rb_eMolbyError, "The MO number (%d) is out of range (should be 1..%d, or 0 as 'arbitrary vector')", mono, mol->bset->ncomps);
        if (RTEST(bval)) {
                if (mol->bset->rflag != 0)
                        rb_raise(rb_eMolbyError, "Beta MO is requested but not present");
@@ -9387,6 +10446,169 @@ s_Molecule_Cubegen(int argc, VALUE *argv, VALUE self)
 
 /*
  *  call-seq:
+ *     clear_surface
+ *
+ *  Clear the MO surface if present.
+ */
+static VALUE
+s_Molecule_ClearSurface(VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mcube != NULL)
+               MoleculeClearMCube(mol, 0, 0, 0, NULL, 0.0, 0.0, 0.0);
+       return self;
+}
+
+/*
+ *  call-seq:
+ *     hide_surface
+ *
+ *  Hide the MO surface if present.
+ */
+static VALUE
+s_Molecule_HideSurface(VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mcube != NULL) {
+               mol->mcube->hidden = 1;
+               MoleculeCallback_notifyModification(mol, 0);
+       }
+       return self;
+}
+
+/*
+ *  call-seq:
+ *     show_surface
+ *
+ *  Show the MO surface if present.
+ */
+static VALUE
+s_Molecule_ShowSurface(VALUE self)
+{
+    Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->mcube != NULL) {
+               mol->mcube->hidden = 0;
+               MoleculeCallback_notifyModification(mol, 0);
+       }
+       return self;
+}
+
+/*
+ *  call-seq:
+ *     create_surface(mo, attr = nil)
+ *
+ *  Create a MO surface. The argument mo is the MO index (1-based); if mo is negative,
+ *  then it denotes the beta orbital.
+ *  If mo is nil, then the attributes of the current surface are modified.
+ *  Attributes:
+ *    :npoints : the approximate number of grid points
+ *    :expand  : the scale factor to expand/shrink the display box size for each atom,
+ *    :thres   : the threshold for the isovalue surface
+ *  If the molecule does not contain MO information, raises exception.
+ */
+static VALUE
+s_Molecule_CreateSurface(int argc, VALUE *argv, VALUE self)
+{
+    Molecule *mol;
+       Vector o, dx, dy, dz;
+       Int nmo, nx, ny, nz, i;
+       Int need_recalc = 0;
+       VALUE nval, hval, aval;
+       Int npoints;
+       Double expand;
+       Double thres;
+       Double d[4];
+    Data_Get_Struct(self, Molecule, mol);
+       rb_scan_args(argc, argv, "11", &nval, &hval);
+       if (mol->bset == NULL)
+               rb_raise(rb_eMolbyError, "No MO information is given");
+       if (nval == Qnil) {
+               nmo = -1;
+       } else if (nval == ID2SYM(rb_intern("total_density"))) {
+               nmo = mol->bset->nmos + 1;
+       } else {
+               nmo = NUM2INT(rb_Integer(nval));
+               if (nmo > mol->bset->nmos || nmo < -mol->bset->ncomps)
+                       rb_raise(rb_eMolbyError, "MO index (%d) is out of range; should be 1..%d (or -1..-%d for beta orbitals); (0 is acceptable as arbitrary vector)", nmo, mol->bset->nmos, mol->bset->ncomps);
+               if (nmo < 0)
+                       nmo = -nmo + mol->bset->ncomps;
+       }
+       if (hval != Qnil && (aval = rb_hash_aref(hval, ID2SYM(rb_intern("npoints")))) != Qnil) {
+               npoints = NUM2INT(rb_Integer(aval));
+               need_recalc = 1;
+       } else if (mol->mcube != NULL) {
+               npoints = mol->mcube->nx * mol->mcube->ny * mol->mcube->nz;
+       } else npoints = 80 * 80 * 80;
+       if (hval != Qnil && (aval = rb_hash_aref(hval, ID2SYM(rb_intern("expand")))) != Qnil) {
+               expand = NUM2DBL(rb_Float(aval));
+       } else if (mol->mcube != NULL) {
+               expand = mol->mcube->expand;
+       } else expand = 1.0;
+       if (hval != Qnil && (aval = rb_hash_aref(hval, ID2SYM(rb_intern("thres")))) != Qnil) {
+               thres = NUM2DBL(rb_Float(aval));
+       } else if (mol->mcube != NULL) {
+               thres = mol->mcube->thres;
+       } else thres = 0.05;
+       if (mol->mcube == NULL || npoints != mol->mcube->nx * mol->mcube->ny * mol->mcube->nz) {
+               if (MoleculeGetDefaultMOGrid(mol, npoints, &o, &dx, &dy, &dz, &nx, &ny, &nz) != 0)
+                       rb_raise(rb_eMolbyError, "Cannot get default grid size (internal error?)");
+               if (MoleculeClearMCube(mol, nx, ny, nz, &o, dx.x, dy.y, dz.z) == NULL)
+                       rb_raise(rb_eMolbyError, "Cannot allocate memory for MO surface calculation");
+       }
+       for (nx = 0; nx < 2; nx++) {
+               aval = ID2SYM(rb_intern(nx == 0 ? "color0" : "color"));
+               if (hval != Qnil && (aval = rb_hash_aref(hval, aval)) != Qnil) {
+                       aval = rb_ary_to_ary(aval);
+                       if (RARRAY_LEN(aval) < 3) {
+                       raise:
+                               rb_raise(rb_eMolbyError, "The color%s value must be an array with at least 3 float numbers", (nx == 0 ? "0" : ""));
+                       }
+                       for (i = 0; i < 4; i++)
+                               d[i] = mol->mcube->c[nx].rgba[i];
+                       for (i = 0; i < 4 && i < RARRAY_LEN(aval); i++) {
+                               d[i] = NUM2DBL(rb_Float(RARRAY_PTR(aval)[i]));
+                               if (d[i] < 0.0 && d[i] > 1.0)
+                                       goto raise;
+                       }
+                       for (i = 0; i < 4; i++)
+                               mol->mcube->c[nx].rgba[i] = d[i];
+               }
+       }
+       if (mol->mcube->expand != expand)
+               need_recalc = 1;
+       mol->mcube->thres = thres;
+       mol->mcube->expand = expand;
+       if (nmo < 0) {
+               if (mol->mcube->idn < 0)
+                       return self;  /*  Only set attributes for now  */
+               if (need_recalc)
+                       nmo = mol->mcube->idn;  /*  Force recalculation  */
+       }
+       if (MoleculeUpdateMCube(mol, nmo) != 0)
+               rb_raise(rb_eMolbyError, "Cannot complete MO surface calculation");
+       return self;
+}
+
+/*
+ *  call-seq:
+ *     set_surface_attr(attr = nil)
+ *
+ *  Set the drawing attributes of the surface. Attr is a hash containing the attributes.
+ */
+static VALUE
+s_Molecule_SetSurfaceAttr(VALUE self, VALUE hval)
+{
+       VALUE args[2];
+       args[0] = Qnil;
+       args[1] = hval;
+       return s_Molecule_CreateSurface(2, args, self);
+}
+
+/*
+ *  call-seq:
  *     nelpots
  *
  *  Get the number of electrostatic potential info.
@@ -9421,22 +10643,47 @@ s_Molecule_Elpot(VALUE self, VALUE ival)
 
 /*
  *  call-seq:
- *     add_gaussian_orbital_shell(sym, nprims, atom_index)
+ *     clear_basis_set
+ *
+ *  Clear the existing basis set info. All gaussian coefficients, MO energies and coefficients,
+ *  cube and marching cube information are discarded. This operation is _not_ undoable!
+ */
+static VALUE
+s_Molecule_ClearBasisSet(VALUE self)
+{
+       Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol != NULL) {
+               if (mol->bset != NULL) {
+                       BasisSetRelease(mol->bset);
+                       mol->bset = NULL;
+               }
+               if (mol->mcube != NULL) {
+                       MoleculeDeallocateMCube(mol->mcube);
+                       mol->mcube = NULL;
+               }
+       }
+       return self;
+}
+
+/*
+ *  call-seq:
+ *     add_gaussian_orbital_shell(atom_index, sym, no_of_primitives)
  *
- *  To be used internally. Add a gaussian orbital shell with symmetry code, number of primitives,
- *  and the corresponding atom index. Symmetry code: 0, S-type; 1, P-type; -1, SP-type; 2, D-type;
+ *  To be used internally. Add a gaussian orbital shell with the atom index, symmetry code,
+ *  and the number of primitives. Symmetry code: 0, S-type; 1, P-type; -1, SP-type; 2, D-type;
  *  -2, D5-type.
  */
 static VALUE
-s_Molecule_AddGaussianOrbitalShell(VALUE self, VALUE symval, VALUE npval, VALUE aval)
+s_Molecule_AddGaussianOrbitalShell(VALUE self, VALUE aval, VALUE symval, VALUE npval)
 {
        Molecule *mol;
        int sym, nprims, a_idx, n;
     Data_Get_Struct(self, Molecule, mol);
+       a_idx = NUM2INT(rb_Integer(aval));
        sym = NUM2INT(rb_Integer(symval));
        nprims = NUM2INT(rb_Integer(npval));
-       a_idx = NUM2INT(rb_Integer(aval));
-       n = MoleculeAddGaussianOrbitalShell(mol, sym, nprims, a_idx);
+       n = MoleculeAddGaussianOrbitalShell(mol, a_idx, sym, nprims);
        if (n == -1)
                rb_raise(rb_eMolbyError, "Molecule is emptry");
        else if (n == -2)
@@ -9476,33 +10723,135 @@ s_Molecule_AddGaussianPrimitiveCoefficients(VALUE self, VALUE expval, VALUE cval
 
 /*
  *  call-seq:
- *     mo_type
+ *     get_gaussian_shell_info(shell_index) -> [atom_index, sym, no_of_primitives, comp_index, no_of_components]
  *
- *  Returns either "RHF", "UHF", or "ROHF". If no MO info is present, returns nil.
+ *  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_MOType(VALUE self)
+s_Molecule_GetGaussianShellInfo(VALUE self, VALUE sval)
 {
        Molecule *mol;
+       ShellInfo *sp;
+       int s_idx, sym;
     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;
+       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]
+ *
+ *  Get the Gaussian shell information for the given MO coefficient index.
+ */
+static VALUE
+s_Molecule_GetGaussianComponentInfo(VALUE self, VALUE cval)
+{
+       Molecule *mol;
+       Int n, c, atom_idx, shell_idx;
+       char label[32];
+    Data_Get_Struct(self, Molecule, mol);
+       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:
+ *     clear_mo_coefficients
+ *
+ *  Clear the existing MO coefficients.
+ */
+static VALUE
+s_Molecule_ClearMOCoefficients(VALUE self)
+{
+       Molecule *mol;
+       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
@@ -9524,7 +10873,7 @@ s_Molecule_SetMOCoefficients(VALUE self, VALUE ival, VALUE eval, VALUE aval)
        }
        for (i = 0; i < ncomps; i++)
                coeffs[i] = NUM2DBL(rb_Float(RARRAY_PTR(aval)[i]));
-       i = MoleculeSetMOCoefficients(mol, idx, energy, ncomps, coeffs);
+       i = MoleculeSetMOCoefficients(mol, idx, energy, ncomps, coeffs); /* Negative (beta orbitals) or zero (arbitrary vector) idx is allowed */
 end:
        if (i == -1)
                rb_raise(rb_eMolbyError, "Molecule is emptry");
@@ -9533,7 +10882,7 @@ end:
        else if (i == -3)
                rb_raise(rb_eMolbyError, "Bad or inconsistent number of MOs");
        else if (i == -4)
-               rb_raise(rb_eMolbyError, "Bad MO index");
+               rb_raise(rb_eMolbyError, "Bad MO index (%d)", idx);
        else if (i == -5)
                rb_raise(rb_eMolbyError, "Insufficient number of coefficients are given");
        else if (i != 0)
@@ -9543,32 +10892,175 @@ end:
 
 /*
  *  call-seq:
- *     allocate_basis_set_record(rflag, ne_alpha, ne_beta)
+ *     get_mo_coefficients(idx)
  *
- *  To be used internally. Allocate a basis set record. rflag: 0, unrestricted; 1, restricted.
- *  ne_alpha, ne_beta: number of alpha/beta electrons.
+ *  To be used internally. Get an array of MO coefficients for the given MO index (1-based).
  */
 static VALUE
-s_Molecule_AllocateBasisSetRecord(VALUE self, VALUE rval, VALUE naval, VALUE nbval)
+s_Molecule_GetMOCoefficients(VALUE self, VALUE ival)
 {
        Molecule *mol;
-       Int rflag, na, nb, n;
+       Int idx, ncomps, n;
+       Double energy;
+       Double *coeffs;
+       VALUE retval;
     Data_Get_Struct(self, Molecule, mol);
-       rflag = NUM2INT(rb_Integer(rval));
-       na = NUM2INT(rb_Integer(naval));
-       nb = NUM2INT(rb_Integer(nbval));
-       n = MoleculeAllocateBasisSetRecord(mol, rflag, na, nb);
+       idx = NUM2INT(rb_Integer(ival));
+       ncomps = 0;
+       coeffs = NULL;
+       n = MoleculeGetMOCoefficients(mol, idx, &energy, &ncomps, &coeffs);
        if (n == -1)
                rb_raise(rb_eMolbyError, "Molecule is emptry");
        else if (n == -2)
-               rb_raise(rb_eMolbyError, "Low memory");
-       else if (n != 0)
-               rb_raise(rb_eMolbyError, "Unknown error");
+               rb_raise(rb_eMolbyError, "No basis set information is present");
+       else if (n == -3)
+               return Qnil;  /*  Silently returns nil  */
+       retval = rb_ary_new2(ncomps);
+       for (n = 0; n < ncomps; n++)
+               rb_ary_store(retval, n, rb_float_new(coeffs[n]));
+       free(coeffs);
+       return retval;
+}
+
+/*
+ *  call-seq:
+ *     get_mo_energy(idx)
+ *
+ *  To be used internally. Get the MO energy for the given MO index (1-based).
+ */
+static VALUE
+s_Molecule_GetMOEnergy(VALUE self, VALUE ival)
+{
+       Molecule *mol;
+       Int idx, n;
+       Double energy;
+    Data_Get_Struct(self, Molecule, mol);
+       idx = NUM2INT(rb_Integer(ival));
+       n = MoleculeGetMOCoefficients(mol, idx, &energy, NULL, NULL);
+       if (n == -1)
+               rb_raise(rb_eMolbyError, "Molecule is emptry");
+       else if (n == -2)
+               rb_raise(rb_eMolbyError, "No basis set information is present");
+       else if (n == -3)
+               return Qnil;
+       return rb_float_new(energy);
+}
+
+static VALUE sTypeSym, sAlphaSym, sBetaSym, sNcompsSym, sNshellsSym;
+
+static inline void
+s_InitMOInfoKeys(void)
+{
+       if (sTypeSym == 0) {
+               sTypeSym = ID2SYM(rb_intern("type"));
+               sAlphaSym = ID2SYM(rb_intern("alpha"));
+               sBetaSym = ID2SYM(rb_intern("beta"));
+               sNcompsSym = ID2SYM(rb_intern("ncomps"));
+               sNshellsSym = ID2SYM(rb_intern("nshells"));
+       }
+}
+
+/*
+ *  call-seq:
+ *     set_mo_info(hash)
+ *
+ *  Set the MO info. hash keys: :type=>"RHF"|"UHF"|"ROHF",
+ *  :alpha=>integer, :beta=>integer
+ */
+static VALUE
+s_Molecule_SetMOInfo(VALUE self, VALUE hval)
+{
+       Molecule *mol;
+       VALUE aval;
+       Int rflag, na, nb, n;
+       char *s;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->bset != NULL) {
+               rflag = mol->bset->rflag;
+               na = mol->bset->ne_alpha;
+               nb = mol->bset->ne_beta;
+       } else {
+               rflag = 1;
+               na = 0;
+               nb = 0;
+       }
+       if (hval != Qnil) {
+               if ((aval = rb_hash_aref(hval, sTypeSym)) != Qnil) {
+                       s = StringValuePtr(aval);
+                       if (strcasecmp(s, "RHF") == 0)
+                               rflag = 1;
+                       else if (strcasecmp(s, "UHF") == 0)
+                               rflag = 0;
+                       else if (strcasecmp(s, "ROHF") == 0)
+                               rflag = 2;
+               }
+               if ((aval = rb_hash_aref(hval, sAlphaSym)) != Qnil) {
+                       n = NUM2INT(rb_Integer(aval));
+                       if (n >= 0)
+                               na = n;
+               }
+               if ((aval = rb_hash_aref(hval, sBetaSym)) != Qnil) {
+                       n = NUM2INT(rb_Integer(aval));
+                       if (n >= 0)
+                               nb = n;
+               }
+               MoleculeSetMOInfo(mol, rflag, na, nb);
+       }
        return self;
 }
 
 /*
  *  call-seq:
+ *     get_mo_info(key)
+ *
+ *  Get the MO info. The key is as described in set_mo_info.
+ *  Read-only: :ncomps the number of components (and the number of MOs), :nshells the number of shells
+ */
+static VALUE
+s_Molecule_GetMOInfo(VALUE self, VALUE kval)
+{
+       Molecule *mol;
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->bset == NULL)
+               return Qnil;
+       if (kval == sTypeSym) {
+               switch (mol->bset->rflag) {
+                       case 0: return Ruby_NewEncodedStringValue2("UHF");
+                       case 1: return Ruby_NewEncodedStringValue2("RHF");
+                       case 2: return Ruby_NewEncodedStringValue2("ROHF");
+                       default: return rb_str_to_str(INT2NUM(mol->bset->rflag));
+               }
+       } else if (kval == sAlphaSym) {
+               return INT2NUM(mol->bset->ne_alpha);
+       } else if (kval == sBetaSym) {
+               return INT2NUM(mol->bset->ne_beta);
+       } else if (kval == sNcompsSym) {
+               return INT2NUM(mol->bset->ncomps);
+       } else if (kval == sNshellsSym) {
+               return INT2NUM(mol->bset->nshells);
+       } else {
+               kval = rb_inspect(kval);
+               rb_raise(rb_eMolbyError, "Unknown MO info key: %s", StringValuePtr(kval));
+               return Qnil;  /*  Does not reach here  */
+       }
+}
+
+/*
+ *  call-seq:
+ *     mo_type
+ *
+ *  Returns either "RHF", "UHF", or "ROHF". If no MO info is present, returns nil.
+ */
+static VALUE
+s_Molecule_MOType(VALUE self)
+{
+       return s_Molecule_GetMOInfo(self, sTypeSym);
+}
+
+#pragma mark ------ Molecular Topology ------
+
+/*
+ *  call-seq:
  *     search_equivalent_atoms(ig = nil)
  *
  *  Search equivalent atoms (within the atom group if given). Returns an array of integers.
@@ -9585,7 +11077,7 @@ s_Molecule_SearchEquivalentAtoms(int argc, VALUE *argv, VALUE self)
                return Qnil;
        rb_scan_args(argc, argv, "01", &val);
        if (val != Qnil)
-               ig = IntGroupFromValue(val);
+               ig = s_Molecule_AtomGroupFromValue(self, val);
        else ig = NULL;
        result = MoleculeSearchEquivalentAtoms(mol, ig);
        if (result == NULL)
@@ -9606,13 +11098,15 @@ s_Molecule_SearchEquivalentAtoms(int argc, VALUE *argv, VALUE self)
  *  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 is the relative weights of the component atoms; if omitted, then
+ *  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_CreatePiAnchor(int argc, VALUE *argv, VALUE self)
@@ -9630,7 +11124,12 @@ s_Molecule_CreatePiAnchor(int argc, VALUE *argv, VALUE self)
        gval = *argv++;
        argc -= 2;
     Data_Get_Struct(self, Molecule, mol);
-       ig = IntGroupFromValue(gval);
+    if (gval == Qnil)
+        ig = NULL;
+    else
+        ig = s_Molecule_AtomGroupFromValue(self, gval);
+    if (ig == NULL || IntGroupGetCount(ig) == 0)
+    rb_raise(rb_eMolbyError, "atom group is not given correctly");
        memset(&a, 0, sizeof(a));
        memset(&an, 0, sizeof(an));
        strncpy(a.aname, StringValuePtr(nval), 4);
@@ -9698,6 +11197,164 @@ s_Molecule_CreatePiAnchor(int argc, VALUE *argv, VALUE self)
     return Data_Wrap_Struct(rb_cAtomRef, 0, (void (*)(void *))AtomRefRelease, aref);
 }
 
+#pragma mark ------ Molecular Properties ------
+
+/*
+ *  call-seq:
+ *     set_property(name, value[, index]) -> value
+ *     set_property(name, values, group) -> values
+ *
+ *  Set molecular property. A property is a floating-point number with a specified name,
+ *  and can be set for each frame separately. The name of the property is given as a String.
+ *  The value can be a single floating point number, which is set to the current frame.
+ *  
+ */
+static VALUE
+s_Molecule_SetProperty(int argc, VALUE *argv, VALUE self)
+{
+       Molecule *mol;
+       VALUE nval, vval, ival;
+       char *name;
+       IntGroup *ig;
+       Int i, n, idx, fidx;
+       Double *dp;
+       rb_scan_args(argc, argv, "21", &nval, &vval, &ival);
+    Data_Get_Struct(self, Molecule, mol);
+       if (rb_obj_is_kind_of(nval, rb_cNumeric)) {
+               idx = NUM2INT(rb_Integer(nval));
+               if (idx < 0 || idx >= mol->nmolprops)
+                       rb_raise(rb_eMolbyError, "The property index (%d) is out of range; should be 0..%d", idx, mol->nmolprops - 1);
+       } else {
+               name = StringValuePtr(nval);
+               idx = MoleculeLookUpProperty(mol, name);
+               if (idx < 0) {
+                       idx = MoleculeCreateProperty(mol, name);
+                       if (idx < 0)
+                               rb_raise(rb_eMolbyError, "Cannot create molecular property %s", name);
+               }
+       }
+       if (rb_obj_is_kind_of(vval, rb_cNumeric)) {
+               if (ival == Qnil)
+                       fidx = mol->cframe;
+               else {
+                       fidx = NUM2INT(rb_Integer(ival));
+                       n = MoleculeGetNumberOfFrames(mol);
+                       if (fidx < 0 || fidx >= n)
+                               rb_raise(rb_eMolbyError, "The frame index (%d) is out of range; should be 0..%d", fidx, n - 1);
+               }
+               ig = IntGroupNewWithPoints(fidx, 1, -1);
+               dp = (Double *)malloc(sizeof(Double));
+               *dp = NUM2DBL(rb_Float(vval));
+               n = 1;
+       } else {
+               vval = rb_ary_to_ary(vval);
+               ig = IntGroupFromValue(ival);
+               n = IntGroupGetCount(ig);
+               if (n == 0)
+                       rb_raise(rb_eMolbyError, "No frames are specified");
+               if (RARRAY_LEN(vval) < n)
+                       rb_raise(rb_eMolbyError, "Values are missing; at least %d values should be given", n);
+               dp = (Double *)calloc(sizeof(Double), n);
+               for (i = 0; i < n; i++)
+                       dp[i] = NUM2DBL(rb_Float(RARRAY_PTR(vval)[i]));
+       }
+       
+       MolActionCreateAndPerform(mol, gMolActionSetProperty, idx, ig, n, dp);
+       free(dp);
+       IntGroupRelease(ig);
+       return self;
+}
+
+/*
+ *  call-seq:
+ *     get_property(name[, index]) -> value
+ *     get_property(name, group) -> values
+ *
+ *  Get molecular property. In the first form, a property value for a single frame is returned.
+ *  (If index is omitted, then the value for the current frame is given)
+ *  In the second form, an array of property values for the given frames is returned.
+ *  If name is not one of known properties or a valid index integer, exception is raised.
+ */
+static VALUE
+s_Molecule_GetProperty(int argc, VALUE *argv, VALUE self)
+{
+       Molecule *mol;
+       VALUE nval, ival;
+       char *name;
+       IntGroup *ig;
+       Int i, n, idx, fidx;
+       Double *dp;
+       rb_scan_args(argc, argv, "11", &nval, &ival);
+    Data_Get_Struct(self, Molecule, mol);
+       if (mol->nmolprops == 0)
+               rb_raise(rb_eMolbyError, "The molecule has no properties");
+       if (rb_obj_is_kind_of(nval, rb_cNumeric)) {
+               idx = NUM2INT(rb_Integer(nval));
+               if (idx < 0 || idx >= mol->nmolprops)
+                       rb_raise(rb_eMolbyError, "The property index (%d) is out of range; should be 0..%d", idx, mol->nmolprops - 1);
+       } else {
+               name = StringValuePtr(nval);
+               idx = MoleculeLookUpProperty(mol, name);
+               if (idx < 0)
+                       rb_raise(rb_eMolbyError, "The molecule has no property '%s'", name);
+       }
+       if (rb_obj_is_kind_of(ival, rb_cNumeric) || ival == Qnil) {
+               if (ival == Qnil)
+                       fidx = mol->cframe;
+               else {
+                       fidx = NUM2INT(rb_Integer(ival));
+                       n = MoleculeGetNumberOfFrames(mol);
+                       if (fidx < 0 || fidx >= n)
+                               rb_raise(rb_eMolbyError, "The frame index (%d) is out of range; should be 0..%d", fidx, n - 1);
+               }
+               ig = IntGroupNewWithPoints(fidx, 1, -1);
+               ival = INT2FIX(fidx);
+               n = 1;
+       } else {
+               ig = IntGroupFromValue(ival);
+               n = IntGroupGetCount(ig);
+               if (n == 0)
+                       return rb_ary_new();
+       }
+       dp = (Double *)calloc(sizeof(Double), n);
+       MoleculeGetProperty(mol, idx, ig, dp);  
+       if (FIXNUM_P(ival))
+               ival = rb_float_new(dp[0]);
+       else {
+               ival = rb_ary_new();
+               for (i = n - 1; i >= 0; i--) {
+                       nval = rb_float_new(dp[i]);
+                       rb_ary_store(ival, i, nval);
+               }
+       }
+       free(dp);
+       IntGroupRelease(ig);
+       return ival;
+}
+
+/*
+ *  call-seq:
+ *     property_names -> Array
+ *
+ *  Get an array of property names.
+ */
+static VALUE
+s_Molecule_PropertyNames(VALUE self)
+{
+       Molecule *mol;
+       VALUE rval, nval;
+       int i;
+    Data_Get_Struct(self, Molecule, mol);
+       rval = rb_ary_new();
+       for (i = mol->nmolprops - 1; i >= 0; i--) {
+               nval = Ruby_NewEncodedStringValue2(mol->molprops[i].propname);
+               rb_ary_store(rval, i, nval);
+       }
+       return rval;
+}
+
+#pragma mark ------ Class methods ------
+
 /*
  *  call-seq:
  *     current       -> Molecule
@@ -9754,7 +11411,7 @@ s_Molecule_MoleculeAtIndex(int argc, VALUE *argv, VALUE klass)
                for (idx = 0; (mol = MoleculeCallback_moleculeAtIndex(idx)) != NULL; idx++) {
                        VALUE name;
                        MoleculeCallback_displayName(mol, buf, sizeof buf);
-                       name = rb_str_new2(buf);
+                       name = Ruby_NewEncodedStringValue2(buf);
                        if (rb_reg_match(val, name) != Qnil && --k == 0)
                                break;
                }       
@@ -9809,58 +11466,119 @@ s_Molecule_OrderedList(VALUE klass)
        return ary;
 }
 
-/*
- *  call-seq:
- *     error_message       -> String
- *
- *  Get the error_message from the last load/save method. If no error, returns nil.
- */
-static VALUE
-s_Molecule_ErrorMessage(VALUE klass)
-{
-       if (gLoadSaveErrorMessage == NULL)
-               return Qnil;
-       else return rb_str_new2(gLoadSaveErrorMessage);
-}
+#pragma mark ------ Call Subprocess ------
 
-/*
- *  call-seq:
- *     set_error_message(String)
- *     Molecule.error_message = String
- *
- *  Get the error_message from the last load/save method. If no error, returns nil.
- */
-static VALUE
-s_Molecule_SetErrorMessage(VALUE klass, VALUE sval)
-{
-       if (gLoadSaveErrorMessage != NULL) {
-               free(gLoadSaveErrorMessage);
-               gLoadSaveErrorMessage = NULL;
+/*  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;
        }
-       if (sval != Qnil) {
-               sval = rb_str_to_str(sval);
-               gLoadSaveErrorMessage = strdup(StringValuePtr(sval));
+       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 sval;
+       return 0;
 }
 
 /*
  *  call-seq:
- *     self == Molecule -> boolean
+ *     call_subprocess_async(cmd [, end_callback [, timer_callback [, standard_output_file [, error_output_file]]]])
  *
- *  True if the two arguments point to the same molecule.
+ *  Call subprocess asynchronically.
+ *  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_Equal(VALUE self, VALUE val)
+s_Molecule_CallSubProcessAsync(int argc, VALUE *argv, VALUE self)
 {
-       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;
+       VALUE cmd, end_proc, timer_proc, stdout_val, stderr_val;
+       Molecule *mol;
+       char *sout, *serr;
+       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);
+       n = MoleculeCallback_callSubProcessAsync(mol, StringValuePtr(cmd), 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)
 {
@@ -9877,9 +11595,13 @@ Init_Molby(void)
 
        /*  class Molecule  */
        rb_cMolecule = rb_define_class_under(rb_mMolby, "Molecule", rb_cObject);
+
        rb_define_alloc_func(rb_cMolecule, s_Molecule_Alloc);
     rb_define_private_method(rb_cMolecule, "initialize", s_Molecule_Initialize, -1);
     rb_define_private_method(rb_cMolecule, "initialize_copy", s_Molecule_InitCopy, 1);
+       rb_define_method(rb_cMolecule, "atom_index", s_Molecule_AtomIndex, 1);
+       rb_define_method(rb_cMolecule, "==", s_Molecule_Equal, 1);
+
     rb_define_method(rb_cMolecule, "loadmbsf", s_Molecule_Loadmbsf, -1);
     rb_define_method(rb_cMolecule, "loadpsf", s_Molecule_Loadpsf, -1);
     rb_define_alias(rb_cMolecule, "loadpsfx", "loadpsf");
@@ -9890,19 +11612,25 @@ Init_Molby(void)
     rb_define_method(rb_cMolecule, "loadfchk", s_Molecule_Loadfchk, -1);
     rb_define_alias(rb_cMolecule, "loadfch", "loadfchk");
     rb_define_method(rb_cMolecule, "loaddat", s_Molecule_Loaddat, -1);
-    rb_define_method(rb_cMolecule, "molload", s_Molecule_Load, -1);
-    rb_define_method(rb_cMolecule, "molsave", s_Molecule_Save, -1);
        rb_define_method(rb_cMolecule, "savembsf", s_Molecule_Savembsf, 1);
     rb_define_method(rb_cMolecule, "savepsf", s_Molecule_Savepsf, 1);
     rb_define_alias(rb_cMolecule, "savepsfx", "savepsf");
     rb_define_method(rb_cMolecule, "savepdb", s_Molecule_Savepdb, 1);
     rb_define_method(rb_cMolecule, "savedcd", s_Molecule_Savedcd, 1);
-/*    rb_define_method(rb_cMolecule, "savetep", s_Molecule_Savetep, 1); */
+    rb_define_method(rb_cMolecule, "molload", s_Molecule_Load, -1);
+    rb_define_method(rb_cMolecule, "molsave", s_Molecule_Save, -1);
+    rb_define_method(rb_cMolecule, "set_molecule", s_Molecule_SetMolecule, 1);
+       rb_define_singleton_method(rb_cMolecule, "open", s_Molecule_Open, -1);
+       rb_define_singleton_method(rb_cMolecule, "error_message", s_Molecule_ErrorMessage, 0);
+       rb_define_singleton_method(rb_cMolecule, "set_error_message", s_Molecule_SetErrorMessage, 1);
+       rb_define_singleton_method(rb_cMolecule, "error_message=", s_Molecule_SetErrorMessage, 1);
+       
     rb_define_method(rb_cMolecule, "name", s_Molecule_Name, 0);
        rb_define_method(rb_cMolecule, "set_name", s_Molecule_SetName, 1);
     rb_define_method(rb_cMolecule, "path", s_Molecule_Path, 0);
     rb_define_method(rb_cMolecule, "dir", s_Molecule_Dir, 0);
     rb_define_method(rb_cMolecule, "inspect", s_Molecule_Inspect, 0);
+
     rb_define_method(rb_cMolecule, "atoms", s_Molecule_Atoms, 0);
     rb_define_method(rb_cMolecule, "bonds", s_Molecule_Bonds, 0);
     rb_define_method(rb_cMolecule, "angles", s_Molecule_Angles, 0);
@@ -9914,112 +11642,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);
@@ -10031,15 +11757,20 @@ Init_Molby(void)
        rb_define_method(rb_cMolecule, "show_dummy_atoms=", s_Molecule_ShowDummyAtoms, -1);
        rb_define_method(rb_cMolecule, "show_expanded", s_Molecule_ShowExpanded, -1);
        rb_define_method(rb_cMolecule, "show_expanded=", s_Molecule_ShowExpanded, -1);
-       rb_define_method(rb_cMolecule, "is_atom_visible", s_Molecule_IsAtomVisible, 1);
        rb_define_method(rb_cMolecule, "show_ellipsoids", s_Molecule_ShowEllipsoids, -1);
        rb_define_method(rb_cMolecule, "show_ellipsoids=", s_Molecule_ShowEllipsoids, -1);
+       rb_define_method(rb_cMolecule, "is_atom_visible", s_Molecule_IsAtomVisible, 1);
+       rb_define_method(rb_cMolecule, "hidden_atoms", s_Molecule_HiddenAtoms, 0);  /*  obsolete  */
+       rb_define_method(rb_cMolecule, "hidden_atoms=", s_Molecule_SetHiddenAtoms, 1);  /*  obsolete  */
+       rb_define_alias(rb_cMolecule, "set_hidden_atoms", "hidden_atoms=");  /*  obsolete  */
        rb_define_method(rb_cMolecule, "show_graphite", s_Molecule_ShowGraphite, -1);
        rb_define_method(rb_cMolecule, "show_graphite=", s_Molecule_ShowGraphite, -1);
        rb_define_method(rb_cMolecule, "show_graphite?", s_Molecule_ShowGraphiteFlag, 0);
        rb_define_method(rb_cMolecule, "show_periodic_image", s_Molecule_ShowPeriodicImage, -1);
        rb_define_method(rb_cMolecule, "show_periodic_image=", s_Molecule_ShowPeriodicImage, -1);
        rb_define_method(rb_cMolecule, "show_periodic_image?", s_Molecule_ShowPeriodicImageFlag, 0);
+       rb_define_method(rb_cMolecule, "show_rotation_center", s_Molecule_ShowRotationCenter, -1);
+       rb_define_method(rb_cMolecule, "show_rotation_center=", s_Molecule_ShowRotationCenter, -1);
        rb_define_alias(rb_cMolecule, "show_unitcell=", "show_unitcell");
        rb_define_alias(rb_cMolecule, "show_unit_cell", "show_unitcell");
        rb_define_alias(rb_cMolecule, "show_unit_cell=", "show_unitcell");
@@ -10049,10 +11780,18 @@ Init_Molby(void)
        rb_define_alias(rb_cMolecule, "show_ellipsoids=", "show_ellipsoids");
        rb_define_alias(rb_cMolecule, "show_graphite=", "show_graphite");
        rb_define_alias(rb_cMolecule, "show_periodic_image=", "show_periodic_image");
+       rb_define_alias(rb_cMolecule, "show_rotation_center=", "show_rotation_center");
        rb_define_method(rb_cMolecule, "line_mode", s_Molecule_LineMode, -1);
        rb_define_alias(rb_cMolecule, "line_mode=", "line_mode");
+       rb_define_method(rb_cMolecule, "atom_radius", s_Molecule_AtomRadius, -1);
+       rb_define_alias(rb_cMolecule, "atom_radius=", "atom_radius");
+       rb_define_method(rb_cMolecule, "bond_radius", s_Molecule_BondRadius, -1);
+       rb_define_alias(rb_cMolecule, "bond_radius=", "bond_radius");
+       rb_define_method(rb_cMolecule, "atom_resolution", s_Molecule_AtomResolution, -1);
+       rb_define_alias(rb_cMolecule, "atom_resolution=", "atom_resolution");
+       rb_define_method(rb_cMolecule, "bond_resolution", s_Molecule_BondResolution, -1);
+       rb_define_alias(rb_cMolecule, "bond_resolution=", "bond_resolution");
        rb_define_method(rb_cMolecule, "resize_to_fit", s_Molecule_ResizeToFit, 0);
-#if 1 || !defined(__CMDMAC__)
        rb_define_method(rb_cMolecule, "get_view_rotation", s_Molecule_GetViewRotation, 0);
        rb_define_method(rb_cMolecule, "get_view_scale", s_Molecule_GetViewScale, 0);
        rb_define_method(rb_cMolecule, "get_view_center", s_Molecule_GetViewCenter, 0);
@@ -10060,41 +11799,74 @@ Init_Molby(void)
        rb_define_method(rb_cMolecule, "set_view_scale", s_Molecule_SetViewScale, 1);
        rb_define_method(rb_cMolecule, "set_view_center", s_Molecule_SetViewCenter, 1);
        rb_define_method(rb_cMolecule, "set_background_color", s_Molecule_SetBackgroundColor, -1);
+       rb_define_method(rb_cMolecule, "export_graphic", s_Molecule_ExportGraphic, -1);
+       
+       rb_define_method(rb_cMolecule, "insert_graphic", s_Molecule_InsertGraphic, -1);
        rb_define_method(rb_cMolecule, "create_graphic", s_Molecule_CreateGraphic, -1);
        rb_define_method(rb_cMolecule, "remove_graphic", s_Molecule_RemoveGraphic, 1);
        rb_define_method(rb_cMolecule, "ngraphics", s_Molecule_NGraphics, 0);
-       rb_define_method(rb_cMolecule, "set_graphic_point", s_Molecule_SetGraphicPoint, 3);
+       rb_define_method(rb_cMolecule, "get_graphic_point", s_Molecule_GetGraphicPoint, -1);
+       rb_define_method(rb_cMolecule, "set_graphic_point", s_Molecule_SetGraphicPoint, -1);
+       rb_define_alias(rb_cMolecule, "get_graphic_points", "get_graphic_point");
+       rb_define_alias(rb_cMolecule, "set_graphic_points", "set_graphic_point");
+       rb_define_method(rb_cMolecule, "get_graphic_color", s_Molecule_SetGraphicColor, 1);
        rb_define_method(rb_cMolecule, "set_graphic_color", s_Molecule_SetGraphicColor, 2);
        rb_define_method(rb_cMolecule, "show_graphic", s_Molecule_ShowGraphic, 1);
        rb_define_method(rb_cMolecule, "hide_graphic", s_Molecule_HideGraphic, 1);
-#endif
        rb_define_method(rb_cMolecule, "show_text", s_Molecule_ShowText, 1);
+
        rb_define_method(rb_cMolecule, "md_arena", s_Molecule_MDArena, 0);
        rb_define_method(rb_cMolecule, "set_parameter_attr", s_Molecule_SetParameterAttr, 5);
        rb_define_method(rb_cMolecule, "parameter", s_Molecule_Parameter, 0);
+       rb_define_method(rb_cMolecule, "start_step", s_Molecule_StartStep, 0);
+       rb_define_method(rb_cMolecule, "start_step=", s_Molecule_SetStartStep, 1);
+       rb_define_method(rb_cMolecule, "steps_per_frame", s_Molecule_StepsPerFrame, 0);
+       rb_define_method(rb_cMolecule, "steps_per_frame=", s_Molecule_SetStepsPerFrame, 1);
+       rb_define_method(rb_cMolecule, "ps_per_step", s_Molecule_PsPerStep, 0);
+       rb_define_method(rb_cMolecule, "ps_per_step=", s_Molecule_SetPsPerStep, 1);
+       rb_define_method(rb_cMolecule, "bond_par", s_Molecule_BondParIsObsolete, 1); /* Obsolete */
+       rb_define_method(rb_cMolecule, "angle_par", s_Molecule_BondParIsObsolete, 1); /* Obsolete */
+       rb_define_method(rb_cMolecule, "dihedral_par", s_Molecule_BondParIsObsolete, 1); /* Obsolete */
+       rb_define_method(rb_cMolecule, "improper_par", s_Molecule_BondParIsObsolete, 1); /* Obsolete */
+       rb_define_method(rb_cMolecule, "vdw_par", s_Molecule_BondParIsObsolete, 1); /* Obsolete */
+               
        rb_define_method(rb_cMolecule, "selected_MO", s_Molecule_SelectedMO, 0);
        rb_define_method(rb_cMolecule, "default_MO_grid", s_Molecule_GetDefaultMOGrid, -1);
        rb_define_method(rb_cMolecule, "cubegen", s_Molecule_Cubegen, -1);
+       rb_define_method(rb_cMolecule, "clear_surface", s_Molecule_ClearSurface, 0);
+       rb_define_method(rb_cMolecule, "show_surface", s_Molecule_ShowSurface, 0);
+       rb_define_method(rb_cMolecule, "hide_surface", s_Molecule_HideSurface, 0);
+       rb_define_method(rb_cMolecule, "create_surface", s_Molecule_CreateSurface, -1);
+       rb_define_method(rb_cMolecule, "set_surface_attr", s_Molecule_SetSurfaceAttr, 1);
        rb_define_method(rb_cMolecule, "nelpots", s_Molecule_NElpots, 0);
        rb_define_method(rb_cMolecule, "elpot", s_Molecule_Elpot, 1);
+       rb_define_method(rb_cMolecule, "clear_basis_set", s_Molecule_ClearBasisSet, 0);
        rb_define_method(rb_cMolecule, "add_gaussian_orbital_shell", s_Molecule_AddGaussianOrbitalShell, 3);
        rb_define_method(rb_cMolecule, "add_gaussian_primitive_coefficients", s_Molecule_AddGaussianPrimitiveCoefficients, 3);
-       rb_define_method(rb_cMolecule, "mo_type", s_Molecule_MOType, 0);
+       rb_define_method(rb_cMolecule, "get_gaussian_shell_info", s_Molecule_GetGaussianShellInfo, 1);
+       rb_define_method(rb_cMolecule, "get_gaussian_primitive_coefficients", s_Molecule_GetGaussianPrimitiveCoefficients, 1);
+       rb_define_method(rb_cMolecule, "get_gaussian_component_info", s_Molecule_GetGaussianComponentInfo, 1);
+       rb_define_method(rb_cMolecule, "clear_mo_coefficients", s_Molecule_ClearMOCoefficients, 0);
        rb_define_method(rb_cMolecule, "set_mo_coefficients", s_Molecule_SetMOCoefficients, 3);
-       rb_define_method(rb_cMolecule, "allocate_basis_set_record", s_Molecule_AllocateBasisSetRecord, 3);
+       rb_define_method(rb_cMolecule, "get_mo_coefficients", s_Molecule_GetMOCoefficients, 1);
+       rb_define_method(rb_cMolecule, "get_mo_energy", s_Molecule_GetMOEnergy, 1);
+       rb_define_method(rb_cMolecule, "set_mo_info", s_Molecule_SetMOInfo, 1);
+       rb_define_method(rb_cMolecule, "get_mo_info", s_Molecule_GetMOInfo, 1);
+       rb_define_method(rb_cMolecule, "mo_type", s_Molecule_MOType, 0);
+
        rb_define_method(rb_cMolecule, "search_equivalent_atoms", s_Molecule_SearchEquivalentAtoms, -1);
-       
        rb_define_method(rb_cMolecule, "create_pi_anchor", s_Molecule_CreatePiAnchor, -1);
-       rb_define_method(rb_cMolecule, "==", s_Molecule_Equal, 1);
-
+       
+       rb_define_method(rb_cMolecule, "set_property", s_Molecule_SetProperty, -1);
+       rb_define_method(rb_cMolecule, "get_property", s_Molecule_GetProperty, -1);
+       rb_define_method(rb_cMolecule, "property_names", s_Molecule_PropertyNames, 0);
+               
        rb_define_singleton_method(rb_cMolecule, "current", s_Molecule_Current, 0);
        rb_define_singleton_method(rb_cMolecule, "[]", s_Molecule_MoleculeAtIndex, -1);
-       rb_define_singleton_method(rb_cMolecule, "open", s_Molecule_Open, -1);
        rb_define_singleton_method(rb_cMolecule, "list", s_Molecule_List, 0);
        rb_define_singleton_method(rb_cMolecule, "ordered_list", s_Molecule_OrderedList, 0);
-       rb_define_singleton_method(rb_cMolecule, "error_message", s_Molecule_ErrorMessage, 0);
-       rb_define_singleton_method(rb_cMolecule, "set_error_message", s_Molecule_SetErrorMessage, 1);
-       rb_define_singleton_method(rb_cMolecule, "error_message=", s_Molecule_SetErrorMessage, 1);
+       
+       rb_define_method(rb_cMolecule, "call_subprocess_async", s_Molecule_CallSubProcessAsync, -1);
        
        /*  class MolEnumerable  */
        rb_cMolEnumerable = rb_define_class_under(rb_mMolby, "MolEnumerable", rb_cObject);
@@ -10120,7 +11892,7 @@ Init_Molby(void)
        rb_define_alias(rb_cAtomRef, "set_attr", "[]=");
        rb_define_method(rb_cAtomRef, "[]", s_AtomRef_GetAttr, 1);
        rb_define_alias(rb_cAtomRef, "get_attr", "[]");
-       s_SetAtomAttrString = rb_str_new2("set_atom_attr");
+       s_SetAtomAttrString = Ruby_NewEncodedStringValue2("set_atom_attr");
        rb_global_variable(&s_SetAtomAttrString);
        rb_define_method(rb_cAtomRef, "molecule", s_AtomRef_GetMolecule, 0);
        rb_define_method(rb_cAtomRef, "==", s_AtomRef_Equal, 1);
@@ -10229,49 +12001,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_evalRubyScriptOnMoleculeSub(VALUE val)
 {
        void **ptr = (void **)val;
        Molecule *mol = (Molecule *)ptr[1];
-       VALUE sval = rb_str_new2((char *)ptr[0]);
-       VALUE fnval;
+       VALUE sval, fnval, lnval, retval;
+       VALUE binding;
+
+       /*  Clear the error information (store in the history array if necessary)  */
+       sval = rb_errinfo();
+       if (sval != Qnil) {
+               rb_eval_string("$error_history.push([$!.to_s, $!.backtrace])");
+               rb_set_errinfo(Qnil);
+       }
+
        if (s_ruby_top_self == Qfalse) {
                s_ruby_top_self = rb_eval_string("eval(\"self\",TOPLEVEL_BINDING)");
        }
+       if (s_ruby_get_binding_for_molecule == Qfalse) {
+               const char *s1 =
+                "lambda { |_mol_, _bind_| \n"
+                "  _proc_ = eval(\"lambda { |__mol__| __mol__.instance_eval { binding } } \", _bind_) \n"
+                "  _proc_.call(_mol_) } ";
+               s_ruby_get_binding_for_molecule = rb_eval_string(s1);
+               rb_define_variable("_get_binding_for_molecule", &s_ruby_get_binding_for_molecule);
+       }
+       if (s_ruby_export_local_variables == Qfalse) {
+               const char *s2 =
+               "lambda { |_bind_| \n"
+               "   # find local variables newly defined in _bind_ \n"
+               " _a_ = _bind_.eval(\"local_variables\") - TOPLEVEL_BINDING.eval(\"local_variables\"); \n"
+               " _a_.each { |_vsym_| \n"
+               "   _vname_ = _vsym_.to_s \n"
+               "   _vval_ = _bind_.eval(_vname_) \n"
+               "   #  Define local variable \n"
+               "   TOPLEVEL_BINDING.eval(_vname_ + \" = nil\") \n"
+               "   #  Then set value  \n"
+               "   TOPLEVEL_BINDING.eval(\"lambda { |_m_| \" + _vname_ + \" = _m_ }\").call(_vval_) \n"
+               " } \n"
+               "}";
+               s_ruby_export_local_variables = rb_eval_string(s2);
+               rb_define_variable("_export_local_variables", &s_ruby_export_local_variables);
+       }
        if (ptr[2] == NULL) {
-               fnval = Qnil;
+               char *scr;
+               /*  String literal: we need to specify string encoding  */
+#if defined(__WXMSW__)
+               asprintf(&scr, "#coding:shift_jis\n%s", (char *)ptr[0]);
+#else
+               asprintf(&scr, "#coding:utf-8\n%s", (char *)ptr[0]);
+#endif
+               sval = Ruby_NewEncodedStringValue2(scr);
+               free(scr);
+               fnval = Ruby_NewEncodedStringValue2("(eval)");
+               lnval = INT2FIX(0);
        } else {
+               sval = Ruby_NewEncodedStringValue2((char *)ptr[0]);
                fnval = Ruby_NewFileStringValue((char *)ptr[2]);
+               lnval = INT2FIX(1);
        }
-       if (mol == NULL) {
-               if (fnval == Qnil)
-                       return rb_funcall(s_ruby_top_self, rb_intern("eval"), 1, sval);
-               else
-                       return rb_funcall(s_ruby_top_self, rb_intern("eval"), 4, sval, Qnil, fnval, INT2FIX(1));
-       } else {
+       binding = rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING"));
+       if (mol != NULL) {
                VALUE mval = ValueFromMolecule(mol);
-               if (fnval == Qnil)
-                       return rb_funcall(mval, rb_intern("instance_eval"), 1, sval);
-               else return rb_funcall(mval, rb_intern("instance_eval"), 3, sval, fnval, INT2FIX(1));
+               binding = rb_funcall(s_ruby_get_binding_for_molecule, rb_intern("call"), 2, mval, binding);
        }
+       retval = rb_funcall(binding, rb_intern("eval"), 3, sval, fnval, lnval);
+       if (mol != NULL) {
+               rb_funcall(s_ruby_export_local_variables, rb_intern("call"), 1, binding);
+       }
+       return retval;
 }
 
 RubyValue
@@ -10280,8 +12132,8 @@ Molby_evalRubyScriptOnMolecule(const char *script, Molecule *mol, const char *fn
        RubyValue retval;
        void *args[3];
        VALUE save_interrupt_flag;
-       char *save_ruby_sourcefile;
-       int save_ruby_sourceline;
+/*     char *save_ruby_sourcefile;
+       int save_ruby_sourceline; */
        if (gMolbyIsCheckingInterrupt) {
                MolActionAlertRubyIsRunning();
                *status = -1;
@@ -10292,8 +12144,8 @@ Molby_evalRubyScriptOnMolecule(const char *script, Molecule *mol, const char *fn
        args[1] = (void *)mol;
        args[2] = (void *)fname;
        save_interrupt_flag = s_SetInterruptFlag(Qnil, Qtrue);
-       save_ruby_sourcefile = ruby_sourcefile;
-       save_ruby_sourceline = ruby_sourceline;
+/*     save_ruby_sourcefile = ruby_sourcefile;
+       save_ruby_sourceline = ruby_sourceline; */
        retval = (RubyValue)rb_protect(s_evalRubyScriptOnMoleculeSub, (VALUE)args, status);
        if (*status != 0) {
                /*  Is this 'exit' exception?  */
@@ -10302,22 +12154,43 @@ Molby_evalRubyScriptOnMolecule(const char *script, Molecule *mol, const char *fn
                        /*  Capture exit and return the status value  */
                        retval = (RubyValue)rb_funcall(last_exception, rb_intern("status"), 0);
                        *status = 0;
+                       rb_set_errinfo(Qnil);
                }
        }
        s_SetInterruptFlag(Qnil, save_interrupt_flag);
-       ruby_sourcefile = save_ruby_sourcefile;
-       ruby_sourceline = save_ruby_sourceline;
+/*     ruby_sourcefile = save_ruby_sourcefile;
+       ruby_sourceline = save_ruby_sourceline; */
        gMolbyRunLevel--;
        return retval;
 }
 
-void
-Molby_showRubyValue(RubyValue value, char **outValueString)
+/*  For debug  */
+char *
+Ruby_inspectValue(RubyValue value)
+{
+    int status;
+    static char buf[256];
+    VALUE val = (VALUE)value;
+    gMolbyRunLevel++;
+    val = rb_protect(rb_inspect, val, &status);
+    gMolbyRunLevel--;
+    if (status == 0) {
+        char *str = StringValuePtr(val);
+        strncpy(buf, str, sizeof(buf) - 1);
+        buf[sizeof(buf) - 1] = 0;
+    } else {
+        snprintf(buf, sizeof(buf), "Error status = %d", status);
+    }
+    return buf;
+}
+
+int
+Ruby_showValue(RubyValue value, char **outValueString)
 {
        VALUE val = (VALUE)value;
        if (gMolbyIsCheckingInterrupt) {
                MolActionAlertRubyIsRunning();
-               return;
+               return 0;
        }
        if (val != Qnil) {
                int status;
@@ -10325,69 +12198,136 @@ Molby_showRubyValue(RubyValue value, char **outValueString)
                gMolbyRunLevel++;
                val = rb_protect(rb_inspect, val, &status);
                gMolbyRunLevel--;
+               if (status != 0)
+                       return status;
                str = StringValuePtr(val);
                if (outValueString != NULL)
                        *outValueString = strdup(str);
                MyAppCallback_showScriptMessage("%s", str);
+       } else {
+               if (outValueString != NULL)
+                       *outValueString = NULL;
        }
+       return 0;
 }
 
 void
-Molby_showError(int status)
+Ruby_showError(int status)
 {
        static const int tag_raise = 6;
+    char *main_message = "Molby script error";
        char *msg = NULL, *msg2;
        VALUE val, backtrace;
        int interrupted = 0;
+    int exit_status = -1;
        if (status == tag_raise) {
-               VALUE eclass = CLASS_OF(ruby_errinfo);
+               VALUE errinfo = rb_errinfo();
+               VALUE eclass = CLASS_OF(errinfo);
                if (eclass == rb_eInterrupt) {
-                       msg = "Interrupt";
+            main_message = "Molby script interrupted";
+            msg = "Interrupt";
                        interrupted = 1;
-               }
+        } else if (eclass == rb_eSystemExit) {
+            main_message = "Molby script exit";
+            interrupted = 2;
+            val = rb_eval_string_protect("$!.status", &status);
+            if (status == 0) {
+                exit_status = NUM2INT(rb_Integer(val));
+                asprintf(&msg, "Molby script exit with status %d", exit_status);
+            } else {
+                asprintf(&msg, "Molby script exit with unknown status");
+            }
+        }
        }
        gMolbyRunLevel++;
-       backtrace = rb_eval_string_protect("$backtrace = $!.backtrace.join(\"\\n\")", &status);
-       if (msg == NULL) {
-               val = rb_eval_string_protect("$!.to_s", &status);
-               if (status == 0)
-                       msg = RSTRING_PTR(val);
-               else msg = "(message not available)";
-       }
-       asprintf(&msg2, "%s\n%s", msg, RSTRING_PTR(backtrace));
-       MyAppCallback_messageBox(msg2, (interrupted == 0 ? "Molby script error" : "Molby script interrupted"), 0, 3);
+    if (exit_status != 0) {
+        backtrace = rb_eval_string_protect("$backtrace = $!.backtrace.join(\"\\n\")", &status);
+        if (msg == NULL) {
+            val = rb_eval_string_protect("$!.to_s", &status);
+            if (status == 0)
+                msg = RSTRING_PTR(val);
+            else
+                msg = "(message not available)";
+        }
+        asprintf(&msg2, "%s\n%s", msg, RSTRING_PTR(backtrace));
+    } else {
+        msg2 = strdup(msg);
+    }
+       MyAppCallback_messageBox(msg2, main_message, 0, 3);
        free(msg2);
+    if (interrupted == 2) {
+        free(msg);
+        if (!gUseGUI && exit_status == 0)
+            exit(0);  // Capture exit(0) here and force exit
+    }
        gMolbyRunLevel--;
 }
 
-char *
-Molby_getDescription(void)
+/*  Wrapper function for rb_load_protect or rb_eval_string_protect. Used only in non-GUI mode.  */
+int
+Molby_loadScript(const char *script, int from_file)
+{
+    int status;
+    gMolbyRunLevel++;
+    if (from_file)
+        rb_load_protect(Ruby_NewEncodedStringValue2(script), 0, &status);
+    else
+        rb_eval_string_protect(script, &status);
+    gMolbyRunLevel--;
+    return status;
+}
+
+void
+Molby_getDescription(char **versionString, char **auxString)
 {
        extern const char *gVersionString, *gCopyrightString;
        extern int gRevisionNumber;
        extern char *gLastBuildString;
-       char *s;
+    char *s1, *s2;
        char *revisionString;
        if (gRevisionNumber > 0) {
                asprintf(&revisionString, ", revision %d", gRevisionNumber);
        } else revisionString = "";
-       asprintf(&s, 
-                        "Molby %s%s\n%s\nLast compile: %s\n"
-#if !defined(__CMDMAC__)
-                        "\nIncluding:\n"
-                        "%s"
+
+    asprintf(&s1, "%s %s%s\n%s\nLast compile: %s\n",
+#if defined(__WXMSW__)
+    #if TARGET_ARCH == 64
+             "Molby (64 bit)",
+    #else
+             "Molby (32 bit)",
+    #endif
 #else
-                        "Including "
+             "Molby",
 #endif
-                        "ruby %s\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
@@ -10402,11 +12342,7 @@ Molby_startup(const char *script, const char *dir)
        {
                gRubyVersion = strdup(ruby_version);
                asprintf(&gRubyCopyright, "%sCopyright (C) %d-%d %s",
-#if defined(__CMDMAC__)
-                                "",
-#else
                                 "  ",  /*  Indent for displaying in About dialog  */
-#endif
                                 RUBY_BIRTH_YEAR, RUBY_RELEASE_YEAR, RUBY_AUTHOR);
        }
        
@@ -10439,40 +12375,71 @@ Molby_startup(const char *script, const char *dir)
                }
     } */
 
-#if defined(__CMDMAC__)
-       wbuf = Molby_getDescription();
-       printf("%s\n", wbuf);
-       free(wbuf);
-#endif
+    if (!gUseGUI) {
+        char *wbuf2;
+        Molby_getDescription(&wbuf, &wbuf2);
+        MyAppCallback_showScriptMessage("%s\n%s\n", wbuf, wbuf2);
+        free(wbuf);
+        free(wbuf2);
+    }
        
        /*  Read atom display parameters  */
        if (ElementParameterInitialize("element.par", &wbuf) != 0) {
-#if defined(__CMDMAC__)
-               fprintf(stderr, "%s\n", wbuf);
-#else
-               MyAppCallback_setConsoleColor(1);
-               MyAppCallback_showScriptMessage("%s", wbuf);
-               MyAppCallback_setConsoleColor(0);
-#endif
+        MyAppCallback_setConsoleColor(1);
+        MyAppCallback_showScriptMessage("%s", wbuf);
+        MyAppCallback_setConsoleColor(0);
                free(wbuf);
        }
        
        /*  Read default parameters  */
        ParameterReadFromFile(gBuiltinParameters, "default.par", &wbuf, NULL);
        if (wbuf != NULL) {
-#if defined(__CMDMAC__)
-               fprintf(stderr, "%s\n", wbuf);
-#else
-               MyAppCallback_setConsoleColor(1);
-               MyAppCallback_showScriptMessage("%s", wbuf);
-               MyAppCallback_setConsoleColor(0);
-#endif
+        MyAppCallback_setConsoleColor(1);
+        MyAppCallback_showScriptMessage("%s", wbuf);
+        MyAppCallback_setConsoleColor(0);
                free(wbuf);
        }
                
        /*  Initialize Ruby interpreter  */
+#if __WXMSW__
+       if (gUseGUI) {
+               /*  On Windows, fileno(stdin|stdout|stderr) returns -2 and
+                   it causes rb_bug() (= fatal error) during ruby_init().
+                   As a workaround, these standard streams are reopend as
+                   NUL stream.  */
+               freopen("NUL", "r", stdin);
+               freopen("NUL", "w", stdout);
+               freopen("NUL", "w", stderr);
+       }
+#endif
        ruby_init();
-       
+
+       {
+        /*  Initialize CP932/Windows-31J encodings  */
+               extern void Init_shift_jis(void), Init_windows_31j(void),  Init_trans_japanese_sjis(void);
+        extern int rb_enc_alias(const char *, const char *);
+        Init_shift_jis();
+        Init_windows_31j();
+        Init_trans_japanese_sjis();
+        rb_enc_alias("CP932", "Windows-31J");
+    }
+    
+#if defined(__WXMSW__)
+    {
+        /*  Set default external encoding  */
+        /*  The following snippet is taken from encoding.c  */
+        extern void rb_enc_set_default_external(VALUE encoding);
+        char cp[sizeof(int) * 8 / 3 + 22];
+        int status;
+        VALUE enc;
+        snprintf(cp, sizeof cp, "Encoding.find('CP%d')", AreFileApisANSI() ? GetACP() : GetOEMCP());
+        enc = rb_eval_string_protect(cp, &status);
+        if (status == 0 && !NIL_P(enc)) {
+            rb_enc_set_default_external(enc);
+        }
+       }
+#endif
+
        /*  Initialize loadpath; the specified directory, "lib" subdirectory, and "."  */
        ruby_incpush(".");
        asprintf(&libpath, "%s%clib", dir, PATH_SEPARATOR);
@@ -10495,7 +12462,8 @@ Molby_startup(const char *script, const char *dir)
 
        /*  Define Molby classes  */
        Init_Molby();
-       RubyDialogInitClass();
+    if (gUseGUI)
+        RubyDialogInitClass();
 
        rb_define_const(rb_mMolby, "ResourcePath", val);
        val = Ruby_NewFileStringValue(dir);
@@ -10505,52 +12473,65 @@ Molby_startup(const char *script, const char *dir)
        rb_define_const(rb_mMolby, "MbsfPath", val);    
        free(p);
        
-#if defined(__CMDMAC__)
-       rb_define_const(rb_mMolby, "HasGUI", Qfalse);
-#else
-       rb_define_const(rb_mMolby, "HasGUI", Qtrue);
-#endif
-
-#if !__CMDMAC__
-       
-       /*  Create objects for stdout and stderr  */
-       val = rb_funcall(rb_cObject, rb_intern("new"), 0);
-       rb_define_singleton_method(val, "write", s_StandardOutput, 1);
-       rb_gv_set("$stdout", val);
-       val = rb_funcall(rb_cObject, rb_intern("new"), 0);
-       rb_define_singleton_method(val, "write", s_StandardErrorOutput, 1);
-       rb_gv_set("$stderr", val);
-
-       /*  Create objects for stdin  */
-       val = rb_funcall(rb_cObject, rb_intern("new"), 0);
-       rb_define_singleton_method(val, "gets", s_StandardInputGets, -1);
-       rb_define_singleton_method(val, "readline", s_StandardInputGets, -1);
-       rb_define_singleton_method(val, "method_missing", s_StandardInputMethodMissing, -1);
-       rb_gv_set("$stdin", val);
+       p = MyAppCallback_getHomeDir();
+       val = (p == NULL ? Qnil : Ruby_NewFileStringValue(p));
+       rb_define_const(rb_mMolby, "HomeDirectory", val);
+       free(p);
+       p = MyAppCallback_getDocumentHomeDir();
+       val = (p == NULL ? Qnil : Ruby_NewFileStringValue(p));
+       rb_define_const(rb_mMolby, "DocumentDirectory", val);
+       free(p);
        
-#endif
+    if (gUseGUI)
+        rb_define_const(rb_mMolby, "HasGUI", Qtrue);
+    else
+        rb_define_const(rb_mMolby, "HasGUI", Qfalse);
+
+    {
+        /*  Create objects for stdout and stderr  */
+        val = rb_funcall(rb_cObject, rb_intern("new"), 0);
+        rb_define_singleton_method(val, "write", s_StandardOutput, 1);
+        rb_define_singleton_method(val, "flush", s_FlushConsoleOutput, 0);
+        rb_gv_set("$stdout", val);
+        val = rb_funcall(rb_cObject, rb_intern("new"), 0);
+        rb_define_singleton_method(val, "write", s_StandardErrorOutput, 1);
+        rb_define_singleton_method(val, "flush", s_FlushConsoleOutput, 0);
+        rb_gv_set("$stderr", val);
+
+        /*  Create objects for stdin  */
+        val = rb_funcall(rb_cObject, rb_intern("new"), 0);
+        rb_define_singleton_method(val, "gets", s_StandardInputGets, -1);
+        rb_define_singleton_method(val, "readline", s_StandardInputGets, -1);
+        rb_define_singleton_method(val, "method_missing", s_StandardInputMethodMissing, -1);
+        rb_gv_set("$stdin", val);
+    }
        
-       /*  Global variable to hold backtrace  */
+       /*  Global variable to hold error information  */
        rb_define_variable("$backtrace", &gMolbyBacktrace);
-
-#if !__CMDMAC__
-       /*  Register interrupt check code  */
-       rb_add_event_hook(s_Event_Callback, RUBY_EVENT_ALL);
-#endif
+       rb_define_variable("$error_history", &gMolbyErrorHistory);
+       gMolbyErrorHistory = rb_ary_new();
        
-#if !__CMDMAC__
-       /*  Start interval timer (for periodic polling of interrupt); firing every 50 msec  */
-       s_SetIntervalTimer(0, 50);
-#endif
+       /*  Global variables for script menus  */
+       rb_define_variable("$script_menu_commands", &gScriptMenuCommands);
+       rb_define_variable("$script_menu_enablers", &gScriptMenuEnablers);
+       gScriptMenuCommands = rb_ary_new();
+       gScriptMenuEnablers = rb_ary_new();
+       
+    if (gUseGUI) {
+        /*  Register interrupt check code  */
+        rb_add_event_hook(s_Event_Callback, RUBY_EVENT_ALL, Qnil);
+        /*  Start interval timer (for periodic polling of interrupt); firing every 50 msec  */
+        s_SetIntervalTimer(0, 50);
+    }
        
        /*  Read the startup script  */
        if (script != NULL && script[0] != 0) {
                MyAppCallback_showScriptMessage("Evaluating %s...\n", script);
                gMolbyRunLevel++;
-               rb_load_protect(rb_str_new2(script), 0, &status);
+               rb_load_protect(Ruby_NewEncodedStringValue2(script), 0, &status);
                gMolbyRunLevel--;
                if (status != 0)
-                       Molby_showError(status);
+                       Ruby_showError(status);
                else
                        MyAppCallback_showScriptMessage("Done.\n");
        }