Files
lammps/src/CG-DNA/pair_oxdna2_dh.cpp
Kierran Falloon 775a73b67c cgDNA 'real' units and potential file reading for non-modifiable potential parameters (#15)
* oxDNA potential file reading and real units

This allows for pair and bond coefficients to be read from an appropriately formatted potential file, and also allows for the use of 'real' units within oxDNA1. The correct backend coefficients for pair and bonded interactions are set when the atom vector is initialised through the "ConstantsOxdna" class, based on the units specified within the input file.

* Extract seqav/seqdep and temp from potential files

Also includes miscellaneous string consistency changes and removes unnecessary parameter from reader.next_line instances.

* oxDNA2 potential file reading and real units

This extends previous changes to oxDNA2 specific potentials, being FENE, excluded volume, coaxial stacking and Debye-Hückel. Units now default to LJ values rather than 0.

* oxDNA potential files

* LJ <-> real units conversion tool

Converts standard oxDNA data and input file to real units, with inverse flag available for real -> LJ.

* oxRNA2 potential file reading and real units

For RNA, d_cs_x is treated as d_cs within ConstantsOxdna in order to reduce code duplication and complexity.

* Reparameterise real units

* Generalise PotentialFileReader logs

* Extract stk xi and kappa from potential files

This allows users to edit these values from the input script, as is documented, rather than them being within the potential files.

* Real unit and potential file documentation

This adds examples for real unit parameters and specific potential file documentation for each bond and pair style.
2024-05-03 15:00:29 +01:00

582 lines
18 KiB
C++

// clang-format off
/* ----------------------------------------------------------------------
LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
https://www.lammps.org/, Sandia National Laboratories
LAMMPS development team: developers@lammps.org
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: Oliver Henrich (University of Strathclyde, Glasgow)
------------------------------------------------------------------------- */
#include "pair_oxdna2_dh.h"
#include "atom.h"
#include "comm.h"
#include "constants_oxdna.h"
#include "error.h"
#include "force.h"
#include "math_extra.h"
#include "memory.h"
#include "neigh_list.h"
#include "potential_file_reader.h"
#include <cmath>
#include <cstring>
using namespace LAMMPS_NS;
/* ---------------------------------------------------------------------- */
PairOxdna2Dh::PairOxdna2Dh(LAMMPS *lmp) : Pair(lmp)
{
single_enable = 0;
writedata = 1;
trim_flag = 0;
}
/* ---------------------------------------------------------------------- */
PairOxdna2Dh::~PairOxdna2Dh()
{
if (allocated) {
memory->destroy(setflag);
memory->destroy(cutsq);
memory->destroy(qeff_dh_pf);
memory->destroy(kappa_dh);
memory->destroy(b_dh);
memory->destroy(cut_dh_ast);
memory->destroy(cutsq_dh_ast);
memory->destroy(cut_dh_c);
memory->destroy(cutsq_dh_c);
}
}
/* ----------------------------------------------------------------------
compute vector COM-sugar-phosphate backbone interaction site in oxDNA2
------------------------------------------------------------------------- */
void PairOxdna2Dh::compute_interaction_sites(double e1[3],
double e2[3], double /*e3*/[3], double r[3])
{
double d_cs_x = ConstantsOxdna::get_d_cs_x();
double d_cs_y = ConstantsOxdna::get_d_cs_y();
r[0] = d_cs_x*e1[0] + d_cs_y*e2[0];
r[1] = d_cs_x*e1[1] + d_cs_y*e2[1];
r[2] = d_cs_x*e1[2] + d_cs_y*e2[2];
}
/* ----------------------------------------------------------------------
compute function for oxDNA pair interactions
s=sugar-phosphate backbone site, b=base site, st=stacking site
------------------------------------------------------------------------- */
void PairOxdna2Dh::compute(int eflag, int vflag)
{
double delf[3],delta[3],deltb[3]; // force, torque increment
double rtmp_s[3],delr[3];
double evdwl,fpair,factor_lj;
double r,rsq,rinv;
// vectors COM-backbone sites in lab frame
double ra_cs[3],rb_cs[3];
// Cartesian unit vectors in lab frame
double ax[3],ay[3],az[3];
double bx[3],by[3],bz[3];
double **x = atom->x;
double **f = atom->f;
double **torque = atom->torque;
int *type = atom->type;
int nlocal = atom->nlocal;
int newton_pair = force->newton_pair;
int *alist,*blist,*numneigh,**firstneigh;
double *special_lj = force->special_lj;
int a,b,ia,ib,anum,bnum,atype,btype;
evdwl = 0.0;
ev_init(eflag,vflag);
anum = list->inum;
alist = list->ilist;
numneigh = list->numneigh;
firstneigh = list->firstneigh;
// n(x/y/z)_xtrct = extracted local unit vectors from oxdna_excv
int dim;
nx_xtrct = (double **) force->pair->extract("nx",dim);
ny_xtrct = (double **) force->pair->extract("ny",dim);
nz_xtrct = (double **) force->pair->extract("nz",dim);
// loop over pair interaction neighbors of my atoms
for (ia = 0; ia < anum; ia++) {
a = alist[ia];
atype = type[a];
ax[0] = nx_xtrct[a][0];
ax[1] = nx_xtrct[a][1];
ax[2] = nx_xtrct[a][2];
ay[0] = ny_xtrct[a][0];
ay[1] = ny_xtrct[a][1];
ay[2] = ny_xtrct[a][2];
az[0] = nz_xtrct[a][0];
az[1] = nz_xtrct[a][1];
az[2] = nz_xtrct[a][2];
// vector COM-backbone site a
compute_interaction_sites(ax,ay,az,ra_cs);
rtmp_s[0] = x[a][0] + ra_cs[0];
rtmp_s[1] = x[a][1] + ra_cs[1];
rtmp_s[2] = x[a][2] + ra_cs[2];
blist = firstneigh[a];
bnum = numneigh[a];
for (ib = 0; ib < bnum; ib++) {
b = blist[ib];
factor_lj = special_lj[sbmask(b)]; // = 0 for nearest neighbors
b &= NEIGHMASK;
btype = type[b];
bx[0] = nx_xtrct[b][0];
bx[1] = nx_xtrct[b][1];
bx[2] = nx_xtrct[b][2];
by[0] = ny_xtrct[b][0];
by[1] = ny_xtrct[b][1];
by[2] = ny_xtrct[b][2];
bz[0] = nz_xtrct[b][0];
bz[1] = nz_xtrct[b][1];
bz[2] = nz_xtrct[b][2];
// vector COM-backbone site b
compute_interaction_sites(bx,by,bz,rb_cs);
// vector backbone site b to a
delr[0] = rtmp_s[0] - x[b][0] - rb_cs[0];
delr[1] = rtmp_s[1] - x[b][1] - rb_cs[1];
delr[2] = rtmp_s[2] - x[b][2] - rb_cs[2];
rsq = delr[0]*delr[0] + delr[1]*delr[1] + delr[2]*delr[2];
if (rsq <= cutsq_dh_c[atype][btype]) {
r = sqrt(rsq);
rinv = 1.0/r;
if (r <= cut_dh_ast[atype][btype]) {
fpair = qeff_dh_pf[atype][btype] * exp(-kappa_dh[atype][btype] * r) *
(kappa_dh[atype][btype] + rinv) * rinv * rinv;
if (eflag) {
evdwl = qeff_dh_pf[atype][btype] * exp(-kappa_dh[atype][btype]*r) * rinv;
}
}
else {
fpair = 2.0 * b_dh[atype][btype] * (cut_dh_c[atype][btype] - r) * rinv;
if (eflag) {
evdwl = b_dh[atype][btype] * (r - cut_dh_c[atype][btype]) * (r - cut_dh_c[atype][btype]);
}
}
// knock out nearest-neighbor interaction between adjacent backbone sites
fpair *= factor_lj;
evdwl *= factor_lj;
delf[0] = delr[0] * fpair;
delf[1] = delr[1] * fpair;
delf[2] = delr[2] * fpair;
// apply force and torque to each of 2 atoms
if (newton_pair || a < nlocal) {
f[a][0] += delf[0];
f[a][1] += delf[1];
f[a][2] += delf[2];
MathExtra::cross3(ra_cs,delf,delta);
torque[a][0] += delta[0];
torque[a][1] += delta[1];
torque[a][2] += delta[2];
}
if (newton_pair || b < nlocal) {
f[b][0] -= delf[0];
f[b][1] -= delf[1];
f[b][2] -= delf[2];
MathExtra::cross3(rb_cs,delf,deltb);
torque[b][0] -= deltb[0];
torque[b][1] -= deltb[1];
torque[b][2] -= deltb[2];
}
// increment energy and virial
// NOTE: The virial is calculated on the 'molecular' basis.
// (see G. Ciccotti and J.P. Ryckaert, Comp. Phys. Rep. 4, 345-392 (1986))
if (evflag) ev_tally_xyz(a,b,nlocal,newton_pair,evdwl,0.0,
delf[0],delf[1],delf[2],x[a][0]-x[b][0],x[a][1]-x[b][1],x[a][2]-x[b][2]);
}
}
}
}
/* ----------------------------------------------------------------------
allocate all arrays
------------------------------------------------------------------------- */
void PairOxdna2Dh::allocate()
{
allocated = 1;
int n = atom->ntypes;
memory->create(setflag,n+1,n+1,"pair:setflag");
for (int i = 1; i <= n; i++)
for (int j = i; j <= n; j++)
setflag[i][j] = 0;
memory->create(cutsq,n+1,n+1,"pair:cutsq");
memory->create(kappa_dh,n+1,n+1,"pair:kappa_dh");
memory->create(qeff_dh_pf,n+1,n+1,"pair:qeff_dh_pf");
memory->create(b_dh,n+1,n+1,"pair:b_dh");
memory->create(cut_dh_ast,n+1,n+1,"pair:cut_dh_ast");
memory->create(cutsq_dh_ast,n+1,n+1,"pair:cutsq_dh_ast");
memory->create(cut_dh_c,n+1,n+1,"pair:cut_dh_c");
memory->create(cutsq_dh_c,n+1,n+1,"pair:cutsq_dh_c");
}
/* ----------------------------------------------------------------------
global settings
------------------------------------------------------------------------- */
void PairOxdna2Dh::settings(int narg, char **/*arg*/)
{
if (narg != 0) error->all(FLERR,"Illegal pair_style command");
}
/* ----------------------------------------------------------------------
set coeffs for one or more type pairs
------------------------------------------------------------------------- */
void PairOxdna2Dh::coeff(int narg, char **arg)
{
int count;
if (narg != 5) error->all(FLERR,"Incorrect args for pair coefficients in oxdna2/dh");
if (!allocated) allocate();
int ilo,ihi,jlo,jhi;
utils::bounds(FLERR,arg[0],1,atom->ntypes,ilo,ihi,error);
utils::bounds(FLERR,arg[1],1,atom->ntypes,jlo,jhi,error);
count = 0;
double T, rhos_dh_one, qeff_dh_one;
T = utils::numeric(FLERR,arg[2],false,lmp);
rhos_dh_one = utils::numeric(FLERR,arg[3],false,lmp);
if (utils::strmatch(arg[4], "^[a-zA-Z0-9]*\\.[a-zA-Z]+$") == true) { // if last arg is a potential file
if (comm->me == 0) { // read value from potential file
PotentialFileReader reader(lmp, arg[4], "oxdna potential", " (dh)");
char * line;
std::string iloc, jloc, potential_name;
while(line = reader.next_line()) {
try {
ValueTokenizer values(line);
iloc = values.next_string();
jloc = values.next_string();
potential_name = values.next_string();
if (iloc == arg[0] && jloc == arg[1] && potential_name == "dh") {
qeff_dh_one = values.next_double();
break;
} else continue;
} catch (std::exception &e) {
error->one(FLERR, "Problem parsing oxDNA2 potential file: {}", e.what());
}
}
if (iloc != arg[0] || jloc != arg[1] || potential_name != "dh") error->one(FLERR, "No corresponding dh potential found in file {} for pair type {} {}", arg[4], arg[0], arg[1]);
}
MPI_Bcast(&qeff_dh_one, 1, MPI_DOUBLE, 0, world);
} else qeff_dh_one = utils::numeric(FLERR,arg[4],false,lmp); // else, it is effective charge
double lambda_dh_one, kappa_dh_one, qeff_dh_pf_one;
double b_dh_one, cut_dh_ast_one, cut_dh_c_one;
// Debye length and inverse Debye length
/*
NOTE:
The numerical factor is the Debye length in s.u.
lambda(T = 300 K = 0.1) =
sqrt(eps_0 * eps_r * k_B * T/(2 * N_A * e^2 * 1000 mol/m^3))
* 1/oxDNA_length_unit for LJ units, or;
* [(8.518 * sqrt(k_B / 4.142e-20))/oxDNA_length_unit] for real units
(see B. Snodin et al., J. Chem. Phys. 142, 234901 (2015).)
We use
eps_0 = vacuum permittivity = 8.854187817e-12 F/m
eps_r = relative permittivity of water = 80
k_B = Boltzmann constant = 1.3806485279e-23 J/K
T = absolute temperature = 300 K
N_A = Avogadro constant = 6.02214085774e23 / mol
e = elementary charge = 1.6021766208e-19 C
oxDNA_length_unit = 8.518e-10 m
*/
lambda_dh_one = ConstantsOxdna::get_lambda_dh_one_prefactor()*sqrt(T/0.1/rhos_dh_one);
kappa_dh_one = 1.0/lambda_dh_one;
// prefactor in DH interaction containing qeff^2
/*
NOTE:
The numerical factor is
qeff_dh_pf = e^2/(4 * pi * eps_0 * eps_r)
* 1/(oxDNA_energy_unit * oxDNA_length_unit) for LJ units, or;
* [(~5.96169* 8.518)/(oxDNA_energy_unit * oxDNA_length_unit)] for real units
(see B. Snodin et al., J. Chem. Phys. 142, 234901 (2015).)
In addition to the above units we use
oxDNA_energy_unit = 4.142e-20 J
*/
qeff_dh_pf_one = ConstantsOxdna::get_qeff_dh_pf_one_prefactor()*qeff_dh_one*qeff_dh_one;
// smoothing parameters - determined through continuity and differentiability
cut_dh_ast_one = 3.0*lambda_dh_one;
b_dh_one = -(exp(-cut_dh_ast_one/lambda_dh_one) * qeff_dh_pf_one * qeff_dh_pf_one *
(cut_dh_ast_one + lambda_dh_one) * (cut_dh_ast_one + lambda_dh_one))/
(-4.0 * cut_dh_ast_one * cut_dh_ast_one * cut_dh_ast_one *
lambda_dh_one * lambda_dh_one * qeff_dh_pf_one);
cut_dh_c_one = cut_dh_ast_one * (qeff_dh_pf_one*cut_dh_ast_one +
3.0*qeff_dh_pf_one * lambda_dh_one)/
(qeff_dh_pf_one * (cut_dh_ast_one+lambda_dh_one));
for (int i = ilo; i <= ihi; i++) {
for (int j = MAX(jlo,i); j <= jhi; j++) {
kappa_dh[i][j] = kappa_dh_one;
qeff_dh_pf[i][j] = qeff_dh_pf_one;
b_dh[i][j] = b_dh_one;
cut_dh_ast[i][j] = cut_dh_ast_one;
cut_dh_c[i][j] = cut_dh_c_one;
setflag[i][j] = 1;
count++;
}
}
if (count == 0) error->all(FLERR,"Incorrect args for pair coefficients in oxdna2/dh");
}
/* ----------------------------------------------------------------------
neighbor callback to inform pair style of neighbor list to use regular
------------------------------------------------------------------------- */
void PairOxdna2Dh::init_list(int id, NeighList *ptr)
{
if (id == 0) list = ptr;
if (id > 0) error->all(FLERR,"Respa not supported");
}
/* ----------------------------------------------------------------------
init for one type pair i,j and corresponding j,i
------------------------------------------------------------------------- */
double PairOxdna2Dh::init_one(int i, int j)
{
if (setflag[i][j] == 0) {
error->all(FLERR,"Coefficient mixing not defined in oxDNA");
}
if (offset_flag) {
error->all(FLERR,"Offset not supported in oxDNA");
}
kappa_dh[j][i] = kappa_dh[i][j];
qeff_dh_pf[j][i] = qeff_dh_pf[i][j];
b_dh[j][i] = b_dh[i][j];
cut_dh_ast[j][i] = cut_dh_ast[i][j];
cut_dh_c[j][i] = cut_dh_c[i][j];
cutsq_dh_ast[i][j] = cut_dh_ast[i][j]*cut_dh_ast[i][j];
cutsq_dh_ast[j][i] = cutsq_dh_ast[i][j];
cutsq_dh_c[i][j] = cut_dh_c[i][j]*cut_dh_c[i][j];
cutsq_dh_c[j][i] = cutsq_dh_c[i][j];
// set the master list distance cutoff
return cut_dh_c[i][j];
}
/* ----------------------------------------------------------------------
proc 0 writes to restart file
------------------------------------------------------------------------- */
void PairOxdna2Dh::write_restart(FILE *fp)
{
write_restart_settings(fp);
int i,j;
for (i = 1; i <= atom->ntypes; i++)
for (j = i; j <= atom->ntypes; j++) {
fwrite(&setflag[i][j],sizeof(int),1,fp);
if (setflag[i][j]) {
fwrite(&kappa_dh[i][j],sizeof(double),1,fp);
fwrite(&qeff_dh_pf[i][j],sizeof(double),1,fp);
fwrite(&b_dh[i][j],sizeof(double),1,fp);
fwrite(&cut_dh_ast[i][j],sizeof(double),1,fp);
fwrite(&cut_dh_c[i][j],sizeof(double),1,fp);
}
}
}
/* ----------------------------------------------------------------------
proc 0 reads from restart file, bcasts
------------------------------------------------------------------------- */
void PairOxdna2Dh::read_restart(FILE *fp)
{
read_restart_settings(fp);
allocate();
int i,j;
int me = comm->me;
for (i = 1; i <= atom->ntypes; i++)
for (j = i; j <= atom->ntypes; j++) {
if (me == 0) utils::sfread(FLERR,&setflag[i][j],sizeof(int),1,fp,nullptr,error);
MPI_Bcast(&setflag[i][j],1,MPI_INT,0,world);
if (setflag[i][j]) {
if (me == 0) {
utils::sfread(FLERR,&kappa_dh[i][j],sizeof(double),1,fp,nullptr,error);
utils::sfread(FLERR,&qeff_dh_pf[i][j],sizeof(double),1,fp,nullptr,error);
utils::sfread(FLERR,&b_dh[i][j],sizeof(double),1,fp,nullptr,error);
utils::sfread(FLERR,&cut_dh_ast[i][j],sizeof(double),1,fp,nullptr,error);
utils::sfread(FLERR,&cut_dh_c[i][j],sizeof(double),1,fp,nullptr,error);
}
MPI_Bcast(&kappa_dh[i][j],1,MPI_DOUBLE,0,world);
MPI_Bcast(&qeff_dh_pf[i][j],1,MPI_DOUBLE,0,world);
MPI_Bcast(&b_dh[i][j],1,MPI_DOUBLE,0,world);
MPI_Bcast(&cut_dh_ast[i][j],1,MPI_DOUBLE,0,world);
MPI_Bcast(&cut_dh_c[i][j],1,MPI_DOUBLE,0,world);
}
}
}
/* ----------------------------------------------------------------------
proc 0 writes to restart file
------------------------------------------------------------------------- */
void PairOxdna2Dh::write_restart_settings(FILE *fp)
{
fwrite(&offset_flag,sizeof(int),1,fp);
fwrite(&mix_flag,sizeof(int),1,fp);
fwrite(&tail_flag,sizeof(int),1,fp);
}
/* ----------------------------------------------------------------------
proc 0 reads from restart file, bcasts
------------------------------------------------------------------------- */
void PairOxdna2Dh::read_restart_settings(FILE *fp)
{
int me = comm->me;
if (me == 0) {
utils::sfread(FLERR,&offset_flag,sizeof(int),1,fp,nullptr,error);
utils::sfread(FLERR,&mix_flag,sizeof(int),1,fp,nullptr,error);
utils::sfread(FLERR,&tail_flag,sizeof(int),1,fp,nullptr,error);
}
MPI_Bcast(&offset_flag,1,MPI_INT,0,world);
MPI_Bcast(&mix_flag,1,MPI_INT,0,world);
MPI_Bcast(&tail_flag,1,MPI_INT,0,world);
}
/* ----------------------------------------------------------------------
proc 0 writes to data file
------------------------------------------------------------------------- */
void PairOxdna2Dh::write_data(FILE *fp)
{
for (int i = 1; i <= atom->ntypes; i++)
fprintf(fp,"%d\
%g %g\
%g %g %g\
\n",i,
kappa_dh[i][i],qeff_dh_pf[i][i],
b_dh[i][i],cut_dh_ast[i][i],cut_dh_c[i][i]);
}
/* ----------------------------------------------------------------------
proc 0 writes all pairs to data file
------------------------------------------------------------------------- */
void PairOxdna2Dh::write_data_all(FILE *fp)
{
for (int i = 1; i <= atom->ntypes; i++)
for (int j = i; j <= atom->ntypes; j++)
fprintf(fp,"%d %d\
%g %g\
%g %g %g\
\n",i,j,
kappa_dh[i][j],qeff_dh_pf[i][j],
b_dh[i][j],cut_dh_ast[i][j],cut_dh_c[i][j]);
}
/* ---------------------------------------------------------------------- */
void *PairOxdna2Dh::extract(const char *str, int &dim)
{
dim = 2;
if (strcmp(str,"kappa_dh") == 0) return (void *) kappa_dh;
if (strcmp(str,"qeff_dh_pf") == 0) return (void *) qeff_dh_pf;
if (strcmp(str,"b_dh") == 0) return (void *) b_dh;
if (strcmp(str,"cut_dh_ast") == 0) return (void *) cut_dh_ast;
if (strcmp(str,"cut_dh_c") == 0) return (void *) cut_dh_c;
return nullptr;
}