5 * Created by Toshi Nagata on 07/10/07.
6 * Copyright 2007 Toshi Nagata. All rights reserved.
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation version 2 of the License.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
18 #include "MDGraphite.h"
26 /* The table should be created for each type of atom of different vdW parameters.
27 So we need to keep the vdW parameters and the table address. */
28 typedef struct MDGraphiteTable {
29 Double A, B; /* A = 4*pow(r0, 12)*eps, B = 4*pow(r0, 6)*eps */
30 Double offset; /* Offset for cutoff truncation */
31 Double cutoff2; /* Square of the cutoff distance */
32 Double *cache; /* Double[Sx*Sy*Sz*4] */
35 struct MDGraphiteArena {
36 Int natoms_uniq; /* Must be the same as arena->natoms_uniq; otherwise, all internal information is rebuilt. */
38 Int needs_update; /* If non-zero, then the internal information is rebuilt at the next force calculation */
39 Int *table_indices; /* Index to tables[] for each symmetry-unique atom */
40 Double *energies; /* Cache energies of symmetry-unique atoms */
44 MDGraphiteTable *tables;
45 Vector origin; /* Origin of the graphite plane in cartesian coordinates */
46 Vector xaxis, yaxis, zaxis; /* The axes of the graphite plane in cartesian coordinates */
47 Mat33 rcell; /* Convert cartesian to graphite internal coordinates */
49 Double R; /* C-C bond length. Default: 1.23 */
50 Double C_eps; /* LJ epsilon for graphite C atoms. Default: 0.0860 * KCAL2INTERNAL */
51 Double C_sig; /* LJ sigma for graphite C atoms. Default: 3.3997 */
53 /* The energies and forces at selected points in the asymmetric unit is
54 cached in graphite->table[]. Sx, Sy, Sz and Sz0 specifies how these points
56 The asymmetric unit is a rectangle (0, 0)-(0.75R, sqrt(3)*0.5R). This rectangle
57 area (including borders) is split into an (Sx * Sy) lattice, and the lattice
58 points (i, j) are selected as sample points. For each point, the z-coordinate
59 in the range [Z_min, Z_lim] is sampled, so that there are Sz0 points span
60 [Z_min, Z_switch) and (Sz - Sz0) points span [Z_switch, Z_lim]. Thus, the
61 cartesian coordinates of a sample point (i, j, k) are:
63 y = sqrt(3)*0.5R*j/(Sy-1),
64 z = (k >= Sz0 ? Z_switch+(Z_lim-Z_switch)*(k-Sz0)/(Sz-Sz0-1)
65 : Z_min+(Z_switch-Z_min)*k/Sz0)
66 The cached value can be accessed by:
67 table[((i*Sy + j)*Sz + k)*4 + n]
68 where n is 0, 1, 2, 3 for the energy, force.x, force.y, force.z, respectively.
71 Int Sx; /* Default: 16 */
72 Int Sy; /* Default: 16 */
73 Int Sz; /* Default: 64 */
74 Int Sz0; /* Default: 52 */
75 Double Z_min; /* Default: 1.0 */
76 Double Z_switch; /* Default: 5.0 */
77 Double Z_lim; /* Default: 10.0 */
79 /* If periodic_average > 0 and periodic boundary conditions are enabled, then
80 the graphite potential will be averaged for periodic_average number of image
81 atoms for each periodic cell axes. */
90 /* A = 4*pow(r0, 12)*eps, B = 4*pow(r0, 6)*eps */
92 s_graphite_lj(Vector r, const MDGraphiteTable *tr, Vector *force)
94 Double r2 = r.x*r.x + r.y*r.y + r.z*r.z;
95 Double w2, w6, w12, k0, k1;
96 if (r2 >= tr->cutoff2) {
97 force->x = force->y = force->z = 0.0;
103 k0 = tr->A * w12 - tr->B * w6 + tr->offset;
104 k1 = 6 * w2 * (2.0 * tr->A * w12 - tr->B * w6);
113 /* Calculate the total potential and force between an atom and the graphite sheet.
114 The graphite sheet is assumed as a perfect hexagonal array of carbon atoms, and
115 only van der Waals force is considered.
116 The origin is at the center of a hexagon, and all carbon atoms lie on the xy plane.
117 The x-axis points from the origin to one carbon atom. The carbon atoms can be
119 (0.5R*(3*i+1), sqrt(3)*0.5R*j), where i+j is odd
120 This function calculates the vdW potential and force from the graphite
121 atoms in the range (i,j)=(-10,-15) to (+10,+15).
122 It is expected that the target atom is close to the z-axis. In the present code,
123 this function is only invoked for the position in the asymmetric unit, (0,0)..
124 (1.5R, sqrt(3)*0.5R).
127 s_graphite_asymunit(MDGraphiteArena *graphite, Vector r, const MDGraphiteTable *tr, Vector *force)
132 force->x = force->y = force->z = 0.0;
134 for (i = -10; i <= 10; i++) {
135 for (j = -15; j <= 15; j++) {
136 if ((i + j) % 2 == 0)
138 r0.x = r.x - 0.5 * graphite->R * (3 * i + 1);
139 r0.y = r.y - graphite->R * 0.866025403784439 * j;
141 e0 = s_graphite_lj(r0, tr, &f0);
144 r0.x = r.x - 0.5 * graphite->R * (3 * i - 1);
145 e0 = s_graphite_lj(r0, tr, &f0);
153 /* Nearest graphite atoms; x, y coordinates should be multiplied by
154 R/2 and (sqrt(3)/2)R, respectively */
156 static Int sNearestTable[] = {
158 -4, -2, -2, -2, 2, -2, 4, -2,
159 -5, -1, -1, -1, 1, -1, 5, -1,
160 -4, 0, -2, 0, 2, 0, 4, 0,
161 -5, 1, -1, 1, 1, 1, 5, 1,
162 -4, 2, -2, 2, 2, 2, 4, 2,
165 static Int sNearestTable[] = {
167 -5, -3, -1, -3, 1, -3, 5, -3,
168 -4, -2, -2, -2, 2, -2, 4, -2,
169 -7, -1, -5, -1, -1, -1, 1, -1, 5, -1, 7, -1,
170 -8, 0, -4, 0, -2, 0, 2, 0, 4, 0, 8, 0,
171 -7, 1, -5, 1, -1, 1, 1, 1, 5, 1, 7, 1,
172 -4, 2, -2, 2, 2, 2, 4, 2,
173 -5, 3, -1, 3, 1, 3, 5, 3,
176 #define kSizeOfNearestTable (sizeof(sNearestTable)/sizeof(sNearestTable[0])/2)
178 /* The nearest 24 graphite atoms are always explicitly calculated */
180 s_graphite_nearest_asymunit(MDGraphiteArena *graphite, Vector r, const MDGraphiteTable *tr, Vector *force)
185 en = force->x = force->y = force->z = 0.0;
186 for (i = 0; i < kSizeOfNearestTable; i++) {
187 q.x = sNearestTable[i*2] * graphite->R * 0.5;
188 q.y = sNearestTable[i*2+1] * 0.866025403784439 * graphite->R;
192 en += s_graphite_lj(dr, tr, &f);
201 s_graphite_make_table(MDGraphiteArena *graphite, MDGraphiteTable *tr)
206 k = graphite->Sx * graphite->Sy * graphite->Sz * sizeof(Double) * 4;
207 if (tr->cache == NULL)
208 tr->cache = (Double *)malloc(k);
210 tr->cache = (Double *)realloc(tr->cache, k);
211 memset(tr->cache, 0, k);
212 for (k = 0; k < graphite->Sz; k++) {
213 if (k >= graphite->Sz0)
214 v.z = graphite->Z_switch + (graphite->Z_lim - graphite->Z_switch) * (k - graphite->Sz0) / (graphite->Sz - graphite->Sz0 - 1);
216 v.z = graphite->Z_min + (graphite->Z_switch - graphite->Z_min) * k / graphite->Sz0; /* Not (Sz0 - 1) */
217 for (i = 0; i < graphite->Sx; i++) {
218 for (j = 0; j < graphite->Sy; j++) {
219 v.x = 0.75 * graphite->R * i / (graphite->Sx - 1.0);
220 v.y = 0.866025403784439 * graphite->R * j / (graphite->Sy - 1.0);
221 en = s_graphite_asymunit(graphite, v, tr, &f);
222 en0 = s_graphite_nearest_asymunit(graphite, v, tr, &f0);
223 fp = tr->cache + ((i * graphite->Sy + j) * graphite->Sz + k) * 4;
234 s_linear_interpolate(MDGraphiteArena *graphite, Double *base, Double dx, Double dy, Double dz)
236 Int Sy = graphite->Sy;
237 Int Sz = graphite->Sz;
239 Double a1 = base[Sy * Sz * 4] - a0;
240 Double a2 = base[Sz * 4] - a0;
241 Double a3 = base[4] - a0;
242 Double a4 = base[(Sy + 1) * Sz * 4] - a0 - a1 - a2;
243 Double a5 = base[(Sz + 1) * 4] - a0 - a2 - a3;
244 Double a6 = base[(Sy * Sz + 1) * 4] - a0 - a1 - a3;
245 Double a7 = base[((Sy + 1) * Sz + 1) * 4] - a0 - a1 - a2 - a3 - a4 - a5 - a6;
246 return a0 + a1 * dx + a2 * dy + a3 * dz + a4 * dx * dy + a5 * dy * dz + a6 * dz * dx + a7 * dx * dy * dz;
250 s_graphite(MDGraphiteArena *graphite, Vector v, MDGraphiteTable *tr, Vector *f)
254 Double en, dx, dy, dz, *fp;
255 Int ix, iy, negx, negy, negz, symop;
262 if (v.z > graphite->Z_lim) {
263 f->x = f->y = f->z = 0.0;
267 cella = 0.75 * graphite->R;
268 cellb = 0.866025403784439 * graphite->R;
269 dx = v.x / (cella * 4);
270 ix = floor(dx + 0.5);
272 dy = v.y / (cellb * 4);
273 iy = floor(dy + 0.5);
276 /* Move vector to the asymmetric unit (0,0)-(0.25,0.25) */
294 } else if (dx > 0.25) {
299 v0.x = dx * cella * 4;
300 v0.y = dy * cellb * 4;
303 if (tr->cache != NULL) {
304 /* 0 <= dx <= 0.25, 0 <= dy <= 0.25, 1.0 <= v.z <= 10.0 */
305 /* 0 <= i <= Sx-1, 0 <= j <= Sy-1, 0 <= k <= Sz-1 */
306 dx = dx * (graphite->Sx - 1.0) / 0.25;
307 dy = dy * (graphite->Sy - 1.0) / 0.25;
308 if (v.z >= graphite->Z_switch)
309 dz = (v.z - graphite->Z_switch) * (graphite->Sz - graphite->Sz0 - 1) / (graphite->Z_lim - graphite->Z_switch) + graphite->Sz0;
311 dz = (v.z - graphite->Z_min) * graphite->Sz0 / (graphite->Z_switch - graphite->Z_min);
318 if (i >= graphite->Sx - 1) {
319 i = graphite->Sx - 2;
322 if (j >= graphite->Sy - 1) {
323 j = graphite->Sy - 2;
329 } else if (k >= graphite->Sz - 1) {
330 k = graphite->Sz - 2;
333 fp = tr->cache + ((i*graphite->Sy + j)*graphite->Sz + k)*4;
334 en = s_linear_interpolate(graphite, fp, dx, dy, dz);
335 f0.x = s_linear_interpolate(graphite, fp + 1, dx, dy, dz);
336 f0.y = s_linear_interpolate(graphite, fp + 2, dx, dy, dz);
337 f0.z = s_linear_interpolate(graphite, fp + 3, dx, dy, dz);
338 en += s_graphite_nearest_asymunit(graphite, v0, tr, f);
343 en = s_graphite_asymunit(graphite, v0, tr, f);
346 /* Back transform to original axis system */
348 case 3: negx = !negx; break;
349 case 2: negy = !negy; break;
350 case 1: negx = !negx; negy = !negy; break;
365 static Vector x = {1, 0, 0}, y = {0, 1, 0}, z = {0, 0, 1};
366 static Mat33 u = {1, 0, 0, 0, 1, 0, 0, 0, 1};
367 MDGraphiteArena *graphite = (MDGraphiteArena *)calloc(sizeof(MDGraphiteArena), 1);
368 graphite->use_table = 1;
373 graphite->Z_min = 1.0;
374 graphite->Z_switch = 5.0;
375 graphite->Z_lim = 10.0;
377 graphite->C_eps = 0.0860 * KCAL2INTERNAL;
378 graphite->C_sig = 3.3997;
382 memmove(graphite->rcell, u, sizeof(Mat33));
387 s_graphite_release_tables(MDGraphiteArena *graphite)
390 if (graphite == NULL || graphite->ntables == 0 || graphite->tables == NULL)
392 for (i = 0; i < graphite->ntables; i++) {
393 if (graphite->tables[i].cache != NULL)
394 free(graphite->tables[i].cache);
396 free(graphite->tables);
397 graphite->tables = NULL;
398 graphite->ntables = 0;
402 graphite_release(MDGraphiteArena *graphite)
404 if (graphite != NULL) {
405 if (graphite->table_indices != NULL)
406 free(graphite->table_indices);
407 if (graphite->tables != NULL)
408 s_graphite_release_tables(graphite);
414 graphite_set_origin(MDGraphiteArena *graphite, const Vector *vp)
416 if (graphite == NULL)
418 graphite->origin = *vp;
421 /* (Re)initialize the MDGraphiteArena */
423 s_graphite_init(MDGraphiteArena *graphite, MDArena *arena)
427 int nuniq = arena->natoms_uniq;
430 /* Reallocate the internal storage */
431 if (graphite->table_indices == NULL)
432 graphite->table_indices = (Int *)malloc(sizeof(Int) * nuniq);
434 graphite->table_indices = (Int *)realloc(graphite->table_indices, sizeof(Int) * nuniq);
435 if (graphite->energies == NULL)
436 graphite->energies = (Double *)malloc(sizeof(Double) * nuniq);
438 graphite->energies = (Double *)realloc(graphite->energies, sizeof(Double) * nuniq);
439 if (graphite->last_forces == NULL)
440 graphite->last_forces = (Vector *)malloc(sizeof(Vector) * nuniq);
442 graphite->last_forces = (Vector *)realloc(graphite->last_forces, sizeof(Vector) * nuniq);
443 if (graphite->tables != NULL)
444 s_graphite_release_tables(graphite);
446 /* Calculate the tables */
447 md_log(arena, "Building the graphite table...\n");
449 for (i = 0, ap = arena->mol->atoms; i < nuniq; i++, ap++) {
452 Double sig, eps, sigc, epsc, A, B;
453 vdw_idx = arena->vdw_par_i[i];
454 if (vdw_idx < 0 || vdw_idx >= arena->par->nvdwPars) {
456 graphite->table_indices[i] = -1;
459 vp = &(arena->par->vdwPars[vdw_idx]);
460 if (fabs(vp->A) < 1e-15) {
464 eps = 0.25 * vp->B * vp->B / vp->A;
465 sig = pow(vp->B * 0.25 / eps, 1.0/6.0);
467 sigc = (graphite->C_sig + sig) * 0.5;
468 epsc = sqrt(graphite->C_eps * eps);
469 A = 4.0 * pow(sigc, 12.0) * epsc;
470 B = 4.0 * pow(sigc, 6.0) * epsc;
471 /* Are there entries with similar A/B values? */
472 for (j = 0; j < graphite->ntables; j++) {
473 tp = graphite->tables + j;
474 if (fabs(tp->A - A) < 1e-10 && fabs(tp->B - B) < 1e-10)
477 if (j >= graphite->ntables) {
478 /* Create a new entry with a table */
480 j = graphite->ntables;
481 tp = (MDGraphiteTable *)AssignArray(&graphite->tables, &graphite->ntables, sizeof(MDGraphiteTable), j, NULL);
484 /* Calculate the offset: set 0 to offset temporarily, and call s_graphite_lj */
485 tp->cutoff2 = arena->cutoff * arena->cutoff + 1.0; /* +1.0 to avoid cutoff */
489 tp->offset = -s_graphite_lj(v, tp, &v);
490 tp->cutoff2 = arena->cutoff * arena->cutoff; /* This is the real value */
491 if (graphite->use_table)
492 s_graphite_make_table(graphite, tp);
493 else tp->cache = NULL;
495 graphite->table_indices[i] = j;
497 graphite->natoms_uniq = arena->natoms_uniq;
498 md_log(arena, "%d table(s) were created %s cached data.\n", graphite->ntables, (graphite->use_table ? "with" : "without"));
499 graphite->needs_update = 0;
502 if (arena->debug_result && arena->debug_output_level > 1) {
503 graphite->debug_fp = arena->debug_result;
504 } else graphite->debug_fp = NULL;
510 graphite_force(MDGraphiteArena *graphite, MDArena *arena, Double *energy, Vector *forces)
512 Molecule *mol = arena->mol;
514 int ix, iy, iz, px, py, pz;
516 Vector *avp, *bvp, *cvp;
517 if (graphite == NULL || mol == NULL || mol->natoms == 0 || arena->natoms_uniq == 0)
519 if (graphite->natoms_uniq != arena->natoms_uniq || graphite->needs_update)
520 s_graphite_init(graphite, arena);
521 graphite->last_energy = 0;
522 memset(graphite->last_forces, 0, sizeof(Vector) * arena->natoms_uniq);
523 if (graphite->periodic_average && arena->mol->cell != NULL) {
524 px = (arena->periodic_a ? graphite->periodic_average : 1);
525 py = (arena->periodic_b ? graphite->periodic_average : 1);
526 pz = (arena->periodic_c ? graphite->periodic_average : 1);
530 pxpypz_1 = 1.0 / (px * py * pz);
531 if (mol->cell != NULL) {
532 avp = &(mol->cell->axes[0]);
533 bvp = &(mol->cell->axes[1]);
534 cvp = &(mol->cell->axes[2]);
536 for (i = 0; i < arena->natoms_uniq; i++) {
539 Vector v = mol->atoms[i].r;
540 if (mol->atoms[i].fix_force < 0)
542 if (mol->atoms[i].occupancy == 0.0)
544 en = f.x = f.y = f.z = 0.0;
545 for (ix = 0; ix < px; ix++) {
546 for (iy = 0; iy < py; iy++) {
547 for (iz = 0; iz < pz; iz++) {
550 if (mol->cell != NULL) {
551 v0.x = v.x + avp->x * ix + bvp->x * iy + cvp->x * iz;
552 v0.y = v.y + avp->y * ix + bvp->y * iy + cvp->y * iz;
553 v0.z = v.z + avp->z * ix + bvp->z * iy + cvp->z * iz;
555 VecDec(v0, graphite->origin);
556 MatrixVec(&v0, graphite->rcell, &v0);
557 en0 = s_graphite(graphite, v0, graphite->tables + graphite->table_indices[i], &f0);
564 VecScaleSelf(f, pxpypz_1);
565 graphite->energies[i] = en;
566 graphite->last_energy += en;
567 graphite->last_forces[i] = f;
569 for (i = arena->natoms_uniq; i < mol->natoms; i++) {
570 Symop symop = mol->atoms[i].symop;
571 if (mol->atoms[i].fix_force < 0)
574 int n = mol->atoms[i].symbase;
575 if (n >= 0 && n < arena->natoms_uniq)
576 graphite->last_energy += graphite->energies[n];
580 *energy += graphite->last_energy;
581 if (forces != NULL) {
582 for (i = 0; i < arena->natoms_uniq; i++)
583 VecInc(forces[i], graphite->last_forces[i]);
588 graphite_get_axes(MDGraphiteArena *graphite, Vector *op, Vector *xp, Vector *yp, Vector *zp, Double *rp)
590 if (graphite != NULL) {
592 *op = graphite->origin;
594 *xp = graphite->xaxis;
596 *yp = graphite->yaxis;
598 *zp = graphite->zaxis;