Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
73a85d6
Add a fixed-size fast path to double_dot_product
dseyler Aug 24, 2026
9ee0f62
Updated documentation for double dot product
dseyler Aug 27, 2026
6edb73d
Compute dyadic product with eigen
dseyler Aug 26, 2026
85d72ec
Updated documentation and removed references to Fortran
dseyler Aug 27, 2026
1e770a1
Compute fixed-size tensor products with Eigen instead of nested loops
dseyler Aug 30, 2026
c3b3303
Templated viscous stress functions on nsd and implemented fixed-size …
dseyler Aug 25, 2026
4915b39
Updated documentation
dseyler Aug 27, 2026
1c22dad
Trimmed comments from viscous stress functions
dseyler Aug 30, 2026
f2f6198
trim comments and assume 27-node element bound rather than checking p…
dseyler Aug 31, 2026
2f45845
Polished documentations, variable names, and types
dseyler Aug 31, 2026
83a4ad2
Removed column hoisting, which had no impact on runtime
dseyler Aug 31, 2026
42a5f4a
Dispatch by nsd within compute_visc_stress_and_tangent
dseyler Sep 2, 2026
990ccb5
Reuse viscosity across gauss points for linear elements. lShpF correc…
dseyler Sep 2, 2026
f0e2ac0
Reverted minor optimizations that made code less readable
dseyler Sep 4, 2026
2b71444
Merge remote-tracking branch 'origin/main' into perf/visc-stress-temp…
dseyler Sep 4, 2026
2db0a06
Merge branch 'main' into perf/visc-stress-templated
aabrown100-git Sep 8, 2026
8e25ed2
Declare viscous buffers as statics. Moved viscosity models into anony…
dseyler Sep 17, 2026
b71a6c2
merge remote-tracking branch 'fork/perf/visc-stress-templated' into p…
dseyler Sep 17, 2026
9104cea
Merge remote-tracking branch 'origin/main' into perf/visc-stress-temp…
dseyler Sep 17, 2026
952b7fc
recompute_visc flag checked in struct / ustruct instead of passed int…
dseyler Sep 17, 2026
f59e22f
Merge branch 'perf/fixed-size-tensor-ops' into perf/visc-stress-templ…
dseyler Sep 17, 2026
1ecce85
Added nsd-templated compute_pk2cc overloads taking Eigen matrices
dseyler Sep 18, 2026
fc4511a
Keep F, vx, S0, Dm, P and Svis as fixed-size Eigen matrices
dseyler Sep 21, 2026
6f2ab9d
Merge remote-tracking branch 'origin/main' into perf/visc-stress-temp…
dseyler Sep 21, 2026
368df7c
Fixed typos
dseyler Sep 21, 2026
8ea22ae
max number of nodes = 27 defined in consts.h
dseyler Sep 21, 2026
8dc84f5
Consolidate Matrix aliases into mat_fun
dseyler Sep 21, 2026
c6d3727
refactor Bm as per-node Eigen blocks
dseyler Sep 22, 2026
0cbfa49
Hoisted and collapsed geometric stiffness computation with Eigen and …
dseyler Sep 22, 2026
3084c8a
Removed unused locals in ustruct and collapsed more arithmetic with e…
dseyler Sep 22, 2026
f8a9e76
Collapse rMNqx and rMNwx with Eigen
dseyler Sep 22, 2026
0c401dd
Collapsed interpolated quantitied and gradients for readability using…
dseyler Sep 22, 2026
d424f50
condensed strain displacmeent computation
dseyler Sep 22, 2026
cc08b5e
Implemented eigen view helper in viscosity models
dseyler Sep 22, 2026
d2590a6
add compound assignments for consistency
dseyler Sep 22, 2026
a22846f
reverted two argument f + s to fl for fiber matrix
dseyler Sep 22, 2026
3c04e25
removed dead locals and bare declarations
dseyler Sep 22, 2026
ae0043b
aligned compute_pk2cc nsd branches for consistency
dseyler Sep 22, 2026
a061129
Added comments to dyadic_product, eigen_view, and Kvis buffers
dseyler Sep 22, 2026
793c716
Removed dead locals and moved declarations closer to use
dseyler Sep 22, 2026
28091da
Organized prestress matrix to show voigt symmetry
dseyler Sep 22, 2026
424ea14
Refactored viscosity to simplify call site and hide Kvis_u, Kvis_v re…
dseyler Sep 22, 2026
d55b935
Moved Eigen declarations closer to use
dseyler Sep 23, 2026
66720df
Hoisted Fiber matrix construction out of gauss loop and cleaned up so…
dseyler Sep 23, 2026
48ed4b7
Remove dead mat_fun functions
dseyler Sep 23, 2026
888f1a5
Cleaned up comments and out-of-scope edits
dseyler Sep 23, 2026
43f93a9
Addressed review comments (constexpr, docs, mutable eigen view)
dseyler Sep 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions Code/Source/solver/consts.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@

namespace consts {

const int maxNSD = 3;
constexpr int maxNSD = 3;

const int maxNProp = 20;
constexpr int maxNoN = 27; // Max node count in nn_elem_props.h

const int maxOutput = 5;
constexpr int maxNProp = 20;

constexpr int maxOutput = 5;

/// Use inf numeric values to represent a value that is not set.
const int int_inf = std::numeric_limits<int>::infinity();
Expand Down
20 changes: 7 additions & 13 deletions Code/Source/solver/fluid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1456,9 +1456,6 @@ void fluid_3d_c(ComMod& com_mod, const int vmsFlag, const int eNoNw, const int e
double start_time = utils::cput();
#endif

// Maximum size of arrays sized by (3,eNoNw) -> (3,MAX_SIZE).
const int MAX_SIZE = 27;

using namespace consts;

int cEq = com_mod.cEq;
Expand Down Expand Up @@ -1583,7 +1580,7 @@ void fluid_3d_c(ComMod& com_mod, const int vmsFlag, const int eNoNw, const int e
es[1][2] = es[2][1];
es[2][0] = es[0][2];

double esNx[3][MAX_SIZE];
double esNx[3][maxNoN];

for (int a = 0; a < eNoNw; a++) {
esNx[0][a] = es[0][0]*Nwx(0,a) + es[1][0]*Nwx(1,a) + es[2][0]*Nwx(2,a);
Expand Down Expand Up @@ -1647,7 +1644,7 @@ void fluid_3d_c(ComMod& com_mod, const int vmsFlag, const int eNoNw, const int e
// Stabilization parameters
//
double up[3] = {};
double updu[3][3][MAX_SIZE] = {};
double updu[3][3][maxNoN] = {};
double tauM = 0.0;

if (vmsFlag) {
Expand Down Expand Up @@ -1782,9 +1779,6 @@ void fluid_3d_m(ComMod& com_mod, const int vmsFlag, const int eNoNw, const int e
double start_time = utils::cput();
#endif

// Maximum size of arrays sized by (3,eNoNw) -> (3,MAX_SIZE).
const int MAX_SIZE = 27;

using namespace consts;

int cEq = com_mod.cEq;
Expand Down Expand Up @@ -1927,7 +1921,7 @@ void fluid_3d_m(ComMod& com_mod, const int vmsFlag, const int eNoNw, const int e
es[1][2] = es[2][1];
es[2][0] = es[0][2];

double esNx[3][MAX_SIZE];
double esNx[3][maxNoN];

for (int a = 0; a < eNoNw; a++) {
esNx[0][a] = es[0][0]*Nwx(0,a) + es[1][0]*Nwx(1,a) + es[2][0]*Nwx(2,a);
Expand Down Expand Up @@ -2100,10 +2094,10 @@ void fluid_3d_m(ComMod& com_mod, const int vmsFlag, const int eNoNw, const int e

// Local residual
//
double updu[3][3][MAX_SIZE] = {};
double uNx[MAX_SIZE] = {};
double upNx[MAX_SIZE] = {};
double uaNx[MAX_SIZE] = {};
double updu[3][3][maxNoN] = {};
double uNx[maxNoN] = {};
double upNx[maxNoN] = {};
double uaNx[maxNoN] = {};

for (int a = 0; a < eNoNw; a++) {
lR(0,a) = lR(0,a) + wr*Nw(a)*rV[0] + w*(Nwx(0,a)*rM[0][0] + Nwx(1,a)*rM[1][0] + Nwx(2,a)*rM[2][0]);
Expand Down
28 changes: 16 additions & 12 deletions Code/Source/solver/fsi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@ void construct_fsi(ComMod& com_mod, CepMod& cep_mod, const mshType& lM, const So
ya_l_s = 0.0;
ya_l_n = 0.0;

if (lM.fN.size() != 0) {
for (int iFn = 0; iFn < nFn; iFn++) {
for (int i = 0; i < nsd; i++) {
fN(i,iFn) = lM.fN(i+nsd*iFn,e);
}
}
}

for (int a = 0; a < eNoN; a++) {
int Ac = lM.IEN(a,e);
ptr(a) = Ac;
Expand All @@ -116,14 +124,6 @@ void construct_fsi(ComMod& com_mod, CepMod& cep_mod, const mshType& lM, const So
dl(i,a) = Dg(i,Ac);
}

if (lM.fN.size() != 0) {
for (int iFn = 0; iFn < nFn; iFn++) {
for (int i = 0; i < nsd; i++) {
fN(i,iFn) = lM.fN(i+nsd*iFn,e);
}
}
}

if (pS0.size() != 0) {
pS0l.set_col(a, pS0.col(Ac));
}
Expand Down Expand Up @@ -186,7 +186,11 @@ void construct_fsi(ComMod& com_mod, CepMod& cep_mod, const mshType& lM, const So
}
}

if (g == 0 || !fs_1[0].lShpF) {
// Shape function gradients and the viscous response are constant
// within linear triangles and tetrahedra.
const bool recompute_visc = (g == 0 || !fs_1[0].lShpF);

if (recompute_visc) {
auto Nx = fs_1[0].Nx.rslice(g);
nn::gnn(fs_1[0].eNoN, nsd, nsd, Nx, xwl, Nwx, Jac, ksix);
if (utils::is_zero(Jac)) {
Expand Down Expand Up @@ -220,7 +224,7 @@ void construct_fsi(ComMod& com_mod, CepMod& cep_mod, const mshType& lM, const So
auto N0 = fs_1[0].N.col(g);
struct_ns::struct_3d(com_mod, cep_mod, fs_1[0].eNoN, nFn, w, N0,
Nwx, al, yl, dl, bfl, fN, pS0l, pSl, ya_l_f,
ya_l_s, ya_l_n, lR, lK);
ya_l_s, ya_l_n, lR, lK, recompute_visc);
} break;
case Equation_lElas:
throw std::runtime_error("[construct_fsi] LELAS3D not implemented");
Expand All @@ -233,7 +237,7 @@ void construct_fsi(ComMod& com_mod, CepMod& cep_mod, const mshType& lM, const So
ustruct::ustruct_3d_m(com_mod, cep_mod, vmsStab, fs_1[0].eNoN,
fs_1[1].eNoN, nFn, w, Jac, N0, N1, Nwx, al,
yl, dl, bfl, fN, ya_l_f, ya_l_s, ya_l_n, lR,
lK, lKd);
lK, lKd, recompute_visc);
break;
}

Expand All @@ -256,7 +260,7 @@ void construct_fsi(ComMod& com_mod, CepMod& cep_mod, const mshType& lM, const So
auto N0 = fs_1[0].N.col(g);
struct_ns::struct_2d(com_mod, cep_mod, fs_1[0].eNoN, nFn, w, N0,
Nwx, al, yl, dl, bfl, fN, pS0l, pSl, ya_l_f,
ya_l_s, ya_l_n, lR, lK);
ya_l_s, ya_l_n, lR, lK, recompute_visc);
} break;

case Equation_ustruct:
Expand Down
137 changes: 105 additions & 32 deletions Code/Source/solver/mat_fun.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <stdexcept>

#include "Array.h"
#include "consts.h"
#include "Tensor4.h"
#include "Vector.h"
#include "FE/Common/FEException.h"
Expand All @@ -21,13 +22,79 @@
/// \todo [TODO:DaveP] this should just be a namespace?
//
namespace mat_fun {
// Define templated type aliases for Eigen matrices and tensors for convenience
template<size_t nsd>
/// @brief A 2nd order tensor, nsd x nsd, fixed size and stack allocated.
/// Used for the deformation gradient, stresses and similar quantities that
/// have a known size at compile time.
template<int nsd>
using Matrix = Eigen::Matrix<double, nsd, nsd>;

template<size_t nsd>
/// @brief A 4th order tensor, nsd x nsd x nsd x nsd, fixed size and stack
/// allocated. Used for the material elasticity tensor and other 4th order tensors
/// that have a known size at compile time.
template<int nsd>
using Tensor = Eigen::TensorFixedSize<double, Eigen::Sizes<nsd, nsd, nsd, nsd>>;

/// @brief One nsd-vector per element node, so nsd x eNoN. Row count is fixed
/// at compile time while column count is the element's node count, known only
/// at run time, so it is bounded by consts::maxNoN to stay stack allocated.
/// Used for shape function gradients and other per-node vector quantities.
template <int nsd>
using NodalMatrix = Eigen::Matrix<double, nsd, Eigen::Dynamic, 0, nsd, consts::maxNoN>;

/// @brief One scalar per element node, so eNoN entries. Dynamic length bounded
/// by consts::maxNoN to stay stack allocated, as for NodalMatrix. Used for shape
/// function values and other per-node scalar quantities.
using NodalVector = Eigen::Matrix<double, Eigen::Dynamic, 1, 0, consts::maxNoN, 1>;

// The eigen_view overloads below wrap an Array or Vector in an Eigen::Map that
// shares its storage, so the container must outlive the view.

/// @brief Read-only Eigen view of an Array, sharing its storage.
///
/// @tparam rows Row count, fixed at compile time; the columns are taken from the Array.
template <int rows>
Eigen::Map<const Eigen::Matrix<double, rows, Eigen::Dynamic>>
eigen_view(const Array<double>& A) {
if (A.nrows() != rows) {
svmp::raise<svmp::FE::InvalidArgumentException>(
"A view of " + std::to_string(rows) + " rows was requested for an array with " +
std::to_string(A.nrows()) + " rows.");
}
return {A.data(), rows, A.ncols()};
}

/// @brief Read-only Eigen view of a whole Array, sharing its storage.
inline Eigen::Map<const Eigen::MatrixXd>
eigen_view(const Array<double>& A) {
return {A.data(), A.nrows(), A.ncols()};
}

/// @brief Writable Eigen view of a whole Array, sharing its storage.
inline Eigen::Map<Eigen::MatrixXd>
eigen_view_mutable(Array<double>& A) {
return {A.data(), A.nrows(), A.ncols()};
}
Comment on lines +72 to +76

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: I suggest the more explicit name eigen_view_mutable.


/// @brief Read-only Eigen view of a whole Vector, sharing its storage.
inline Eigen::Map<const Eigen::VectorXd>
eigen_view(const Vector<double>& v) {
return {v.data(), v.size()};
}

/// @brief Read-only Eigen view of a Vector, sharing its storage.
///
/// @tparam rows Entry count, fixed at compile time.
template <int rows>
Eigen::Map<const Eigen::Matrix<double, rows, 1>>
eigen_view(const Vector<double>& v) {
if (v.size() != rows) {
svmp::raise<svmp::FE::InvalidArgumentException>(
"A view of " + std::to_string(rows) + " entries was requested for a vector with " +
std::to_string(v.size()) + " entries.");
}
return Eigen::Map<const Eigen::Matrix<double, rows, 1>>(v.data());
}

// Function to convert Array<double> to Eigen::Matrix
template <typename MatrixType>
MatrixType convert_to_eigen_matrix(const Array<double>& src) {
Expand Down Expand Up @@ -179,14 +246,32 @@ namespace mat_fun {
Tensor4<double> ten_ddot_3424(const Tensor4<double>& A, const Tensor4<double>& B, const int nd);

/**
* @brief Contracts two 4th order tensors A and B over two dimensions,
*
* @brief Contracts two 4th order tensors A and B over two dimensions.
*
* For example, if dimsA = {0, 1} and dimsB = {2, 3} this is
* C_klmn = A_ijkl B_mnij (sum over i, j)
*
* @tparam nsd Number of spatial dimensions; each tensor is nsd^4.
* @param[in] A,B Fourth order tensors to contract.
* @param[in] dimsA,dimsB Indices of the contracted dimensions of A and B.
* @return The contracted tensor.
*/
template <int nsd>
Tensor<nsd>
double_dot_product(const Tensor<nsd>& A, const std::array<int, 2>& dimsA,
const Tensor<nsd>& B, const std::array<int, 2>& dimsB) {

// Fast path for dimsA = dimsB = {2,3}: C_ijmn = A_ijkl * B_mnkl.
if (dimsA[0] == 2 && dimsA[1] == 3 && dimsB[0] == 2 && dimsB[1] == 3) {
constexpr int N = nsd * nsd;
Tensor<nsd> C;
Eigen::Map<const Eigen::Matrix<double, N, N>> a(A.data());
Eigen::Map<const Eigen::Matrix<double, N, N>> b(B.data());
Eigen::Map<Eigen::Matrix<double, N, N>> c(C.data());
c.noalias() = a * b.transpose();
return C;
}

// Define the contraction dimensions
Eigen::array<Eigen::IndexPair<int>, 2> contractionDims = {
Eigen::IndexPair<int>(dimsA[0], dimsB[0]), // Contract A's dimsA[0] with B's dimsB[0]
Expand All @@ -195,9 +280,6 @@ namespace mat_fun {

// Return the double dot product
return A.contract(B, contractionDims);

// For some reason, in this case the Eigen::Tensor contract function is
// faster than a for loop implementation.
}

Tensor4<double> ten_dyad_prod(const Array<double>& A, const Array<double>& B, const int nd);
Expand All @@ -215,20 +297,13 @@ namespace mat_fun {
dyadic_product(const Matrix<nsd>& A, const Matrix<nsd>& B) {
// Initialize the result tensor
Tensor<nsd> C;
constexpr int N = nsd * nsd;

// Compute the dyadic product: C_ijkl = A_ij * B_kl
for (int i = 0; i < nsd; ++i) {
for (int j = 0; j < nsd; ++j) {
for (int k = 0; k < nsd; ++k) {
for (int l = 0; l < nsd; ++l) {
C(i,j,k,l) = A(i,j) * B(k,l);
}
}
}
}
// For some reason, in this case the Eigen::Tensor contract function is
// slower than the for loop implementation

// Column-major storage flattens index pairs: c(ij,kl) = a(ij) * b(kl).
Eigen::Map<const Eigen::Matrix<double, N, 1>> a(A.data());
Eigen::Map<const Eigen::Matrix<double, N, 1>> b(B.data());
Eigen::Map<Eigen::Matrix<double, N, N>> c(C.data());
c.noalias() = a * b.transpose();
return C;
}

Expand Down Expand Up @@ -265,8 +340,9 @@ namespace mat_fun {

/// @brief Create a 4th order tensor from symmetric outer product of two matrices: C_ijkl = 0.5 * (A_ik * B_jl + A_il * B_jk)
///
/// Reproduces 'FUNCTION TEN_SYMMPROD(A, B, nd) RESULT(C)'.
//
/// @tparam nsd Number of spatial dimensions.
/// @param[in] A,B Second order tensors.
/// @return The resulting 4th order tensor.
template <int nsd>
Tensor<nsd>
symmetric_dyadic_product(const Matrix<nsd>& A, const Matrix<nsd>& B) {
Expand All @@ -275,17 +351,14 @@ namespace mat_fun {
Tensor<nsd> C;

// Compute the symmetric product: C_ijkl = 0.5 * (A_ik * B_jl + A_il * B_jk)
for (int i = 0; i < nsd; ++i) {
for (int j = 0; j < nsd; ++j) {
for (int k = 0; k < nsd; ++k) {
for (int l = 0; l < nsd; ++l) {
C(i,j,k,l) = 0.5 * (A(i,k) * B(j,l) + A(i,l) * B(j,k));
}
}
for (int l = 0; l < nsd; ++l) {
for (int k = 0; k < nsd; ++k) {
// blk views the (k,l) block of C, so blk(i,j) is C(i,j,k,l).
Eigen::Map<Eigen::Matrix<double, nsd, nsd>> blk(C.data() + nsd * nsd * (k + nsd * l));
blk.noalias() = 0.5 * (A.col(k) * B.col(l).transpose()
+ A.col(l) * B.col(k).transpose());
}
}
// For some reason, in this case the for loop implementation is faster
// than the Eigen::Tensor contract method

// Return the symmetric product
return C;
Expand Down
Loading
Loading