/*! This function calculates the torsion angle of three vectors, represented by four points A--B--C--D, i.e. B and C are vertexes, but none of A--B, B--C, and C--D are colinear. A "torsion angle" is the amount of "twist" or torsion needed around the B--C axis to bring A--B into the same plane as B--C--D. The torsion is measured by "looking down" the vector B--C so that B is superimposed on C, then noting how far you'd have to rotate A--B to superimpose A over D. Angles are + in theanticlockwise direction. The operation is symmetrical in that if you reverse the image (look from C to B and rotate D over A), you get the same answer. */ OBAPI double VectorTorsion(const Eigen::Vector3d& a, const Eigen::Vector3d& b, const Eigen::Vector3d& c, const Eigen::Vector3d& d) { // Bond vectors of the three atoms Eigen::Vector3d ab = b - a; Eigen::Vector3d bc = c - b; Eigen::Vector3d cd = d - c; // length of the three bonds const double l_ab = ab.norm(); const double l_bc = bc.norm(); const double l_cd = cd.norm(); if (IsNearZero(l_ab) || IsNearZero(l_bc) || IsNearZero(l_cd) ) { return 0.0; } // normalize the bond vectors: ab *= (1.0 / l_ab); bc *= (1.0 / l_bc); cd *= (1.0 / l_cd); const Eigen::Vector3d ca = ab.cross(bc); const Eigen::Vector3d cb = bc.cross(cd); const Eigen::Vector3d cc = ca.cross(cb); const double d1 = cc.dot(bc); const double d2 = ca.dot(cb); const double tor = RAD_TO_DEG * atan2(d1, d2); return tor; }
/*! This method calculates the angle between two vectors \warning If length() of any of the two vectors is == 0.0, this method will divide by zero. If the product of the length() of the two vectors is very close to 0.0, but not == 0.0, this method may behave in unexpected ways and return almost random results; details may depend on your particular floating point implementation. The use of this method is therefore highly discouraged, unless you are certain that the length()es are in a reasonable range, away from 0.0 (Stefan Kebekus) \deprecated This method will probably replaced by a safer algorithm in the future. \todo Replace this method with a more fool-proof version. @returns the angle in degrees (0-360) */ OBAPI double VectorAngle (const Eigen::Vector3d& ab, const Eigen::Vector3d& bc) { // length of the two bonds const double l_ab = ab.norm(); const double l_bc = bc.norm(); if (IsNearZero(l_ab) || IsNearZero(l_bc)) { return 0.0; } // Calculate the cross product of v1 and v2, test if it has length unequal 0 const Eigen::Vector3d c1 = ab.cross(bc); if (IsNearZero(c1.norm())) { return 0.0; } // Calculate the cos of theta and then theta const double dp = ab.dot(bc) / (l_ab * l_bc); if (dp > 1.0) { return 0.0; } else if (dp < -1.0) { return 180.0; } else { #ifdef USE_ACOS_LOOKUP_TABLE return (RAD_TO_DEG * acosLookup(dp)); #else return (RAD_TO_DEG * acos(dp)); #endif } return 0.0; }
double DiscretizedArcLength(const LLPoint ¢er, double dRadius, double dStartCrs, double dEndCrs, int nOrient, int nSegments, double dTol) { nSegments = clamp(nSegments, 1, 128); double dError = 0.0; double dArcLength = 0.0; double dOldArcLength = 0.0; const double dSubtendedAngle = GetArcExtent(dStartCrs, dEndCrs, nOrient, dTol); // with k equal to 0 then there will only be nSegments subsegments calculated. // need to figure out how to make this flexible based on the value in dRadius. // Bigger radius need more segments than 16 and smaller segments need less. // For now 16 is enough to pass the 8260.54A test case. int k = 0; while (k == 0 || ((dError > kTol) && (k <= 0))) { const double dTheta = dSubtendedAngle / nSegments; const double dAltitude = 0.0; dArcLength = 0.0; for (int i = 0; i < nSegments; i++) { const double theta = dStartCrs + i * dTheta; const LLPoint p1 = DestVincenty(center, theta, dRadius); const LLPoint p2 = DestVincenty(center, theta + 0.5 * dTheta, dRadius); const LLPoint p3 = DestVincenty(center, theta + dTheta, dRadius); const VMath::Vector3 v1 = ECEF(p1, dAltitude); const VMath::Vector3 v2 = ECEF(p2, dAltitude); const VMath::Vector3 v3 = ECEF(p3, dAltitude); const VMath::Vector3 vChord1 = v2 - v1; const VMath::Vector3 vChord2 = v2 - v3; const double x1 = VMath::Vector3::Length(vChord1); //v2 - v1); const double x2 = VMath::Vector3::Length(vChord2); //v2 - v3); const double d = VMath::Vector3::Dot(vChord1, vChord2); if (IsNearZero(x1, kTol) && IsNearZero(x2, kTol) && IsNearZero(d, kTol)) { dArcLength = 0.0; break; } const double xi = d / (x1 * x2); const double _x1_x2Sq = x1 / x2 - xi; const double sigma = sqrt(1.0 - (xi * xi)); const double R = (x2 * sqrt((_x1_x2Sq * _x1_x2Sq) + (sigma * sigma))) / (2.0 * sigma); const double A = 2 * (M_PI - acos(xi)); const double L = R * A; dArcLength += L; } nSegments *= 2; dError = fabs(dArcLength - dOldArcLength); dOldArcLength = dArcLength; k++; } return dArcLength; }
OBAPI double VectorOOP(const Eigen::Vector3d &a, const Eigen::Vector3d &b, const Eigen::Vector3d &c,const Eigen::Vector3d &d) { // This is adapted from http://scidok.sulb.uni-saarland.de/volltexte/2007/1325/pdf/Dissertation_1544_Moll_Andr_2007.pdf // Many thanks to Andreas Moll and the BALLView developers for this // calculate normalized bond vectors from central atom to outer atoms: Eigen::Vector3d ab = a - b; // store length of this bond: const double length_ab = ab.norm(); if (IsNearZero(length_ab)) { return 0.0; } // store the normalized bond vector from central atom to outer atoms: // normalize the bond vector: ab /= length_ab; Eigen::Vector3d cb = c - b; const double length_cb = cb.norm(); if (IsNearZero(length_cb)) { return 0.0; } cb /= length_cb; Eigen::Vector3d db = d - b; const double length_db = db.norm(); if (IsNearZero(length_db)) { return 0.0; } db /= length_db; // the normal vectors of the three planes: const Eigen::Vector3d an = ab.cross(cb); const Eigen::Vector3d bn = cb.cross(db); const Eigen::Vector3d cn = db.cross(ab); // Bond angle ji to jk const double cos_theta = ab.dot(cb); #ifdef USE_ACOS_LOOKUP_TABLE const double theta = acosLookup(cos_theta); #else const double theta = acos(cos_theta); #endif // If theta equals 180 degree or 0 degree if (IsNearZero(theta) || IsNearZero(fabs(theta - M_PI))) { return 0.0; } const double sin_theta = sin(theta); const double sin_dl = an.dot(db) / sin_theta; // the wilson angle: const double dl = asin(sin_dl); return RAD_TO_DEG * dl; }
void FindLinearRoot( double *x, double *errArray, double & root ) { if(x[0] == x[1]) { root = std::numeric_limits<double>::signaling_NaN(); } else if(errArray[0] == errArray[1]) { if(IsNearZero(errArray[0] - errArray[1], 1e-15)) { root = x[0]; } else { root = std::numeric_limits<double>::signaling_NaN(); } } else { root = -errArray[0] * (x[1] - x[0]) / (errArray[1] - errArray[0]) + x[0]; } }
void OBBond::SetLength(OBAtom *fixed, double length) { unsigned int i; OBMol *mol = (OBMol*)fixed->GetParent(); vector3 v1,v2,v3,v4,v5; vector<int> children; obErrorLog.ThrowError(__FUNCTION__, "Ran OpenBabel::SetBondLength", obAuditMsg); int a = fixed->GetIdx(); int b = GetNbrAtom(fixed)->GetIdx(); if (a == b) return; // this would be a problem... mol->FindChildren(children,a,b); children.push_back(b); v1 = GetNbrAtom(fixed)->GetVector(); v2 = fixed->GetVector(); v3 = v1 - v2; if (IsNearZero(v3.length_2())) { // too small to normalize, move the atoms apart obErrorLog.ThrowError(__FUNCTION__, "Atoms are both at the same location, moving out of the way.", obWarning); v3.randomUnitVector(); } else { v3.normalize(); } v3 *= length; v3 += v2; v4 = v3 - v1; cerr << "v3: " << v3 << " v4: " << v4 << endl; for ( i = 0 ; i < children.size() ; i++ ) { v1 = mol->GetAtom(children[i])->GetVector(); v1 += v4; mol->GetAtom(children[i])->SetVector(v1); } }
int GeoLocusIntersect(const LLPoint &gStart, const LLPoint &gEnd, const Locus &loc, LLPoint &intersect, double dTol, double dEps) { InverseResult result; DistVincenty(gStart, gEnd, result); const double gAz = result.azimuth; DistVincenty(loc.locusStart, loc.locusEnd, result); const double locStAz = result.azimuth; const double locLength = result.distance; double crs31, crs32, dist13, dist23; LLPoint pt1; if (!CrsIntersect(loc.locusStart, locStAz, crs31, dist13, gStart, gAz, crs32, dist23, dTol, pt1)) return 0; DistVincenty(loc.geoStart, loc.geoEnd, result); const double tcrs = result.azimuth; double crsFromPt, distFromPt; LLPoint ptInt = PerpIntercept(loc.geoStart, tcrs, pt1, crsFromPt, distFromPt, dTol); double distLoc = DistToLocusP(loc, ptInt, dTol, dEps); double distarray[2]; double errarray[2]; errarray[1] = distFromPt - fabs(distLoc); distarray[1] = dist23; double distBase = dist23 - errarray[1] / cos(fabs(SignAzimuthDifference(crsFromPt, crs32))); int k = 0; const int maxCount = 10; while (!std::isnan(distBase) && fabs(errarray[1]) > dTol && k < maxCount) { pt1 = DestVincenty(gStart, gAz, distBase); errarray[0] = errarray[1]; distarray[0] = distarray[1]; distarray[1] = distBase; ptInt = PerpIntercept(loc.geoStart, tcrs, pt1, crsFromPt, distFromPt, dTol); distLoc = DistToLocusP(loc, ptInt, dTol, dEps); errarray[1] = distFromPt - fabs(distLoc); FindLinearRoot(distarray, errarray, distBase); k++; } intersect = pt1; DistVincenty(intersect, loc.locusStart, result); const double distLocStPt1 = result.distance; DistVincenty(intersect, loc.locusEnd, result); // found intersect point must be on or between locus // If 5e-3 is to tight a tolerance then try setting to 5e-2 // For the 8260.54A Appendix test cases 1e-3 was to tight, 5e-3 // works just fine. if (!IsNearZero(locLength - (distLocStPt1 + result.distance), 5e-3)) return 0; return 1; }
bool MOL2Format::ReadMolecule(OBBase* pOb, OBConversion* pConv) { OBMol* pmol = pOb->CastAndClear<OBMol>(); if(pmol==NULL) return false; //Define some references so we can use the old parameter names istream &ifs = *pConv->GetInStream(); OBMol &mol = *pmol; //Old code follows... bool foundAtomLine = false; char buffer[BUFF_SIZE]; char *comment = NULL; string str,str1; vector<string> vstr; int len; mol.BeginModify(); for (;;) { if (!ifs.getline(buffer,BUFF_SIZE)) return(false); if (EQn(buffer,"@<TRIPOS>MOLECULE",17)) break; } int lcount; int natoms,nbonds; for (lcount=0;;lcount++) { if (!ifs.getline(buffer,BUFF_SIZE)) return(false); if (EQn(buffer,"@<TRIPOS>ATOM",13)) { foundAtomLine = true; break; } if (lcount == 0) { tokenize(vstr,buffer); if (!vstr.empty()) mol.SetTitle(buffer); } else if (lcount == 1) sscanf(buffer,"%d%d",&natoms,&nbonds); else if (lcount == 4) //energy { tokenize(vstr,buffer); if (!vstr.empty() && vstr.size() == 3) if (vstr[0] == "Energy") mol.SetEnergy(atof(vstr[2].c_str())); } else if (lcount == 5) //comment { if ( buffer[0] ) { len = (int) strlen(buffer)+1; // TODO allow better multi-line comments // which don't allow ill-formed data to consume memory // Thanks to Andrew Dalke for the pointer if (comment != NULL) delete [] comment; comment = new char [len]; memcpy(comment,buffer,len); } } } if (!foundAtomLine) { mol.EndModify(); mol.Clear(); obErrorLog.ThrowError(__FUNCTION__, "Unable to read Mol2 format file. No atoms found.", obWarning); return(false); } mol.ReserveAtoms(natoms); int i; vector3 v; OBAtom atom; bool hasPartialCharges=false; double x,y,z,pcharge; char temp_type[BUFF_SIZE], resname[BUFF_SIZE], atmid[BUFF_SIZE]; int elemno, resnum = -1; ttab.SetFromType("SYB"); for (i = 0;i < natoms;i++) { if (!ifs.getline(buffer,BUFF_SIZE)) return(false); sscanf(buffer," %*s %1024s %lf %lf %lf %1024s %d %1024s %lf", atmid, &x,&y,&z, temp_type, &resnum, resname, &pcharge); atom.SetVector(x, y, z); // Handle "CL" and "BR" and other mis-typed atoms str = temp_type; if (strncmp(temp_type, "CL", 2) == 0) { str = "Cl"; } else if (strncmp(temp_type,"BR",2) == 0) { str = "Br"; } else if (strncmp(temp_type,"S.o2", 4) == 02) { str = "S.O2"; } else if (strncmp(temp_type,"S.o", 3) == 0) { str = "S.O"; } else if (strncmp(temp_type,"SI", 2) == 0) { str = "Si"; // The following cases are entries which are not in openbabel/data/types.txt // and should probably be added there } else if (strncmp(temp_type,"S.1", 3) == 0) { str = "S.2"; // no idea what the best type might be here } else if (strncmp(temp_type,"P.", 2) == 0) { str = "P.3"; } else if (strncasecmp(temp_type,"Ti.", 3) == 0) { // e.g. Ti.th str = "Ti"; } else if (strncasecmp(temp_type,"Ru.", 3) == 0) { // e.g. Ru.oh str = "Ru"; } ttab.SetToType("ATN"); ttab.Translate(str1,str); elemno = atoi(str1.c_str()); ttab.SetToType("IDX"); // We might have missed some SI or FE type things above, so here's // another check if( !elemno && isupper(temp_type[1]) ) { temp_type[1] = (char)tolower(temp_type[1]); str = temp_type; ttab.Translate(str1,str); elemno = atoi(str1.c_str()); } // One last check if there isn't a period in the type, // it's a malformed atom type, but it may be the element symbol // GaussView does this (PR#1739905) if ( !elemno ) { obErrorLog.ThrowError(__FUNCTION__, "This Mol2 file is non-standard. Cannot interpret atom types correctly, instead attempting to interpret as elements instead.", obWarning); string::size_type dotPos = str.find('.'); if (dotPos == string::npos) { elemno = etab.GetAtomicNum(str.c_str()); } } atom.SetAtomicNum(elemno); ttab.SetToType("INT"); ttab.Translate(str1,str); atom.SetType(str1); atom.SetPartialCharge(pcharge); if (!mol.AddAtom(atom)) return(false); if (!IsNearZero(pcharge)) hasPartialCharges = true; // Add residue information if it exists if (resnum != -1 && resnum != 0 && strlen(resname) != 0 && strncmp(resname,"<1>", 3) != 0) { OBResidue *res = (mol.NumResidues() > 0) ? mol.GetResidue(mol.NumResidues()-1) : NULL; if (res == NULL || res->GetName() != resname || static_cast<int>(res->GetNum()) != resnum) { vector<OBResidue*>::iterator ri; for (res = mol.BeginResidue(ri) ; res ; res = mol.NextResidue(ri)) if (res->GetName() == resname && static_cast<int>(res->GetNum()) == resnum) break; if (res == NULL) { res = mol.NewResidue(); res->SetName(resname); res->SetNum(resnum); } } OBAtom *atomPtr = mol.GetAtom(mol.NumAtoms()); res->AddAtom(atomPtr); res->SetAtomID(atomPtr, atmid); } // end adding residue info } for (;;) { if (!ifs.getline(buffer,BUFF_SIZE)) return(false); str = buffer; if (!strncmp(buffer,"@<TRIPOS>BOND",13)) break; } int start,end,order; for (i = 0; i < nbonds; i++) { if (!ifs.getline(buffer,BUFF_SIZE)) return(false); sscanf(buffer,"%*d %d %d %1024s",&start,&end,temp_type); str = temp_type; order = 1; if (str == "ar" || str == "AR" || str == "Ar") order = 5; else if (str == "AM" || str == "am" || str == "Am") order = 1; else order = atoi(str.c_str()); mol.AddBond(start,end,order); } // update neighbour bonds information for each atom. vector<OBAtom*>::iterator apos; vector<OBBond*>::iterator bpos; OBAtom* patom; OBBond* pbond; for (patom = mol.BeginAtom(apos); patom; patom = mol.NextAtom(apos)) { patom->ClearBond(); for (pbond = mol.BeginBond(bpos); pbond; pbond = mol.NextBond(bpos)) { if (patom == pbond->GetBeginAtom() || patom == pbond->GetEndAtom()) { patom->AddBond(pbond); } } } // Suggestion by Liu Zhiguo 2008-01-26 // Mol2 files define atom types -- there is no need to re-perceive mol.SetAtomTypesPerceived(); mol.EndModify(); //must add generic data after end modify - otherwise it will be blown away if (comment) { OBCommentData *cd = new OBCommentData; cd->SetData(comment); cd->SetOrigin(fileformatInput); mol.SetData(cd); delete [] comment; comment = NULL; } if (hasPartialCharges) mol.SetPartialChargesPerceived(); // continue untill EOF or untill next molecule record streampos pos; for(;;) { pos = ifs.tellg(); if (!ifs.getline(buffer,BUFF_SIZE)) break; if (EQn(buffer,"@<TRIPOS>MOLECULE",17)) break; } ifs.seekg(pos); // go back to the end of the molecule return(true); }
OBAPI double VectorAngleDerivative(const Eigen::Vector3d &a, const Eigen::Vector3d &b, const Eigen::Vector3d &c, Eigen::Vector3d &Fa, Eigen::Vector3d &Fb, Eigen::Vector3d &Fc) { // This is adapted from http://scidok.sulb.uni-saarland.de/volltexte/2007/1325/pdf/Dissertation_1544_Moll_Andr_2007.pdf // Many thanks to Andreas Moll and the BALLView developers for this Eigen::Vector3d v1, v2; // Calculate the vector between atom1 and atom2, // test if the vector has length larger than 0 and normalize it v1 = a - b; v2 = c - b; const double length1 = v1.norm(); const double length2 = v2.norm(); // test if the vector has length larger than 0 and normalize it if (IsNearZero(length1) || IsNearZero(length2)) { Fa = VZero; Fb = VZero; Fc = VZero; return 0.0; } // Calculate the normalized bond vectors const double inverse_length_v1 = 1.0 / length1; const double inverse_length_v2 = 1.0 / length2; v1 *= inverse_length_v1 ; v2 *= inverse_length_v2; // Calculate the cross product of v1 and v2, test if it has length unequal 0, // and normalize it. Eigen::Vector3d c1 = v1.cross(v2); const double length = c1.norm(); if (IsNearZero(length)) { Fa = VZero; Fb = VZero; Fc = VZero; return 0.0; } c1 /= length; // Calculate the cos of theta and then theta double costheta = v1.dot(v2); double theta; if (costheta > 1.0) { theta = 0.0; costheta = 1.0; } else if (costheta < -1.0) { theta = 180.0; costheta = -1.0; } else { #ifdef USE_ACOS_LOOKUP_TABLE theta = RAD_TO_DEG * acosLookup(costheta); #else theta = RAD_TO_DEG * acos(costheta); #endif } Eigen::Vector3d t1 = v1.cross(c1); t1.normalize(); Eigen::Vector3d t2 = v2.cross(c1); t2.normalize(); Fa = -t1 * inverse_length_v1; Fc = t2 * inverse_length_v2; Fb = - (Fa + Fc); return theta; }
OBAPI double VectorOOPDerivative(const Eigen::Vector3d &a, const Eigen::Vector3d &b, const Eigen::Vector3d &c, const Eigen::Vector3d &d, Eigen::Vector3d &Fa, Eigen::Vector3d &Fb, Eigen::Vector3d &Fc, Eigen::Vector3d &Fd) { // This is adapted from http://scidok.sulb.uni-saarland.de/volltexte/2007/1325/pdf/Dissertation_1544_Moll_Andr_2007.pdf // Many thanks to Andreas Moll and the BALLView developers for this // temp variables: double length; Eigen::Vector3d delta; // calculate normalized bond vectors from central atom to outer atoms: delta = a - b; length = delta.norm(); if (IsNearZero(length)) { Fa = VZero; Fb = VZero; Fc = VZero; Fd = VZero; return 0.0; } // normalize the bond vector: delta /= length; // store the normalized bond vector from central atom to outer atoms: const Eigen::Vector3d ab = delta; // store length of this bond: const double length_ab = length; delta = c - b; length = delta.norm(); if (IsNearZero(length)) { Fa = VZero; Fb = VZero; Fc = VZero; Fd = VZero; return 0.0; } // normalize the bond vector: delta /= length; // store the normalized bond vector from central atom to outer atoms: const Eigen::Vector3d bc = delta; // store length of this bond: const double length_bc = length; delta = d - b; length = delta.norm(); if (IsNearZero(length)) { Fa = VZero; Fb = VZero; Fc = VZero; Fd = VZero; return 0.0; } // normalize the bond vector: delta /= length; // store the normalized bond vector from central atom to outer atoms: const Eigen::Vector3d bd = delta; // store length of this bond: const double length_bd = length; // the normal vectors of the three planes: const Eigen::Vector3d an = ab.cross(bc); const Eigen::Vector3d bn = bc.cross(bd); const Eigen::Vector3d cn = bd.cross(ab); // Bond angle ab to bc const double cos_theta = ab.dot(bc); #ifdef USE_ACOS_LOOKUP_TABLE const double theta = acosLookup(cos_theta); #else const double theta = acos(cos_theta); #endif // If theta equals 180 degree or 0 degree if (IsNearZero(theta) || IsNearZero(fabs(theta - M_PI))) { Fa = VZero; Fb = VZero; Fc = VZero; Fd = VZero; return 0.0; } const double sin_theta = sin(theta); const double sin_dl = an.dot(bd) / sin_theta; // the wilson angle: const double dl = asin(sin_dl); // In case: wilson angle equals 0 or 180 degree: do nothing if (IsNearZero(dl) || IsNearZero(fabs(dl - M_PI))) { Fa = VZero; Fb = VZero; Fc = VZero; Fd = VZero; return RAD_TO_DEG * dl; } const double cos_dl = cos(dl); // if wilson angle equal 90 degree: abort if (cos_dl < 0.0001) { Fa = VZero; Fb = VZero; Fc = VZero; Fd = VZero; return RAD_TO_DEG * dl; } Fd = (an / sin_theta - bd * sin_dl) / length_bd; Fa = ((bn + (((-ab + bc * cos_theta) * sin_dl) / sin_theta)) / length_ab) / sin_theta; Fc = ((cn + (((-bc + ab * cos_theta) * sin_dl) / sin_theta)) / length_bc) / sin_theta; Fb = -(Fa + Fc + Fd); return RAD_TO_DEG * dl; }
OBAPI double VectorTorsionDerivative(const Eigen::Vector3d &a, const Eigen::Vector3d &b, const Eigen::Vector3d &c, const Eigen::Vector3d &d, Eigen::Vector3d &Fa, Eigen::Vector3d &Fb, Eigen::Vector3d &Fc, Eigen::Vector3d &Fd) { // This is adapted from http://scidok.sulb.uni-saarland.de/volltexte/2007/1325/pdf/Dissertation_1544_Moll_Andr_2007.pdf // Many thanks to Andreas Moll and the BALLView developers for this // angle between abc and bcd: double angle_abc, angle_bcd; // Bond vectors of the three atoms Eigen::Vector3d ab = b - a; Eigen::Vector3d bc = c - b; Eigen::Vector3d cd = d - c; // length of the three bonds const double l_ab = ab.norm(); const double l_bc = bc.norm(); const double l_cd = cd.norm(); if (IsNearZero(l_ab) || IsNearZero(l_bc) || IsNearZero(l_cd) ) { Fa = VZero; Fb = VZero; Fc = VZero; Fd = VZero; return 0.0; } angle_abc = DEG_TO_RAD * VectorAngle(ab, bc); angle_bcd = DEG_TO_RAD * VectorAngle(bc, cd); // normalize the bond vectors: ab /= l_ab; bc /= l_bc; cd /= l_cd; const double sin_j = sin(angle_abc); const double sin_k = sin(angle_bcd); const double rsj = l_ab * sin_j; const double rsk = l_cd * sin_k; const double rs2j = 1. / (rsj * sin_j); const double rs2k = 1. / (rsk * sin_k); const double rrj = l_ab / l_bc; const double rrk = l_cd / l_bc; const double rrcj = rrj * (-cos(angle_abc)); const double rrck = rrk * (-cos(angle_bcd)); const Eigen::Vector3d ca = ab.cross(bc); const Eigen::Vector3d cb = bc.cross(cd); const Eigen::Vector3d cc = ca.cross(cb); double d1 = cc.dot(bc); double d2 = ca.dot(cb); double tor = RAD_TO_DEG * atan2(d1, d2); Fa = -ca * rs2j; Fd = cb * rs2k; Fb = Fa * (rrcj - 1.) - Fd * rrck; Fc = -(Fa + Fb + Fd); return tor; }
constexpr bool IsRotated() const { return !IsNearZero(y_rotation) || !IsNearZero(x_rotation); }