#include <stdlib.h> #include <stdio.h> #include <string.h> #include <math.h> #include <time.h> #include "mdgxVector.h" #include "Matrix.h" #include "Grid.h" #include "CellManip.h" #include "VirtualSites.h" #include "Macros.h" #include "CrdManip.h" #include "Parse.h" #include "Manual.h" #include "ChargeFit.h" #include "Trajectory.h" #include "Topology.h" #include "Random.h" #include "Timings.h" /***=======================================================================***/ /*** AssingGridTopologies: when fitting electrostatic potential grids, it ***/ /*** may be that some grids pertain to different ***/ /*** models than the original topology. In that ***/ /*** case, the topologies must be read into an ***/ /*** array and accessed with each grid in the fit. ***/ /*** ***/ /*** Arguments: ***/ /*** myfit: the fitting control data (contains matrices of names for ***/ /*** topologies and extra points rules files, number of grids) ***/ /*** tj: trajectory control data ***/ /***=======================================================================***/ static void AssignGridTopologies(fset *myfit, trajcon *tj) { int i, j; /*** Count the number of unique topologies. ***/ myfit->tpidx = (int*)malloc(myfit->ngrd*sizeof(int)); myfit->tpcount = 0; SetIVec(myfit->tpidx, myfit->ngrd, -1); for (i = 0; i < myfit->ngrd; i++) { if (myfit->tpidx[i] >= 0) { continue; } myfit->tpidx[i] = myfit->tpcount; for (j = 0; j < myfit->tpname.row; j++) { if (strcmp(myfit->tpname.map[i], myfit->tpname.map[j]) == 0) { myfit->tpidx[j] = myfit->tpidx[i]; } } myfit->tpcount += 1; } /*** Read all topologies ***/ myfit->TPbank = (prmtop*)malloc(myfit->tpcount*sizeof(prmtop)); j = 0; for (i = 0; i < myfit->tpcount; i++) { /*** Data that gets added to the topology from input file ***/ myfit->TPbank[i].lj14fac = 0.5; myfit->TPbank[i].elec14fac = 5.0/6.0; myfit->TPbank[i].rattle = 0; myfit->TPbank[i].settle = 0; myfit->TPbank[i].ljbuck = 0; sprintf(myfit->TPbank[i].WaterName, "WAT"); myfit->TPbank[i].norattlemask = (char*)calloc(MAXNAME, sizeof(char)); myfit->TPbank[i].rattlemask = (char*)calloc(MAXNAME, sizeof(char)); myfit->TPbank[i].norattlemask[0] = '\0'; myfit->TPbank[i].rattlemask[0] = '\0'; while (myfit->tpidx[j] != i) { j++; } strcpy(myfit->TPbank[i].source, myfit->tpname.map[j]); strcpy(myfit->TPbank[i].eprulesource, myfit->eprule); myfit->TPbank[i].lVDWc = (double*)calloc(32, sizeof(double)); GetPrmTop(&myfit->TPbank[i], tj, 1); } /*** If there are multiple topologies in this fit, take the ***/ /*** total charge constraints from the topologies themselves. ***/ /*** Charges will be refit, but their sum should stay the same. ***/ myfit->totalq = (double*)malloc(myfit->tpcount*sizeof(double)); for (i = 0; i < myfit->tpcount; i++) { myfit->totalq[i] = DSum(myfit->TPbank[i].Charges, myfit->TPbank[i].natom); } } /***=======================================================================***/ /*** ColumnsForAtoms: there are a certain number of topologies involved in ***/ /*** the fit, implying a number of unique atoms. This ***/ /*** routine will determine which atoms are unique, with ***/ /*** consideration to user-specified charge equalization ***/ /*** constraints, and return a matrix of integers that ***/ /*** assigns each atom to a column of the fitting matrix. ***/ /*** ***/ /*** Arguments: ***/ /*** myfit: the fitting control data, contains a bank of topologies ***/ /***=======================================================================***/ static imat ColumnsForAtoms(fset *myfit) { int i, j, k, maxatm, icol, natm, offset, maxcol; int* atmmask; int* atmresv; int* colscr; imat AC; coord crd; /*** Allocate the correspondence matrix ***/ icol = 0; maxatm = 0; for (i = 0; i < myfit->tpcount; i++) { if (myfit->TPbank[i].natom > maxatm) { maxatm = myfit->TPbank[i].natom; } icol += myfit->TPbank[i].natom; } AC = CreateImat(myfit->tpcount, maxatm); maxcol = icol; /*** Determine the charge equalization restraints, which may ***/ /*** span multiple systems, and count the number of columns ***/ atmresv = (int*)calloc(maxcol, sizeof(int)); for (i = 0; i < myfit->nqeq; i++) { natm = 0; for (j = 0; j < myfit->tpcount; j++) { crd = CreateCoord(myfit->TPbank[j].natom); atmmask = ParseAmbMask(myfit->qeq[i].maskstr, &myfit->TPbank[j], &crd); natm += ISum(atmmask, myfit->TPbank[j].natom); free(atmmask); DestroyCoord(&crd); } myfit->qeq[i].atoms = (int*)malloc(natm*sizeof(int)); myfit->qeq[i].natm = natm; natm = 0; offset = 0; for (j = 0; j < myfit->tpcount; j++) { crd = CreateCoord(myfit->TPbank[j].natom); atmmask = ParseAmbMask(myfit->qeq[i].maskstr, &myfit->TPbank[j], &crd); for (k = 0; k < myfit->TPbank[j].natom; k++) { if (atmmask[k] == 1) { myfit->qeq[i].atoms[natm] = k + offset; if (atmresv[k+offset] == 1) { printf("ColumnsForAtoms >> Error. Overlapping charge " "equalization constraints\nConlumnsForAtoms >> detected. " "Terminating on restraint %s.\n", myfit->qeq[i].maskstr); exit(1); } atmresv[k+offset] = 1; natm++; } } offset += myfit->TPbank[j].natom; free(atmmask); DestroyCoord(&crd); } /*** The total number of columns is decreased by the ***/ /*** number of atoms in this restraint minus one ***/ if (natm >= 2) { icol -= natm-1; } } free(atmresv); /*** Fill out the correspondence array ***/ natm = 0; for (i = 0; i < myfit->tpcount; i++) { for (j = 0; j < myfit->TPbank[i].natom; j++) { AC.map[i][j] = natm; natm++; } } colscr = CountUp(maxcol); for (i = 0; i < myfit->nqeq; i++) { for (j = 1; j < myfit->qeq[i].natm; j++) { colscr[myfit->qeq[i].atoms[j]] = myfit->qeq[i].atoms[0]; } } /*** Adjust the correspondence array to be sequential ***/ k = 0; atmmask = (int*)malloc(maxcol*sizeof(int)); SetIVec(atmmask, maxcol, 0); for (i = 0; i < maxcol; i++) { if (atmmask[i] == 1) { continue; } offset = colscr[i]; for (j = i; j < maxcol; j++) { if (atmmask[j] == 0 && colscr[j] == offset) { colscr[j] = k; atmmask[j] = 1; } } k++; } free(atmmask); /*** Atom correspondence back into the matrix ***/ k = 0; for (i = 0; i < myfit->tpcount; i++) { for (j = 0; j < myfit->TPbank[i].natom; j++) { AC.map[i][j] = colscr[k]; k++; } } /*** Free allocated memory ***/ free(colscr); myfit->q2fit = icol; return AC; } /***=======================================================================***/ /*** FitPlaceXpt: place extra points around a molecule read from a cubegen ***/ /*** input file. This routine places the molecule in a cell ***/ /*** as part of a 1x1x1 cell grid for the purpose of feeding ***/ /*** it into the standard extra point placement routines. ***/ /*** ***/ /*** Arguments: ***/ /*** crd: coordinates of the molecule (extra point sites are ***/ /*** included but all set to zero) ***/ /*** tp: topology decribing the molecule ***/ /***=======================================================================***/ static void FitPlaceXpt(coord *crd, prmtop *tp) { int i; double *loctmp, *aloc, *bloc, *cloc, *dloc, *eploc; expt *tmr; /*** Loop over all extra points and place them ***/ for (i = 0; i < tp->nxtrapt; i++) { tmr = &tp->xtrapts[i]; loctmp = crd->loc; aloc = &loctmp[3*tmr->fr1]; bloc = &loctmp[3*tmr->fr2]; if (tmr->frstyle > 1) { cloc = &loctmp[3*tmr->fr3]; } if (tmr->frstyle == 6) { dloc = &loctmp[3*tmr->fr4]; } eploc = &loctmp[3*tmr->atomid]; XptLocator(aloc, aloc, bloc, cloc, dloc, eploc, eploc, tmr); } } /***=======================================================================***/ /*** ReadEPotGrid: read an electrostatic potential grid as output by the ***/ /*** Gaussian cubegen utility. Only formatted cubegen ***/ /*** outputs are read. ***/ /*** ***/ /*** Arguments: ***/ /*** fname: the name of the cubegen output file ***/ /*** tp: the name of the topology used to interpret the molecular ***/ /*** coordiantes at the head of the cubegen output file ***/ /*** crd: a coord struct to hold the coordinates at the head of ***/ /*** the cubegen output file ***/ /***=======================================================================***/ fbook ReadEPotGrid(char* fname, prmtop *tp, coord *crd) { int h, i, j, k, iinit, jinit, kinit, headerfound; int slen, nnum, irexp, formgss; double rsign, rsum; double orig[3]; double* nucchg; double* eptab; float *ftmp; char line[MAXLINE]; char *ctmp; FILE *inp; dmat rL; cmat lwords; fbook Ue; /*** Lookup table for exponents with strictly formatted input ***/ eptab = (double*)malloc(199*sizeof(double)); eptab[99] = 1.0; for (i = 1; i <= 99; i++) { eptab[99+i] = 10.0*eptab[99+i-1]; eptab[99-i] = 0.1*eptab[99-i+1]; } /*** Open the cubegen file ***/ if ((inp = fopen(fname, "r")) == NULL) { printf("ReadEPotGrid >> Error. File %s not found.\n", fname); exit(1); } /*** Process the cubegen output ***/ headerfound = 1; while (headerfound == 1) { fgets(line, 128, inp); j = strlen(line); headerfound = 0; for (i = 0; i < strlen(line); i++) { if ((line[i] < '0' || line[i] > '9') && line[i] != '.' && line[i] != '-' && line[i] != ' ' && line[i] != '\n') { headerfound = 1; break; } } } sscanf(line, "%d%lf%lf%lf", &crd->natom, &orig[0], &orig[1], &orig[2]); if (crd->natom > tp->natom) { printf("ReadEPotGrid >> Error. %d atoms present in %s," "\nReadEPotGrid >> %d in the associated topology %s.\n", crd->natom, fname, tp->natom, tp->source); exit(1); } rL = CreateDmat(3, 3, 0); fgets(line, MAXLINE, inp); sscanf(line, "%d%lf%lf%lf", &i, &rL.data[0], &rL.data[1], &rL.data[2]); fgets(line, MAXLINE, inp); sscanf(line, "%d%lf%lf%lf", &j, &rL.data[3], &rL.data[4], &rL.data[5]); fgets(line, MAXLINE, inp); sscanf(line, "%d%lf%lf%lf", &k, &rL.data[6], &rL.data[7], &rL.data[8]); Ue = CreateFbook(i, j, k, &rL, orig); DestroyDmat(&rL); nucchg = (double*)malloc(crd->natom*sizeof(double)); for (i = 0; i < crd->natom; i++) { fgets(line, MAXLINE, inp); sscanf(line, "%d%lf%lf%lf%lf", &j, &nucchg[i], &crd->loc[3*i], &crd->loc[3*i+1], &crd->loc[3*i+2]); } if (tp->EPInserted == 1) { ExtendCoordinates(crd, tp); } if (crd->natom != tp->natom) { printf("ReadEPotGrid >> Error. Atom count on grid %d, %d in topology " "%s.\n", crd->natom, tp->natom, tp->source); exit(1); } /*** Scan the grid data into memory, with routines to ***/ /*** boost performance when reading a standard format ***/ i = 0; j = 0; k = 0; formgss = 1; ftmp = Ue.map[i][j]; while (formgss == 1 && fgets(line, MAXLINE, inp) != NULL && i < Ue.pag) { nnum = 0; slen = strlen(line); for (h = 0; h < slen; h++) { if (line[h] == '.') { nnum++; } } for (h = 0; h < nnum; h++) { if (line[13*h] != ' ' || line[13*h+3] != '.' || !(line[13*h+9] == 'E' || line[13*h+9] == 'e')) { formgss = 0; } } if (formgss == 0) { printf("Format broken!\n"); lwords = ParseWords(line); for (h = 0; h < lwords.row; h++) { ftmp[k] = atof(lwords.map[h]); k++; if (k == Ue.col) { k = 0; j++; if (j == Ue.row) { j = 0; i++; } if (i < Ue.pag) { ftmp = Ue.map[i][j]; } } } DestroyCmat(&lwords); continue; } /*** The format appears to be upheld; read the numbers ***/ for (h = 0; h < nnum; h++) { ctmp = &line[13*h]; rsign = (ctmp[1] == '-') ? -1.0 : 1.0; rsum = (ctmp[2] - '0') + 0.1*(ctmp[4] - '0') + 0.01*(ctmp[5] - '0') + 0.001*(ctmp[6] - '0') + 0.0001*(ctmp[7] - '0') + 0.00001*(ctmp[8] - '0'); irexp = 10*(ctmp[11] - '0') + ctmp[12] - '0'; if (ctmp[10] == '-') { irexp = -irexp; } ftmp[k] = rsign*rsum*eptab[irexp+99]; k++; if (k == Ue.col) { k = 0; j++; if (j == Ue.row) { j = 0; i++; } if (i < Ue.pag) { ftmp = Ue.map[i][j]; } } } } /*** Bail out of strict formatting and pick up where we left off ***/ if (formgss == 0) { iinit = i; jinit = j; kinit = k; for (i = iinit; i < Ue.pag; i++) { for (j = jinit; j < Ue.row; j++) { ftmp = Ue.map[i][j]; for (k = kinit; k < Ue.col; k++) { fscanf(inp, "%f", &ftmp[k]); iinit = 0; jinit = 0; kinit = 0; } } } } fclose(inp); /*** Free allocated memory ***/ free(nucchg); free(eptab); const int nelem = Ue.pag*Ue.row*Ue.col; for (j = 0; j < nelem; j++) { Ue.data[j] *= H2KCAL; } for (j = 0; j < 9; j++) { Ue.L.data[j] *= B2ANG; } for (j = 0; j < 3; j++) { Ue.orig[j] *= B2ANG; } for (j = 0; j < 3*crd->natom; j++) { crd->loc[j] *= B2ANG; } FitPlaceXpt(crd, tp); return Ue; } /***=======================================================================***/ /*** PrepUPot: prepares an electrostatic potential grid read from a file ***/ /*** for RESP fitting. Units of the grid potential, scale, and ***/ /*** molecular coordinates are fitted. ***/ /*** ***/ /*** Arguments: ***/ /*** UPot: the electrostatic potential grid ***/ /*** crd: molecular coordinates associated with UPot ***/ /*** tp: the topology ***/ /*** Uflag: grid of indices describing the accessibility and ***/ /*** availability of points in UPot ***/ /*** Uminr: grid recording the minimum range of grid points to an atom ***/ /*** myfit: RESP fitting control data ***/ /***=======================================================================***/ static void PrepUPot(fbook *UPot, coord *crd, prmtop *tp, cbook *Uflag, dbook *Uminr, fset *myfit) { int h, i, j, k, ib, jb, kb, m, ljt; int mini, minj, mink, maxi, maxj, maxk, buffi, buffj, buffk; double r2, invr2, invr4, sig, eps, Afac, Bfac, dx, dy, dz, di, dj, dk; double atmloc[3], ptloc[3], orig[3], cdepth[3]; double *dtmp, *dtm2p, *Ldata; char *ctmp; dmat *Utbl; dbook Ulj; Ulj = CreateDbook(UPot->pag, UPot->row, UPot->col, 0); /*** Compute the Lennard-Jones energy of each grid point ***/ Utbl = &tp->LJutab; Ldata = UPot->L.data; orig[0] = UPot->orig[0]; orig[1] = UPot->orig[1]; orig[2] = UPot->orig[2]; SetDVec(Uminr->data, Uminr->pag * Uminr->row * Uminr->col, 1.0e12); for (h = 0; h < tp->natom; h++) { for (m = 0; m < 3; m++) { atmloc[m] = crd->loc[3*h+m]; } ljt = tp->LJIdx[h]; if (ljt < 0) { sig = 0.0; eps = 0.0; } else { sig = pow(-1.0*Utbl->map[ljt][2*ljt]/Utbl->map[ljt][2*ljt+1], 1.0/6.0); eps = -0.25 * Utbl->map[ljt][2*ljt+1] / pow(sig, 6.0); sig = 0.5*(sig + myfit->psig); eps = sqrt(eps*myfit->peps); Afac = 4.0*eps*pow(sig, 12.0); Bfac = 4.0*eps*pow(sig, 6.0); } for (i = 0; i < Ulj.pag; i++) { di = i; for (j = 0; j < Ulj.row; j++) { dtmp = Ulj.map[i][j]; dtm2p = Uminr->map[i][j]; dj = j; ptloc[0] = Ldata[0]*di + Ldata[1]*dj + orig[0] - atmloc[0]; ptloc[1] = Ldata[3]*di + Ldata[4]*dj + orig[1] - atmloc[1]; ptloc[2] = Ldata[6]*di + Ldata[7]*dj + orig[2] - atmloc[2]; for (k = 0; k < Ulj.col; k++) { r2 = ptloc[0]*ptloc[0] + ptloc[1]*ptloc[1] + ptloc[2]*ptloc[2]; ptloc[0] += Ldata[2]; ptloc[1] += Ldata[5]; ptloc[2] += Ldata[8]; invr2 = 1.0/(r2); invr4 = invr2*invr2; dtmp[k] += Afac*invr4*invr4*invr4 - Bfac*invr4*invr2; if (r2 < dtm2p[k]) { dtm2p[k] = r2; } } } } } /*** Compute the grid buffer region for accessibility ***/ HessianNorms(&UPot->L, cdepth); buffi = myfit->prbarm/cdepth[0] + 1; buffj = myfit->prbarm/cdepth[1] + 1; buffk = myfit->prbarm/cdepth[2] + 1; for (i = 0; i < Ulj.pag; i++) { for (j = 0; j < Ulj.row; j++) { dtmp = Ulj.map[i][j]; ctmp = Uflag->map[i][j]; for (k = 0; k < Ulj.col; k++) { /*** If this point is accessible to the probe, ***/ /*** declare it so and mark all surrounding ***/ /*** points accessible if they are within the ***/ /*** probe arm's reach. ***/ if (dtmp[k] < myfit->stericlim) { ctmp[k] = 1; continue; } /*** If we're still here, this point is not ***/ /*** accessible to probe but may nonetheless ***/ /*** be accessible to the probe arm. ***/ mini = MAX(i-buffi, 0); minj = MAX(j-buffj, 0); mink = MAX(k-buffk, 0); maxi = MIN(i+buffi, Ulj.pag-1); maxj = MIN(j+buffj, Ulj.row-1); maxk = MIN(k+buffk, Ulj.col-1); for (ib = mini; ib <= maxi; ib++) { di = ib - i; for (jb = minj; jb <= maxj; jb++) { dj = jb - j; dtm2p = Ulj.map[ib][jb]; dk = mink-k-1; ptloc[0] = di*Ldata[0] + dj*Ldata[1] + dk*Ldata[2]; ptloc[1] = di*Ldata[3] + dj*Ldata[4] + dk*Ldata[5]; ptloc[2] = di*Ldata[6] + dj*Ldata[7] + dk*Ldata[8]; for (kb = mink; kb <= maxk; kb++) { ptloc[0] += Ldata[2]; ptloc[1] += Ldata[5]; ptloc[2] += Ldata[8]; /*** In order for this point to be worth investigating, ***/ /*** it has to be possible for a probe to be situated ***/ /*** on this point with an acceptable steric energy and ***/ /*** then reach its arm out to the point that we've ***/ /*** thus far declared inaccessible. ***/ if (dtm2p[kb] > myfit->stericlim) { continue; } if (ptloc[0]*ptloc[0] + ptloc[1]*ptloc[1] + ptloc[2]*ptloc[2] < myfit->prbarm) { /*** Mark the point accessible and ***/ /*** bail out of these nested loops ***/ ctmp[k] = 1; ib = maxi+1; jb = maxj+1; kb = maxk+1; } } } } } } } /*** Free allocated memory ***/ DestroyDbook(&Ulj); } /***=======================================================================***/ /*** MaskAllAtoms: restraints may span multiple systems. This function ***/ /*** examines the restraint mask and applies it to each ***/ /*** system individually to determine how many unique atoms ***/ /*** it involves. ***/ /*** ***/ /*** Arguments: ***/ /*** myfit: the fitting control data ***/ /*** qmask: the mask of all charges (length the number of columns ***/ /*** in the fitting matrix) ***/ /*** maskstr: the mask string (ambmask format) ***/ /*** Atm2Col: matrix describing the mapping of atoms in each system ***/ /*** to columns of the fitting constraint matrix ***/ /*** stack: flag to record counts of how many atoms share each ***/ /*** unique fitted charge state; set to zero for boolean ***/ /*** indications as to whether each fitted charge is ***/ /*** present in the mask, 1 to record counts ***/ /*** spec: do the mask only for a specific topology; set to -1 to ***/ /*** make the mask for all topologies ***/ /***=======================================================================***/ static int MaskAllAtoms(fset *myfit, int* qmask, char* maskstr, imat *Atm2Col, int stack, int spec) { int i, j, natm; int *itmp; int* atmmask; prmtop *tp; coord crd; SetIVec(qmask, myfit->q2fit, 0); for (i = 0; i < myfit->tpcount; i++) { if (spec >= 0 && i != spec) { continue; } tp = &myfit->TPbank[i]; crd = CreateCoord(tp->natom); atmmask = ParseAmbMask(maskstr, tp, &crd); itmp = Atm2Col->map[i]; for (j = 0; j < tp->natom; j++) { if (atmmask[j] == 1) { qmask[itmp[j]] = (stack == 1) ? qmask[itmp[j]] + 1 : 1; } } free(atmmask); DestroyCoord(&crd); } natm = ISum(qmask, myfit->q2fit); return natm; } /***=======================================================================***/ /*** MakeCnstMatrix: make a constraint matrix for charge fitting based on ***/ /*** a series of ambmask strings and other data specified ***/ /*** by the user. This constraint matrix has n+1 columns ***/ /*** for fitting n charges, the final column being the ***/ /*** corresponding targets on the right hand side of the ***/ /*** linear least squares problem (the "b" vector in ***/ /*** Ax=b). ***/ /*** ***/ /*** Arguments: ***/ /*** myfit: the fitting control data ***/ /*** Atm2Col: matrix describing the mapping of atoms in each system to ***/ /*** columns of the fitting constraint matrix ***/ /*** fitmat: the augmented fitting matrix [ A b ] ***/ /***=======================================================================***/ static dmat MakeCnstMatrix(fset *myfit, imat *Atm2Col, dmat *fitmat) { int i, j, k, nrst, rstcon, natm, tpkey, maxatom; int* allqmask; int* tmpqmask; int* nsamp; double rstfac, totq, ninst; double *dtmp; dmat cnstmat; /*** The obligatory total charge restraints, one per system ***/ cnstmat = CreateDmat(myfit->tpcount, myfit->q2fit+2, 0); maxatom = 0; for (i = 0; i < myfit->tpcount; i++) { maxatom += myfit->TPbank[i].natom; for (j = 0; j < myfit->TPbank[i].natom; j++) { cnstmat.map[i][Atm2Col->map[i][j]] += 1.0e7; } cnstmat.map[i][myfit->q2fit] = (1.0e7)*myfit->totalq[i]; } nrst = myfit->tpcount; rstcon = myfit->tpcount; /*** Count the number of times each atom is sampled in the ***/ /*** fitting matrix, the number of equations involved, in ***/ /*** order to properly scale the restraint weights. ***/ nsamp = (int*)calloc(myfit->q2fit, sizeof(int)); for (i = 0; i < fitmat->row; i++) { dtmp = fitmat->map[i]; for (j = 0; j < myfit->q2fit; j++) { if (fabs(dtmp[j]) > 1.0e-8) { nsamp[j] += 1; } } } /*** Store the sampling for later analysis (and perhaps use) ***/ myfit->nsamp = nsamp; /*** Charge minimization restraints ***/ allqmask = (int*)malloc(maxatom*sizeof(int)); for (i = 0; i < myfit->nqmin; i++) { /*** This restraint may span multiple systems. We must ***/ /*** determine how many unique atoms are being restrained. ***/ natm = MaskAllAtoms(myfit, allqmask, myfit->qmin[i].maskstr, Atm2Col, 0, -1); if (natm == 0) { continue; } /*** Add new restraints for each specified charge ***/ nrst += natm; cnstmat = ReallocDmat(&cnstmat, nrst, myfit->q2fit+2); for (j = 0; j < myfit->q2fit; j++) { if (allqmask[j] == 1) { dtmp = cnstmat.map[rstcon]; dtmp[j] = myfit->qminwt*nsamp[j]; dtmp[myfit->q2fit+1] = 1.01; rstcon++; } } } /*** Charge group sum restraints ***/ tmpqmask = (int*)malloc(maxatom*sizeof(int)); for (i = 0; i < myfit->nqsum; i++) { /*** Check to see that this charge group ***/ /*** does exist in some system. ***/ natm = 0; for (j = 0; j < myfit->tpcount; j++) { natm = MaskAllAtoms(myfit, allqmask, myfit->qsum[i].maskstr, Atm2Col, 1, j); if (natm > 0) { tpkey = j; break; } } if (natm == 0) { continue; } /*** It is now known that this charge group exists in ***/ /*** one or more of the systems, in whole or in part. ***/ /*** Now we must determine whether the charge group ***/ /*** exists entirely or not at all in every system. ***/ /*** If this condition is not met, it would result in ***/ /*** very strange and contradictory restraints, so ***/ /*** stop the program if a bad case is detected. ***/ for (j = 0; j < myfit->tpcount; j++) { natm = MaskAllAtoms(myfit, tmpqmask, myfit->qsum[i].maskstr, Atm2Col, 1, j); if (ISum(tmpqmask, natm) == 0) { continue; } for (k = 0; k < myfit->q2fit; k++) { if (tmpqmask[k] != allqmask[k]) { printf("MakeCnstMatrix >> Error. Mismatch in masks generated for " "topologies\nMakeCnstMatrix >> %s and %s.\n", myfit->TPbank[tpkey].source, myfit->TPbank[j].source); exit(1); } } } /*** Add the new restraint for this group ***/ nrst += 1; cnstmat = ReallocDmat(&cnstmat, nrst, myfit->q2fit+2); dtmp = cnstmat.map[rstcon]; for (j = 0; j < myfit->q2fit; j++) { if (allqmask[j] > 0) { dtmp[j] = allqmask[j] * 1.0e7; } } dtmp[myfit->q2fit] = 1.0e7 * myfit->qsum[i].target; dtmp[myfit->q2fit+1] = 2.01; rstcon++; } /*** Charge tethering ***/ if (myfit->model == 0 && myfit->tether == 1) { cnstmat = ReallocDmat(&cnstmat, nrst+myfit->q2fit, myfit->q2fit+2); for (i = 0; i < myfit->q2fit; i++) { rstfac = myfit->qtthwt*nsamp[i]; cnstmat.map[rstcon][i] = rstfac; ninst = 0.0; totq = 0.0; for (j = 0; j < myfit->tpcount; j++) { for (k = 0; k < myfit->TPbank[j].natom; k++) { if (Atm2Col->map[j][k] == i) { totq += myfit->TPbank[j].Charges[k]; ninst += 1.0; } } } cnstmat.map[rstcon][myfit->q2fit] = rstfac * totq / ninst; cnstmat.map[rstcon][myfit->q2fit+1] = 3.01; rstcon++; } nrst += myfit->q2fit; } /*** Free allocated memory ***/ free(allqmask); free(tmpqmask); return cnstmat; } /***=======================================================================***/ /*** SortPtDistance: function called by quicksort for comparing minimum ***/ /*** distances between a grid point and the molecule. ***/ /*** ***/ /*** Arguments: ***/ /*** pt[A,B]: the atomc structs ***/ /***=======================================================================***/ static int SortPtDistance(const void *ptA, const void *ptB) { double disA = ((fitpt*)ptA)[0].minr; double disB = ((fitpt*)ptB)[0].minr; if (disA < disB) { return -1; } else if (disA > disB) { return 1; } else { return 0; } } /***=======================================================================***/ /*** ContributeFitPt: contribute this fitting point to the matrix of ***/ /*** candidates. ***/ /*** ***/ /*** Arguments: ***/ /*** myfit: the fitting set ***/ /*** nsys: the system number ***/ /*** A: the fitting matrix ***/ /*** nln: the line number ***/ /*** grdpt: pointer to grid point catalog ***/ /*** crd: coordinates ***/ /*** tp: topology ***/ /*** fitline: the next line of the growing fitting matrix to be written ***/ /***=======================================================================***/ static void ContributeFitPt(fset *myfit, int nsys, dmat *A, int nln, fitpt *grdpt, coord *crd, fbook *UPotA, fbook *UPotB, imat *Atm2Col) { int i, ncol; int *colmap; double dx, dy, dz, r, wt; double dijk[3]; double *fitline; /*** Unpack structures and perform simple catalogging ***/ wt = myfit->wt[nsys]; colmap = Atm2Col->map[myfit->tpidx[nsys]]; ncol = myfit->q2fit; fitline = A->map[nln]; myfit->FPtOrigins[nln] = myfit->tpidx[nsys]; /*** Compute the sum of kq/r interactions ***/ dijk[0] = grdpt->ix; dijk[1] = grdpt->iy; dijk[2] = grdpt->iz; RotateCrd(dijk, 1, UPotA->L); dijk[0] += UPotA->orig[0]; dijk[1] += UPotA->orig[1]; dijk[2] += UPotA->orig[2]; for (i = 0; i < crd->natom; i++) { dx = crd->loc[3*i] - dijk[0]; dy = crd->loc[3*i+1] - dijk[1]; dz = crd->loc[3*i+2] - dijk[2]; r = sqrt(dx*dx + dy*dy + dz*dz); fitline[colmap[i]] += wt*BIOQ/r; } fitline[ncol] = wt*0.5*(UPotA->map[grdpt->ix][grdpt->iy][grdpt->iz] + UPotB->map[grdpt->ix][grdpt->iy][grdpt->iz]); fitline[ncol+1] = wt*UPotA->map[grdpt->ix][grdpt->iy][grdpt->iz]; } /***=======================================================================***/ /*** FlagFitPt: flag all points within a short cutoff around a fitting ***/ /*** point that was just committed to the matrix of candidate ***/ /*** fitting points. These flags, which prevent points from ***/ /*** being used in the fit, make it so that extremely closely ***/ /*** positioned points do not both contribute to the fitting ***/ /*** matrix. ***/ /*** ***/ /*** Arguments: ***/ /*** grdpts: array of candidate fitting points ***/ /*** ptid: ID number of the fitting point within the grdpts catalog ***/ /*** npt: the total number of points catalogged in grdpts ***/ /*** UPot: the electrostatic potential grid ***/ /*** UIdx: indexes for whether the grid points are flagged or not ***/ /*** catIdx: maps the points in UPot/UIdx to their entries in grdpts ***/ /***=======================================================================***/ static void FlagFitPt(fset *myfit, fitpt* grdpts, int ptid, fbook *UPot, cbook *Uflag, ibook *UIdx, int buffi, int buffj, int buffk) { int i, j, k, imin, imax, jmin, jmax, kmin, kmax, homei, homej, homek; double r2, dx, dy, dz; double dijk[3]; double *Ldata; int *itmp; char *ctmp; homei = grdpts[ptid].ix; homej = grdpts[ptid].iy; homek = grdpts[ptid].iz; imin = MAX(0, homei - buffi); imax = MIN(UPot->pag, homei + buffi); jmin = MAX(0, homej - buffj); jmax = MIN(UPot->pag, homej + buffj); kmin = MAX(0, homek - buffk); kmax = MIN(UPot->pag, homek + buffk); Ldata = UPot->L.data; for (i = imin; i < imax; i++) { for (j = jmin; j < jmax; j++) { ctmp = Uflag->map[i][j]; itmp = UIdx->map[i][j]; for (k = kmin; k < kmax; k++) { if (ctmp[k] == 0) { continue; } dx = i - homei; dy = j - homej; dz = k - homek; dijk[0] = Ldata[0]*dx + Ldata[1]*dy + Ldata[2]*dz; dijk[1] = Ldata[3]*dx + Ldata[4]*dy + Ldata[5]*dz; dijk[2] = Ldata[6]*dx + Ldata[7]*dy + Ldata[8]*dz; r2 = dijk[0]*dijk[0] + dijk[1]*dijk[1] + dijk[2]*dijk[2]; if (r2 < myfit->flimit) { ctmp[k] = 0; grdpts[itmp[k]].flagged = 1; } } } } } /***=======================================================================***/ /*** HistogramFitPt: place a fitting point in a histogram based on its ***/ /*** distance from the molecule. ***/ /*** ***/ /*** Arguments: ***/ /*** myfit: the fitting control structure (contains the pre-allocated ***/ /*** histogram) ***/ /*** gpt: the fitting point ***/ /***=======================================================================***/ static void HistogramFitPt(fset *myfit, fitpt *gpt) { int ir; ir = gpt->minr/myfit->fhistbin; myfit->fitpthist[ir] += 1; } /***=======================================================================***/ /*** WriteConformation: write a conformation of the molecule, for purposes ***/ /*** of visualizing the charge distribution, in PDB ***/ /*** format. ***/ /*** ***/ /*** Arguments: ***/ /*** h: the number of the conformation ***/ /*** crd: the coordinates of the conformation ***/ /*** tp: the topology ***/ /*** mfit: the fitting set ***/ /*** tj: trajectory control data (directive to overwrite outputs) ***/ /***=======================================================================***/ static void WriteConformation(int h, double* crd, prmtop *tp, fset *myfit, trajcon *tj) { int i, ires; char outname[MAXNAME]; FILE *outp; /*** Only do this for the first instance of each system ***/ for (i = 0; i < h; i++) { if (myfit->tpidx[i] == myfit->tpidx[h]) { return; } } /*** Print the conformation iin PDB format ***/ sprintf(outname, "%s.%s", tp->source, myfit->confext); outp = FOpenSafe(outname, tj->OverwriteOutput); for (i = 0; i < tp->natom; i++) { ires = LocateResID(tp, i, 0, tp->nres); fprintf(outp, "ATOM %6d %.4s %.4s%c%4d %8.3lf%8.3lf%8.3lf\n", i, &tp->AtomNames[4*i], &tp->ResNames[4*ires], 'A', 1, crd[3*i], crd[3*i+1], crd[3*i+2]); } fprintf(outp, "END\n"); fclose(outp); } /***=======================================================================***/ /*** SelectFitPoint: introduce a criterion to limit the number of points ***/ /*** far from the molecular surface which enter the fit. ***/ /*** The criterion is that, after a certain cutoff Rc, the ***/ /*** probability of accepting a point at a distance r from ***/ /*** the molecular surface drops off such that the number ***/ /*** of points accepted at r would be equal to the number ***/ /*** at Rc if the molecule were perfectly spherical. ***/ /*** ***/ /*** Arguments: ***/ /*** gpt: the grid point ***/ /*** myfit: the fitting control structure ***/ /*** tj: trajectory control data (contains random number counter) ***/ /***=======================================================================***/ static int SelectFitPoint(fitpt *gpt, fset *myfit, trajcon *tj) { double r; /*** Accept if the point is within Rc of the molecule ***/ if (gpt->minr < myfit->Rc) { return 1; } /*** Accept a roughly constant number of points for ***/ /*** any given distance from the molecule beyond Rc ***/ r = myfit->Rc / gpt->minr; if (gpt->minr < myfit->Rmax && ran2(&tj->rndcon) < r*r) { return 1; } /*** Reject the point ***/ return 0; } /***=======================================================================***/ /*** MakeFittingMatrix: compute the fitting matrix based on points taken ***/ /*** from all grids, seived by various user-specified ***/ /*** cutoffs. ***/ /*** ***/ /*** Arguments: ***/ /*** myfit: the fitting control structure ***/ /*** Atm2Col: correspondence between atoms and matrix columns ***/ /*** allcrd: matrix to accumulate coordinates of all molecular ***/ /*** conformations ***/ /*** tj: trajectory control information (contains random number ***/ /*** generator counter) ***/ /*** etimers: the execution timings data ***/ /***=======================================================================***/ static dmat MakeFittingMatrix(fset *myfit, imat *Atm2Col, dmat *allcrd, trajcon *tj, execon *etimers) { int h, i, j, k, m, buffi, buffj, buffk, gcon, ngpt, namelen; int congruent, setfitpt, totalfitpt; long long int totalmem; double dx, dy, dz, r, minr; double cdepth[3], dijk[3]; double *dtmp; char *ctmp; dmat fitmat; fitpt* grdpts; ibook UIdx; dbook Uminr; fbook UPot, auxUPot; cbook Uflag; coord crd, auxcrd; prmtop *tp; /*** Check to ensure that there is sufficient ***/ /*** room in memory for the fitting matrix ***/ totalmem = myfit->ngrd; totalmem *= myfit->nfitpt; totalmem *= (myfit->q2fit+1) * 8; if (myfit->model == 0) { totalmem *= 2; } else { totalmem *= 5; } if (totalmem > myfit->MaxMem) { printf("MakeFittingMatrix >> Fitting matrix would require %lld bytes of " "memory.\nMakeFittingMatrix >> This exceeds the allowed %lld byte " "limit. Increase\nMakeFittingMatrix >> the maximum allowed memory " "with the maxmem keyword in the\nMakeFittingMatrix >> &fit " "namelist or reduce the number of fitting points.\n", totalmem, myfit->MaxMem); exit(1); } if (myfit->verbose == 1) { printf("mdgx >> Fitting matrices will occupy %lld bytes of memory.\n", totalmem); namelen = 0; for (i = 0; i < myfit->ngrd; i++) { namelen = MAX(namelen, strlen(myfit->gname.map[i])); } } /*** Allocate space for the fitting matrix ***/ totalfitpt = 0; fitmat = CreateDmat(myfit->ngrd*myfit->nfitpt, myfit->q2fit+2, 0); myfit->FPtOrigins = (int*)malloc(myfit->ngrd*myfit->nfitpt*sizeof(int)); /*** Allocate memory for the fitting histogram ***/ myfit->fitpthist = (int*)calloc(10.0/myfit->fhistbin+1, sizeof(int)); etimers->Setup += mdgxStopTimer(etimers); /*** Loop over all grids ***/ for (h = 0; h < myfit->ngrd; h++) { /*** Titillate the user ***/ if (myfit->verbose == 1) { fprintf(stderr, "\rmdgx >> Composing fit for %s", myfit->gname.map[h]); for (i = 0; i < namelen - strlen(myfit->gname.map[h]); i++) { fprintf(stderr, " "); } fflush(stderr); } /*** Allocate coordinates for this grid's system ***/ tp = &myfit->TPbank[myfit->tpidx[h]]; crd = CreateCoord(tp->natom); auxcrd = CreateCoord(tp->natom); etimers->Setup += mdgxStopTimer(etimers); /*** Order the list of points as a function of distance from ***/ /*** the solute. The list will then be searched in increasing ***/ /*** order of distance for candidate fitting points. ***/ UPot = ReadEPotGrid(myfit->gname.map[h], tp, &crd); etimers->Integ += mdgxStopTimer(etimers); Uflag = CreateCbook(UPot.pag, UPot.row, UPot.col); UIdx = CreateIbook(UPot.pag, UPot.row, UPot.col); Uminr = CreateDbook(UPot.pag, UPot.row, UPot.col, 0); etimers->Setup += mdgxStopTimer(etimers); PrepUPot(&UPot, &crd, tp, &Uflag, &Uminr, myfit); etimers->cellcomm += mdgxStopTimer(etimers); if (myfit->model == 1) { auxUPot = ReadEPotGrid(myfit->auxgname.map[h], tp, &auxcrd); etimers->Integ += mdgxStopTimer(etimers); congruent = 1; for (i = 0; i < 9; i++) { if (fabs(auxUPot.L.data[i] - UPot.L.data[i]) > 1.0e-8) { congruent = 0; } } for (i = 0; i < 3; i++) { if (fabs(auxUPot.orig[i] - UPot.orig[i]) > 1.0e-8) { congruent = 0; } } if (congruent == 0) { printf("MakeFittingMatrix >> Error. Non-correspondence in grid " "dimensions for\nMakeFittingMatrix >> %s and %s.\n", myfit->auxgname.map[h], myfit->gname.map[h]); exit(1); } for (i = 0; i < 3*crd.natom; i++) { if (fabs(auxcrd.loc[i] - crd.loc[i]) > 1.0e-5) { congruent = 0; } } if (congruent == 0) { printf("MakeFittingMatrix >> Error. Non-correspondence in atom " "positions for\nMakeFittingMatrix >> %s and %s.\n", myfit->auxgname.map[h], myfit->gname.map[h]); exit(1); } /*** If we're still here, this auxiliary grid is ***/ /*** clean and compatible with the original grid ***/ } etimers->Setup += mdgxStopTimer(etimers); /*** Order the fitting data ***/ grdpts = (fitpt*)malloc(UPot.pag*UPot.row*UPot.col*sizeof(fitpt)); gcon = 0; for (i = 0; i < UPot.pag; i++) { for (j = 0; j < UPot.row; j++) { ctmp = Uflag.map[i][j]; dtmp = Uminr.map[i][j]; for (k = 0; k < UPot.col; k++) { /*** Skip this point if it is inaccessible ***/ if (ctmp[k] == 0) { continue; } /*** Catalog this grid point ***/ grdpts[gcon].ix = i; grdpts[gcon].iy = j; grdpts[gcon].iz = k; grdpts[gcon].flagged = 0; grdpts[gcon].minr = sqrt(dtmp[k]); gcon++; } } } ngpt = gcon; /*** Sort fitting points based on distance to solute ***/ qsort(grdpts, ngpt, sizeof(fitpt), SortPtDistance); etimers->nbFFT += mdgxStopTimer(etimers); /*** Label points in the index map based on the new catalog order ***/ for (i = 0; i < ngpt; i++) { UIdx.map[grdpts[i].ix][grdpts[i].iy][grdpts[i].iz] = i; } /*** Determine the buffer region for testing exclusions ***/ HessianNorms(&UPot.L, cdepth); buffi = myfit->prbarm/cdepth[0] + 1; buffj = myfit->prbarm/cdepth[1] + 1; buffk = myfit->prbarm/cdepth[2] + 1; /*** Loop over each grid point, selecting points for fitting ***/ /*** when they are accessible. Selected points will mask out ***/ /*** others nearby acccording to a minimum distance (flimit) ***/ /*** specified in the fitting struct. ***/ setfitpt = 0; for (i = 0; i < ngpt; i++) { /*** Bail out if the quota for this set is reached ***/ if (setfitpt == myfit->nfitpt) { break; } /*** Continue if this point is already flagged ***/ if (grdpts[i].flagged == 1) { continue; } /*** Accept the point with some probability based ***/ /*** on its distance from the molecular surface ***/ if (SelectFitPoint(&grdpts[i], myfit, tj) == 1) { if (myfit->model == 0) { ContributeFitPt(myfit, h, &fitmat, totalfitpt, &grdpts[i], &crd, &UPot, &UPot, Atm2Col); } else { ContributeFitPt(myfit, h, &fitmat, totalfitpt, &grdpts[i], &crd, &UPot, &auxUPot, Atm2Col); } FlagFitPt(myfit, grdpts, i, &UPot, &Uflag, &UIdx, buffi, buffj, buffk); HistogramFitPt(myfit, &grdpts[i]); totalfitpt++; setfitpt++; } else { /*** Even if the point is rejected, the region around ***/ /*** it must be flagged in order to achieve the point ***/ /*** spread that is desired. ***/ FlagFitPt(myfit, grdpts, i, &UPot, &Uflag, &UIdx, buffi, buffj, buffk); } } etimers->nbBsp += mdgxStopTimer(etimers); /*** Commit coordinates to buffer for printing if necessary ***/ for (i = 0; i < 3*tp->natom; i++) { allcrd->map[h][i] = crd.loc[i]; } /*** Print coordinates to conformation file ***/ WriteConformation(h, crd.loc, tp, myfit, tj); /*** Free allocated memory ***/ DestroyFbook(&UPot); if (myfit->model == 1) { DestroyFbook(&auxUPot); } DestroyIbook(&UIdx); DestroyDbook(&Uminr); DestroyCbook(&Uflag); free(grdpts); DestroyCoord(&crd); DestroyCoord(&auxcrd); } /*** Titillate the user ***/ if (myfit->verbose == 1) { printf("\nmdgx >> Fitting matrix composed.\n"); } return fitmat; } /***=======================================================================***/ /*** CalcGroupQ: calculate the total charge of a group of atoms in a ***/ /*** system; it could be the entire system. ***/ /*** ***/ /*** Arguments: ***/ /*** q: the array of charge variables ***/ /*** mask: the mask of atoms defining this group ***/ /*** Atm2Col: the key by which atoms map to the charges in q ***/ /*** natom: the number of atoms in the system (length of mask) ***/ /***=======================================================================***/ static double CalcGroupQ(double* q, int* mask, int* Atm2Col, int natom) { int i; double totq; totq = 0.0; for (i = 0; i < natom; i++) { totq += q[Atm2Col[i]] * mask[i]; } return totq; } /***=======================================================================***/ /*** EvalGroupQSums: evaluate all charge group sums. ***/ /*** ***/ /*** Arguments: ***/ /*** q: the array of charge variables ***/ /*** myfit: fitting control data ***/ /*** Atm2Col: matrix of atom indices into the fitting matrix columns ***/ /***=======================================================================***/ static double EvalGroupQSums(double* q, fset *myfit, imat *Atm2Col) { int i, j, ngrp; int* atmmask; double dq, rmsdq; prmtop *tp; coord crd; ngrp = 0; rmsdq = 0.0; for (i = 0; i < myfit->tpcount; i++) { tp = &myfit->TPbank[i]; /*** Evaluate dq for the system ***/ crd = CreateCoord(tp->natom); atmmask = (int*)malloc(tp->natom*sizeof(int)); SetIVec(atmmask, tp->natom, 1); dq = CalcGroupQ(q, atmmask, Atm2Col->map[i], tp->natom) - myfit->totalq[i]; rmsdq += dq*dq; free(atmmask); ngrp++; /*** Evaluate dq for any relevant charge group sum restraints ***/ for (j = 0; j < myfit->nqsum; j++) { atmmask = ParseAmbMask(myfit->qsum[j].maskstr, tp, &crd); if (ISum(atmmask, tp->natom) > 0) { dq = CalcGroupQ(q, atmmask, Atm2Col->map[i], tp->natom) - myfit->qsum[j].target; rmsdq += dq*dq; ngrp++; } free(atmmask); } DestroyCoord(&crd); } return sqrt(rmsdq / ngrp); } /***=======================================================================***/ /*** SnapFittedQ: snap the fitted set of charges to the nearest 1.0e-5 ***/ /*** proton units, ensure that all charges that were intended ***/ /*** to be equalized are perfectly equalized, and ensure that ***/ /*** the sum of all charges adds to the requested value. ***/ /*** ***/ /*** Arguments: ***/ /*** q: the fitted charges ***/ /*** myfit: fitting control data ***/ /*** Atm2Col: matrix of atom indices into the fitting matrix columns ***/ /***=======================================================================***/ static int SnapFittedQ(double* q, fset *myfit, imat *Atm2Col) { int i, j, niter; double chksum, Sbest, Scurr, qorig; double* qbest; /*** Round all charges ***/ for (i = 0; i < myfit->q2fit; i++) { if (q[i] >= 0.0) { j = q[i]*1.0e5; q[i] = j*1.0e-5; } else { j = -q[i]*1.0e5; q[i] = -j*1.0e-5; } } /*** Work on the charge variables, one at a time. Use steepest ***/ /*** descent optimization in set increments of 1.0e-5 proton ***/ /*** charges to minimize (hopefully eliminate) any deviations ***/ /*** from the ideal charge sum values. ***/ qbest = CpyDVec(q, myfit->q2fit); Sbest = EvalGroupQSums(q, myfit, Atm2Col); niter = 0; while (niter < myfit->maxsnap && Sbest > 1.0e-10) { for (i = 0; i < myfit->q2fit; i++) { qorig = q[i]; q[i] = qorig - 1.0e-5; Scurr = EvalGroupQSums(q, myfit, Atm2Col); if (Scurr < Sbest) { Sbest = Scurr; qbest[i] = q[i]; continue; } q[i] = qorig + 1.0e-5; Scurr = EvalGroupQSums(q, myfit, Atm2Col); if (Scurr < Sbest) { Sbest = Scurr; qbest[i] = q[i]; continue; } q[i] = qorig; } niter++; } /*** Store the best charge set ***/ ReflectDVec(q, qbest, myfit->q2fit); /*** Free allocated memory ***/ free(qbest); if (niter == myfit->maxsnap && Sbest > 1.0e-10) { return -1; } else { return niter; } } /***=======================================================================***/ /*** SystemQTest: test the accuracy of the electrostatics for each system. ***/ /*** ***/ /*** Arguments: ***/ /*** btar: the vector of target results ***/ /*** bpred: predicted electrostatic potential values ***/ /*** npt: the number of elements in btar, bpred ***/ /*** myfit: the fitting command data ***/ /*** row: row of the results matrices to write ***/ /***=======================================================================***/ static void SystemQTest(double* btar, double* bpred, int npt, fset *myfit, int row) { int i, j, nspt; double* ttar; double* tpred; /*** Allocate scratch arrays ***/ ttar = (double*)malloc(npt*sizeof(double)); tpred = (double*)malloc(npt*sizeof(double)); /*** Loop over all systems ***/ for (i = 0; i < myfit->tpcount; i++) { nspt = 0; for (j = 0; j < npt; j++) { if (myfit->FPtOrigins[j] == i) { ttar[nspt] = btar[j]; tpred[nspt] = bpred[j]; nspt++; } } myfit->QScorr.map[row][i] = Pearson(ttar, tpred, nspt); myfit->QSrmsd.map[row][i] = VecRMSD(ttar, tpred, nspt); } /*** Free allocated memory ***/ free(ttar); free(tpred); } /***=======================================================================***/ /*** SolvRESP: solve a linear least squares problem and report statistics ***/ /*** on the results. The fitting matrix is stored in the first ***/ /*** n-1 columns of the fitting and constraint matrices; the ***/ /*** target vector is stored in the final column, for simplified ***/ /*** data passing. ***/ /*** ***/ /*** Arguments: ***/ /*** fitmat: the fitting matrix and target vector derived from ***/ /*** electrostatic potential data ***/ /*** cnstmat: the constraints matrix and target vector ***/ /*** myfit: the fitting command data ***/ /*** etimers: the execution timings data ***/ /***=======================================================================***/ static dmat SolveRESP(dmat *fitmat, dmat *cnstmat, fset *myfit, imat *Atm2Col, execon *etimers) { int i, j, ip, tval, nqrest; double* bfit; double* btst; double* bpred; dmat Afit, Atst, qres; /*** Titillate the user ***/ if (myfit->verbose == 1) { printf("mdgx >> Solving linear least-squares problem for %d independent " "charges.\n", myfit->q2fit); } /*** Fit new charges ***/ if (myfit->model == 0) { i = 1; j = 1; } else { i = 4; j = 3; } qres = CreateDmat(i, myfit->q2fit, 0); myfit->QScorr = CreateDmat(j, myfit->tpcount, 0); myfit->QSrmsd = CreateDmat(j, myfit->tpcount, 0); Afit = CreateDmat(fitmat->row+cnstmat->row, myfit->q2fit, 0); bfit = (double*)malloc((fitmat->row+cnstmat->row)*sizeof(double)); for (i = 0; i < fitmat->row; i++) { for (j = 0; j < myfit->q2fit; j++) { Afit.map[i][j] = fitmat->map[i][j]; } bfit[i] = fitmat->map[i][myfit->q2fit]; } ip = fitmat->row; for (i = 0; i < cnstmat->row; i++) { for (j = 0; j < myfit->q2fit; j++) { Afit.map[ip][j] = cnstmat->map[i][j]; } bfit[ip] = cnstmat->map[i][myfit->q2fit]; ip++; } AxbQRRxc(Afit, bfit, myfit->verbose); BackSub(Afit, bfit); DestroyDmat(&Afit); etimers->nbPtM += mdgxStopTimer(etimers); /*** Test the result ***/ Atst = CreateDmat(fitmat->row, myfit->q2fit, 0); btst = (double*)malloc(fitmat->row*sizeof(double)); bpred = (double*)malloc(fitmat->row*sizeof(double)); for (i = 0; i < fitmat->row; i++) { for (j = 0; j < myfit->q2fit; j++) { Atst.map[i][j] = fitmat->map[i][j]; } btst[i] = fitmat->map[i][myfit->q2fit]; } DMatVecMult(&Atst, bfit, bpred); SystemQTest(btst, bpred, fitmat->row, myfit, 0); /*** Titillate the user ***/ if (myfit->verbose == 1) { printf("mdgx >> Fit complete.\n"); } /*** Store this set of charges along with its statistics ***/ ReflectDVec(qres.map[0], bfit, myfit->q2fit); myfit->SnapCount[0] = SnapFittedQ(qres.map[0], myfit, Atm2Col); myfit->Qcorr[0] = Pearson(btst, bpred, Atst.row); myfit->Qrmsd[0] = VecRMSD(btst, bpred, Atst.row); /*** Free allocated memory ***/ DestroyDmat(&Atst); free(bfit); free(btst); free(bpred); etimers->nbCnv += mdgxStopTimer(etimers); /*** If this is IPolQ, try a different approach ***/ if (myfit->model == 1) { /*** Count the number of total charge restraints ***/ nqrest = 0; for (i = 0; i < cnstmat->row; i++) { tval = cnstmat->map[i][myfit->q2fit+1]; if (tval == 0 || tval == 2) { nqrest++; } } Afit = CreateDmat(2*fitmat->row + cnstmat->row + myfit->q2fit + nqrest, 2*myfit->q2fit, 0); bfit = (double*)malloc((2*fitmat->row + cnstmat->row + myfit->q2fit + nqrest) * sizeof(double)); for (i = 0; i < fitmat->row; i++) { for (j = 0; j < myfit->q2fit; j++) { Afit.map[i][j] = fitmat->map[i][j]; } bfit[i] = fitmat->map[i][myfit->q2fit+1]; } ip = fitmat->row; for (i = 0; i < fitmat->row; i++) { for (j = 0; j < myfit->q2fit; j++) { Afit.map[ip][j] = fitmat->map[i][j]; Afit.map[ip][j + myfit->q2fit] = fitmat->map[i][j]; } bfit[ip] = fitmat->map[i][myfit->q2fit]; ip++; } for (i = 0; i < cnstmat->row; i++) { for (j = 0; j < myfit->q2fit; j++) { Afit.map[ip][j] = cnstmat->map[i][j]; } bfit[ip] = cnstmat->map[i][myfit->q2fit]; ip++; /*** There may be an extra total charge constraint to add ***/ tval = cnstmat->map[i][myfit->q2fit+1]; if (tval == 0 || tval == 2) { for (j = 0; j < myfit->q2fit; j++) { Afit.map[ip][j] = cnstmat->map[i][j]; Afit.map[ip][j + myfit->q2fit] = cnstmat->map[i][j]; } bfit[ip] = cnstmat->map[i][myfit->q2fit]; ip++; } } for (i = 0; i < myfit->q2fit; i++) { Afit.map[ip][myfit->q2fit+i] = myfit->qtthwt * myfit->nsamp[i]; bfit[ip] = 0.0; ip++; } AxbQRRxc(Afit, bfit, myfit->verbose); BackSub(Afit, bfit); DestroyDmat(&Afit); etimers->nbMtP += mdgxStopTimer(etimers); /*** Store the vacuum, polarized, and delta charge sets ***/ ReflectDVec(qres.map[1], bfit, myfit->q2fit); myfit->SnapCount[1] = SnapFittedQ(qres.map[1], myfit, Atm2Col); ReflectDVec(qres.map[2], bfit, myfit->q2fit); DVec2VecAdd(qres.map[2], &bfit[myfit->q2fit], myfit->q2fit); myfit->SnapCount[2] = SnapFittedQ(qres.map[2], myfit, Atm2Col); ReflectDVec(qres.map[3], &bfit[myfit->q2fit], myfit->q2fit); /*** Test the result ***/ Atst = CreateDmat(fitmat->row, myfit->q2fit, 0); btst = (double*)malloc(fitmat->row*sizeof(double)); bpred = (double*)malloc(fitmat->row*sizeof(double)); for (i = 0; i < fitmat->row; i++) { for (j = 0; j < myfit->q2fit; j++) { Atst.map[i][j] = fitmat->map[i][j]; } btst[i] = fitmat->map[i][myfit->q2fit+1]; } DMatVecMult(&Atst, bfit, bpred); myfit->Qcorr[1] = Pearson(btst, bpred, Atst.row); myfit->Qrmsd[1] = VecRMSD(btst, bpred, Atst.row); SystemQTest(btst, bpred, fitmat->row, myfit, 1); DestroyDmat(&Atst); Atst = CreateDmat(fitmat->row, 2*myfit->q2fit, 0); for (i = 0; i < fitmat->row; i++) { for (j = 0; j < myfit->q2fit; j++) { Atst.map[i][j] = fitmat->map[i][j]; Atst.map[i][j+myfit->q2fit] = fitmat->map[i][j]; } btst[i] = fitmat->map[i][myfit->q2fit]; } DMatVecMult(&Atst, bfit, bpred); myfit->Qcorr[2] = Pearson(btst, bpred, Atst.row); myfit->Qrmsd[2] = VecRMSD(btst, bpred, Atst.row); SystemQTest(btst, bpred, fitmat->row, myfit, 2); DestroyDmat(&Atst); etimers->nbCnv += mdgxStopTimer(etimers); } /*** Titillate the user ***/ if (myfit->verbose == 1) { printf("mdgx >> Fit complete.\n"); } return qres; } /***=======================================================================***/ /*** SystemReeval: re-evaluate each system in light of the fitted charges. ***/ /*** The grids are re-read, LJ exclusions applied, and the ***/ /*** molecular mechanics electrostatic potential is then ***/ /*** calculated. One-dimensional radial distribution ***/ /*** functions (RDFs) are computed for each atom's error. ***/ /*** In addition, this routine computes the best possible ***/ /*** molecular mechanics electrostatic potential for that ***/ /*** particular conformation using a separate REsP fit with ***/ /*** a similar selection of data points to that used in the ***/ /*** complete fit. The resulting potentials indicate the ***/ /*** degree to which polarization is important in each ***/ /*** system, as well as the limitations of the chosen charge ***/ /*** distribution for reproducing the QM target. ***/ /*** ***/ /*** Arguments: ***/ /***=======================================================================***/ #if 0 static void SystemReeval(fset *myfit, trajcon *tj, dmat *fitmat, dmat *qres, imat *Atm2Col, execon *etimers) { int h, i; coord crd, auxcrd; ibook UIdx; dbook Uminr; fbook UPot, auxUPot; cbook Uflag; prmtop *tp; /*** Loop back over each system ***/ for (h = 0; h < myfit->ngrd; h++) { /*** Titillate the user ***/ if (myfit->verbose == 1) { fprintf(stderr, "\rmdgx >> Evaluating fit for %s", myfit->gname.map[h]); for (i = 0; i < namelen - strlen(myfit->gname.map[h]); i++) { fprintf(stderr, " "); } fflush(stderr); } /*** Allocate coordinates for this grid's system ***/ tp = &myfit->TPbank[myfit->tpidx[h]]; crd = CreateCoord(tp->natom); auxcrd = CreateCoord(tp->natom); etimers->Setup += mdgxStopTimer(etimers); /*** Order the list of points as a function of distance from ***/ /*** the solute. The list will then be searched in increasing ***/ /*** order of distance for candidate fitting points. ***/ UPot = ReadEPotGrid(myfit->gname.map[h], tp, &crd); etimers->nbInt += mdgxStopTimer(etimers); Uflag = CreateCbook(UPot.pag, UPot.row, UPot.col); UIdx = CreateIbook(UPot.pag, UPot.row, UPot.col); Uminr = CreateDbook(UPot.pag, UPot.row, UPot.col, 0); etimers->Setup += mdgxStopTimer(etimers); PrepUPot(&UPot, &crd, tp, &Uflag, &Uminr, myfit); etimers->nbInt += mdgxStopTimer(etimers); if (myfit->model == 1) { /*** Read the grid and just trust it; if we've made ***/ /*** it this far then the grids should line up. ***/ auxUPot = ReadEPotGrid(myfit->auxgname.map[h], tp, &auxcrd); } /*** The fitting points for this system were already selected ***/ /*** and their contributions mapped. Read them from the ***/ /*** specific section of the fitting matrix. ***/ } } #endif /***=======================================================================***/ /*** CalculateDipoles: this function takes the best fitted charge set and ***/ /*** puts it on each of the fitting conformations to ***/ /*** compute dipole moments. Generates additional text ***/ /*** in the output file. ***/ /*** ***/ /*** Arguments: ***/ /*** myfit: the fitting data and input variables ***/ /*** qres: the fitted charges ***/ /*** allcrd: matrix of all coordinates for all systems ***/ /*** Atm2Col: matrix of atom indices into the fitting matrix columns ***/ /*** outp: the output file being written ***/ /***=======================================================================***/ static void CalculateDipoles(fset *myfit, dmat *qres, dmat *allcrd, imat *Atm2Col, FILE *outp) { int h, i, j, k, i3, gnamelen, tpnamelen, nrep; int *qidx; double DPdev; double* sqscr; dmat dp; dmat sqdp; prmtop *tp; /*** Allocate memory and compute dipoles ***/ dp = CreateDmat(3, 3*myfit->ngrd, 0); sqdp = CreateDmat(3, 3*myfit->ngrd, 0); sqscr = (double*)malloc(3*myfit->ngrd*sizeof(double)); for (i = 0; i < myfit->ngrd; i++) { /*** The dipole is not relevant if the molecule is charged ***/ if (fabs(myfit->totalq[myfit->tpidx[i]]) > 1.0e-8) { continue; } tp = &myfit->TPbank[myfit->tpidx[i]]; qidx = Atm2Col->map[myfit->tpidx[i]]; for (j = 0; j < tp->natom; j++) { for (k = 0; k < 3; k++) { dp.map[0][3*i+k] += qres->map[0][qidx[j]]*allcrd->map[i][3*j+k]; if (myfit->model == 1) { dp.map[1][3*i+k] += qres->map[1][qidx[j]]*allcrd->map[i][3*j+k]; dp.map[2][3*i+k] += qres->map[2][qidx[j]]*allcrd->map[i][3*j+k]; } } } } /*** Print outputs ***/ HorizontalRule(outp, 0); fprintf(outp, "(4.) Dipole moments on all conformations, (units of Debye)" "\n\n"); tpnamelen = 0; for (i = 0; i < myfit->tpcount; i++) { j = strlen(myfit->TPbank[i].source); if (tpnamelen < j) { tpnamelen = j; } } for (i = 0; i < myfit->ngrd; i++) { i3 = 3*i; for (j = 0; j < 3; j++) { sqdp.map[j][i] = sqrt(dp.map[j][i3]*dp.map[j][i3] + dp.map[j][i3+1]*dp.map[j][i3+1] + dp.map[j][i3+2]*dp.map[j][i3+2]) * EA2DEBYE; } } if (myfit->DispAllDP == 1) { gnamelen = 0; for (i = 0; i < myfit->ngrd; i++) { j = strlen(myfit->gname.map[i]); if (gnamelen < j) { gnamelen = j; } } if (gnamelen < 9) { gnamelen = 9; } if (myfit->model == 0) { fprintf(outp, " %-*s %-*s IPolQ,orig Vacuum IPolQ,pert\n", gnamelen, " ", tpnamelen, " "); } fprintf(outp, " %-*s %-*s", gnamelen, "Grid File", tpnamelen, "System"); fprintf(outp, " "); for (i = 0; i < gnamelen; i++) { fprintf(outp, "-"); } fprintf(outp, " "); for (i = 0; i < tpnamelen; i++) { fprintf(outp, "-"); } fprintf(outp, "\n"); for (i = 0; i < myfit->ngrd; i++) { fprintf(outp, " %-*s %-*s", gnamelen, myfit->gname.map[i], tpnamelen, myfit->TPbank[myfit->tpidx[i]].source); for (j = 0; j < 3; j++) { fprintf(outp, " %9.5lf", sqdp.map[j][i]); } fprintf(outp, "\n"); } fprintf(outp, "\n"); } if (myfit->model == 0) { fprintf(outp, " %-*s %-*s Result\n", gnamelen, " ", tpnamelen, " "); } else if (myfit->model == 1) { fprintf(outp, " %-*s IPolQ,orig Vacuum IPolQ,pert\n", tpnamelen, " "); } fprintf(outp, " System"); for (i = 0; i < tpnamelen-6; i++) { fprintf(outp, " "); } nrep = (myfit->model == 0) ? 1 : 3; for (i = 0; i < nrep; i++) { fprintf(outp, " Mean Stdev."); } fprintf(outp, "\n "); for (i = 0; i < tpnamelen; i++) { fprintf(outp, "-"); } for (i = 0; i < nrep; i++) { fprintf(outp, " ------ ------"); } fprintf(outp, "\n"); for (h = 0; h < myfit->tpcount; h++) { fprintf(outp, " %-*s", tpnamelen, myfit->TPbank[h].source); for (i = 0; i < nrep; i++) { k = 0; for (j = 0; j < myfit->ngrd; j++) { if (myfit->tpidx[j] == h) { sqscr[k] = sqdp.map[i][j]; k++; } } DPdev = (k >= 2) ? DStDev(sqscr, k) : 0.0; fprintf(outp, " %6.2lf %6.2lf", DAverage(sqscr, k), DPdev); } fprintf(outp, "\n"); } HorizontalRule(outp, 1); /*** Free allocated memory ***/ DestroyDmat(&dp); DestroyDmat(&sqdp); free(sqscr); } /***=======================================================================***/ /*** QFitTimingsData: print the timings data from this charge fitting run. ***/ /*** ***/ /*** Arguments: ***/ /*** etimers: the execution timings struct ***/ /*** outp: the mdout file currently being written ***/ /***=======================================================================***/ static void QFitTimingsData(execon *etimers, FILE *outp) { double tt; tt = etimers->Setup + // Setup time (memory allocation, constraint calc) etimers->cellcomm + // Grid coloring time for fitting data etimers->nbPtM + // Small REsP problem time etimers->Integ + // Grid reading time etimers->nbFFT + // Point sorting time etimers->nbBsp; // Point marking time if (etimers->nbMtP > 1.0e-8) { tt += etimers->nbMtP; // Large IPolQ problem time } tt = 100.0/tt; HorizontalRule(outp, 0); fprintf(outp, "(5.) Timings for the fitting problem.\n\n"); fprintf(outp, " Segment Time / Percentage\n" " ---------------------- --------- ------\n"); fprintf(outp, " Setup %9.2lf %6.2lf\n", etimers->Setup, etimers->Setup*tt); fprintf(outp, " Grid Reading %9.2lf %6.2lf\n", etimers->Integ, etimers->Integ*tt); fprintf(outp, " Grid Processing %9.2lf %6.2lf\n", etimers->cellcomm, etimers->cellcomm*tt); fprintf(outp, " Point Sorting %9.2lf %6.2lf\n", etimers->nbFFT, etimers->nbFFT*tt); fprintf(outp, " Point Selection %9.2lf %6.2lf\n", etimers->nbBsp, etimers->nbBsp*tt); fprintf(outp, " Least Squares, REsP %9.2lf %6.2lf\n", etimers->nbPtM, etimers->nbPtM*tt); if (etimers->nbMtP > 1.0e-8) { fprintf(outp, " Least Squares, IPolQ %9.2lf %6.2lf\n", etimers->nbMtP, etimers->nbMtP*tt); } fprintf(outp, " Output Printing %9.2lf %6.2lf\n", etimers->Write, etimers->Write*tt); fprintf(outp, " Total CPU Time %9.2lf %6.2lf\n", 100.0/tt, 100.0); HorizontalRule(outp, 1); } /***=======================================================================***/ /*** PrintEPRules: print a file of extra point rules that will modify each ***/ /*** topology and add any needed extra points. ***/ /*** ***/ /*** Arguments: ***/ /*** myfit: fitting control data ***/ /*** tpnum: the number of the topology for which to print rules ***/ /*** (the fitted charges for each topology are now stored in ***/ /*** the topology structs themselves) ***/ /*** tj: trajectory control information ***/ /***=======================================================================***/ static void PrintEPRules(fset *myfit, int tpnum, trajcon *tj) { int i, j, natm, nres; eprule *tmr; char outname[MAXNAME]; FILE *outp; prmtop *tp; tp = &myfit->TPbank[tpnum]; sprintf(outname, "%s.%s", tp->source, myfit->epext); outp = FOpenSafe(outname, tj->OverwriteOutput); fprintf(outp, "%% Extra points rules for charge model fitted as described " "in\n%% %s.\n%%\n\n", tj->outbase); fprintf(outp, "%% The following rules will modify the charges of\n%% atoms " "given in the topology %s.\n", tp->source); for (i = 0; i < tp->natom; i++) { nres = LocateResID(tp, i, 0, tp->nres); if (tp->EPInserted == 0 || tp->OldAtomNum[i] >= 0) { fprintf(outp, "&rule\n ResidueName %.4s\n ExtraPoint %.4s\n " "FrameStyle 0\n Charge %9.5lf\n&end\n\n", &tp->ResNames[4*nres], &tp->AtomNames[4*i], tp->Charges[i]); } } if (tp->EPInserted == 0) { return; } fprintf(outp, "%% The following rules will add charged extra points\n%% " "to the original topology.\n"); for (i = 0; i < tp->neprule; i++) { tmr = &tp->eprules[i]; for (j = 0; j < tp->natom; j++) { if (strncmp(&tp->AtomNames[4*j], tmr->epname, 4) == 0) { natm = j; } } fprintf(outp, "&rule\n ResidueName %.4s\n ExtraPoint %.4s\n " "FrameStyle %d\n", tmr->resname, tmr->epname, tmr->frstyle); fprintf(outp, " FrameAtom1 %.4s\n FrameAtom2 %.4s\n", tmr->fr1, tmr->fr2); if (tmr->frstyle > 1) { fprintf(outp, " FrameAtom3 %.4s\n", tmr->fr3); } if (tmr->frstyle == 6) { fprintf(outp, " FrameAtom4 %.4s\n", tmr->fr4); } fprintf(outp, " Vector12 %16.10lf\n", tmr->d1); if (tmr->frstyle == 2 || tmr->frstyle == 5 || tmr->frstyle == 6) { fprintf(outp, " Vector13 %16.10lf\n", tmr->d2); } else if (tmr->frstyle == 4) { fprintf(outp, " Theta %16.10lf\n", tmr->d2); } if (tmr->frstyle == 3) { fprintf(outp, " Vector23 %16.10lf\n", tmr->d3); } else if (tmr->frstyle == 5) { fprintf(outp, " Vector12x13 %16.10lf\n", tmr->d3); } fprintf(outp, " Charge %9.5lf\n", tp->Charges[natm]); if (tmr->sig >= 0.0) { fprintf(outp, " Sigma %16.10lf\n", tmr->sig); } if (tmr->eps >= 0.0) { fprintf(outp, " Epsilon %16.10lf\n", tmr->eps); } fprintf(outp, "&end\n\n"); } fclose(outp); } /***=======================================================================***/ /*** PrintRespHistogram: print a histogram of the RESP fitting points, ***/ /*** indicating their minimum distance to the solute. ***/ /*** ***/ /*** Arguments: ***/ /*** myfit: the fitting command data, used here to determine the ***/ /*** number of jackknife fits and their style ***/ /***=======================================================================***/ static void PrintRespHistogram(fset *myfit, trajcon *tj) { int i; FILE *outp; outp = FOpenSafe(myfit->histfile, tj->OverwriteOutput); fprintf(outp, "%% Molecule:fit point distance histogram for charge model " "fitted\n%% as described in %s.\n%%\n%% Counts are normalized by " "the total number of fitting points.\n\n%% Bin Center Count\n" "%% ---------- ---------\n", tj->outbase); for (i = 0; i < 10.0/myfit->fhistbin; i++) { fprintf(outp, " %10.6lf %9.6lf\n", (i+0.5)*myfit->fhistbin, (double)myfit->fitpthist[i]/(myfit->ngrd*myfit->nfitpt)); } fclose(outp); } /***=======================================================================***/ /*** OutputRESP: produce formatted output to present the results of the ***/ /*** RESP calculation. If an extra points file was specified, ***/ /*** then a new extra points file with the appropriate names ***/ /*** and charges assigned to each point will be written. A ***/ /*** set of scaled charges will be written in 5 x %16.8e ***/ /*** format for inclusion in the topology file. ***/ /*** ***/ /*** Arguments: ***/ /*** myfit: the fitting command data, used here to determine the ***/ /*** number of jackknife fits and their style ***/ /*** tj: trajectory control data, used here for directives on ***/ /*** output overwriting and input file reprinting ***/ /*** qres: matrix filled with putative solutions to the RESP fit ***/ /*** allcrd: coordinates of all fitting conformations ***/ /*** Atm2Col: correspondence of atoms in each system and columns in ***/ /*** the fitting matrix ***/ /*** etimers: the execution timings data ***/ /***=======================================================================***/ static void OutputRESP(fset *myfit, trajcon *tj, dmat *qres, dmat *allcrd, imat *Atm2Col, execon *etimers) { int i, j, k, nchg; double qval; FILE *outp; time_t ct; prmtop *tp; /*** Open the output file ***/ ct = time(&ct); outp = FOpenSafe(tj->outbase, tj->OverwriteOutput); fprintf(outp, "Run on %s", asctime(localtime(&ct))); /*** Reprint the input file ***/ HorizontalRule(outp, 0); fprintf(outp, "\nINPUT LINE TEXT:\n\n"); PrintParagraph(tj->inpline, 79, outp); fprintf(outp, "\nINPUT FILE TEXT:\n\n"); for (i = 0; i < tj->inptext.row; i++) { fprintf(outp, "%s", tj->inptext.map[i]); } HorizontalRule(outp, 1); /*** Print the best resulting charge set(s) ***/ HorizontalRule(outp, 0); fprintf(outp, "(1.) Overall accuracy of the derived charges.\n\n"); if (myfit->model == 0) { fprintf(outp, " Correlation of fitted to original electrostatic " "potential: %8.5lf\n", myfit->Qcorr[0]); fprintf(outp, " Error fitted versus original electrostatic potential " "(kcal/mol): %8.5lf\n", myfit->Qrmsd[0]); fprintf(outp, " Snap iterations required: " " %4d\n\n", myfit->SnapCount[0]); PrintVADesc(0, " ", 1, " ", 1, "The electrostatic potential is easier to " "fit for some systems and targets than others. The following " "table gives values for individual systems.\n", 77, 0,outp); } else { PrintVADesc(0, " ", 1, " ", 1, "The electrostatic potential was fitted in " "two ways. First, a traditional REsP fit was performed to " "obtain a set of IPolQ charges by fitting them directly to " "the average of the vacuum and condensed-phase potentials. " "Next, a two-component REsP was performed to fit a set of " "charges to the vacuum phase potential only, restrained under " "the same conditions as the first set of IPolQ charges, and " "to simultaneously fit a second set of IPolQ charges composed " "of the vacuum charge set plus a minimal perturbation. This " "second approach to fitting IPolQ charges produces a result " "which can be related to charges appropriate for potential " "energy surfaces of systems in vacuum, which suggests a means " "of obtaining an appropriate set of torsion potential Fourier " "terms for IPolQ charges.\n", 77, 0, outp); fprintf(outp, " Accuracy to Aggregate Target\n" " Charge Set Correlation RMS Error Snap\n" " ------------- ----------- ----------- ----\n" " IPolQ (orig) %9.4lf %11.4lf %4d\n" " Vacuum %9.4lf %11.4lf %4d\n" " IPolQ (pert) %9.4lf %11.4lf %4d\n\n", myfit->Qcorr[0], myfit->Qrmsd[0], myfit->SnapCount[0], myfit->Qcorr[1], myfit->Qrmsd[1], myfit->SnapCount[1], myfit->Qcorr[2], myfit->Qrmsd[2], myfit->SnapCount[2]); } PrintVADesc(0, " ", 1, " ", 1, "The electrostatic potential is easier to " "fit for some systems and targets than others. The following " "table gives values for individual systems.\n", 77, 0,outp); if (myfit->model == 1) { fprintf(outp, " IPolQ (orig) " " Vacuum IPolQ (pert)\n"); } fprintf(outp, " System Corr RMSE "); if (myfit->model == 0) { fprintf(outp, "\n"); } else { fprintf(outp, " Corr RMSE Corr RMSE\n"); } fprintf(outp, " ------------------------------ ------ ------ "); if (myfit->model == 0) { fprintf(outp, "\n"); } else { fprintf(outp, "------ ------ ------ ------\n"); } for (i = 0; i < myfit->tpcount; i++) { fprintf(outp, " %-30.30s %6.3lf %6.2lf", myfit->TPbank[i].source, myfit->QScorr.map[0][i], myfit->QSrmsd.map[0][i]); if (myfit->model == 0) { fprintf(outp, "\n"); } else { fprintf(outp, " %6.3lf %6.2lf %6.3lf %6.2lf\n", myfit->QScorr.map[1][i], myfit->QSrmsd.map[1][i], myfit->QScorr.map[2][i], myfit->QSrmsd.map[2][i]); } } HorizontalRule(outp, 1); HorizontalRule(outp, 0); fprintf(outp, "(2.) Charges on all atoms, proton units\n"); for (i = 0; i < myfit->tpcount; i++) { if (myfit->model == 0) { fprintf(outp, "\n System %d: %s\n Atom Charge \n" " ---- ----------\n", i+1, myfit->TPbank[i].source); } else { fprintf(outp, "\n System %d: %s\n" " Atom IPolQ,orig Vacuum Q Delta Q IPolQ,pert\n" " ---- ---------- ---------- ---------- ----------\n", i+1, myfit->TPbank[i].source); } tp = &myfit->TPbank[i]; for (j = 0; j < tp->natom; j++) { nchg = Atm2Col->map[i][j]; if (myfit->model == 0) { fprintf(outp, " %.4s %10.5lf\n", &tp->AtomNames[4*j], qres->map[0][nchg]); } else { fprintf(outp, " %.4s %10.5lf %10.5lf %10.5lf %10.5lf\n", &tp->AtomNames[4*j], qres->map[0][nchg], qres->map[1][nchg], qres->map[3][nchg], qres->map[2][nchg]); } } } HorizontalRule(outp, 1); /*** Print the best resulting charge set in prmtop format ***/ HorizontalRule(outp, 0); fprintf(outp, "(3.) Charges on all atoms, prmtop format (proton units " "scaled by 18.2223)\n"); for (i = 0; i < myfit->tpcount; i++) { tp = &myfit->TPbank[i]; fprintf(outp, "\n System %d: %s\n", i+1, myfit->TPbank[i].source); k = 0; for (j = 0; j < tp->natom; j++) { qval = (myfit->model == 0) ? qres->map[0][Atm2Col->map[i][j]] : qres->map[2][Atm2Col->map[i][j]]; fprintf(outp, "%16.8e", qval*sqrt(BIOQ)); k++; if (k == 5) { k = 0; fprintf(outp, "\n"); } } if (k > 0) { fprintf(outp, "\n"); } } HorizontalRule(outp, 1); /*** Compute and print dipole moments ***/ i = (myfit->model == 0) ? 0 : 2; CalculateDipoles(myfit, qres, allcrd, Atm2Col, outp); etimers->Write = mdgxStopTimer(etimers); QFitTimingsData(etimers, outp); fclose(outp); /*** Print an extra points file ***/ if (myfit->epext[0] != '\0') { for (i = 0; i < myfit->tpcount; i++) { PrintEPRules(myfit, i, tj); } } /*** Print the histogram of fitting points ***/ if (myfit->histfile[0] != '\0') { PrintRespHistogram(myfit, tj); } } /***=======================================================================***/ /*** DestroyFitData: cleanup for all data related to charge fitting. ***/ /*** ***/ /*** Arguments: ***/ /*** myfit: the fitting control data ***/ /***=======================================================================***/ static void DestroyFitData(fset *myfit) { int i; /*** Simple frees ***/ free(myfit->fitpthist); free(myfit->tpidx); free(myfit->totalq); free(myfit->wt); free(myfit->nsamp); free(myfit->FPtOrigins); /*** Constraint structs ***/ for (i = 0; i < myfit->nqeq; i++) { free(myfit->qeq[i].atoms); free(myfit->qeq[i].maskstr); } free(myfit->qeq); for (i = 0; i < myfit->nqmin; i++) { free(myfit->qmin[i].maskstr); } free(myfit->qmin); for (i = 0; i < myfit->nqsum; i++) { free(myfit->qsum[i].maskstr); } free(myfit->qsum); /*** File names ***/ DestroyCmat(&myfit->gname); DestroyCmat(&myfit->auxgname); DestroyCmat(&myfit->tpname); /*** Topologies ***/ for (i = 0; i < myfit->tpcount; i++) { FreeTopology(&myfit->TPbank[i]); } free(myfit->TPbank); free(myfit->eprule); } /***=======================================================================***/ /*** FitCharges: the main function for parameter optimization. ***/ /*** ***/ /*** Arguments: ***/ /*** tj: trajectory control information ***/ /*** myfit: the fitting control data ***/ /*** etimers: the execution timings ***/ /***=======================================================================***/ void FitCharges(fset *myfit, trajcon *tj, execon *etimers) { int i, maxatm; imat Atm2Col; dmat cnstmat, fitmat, qres; dmat allcrd; /*** Match up topology names with grids ***/ AssignGridTopologies(myfit, tj); /*** Match up atoms in all topologies with matrix columns ***/ Atm2Col = ColumnsForAtoms(myfit); /*** Allocate space for coordinate buffer ***/ maxatm = 0; for (i = 0; i < myfit->tpcount; i++) { if (myfit->TPbank[i].natom > maxatm) { maxatm = myfit->TPbank[i].natom; } } allcrd = CreateDmat(myfit->ngrd, 3*maxatm, 0); etimers->Setup += mdgxStopTimer(etimers); /*** Create the matrix of all fitting points ***/ fitmat = MakeFittingMatrix(myfit, &Atm2Col, &allcrd, tj, etimers); /*** Create the matrix of constraints ***/ cnstmat = MakeCnstMatrix(myfit, &Atm2Col, &fitmat); etimers->Setup += mdgxStopTimer(etimers); /*** Solve the linear least squares problem ***/ qres = SolveRESP(&fitmat, &cnstmat, myfit, &Atm2Col, etimers); etimers->Setup += mdgxStopTimer(etimers); /*** Re-evaluate each system in light of the fitted charges ***/ // SystemReeval(myfit, tj, &qres, &Atm2Col, etimers); /*** Output results ***/ OutputRESP(myfit, tj, &qres, &allcrd, &Atm2Col, etimers); /*** Free allocated memory ***/ DestroyDmat(&fitmat); DestroyDmat(&cnstmat); DestroyDmat(&allcrd); DestroyImat(&Atm2Col); DestroyFitData(myfit); /*** Exit! We are done. ***/ exit(1); }