// ATC Headers #include "ExtrinsicModelElectrostatic.h" #include "PhysicsModel.h" #include "ATC_Error.h" #include "FieldEulerIntegrator.h" #include "ATC_Coupling.h" #include "LammpsInterface.h" #include "PrescribedDataManager.h" #include "PoissonSolver.h" #include "PerAtomQuantityLibrary.h" #include "AtomToMoleculeTransfer.h" #include "MoleculeSet.h" #include "ChargeRegulator.h" #include using std::string; using std::vector; using std::map; using std::pair; using std::set; namespace ATC { //-------------------------------------------------------- //-------------------------------------------------------- // Class ExtrinsicModelElectrostatic //-------------------------------------------------------- //-------------------------------------------------------- //-------------------------------------------------------- // Constructor //-------------------------------------------------------- ExtrinsicModelElectrostatic::ExtrinsicModelElectrostatic (ExtrinsicModelManager * modelManager, ExtrinsicModelType modelType, string matFileName) : ExtrinsicModel(modelManager,modelType,matFileName), poissonSolverType_(DIRECT), // ITERATIVE | DIRECT poissonSolverTol_(0), poissonSolverMaxIter_(0), poissonSolver_(NULL), maxSolves_(0), baseSize_(0), chargeRegulator_(NULL), useSlab_(false), includeShortRange_(true), atomForces_(NULL), nodalAtomicCharge_(NULL), nodalAtomicGhostCharge_(NULL) { physicsModel_ = new PhysicsModelSpeciesElectrostatic(matFileName); // set up correct masks for coupling rhsMaskIntrinsic_.reset(NUM_FIELDS,NUM_FLUX); rhsMaskIntrinsic_ = false; if (atc_->track_charge()) { if (! chargeRegulator_) chargeRegulator_ = new ChargeRegulator(atc_); } } //-------------------------------------------------------- // Destructor //-------------------------------------------------------- ExtrinsicModelElectrostatic::~ExtrinsicModelElectrostatic() { if (poissonSolver_) delete poissonSolver_; if (chargeRegulator_) delete chargeRegulator_; } //-------------------------------------------------------- // modify //-------------------------------------------------------- bool ExtrinsicModelElectrostatic::modify(int narg, char **arg) { bool match = false; int argIndx = 0; /** */ if (strcmp(arg[argIndx],"poisson_solver")==0) { argIndx++; if (strcmp(arg[argIndx],"max_solves")==0) { argIndx++; maxSolves_ = atoi(arg[argIndx]) ; } else if (strcmp(arg[argIndx],"tolerance")==0) { argIndx++; poissonSolverTol_ = atof(arg[argIndx]); } else if (strcmp(arg[argIndx],"max_iterations")==0) { argIndx++; poissonSolverMaxIter_ = atoi(arg[argIndx]); } else if (strcmp(arg[argIndx],"iterative")==0) { poissonSolverType_ = ITERATIVE; } else { poissonSolverType_ = DIRECT; } match = true; } // end "poisson_solver" /** creates fixed charge on faceset units on surface charge density are lammps charge units / lammps length units ^ 2 fix_modify ATC extrinsic fix_charge faceset_id value */ #ifdef CHARGED_SURFACE else if (strcmp(arg[argIndx],"fix_charge")==0) { argIndx++; string facesetName(arg[argIndx]); argIndx++; double chargeDensity = atof(arg[argIndx]); surfaceCharges_[facesetName] = chargeDensity; match = true; } /** */ else if (strcmp(arg[argIndx],"unfix_charge")==0) { argIndx++; string fsetName(arg[argIndx]); throw ATC_Error("Ability to unfix charge not yet implemented"); match = true; } #endif else if (strcmp(arg[argIndx],"control")==0) { argIndx++; if (strcmp(arg[argIndx],"charge")==0) { argIndx++; if (!atc_->track_charge()) throw ATC_Error("must have charges to regulate"); match = chargeRegulator_->modify(narg-argIndx,&arg[argIndx]); } } /** switch to use slabbing */ else if (strcmp(arg[argIndx],"slab")==0) { argIndx++; if (strcmp(arg[argIndx],"on")==0) { useSlab_ = true; match = true; } else if (strcmp(arg[argIndx],"off")==0) { useSlab_ = false; match = true; } } /** switch to account for short range interaces */ else if (strcmp(arg[argIndx],"short_range")==0) { argIndx++; if (strcmp(arg[argIndx],"on")==0) { includeShortRange_ = true; match = true; } else if (strcmp(arg[argIndx],"off")==0) { includeShortRange_ = false; match = true; } } return match; } //-------------------------------------------------------- // initialize //-------------------------------------------------------- void ExtrinsicModelElectrostatic::construct_transfers() { // add charge density transfer operator if (atc_->track_charge()) { InterscaleManager & interscaleManager(atc_->interscale_manager()); // make sure we have gradients at atoms VectorDependencyManager * interpolantGradient = interscaleManager.vector_sparse_matrix("InterpolantGradient"); if (!interpolantGradient) { interpolantGradient = new PerAtomShapeFunctionGradient(atc_); interscaleManager.add_vector_sparse_matrix(interpolantGradient, "InterpolantGradient"); } FundamentalAtomQuantity * atomicCharge = interscaleManager.fundamental_atom_quantity(LammpsInterface::ATOM_CHARGE); AtfShapeFunctionRestriction * nodalAtomicCharge = new AtfShapeFunctionRestriction(atc_,atomicCharge,atc_->accumulant()); interscaleManager.add_dense_matrix(nodalAtomicCharge,"NodalAtomicCharge"); AtfShapeFunctionMdProjection * nodalAtomicChargeDensity = new AtfShapeFunctionMdProjection(atc_,nodalAtomicCharge,MASS_DENSITY); interscaleManager.add_dense_matrix(nodalAtomicChargeDensity,"NodalAtomicChargeDensity"); // get the total charge and dipole moment at the node per molecule // small molecules require per atom quantities with ghosts const map > & moleculeIds(atc_->molecule_ids()); map >::const_iterator molecule; PerAtomQuantity * atomProcGhostCoarseGrainingPositions = interscaleManager.per_atom_quantity("AtomicProcGhostCoarseGrainingPositions"); FundamentalAtomQuantity * charge = interscaleManager.fundamental_atom_quantity(LammpsInterface::ATOM_CHARGE, PROC_GHOST); for (molecule = moleculeIds.begin(); molecule != moleculeIds.end(); molecule++) { const string moleculeName = molecule->first; SmallMoleculeSet * smallMoleculeSet = interscaleManager.small_molecule_set(moleculeName); // calculate nodal charge from the molecules AtomToSmallMoleculeTransfer * moleculeCharge = new AtomToSmallMoleculeTransfer(atc_,charge,smallMoleculeSet); interscaleManager.add_dense_matrix(moleculeCharge,"MoleculeCharge"+moleculeName); MotfShapeFunctionRestriction * nodalAtomicMoleculeCharge = new MotfShapeFunctionRestriction(moleculeCharge, interscaleManager.sparse_matrix("ShapeFunction"+moleculeName)); interscaleManager.add_dense_matrix(nodalAtomicMoleculeCharge,"NodalMoleculeCharge"+moleculeName); AtfShapeFunctionMdProjection * nodalAtomicMoleculeChargeDensity = new AtfShapeFunctionMdProjection(atc_,nodalAtomicMoleculeCharge,MASS_DENSITY); interscaleManager.add_dense_matrix(nodalAtomicMoleculeChargeDensity,"NodalMoleculeChargeDensity"+moleculeName); // dipole moment density // calculate the dipole moment of the molecules SmallMoleculeCentroid * moleculeCentroid = static_cast(interscaleManager.dense_matrix("MoleculeCentroid"+moleculeName)); SmallMoleculeDipoleMoment * dipoleMoment = new SmallMoleculeDipoleMoment(atc_,charge,smallMoleculeSet,atomProcGhostCoarseGrainingPositions,moleculeCentroid); interscaleManager.add_dense_matrix(dipoleMoment,"DipoleMoment"+moleculeName); MotfShapeFunctionRestriction * nodalAtomicMoleculeDipole = new MotfShapeFunctionRestriction(dipoleMoment, interscaleManager.sparse_matrix("ShapeFunction"+moleculeName)); interscaleManager.add_dense_matrix(nodalAtomicMoleculeDipole,"NodalMoleculeDipole"+moleculeName); AtfShapeFunctionMdProjection * nodalAtomicMoleculeDipoleDensity = new AtfShapeFunctionMdProjection(atc_,nodalAtomicMoleculeDipole,MASS_DENSITY); interscaleManager.add_dense_matrix(nodalAtomicMoleculeDipoleDensity,"NodalMoleculeDipoleDensity"+moleculeName); } } } //-------------------------------------------------------- // initialize //-------------------------------------------------------- void ExtrinsicModelElectrostatic::initialize() { ExtrinsicModel::initialize(); InterscaleManager & interscaleManager = atc_->interscale_manager(); int nNodes = atc_->num_nodes(); atomForces_ = interscaleManager.fundamental_atom_quantity(LammpsInterface::ATOM_FORCE); rhs_[ELECTRIC_POTENTIAL].reset(nNodes,1); #ifdef CHARGED_SURFACE // set fixed potential surfaces form charged surfaces map::const_iterator isurface; for (isurface = surfaceCharges_.begin(); isurface != surfaceCharges_.end(); isurface++) add_charged_surface(isurface->first,isurface->second); #endif // set up poisson solver rhsMask_.reset(NUM_FIELDS,NUM_FLUX); rhsMask_ = false; for (int i = 0; i < NUM_FLUX; i++) { rhsMask_(ELECTRIC_POTENTIAL,i) = atc_->fieldMask_(ELECTRIC_POTENTIAL,i); } rhsMask_(ELECTRIC_POTENTIAL,FLUX) = false;// for poisson solve & rhs compute // need to create the bcs for the solver to configure properly atc_->set_fixed_nodes(); if (poissonSolver_) delete poissonSolver_; int type = ATC::LinearSolver::ITERATIVE_SOLVE_SYMMETRIC; if (poissonSolverType_ == DIRECT) { type = ATC::LinearSolver::DIRECT_SOLVE; } poissonSolver_ = new PoissonSolver(ELECTRIC_POTENTIAL, physicsModel_, atc_->feEngine_, atc_->prescribedDataMgr_, atc_, rhsMask_,type, true); if (poissonSolverTol_) poissonSolver_->set_tolerance(poissonSolverTol_); if (poissonSolverMaxIter_) poissonSolver_->set_max_iterations(poissonSolverMaxIter_); poissonSolver_->initialize(); // initialize localized Green's function for FE electric field correction if (atc_->track_charge() && includeShortRange_) { greensFunctions_.reserve(nNodes); // set up Green's function per node for (int i = 0; i < nNodes; i++) { set localNodes; for (int j = 0; j < nNodes; j++) localNodes.insert(j); // call Poisson solver to get Green's function for node i DENS_VEC globalGreensFunction; poissonSolver_->greens_function(i,globalGreensFunction); // store green's functions as sparse vectors only on local nodes set::const_iterator thisNode; SparseVector sparseGreensFunction(nNodes); for (thisNode = localNodes.begin(); thisNode != localNodes.end(); thisNode++) sparseGreensFunction(*thisNode) = globalGreensFunction(*thisNode); greensFunctions_.push_back(sparseGreensFunction); } } if (atc_->track_charge()) { double * q = LammpsInterface::instance()->atom_charge(); if (!q) throw ATC_Error(" charge tracking requested but charge pointer is null"); nodalAtomicCharge_ = interscaleManager.dense_matrix("NodalAtomicCharge"); if (! nodalAtomicCharge_) { FundamentalAtomQuantity * atomCharge = interscaleManager.fundamental_atom_quantity(LammpsInterface::ATOM_CHARGE); nodalAtomicCharge_ = new AtfShapeFunctionRestriction(atc_,atomCharge, atc_->accumulant()); interscaleManager.add_dense_matrix(nodalAtomicCharge_,"NodalAtomicCharge"); } if (atc_->groupbitGhost_) { nodalAtomicGhostCharge_ = interscaleManager.dense_matrix("NodalAtomicGhostCharge"); if (! nodalAtomicGhostCharge_) { FundamentalAtomQuantity * ghostCharge = interscaleManager.fundamental_atom_quantity(LammpsInterface::ATOM_CHARGE, GHOST); PerAtomSparseMatrix * ghostShapeFunctions = interscaleManager.per_atom_sparse_matrix("InterpolantGhost"); if (!ghostShapeFunctions) { ghostShapeFunctions = new PerAtomShapeFunction(atc_, interscaleManager.per_atom_quantity("AtomicGhostCoarseGrainingPositions"), interscaleManager.per_atom_int_quantity("AtomGhostElement"), GHOST); interscaleManager.add_per_atom_sparse_matrix(ghostShapeFunctions,"InterpolantGhost"); } nodalAtomicGhostCharge_ = new AtfShapeFunctionRestriction(atc_,ghostCharge, ghostShapeFunctions); interscaleManager.add_dense_matrix(nodalAtomicGhostCharge_,"NodalAtomicGhostCharge"); } } } if (chargeRegulator_) { if (! poissonSolver_) throw ATC_Error("passing of Poisson solver from ExtrinsicModelElectrostatic to ChargeRegulator failed"); chargeRegulator_->assign_poisson_solver(poissonSolver_); chargeRegulator_->construct_methods(); chargeRegulator_->initialize(); } // set initial force post_force(); } //-------------------------------------------------------- // pre final integration //-------------------------------------------------------- void ExtrinsicModelElectrostatic::post_init_integrate() { if (chargeRegulator_) chargeRegulator_->apply_pre_force(atc_->dt()); } //-------------------------------------------------------- // post force //-------------------------------------------------------- void ExtrinsicModelElectrostatic::post_force() { if (chargeRegulator_) chargeRegulator_->apply_post_force(atc_->dt()); // add in correction accounting for lumped mass matrix in charge density // in atomistic part of domain & account for physics model fluxes,resets rhs // set Dirchlet data atc_->set_fixed_nodes(); // set sources (atc_->prescribed_data_manager())->set_sources(atc_->time()+0.5*(atc_->dt()),atc_->sources()); // compute Poisson equation RHS sources atc_->compute_rhs_vector(rhsMask_, atc_->fields_, rhs_, atc_->source_integration(), physicsModel_); // add atomic charges to rhs DENS_MAT & rhs = rhs_[ELECTRIC_POTENTIAL].set_quantity(); if (atc_->track_charge()) { rhs += nodalAtomicCharge_->quantity(); if (nodalAtomicGhostCharge_) { rhs += nodalAtomicGhostCharge_->quantity(); } } // solve poisson eqn for electric potential // electron charge density added to Poisson RHS in solver DENS_MAT & potential = (atc_->field(ELECTRIC_POTENTIAL)).set_quantity(); if ( maxSolves_ == 0 || (atc_->local_step() < maxSolves_) ) { //potential.print("POT"); // rhs.print("RHS"); bool converged = poissonSolver_->solve(potential,rhs); if (! converged ) throw ATC_Error("Poisson solver did not converge in ExtrinsicModelElectrostatic"); } // do this for intrinsic charges or effective electron charges at atoms if (atc_->track_charge() || ( LammpsInterface::instance()->atom_charge() && atc_->source_atomic_quadrature(ELECTRIC_POTENTIAL) ) ) { _atomElectricalForce_.resize(atc_->nlocal(),atc_->nsd()); add_electrostatic_forces(potential); #ifdef CHARGED_SURFACE if (includeShortRange_) apply_charged_surfaces(potential); #endif InterscaleManager & interscaleManager_ = atc_->interscale_manager(); atomForces_ = interscaleManager_.fundamental_atom_quantity(LammpsInterface::ATOM_FORCE); (*atomForces_) += _atomElectricalForce_; // f_E in ours, f in lammps ultimately } } //-------------------------------------------------------- // output //-------------------------------------------------------- void ExtrinsicModelElectrostatic::output(OUTPUT_LIST & outputData) { double scale = 1./(LammpsInterface::instance()->ftm2v()); double localF[3]; if (_atomElectricalForce_.nRows() > 0) { localF[0] = scale*(_atomElectricalForce_).col_sum(0); localF[1] = scale*(_atomElectricalForce_).col_sum(1); localF[2] = scale*(_atomElectricalForce_).col_sum(2); } else { localF[0] = 0.;localF[1] = 0.; localF[2] = 0.; } LammpsInterface::instance()->allsum(localF,totalElectricalForce_,3); if (LammpsInterface::instance()->rank_zero()) { atc_->feEngine_->add_global("electrostatic_force_x", totalElectricalForce_[0]); atc_->feEngine_->add_global("electrostatic_force_y", totalElectricalForce_[1]); atc_->feEngine_->add_global("electrostatic_force_z", totalElectricalForce_[2]); } // add in FE fields related to charge FIELDS & fields(atc_->fields()); FIELDS::const_iterator rhoField = fields.find(CHARGE_DENSITY); if (rhoField!=fields.end()) { InterscaleManager & interscaleManager(atc_->interscale_manager()); const DENS_MAN * atomicChargeDensity(interscaleManager.dense_matrix("NodalAtomicChargeDensity")); atc_->nodal_atomic_field(CHARGE_DENSITY) = atomicChargeDensity->quantity(); fields[CHARGE_DENSITY] = atomicChargeDensity->quantity(); DENS_MAT & chargeDensity(fields[CHARGE_DENSITY].set_quantity()); DENS_MAT & nodalAtomicChargeDensity((atc_->nodal_atomic_field(CHARGE_DENSITY)).set_quantity()); if ((atc_->lammps_interface())->rank_zero()) { outputData["charge_density"] = &chargeDensity; outputData["NodalAtomicChargeDensity"] = &nodalAtomicChargeDensity; } } if (fields.find(ELECTRON_DENSITY)==fields.end()) { fields[ELECTRON_DENSITY].reset(fields[CHARGE_DENSITY].nRows(),1); DENS_MAT & electronDensity(fields[ELECTRON_DENSITY].set_quantity()); if ((atc_->lammps_interface())->rank_zero()) { outputData["electron_density"] = &electronDensity; } } const map > & moleculeIds(atc_->molecule_ids()); map >::const_iterator molecule; for (molecule = moleculeIds.begin(); molecule != moleculeIds.end(); molecule++) { // net charge DENS_MAN & nodalMoleculeChargeDensityOut(atc_->tagged_dens_man("NodalMoleculeChargeDensity"+molecule->first)); DENS_MAN * nodalMoleculeChargeDensity((atc_->interscale_manager()).dense_matrix("NodalMoleculeChargeDensity"+molecule->first)); nodalMoleculeChargeDensityOut = nodalMoleculeChargeDensity->quantity(); // dipole moment DENS_MAN & nodalMoleculeDipoleDensityOut(atc_->tagged_dens_man("NodalMoleculeDipoleDensity"+molecule->first)); DENS_MAN * nodalMoleculeDipoleDensity((atc_->interscale_manager()).dense_matrix("NodalMoleculeDipoleDensity"+molecule->first)); nodalMoleculeDipoleDensityOut = nodalMoleculeDipoleDensity->quantity(); } if(chargeRegulator_) chargeRegulator_->output(outputData); } //-------------------------------------------------------- // size_vector //-------------------------------------------------------- int ExtrinsicModelElectrostatic::size_vector(int intrinsicSize) { baseSize_ = intrinsicSize; return 5; } //-------------------------------------------------------- // compute_scalar : added energy = - f.x //-------------------------------------------------------- double ExtrinsicModelElectrostatic::compute_scalar(void) { //((atc_->interscale_manager()).fundamental_atom_quantity(LammpsInterface::ATOM_POSITION))->force_reset(); const DENS_MAT & atomPosition = ((atc_->interscale_manager()).fundamental_atom_quantity(LammpsInterface::ATOM_POSITION))->quantity(); double local_fdotx = 0, fdotx; for (int i = 0; i < _atomElectricalForce_.nRows() ; i++) { for (int j = 0; j < _atomElectricalForce_.nCols() ; j++) { local_fdotx -= _atomElectricalForce_(i,j)*atomPosition(i,j); } } LammpsInterface::instance()->allsum(&local_fdotx,&fdotx,1); // convert fdotx *= LammpsInterface::instance()->mvv2e(); return fdotx; } //-------------------------------------------------------- // compute_vector //-------------------------------------------------------- bool ExtrinsicModelElectrostatic::compute_vector(int n, double & value) { if (n == baseSize_) { double nSum = ((atc_->field(ELECTRON_DENSITY)).quantity()).col_sum(); value = nSum; return true; } else if (n > baseSize_ && n < baseSize_+4) { int dof = n-baseSize_-1; double localF = (_atomElectricalForce_).col_sum(dof), F=0; LammpsInterface::instance()->allsum(&localF,&F,1); double ftm2v = LammpsInterface::instance()->ftm2v(); value = F/ftm2v; return true; } else if (n == baseSize_+4) { double nSum = ((atc_->field(ELECTRIC_POTENTIAL)).quantity()).col_sum(); value = nSum; return true; } return false; } //-------------------------------------------------------- // add_electrostatic_forces //-------------------------------------------------------- void ExtrinsicModelElectrostatic::add_electrostatic_forces (MATRIX & potential) { //double qE2f = LammpsInterface::instance()->qe2f(); double qV2e = LammpsInterface::instance()->qv2e(); // charge volts to our energy units //double ** f = LammpsInterface::instance()->fatom(); double * q = LammpsInterface::instance()->atom_charge(); // f_ai = \sum_IJ N_Ia Bi_IJ phi_J = \sum_I N_Ia Ei_I int nsd = atc_->nsd(); int nLocal = atc_->nlocal(); DENS_MAT E(nLocal,nsd); const SPAR_MAT_VEC & shapeFucntionDerivatives(((atc_->interscale_manager()).vector_sparse_matrix("InterpolantGradient"))->quantity()); if (nLocal > 0) { for (int i=0; i < nsd; i++) { CLON_VEC Ei = column(E,i); Ei = -1.*(*(shapeFucntionDerivatives[i])*potential); } } int dimOffset = 0; if (useSlab_) dimOffset = nsd - 1; for (int i = 0; i < nLocal; i++) { int atomIdx = atc_->internalToAtom_(i); double c = qV2e*q[atomIdx]; for (int j = 0; j < dimOffset; j ++) _atomElectricalForce_(i,j) = 0.; for (int j = dimOffset; j < nsd; j ++) _atomElectricalForce_(i,j) = c*E(i,j); } // correct field for short range interactions if (includeShortRange_) correct_electrostatic_forces(); } //-------------------------------------------------------- // correct_electrostatic_forces //-------------------------------------------------------- void ExtrinsicModelElectrostatic::correct_electrostatic_forces() { // compute restricted sparse shape function set for each atom // to account for its Green's Function //double qE2f = LammpsInterface::instance()->qe2f(); double qV2e = LammpsInterface::instance()->qv2e(); double * q = LammpsInterface::instance()->atom_charge(); vector > atomicFePotential; int nLocal = atc_->nlocal(); int nGhostLammps = LammpsInterface::instance()->nghost(); int nLocalLammps = LammpsInterface::instance()->nlocal(); int nLocalTotal = nLocalLammps + nGhostLammps; // total number of atoms on this processor atomicFePotential.reserve(nLocalTotal); SparseVector dummy(atc_->num_nodes()); for (int i = 0; i < nLocalTotal; i++) atomicFePotential.push_back(dummy); // compute local potential contributions from atoms on this processor InterscaleManager & interscaleManager(atc_->interscale_manager()); const SPAR_MAT & myShpFcn((interscaleManager.per_atom_sparse_matrix("Interpolant"))->quantity()); for (int i = 0; i < nLocal; i++) { DenseVector nodeIndices; DENS_VEC nodeValues; myShpFcn.row(i,nodeValues,nodeIndices); int atomIdx = atc_->internalToAtom_(i); //double c = qE2f*q[atomIdx]; //double c = qV2e*q[atomIdx]; //nodeValues *= c; nodeValues *= q[atomIdx]; for (int j = 0; j < nodeIndices.size(); j++) atomicFePotential[atomIdx].add_scaled(greensFunctions_[nodeIndices(j)],nodeValues(j)); } // compute local potential contribtutions for lammps ghost atoms // which are known to ATC, // this will grab both processor and periodic neighbors, // so we need to add in neighbor contributions using lammps indices // rather than atc indices or we could potentially // double count periodic contributions double ** xatom = LammpsInterface::instance()->xatom(); const int * mask = LammpsInterface::instance()->atom_mask(); int nodesPerElement = ((atc_->feEngine_)->fe_mesh())->num_nodes_per_element(); int nsd = atc_->nsd(); for (int i = nLocalLammps; i < nLocalTotal; i++) { if (mask[i] & atc_->groupbit_) { DENS_VEC coords(nsd); coords.copy(xatom[i],nsd); Array nodeIndices(nodesPerElement); DENS_VEC nodeValues(nodesPerElement); (atc_->feEngine_)->shape_functions(coords,nodeValues,nodeIndices); //double c = qV2e*q[i]; //nodeValues *= c; nodeValues *= q[i]; for (int j = 0; j < nodeIndices.size(); j++) { atomicFePotential[i].add_scaled(greensFunctions_[nodeIndices(j)],nodeValues(j)); } } } // Get sparse vectors of derivatives at each atom // to compute this only when the shape functions change vector > > atomicDerivatives; atomicDerivatives.reserve(nLocal); for (int i = 0; i < nLocal; i++) { // determine shape function derivatives at atomic location // and construct sparse vectors to store derivative data vector > derivativeVectors; derivativeVectors.reserve(nsd); for (int j = 0; j < nsd; j++) derivativeVectors.push_back(dummy); atomicDerivatives.push_back(derivativeVectors); InterscaleManager & interscaleManager(atc_->interscale_manager()); const SPAR_MAT_VEC & shapeFucntionDerivatives((interscaleManager.vector_sparse_matrix("InterpolantGradient"))->quantity()); for (int j = 0; j < nsd; j++) { DenseVector nodeIndices; DENS_VEC nodeValues; shapeFucntionDerivatives[j]->row(i,nodeValues,nodeIndices); for (int k = 0; k < nodeIndices.size(); k++) atomicDerivatives[i][j](nodeIndices(k)) = nodeValues(k); } } // loop over all atoms and correct their efield based on all their // neighbor's local efield response // need to use specific coulombic cutoff from different pairs // see pair_coul_cut for an example of the data structures // unfortunately don't know how to get at this data in general // beyond a cast from the LAMMPS pair object (see force.h). // Until this is fixed, only use this method with the coulombic force // the same for all pairs and equal to the largest force cutoff. // Probably the best fix is to implement our own pair style for this. double cutoffRadius = LammpsInterface::instance()->pair_cutoff(); double cutoffSq = cutoffRadius*cutoffRadius; int inum = LammpsInterface::instance()->neighbor_list_inum(); int * ilist = LammpsInterface::instance()->neighbor_list_ilist(); int * numneigh = LammpsInterface::instance()->neighbor_list_numneigh(); int ** firstneigh = LammpsInterface::instance()->neighbor_list_firstneigh(); // loop over neighbors of my atoms for (int ii = 0; ii < inum; ii++) { int i = ilist[ii]; if (mask[i] & atc_->groupbit_) { double xtmp = xatom[i][0]; double ytmp = xatom[i][1]; double ztmp = xatom[i][2]; int * jlist = firstneigh[i]; int jnum = numneigh[i]; for (int jj = 0; jj < jnum; jj++) { int j = jlist[jj]; if (mask[j] & atc_->groupbit_) { //double factor_coul = LammpsInterface::instance()->coulomb_factor(j); LammpsInterface::instance()->neighbor_remap(j); double delx = xtmp - xatom[j][0]; double dely = ytmp - xatom[j][1]; double delz = ztmp - xatom[j][2]; double rsq = delx*delx + dely*dely + delz*delz; if (rsq < cutoffSq) { DENS_VEC efield(nsd); efield = 0.; int atcIdx = atc_->atomToInternal_[i]; for (int k = 0; k < nsd; k++) efield(k) = -1.*dot(atomicDerivatives[atcIdx][k],atomicFePotential[j]); // apply correction in atomic forces //double c = factor_coul*qE2f*q[i]; //double c = factor_coul*qV2e*q[i]; double c = qV2e*q[i]; for (int k = 0; k < nsd; k++) { if ((!useSlab_) || (k==nsd)) { //f[i][k] -= c*efield(k); _atomElectricalForce_(atcIdx,k) -= c*efield(k); } } } } } } } } #ifdef CHARGED_SURFACE //-------------------------------------------------------- // add_charged_surface //-------------------------------------------------------- void ExtrinsicModelElectrostatic::add_charged_surface(const string & facesetName, const double chargeDensity) { // get faceset information int nNodes = atc_->num_nodes(); const FE_Mesh * feMesh = (atc_->feEngine_)->fe_mesh(); const set< pair > * faceset = & ( feMesh->faceset(facesetName)); // set face sources to all point at one function for use in integration SURFACE_SOURCE faceSources; XT_Function * f = XT_Function_Mgr::instance()->constant_function(1.); set< pair >::const_iterator iset; for (iset = faceset->begin(); iset != faceset->end(); iset++) { pair face = *iset; // allocate Array < XT_Function * > & dof = faceSources[ELECTRIC_POTENTIAL][face]; dof.reset(1); dof(0) = f; } // Get associated nodeset set nodeset; feMesh->faceset_to_nodeset(facesetName,nodeset); // Get coordinates of each node in face set map > & myFaceset = chargedSurfaces_[facesetName]; set::const_iterator myNode; for (myNode = nodeset.begin(); myNode != nodeset.end(); myNode++) { DENS_VEC myCoords = feMesh->nodal_coordinates(*myNode); pair myPair(myCoords,0.); myFaceset[*myNode] = myPair; } // computed integrals of nodal shape functions on face FIELDS nodalFaceWeights; nodalFaceWeights[ELECTRIC_POTENTIAL].reset(nNodes,1); Array fieldMask(NUM_FIELDS); fieldMask(ELECTRIC_POTENTIAL) = true; (atc_->feEngine_)->add_fluxes(fieldMask,0.,faceSources,nodalFaceWeights); // set up data structure holding charged faceset information FIELDS sources; double coulombConstant = LammpsInterface::instance()->coulomb_constant(); map >::iterator myNodeData; for (myNodeData = myFaceset.begin(); myNodeData != myFaceset.end(); myNodeData++) { // evaluate voltage at each node I // set up X_T function for integration: k*chargeDensity/||x_I - x_s|| // integral is approximated in two parts: // 1) near part with all faces within r < rcrit evaluated as 2 * pi * rcrit * k sigma A/A0, A is area of this region and A0 = pi * rcrit^2, so 2 k sigma A / rcrit // 2) far part evaluated using Gaussian quadrature on faceset double rcritSq = LammpsInterface::instance()->pair_cutoff(); rcritSq *= rcritSq; int nodalIndex = myNodeData->first; DENS_VEC myCoords((myNodeData->second).first); double xtArgs[8]; xtArgs[0] = myCoords(0); xtArgs[1] = myCoords(1); xtArgs[2] = myCoords(2); xtArgs[3] = 1.; xtArgs[4] = 1.; xtArgs[5] = 1.; xtArgs[6] = coulombConstant*chargeDensity; xtArgs[7] = -1.; string radialPower = "radial_power"; f = XT_Function_Mgr::instance()->function(radialPower,8,xtArgs); for (iset = faceset->begin(); iset != faceset->end(); iset++) { pair face = *iset; // allocate Array < XT_Function * > & dof = faceSources[ELECTRIC_POTENTIAL][face]; dof.reset(1); dof(0) = f; } // perform integration to get quantities at nodes on facesets // V_J' = int_S N_J k*sigma/|x_I - x_s| dS sources[ELECTRIC_POTENTIAL].reset(nNodes,1); (atc_->feEngine_)->add_fluxes(fieldMask,0.,faceSources,sources); double myPotential = 0.; // sum over all nodes in faceset to get total potential: // V_I = sum_J VJ' const DENS_MAT & myPotentialSource(sources[ELECTRIC_POTENTIAL].quantity()); nodalChargePotential_[facesetName][nodalIndex] = myPotentialSource(nodalIndex,0); for (myNode = nodeset.begin(); myNode != nodeset.end(); myNode++) myPotential += myPotentialSource(*myNode,0); // assign an XT function per each node and // then call the prescribed data manager and fix each node individually. f = XT_Function_Mgr::instance()->constant_function(myPotential); (atc_->prescribedDataMgr_)->fix_field(nodalIndex,ELECTRIC_POTENTIAL,0,f); // compute effective charge at each node I // multiply charge density by integral of N_I over face (myNodeData->second).second = (nodalFaceWeights[ELECTRIC_POTENTIAL].quantity())(nodalIndex,0)*chargeDensity; } } //-------------------------------------------------------- // apply_charged_surfaces //-------------------------------------------------------- void ExtrinsicModelElectrostatic::apply_charged_surfaces (MATRIX & potential) { //double qE2f = LammpsInterface::instance()->qe2f(); double qV2e = LammpsInterface::instance()->qv2e(); double qqrd2e = LammpsInterface::instance()->qqrd2e(); //double ** fatom = LammpsInterface::instance()->fatom(); //double * qatom = LammpsInterface::instance()->atom_charge(); InterscaleManager & interscaleManager(atc_->interscale_manager()); const DENS_MAT & qatom((interscaleManager.fundamental_atom_quantity(LammpsInterface::ATOM_CHARGE))->quantity()); double cutoffRadius = LammpsInterface::instance()->pair_cutoff(); double cutoffSq = cutoffRadius*cutoffRadius; int nLocal = qatom.nRows(); int nsd = atc_->nsd(); int nNodes = atc_->num_nodes(); double penalty = poissonSolver_->penalty_coefficient(); if (penalty <= 0.0) throw ATC_Error("ExtrinsicModelElectrostatic::apply_charged_surfaces expecting non zero penalty"); SparseVector dummy(atc_->num_nodes()); map > >::const_iterator isurface; for (isurface = chargedSurfaces_.begin(); isurface != chargedSurfaces_.end(); isurface++) { string facesetName = isurface->first; map >::const_iterator inode; for (inode = (isurface->second).begin(); inode != (isurface->second).end(); inode++) { int nodeId = inode->first; DENS_VEC nodalCoords = (inode->second).first; double nodalCharge = (inode->second).second; double nodalPotential = nodalChargePotential_[facesetName][nodeId]; PerAtomQuantity * atomicCoords = (atc_->interscale_manager()).per_atom_quantity("AtomicCoarseGrainingPositions"); const DENS_MAT & myAtomicCoords(atomicCoords->quantity()); for (int i = 0; i < nLocal; i++) { if (abs(qatom(i,0)) > 0) { double distanceSq = 0.; double deltaX[3]; for (int j = 0; j < nsd; j++) { deltaX[j] = myAtomicCoords(i,j) - nodalCoords(j); distanceSq += deltaX[j]*deltaX[j]; } if (distanceSq < cutoffSq) { // first apply pairwise coulombic interaction if (!useSlab_) { double coulForce = qqrd2e*nodalCharge*qatom(i,0)/(distanceSq*sqrtf(distanceSq)); for (int j = 0; j < nsd; j++) //fatom[atomIdx][j] += deltaX[j]*coulForce; _atomElectricalForce_(i,j) += deltaX[j]*coulForce; } // second correct for FE potential induced by BCs // determine shape function derivatives at atomic location // and construct sparse vectors to store derivative data vector > derivativeVectors; derivativeVectors.reserve(nsd); const SPAR_MAT_VEC & shapeFunctionDerivatives((interscaleManager.vector_sparse_matrix("InterpolantGradient"))->quantity()); for (int j = 0; j < nsd; j++) { DenseVector nodeIndices; DENS_VEC nodeValues; shapeFunctionDerivatives[j]->row(i,nodeValues,nodeIndices); derivativeVectors.push_back(dummy); for (int k = 0; k < nodeIndices.size(); k++) derivativeVectors[j](nodeIndices(k)) = nodeValues(k); } // compute greens function from charge quadrature SparseVector shortFePotential(nNodes); shortFePotential.add_scaled(greensFunctions_[nodeId],penalty*nodalPotential); // compute electric field induced by charge DENS_VEC efield(nsd); efield = 0.; for (int j = 0; j < nsd; j++) efield(j) = -.1*dot(derivativeVectors[j],shortFePotential); // apply correction in atomic forces //double c = qE2f*qatom[atomIdx]; double c = qV2e*qatom(i,0); for (int j = 0; j < nsd; j++) if ((!useSlab_) || (j==nsd)) { //fatom[atomIdx][j] -= c*efield(j); _atomElectricalForce_(i,j) -= c*efield(j); } } } } } } } #endif //-------------------------------------------------------- //-------------------------------------------------------- // Class ExtrinsicModelElectrostaticMomentum //-------------------------------------------------------- //-------------------------------------------------------- //-------------------------------------------------------- // Constructor //-------------------------------------------------------- ExtrinsicModelElectrostaticMomentum::ExtrinsicModelElectrostaticMomentum (ExtrinsicModelManager * modelManager, ExtrinsicModelType modelType, string matFileName) : ExtrinsicModelElectrostatic(modelManager,modelType,matFileName) { if (physicsModel_) delete physicsModel_; if (modelType == ELECTROSTATIC) { physicsModel_ = new PhysicsModelElectrostatic(matFileName); } else { physicsModel_ = new PhysicsModelElectrostaticEquilibrium(matFileName); } // set up correct masks for coupling rhsMaskIntrinsic_(VELOCITY,SOURCE) = true; atc_->fieldMask_(VELOCITY,EXTRINSIC_SOURCE) = true; } //-------------------------------------------------------- // Destructor //-------------------------------------------------------- ExtrinsicModelElectrostaticMomentum::~ExtrinsicModelElectrostaticMomentum() { // do nothing } //-------------------------------------------------------- // modify //-------------------------------------------------------- bool ExtrinsicModelElectrostaticMomentum::modify(int narg, char **arg) { bool match = false; if (!match) match = ExtrinsicModelElectrostatic::modify(narg,arg); return match; } //-------------------------------------------------------- // initialize //-------------------------------------------------------- void ExtrinsicModelElectrostaticMomentum::initialize() { ExtrinsicModelElectrostatic::initialize(); int nNodes = atc_->num_nodes(); int nsd = atc_->nsd(); rhs_[VELOCITY].reset(nNodes,nsd); } //-------------------------------------------------------- // set coupling source terms //-------------------------------------------------------- void ExtrinsicModelElectrostaticMomentum::set_sources(FIELDS & fields, FIELDS & sources) { // compute charge density if (modelType_ == ELECTROSTATIC_EQUILIBRIUM) { DENS_MAN & n = atc_->field(ELECTRON_DENSITY); atc_->nodal_projection(ELECTRON_DENSITY,physicsModel_,n); } // else { // FIELDS rhs; // Array2D mask; // mask(ELECTRON_DENSITY,SOURCE) = true; // atc_->evaluate_rhs_integral(mask,fields,rhs,FULL_DOMAIN,physicsModel_); // atc_->apply_inverse_mass_matrix(rhs[ELECTRON_DENSITY].quantity(),n.set_quantity(),ELECTRON_DENSITY); // } // compute source term with appropriate masking and physics model atc_->evaluate_rhs_integral(rhsMaskIntrinsic_, fields, sources, atc_->source_integration(), physicsModel_); //(sources[VELOCITY].quantity()).print("V SRC"); } //-------------------------------------------------------- // output //-------------------------------------------------------- void ExtrinsicModelElectrostaticMomentum::output(OUTPUT_LIST & outputData) { ExtrinsicModelElectrostatic::output(outputData); } };