lammps/lib/atc/ExtrinsicModelElectrostatic...

1010 lines
42 KiB
C++

// 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"
static const double kTol_ = 1.0e-8;
static const double tol_sparse = 1.e-30;//tolerance for compaction from dense
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());
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");
// add species charge density transfer operators
const map<string,pair<IdType,int> > & speciesIds(atc_->species_ids());
map<string,pair<IdType,int> >::const_iterator specid;
for (specid = speciesIds.begin(); specid != speciesIds.end(); specid++) {
const string specie = specid->first;
AtfShapeFunctionRestriction * nodalAtomicSpeciesCharge =
new AtfShapeFunctionRestriction(atc_,atomicCharge,
interscaleManager.sparse_matrix("Accumulant"+specie));
interscaleManager.add_dense_matrix(nodalAtomicSpeciesCharge,"NodalAtomicSpeciesCharge"+specie);
AtfShapeFunctionMdProjection * nodalAtomicSpeciesChargeDensity =
new AtfShapeFunctionMdProjection(atc_,nodalAtomicSpeciesCharge,MASS_DENSITY);
interscaleManager.add_dense_matrix(nodalAtomicSpeciesChargeDensity,"NodalAtomicSpeciesChargeDensity"+specie);
}
// get the total charge and dipole moment at the node per molecule
// small molecules require per atom quantities with ghosts
const map<string,pair<MolSize,int> > & moleculeIds(atc_->molecule_ids());
map<string,pair<MolSize,int> >::const_iterator molecule;
PerAtomQuantity<double> * 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<double> * moleculeCharge =
new AtomToSmallMoleculeTransfer<double>(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<SmallMoleculeCentroid*>(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<string,double>::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<int> 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<int>::const_iterator thisNode;
SparseVector<double> 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);
nodalAtomicGhostCharge_ = new AtfShapeFunctionRestriction(atc_,ghostCharge,
interscaleManager.per_atom_sparse_matrix("InterpolantGhost"));
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<string,pair<IdType,int> > & speciesIds(atc_->species_ids());
map<string,pair<IdType,int> >::const_iterator specie;
for (specie = speciesIds.begin(); specie != speciesIds.end(); specie++) {
DENS_MAN & nodalAtomicSpeciesChargeDensityOut(atc_->tagged_dens_man("NodalAtomicSpeciesChargeDensity"+specie->first));
DENS_MAN * nodalAtomicSpeciesChargeDensity((atc_->interscale_manager()).dense_matrix("NodalAtomicSpeciesChargeDensity"+specie->first));
nodalAtomicSpeciesChargeDensityOut = nodalAtomicSpeciesChargeDensity->quantity();
}
const map<string,pair<MolSize,int> > & moleculeIds(atc_->molecule_ids());
map<string,pair<MolSize,int> >::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);
if (nLocal > 0) {
for (int i=0; i < nsd; i++) {
CLON_VEC Ei = column(E,i);
Ei = -1.*(*(((atc_->shpFcnDerivs_)->quantity())[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<SparseVector<double> > 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<double> 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<INDEX> 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<int> 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<vector<SparseVector<double> > > 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<SparseVector<double> > 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<INDEX> 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 <int,int> > * 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<int,int> >::const_iterator iset;
for (iset = faceset->begin(); iset != faceset->end(); iset++) {
pair<int,int> face = *iset;
// allocate
Array < XT_Function * > & dof = faceSources[ELECTRIC_POTENTIAL][face];
dof.reset(1);
dof(0) = f;
}
// Get associated nodeset
set<int> nodeset;
feMesh->faceset_to_nodeset(facesetName,nodeset);
// Get coordinates of each node in face set
map<int,pair<DENS_VEC,double> > & myFaceset = chargedSurfaces_[facesetName];
set<int>::const_iterator myNode;
for (myNode = nodeset.begin(); myNode != nodeset.end(); myNode++) {
DENS_VEC myCoords = feMesh->nodal_coordinates(*myNode);
pair<DENS_VEC,double> myPair(myCoords,0.);
myFaceset[*myNode] = myPair;
}
// computed integrals of nodal shape functions on face
FIELDS nodalFaceWeights;
nodalFaceWeights[ELECTRIC_POTENTIAL].reset(nNodes,1);
Array<bool> 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<int,pair<DENS_VEC,double> >::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<int,int> 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();
double cutoffRadius = LammpsInterface::instance()->pair_cutoff();
double cutoffSq = cutoffRadius*cutoffRadius;
int nLocal = atc_->nlocal();
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<double> dummy(atc_->num_nodes());
map<string,map<int,pair<DENS_VEC,double> > >::const_iterator isurface;
for (isurface = chargedSurfaces_.begin(); isurface != chargedSurfaces_.end(); isurface++) {
string facesetName = isurface->first;
map<int,pair<DENS_VEC,double> >::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<double> * atomicCoords = (atc_->interscale_manager()).per_atom_quantity("AtomicCoarseGrainingPositions");
const DENS_MAT & myAtomicCoords(atomicCoords->quantity());
for (int i = 0; i < nLocal; i++) {
int atomIdx = atc_->internalToAtom_(i);
if (abs(qatom[atomIdx]) > 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[atomIdx]/(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<SparseVector<double> > derivativeVectors;
derivativeVectors.reserve(nsd);
InterscaleManager & interscaleManager(atc_->interscale_manager());
const SPAR_MAT_VEC & shapeFunctionDerivatives((interscaleManager.vector_sparse_matrix("InterpolantGradient"))->quantity());
for (int j = 0; j < nsd; j++) {
DenseVector<INDEX> 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<double> 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[atomIdx];
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<bool> 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);
}
};