Page MenuHomec4science

dihedral_table.cpp
No OneTemporary

File Metadata

Created
Sun, Jun 30, 10:36

dihedral_table.cpp

/* ----------------------------------------------------------------------
LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
http://lammps.sandia.gov, Sandia National Laboratories
Steve Plimpton, sjplimp@sandia.gov
Copyright (2003) Sandia Corporation. Under the terms of Contract
DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains
certain rights in this software. This software is distributed under
the GNU General Public License.
See the README file in the top-level LAMMPS directory.
------------------------------------------------------------------------- */
/* ----------------------------------------------------------------------
Contributing author: Andrew Jewett (jewett.aij g m ail)
The cyclic tridiagonal matrix solver was borrowed from
the "tridiag.c" written by Gerard Jungman for GSL
------------------------------------------------------------------------- */
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <string>
#include <fstream>
#include <iostream>
#include <sstream>
#include "atom.h"
#include "comm.h"
#include "neighbor.h"
#include "domain.h"
#include "force.h"
#include "update.h"
#include "memory.h"
#include "error.h"
#include "dihedral_table.h"
#include "math_const.h"
#include "math_extra.h"
using namespace std;
using namespace LAMMPS_NS;
using namespace MathConst;
using namespace MathExtra;
// ------------------------------------------------------------------------
// The following auxiliary functions were left out of the
// DihedralTable class either because they require template parameters,
// or because they have nothing to do with dihedral angles.
// ------------------------------------------------------------------------
// -------------------------------------------------------------------
// --------- The function was stolen verbatim from the ---------
// --------- GNU Scientific Library (GSL, version 1.15) ---------
// -------------------------------------------------------------------
/* Author: Gerard Jungman */
/* for description of method see [Engeln-Mullges + Uhlig, p. 96]
*
* diag[0] offdiag[0] 0 ..... offdiag[N-1]
* offdiag[0] diag[1] offdiag[1] .....
* 0 offdiag[1] diag[2]
* 0 0 offdiag[2] .....
* ... ...
* offdiag[N-1] ...
*
*/
// -- (A non-symmetric version of this function is also available.) --
enum { //GSL status return codes.
GSL_FAILURE = -1,
GSL_SUCCESS = 0,
GSL_ENOMEM = 8,
GSL_EZERODIV = 12,
GSL_EBADLEN = 19
};
static int solve_cyc_tridiag( const double diag[], size_t d_stride,
const double offdiag[], size_t o_stride,
const double b[], size_t b_stride,
double x[], size_t x_stride,
size_t N, bool warn)
{
int status = GSL_SUCCESS;
double * delta = (double *) malloc (N * sizeof (double));
double * gamma = (double *) malloc (N * sizeof (double));
double * alpha = (double *) malloc (N * sizeof (double));
double * c = (double *) malloc (N * sizeof (double));
double * z = (double *) malloc (N * sizeof (double));
if (delta == 0 || gamma == 0 || alpha == 0 || c == 0 || z == 0) {
if (warn)
fprintf(stderr,"Internal Cyclic Spline Error: failed to allocate working space\n");
if (delta) free(delta);
if (gamma) free(gamma);
if (alpha) free(alpha);
if (c) free(c);
if (z) free(z);
return GSL_ENOMEM;
}
else
{
size_t i, j;
double sum = 0.0;
/* factor */
if (N == 1)
{
x[0] = b[0] / diag[0];
free(delta);
free(gamma);
free(alpha);
free(c);
free(z);
return GSL_SUCCESS;
}
alpha[0] = diag[0];
gamma[0] = offdiag[0] / alpha[0];
delta[0] = offdiag[o_stride * (N-1)] / alpha[0];
if (alpha[0] == 0) {
status = GSL_EZERODIV;
}
for (i = 1; i < N - 2; i++)
{
alpha[i] = diag[d_stride * i] - offdiag[o_stride * (i-1)] * gamma[i - 1];
gamma[i] = offdiag[o_stride * i] / alpha[i];
delta[i] = -delta[i - 1] * offdiag[o_stride * (i-1)] / alpha[i];
if (alpha[i] == 0) {
status = GSL_EZERODIV;
}
}
for (i = 0; i < N - 2; i++)
{
sum += alpha[i] * delta[i] * delta[i];
}
alpha[N - 2] = diag[d_stride * (N - 2)] - offdiag[o_stride * (N - 3)] * gamma[N - 3];
gamma[N - 2] = (offdiag[o_stride * (N - 2)] - offdiag[o_stride * (N - 3)] * delta[N - 3]) / alpha[N - 2];
alpha[N - 1] = diag[d_stride * (N - 1)] - sum - alpha[(N - 2)] * gamma[N - 2] * gamma[N - 2];
/* update */
z[0] = b[0];
for (i = 1; i < N - 1; i++)
{
z[i] = b[b_stride * i] - z[i - 1] * gamma[i - 1];
}
sum = 0.0;
for (i = 0; i < N - 2; i++)
{
sum += delta[i] * z[i];
}
z[N - 1] = b[b_stride * (N - 1)] - sum - gamma[N - 2] * z[N - 2];
for (i = 0; i < N; i++)
{
c[i] = z[i] / alpha[i];
}
/* backsubstitution */
x[x_stride * (N - 1)] = c[N - 1];
x[x_stride * (N - 2)] = c[N - 2] - gamma[N - 2] * x[x_stride * (N - 1)];
if (N >= 3)
{
for (i = N - 3, j = 0; j <= N - 3; j++, i--)
{
x[x_stride * i] = c[i] - gamma[i] * x[x_stride * (i + 1)] - delta[i] * x[x_stride * (N - 1)];
}
}
}
free (z);
free (c);
free (alpha);
free (gamma);
free (delta);
if ((status == GSL_EZERODIV) && warn)
fprintf(stderr, "Internal Cyclic Spline Error: Matrix must be positive definite.\n");
return status;
} //solve_cyc_tridiag()
/* ----------------------------------------------------------------------
spline and splint routines modified from Numerical Recipes
------------------------------------------------------------------------- */
static int cyc_spline(double const *xa,
double const *ya,
int n,
double period,
double *y2a, bool warn)
{
double *diag = new double[n];
double *offdiag = new double[n];
double *rhs = new double[n];
double xa_im1, xa_ip1;
// In the cyclic case, there are n equations with n unknows.
// The for loop sets up the equations we need to solve.
// Later we invoke the GSL tridiagonal matrix solver to solve them.
for(int i=0; i < n; i++) {
// I have to lookup xa[i+1] and xa[i-1]. This gets tricky because of
// periodic boundary conditions. We handle that now.
int im1 = i-1;
if (im1<0) {
im1 += n;
xa_im1 = xa[im1] - period;
}
else
xa_im1 = xa[im1];
int ip1 = i+1;
if (ip1>=n) {
ip1 -= n;
xa_ip1 = xa[ip1] + period;
}
else
xa_ip1 = xa[ip1];
// Recall that we want to find the y2a[] parameters (there are n of them).
// To solve for them, we have a linear equation with n unknowns
// (in the cyclic case that is). For details, the non-cyclic case is
// explained in equation 3.3.7 in Numerical Recipes in C, p. 115.
diag[i] = (xa_ip1 - xa_im1) / 3.0;
offdiag[i] = (xa_ip1 - xa[i]) / 6.0;
rhs[i] = ((ya[ip1] - ya[i]) / (xa_ip1 - xa[i])) -
((ya[i] - ya[im1]) / (xa[i] - xa_im1));
}
// Because this matrix is tridiagonal (and cyclic), we can use the following
// cheap method to invert it.
if (solve_cyc_tridiag(diag, 1,
offdiag, 1,
rhs, 1,
y2a, 1,
n, warn) != GSL_SUCCESS) {
if (warn)
fprintf(stderr,"Error in inverting matrix for splines.\n");
delete [] diag;
delete [] offdiag;
delete [] rhs;
return 1;
}
delete [] diag;
delete [] offdiag;
delete [] rhs;
return 0;
} // cyc_spline()
/* ---------------------------------------------------------------------- */
// cyc_splint(): Evaluates a spline at position x, with n control
// points located at xa[], ya[], and with parameters y2a[]
// The xa[] must be monotonically increasing and their
// range should not exceed period (ie xa[n-1] < xa[0] + period).
// x must lie in the range: [(xa[n-1]-period), (xa[0]+period)]
// "period" is typically 2*PI.
static double cyc_splint(double const *xa,
double const *ya,
double const *y2a,
int n,
double period,
double x)
{
int klo = -1;
int khi = n;
int k;
double xlo = xa[n-1] - period;
double xhi = xa[0] + period;
while (khi-klo > 1) {
k = (khi+klo) >> 1; //(k=(khi+klo)/2)
if (xa[k] > x) {
khi = k;
xhi = xa[k];
}
else {
klo = k;
xlo = xa[k];
}
}
if (khi == n) khi = 0;
if (klo ==-1) klo = n-1;
double h = xhi-xlo;
double a = (xhi-x) / h;
double b = (x-xlo) / h;
double y = a*ya[klo] + b*ya[khi] +
((a*a*a-a)*y2a[klo] + (b*b*b-b)*y2a[khi]) * (h*h)/6.0;
return y;
} // cyc_splint()
static double cyc_lin(double const *xa,
double const *ya,
int n,
double period,
double x)
{
int klo = -1;
int khi = n;
int k;
double xlo = xa[n-1] - period;
double xhi = xa[0] + period;
while (khi-klo > 1) {
k = (khi+klo) >> 1; //(k=(khi+klo)/2)
if (xa[k] > x) {
khi = k;
xhi = xa[k];
}
else {
klo = k;
xlo = xa[k];
}
}
if (khi == n) khi = 0;
if (klo ==-1) klo = n-1;
double h = xhi-xlo;
double a = (xhi-x) / h;
double b = (x-xlo) / h;
double y = a*ya[klo] + b*ya[khi];
return y;
} // cyc_lin()
// cyc_splintD(): Evaluate the deriviative of a cyclic spline at position x,
// with n control points at xa[], ya[], with parameters y2a[].
// The xa[] must be monotonically increasing and their
// range should not exceed period (ie xa[n-1] < xa[0] + period).
// x must lie in the range: [(xa[n-1]-period), (xa[0]+period)]
// "period" is typically 2*PI.
static double cyc_splintD(double const *xa,
double const *ya,
double const *y2a,
int n,
double period,
double x)
{
int klo = -1;
int khi = n; // (not n-1)
int k;
double xlo = xa[n-1] - period;
double xhi = xa[0] + period;
while (khi-klo > 1) {
k = (khi+klo) >> 1; //(k=(khi+klo)/2)
if (xa[k] > x) {
khi = k;
xhi = xa[k];
}
else {
klo = k;
xlo = xa[k];
}
}
if (khi == n) khi = 0;
if (klo ==-1) klo = n-1;
double yhi = ya[khi];
double ylo = ya[klo];
double h = xhi-xlo;
double g = yhi-ylo;
double a = (xhi-x) / h;
double b = (x-xlo) / h;
// Formula below taken from equation 3.3.5 of "numerical recipes in c"
// "yD" = the derivative of y
double yD = g/h - ( (3.0*a*a-1.0)*y2a[klo] - (3.0*b*b-1.0)*y2a[khi] ) * h/6.0;
// For rerefence: y = a*ylo + b*yhi +
// ((a*a*a-a)*y2a[klo] + (b*b*b-b)*y2a[khi]) * (h*h)/6.0;
return yD;
} // cyc_splintD()
// --------------------------------------------
// ------- Calculate the dihedral angle -------
// --------------------------------------------
static const int g_dim=3;
static double Phi(double const *x1, //array holding x,y,z coords atom 1
double const *x2, // : : : : 2
double const *x3, // : : : : 3
double const *x4, // : : : : 4
Domain *domain, //<-periodic boundary information
// The following arrays are of doubles with g_dim elements.
// (g_dim is a constant known at compile time, usually 3).
// Their contents is calculated by this function.
// Space for these vectors must be allocated in advance.
// (This is not hidden internally because these vectors
// may be needed outside the function, later on.)
double *vb12, // will store x2-x1
double *vb23, // will store x3-x2
double *vb34, // will store x4-x3
double *n123, // will store normal to plane x1,x2,x3
double *n234) // will store normal to plane x2,x3,x4
{
for (int d=0; d < g_dim; ++d) {
vb12[d] = x2[d] - x1[d]; // 1st bond
vb23[d] = x3[d] - x2[d]; // 2nd bond
vb34[d] = x4[d] - x3[d]; // 3rd bond
}
//Consider periodic boundary conditions:
domain->minimum_image(vb12[0],vb12[1],vb12[2]);
domain->minimum_image(vb23[0],vb23[1],vb23[2]);
domain->minimum_image(vb34[0],vb34[1],vb34[2]);
//--- Compute the normal to the planes formed by atoms 1,2,3 and 2,3,4 ---
cross3(vb23, vb12, n123); // <- n123=vb23 x vb12
cross3(vb23, vb34, n234); // <- n234=vb23 x vb34
norm3(n123);
norm3(n234);
double cos_phi = -dot3(n123, n234);
if (cos_phi > 1.0)
cos_phi = 1.0;
else if (cos_phi < -1.0)
cos_phi = -1.0;
double phi = acos(cos_phi);
if (dot3(n123, vb34) > 0.0) {
phi = -phi; //(Note: Negative dihedral angles are possible only in 3-D.)
phi += MY_2PI; //<- This insure phi is always in the range 0 to 2*PI
}
return phi;
} // DihedralTable::Phi()
/* ---------------------------------------------------------------------- */
DihedralTable::DihedralTable(LAMMPS *lmp) : Dihedral(lmp)
{
ntables = 0;
tables = NULL;
checkU_fname = checkF_fname = NULL;
}
/* ---------------------------------------------------------------------- */
DihedralTable::~DihedralTable()
{
for (int m = 0; m < ntables; m++) free_table(&tables[m]);
memory->sfree(tables);
memory->sfree(checkU_fname);
memory->sfree(checkF_fname);
if (allocated) {
memory->destroy(setflag);
//memory->destroy(phi0); <- equilibrium angles not supported
memory->destroy(tabindex);
}
}
/* ---------------------------------------------------------------------- */
void DihedralTable::compute(int eflag, int vflag)
{
int i1,i2,i3,i4,n,type;
double edihedral,f1[3],f2[3],f3[3],f4[3];
double **x = atom->x;
double **f = atom->f;
int **dihedrallist = neighbor->dihedrallist;
int ndihedrallist = neighbor->ndihedrallist;
int nlocal = atom->nlocal;
int newton_bond = force->newton_bond;
// The dihedral angle "phi" is the angle between n123 and n234
// the planes defined by atoms i1,i2,i3, and i2,i3,i4.
//
// Definitions of vectors: vb12, vb23, vb34, perp12on23
// proj12on23, perp43on32, proj43on32
//
// Note: The positions of the 4 atoms are labeled x[i1], x[i2], x[i3], x[i4]
// (which are also vectors)
//
// proj12on23 proj34on23
// ---------> ----------->
// .
// .
// .
// x[i2] . x[i3]
// . __@----------vb23-------->@ . . . . .
// /|\ /| \ |
// | / \ |
// | / \ |
// perp12vs23 / \ |
// | / \ perp34vs23
// | vb12 \ |
// | / vb34 |
// | / \ |
// | / \ |
// | / \ |
// @ \ |
// _\| \|/
// x[i1] @
//
// x[i4]
//
double vb12[g_dim]; // displacement vector from atom i1 towards atom i2
// vb12[d] = x[i2][d] - x[i1][d] (for d=0,1,2)
double vb23[g_dim]; // displacement vector from atom i2 towards atom i3
// vb23[d] = x[i3][d] - x[i2][d] (for d=0,1,2)
double vb34[g_dim]; // displacement vector from atom i3 towards atom i4
// vb34[d] = x[i4][d] - x[i3][d] (for d=0,1,2)
// n123 & n234: These two unit vectors are normal to the planes
// defined by atoms 1,2,3 and 2,3,4.
double n123[g_dim]; //n123=vb23 x vb12 / |vb23 x vb12| ("x" is cross product)
double n234[g_dim]; //n234=vb23 x vb34 / |vb23 x vb34| ("x" is cross product)
double proj12on23[g_dim];
// proj12on23[d] = (vb23[d]/|vb23|) * dot3(vb12,vb23)/|vb12|*|vb23|
double proj34on23[g_dim];
// proj34on23[d] = (vb34[d]/|vb23|) * dot3(vb34,vb23)/|vb34|*|vb23|
double perp12on23[g_dim];
// perp12on23[d] = v12[d] - proj12on23[d]
double perp34on23[g_dim];
// perp34on23[d] = v34[d] - proj34on23[d]
edihedral = 0.0;
if (eflag || vflag) ev_setup(eflag,vflag);
else evflag = 0;
for (n = 0; n < ndihedrallist; n++) {
i1 = dihedrallist[n][0];
i2 = dihedrallist[n][1];
i3 = dihedrallist[n][2];
i4 = dihedrallist[n][3];
type = dihedrallist[n][4];
// ------ Step 1: Compute the dihedral angle "phi" ------
//
// Phi() calculates the dihedral angle.
// This function also calculates the vectors:
// vb12, vb23, vb34, n123, and n234, which we will need later.
double phi = Phi(x[i1], x[i2], x[i3], x[i4], domain,
vb12, vb23, vb34, n123, n234);
// ------ Step 2: Compute the gradient of phi with atomic position: ------
//
// Gradient variables:
//
// dphi_dx1, dphi_dx2, dphi_dx3, dphi_dx4 are the gradients of phi with
// respect to the atomic positions of atoms i1, i2, i3, i4, respectively.
// As an example, consider dphi_dx1. The d'th element is:
double dphi_dx1[g_dim]; // d phi
double dphi_dx2[g_dim]; // dphi_dx1[d] = ---------- (partial derivatives)
double dphi_dx3[g_dim]; // d x[i1][d]
double dphi_dx4[g_dim]; //where d=0,1,2 corresponds to x,y,z (if g_dim==3)
double dot123 = dot3(vb12, vb23);
double dot234 = dot3(vb23, vb34);
double L23sqr = dot3(vb23, vb23);
double L23 = sqrt(L23sqr); // (central bond length)
double inv_L23sqr = 0.0;
double inv_L23 = 0.0;
if (L23sqr != 0.0) {
inv_L23sqr = 1.0 / L23sqr;
inv_L23 = 1.0 / L23;
}
double neg_inv_L23 = -inv_L23;
double dot123_over_L23sqr = dot123 * inv_L23sqr;
double dot234_over_L23sqr = dot234 * inv_L23sqr;
for (int d=0; d < g_dim; ++d) {
// See figure above for a visual definitions of these vectors:
proj12on23[d] = vb23[d] * dot123_over_L23sqr;
proj34on23[d] = vb23[d] * dot234_over_L23sqr;
perp12on23[d] = vb12[d] - proj12on23[d];
perp34on23[d] = vb34[d] - proj34on23[d];
}
// --- Compute the gradient vectors dphi/dx1 and dphi/dx4: ---
// These two gradients point in the direction of n123 and n234,
// and are scaled by the distances of atoms 1 and 4 from the central axis.
// Distance of atom 1 to central axis:
double perp12on23_len = sqrt(dot3(perp12on23, perp12on23));
// Distance of atom 4 to central axis:
double perp34on23_len = sqrt(dot3(perp34on23, perp34on23));
double inv_perp12on23 = 0.0;
if (perp12on23_len != 0.0) inv_perp12on23 = 1.0 / perp12on23_len;
double inv_perp34on23 = 0.0;
if (perp34on23_len != 0.0) inv_perp34on23 = 1.0 / perp34on23_len;
for (int d=0; d < g_dim; ++d) {
dphi_dx1[d] = n123[d] * inv_perp12on23;
dphi_dx4[d] = n234[d] * inv_perp34on23;
}
// --- Compute the gradient vectors dphi/dx2 and dphi/dx3: ---
//
// This is more tricky because atoms 2 and 3 are shared by both planes
// 123 and 234 (the angle between which defines "phi"). Moving either
// one of these atoms effects both the 123 and 234 planes
// Both the 123 and 234 planes intersect with the plane perpendicular to the
// central bond axis (vb23). The two lines where these intersections occur
// will shift when you move either atom 2 or atom 3. The angle between
// these lines is the dihedral angle, phi. We can define four quantities:
// dphi123_dx2 is the change in "phi" due to the movement of the 123 plane
// ...as a result of moving atom 2.
// dphi234_dx2 is the change in "phi" due to the movement of the 234 plane
// ...as a result of moving atom 2.
// dphi123_dx3 is the change in "phi" due to the movement of the 123 plane
// ...as a result of moving atom 3.
// dphi234_dx3 is the change in "phi" due to the movement of the 234 plane
// ...as a result of moving atom 3.
double proj12on23_len = dot123 * inv_L23;
double proj34on23_len = dot234 * inv_L23;
// Interpretation:
//The magnitude of "proj12on23_len" is the length of the proj12on23 vector.
//The sign is positive if it points in the same direction as the central
//bond (vb23). Otherwise it is negative. The same goes for "proj34on23".
//(In the example figure in the comment above, both variables are positive.)
// The forumula used in the 8 lines below explained here:
// "supporting_information/doc/gradient_formula_explanation/"
double dphi123_dx2_coef = neg_inv_L23 * (L23 + proj12on23_len);
double dphi234_dx2_coef = inv_L23 * proj34on23_len;
double dphi234_dx3_coef = neg_inv_L23 * (L23 + proj34on23_len);
double dphi123_dx3_coef = inv_L23 * proj12on23_len;
for (int d=0; d < g_dim; ++d) {
// Recall that the n123 and n234 plane normal vectors are proportional to
// the dphi/dx1 and dphi/dx2 gradients vectors
// It turns out we can save slightly more CPU cycles by expressing
// dphi/dx2 and dphi/dx3 as linear combinations of dphi/dx1 and dphi/dx2
// which we computed already (instead of n123 & n234).
dphi_dx2[d] = dphi123_dx2_coef*dphi_dx1[d] + dphi234_dx2_coef*dphi_dx4[d];
dphi_dx3[d] = dphi123_dx3_coef*dphi_dx1[d] + dphi234_dx3_coef*dphi_dx4[d];
}
// ----- Step 3: Calculate the energy and force in the phi direction -----
// tabulated force & energy
double u=0.0, m_du_dphi=0.0; //u = energy. m_du_dphi = "minus" du/dphi
uf_lookup(type, phi, u, m_du_dphi);
if (eflag) edihedral = u;
// ----- Step 4: Calculate the force direction in real space -----
// chain rule:
// d U d U d phi
// -f = ----- = ----- * -----
// d x d phi d x
for(int d=0; d < g_dim; ++d) {
f1[d] = m_du_dphi * dphi_dx1[d];
f2[d] = m_du_dphi * dphi_dx2[d];
f3[d] = m_du_dphi * dphi_dx3[d];
f4[d] = m_du_dphi * dphi_dx4[d];
}
// apply force to each of 4 atoms
if (newton_bond || i1 < nlocal) {
f[i1][0] += f1[0];
f[i1][1] += f1[1];
f[i1][2] += f1[2];
}
if (newton_bond || i2 < nlocal) {
f[i2][0] += f2[0];
f[i2][1] += f2[1];
f[i2][2] += f2[2];
}
if (newton_bond || i3 < nlocal) {
f[i3][0] += f3[0];
f[i3][1] += f3[1];
f[i3][2] += f3[2];
}
if (newton_bond || i4 < nlocal) {
f[i4][0] += f4[0];
f[i4][1] += f4[1];
f[i4][2] += f4[2];
}
if (evflag)
ev_tally(i1,i2,i3,i4,
nlocal,newton_bond,edihedral,
f1,f3,f4,
vb12[0],vb12[1],vb12[2],
vb23[0],vb23[1],vb23[2],
vb34[0],vb34[1],vb34[2]);
}
} // void DihedralTable::compute()
// single() calculates the dihedral-angle energy of atoms i1, i2, i3, i4.
double DihedralTable::single(int type, int i1, int i2, int i3, int i4)
{
double vb12[g_dim];
double vb23[g_dim];
double vb34[g_dim];
double n123[g_dim];
double n234[g_dim];
double **x = atom->x;
double phi = Phi(x[i1], x[i2], x[i3], x[i4], domain,
vb12, vb23, vb34, n123, n234);
double u=0.0;
u_lookup(type, phi, u); //Calculate the energy, and store it in "u"
return u;
}
/* ---------------------------------------------------------------------- */
void DihedralTable::allocate()
{
allocated = 1;
int n = atom->ndihedraltypes;
memory->create(tabindex,n+1,"dihedral:tabindex");
//memory->create(phi0,n+1,"dihedral:phi0"); <-equilibrium angles not supported
memory->create(setflag,n+1,"dihedral:setflag");
for (int i = 1; i <= n; i++) setflag[i] = 0;
}
/* ----------------------------------------------------------------------
global settings
------------------------------------------------------------------------- */
void DihedralTable::settings(int narg, char **arg)
{
if (narg != 2) error->all(FLERR,"Illegal dihedral_style command");
if (strcmp(arg[0],"linear") == 0) tabstyle = LINEAR;
else if (strcmp(arg[0],"spline") == 0) tabstyle = SPLINE;
else error->all(FLERR,"Unknown table style in dihedral style table");
tablength = force->inumeric(FLERR,arg[1]);
if (tablength < 3)
error->all(FLERR,"Illegal number of dihedral table entries");
// delete old tables, since cannot just change settings
for (int m = 0; m < ntables; m++) free_table(&tables[m]);
memory->sfree(tables);
if (allocated) {
memory->destroy(setflag);
memory->destroy(tabindex);
}
allocated = 0;
ntables = 0;
tables = NULL;
}
/* ----------------------------------------------------------------------
set coeffs for one type
------------------------------------------------------------------------- */
void DihedralTable::coeff(int narg, char **arg)
{
if (narg != 3) error->all(FLERR,"Illegal dihedral_coeff command");
if (!allocated) allocate();
int ilo,ihi;
force->bounds(arg[0],atom->ndihedraltypes,ilo,ihi);
int me;
MPI_Comm_rank(world,&me);
tables = (Table *)
memory->srealloc(tables,(ntables+1)*sizeof(Table), "dihedral:tables");
Table *tb = &tables[ntables];
null_table(tb);
if (me == 0) read_table(tb,arg[1],arg[2]);
bcast_table(tb);
// --- check the angle data for range errors ---
// --- and resolve issues with periodicity ---
if (tb->ninput < 2) {
string err_msg;
err_msg = string("Invalid dihedral table length (")
+ string(arg[2]) + string(").");
error->one(FLERR,err_msg.c_str());
}
else if ((tb->ninput == 2) && (tabstyle == SPLINE)) {
string err_msg;
err_msg = string("Invalid dihedral spline table length. (Try linear)\n (")
+ string(arg[2]) + string(").");
error->one(FLERR,err_msg.c_str());
}
// check for monotonicity
for (int i=0; i < tb->ninput-1; i++) {
if (tb->phifile[i] >= tb->phifile[i+1]) {
stringstream i_str;
i_str << i+1;
string err_msg =
string("Dihedral table values are not increasing (") +
string(arg[2]) + string(", ")+i_str.str()+string("th entry)");
if (i==0)
err_msg += string("\n(This is probably a mistake with your table format.)\n");
error->all(FLERR,err_msg.c_str());
}
}
// check the range of angles
double philo = tb->phifile[0];
double phihi = tb->phifile[tb->ninput-1];
if (tb->use_degrees) {
if ((phihi - philo) >= 360) {
string err_msg;
err_msg = string("Dihedral table angle range must be < 360 degrees (")
+string(arg[2]) + string(").");
error->all(FLERR,err_msg.c_str());
}
}
else {
if ((phihi - philo) >= MY_2PI) {
string err_msg;
err_msg = string("Dihedral table angle range must be < 2*PI radians (")
+ string(arg[2]) + string(").");
error->all(FLERR,err_msg.c_str());
}
}
// convert phi from degrees to radians
if (tb->use_degrees) {
for (int i=0; i < tb->ninput; i++) {
tb->phifile[i] *= MY_PI/180.0;
// I assume that if angles are in degrees, then the forces (f=dU/dphi)
// are specified with "phi" in degrees as well.
tb->ffile[i] *= 180.0/MY_PI;
}
}
// We want all the phi dihedral angles to lie in the range from 0 to 2*PI.
// But I don't want to restrict users to input their data in this range.
// We also want the angles to be sorted in increasing order.
// This messy code fixes these problems with the user's data:
{
double *phifile_tmp = new double [tb->ninput]; //temporary arrays
double *ffile_tmp = new double [tb->ninput]; //used for sorting
double *efile_tmp = new double [tb->ninput];
// After re-imaging, does the range of angles cross the 0 or 2*PI boundary?
// If so, find the discontinuity:
int i_discontinuity = tb->ninput;
for (int i=0; i < tb->ninput; i++) {
double phi = tb->phifile[i];
// Add a multiple of 2*PI to phi until it lies in the range [0, 2*PI).
phi -= MY_2PI * floor(phi/MY_2PI);
phifile_tmp[i] = phi;
efile_tmp[i] = tb->efile[i];
ffile_tmp[i] = tb->ffile[i];
if ((i>0) && (phifile_tmp[i] < phifile_tmp[i-1])) {
//There should only be at most one discontinuity, because we have
//insured that the data was sorted before imaging, and because the
//range of angle values does not exceed 2*PI.
i_discontinuity = i;
}
}
int I = 0;
for (int i = i_discontinuity; i < tb->ninput; i++) {
tb->phifile[I] = phifile_tmp[i];
tb->efile[I] = efile_tmp[i];
tb->ffile[I] = ffile_tmp[i];
I++;
}
for (int i = 0; i < i_discontinuity; i++) {
tb->phifile[I] = phifile_tmp[i];
tb->efile[I] = efile_tmp[i];
tb->ffile[I] = ffile_tmp[i];
I++;
}
// clean up temporary storage
delete[] phifile_tmp;
delete[] ffile_tmp;
delete[] efile_tmp;
}
// spline read-in and compute r,e,f vectors within table
spline_table(tb);
compute_table(tb);
// Optional: allow the user to print out the interpolated spline tables
if (me == 0) {
if (checkU_fname && (strlen(checkU_fname) != 0))
{
ofstream checkU_file;
checkU_file.open(checkU_fname, ios::out);
for (int i=0; i < tablength; i++) {
double phi = i*MY_2PI/tablength;
double u = tb->e[i];
if (tb->use_degrees)
phi *= 180.0/MY_PI;
checkU_file << phi << " " << u << "\n";
}
checkU_file.close();
}
if (checkF_fname && (strlen(checkF_fname) != 0))
{
ofstream checkF_file;
checkF_file.open(checkF_fname, ios::out);
for (int i=0; i < tablength; i++)
{
double phi = i*MY_2PI/tablength;
double f;
if ((tabstyle == SPLINE) && (tb->f_unspecified)) {
double dU_dphi =
// (If the user did not specify the forces now, AND the user
// selected the "spline" option, (as opposed to "linear")
// THEN the tb->f array is uninitialized, so there's
// no point to print out the contents of the tb->f[] array.
// Instead, later on, we will calculate the force using the
// -cyc_splintD() routine to calculate the derivative of the
// energy spline, using the energy data (tb->e[]).
// To be nice and report something, I do the same thing here.)
cyc_splintD(tb->phi, tb->e, tb->e2, tablength, MY_2PI,phi);
f = -dU_dphi;
}
else
// Otherwise we calculated the tb->f[] array. Report its contents.
f = tb->f[i];
if (tb->use_degrees) {
phi *= 180.0/MY_PI;
// If the user wants degree angle units, we should convert our
// internal force tables (in energy/radians) to (energy/degrees)
f *= MY_PI/180.0;
}
checkF_file << phi << " " << f << "\n";
}
checkF_file.close();
} // if (checkF_fname && (strlen(checkF_fname) != 0))
} // if (me == 0)
// store ptr to table in tabindex
int count = 0;
for (int i = ilo; i <= ihi; i++)
{
tabindex[i] = ntables;
//phi0[i] = tb->phi0; <- equilibrium dihedral angles not supported
setflag[i] = 1;
count++;
}
ntables++;
if (count == 0)
error->all(FLERR,"Illegal dihedral_coeff command");
} //DihedralTable::coeff()
/* ----------------------------------------------------------------------
proc 0 writes to restart file
------------------------------------------------------------------------- */
void DihedralTable::write_restart(FILE *fp)
{
fwrite(&tabstyle,sizeof(int),1,fp);
fwrite(&tablength,sizeof(int),1,fp);
}
/* ----------------------------------------------------------------------
proc 0 reads from restart file, bcasts
------------------------------------------------------------------------- */
void DihedralTable::read_restart(FILE *fp)
{
if (comm->me == 0) {
fread(&tabstyle,sizeof(int),1,fp);
fread(&tablength,sizeof(int),1,fp);
}
MPI_Bcast(&tabstyle,1,MPI_INT,0,world);
MPI_Bcast(&tablength,1,MPI_INT,0,world);
allocate();
}
/* ---------------------------------------------------------------------- */
void DihedralTable::null_table(Table *tb)
{
tb->phifile = tb->efile = tb->ffile = NULL;
tb->e2file = tb->f2file = NULL;
tb->phi = tb->e = tb->de = NULL;
tb->f = tb->df = tb->e2 = tb->f2 = NULL;
}
/* ---------------------------------------------------------------------- */
void DihedralTable::free_table(Table *tb)
{
memory->destroy(tb->phifile);
memory->destroy(tb->efile);
memory->destroy(tb->ffile);
memory->destroy(tb->e2file);
memory->destroy(tb->f2file);
memory->destroy(tb->phi);
memory->destroy(tb->e);
memory->destroy(tb->de);
memory->destroy(tb->f);
memory->destroy(tb->df);
memory->destroy(tb->e2);
memory->destroy(tb->f2);
}
/* ----------------------------------------------------------------------
read table file, only called by proc 0
------------------------------------------------------------------------- */
static const int MAXLINE=2048;
void DihedralTable::read_table(Table *tb, char *file, char *keyword)
{
char line[MAXLINE];
// open file
FILE *fp = force->open_potential(file);
if (fp == NULL) {
string err_msg = string("Cannot open file ") + string(file);
error->one(FLERR,err_msg.c_str());
}
// loop until section found with matching keyword
while (1) {
if (fgets(line,MAXLINE,fp) == NULL) {
string err_msg=string("Did not find keyword \"")
+string(keyword)+string("\" in dihedral table file.");
error->one(FLERR, err_msg.c_str());
}
if (strspn(line," \t\n\r") == strlen(line)) continue; // blank line
if (line[0] == '#') continue; // comment
char *word = strtok(line," \t\n\r");
if (strcmp(word,keyword) == 0) break; // matching keyword
fgets(line,MAXLINE,fp); // no match, skip section
param_extract(tb,line);
fgets(line,MAXLINE,fp);
for (int i = 0; i < tb->ninput; i++)
fgets(line,MAXLINE,fp);
}
// read args on 2nd line of section
// allocate table arrays for file values
fgets(line,MAXLINE,fp);
param_extract(tb,line);
memory->create(tb->phifile,tb->ninput,"dihedral:phifile");
memory->create(tb->efile,tb->ninput,"dihedral:efile");
memory->create(tb->ffile,tb->ninput,"dihedral:ffile");
// read a,e,f table values from file
int itmp;
for (int i = 0; i < tb->ninput; i++) {
// Read the next line. Make sure the file is long enough.
if (! fgets(line,MAXLINE,fp))
error->one(FLERR, "Dihedral table does not contain enough entries.");
// Skip blank lines and delete text following a '#' character
char *pe = strchr(line, '#');
if (pe != NULL) *pe = '\0'; //terminate string at '#' character
char *pc = line;
while ((*pc != '\0') && isspace(*pc))
pc++;
if (*pc != '\0') { //If line is not a blank line
stringstream line_ss(line);
if (tb->f_unspecified) {
//sscanf(line,"%d %lg %lg",
// &itmp,&tb->phifile[i],&tb->efile[i]);
line_ss >> itmp;
line_ss >> tb->phifile[i];
line_ss >> tb->efile[i];
}
else {
//sscanf(line,"%d %lg %lg %lg",
// &itmp,&tb->phifile[i],&tb->efile[i],&tb->ffile[i]);
line_ss >> itmp;
line_ss >> tb->phifile[i];
line_ss >> tb->efile[i];
line_ss >> tb->ffile[i];
}
if (! line_ss) {
stringstream err_msg;
err_msg << "Read error in table "<< keyword<<", near line "<<i+1<<"\n"
<< " (Check to make sure the number of colums is correct.)";
if ((! tb->f_unspecified) && (i==0))
err_msg << "\n (This sometimes occurs if users forget to specify the \"NOF\" option.)\n";
error->one(FLERR, err_msg.str().c_str());
}
}
else //if it is a blank line, then skip it.
i--;
} //for (int i = 0; (i < tb->ninput) && fp; i++) {
fclose(fp);
}
/* ----------------------------------------------------------------------
build spline representation of e,f over entire range of read-in table
this function sets these values in e2file,f2file.
I also perform a crude check for force & energy consistency.
------------------------------------------------------------------------- */
void DihedralTable::spline_table(Table *tb)
{
memory->create(tb->e2file,tb->ninput,"dihedral:e2file");
memory->create(tb->f2file,tb->ninput,"dihedral:f2file");
if (cyc_spline(tb->phifile, tb->efile, tb->ninput,
MY_2PI,tb->e2file,comm->me == 0))
error->one(FLERR,"Error computing dihedral spline tables");
if (! tb->f_unspecified) {
if (cyc_spline(tb->phifile, tb->ffile, tb->ninput,
MY_2PI, tb->f2file, comm->me == 0))
error->one(FLERR,"Error computing dihedral spline tables");
}
// CHECK to help make sure the user calculated forces in a way
// which is grossly numerically consistent with the energy table.
if (! tb->f_unspecified) {
int num_disagreements = 0;
for (int i=0; i<tb->ninput; i++) {
// Calculate what the force should be at the control points
// by using linear interpolation of the derivatives of the energy:
double phi_i = tb->phifile[i];
// First deal with periodicity
double phi_im1, phi_ip1;
int im1 = i-1;
if (im1 < 0) {
im1 += tb->ninput;
phi_im1 = tb->phifile[im1] - MY_2PI;
}
else
phi_im1 = tb->phifile[im1];
int ip1 = i+1;
if (ip1 >= tb->ninput) {
ip1 -= tb->ninput;
phi_ip1 = tb->phifile[ip1] + MY_2PI;
}
else
phi_ip1 = tb->phifile[ip1];
// Now calculate the midpoints above and below phi_i = tb->phifile[i]
double phi_lo= 0.5*(phi_im1 + phi_i); //midpoint between phi_im1 and phi_i
double phi_hi= 0.5*(phi_i + phi_ip1); //midpoint between phi_i and phi_ip1
// Use a linear approximation to the derivative at these two midpoints
double dU_dphi_lo =
(tb->efile[i] - tb->efile[im1])
/
(phi_i - phi_im1);
double dU_dphi_hi =
(tb->efile[ip1] - tb->efile[i])
/
(phi_ip1 - phi_i);
// Now calculate the derivative at position
// phi_i (=tb->phifile[i]) using linear interpolation
double a = (phi_i - phi_lo) / (phi_hi - phi_lo);
double b = (phi_hi - phi_i) / (phi_hi - phi_lo);
double dU_dphi = a*dU_dphi_lo + b*dU_dphi_hi;
double f = -dU_dphi;
// Alternately, we could use spline interpolation instead:
// double f = - splintD(tb->phifile, tb->efile, tb->e2file,
// tb->ninput, MY_2PI, tb->phifile[i]);
// This is the way I originally did it, but I trust
// the ugly simple linear way above better.
// Recall this entire block of code doess not calculate
// anything important. It does not have to be perfect.
// We are only checking for stupid user errors here.
if ((f != 0.0) &&
(tb->ffile[i] != 0.0) &&
((f/tb->ffile[i] < 0.5) || (f/tb->ffile[i] > 2.0))) {
num_disagreements++;
}
} // for (int i=0; i<tb->ninput; i++)
if ((num_disagreements > tb->ninput/2) && (num_disagreements > 2)) {
string msg("Dihedral table has inconsistent forces and energies. (Try \"NOF\".)\n");
error->all(FLERR,msg.c_str());
}
} // check for consistency if (! tb->f_unspecified)
} // DihedralTable::spline_table()
/* ----------------------------------------------------------------------
compute a,e,f vectors from splined values
------------------------------------------------------------------------- */
void DihedralTable::compute_table(Table *tb)
{
//delta = table spacing in dihedral angle for tablength (cyclic) bins
tb->delta = MY_2PI / tablength;
tb->invdelta = 1.0/tb->delta;
tb->deltasq6 = tb->delta*tb->delta / 6.0;
// N evenly spaced bins in dihedral angle from 0 to 2*PI
// phi,e,f = value at lower edge of bin
// de,df values = delta values of e,f (cyclic, in this case)
// phi,e,f,de,df are arrays containing "tablength" number of entries
memory->create(tb->phi,tablength,"dihedral:phi");
memory->create(tb->e,tablength,"dihedral:e");
memory->create(tb->de,tablength,"dihedral:de");
memory->create(tb->f,tablength,"dihedral:f");
memory->create(tb->df,tablength,"dihedral:df");
memory->create(tb->e2,tablength,"dihedral:e2");
memory->create(tb->f2,tablength,"dihedral:f2");
if (tabstyle == SPLINE) {
// Use cubic spline interpolation to calculate the entries in the
// internal table. (This is true regardless...even if tabstyle!=SPLINE.)
for (int i = 0; i < tablength; i++) {
double phi = i*tb->delta;
tb->phi[i] = phi;
tb->e[i]= cyc_splint(tb->phifile,tb->efile,tb->e2file,tb->ninput,MY_2PI,phi);
if (! tb->f_unspecified)
tb->f[i] = cyc_splint(tb->phifile,tb->ffile,tb->f2file,tb->ninput,MY_2PI,phi);
}
} // if (tabstyle == SPLINE)
else if (tabstyle == LINEAR) {
if (! tb->f_unspecified) {
for (int i = 0; i < tablength; i++) {
double phi = i*tb->delta;
tb->phi[i] = phi;
tb->e[i]= cyc_lin(tb->phifile,tb->efile,tb->ninput,MY_2PI,phi);
tb->f[i]= cyc_lin(tb->phifile,tb->ffile,tb->ninput,MY_2PI,phi);
}
}
else {
for (int i = 0; i < tablength; i++) {
double phi = i*tb->delta;
tb->phi[i] = phi;
tb->e[i]= cyc_lin(tb->phifile,tb->efile,tb->ninput,MY_2PI,phi);
}
// In the linear case, if the user did not specify the forces, then we
// must generate the "f" array. Do this using linear interpolation
// of the e array (which itself was generated above)
for (int i = 0; i < tablength; i++) {
int im1 = i-1; if (im1 < 0) im1 += tablength;
int ip1 = i+1; if (ip1 >= tablength) ip1 -= tablength;
double dedx = (tb->e[ip1] - tb->e[im1]) / (2.0 * tb->delta);
// (This is the average of the linear slopes on either side of the node.
// Note that the nodes in the internal table are evenly spaced.)
tb->f[i] = -dedx;
}
}
// Fill the linear interpolation tables (de, df)
for (int i = 0; i < tablength; i++) {
int ip1 = i+1; if (ip1 >= tablength) ip1 -= tablength;
tb->de[i] = tb->e[ip1] - tb->e[i];
tb->df[i] = tb->f[ip1] - tb->f[i];
}
} // else if (tabstyle == LINEAR)
cyc_spline(tb->phi, tb->e, tablength, MY_2PI, tb->e2, comm->me == 0);
if (! tb->f_unspecified)
cyc_spline(tb->phi, tb->f, tablength, MY_2PI, tb->f2, comm->me == 0);
}
/* ----------------------------------------------------------------------
extract attributes from parameter line in table section
format of line: N value NOF DEGREES RADIANS
N is required, other params are optional
------------------------------------------------------------------------- */
void DihedralTable::param_extract(Table *tb, char *line)
{
//tb->theta0 = 180.0; <- equilibrium angles not supported
tb->ninput = 0;
tb->f_unspecified = false; //default
tb->use_degrees = true; //default
char *word = strtok(line," \t\n\r\f");
while (word) {
if (strcmp(word,"N") == 0) {
word = strtok(NULL," \t\n\r\f");
tb->ninput = atoi(word);
}
else if (strcmp(word,"NOF") == 0) {
tb->f_unspecified = true;
}
else if ((strcmp(word,"DEGREES") == 0) || (strcmp(word,"degrees") == 0)) {
tb->use_degrees = true;
}
else if ((strcmp(word,"RADIANS") == 0) || (strcmp(word,"radians") == 0)) {
tb->use_degrees = false;
}
else if (strcmp(word,"CHECKU") == 0) {
word = strtok(NULL," \t\n\r\f");
memory->sfree(checkU_fname);
memory->create(checkU_fname,strlen(word)+1,"dihedral_table:checkU");
strcpy(checkU_fname, word);
}
else if (strcmp(word,"CHECKF") == 0) {
word = strtok(NULL," \t\n\r\f");
memory->sfree(checkF_fname);
memory->create(checkF_fname,strlen(word)+1,"dihedral_table:checkF");
strcpy(checkF_fname, word);
}
// COMMENTING OUT: equilibrium angles are not supported
//else if (strcmp(word,"EQ") == 0) {
// word = strtok(NULL," \t\n\r\f");
// tb->theta0 = atof(word);
//}
else {
string err_msg("Invalid keyword in dihedral angle table parameters");
err_msg += string(" (") + string(word) + string(")");
error->one(FLERR,err_msg.c_str());
}
word = strtok(NULL," \t\n\r\f");
}
if (tb->ninput == 0)
error->one(FLERR,"Dihedral table parameters did not set N");
} // DihedralTable::param_extract()
/* ----------------------------------------------------------------------
broadcast read-in table info from proc 0 to other procs
this function communicates these values in Table:
ninput,phifile,efile,ffile,
f_unspecified,use_degrees
------------------------------------------------------------------------- */
void DihedralTable::bcast_table(Table *tb)
{
MPI_Bcast(&tb->ninput,1,MPI_INT,0,world);
int me;
MPI_Comm_rank(world,&me);
if (me > 0) {
memory->create(tb->phifile,tb->ninput,"dihedral:phifile");
memory->create(tb->efile,tb->ninput,"dihedral:efile");
memory->create(tb->ffile,tb->ninput,"dihedral:ffile");
}
MPI_Bcast(tb->phifile,tb->ninput,MPI_DOUBLE,0,world);
MPI_Bcast(tb->efile,tb->ninput,MPI_DOUBLE,0,world);
MPI_Bcast(tb->ffile,tb->ninput,MPI_DOUBLE,0,world);
MPI_Bcast(&tb->f_unspecified,1,MPI_INT,0,world);
MPI_Bcast(&tb->use_degrees,1,MPI_INT,0,world);
// COMMENTING OUT: equilibrium angles are not supported
//MPI_Bcast(&tb->theta0,1,MPI_DOUBLE,0,world);
}

Event Timeline