#include "FE_Engine.h" #include "ATC_Transfer.h" #include "FE_Element.h" #include "XT_Function.h" #include "LammpsInterface.h" #include "PhysicsModel.h" #include #include using namespace std; namespace ATC{ //----------------------------------------------------------------- FE_Engine::FE_Engine(ATC_Transfer * atcTransfer) : atcTransfer_(atcTransfer), feMesh_(NULL), initialized_(false), amendedMeshData_(false), outputManager_() { } FE_Engine::~FE_Engine() { if (feMesh_) delete feMesh_; if (connectivity_ && amendedMeshData_) delete connectivity_; if (nodeMap_ && amendedMeshData_) delete nodeMap_; if (coordinates_ && amendedMeshData_) delete coordinates_; } //----------------------------------------------------------------- void FE_Engine::initialize() { if (! feMesh_) throw ATC_Error(0,"FE_Engine has no mesh"); if (! initialized_ ) { // mesh data nodeMap_ = &(feMesh_->global_to_unique_map()); connectivity_ = &(feMesh_->connectivity()); coordinates_ = &(feMesh_->nodal_coordinates()); // set up work spaces nNodesPerElement_ = feMesh_->get_nNodesPerElement(); nIPsPerElement_ = feMesh_->get_nIPsPerElement(); nIPsPerFace_ = feMesh_->get_nIPsPerFace(); nSD_ = feMesh_->get_nSpatialDimensions(); nElems_ = feMesh_->get_nElements(); // remove specified elements if (nullElements_.size() > 0) delete_elements(nullElements_); initialized_ = true; } } //----------------------------------------------------------------- bool FE_Engine::modify(int narg, char **arg) { bool match = false; /*! \page man_fem_mesh fix_modify AtC fem create mesh \section syntax fix_modify AtC fem create mesh - nx ny nz = number of elements in x, y, z - region-id = id of region that is to be meshed - f p p = perioidicity flags for x, y, z \section examples fix_modify AtC fem create mesh 10 1 1 feRegion p p p \section description Creates a uniform mesh in a rectangular region \section restrictions creates only uniform rectangular grids in a rectangular region \section related \section default none */ if (strcmp(arg[0],"fem")==0) { // create mesh if (strcmp(arg[1],"create")==0) { if (strcmp(arg[2],"mesh")==0) { int nx = atoi(arg[3]); int ny = atoi(arg[4]); int nz = atoi(arg[5]); int xperiodic = 0; if (strcmp(arg[7],"p")==0) xperiodic = 1; int yperiodic = 0; if (strcmp(arg[8],"p")==0) yperiodic = 1; int zperiodic = 0; if (strcmp(arg[9],"p")==0) zperiodic = 1; if (feMesh_) throw ATC_Error(0,"FE Engine already has a mesh"); create_mesh(nx, ny, nz, arg[6], xperiodic, yperiodic, zperiodic ); // construct prescribed data manager NOTE move to ATC_Transfer? atcTransfer_->initialize_mesh_data(); match = true; } } } /*! \page man_mesh_delete_elements fix_modify AtC mesh delete_elements \section syntax fix_modify AtC mesh delete_elements - = name of an element set \section examples fix_modify AtC delete_elements gap \section description Deletes a group of elements from the mesh. \section restrictions \section related \section default none */ else if ( (strcmp(arg[0],"mesh")==0) && (strcmp(arg[1],"delete_elements")==0) ) { string esetName = arg[2]; set elemSet = feMesh_->get_elementset(esetName); nullElements_.insert(elemSet.begin(), elemSet.end()); match = true; } // FE_Mesh else { match = feMesh_->modify(narg,arg); } return match; } //----------------------------------------------------------------- void FE_Engine::finish() { // Nothing to do } //----------------------------------------------------------------- // write geometry //----------------------------------------------------------------- void FE_Engine::initialize_output(int rank, string outputPrefix, OutputType otype) { outputManager_.initialize(outputPrefix, otype); if (! feMesh_) throw ATC_Error(0,"output needs mesh"); if (! initialized_) initialize(); if (rank == 0) outputManager_.write_geometry(*coordinates_, connectivity_); } //----------------------------------------------------------------- // write geometry //----------------------------------------------------------------- void FE_Engine::write_geometry(void) { outputManager_.write_geometry(*coordinates_, connectivity_); } // ------------------------------------------------------------- // write data // ------------------------------------------------------------- void FE_Engine::write_data(double time, FIELDS &soln, OUTPUT_LIST *data) { outputManager_.write_data(time, &soln, data, nodeMap_->get_data()); } // ------------------------------------------------------------- // write data // ------------------------------------------------------------- void FE_Engine::write_data(double time, OUTPUT_LIST *data) { outputManager_.write_data(time, data, nodeMap_->get_data()); } // ------------------------------------------------------------- // amend mesh for deleted elements // ------------------------------------------------------------- void FE_Engine::delete_elements(const set & elementList) { int nsd = feMesh_->get_nSpatialDimensions(); set elementsNew; feMesh_->elementset_complement(elementList,elementsNew); int nElementsNew = elementsNew.size(); set newToOld; map oldToNewMap; feMesh_->elementset_to_nodeset(elementsNew,newToOld); int nNodesNew = newToOld.size(); set::const_iterator itr; // coordinates & node map (from nodes to data) const DENS_MAT &coordinates = feMesh_->nodal_coordinates(); const Array & node_map = feMesh_->global_to_unique_map(); if (coordinates_ && amendedMeshData_) delete coordinates_; coordinates_ = new DENS_MAT(nsd,nNodesNew); DENS_MAT & coor = * (const_cast ( coordinates_)); if (nodeMap_ && amendedMeshData_) delete nodeMap_; nodeMap_ = new Array (nNodesNew); Array & nmap = * (const_cast *> ( nodeMap_)); int k = 0, i = 0; for (itr = newToOld.begin(); itr != newToOld.end(); itr++) { int node = *itr; oldToNewMap[node]=i++; nmap(k) = node_map(node); for(int j = 0; j < nsd; j++) { coor(j,k) = coordinates(j,node); } k++; } // connectivity const Array2D & connectivity = feMesh_->connectivity(); int nNodesPerElement = connectivity.nRows(); int nElements = connectivity.nCols(); if (connectivity_ && amendedMeshData_) delete connectivity_; connectivity_ = new Array2D(nNodesPerElement,nElementsNew); Array2D & conn = * (const_cast *> ( connectivity_)); k = 0; for (itr = elementsNew.begin(); itr != elementsNew.end(); itr++) { int ielem = *itr; for(int j = 0; j < nNodesPerElement; j++) { int old_node = connectivity(j,ielem); map::iterator map_itr = oldToNewMap.find(old_node); if (map_itr == oldToNewMap.end()) { cout << "map failure " << old_node << "\n"; } int node = map_itr->second; conn(j,k) = node; } k++; } amendedMeshData_ = true; } // ------------------------------------------------------------- // compute dimensionless stiffness matrix using native quadrature // ------------------------------------------------------------- void FE_Engine::compute_stiffness_matrix(SPAR_MAT &matrix) const { //int nfields = field_mask.get_length(); int nNodes = feMesh_->get_nNodesUnique(); int nNodesPerElement = feMesh_->get_nNodesPerElement(); int nIPsPerElement = feMesh_->get_nIPsPerElement(); int nsd = feMesh_->get_nSpatialDimensions(); int nelems = feMesh_->get_nElements(); DENS_MAT N(nIPsPerElement,nNodesPerElement); vector dN(nsd); dN.assign(nsd, DENS_MAT(nIPsPerElement,nNodesPerElement)); DiagonalMatrix weights(nIPsPerElement,nIPsPerElement); Array conn(nNodesPerElement); // assemble consistent mass (nnodes X nnodes) matrix.reset(nNodes,nNodes); DENS_MAT elementMassMatrix(nNodesPerElement,nNodesPerElement); for (int ielem = 0; ielem < nelems; ++ielem) { // evaluate shape functions feMesh_->shape_function(ielem, N, dN, weights); // 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 feMesh_->element_connectivity_unique(ielem, conn); 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)); } } } } // ------------------------------------------------------------- // compute dimensionless consistent mass using native quadrature // ------------------------------------------------------------- void FE_Engine::compute_mass_matrix(SPAR_MAT &mass_matrix) const { int nNodes = feMesh_->get_nNodesUnique(); int nNodesPerElement = feMesh_->get_nNodesPerElement(); int nIPsPerElement = feMesh_->get_nIPsPerElement(); int nsd = feMesh_->get_nSpatialDimensions(); int nelems = feMesh_->get_nElements(); DENS_MAT N(nIPsPerElement,nNodesPerElement); DiagonalMatrix weights(nIPsPerElement,nIPsPerElement); Array conn(nNodesPerElement); // assemble consistent mass (nnodes X nnodes) mass_matrix.reset(nNodes,nNodes); DENS_MAT elementMassMatrix(nNodesPerElement,nNodesPerElement); for (int ielem = 0; ielem < nelems; ++ielem) { // evaluate shape functions feMesh_->shape_function(ielem, N, weights); // perform quadrature elementMassMatrix = N.transMat(weights*N); // get connectivity feMesh_->element_connectivity_unique(ielem, conn); for (int i = 0; i < nNodesPerElement; ++i) { int inode = conn(i); for (int j = 0; j < nNodesPerElement; ++j) { int jnode = conn(j); mass_matrix.add(inode, jnode, elementMassMatrix(i,j)); } } } } // ------------------------------------------------------------- // compute consistent mass using given quadrature // ------------------------------------------------------------- void FE_Engine::compute_mass_matrix(const DIAG_MAT &weights, const SPAR_MAT &N, SPAR_MAT &mass_matrix) 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; LammpsInterface::instance()->allsum( tmp_mass_matrix_local.get_ptr(), tmp_mass_matrix.get_ptr(), count); // create sparse from dense mass_matrix.reset(tmp_mass_matrix); } // ------------------------------------------------------------- // compute dimensionless lumped mass using native quadrature // ------------------------------------------------------------- void FE_Engine::compute_lumped_mass_matrix(DiagonalMatrix & M) const { // initialize int nNodes = feMesh_->get_nNodesUnique(); M.reset(nNodes,nNodes); int nNodesPerElement = feMesh_->get_nNodesPerElement(); int nIPsPerElement = feMesh_->get_nIPsPerElement(); int nsd = feMesh_->get_nSpatialDimensions(); int nelems = feMesh_->get_nElements(); DENS_MAT N(nNodesPerElement,nIPsPerElement); DIAG_MAT weights(nIPsPerElement,nIPsPerElement); Array conn(nNodesPerElement); // assemble lumped diagonal mass (nnodes X nnodes) int inode = -1; DENS_VEC weightVec(nIPsPerElement); DENS_VEC Nvec(nNodesPerElement); for (int ielem = 0; ielem < nelems; ++ielem) { // evaluate shape functions feMesh_->shape_function(ielem, N, weights); int nips = weights.nRows(); // different than nIPsPerElement? weightVec = 1.; weightVec = weights*weightVec; // get connectivity feMesh_->element_connectivity_unique(ielem, conn); Nvec = N*weightVec; for (int i = 0; i < nNodesPerElement; ++i) { inode = conn(i); M(inode,inode) += Nvec(i); } } } // ------------------------------------------------------------- // compute physical lumped mass using native quadrature // ------------------------------------------------------------- void FE_Engine::compute_lumped_mass_matrix( const Array& field_mask, const FIELDS & fields, const PhysicsModel * physicsModel, const Array &elementMaterials, map& M, // mass matrix const Array *element_mask) const { int nfields = field_mask.get_length(); // initialize int nNodes = feMesh_->get_nNodesUnique(); for (int j = 0; j < nfields; ++j) { M[field_mask(j)].reset(nNodes,nNodes); } int nNodesPerElement = feMesh_->get_nNodesPerElement(); int nIPsPerElement = feMesh_->get_nIPsPerElement(); int nsd = feMesh_->get_nSpatialDimensions(); int nelems = feMesh_->get_nElements(); DENS_MAT N(nNodesPerElement,nIPsPerElement); DIAG_MAT weights(nIPsPerElement,nIPsPerElement); Array conn(nNodesPerElement); FIELDS fieldsAtIPs, localElementFields; DENS_MAT Nmat; // assemble lumped diagonal mass (nnodes X nnodes) for (int ielem = 0; ielem < nelems; ++ielem) { // if element is masked, skip it if (element_mask && !(*element_mask)(ielem)) continue; // material id int imat = elementMaterials(ielem); // evaluate shape functions feMesh_->shape_function(ielem, N, weights); int nips = weights.nRows(); // get connectivity feMesh_->element_connectivity_unique(ielem, conn); // interpolate fields at IPs FIELDS::const_iterator field; for (field = fields.begin(); field != fields.end(); field++) { // field values at all nodes const DENS_MAT &vI = field->second; // field values at element nodes DENS_MAT &vIe = localElementFields[field->first]; // field values at integration points -> to be computed DENS_MAT &vP = fieldsAtIPs[field->first]; int numFieldDOF = vI.nCols(); vIe.reset(nNodesPerElement, numFieldDOF); // gather local field for (int i = 0; i < nNodesPerElement; i++) { for (int j = 0; j < numFieldDOF; j++) { vIe(i,j) = vI(conn(i),j); } } // interpolate field at integration points vP = N*vIe; } // compute energy/momentum/mass densities FIELDS capacities; physicsModel->M_integrand(field_mask, fieldsAtIPs, capacities, imat); // integrate & assemble for (int j = 0; j < nfields; ++j) { FieldName thisFieldName = field_mask(j); Nmat = N.transMat(weights*capacities[thisFieldName]); for (int i = 0; i < nNodesPerElement; ++i) { int inode = conn(i); M[thisFieldName](inode,inode) += Nmat(i,0);// assume all dof same } } } } // ------------------------------------------------------------- // compute physical lumped mass using given quadrature // ------------------------------------------------------------- void FE_Engine::compute_lumped_mass_matrix( const Array &field_mask, const FIELDS &fields, const PhysicsModel * physicsModel, const Array > & pointMaterialGroups, const DIAG_MAT &weights, // NOTE use these as a mask const SPAR_MAT &N, map &M) const // mass matrices { int nips = weights.nCols(); int nsd = feMesh_->get_nSpatialDimensions(); int nfields = field_mask.get_length(); int nNodes = feMesh_->get_nNodesUnique(); // initialize FieldName thisFieldName; map M_local; for (int j = 0; j < nfields; ++j) { thisFieldName = field_mask(j); M_local[thisFieldName].reset(nNodes,nNodes); M[thisFieldName].reset(nNodes,nNodes); } if (nips>0) { // compute fields at given ips FIELDS fieldsAtIPs; for (int j = 0; j < nfields; ++j) { thisFieldName = field_mask(j); const FIELD & field = (fields.find(thisFieldName))->second; int numFieldDOF = field.nCols(); fieldsAtIPs[thisFieldName].reset(nips,numFieldDOF); fieldsAtIPs[thisFieldName] = N*field; } // treat single material point sets specially int nMatls = pointMaterialGroups.size(); int atomMatls = 0; for (int imat = 0; imat < nMatls; imat++) { const set & indices = pointMaterialGroups(imat); if ( indices.size() > 0) atomMatls++; } bool singleMaterial = ( atomMatls == 1 ); // setup data structures FIELDS capacities; // evaluate physics model if (singleMaterial) { physicsModel->M_integrand(field_mask, fieldsAtIPs, capacities); } else { throw ATC_Error(0,"unimplemented function in FE_Engine::compute_mass_matrix"); } // integrate & assemble for (int j = 0; j < nfields; ++j) { FieldName thisFieldName = field_mask(j); M_local[thisFieldName].reset( // assume all columns same column(N.transMat(weights*capacities[thisFieldName]),0) ); } } // Share information between processors for (int j = 0; j < nfields; ++j) { FieldName thisFieldName = field_mask(j); int count = M_local[thisFieldName].size(); LammpsInterface::instance()->allsum( M_local[thisFieldName].get_ptr(), M[thisFieldName].get_ptr(), count); } } //----------------------------------------------------------------- // compute assembled average gradient evaluated at the nodes //----------------------------------------------------------------- void FE_Engine::compute_gradient_matrix( GRAD_SHPFCN & grad_matrix) const { int nNodesUnique = feMesh_->get_nNodesUnique(); int nNodesPerElement = feMesh_->get_nNodesPerElement(); int nsd = feMesh_->get_nSpatialDimensions(); int nelems = feMesh_->get_nElements(); int nIPsPerElement = feMesh_->get_nIPsPerElement(); DENS_MAT N(nIPsPerElement,nNodesPerElement); vector< DENS_MAT > dN(nsd); vector< DENS_MAT > tmp_grad_matrix(nsd); for (int i = 0; i < nsd; i++) { dN[i].reset(nNodesPerElement,nNodesPerElement); tmp_grad_matrix[i].reset(nNodesUnique,nNodesUnique); } DiagonalMatrix weights(nIPsPerElement,nIPsPerElement); Array conn(nNodesPerElement); // element wise assembly Array count(nNodesUnique); count = 0; feMesh_->set_quadrature(FE_Element::NODAL_QUADRATURE); for (int ielem = 0; ielem < nelems; ++ielem) { // evaluate shape functions feMesh_->shape_function(ielem, N, dN, weights); int nips = weights.nRows(); // get connectivity feMesh_->element_connectivity_unique(ielem, conn); for (int j = 0; j < nIPsPerElement; ++j) { int jnode = conn(j); // NOTE assumes ip order is node order 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); } } } } feMesh_->set_quadrature(FE_Element::GAUSSIAN_QUADRATURE); // reset to default for (int inode = 0; inode < nNodesUnique; ++inode) { //cout << inode << " count " << count(inode) << "\n"; 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 grad_matrix.resize(nsd); for (int k = 0; k < nsd; ++k) { grad_matrix[k].reset(tmp_grad_matrix[k]); } } // ------------------------------------------------------------- // compute energy per node using native quadrature // ------------------------------------------------------------- void FE_Engine::compute_energy(const Array &mask, const FIELDS &fields, const PhysicsModel * physicsModel, const Array & elementMaterials, FIELDS &energy, const Array *element_mask) const { //NOTE move to initialization and make workspace const int nNodesPerElement = feMesh_->get_nNodesPerElement(); const int nIPsPerElement = feMesh_->get_nIPsPerElement(); const int nsd = feMesh_->get_nSpatialDimensions(); const int nelems = feMesh_->get_nElements(); DENS_MAT Nmat; //NOTE move to initialization and make workspace DENS_MAT N(nIPsPerElement,nNodesPerElement); vector dN(nsd); dN.assign(nsd, DENS_MAT(nIPsPerElement,nNodesPerElement)); FIELDS fieldsAtIPs, localElementFields; GRAD_FIELDS grad_fieldsAtIPs; FIELDS::const_iterator field; FieldName thisFieldName; int numFieldDOF; for (int n = 0; n < mask.get_length(); n++) { FieldName thisFieldName = mask(n); const FIELD & field = (fields.find(thisFieldName))->second; energy[thisFieldName].reset(field.nRows(), field.nCols()); } DIAG_MAT weights(nIPsPerElement,nIPsPerElement); Array conn(nNodesPerElement); // element wise assembly int inode = -1; for (int ielem=0; ielem < nelems; ielem++) { // if element is masked, skip it if (element_mask && !(*element_mask)(ielem)) continue; // material id int imat = elementMaterials(ielem); // evaluate shape functions feMesh_->shape_function(ielem, N, dN, weights); int nips = weights.nRows(); // get connectivity feMesh_->element_connectivity_unique(ielem, conn); // compute fields and gradients of fields ips of this element for (int n = 0; n < mask.get_length(); n++) { FieldName thisFieldName = mask(n); FIELDS::const_iterator field = (fields.find(thisFieldName)); // field values at all nodes const DENS_MAT &vI = field->second; // field values at element nodes DENS_MAT &vIe = localElementFields[field->first]; // field values at integration points -> to be computed DENS_MAT &vP = fieldsAtIPs[field->first]; // gradients of field at integration points -> to be computed vector &dvP = grad_fieldsAtIPs[field->first]; numFieldDOF = vI.nCols(); vIe.reset(nNodesPerElement, numFieldDOF); // gather local field for (int i = 0; i < nNodesPerElement; i++) for (int j = 0; j < numFieldDOF; j++) vIe(i,j) = vI(conn(i),j); // interpolate field at integration points vP = N*vIe; dvP.assign(nsd, DENS_MAT(nIPsPerElement, numFieldDOF)); for (int j = 0; j < nsd; ++j) dvP[j] = dN[j]*vIe; } FIELDS energies; physicsModel->E_integrand(mask, fieldsAtIPs, grad_fieldsAtIPs, energies, imat); // assemble for (int n = 0; n < mask.get_length(); n++) { FieldName thisFieldName = mask(n); FIELDS::const_iterator field = (fields.find(thisFieldName)); numFieldDOF = (field->second).nCols(); Nmat = N.transMat(weights*energies[thisFieldName]); for (int i = 0; i < nNodesPerElement; ++i) { inode = conn(i); for (int k = 0; k < numFieldDOF; ++k) { energy[thisFieldName](inode,k) += Nmat(i,k); } } } } // element loop } // function call // ------------------------------------------------------------- // compute rhs using native quadrature // ------------------------------------------------------------- void FE_Engine::compute_rhs_vector(const Array2D &rhs_mask, const FIELDS &fields, const PhysicsModel * physicsModel, const Array & elementMaterials, FIELDS &rhs, const Array *element_mask) const { //NOTE move to initialization and make workspace const int nNodesPerElement = feMesh_->get_nNodesPerElement(); const int nIPsPerElement = feMesh_->get_nIPsPerElement(); const int nsd = feMesh_->get_nSpatialDimensions(); const int nelems = feMesh_->get_nElements(); DENS_MAT Nmat; //NOTE move to initialization and make workspace DENS_MAT N(nIPsPerElement,nNodesPerElement); vector dN(nsd); dN.assign(nsd, DENS_MAT(nIPsPerElement,nNodesPerElement)); FIELDS fieldsAtIPs, localElementFields; GRAD_FIELDS grad_fieldsAtIPs; FIELDS::const_iterator field; FieldName thisFieldName; int numFieldDOF; for (field = fields.begin(); field != fields.end(); field++) { thisFieldName = field->first; if (rhs_mask(thisFieldName,FLUX) || rhs_mask(thisFieldName,SOURCE)) { int nrows = (field->second).nRows(); int ncols = (field->second).nCols(); rhs[thisFieldName].reset(nrows,ncols); } } DIAG_MAT weights(nIPsPerElement,nIPsPerElement); Array conn(nNodesPerElement); // element wise assembly int inode = -1; for (int ielem=0; ielem < nelems; ielem++) { // if element is masked, skip it if (element_mask && !(*element_mask)(ielem)) continue; // material id int imat = elementMaterials(ielem); // evaluate shape functions feMesh_->shape_function(ielem, N, dN, weights); int nips = weights.nRows(); // get connectivity feMesh_->element_connectivity_unique(ielem, conn); // compute fields and gradients of fields ips of this element for (field = fields.begin(); field != fields.end(); field++) { // field values at all nodes const DENS_MAT &vI = field->second; // field values at element nodes DENS_MAT &vIe = localElementFields[field->first]; // field values at integration points -> to be computed DENS_MAT &vP = fieldsAtIPs[field->first]; // gradients of field at integration points -> to be computed vector &dvP = grad_fieldsAtIPs[field->first]; numFieldDOF = vI.nCols(); vIe.reset(nNodesPerElement, numFieldDOF); // gather local field for (int i = 0; i < nNodesPerElement; i++) for (int j = 0; j < numFieldDOF; j++) vIe(i,j) = vI(conn(i),j); // interpolate field at integration points vP = N*vIe; dvP.assign(nsd, DENS_MAT(nIPsPerElement, numFieldDOF)); for (int j = 0; j < nsd; ++j) dvP[j] = dN[j]*vIe; } // NOTE move scaling of N by weights up here // evaluate physics model // N_fluxes is a set of fields if(physicsModel->has_N_integrand()) { FIELDS N_fluxes; physicsModel->N_integrand(rhs_mask, fieldsAtIPs, grad_fieldsAtIPs, N_fluxes, imat); // assemble for (field = fields.begin(); field != fields.end(); field++) { thisFieldName = field->first; // NOTE why is this line here? // SOURCE should refer only to adding extrinsic coupling sources if (!rhs_mask(thisFieldName,SOURCE)) continue; numFieldDOF = (field->second).nCols(); Nmat = N.transMat(weights*N_fluxes[thisFieldName]); for (int i = 0; i < nNodesPerElement; ++i) { inode = conn(i); for (int k = 0; k < numFieldDOF; ++k) { rhs[thisFieldName](inode,k) += Nmat(i,k); } } } } // evaluate Physics model // B_fluxes is a set of field gradients if (physicsModel->has_B_integrand()) { GRAD_FIELDS B_fluxes; physicsModel->B_integrand(rhs_mask, fieldsAtIPs, grad_fieldsAtIPs, B_fluxes, imat); // assemble for (field = fields.begin(); field != fields.end(); field++) { thisFieldName = field->first; numFieldDOF = (field->second).nCols(); if (!rhs_mask(thisFieldName,FLUX)) continue; for (int j = 0; j < nsd; j++) { Nmat = dN[j].transMat(weights*B_fluxes[thisFieldName][j]); for (int i = 0; i < nNodesPerElement; ++i) { inode = conn(i); for (int k = 0; k < numFieldDOF; ++k) { rhs[thisFieldName](inode,k) += Nmat(i,k); } // scatter k } // scatter i } // loop over nsd } //loop on fields } // if B_integrand } // element loop } // function call // ------------------------------------------------------------- // compute rhs using given quadrature // ------------------------------------------------------------- void FE_Engine::compute_rhs_vector(const Array2D &rhs_mask, const FIELDS &fields, const PhysicsModel *physicsModel, const Array >&pointMaterialGroups, const DIAG_MAT &weights, const SPAR_MAT &N, const GRAD_SHPFCN &dN, FIELDS &rhs) const { int nips = weights.nCols(); int nNodesUnique = feMesh_->get_nNodesUnique(); int nsd = feMesh_->get_nSpatialDimensions(); int nelems = feMesh_->get_nElements(); FieldName thisFieldName; FIELDS::const_iterator field; FIELDS rhs_local; for (field = rhs.begin(); field != rhs.end(); field++) { thisFieldName = field->first; if (rhs_mask(thisFieldName,FLUX) || rhs_mask(thisFieldName,SOURCE)) { int nrows = (field->second).nRows(); int ncols = (field->second).nCols(); rhs [thisFieldName].reset(nrows,ncols); rhs_local[thisFieldName].reset(nrows,ncols); } } if (nips>0) { // compute fields and gradients of fields at given ips GRAD_FIELDS grad_fieldsAtIPs; FIELDS fieldsAtIPs; int numFieldDOF; for (field = fields.begin(); field != fields.end(); field++) { thisFieldName = field->first; const FIELD & field = (fields.find(thisFieldName))->second; numFieldDOF = field.nCols(); grad_fieldsAtIPs[thisFieldName].assign(nsd,DENS_MAT(nips,numFieldDOF)); fieldsAtIPs[thisFieldName].reset(nips,numFieldDOF); fieldsAtIPs[thisFieldName] = N*field; for (int j = 0; j < nsd; ++j) { grad_fieldsAtIPs[thisFieldName][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 & indices = pointMaterialGroups(imat); if ( indices.size() > 0) atomMatls++; } bool singleMaterial = ( atomMatls == 1 ); // setup data structures FIELDS N_fluxes; GRAD_FIELDS B_fluxes; if(physicsModel->has_B_integrand()) { for (field = fields.begin(); field != fields.end(); field++) { thisFieldName = field->first; int index = (int) thisFieldName; if ( rhs_mask(index,FLUX) ) { B_fluxes[thisFieldName].reserve(nsd); } } } // evaluate physics model if (singleMaterial) { if(physicsModel->has_N_integrand()) { physicsModel->N_integrand(rhs_mask, fieldsAtIPs, grad_fieldsAtIPs, N_fluxes); } if(physicsModel->has_B_integrand()) { physicsModel->B_integrand(rhs_mask, fieldsAtIPs, grad_fieldsAtIPs, B_fluxes); } } else { // NOTE this copy in fields/copy out fluxes is extremely slow // NOTE alternates: // 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 FIELDS groupN_fluxes, fieldsGroup; GRAD_FIELDS groupB_fluxes, grad_fieldsGroup; if(physicsModel->has_B_integrand()) { for (field = fields.begin(); field != fields.end(); field++) { thisFieldName = field->first; int index = (int) thisFieldName; int ndof = (field->second).nCols(); grad_fieldsGroup[thisFieldName].assign(nsd,DENS_MAT(nips,ndof)); N_fluxes[thisFieldName].reset(nips,ndof); B_fluxes[thisFieldName].assign(nsd,DENS_MAT(nips,ndof)); if ( rhs_mask(index,FLUX) ) { groupB_fluxes[thisFieldName].reserve(nsd); } } } // copy fields for ( int imat = 0; imat < pointMaterialGroups.get_length(); imat++) { const set & indices = pointMaterialGroups(imat); int npts = indices.size(); int i = 0; for (set::const_iterator iter=indices.begin(); iter != indices.end(); iter++, i++ ) { for (field = fields.begin(); field != fields.end(); field++) { thisFieldName = field->first; int ndof = (field->second).nCols(); fieldsGroup[thisFieldName].reset(npts,ndof); for (int j = 0; j < nsd; ++j) { (grad_fieldsGroup[thisFieldName][j]).reset(npts,ndof); } for (int dof = 0; dof < ndof; ++dof) { fieldsGroup[thisFieldName](i,dof) = fieldsAtIPs[thisFieldName](*iter,dof); for (int j = 0; j < nsd; ++j) { grad_fieldsGroup[thisFieldName][j](i,dof) = grad_fieldsAtIPs[thisFieldName][j](*iter,dof); } } } } // calculate forces, & assemble if(physicsModel->has_N_integrand()) { physicsModel->N_integrand(rhs_mask, fieldsGroup, grad_fieldsGroup, groupN_fluxes, imat); for (field = fields.begin(); field != fields.end(); field++) { thisFieldName = field->first; int index = (int) thisFieldName; int ndof = (field->second).nCols(); if ( rhs_mask(index,SOURCE) ) { int i = 0; for (set::const_iterator iter=indices.begin(); iter != indices.end(); iter++, i++ ) { for (int dof = 0; dof < ndof; ++dof) { N_fluxes[thisFieldName](*iter,dof) = groupN_fluxes[thisFieldName](i,dof); } } } } } // calculate forces, & assemble if(physicsModel->has_B_integrand()) { physicsModel->B_integrand(rhs_mask, fieldsGroup, grad_fieldsGroup, groupB_fluxes, imat); for (field = fields.begin(); field != fields.end(); field++) { thisFieldName = field->first; int index = (int) thisFieldName; int ndof = (field->second).nCols(); if ( rhs_mask(index,FLUX) ) { int i = 0; for (set::const_iterator iter=indices.begin(); iter != indices.end(); iter++, i++ ) { for (int dof = 0; dof < ndof; ++dof) { for (int j = 0; j < nsd; ++j) { B_fluxes[thisFieldName][j](*iter,dof) = groupB_fluxes[thisFieldName][j](i,dof); } } } } } } } } // assemble for (field = fields.begin(); field != fields.end(); field++) { thisFieldName = field->first; int index = (int) thisFieldName; if(physicsModel->has_N_integrand()) { if ( rhs_mask(index,SOURCE) ) { rhs_local[thisFieldName] += N.transMat(weights*N_fluxes[thisFieldName]); } } if(physicsModel->has_B_integrand()) { if ( rhs_mask(index,FLUX) ) { for (int j = 0; j < nsd; ++j) { rhs_local[thisFieldName] += dN[j].transMat(weights*B_fluxes[thisFieldName][j]); } } } } } // Share information between processors for (field = rhs.begin(); field != rhs.end(); field++) { thisFieldName = field->first; if (rhs_mask(thisFieldName,FLUX) || rhs_mask(thisFieldName,SOURCE)) { int count = rhs_local[thisFieldName].size(); //rhs_local[thisFieldName].print("RHS LCL"); LammpsInterface::instance()->allsum( rhs_local[thisFieldName].get_ptr(), rhs[thisFieldName].get_ptr(), count); } } } // ------------------------------------------------------------- // compute flux for post processing // ------------------------------------------------------------- void FE_Engine::compute_flux(const Array2D &rhs_mask, const FIELDS &fields, const PhysicsModel * physicsModel, const Array & elementMaterials, GRAD_FIELDS &flux, const Array *element_mask) const { //NOTE move to initialization and make workspace const int nNodesPerElement = feMesh_->get_nNodesPerElement(); const int nIPsPerElement = feMesh_->get_nIPsPerElement(); const int nsd = feMesh_->get_nSpatialDimensions(); const int nelems = feMesh_->get_nElements(); DENS_MAT Nmat; //NOTE move to initialization and make workspace DENS_MAT N(nIPsPerElement,nNodesPerElement); vector dN(nsd); dN.assign(nsd, DENS_MAT(nIPsPerElement,nNodesPerElement)); FIELDS fieldsAtIPs, localElementFields; GRAD_FIELDS grad_fieldsAtIPs; FIELDS::const_iterator field; FieldName thisFieldName; int numFieldDOF; for (field = fields.begin(); field != fields.end(); field++) { thisFieldName = field->first; if (rhs_mask(thisFieldName,FLUX)) { int nrows = (field->second).nRows(); int ncols = (field->second).nCols(); flux[thisFieldName].assign(nsd, DENS_MAT(nrows,ncols)); } } DIAG_MAT weights(nIPsPerElement,nIPsPerElement); Array conn(nNodesPerElement); // element wise assembly int inode = -1; for (int ielem=0; ielem < nelems; ielem++) { // if element is masked, skip it if (element_mask && !(*element_mask)(ielem)) continue; // material id int imat = elementMaterials(ielem); // evaluate shape functions feMesh_->shape_function(ielem, N, dN, weights); int nips = weights.nRows(); // get connectivity feMesh_->element_connectivity_unique(ielem, conn); // compute fields and gradients of fields ips of this element for (field = fields.begin(); field != fields.end(); field++) { // field values at all nodes const DENS_MAT &vI = field->second; // field values at element nodes DENS_MAT &vIe = localElementFields[field->first]; // field values at integration points -> to be computed DENS_MAT &vP = fieldsAtIPs[field->first]; // gradients of field at integration points -> to be computed vector &dvP = grad_fieldsAtIPs[field->first]; numFieldDOF = vI.nCols(); vIe.reset(nNodesPerElement, numFieldDOF); // gather local field for (int i = 0; i < nNodesPerElement; i++) for (int j = 0; j < numFieldDOF; j++) vIe(i,j) = vI(conn(i),j); // interpolate field at integration points vP = N*vIe; dvP.assign(nsd, DENS_MAT(nIPsPerElement, numFieldDOF)); for (int j = 0; j < nsd; ++j) dvP[j] = dN[j]*vIe; } // NOTE move scaling of N by weights up here // evaluate Physics model // B_fluxes is a set of field gradients if (physicsModel->has_B_integrand()) { GRAD_FIELDS B_fluxes; physicsModel->B_integrand(rhs_mask, fieldsAtIPs, grad_fieldsAtIPs, B_fluxes, imat); // assemble for (field = fields.begin(); field != fields.end(); field++) { thisFieldName = field->first; numFieldDOF = (field->second).nCols(); if (!rhs_mask(thisFieldName,FLUX)) continue; for (int j = 0; j < nsd; j++) { Nmat = N.transMat(weights*B_fluxes[thisFieldName][j]); for (int i = 0; i < nNodesPerElement; ++i) { inode = conn(i); for (int k = 0; k < numFieldDOF; ++k) { flux[thisFieldName][j](inode,k) += Nmat(i,k); } // scatter k } // scatter i } // loop over nsd } //loop on fields } // if B_integrand } // element loop } // function call //----------------------------------------------------------------- // boundary flux using native quadrature //----------------------------------------------------------------- void FE_Engine::compute_boundary_flux( const Array2D & rhs_mask, const FIELDS & fields, const PhysicsModel * physicsModel, const Array & elementMaterials, const set< pair > & faceSet, FIELDS & rhs) const { //NOTE move to initialization and make workspace int nNodesPerElement = feMesh_->get_nNodesPerElement(); int nIPsPerFace = feMesh_->get_nIPsPerFace(); int nsd = feMesh_->get_nSpatialDimensions(); int nelems = feMesh_->get_nElements(); DENS_MAT Nmat(nNodesPerElement,3); FieldName thisFieldName; FIELDS::const_iterator field; for (field = fields.begin(); field != fields.end(); field++) { thisFieldName = field->first; if (rhs_mask(thisFieldName,FLUX)) { int nrows = (field->second).nRows(); int ncols = (field->second).nCols(); rhs [thisFieldName].reset(nrows,ncols); } } //NOTE move to initialization and make workspace // sizing working arrays DENS_MAT N(nIPsPerFace,nNodesPerElement); vector< DENS_MAT > dN(nsd), nN(nsd); for (int i = 0; i < nsd; i++) { dN[i].reset(nIPsPerFace,nNodesPerElement); nN[i].reset(nIPsPerFace,nNodesPerElement); } GRAD_FIELDS grad_fieldsAtIPs; FIELDS fieldsAtIPs; FIELDS localElementFields; int numFieldDOF; for (field = fields.begin(); field != fields.end(); field++) { thisFieldName = field->first; numFieldDOF = field->second.nCols(); grad_fieldsAtIPs[thisFieldName].reserve(nsd); for (int i = 0; i < nsd; ++i) { grad_fieldsAtIPs[thisFieldName].push_back(DENS_MAT(nIPsPerFace,numFieldDOF)); } fieldsAtIPs[thisFieldName].reset(nIPsPerFace,numFieldDOF); localElementFields[thisFieldName].reset(nNodesPerElement,numFieldDOF); } DiagonalMatrix weights(nIPsPerFace,nIPsPerFace); Array conn(nNodesPerElement); // element wise assembly int inode = -1; set< pair >::iterator iter; for (iter = faceSet.begin(); iter != faceSet.end(); iter++) { // evaluate shape functions at ips feMesh_->face_shape_function(*iter, N, dN, nN, weights); int nips = weights.nRows(); // get connectivity int ielem = iter->first; int imat = elementMaterials(ielem); feMesh_->element_connectivity_unique(ielem, conn); // interpolate fields and gradients of fields ips of this element for (field = fields.begin(); field != fields.end(); field++) { thisFieldName = field->first; const DenseMatrix * thisField = (const DENS_MAT *) &(field->second); numFieldDOF = thisField->nCols(); //NOTE an hopefully unnecessary copy (should alias) for (int i = 0; i < nNodesPerElement; i++) { for (int j = 0; j < numFieldDOF; j++) { localElementFields[thisFieldName](i,j) = (*thisField)(conn(i),j); } } // ips X dof = ips X nodes * nodes X dof fieldsAtIPs[thisFieldName] = N*localElementFields[thisFieldName]; for (int j = 0; j < nsd; ++j) { grad_fieldsAtIPs[thisFieldName][j] = dN[j]*localElementFields[thisFieldName]; } } // Evaluate- physics model // do nothing for N_integrand // nN : precomputed and held by ATC_Transfer if(physicsModel->has_B_integrand()) { map > B_fluxes; for (field = fields.begin(); field != fields.end(); field++) { thisFieldName = field->first; if ( rhs_mask(thisFieldName,FLUX) ) { B_fluxes[thisFieldName].reserve(nsd); } } physicsModel->B_integrand(rhs_mask, fieldsAtIPs, grad_fieldsAtIPs, B_fluxes,imat); // assemble for (field = fields.begin(); field != fields.end(); field++) { thisFieldName = field->first; int index = (int) thisFieldName; const DenseMatrix * thisField = (const DENS_MAT *) &(field->second); if ( rhs_mask(index,FLUX) ) { numFieldDOF = thisField->nCols(); Nmat.reset(nNodesPerElement,numFieldDOF); for (int j = 0; j < nsd; j++) { // nodes X dof = nodes X ips * ips X dof ??? Nmat = nN[j].transMat(weights*B_fluxes[thisFieldName][j]); for (int i = 0; i < nNodesPerElement; ++i) { inode = conn(i); for (int k = 0; k < numFieldDOF; ++k) { rhs[thisFieldName](inode,k) += Nmat(i,k); } } } } } } } } // ------------------------------------------------------------- // compute boundary flux using given quadrature and interpolation // ------------------------------------------------------------- void FE_Engine::compute_boundary_flux( const Array2D & rhs_mask, const FIELDS & fields, const PhysicsModel * physicsModel, const Array< int > & elementMaterials, const Array< set > & pointMaterialGroups, const DIAG_MAT & weights, const SPAR_MAT & N, const GRAD_SHPFCN & dN, const DIAG_MAT & flux_mask, FIELDS & flux) const { int nNodesUnique = feMesh_->get_nNodesUnique(); FieldName thisFieldName; map rhs; map rhsSubset; map rhsSubset_local; FIELDS::const_iterator field; for (field = fields.begin(); field != fields.end(); field++) { thisFieldName = field->first; if (rhs_mask(thisFieldName,FLUX)) { int nrows = (field->second).nRows(); int ncols = (field->second).nCols(); rhs [thisFieldName].reset(nrows,ncols); rhsSubset[thisFieldName].reset(nrows,ncols); } } // R_I = - int_Omega Delta N_I .q dV compute_rhs_vector(rhs_mask, fields, physicsModel, elementMaterials, rhs); // R_I^md = - int_Omega^md Delta N_I .q dV compute_rhs_vector(rhs_mask, 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 < NUM_FIELDS; ++n) { FieldName thisFieldName = (FieldName) n; if ( rhs_mask(thisFieldName,FLUX) ) { flux[thisFieldName] = rhsSubset[thisFieldName] - flux_mask*rhs[thisFieldName]; } } } //----------------------------------------------------------------- // prescribed boundary flux using native quadrature // integrate \int_delV N_I s(x,t).n dA //----------------------------------------------------------------- void FE_Engine::add_fluxes(const Array &fieldMask, const double time, const SURFACE_SOURCE & sourceFunctions, FIELDS &nodalSources) const { int nNodesPerElement = feMesh_->get_nNodesPerElement(); int nNodesPerFace = feMesh_->get_nNodesPerFace(); int nIPsPerFace = feMesh_->get_nIPsPerFace(); int nsd = feMesh_->get_nSpatialDimensions(); FieldName thisField; int nFieldDOF = 1; XT_Function * f = NULL; //NOTE move arrays to initialization and make workspace // sizing working arrays vector dN, nN; dN.assign(nsd, DENS_MAT(nIPsPerFace, nNodesPerElement)); nN.assign(nsd, DENS_MAT(nIPsPerFace, nNodesPerElement)); DENS_MAT N(nIPsPerFace,nNodesPerElement); DENS_MAT xCoords(nsd,nNodesPerElement); DENS_MAT xAtIPs(nsd,nIPsPerFace); DENS_MAT Nmat(nNodesPerElement,nsd); DIAG_MAT weights(nIPsPerFace,nIPsPerFace); Array conn(nNodesPerElement); DENS_MAT faceSource(nIPsPerFace,nFieldDOF); // element wise assembly SURFACE_SOURCE::const_iterator src_iter; for (src_iter=sourceFunctions.begin(); src_iter!=sourceFunctions.end(); src_iter++) { FieldName thisFieldName = src_iter->first; if (!fieldMask((int)thisFieldName)) continue; typedef map > 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; const Array &fs = fset_iter->second; feMesh_->element_connectivity_unique(elem, conn); // evaluate location at ips feMesh_->face_shape_function(face, N, dN, nN, weights); feMesh_->element_coordinates(elem, xCoords); MultAB(xCoords,N,xAtIPs,0,1); //xAtIPs = xCoords*(N.transpose()); // interpolate prescribed flux at ips of this element // NOTE this seems totally redundant FSET::const_iterator face_iter = fset->find(face); if (face_iter == fset->end()) { cout << "face not found" << std::endl; // NOTE: do something there } // NOTE why can't I know the size earlier?? // NOTE only normal flux here nFieldDOF = (face_iter->second).size(); faceSource.reset(nIPsPerFace,nFieldDOF); for (int ip = 0; ip < nIPsPerFace; ++ip) { for (int idof = 0; idoff(column(xAtIPs,ip).get_ptr(),time); } } // assemble Nmat = N.transMat(weights*faceSource); for (int i = 0; i < nNodesPerElement; ++i) { int inode = conn(i); for (int idof = 0; idof < nFieldDOF; ++idof) { nodalSources[thisFieldName](inode,idof) += Nmat(i,idof); } // end assemble nFieldDOF } // end assemble nNodesPerElement } // end fset loop } // field loop } //----------------------------------------------------------------- // prescribed volume flux using native quadrature // integrate \int_V N_I s(x,t) dV //----------------------------------------------------------------- void FE_Engine::add_sources(const Array &fieldMask, const double time, const VOLUME_SOURCE &sourceFunctions, FIELDS &nodalSources) const { int nNodes = feMesh_->get_nNodes(); int nNodesPerElement = feMesh_->get_nNodesPerElement(); int nIPsPerElement = feMesh_->get_nIPsPerElement(); int nsd = feMesh_->get_nSpatialDimensions(); int nelems = feMesh_->get_nElements(); int nFieldDOF = 1; XT_Function * f = NULL; //NOTE move arrays to initialization and make workspace // sizing working arrays DENS_MAT elemSource(nIPsPerElement,nFieldDOF); DENS_MAT N(nIPsPerElement,nNodesPerElement); DENS_MAT xCoords(nsd,nNodesPerElement); DENS_MAT xAtIPs(nsd,nIPsPerElement); DENS_MAT Nwg(nNodesPerElement,1); DENS_MAT Nmat(nNodesPerElement,nsd); DiagonalMatrix weights(nIPsPerElement,nIPsPerElement); Array conn(nNodesPerElement); // element wise assembly for (int ielem = 0; ielem < nelems; ++ielem) { feMesh_->element_connectivity_unique(ielem, conn); // evaluate location at ips feMesh_->shape_function(ielem, N, weights); feMesh_->element_coordinates(ielem, xCoords); xAtIPs =xCoords*(N.transpose()); VOLUME_SOURCE ::const_iterator src_iter; for (src_iter = sourceFunctions.begin(); src_iter != sourceFunctions.end(); src_iter++) { FieldName thisField = src_iter->first; int index = (int) thisField; if ( fieldMask(index) ) { const Array2D * thisSource = (const Array2D *) &(src_iter->second); 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) { f = (*thisSource)(ielem,idof); if (f) { elemSource(ip,idof) = f->f(column(xAtIPs,ip).get_ptr(),time); } } } // assemble Nmat.reset(nNodesPerElement,nFieldDOF); Nmat = N.transMat(weights*elemSource); for (int i = 0; i < nNodesPerElement; ++i) { int inode = conn(i); for (int idof = 0; idof < nFieldDOF; ++idof) { nodalSources[thisField](inode,idof) += Nmat(i,idof); } } } } } } //----------------------------------------------------------------- // 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 nNodesPerElement = feMesh_->get_nNodesPerElement(); int nIPsPerFace = feMesh_->get_nIPsPerFace(); int nsd = feMesh_->get_nSpatialDimensions(); int nelems = feMesh_->get_nElements(); int numFieldDOF = field.nCols(); double a[3] = {0,0,0}; a[axis] = 1; // sizing working arrays DENS_MAT N(nIPsPerFace,nNodesPerElement); DENS_MAT n(nsd,nIPsPerFace); DENS_MAT fieldsAtIPs(nIPsPerFace,numFieldDOF); DENS_MAT localElementFields(nNodesPerElement,numFieldDOF); DiagonalMatrix weights(nIPsPerFace,nIPsPerFace); Array conn(nNodesPerElement); DENS_MAT Nmat(nsd,numFieldDOF); DENS_MAT integrals(numFieldDOF,nsd); // element wise assembly set< pair >::iterator iter; for (iter = faceSet.begin(); iter != faceSet.end(); iter++) { // evaluate shape functions at ips feMesh_->face_shape_function(*iter, N, n, weights); // 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]; } } int nips = weights.nRows(); // get connectivity int ielem = iter->first; feMesh_->element_connectivity_unique(ielem, conn); // interpolate fields and gradients of fields ips of this element for (int i = 0; i < nNodesPerElement; i++) { for (int j = 0; j < numFieldDOF; 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*weights*fieldsAtIPs; for (int j = 0; j < nsd; j++) { for (int k = 0; k < numFieldDOF; ++k) { integrals(k,j) += Nmat(j,k); } } } // (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 (numFieldDOF == 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 (numFieldDOF == 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 (numFieldDOF == 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 (numFieldDOF == 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 += tostring(numFieldDOF); throw ATC_Error(0,msg); } } //----------------------------------------------------------------- // evaluate shape functions at given points //----------------------------------------------------------------- void FE_Engine::evaluate_shape_functions( const MATRIX & pt_coords, SPAR_MAT & N, Array & pointToEltMap) const { // Get shape function and derivatives at atomic locations int npe = feMesh_->get_nNodesPerElement(); int nsd = feMesh_->get_nSpatialDimensions(); int nnodes = feMesh_->get_nNodesUnique(); int npts = pt_coords.nCols(); pointToEltMap.reset(npts); // loop over point (atom) coordinates DENS_VEC x(nsd); Array node_index(npe); DENS_VEC shp(npe); N.reset(npts,nnodes); for (int i = 0; i < npts; ++i) { for (int k = 0; k < nsd; ++k) { x(k) = pt_coords(k,i); } int eltID; feMesh_->shape_functions(x,shp,eltID,node_index); for (int j = 0; j < npe; ++j) { int jnode = node_index(j); N.add(i,jnode,shp(j)); } pointToEltMap(i) = eltID; } 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, GRAD_SHPFCN & dN, Array & pointToEltMap) const { // Get shape function and derivatives at atomic locations int npe = feMesh_->get_nNodesPerElement(); int nsd = feMesh_->get_nSpatialDimensions(); int nnodes = feMesh_->get_nNodesUnique(); int npts = pt_coords.nCols(); pointToEltMap.reset(npts); // loop over point (atom) coordinates DENS_VEC x(nsd); Array node_index(npe); DENS_VEC shp(npe); DENS_MAT dshp(nsd,npe); 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(k,i); } int eltID; feMesh_->shape_functions(x,shp,dshp,eltID,node_index); for (int j = 0; j < npe; ++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)); } } pointToEltMap(i) = eltID; } 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 VECTOR & x, Array& node_index, DENS_VEC& shp, DENS_MAT& dshp, int & eltID) const { // Get shape function and derivatives at a specific point int nsd = feMesh_->get_nSpatialDimensions(); int npe = feMesh_->get_nNodesPerElement(); dshp.reset(nsd,npe); feMesh_->shape_functions(x,shp,dshp,eltID,node_index); } //----------------------------------------------------------------- // create a uniform rectangular mesh on a rectangular region //----------------------------------------------------------------- void FE_Engine::create_mesh(int nx, int ny, int nz, char * regionName, int xperiodic, int yperiodic, int zperiodic) { double xmin, xmax, ymin, ymax, zmin, zmax; double xscale, yscale, zscale; double boxxlo, boxxhi, boxylo, boxyhi, boxzlo, boxzhi; // check to see if region exists and is it a box, and if so get the bounds bool isBox; isBox = atcTransfer_->get_region_bounds(regionName, xmin, xmax, ymin, ymax, zmin, zmax, xscale, yscale, zscale); if (!isBox) throw ATC_Error(0,"Region for FE mesh is not a box"); feMesh_ = new FE_Uniform3DMesh(nx,ny,nz, xmin,xmax, ymin,ymax, zmin,zmax, xscale, yscale, zscale, xperiodic, yperiodic, zperiodic); if (LammpsInterface::instance()->comm_rank()==0) { printf(" ATC:: created FEM Mesh with %i Global Nodes, %i Unique Nodes, and %i Elements\n", feMesh_->get_nNodes(),feMesh_->get_nNodesUnique(),feMesh_->get_nElements()); } } };