lammps/lib/atc/FE_Engine.cpp

2975 lines
109 KiB
C++
Raw Normal View History

#include "FE_Engine.h"
#include "ATC_Transfer.h"
#include "FE_Element.h"
#include "Function.h"
#include "PhysicsModel.h"
#include "KernelFunction.h"
#include "Utility.h"
#include "MPI_Wrappers.h"
#include <stdio.h>
#include <stdlib.h>
#include <map>
#include <string>
using namespace std;
using ATC_Utility::is_numeric;
using ATC_Utility::to_string;;
using MPI_Wrappers::allsum;
using MPI_Wrappers::int_allsum;
using MPI_Wrappers::rank_zero;
using MPI_Wrappers::print_msg;
using MPI_Wrappers::print_msg_once;
namespace ATC{
static const double tol_sparse = 1.e-30;//tolerance for compaction from dense
//-----------------------------------------------------------------
FE_Engine::FE_Engine(MPI_Comm comm)
: communicator_(comm),
feMesh_(NULL),
initialized_(false),
outputManager_()
{
// Nothing to do here
}
//-----------------------------------------------------------------
FE_Engine::~FE_Engine()
{
if (feMesh_) delete feMesh_;
}
//-----------------------------------------------------------------
void FE_Engine::initialize()
{
if (!feMesh_) throw ATC_Error("FE_Engine has no mesh");
if (!initialized_) {
// set up work spaces
nNodesPerElement_ = feMesh_->num_nodes_per_element();
nIPsPerElement_ = feMesh_->num_ips_per_element();
nIPsPerFace_ = feMesh_->num_ips_per_face();
nSD_ = feMesh_->num_spatial_dimensions();
nElems_ = feMesh_->num_elements();
nNodesUnique_ = feMesh_->num_nodes_unique();
nNodes_ = feMesh_->num_nodes();
// arrays & matrices
_weights_.reset(nIPsPerElement_,nIPsPerElement_);
_N_.reset(nIPsPerElement_,nNodesPerElement_);
_dN_.assign(nSD_, DENS_MAT(nIPsPerElement_,nNodesPerElement_));
_Nw_.reset(nIPsPerElement_,nNodesPerElement_);
_dNw_.assign(nSD_, DENS_MAT(nIPsPerElement_,nNodesPerElement_));
_Bfluxes_.assign(nSD_, DENS_MAT());
// faces
_fweights_.reset(nIPsPerElement_,nIPsPerElement_);
_fN_.reset(nIPsPerFace_,nNodesPerElement_);
_fdN_.assign(nSD_, DENS_MAT(nIPsPerFace_, nNodesPerElement_));
_nN_.assign(nSD_, DENS_MAT(nIPsPerFace_, nNodesPerElement_));
// remove specified elements
if (nullElements_.size() > 0) delete_elements(nullElements_);
initialized_ = true;
}
}
void FE_Engine::partition_mesh()
{
if (is_partitioned()) return;
feMesh_->partition_mesh();
// now do all FE_Engine data structure partitioning
// partition nullElements_
/*for (vector<int>::iterator elemsIter = feMesh_->processor_elts().begin();
elemsIter != feMesh_->processor_elts().end();
++elemsIter)
{
if (nullElements_.find(*elemsIter) != nullElements_.end()) {
myNullElements_.insert(map_elem_to_myElem(*elemsIter));
}
}*/
}
void FE_Engine::departition_mesh()
{
if (!is_partitioned()) return;
feMesh_->departition_mesh();
}
void FE_Engine::set_quadrature(FeIntQuadrature type, bool temp) const
{
if (!feMesh_) throw ATC_Error("FE_Engine has no mesh");
feMesh_->set_quadrature(type);
if (!temp) quadrature_ = type;
int nIPsPerElement_new = feMesh_->num_ips_per_element();
int nIPsPerFace_new = feMesh_->num_ips_per_face();
if (nIPsPerElement_ != nIPsPerElement_new) {
// arrays & matrices
nIPsPerElement_ = nIPsPerElement_new;
_weights_.resize(nIPsPerElement_,nIPsPerElement_);
_N_.resize(nIPsPerElement_,nNodesPerElement_);
_dN_.assign(nSD_, DENS_MAT(nIPsPerElement_,nNodesPerElement_));
_Nw_.reset(nIPsPerElement_,nNodesPerElement_);
_dNw_.assign(nSD_, DENS_MAT(nIPsPerElement_,nNodesPerElement_));
}
if (nIPsPerFace_ != nIPsPerFace_new) {
// faces
nIPsPerFace_ = nIPsPerFace_new;
_fweights_.reset(nIPsPerElement_,nIPsPerElement_);
_fN_.reset(nIPsPerFace_,nNodesPerElement_);
_fdN_.assign(nSD_, DENS_MAT(nIPsPerFace_, nNodesPerElement_));
_nN_.assign(nSD_, DENS_MAT(nIPsPerFace_, nNodesPerElement_));
}
}
//-----------------------------------------------------------------
bool FE_Engine::modify(int narg, char **arg)
{
bool match = false;
/*! \page man_mesh_create fix_modify AtC mesh create
\section syntax
fix_modify AtC mesh create <nx> <ny> <nz> <region-id>
<f|p> <f|p> <f|p> \n
- nx ny nz = number of elements in x, y, z
- region-id = id of region that is to be meshed
- f p p = periodicity flags for x, y, z
\section examples
<TT> fix_modify AtC mesh create 10 1 1 feRegion p p p </TT> \n
\section description
Creates a uniform mesh in a rectangular region
\section restrictions
Creates only uniform rectangular grids in a rectangular region
\section related
\ref man_mesh_quadrature
\section default
When created, mesh defaults to gauss2 (2-point Gaussian) quadrature.
Use "mesh quadrature" command to change quadrature style.
*/
int argIdx = 0;
if (strcmp(arg[argIdx],"mesh")==0) {
argIdx++;
// create mesh
if (strcmp(arg[argIdx],"create")==0) {
if (feMesh_) throw ATC_Error("FE Engine already has a mesh");
argIdx++;
int nx = atoi(arg[argIdx++]);
int ny = atoi(arg[argIdx++]);
int nz = atoi(arg[argIdx++]);
string box = arg[argIdx++];
Array<bool> periodicity(3);
periodicity(0) = (strcmp(arg[argIdx++],"p")==0) ? true : false;
periodicity(1) = (strcmp(arg[argIdx++],"p")==0) ? true : false;
periodicity(2) = (strcmp(arg[argIdx++],"p")==0) ? true : false;
if (argIdx < narg ) {
Array<double> dx(nx),dy(ny),dz(nz);
dx = 0;
dy = 0;
dz = 0;
while (argIdx < narg) {
if (strcmp(arg[argIdx],"dx")==0) {
parse_partitions(++argIdx, narg, arg, 0, dx);
}
else if (strcmp(arg[argIdx],"dy")==0) {
parse_partitions(++argIdx, narg, arg, 1, dy);
}
else if (strcmp(arg[argIdx],"dz")==0) {
parse_partitions(++argIdx, narg, arg, 2, dz);
}
}
create_mesh(dx, dy, dz, box.c_str(), periodicity);
}
else {
create_mesh(nx, ny, nz, box.c_str(), periodicity);
}
quadrature_ = GAUSS2;
match = true;
}
/*! \page man_mesh_quadrature fix_modify AtC mesh quadrature
\section syntax
fix_modify AtC mesh quadrature <quad>
- quad = one of <nodal|gauss1|gauss2|gauss3|face> --- when a mesh is created it defaults to gauss2, use this call to change it after the fact
\section examples
<TT> fix_modify AtC mesh quadrature face </TT>
\section description
(Re-)assigns the quadrature style for the existing mesh.
\section restrictions
\section related
\ref man_mesh_create
\section default
none
*/
else if (strcmp(arg[argIdx],"quadrature")==0) {
argIdx++;
string quadStr = arg[argIdx];
FeIntQuadrature quadEnum = string_to_FIQ(quadStr);
set_quadrature(quadEnum);
match = true;
}
/*! \page man_mesh_read fix_modify AtC mesh read
\section syntax
fix_modify AtC mesh read <filename> <f|p> <f|p> <f|p>
- filename = name of file containing mesh to be read
- f p p = periodicity flags for x, y, z
\section examples
<TT> fix_modify AtC mesh read myComponent.mesh p p p </TT> \n
<TT> fix_modify AtC mesh read myOtherComponent.exo </TT>
\section description
Reads a mesh from a text or exodus file, and assigns periodic
boundary conditions if needed.
\section restrictions
\section related
\section default
periodicity flags are false by default
*/
else if (strcmp(arg[argIdx],"read")==0) {
argIdx++;
string meshFile = arg[argIdx++];
Array<bool> periodicity(3);
periodicity = false;
if (argIdx < narg) {
periodicity(0) = (strcmp(arg[argIdx++],"p")==0) ? true : false;
periodicity(1) = (strcmp(arg[argIdx++],"p")==0) ? true : false;
periodicity(2) = (strcmp(arg[argIdx++],"p")==0) ? true : false;
}
read_mesh(meshFile,periodicity);
if (periodicity(0) || periodicity(1) || periodicity(2)) {
meshFile = "periodic_"+meshFile;
stringstream ss;
ss << "writing periodicity corrected mesh: " << meshFile;
print_msg(communicator_,ss.str());
feMesh_->write_mesh(meshFile);
feMesh_->output(meshFile);
}
match = true;
}
/*! \page man_mesh_write fix_modify AtC mesh write
\section syntax
fix_modify AtC mesh write <filename>
- filename = name of file to write mesh
\section examples
<TT> fix_modify AtC mesh write myMesh.mesh </TT> \n
\section description
Writes a mesh to a text file.
\section restrictions
\section related
\section default
*/
else if (strcmp(arg[argIdx],"write")==0) {
argIdx++;
string meshFile = arg[argIdx];
feMesh_->write_mesh(meshFile);
match = true;
}
/*! \page man_mesh_delete_elements fix_modify AtC mesh delete_elements
\section syntax
fix_modify AtC mesh delete_elements <element_set>
- <element_set> = name of an element set
\section examples
<TT> fix_modify AtC delete_elements gap </TT>
\section description
Deletes a group of elements from the mesh.
\section restrictions
\section related
\section default
none
*/
else if (strcmp(arg[argIdx],"delete_elements")==0) {
argIdx++;
string esetName = arg[argIdx];
set<int> elemSet = feMesh_->elementset(esetName);
nullElements_.insert(elemSet.begin(), elemSet.end());
match = true;
}
else if (strcmp(arg[argIdx],"cut")==0) {
argIdx++;
string fsetName = arg[argIdx++];
set<PAIR> faceSet = feMesh_->faceset(fsetName);
cutFaces_.insert(faceSet.begin(), faceSet.end());
if (narg > argIdx && strcmp(arg[argIdx],"edge")==0) {
argIdx++;
string nsetName = arg[argIdx];
set<int> nodeSet = feMesh_->nodeset(nsetName);
cutEdge_.insert(nodeSet.begin(), nodeSet.end());
}
// cut mesh
if (cutFaces_.size() > 0) cut_mesh(cutFaces_,cutEdge_);
match = true;
}
else if (strcmp(arg[argIdx],"lammps_partition")==0) {
feMesh_->set_lammps_partition(true);
match = true;
}
else if (strcmp(arg[argIdx],"data_decomposition")==0) {
feMesh_->set_data_decomposition(true);
match = true;
}
else {
if ( ! feMesh_ ) throw ATC_Error("need mesh for parsing");
match = feMesh_->modify(narg,arg);
}
}
// FE_Mesh
else {
if ( ! feMesh_ ) throw ATC_Error("need mesh for parsing");
match = feMesh_->modify(narg,arg);
}
return match;
}
//-----------------------------------------------------------------
void FE_Engine::parse_partitions(int & argIdx, int narg, char ** arg,
int idof, Array<double> & dx ) const
{
double x[3] = {0,0,0};
int nx = dx.size();
// parse relative values for each element
if (is_numeric(arg[argIdx])) {
for (int i = 0; i < nx; ++i) {
if (is_numeric(arg[argIdx])) { dx(i) = atof(arg[argIdx++]); }
else throw ATC_Error("not enough element partitions");
}
}
// each segment of the piecewise funcion is length-normalized separately
else if (strcmp(arg[argIdx],"position-number-density")==0) {
argIdx++;
double y[nx],w[nx];
int n[nx];
int nn = 0;
while (argIdx < narg) {
if (! is_numeric(arg[argIdx])) break;
y[nn] = atof(arg[argIdx++]);
n[nn] = atoi(arg[argIdx++]);
w[nn++] = atof(arg[argIdx++]);
}
if (n[nn-1] != nx) throw ATC_Error("total element partitions do not match");
int k = 0;
for (int i = 1; i < nn; ++i) {
int dn = n[i]-n[i-1];
double dy = y[i]-y[i-1];
double w0 = w[i-1];
double dw = w[i]-w0;
double lx = 0;
double l[dn];
for (int j = 0; j < dn; ++j) {
double x = (j+0.5)/dn;
double dl = w0+x*dw;
lx += dl;
l[j]= dl;
}
double scale = dy/lx;
for (int j = 0; j < dn; ++j) {
dx(k++) = scale*l[j];
}
}
}
// construct relative values from a density function
// evaluate for a domain (0,1)
else {
XT_Function * f = XT_Function_Mgr::instance()->function(&(arg[argIdx]),narg-argIdx);
argIdx++;
while (argIdx < narg) {
if (! is_numeric(arg[argIdx++])) break;
}
for (int i = 0; i < nx; ++i) {
x[idof] = (i+0.5)/nx; dx(i) = f->f(x,0.);
}
}
}
//-----------------------------------------------------------------
void FE_Engine::finish()
{
// Nothing to do
}
//-----------------------------------------------------------------
// write geometry
//-----------------------------------------------------------------
void FE_Engine::initialize_output(int rank,
string outputPrefix, set<int> otypes)
{
outputManager_.initialize(outputPrefix, otypes);
if (!feMesh_) throw ATC_Error("output needs mesh");
if (!initialized_) initialize();
if (!feMesh_->coordinates() || !feMesh_->connectivity())
throw ATC_Error("output mesh not properly initialized");
if (!feMesh_->coordinates()->nCols() ||
!feMesh_->connectivity()->nCols())
throw ATC_Error("output mesh is empty");
if (rank == 0)
outputManager_.write_geometry(feMesh_->coordinates(),
feMesh_->connectivity());
outputManager_.print_custom_names();
}
//-----------------------------------------------------------------
// write geometry
//-----------------------------------------------------------------
void FE_Engine::write_geometry(void)
{
outputManager_.write_geometry(feMesh_->coordinates(),
feMesh_->connectivity());
}
// -------------------------------------------------------------
// write data
// -------------------------------------------------------------
void FE_Engine::write_data(double time,
FIELDS &soln,
OUTPUT_LIST *data)
{
outputManager_.write_data(
time, &soln, data,
(feMesh_->node_map())->data());
}
// -------------------------------------------------------------
// write data
// -------------------------------------------------------------
void FE_Engine::write_data(double time, OUTPUT_LIST *data)
{
outputManager_.write_data(
time, data,
feMesh_->node_map()->data());
}
// -------------------------------------------------------------
// amend mesh for deleted elements
// -------------------------------------------------------------
void FE_Engine::delete_elements(const set<int> & elementList)
{
feMesh_->delete_elements(elementList);
}
// -------------------------------------------------------------
// amend mesh for cut at specified faces
// -------------------------------------------------------------
void FE_Engine::cut_mesh(const set<PAIR> &faceSet,
const set<int> &nodeSet)
{
feMesh_->cut_mesh(faceSet,nodeSet);
}
// -------------------------------------------------------------
// interpolate one value
// -------------------------------------------------------------
DENS_VEC FE_Engine::interpolate_field(const DENS_VEC & x, const FIELD & f) const
{
DENS_VEC N;
Array<int> nodelist;
feMesh_->shape_functions(x, N, nodelist);
const DENS_MAT &vI = f.quantity();
int dof = vI.nCols();
DENS_MAT vIe(nNodesPerElement_, dof);
for (int i = 0; i < nNodesPerElement_; i++)
for (int j = 0; j < dof; j++)
vIe(i,j) = vI(nodelist(i),j);
DENS_VEC vP;
vP = N*vIe;
return vP;
}
// -------------------------------------------------------------
// interpolate fields and gradients
// Currently, this function will break if called with an unowned ielem.
// Currently, this function is only called with owned ielems.
// -------------------------------------------------------------
void FE_Engine::interpolate_fields(
const int ielem,
const FIELDS &fields,
AliasArray<int> & conn,
DENS_MAT & N,
DENS_MAT_VEC & dN,
DIAG_MAT & _weights_,
FIELD_MATS &fieldsAtIPs,
GRAD_FIELD_MATS &gradFieldsAtIPs) const
{
// evaluate shape functions
feMesh_->shape_function(ielem, N, dN, _weights_);
// get connectivity
_conn_ = feMesh_->element_connectivity_unique(ielem);
// compute fields and gradients of fields ips of this element
FIELD_MATS localElementFields;
for (_fieldItr_ = fields.begin(); _fieldItr_ != fields.end(); _fieldItr_++) {
// field values at all nodes
_fieldName_ = _fieldItr_->first;
const DENS_MAT &vI = (_fieldItr_->second).quantity();
int dof = vI.nCols();
// field values at integration points -> to be computed
DENS_MAT &vP = fieldsAtIPs[_fieldName_];
// gradients of field at integration points -> to be computed
DENS_MAT_VEC &dvP = gradFieldsAtIPs[_fieldName_];
if (_fieldName_ == ELECTRON_WAVEFUNCTION_ENERGIES ) {
vP = vI;
continue;
}
// field values at element nodes
DENS_MAT &vIe = localElementFields[_fieldName_];
// gather local field
vIe.reset(nNodesPerElement_, dof);
for (int i = 0; i < nNodesPerElement_; i++)
for (int j = 0; j < dof; j++)
vIe(i,j) = vI(conn(i),j);
// interpolate field at integration points
vP = N*vIe;
// gradients
dvP.assign(nSD_, DENS_MAT(nIPsPerElement_, dof));
for (int j = 0; j < nSD_; ++j) dvP[j] = dN[j]*vIe;
}
}
// -------------------------------------------------------------
// interpolate fields
// Currently, this function will break if called with an unowned ielem.
// Currently, this function is only called with owned ielems.
// -------------------------------------------------------------
void FE_Engine::interpolate_fields(
const int ielem,
const FIELDS &fields,
AliasArray<int> & conn,
DENS_MAT & N,
DIAG_MAT & _weights_,
FIELD_MATS &fieldsAtIPs) const
{
// evaluate shape functions
feMesh_->shape_function(ielem, N, _weights_);
// get connectivity
_conn_ = feMesh_->element_connectivity_unique(ielem);
// compute fields and gradients of fields ips of this element
FIELD_MATS localElementFields;
for (_fieldItr_ = fields.begin(); _fieldItr_ != fields.end(); _fieldItr_++) {
// field values at all nodes
_fieldName_ = _fieldItr_->first;
const DENS_MAT &vI = (_fieldItr_->second).quantity();
int dof = vI.nCols();
// field values at integration points -> to be computed
DENS_MAT &vP = fieldsAtIPs[_fieldName_];
// field values at element nodes
DENS_MAT &vIe = localElementFields[_fieldName_];
if (_fieldName_ == ELECTRON_WAVEFUNCTION_ENERGIES ) {
vP = vI;
continue;
}
// gather local field
vIe.reset(nNodesPerElement_, dof);
for (int i = 0; i < nNodesPerElement_; i++)
for (int j = 0; j < dof; j++)
vIe(i,j) = vI(conn(i),j);
// interpolate field at integration points
vP = N*vIe;
}
}
// -------------------------------------------------------------
// compute dimensionless stiffness matrix using native quadrature
// -------------------------------------------------------------
void FE_Engine::stiffness_matrix(SPAR_MAT &matrix) const
{
// assemble consistent mass
matrix.reset(nNodesUnique_,nNodesUnique_);// zero since partial fill
DENS_MAT elementMassMatrix(nNodesPerElement_,nNodesPerElement_);
vector<int> myElems = feMesh_->owned_elts();
for (vector<int>::iterator elemsIter = myElems.begin();
elemsIter != myElems.end();
++elemsIter)
{
int ielem = *elemsIter;
// evaluate shape functions
feMesh_->shape_function(ielem, _N_, _dN_, _weights_); // _N_ unused
// perform quadrature
elementMassMatrix = _dN_[0].transMat(_weights_*_dN_[0]);
for (int i = 1; i < nSD_; ++i) {
elementMassMatrix += _dN_[i].transMat(_weights_*_dN_[i]);
}
// get connectivity
_conn_ = feMesh_->element_connectivity_unique(ielem);
for (int i = 0; i < nNodesPerElement_; ++i)
{
int inode = _conn_(i);
for (int j = 0; j < nNodesPerElement_; ++j)
{
int jnode = _conn_(j);
matrix.add(inode, jnode, elementMassMatrix(i,j));
}
}
}
#ifdef ISOLATE_FE
sparse_allsum(communicator_,matrix);
#else
LammpsInterface::instance()->sparse_allsum(matrix);
#endif
matrix.compress();
}
// -------------------------------------------------------------
// compute tangent using native quadrature for one (field,field) pair
// -------------------------------------------------------------
void FE_Engine::compute_tangent_matrix(
const Array2D<bool> &rhsMask,
const pair<FieldName,FieldName> row_col,
const FIELDS &fields,
const PhysicsModel * physicsModel,
const Array<int> & elementMaterials,
SPAR_MAT &tangent,
const DenseMatrix<bool> *elementMask) const
{
tangent.reset(nNodesUnique_,nNodesUnique_);
FieldName rowField = row_col.first;
FieldName colField = row_col.second;
bool BB = rhsMask(rowField,FLUX);
bool NN = rhsMask(rowField,SOURCE);
DENS_MAT elementMatrix(nNodesPerElement_,nNodesPerElement_);
DENS_MAT coefsAtIPs;
vector<int> myElems = feMesh_->owned_elts();
for (vector<int>::iterator elemsIter = myElems.begin();
elemsIter != myElems.end();
++elemsIter)
{
int ielem = *elemsIter;
// if element is masked, skip it
if (elementMask && !(*elementMask)(ielem,0)) continue;
// material id
int imat = elementMaterials(ielem);
const Material * mat = physicsModel->material(imat);
// interpolate fields and gradients (nonlinear only)
interpolate_fields(ielem,fields,_conn_,_N_,_dN_,_weights_,
_fieldsAtIPs_,_gradFieldsAtIPs_);
// evaluate Physics model
if (! (physicsModel->null(rowField,imat)) ) {
if (BB && physicsModel->weak_equation(rowField)->
has_BB_tangent_coefficients() ) {
physicsModel->weak_equation(rowField)->
BB_tangent_coefficients(colField, _fieldsAtIPs_, mat, coefsAtIPs);
DIAG_MAT D(column(coefsAtIPs,0));
D = _weights_*D;
elementMatrix = _dN_[0].transMat(D*_dN_[0]);
for (int i = 1; i < nSD_; i++) {
elementMatrix += _dN_[i].transMat(D*_dN_[i]);
}
}
else {
elementMatrix.reset(nNodesPerElement_,nNodesPerElement_);
}
if (NN && physicsModel->weak_equation(rowField)->
has_NN_tangent_coefficients() ) {
physicsModel->weak_equation(rowField)->
NN_tangent_coefficients(colField, _fieldsAtIPs_, mat, coefsAtIPs);
DIAG_MAT D(column(coefsAtIPs,0));
D = _weights_*D;
elementMatrix += _N_.transMat(D*_N_);
}
// assemble
for (int i = 0; i < nNodesPerElement_; ++i)
{
int inode = _conn_(i);
for (int j = 0; j < nNodesPerElement_; ++j)
{
int jnode = _conn_(j);
tangent.add(inode, jnode, elementMatrix(i,j));
}
}
}
}
#ifdef ISOLATE_FE
sparse_allsum(communicator_,tangent);
#else
LammpsInterface::instance()->sparse_allsum(tangent);
#endif
tangent.compress();
}
// -------------------------------------------------------------
// compute tangent using given quadrature for one (field,field) pair
// -------------------------------------------------------------
void FE_Engine::compute_tangent_matrix(const Array2D<bool> &rhsMask,
const pair<FieldName,FieldName> row_col,
const FIELDS &fields,
const PhysicsModel * physicsModel,
const Array<set<int> > & pointMaterialGroups,
const DIAG_MAT &weights,
const SPAR_MAT &N,
const SPAR_MAT_VEC &dN,
SPAR_MAT &tangent,
const DenseMatrix<bool> *elementMask ) const
{
int nn = nNodesUnique_;
FieldName rowField = row_col.first;
FieldName colField = row_col.second;
bool BB = rhsMask(rowField,FLUX);
bool NN = rhsMask(rowField,SOURCE);
DENS_MAT K(nn,nn);
DENS_MAT coefsAtIPs;
int nips = weights.nCols();
if (nips>0) {
// compute fields and gradients of fields at given ips
GRAD_FIELD_MATS gradFieldsAtIPs;
FIELD_MATS fieldsAtIPs;
for (_fieldItr_ = fields.begin(); _fieldItr_ != fields.end(); _fieldItr_++) {
_fieldName_ = _fieldItr_->first;
const DENS_MAT & field = (_fieldItr_->second).quantity();
int dof = field.nCols();
gradFieldsAtIPs[_fieldName_].assign(nSD_,DENS_MAT(nips,dof));
fieldsAtIPs[_fieldName_] = N*field;
for (int j = 0; j < nSD_; ++j) {
gradFieldsAtIPs[_fieldName_][j] = (*dN[j])*field;
}
}
// treat single material point sets specially
int nMatls = pointMaterialGroups.size();
int atomMatls = 0;
for (int imat = 0; imat < nMatls; imat++) {
const set<int> & indices = pointMaterialGroups(imat);
if ( indices.size() > 0) atomMatls++;
}
bool singleMaterial = ( atomMatls == 1 );
if (! singleMaterial ) throw ATC_Error("FE_Engine::compute_tangent_matrix-given quadrature can not handle multiple atom material currently");
if (singleMaterial)
{
int imat = 0;
const Material * mat = physicsModel->material(imat);
// evaluate Physics model
if (! (physicsModel->null(rowField,imat)) ) {
if (BB && physicsModel->weak_equation(rowField)->
has_BB_tangent_coefficients() ) {
physicsModel->weak_equation(rowField)->
BB_tangent_coefficients(colField, fieldsAtIPs, mat, coefsAtIPs);
DIAG_MAT D(column(coefsAtIPs,0));
D = weights*D;
K = (*dN[0]).transMat(D*(*dN[0]));
for (int i = 1; i < nSD_; i++) {
K += (*dN[i]).transMat(D*(*dN[i]));
}
}
if (NN && physicsModel->weak_equation(rowField)->
has_NN_tangent_coefficients() ) {
physicsModel->weak_equation(rowField)->
NN_tangent_coefficients(colField, fieldsAtIPs, mat, coefsAtIPs);
DIAG_MAT D(column(coefsAtIPs,0));
D = weights*D;
K += N.transMat(D*N);
}
}
}
}
// share information between processors
int count = nn*nn;
DENS_MAT K_sum(nn,nn);
allsum(communicator_, K.ptr(), K_sum.ptr(), count);
// create sparse from dense
tangent.reset(K_sum,tol_sparse);
tangent.compress();
}
// -------------------------------------------------------------
// compute a consistent mass matrix for native quadrature
// -------------------------------------------------------------
void FE_Engine::compute_mass_matrix(
const Array<FieldName>& field_mask,
const FIELDS &fields,
const PhysicsModel * physicsModel,
const Array<int> & elementMaterials,
CON_MASS_MATS & massMatrices,
const DenseMatrix<bool> *elementMask) const
{
int nfields = field_mask.size();
vector<FieldName> usedFields;
DENS_MAT elementMassMatrix(nNodesPerElement_,nNodesPerElement_);
// (JAT, 04/21/11) FIX THIS
DENS_MAT capacity;
// zero, use incoming matrix as template if possible
for (int j = 0; j < nfields; ++j) {
_fieldName_ = field_mask(j);
SPAR_MAT & M = massMatrices[_fieldName_].set_quantity();
// compresses 2May11
if (M.has_template()) { M = 0; }
else { M.reset(nNodesUnique_,nNodesUnique_); }
M.reset(nNodesUnique_,nNodesUnique_);
}
// element wise assembly
vector<int> myElems = feMesh_->owned_elts();
for (vector<int>::iterator elemsIter = myElems.begin();
elemsIter != myElems.end();
++elemsIter)
{
int ielem = *elemsIter;
// if element is masked, skip
if (elementMask && !(*elementMask)(ielem,0)) continue;
// material id
int imat = elementMaterials(ielem);
const Material * mat = physicsModel->material(imat);
// interpolate fields
interpolate_fields(ielem,fields,_conn_,_N_,_weights_,_fieldsAtIPs_);
for (int j = 0; j < nfields; ++j) {
_fieldName_ = field_mask(j);
SPAR_MAT & M = massMatrices[_fieldName_].set_quantity();
// skip null weakEqns by material
if (! (physicsModel->null(_fieldName_,imat)) ) {
physicsModel->weak_equation(_fieldName_)->
M_integrand(_fieldsAtIPs_, mat, capacity);
DIAG_MAT rho(column(capacity,0));
elementMassMatrix = _N_.transMat(_weights_*rho*_N_);
// assemble
for (int i = 0; i < nNodesPerElement_; ++i) {
int inode = _conn_(i);
for (int j = 0; j < nNodesPerElement_; ++j) {
int jnode = _conn_(j);
M.add(inode, jnode, elementMassMatrix(i,j));
}
}
}
}
}
for (int j = 0; j < nfields; ++j) {
_fieldName_ = field_mask(j);
SPAR_MAT & M = massMatrices[_fieldName_].set_quantity();
#ifdef ISOLATE_FE
sparse_allsum(communicator_,M);
#else
LammpsInterface::instance()->sparse_allsum(M);
#endif
}
// fix zero diagonal entries due to null material elements
for (int j = 0; j < nfields; ++j) {
_fieldName_ = field_mask(j);
SPAR_MAT & M = massMatrices[_fieldName_].set_quantity();
for (int inode = 0; inode < nNodesUnique_; ++inode) {
if (! M.has_entry(inode,inode)) {
M.set(inode,inode,1.0);
}
}
M.compress();
}
}
// -------------------------------------------------------------
// compute dimensionless consistent mass using native quadrature
// -------------------------------------------------------------
void FE_Engine::compute_mass_matrix(SPAR_MAT &massMatrix) const
{
// assemble nnodes X nnodes matrix
massMatrix.reset(nNodesUnique_,nNodesUnique_);// zero since partial fill
DENS_MAT elementMassMatrix(nNodesPerElement_,nNodesPerElement_);
vector<int> myElems = feMesh_->owned_elts();
for (vector<int>::iterator elemsIter = myElems.begin();
elemsIter != myElems.end();
++elemsIter)
{
int ielem = *elemsIter;
// evaluate shape functions
feMesh_->shape_function(ielem, _N_, _weights_);
// perform quadrature
elementMassMatrix = _N_.transMat(_weights_*_N_);
// get connectivity
_conn_ = feMesh_->element_connectivity_unique(ielem);
for (int i = 0; i < nNodesPerElement_; ++i) {
int inode = _conn_(i);
for (int j = 0; j < nNodesPerElement_; ++j) {
int jnode = _conn_(j);
massMatrix.add(inode, jnode, elementMassMatrix(i,j));
}
}
}
// Assemble partial results
#ifdef ISOLATE_FE
sparse_allsum(communicator_,massMatrix);
#else
LammpsInterface::instance()->sparse_allsum(massMatrix);
#endif
}
// -------------------------------------------------------------
// compute dimensionless consistent mass using given quadrature
// -------------------------------------------------------------
void FE_Engine::compute_mass_matrix(const DIAG_MAT &weights,
const SPAR_MAT &N,
SPAR_MAT &massMatrix) const
{
int nn = N.nCols();
int nips = N.nRows();
DENS_MAT tmp_mass_matrix_local(nn,nn), tmp_mass_matrix(nn,nn);
if (nips>0) { tmp_mass_matrix_local = N.transMat(weights*N); }
// share information between processors
int count = nn*nn;
allsum(communicator_,
tmp_mass_matrix_local.ptr(),
tmp_mass_matrix.ptr(), count);
// create sparse from dense
massMatrix.reset(tmp_mass_matrix,tol_sparse);
}
// -------------------------------------------------------------
// compute dimensionless lumped mass using native quadrature
// -------------------------------------------------------------
void FE_Engine::compute_lumped_mass_matrix(DIAG_MAT & M) const
{
M.reset(nNodesUnique_,nNodesUnique_); // zero since partial fill
// assemble lumped diagonal mass
DENS_VEC Nvec(nNodesPerElement_);
vector<int> myElems = feMesh_->owned_elts();
for (vector<int>::iterator elemsIter = myElems.begin();
elemsIter != myElems.end();
++elemsIter)
{
int ielem = *elemsIter;
// evaluate shape functions
feMesh_->shape_function(ielem, _N_, _weights_);
CLON_VEC w(_weights_);
// get connectivity
_conn_ = feMesh_->element_connectivity_unique(ielem);
Nvec = w*_N_;
for (int i = 0; i < nNodesPerElement_; ++i) {
int inode = _conn_(i);
M(inode,inode) += Nvec(i);
}
}
// Assemble partial results
allsum(communicator_,MPI_IN_PLACE, M.ptr(), M.size());
}
// -------------------------------------------------------------
// compute physical lumped mass using native quadrature
// -------------------------------------------------------------
void FE_Engine::compute_lumped_mass_matrix(
const Array<FieldName>& field_mask,
const FIELDS & fields,
const PhysicsModel * physicsModel,
const Array<int> &elementMaterials,
MASS_MATS & massMatrices, // mass matrix
const DenseMatrix<bool> *elementMask) const
{
int nfields = field_mask.size();
// zero initialize for assembly
for (int j = 0; j < nfields; ++j) {
DIAG_MAT & M = massMatrices[field_mask(j)].set_quantity();
M.reset(nNodesUnique_,nNodesUnique_);
}
// assemble diagonal matrix
vector<int> myElems = feMesh_->owned_elts();
for (vector<int>::iterator elemsIter = myElems.begin();
elemsIter != myElems.end();
++elemsIter)
{
int ielem = *elemsIter;
// if element is masked, skip it
if (elementMask && !(*elementMask)(ielem,0)) continue;
// material id
int imat = elementMaterials(ielem);
const Material * mat = physicsModel->material(imat);
interpolate_fields(ielem,fields,_conn_,_N_,_weights_,_fieldsAtIPs_);
// compute densities, integrate & assemble
DENS_MAT capacity;
for (int j = 0; j < nfields; ++j) {
_fieldName_ = field_mask(j);
if (! physicsModel->null(_fieldName_,imat)) {
physicsModel->weak_equation(_fieldName_)->
M_integrand(_fieldsAtIPs_, mat, capacity);
_Nmat_ = _N_.transMat(_weights_*capacity);
DIAG_MAT & M = massMatrices[_fieldName_].set_quantity();
for (int i = 0; i < nNodesPerElement_; ++i) {
int inode = _conn_(i);
M(inode,inode) += _Nmat_(i,0);
}
}
}
}
// Assemble partial results
for (int j = 0; j < nfields; ++j) {
_fieldName_ = field_mask(j);
DIAG_MAT & M = massMatrices[_fieldName_].set_quantity();
allsum(communicator_,MPI_IN_PLACE, M.ptr(), M.size());
}
// fix zero diagonal entries due to null material elements
for (int inode = 0; inode < nNodesUnique_; ++inode) {
for (int j = 0; j < nfields; ++j) {
_fieldName_ = field_mask(j);
DIAG_MAT & M = massMatrices[_fieldName_].set_quantity();
if (M(inode,inode) == 0.0) {
M(inode,inode) = 1.0;
}
}
}
}
// -------------------------------------------------------------
// compute physical lumped mass using given quadrature
// -------------------------------------------------------------
void FE_Engine::compute_lumped_mass_matrix(
const Array<FieldName> &field_mask,
const FIELDS &fields,
const PhysicsModel * physicsModel,
const Array<set<int> > & pointMaterialGroups,
const DIAG_MAT &weights,
const SPAR_MAT &N,
MASS_MATS &M) const // mass matrices
{
int nips = weights.nCols();
int nfields = field_mask.size();
// initialize
map<FieldName, DIAG_MAT> M_local;
for (int j = 0; j < nfields; ++j) {
_fieldName_ = field_mask(j);
M_local[_fieldName_].reset(nNodesUnique_,nNodesUnique_);
M[_fieldName_].reset(nNodesUnique_,nNodesUnique_);
}
if (nips>0) {
// compute fields at given ips
// compute all fields since we don't the capacities dependencies
FIELD_MATS fieldsAtIPs;
for (_fieldItr_ = fields.begin(); _fieldItr_ != fields.end(); _fieldItr_++) {
_fieldName_ = _fieldItr_->first;
const DENS_MAT & field = (_fieldItr_->second).quantity();
int dof = field.nCols();
fieldsAtIPs[_fieldName_].reset(nips,dof);
fieldsAtIPs[_fieldName_] = N*field;
}
// treat single material point sets specially
int nMatls = pointMaterialGroups.size();
int atomMatls = 0;
int iatomMat = 0;
for (int imat = 0; imat < nMatls; imat++) {
const set<int> & indices = pointMaterialGroups(imat);
if ( indices.size() > 0) {
iatomMat = imat;
atomMatls++;
}
}
if (atomMatls == 0) {
throw ATC_Error("no materials in atom region - atoms may have migrated to FE-only region");
}
bool singleMaterial = ( atomMatls == 1 );
if (! singleMaterial ) {
stringstream ss; ss << " WARNING: multiple materials in atomic region";
print_msg(communicator_,ss.str());
}
// setup data structures
FIELD_MATS capacities;
// evaluate physics model & compute capacity|density for requested fields
if (singleMaterial) {
const Material * mat = physicsModel->material(iatomMat);
for (int j = 0; j < nfields; ++j) {
_fieldName_ = field_mask(j);
// mass matrix needs to be invertible so null matls have cap=1
if (physicsModel->null(_fieldName_,iatomMat)) {
throw ATC_Error("null material not supported for atomic region (single material)");
const FIELD & f = (fields.find(_fieldName_))->second;
capacities[_fieldName_].reset(f.nRows(),f.nCols());
capacities[_fieldName_] = 1.;
}
else {
physicsModel->weak_equation(_fieldName_)->
M_integrand(fieldsAtIPs, mat, capacities[_fieldName_]);
}
}
}
else {
FIELD_MATS groupCapacities, fieldsGroup;
for (int j = 0; j < nfields; ++j) {
_fieldName_ = field_mask(j);
capacities[_fieldName_].reset(nips,1);
}
for ( int imat = 0; imat < pointMaterialGroups.size(); imat++) {
const Material * mat = physicsModel->material(imat);
const set<int> & indices = pointMaterialGroups(imat);
int npts = indices.size();
if (npts > 0) {
for (int j = 0; j < nfields; ++j) {
_fieldName_ = field_mask(j);
groupCapacities[_fieldName_].reset(npts,1);
const FIELD & f = (fields.find(_fieldName_))->second;
int ndof = f.nCols();
fieldsGroup[_fieldName_].reset(npts,ndof);
int i = 0;
for (set<int>::const_iterator iter=indices.begin();
iter != indices.end(); iter++, i++ ) {
for (int dof = 0; dof < ndof; ++dof) {
fieldsGroup[_fieldName_](i,dof)
= fieldsAtIPs[_fieldName_](*iter,dof);
}
}
}
for (int j = 0; j < nfields; ++j) {
_fieldName_ = field_mask(j);
if (physicsModel->null(_fieldName_,imat)) {
throw ATC_Error("null material not supported for atomic region (multiple materials)");
const FIELD & f = (fields.find(_fieldName_))->second;
groupCapacities[_fieldName_].reset(f.nRows(),f.nCols());
groupCapacities[_fieldName_] = 1.;
}
else {
physicsModel->weak_equation(_fieldName_)->
M_integrand(fieldsGroup, mat, groupCapacities[_fieldName_]);
}
int i = 0;
for (set<int>::const_iterator iter=indices.begin();
iter != indices.end(); iter++, i++ ) {
capacities[_fieldName_](*iter,0)
= groupCapacities[_fieldName_](i,0);
}
}
}
}
}
// integrate & assemble
for (int j = 0; j < nfields; ++j) {
_fieldName_ = field_mask(j);
M_local[_fieldName_].reset( // assume all columns same
column(N.transMat(weights*capacities[_fieldName_]),0) );
}
}
// Share information between processors
for (int j = 0; j < nfields; ++j) {
_fieldName_ = field_mask(j);
DIAG_MAT & myMassMat(M[_fieldName_].set_quantity());
int count = M_local[_fieldName_].size();
allsum(communicator_, M_local[_fieldName_].ptr(), myMassMat.ptr(), count);
}
}
//-----------------------------------------------------------------
// compute assembled average gradient evaluated at the nodes
//-----------------------------------------------------------------
void FE_Engine::compute_gradient_matrix(SPAR_MAT_VEC & grad_matrix) const
{
// zero
DENS_MAT_VEC tmp_grad_matrix(nSD_);
for (int i = 0; i < nSD_; i++) {
tmp_grad_matrix[i].reset(nNodesUnique_,nNodesUnique_);
}
// element wise assembly
Array<int> count(nNodesUnique_); count = 0;
set_quadrature(NODAL);
vector<int> myElems = feMesh_->owned_elts();
for (vector<int>::iterator elemsIter = myElems.begin();
elemsIter != myElems.end();
++elemsIter)
{
int ielem = *elemsIter;
// evaluate shape functions
feMesh_->shape_function(ielem, _N_, _dN_, _weights_);
// get connectivity
_conn_ = feMesh_->element_connectivity_unique(ielem);
for (int j = 0; j < nIPsPerElement_; ++j) {
int jnode = _conn_(j);
count(jnode) += 1;
for (int i = 0; i < nNodesPerElement_; ++i) {
int inode = _conn_(i);
for (int k = 0; k < nSD_; ++k) {
tmp_grad_matrix[k](jnode,inode) += _dN_[k](j,i);
}
}
}
}
// Assemble partial results
for (int k = 0; k < nSD_; ++k) {
allsum(communicator_,MPI_IN_PLACE, tmp_grad_matrix[k].ptr(), tmp_grad_matrix[k].size());
}
int_allsum(communicator_,MPI_IN_PLACE, count.ptr(), count.size());
set_quadrature(quadrature_); //reset to default
for (int inode = 0; inode < nNodesUnique_; ++inode) {
for (int jnode = 0; jnode < nNodesUnique_; ++jnode) {
for (int k = 0; k < nSD_; ++k) {
tmp_grad_matrix[k](jnode,inode) /= count(jnode);
}
}
}
// compact dense matrices
for (int k = 0; k < nSD_; ++k) {
grad_matrix[k]->reset(tmp_grad_matrix[k],tol_sparse);
}
}
// -------------------------------------------------------------
// compute energy per node using native quadrature
// -------------------------------------------------------------
void FE_Engine::compute_energy(const Array<FieldName> &mask,
const FIELDS &fields,
const PhysicsModel * physicsModel,
const Array<int> & elementMaterials,
FIELD_MATS &energies,
const DenseMatrix<bool> *elementMask,
const IntegrationDomainType domain) const
{
// Zero out all fields
for (int n = 0; n < mask.size(); n++) {
_fieldName_ = mask(n);
_fieldItr_ = fields.find(_fieldName_);
const DENS_MAT & field = (_fieldItr_->second).quantity();
energies[_fieldName_].reset(field.nRows(), 1);
}
DENS_MAT elementEnergy(nNodesPerElement_,1); // workspace
vector<int> myElems = feMesh_->owned_elts();
for (vector<int>::iterator elemsIter = myElems.begin();
elemsIter != myElems.end();
++elemsIter)
{
int ielem = *elemsIter;
// if element is masked, skip it
if (domain != FULL_DOMAIN && elementMask && !(*elementMask)(ielem,0)) continue;
// material id
int imat = elementMaterials(ielem);
const Material * mat = physicsModel->material(imat);
interpolate_fields(ielem,fields,_conn_,_N_,_dN_,_weights_,
_fieldsAtIPs_,_gradFieldsAtIPs_);
// assemble
for (int n = 0; n < mask.size(); n++) {
_fieldName_ = mask(n);
if (physicsModel->null(_fieldName_,imat)) continue;
if( ! (physicsModel->weak_equation(_fieldName_)-> has_E_integrand())) continue;
physicsModel->weak_equation(_fieldName_)->
E_integrand(_fieldsAtIPs_, _gradFieldsAtIPs_, mat, elementEnergy);
_fieldItr_ = fields.find(_fieldName_);
_Nmat_ = _N_.transMat(_weights_*elementEnergy);
DENS_MAT & energy = energies[_fieldName_];
for (int i = 0; i < nNodesPerElement_; ++i) {
int inode = _conn_(i);
energy(inode,0) += _Nmat_(i,0);
}
}
}
for (int n = 0; n < mask.size(); n++) {
_fieldName_ = mask(n);
DENS_MAT& myEnergy(energies[_fieldName_]);
allsum(communicator_,MPI_IN_PLACE, myEnergy.ptr(), myEnergy.size());
}
}
// -------------------------------------------------------------
// compute rhs using native quadrature
// -------------------------------------------------------------
void FE_Engine::compute_rhs_vector(const Array2D<bool> &rhsMask,
const FIELDS &fields,
const PhysicsModel * physicsModel,
const Array<int> & elementMaterials,
FIELDS &rhs,
bool freeOnly,
const DenseMatrix<bool> *elementMask) const
{
vector<FieldName> usedFields;
// size and zero output
for (int n = 0; n < rhsMask.nRows(); n++) {
_fieldName_ = FieldName(n);
if (rhsMask(_fieldName_, FLUX) || rhsMask(_fieldName_, SOURCE)) {
_fieldItr_ = fields.find(_fieldName_);
const DENS_MAT & field = (_fieldItr_->second).quantity();
rhs[_fieldName_].reset(field.nRows(), field.nCols());
// Save field names for easy lookup later.
usedFields.push_back(_fieldName_);
}
}
// Iterate over elements partitioned onto current processor.
vector<int> myElems = feMesh_->owned_elts();
for (vector<int>::iterator elemsIter = myElems.begin();
elemsIter != myElems.end();
++elemsIter)
{
int ielem = *elemsIter;
// Skip masked elements
if (elementMask && !(*elementMask)(ielem,0)) continue;
int imat = elementMaterials(ielem); // material id
const Material * mat = physicsModel->material(imat);
// interpolate field values to integration points
interpolate_fields(ielem,fields,_conn_,_N_,_dN_,_weights_,
_fieldsAtIPs_,_gradFieldsAtIPs_);
// rescale by _weights_, a diagonal matrix
_Nw_ = _weights_*_N_;
for (int j = 0; j < nSD_; ++j) _dNw_[j] = _weights_*_dN_[j];
// evaluate physics model and assemble
// _Nfluxes is a set of fields
for (int n = 0; n < rhsMask.nRows(); n++) {
_fieldName_ = FieldName(n);
if (!rhsMask(_fieldName_,SOURCE)) continue;
if (physicsModel->null(_fieldName_,imat)) continue;
_fieldItr_ = fields.find(_fieldName_);
const DENS_MAT & field = (_fieldItr_->second).quantity();
DENS_MAT & myRhs(rhs[_fieldName_].set_quantity());
int dof = field.nCols();
bool has = physicsModel->weak_equation(_fieldName_)->
N_integrand(_fieldsAtIPs_,_gradFieldsAtIPs_, mat, _Nfluxes_);
if (!has) continue;
_Nmat_ = _Nw_.transMat(_Nfluxes_);
for (int i = 0; i < nNodesPerElement_; ++i) {
int inode = _conn_(i);
for (int k = 0; k < dof; ++k) {
myRhs(inode,k) += _Nmat_(i,k);
}
}
}
// _Bfluxes_ is a set of field gradients
for (int n = 0; n < rhsMask.nRows(); n++) {
_fieldName_ = FieldName(n);
if (!rhsMask(_fieldName_,FLUX)) continue;
if (physicsModel->null(_fieldName_,imat)) continue;
_fieldItr_ = fields.find(_fieldName_);
const DENS_MAT & field = (_fieldItr_->second).quantity();
DENS_MAT & myRhs(rhs[_fieldName_].set_quantity());
int dof = field.nCols();
physicsModel->weak_equation(_fieldName_)->
B_integrand(_fieldsAtIPs_, _gradFieldsAtIPs_, mat, _Bfluxes_);
for (int j = 0; j < nSD_; j++) {
_Nmat_ = _dNw_[j].transMat(_Bfluxes_[j]);
for (int i = 0; i < nNodesPerElement_; ++i) {
int inode = _conn_(i);
for (int k = 0; k < dof; ++k) {
myRhs(inode,k) += _Nmat_(i,k);
}
}
}
}
}
vector<FieldName>::iterator _fieldIter_;
for (_fieldIter_ = usedFields.begin(); _fieldIter_ != usedFields.end();
++_fieldIter_) {
// myRhs is where we put the global result for this field.
_fieldName_ = *_fieldIter_;
DENS_MAT & myRhs(rhs[_fieldName_].set_quantity());
// Sum matrices in-place across all processors into myRhs.
allsum(communicator_,MPI_IN_PLACE, myRhs.ptr(), myRhs.size());
}
}
// -------------------------------------------------------------
// compute rhs using given quadrature
// -------------------------------------------------------------
void FE_Engine::compute_rhs_vector(const Array2D<bool> &rhsMask,
const FIELDS &fields,
const PhysicsModel *physicsModel,
const Array<set<int> >&pointMaterialGroups,
const DIAG_MAT &weights,
const SPAR_MAT &N,
const SPAR_MAT_VEC &dN,
FIELDS &rhs) const
{
FIELD_MATS rhs_local;
for (int n = 0; n < rhsMask.nRows(); n++) {
_fieldName_ = FieldName(n);
if (rhsMask(_fieldName_,FLUX) || rhsMask(_fieldName_,SOURCE)) {
_fieldItr_ = fields.find(_fieldName_);
const DENS_MAT & field = (_fieldItr_->second).quantity();
int nrows = field.nRows();
int ncols = field.nCols();
rhs [_fieldName_].reset(nrows,ncols);
rhs_local[_fieldName_].reset(nrows,ncols);
}
}
int nips = weights.nCols();
if (nips>0) {
// compute fields and gradients of fields at given ips
GRAD_FIELD_MATS gradFieldsAtIPs;
FIELD_MATS fieldsAtIPs;
for (_fieldItr_ = fields.begin(); _fieldItr_ != fields.end(); _fieldItr_++) {
_fieldName_ = _fieldItr_->first;
const DENS_MAT & field = (_fieldItr_->second).quantity();
int dof = field.nCols();
gradFieldsAtIPs[_fieldName_].assign(nSD_,DENS_MAT(nips,dof));
fieldsAtIPs[_fieldName_] = N*field;
for (int j = 0; j < nSD_; ++j) {
gradFieldsAtIPs[_fieldName_][j] = (*dN[j])*field;
}
}
// setup data structures
FIELD_MATS Nfluxes;
GRAD_FIELD_MATS Bfluxes;
for (int n = 0; n < rhsMask.nRows(); n++) {
_fieldName_ = FieldName(n);
int index = (int) _fieldName_;
if ( rhsMask(index,FLUX) ) {
Bfluxes[_fieldName_].assign(nSD_, DENS_MAT());
}
}
// treat single material point sets specially
int nMatls = pointMaterialGroups.size();
int atomMatls = 0;
for (int imat = 0; imat < nMatls; imat++) {
const set<int> & indices = pointMaterialGroups(imat);
if ( indices.size() > 0) atomMatls++;
}
bool singleMaterial = ( atomMatls == 1 );
// evaluate physics model
if (singleMaterial)
{
int imat = 0;
const Material * mat = physicsModel->material(imat);
for (int n = 0; n < rhsMask.nRows(); n++) {
_fieldName_ = FieldName(n);
int index = (int) _fieldName_;
if (! physicsModel->null(_fieldName_,imat)) {
if ( rhsMask(index,SOURCE) ) {
physicsModel->weak_equation(_fieldName_)->
N_integrand(fieldsAtIPs, gradFieldsAtIPs, mat, Nfluxes[_fieldName_]);
}
if ( rhsMask(index,FLUX) ) {
physicsModel->weak_equation(_fieldName_)->
B_integrand(fieldsAtIPs, gradFieldsAtIPs, mat, Bfluxes[_fieldName_]);
}
}
}
}
else
{
// 1) permanent workspace with per-row mapped clones per matl
// from caller/atc
// 2) per point calls to N/B_integrand
// 3) collect internalToAtom into materials and use mem-cont clones
// what about dof for matrices and data storage: clone _matrix_
// for each material group:
// set up storage
DENS_MAT group_Nfluxes;
DENS_MAT_VEC group_Bfluxes;
group_Bfluxes.reserve(nSD_);
FIELD_MATS fieldsGroup;
GRAD_FIELD_MATS gradFieldsGroup;
for (_fieldItr_ = fields.begin(); _fieldItr_ != fields.end(); _fieldItr_++) {
_fieldName_ = _fieldItr_->first;
const DENS_MAT & field = (_fieldItr_->second).quantity();
int ndof = field.nCols();
gradFieldsGroup[_fieldName_].assign(nSD_,DENS_MAT(nips,ndof));
Nfluxes[_fieldName_].reset(nips,ndof);
Bfluxes[_fieldName_].assign(nSD_,DENS_MAT(nips,ndof));
//}
}
// copy fields
for ( int imat = 0; imat < pointMaterialGroups.size(); imat++)
{
const set<int> & indices = pointMaterialGroups(imat);
const Material * mat = physicsModel->material(0);
int npts = indices.size();
int i = 0;
for (set<int>::const_iterator iter=indices.begin();
iter != indices.end(); iter++, i++ ) {
for (_fieldItr_ = fields.begin(); _fieldItr_ != fields.end(); _fieldItr_++) {
_fieldName_ = _fieldItr_->first;
const DENS_MAT & field = (_fieldItr_->second).quantity();
int ndof = field.nCols();
fieldsGroup[_fieldName_].reset(npts,ndof);
for (int j = 0; j < nSD_; ++j) {
(gradFieldsGroup[_fieldName_][j]).reset(npts,ndof);
}
for (int dof = 0; dof < ndof; ++dof) {
fieldsGroup[_fieldName_](i,dof)
= fieldsAtIPs[_fieldName_](*iter,dof);
for (int j = 0; j < nSD_; ++j) {
gradFieldsGroup[_fieldName_][j](i,dof)
= gradFieldsAtIPs[_fieldName_][j](*iter,dof);
}
}
}
}
// calculate forces, & assemble
for (int n = 0; n < rhsMask.nRows(); n++) {
_fieldName_ = FieldName(n);
_fieldItr_ = fields.find(_fieldName_);
int index = (int) _fieldName_;
if (physicsModel->null(_fieldName_,imat)) continue;
if ( rhsMask(index,SOURCE) ) {
const DENS_MAT & field = (_fieldItr_->second).quantity();
int ndof = field.nCols();
bool has = physicsModel->weak_equation(_fieldName_)->
N_integrand(fieldsGroup, gradFieldsGroup, mat, group_Nfluxes);
if (! has) throw ATC_Error("atomic source can not be null currently");
int i = 0;
for (set<int>::const_iterator iter=indices.begin();
iter != indices.end(); iter++, i++ ) {
for (int dof = 0; dof < ndof; ++dof) {
Nfluxes[_fieldName_](*iter,dof) += group_Nfluxes(i,dof);
}
}
}
}
// calculate forces, & assemble
for (int n = 0; n < rhsMask.nRows(); n++) {
_fieldName_ = FieldName(n);
if (physicsModel->null(_fieldName_,imat)) continue;
int index = (int) _fieldName_;
if ( rhsMask(index,FLUX) ) {
_fieldItr_ = fields.find(_fieldName_);
const DENS_MAT & field = (_fieldItr_->second).quantity();
int ndof = field.nCols();
physicsModel->weak_equation(_fieldName_)->
B_integrand(fieldsGroup, gradFieldsGroup, mat, group_Bfluxes);
int i = 0;
for (set<int>::const_iterator iter=indices.begin();
iter != indices.end(); iter++, i++ ) {
for (int dof = 0; dof < ndof; ++dof) {
for (int j = 0; j < nSD_; ++j) {
Bfluxes[_fieldName_][j](*iter,dof) += group_Bfluxes[j](i,dof);
}
}
}
}
}
}
} // endif multiple materials
// assemble : N/Bfluxes -> rhs_local
for (int n = 0; n < rhsMask.nRows(); n++) {
_fieldName_ = FieldName(n);
int index = (int) _fieldName_;
if ( rhsMask(index,SOURCE) && Nfluxes[_fieldName_].nCols() > 0 ) {
rhs_local[_fieldName_]
+= N.transMat(weights*Nfluxes[_fieldName_]);
}
if ( rhsMask(index,FLUX) && (Bfluxes[_fieldName_][0]).nCols() > 0 ) {
for (int j = 0; j < nSD_; ++j) {
rhs_local[_fieldName_]
+= dN[j]->transMat(weights*Bfluxes[_fieldName_][j]);
}
}
}
} // end nips > 0
// Share information between processors: rhs_local -> rhs
for (int n = 0; n < rhsMask.nRows(); n++) {
_fieldName_ = FieldName(n);
if (rhsMask(_fieldName_,FLUX) || rhsMask(_fieldName_,SOURCE)) {
DENS_MAT & myRhs(rhs[_fieldName_].set_quantity());
int count = rhs_local[_fieldName_].size();
allsum(communicator_, rhs_local[_fieldName_].ptr(), myRhs.ptr(), count);
}
}
}
// -------------------------------------------------------------
// compute sources using given quadrature
// -------------------------------------------------------------
void FE_Engine::compute_source(const Array2D<bool> &rhsMask,
const FIELDS &fields,
const PhysicsModel *physicsModel,
const Array<set<int> >&pointMaterialGroups,
const DIAG_MAT &weights,
const SPAR_MAT &N,
const SPAR_MAT_VEC &dN,
FIELD_MATS &sources) const
{
int nips = weights.nCols();
if (nips>0) {
FIELD_MATS Nfluxes;
// compute fields and gradients of fields at given ips
GRAD_FIELD_MATS gradFieldsAtIPs;
FIELD_MATS fieldsAtIPs;
for (_fieldItr_ = fields.begin(); _fieldItr_ != fields.end(); _fieldItr_++) {
_fieldName_ = _fieldItr_->first;
const DENS_MAT & field = (_fieldItr_->second).quantity();
int dof = field.nCols();
gradFieldsAtIPs[_fieldName_].assign(nSD_,DENS_MAT(nips,dof));
fieldsAtIPs[_fieldName_] = N*field;
for (int j = 0; j < nSD_; ++j) {
gradFieldsAtIPs[_fieldName_][j] = (*dN[j])*field;
}
}
// treat single material point sets specially
int nMatls = pointMaterialGroups.size();
int atomMatls = 0;
for (int imat = 0; imat < nMatls; imat++) {
const set<int> & indices = pointMaterialGroups(imat);
if ( indices.size() > 0) atomMatls++;
}
bool singleMaterial = ( atomMatls == 1 );
// evaluate physics model
if (singleMaterial)
{
int imat = 0;
const Material * mat = physicsModel->material(imat);
for (int n = 0; n < rhsMask.nRows(); n++) {
_fieldName_ = FieldName(n);
int index = (int) _fieldName_;
if (! physicsModel->null(_fieldName_,imat)) {
if ( rhsMask(index,SOURCE) ) {
bool has = physicsModel->weak_equation(_fieldName_)->
N_integrand(fieldsAtIPs, gradFieldsAtIPs, mat, Nfluxes[_fieldName_]);
if (! has) throw ATC_Error("atomic source can not be null currently");
}
}
}
}
else
{
throw ATC_Error("compute source does not handle multiple materials currently");
}
// assemble
for (int n = 0; n < rhsMask.nRows(); n++) {
_fieldName_ = FieldName(n);
int index = (int) _fieldName_;
if ( rhsMask(index,SOURCE) ) {
sources[_fieldName_] =weights*Nfluxes[_fieldName_];
}
}
}
// no need to share information between processors
}
// -------------------------------------------------------------
// compute flux for post processing
// -------------------------------------------------------------
void FE_Engine::compute_flux(const Array2D<bool> &rhsMask,
const FIELDS &fields,
const PhysicsModel * physicsModel,
const Array<int> & elementMaterials,
GRAD_FIELD_MATS &fluxes,
const DenseMatrix<bool> *elementMask) const
{
for (int n = 0; n < rhsMask.nRows(); n++) {
_fieldName_ = FieldName(n);
if (rhsMask(_fieldName_,FLUX)) {
_fieldItr_ = fields.find(_fieldName_);
const DENS_MAT & field = (_fieldItr_->second).quantity();
fluxes[_fieldName_].assign(nSD_, DENS_MAT(field.nRows(),field.nCols()));
}
}
// element wise assembly
vector<int> myElems = feMesh_->owned_elts();
for (vector<int>::iterator elemsIter = myElems.begin();
elemsIter != myElems.end();
++elemsIter)
{
int ielem = *elemsIter;
// if element is masked, skip it
if (elementMask && !(*elementMask)(ielem,0)) continue;
// material id
int imat = elementMaterials(ielem);
const Material * mat = physicsModel->material(imat);
interpolate_fields(ielem,fields,_conn_,_N_,_dN_,_weights_,
_fieldsAtIPs_,_gradFieldsAtIPs_);
_Nw_ = _weights_*_N_;
// evaluate Physics model & assemble
for (int n = 0; n < rhsMask.nRows(); n++) {
_fieldName_ = FieldName(n);
if (!rhsMask(_fieldName_,FLUX)) continue;
if (physicsModel->null(_fieldName_,imat)) continue;
_fieldItr_ = fields.find(_fieldName_);
const DENS_MAT & field = (_fieldItr_->second).quantity();
int dof = field.nCols();
physicsModel->weak_equation(_fieldName_)->
B_integrand(_fieldsAtIPs_, _gradFieldsAtIPs_, mat, _Bfluxes_);
for (int j = 0; j < nSD_; j++) {
_Nmat_ = _Nw_.transMat(_Bfluxes_[j]);
for (int i = 0; i < nNodesPerElement_; ++i) {
int inode = _conn_(i);
for (int k = 0; k < dof; ++k) {
fluxes[_fieldName_][j](inode,k) += _Nmat_(i,k);
}
}
}
}
}
// Assemble partial results
for (int n = 0; n < rhsMask.nRows(); n++) {
_fieldName_ = FieldName(n);
if (!rhsMask(_fieldName_,FLUX)) continue;
for (int j = 0; j < nSD_; j++) {
allsum(communicator_,MPI_IN_PLACE, fluxes[_fieldName_][j].ptr(), fluxes[_fieldName_][j].size());
}
}
}
//-----------------------------------------------------------------
// boundary flux using native quadrature
//-----------------------------------------------------------------
void FE_Engine::compute_boundary_flux(const Array2D<bool> & rhsMask,
const FIELDS & fields,
const PhysicsModel * physicsModel,
const Array<int> & elementMaterials,
const set< pair <int,int> > & faceSet,
FIELDS & rhs) const
{
for (int n = 0; n < rhsMask.nRows(); n++) {
_fieldName_ = FieldName(n);
if (rhsMask(_fieldName_,FLUX)) {
_fieldItr_ = fields.find(_fieldName_);
const DENS_MAT & field = (_fieldItr_->second).quantity();
rhs[_fieldName_].reset(field.nRows(),field.nCols());
}
}
FIELD_MATS localElementFields;
GRAD_FIELD_MATS gradFieldsAtIPs;
FIELD_MATS fieldsAtIPs;
for (_fieldItr_ = fields.begin(); _fieldItr_ != fields.end(); _fieldItr_++) {
_fieldName_ = _fieldItr_->first;
const FIELD & field = _fieldItr_->second;
int dof = field.nCols();
gradFieldsAtIPs[_fieldName_].reserve(nSD_);
for (int i = 0; i < nSD_; ++i) {
gradFieldsAtIPs[_fieldName_].push_back(DENS_MAT(nIPsPerFace_,dof));
}
fieldsAtIPs[_fieldName_].reset(nIPsPerFace_,dof);
localElementFields[_fieldName_].reset(nNodesPerElement_,dof);
}
// element wise assembly
set< PAIR >::iterator iter;
for (iter = faceSet.begin(); iter != faceSet.end(); iter++) {
// get connectivity
int ielem = iter->first;
// if this is not our element, do not do calculations
if (!feMesh_->is_owned_elt(ielem)) continue;
int imat = elementMaterials(ielem);
const Material * mat = physicsModel->material(imat);
_conn_ = feMesh_->element_connectivity_unique(ielem);
// evaluate shape functions at ips
feMesh_->face_shape_function(*iter, _fN_, _fdN_, _nN_, _fweights_);
// interpolate fields and gradients of fields ips of this element
for (_fieldItr_ = fields.begin(); _fieldItr_ != fields.end(); _fieldItr_++) {
_fieldName_ = _fieldItr_->first;
const DENS_MAT & field = (_fieldItr_->second).quantity();
int dof = field.nCols();
for (int i = 0; i < nNodesPerElement_; i++) {
for (int j = 0; j < dof; j++) {
localElementFields[_fieldName_](i,j) = field(_conn_(i),j);
}
}
// ips X dof = ips X nodes * nodes X dof
fieldsAtIPs[_fieldName_] = _fN_*localElementFields[_fieldName_];
for (int j = 0; j < nSD_; ++j) {
gradFieldsAtIPs[_fieldName_][j] = _fdN_[j]*localElementFields[_fieldName_];
}
}
// Evaluate- physics model
// do nothing for N_integrand
// nN : precomputed and held by ATC_Transfer
// assemble
for (int n = 0; n < rhsMask.nRows(); n++) {
_fieldName_ = FieldName(n);
int index = (int) _fieldName_;
if ( rhsMask(index,FLUX) ) {
_fieldItr_ = fields.find(_fieldName_);
const DENS_MAT & field = (_fieldItr_->second).quantity();
DENS_MAT & myRhs(rhs[_fieldName_].set_quantity());
physicsModel->weak_equation(_fieldName_)->
B_integrand(fieldsAtIPs, gradFieldsAtIPs, mat, _Bfluxes_);
int dof = field.nCols();
for (int j = 0; j < nSD_; j++) {
_Nmat_ = _nN_[j].transMat(_fweights_*_Bfluxes_[j]);
for (int i = 0; i < nNodesPerElement_; ++i) {
int inode = _conn_(i);
for (int k = 0; k < dof; ++k) {
myRhs(inode,k) += _Nmat_(i,k);
}
}
}
}
}
}
for (int n = 0; n < rhsMask.nRows(); n++) {
_fieldName_ = FieldName(n);
int index = (int) _fieldName_;
if ( rhsMask(index,FLUX) ) {
DENS_MAT & myRhs(rhs[_fieldName_].set_quantity());
allsum(communicator_,MPI_IN_PLACE, myRhs.ptr(), myRhs.size());
}
}
}
// -------------------------------------------------------------
// compute boundary flux using given quadrature and interpolation
// -------------------------------------------------------------
void FE_Engine::compute_boundary_flux(const Array2D<bool> & rhsMask,
const FIELDS & fields,
const PhysicsModel * physicsModel,
const Array< int > & elementMaterials,
const Array< set<int> > & pointMaterialGroups,
const DIAG_MAT & _weights_,
const SPAR_MAT & N,
const SPAR_MAT_VEC & dN,
const DIAG_MAT & flux_mask,
FIELDS & flux,
const DenseMatrix<bool> * elementMask,
const set<int> * nodeSet) const
{
FIELDS rhs, rhsSubset;
for (int n = 0; n < rhsMask.nRows(); n++) {
_fieldName_ = FieldName(n);
if (rhsMask(_fieldName_,FLUX)) {
_fieldItr_ = fields.find(_fieldName_);
const DENS_MAT & field = (_fieldItr_->second).quantity();
int nrows = field.nRows();
int ncols = field.nCols();
rhs [_fieldName_].reset(nrows,ncols);
rhsSubset[_fieldName_].reset(nrows,ncols);
}
}
// R_I = - int_Omega Delta _N_I .q dV
compute_rhs_vector(rhsMask, fields, physicsModel, elementMaterials, rhs, elementMask);
// R_I^md = - int_Omega^md Delta _N_I .q dV
compute_rhs_vector(rhsMask, fields, physicsModel, pointMaterialGroups,
_weights_, N, dN, rhsSubset);
// flux_I = 1/Delta V_I V_I^md R_I + R_I^md
// where : Delta V_I = int_Omega _N_I dV
for (int n = 0; n < rhsMask.nRows(); n++) {
_fieldName_ = FieldName(n);
if ( rhsMask(_fieldName_,FLUX) ) {
if (nodeSet) {
_fieldItr_ = fields.find(_fieldName_);
const DENS_MAT & field = (_fieldItr_->second).quantity();
int nrows = field.nRows();
int ncols = field.nCols();
DENS_MAT & myFlux(flux[_fieldName_].set_quantity());
const DENS_MAT & myRhsSubset(rhsSubset[_fieldName_].quantity());
const DENS_MAT & myRhs(rhs[_fieldName_].quantity());
myFlux.reset(nrows,ncols);
set<int>::const_iterator iset;
for (iset = nodeSet->begin(); iset != nodeSet->end(); iset++) {
for (int j = 0; j < ncols; j++) {
myFlux(*iset,j) = myRhsSubset(*iset,j) - flux_mask(*iset,*iset)*myRhs(*iset,j);
}
}
}
else {
flux[_fieldName_]
= rhsSubset[_fieldName_].quantity() - flux_mask*(rhs[_fieldName_].quantity());
}
}
}
}
/** integrate a nodal field over an element set */
DENS_VEC FE_Engine::integrate(const DENS_MAT &field, const ESET & eset) const
{
int dof = field.nCols();
DENS_MAT eField(nNodesPerElement_, dof);
int nips = nIPsPerElement_;
DENS_MAT ipField(nips, dof);
DENS_VEC integral(dof); integral = 0;
for (ESET::const_iterator itr = eset.begin(); itr != eset.end(); ++itr) {
int ielem = *itr;
// if this is not our element, do not do calculations
if (!feMesh_->is_owned_elt(ielem)) continue;
feMesh_->shape_function(ielem,_N_, _weights_);
_conn_ = feMesh_->element_connectivity_unique(ielem);
for (int i = 0; i < nNodesPerElement_; i++) {
for (int j = 0; j < dof; j++) {
eField(i,j) = field(_conn_(i),j); }}
ipField = _N_*eField;
for (int i = 0; i < nips; ++i) {
for (int j = 0; j < dof; ++j) {
integral(j) += ipField(i,j)*_weights_[i];
}
}
}
// assemble partial results
allsum(communicator_,MPI_IN_PLACE, integral.ptr(), integral.size());
return integral;
}
//-----------------------------------------------------------------
// Robin boundary flux using native quadrature
// integrate \int_delV _N_I s(x,t).n dA
//-----------------------------------------------------------------
void FE_Engine::add_robin_fluxes(const Array2D<bool> &rhsMask,
const FIELDS & fields,
const double time,
const ROBIN_SURFACE_SOURCE & sourceFunctions,
FIELDS &nodalSources) const
{
// sizing working arrays
DENS_MAT xCoords(nSD_,nNodesPerElement_);
DENS_MAT faceSource;
DENS_MAT localField;
DENS_MAT xAtIPs(nSD_,nIPsPerFace_);
DENS_MAT uAtIPs(nIPsPerFace_,1);
FIELDS myNodalSources;
// element wise assembly
ROBIN_SURFACE_SOURCE::const_iterator src_iter;
for (src_iter=sourceFunctions.begin(); src_iter!=sourceFunctions.end(); src_iter++)
{
_fieldName_ = src_iter->first;
if (!rhsMask((int)_fieldName_,ROBIN_SOURCE)) continue;
DENS_MAT & s(nodalSources[_fieldName_].set_quantity());
myNodalSources[_fieldName_] = DENS_MAT(s.nRows(),s.nCols());
}
for (src_iter=sourceFunctions.begin(); src_iter!=sourceFunctions.end(); src_iter++)
{
_fieldName_ = src_iter->first;
if (!rhsMask((int)_fieldName_,ROBIN_SOURCE)) continue;
typedef map<PAIR,Array<UXT_Function*> > FSET;
const FSET *fset = (const FSET *)&(src_iter->second);
FSET::const_iterator fset_iter;
for (fset_iter = fset->begin(); fset_iter != fset->end(); fset_iter++)
{
const PAIR &face = fset_iter->first;
const int elem = face.first;
// if this is not our element, do not do calculations
if (!feMesh_->is_owned_elt(elem)) continue;
const Array <UXT_Function*> &fs = fset_iter->second;
_conn_ = feMesh_->element_connectivity_unique(elem);
// evaluate location at ips
feMesh_->face_shape_function(face, _fN_, _fdN_, _nN_, _fweights_);
feMesh_->element_coordinates(elem, xCoords);
xAtIPs = xCoords*(_fN_.transpose());
// collect field
_fieldItr_ = fields.find(_fieldName_);
const DENS_MAT & field = (_fieldItr_->second).quantity();
feMesh_->element_field(elem, field, localField);
uAtIPs = _fN_*localField;
// interpolate prescribed flux at ips of this element
FSET::const_iterator face_iter = fset->find(face);
int nFieldDOF = (face_iter->second).size();
faceSource.reset(nIPsPerFace_,nFieldDOF);
for (int ip = 0; ip < nIPsPerFace_; ++ip) {
for (int idof = 0; idof<nFieldDOF; ++idof) {
UXT_Function * f = fs(idof);
if (!f) continue;
faceSource(ip,idof) = f->f(&(uAtIPs(ip,0)),
column(xAtIPs,ip).ptr(),time);
DENS_MAT coefsAtIPs(nIPsPerFace_,1);
coefsAtIPs(ip,idof) = f->dfdu(&(uAtIPs(ip,0)),
column(xAtIPs,ip).ptr(),time);
faceSource(ip,idof) -= coefsAtIPs(ip,0)*uAtIPs(ip,0);
}
}
// assemble
DENS_MAT & s(myNodalSources[_fieldName_].set_quantity());
_Nmat_ = _fN_.transMat(_fweights_*faceSource);
for (int i = 0; i < nNodesPerElement_; ++i) {
int inode = _conn_(i);
for (int idof = 0; idof < nFieldDOF; ++idof) {
s(inode,idof) += _Nmat_(i,idof);
}
}
}
}
// assemble partial result matrices
for (src_iter=sourceFunctions.begin(); src_iter!=sourceFunctions.end(); src_iter++) {
_fieldName_ = src_iter->first;
if (!rhsMask((int) _fieldName_,ROBIN_SOURCE)) continue;
DENS_MAT & s(myNodalSources[_fieldName_].set_quantity());
allsum(communicator_,MPI_IN_PLACE, s.ptr(), s.size());
DENS_MAT & src(nodalSources[_fieldName_].set_quantity());
src += s;
}
}
//-----------------------------------------------------------------
// Robin boundary flux stiffness using native quadrature
// integrate \int_delV _N_I ds/du(x,t).n dA
//-----------------------------------------------------------------
void FE_Engine::add_robin_tangent(const Array2D<bool> &rhsMask,
const FIELDS & fields,
const double time,
const ROBIN_SURFACE_SOURCE & sourceFunctions,
SPAR_MAT &tangent) const
{
// sizing working arrays
DENS_MAT xCoords(nSD_,nNodesPerElement_);
DENS_MAT coefsAtIPs;
DENS_MAT localField;
DENS_MAT xAtIPs(nSD_,nIPsPerFace_);
DENS_MAT uAtIPs(nIPsPerFace_,1);
// element wise assembly
ROBIN_SURFACE_SOURCE::const_iterator src_iter;
SPAR_MAT K(tangent.nRows(), tangent.nCols());
for (src_iter=sourceFunctions.begin(); src_iter!=sourceFunctions.end(); src_iter++)
{
_fieldName_ = src_iter->first;
if (!rhsMask((int)_fieldName_,ROBIN_SOURCE)) continue;
typedef map<PAIR,Array<UXT_Function*> > FSET;
const FSET *fset = (const FSET *)&(src_iter->second);
FSET::const_iterator fset_iter;
for (fset_iter = fset->begin(); fset_iter != fset->end(); fset_iter++)
{
const PAIR &face = fset_iter->first;
const int elem = face.first;
// if this is not our element, do not do calculations
if (!feMesh_->is_owned_elt(elem)) continue;
const Array <UXT_Function*> &fs = fset_iter->second;
_conn_ = feMesh_->element_connectivity_unique(elem);
// evaluate location at ips
feMesh_->face_shape_function(face, _fN_, _fdN_, _nN_, _fweights_);
feMesh_->element_coordinates(elem, xCoords);
xAtIPs = xCoords*(_fN_.transpose());
// collect field
_fieldItr_ = fields.find(_fieldName_);
const DENS_MAT & field = (_fieldItr_->second).quantity();
feMesh_->element_field(elem, field, localField);
uAtIPs = _fN_*localField;
// interpolate prescribed flux at ips of this element
FSET::const_iterator face_iter = fset->find(face);
int nFieldDOF = (face_iter->second).size();
coefsAtIPs.reset(nIPsPerFace_,nFieldDOF);
for (int ip = 0; ip < nIPsPerFace_; ++ip) {
for (int idof = 0; idof<nFieldDOF; ++idof) {
UXT_Function * f = fs(idof);
if (!f) continue;
coefsAtIPs(ip,idof) = f->dfdu(&(uAtIPs(ip,0)),
column(xAtIPs,ip).ptr(),time);
}
}
// assemble
DIAG_MAT D(column(coefsAtIPs,0));
D *= -1;
D *= _fweights_;
_Nmat_ = _fN_.transMat(D*_fN_);
for (int i = 0; i < nNodesPerElement_; ++i) {
int inode = _conn_(i);
for (int j = 0; j < nNodesPerElement_; ++j) {
int jnode = _conn_(j);
K.add(inode, jnode, _Nmat_(i,j));
}
}
}
}
// assemble partial result matrices
#ifdef ISOLATE_FE
sparse_allsum(communicator_,K);
#else
LammpsInterface::instance()->sparse_allsum(K);
#endif
tangent += K;
}
//-----------------------------------------------------------------
// Robin boundary flux using native quadrature
// integrate \int_delV _N_I s(x,t).n dA
//-----------------------------------------------------------------
void FE_Engine::add_open_fluxes(const Array2D<bool> &rhsMask,
const FIELDS & fields,
const OPEN_SURFACE & openFaces,
FIELDS &nodalSources,
const FieldName Velocity) const
{
// sizing working arrays
DENS_MAT xCoords(nSD_,nNodesPerElement_);
DENS_MAT faceSource;
DENS_MAT localField;
DENS_MAT xAtIPs(nSD_,nIPsPerFace_);
DENS_MAT uAtIPs; // (nIPsPerFace_,field nCols);
DENS_MAT aAtIPs(nIPsPerFace_,1);
FIELDS myNodalSources;
// throw error if electron velocity is not defined
// element wise assembly
OPEN_SURFACE::const_iterator face_iter;
for (face_iter=openFaces.begin(); face_iter!=openFaces.end(); face_iter++)
{
_fieldName_ = face_iter->first;
if (!rhsMask((int)_fieldName_,OPEN_SOURCE)) continue;
DENS_MAT & s(nodalSources[_fieldName_].set_quantity());
myNodalSources[_fieldName_] = DENS_MAT(s.nRows(),s.nCols());
}
for (face_iter=openFaces.begin(); face_iter!=openFaces.end(); face_iter++)
{
_fieldName_ = face_iter->first;
if (!rhsMask((int)_fieldName_,OPEN_SOURCE)) continue;
typedef set<PAIR> FSET;
const FSET *fset = (const FSET *)&(face_iter->second);
FSET::const_iterator fset_iter;
for (fset_iter = fset->begin(); fset_iter != fset->end(); fset_iter++)
{
const PAIR &face = *fset_iter;
const int elem = face.first;
// if this is not our element, do not do calculations
if (!feMesh_->is_owned_elt(elem)) continue;
// get velocity, multiply with field (vector gives tensor)
// dot with face normal
_conn_ = feMesh_->element_connectivity_unique(elem);
// evaluate location at ips
feMesh_->face_shape_function(face, _fN_, _fdN_, _nN_, _fweights_);
// collect field at ips
_fieldItr_ = fields.find(_fieldName_);
const DENS_MAT & field = (_fieldItr_->second).quantity();
feMesh_->element_field(elem, field, localField);
int nFieldDOF = field.nCols();
// "u" is the quantity being advected
uAtIPs.reset(nIPsPerFace_,nFieldDOF);
uAtIPs = _fN_*localField;
// collect velocity at ips for "advection" = v.n
_fieldItr_ = fields.find(Velocity);
const DENS_MAT & advection = (_fieldItr_->second).quantity(); // advection velocity
feMesh_->element_field(elem, advection, localField);
for (int j = 0; j < nSD_; ++j) { // nSD_ == nDOF for the velocity
for (int ip = 0; ip < nIPsPerFace_; ++ip) {
for (int I = 0; I < nNodesPerElement_; ++I) {
aAtIPs(ip,0) += (_nN_[j])(ip,I)*localField(I,j);
}
}
}
// construct open flux at ips of this element
// flux = field \otimes advection_vector \dot n
faceSource.reset(nIPsPerFace_,nFieldDOF);
for (int ip = 0; ip < nIPsPerFace_; ++ip) {
for (int idof = 0; idof<nFieldDOF; ++idof) {
// multiply field DOF value times advection vector
faceSource(ip,idof) = aAtIPs(ip,1)*uAtIPs(ip,idof);//(v.n) u
}
}
// assemble contributions for this direction of the face normal
// _nN_[j](ip,I) nodal shapefunction(I) at ip X face normal(j)
// _fweights_ diagonal nips X nips ==> facesource is nips X ndofs
DENS_MAT & s(myNodalSources[_fieldName_].set_quantity());
_Nmat_ = _fN_.transMat(_fweights_*faceSource);
// s_Ii = \sum_ip N_I (u_i v.n)_ip wg_ip
for (int i = 0; i < nNodesPerElement_; ++i) {
int inode = _conn_(i);
for (int idof = 0; idof < nFieldDOF; ++idof) {
s(inode,idof) += _Nmat_(i,idof);
}
}
}
}
// assemble partial result matrices
for (face_iter=openFaces.begin(); face_iter!=openFaces.end(); face_iter++) {
_fieldName_ = face_iter->first;
if (!rhsMask((int) _fieldName_,OPEN_SOURCE)) continue;
DENS_MAT & s(myNodalSources[_fieldName_].set_quantity());
allsum(communicator_,MPI_IN_PLACE, s.ptr(), s.size());
DENS_MAT & src(nodalSources[_fieldName_].set_quantity());
src += s;
}
}
//-----------------------------------------------------------------
// Open boundary flux stiffness using native quadrature
// integrate \int_delV _N_I ds/du(x,t).n dA
//-----------------------------------------------------------------
void FE_Engine::add_open_tangent(const Array2D<bool> &rhsMask,
const FIELDS & fields,
const OPEN_SURFACE & openFaces,
SPAR_MAT &tangent,
const FieldName Velocity) const
{
// sizing working arrays
DENS_MAT xCoords(nSD_,nNodesPerElement_);
DENS_MAT faceSource;
DENS_MAT localField;
DENS_MAT xAtIPs(nSD_,nIPsPerFace_);
DENS_MAT uAtIPs; // (nIPsPerFace_,field nCols);
DENS_MAT aAtIPs(nIPsPerFace_,nSD_);
SPAR_MAT K(tangent.nRows(), tangent.nCols());
// element wise assembly
OPEN_SURFACE::const_iterator face_iter;
for (face_iter=openFaces.begin(); face_iter!=openFaces.end(); face_iter++)
{
_fieldName_ = face_iter->first;
if (!rhsMask((int)_fieldName_,OPEN_SOURCE)) continue;
bool convective = false;
if (_fieldName_ == Velocity) convective = true;
typedef set<PAIR> FSET;
const FSET *fset = (const FSET *)&(face_iter->second);
FSET::const_iterator fset_iter;
for (fset_iter = fset->begin(); fset_iter != fset->end(); fset_iter++)
{
const PAIR &face = *fset_iter;
const int elem = face.first;
// if this is not our element, do not do calculations
if (!feMesh_->is_owned_elt(elem)) continue;
_conn_ = feMesh_->element_connectivity_unique(elem);
// evaluate location at ips
feMesh_->face_shape_function(face, _fN_, _fdN_, _nN_, _fweights_);
// collect field at ips
_fieldItr_ = fields.find(_fieldName_);
const DENS_MAT & field = (_fieldItr_->second).quantity();
feMesh_->element_field(elem, field, localField);
int nFieldDOF = field.nCols();
// "u" is the quantity being advected
uAtIPs.reset(nIPsPerFace_,nFieldDOF);
uAtIPs = _fN_*localField;
// collect velocity at ips for "advection" = v.n
_fieldItr_ = fields.find(Velocity);
const DENS_MAT & advection = (_fieldItr_->second).quantity(); // advection velocity
feMesh_->element_field(elem, advection, localField);
for (int j = 0; j < nSD_; ++j) { // nSD_ == nDOF for the velocity
for (int ip = 0; ip < nIPsPerFace_; ++ip) {
for (int I = 0; I < nNodesPerElement_; ++I) {
aAtIPs(ip,0) += (_nN_[j])(ip,I)*localField(I,j);
}
}
}
// K_IJ = \sum_ip N_I ( v.n I + u (x) n ) N_J wg_ip
DENS_MAT D(nFieldDOF,nFieldDOF);
DENS_VEC n(nSD_);
for (int ip = 0; ip < nIPsPerFace_; ++ip) {
for (int idof = 0; idof<nFieldDOF; ++idof) {
D(idof,idof) -= aAtIPs(ip,0);
if (convective) {
feMesh_->face_normal(face,ip,n);
for (int jdof = 0; jdof<nFieldDOF; ++jdof) {
D(idof,jdof) -= uAtIPs(ip,idof)*n(jdof);
}
}
}
}
// assemble
_Nmat_ = _fN_.transMat(D*_fN_);
for (int i = 0; i < nNodesPerElement_; ++i) {
int inode = _conn_(i);
for (int j = 0; j < nNodesPerElement_; ++j) {
int jnode = _conn_(j);
K.add(inode, jnode, _Nmat_(i,j));
}
}
}
}
// assemble partial result matrices
#ifdef ISOLATE_FE
sparse_allsum(communicator_,K);
#else
LammpsInterface::instance()->sparse_allsum(K);
#endif
tangent += K;
}
//-----------------------------------------------------------------
// prescribed boundary flux using native quadrature
// integrate \int_delV _N_I s(x,t).n dA
//-----------------------------------------------------------------
void FE_Engine::add_fluxes(const Array<bool> &fieldMask,
const double time,
const SURFACE_SOURCE & sourceFunctions,
FIELDS &nodalSources) const
{
// sizing working arrays
DENS_MAT xCoords(nSD_,nNodesPerElement_);
DENS_MAT xAtIPs(nSD_,nIPsPerFace_);
DENS_MAT faceSource;
FIELDS myNodalSources;
// element wise assembly
SURFACE_SOURCE::const_iterator src_iter;
for (src_iter=sourceFunctions.begin(); src_iter!=sourceFunctions.end(); src_iter++)
{
_fieldName_ = src_iter->first;
if (!fieldMask((int)_fieldName_)) continue;
DENS_MAT & s(nodalSources[_fieldName_].set_quantity());
myNodalSources[_fieldName_] = DENS_MAT(s.nRows(),s.nCols());
}
for (src_iter=sourceFunctions.begin(); src_iter!=sourceFunctions.end(); src_iter++)
{
_fieldName_ = src_iter->first;
if (!fieldMask((int)_fieldName_)) continue;
typedef map<PAIR,Array<XT_Function*> > FSET;
const FSET *fset = (const FSET *)&(src_iter->second);
FSET::const_iterator fset_iter;
for (fset_iter = fset->begin(); fset_iter != fset->end(); fset_iter++)
{
const PAIR &face = fset_iter->first;
const int elem = face.first;
// if this is not our element, do not do calculations
if (!feMesh_->is_owned_elt(elem)) continue;
const Array <XT_Function*> &fs = fset_iter->second;
_conn_ = feMesh_->element_connectivity_unique(elem);
// evaluate location at ips
feMesh_->face_shape_function(face, _fN_, _fdN_, _nN_, _fweights_);
feMesh_->element_coordinates(elem, xCoords);
MultAB(xCoords,_fN_,xAtIPs,0,1); //xAtIPs = xCoords*(N.transpose());
// interpolate prescribed flux at ips of this element
FSET::const_iterator face_iter = fset->find(face);
if (face_iter == fset->end())
{
stringstream ss;
ss << "face not found" << std::endl;
print_msg(communicator_,ss.str());
}
int nFieldDOF = (face_iter->second).size();
faceSource.reset(nIPsPerFace_,nFieldDOF);
for (int ip = 0; ip < nIPsPerFace_; ++ip) {
for (int idof = 0; idof<nFieldDOF; ++idof) {
XT_Function * f = fs(idof);
if (!f) continue;
faceSource(ip,idof) = f->f(column(xAtIPs,ip).ptr(),time);
}
}
// assemble
DENS_MAT & s(myNodalSources[_fieldName_].set_quantity());
_Nmat_ = _fN_.transMat(_fweights_*faceSource);
for (int i = 0; i < nNodesPerElement_; ++i)
{
int inode = _conn_(i);
for (int idof = 0; idof < nFieldDOF; ++idof)
{
s(inode,idof) += _Nmat_(i,idof);
} // end assemble nFieldDOF
} // end assemble nNodesPerElement_
} // end fset loop
} // field loop
// assemble partial result matrices
for (src_iter=sourceFunctions.begin(); src_iter!=sourceFunctions.end(); src_iter++) {
_fieldName_ = src_iter->first;
if (!fieldMask((int)_fieldName_)) continue;
DENS_MAT & s(myNodalSources[_fieldName_].set_quantity());
allsum(communicator_,MPI_IN_PLACE, s.ptr(), s.size());
DENS_MAT & src(nodalSources[_fieldName_].set_quantity());
src += s;
}
}
//-----------------------------------------------------------------
// prescribed volume flux using native quadrature
// integrate \int_V _N_I s(x,t) dV
//-----------------------------------------------------------------
void FE_Engine::add_sources(const Array<bool> &fieldMask,
const double time,
const VOLUME_SOURCE &sourceFunctions,
FIELDS &nodalSources) const
{
// sizing working arrays
DENS_MAT elemSource;
DENS_MAT xCoords(nSD_,nNodesPerElement_);
DENS_MAT xAtIPs(nSD_,nIPsPerElement_);
FIELDS myNodalSources;
for (VOLUME_SOURCE::const_iterator src_iter = sourceFunctions.begin();
src_iter != sourceFunctions.end(); src_iter++) {
_fieldName_ = src_iter->first;
int index = (int) _fieldName_;
if (! fieldMask(index)) continue;
DENS_MAT & s(nodalSources[_fieldName_].set_quantity());
myNodalSources[_fieldName_] = DENS_MAT(s.nRows(),s.nCols());
}
vector<int> myElems = feMesh_->owned_elts();
for (vector<int>::iterator elemsIter = myElems.begin();
elemsIter != myElems.end();
++elemsIter)
{
int ielem = *elemsIter;
_conn_ = feMesh_->element_connectivity_unique(ielem);
// evaluate location at ips
feMesh_->shape_function(ielem, _N_, _weights_);
feMesh_->element_coordinates(ielem, xCoords);
xAtIPs =xCoords*(_N_.transpose());
for (VOLUME_SOURCE::const_iterator src_iter = sourceFunctions.begin();
src_iter != sourceFunctions.end(); src_iter++) {
_fieldName_ = src_iter->first;
int index = (int) _fieldName_;
if ( fieldMask(index) ) {
const Array2D<XT_Function *> * thisSource
= (const Array2D<XT_Function *> *) &(src_iter->second);
int nFieldDOF = thisSource->nCols();
elemSource.reset(nIPsPerElement_,nFieldDOF);
// interpolate prescribed flux at ips of this element
for (int ip = 0; ip < nIPsPerElement_; ++ip) {
for (int idof = 0; idof < nFieldDOF; ++idof) {
XT_Function * f = (*thisSource)(ielem,idof);
if (f) {
elemSource(ip,idof) = f->f(column(xAtIPs,ip).ptr(),time);
}
}
}
// assemble
_Nmat_ = _N_.transMat(_weights_*elemSource);
DENS_MAT & s(myNodalSources[_fieldName_].set_quantity());
for (int i = 0; i < nNodesPerElement_; ++i) {
int inode = _conn_(i);
for (int idof = 0; idof < nFieldDOF; ++idof) {
s(inode,idof) += _Nmat_(i,idof);
}
}
}
}
}
// Aggregate unmasked nodal sources on all processors.
for (VOLUME_SOURCE::const_iterator src_iter = sourceFunctions.begin();
src_iter != sourceFunctions.end(); src_iter++) {
_fieldName_ = src_iter->first;
int index = (int) _fieldName_;
if (!fieldMask(index)) continue;
DENS_MAT & s(myNodalSources[_fieldName_].set_quantity());
allsum(communicator_,MPI_IN_PLACE, s.ptr(), s.size());
DENS_MAT & src(nodalSources[_fieldName_].set_quantity());
src += s;
}
}
//-----------------------------------------------------------------
// previously computed nodal sources
//-----------------------------------------------------------------
void FE_Engine::add_sources(const Array<bool> &fieldMask,
const double time,
const FIELDS &sources,
FIELDS &nodalSources) const
{
FIELDS::const_iterator itr;
for (itr=sources.begin(); itr!=sources.end(); itr++) {
_fieldName_ = itr->first;
if (!fieldMask((int)_fieldName_)) continue;
DENS_MAT & src(nodalSources[_fieldName_].set_quantity());
const DENS_MAT & s((sources.find(_fieldName_)->second).quantity());
for (int inode = 0; inode < nNodesUnique_; ++inode) {
for (int j = 0; j < src.nCols(); ++j) {
src(inode,j) += s(inode,j);
}
}
}
}
//-----------------------------------------------------------------
// boundary integral of a nodal field
//-----------------------------------------------------------------
void FE_Engine::field_surface_flux(
const DENS_MAT & field,
const set< PAIR > & faceSet,
DENS_MAT & values,
const bool contour,
const int axis) const
{
int dof = field.nCols();
double a[3] = {0,0,0};
a[axis] = 1;
// sizing working arrays
DENS_MAT n(nSD_,nIPsPerFace_);
DENS_MAT localElementFields(nNodesPerElement_,dof);
DENS_MAT integrals(dof,nSD_);
DENS_MAT fieldsAtIPs;
// SJL shouldn't this just be _fieldsAtIPs_
// the data member?
// element wise assembly
set< pair <int,int> >::iterator iter;
for (iter = faceSet.begin(); iter != faceSet.end(); iter++) {
int ielem = iter->first;
// if this is not our element, do not do calculations
//if (!feMesh_->is_owned_elt(ielem)) continue;
// evaluate shape functions at ips
feMesh_->face_shape_function(*iter, _N_, n, _fweights_);
// cross n with axis to get tangent
if (contour) {
double t[3];
for (int i = 0; i < nIPsPerFace_; i++) {
t[0] = a[1]*n(2,i) - a[2]*n(1,i);
t[1] = a[2]*n(0,i) - a[0]*n(2,i);
t[2] = a[0]*n(1,i) - a[1]*n(0,i);
n(0,i) = t[0];
n(1,i) = t[1];
n(2,i) = t[2];
}
}
// get connectivity
_conn_ = feMesh_->element_connectivity_unique(ielem);
// interpolate fields and gradients of fields ips of this element
for (int i = 0; i < nNodesPerElement_; i++) {
for (int j = 0; j < dof; j++) {
localElementFields(i,j) = field(_conn_(i),j);
}
}
// ips X dof = ips X nodes * nodes X dof
fieldsAtIPs = _N_*localElementFields;
// assemble : integral(k,j) = sum_ip n(j,ip) wg(ip,ip) field(ip,k)
_Nmat_ = n*_fweights_*fieldsAtIPs;
for (int j = 0; j < nSD_; j++) {
for (int k = 0; k < dof; ++k) {
integrals(k,j) += _Nmat_(j,k);
}
}
}
// assemble partial results
//LammpsInterface::instance()->allsum(MPI_IN_PLACE, integrals.ptr(), integrals.size());
// (S.n)_1 = S_1j n_j = S_11 n_1 + S_12 n_2 + S_13 n_3
// (S.n)_2 = S_2j n_j = S_21 n_1 + S_22 n_2 + S_23 n_3
// (S.n)_3 = S_3j n_j = S_31 n_1 + S_32 n_2 + S_33 n_3
if (dof == 9) { // tensor
values.reset(nSD_,1);
values(0,0) = integrals(0,0)+integrals(1,1)+integrals(2,2);
values(1,0) = integrals(3,0)+integrals(4,1)+integrals(5,2);
values(2,0) = integrals(6,0)+integrals(7,1)+integrals(8,2);
}
else if (dof == 6) { // sym tensor
values.reset(nSD_,1);
values(0,0) = integrals(0,0)+integrals(3,1)+integrals(4,2);
values(1,0) = integrals(3,0)+integrals(1,1)+integrals(5,2);
values(2,0) = integrals(4,1)+integrals(5,1)+integrals(2,2);
}
// (v.n) = v_j n_j
else if (dof == 3) { // vector
values.reset(1,1);
values(0,0) = integrals(0,0)+integrals(1,1)+integrals(2,2);
}
// s n = s n_j e_j
else if (dof == 1) { // scalar
values.reset(nSD_,1);
values(0,0) = integrals(0,0);
values(1,0) = integrals(0,1);
values(2,0) = integrals(0,2);
}
else {
string msg = "FE_Engine::field_surface_flux unsupported field width: ";
msg += to_string(dof);
throw ATC_Error(msg);
}
}
//-----------------------------------------------------------------
// evaluate shape functions at given points
//-----------------------------------------------------------------
void FE_Engine::evaluate_shape_functions(
const MATRIX & pt_coords,
SPAR_MAT & N) const
{
// Get shape function and derivatives at atomic locations
int nnodes = feMesh_->num_nodes_unique();
int npts = pt_coords.nRows();
// loop over point (atom) coordinates
DENS_VEC x(nSD_);
Array<int> node_index(nNodesPerElement_);
DENS_VEC shp(nNodesPerElement_);
N.reset(npts,nnodes);
for (int i = 0; i < npts; ++i) {
for (int k = 0; k < nSD_; ++k) { x(k) = pt_coords(i,k); }
feMesh_->shape_functions(x,shp,node_index);
for (int j = 0; j < nNodesPerElement_; ++j) {
int jnode = node_index(j);
N.add(i,jnode,shp(j));
}
}
N.compress();
}
//-----------------------------------------------------------------
// evaluate shape functions and their spatial derivatives at given points
//-----------------------------------------------------------------
void FE_Engine::evaluate_shape_functions(
const MATRIX & pt_coords,
SPAR_MAT & N,
SPAR_MAT_VEC & dN) const
{
// Get shape function and derivatives at atomic locations
int nnodes = feMesh_->num_nodes_unique();
int npts = pt_coords.nRows();
// loop over point (atom) coordinates
DENS_VEC x(nSD_);
Array<int> node_index(nNodesPerElement_);
DENS_VEC shp(nNodesPerElement_);
DENS_MAT dshp(nSD_,nNodesPerElement_);
for (int k = 0; k < nSD_; ++k) {
dN[k]->reset(npts,nnodes);
}
N.reset(npts,nnodes);
for (int i = 0; i < npts; ++i) {
for (int k = 0; k < nSD_; ++k) { x(k) = pt_coords(i,k); }
feMesh_->shape_functions(x,shp,dshp,node_index);
for (int j = 0; j < nNodesPerElement_; ++j) {
int jnode = node_index(j);
N.add(i,jnode,shp(j));
for (int k = 0; k < nSD_; ++k) {
dN[k]->add(i,jnode,dshp(k,j));
}
}
}
N.compress();
for (int k = 0; k < nSD_; ++k) {
dN[k]->compress();
}
}
//-----------------------------------------------------------------
// evaluate shape functions at given points
//-----------------------------------------------------------------
void FE_Engine::evaluate_shape_functions(
const MATRIX & pt_coords,
const INT_ARRAY & pointToEltMap,
SPAR_MAT & N) const
{
// Get shape function and derivatives at atomic locations
int nnodes = feMesh_->num_nodes_unique();
int npts = pt_coords.nRows();
// loop over point (atom) coordinates
DENS_VEC x(nSD_);
Array<int> node_index(nNodesPerElement_);
DENS_VEC shp(nNodesPerElement_);
N.reset(npts,nnodes);
for (int i = 0; i < npts; ++i) {
for (int k = 0; k < nSD_; ++k) { x(k) = pt_coords(i,k); }
int eltId = pointToEltMap(i,0);
feMesh_->shape_functions(x,eltId,shp,node_index);
for (int j = 0; j < nNodesPerElement_; ++j) {
int jnode = node_index(j);
N.add(i,jnode,shp(j));
}
}
N.compress();
}
//-----------------------------------------------------------------
// evaluate shape functions and their spatial derivatives at given points
//-----------------------------------------------------------------
void FE_Engine::evaluate_shape_functions(
const MATRIX & pt_coords,
const INT_ARRAY & pointToEltMap,
SPAR_MAT & N,
SPAR_MAT_VEC & dN) const
{
// Get shape function and derivatives at atomic locations
int nnodes = feMesh_->num_nodes_unique();
int npts = pt_coords.nRows();
// loop over point (atom) coordinates
DENS_VEC x(nSD_);
Array<int> node_index(nNodesPerElement_);
DENS_VEC shp(nNodesPerElement_);
DENS_MAT dshp(nSD_,nNodesPerElement_);
for (int k = 0; k < nSD_; ++k) {
dN[k]->reset(npts,nnodes);
}
N.reset(npts,nnodes);
for (int i = 0; i < npts; ++i) {
for (int k = 0; k < nSD_; ++k) { x(k) = pt_coords(i,k); }
int eltId = pointToEltMap(i,0);
feMesh_->shape_functions(x,eltId,shp,dshp,node_index);
for (int j = 0; j < nNodesPerElement_; ++j) {
int jnode = node_index(j);
N.add(i,jnode,shp(j));
for (int k = 0; k < nSD_; ++k) {
dN[k]->add(i,jnode,dshp(k,j));
}
}
}
N.compress();
for (int k = 0; k < nSD_; ++k) {
dN[k]->compress();
}
}
//-----------------------------------------------------------------
// evaluate shape functions spatial derivatives at given points
//-----------------------------------------------------------------
void FE_Engine::evaluate_shape_function_derivatives(
const MATRIX & pt_coords,
const INT_ARRAY & pointToEltMap,
SPAR_MAT_VEC & dNdx ) const
{
int nnodes = feMesh_->num_nodes_unique();
int npts = pt_coords.nRows();
for (int k = 0; k < nSD_; ++k) {
dNdx[k]->reset(npts,nnodes);
}
// loop over point (atom) coordinates
DENS_VEC x(nSD_);
Array<int> node_index(nNodesPerElement_);
DENS_MAT dshp(nSD_,nNodesPerElement_);
for (int i = 0; i < npts; ++i) {
for (int k = 0; k < nSD_; ++k) { x(k) = pt_coords(i,k); }
int eltId = pointToEltMap(i,0);
feMesh_->shape_function_derivatives(x,eltId,dshp,node_index);
for (int j = 0; j < nNodesPerElement_; ++j) {
int jnode = node_index(j);
for (int k = 0; k < nSD_; ++k) {
dNdx[k]->add(i,jnode,dshp(k,j));
}
}
}
for (int k = 0; k < nSD_; ++k) {
dNdx[k]->compress();
}
}
//-------------------------------------------------------------------
// set kernels
//-------------------------------------------------------------------
void FE_Engine::set_kernel(KernelFunction* ptr){kernelFunction_=ptr;}
//-------------------------------------------------------------------
// kernel_matrix_bandwidth
//-------------------------------------------------------------------
int FE_Engine::kernel_matrix_bandwidth(
const MATRIX & ptCoords) const
{
if (! kernelFunction_) throw ATC_Error("no kernel function specified");
int npts = ptCoords.nRows();
int bw = 0;
if (npts>0) {
DENS_VEC xI(nSD_),x(nSD_),dx(nSD_);
for (int i = 0; i < nNodes_; i++) {
xI = feMesh_->global_coordinates(i);
for (int j = 0; j < npts; j++) {
for (int k = 0; k < nSD_; ++k) { x(k) = ptCoords(j,k); }
dx = x - xI;
if (kernelFunction_->in_support(dx)) bw = max(bw,abs(j-map_global_to_unique(i)));
}
}
}
return bw;
}
//-------------------------------------------------------------------
// evaluate kernels
//-------------------------------------------------------------------
void FE_Engine::evaluate_kernel_functions(
const MATRIX & ptCoords,
SPAR_MAT & N ) const
{
#ifdef EXTENDED_ERROR_CHECKING
print_msg_once(communicator_,"kernel matrix bandwidth " +to_string(kernel_matrix_bandwidth(ptCoords)));
#endif
if (! kernelFunction_) throw ATC_Error("no kernel function specified");
int npts = ptCoords.nRows();
if (npts>0) {
N.reset(npts,nNodesUnique_); // transpose of N_Ia
DENS_VEC xI(nSD_),x(nSD_),dx(nSD_);
for (int i = 0; i < nNodes_; i++) {
xI = feMesh_->global_coordinates(i);
for (int j = 0; j < npts; j++) {
for (int k = 0; k < nSD_; ++k) { x(k) = ptCoords(j,k); }
dx = x - xI;
double val = kernelFunction_->value(dx);
val *= kernelFunction_->dimensionality_factor();
if (val > 0) N.add(j,map_global_to_unique(i),val);
}
}
N.compress();
}
}
//-----------------------------------------------------------------
// create a non-uniform rectangular mesh on a rectangular region
//-----------------------------------------------------------------
void FE_Engine::create_mesh(Array<double> & dx,
Array<double> & dy,
Array<double> & dz,
const char * regionName,
Array<bool> periodicity)
{
double xmin, xmax, ymin, ymax, zmin, zmax;
double xscale, yscale, zscale;
// check to see if region exists and is it a box, and if so get the bounds
bool isBox;
isBox = ATC::LammpsInterface::instance()->region_bounds(
regionName,
xmin, xmax,
ymin, ymax,
zmin, zmax,
xscale,
yscale,
zscale);
if (!isBox) throw ATC_Error("Region for FE mesh is not a box");
if (dx(0) == 0.0) dx = (xmax-xmin)/dx.size();
if (dy(0) == 0.0) dy = (ymax-ymin)/dy.size();
if (dz(0) == 0.0) dz = (zmax-zmin)/dz.size();
feMesh_ = new FE_Rectangular3DMesh(dx,dy,dz,
xmin, xmax,
ymin, ymax,
zmin, zmax,
periodicity,
xscale, yscale, zscale);
stringstream ss;
ss << "created structured mesh with " << feMesh_->num_nodes() << " nodes, "
<< feMesh_->num_nodes_unique() << " unique nodes, and "
<< feMesh_->num_elements() << " elements";
print_msg_once(communicator_,ss.str());
#ifdef ATC_VERBOSE
print_partitions(xmin,xmax,dx);
print_partitions(ymin,ymax,dy);
print_partitions(zmin,zmax,dz);
#endif
}
//-----------------------------------------------------------------
void FE_Engine::print_partitions(double xmin, double xmax, Array<double> & dx) const {
stringstream msg;
msg.precision(3);
msg << std::fixed;
msg << "\nindex weight fraction location size[A] size[uc]:\n";
double sum = 0;
for (int i = 0; i < dx.size(); ++i) { sum += dx(i); }
double xn= 1.0/sum;
double xs= xn*(xmax-xmin);
double xl= xs/(ATC::LammpsInterface::instance()->xlattice());
double sumn = 0, sums = 0, suml = 0;
double x = xmin;
for (int i = 0; i < dx.size(); ++i) {
double dxn = dx(i)*xn; sumn += dxn;
double dxs = dx(i)*xs; sums += dxs;
double dxl = dx(i)*xl; suml += dxl;
msg << std::setw(5) << i+1
<< std::setw(7) << dx(i)
<< std::setw(9) << dxn
<< std::setw(9) << x
<< std::setw(8) << dxs
<< std::setw(9) << dxl << "\n";
x += dxs;
}
msg << "sum " << setw(7) << sum
<< setw(9) << sumn
<< setw(9) << x
<< setw(8) << sums
<< setw(9) << suml << "\n";
print_msg_once(communicator_,msg.str());
}
//-----------------------------------------------------------------
// create a uniform rectangular mesh on a rectangular region
//-----------------------------------------------------------------
void FE_Engine::create_mesh(int nx, int ny, int nz, const char * regionName,
Array<bool> periodicity)
{
double xmin, xmax, ymin, ymax, zmin, zmax;
double xscale, yscale, zscale;
// check to see if region exists and is it a box, and if so get the bounds
bool isBox;
isBox = ATC::LammpsInterface::instance()->region_bounds(
regionName,
xmin, xmax,
ymin, ymax,
zmin, zmax,
xscale, yscale, zscale);
if (!isBox) throw ATC_Error("Region for FE mesh is not a box");
feMesh_ = new FE_Uniform3DMesh(nx,ny,nz,
xmin, xmax,
ymin, ymax,
zmin, zmax,
periodicity,
xscale, yscale, zscale);
stringstream ss;
ss << "created uniform mesh with " << feMesh_->num_nodes() << " nodes, "
<< feMesh_->num_nodes_unique() << " unique nodes, and "
<< feMesh_->num_elements() << " elements";
print_msg_once(communicator_,ss.str());
}
//-----------------------------------------------------------------
// read a mesh from an input file using MeshReader object
//-----------------------------------------------------------------
void FE_Engine::read_mesh(string meshFile, Array<bool> & periodicity)
{
if (feMesh_) throw ATC_Error("FE_Engine already has a mesh");
feMesh_ = MeshReader(meshFile,periodicity).create_mesh();
feMesh_->initialize();
}
};