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