int main(int argc, char *argv[]) { //============= set up MPI ================= MPI_Status status; const int root_process = 0; int ierr, my_id, an_id, num_procs; // tag for MPI message: energy minimization convergence const int tag_99 = 99; // initialize MPI, get my_id for this process and total number of processes ierr = MPI_Init(&argc, &argv); ierr = MPI_Comm_rank(MPI_COMM_WORLD, &my_id); ierr = MPI_Comm_size(MPI_COMM_WORLD, &num_procs); //============ get input files ============= // default filenames char input_gro[MAX_STR_LEN], input_param[MAX_STR_LEN], input_mdset[MAX_STR_LEN]; strcpy(input_gro, "init.gro"); strcpy(input_param, "param.txt"); strcpy(input_mdset, "mdset.txt"); // parse arguments (input files) // -gro for gro file // -par for parameter file // -mds for MD settings int iarg = 1; while (1) { if (iarg >= argc) { break; } if (0 == strcmp(argv[iarg], "-gro") && iarg < argc) { strcpy(input_gro, argv[iarg + 1]); iarg += 2; } else if (0 == strcmp(argv[iarg], "-par") && iarg < argc) { strcpy(input_param, argv[iarg + 1]); iarg += 2; } else if (0 == strcmp(argv[iarg], "-mds") && iarg < argc) { strcpy(input_mdset, argv[iarg + 1]); iarg += 2; } else { ++ iarg; } } //========= define variables and read MD settings =========== // varialbles to read from input_mdset RunSet *p_runset = my_malloc(sizeof(RunSet)); Metal *p_metal = my_malloc(sizeof(Metal)); // read md settings from input_mdset read_settings(input_mdset, p_runset, p_metal); // initialize timer time_t start_t = time(NULL); struct timeval tv; gettimeofday(&tv, NULL); double start_time = (tv.tv_sec) + (tv.tv_usec) * 1.0e-6; // time_used[0] = total // time_used[1] = QSC density // time_used[2] = QSC force // time_used[3] = Bonded // time_used[4] = Nonbonded // time_used[5] = CPIM matrix // time_used[6] = CPIM vector // time_used[7] = CPIM solve // time_used[8] = CPIM force // time_used[10] = QSC density communication double **time_used = my_malloc(sizeof(double *) * num_procs); for(an_id = 0; an_id < num_procs; ++ an_id) { time_used[an_id] = my_malloc(sizeof(double) * 15); int it; for(it = 0; it < 15; ++ it) { time_used[an_id][it] = 0.0; } } // Coulomb type: cut_off, wolf_sum if (0 == strcmp(p_runset->coulomb_type, "cut_off" )) { p_runset->use_coulomb = 0; } else if (0 == strcmp(p_runset->coulomb_type, "wolf_sum")) { p_runset->use_coulomb = 1; } else { printf("Error: unknown coulomb_type %s!\n", p_runset->coulomb_type); exit(1); } // Damped shifted force (DSF) approach for electrostatics // Ref.: Fennell and Gezelter, J. Chem. Phys. 2006, 124, 234104 // dx.doi.org/10.1063/1.2206581 // Based on: Wolf et al., J. Chem. Phys. 1999, 110, 8254 // dx.doi.org/10.1063/1.478738 // Zahn et al., J. Phys. Chem. B 2002, 106, 10725-10732 // dx.doi.org/10.1021/jp025949h // Benchmark: McCann and Acevedo, J. Chem. Theory Comput. 2013, 9, 944-950 // dx.doi.org/10.1021/ct300961e // note: p_runset->rCut and p_runset->w_alpha were read from mdset.txt p_runset->rCut2 = p_runset->rCut * p_runset->rCut; double rCut = p_runset->rCut; double rCut2 = p_runset->rCut2; double w_alpha = p_runset->w_alpha; p_runset->w_a2_sqrtPI = w_alpha * 2.0 * INV_SQRT_PI; p_runset->w_Const = erfc(w_alpha * rCut) / rCut2 + p_runset->w_a2_sqrtPI * exp(-w_alpha * w_alpha * rCut2) / rCut; p_runset->w_erfc_arCut = erfc(w_alpha * rCut)/ rCut; // van der Waals type: cut_off or shifted if (0 == strcmp(p_runset->vdw_type, "cut_off")) { p_runset->use_vdw = 0; } else if (0 == strcmp(p_runset->vdw_type, "shifted")) { p_runset->use_vdw = 1; } else { printf("Error: unknown vdw_type %s!\n", p_runset->vdw_type); exit(1); } // Shifted force method for Lennard-Jones potential // Ref: Toxvaerd and Dyre, J. Chem. Phys. 2011, 134, 081102 // dx.doi.org/10.1063/1.3558787 p_runset->inv_rc12 = 1.0 / pow(rCut, 12.0); p_runset->inv_rc6 = 1.0 / pow(rCut, 6.0); //============== read force field parameters from param.txt ================= Topol *p_topol = my_malloc(sizeof(Topol)); int mol, atom; int number_VSites, number_Cstrs; // variables for bonded and nonbonded interaction // CPIM: capacitance-polarizability interaction model // Ref.: a) Jensen and Jensen, J. Phys. Chem. C, 2008, 112, 15697-15703 // dx.doi.org/10.1021/jp804116z // b) Morton and Jensen, J. Chem. Phys., 2010, 133, 074103 // dx.doi.org/10.1063/1.3457365 // // p_metal->cpff_polar: polarizability // p_metal->cpff_capac: capacitance // p_metal->n_NPs: number of nanoparticles // p_metal->cpff_chg: total charge of a nanoparticle // p_metal->start_NP: first atom of a nanoparticle // p_metal->end_NP: last atom of a nanoparticle // read parameters from input_param, step 1 read_param_1(input_param, p_topol, p_metal); // allocate memory for arrays // CPIM charge and indices p_metal->cpff_chg = my_malloc(p_metal->n_NPs * sizeof(double)); p_metal->start_NP = my_malloc(p_metal->n_NPs * sizeof(int)); p_metal->end_NP = my_malloc(p_metal->n_NPs * sizeof(int)); // molecules and atom parameters // For a given molecule type (mol from 0 to p_topol->mol_types-1): // p_topol->atom_num[mol]: its number of atoms // p_topol->mol_num[mol]: the number of this type of molecule in the system p_topol->atom_num = my_malloc(p_topol->mol_types * sizeof(int)); p_topol->mol_num = my_malloc(p_topol->mol_types * sizeof(int)); p_topol->atom_param = my_malloc(p_topol->mol_types * sizeof(AtomParam *)); // van der Waals interaction parameters NonbondedParam *data_nonbonded = my_malloc_2(p_topol->n_types * p_topol->n_types * sizeof(NonbondedParam), "data_nonbonded"); p_topol->nonbonded_param = my_malloc(p_topol->n_types * sizeof(NonbondedParam *)); int i_type; for(i_type = 0; i_type < p_topol->n_types; ++ i_type) { p_topol->nonbonded_param[i_type] = &(data_nonbonded[p_topol->n_types * i_type]); } // bonded potentials: bond, pair, angle, dihedral int *data_bonded = my_malloc(sizeof(int) * p_topol->mol_types * 6); p_topol->n_bonds = &(data_bonded[0]); p_topol->n_pairs = &(data_bonded[p_topol->mol_types]); p_topol->n_angles = &(data_bonded[p_topol->mol_types * 2]); p_topol->n_dihedrals = &(data_bonded[p_topol->mol_types * 3]); p_topol->n_vsites = &(data_bonded[p_topol->mol_types * 4]); p_topol->n_constraints = &(data_bonded[p_topol->mol_types * 5]); p_topol->vsite_funct = my_malloc(p_topol->mol_types * sizeof(int *)) ; p_topol->bond_param = my_malloc(p_topol->mol_types * sizeof(BondParam *)); p_topol->pair_param = my_malloc(p_topol->mol_types * sizeof(PairParam *)) ; p_topol->angle_param = my_malloc(p_topol->mol_types * sizeof(AngleParam *)); p_topol->dihedral_param = my_malloc(p_topol->mol_types * sizeof(DihedralParam *)) ; p_topol->vsite_4 = my_malloc(p_topol->mol_types * sizeof(VSite_4 *)) ; p_topol->constraint = my_malloc(p_topol->mol_types * sizeof(Constraint *)) ; p_topol->exclude = my_malloc(p_topol->mol_types * sizeof(int **)) ; // Quantum Sutton-Chen densities for metal if (p_metal->min >=0 && p_metal->max >= p_metal->min) { p_metal->inv_sqrt_dens = my_malloc(sizeof(double) * p_metal->num); } // read parameters from input_param, step 2 read_param_2(input_param, p_topol, p_metal); int nAtoms = p_topol->n_atoms; int nMols = p_topol->n_mols; // count number of virtual sites and constraints number_VSites = 0; number_Cstrs = 0; for(mol = 0; mol < p_topol->mol_types; ++ mol) { number_VSites += p_topol->n_vsites[mol] * p_topol->mol_num[mol]; number_Cstrs += p_topol->n_constraints[mol] * p_topol->mol_num[mol]; } // Gaussian distribution width for capacitance-polarizability model // see Mayer, Phys. Rev. B 2007, 75, 045407 // and Jensen, J. Phys. Chem. C 2008, 112, 15697 p_metal->inv_polar = 1.0 / p_metal->cpff_polar; p_metal->inv_capac = 1.0 / p_metal->cpff_capac; double R_q = sqrt(2.0 / M_PI) * p_metal->cpff_capac; double R_p = pow(sqrt(2.0 / M_PI) * p_metal->cpff_polar / 3.0, 1.0 / 3.0); p_metal->inv_R_qq = 1.0 / sqrt(R_q * R_q + R_q * R_q); p_metal->inv_R_pq = 1.0 / sqrt(R_p * R_p + R_q * R_q); p_metal->inv_R_pp = 1.0 / sqrt(R_p * R_p + R_p * R_p); // print info if (root_process == my_id) { printf("\n"); printf(" +-----------------------------------------------------+\n"); printf(" | CapacMD program version 1.0 |\n"); printf(" | Xin Li, TheoChemBio, KTH, Stockholm |\n"); printf(" +-----------------------------------------------------+\n"); printf("\n"); printf(" .------------------ reference paper ------------------.\n"); printf("\n"); printf(" Molecular Dynamics Simulations using a Capacitance-Polarizability Force Field,\n"); printf(" Xin Li and Hans Agren, J. Phys. Chem. C, 2015, DOI: 10.1021/acs.jpcc.5b04347\n"); printf("\n"); printf("\n"); printf(" Calculation started at %s", ctime(&start_t)); printf(" Parallelized via MPI, number of processors = %d\n", num_procs); printf("\n"); printf("\n"); printf(" .------------------ run parameters -------------------.\n"); printf("\n"); printf(" run_type = %s, ensemble = %s\n", p_runset->run_type, p_runset->ensemble); printf(" vdw_type = %s, coulomb_type = %s\n", p_runset->vdw_type, p_runset->coulomb_type); printf(" rCut = %.3f nm, ", rCut); if (1 == p_runset->use_coulomb) { printf("alpha = %.3f nm^-1", p_runset->w_alpha); } printf("\n"); printf(" ref_T = %.1f K\n", p_runset->ref_temp); printf("\n"); printf("\n"); printf(" .------------------- molecule info -------------------.\n"); printf("\n"); printf(" There are %d types of molecules.\n", p_topol->mol_types); printf("\n"); for(mol = 0; mol < p_topol->mol_types; ++ mol) { printf(" Molecule[%5d], num= %5d\n", mol, p_topol->mol_num[mol]); for(atom = 0; atom < p_topol->atom_num[mol]; ++ atom) { printf(" Atom[%5d], charge= %8.3f, mass= %8.3f, atomtype= %5d\n", atom, p_topol->atom_param[mol][atom].charge, p_topol->atom_param[mol][atom].mass, p_topol->atom_param[mol][atom].atomtype); } printf("\n"); } printf("\n"); } //=========== distribute molecules/atoms/metals among the procs ============== Task *p_task = my_malloc(sizeof(Task)); int *data_start_end = my_malloc(sizeof(int) * num_procs * 6); p_task->start_mol = &(data_start_end[0]); p_task->end_mol = &(data_start_end[num_procs]); p_task->start_atom = &(data_start_end[num_procs * 2]); p_task->end_atom = &(data_start_end[num_procs * 3]); p_task->start_metal = &(data_start_end[num_procs * 4]); p_task->end_metal = &(data_start_end[num_procs * 5]); find_start_end(p_task->start_mol, p_task->end_mol, nMols, num_procs); find_start_end(p_task->start_atom, p_task->end_atom, nAtoms, num_procs); find_start_end(p_task->start_metal, p_task->end_metal, p_metal->num, num_procs); long int *data_long_start_end = my_malloc(sizeof(long int) * num_procs * 2); p_task->start_pair = &(data_long_start_end[0]); p_task->end_pair = &(data_long_start_end[num_procs]); long int n_pairs = nAtoms * (nAtoms - 1) / 2; find_start_end_long(p_task->start_pair, p_task->end_pair, n_pairs, num_procs); //============= assign indices, masses and charges ======================= // For each atom in the system (i from 0 to p_topol->n_atoms-1) // atom_info[i].iAtom: the index of this atom in its molecule type // atom_info[i].iMol: the index of its molecule type // atom_info[i].molID: the index of its molecule in the system // For each molecule in the system (im from 0 to p_topol->n_mols-1) // mol_info[im].mini: the index of its first atom in the system // mol_info[im].maxi: the index of its last atom in the system Atom_Info *atom_info = my_malloc(nAtoms * sizeof(Atom_Info)); Mol_Info *mol_info = my_malloc(nMols * sizeof(Mol_Info)); assign_indices(p_topol, p_metal, atom_info, mol_info); //======= allocate memory for coordinates, velocities and forces ========== System *p_system = my_malloc(sizeof(System)); // potential energy // p_system->potential[0] = total energy // p_system->potential[1] = metal quantum Sutton-Chen energy // p_system->potential[2] = non-metal bond stretching energy // p_system->potential[3] = non-metal angle bending energy // p_system->potential[4] = non-metal torsional energy // p_system->potential[5] = // p_system->potential[6] = Long range Coulomb // p_system->potential[7] = Coulomb energy (including 1-4) // p_system->potential[8] = vdW energy (including 1-4) // p_system->potential[9] = // p_system->potential[10] = CPIM metal charge - non-metal charge // p_system->potential[11] = CPIM metal dipole - non-metal charge // p_system->potential[12] = CPIM metal charge - metal charge // p_system->potential[13] = CPIM metal charge - metal dipole // p_system->potential[14] = CPIM metal dipole - metal dipole double *data_potential = my_malloc(sizeof(double) * 15 * 2); p_system->potential = &(data_potential[0]); p_system->partial_pot = &(data_potential[15]); p_system->old_potential = 0.0; // virial tensor p_system->virial = my_malloc(DIM * sizeof(double*)); p_system->partial_vir = my_malloc(DIM * sizeof(double*)); double *data_vir = my_malloc(DIM*2 * DIM * sizeof(double)); int i; for (i = 0; i < DIM; ++ i) { p_system->virial[i] = &(data_vir[DIM * i]); p_system->partial_vir[i] = &(data_vir[DIM * (DIM + i)]); } // box size // Note: for now we treat rectangular box only. // "p_system->box" has six elements // the first three are length in x, y, z // the second three are half of the length in x, y, z p_system->box = my_malloc(sizeof(double) * DIM*2); // coordinates, velocities and forces double *data_rvf = my_malloc_2(nAtoms*7 * DIM * sizeof(double), "data_rvf"); p_system->rx = &(data_rvf[0]); p_system->ry = &(data_rvf[nAtoms]); p_system->rz = &(data_rvf[nAtoms*2]); p_system->vx = &(data_rvf[nAtoms*3]); p_system->vy = &(data_rvf[nAtoms*4]); p_system->vz = &(data_rvf[nAtoms*5]); p_system->fx = &(data_rvf[nAtoms*6]); p_system->fy = &(data_rvf[nAtoms*7]); p_system->fz = &(data_rvf[nAtoms*8]); // forces from slave processors p_system->partial_fx = &(data_rvf[nAtoms*9]); p_system->partial_fy = &(data_rvf[nAtoms*10]); p_system->partial_fz = &(data_rvf[nAtoms*11]); // old position for RATTLE constraints p_system->old_rx = &(data_rvf[nAtoms*12]); p_system->old_ry = &(data_rvf[nAtoms*13]); p_system->old_rz = &(data_rvf[nAtoms*14]); // old force for CG optimization p_system->old_fx = &(data_rvf[nAtoms*15]); p_system->old_fy = &(data_rvf[nAtoms*16]); p_system->old_fz = &(data_rvf[nAtoms*17]); // direction for CG optimization p_system->sx = &(data_rvf[nAtoms*18]); p_system->sy = &(data_rvf[nAtoms*19]); p_system->sz = &(data_rvf[nAtoms*20]); //================ read input gro file ================== // vQ and vP are "velocities" of the thermostat/barostat particles p_system->vQ = 0.0; p_system->vP = 0.0; int groNAtoms; read_gro(input_gro, p_system, &groNAtoms, atom_info); if (groNAtoms != nAtoms) { printf("Error: groNAtoms(%d) not equal to nAtoms(%d)!\n", groNAtoms, nAtoms); exit(1); } // half of box length for PBC p_system->box[3] = p_system->box[0] * 0.5; p_system->box[4] = p_system->box[1] * 0.5; p_system->box[5] = p_system->box[2] * 0.5; p_system->volume = p_system->box[0] * p_system->box[1] * p_system->box[2]; p_system->inv_volume = 1.0 / p_system->volume; //================== set MD variables ========================== // degree of freedom p_system->ndf = 3 * (nAtoms - number_VSites) - 3 - number_Cstrs; // temperature coupling; kT = kB*T, in kJ mol^-1 p_runset->kT = K_BOLTZ * p_runset->ref_temp; p_system->qMass = (double)p_system->ndf * p_runset->kT * p_runset->tau_temp * p_runset->tau_temp; //p_system->pMass = (double)p_system->ndf * p_runset->kT * p_runset->tau_pres * p_runset->tau_pres; // temperature control p_system->first_temp = 0.0; p_system->ext_temp = 0.0; // time step p_runset->dt_2 = 0.5 * p_runset->dt; //=================== CPIM matrix and arrays ========================= // Relay matrix // external electric field and potential // induced dipoles and charges double *data_relay = NULL; // dimension of matrix: 4M + n_NPs int n_mat = p_metal->num * 4 + p_metal->n_NPs; // initialize mat_relay, vec_ext and vec_pq if (p_metal->min >=0 && p_metal->max >= p_metal->min) { data_relay = my_malloc_2(sizeof(double) * n_mat * 3, "data_relay"); p_metal->vec_pq = &(data_relay[0]); p_metal->vec_ext = &(data_relay[n_mat]); p_metal->diag_relay = &(data_relay[n_mat * 2]); int i_mat; for(i_mat = 0; i_mat < n_mat; ++ i_mat) { p_metal->vec_pq[i_mat] = 0.0; p_metal->vec_ext[i_mat] = 0.0; p_metal->diag_relay[i_mat] = 1.0; } } //================== compute forces ========================== mpi_force(p_task, p_topol, atom_info, mol_info, p_runset, p_metal, p_system, my_id, num_procs, time_used); //========== file handles: gro, vec_pq, binary dat, parameters ======== FILE *file_gro, *file_pq, *file_dat; file_gro = NULL; file_pq = NULL; file_dat = NULL; //========== adjust velocities and write trajectories ================= if (root_process == my_id) { // sum potential energies sum_potential(p_system->potential); // update temperature remove_comm(nAtoms, atom_info, p_system); kinetic_energy(p_system, nAtoms, atom_info); // save the starting temperature p_system->first_temp = p_system->inst_temp; p_system->ext_temp = p_runset->ref_temp; // creat traj.gro for writing file_gro = fopen("traj.gro","w") ; if (NULL == file_gro) { printf( "Cannot write to traj.gro!\n" ) ; exit(1); } // creat vec_pq.txt for writing file_pq = fopen("vec_pq.txt","w") ; if (NULL == file_pq) { printf( "Cannot write to vec_pq.txt!\n" ) ; exit(1); } // creat traj.dat for writing file_dat = fopen("traj.dat","w") ; if (NULL == file_dat) { printf( "Cannot write to traj.dat!\n" ) ; exit(1); } // get maximal force get_fmax_rms(nAtoms, p_system); // write the starting geometry to gro and dat file write_gro(file_gro, p_system, nAtoms, atom_info, 0); write_binary(file_dat, p_system, nAtoms, 0); if (p_metal->min >=0 && p_metal->max >= p_metal->min) { write_vec_pq(file_pq, p_metal, 0); } printf(" .---------------- start MD calculation ---------------.\n"); printf("\n"); // check p_runset->run_type if (0 == strcmp(p_runset->run_type, "em") || 0 == strcmp(p_runset->run_type, "cg")) { printf(" Step %-5d Fmax=%10.3e E=%15.8e\n", 0, p_system->f_max, p_system->potential[0]); } else if (0 == strcmp(p_runset->run_type, "md")) { printf(" %10.3f Fmax=%.2e E=%.6e T=%.3e\n", 0.0, p_system->f_max, p_system->potential[0], p_system->inst_temp); } } //========= Now start MD steps ============================ int step = 0; //=================================================== // Energy minimization using steepest descent or CG //=================================================== if (0 == strcmp(p_runset->run_type, "em") || 0 == strcmp(p_runset->run_type, "cg")) { p_system->vQ = 0.0; p_system->vP = 0.0; int converged = 0; double gamma = 0.0; double delta_pot; // initialize direction sx,sy,sz for(i = 0; i < nAtoms; ++ i) { p_system->old_fx[i] = p_system->fx[i]; p_system->old_fy[i] = p_system->fy[i]; p_system->old_fz[i] = p_system->fz[i]; p_system->sx[i] = p_system->fx[i]; p_system->sy[i] = p_system->fy[i]; p_system->sz[i] = p_system->fz[i]; } for(step = 1; step <= p_runset->em_steps && 0 == converged; ++ step) { // update coordinates on root processor if (root_process == my_id) { p_system->old_potential = p_system->potential[0]; for(i = 0; i < nAtoms; ++ i) { p_system->old_rx[i] = p_system->rx[i]; p_system->old_ry[i] = p_system->ry[i]; p_system->old_rz[i] = p_system->rz[i]; // fix metal coordinates? if (1 == p_metal->fix_pos && 1 == atom_info[i].is_metal) { continue; } p_system->rx[i] += p_system->sx[i] / p_system->f_max * p_runset->em_length; p_system->ry[i] += p_system->sy[i] / p_system->f_max * p_runset->em_length; p_system->rz[i] += p_system->sz[i] / p_system->f_max * p_runset->em_length; } // apply constraints rattle_1st(p_runset->dt, mol_info, atom_info, p_topol, p_system); // zero velocities for(i = 0; i < nAtoms; ++ i) { p_system->vx[i] = 0.0; p_system->vy[i] = 0.0; p_system->vz[i] = 0.0; } } // update forces mpi_force(p_task, p_topol, atom_info, mol_info, p_runset, p_metal, p_system, my_id, num_procs, time_used); if (root_process == my_id) { // check potential and fmax on root processor sum_potential(p_system->potential); get_fmax_rms(nAtoms, p_system); delta_pot = p_system->potential[0] - p_system->old_potential; if (delta_pot <= 0.0) { p_runset->em_length *= 1.2; } else { p_runset->em_length *= 0.2; } // print info and write to the gro file printf(" Step %-5d Fmax=%10.3e E=%15.8e\n", step, p_system->f_max, p_system->potential[0]); // write trajectories write_gro(file_gro, p_system, nAtoms, atom_info, step); write_binary(file_dat, p_system, nAtoms, step); if (p_metal->min >=0 && p_metal->max >= p_metal->min) { write_vec_pq(file_pq, p_metal, step); } // check convergence if (p_system->f_max < p_runset->em_tol && p_system->f_rms < p_runset->em_tol * 0.5 && fabs(delta_pot) < p_runset->em_tol * 0.1) { printf("\n"); printf(" F_max (%13.6e) smaller than %e\n", p_system->f_max, p_runset->em_tol); printf(" F_rms (%13.6e) smaller than %e\n", p_system->f_rms, p_runset->em_tol * 0.5); printf(" delta_E (%13.6e) smaller than %e\n", delta_pot, p_runset->em_tol * 0.1); printf("\n"); printf(" =========== Optimization converged ============\n"); converged = 1; } else { // update gamma for CG optimization // for steep descent, gamma = 0.0 if (0 == strcmp(p_runset->run_type, "cg")) { double g22 = 0.0; double g12 = 0.0; double g11 = 0.0; for(i = 0; i < nAtoms; ++ i) { g22 += p_system->fx[i] * p_system->fx[i] + p_system->fy[i] * p_system->fy[i] + p_system->fz[i] * p_system->fz[i]; g12 += p_system->old_fx[i] * p_system->fx[i] + p_system->old_fy[i] * p_system->fy[i] + p_system->old_fz[i] * p_system->fz[i]; g11 += p_system->old_fx[i] * p_system->old_fx[i] + p_system->old_fy[i] * p_system->old_fy[i] + p_system->old_fz[i] * p_system->old_fz[i]; } gamma = (g22 - g12) / g11; } for(i = 0; i < nAtoms; ++ i) { p_system->sx[i] = p_system->fx[i] + gamma * p_system->sx[i]; p_system->sy[i] = p_system->fy[i] + gamma * p_system->sy[i]; p_system->sz[i] = p_system->fz[i] + gamma * p_system->sz[i]; p_system->old_fx[i] = p_system->fx[i]; p_system->old_fy[i] = p_system->fy[i]; p_system->old_fz[i] = p_system->fz[i]; } } // communicate convergence for(an_id = 1; an_id < num_procs; ++ an_id) { ierr = MPI_Send(&converged, 1, MPI_INT, an_id, tag_99, MPI_COMM_WORLD); } } else { ierr = MPI_Recv(&converged, 1, MPI_INT, root_process, tag_99, MPI_COMM_WORLD, &status); } // exit loop if converged if (1 == converged) { break; } } } //=============================== // MD with nvt ensemble //=============================== else if (0 == strcmp(p_runset->run_type, "md")) { for (step = 1; step <= p_runset->nSteps; ++ step) { if (root_process == my_id) { // gradually increase the reference temperature if (step < p_runset->nHeating) { p_runset->ref_temp = p_system->first_temp + (p_system->ext_temp - p_system->first_temp) * step / p_runset->nHeating; } else { p_runset->ref_temp = p_system->ext_temp; } // update kT accordingly p_runset->kT = K_BOLTZ * p_runset->ref_temp; // thermostat for 1st half step if (0 == strcmp(p_runset->ensemble, "nvt")) { nose_hoover(p_runset, p_system, nAtoms); } // update velocity for 1st half step for(i = 0; i < nAtoms; ++ i) { // fix metal coordinates? if (1 == p_metal->fix_pos && 1 == atom_info[i].is_metal) { continue; } // for virtual sites, inv_mass == 0.0; double inv_mass = atom_info[i].inv_mass; p_system->vx[i] += p_runset->dt_2 * p_system->fx[i] * inv_mass; p_system->vy[i] += p_runset->dt_2 * p_system->fy[i] * inv_mass; p_system->vz[i] += p_runset->dt_2 * p_system->fz[i] * inv_mass; } // update position for the whole time step // using velocity at half time step for(i = 0; i < nAtoms; ++ i) { // fix metal coordinates? if (1 == p_metal->fix_pos && 1 == atom_info[i].is_metal) { continue; } p_system->old_rx[i] = p_system->rx[i]; p_system->old_ry[i] = p_system->ry[i]; p_system->old_rz[i] = p_system->rz[i]; p_system->rx[i] += p_runset->dt * p_system->vx[i]; p_system->ry[i] += p_runset->dt * p_system->vy[i]; p_system->rz[i] += p_runset->dt * p_system->vz[i]; } // apply constraints for the 1st half rattle_1st(p_runset->dt, mol_info, atom_info, p_topol, p_system); } // compute forces mpi_force(p_task, p_topol, atom_info, mol_info, p_runset, p_metal, p_system, my_id, num_procs, time_used); // update velocities if (root_process == my_id) { // sum potential energies sum_potential(p_system->potential); // update velocity for 2nd half step for(i = 0; i < nAtoms; ++ i) { // fix metal coordinates? if (1 == p_metal->fix_pos && 1 == atom_info[i].is_metal) { continue; } // for virtual sites, inv_mass == 0.0; double inv_mass = atom_info[i].inv_mass; p_system->vx[i] += p_runset->dt_2 * p_system->fx[i] * inv_mass; p_system->vy[i] += p_runset->dt_2 * p_system->fy[i] * inv_mass; p_system->vz[i] += p_runset->dt_2 * p_system->fz[i] * inv_mass; } // apply constraints for the 2nd half rattle_2nd(p_runset->dt, mol_info, atom_info, p_topol, p_system); // update temperature kinetic_energy(p_system, nAtoms, atom_info); // thermostat for 2nd half step if (0 == strcmp(p_runset->ensemble, "nvt")) { nose_hoover(p_runset, p_system, nAtoms); } // remove center of mass motion and update temperature remove_comm(nAtoms, atom_info, p_system); kinetic_energy(p_system, nAtoms, atom_info); // print information for this step if (0 == step % p_runset->nSave) { // apply PBC apply_pbc(nMols, mol_info, p_system->rx, p_system->ry, p_system->rz, p_system->box); get_fmax_rms(nAtoms, p_system); printf(" %10.3f Fmax=%.2e E=%.6e T=%.3e (%.1f)\n", p_runset->dt * step, p_system->f_max, p_system->potential[0], p_system->inst_temp, p_runset->ref_temp); // write to dat file write_binary(file_dat, p_system, nAtoms, step); } // regularly write to gro file if (0 == step % (p_runset->nSave*10)) { write_gro(file_gro, p_system, nAtoms, atom_info, step); if (p_metal->min >=0 && p_metal->max >= p_metal->min) { write_vec_pq(file_pq, p_metal, step); } } } } } //=========== Finalize MD ==================== sum_time_used(time_used, my_id, num_procs); if (root_process == my_id) { fclose(file_dat); fclose(file_pq); fclose(file_gro); #ifdef DEBUG int i; for(i = 0; i < nAtoms; ++ i) { printf(" f[%5d]= %12.5e, %12.5e, %12.5e\n", i, p_system->fx[i], p_system->fy[i], p_system->fz[i]); } #endif printf("\n"); printf("\n"); printf(" .------------- final potential energies --------------.\n"); printf("\n"); print_potential(p_system->potential); time_t end_t = time(NULL); gettimeofday(&tv, NULL); double end_time = (tv.tv_sec) + (tv.tv_usec) * 1.0e-6; printf(" Calculation ended normally at %s", ctime(&end_t)); printf(" %.3f seconds were used\n", end_time - start_time ); printf("\n"); printf("\n"); printf(" .------------------- time usage ----------------------.\n"); printf("\n"); analyze_time_used(time_used, num_procs); printf("\n"); } // free arrays for(an_id = 0; an_id < num_procs; ++ an_id) { free(time_used[an_id]); } free(time_used); free(p_metal->cpff_chg); free(p_metal->start_NP); free(p_metal->end_NP); free(data_bonded); data_bonded = NULL; for(mol = 0; mol < p_topol->mol_types; ++ mol) { int atom_i; for(atom_i = 0; atom_i < p_topol->atom_num[mol]; ++ atom_i) { free(p_topol->exclude[mol][atom_i]); } free(p_topol->exclude[mol]); free(p_topol->bond_param[mol]); free(p_topol->pair_param[mol]); free(p_topol->angle_param[mol]); free(p_topol->dihedral_param[mol]); free(p_topol->vsite_4[mol]); free(p_topol->vsite_funct[mol]); free(p_topol->constraint[mol]); } free(p_topol->exclude); free(p_topol->bond_param); free(p_topol->pair_param); free(p_topol->angle_param); free(p_topol->dihedral_param); p_topol->bond_param = NULL; p_topol->pair_param = NULL; p_topol->angle_param = NULL; p_topol->dihedral_param = NULL; free(p_topol->vsite_4); free(p_topol->vsite_funct); p_topol->vsite_4 = NULL; p_topol->vsite_funct = NULL; free(p_topol->constraint); p_topol->constraint = NULL; free(p_topol->mol_num); free(p_topol->atom_num); for(mol = 0; mol < p_topol->mol_types; ++ mol) { free(p_topol->atom_param[mol]); } free(p_topol->atom_param); if (p_metal->min >=0 && p_metal->max >= p_metal->min) { free(p_metal->inv_sqrt_dens); p_metal->inv_sqrt_dens = NULL; free(data_relay); //free(p_metal->mat_relay); data_relay = NULL; //p_metal->mat_relay = NULL; p_metal->vec_pq = NULL; p_metal->vec_ext = NULL; p_metal->diag_relay = NULL; } free(data_start_end); free(data_long_start_end); data_start_end = NULL; data_long_start_end = NULL; p_task->start_mol = NULL; p_task->end_mol = NULL; p_task->start_atom = NULL; p_task->end_atom = NULL; p_task->start_metal = NULL; p_task->end_metal = NULL; p_task->start_pair = NULL; p_task->end_pair = NULL; free(atom_info); free(mol_info); atom_info = NULL; mol_info = NULL; free(data_potential); data_potential = NULL; p_system->potential = NULL; p_system->partial_pot = NULL; free(p_system->virial); free(p_system->partial_vir); free(data_vir); free(p_system->box); p_system->box = NULL; free(data_rvf); data_rvf = NULL; p_system->rx = NULL; p_system->ry = NULL; p_system->rz = NULL; p_system->vx = NULL; p_system->vy = NULL; p_system->vz = NULL; p_system->fx = NULL; p_system->fy = NULL; p_system->fz = NULL; p_system->partial_fx = NULL; p_system->partial_fy = NULL; p_system->partial_fz = NULL; p_system->old_rx = NULL; p_system->old_ry = NULL; p_system->old_rz = NULL; p_system->old_fx = NULL; p_system->old_fy = NULL; p_system->old_fz = NULL; p_system->sx = NULL; p_system->sy = NULL; p_system->sz = NULL; free(p_topol->nonbonded_param); free(data_nonbonded); p_topol->nonbonded_param = NULL; data_nonbonded = NULL; free(p_task); free(p_system); free(p_topol); free(p_metal); free(p_runset); p_task = NULL; p_system = NULL; p_topol = NULL; p_metal = NULL; p_runset = NULL; ierr = MPI_Finalize(); if (ierr) {} return 0; }
void make_nlist_auto(int Nat,VECTOR* r,MATRIX3x3& H, double cellx,double celly,double cellz, double Roff,vector< vector<quartet> >& nlist){ /** \brief Create neighbor list (no manual maximal translation parameters) Implement improved Verlet list method (combination of Verlet list and linked cell list). Many ideas according to: Yao, Z.; Wang, J-S.; Liu, G-R. and Cheng, M "Improved neighbor list algorithm in molecular simulations using cell decomposition and data sorting method" Computer Physics Communications 2004, 161, 27-35 This function calculates the neighbor lists for all <Nat> atoms with original coordinates given by array <r> in simulation cell given by box <H> subject to periodic boundary conditions. The parameters <maxa>, <maxb> and <maxc> give the number of periodic translations of original simulation cell in each direction. In this version they are determined automatically from the shape of the simulation cell such that all real atoms interact with all other atoms and images within <Roff> distance Parameters <cellx>, <celly>, <cellz> give the size of the sub-cell used to accelerate in computations and make them scale as O(NlogN) Final results will then be stored in <nlist> array \param[in] Nat The number of atoms in the system \param[in] r The pointer to the array containing the coordinates of all atoms \param[in] H is the matrix describing the shape and size of the unit cell \param[in] cellx The size of the sub-cells in x direction \param[in] celly The size of the sub-cells in y direction \param[in] cellz The size of the sub-cells in z direction \param[in] Roff The cutoff distance which controls the formation of the neighbor list \param[in,out] nlist Indices of the neighboring sub-cells for all sub-cells ************************************************************************/ VECTOR t1,t2,t3,g1,g2,g3; VECTOR t;//,maxT; // Calculate constants double Roff2 = Roff * Roff; // Get cell translation vectors and their reciprocal // and find biggest vector in simulation cell H.get_vectors(t1,t2,t3); H.inverse().T().get_vectors(g1,g2,g3); // max_vector(H,maxT); vector<VECTOR> R; vector<quartet> initT; // initial translations VECTOR* tmp; tmp = new VECTOR[Nat]; apply_pbc(H,Nat,r,tmp,initT); // now all tmp in simulation cell, no outside triple minb,maxb; find_min_shell(t1,t2,t3,g1,g2,g3,Roff,minb,maxb); // cout<<"a in range ["<<minb.n1<<","<<maxb.n1<<"]\n"; // cout<<"b in range ["<<minb.n2<<","<<maxb.n2<<"]\n"; // cout<<"c in range ["<<minb.n3<<","<<maxb.n3<<"]\n"; // Bounding box is based on maximal atomic displacements int indx = 0; vector<quartet> globqt; VECTOR minr,maxr; minr = maxr = r[0]; for(int a=minb.n1;a<=maxb.n1;a++){ for(int b=minb.n2;b<=maxb.n2;b++){ for(int c=minb.n3;c<=maxb.n3;c++){ for(int i=0;i<Nat;i++){ t = tmp[i] + a*t1 + b*t2 + c*t3; R.push_back(t); if(t.x<=minr.x){ minr.x = t.x; } if(t.x>=maxr.x){ maxr.x = t.x; } if(t.y<=minr.y){ minr.y = t.y; } if(t.y>=maxr.y){ maxr.y = t.y; } if(t.z<=minr.z){ minr.z = t.z; } if(t.z>=maxr.z){ maxr.z = t.z; } quartet locqt; locqt.j = i; locqt.n1 = a; locqt.n2 = b; locqt.n3 = c; globqt.push_back(locqt); indx++; }// for i }// for c }// for b }// for a int sz = indx; // number of "complex atoms" = atom position + PBC translation // Number of partitions in corresponding direction VECTOR maxdr; maxdr = maxr - minr; int Nx = (floor(maxdr.x/cellx)+1); int Ny = (floor(maxdr.y/celly)+1); int Nz = (floor(maxdr.z/cellz)+1); int Ncells = Nx*Ny*Nz; // cout<<"Number of X partitions = "<<Nx<<endl; // cout<<"Number of Y partitions = "<<Ny<<endl; // cout<<"Number of Z partitions = "<<Nz<<endl; // cout<<"Number of cells = "<<Ncells<<endl; // exit(0); vector<int> dummy; vector<quartet> qdummy; vector<int> at2cell_indx(sz,-1); // is a complex index of the given atom vector< vector<int> > cell2at(Ncells,dummy);// cell2at[i] - contains complex indexes of all atoms in cell i // Clean the results array if(nlist.size()>0){ nlist.clear(); } nlist = std::vector< vector<quartet> >(Nat,qdummy); // Calculate neighbors of each cell (sub-cell) // we use serial indexes of both central cell and its neighbors vector< vector<int> > neibc(Ncells,dummy); // indexes of neighboring cells for given cell index for(int c=0;c<Ncells;c++){ // vector<int> neibc_c; form_neibc(c,neibc[c],Nx,Ny,Nz,cellx,celly,cellz,Roff); // neibc.push_back(neibc_c); // Check indexes of neighbor cells //for(int nc=0;nc<neibc_c.size();nc++){ // if(neibc_c[nc]>=Ncells){ cout<<"Hey! Potential problem\n"; } //} } // Calculate mappings between atom indexes and cell (sub-cell) indexes for(int i=0;i<sz;i++){ VECTOR diff = R[i] - minr; // position of triple trp; trp.n1 = floor(diff.x/cellx); trp.n2 = floor(diff.y/celly); trp.n3 = floor(diff.z/cellz); c = Nz*Ny*trp.n1 + Nz*trp.n2 + trp.n3; at2cell_indx[i] = c; cell2at[c].push_back(i); } int cc = (maxb.n3-minb.n3+1)*(maxb.n2-minb.n2+1)*(-minb.n1) + (maxb.n3-minb.n3+1)*(-minb.n2) + (-minb.n3); // index of central cell for(int at_indx1=0;at_indx1<Nat;at_indx1++){ int i1 = Nat*cc+at_indx1; //complex index of real atom at_indx1 int ci1 = at2cell_indx[i1]; // complex index of cell to which atom i belongs int sz1 = neibc[ci1].size();// number of neighbor cells of the cell with index ci1 int newsize = 0; for(int c=0;c<sz1;c++){ int ci2 = neibc[ci1][c]; // one of the neighboring cell of cell l int sz2 = cell2at[ci2].size();// number of atoms in the cell with index ci2 newsize += sz2; // total possible(max) number of neighbor complex atoms } if(nlist[at_indx1].capacity()<=newsize){ nlist[at_indx1].reserve(newsize); } for(c=0;c<sz1;c++){ int ci2 = neibc[ci1][c]; // one of the neighboring cell of cell l int sz2 = cell2at[ci2].size();// number of atoms in the cell with index ci2 for(int a=0;a<sz2;a++){ // iterations over atoms in cell ci2 int i2 = cell2at[ci2][a]; // complex intex of atom a of the cell ci2 int at_indx2 = i2 % Nat; // real index of atom, corresponding to the atom with complex index i2 if(at_indx2>=at_indx1){ VECTOR dR = R[i1] - R[i2]; double modR = dR.x*dR.x; if(modR<=Roff2){ modR += dR.y*dR.y; if(modR<=Roff2){ modR += dR.z*dR.z; if(modR<=Roff2){ quartet qt; qt = globqt[i2]; qt.n1 += (initT[at_indx1].n1 - initT[at_indx2].n1); qt.n2 += (initT[at_indx1].n2 - initT[at_indx2].n2); qt.n3 += (initT[at_indx1].n3 - initT[at_indx2].n3); qt.is_central = 0; if(qt.n1==0 && qt.n2==0 && qt.n3==0) { qt.is_central = 1; } nlist[at_indx1].push_back(qt); }//zik }//yik }//xik }// at_indx2>=at_indx1 }// for a }// for c }// for at_indx1 delete [] tmp; }