using MathNet.Numerics.LinearAlgebra;
public class SAP2000PlateElement
public double[,] NodeCoordinates { get; }
public double Thickness { get; }
public double YoungsModulus { get; }
public double PoissonsRatio { get; }
public double DrillingStiffnessFactor { get; } = 1e-4;
public SAP2000PlateElement(double[,] nodeCoordinates, double thickness, double youngsModulus, double poissonsRatio)
NodeCoordinates = nodeCoordinates;
YoungsModulus = youngsModulus;
PoissonsRatio = poissonsRatio;
public Matrix<double> ComputeStiffnessMatrix()
var totalDof = 4 * dofPerNode;
var ke = Matrix<double>.Build.Dense(totalDof, totalDof);
[-1.0 / Math.Sqrt(3), -1.0 / Math.Sqrt(3)],
[1.0 / Math.Sqrt(3), -1.0 / Math.Sqrt(3)],
[1.0 / Math.Sqrt(3), 1.0 / Math.Sqrt(3)],
[-1.0 / Math.Sqrt(3), 1.0 / Math.Sqrt(3)]
foreach (var point in gaussPoints)
var dN_nat = ComputeShapeFunctionDerivatives(xi, eta);
var N = ComputeShapeFunctions(xi, eta);
var J = ComputeJacobian(dN_nat);
var detJ = J.Determinant();
var dN_global = invJ * dN_nat;
var dA = detJ * gaussWeight;
var B_mem = ComputeMembraneBMatrix(dN_global);
var D_mem = ComputeMembraneMaterialMatrix();
ke += AssembleFullMatrix(B_mem.Transpose() * D_mem * B_mem * dA, 0);
var B_bend = ComputeBendingBMatrix(dN_global);
var D_bend = ComputeBendingMaterialMatrix();
ke += AssembleFullMatrix(B_bend.Transpose() * D_bend * B_bend * dA, 3);
var B_shear = ComputeShearBMatrix(dN_global, N);
var D_shear = ComputeShearMaterialMatrix();
ke += AssembleFullMatrix(B_shear.Transpose() * D_shear * B_shear * dA, 2);
for (var i = 0; i < 4; i++)
var idx = i * dofPerNode + 5;
ke[idx, idx] += DrillingStiffnessFactor * YoungsModulus * Thickness;
#region Shape Functions and Jacobian
private double[] ComputeShapeFunctions(double xi, double eta)
N[0] = 0.25 * (1 - xi) * (1 - eta);
N[1] = 0.25 * (1 + xi) * (1 - eta);
N[2] = 0.25 * (1 + xi) * (1 + eta);
N[3] = 0.25 * (1 - xi) * (1 + eta);
private Matrix<double> ComputeShapeFunctionDerivatives(double xi, double eta)
var dN = Matrix<double>.Build.Dense(2, 4);
dN[0, 0] = -0.25 * (1 - eta);
dN[0, 1] = 0.25 * (1 - eta);
dN[0, 2] = 0.25 * (1 + eta);
dN[0, 3] = -0.25 * (1 + eta);
dN[1, 0] = -0.25 * (1 - xi);
dN[1, 1] = -0.25 * (1 + xi);
dN[1, 2] = 0.25 * (1 + xi);
dN[1, 3] = 0.25 * (1 - xi);
private Matrix<double> ComputeJacobian(Matrix<double> dN_nat)
var J = Matrix<double>.Build.Dense(2, 2);
for (var i = 0; i < 4; i++)
var x = NodeCoordinates[i, 0];
var y = NodeCoordinates[i, 1];
J[0, 0] += dN_nat[0, i] * x;
J[0, 1] += dN_nat[0, i] * y;
J[1, 0] += dN_nat[1, i] * x;
J[1, 1] += dN_nat[1, i] * y;
#region B-Matrices and Material Matrices
private Matrix<double> ComputeMembraneBMatrix(Matrix<double> dN_global)
var Bm = Matrix<double>.Build.Dense(3, 4 * 2);
for (var i = 0; i < 4; i++)
var dN_dx = dN_global[0, i];
var dN_dy = dN_global[1, i];
private Matrix<double> ComputeMembraneMaterialMatrix()
var coeff = YoungsModulus / (1 - Math.Pow(PoissonsRatio, 2));
var Dm = Matrix<double>.Build.Dense(3, 3);
Dm[0, 0] = coeff; Dm[0, 1] = coeff * PoissonsRatio;
Dm[1, 0] = coeff * PoissonsRatio; Dm[1, 1] = coeff;
Dm[2, 2] = coeff * (1 - PoissonsRatio) / 2;
private Matrix<double> ComputeBendingBMatrix(Matrix<double> dN_global)
var Bb = Matrix<double>.Build.Dense(3, 4 * 2);
for (var i = 0; i < 4; i++)
var dN_dx = dN_global[0, i];
var dN_dy = dN_global[1, i];
private Matrix<double> ComputeBendingMaterialMatrix()
var coeff = (YoungsModulus * Math.Pow(Thickness, 3)) / (12 * (1 - Math.Pow(PoissonsRatio, 2)));
var Db = Matrix<double>.Build.Dense(3, 3);
Db[0, 1] = PoissonsRatio * coeff;
Db[1, 0] = PoissonsRatio * coeff;
Db[2, 2] = (1 - PoissonsRatio) * coeff / 2;
private Matrix<double> ComputeShearBMatrix(Matrix<double> dN_global, double[] N)
var Bs = Matrix<double>.Build.Dense(2, 4 * 3);
for (var i = 0; i < 4; i++)
var dN_dx = dN_global[0, i];
var dN_dy = dN_global[1, i];
private Matrix<double> ComputeShearMaterialMatrix()
var G = YoungsModulus / (2 * (1 + PoissonsRatio));
var coeff = k * (YoungsModulus * Thickness) / (2 * (1 + PoissonsRatio));
var Ds = Matrix<double>.Build.DenseIdentity(2) * coeff;
#region Assembly of Contributions into the Full DOF Ordering
private Matrix<double> AssembleFullMatrix(Matrix<double> keSub, int offset)
var totalDof = 4 * dofPerNode;
var Kfull = Matrix<double>.Build.Dense(totalDof, totalDof);
if (offset == 0 || offset == 3) subDofPerNode = 2;
else if (offset == 2) subDofPerNode = 3;
else throw new Exception("Invalid offset in assembly.");
for (var node = 0; node < 4; node++)
for (var i = 0; i < subDofPerNode; i++)
for (var j = 0; j < subDofPerNode; j++)
var row = node * dofPerNode + (offset + i);
var col = node * dofPerNode + (offset + j);
var subIndex = node * subDofPerNode + i;
var subCol = node * subDofPerNode + j;
Kfull[row, col] += keSub[subIndex, subCol];