forked from lijiext/lammps
954 lines
34 KiB
C++
954 lines
34 KiB
C++
// ATC Headers
|
|
#include "SchrodingerSolver.h"
|
|
#include "ATC_Error.h"
|
|
#include "ATC_Coupling.h"
|
|
#include "LammpsInterface.h"
|
|
#include "PrescribedDataManager.h"
|
|
#include "PhysicsModel.h"
|
|
#include "LinearSolver.h"
|
|
#include "PoissonSolver.h"
|
|
|
|
#include "Utility.h"
|
|
|
|
#include <utility>
|
|
using std::pair;
|
|
using std::set;
|
|
using std::stringstream;
|
|
using std::min;
|
|
using ATC_Utility::to_string;
|
|
using ATC_Utility::sgn;
|
|
|
|
const double zero_tol = 1.e-12;
|
|
const double f_tol = 1.e-8;
|
|
|
|
namespace ATC {
|
|
|
|
enum oneDconservationEnum {ONED_DENSITY=0, ONED_FLUX, ONED_GLOBAL_FLUX};
|
|
|
|
|
|
|
|
|
|
|
|
double fermi_dirac(const double E, const double T)
|
|
{
|
|
double f = 1.0;
|
|
if (T > 0) f = 1.0 / ( exp(E/(kBeV_*T))+1.0 );
|
|
else if (E > 0) f = 0;
|
|
return f;
|
|
};
|
|
|
|
|
|
//========================================================
|
|
// Schrodinger solve
|
|
//========================================================
|
|
SchrodingerSolver::SchrodingerSolver(
|
|
const FieldName fieldName,
|
|
const PhysicsModel * physicsModel,
|
|
const FE_Engine * feEngine,
|
|
const PrescribedDataManager * prescribedDataMgr,
|
|
/*const*/ ATC_Coupling * atc,
|
|
bool parallel
|
|
)
|
|
: atc_(atc),
|
|
feEngine_(feEngine),
|
|
prescribedDataMgr_(prescribedDataMgr),
|
|
physicsModel_(physicsModel),
|
|
fieldName_(fieldName),
|
|
nNodes_(atc->num_nodes()),
|
|
parallel_(parallel)
|
|
{
|
|
}
|
|
//-----------------------------------------------------
|
|
void SchrodingerSolver::initialize()
|
|
{
|
|
SPAR_MAT sparseM;
|
|
atc_->fe_engine()->compute_mass_matrix(sparseM);
|
|
M_ = sparseM.dense_copy();
|
|
}
|
|
//-----------------------------------------------------
|
|
bool SchrodingerSolver::solve(FIELDS & /* fields */)
|
|
{
|
|
|
|
// typedef struct{float real, imag;} COMPLEX;
|
|
SPAR_MAT stiffness_;
|
|
Array2D <bool> rhsMask(NUM_FIELDS,NUM_FLUX);
|
|
rhsMask = false;
|
|
rhsMask(ELECTRON_WAVEFUNCTION,FLUX) = true;
|
|
rhsMask(ELECTRON_WAVEFUNCTION,SOURCE) = true;
|
|
pair<FieldName,FieldName> row_col(ELECTRON_WAVEFUNCTION,
|
|
ELECTRON_WAVEFUNCTION);
|
|
//set_fixed_nodes();
|
|
atc_->fe_engine()->compute_tangent_matrix(
|
|
rhsMask, row_col, atc_->fields(), physicsModel_,
|
|
atc_->element_to_material_map(), stiffness_);
|
|
DENS_MAT K(stiffness_.dense_copy());
|
|
set<int> fixedNodes = prescribedDataMgr_->fixed_nodes(ELECTRON_WAVEFUNCTION);
|
|
const BC_SET & bcs
|
|
= (prescribedDataMgr_->bcs(ELECTRON_WAVEFUNCTION))[0];
|
|
DENS_MAT & psi = (atc_->field(ELECTRON_WAVEFUNCTION)).set_quantity();
|
|
DENS_MAT & eVecs = (atc_->field(ELECTRON_WAVEFUNCTIONS)).set_quantity();
|
|
DENS_MAT & eVals = (atc_->field(ELECTRON_WAVEFUNCTION_ENERGIES)).set_quantity();
|
|
|
|
if (prescribedDataMgr_->all_fixed(ELECTRON_WAVEFUNCTION)) {
|
|
ATC::LammpsInterface::instance()->print_msg("all wavefunctions fixed");
|
|
psi.reset(nNodes_,1);
|
|
eVecs.reset(nNodes_,1);
|
|
eVals.reset(nNodes_,1);
|
|
return true;
|
|
}
|
|
// (1) Helmholtz solve for inhomongeneous bcs
|
|
|
|
LinearSolver helmholtzSolver_(K,bcs,LinearSolver::AUTO_SOLVE,-1,parallel_);
|
|
|
|
psi.reset(nNodes_,1);
|
|
// (2) Eigenvalue solve
|
|
helmholtzSolver_.eigen_system(eVals,eVecs,&M_);
|
|
return true;
|
|
}
|
|
|
|
//========================================================
|
|
// Schrodinger solve on slices
|
|
//========================================================
|
|
SliceSchrodingerSolver::SliceSchrodingerSolver(
|
|
const FieldName fieldName,
|
|
const PhysicsModel * physicsModel,
|
|
const FE_Engine * feEngine,
|
|
const PrescribedDataManager * prescribedDataMgr,
|
|
/*const*/ ATC_Coupling * atc,
|
|
const Array< set<int> > & oneDslices,
|
|
const Array< double > & oneDdxs,
|
|
bool parallel
|
|
)
|
|
: SchrodingerSolver(fieldName, physicsModel, feEngine, prescribedDataMgr,
|
|
atc, parallel),
|
|
oneDslices_(oneDslices),
|
|
oneDdxs_(oneDdxs)
|
|
{}
|
|
//--------------------------------------------------------
|
|
void SliceSchrodingerSolver::initialize()
|
|
{
|
|
SchrodingerSolver::initialize();
|
|
}
|
|
//--------------------------------------------------------
|
|
// compute charge density per slice
|
|
//--------------------------------------------------------
|
|
bool SliceSchrodingerSolver::solve(FIELDS & /* fields */)
|
|
{
|
|
// fields
|
|
DENS_MAT & psi = (atc_->field(ELECTRON_WAVEFUNCTION)).set_quantity();
|
|
DENS_MAT & eVecs = (atc_->field(ELECTRON_WAVEFUNCTIONS)).set_quantity();
|
|
DENS_MAT & eVals = (atc_->field(ELECTRON_WAVEFUNCTION_ENERGIES)).set_quantity();
|
|
psi.reset(nNodes_,1);
|
|
eVecs.reset(nNodes_,nNodes_);
|
|
eVals.reset(nNodes_,1);
|
|
DENS_MAT & Ef = (atc_->field(FERMI_ENERGY)).set_quantity();
|
|
DENS_MAT & n = (atc_->field(ELECTRON_DENSITY)).set_quantity();
|
|
DENS_MAT & T = (atc_->field(ELECTRON_TEMPERATURE)).set_quantity();
|
|
|
|
// stiffness = K + V M
|
|
SPAR_MAT stiffness_;
|
|
Array2D <bool> rhsMask(NUM_FIELDS,NUM_FLUX);
|
|
rhsMask = false;
|
|
rhsMask(ELECTRON_WAVEFUNCTION,FLUX) = true;
|
|
rhsMask(ELECTRON_WAVEFUNCTION,SOURCE) = true;
|
|
pair<FieldName,FieldName> row_col(ELECTRON_WAVEFUNCTION,
|
|
ELECTRON_WAVEFUNCTION);
|
|
atc_->fe_engine()->compute_tangent_matrix(
|
|
rhsMask, row_col, atc_->fields(), physicsModel_,
|
|
atc_->element_to_material_map(), stiffness_);
|
|
DENS_MAT K(stiffness_.dense_copy());
|
|
|
|
// Eigenvalue solve
|
|
DENS_MAT K1,M1;
|
|
int nslices = oneDslices_.size();
|
|
DENS_MAT b ;
|
|
DENS_MAT evals1,evecs1 ;
|
|
DENS_MAT n1 ;
|
|
BCS bcs;
|
|
set <int> one;
|
|
one.insert(0);
|
|
set <int> eindex;
|
|
int iEVal = 0;
|
|
for (int islice = 0; islice < nslices ; islice++) {
|
|
set<int> & slice = oneDslices_(islice);
|
|
int snodes = slice.size();
|
|
prescribedDataMgr_->bcs(ELECTRON_WAVEFUNCTION,slice,bcs,true);
|
|
const BC_SET & bc = bcs[0];
|
|
int nfixed = bc.size();
|
|
if (nfixed != snodes) {
|
|
// A: solve for e-values and wavefunctions
|
|
K.map(slice,slice,K1);
|
|
M_.map(slice,slice,M1);
|
|
LinearSolver eigensolver(K1,bc,LinearSolver::AUTO_SOLVE,-1);
|
|
// wave functions
|
|
evals1.reset(snodes,1);
|
|
evecs1.reset(snodes,snodes);
|
|
eigensolver.eigen_system(evals1,evecs1,&M1);
|
|
eindex.clear();
|
|
for (int j = 0; j < snodes; j++) eindex.insert(iEVal++);
|
|
eVals.insert(eindex,one, evals1);
|
|
eindex.clear();
|
|
for (int j = 0; j < snodes; j++) eindex.insert(j);
|
|
eVecs.insert(slice,eindex,evecs1);
|
|
// slice charge density
|
|
n1.reset(snodes,1);
|
|
|
|
set<int>::const_iterator iset;
|
|
double aveE_f = 0;
|
|
for (iset = slice.begin(); iset != slice.end(); iset++) {
|
|
int gnode = *iset;
|
|
aveE_f += Ef(gnode,0);
|
|
}
|
|
aveE_f /= snodes;
|
|
//#define VERBOSE
|
|
#ifdef VERBOSE
|
|
stringstream ss;
|
|
ss << " slice "+to_string(islice+1)+" E_f "+to_string(aveE_f) << "\n"
|
|
<< "#-----------------------------------------------\n"
|
|
<< "# E-Ef f psi n\n"
|
|
<< "#-----------------------------------------------\n";
|
|
#endif
|
|
// B: compute charge density on slice
|
|
int node = 0;
|
|
for (iset = slice.begin(); iset != slice.end(); iset++) { // node
|
|
int gnode = *iset;
|
|
double temp = T(gnode,0);
|
|
for (int mode = 0; mode < snodes-nfixed; mode++) {
|
|
double Ei = evals1(mode,0);
|
|
double E = Ei-aveE_f;
|
|
double f = fermi_dirac(E,temp);
|
|
double psi1 = evecs1(node,mode); // 2nd index corresp to evals order
|
|
#ifdef VERBOSE
|
|
ss << node<<":"<<mode << " " << to_string(6,E) << " " << to_string(6,f) << " " << to_string(6,psi1) << " " << to_string(6,n1(node,0)+psi1*psi1*f) << "\n";
|
|
#endif
|
|
if (f < f_tol) break; // take advantage of E ordering
|
|
n1(node,0) += psi1*psi1*f;
|
|
}
|
|
node++;
|
|
}
|
|
#ifdef VERBOSE
|
|
ATC::LammpsInterface::instance()->print_msg_once(ss.str());
|
|
#endif
|
|
n.insert(slice,one, n1); // note not "assemble"
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//========================================================
|
|
// Schrodinger-Poisson Manager
|
|
//========================================================
|
|
SchrodingerPoissonManager::SchrodingerPoissonManager() :
|
|
maxConsistencyIter_(0),
|
|
maxConstraintIter_(0),
|
|
oneD_(false),
|
|
oneDconserve_(ONED_FLUX),
|
|
Ef_shift_(0.),
|
|
safe_dEf_(0.),
|
|
tol_(1.e-10),
|
|
mu_(1.),D_(0.)
|
|
{
|
|
}
|
|
//----------------------------------------------------------
|
|
bool SchrodingerPoissonManager::modify(int /* narg */, char **arg)
|
|
{
|
|
bool match = false;
|
|
int argIndx = 0;
|
|
if (strcmp(arg[argIndx],"self_consistency")==0) {
|
|
argIndx++;
|
|
maxConsistencyIter_ = atoi(arg[argIndx]);
|
|
match = true;
|
|
}
|
|
else if (strcmp(arg[argIndx],"conserve")==0) {
|
|
oneD_ = true;
|
|
argIndx++;
|
|
if (strcmp(arg[argIndx],"density")==0) oneDconserve_ = ONED_DENSITY;
|
|
else if (strcmp(arg[argIndx],"flux")==0) oneDconserve_ = ONED_FLUX;
|
|
else oneDconserve_ = ONED_GLOBAL_FLUX;
|
|
argIndx++;
|
|
maxConstraintIter_ = atoi(arg[argIndx]);
|
|
match = true;
|
|
}
|
|
else if (strcmp(arg[argIndx],"initial_fermi_level")==0) {
|
|
argIndx++;
|
|
Ef_shift_ = atof(arg[argIndx]);
|
|
match = true;
|
|
}
|
|
else if (strcmp(arg[argIndx],"safe_fermi_increment")==0) {
|
|
argIndx++;
|
|
safe_dEf_ = atof(arg[argIndx]);
|
|
match = true;
|
|
}
|
|
else if (strcmp(arg[argIndx],"relaxation")==0) {
|
|
argIndx++;
|
|
alpha_ = atof(arg[argIndx]);
|
|
match = true;
|
|
}
|
|
else if (strcmp(arg[argIndx],"tolerance")==0) {
|
|
argIndx++;
|
|
tol_ = atof(arg[argIndx]);
|
|
match = true;
|
|
}
|
|
else if (strcmp(arg[argIndx],"mobility")==0) {
|
|
argIndx++;
|
|
mu_ = atof(arg[argIndx]);
|
|
match = true;
|
|
}
|
|
else if (strcmp(arg[argIndx],"diffusivity")==0) {
|
|
argIndx++;
|
|
D_ = atof(arg[argIndx]);
|
|
match = true;
|
|
}
|
|
return match;
|
|
}
|
|
//----------------------------------------------------------------
|
|
SchrodingerPoissonSolver * SchrodingerPoissonManager::initialize(
|
|
/*const*/ ATC_Coupling * atc,
|
|
SchrodingerSolver * schrodingerSolver,
|
|
PoissonSolver * poissonSolver,
|
|
const PhysicsModel * physicsModel
|
|
)
|
|
{
|
|
SchrodingerPoissonSolver * ptr;
|
|
if (oneD_) {
|
|
if (oneDconserve_ == ONED_GLOBAL_FLUX) {
|
|
ptr = new GlobalSliceSchrodingerPoissonSolver(atc,
|
|
schrodingerSolver,poissonSolver,physicsModel,maxConsistencyIter_,
|
|
maxConstraintIter_, oneDconserve_, Ef_shift_, alpha_, safe_dEf_, tol_,
|
|
mu_,D_);
|
|
}
|
|
else {
|
|
ptr = new SliceSchrodingerPoissonSolver(atc,
|
|
schrodingerSolver,poissonSolver,physicsModel,maxConsistencyIter_,
|
|
maxConstraintIter_, oneDconserve_, Ef_shift_, safe_dEf_);
|
|
}
|
|
}
|
|
else {
|
|
ptr = new SchrodingerPoissonSolver(atc,
|
|
schrodingerSolver,poissonSolver,physicsModel,maxConsistencyIter_);
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
//===================================================================
|
|
// SchrodingerPoissonSolver
|
|
//===================================================================
|
|
SchrodingerPoissonSolver::SchrodingerPoissonSolver(
|
|
/*const*/ ATC_Coupling * atc,
|
|
SchrodingerSolver * schrodingerSolver,
|
|
PoissonSolver * poissonSolver,
|
|
const PhysicsModel * physicsModel,
|
|
int maxConsistencyIter
|
|
) :
|
|
atc_(atc),
|
|
schrodingerSolver_(schrodingerSolver),
|
|
poissonSolver_(poissonSolver),
|
|
physicsModel_(physicsModel),
|
|
maxConsistencyIter_(maxConsistencyIter),
|
|
nNodes_(atc_->num_nodes())
|
|
{
|
|
}
|
|
//----------------------------------------------------------------------
|
|
|
|
void SchrodingerPoissonSolver::solve(FIELDS & rhs, GRAD_FIELD_MATS & /* fluxes */)
|
|
{
|
|
if ((atc_->prescribed_data_manager()->all_fixed(ELECTRON_WAVEFUNCTION))
|
|
&& (atc_->prescribed_data_manager()->all_fixed(ELECTRIC_POTENTIAL))) {
|
|
return;
|
|
}
|
|
double norm = 1.0, norm0 = 1.0; // normPrev = 1.0;
|
|
DENS_MAT nPrev,psiPrev,phiPrev;
|
|
|
|
DENS_MAT & psi = (atc_->field(ELECTRON_WAVEFUNCTIONS)).set_quantity();
|
|
DENS_MAT & phi = (atc_->field(ELECTRIC_POTENTIAL)).set_quantity();
|
|
DENS_MAT & E_I = (atc_->field(ELECTRON_WAVEFUNCTION_ENERGIES)).set_quantity();
|
|
DENS_MAT & Te = (atc_->field(ELECTRON_TEMPERATURE)).set_quantity();
|
|
atc_->set_fixed_nodes();
|
|
DENS_MAT Te0 = Te; // save
|
|
|
|
const double tol = 1.e-4;
|
|
|
|
int k = 0;
|
|
double logRatio = 3;
|
|
int maxIter = (int) logRatio;
|
|
double base = 2.0;
|
|
|
|
// temperature relaxation loop
|
|
for (int i = 0; i < maxIter ; ++i) {
|
|
//double alpha = ((double) i) /( (double) maxIter-1);
|
|
//double beta = 0.1;
|
|
//alpha = (exp(beta*i)-1.0)/(exp(beta*(maxIter-1))-1.0);
|
|
double alpha = pow(base,logRatio-i-1);
|
|
// self consistency loop
|
|
int j = 0; // for storage of last iterate
|
|
|
|
for (j = 0; j < maxConsistencyIter_ ; ++j) {
|
|
// compute eigen-values and vectors
|
|
atc_->set_fixed_nodes();
|
|
Te = alpha*Te0;
|
|
|
|
schrodingerSolver_->solve(atc_->fields());
|
|
|
|
|
|
for (int l = 0; l < nNodes_; l++) {
|
|
int count = 0;
|
|
double T_e = Te(l,0);
|
|
for (int m = 0; m < nNodes_; m++) {
|
|
double f = fermi_dirac(E_I(m,0), T_e);
|
|
if (f > tol) count++;
|
|
}
|
|
}
|
|
// compute charge density
|
|
DENS_MAN & n = atc_->field(ELECTRON_DENSITY);
|
|
//(n.quantity()).print("DENSITY");
|
|
atc_->nodal_projection(ELECTRON_DENSITY,physicsModel_,n);
|
|
atc_->set_fixed_nodes();
|
|
|
|
|
|
// solve poisson eqn for electric potential
|
|
atc_->set_fixed_nodes();
|
|
Te = alpha*Te0;
|
|
poissonSolver_->solve(atc_->fields(),rhs);
|
|
|
|
//DENS_MAT dn = n;
|
|
//DENS_MAT dpsi = psi;
|
|
//DENS_MAT dphi = phi;
|
|
if (i == 0 && j==0) {
|
|
nPrev = n.quantity();
|
|
psiPrev = psi;
|
|
phiPrev = phi;
|
|
}
|
|
//dn -= nPrev;
|
|
//dpsi -= psiPrev;
|
|
//dphi -= phiPrev;
|
|
|
|
norm = (n.quantity()-nPrev).norm();
|
|
if (i == 0 && j==0) norm0 = (n.quantity()).norm();
|
|
//normPrev = norm;
|
|
//psi_normPrev = psi_norm;
|
|
//phi_normPrev = phi_norm;
|
|
nPrev = n.quantity();
|
|
psiPrev = psi;
|
|
phiPrev = phi;
|
|
k++;
|
|
if (j > 0 && norm <= tol*norm0) break;
|
|
}
|
|
// Tmax_ *= 0.5;
|
|
}
|
|
}
|
|
|
|
//===================================================================
|
|
// SliceSchrodingerPoissonSolver
|
|
//===================================================================
|
|
SliceSchrodingerPoissonSolver::SliceSchrodingerPoissonSolver(
|
|
/*const*/ ATC_Coupling * atc,
|
|
SchrodingerSolver * schrodingerSolver,
|
|
PoissonSolver * poissonSolver,
|
|
const PhysicsModel * physicsModel,
|
|
int maxConsistencyIter,
|
|
int maxConstraintIter,
|
|
int oneDconserve,
|
|
double Ef_shift,
|
|
double safe_dEf
|
|
) :
|
|
SchrodingerPoissonSolver(atc,schrodingerSolver,poissonSolver,physicsModel,maxConsistencyIter),
|
|
oneDconserve_(oneDconserve),
|
|
oneDcoor_(0),
|
|
oneDslices_(((SliceSchrodingerSolver *) schrodingerSolver_)->slices()),
|
|
oneDdxs_(((SliceSchrodingerSolver *) schrodingerSolver_)->dxs())
|
|
{
|
|
Ef_shift_=Ef_shift;
|
|
safe_dEf_=safe_dEf;
|
|
maxConstraintIter_=maxConstraintIter;
|
|
EfHistory_.reset(oneDslices_.size(),2);
|
|
}
|
|
//--------------------------------------------------------------------------
|
|
void SliceSchrodingerPoissonSolver::solve(FIELDS & rhs, GRAD_FIELD_MATS & fluxes)
|
|
{
|
|
const double tol = 1.e-4; // tolerance on consistency & constraint
|
|
double norm = 1.0, norm0 = 1.0;
|
|
DENS_MAT nPrev;
|
|
DENS_MAT & n = (atc_->field(ELECTRON_DENSITY)).set_quantity();
|
|
DENS_MAT & phi = (atc_->field(ELECTRIC_POTENTIAL)).set_quantity();
|
|
|
|
// fermi energy
|
|
DENS_MAT & Ef = (atc_->field(FERMI_ENERGY)).set_quantity();
|
|
Ef.reset(nNodes_,1);
|
|
|
|
int nslices = oneDslices_.size();
|
|
Array2D<double> nHistory(nslices,2);
|
|
|
|
// target for constraint
|
|
double target = 0.0;
|
|
|
|
set<int> & slice = oneDslices_(0); // note assume first slice is fixed
|
|
if (oneDconserve_ == ONED_FLUX) atc_->set_sources();
|
|
DENS_MAT & nSource = (atc_->source(ELECTRON_DENSITY)).set_quantity();
|
|
for (set<int>::const_iterator iset = slice.begin(); iset != slice.end(); iset++) {
|
|
if (oneDconserve_ == ONED_FLUX) target += nSource(*iset,0);
|
|
else target += n(*iset,0);
|
|
}
|
|
target /= slice.size();
|
|
#ifdef VERBOSE
|
|
if (oneDconserve_ == ONED_FLUX) {
|
|
if (target > 0) ATC::LammpsInterface::instance()->print_msg_once(" influx target "+ to_string(target));
|
|
else ATC::LammpsInterface::instance()->print_msg_once(" efflux target "+ to_string(target));
|
|
}
|
|
#endif
|
|
|
|
// A: self consistency loop between Phi and n(psi_i)
|
|
double error = 1.0;
|
|
for (int i = 0; i < maxConsistencyIter_ ; ++i) {
|
|
atc_->set_fixed_nodes();
|
|
if (! atc_->prescribedDataMgr_->all_fixed(ELECTRIC_POTENTIAL) )
|
|
poissonSolver_->solve(atc_->fields(),rhs);
|
|
if (! atc_->prescribedDataMgr_->all_fixed(ELECTRON_DENSITY) ) {
|
|
// iterate on Ef
|
|
//if (i==0) Ef = -1.0*phi;// E ~ -|e| \Phi, charge of electron e = 1
|
|
Ef = -1.0*phi;
|
|
|
|
Ef +=Ef_shift_;
|
|
// B: conservation constraint
|
|
for (int j = 0; j < maxConstraintIter_ ; ++j) {
|
|
schrodingerSolver_->solve(atc_->fields()); // n(E_f)
|
|
atc_->set_fixed_nodes();
|
|
error = update_fermi_energy(target,(j==0),fluxes);// root finder
|
|
#ifdef VERBOSE
|
|
ATC::LammpsInterface::instance()->print_msg_once(to_string(i)+":"+to_string(j)+" constraint_error "+to_string(error)+" / "+to_string(tol*target)+"\n");
|
|
#endif
|
|
// exit condition based on constraint satisfaction
|
|
if (error < tol*fabs(target)) break;
|
|
} // loop j : flux constraint
|
|
// error based on change in field (Cauchy convergence)
|
|
if (i == 0) {
|
|
norm = norm0 = n.norm();
|
|
}
|
|
else {
|
|
DENS_MAT dn = n;
|
|
dn -= nPrev;
|
|
norm = dn.norm();
|
|
}
|
|
nPrev = n;
|
|
#ifdef VERBOSE
|
|
#if 0
|
|
if (i > 0) ATC::LammpsInterface::instance()->print_msg_once(to_string(i)+" density_change: "+to_string(norm)+" / "+to_string(norm0));
|
|
else ATC::LammpsInterface::instance()->print_msg_once("initial norm "+to_string(norm));
|
|
#endif
|
|
#endif
|
|
if (i > 0 && norm <= tol*norm0 && error < tol) break;
|
|
}
|
|
} // loop i : self consistency
|
|
}
|
|
|
|
//--------------------------------------------------------
|
|
// update fermi energy
|
|
//--------------------------------------------------------
|
|
|
|
double SliceSchrodingerPoissonSolver::update_fermi_energy
|
|
(double target, bool first, GRAD_FIELD_MATS & fluxes)
|
|
{
|
|
DENS_MAT & Ef = (atc_->field(FERMI_ENERGY)).set_quantity();
|
|
DENS_MAT & n = (atc_->field(ELECTRON_DENSITY)).set_quantity();
|
|
DENS_MAT & phi = (atc_->field(ELECTRIC_POTENTIAL)).set_quantity();
|
|
const DENS_MAT * y = &n;
|
|
if (oneDconserve_ == ONED_FLUX) { // compute J_x
|
|
Array2D <bool> rhsMask(NUM_FIELDS,NUM_FLUX); rhsMask = false;
|
|
rhsMask(ELECTRON_DENSITY,FLUX) = true;
|
|
//#define WIP_REJ
|
|
atc_->compute_flux(rhsMask,atc_->fields_,fluxes,physicsModel_);
|
|
y = & ( fluxes[ELECTRON_DENSITY][oneDcoor_] );
|
|
}
|
|
BCS bcs;
|
|
double error = 0;
|
|
// slice
|
|
for (int islice = 0; islice < oneDslices_.size(); islice++) {
|
|
#ifdef VERBOSE
|
|
std::string cStr(" conserved ");
|
|
std::string Estr(" Ef");
|
|
#endif
|
|
set<int> & slice = oneDslices_(islice);
|
|
int nSlice = slice.size();
|
|
atc_->prescribedDataMgr_->bcs(ELECTRON_WAVEFUNCTION,slice,bcs,true);
|
|
const BC_SET & bc = bcs[0];
|
|
int nFixed = bc.size();
|
|
if (nFixed == nSlice) continue; // skip if all fixed
|
|
double Y = 0.0, X = 0.0;
|
|
double nAve = 0., phiAve = 0.;
|
|
for (set<int>::const_iterator iset = slice.begin(); iset != slice.end(); iset++) {
|
|
int gnode = *iset;
|
|
X += Ef(gnode,0);
|
|
Y += (*y)(gnode,0);
|
|
nAve += n(gnode,0);
|
|
phiAve += phi(gnode,0);
|
|
}
|
|
X /= nSlice;
|
|
Y /= nSlice;
|
|
nAve /= nSlice;
|
|
phiAve /= nSlice;
|
|
// now adjust Ef for each slice
|
|
double dY = Y - EfHistory_(islice,0);
|
|
double dX = X - EfHistory_(islice,1);
|
|
double err = target - Y;
|
|
if (target*Y < -zero_tol*target) {
|
|
#ifdef VERBOSE
|
|
cStr = " opp. SIGNS";
|
|
#else
|
|
ATC::LammpsInterface::instance()->print_msg_once("WARNING: slice "+to_string(islice)+" target and quantity opposite signs "+to_string(Y));
|
|
#endif
|
|
}
|
|
error += fabs(err);
|
|
double dEf = 0.;
|
|
if (first) {
|
|
dEf = (err < 0) ? -safe_dEf_ : safe_dEf_;
|
|
}
|
|
else {
|
|
if (fabs(dY) < zero_tol*dX) throw ATC_Error("zero increment in conserved field on slice:"+to_string(islice));
|
|
dEf = err / dY * dX;
|
|
if (fabs(dEf) > safe_dEf_) {
|
|
dEf = safe_dEf_* dEf / fabs(dEf);
|
|
#ifdef VERBOSE
|
|
Estr = " !!";
|
|
#else
|
|
ATC::LammpsInterface::instance()->print_msg_once("WARNING: slice "+to_string(islice)+ " large Delta E_f "+to_string(dEf));
|
|
#endif
|
|
}
|
|
}
|
|
for (set<int>::const_iterator iset = slice.begin(); iset != slice.end(); iset++) {
|
|
int gnode = *iset;
|
|
Ef(gnode,0) += dEf;
|
|
}
|
|
EfHistory_(islice,0) = Y;
|
|
EfHistory_(islice,1) = X;
|
|
if ( std::isnan(Y) ) throw ATC_Error("target on slice is not a number");
|
|
#ifdef VERBOSE
|
|
ATC::LammpsInterface::instance()->print_msg_once(" slice"+to_string(islice,2) +cStr+to_string(4,Y/target) +Estr+to_string(4,X)+" n"+to_string(5,nAve)+" phi"+to_string(4,phiAve));
|
|
//ATC::LammpsInterface::instance()->print_msg_once(" slice "+to_string(islice) +cStr+to_string(4,Y/target) +" E_f"+to_string(4,X)+dEstr+to_string(4,X-EfHistory_(std::max(0,islice-1),1))+" n"+to_string(4,nAve)+" phi"+to_string(4,phiAve)+" "+to_string(nFixed)+" dn "+to_string(4,dnAve)+" dphi "+to_string(4,dphiAve));
|
|
#endif
|
|
} // loop slice
|
|
return error;
|
|
}
|
|
|
|
//===================================================================
|
|
// GlobalSliceSchrodingerPoissonSolver
|
|
//===================================================================
|
|
GlobalSliceSchrodingerPoissonSolver::GlobalSliceSchrodingerPoissonSolver(
|
|
/*const*/ ATC_Coupling * atc,
|
|
SchrodingerSolver * schrodingerSolver,
|
|
PoissonSolver * poissonSolver,
|
|
const PhysicsModel * physicsModel,
|
|
int maxConsistencyIter,
|
|
int maxConstraintIter,
|
|
int oneDconserve,
|
|
double Ef0,
|
|
double alpha,
|
|
double safe_dEf,
|
|
double tol,
|
|
double mu, double D
|
|
) :
|
|
SliceSchrodingerPoissonSolver(atc,schrodingerSolver,poissonSolver,physicsModel,maxConsistencyIter,maxConstraintIter,oneDconserve,0,0),
|
|
solver_(NULL),
|
|
mobility_(mu),diffusivity_(D)
|
|
{
|
|
Ef0_ = Ef0;
|
|
alpha_ = alpha;
|
|
safe_dEf_ = safe_dEf;
|
|
if (safe_dEf_ < 1.e-20) throw ATC_Error("safe dE_f must be positive");
|
|
ATC::LammpsInterface::instance()->print_msg("mobility:"+to_string(mobility_)+" diffusivity:"+to_string(diffusivity_));
|
|
tol_ = tol;
|
|
nslices_ = oneDslices_.size();
|
|
sliceSize_ = (oneDslices_(0)).size();
|
|
nNodes_ = nslices_*sliceSize_;
|
|
flux_.reset(nNodes_);
|
|
J_.reset(nslices_);
|
|
//nfixed_ = 2;
|
|
nfixed_ = 1;
|
|
nfreeSlices_ = nslices_-nfixed_;
|
|
nLambda_ = nslices_-1;
|
|
lambda_.reset(nLambda_);
|
|
dJ_.reset(nLambda_);
|
|
F_.reset(nslices_);
|
|
Phi_.reset(nslices_);
|
|
n_.reset(nslices_);
|
|
// form stiffness, lhs dirichlet bc, rhs homogeneous neumann bc
|
|
//int m = nfreeSlices_;
|
|
int m = nLambda_;
|
|
DENS_MAT A(m,m);
|
|
for (int i = 1; i < m; ++i) {
|
|
A(i,i) = -2;
|
|
if (i>0) A(i,i-1) = 1;
|
|
if (i<m-1) A(i,i+1) = 1;
|
|
}
|
|
A(0,0) = -2;
|
|
A(0,1) = 1;
|
|
A(m-1,m-1) = -2;
|
|
A(m-1,m-2) = 1;
|
|
//if (nfixed_ == 1) { A(m-1,m-1) = -1; }
|
|
double dx = oneDdxs_(0);
|
|
A *= 1./dx;
|
|
A.print("stiffness",4);
|
|
SPAR_MAT K(A);
|
|
K_ = K;
|
|
// form gradient (account for lhs bc)
|
|
int n = nslices_;
|
|
DENS_MAT B(m,n);
|
|
//for (int i = 0; i < m-1; ++i) {
|
|
for (int i = 0; i < m; ++i) {
|
|
B(i,i) =-1;
|
|
B(i,i+1) = 1; //B(i,i+2) = 1;
|
|
}
|
|
if (nfixed_ == 1) {
|
|
B(m-1,n-2) = -1;
|
|
B(m-1,n-1) = 1;
|
|
}
|
|
B.print("gradient",4);
|
|
SPAR_MAT G(B);
|
|
G_ = G;
|
|
|
|
DENS_MAT C(nNodes_,nNodes_);
|
|
// local to ATC nodemap: k --> gnode = *iset
|
|
int k = 0;
|
|
set<int>::const_iterator iset;
|
|
for (int islice = 0; islice < nslices_; islice++) {
|
|
set<int> & slice = oneDslices_(islice);
|
|
for (iset = slice.begin(); iset != slice.end(); iset++) {
|
|
double v = 0.5/dx;
|
|
if ( k < sliceSize_ || k+1 > (nslices_-1)*sliceSize_ ) v *=2.0;
|
|
if (islice > 0) { C(k,k-sliceSize_) += v; }
|
|
else { C(k,k) += v; }
|
|
if (islice < nslices_-1) { C(k,k+sliceSize_) -= v; }
|
|
else { C(k,k) -= v; }
|
|
k++;
|
|
}
|
|
}
|
|
//C.print("2D gradient",4);
|
|
SPAR_MAT G2(C);
|
|
G2_ = G2;
|
|
solver_ = new LinearSolver(K_); // for lambda
|
|
rhsMask_.reset(NUM_FIELDS,NUM_FLUX); rhsMask_ = false;
|
|
rhsMask_(ELECTRON_DENSITY,FLUX) = true;
|
|
|
|
// report
|
|
if (nfixed_ ==2)
|
|
ATC::LammpsInterface::instance()->print_msg_once("schrodinger-poisson solver: Dirichlet INLET, Dirichlet; OUTLET");
|
|
else if (nfixed_ ==1)
|
|
ATC::LammpsInterface::instance()->print_msg_once("schrodinger-poisson solver: Dirichlet INLET, Neumann; OUTLET");
|
|
else
|
|
ATC_Error("schrodinger-poisson solver:too many fixed");
|
|
}
|
|
GlobalSliceSchrodingerPoissonSolver::~GlobalSliceSchrodingerPoissonSolver(void) {
|
|
if (solver_) delete solver_;
|
|
}
|
|
//--------------------------------------------------------------------------
|
|
void GlobalSliceSchrodingerPoissonSolver::solve(FIELDS & rhs, GRAD_FIELD_MATS & /* fluxes */)
|
|
{
|
|
const DENS_MAT & phi = (atc_->fields_[ELECTRIC_POTENTIAL]).quantity();
|
|
const DENS_MAT & n = (atc_->fields_[ELECTRON_DENSITY] ).quantity();
|
|
DENS_MAT & Ef = (atc_->field(FERMI_ENERGY)).set_quantity();
|
|
Ef.reset(phi.nRows(),1);
|
|
norm_ = norm0_ = 1.0;
|
|
for (int i = 0; i < maxConstraintIter_ ; ++i) {
|
|
atc_->set_fixed_nodes();
|
|
if (! atc_->prescribedDataMgr_->all_fixed(ELECTRIC_POTENTIAL) ) {
|
|
poissonSolver_->solve(atc_->fields(),rhs);
|
|
}
|
|
else {
|
|
ATC::LammpsInterface::instance()->print_msg_once("WARNING: phi is fixed");
|
|
}
|
|
if (i == 0) { report(0); }
|
|
if (! atc_->prescribedDataMgr_->all_fixed(ELECTRON_DENSITY) ) {
|
|
update_fermi_level(); // update Ef = Ef0 +lambda
|
|
schrodingerSolver_->solve(atc_->fields()); // updates n(E_f)
|
|
//exponential_electron_density(); // surrogate
|
|
compute_flux(n,phi); // compute J(n,phi) & dJ_
|
|
solver_->solve(lambda_,dJ_); // conservation constraint
|
|
//lambda_.print("lambda");
|
|
//lambda_.print("[[J}}");
|
|
}
|
|
else {
|
|
ATC::LammpsInterface::instance()->print_msg_once("WARNING: rho is fixed");
|
|
}
|
|
norm_ = dJ_.norm();
|
|
report(i+1);
|
|
if (i == 0 && norm_ > tol_) norm0_ = norm_;
|
|
else { if (norm_ < tol_*norm0_) break; }
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------
|
|
void GlobalSliceSchrodingerPoissonSolver::exponential_electron_density()
|
|
{
|
|
std::cout << "******************HACK******************\n";
|
|
|
|
DENS_MAT & n = (atc_->fields_[ELECTRON_DENSITY] ).set_quantity();
|
|
DENS_MAT & Ef = (atc_->field(FERMI_ENERGY)).set_quantity();
|
|
double T = 300;
|
|
double n0 = 1.e-2;
|
|
set<int>::const_iterator iset;
|
|
for (int islice = 0; islice < nslices_; islice++) {
|
|
set<int> & slice = oneDslices_(islice);
|
|
double aveE_f = 0.0;
|
|
for (iset = slice.begin(); iset != slice.end(); iset++) {
|
|
int gnode = *iset;
|
|
aveE_f += Ef(gnode,0);
|
|
}
|
|
aveE_f /= slice.size();
|
|
for (iset = slice.begin(); iset != slice.end(); iset++) {
|
|
int gnode = *iset;
|
|
//std::cout << phi(gnode,0)+aveE_f << "\n";
|
|
//n(gnode,0) = -n0*exp(-(phi(gnode,0)+aveE_f)/(kBeV_*T));
|
|
//n(gnode,0) = -n0*exp((-phi(gnode,0))/(kBeV_*T));
|
|
//n(gnode,0) = -n0*exp(aveE_f/(kBeV_*T));
|
|
//n(gnode,0) = aveE_f+0.01;
|
|
//n(gnode,0) = aveE_f;
|
|
//n(gnode,0) = phi(gnode,0);
|
|
//n(gnode,0) = -n0*(phi(gnode,0)+aveE_f)/(kBeV_*T);
|
|
n(gnode,0) = -n0*(aveE_f)/(kBeV_*T);
|
|
}
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------
|
|
void GlobalSliceSchrodingerPoissonSolver::report(int i)
|
|
{
|
|
const DENS_MAT & phi = (atc_->fields_[ELECTRIC_POTENTIAL]).quantity();
|
|
const DENS_MAT & n = (atc_->fields_[ELECTRON_DENSITY] ).quantity();
|
|
const DENS_MAT & Ef = (atc_->field(FERMI_ENERGY)).quantity();
|
|
set<int>::const_iterator iset;
|
|
for (int islice = 0; islice < nslices_; islice++) {
|
|
set<int> & slice = oneDslices_(islice);
|
|
double Phi = 0.0;
|
|
double N = 0.0;
|
|
double EF = 0.0;
|
|
for (iset = slice.begin(); iset != slice.end(); iset++) {
|
|
int gnode = *iset;
|
|
Phi += phi(gnode,0);
|
|
N += n(gnode,0);
|
|
EF += Ef(gnode,0);
|
|
}
|
|
Phi /= slice.size();
|
|
Phi_(islice) = Phi; // average potential
|
|
N /= slice.size();
|
|
n_(islice) = N; // average electron density
|
|
EF /= slice.size();
|
|
F_(islice) = EF; // average Fermi level
|
|
}
|
|
stringstream header;
|
|
header << "\n";
|
|
header << "#----------------------------------------------------------------------\n";
|
|
header << "# [[J]] lambda E_f phi n J\n";
|
|
header << "#----------------------------------------------------------------------\n";
|
|
if (i == 0) {
|
|
ATC::LammpsInterface::instance()->write_file("slice.dat",header.str());
|
|
}
|
|
stringstream ss;
|
|
ss << "\n";
|
|
// first slice (fixed E_F)
|
|
double dJ0 = J_(1)-J_(0);
|
|
ss << to_string(1,2) << "*" << to_string(6,dJ0) << " " << to_string(6,0.) << " " << to_string(6,F_(0)) << " " << to_string(6,Phi_(0)) << " " << to_string(6,n_(0)) << " " << to_string(6,J_(0)) << "\n";
|
|
// interior
|
|
for (int j = 1; j < nslices_-1; ++j) {
|
|
ss << to_string(j+1,2) << " " << to_string(6,dJ_(j-1)) << " " << to_string(6,lambda_(j-1)) << " " << to_string(6,F_(j)) << " " << to_string(6,Phi_(j)) << " " << to_string(6,n_(j)) << " " << to_string(6,J_(j)) << "\n";
|
|
}
|
|
// last slice (fixed E_F)
|
|
double dJn = J_(nslices_-1)-J_(nslices_-2);
|
|
int j = nslices_-1;
|
|
double lambdaN = 0.;
|
|
std::string space = "*";
|
|
if (nfixed_ == 1) {
|
|
lambdaN = lambda_(nslices_-2);
|
|
space = " ";
|
|
}
|
|
ss << to_string(nslices_,2) << space << to_string(6,dJn) << " " << to_string(6,lambdaN) << " " << to_string(6,F_(j)) << " " << to_string(6,Phi_(j)) << " " << to_string(6,n_(j)) << " " << to_string(6,J_(j)) << "\n";
|
|
stringstream is;
|
|
is << "\n# iteration: " << to_string(i)+"/ "+to_string(maxConstraintIter_)+" constraint norm:"+to_string(6,norm_/norm0_) << " " << nslices_ << " slices";
|
|
ATC::LammpsInterface::instance()->print_msg(is.str()+header.str()+ss.str());
|
|
ATC::LammpsInterface::instance()->write_file("slice.dat",ss.str()+is.str()+"\n",std::ofstream::app);
|
|
}
|
|
//--------------------------------------------------------------------------
|
|
void GlobalSliceSchrodingerPoissonSolver::compute_flux(
|
|
const DENS_MAT & n, const DENS_MAT & phi)
|
|
{
|
|
DENS_VEC f(nNodes_);
|
|
DENS_VEC gradphi(nNodes_);
|
|
DENS_VEC gradn(nNodes_);
|
|
int k = 0;
|
|
set<int>::const_iterator iset;
|
|
// grad phi
|
|
for (int islice = 0; islice < nslices_; islice++) {
|
|
set<int> & slice = oneDslices_(islice);
|
|
for (iset = slice.begin(); iset != slice.end(); iset++) {
|
|
int gnode = *iset;
|
|
f(k) = phi(gnode,0);
|
|
k++;
|
|
}
|
|
}
|
|
//f.print("phi");
|
|
gradphi = G2_*f;
|
|
//gradphi.print("grad phi");
|
|
k = 0;
|
|
// grad n
|
|
for (int islice = 0; islice < nslices_; islice++) {
|
|
set<int> & slice = oneDslices_(islice);
|
|
for (iset = slice.begin(); iset != slice.end(); iset++) {
|
|
int gnode = *iset;
|
|
f(k) = n(gnode,0);
|
|
k++;
|
|
}
|
|
}
|
|
//f.print("n");
|
|
gradn = G2_*f;
|
|
////gradn.print("grad n");
|
|
flux_.reset(nNodes_);
|
|
for (k = 0; k < nNodes_; k++) {
|
|
flux_(k) = -mobility_*f(k)*gradphi(k)-diffusivity_*gradn(k);
|
|
}
|
|
//flux_.print("flux");
|
|
// per slice flux and diference
|
|
k = 0;
|
|
for (int islice = 0; islice < nslices_; islice++) {
|
|
set<int> & slice = oneDslices_(islice);
|
|
J_(islice) = 0;
|
|
for (iset = slice.begin(); iset != slice.end(); iset++) {
|
|
J_(islice) += flux_(k);
|
|
k++;
|
|
}
|
|
J_(islice) /= slice.size();
|
|
//std::cout << islice << " J " << J_(islice) << "\n";
|
|
}
|
|
//J_.print("J");
|
|
dJ_ = G_*J_;
|
|
}
|
|
//--------------------------------------------------------------------------
|
|
void GlobalSliceSchrodingerPoissonSolver::update_fermi_level()
|
|
{
|
|
|
|
DENS_MAT & Ef = (atc_->field(FERMI_ENERGY) ).set_quantity();
|
|
DENS_MAT & phi = (atc_->field(ELECTRIC_POTENTIAL)).set_quantity();
|
|
DENS_MAT & n = (atc_->field(ELECTRON_DENSITY) ).set_quantity();
|
|
set<int>::const_iterator iset;
|
|
for (int islice = 0; islice < nslices_; islice++) {
|
|
set<int> & slice = oneDslices_(islice);
|
|
double Phi = 0.;
|
|
double N = 0.;
|
|
//F_(islice) = Ef0_;
|
|
if (islice > 0 && islice < nslices_-1) {
|
|
F_(islice) += alpha_*lambda_(islice-1);
|
|
}
|
|
for (iset = slice.begin(); iset != slice.end(); iset++) {
|
|
int gnode = *iset;
|
|
Phi += phi(gnode,0);
|
|
N += n(gnode,0);
|
|
}
|
|
Phi /= slice.size();
|
|
Phi_(islice) = Phi; // average potential
|
|
N /= slice.size();
|
|
n_(islice) = N; // average electron density
|
|
|
|
//F_(j) += min(fabs(alpha_*lambda),safe_dEf_)*sgn(lambda);
|
|
for (iset = slice.begin(); iset != slice.end(); iset++) {
|
|
int gnode = *iset;
|
|
Ef(gnode,0) = F_(islice);
|
|
}
|
|
}
|
|
//Ef.print("Ef");
|
|
}
|
|
};
|