X-Git-Url: http://git.osdn.net/view?a=blobdiff_plain;f=MolLib%2FRuby_bind%2Fruby_bind.c;h=76586367a44f00eab769c5cc45cf702f10f0b3f9;hb=874b25b5b7a0ae7dbcfe9c043447273bf7c546f9;hp=7f2295bbcbb91b59a21fba47b73c08337785a9cc;hpb=c2beec1f60806131ad834055ae16dc7a329a97f2;p=molby%2FMolby.git diff --git a/MolLib/Ruby_bind/ruby_bind.c b/MolLib/Ruby_bind/ruby_bind.c index 7f2295b..7658636 100644 --- a/MolLib/Ruby_bind/ruby_bind.c +++ b/MolLib/Ruby_bind/ruby_bind.c @@ -76,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 @@ -91,6 +91,10 @@ static VALUE 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 @@ -116,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 @@ -131,11 +135,11 @@ Ruby_NewFileStringValue(const char *fstr) VALUE retval; char *p = strdup(fstr); translate_char(p, '\\', '/'); - retval = rb_enc_str_new(p, strlen(p), rb_default_external_encoding()); + retval = Ruby_NewEncodedStringValue2(p); free(p); return retval; #else - return rb_str_new2(fstr); + return Ruby_NewEncodedStringValue2(fstr); #endif } @@ -156,6 +160,12 @@ Ruby_NewEncodedStringValue(const char *str, int len) } VALUE +Ruby_NewEncodedStringValue2(const char *str) +{ + return Ruby_NewEncodedStringValue(str, -1); +} + +VALUE Ruby_ObjToStringObj(VALUE val) { switch (TYPE(val)) { @@ -182,7 +192,7 @@ static VALUE s_Kernel_MessageBox(int argc, VALUE *argv, VALUE self) { char *str, *title, *s; - int buttons, icon; + int buttons, icon, retval; VALUE sval, tval, bval, ival; rb_scan_args(argc, argv, "22", &sval, &tval, &bval, &ival); str = StringValuePtr(sval); @@ -209,8 +219,8 @@ s_Kernel_MessageBox(int argc, VALUE *argv, VALUE self) else rb_raise(rb_eMolbyError, "the icon specification should be either :info, :warning or :error"); } else icon = 1; - MyAppCallback_messageBox(str, title, buttons, icon); - return Qnil; + retval = MyAppCallback_messageBox(str, title, buttons, icon); + return (retval ? Qtrue : Qfalse); } /* @@ -246,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; } @@ -336,8 +346,11 @@ static VALUE s_Kernel_ExportToClipboard(VALUE self, VALUE sval) { #if !defined(__CMDMAC__) - const char *s = StringValuePtr(sval); + 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; @@ -375,6 +388,84 @@ s_Kernel_ExportToClipboard(VALUE self, VALUE sval) /* * 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. @@ -427,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(); @@ -515,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; } @@ -743,13 +831,13 @@ s_Kernel_RegisterMenu(int argc, VALUE *argv, VALUE self) 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_global_variable(&sMolProc); - rb_global_variable(&sNonEmptyProc); - rb_global_variable(&sSelectionProc); - rb_global_variable(&sTrueProc); + rb_define_variable("$always_true_proc", &sTrueProc); } if (pval == Qnil) { @@ -779,7 +867,7 @@ static VALUE s_Ruby_UpdateUI_handler(VALUE data) { void **p = (void **)data; - int index = (int)p[0]; + int index = (intptr_t)p[0]; Molecule *mol = (Molecule *)p[1]; int *outChecked = (int *)p[2]; char **outTitle = (char **)p[3]; @@ -814,7 +902,7 @@ Ruby_UpdateUI(int index, Molecule *mol, int *outChecked, char **outTitle) int status; void *p[4]; VALUE retval; - p[0] = (void *)index; + p[0] = (void *)(intptr_t)index; p[1] = mol; p[2] = outChecked; p[3] = outTitle; @@ -910,6 +998,10 @@ s_Kernel_CallSubProcess_Callback(void *data) * * Call subprocess. A progress dialog window is displayed, with a message * "Running #{process_name}...". + * cmd is either a single string of an array of string. If it is a single string, then + * it will be given to wxExecute as a single argument. In this case, the string can be + * split into arguments by whitespace. If this behavior is not intended, then use an array + * containing a single string. * A callback proc can be given, which is called periodically during execution. If the proc returns * nil or false, then the execution will be interrupted. * If stdout_file or stderr_file is a filename, then the message will be sent to the file; if the @@ -921,8 +1013,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, **cmdargv; FILE *fpout, *fperr; rb_scan_args(argc, argv, "23", &cmd, &procname, &cproc, &stdout_val, &stderr_val); @@ -965,9 +1059,28 @@ s_Kernel_CallSubProcess(int argc, VALUE *argv, VALUE self) rb_raise(rb_eMolbyError, "Cannot open file for standard output (%s)", serr); } } + + save_interruptFlag = s_SetInterruptFlag(self, Qnil); + if (procname != Qnil) + pnamestr = StringValuePtr(procname); + else pnamestr = NULL; + if (rb_obj_is_kind_of(cmd, rb_cString)) { + cmdargv = calloc(sizeof(cmdargv[0]), 3); + cmdargv[0] = StringValuePtr(cmd); + cmdargv[1] = ""; + cmdargv[2] = NULL; + } else { + cmd = rb_ary_to_ary(cmd); + cmdargv = calloc(sizeof(cmdargv[0]), RARRAY_LEN(cmd) + 1); + for (n = 0; n < RARRAY_LEN(cmd); n++) { + cmdargv[n] = StringValuePtr(RARRAY_PTR(cmd)[n]); + } + cmdargv[n] = NULL; + } + n = MyAppCallback_callSubProcess(cmdargv, pnamestr, (cproc == Qnil ? NULL : s_Kernel_CallSubProcess_Callback), (cproc == Qnil ? NULL : (void *)cproc), fpout, fperr, &exitstatus, &pid); + s_SetInterruptFlag(self, save_interruptFlag); + free(cmdargv); - n = MyAppCallback_callSubProcess(StringValuePtr(cmd), StringValuePtr(procname), (cproc == Qnil ? NULL : s_Kernel_CallSubProcess_Callback), (cproc == Qnil ? NULL : (void *)cproc), fpout, fperr); - if (fpout != NULL && fpout != (FILE *)1) fclose(fpout); if (fperr != NULL && fperr != (FILE *)1) @@ -988,17 +1101,21 @@ 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) { + const char *cmdargv[3]; + cmdargv[0] = StringValuePtr(cmd); + cmdargv[1] = ""; + cmdargv[2] = NULL; + n = MyAppCallback_callSubProcess(cmdargv, NULL, DUMMY_CALLBACK, &buf, NULL, NULL, &exitstatus, &pid); +/* fprintf(stderr, "n = %d, exitstatus = %d, pid = %d\n", n, exitstatus, pid); */ + if (n >= 0 && buf != NULL) { val = Ruby_NewEncodedStringValue(buf, 0); free(buf); } else { val = Ruby_NewEncodedStringValue("", 0); } + rb_last_status_set(exitstatus, pid); return val; } @@ -1035,6 +1152,75 @@ s_Kernel_SetGlobalSettings(VALUE self, VALUE key, VALUE value) return value; } +#pragma mark ====== IO extension ====== + +static VALUE +s_Ruby_str_encode_protected(VALUE val) +{ + return rb_str_encode(val, rb_enc_from_encoding(rb_default_external_encoding()), + ECONV_INVALID_REPLACE | ECONV_UNDEF_REPLACE, Qnil); +} + +/* + * call-seq: + * gets_any_eol + * + * A gets variant that works for CR, LF, and CRLF end-of-line characters. + */ +static VALUE +s_IO_gets_any_eol(VALUE self) +{ + VALUE val, val2, cval; + char buf[1024]; + int i, c, status; + static ID id_getbyte = 0, id_ungetbyte; + if (id_getbyte == 0) { + id_getbyte = rb_intern("getbyte"); + id_ungetbyte = rb_intern("ungetbyte"); + } + i = 0; + val = Qnil; + while ((cval = rb_funcall(self, id_getbyte, 0)) != Qnil) { + c = NUM2INT(rb_Integer(cval)); + if (c == 0x0d) { + cval = rb_funcall(self, id_getbyte, 0); + if (cval != Qnil) { + c = NUM2INT(rb_Integer(cval)); + if (c != 0x0a) + rb_funcall(self, id_ungetbyte, 1, cval); + } + break; + } else if (c != 0x0a) { + buf[i++] = c; + if (i >= 1020) { + buf[i] = 0; + if (val == Qnil) + val = rb_str_new(buf, i); + else + rb_str_append(val, rb_str_new(buf, i)); + i = 0; + } + } else break; + } + if (cval == Qnil && i == 0 && val == Qnil) + return Qnil; /* End of file */ + buf[i] = 0; + if (val == Qnil) + val = rb_str_new(buf, i); + else if (i > 0) + rb_str_append(val, rb_str_new(buf, i)); + val2 = rb_protect(s_Ruby_str_encode_protected, val, &status); /* Ignore exception */ + if (status == 0) + val = val2; + if (cval != Qnil) { + /* Needs a end-of-line mark */ + cval = rb_gv_get("$/"); + rb_str_append(val, cval); + } + rb_gv_set("$_", val); + return val; +} + #pragma mark ====== Utility functions (protected funcall) ====== struct Ruby_funcall2_record { @@ -1180,10 +1366,10 @@ static VALUE s_ParameterRef_GetParType(VALUE self) { Int tp; s_UnionParFromValue(self, &tp, 0); if (tp == kElementParType) - return rb_str_new2("element"); + return Ruby_NewEncodedStringValue2("element"); tp -= kFirstParType; if (tp >= 0 && tp < sizeof(s_ParameterTypeNames) / sizeof(s_ParameterTypeNames[0])) - return rb_str_new2(s_ParameterTypeNames[tp]); + return Ruby_NewEncodedStringValue2(s_ParameterTypeNames[tp]); else rb_raise(rb_eMolbyError, "Internal error: parameter type tag is out of range (%d)", tp); } @@ -1616,7 +1802,7 @@ static VALUE s_ParameterRef_GetName(VALUE self) { char name[5]; strncpy(name, up->atom.name, 4); name[4] = 0; - return rb_str_new2(name); + return Ruby_NewEncodedStringValue2(name); } else rb_raise(rb_eMolbyError, "invalid member name"); } @@ -1651,7 +1837,7 @@ static VALUE s_ParameterRef_GetFullName(VALUE self) { char fullname[16]; strncpy(fullname, up->atom.fullname, 15); fullname[15] = 0; - return rb_str_new2(fullname); + return Ruby_NewEncodedStringValue2(fullname); } else rb_raise(rb_eMolbyError, "invalid member fullname"); } @@ -1668,7 +1854,7 @@ static VALUE s_ParameterRef_GetComment(VALUE self) { com = up->bond.com; if (com == 0) return Qnil; - else return rb_str_new2(ParameterGetComment(com)); + else return Ruby_NewEncodedStringValue2(ParameterGetComment(com)); } /* @@ -1687,7 +1873,7 @@ static VALUE s_ParameterRef_GetSource(VALUE self) { return Qfalse; /* undefined */ else if (src == 0) return Qnil; /* local */ - else return rb_str_new2(ParameterGetComment(src)); + else return Ruby_NewEncodedStringValue2(ParameterGetComment(src)); } static void @@ -2445,7 +2631,7 @@ s_ParameterRef_ToString(VALUE self) 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); } /* @@ -3331,10 +3517,10 @@ s_ParEnumerable_ParType(VALUE self) { Data_Get_Struct(self, ParEnumerable, pen); tp = pen->parType; if (tp == kElementParType) - return rb_str_new2("element"); + return Ruby_NewEncodedStringValue2("element"); tp -= kFirstParType; if (tp >= 0 && tp < sizeof(s_ParameterTypeNames) / sizeof(s_ParameterTypeNames[0])) - return rb_str_new2(s_ParameterTypeNames[tp]); + return Ruby_NewEncodedStringValue2(s_ParameterTypeNames[tp]); else rb_raise(rb_eMolbyError, "Internal error: parameter type tag is out of range (%d)", tp); } @@ -3696,7 +3882,7 @@ static VALUE s_AtomRef_GetSegSeq(VALUE self) { static VALUE s_AtomRef_GetSegName(VALUE self) { char *p = s_AtomFromValue(self)->segName; - return rb_str_new(p, strlen_limit(p, 4)); + return Ruby_NewEncodedStringValue(p, strlen_limit(p, 4)); } static VALUE s_AtomRef_GetResSeq(VALUE self) { @@ -3705,18 +3891,18 @@ static VALUE s_AtomRef_GetResSeq(VALUE self) { static VALUE s_AtomRef_GetResName(VALUE self) { char *p = s_AtomFromValue(self)->resName; - return rb_str_new(p, strlen_limit(p, 4)); + return Ruby_NewEncodedStringValue(p, strlen_limit(p, 4)); } static VALUE s_AtomRef_GetName(VALUE self) { char *p = s_AtomFromValue(self)->aname; - return rb_str_new(p, strlen_limit(p, 4)); + return Ruby_NewEncodedStringValue(p, strlen_limit(p, 4)); } static VALUE s_AtomRef_GetAtomType(VALUE self) { int type = s_AtomFromValue(self)->type; char *p = (type == 0 ? "" : AtomTypeDecodeToString(type, NULL)); - return rb_str_new(p, strlen_limit(p, 6)); + return Ruby_NewEncodedStringValue(p, strlen_limit(p, 6)); } static VALUE s_AtomRef_GetCharge(VALUE self) { @@ -3729,7 +3915,7 @@ static VALUE s_AtomRef_GetWeight(VALUE self) { static VALUE s_AtomRef_GetElement(VALUE self) { char *p = s_AtomFromValue(self)->element; - return rb_str_new(p, strlen_limit(p, 4)); + return Ruby_NewEncodedStringValue(p, strlen_limit(p, 4)); } static VALUE s_AtomRef_GetAtomicNumber(VALUE self) { @@ -3842,6 +4028,18 @@ static VALUE s_AtomRef_GetAniso(VALUE self) { return retval; } +static VALUE s_AtomRef_GetAnisoEigenValues(VALUE self) { + VALUE retval; + int i; + Atom *ap = s_AtomFromValue(self); + if (ap->aniso == NULL) + return Qnil; + retval = rb_ary_new(); + for (i = 0; i < 3; i++) + rb_ary_push(retval, rb_float_new(ap->aniso->eigval[i])); + return retval; +} + static VALUE s_AtomRef_GetSymop(VALUE self) { VALUE retval; Atom *ap = s_AtomFromValue(self); @@ -3930,7 +4128,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) { @@ -4281,6 +4479,11 @@ static VALUE s_AtomRef_SetAniso(VALUE self, VALUE val) { return val; } +static VALUE s_AtomRef_SetAnisoEigenValues(VALUE self, VALUE val) { + rb_raise(rb_eMolbyError, "Eigenvalues of anisotropic factors are read-only."); + return val; /* Not reached */ +} + static VALUE s_AtomRef_SetSymop(VALUE self, VALUE val) { Molecule *mol; Atom *ap; @@ -4515,6 +4718,7 @@ static struct s_AtomAttrDef { {"occupancy", &s_OccupancySym, 0, s_AtomRef_GetOccupancy, s_AtomRef_SetOccupancy}, {"temp_factor", &s_TempFactorSym, 0, s_AtomRef_GetTempFactor, s_AtomRef_SetTempFactor}, {"aniso", &s_AnisoSym, 0, s_AtomRef_GetAniso, s_AtomRef_SetAniso}, + {"aniso_eigenvalues", &s_AnisoEigvalSym, 0, s_AtomRef_GetAnisoEigenValues, s_AtomRef_SetAnisoEigenValues}, {"symop", &s_SymopSym, 0, s_AtomRef_GetSymop, s_AtomRef_SetSymop}, {"int_charge", &s_IntChargeSym, 0, s_AtomRef_GetIntCharge, s_AtomRef_SetIntCharge}, {"fix_force", &s_FixForceSym, 0, s_AtomRef_GetFixForce, s_AtomRef_SetFixForce}, @@ -4624,7 +4828,7 @@ s_MolEnumerable_Aref(VALUE self, VALUE arg1) if (idx2 < 0 || idx2 >= mol->nresidues) rb_raise(rb_eIndexError, "residue index out of range (%d; should be %d..%d)", idx1, -mol->nresidues, mol->nresidues - 1); p = mol->residues[idx2]; - return rb_str_new(p, strlen_limit(p, 4)); + return Ruby_NewEncodedStringValue(p, strlen_limit(p, 4)); } } return Qnil; @@ -4712,7 +4916,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; @@ -4829,26 +5035,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 @@ -4869,6 +5066,56 @@ s_Molecule_InitCopy(VALUE self, VALUE arg) /* * call-seq: + * atom_index(val) -> Integer + * + * Returns the atom index represented by val. val can be either a non-negative integer + * (directly representing the atom index), a negative integer (representing 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. @@ -4886,7 +5133,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; } @@ -4913,13 +5160,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; } @@ -4944,7 +5191,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; } @@ -4967,7 +5214,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; } @@ -4990,7 +5237,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; } @@ -5013,7 +5260,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; } @@ -5036,7 +5283,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; } @@ -5061,7 +5308,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; } @@ -5081,7 +5328,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; } @@ -5101,7 +5348,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; } @@ -5121,7 +5368,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; } @@ -5141,36 +5388,15 @@ s_Molecule_Savedcd(VALUE self, VALUE fname) MoleculeClearLoadSaveErrorMessage(); fstr = FileStringValuePtr(fname); retval = MoleculeWriteToDcdFile(mol, fstr, &gLoadSaveErrorMessage); - s_Molecule_RaiseOnLoadSave(retval, "Failed to save dcd", fstr); - return Qtrue; -} - -/* - * call-seq: - * savetep(file) -> bool - * - * Write coordinates as an ORTEP file. Returns true if successful. - */ -/* -static VALUE -s_Molecule_Savetep(VALUE self, VALUE fname) -{ - char *fstr; - Molecule *mol; - char errbuf[128]; - Data_Get_Struct(self, Molecule, mol); - fstr = FileStringValuePtr(fname); - if (MoleculeWriteToTepFile(mol, fstr, errbuf, sizeof errbuf) != 0) - rb_raise(rb_eMolbyError, errbuf); + s_Molecule_RaiseOnLoadSave(retval, 0, "Failed to save dcd", fstr); return Qtrue; } -*/ /* load([ftype, ] fname, ...) */ static VALUE s_Molecule_LoadSave(int argc, VALUE *argv, VALUE self, int loadFlag) { - VALUE rval; + VALUE rval, argv0; char *argstr, *methname, *p, *type = ""; ID mid = 0; int i; @@ -5180,7 +5406,8 @@ s_Molecule_LoadSave(int argc, VALUE *argv, VALUE self, int loadFlag) if (argc == 0) return Qnil; - if (argc == 0 || (argstr = StringValuePtr(argv[0])) == NULL) + argv0 = argv[0]; /* Keep as a local variable to avoid GC */ + if (argc == 0 || (argstr = StringValuePtr(argv0)) == NULL) rb_raise(rb_eMolbyError, "the first argument must be either filename or \":filetype\""); if (argstr[0] == ':') { /* Call "loadXXX" (or "saveXXX") for type ":XXX" */ @@ -5235,9 +5462,9 @@ s_Molecule_LoadSave(int argc, VALUE *argv, VALUE self, int loadFlag) } } failure: - rval = rb_str_to_str(argv[0]); + rval = rb_str_to_str(argv0); 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: @@ -5246,7 +5473,7 @@ success: Molecule *mol; /* Atom *ap; */ Data_Get_Struct(self, Molecule, mol); - MoleculeSetPath(mol, StringValuePtr(argv[0])); + MoleculeSetPath(mol, StringValuePtr(argv0)); /* Check if all occupancy factors are zero; if that is the case, then all set to 1.0 */ /* for (i = 0, ap = mol->atoms; i < mol->natoms; i++, ap = ATOM_NEXT(ap)) { @@ -5291,6 +5518,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 @@ -5306,7 +5639,7 @@ s_Molecule_Name(VALUE self) if (buf[0] == 0) return Qnil; else - return rb_str_new2(buf); + return Ruby_NewEncodedStringValue2(buf); } /* @@ -5370,7 +5703,7 @@ s_Molecule_Dir(VALUE self) p = strrchr(buf, '/'); if (p != NULL) *p = 0; - return rb_str_new2(buf); + return Ruby_NewEncodedStringValue2(buf); } } @@ -5392,7 +5725,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]; @@ -5411,55 +5744,11 @@ s_Molecule_Inspect(VALUE self) } else { snprintf(buf2, sizeof buf2, "Molecule[\"%s\"]", buf); } - return rb_str_new2(buf2); + return Ruby_NewEncodedStringValue2(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 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) @@ -5631,327 +5920,6 @@ 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 - * - * Returns the MD parameter for the idx-th bond. - */ -/* -static VALUE -s_Molecule_BondPar(VALUE self, VALUE val) -{ - Molecule *mol; - BondPar *bp; - UInt t1, t2; - Int i1, i2; - Int ival; - Data_Get_Struct(self, Molecule, mol); - ival = NUM2INT(rb_Integer(val)); - if (ival < -mol->nbonds || ival >= mol->nbonds) - rb_raise(rb_eMolbyError, "bond index (%d) out of range", ival); - if (ival < 0) - ival += mol->nbonds; - s_RebuildMDParameterIfNecessary(self, Qtrue); - i1 = mol->bonds[ival * 2]; - i2 = mol->bonds[ival * 2 + 1]; - t1 = ATOM_AT_INDEX(mol->atoms, i1)->type; - t2 = ATOM_AT_INDEX(mol->atoms, i2)->type; - bp = ParameterLookupBondPar(mol->par, t1, t2, i1, i2, 0); - if (bp == NULL) - return Qnil; - return ValueFromMoleculeWithParameterTypeAndIndex(mol, kBondParType, bp - mol->par->bondPars); -} -*/ - -/* - * call-seq: - * angle_par(idx) -> ParameterRef - * - * Returns the MD parameter for the idx-th angle. - */ -/* -static VALUE -s_Molecule_AnglePar(VALUE self, VALUE val) -{ - Molecule *mol; - AnglePar *ap; - UInt t1, t2, t3; - Int i1, i2, i3; - Int ival; - Data_Get_Struct(self, Molecule, mol); - ival = NUM2INT(rb_Integer(val)); - if (ival < -mol->nangles || ival >= mol->nangles) - rb_raise(rb_eMolbyError, "angle index (%d) out of range", ival); - if (ival < 0) - ival += mol->nangles; - s_RebuildMDParameterIfNecessary(self, Qtrue); - i1 = mol->angles[ival * 3]; - i2 = mol->angles[ival * 3 + 1]; - i3 = mol->angles[ival * 3 + 2]; - t1 = ATOM_AT_INDEX(mol->atoms, i1)->type; - t2 = ATOM_AT_INDEX(mol->atoms, i2)->type; - t3 = ATOM_AT_INDEX(mol->atoms, i3)->type; - ap = ParameterLookupAnglePar(mol->par, t1, t2, t3, i1, i2, i3, 0); - if (ap == NULL) - return Qnil; - return ValueFromMoleculeWithParameterTypeAndIndex(mol, kAngleParType, ap - mol->par->anglePars); -} -*/ -/* - * call-seq: - * dihedral_par(idx) -> ParameterRef - * - * Returns the MD parameter for the idx-th dihedral. - */ -/* -static VALUE -s_Molecule_DihedralPar(VALUE self, VALUE val) -{ - Molecule *mol; - Int ival; - TorsionPar *tp; - UInt t1, t2, t3, t4; - Int i1, i2, i3, i4; - Data_Get_Struct(self, Molecule, mol); - ival = NUM2INT(rb_Integer(val)); - if (ival < -mol->ndihedrals || ival >= mol->ndihedrals) - rb_raise(rb_eMolbyError, "dihedral index (%d) out of range", ival); - if (ival < 0) - ival += mol->ndihedrals; - s_RebuildMDParameterIfNecessary(self, Qtrue); - i1 = mol->dihedrals[ival * 4]; - i2 = mol->dihedrals[ival * 4 + 1]; - i3 = mol->dihedrals[ival * 4 + 2]; - i4 = mol->dihedrals[ival * 4 + 3]; - t1 = ATOM_AT_INDEX(mol->atoms, i1)->type; - t2 = ATOM_AT_INDEX(mol->atoms, i2)->type; - t3 = ATOM_AT_INDEX(mol->atoms, i3)->type; - t4 = ATOM_AT_INDEX(mol->atoms, i4)->type; - tp = ParameterLookupDihedralPar(mol->par, t1, t2, t3, t4, i1, i2, i3, i4, 0); - if (tp == NULL) - return Qnil; - return ValueFromMoleculeWithParameterTypeAndIndex(mol, kDihedralParType, tp - mol->par->dihedralPars); -} -*/ -/* - * call-seq: - * improper_par(idx) -> ParameterRef - * - * Returns the MD parameter for the idx-th improper. - */ -/* -static VALUE -s_Molecule_ImproperPar(VALUE self, VALUE val) -{ - Molecule *mol; - Int ival; - TorsionPar *tp; - UInt t1, t2, t3, t4; - Int i1, i2, i3, i4; - Data_Get_Struct(self, Molecule, mol); - ival = NUM2INT(rb_Integer(val)); - if (ival < -mol->nimpropers || ival >= mol->nimpropers) - rb_raise(rb_eMolbyError, "improper index (%d) out of range", ival); - if (ival < 0) - ival += mol->nimpropers; - s_RebuildMDParameterIfNecessary(self, Qtrue); - i1 = mol->impropers[ival * 4]; - i2 = mol->impropers[ival * 4 + 1]; - i3 = mol->impropers[ival * 4 + 2]; - i4 = mol->impropers[ival * 4 + 3]; - t1 = ATOM_AT_INDEX(mol->atoms, i1)->type; - t2 = ATOM_AT_INDEX(mol->atoms, i2)->type; - t3 = ATOM_AT_INDEX(mol->atoms, i3)->type; - t4 = ATOM_AT_INDEX(mol->atoms, i4)->type; - tp = ParameterLookupImproperPar(mol->par, t1, t2, t3, t4, i1, i2, i3, i4, 0); - if (tp == NULL) - return Qnil; - return ValueFromMoleculeWithParameterTypeAndIndex(mol, kImproperParType, tp - mol->par->improperPars); -} -*/ - -/* - * call-seq: - * start_step -> Integer - * - * Returns the start step (defined by dcd format). - */ -static VALUE -s_Molecule_StartStep(VALUE self) -{ - Molecule *mol; - Data_Get_Struct(self, Molecule, mol); - return INT2NUM(mol->startStep); -} - -/* - * call-seq: - * start_step = Integer - * - * Set the start step (defined by dcd format). - */ -static VALUE -s_Molecule_SetStartStep(VALUE self, VALUE val) -{ - Molecule *mol; - Data_Get_Struct(self, Molecule, mol); - mol->startStep = NUM2INT(rb_Integer(val)); - return val; -} - -/* - * call-seq: - * steps_per_frame -> Integer - * - * Returns the number of steps between frames (defined by dcd format). - */ -static VALUE -s_Molecule_StepsPerFrame(VALUE self) -{ - Molecule *mol; - Data_Get_Struct(self, Molecule, mol); - return INT2NUM(mol->stepsPerFrame); -} - -/* - * call-seq: - * steps_per_frame = Integer - * - * Set the number of steps between frames (defined by dcd format). - */ -static VALUE -s_Molecule_SetStepsPerFrame(VALUE self, VALUE val) -{ - Molecule *mol; - Data_Get_Struct(self, Molecule, mol); - mol->stepsPerFrame = NUM2INT(rb_Integer(val)); - return val; -} - -/* - * call-seq: - * ps_per_step -> Float - * - * Returns the time increment (in picoseconds) for one step (defined by dcd format). - */ -static VALUE -s_Molecule_PsPerStep(VALUE self) -{ - Molecule *mol; - Data_Get_Struct(self, Molecule, mol); - return rb_float_new(mol->psPerStep); -} - -/* - * call-seq: - * ps_per_step = Float - * - * Set the time increment (in picoseconds) for one step (defined by dcd format). - */ -static VALUE -s_Molecule_SetPsPerStep(VALUE self, VALUE val) -{ - Molecule *mol; - Data_Get_Struct(self, Molecule, mol); - mol->psPerStep = NUM2DBL(rb_Float(val)); - return val; -} - -/* - * call-seq: - * find_angles -> Integer - * - * Find the angles from the bonds. Returns the number of angles newly created. - */ -/* -static VALUE -s_Molecule_FindAngles(VALUE self) -{ - Molecule *mol; - Atom *ap; - int n1, i, j, nc; - Int *ip, nip, n[3]; - Data_Get_Struct(self, Molecule, mol); - if (mol == NULL || mol->natoms == 0) - return INT2NUM(0); - ip = NULL; - nip = 0; - for (n1 = 0, ap = mol->atoms; n1 < mol->natoms; n1++, ap = ATOM_NEXT(ap)) { - nc = ap->connect.count; - n[1] = n1; - for (i = 0; i < nc; i++) { - n[0] = ap->connects[i]; - for (j = i + 1; j < nc; j++) { - n[2] = ap->connects[j]; - if (MoleculeLookupAngle(mol, n[0], n[1], n[2]) < 0) - AssignArray(&ip, &nip, sizeof(Int) * 3, nip, n); - } - } - } - if (nip > 0) { - MolActionCreateAndPerform(mol, gMolActionAddAngles, nip * 3, ip, NULL); - free(ip); - } - return INT2NUM(nip); -} -*/ -/* - * call-seq: - * find_dihedrals -> Integer - * - * Find the dihedrals from the bonds. Returns the number of dihedrals newly created. - */ -/* -static VALUE -s_Molecule_FindDihedrals(VALUE self) -{ - Molecule *mol; - Atom *ap1, *ap2; - int n1, i, j, k, nc1, nc2; - Int *ip, nip, n[4]; - Data_Get_Struct(self, Molecule, mol); - if (mol == NULL || mol->natoms == 0) - return INT2NUM(0); - ip = NULL; - nip = 0; - for (n1 = 0, ap1 = mol->atoms; n1 < mol->natoms; n1++, ap1 = ATOM_NEXT(ap1)) { - nc1 = ap1->connect.count; - n[1] = n1; - for (i = 0; i < nc1; i++) { - n[2] = ap1->connects[i]; - if (n[1] > n[2]) - continue; - ap2 = ATOM_AT_INDEX(mol->atoms, n[2]); - nc2 = ap2->connect.count; - for (j = 0; j < nc1; j++) { - n[0] = ap1->connects[j]; - if (n[0] == n[2]) - continue; - for (k = 0; k < nc2; k++) { - n[3] = ap2->connects[k]; - if (n[3] == n1 || n[3] == n[0]) - continue; - if (MoleculeLookupDihedral(mol, n[0], n[1], n[2], n[3]) < 0) - AssignArray(&ip, &nip, sizeof(Int) * 4, nip, n); - } - } - } - } - if (nip > 0) { - MolActionCreateAndPerform(mol, gMolActionAddDihedrals, nip * 4, ip, NULL); - free(ip); - } - return INT2NUM(nip); -} -*/ - /* * call-seq: * nresidues = Integer @@ -6045,1187 +6013,1252 @@ s_Molecule_EachAtom(int argc, VALUE *argv, VALUE self) return self; } +#pragma mark ------ Atom Group ------ + +static VALUE +s_Molecule_AtomGroup_i(VALUE arg, VALUE values) +{ + Molecule *mol = (Molecule *)(((VALUE *)values)[0]); + IntGroup *ig1 = (IntGroup *)(((VALUE *)values)[1]); + int idx = s_Molecule_AtomIndexFromValue(mol, arg); + IntGroup_RaiseIfError(IntGroupAdd(ig1, idx, 1)); + return Qnil; +} + /* * call-seq: - * cell -> [a, b, c, alpha, beta, gamma [, sig_a, sig_b, sig_c, sig_alpha, sig_beta, sig_gamma]] + * atom_group + * atom_group {|aref| ...} + * atom_group(arg1, arg2, ...) + * atom_group(arg1, arg2, ...) {|aref| ...} + * + * Specify a group of atoms. If no arguments are given, IntGroup\[0...natoms] is the result. + * If arguments are given, then the atoms reprensented by the arguments are added to the + * group. For a conversion of a string to an atom index, see the description + * of Molecule#atom_index. + * If a block is given, it is evaluated with an AtomRef (not atom index integers) + * representing each atom, and the atoms are removed from the result if the block returns false. * - * Returns the unit cell parameters. If cell is not set, returns nil. */ static VALUE -s_Molecule_Cell(VALUE self) +s_Molecule_AtomGroup(int argc, VALUE *argv, VALUE self) { + IntGroup *ig1, *ig2; Molecule *mol; - int i; - VALUE val; + Int i, startPt, interval; + VALUE retval = IntGroup_Alloc(rb_cIntGroup); + Data_Get_Struct(retval, IntGroup, ig1); 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 (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++; } } - return val; + 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: - * 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) + * selection -> IntGroup * - * 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. + * Returns the current selection. */ static VALUE -s_Molecule_SetCell(int argc, VALUE *argv, VALUE self) +s_Molecule_Selection(VALUE self) { Molecule *mol; - VALUE val, cval; - int i, convert_coord, n; - double d[12]; + IntGroup *ig; + VALUE val; Data_Get_Struct(self, Molecule, mol); - rb_scan_args(argc, argv, "11", &val, &cval); - if (val == Qnil) { - n = 0; + 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 { - 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])); + val = IntGroup_Alloc(rb_cIntGroup); } - convert_coord = (RTEST(cval) ? 1 : 0); - MolActionCreateAndPerform(mol, gMolActionSetCell, n, d, convert_coord); return val; } -/* - * call-seq: - * box -> [avec, bvec, cvec, origin, flags] - * - * 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_Box(VALUE self) +s_Molecule_SetSelectionSub(VALUE self, VALUE val, int undoable) { Molecule *mol; - VALUE v[5], val; + IntGroup *ig; Data_Get_Struct(self, Molecule, mol); - if (mol == NULL || mol->cell == NULL) - return Qnil; - v[0] = ValueFromVector(&(mol->cell->axes[0])); - v[1] = ValueFromVector(&(mol->cell->axes[1])); - v[2] = ValueFromVector(&(mol->cell->axes[2])); - v[3] = ValueFromVector(&(mol->cell->origin)); - v[4] = rb_ary_new3(3, INT2NUM(mol->cell->flags[0]), INT2NUM(mol->cell->flags[1]), INT2NUM(mol->cell->flags[2])); - val = rb_ary_new4(5, v); + if (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: - * 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 + * selection = IntGroup * - * 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. + * 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_SetBox(VALUE self, VALUE aval) +s_Molecule_SetSelection(VALUE self, VALUE val) { - Molecule *mol; - VALUE v[6]; - static Vector ax[3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; - Vector vv[3]; - Vector origin = {0, 0, 0}; - char flags[3]; - Double d; - int i, convertCoordinates = 0; - Data_Get_Struct(self, Molecule, mol); - if (aval == Qnil) { - MolActionCreateAndPerform(mol, gMolActionClearBox); - return self; - } - aval = rb_ary_to_ary(aval); - for (i = 0; i < 6; i++) { - if (i < RARRAY_LEN(aval)) - v[i] = (RARRAY_PTR(aval))[i]; - else v[i] = Qnil; - } - if (v[0] == Qnil) { - MolActionCreateAndPerform(mol, gMolActionClearBox); - return self; - } - if ((v[1] == Qnil || v[2] == Qnil) && rb_obj_is_kind_of(v[0], rb_cNumeric)) { - d = NUM2DBL(rb_Float(v[0])); - for (i = 0; i < 3; i++) - VecScale(vv[i], ax[i], d); - if (v[1] != Qnil) - VectorFromValue(v[1], &origin); - flags[0] = flags[1] = flags[2] = 1; - } else { - for (i = 0; i < 3; i++) { - if (v[i] == Qnil) { - VecZero(vv[i]); - } else if (rb_obj_is_kind_of(v[i], rb_cNumeric)) { - d = NUM2DBL(rb_Float(v[i])); - VecScale(vv[i], ax[i], d); - } else { - VectorFromValue(v[i], &vv[i]); - } - flags[i] = (VecLength2(vv[i]) > 0.0); - } - if (v[3] != Qnil) - VectorFromValue(v[3], &origin); - if (v[4] != Qnil) { - for (i = 0; i < 3; i++) { - VALUE val = Ruby_ObjectAtIndex(v[4], i); - flags[i] = (NUM2INT(rb_Integer(val)) != 0); - } - } - if (RTEST(v[5])) - convertCoordinates = 1; - } - MolActionCreateAndPerform(mol, gMolActionSetBox, &(vv[0]), &(vv[1]), &(vv[2]), &origin, (flags[0] * 4 + flags[1] * 2 + flags[2]), convertCoordinates); - return self; + return s_Molecule_SetSelectionSub(self, val, 0); } /* * call-seq: - * cell_periodicity -> [n1, n2, n3] + * set_undoable_selection(IntGroup) * - * Get flags denoting whether the cell is periodic along the a/b/c axes. If the cell is not defined - * nil is returned. + * Set the current selection with undo registration. The right-hand operand may be nil. + * This operation is undoable. */ static VALUE -s_Molecule_CellPeriodicity(VALUE self) +s_Molecule_SetUndoableSelection(VALUE self, VALUE val) { - Molecule *mol; - Data_Get_Struct(self, Molecule, mol); - if (mol->cell == NULL) - return Qnil; - return rb_ary_new3(3, INT2FIX((int)mol->cell->flags[0]), INT2FIX((int)mol->cell->flags[1]), INT2FIX((int)mol->cell->flags[2])); + return s_Molecule_SetSelectionSub(self, val, 1); } +#pragma mark ------ Editing ------ + /* * call-seq: - * self.cell_periodicity = [n1, n2, n3] or Integer or nil - * set_cell_periodicity = [n1, n2, n3] or Integer or nil + * extract(group, dummy_flag = nil) -> Molecule * - * Set whether the cell is periodic along the a/b/c axes. If an integer is given as an argument, - * its bits 2/1/0 (from the lowest) correspond to the a/b/c axes. Nil is equivalent to [0, 0, 0]. - * If cell is not defined, exception is raised. - * This operation is undoable. + * 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_SetCellPeriodicity(VALUE self, VALUE arg) +s_Molecule_Extract(int argc, VALUE *argv, VALUE self) { - 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)); - } + 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); } - MolActionCreateAndPerform(mol, gMolActionSetCellPeriodicity, flag); - return arg; + IntGroupRelease(ig); + return retval; } /* * call-seq: - * cell_flexibility -> bool + * add(molecule2) -> self * - * Returns the unit cell is flexible or not + * Combine two molecules. The residue numbers of the newly added atoms may be renumbered to avoid + conflicts. + This operation is undoable. */ static VALUE -s_Molecule_CellFlexibility(VALUE self) +s_Molecule_Add(VALUE self, VALUE val) { - rb_warn("cell_flexibility is obsolete (unit cell is always frame dependent)"); - return Qtrue; -/* Molecule *mol; - Data_Get_Struct(self, Molecule, mol); - if (mol->cell == NULL) - return Qfalse; - if (mol->useFlexibleCell) - return Qtrue; - else return Qfalse; */ + Molecule *mol1, *mol2; + Data_Get_Struct(self, Molecule, mol1); + mol2 = MoleculeFromValue(val); + MolActionCreateAndPerform(mol1, gMolActionMergeMolecule, mol2, NULL); + return self; } /* * call-seq: - * self.cell_flexibility = bool - * set_cell_flexibility(bool) + * remove(group) -> Molecule * - * Change the unit cell is flexible or not + * The atoms designated by the given group are removed from the molecule. + * This operation is undoable. */ static VALUE -s_Molecule_SetCellFlexibility(VALUE self, VALUE arg) +s_Molecule_Remove(VALUE self, VALUE group) { - rb_warn("set_cell_flexibility is obsolete (unit cell is always frame dependent)"); + 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); + } + } + } + } + IntGroupIteratorRelease(&iter); + if (bg != NULL) { + /* Remove bonds */ + MolActionCreateAndPerform(mol1, gMolActionDeleteBonds, bg); + IntGroupRelease(bg); + } + /* Remove atoms */ + if (MolActionCreateAndPerform(mol1, gMolActionUnmergeMolecule, ig) == 0) + return Qnil; return self; -/* Molecule *mol; - Data_Get_Struct(self, Molecule, mol); - MolActionCreateAndPerform(mol, gMolActionSetCellFlexibility, RTEST(arg) != 0); - return self; */ } /* * call-seq: - * cell_transform -> Transform + * create_atom(name, pos = -1) -> AtomRef * - * Get the transform matrix that converts internal coordinates to cartesian coordinates. - * If cell is not defined, nil is returned. + * 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_CellTransform(VALUE self) +s_Molecule_CreateAnAtom(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; Data_Get_Struct(self, Molecule, mol); - if (mol == NULL || mol->cell == NULL) + 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; - return ValueFromTransform(&(mol->cell->tr)); + aref = AtomRefNew(mol, pos); + return Data_Wrap_Struct(rb_cAtomRef, 0, (void (*)(void *))AtomRefRelease, aref); } /* * call-seq: - * symmetry -> Array of Transforms - * symmetries -> Array of Transforms + * duplicate_atom(atomref, pos = -1) -> AtomRef * - * Get the currently defined symmetry operations. If no symmetry operation is defined, - * returns an empty array. + * 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_Symmetry(VALUE self) +s_Molecule_DuplicateAnAtom(int argc, VALUE *argv, VALUE self) { Molecule *mol; - VALUE val; - int i; + const Atom *apsrc; + Atom arec; + AtomRef *aref; + VALUE retval, aval, ival; + Int pos; 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])); + 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); } - return val; + 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: - * nsymmetries -> Integer + * create_bond(n1, n2, ...) -> Integer * - * Get the number of currently defined symmetry operations. + * 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_Nsymmetries(VALUE self) +s_Molecule_CreateBond(int argc, VALUE *argv, VALUE self) { 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); - return INT2NUM(mol->nsyms); + 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: - * add_symmetry(Transform) -> Integer + * molecule.remove_bonds(n1, n2, ...) -> Integer * - * Add a new symmetry operation. If no symmetry operation is defined and the - * given argument is not an identity transform, then also add an identity - * transform at the index 0. - * Returns the total number of symmetries after operation. + * 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_AddSymmetry(VALUE self, VALUE trans) +s_Molecule_RemoveBond(int argc, VALUE *argv, VALUE self) { Molecule *mol; - Transform tr; + 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); - TransformFromValue(trans, &tr); - MolActionCreateAndPerform(mol, gMolActionAddSymmetryOperation, &tr); - return INT2NUM(mol->nsyms); + 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: - * remove_symmetry(count = nil) -> Integer - * remove_symmetries(count = nil) -> Integer + * assign_bond_order(idx, d1) + * assign_bond_orders(group, [d1, d2, ...]) * - * 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. + * 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_RemoveSymmetry(int argc, VALUE *argv, VALUE self) +s_Molecule_AssignBondOrder(VALUE self, VALUE idxval, VALUE dval) { Molecule *mol; - VALUE cval; - int i, n; + IntGroup *ig; 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; + 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); } - for (i = 0; i < n; i++) - MolActionCreateAndPerform(mol, gMolActionDeleteSymmetryOperation); - return INT2NUM(mol->nsyms); -} - -static VALUE -s_Molecule_AtomGroup_i(VALUE arg, VALUE values) -{ - Molecule *mol = (Molecule *)(((VALUE *)values)[0]); - IntGroup *ig1 = (IntGroup *)(((VALUE *)values)[1]); - int idx = s_Molecule_AtomIndexFromValue(mol, arg); - IntGroup_RaiseIfError(IntGroupAdd(ig1, idx, 1)); - return Qnil; + return self; } /* * 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. + * 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_AtomGroup(int argc, VALUE *argv, VALUE self) +s_Molecule_GetBondOrder(VALUE self, VALUE idxval) { - IntGroup *ig1, *ig2; Molecule *mol; - Int i, startPt, interval; - VALUE retval = IntGroup_Alloc(rb_cIntGroup); - Data_Get_Struct(retval, IntGroup, ig1); + IntGroup *ig; + Double *dp; + VALUE retval; + Int i, n, numericArg; Data_Get_Struct(self, Molecule, mol); - if (argc == 0) { - IntGroup_RaiseIfError(IntGroupAdd(ig1, 0, mol->natoms)); + 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 { - 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 (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; } - 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); + 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])); } - - /* Remove points that are out of bounds */ - IntGroup_RaiseIfError(IntGroupRemove(ig1, mol->natoms, INT_MAX)); - - return retval; + free(dp); + IntGroupRelease(ig); + return retval; } /* * call-seq: - * atom_index(val) -> Integer + * bond_exist?(idx1, idx2) -> 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 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_AtomIndex(VALUE self, VALUE val) +s_Molecule_BondExist(VALUE self, VALUE ival1, VALUE ival2) { - Molecule *mol; + Molecule *mol; + Int idx1, idx2, i; + Atom *ap; + Int *cp; Data_Get_Struct(self, Molecule, mol); - return INT2NUM(s_Molecule_AtomIndexFromValue(mol, 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: - * extract(group, dummy_flag = nil) -> Molecule + * add_angle(n1, n2, n3) -> Molecule * - * Extract the atoms given by group and return as a new molecule object. - * If dummy_flag is true, then the atoms that are not included in the group but are connected - * to any atoms in the group are converted to "dummy" atoms (i.e. with element "Du" and - * names beginning with an underscore) and included in the new molecule object. + * 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_Extract(int argc, VALUE *argv, VALUE self) +s_Molecule_AddAngle(VALUE self, VALUE v1, VALUE v2, VALUE v3) { - Molecule *mol1, *mol2; - IntGroup *ig; - VALUE group, dummy_flag, retval; - Data_Get_Struct(self, Molecule, mol1); - rb_scan_args(argc, argv, "11", &group, &dummy_flag); - ig = s_Molecule_AtomGroupFromValue(self, group); - if (MoleculeExtract(mol1, &mol2, ig, (dummy_flag != Qnil && dummy_flag != Qfalse)) != 0) { - retval = Qnil; - } else { - retval = ValueFromMolecule(mol2); - } - IntGroupRelease(ig); - return retval; + 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: - * add(molecule2) -> self + * remove_angle(n1, n2, n3) -> Molecule * - * Combine two molecules. The residue numbers of the newly added atoms may be renumbered to avoid - conflicts. - This operation is undoable. + * 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_Add(VALUE self, VALUE val) +s_Molecule_RemoveAngle(VALUE self, VALUE v1, VALUE v2, VALUE v3) { - Molecule *mol1, *mol2; - Data_Get_Struct(self, Molecule, mol1); - mol2 = MoleculeFromValue(val); - MolActionCreateAndPerform(mol1, gMolActionMergeMolecule, mol2, NULL); - return self; + 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: - * remove(group) -> Molecule + * add_dihedral(n1, n2, n3, n4) -> Molecule * - * The atoms designated by the given group are removed from the 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_Remove(VALUE self, VALUE group) +s_Molecule_AddDihedral(VALUE self, VALUE v1, VALUE v2, VALUE v3, VALUE v4) { - 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; + 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: - * create_atom(name, pos = -1) -> AtomRef + * remove_dihedral(n1, n2, n3, n4) -> Molecule * - * 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. + * 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_CreateAnAtom(int argc, VALUE *argv, VALUE self) +s_Molecule_RemoveDihedral(VALUE self, VALUE v1, VALUE v2, VALUE v3, VALUE v4) { + Int n[5]; Molecule *mol; - Int i, pos; - VALUE name, ival; - Atom arec; - AtomRef *aref; - char *p, resName[6], atomName[6]; - int resSeq; + IntGroup *ig; 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; - } - 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); + 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: - * duplicate_atom(atomref, pos = -1) -> AtomRef + * add_improper(n1, n2, n3, n4) -> Molecule * - * Create a new atom with the same attributes (but no bonding information) - * with the specified atom. Returns the reference to the new atom. + * 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_DuplicateAnAtom(int argc, VALUE *argv, VALUE self) +s_Molecule_AddImproper(VALUE self, VALUE v1, VALUE v2, VALUE v3, VALUE v4) { + Int n[5]; Molecule *mol; - const Atom *apsrc; - Atom arec; - AtomRef *aref; - VALUE retval, aval, ival; - Int pos; Data_Get_Struct(self, Molecule, mol); - rb_scan_args(argc, argv, "11", &aval, &ival); - if (FIXNUM_P(aval)) { - int idx = NUM2INT(aval); - if (idx < 0 || idx >= mol->natoms) - rb_raise(rb_eMolbyError, "atom index out of range: %d", idx); - apsrc = ATOM_AT_INDEX(mol->atoms, idx); - } 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; + 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: - * create_bond(n1, n2, ...) -> Integer + * remove_improper(n1, n2, n3, n4) -> Molecule + * remove_improper(intgroup) -> Molecule * - * 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. + * 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_CreateBond(int argc, VALUE *argv, VALUE self) +s_Molecule_RemoveImproper(int argc, VALUE *argv, VALUE self) { + Int n[5]; + VALUE v1, v2, v3, v4; 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"); + IntGroup *ig; 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; - } - } - } - } + 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); } - 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); + MolActionCreateAndPerform(mol, gMolActionDeleteImpropers, ig); + IntGroupRelease(ig); + return self; } /* * call-seq: - * molecule.remove_bonds(n1, n2, ...) -> Integer + * assign_residue(group, res) -> Molecule * - * 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. + * 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_RemoveBond(int argc, VALUE *argv, VALUE self) +s_Molecule_AssignResidue(VALUE self, VALUE range, VALUE res) { 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"); + IntGroup *ig; + char *p, *pp, buf[16]; + Int resid, n; + Atom *ap; 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); - } + + /* 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 (bg != NULL) { - MolActionCreateAndPerform(mol, gMolActionDeleteBonds, bg); - i = IntGroupGetCount(bg); - IntGroupRelease(bg); - } else i = 0; - return INT2NUM(i); + 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: - * assign_bond_order(idx, d1) - * assign_bond_orders(group, [d1, d2, ...]) + * offset_residue(group, offset) -> Molecule * - * 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) + * 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_AssignBondOrder(VALUE self, VALUE idxval, VALUE dval) +s_Molecule_OffsetResidue(VALUE self, VALUE range, VALUE offset) { Molecule *mol; IntGroup *ig; + int ofs, result; 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); - } + 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: - * get_bond_order(idx) -> Float - * get_bond_orders(group) -> Array + * renumber_atoms(array) -> IntGroup * - * 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). + * 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_GetBondOrder(VALUE self, VALUE idxval) +s_Molecule_RenumberAtoms(VALUE self, VALUE array) { Molecule *mol; + Int *new2old; IntGroup *ig; - Double *dp; - VALUE retval; - Int i, n, numericArg; + int i, n; + VALUE *valp, retval; 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); + 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: - * bond_exist?(idx1, idx2) -> bool + * set_atom_attr(index, key, value) * - * 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. + * Set the atom attribute for the specified atom. + * This operation is undoable. */ static VALUE -s_Molecule_BondExist(VALUE self, VALUE ival1, VALUE ival2) +s_Molecule_SetAtomAttr(VALUE self, VALUE idx, VALUE key, VALUE val) { Molecule *mol; - Int idx1, idx2, i; - Atom *ap; - Int *cp; + VALUE aref, oldval; Data_Get_Struct(self, Molecule, mol); - idx1 = NUM2INT(rb_Integer(ival1)); - idx2 = NUM2INT(rb_Integer(ival2)); - if (idx1 < 0 || idx1 >= mol->natoms || idx2 < 0 || idx2 >= mol->natoms) - rb_raise(rb_eMolbyError, "Atom index (%d or %d) out of range", idx1, idx2); - ap = ATOM_AT_INDEX(mol->atoms, idx1); - cp = AtomConnectData(&ap->connect); - for (i = 0; i < ap->connect.count; i++) { - if (cp[i] == idx2) - return Qtrue; - } - return Qfalse; + 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: - * add_angle(n1, n2, n3) -> Molecule + * get_atom_attr(index, key) * - * 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. + * Get the atom attribute for the specified atom. */ static VALUE -s_Molecule_AddAngle(VALUE self, VALUE v1, VALUE v2, VALUE v3) +s_Molecule_GetAtomAttr(VALUE self, VALUE idx, VALUE key) { - 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; + return s_Molecule_SetAtomAttr(self, idx, key, Qundef); } +#pragma mark ------ Undo Support ------ + /* * call-seq: - * remove_angle(n1, n2, n3) -> Molecule + * register_undo(script, *args) * - * 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. + * Register an undo operation with the current molecule. */ static VALUE -s_Molecule_RemoveAngle(VALUE self, VALUE v1, VALUE v2, VALUE v3) +s_Molecule_RegisterUndo(int argc, VALUE *argv, VALUE self) { - Int n[4]; - Molecule *mol; - IntGroup *ig; + Molecule *mol; + VALUE script, args; + MolAction *act; 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; + rb_scan_args(argc, argv, "1*", &script, &args); + act = MolActionNew(SCRIPT_ACTION("R"), StringValuePtr(script), args); + MolActionCallback_registerUndo(mol, act); + return script; } /* * call-seq: - * add_dihedral(n1, n2, n3, n4) -> Molecule + * undo_enabled? -> bool * - * 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. + * Returns true if undo is enabled for this molecule; otherwise no. */ static VALUE -s_Molecule_AddDihedral(VALUE self, VALUE v1, VALUE v2, VALUE v3, VALUE v4) +s_Molecule_UndoEnabled(VALUE self) { - 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; + if (MolActionCallback_isUndoRegistrationEnabled(mol)) + return Qtrue; + else return Qfalse; } /* * call-seq: - * remove_dihedral(n1, n2, n3, n4) -> Molecule + * undo_enabled = bool * - * 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. + * Enable or disable undo. */ static VALUE -s_Molecule_RemoveDihedral(VALUE self, VALUE v1, VALUE v2, VALUE v3, VALUE v4) +s_Molecule_SetUndoEnabled(VALUE self, VALUE val) { - 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; + 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; + } } /* * call-seq: - * add_improper(n1, n2, n3, n4) -> Molecule + * center_of_mass(group = nil) -> Vector3D * - * 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. + * 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_AddImproper(VALUE self, VALUE v1, VALUE v2, VALUE v3, VALUE v4) +s_Molecule_CenterOfMass(int argc, VALUE *argv, VALUE self) { - Int n[5]; Molecule *mol; + VALUE group; + IntGroup *ig; + Vector v; Data_Get_Struct(self, Molecule, mol); - n[0] = s_Molecule_AtomIndexFromValue(mol, v1); - n[1] = s_Molecule_AtomIndexFromValue(mol, v2); - n[2] = s_Molecule_AtomIndexFromValue(mol, v3); - n[3] = s_Molecule_AtomIndexFromValue(mol, v4); - if (MoleculeLookupImproper(mol, n[0], n[1], n[2], n[3]) >= 0) - rb_raise(rb_eMolbyError, "improper %d-%d-%d-%d is already present", n[0], n[1], n[2], n[3]); - n[4] = kInvalidIndex; - MolActionCreateAndPerform(mol, gMolActionAddImpropers, 4, n, NULL); - return self; + rb_scan_args(argc, argv, "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_improper(n1, n2, n3, n4) -> Molecule - * remove_improper(intgroup) -> Molecule + * centralize(group = nil) -> self * - * 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. + * 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_RemoveImproper(int argc, VALUE *argv, VALUE self) +s_Molecule_Centralize(int argc, VALUE *argv, VALUE self) { - Int n[5]; - VALUE v1, v2, v3, v4; Molecule *mol; + VALUE group; IntGroup *ig; + Vector v; 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); + 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: - * assign_residue(group, res) -> Molecule + * bounds(group = nil) -> [min, max] * - * 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. + * Calculate the boundary. The return value is an array of two Vector3D objects. */ static VALUE -s_Molecule_AssignResidue(VALUE self, VALUE range, VALUE res) +s_Molecule_Bounds(int argc, VALUE *argv, VALUE self) { Molecule *mol; + VALUE group; IntGroup *ig; - char *p, *pp, buf[16]; - Int resid, n; + Vector vmin, vmax; + int 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; + 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; } - ig = s_Molecule_AtomGroupFromValue(self, range); - if (ig == NULL || IntGroupGetCount(ig) == 0) - return Qnil; + return rb_ary_new3(2, ValueFromVector(&vmin), ValueFromVector(&vmax)); +} - 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); +/* 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); } - IntGroupRelease(ig); - return self; } /* * call-seq: - * offset_residue(group, offset) -> Molecule + * measure_bond(n1, n2) -> Float * - * Offset the residue number of the specified atoms. If any of the residue number gets - * negative, then exception is thrown. - * 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_OffsetResidue(VALUE self, VALUE range, VALUE offset) +s_Molecule_MeasureBond(VALUE self, VALUE nval1, VALUE nval2) { Molecule *mol; - IntGroup *ig; - int ofs, result; + Vector v1, v2; 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; + s_Molecule_GetVectorFromArg(mol, nval1, &v1); + s_Molecule_GetVectorFromArg(mol, nval2, &v2); + return rb_float_new(MoleculeMeasureBond(mol, &v1, &v2)); } /* * call-seq: - * renumber_atoms(array) -> IntGroup + * measure_angle(n1, n2, n3) -> Float * - * 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. + * 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_RenumberAtoms(VALUE self, VALUE array) +s_Molecule_MeasureAngle(VALUE self, VALUE nval1, VALUE nval2, VALUE nval3) { Molecule *mol; - Int *new2old; - IntGroup *ig; - int i, n; - VALUE *valp, retval; + Vector v1, v2, v3; + Double d; 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; + 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, radius = 0.77) -> array of Integers (atom indices) + * measure_dihedral(n1, n2, n3, n4) -> Float * - * 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. - */ + * Calculate the dihedral angle. The arguments can either be atom indices, the "residue:name" representation, + * or Vector3D values. The return value is in degree. + * If the crystallographic cell is defined, then the internal coordinates are convereted to the cartesian. + */ +static VALUE +s_Molecule_MeasureDihedral(VALUE self, VALUE nval1, VALUE nval2, VALUE nval3, VALUE nval4) +{ + Molecule *mol; + Vector v1, v2, v3, v4; + Double d; + Data_Get_Struct(self, Molecule, mol); + s_Molecule_GetVectorFromArg(mol, nval1, &v1); + s_Molecule_GetVectorFromArg(mol, nval2, &v2); + s_Molecule_GetVectorFromArg(mol, nval3, &v3); + s_Molecule_GetVectorFromArg(mol, nval4, &v4); + d = MoleculeMeasureDihedral(mol, &v1, &v2, &v3, &v4); + if (isnan(d)) + return Qnil; /* Cannot define */ + else return rb_float_new(d); +} + +/* + * call-seq: + * 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); + } + } + } + } + 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: + * find_close_atoms(atom, limit = 1.2, radius = 0.77) -> array of Integers (atom indices) + * + * 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_FindCloseAtoms(int argc, VALUE *argv, VALUE self) { @@ -7298,803 +7331,548 @@ s_Molecule_GuessBonds(int argc, VALUE *argv, VALUE self) } return INT2NUM(nbonds); } - + +#pragma mark ------ Cell and Symmetry ------ + /* * call-seq: - * register_undo(script, *args) + * cell -> [a, b, c, alpha, beta, gamma [, sig_a, sig_b, sig_c, sig_alpha, sig_beta, sig_gamma]] * - * Register an undo operation with the current molecule. + * Returns the unit cell parameters. If cell is not set, returns nil. */ static VALUE -s_Molecule_RegisterUndo(int argc, VALUE *argv, VALUE self) +s_Molecule_Cell(VALUE self) { - Molecule *mol; - VALUE script, args; - MolAction *act; + Molecule *mol; + int i; + VALUE val; Data_Get_Struct(self, Molecule, mol); - rb_scan_args(argc, argv, "1*", &script, &args); - act = MolActionNew(SCRIPT_ACTION("R"), StringValuePtr(script), args); - MolActionCallback_registerUndo(mol, act); - return script; + if (mol->cell == NULL) + 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: - * undo_enabled? -> bool + * 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) * - * Returns true if undo is enabled for this molecule; otherwise no. + * 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_UndoEnabled(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); - if (MolActionCallback_isUndoRegistrationEnabled(mol)) - return Qtrue; - else return Qfalse; + 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: - * undo_enabled = bool + * box -> [avec, bvec, cvec, origin, flags] * - * Enable or disable undo. + * 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_SetUndoEnabled(VALUE self, VALUE val) +s_Molecule_Box(VALUE self) { Molecule *mol; + VALUE v[5], val; Data_Get_Struct(self, Molecule, mol); - MolActionCallback_setUndoRegistrationEnabled(mol, (val != Qfalse && val != Qnil)); + 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: - * selection -> IntGroup + * 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 * - * Returns the current selection. + * 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_Selection(VALUE self) +s_Molecule_SetBox(VALUE self, VALUE aval) { Molecule *mol; - IntGroup *ig; - VALUE val; + 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 (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); + if (aval == Qnil) { + MolActionCreateAndPerform(mol, gMolActionClearBox); + return self; } - 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; + aval = rb_ary_to_ary(aval); + for (i = 0; i < 6; i++) { + if (i < RARRAY_LEN(aval)) + v[i] = (RARRAY_PTR(aval))[i]; + else v[i] = Qnil; + } + if (v[0] == Qnil) { + MolActionCreateAndPerform(mol, gMolActionClearBox); + return self; + } + if ((v[1] == Qnil || v[2] == Qnil) && rb_obj_is_kind_of(v[0], rb_cNumeric)) { + d = NUM2DBL(rb_Float(v[0])); + for (i = 0; i < 3; i++) + VecScale(vv[i], ax[i], d); + if (v[1] != Qnil) + VectorFromValue(v[1], &origin); + flags[0] = flags[1] = flags[2] = 1; + } else { + for (i = 0; i < 3; i++) { + if (v[i] == Qnil) { + VecZero(vv[i]); + } else if (rb_obj_is_kind_of(v[i], rb_cNumeric)) { + d = NUM2DBL(rb_Float(v[i])); + VecScale(vv[i], ax[i], d); + } else { + VectorFromValue(v[i], &vv[i]); + } + flags[i] = (VecLength2(vv[i]) > 0.0); + } + if (v[3] != Qnil) + VectorFromValue(v[3], &origin); + if (v[4] != Qnil) { + for (i = 0; i < 3; i++) { + VALUE val = Ruby_ObjectAtIndex(v[4], i); + flags[i] = (NUM2INT(rb_Integer(val)) != 0); + } + } + if (RTEST(v[5])) + convertCoordinates = 1; + } + MolActionCreateAndPerform(mol, gMolActionSetBox, &(vv[0]), &(vv[1]), &(vv[2]), &origin, (flags[0] * 4 + flags[1] * 2 + flags[2]), convertCoordinates); + return self; } /* * call-seq: - * selection = IntGroup + * cell_periodicity -> [n1, n2, n3] * - * Set the current selection. The right-hand operand may be nil. - * This operation is _not_ undoable. If you need undo, use set_undoable_selection instead. + * Get 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_SetSelection(VALUE self, VALUE val) +s_Molecule_CellPeriodicity(VALUE self) { - return s_Molecule_SetSelectionSub(self, val, 0); + 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: - * set_undoable_selection(IntGroup) + * self.cell_periodicity = [n1, n2, n3] or Integer or nil + * set_cell_periodicity = [n1, n2, n3] or Integer or nil * - * Set the current selection with undo registration. The right-hand operand may be nil. + * 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_SetUndoableSelection(VALUE self, VALUE val) +s_Molecule_SetCellPeriodicity(VALUE self, VALUE arg) { - return s_Molecule_SetSelectionSub(self, val, 1); + 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; } /* * call-seq: - * hidden_atoms -> IntGroup + * cell_flexibility -> bool * - * Returns the currently hidden atoms. + * Returns the unit cell is flexible or not */ static VALUE -s_Molecule_HiddenAtoms(VALUE self) +s_Molecule_CellFlexibility(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; - 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_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_hidden_atoms(IntGroup) - * self.hidden_atoms = IntGroup + * self.cell_flexibility = bool + * set_cell_flexibility(bool) * - * Hide the specified atoms. This operation is _not_ undoable. + * Change the unit cell is flexible or not */ static VALUE -s_Molecule_SetHiddenAtoms(VALUE self, VALUE val) +s_Molecule_SetCellFlexibility(VALUE self, VALUE arg) { - rb_raise(rb_eMolbyError, "set_hidden_atoms is now obsolete. Try using Molecule#is_atom_visible or AtomRef#hidden."); - return Qnil; /* Not reached */ + rb_warn("set_cell_flexibility is obsolete (unit cell is always frame dependent)"); + return self; + /* Molecule *mol; + Data_Get_Struct(self, Molecule, mol); + MolActionCreateAndPerform(mol, gMolActionSetCellFlexibility, RTEST(arg) != 0); + return self; */ +} + /* - Molecule *mol; + * call-seq: + * cell_transform -> Transform + * + * Get the transform matrix that converts internal coordinates to cartesian coordinates. + * If cell is not defined, nil is returned. + */ +static VALUE +s_Molecule_CellTransform(VALUE self) +{ + Molecule *mol; Data_Get_Struct(self, Molecule, mol); - if (mol != NULL) { - 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); + if (mol == NULL || mol->cell == NULL) + return Qnil; + return ValueFromTransform(&(mol->cell->tr)); +} + +/* + * call-seq: + * symmetry -> Array of Transforms + * symmetries -> Array of Transforms + * + * Get the currently defined symmetry operations. If no symmetry operation is defined, + * returns an empty array. + */ +static VALUE +s_Molecule_Symmetry(VALUE self) +{ + Molecule *mol; + VALUE val; + int i; + Data_Get_Struct(self, Molecule, mol); + if (mol->nsyms <= 0) + return rb_ary_new(); + val = rb_ary_new2(mol->nsyms); + for (i = 0; i < mol->nsyms; i++) { + rb_ary_push(val, ValueFromTransform(&mol->syms[i])); } - return val; */ + return val; } /* * call-seq: - * select_frame(index) - * frame = index + * nsymmetries -> Integer * - * Select the specified frame. If successful, returns true, otherwise returns false. + * Get the number of currently defined symmetry operations. */ static VALUE -s_Molecule_SelectFrame(VALUE self, VALUE val) +s_Molecule_Nsymmetries(VALUE self) { Molecule *mol; - int ival = NUM2INT(val); Data_Get_Struct(self, Molecule, mol); - ival = MoleculeSelectFrame(mol, ival, 1); - if (ival >= 0) - return Qtrue; - else return Qfalse; + return INT2NUM(mol->nsyms); } /* * call-seq: - * frame -> Integer + * add_symmetry(Transform) -> Integer * - * Get the current frame. + * 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_Frame(VALUE self) +s_Molecule_AddSymmetry(VALUE self, VALUE trans) { Molecule *mol; + Transform tr; Data_Get_Struct(self, Molecule, mol); - return INT2NUM(mol->cframe); + TransformFromValue(trans, &tr); + MolActionCreateAndPerform(mol, gMolActionAddSymmetryOperation, &tr); + return INT2NUM(mol->nsyms); } /* * call-seq: - * nframes -> Integer + * remove_symmetry(count = nil) -> Integer + * remove_symmetries(count = nil) -> Integer * - * Get the number of frames. + * 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_Nframes(VALUE self) +s_Molecule_RemoveSymmetry(int argc, VALUE *argv, VALUE self) { Molecule *mol; + VALUE cval; + int i, n; Data_Get_Struct(self, Molecule, mol); - return INT2NUM(MoleculeGetNumberOfFrames(mol)); + rb_scan_args(argc, argv, "01", &cval); + if (cval == Qnil) + n = mol->nsyms - 1; + else { + n = NUM2INT(rb_Integer(cval)); + if (n < 0 || n > mol->nsyms) + rb_raise(rb_eMolbyError, "the given count of symops is out of range"); + if (n == mol->nsyms) + n = mol->nsyms - 1; + } + for (i = 0; i < n; i++) + MolActionCreateAndPerform(mol, gMolActionDeleteSymmetryOperation); + return INT2NUM(mol->nsyms); } /* * call-seq: - * insert_frame(integer, coordinates = nil, cell_axes = nil) -> bool - * insert_frames(intGroup = nil, coordinates = nil, cell_axes = nil) -> bool + * wrap_unit_cell(group) -> Vector3D * - * 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. + * 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_InsertFrames(int argc, VALUE *argv, VALUE self) +s_Molecule_WrapUnitCell(VALUE self, VALUE gval) { - VALUE val, coords, cells; Molecule *mol; IntGroup *ig; - int count, ival, i, j, len, len_c, len2, nframes; - VALUE *ptr, *ptr2; - Vector *vp, *vp2; + Vector v, cv, dv; Data_Get_Struct(self, Molecule, mol); - rb_scan_args(argc, argv, "12", &val, &coords, &cells); - if (coords != Qnil) { - if (TYPE(coords) != T_ARRAY) - rb_raise(rb_eTypeError, "the coordinates should be given as an array of Vector3D"); - len = RARRAY_LEN(coords); - } else len = 0; - if (cells != Qnil) { - if (mol->cell == NULL) - rb_raise(rb_eTypeError, "the unit cell is not defined but the cell axes are given"); - if (TYPE(cells) != T_ARRAY) - rb_raise(rb_eTypeError, "the cell axes should be given as an array of Vector3D"); - len_c = RARRAY_LEN(cells); - } else len_c = 0; - count = (len > len_c ? len : len_c); /* May be zero; will be updated later */ - nframes = MoleculeGetNumberOfFrames(mol); - if (val == Qnil) { - ig = IntGroupNewWithPoints(nframes, (count > 0 ? count : 1), -1); - val = ValueFromIntGroup(ig); - } else { - ig = IntGroupFromValue(val); - } - count = IntGroupGetCount(ig); /* Count is updated here */ - vp = ALLOC_N(Vector, mol->natoms * count); - if (cells != Qnil) - vp2 = ALLOC_N(Vector, 4 * count); - else vp2 = NULL; - if (len > 0) { - if (len < count) - rb_raise(rb_eMolbyError, "the coordinates should contain no less than %d arrays (for frames)", count); - ptr = RARRAY_PTR(coords); - for (i = 0; i < count; i++) { - if (TYPE(ptr[i]) != T_ARRAY) - rb_raise(rb_eTypeError, "the coordinate array contains non-array object at index %d", i); - len2 = RARRAY_LEN(ptr[i]); - if (len2 < mol->natoms) - rb_raise(rb_eMolbyError, "the array at index %d contains less than %d elements", i, mol->natoms); - ptr2 = RARRAY_PTR(ptr[i]); - for (j = 0; j < mol->natoms; j++) - VectorFromValue(ptr2[j], &vp[i * mol->natoms + j]); - } - } else { - Atom *ap; - for (i = 0; i < count; i++) { - for (j = 0, ap = mol->atoms; j < mol->natoms; j++, ap = ATOM_NEXT(ap)) { - vp[i * mol->natoms + j] = ap->r; - } - } - } - if (len_c > 0) { - if (len_c < count) - rb_raise(rb_eMolbyError, "the cell vectors should contain no less than %d arrays (for frames)", count); - ptr = RARRAY_PTR(cells); - for (i = 0; i < count; i++) { - if (TYPE(ptr[i]) != T_ARRAY) - rb_raise(rb_eTypeError, "the cell parameter array contains non-array object at index %d", i); - len2 = RARRAY_LEN(ptr[i]); - if (len2 < 4) - rb_raise(rb_eMolbyError, "the cell parameter should contain 4 vectors"); - ptr2 = RARRAY_PTR(ptr[i]); - for (j = 0; j < 4; j++) - VectorFromValue(ptr2[j], &vp2[i * 4 + j]); - } - } - ival = MolActionCreateAndPerform(mol, gMolActionInsertFrames, ig, mol->natoms * count, vp, (vp2 != NULL ? 4 * count : 0), vp2); + if (mol->cell == NULL) + rb_raise(rb_eMolbyError, "no unit cell is defined"); + ig = s_Molecule_AtomGroupFromValue(self, gval); + s_Molecule_DoCenterOfMass(mol, &cv, ig); + TransformVec(&v, mol->cell->rtr, &cv); + if (mol->cell->flags[0]) + v.x -= floor(v.x); + if (mol->cell->flags[1]) + v.y -= floor(v.y); + if (mol->cell->flags[2]) + v.z -= floor(v.z); + TransformVec(&dv, mol->cell->tr, &v); + VecDec(dv, cv); + MolActionCreateAndPerform(mol, gMolActionTranslateAtoms, &dv, ig); IntGroupRelease(ig); - xfree(vp); - if (vp2 != NULL) - xfree(vp2); - return (ival >= 0 ? val : Qnil); -} - -/* - * call-seq: - * create_frame(coordinates = nil) -> Integer - * create_frames(coordinates = nil) -> Integer - * - * Same as molecule.insert_frames(nil, coordinates). - */ -static VALUE -s_Molecule_CreateFrames(int argc, VALUE *argv, VALUE self) -{ - VALUE vals[3]; - rb_scan_args(argc, argv, "02", &vals[1], &vals[2]); - vals[0] = Qnil; - return s_Molecule_InsertFrames(3, vals, self); + return ValueFromVector(&dv); } /* * call-seq: - * remove_frames(IntGroup, wantCoordinates = false) + * expand_by_symmetry(group, sym, dx=0, dy=0, dz=0, allow_overlap = false) -> Array * - * 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. + * 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_RemoveFrames(int argc, VALUE *argv, VALUE self) +s_Molecule_ExpandBySymmetry(int argc, VALUE *argv, VALUE self) { - VALUE val, flag; - VALUE retval; Molecule *mol; + VALUE gval, sval, xval, yval, zval, rval, oval; IntGroup *ig; - int count; + Int n[4], allow_overlap; + Int natoms; + Int nidx, *idx; + Data_Get_Struct(self, Molecule, mol); - rb_scan_args(argc, argv, "11", &val, &flag); - ig = IntGroupFromValue(val); - count = IntGroupGetCount(ig); - if (RTEST(flag)) { - /* Create return value before removing frames */ - VALUE coords; - int i, j, n; - Atom *ap; - Vector v; - retval = rb_ary_new2(count); - for (i = 0; i < count; i++) { - n = IntGroupGetNthPoint(ig, i); - coords = rb_ary_new2(mol->natoms); - for (j = 0, ap = mol->atoms; j < mol->natoms; j++, ap = ATOM_NEXT(ap)) { - if (n < ap->nframes && n != mol->cframe) - v = ap->frames[n]; - else v = ap->r; - rb_ary_push(coords, ValueFromVector(&v)); - } - rb_ary_push(retval, coords); - } - } else retval = Qtrue; - if (MolActionCreateAndPerform(mol, gMolActionRemoveFrames, ig) >= 0) - return retval; - else return Qnil; + rb_scan_args(argc, argv, "24", &gval, &sval, &xval, &yval, &zval, &oval); + n[0] = NUM2INT(rb_Integer(sval)); + n[1] = (xval == Qnil ? 0 : NUM2INT(rb_Integer(xval))); + n[2] = (yval == Qnil ? 0 : NUM2INT(rb_Integer(yval))); + n[3] = (zval == Qnil ? 0 : NUM2INT(rb_Integer(zval))); + allow_overlap = (RTEST(oval) ? 1 : 0); + ig = s_Molecule_AtomGroupFromValue(self, gval); + if (n[0] < 0 || (n[0] > 0 && n[0] >= mol->nsyms)) + rb_raise(rb_eMolbyError, "symmetry index is out of bounds"); + natoms = mol->natoms; + + MolActionCreateAndPerform(mol, gMolActionExpandBySymmetry, ig, n[1], n[2], n[3], n[0], allow_overlap, &nidx, &idx); + + rval = rb_ary_new2(nidx); + while (--nidx >= 0) { + rb_ary_store(rval, nidx, INT2NUM(idx[nidx])); + } + /* if (natoms == mol->natoms) + rval = Qnil; + else { + rval = IntGroup_Alloc(rb_cIntGroup); + Data_Get_Struct(rval, IntGroup, ig); + IntGroup_RaiseIfError(IntGroupAdd(ig, natoms, mol->natoms - natoms)); + } */ + return rval; } /* * call-seq: - * each_frame {|n| ...} + * amend_by_symmetry(group = nil) -> IntGroup * - * 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. + * Expand the specified part of the molecule by the given symmetry operation. + * Returns an IntGroup containing the added atoms. */ static VALUE -s_Molecule_EachFrame(VALUE self) +s_Molecule_AmendBySymmetry(int argc, VALUE *argv, VALUE self) { - int i, cframe, nframes; Molecule *mol; + IntGroup *ig, *ig2; + VALUE rval, gval; Data_Get_Struct(self, Molecule, mol); - cframe = mol->cframe; - nframes = MoleculeGetNumberOfFrames(mol); - if (nframes > 0) { - for (i = 0; i < nframes; i++) { - MoleculeSelectFrame(mol, i, 1); - rb_yield(INT2NUM(i)); - } - MoleculeSelectFrame(mol, cframe, 1); - } - return self; + rb_scan_args(argc, argv, "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: - * set_atom_attr(index, key, value) + * translate(vec, group = nil) -> Molecule * - * Set the atom attribute for the specified atom. + * Translate the molecule by vec. If group is given, only atoms in the group are moved. * This operation is undoable. */ static VALUE -s_Molecule_SetAtomAttr(VALUE self, VALUE idx, VALUE key, VALUE val) +s_Molecule_Translate(int argc, VALUE *argv, VALUE self) { - Molecule *mol; - VALUE aref, oldval; + Molecule *mol; + VALUE vec, group; + Vector v; + IntGroup *ig; 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_scan_args(argc, argv, "11", &vec, &group); + ig = (NIL_P(group) ? NULL : s_Molecule_AtomGroupFromValue(self, group)); + VectorFromValue(vec, &v); + // MoleculeTranslate(mol, &v, ig); + MolActionCreateAndPerform(mol, gMolActionTranslateAtoms, &v, ig); + if (ig != NULL) + IntGroupRelease(ig); + return self; } /* * call-seq: - * get_atom_attr(index, key) + * rotate(axis, angle, center = [0,0,0], group = nil) -> Molecule * - * Get the atom attribute for the specified atom. + * Rotate the molecule. The axis must not a zero vector. angle is given in degree. + * If group is given, only atoms in the group are moved. + * This operation is undoable. */ static VALUE -s_Molecule_GetAtomAttr(VALUE self, VALUE idx, VALUE key) +s_Molecule_Rotate(int argc, VALUE *argv, VALUE self) { - return s_Molecule_SetAtomAttr(self, idx, key, Qundef); + Molecule *mol; + volatile VALUE aval, anval, cval, gval; + Double angle; + Vector av, cv; + Transform tr; + IntGroup *ig; + Data_Get_Struct(self, Molecule, mol); + rb_scan_args(argc, argv, "22", &aval, &anval, &cval, &gval); + ig = (NIL_P(gval) ? NULL : s_Molecule_AtomGroupFromValue(self, gval)); + angle = NUM2DBL(rb_Float(anval)) * kDeg2Rad; + VectorFromValue(aval, &av); + if (NIL_P(cval)) + cv.x = cv.y = cv.z = 0.0; + else + VectorFromValue(cval, &cv); + if (TransformForRotation(tr, &av, angle, &cv)) + rb_raise(rb_eMolbyError, "rotation axis cannot be a zero vector"); + MolActionCreateAndPerform(mol, gMolActionTransformAtoms, &tr, ig); + if (ig != NULL) + IntGroupRelease(ig); + return self; } /* * call-seq: - * get_coord_from_frame(index, group = nil) + * reflect(axis, center = [0,0,0], group = nil) -> Molecule * - * 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_GetCoordFromFrame(int argc, VALUE *argv, VALUE self) -{ - 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); - rb_scan_args(argc, argv, "12", &ival, &gval, &cval); - if (argc == 3) - rb_warn("The 3rd argument to get_coord_from_frame() is now obsolete"); - index = NUM2INT(rb_Integer(ival)); - if (index < 0 || index >= (n = MoleculeGetNumberOfFrames(mol))) { - if (n == 0) - rb_raise(rb_eMolbyError, "No frame is present"); - else - rb_raise(rb_eMolbyError, "Frame index (%d) out of range (should be 0..%d)", index, n - 1); - } - if (gval == Qnil) { - ig = IntGroupNewWithPoints(0, mol->natoms, -1); - } else { - ig = s_Molecule_AtomGroupFromValue(self, gval); - } - n = IntGroupGetCount(ig); - if (n > 0) { - vp = (Vector *)calloc(sizeof(Vector), n); - IntGroupIteratorInit(ig, &iter); - j = 0; - nn = 0; - while ((i = IntGroupIteratorNext(&iter)) >= 0) { - ap = ATOM_AT_INDEX(mol->atoms, i); - if (index < ap->nframes) { - vp[j] = ap->frames[index]; - nn++; - } else { - vp[j] = ap->r; - } - j++; - } - if (nn > 0) - MolActionCreateAndPerform(mol, gMolActionSetAtomPositions, ig, n, vp); - free(vp); - if (mol->cell != NULL && mol->frame_cells != NULL && index < mol->nframe_cells) { - vp = mol->frame_cells + index * 4; - MolActionCreateAndPerform(mol, gMolActionSetBox, vp, vp + 1, vp + 2, vp + 3, -1, 0); - } - IntGroupIteratorRelease(&iter); - } - IntGroupRelease(ig); - return self; -} - -/* - * call-seq: - * fragment(n1, *exatoms) -> IntGroup - * fragment(group, *exatoms) -> IntGroup - * - * 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_Fragment(int argc, VALUE *argv, 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; -} - -/* - * call-seq: - * fragments(exclude = nil) - * - * 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_Fragments(int argc, VALUE *argv, VALUE self) -{ - Molecule *mol; - IntGroup *ag, *fg, *eg; - VALUE gval, exval, retval; - 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; -} - -/* - * call-seq: - * each_fragment(exclude = nil) {|group| ...} - * - * Execute the block, with the IntGroup object for each fragment as the argument. - * Atoms or bonds should not be added or removed during the execution of the block. - * If exclude is given (as an array or an IntGroup), then those atoms are excluded - * in defining the fragment. - */ -static VALUE -s_Molecule_EachFragment(int argc, VALUE *argv, VALUE self) -{ - Molecule *mol; - IntGroup *ag, *fg, *eg; - VALUE gval, exval; - Data_Get_Struct(self, Molecule, mol); - if (mol == NULL || mol->natoms == 0) - return self; - rb_scan_args(argc, argv, "01", &exval); - if (exval == Qnil) - eg = NULL; - else - eg = IntGroupFromValue(exval); - ag = IntGroupNewWithPoints(0, mol->natoms, -1); - if (eg != NULL) - IntGroupRemoveIntGroup(ag, eg); - while (IntGroupGetCount(ag) > 0) { - int n = IntGroupGetNthPoint(ag, 0); - fg = MoleculeFragmentExcludingAtomGroup(mol, n, 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: - * detachable?(group) -> [n1, n2] - * - * 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_Detachable_P(VALUE self, VALUE gval) -{ - Molecule *mol; - IntGroup *ig; - int n1, n2; - VALUE retval; - Data_Get_Struct(self, Molecule, mol); - ig = s_Molecule_AtomGroupFromValue(self, gval); - if (MoleculeIsFragmentDetachable(mol, ig, &n1, &n2)) { - retval = rb_ary_new3(2, INT2NUM(n1), INT2NUM(n2)); - } else retval = Qnil; - IntGroupRelease(ig); - return retval; -} - -/* - * call-seq: - * 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); - } - retval = rb_ary_new(); - if (ig == NULL) - return retval; - bg = MoleculeSearchBondsAcrossAtomGroup(mol, ig); - if (bg != NULL) { - IntGroupIterator iter; - Int i; - IntGroupIteratorInit(bg, &iter); - while ((i = IntGroupIteratorNext(&iter)) >= 0) { - /* The atoms at the border */ - Int n1, n2; - n1 = mol->bonds[i * 2]; - n2 = mol->bonds[i * 2 + 1]; - if (IntGroupLookupPoint(ig, n1) < 0) { - int w = n1; - n1 = n2; - n2 = w; - if (IntGroupLookupPoint(ig, n1) < 0) - continue; /* Actually this is an internal error */ - } - rb_ary_push(retval, rb_ary_new3(2, INT2NUM(n1), INT2NUM(n2))); - } - IntGroupIteratorRelease(&iter); - } - IntGroupRelease(bg); - IntGroupRelease(ig); - return retval; -} - -/* - * call-seq: - * translate(vec, group = nil) -> Molecule - * - * Translate the molecule by vec. If group is given, only atoms in the group are moved. - * This operation is undoable. - */ -static VALUE -s_Molecule_Translate(int argc, VALUE *argv, VALUE self) -{ - Molecule *mol; - VALUE vec, group; - Vector v; - IntGroup *ig; - Data_Get_Struct(self, Molecule, mol); - rb_scan_args(argc, argv, "11", &vec, &group); - ig = (NIL_P(group) ? NULL : s_Molecule_AtomGroupFromValue(self, group)); - VectorFromValue(vec, &v); -// MoleculeTranslate(mol, &v, ig); - MolActionCreateAndPerform(mol, gMolActionTranslateAtoms, &v, ig); - if (ig != NULL) - IntGroupRelease(ig); - return self; -} - -/* - * call-seq: - * rotate(axis, angle, center = [0,0,0], group = nil) -> Molecule - * - * Rotate the molecule. The axis must not a zero vector. angle is given in degree. - * If group is given, only atoms in the group are moved. - * This operation is undoable. - */ -static VALUE -s_Molecule_Rotate(int argc, VALUE *argv, VALUE self) -{ - Molecule *mol; - volatile VALUE aval, anval, cval, gval; - Double angle; - Vector av, cv; - Transform tr; - IntGroup *ig; - Data_Get_Struct(self, Molecule, mol); - rb_scan_args(argc, argv, "22", &aval, &anval, &cval, &gval); - ig = (NIL_P(gval) ? NULL : s_Molecule_AtomGroupFromValue(self, gval)); - angle = NUM2DBL(rb_Float(anval)) * kDeg2Rad; - VectorFromValue(aval, &av); - if (NIL_P(cval)) - cv.x = cv.y = cv.z = 0.0; - else - VectorFromValue(cval, &cv); - if (TransformForRotation(tr, &av, angle, &cv)) - rb_raise(rb_eMolbyError, "rotation axis cannot be a zero vector"); - MolActionCreateAndPerform(mol, gMolActionTransformAtoms, &tr, ig); - if (ig != NULL) - IntGroupRelease(ig); - return self; -} - -/* - * call-seq: - * reflect(axis, center = [0,0,0], group = nil) -> Molecule - * - * Reflect the molecule by the plane which is perpendicular to axis and including center. - * axis must not be a zero vector. - * If group is given, only atoms in the group are moved. - * This operation is undoable. + * Reflect the molecule by the plane which is perpendicular to axis and including center. + * axis must not be a zero vector. + * If group is given, only atoms in the group are moved. + * This operation is undoable. */ static VALUE s_Molecule_Reflect(int argc, VALUE *argv, VALUE self) @@ -8169,477 +7947,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) +{ + Molecule *mol; + VALUE sval, fval; + Symop symop; + Transform tr; + Data_Get_Struct(self, Molecule, mol); + if (mol->cell == NULL) + rb_raise(rb_eMolbyError, "no unit cell is defined"); + if (mol->nsyms == 0) + rb_raise(rb_eMolbyError, "no symmetry operation is defined"); + rb_scan_args(argc, argv, "11", &sval, &fval); + if (rb_obj_is_kind_of(sval, rb_cNumeric)) { + symop.sym = NUM2INT(rb_Integer(sval)); + symop.dx = symop.dy = symop.dz = 0; + } else { + sval = rb_ary_to_ary(sval); + if (RARRAY_LEN(sval) < 4) + rb_raise(rb_eMolbyError, "missing arguments as symop; at least four integers should be given"); + symop.sym = NUM2INT(rb_Integer(RARRAY_PTR(sval)[0])); + symop.dx = NUM2INT(rb_Integer(RARRAY_PTR(sval)[1])); + symop.dy = NUM2INT(rb_Integer(RARRAY_PTR(sval)[2])); + symop.dz = NUM2INT(rb_Integer(RARRAY_PTR(sval)[3])); + } + if (symop.sym >= mol->nsyms) + rb_raise(rb_eMolbyError, "index of symmetry operation (%d) is out of range", symop.sym); + MoleculeGetTransformForSymop(mol, symop, &tr, (RTEST(fval) != 0)); + return ValueFromTransform(&tr); +} + +/* + * call-seq: + * symop_for_transform(transform, is_cartesian = nil) -> [sym, dx, dy, dz] + * + * Get the symmetry operation corresponding to the given transform. + * If is_cartesian is true, the given transform is for cartesian coordinates. + * Otherwise, the given transform is for fractional coordinates. + * Raises exception when no cell or no transform are defined. + */ +static VALUE +s_Molecule_SymopForTransform(int argc, VALUE *argv, VALUE self) +{ + Molecule *mol; + VALUE tval, fval; + Symop symop; + Transform tr; + int n; + Data_Get_Struct(self, Molecule, mol); + if (mol->cell == NULL) + rb_raise(rb_eMolbyError, "no unit cell is defined"); + if (mol->nsyms == 0) + rb_raise(rb_eMolbyError, "no symmetry operation is defined"); + rb_scan_args(argc, argv, "11", &tval, &fval); + TransformFromValue(tval, &tr); + n = MoleculeGetSymopForTransform(mol, tr, &symop, (RTEST(fval) != 0)); + if (n == 0) { + return rb_ary_new3(4, INT2NUM(symop.sym), INT2NUM(symop.dx), INT2NUM(symop.dy), INT2NUM(symop.dz)); + } else { + return Qnil; /* Not found */ + } +} + +#pragma mark ------ Frames ------ + +/* + * call-seq: + * select_frame(index) + * frame = index + * + * Select the specified frame. If successful, returns true, otherwise returns false. + */ +static VALUE +s_Molecule_SelectFrame(VALUE self, VALUE val) { - 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; + int ival = NUM2INT(val); + Data_Get_Struct(self, Molecule, mol); + ival = MoleculeSelectFrame(mol, ival, 1); + if (ival >= 0) + return Qtrue; + else return Qfalse; } /* * call-seq: - * center_of_mass(group = nil) -> Vector3D + * frame -> Integer * - * Calculate the center of mass for the given set of atoms. The argument - * group is null, then all atoms are considered. + * Get the current frame. */ static VALUE -s_Molecule_CenterOfMass(int argc, VALUE *argv, VALUE self) +s_Molecule_Frame(VALUE self) { Molecule *mol; - VALUE group; - IntGroup *ig; - Vector v; Data_Get_Struct(self, Molecule, mol); - rb_scan_args(argc, argv, "01", &group); - ig = (NIL_P(group) ? NULL : s_Molecule_AtomGroupFromValue(self, group)); - s_Molecule_DoCenterOfMass(mol, &v, ig); - if (ig != NULL) - IntGroupRelease(ig); - return ValueFromVector(&v); + return INT2NUM(mol->cframe); } /* * call-seq: - * centralize(group = nil) -> self + * nframes -> Integer * - * Translate the molecule so that the center of mass of the given group is located - * at (0, 0, 0). Equivalent to molecule.translate(molecule.center_of_mass(group) * -1). + * Get the number of frames. */ static VALUE -s_Molecule_Centralize(int argc, VALUE *argv, VALUE self) +s_Molecule_Nframes(VALUE self) { Molecule *mol; - VALUE group; - IntGroup *ig; - Vector v; Data_Get_Struct(self, Molecule, mol); - rb_scan_args(argc, argv, "01", &group); - ig = (NIL_P(group) ? NULL : s_Molecule_AtomGroupFromValue(self, group)); - s_Molecule_DoCenterOfMass(mol, &v, ig); - if (ig != NULL) - IntGroupRelease(ig); - v.x = -v.x; - v.y = -v.y; - v.z = -v.z; - MolActionCreateAndPerform(mol, gMolActionTranslateAtoms, &v, NULL); - return self; + return INT2NUM(MoleculeGetNumberOfFrames(mol)); } /* * call-seq: - * bounds(group = nil) -> [min, max] + * insert_frame(integer, coordinates = nil, cell_axes = nil) -> bool + * insert_frames(intGroup = nil, coordinates = nil, cell_axes = nil) -> bool * - * Calculate the boundary. The return value is an array of two Vector3D objects. + * 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_Bounds(int argc, VALUE *argv, VALUE self) +s_Molecule_InsertFrames(int argc, VALUE *argv, VALUE self) { + VALUE val, coords, cells; Molecule *mol; - VALUE group; 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); } - return rb_ary_new3(2, ValueFromVector(&vmin), ValueFromVector(&vmax)); + count = IntGroupGetCount(ig); /* Count is updated here */ + vp = ALLOC_N(Vector, mol->natoms * count); + if (cells != Qnil) + vp2 = ALLOC_N(Vector, 4 * count); + else vp2 = NULL; + if (len > 0) { + if (len < count) + rb_raise(rb_eMolbyError, "the coordinates should contain no less than %d arrays (for frames)", count); + ptr = RARRAY_PTR(coords); + for (i = 0; i < count; i++) { + if (TYPE(ptr[i]) != T_ARRAY) + rb_raise(rb_eTypeError, "the coordinate array contains non-array object at index %d", i); + len2 = RARRAY_LEN(ptr[i]); + if (len2 < mol->natoms) + rb_raise(rb_eMolbyError, "the array at index %d contains less than %d elements", i, mol->natoms); + ptr2 = RARRAY_PTR(ptr[i]); + for (j = 0; j < mol->natoms; j++) + VectorFromValue(ptr2[j], &vp[i * mol->natoms + j]); + } + } else { + Atom *ap; + for (i = 0; i < count; i++) { + for (j = 0, ap = mol->atoms; j < mol->natoms; j++, ap = ATOM_NEXT(ap)) { + vp[i * mol->natoms + j] = ap->r; + } + } + } + if (len_c > 0) { + if (len_c < count) + rb_raise(rb_eMolbyError, "the cell vectors should contain no less than %d arrays (for frames)", count); + ptr = RARRAY_PTR(cells); + for (i = 0; i < count; i++) { + if (TYPE(ptr[i]) != T_ARRAY) + rb_raise(rb_eTypeError, "the cell parameter array contains non-array object at index %d", i); + len2 = RARRAY_LEN(ptr[i]); + if (len2 < 4) + rb_raise(rb_eMolbyError, "the cell parameter should contain 4 vectors"); + ptr2 = RARRAY_PTR(ptr[i]); + for (j = 0; j < 4; j++) + VectorFromValue(ptr2[j], &vp2[i * 4 + j]); + } + } + ival = MolActionCreateAndPerform(mol, gMolActionInsertFrames, ig, mol->natoms * count, vp, (vp2 != NULL ? 4 * count : 0), vp2); + IntGroupRelease(ig); + xfree(vp); + if (vp2 != NULL) + xfree(vp2); + return (ival >= 0 ? val : Qnil); } -/* 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, allow_overlap = false) -> 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. - * 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. + * 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, oval; - IntGroup *ig; - Int n[4], allow_overlap; - Int natoms; - Int nidx, *idx; - + Molecule *mol; + Int *ip, *ip2, i, n, nframes; Data_Get_Struct(self, Molecule, mol); - rb_scan_args(argc, argv, "24", &gval, &sval, &xval, &yval, &zval, &oval); - n[0] = NUM2INT(rb_Integer(sval)); - n[1] = (xval == Qnil ? 0 : NUM2INT(rb_Integer(xval))); - n[2] = (yval == Qnil ? 0 : NUM2INT(rb_Integer(yval))); - n[3] = (zval == Qnil ? 0 : NUM2INT(rb_Integer(zval))); - allow_overlap = (RTEST(oval) ? 1 : 0); - ig = s_Molecule_AtomGroupFromValue(self, gval); - if (n[0] < 0 || (n[0] > 0 && n[0] >= mol->nsyms)) - rb_raise(rb_eMolbyError, "symmetry index is out of bounds"); - natoms = mol->natoms; - - MolActionCreateAndPerform(mol, gMolActionExpandBySymmetry, ig, n[1], n[2], n[3], n[0], allow_overlap, &nidx, &idx); - - rval = rb_ary_new2(nidx); - while (--nidx >= 0) { - rb_ary_store(rval, nidx, INT2NUM(idx[nidx])); + 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], ...] + * bonds_on_border(group = selection) -> Array of Array of two Integers * - * 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. + * 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_FindConflicts(int argc, VALUE *argv, VALUE self) +s_Molecule_BondsOnBorder(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; - + Molecule *mol; + IntGroup *ig, *bg; + VALUE gval, retval; Data_Get_Struct(self, Molecule, mol); - rb_scan_args(argc, argv, "13", &limval, &gval1, &gval2, &igval); - lim = NUM2DBL(rb_Float(limval)); - if (lim <= 0.0) - rb_raise(rb_eMolbyError, "the limit (%g) should be positive", lim); - if (gval1 != Qnil) - ig1 = s_Molecule_AtomGroupFromValue(self, gval1); - else - ig1 = IntGroupNewWithPoints(0, mol->natoms, -1); - if (gval2 != Qnil) - ig2 = s_Molecule_AtomGroupFromValue(self, gval2); - else - ig2 = IntGroupNewWithPoints(0, mol->natoms, -1); - - if (!RTEST(igval)) { - /* Use the exclusion table in MDArena */ - if (mol->par == NULL || mol->arena == NULL || mol->arena->is_initialized == 0 || mol->needsMDRebuild) { - VALUE mval = ValueFromMolecule(mol); - s_RebuildMDParameterIfNecessary(mval, Qnil); - } - exinfo = mol->arena->exinfo; /* May be NULL */ - exlist = mol->arena->exlist; + rb_scan_args(argc, argv, "01", &gval); + if (gval == Qnil) { + ig = MoleculeGetSelection(mol); + if (ig != NULL) + IntGroupRetain(ig); } else { - exinfo = NULL; - exlist = NULL; + ig = s_Molecule_AtomGroupFromValue(self, gval); } - IntGroupIteratorInit(ig1, &iter1); - IntGroupIteratorInit(ig2, &iter2); - npairs = 0; - pairs = NULL; - while ((n[0] = IntGroupIteratorNext(&iter1)) >= 0) { - Int exn1, exn2; - ap1 = ATOM_AT_INDEX(mol->atoms, n[0]); - r1 = ap1->r; - if (exinfo != NULL) { - exn1 = exinfo[n[0]].index1; - exn2 = exinfo[n[0] + 1].index1; - } else exn1 = exn2 = -1; - IntGroupIteratorReset(&iter2); - while ((n[1] = IntGroupIteratorNext(&iter2)) >= 0) { - ap2 = ATOM_AT_INDEX(mol->atoms, n[1]); - if (n[0] == n[1]) - continue; /* Same atom */ - if (exinfo != NULL) { - /* Look up exclusion table to exclude 1-2, 1-3, and 1-4 pairs */ - for (i = exn1; i < exn2; i++) { - if (exlist[i] == n[1]) - break; - } - if (i < exn2) - continue; /* Should be excluded */ - } - if (MoleculeMeasureBond(mol, &r1, &(ap2->r)) < lim) { - /* Is this pair already registered? */ - Int *ip; - for (i = 0, ip = pairs; i < npairs; i++, ip += 2) { - if ((ip[0] == n[0] && ip[1] == n[1]) || (ip[0] == n[1] && ip[1] == n[0])) - break; - } - if (i >= npairs) { - /* Not registered yet */ - AssignArray(&pairs, &npairs, sizeof(Int) * 2, npairs, n); - } + retval = rb_ary_new(); + if (ig == NULL) + return retval; + bg = MoleculeSearchBondsAcrossAtomGroup(mol, ig); + if (bg != NULL) { + IntGroupIterator iter; + Int i; + IntGroupIteratorInit(bg, &iter); + while ((i = IntGroupIteratorNext(&iter)) >= 0) { + /* The atoms at the border */ + Int n1, n2; + n1 = mol->bonds[i * 2]; + n2 = mol->bonds[i * 2 + 1]; + if (IntGroupLookupPoint(ig, n1) < 0) { + int w = n1; + n1 = n2; + n2 = w; + if (IntGroupLookupPoint(ig, n1) < 0) + continue; /* Actually this is an internal error */ } + rb_ary_push(retval, rb_ary_new3(2, INT2NUM(n1), INT2NUM(n2))); } + IntGroupIteratorRelease(&iter); } - IntGroupIteratorRelease(&iter2); - IntGroupIteratorRelease(&iter1); - IntGroupRelease(ig2); - IntGroupRelease(ig1); - rval = rb_ary_new2(npairs); - if (pairs != NULL) { - for (i = 0; i < npairs; i++) { - rb_ary_push(rval, rb_ary_new3(2, INT2NUM(pairs[i * 2]), INT2NUM(pairs[i * 2 + 1]))); - } - free(pairs); - } - return rval; + IntGroupRelease(bg); + IntGroupRelease(ig); + return retval; } /* Calculate the transform that moves the current coordinates to the reference @@ -8780,7 +8731,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; @@ -8790,7 +8741,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"); @@ -8801,7 +8752,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; } @@ -8814,7 +8765,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++) { @@ -8826,24 +8777,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); @@ -8861,18 +8812,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); @@ -8894,17 +8845,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); @@ -8912,32 +8863,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 @@ -9151,6 +9104,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 @@ -9576,46 +9556,155 @@ s_Molecule_SetBackgroundColor(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "30", &rval, &gval, &bval); MainView_setBackgroundColor(mol->mview, NUM2DBL(rb_Float(rval)), NUM2DBL(rb_Float(gval)), NUM2DBL(rb_Float(bval))); } - return self; + return self; +} + +/* + * call-seq: + * 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: - * create_graphic(kind, color, points, fill = nil) -> integer + * 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); @@ -9655,11 +9744,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; @@ -9671,8 +9766,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); } /* @@ -9692,6 +9815,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; } @@ -9711,55 +9862,189 @@ s_Molecule_NGraphics(VALUE self) rb_raise(rb_eMolbyError, "this molecule has no associated graphic view"); return INT2NUM(mol->mview->ngraphics); } - + /* * call-seq: - * set_graphic_point(graphic_index, point_index, new_value) -> new_value + * get_graphic_point(graphic_index, point_index) -> value + * get_graphic_points(graphic_index) -> values * - * Change the point_index-th control point of graphic_index-th graphic object + * Get the point_index-th control point of graphic_index-th graphic object. + * Get an array of all control points with the given values. * */ static VALUE -s_Molecule_SetGraphicPoint(VALUE self, VALUE gval, VALUE pval, VALUE nval) +s_Molecule_GetGraphicPoint(int argc, VALUE *argv, VALUE self) { MainViewGraphic *gp; Molecule *mol; - int index; + int index, pindex; Vector v; + VALUE gval, pval; Data_Get_Struct(self, Molecule, mol); if (mol->mview == NULL) rb_raise(rb_eMolbyError, "this molecule has no associated graphic view"); + rb_scan_args(argc, argv, "11", &gval, &pval); index = NUM2INT(rb_Integer(gval)); if (index < 0 || index >= mol->mview->ngraphics) rb_raise(rb_eArgError, "the graphic index is out of range"); gp = mol->mview->graphics + index; - index = NUM2INT(rb_Integer(pval)); - if (index < 0 || index >= gp->npoints) - rb_raise(rb_eArgError, "the point index is out of range"); - if (rb_obj_is_kind_of(nval, rb_cNumeric)) { - if ((gp->kind == kMainViewGraphicCylinder || gp->kind == kMainViewGraphicCone) && index == 2) { - v.x = NUM2DBL(rb_Float(nval)); - v.y = v.z = 0; - } else if (gp->kind == kMainViewGraphicEllipsoid && index == 1) { - gp->points[3] = gp->points[7] = gp->points[11] = NUM2DBL(rb_Float(nval)); - gp->points[4] = gp->points[5] = gp->points[6] = gp->points[8] = gp->points[9] = gp->points[10] = 0; - return nval; - } else rb_raise(rb_eArgError, "the argument must be an array-like object"); + if (pval != Qnil) { + pindex = NUM2INT(rb_Integer(pval)); + if (pindex < 0 || pindex >= gp->npoints) + rb_raise(rb_eArgError, "the point index is out of range"); + v.x = gp->points[pindex * 3]; + v.y = gp->points[pindex * 3 + 1]; + v.z = gp->points[pindex * 3 + 2]; + if ((gp->kind == kMainViewGraphicCylinder || gp->kind == kMainViewGraphicCone) && pindex == 2) { + return rb_float_new(v.x); + } else { + return ValueFromVector(&v); + } + } else { + pval = rb_ary_new(); + for (pindex = 0; pindex < gp->npoints; pindex++) { + v.x = gp->points[pindex * 3]; + v.y = gp->points[pindex * 3 + 1]; + v.z = gp->points[pindex * 3 + 2]; + rb_ary_push(pval, ValueFromVector(&v)); + } + return pval; + } +} + +/* + * call-seq: + * set_graphic_point(graphic_index, point_index, new_value) -> new_value + * set_graphic_points(graphic_index, new_values) -> new_values + * + * Change the point_index-th control point of graphic_index-th graphic object. + * Replace the control points with the given values. + * + */ +static VALUE +s_Molecule_SetGraphicPoint(int argc, VALUE *argv, VALUE self) +{ + MainViewGraphic *gp; + Molecule *mol; + int index, pindex; + Vector v, v0; + VALUE gval, pval, nval; + MolAction *act; + Data_Get_Struct(self, Molecule, mol); + if (mol->mview == NULL) + rb_raise(rb_eMolbyError, "this molecule has no associated graphic view"); + rb_scan_args(argc, argv, "21", &gval, &pval, &nval); + index = NUM2INT(rb_Integer(gval)); + if (index < 0 || index >= mol->mview->ngraphics) + rb_raise(rb_eArgError, "the graphic index is out of range"); + gp = mol->mview->graphics + index; + if (nval != Qnil) { + pindex = NUM2INT(rb_Integer(pval)); + if (pindex < 0 || pindex >= gp->npoints) + rb_raise(rb_eArgError, "the point index is out of range"); + v0.x = gp->points[pindex * 3]; + v0.y = gp->points[pindex * 3 + 1]; + v0.z = gp->points[pindex * 3 + 2]; + if (rb_obj_is_kind_of(nval, rb_cNumeric)) { + if ((gp->kind == kMainViewGraphicCylinder || gp->kind == kMainViewGraphicCone) && pindex == 2) { + v.x = NUM2DBL(rb_Float(nval)); + v.y = v.z = 0; + } else if (gp->kind == kMainViewGraphicEllipsoid && pindex == 1) { + v.x = NUM2DBL(rb_Float(nval)); + v.y = v.z = 0; + gp->points[7] = gp->points[11] = v.x; + gp->points[6] = gp->points[8] = gp->points[9] = gp->points[10] = 0; + } else rb_raise(rb_eArgError, "the argument must be an array-like object"); + } else { + if (nval == Qnil) { + v.x = kInvalidFloat; + v.y = v.z = 0.0; + } else VectorFromValue(nval, &v); + } + gp->points[pindex * 3] = v.x; + gp->points[pindex * 3 + 1] = v.y; + gp->points[pindex * 3 + 2] = v.z; + act = MolActionNew(SCRIPT_ACTION("iiv"), "set_graphic_point", index, pindex, &v0); } else { - if (nval == Qnil) { - v.x = kInvalidFloat; - v.y = v.z = 0.0; - } else VectorFromValue(nval, &v); - } - gp->points[index * 3] = v.x; - gp->points[index * 3 + 1] = v.y; - gp->points[index * 3 + 2] = v.z; + VALUE aval; + int len; + Vector *vp = (Vector *)malloc(sizeof(Vector) * gp->npoints); + for (pindex = 0; pindex < gp->npoints; pindex++) { + vp[pindex].x = gp->points[pindex * 3]; + vp[pindex].y = gp->points[pindex * 3 + 1]; + vp[pindex].z = gp->points[pindex * 3 + 2]; + } + act = MolActionNew(SCRIPT_ACTION("iV"), "set_graphic_points", index, gp->npoints, vp); + free(vp); + pval = rb_ary_to_ary(pval); + len = RARRAY_LEN(pval); + if (gp->npoints < len) { + gp->points = (GLfloat *)realloc(gp->points, sizeof(GLfloat) * 3 * len); + gp->npoints = len; + } else if (gp->npoints > len) { + int len2 = 3; + switch (gp->kind) { + case kMainViewGraphicLine: len2 = 2; break; + case kMainViewGraphicPoly: len2 = 3; break; + case kMainViewGraphicCylinder: len2 = 3; break; + case kMainViewGraphicCone: len2 = 3; break; + case kMainViewGraphicEllipsoid: len2 = 4; break; + } + if (len2 < len) + len2 = len; + gp->npoints = len2; + } + for (pindex = 0; pindex < len && pindex < gp->npoints; pindex++) { + aval = RARRAY_PTR(pval)[pindex]; + if ((gp->kind == kMainViewGraphicCylinder || gp->kind == kMainViewGraphicCone) && pindex == 2) { + v.x = NUM2DBL(rb_Float(aval)); + v.y = v.z = 0; + } else if (gp->kind == kMainViewGraphicEllipsoid && pindex == 1 && len == 2) { + v.x = NUM2DBL(rb_Float(aval)); + v.y = v.z = 0; + gp->points[7] = gp->points[11] = v.x; + gp->points[6] = gp->points[8] = gp->points[9] = gp->points[10] = 0; + break; + } else VectorFromValue(aval, &v); + gp->points[pindex * 3] = v.x; + gp->points[pindex * 3 + 1] = v.y; + gp->points[pindex * 3 + 2] = v.z; + } + } + if (gp->kind == kMainViewGraphicPoly) { + /* Calculate normals */ + s_CalculateGraphicNormals(gp); + } + MolActionCallback_registerUndo(mol, act); + MolActionRelease(act); MoleculeCallback_notifyModification(mol, 0); return nval; } /* * call-seq: + * get_graphic_color(graphic_index) -> value + * + * Get the color of graphic_index-th graphic object + */ +static VALUE +s_Molecule_GetGraphicColor(VALUE self, VALUE gval) +{ + MainViewGraphic *gp; + Molecule *mol; + int index; + Data_Get_Struct(self, Molecule, mol); + if (mol->mview == NULL) + rb_raise(rb_eMolbyError, "this molecule has no associated graphic view"); + index = NUM2INT(rb_Integer(gval)); + if (index < 0 || index >= mol->mview->ngraphics) + rb_raise(rb_eArgError, "the graphic index is out of range"); + gp = mol->mview->graphics + index; + return rb_ary_new3(4, rb_float_new(gp->rgba[0]), rb_float_new(gp->rgba[1]), rb_float_new(gp->rgba[2]), rb_float_new(gp->rgba[3])); +} + +/* + * call-seq: * set_graphic_color(graphic_index, new_value) -> new_value * * Change the color of graphic_index-th graphic object @@ -9770,7 +10055,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"); @@ -9778,15 +10065,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; } @@ -9857,6 +10150,8 @@ s_Molecule_ShowText(VALUE self, VALUE arg) return Qnil; } +#pragma mark ------ MD Support ------ + /* * call-seq: * md_arena -> MDArena @@ -9921,6 +10216,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 @@ -10009,8 +10399,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"); @@ -10079,10 +10469,63 @@ 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 -1, - * then the attributes of the current surface are modified. + * 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, @@ -10103,11 +10546,19 @@ s_Molecule_CreateSurface(int argc, VALUE *argv, VALUE self) Double d[4]; Data_Get_Struct(self, Molecule, mol); rb_scan_args(argc, argv, "11", &nval, &hval); - nmo = NUM2INT(rb_Integer(nval)); if (mol->bset == NULL) rb_raise(rb_eMolbyError, "No MO information is given"); - if ((nmo <= 0 && nmo != -1) || nmo > mol->bset->nmos) - rb_raise(rb_eMolbyError, "MO index (%d) is out of range; should be 1..%d", nmo, mol->bset->nmos); + 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; @@ -10130,29 +10581,35 @@ s_Molecule_CreateSurface(int argc, VALUE *argv, VALUE self) 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"); } - if (hval != Qnil && (((nx = 1), (aval = rb_hash_aref(hval, ID2SYM(rb_intern("color")))) != Qnil) || - ((nx = 0), (aval = rb_hash_aref(hval, ID2SYM(rb_intern("color0")))) != 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 (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]; } - 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 && need_recalc) - nmo = mol->mcube->idn; /* Force recalculation */ + 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; @@ -10168,7 +10625,7 @@ static VALUE s_Molecule_SetSurfaceAttr(VALUE self, VALUE hval) { VALUE args[2]; - args[0] = INT2FIX(-1); + args[0] = Qnil; args[1] = hval; return s_Molecule_CreateSurface(2, args, self); } @@ -10196,35 +10653,94 @@ s_Molecule_NElpots(VALUE self) * returns nil. */ static VALUE -s_Molecule_Elpot(VALUE self, VALUE ival) +s_Molecule_Elpot(VALUE self, VALUE ival) +{ + Molecule *mol; + int idx; + Data_Get_Struct(self, Molecule, mol); + idx = NUM2INT(rb_Integer(ival)); + if (idx < 0 || idx >= mol->nelpots) + return Qnil; + return rb_ary_new3(2, ValueFromVector(&mol->elpots[idx].pos), rb_float_new(mol->elpots[idx].esp)); +} + +/* + * call-seq: + * 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; - int idx; Data_Get_Struct(self, Molecule, mol); - idx = NUM2INT(rb_Integer(ival)); - if (idx < 0 || idx >= mol->nelpots) - return Qnil; - return rb_ary_new3(2, ValueFromVector(&mol->elpots[idx].pos), rb_float_new(mol->elpots[idx].esp)); + 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(sym, nprims, atom_index) + * add_gaussian_orbital_shell(atom_index, sym, no_of_primitives[, additional_exponent]) * - * To be used internally. Add a gaussian orbital shell with symmetry code, number of primitives, - * and the corresponding atom index. Symmetry code: 0, S-type; 1, P-type; -1, SP-type; 2, D-type; - * -2, D5-type. + * To be used internally. Add a gaussian orbital shell with the atom index, symmetry code, + * and the number of primitives. + * Additional exponent is for JANPA only; implements an additinal r^N component that + * appears in cartesian->spherical conversion. + * Symmetry code: 0, S-type; 1, P-type; -1, SP-type; 2, D-type; -2, D5-type; + * 3, F-type; -3, F7-type; 4, G-type; -4, G9-type. + * Or: "s", S-type; "p", P-type; "sp", SP-type; "d", D-type; "d5", D5-type; + * "f", F-type; "f7", F7-type; "g", G-type; "g9", G9-type */ static VALUE -s_Molecule_AddGaussianOrbitalShell(VALUE self, VALUE symval, VALUE npval, VALUE aval) +s_Molecule_AddGaussianOrbitalShell(int argc, VALUE *argv, VALUE self) { Molecule *mol; - int sym, nprims, a_idx, n; - Data_Get_Struct(self, Molecule, mol); - sym = NUM2INT(rb_Integer(symval)); - nprims = NUM2INT(rb_Integer(npval)); + int sym, nprims, a_idx, n, add_exp; + VALUE aval, symval, npval, addval; + Data_Get_Struct(self, Molecule, mol); + rb_scan_args(argc, argv, "31", &aval, &symval, &npval, &addval); + if (rb_obj_is_kind_of(symval, rb_cString)) { + const char *p = StringValuePtr(symval); + if (strcasecmp(p, "s") == 0) + sym = 0; + else if (strcasecmp(p, "p") == 0) + sym = 1; + else if (strcasecmp(p, "sp") == 0) + sym = -1; + else if (strcasecmp(p, "d") == 0) + sym = 2; + else if (strcasecmp(p, "d5") == 0) + sym = -2; + else if (strcasecmp(p, "f") == 0) + sym = 3; + else if (strcasecmp(p, "f7") == 0) + sym = -3; + else if (strcasecmp(p, "g") == 0) + sym = 4; + else if (strcasecmp(p, "g9") == 0) + sym = -4; + else + rb_raise(rb_eArgError, "Unknown orbital type '%s'", p); + } else { + sym = NUM2INT(rb_Integer(symval)); + } a_idx = NUM2INT(rb_Integer(aval)); - n = MoleculeAddGaussianOrbitalShell(mol, sym, nprims, a_idx); + nprims = NUM2INT(rb_Integer(npval)); + if (addval != Qnil) + add_exp = NUM2INT(rb_Integer(addval)); + else add_exp = 0; + n = MoleculeAddGaussianOrbitalShell(mol, a_idx, sym, nprims, add_exp); if (n == -1) rb_raise(rb_eMolbyError, "Molecule is emptry"); else if (n == -2) @@ -10264,33 +10780,135 @@ s_Molecule_AddGaussianPrimitiveCoefficients(VALUE self, VALUE expval, VALUE cval /* * call-seq: - * mo_type + * get_gaussian_shell_info(shell_index) -> [atom_index, sym, no_of_primitives, comp_index, no_of_components] * - * Returns either "RHF", "UHF", or "ROHF". If no MO info is present, returns nil. + * Get the Gaussian shell information for the given MO coefficient index. + * The symmetry code is the same as in add_gaussian_orbital_shell. + * The comp_index is the index of the first MO component belonging to this shell, and no_of_components + * is the number of MO component belonging to this shell. */ static VALUE -s_Molecule_MOType(VALUE self) +s_Molecule_GetGaussianShellInfo(VALUE self, VALUE sval) { Molecule *mol; + ShellInfo *sp; + int s_idx, sym; Data_Get_Struct(self, Molecule, mol); - if (mol != NULL && mol->bset != NULL) { - const char *s; - int rflag = mol->bset->rflag; - if (rflag == 0) - s = "UHF"; - else if (rflag == 2) - s = "ROHF"; - else s = "RHF"; - return rb_str_new2(s); - } else return Qnil; + if (mol->bset == NULL) + rb_raise(rb_eMolbyError, "No basis set information is defined"); + s_idx = NUM2INT(rb_Integer(sval)); + if (s_idx < 0 || s_idx >= mol->bset->nshells) + return Qnil; + sp = mol->bset->shells + s_idx; + sym = sp->sym; + switch (sym) { + case kGTOType_S: sym = 0; break; + case kGTOType_SP: sym = -1; break; + case kGTOType_P: sym = 1; break; + case kGTOType_D: sym = 2; break; + case kGTOType_D5: sym = -2; break; + case kGTOType_F: sym = 3; break; + case kGTOType_F7: sym = -3; break; + case kGTOType_G: sym = 4; break; + case kGTOType_G9: sym = -4; break; + default: + rb_raise(rb_eMolbyError, "The Gaussian shell type (%d) is unknown (internal error?)", sym); + } + return rb_ary_new3(5, INT2NUM(sp->a_idx), INT2NUM(sym), INT2NUM(sp->nprim), INT2NUM(sp->m_idx), INT2NUM(sp->ncomp)); +} + +/* + * call-seq: + * get_gaussian_primitive_coefficients(shell_index) -> [[exp1, con1 [, con_sp1]], [exp2, con2 [, con_sp2]],...] + * + * Get the Gaussian primitive coefficients for the given MO component. + */ +static VALUE +s_Molecule_GetGaussianPrimitiveCoefficients(VALUE self, VALUE sval) +{ + Molecule *mol; + ShellInfo *sp; + PrimInfo *pp; + int s_idx, i; + VALUE retval, aval; + Data_Get_Struct(self, Molecule, mol); + if (mol->bset == NULL) + rb_raise(rb_eMolbyError, "No basis set information is defined"); + s_idx = NUM2INT(rb_Integer(sval)); + if (s_idx < 0 || s_idx >= mol->bset->nshells) + return Qnil; + sp = mol->bset->shells + s_idx; + pp = mol->bset->priminfos + sp->p_idx; + retval = rb_ary_new2(sp->nprim); + for (i = 0; i < sp->nprim; i++) { + if (sp->sym == kGTOType_SP) { + /* With P contraction coefficient */ + aval = rb_ary_new3(3, rb_float_new(pp[i].A), rb_float_new(pp[i].C), rb_float_new(pp[i].Csp)); + } else { + /* Without P contraction coefficient */ + aval = rb_ary_new3(2, rb_float_new(pp[i].A), rb_float_new(pp[i].C)); + } + rb_ary_store(retval, i, aval); + } + return retval; +} + +/* + * call-seq: + * get_gaussian_component_info(comp_index) -> [atom_index, shell_index, orbital_description] + * + * Get the Gaussian shell information for the given MO coefficient index. + */ +static VALUE +s_Molecule_GetGaussianComponentInfo(VALUE self, VALUE cval) +{ + Molecule *mol; + Int n, c, atom_idx, shell_idx; + char label[32]; + Data_Get_Struct(self, Molecule, mol); + if (mol->bset == NULL) + rb_raise(rb_eMolbyError, "No basis set information is defined"); + c = NUM2INT(rb_Integer(cval)); + if (c < 0 || c >= mol->bset->ncomps) + return Qnil; + n = MoleculeGetGaussianComponentInfo(mol, c, &atom_idx, label, &shell_idx); + if (n != 0) + rb_raise(rb_eMolbyError, "Cannot get the shell info for component index (%d)", c); + return rb_ary_new3(3, INT2NUM(atom_idx), INT2NUM(shell_idx), Ruby_NewEncodedStringValue2(label)); +} + +/* + * call-seq: + * clear_mo_coefficients + * + * Clear the existing MO coefficients. + */ +static VALUE +s_Molecule_ClearMOCoefficients(VALUE self) +{ + Molecule *mol; + Data_Get_Struct(self, Molecule, mol); + if (mol->bset != NULL) { + if (mol->bset->moenergies != NULL) { + free(mol->bset->moenergies); + mol->bset->moenergies = NULL; + } + if (mol->bset->mo != NULL) { + free(mol->bset->mo); + mol->bset->mo = NULL; + } + mol->bset->nmos = 0; + } + return self; } /* * call-seq: * set_mo_coefficients(idx, energy, coefficients) * - * To be used internally. Add a MO coefficients. Idx is the MO index (for open shell system, - * beta MOs comes after all alpha MOs), energy is the MO energy, coefficients is an array + * To be used internally. Add a MO coefficients. Idx is the MO index (1-based; for open shell system, + * beta MOs comes after all alpha MOs; alternatively, the beta MO can be specified as a negative MO number) + * Energy is the MO energy, and coefficients is an array * of MO coefficients. */ static VALUE @@ -10312,7 +10930,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"); @@ -10321,7 +10939,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) @@ -10333,7 +10951,7 @@ end: * call-seq: * get_mo_coefficients(idx) * - * To be used internally. Get an array of MO coefficients for the given MO index (0-based). + * To be used internally. Get an array of MO coefficients for the given MO index (1-based). */ static VALUE s_Molecule_GetMOCoefficients(VALUE self, VALUE ival) @@ -10365,7 +10983,7 @@ s_Molecule_GetMOCoefficients(VALUE self, VALUE ival) * call-seq: * get_mo_energy(idx) * - * To be used internally. Get the MO energy for the given MO index (0-based). + * To be used internally. Get the MO energy for the given MO index (1-based). */ static VALUE s_Molecule_GetMOEnergy(VALUE self, VALUE ival) @@ -10385,34 +11003,121 @@ s_Molecule_GetMOEnergy(VALUE self, VALUE ival) 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: - * allocate_basis_set_record(rflag, ne_alpha, ne_beta) + * set_mo_info(hash) * - * To be used internally. Allocate a basis set record. rflag: 0, unrestricted; 1, restricted. - * ne_alpha, ne_beta: number of alpha/beta electrons. + * Set the MO info. hash keys: :type=>"RHF"|"UHF"|"ROHF", + * :alpha=>integer, :beta=>integer */ static VALUE -s_Molecule_AllocateBasisSetRecord(VALUE self, VALUE rval, VALUE naval, VALUE nbval) +s_Molecule_SetMOInfo(VALUE self, VALUE hval) { Molecule *mol; + VALUE aval; Int rflag, na, nb, n; + char *s; 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); - 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"); + 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. @@ -10429,7 +11134,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) @@ -10476,7 +11181,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); @@ -10544,6 +11254,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 @@ -10600,7 +11468,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; } @@ -10623,90 +11491,40 @@ s_Molecule_List(VALUE klass) { Molecule *mol; int i; - VALUE ary; - i = 0; - ary = rb_ary_new(); - while ((mol = MoleculeCallback_moleculeAtIndex(i)) != NULL) { - rb_ary_push(ary, ValueFromMolecule(mol)); - i++; - } - return ary; -} - -/* - * call-seq: - * ordered_list -> array of Molecules - * - * Get the list of molecules associated to the documents, in the order of front-to-back - * ordering of the associated window. If no document is open, returns an empry array. - */ -static VALUE -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)); + VALUE ary; + i = 0; + ary = rb_ary_new(); + while ((mol = MoleculeCallback_moleculeAtIndex(i)) != NULL) { + rb_ary_push(ary, ValueFromMolecule(mol)); + i++; } - return sval; + return ary; } /* * call-seq: - * self == Molecule -> boolean + * ordered_list -> array of Molecules * - * True if the two arguments point to the same molecule. + * Get the list of molecules associated to the documents, in the order of front-to-back + * ordering of the associated window. If no document is open, returns an empry array. */ static VALUE -s_Molecule_Equal(VALUE self, VALUE val) +s_Molecule_OrderedList(VALUE klass) { - 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; + 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; } +#pragma mark ------ Call Subprocess ------ + /* The callback functions for call_subprocess_async */ static int s_Molecule_CallSubProcessAsync_EndCallback(Molecule *mol, int status) @@ -10745,6 +11563,10 @@ s_Molecule_CallSubProcessAsync_TimerCallback(Molecule *mol, int tcount) * call_subprocess_async(cmd [, end_callback [, timer_callback [, standard_output_file [, error_output_file]]]]) * * Call subprocess asynchronically. + * cmd is either a single string of an array of string. If it is a single string, then + * it will be given to wxExecute as a single argument. In this case, the string can be + * split into arguments by whitespace. If this behavior is not intended, then use an array + * containing a single string. * If end_callback is given, it will be called (with two arguments self and termination status) * when the subprocess terminated. * If timer_callback is given, it will be called (also with two arguments, self and timer count). @@ -10761,6 +11583,7 @@ s_Molecule_CallSubProcessAsync(int argc, VALUE *argv, VALUE self) VALUE cmd, end_proc, timer_proc, stdout_val, stderr_val; Molecule *mol; char *sout, *serr; + const char **cmdargv; int n; FILE *fpout, *fperr; rb_scan_args(argc, argv, "14", &cmd, &end_proc, &timer_proc, &stdout_val, &stderr_val); @@ -10808,7 +11631,21 @@ s_Molecule_CallSubProcessAsync(int argc, VALUE *argv, VALUE self) /* Register procs as instance variables */ rb_ivar_set(self, rb_intern("end_proc"), end_proc); rb_ivar_set(self, rb_intern("timer_proc"), timer_proc); - n = MoleculeCallback_callSubProcessAsync(mol, StringValuePtr(cmd), s_Molecule_CallSubProcessAsync_EndCallback, (timer_proc == Qnil ? NULL : s_Molecule_CallSubProcessAsync_TimerCallback), fpout, fperr); + + if (rb_obj_is_kind_of(cmd, rb_cString)) { + cmdargv = calloc(sizeof(cmdargv[0]), 3); + cmdargv[0] = StringValuePtr(cmd); + cmdargv[1] = ""; + cmdargv[2] = NULL; + } else { + cmd = rb_ary_to_ary(cmd); + cmdargv = calloc(sizeof(cmdargv[0]), RARRAY_LEN(cmd) + 1); + for (n = 0; n < RARRAY_LEN(cmd); n++) { + cmdargv[n] = StringValuePtr(RARRAY_PTR(cmd)[n]); + } + cmdargv[n] = NULL; + } + n = MoleculeCallback_callSubProcessAsync(mol, cmdargv, s_Molecule_CallSubProcessAsync_EndCallback, (timer_proc == Qnil ? NULL : s_Molecule_CallSubProcessAsync_TimerCallback), fpout, fperr); if (fpout != NULL && fpout != (FILE *)1) fclose(fpout); if (fperr != NULL && fperr != (FILE *)1) @@ -10816,6 +11653,8 @@ s_Molecule_CallSubProcessAsync(int argc, VALUE *argv, VALUE self) return INT2NUM(n); } +#pragma mark ====== Define Molby Classes ====== + void Init_Molby(void) { @@ -10832,9 +11671,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"); @@ -10845,19 +11688,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); @@ -10869,50 +11718,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); @@ -10933,54 +11753,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); @@ -10992,9 +11833,12 @@ 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); @@ -11031,45 +11875,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); rb_define_method(rb_cMolecule, "show_text", s_Molecule_ShowText, 1); + rb_define_method(rb_cMolecule, "md_arena", s_Molecule_MDArena, 0); rb_define_method(rb_cMolecule, "set_parameter_attr", s_Molecule_SetParameterAttr, 5); rb_define_method(rb_cMolecule, "parameter", s_Molecule_Parameter, 0); + rb_define_method(rb_cMolecule, "start_step", s_Molecule_StartStep, 0); + rb_define_method(rb_cMolecule, "start_step=", s_Molecule_SetStartStep, 1); + rb_define_method(rb_cMolecule, "steps_per_frame", s_Molecule_StepsPerFrame, 0); + rb_define_method(rb_cMolecule, "steps_per_frame=", s_Molecule_SetStepsPerFrame, 1); + rb_define_method(rb_cMolecule, "ps_per_step", s_Molecule_PsPerStep, 0); + rb_define_method(rb_cMolecule, "ps_per_step=", s_Molecule_SetPsPerStep, 1); + rb_define_method(rb_cMolecule, "bond_par", s_Molecule_BondParIsObsolete, 1); /* Obsolete */ + rb_define_method(rb_cMolecule, "angle_par", s_Molecule_BondParIsObsolete, 1); /* Obsolete */ + rb_define_method(rb_cMolecule, "dihedral_par", s_Molecule_BondParIsObsolete, 1); /* Obsolete */ + rb_define_method(rb_cMolecule, "improper_par", s_Molecule_BondParIsObsolete, 1); /* Obsolete */ + rb_define_method(rb_cMolecule, "vdw_par", s_Molecule_BondParIsObsolete, 1); /* Obsolete */ + rb_define_method(rb_cMolecule, "selected_MO", s_Molecule_SelectedMO, 0); rb_define_method(rb_cMolecule, "default_MO_grid", s_Molecule_GetDefaultMOGrid, -1); rb_define_method(rb_cMolecule, "cubegen", s_Molecule_Cubegen, -1); + rb_define_method(rb_cMolecule, "clear_surface", s_Molecule_ClearSurface, 0); + rb_define_method(rb_cMolecule, "show_surface", s_Molecule_ShowSurface, 0); + rb_define_method(rb_cMolecule, "hide_surface", s_Molecule_HideSurface, 0); rb_define_method(rb_cMolecule, "create_surface", s_Molecule_CreateSurface, -1); rb_define_method(rb_cMolecule, "set_surface_attr", s_Molecule_SetSurfaceAttr, 1); rb_define_method(rb_cMolecule, "nelpots", s_Molecule_NElpots, 0); rb_define_method(rb_cMolecule, "elpot", s_Molecule_Elpot, 1); - rb_define_method(rb_cMolecule, "add_gaussian_orbital_shell", s_Molecule_AddGaussianOrbitalShell, 3); + rb_define_method(rb_cMolecule, "clear_basis_set", s_Molecule_ClearBasisSet, 0); + rb_define_method(rb_cMolecule, "add_gaussian_orbital_shell", s_Molecule_AddGaussianOrbitalShell, -1); rb_define_method(rb_cMolecule, "add_gaussian_primitive_coefficients", s_Molecule_AddGaussianPrimitiveCoefficients, 3); - rb_define_method(rb_cMolecule, "mo_type", s_Molecule_MOType, 0); + rb_define_method(rb_cMolecule, "get_gaussian_shell_info", s_Molecule_GetGaussianShellInfo, 1); + rb_define_method(rb_cMolecule, "get_gaussian_primitive_coefficients", s_Molecule_GetGaussianPrimitiveCoefficients, 1); + rb_define_method(rb_cMolecule, "get_gaussian_component_info", s_Molecule_GetGaussianComponentInfo, 1); + rb_define_method(rb_cMolecule, "clear_mo_coefficients", s_Molecule_ClearMOCoefficients, 0); rb_define_method(rb_cMolecule, "set_mo_coefficients", s_Molecule_SetMOCoefficients, 3); rb_define_method(rb_cMolecule, "get_mo_coefficients", s_Molecule_GetMOCoefficients, 1); rb_define_method(rb_cMolecule, "get_mo_energy", s_Molecule_GetMOEnergy, 1); - rb_define_method(rb_cMolecule, "allocate_basis_set_record", s_Molecule_AllocateBasisSetRecord, 3); + 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); @@ -11095,7 +11968,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); @@ -11220,9 +12093,35 @@ Init_Molby(void) 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 ====== @@ -11282,12 +12181,12 @@ s_evalRubyScriptOnMoleculeSub(VALUE val) #else asprintf(&scr, "#coding:utf-8\n%s", (char *)ptr[0]); #endif - sval = rb_str_new2(scr); + sval = Ruby_NewEncodedStringValue2(scr); free(scr); - fnval = rb_str_new2("(eval)"); + fnval = Ruby_NewEncodedStringValue2("(eval)"); lnval = INT2FIX(0); } else { - sval = rb_str_new2((char *)ptr[0]); + sval = Ruby_NewEncodedStringValue2((char *)ptr[0]); fnval = Ruby_NewFileStringValue((char *)ptr[2]); lnval = INT2FIX(1); } @@ -11341,8 +12240,28 @@ Molby_evalRubyScriptOnMolecule(const char *script, Molecule *mol, const char *fn return retval; } +/* 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 -Molby_showRubyValue(RubyValue value, char **outValueString) +Ruby_showValue(RubyValue value, char **outValueString) { VALUE val = (VALUE)value; if (gMolbyIsCheckingInterrupt) { @@ -11369,66 +12288,126 @@ Molby_showRubyValue(RubyValue value, char **outValueString) } 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 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 " -#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(), + "Molby", #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\n" + "JANPA 2.01, https://janpa.sourceforge.net/\n" + " Copyright (C) 2014, Tymofii Nikolaienko", + 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\n" + "JANPA 2.01, https://janpa.sourceforge.net/\n" + " Copyright (C) 2014, Tymofii Nikolaienko", + gRubyVersion, gRubyCopyright); + + } if (revisionString[0] != 0) free(revisionString); - return s; + if (versionString != NULL) + *versionString = s1; + if (auxString != NULL) + *auxString = s2; } void @@ -11443,11 +12422,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); } @@ -11480,40 +12455,34 @@ 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 @@ -11526,11 +12495,31 @@ Molby_startup(const char *script, const char *dir) ruby_init(); { - extern void Init_shift_jis(void), Init_trans_japanese_sjis(void); - Init_shift_jis(); - Init_trans_japanese_sjis(); + /* 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); @@ -11553,7 +12542,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); @@ -11572,32 +12562,29 @@ Molby_startup(const char *script, const char *dir) rb_define_const(rb_mMolby, "DocumentDirectory", 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_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); - -#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 error information */ rb_define_variable("$backtrace", &gMolbyBacktrace); @@ -11610,24 +12597,21 @@ Molby_startup(const char *script, const char *dir) gScriptMenuCommands = rb_ary_new(); gScriptMenuEnablers = rb_ary_new(); -#if !__CMDMAC__ - /* Register interrupt check code */ - rb_add_event_hook(s_Event_Callback, RUBY_EVENT_ALL, Qnil); -#endif - -#if !__CMDMAC__ - /* Start interval timer (for periodic polling of interrupt); firing every 50 msec */ - s_SetIntervalTimer(0, 50); -#endif + 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"); }