awatif-fem
Version:
Awatif Finite Element Method (FEM) Solver
434 lines (387 loc) • 10.9 kB
text/typescript
import { Node, ElementInputs } from ".././data-model";
import {
add,
matrix,
multiply,
norm,
subtract,
transpose,
zeros,
Matrix,
} from "mathjs";
export function getLocalStiffnessMatrix(
nodes: Node[],
elementInputs: ElementInputs,
index: number
): number[][] {
if (nodes.length === 2)
return getLocalStiffnessMatrixFrame(nodes, elementInputs, index);
if (nodes.length === 3)
return getLocalStiffnessMatrixPlate(nodes, elementInputs, index);
}
function getLocalStiffnessMatrixFrame(
nodes: Node[],
elementInputs: ElementInputs,
index: number
): number[][] {
const Iz = elementInputs?.momentsOfInertiaZ?.get(index) ?? 0;
const Iy = elementInputs?.momentsOfInertiaY?.get(index) ?? 0;
const E = elementInputs?.elasticities?.get(index) ?? 0;
const A = elementInputs?.areas?.get(index) ?? 0;
const G = elementInputs?.shearModuli?.get(index) ?? 0;
const J = elementInputs?.torsionalConstants?.get(index) ?? 0;
const L = norm(subtract(nodes[0], nodes[1])) as number;
const EA = (E * A) / L;
const EIz = (E * Iz) / L ** 3;
const EIy = (E * Iy) / L ** 3;
const GJ = (G * J) / L;
return [
[EA, 0, 0, 0, 0, 0, -EA, 0, 0, 0, 0, 0],
[0, 12 * EIz, 0, 0, 0, 6 * L * EIz, 0, -12 * EIz, 0, 0, 0, 6 * L * EIz],
[0, 0, 12 * EIy, 0, -6 * L * EIy, 0, 0, 0, -12 * EIy, 0, -6 * L * EIy, 0],
[0, 0, 0, GJ, 0, 0, 0, 0, 0, -GJ, 0, 0],
[
0,
0,
-6 * L * EIy,
0,
4 * EIy * L ** 2,
0,
0,
0,
6 * L * EIy,
0,
2 * EIy * L ** 2,
0,
],
[
0,
6 * L * EIz,
0,
0,
0,
4 * EIz * L ** 2,
0,
-6 * L * EIz,
0,
0,
0,
2 * EIz * L ** 2,
],
[-EA, 0, 0, 0, 0, 0, EA, 0, 0, 0, 0, 0],
[0, -12 * EIz, 0, 0, 0, -6 * EIz * L, 0, 12 * EIz, 0, 0, 0, -6 * EIz * L],
[0, 0, -12 * EIy, 0, 6 * L * EIy, 0, 0, 0, 12 * EIy, 0, 6 * L * EIy, 0],
[0, 0, 0, -GJ, 0, 0, 0, 0, 0, GJ, 0, 0],
[
0,
0,
-6 * L * EIy,
0,
2 * EIy * L ** 2,
0,
0,
0,
6 * L * EIy,
0,
4 * EIy * L ** 2,
0,
],
[
0,
6 * L * EIz,
0,
0,
0,
2 * EIz * L ** 2,
0,
-6 * L * EIz,
0,
0,
0,
4 * EIz * L ** 2,
],
];
}
export function buildOrthotropicDb(
Ex: number,
Ey: number,
Gxy: number,
nu_xy: number,
t: number
): Matrix {
// reciprocal Poisson
const nu_yx = (Ey * nu_xy) / Ex;
const denom = 1 - nu_xy * nu_yx;
// reduced stiffnesses
const Q11 = Ex / denom;
const Q22 = Ey / denom;
const Q12 = (nu_xy * Ey) / denom;
const Q66 = Gxy;
// base Q matrix
let Q = matrix([
[Q11, Q12, 0],
[Q12, Q22, 0],
[0, 0, Q66],
]);
return multiply(t ** 3 / 12, Q) as Matrix;
}
function getLocalStiffnessMatrixPlate(
nodes: Node[],
elementInputs: ElementInputs,
index: number
): number[][] {
// Based on thesis: Development of Membrane, Plate and Flat Shell Elements in Java Chapter 4.4
// https://vtechworks.lib.vt.edu/server/api/core/bitstreams/edb7e2db-eebf-43e9-aa1f-cfca4b8a46e9/content
const E = elementInputs?.elasticities?.get(index) ?? 0;
const Eo = elementInputs.elasticitiesOrthogonal?.get(index) ?? 0;
const nu = elementInputs?.poissonsRatios?.get(index) ?? 0;
const Gxy = elementInputs.shearModuli?.get(index) ?? 0;
const thickness = elementInputs?.thicknesses?.get(index) ?? 0;
let Db: Matrix;
if (Eo) {
Db = buildOrthotropicDb(E, Eo, Gxy, nu, thickness);
} else {
Db = buildIsoDb(E, nu, thickness);
}
// 1) extract coords
const [x1, y1] = [nodes[0][0], nodes[0][1]];
const [x2, y2] = [nodes[1][0], nodes[1][1]];
const [x3, y3] = [nodes[2][0], nodes[2][1]];
// 3) area factor
const twoA = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2);
const A = 0.5 * Math.abs(twoA);
// 4) 3 integration points, each with weight=1/3 (over ref triangle area=1/2)
// => The factor will be 2A * w in the integral
const gaussPoints: Array<[number, number, number]> = [
[0.5, 0.0, 1 / 3],
[0.0, 0.5, 1 / 3],
[0.5, 0.5, 1 / 3],
];
// 5) assemble K
let K = zeros(9, 9) as Matrix;
for (const [k, e, w] of gaussPoints) {
// build B at (k,e)
const B = buildBMatrix(k, e, x1, y1, x2, y2, x3, y3);
const Bt = transpose(B) as Matrix;
const factor = 2 * A * w; // "2A" because the reference triangle has area=1/2
// B^T * Db * B
const BtDb = multiply(Bt, Db) as Matrix; // (9x3)
const BtDbB = multiply(BtDb, B) as Matrix; // (9x9)
const stiffPart = multiply(factor, BtDbB) as Matrix;
K = add(K, stiffPart) as Matrix;
}
// 6) return as a 2D array
return expandStiffnessMatrix(K.toArray() as number[][]);
// Utils
function buildEdgeCoeffs(
x1: number,
y1: number,
x2: number,
y2: number,
x3: number,
y3: number
) {
// side vectors
const x12 = x1 - x2,
y12 = y1 - y2;
const x23 = x2 - x3,
y23 = y2 - y3;
const x31 = x3 - x1,
y31 = y3 - y1;
// squared lengths
const l12 = x12 * x12 + y12 * y12;
const l23 = x23 * x23 + y23 * y23;
const l31 = x31 * x31 + y31 * y31;
// P4..P6, q4..q6, r4..r6, t4..t6
const P4 = (-6 * x23) / l23;
const P5 = (-6 * x31) / l31;
const P6 = (-6 * x12) / l12;
const q4 = (3 * x23 * y23) / l23;
const q5 = (3 * x31 * y31) / l31;
const q6 = (3 * x12 * y12) / l12;
const r4 = (3 * (y23 * y23)) / l23;
const r5 = (3 * (y31 * y31)) / l31;
const r6 = (3 * (y12 * y12)) / l12;
const t4 = (-6 * y23) / l23;
const t5 = (-6 * y31) / l31;
const t6 = (-6 * y12) / l12;
return {
x12,
y12,
x23,
y23,
x31,
y31,
l12,
l23,
l31,
P4,
P5,
P6,
q4,
q5,
q6,
r4,
r5,
r6,
t4,
t5,
t6,
};
}
function buildHxk(
k: number,
e: number,
ec: ReturnType<typeof buildEdgeCoeffs>
): number[] {
const { P5, P6, q5, q6, r5, r6 } = ec;
// directly transcribe from snippet
// Hxk(9 entries):
return [
P6 * (1 - 2 * k) + (P5 - P6) * e,
q6 * (1 - 2 * k) - (q5 + q6) * e,
-4 + 6 * (k + e) + r6 * (1 - 2 * k) - e * (r5 + r6),
-P6 * (1 - 2 * k) + e * (ec.P4 + P6),
q6 * (1 - 2 * k) - e * (q6 - ec.q4),
-2 + 6 * k + r6 * (1 - 2 * k) + e * (ec.r4 - r6),
-e * (P5 + ec.P4),
e * (ec.q4 - q5),
-e * (r5 - ec.r4),
];
}
function buildHyk(
k: number,
e: number,
ec: ReturnType<typeof buildEdgeCoeffs>
): number[] {
const { t5, t6, r5, r6, q5, q6 } = ec;
return [
t6 * (1 - 2 * k) + e * (t5 - t6),
1 + r6 * (1 - 2 * k) - e * (r5 + r6),
-q6 * (1 - 2 * k) + e * (q5 + q6),
-t6 * (1 - 2 * k) + e * (ec.t4 + t6),
-1 + r6 * (1 - 2 * k) + e * (ec.r4 - r6),
-q6 * (1 - 2 * k) - e * (ec.q4 - q6),
-e * (ec.t4 + t5),
e * (ec.r4 - r5),
-e * (ec.q4 - q5),
];
}
function buildHxe(
k: number,
e: number,
ec: ReturnType<typeof buildEdgeCoeffs>
): number[] {
const { P4, P5, P6, q4, q5, q6, r4, r5, r6 } = ec;
return [
-P5 * (1 - 2 * e) - k * (P6 - P5),
q5 * (1 - 2 * e) - k * (q5 + q6),
-4 + 6 * (k + e) + r5 * (1 - 2 * e) - k * (r5 + r6),
k * (P4 + P6),
k * (q4 - q6),
-k * (r6 - r4),
P5 * (1 - 2 * e) - k * (P4 + P5),
q5 * (1 - 2 * e) + k * (q4 - q5),
-2 + 6 * e + r5 * (1 - 2 * e) + k * (r4 - r5),
];
}
function buildHye(
k: number,
e: number,
ec: ReturnType<typeof buildEdgeCoeffs>
): number[] {
const { t4, t5, t6, r4, r5, r6, q4, q5, q6 } = ec;
return [
-t5 * (1 - 2 * e) - k * (t6 - t5),
1 + r5 * (1 - 2 * e) - k * (r5 + r6),
-q5 * (1 - 2 * e) + k * (q5 + q6),
k * (t4 + t6),
k * (r4 - r6),
-k * (q4 - q6),
t5 * (1 - 2 * e) - k * (t4 + t5),
-1 + r5 * (1 - 2 * e) + k * (r4 - r5),
-q5 * (1 - 2 * e) - k * (q4 - q5),
];
}
function buildBMatrix(
k: number,
e: number,
x1: number,
y1: number,
x2: number,
y2: number,
x3: number,
y3: number
): Matrix {
// 1) signed 2*Area
const twoA = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2);
// 2) gather edge coefficients (P4..P6, q4..q6, etc.)
const ec = buildEdgeCoeffs(x1, y1, x2, y2, x3, y3);
// 3) build partial arrays
const Hxk = buildHxk(k, e, ec);
const Hxe = buildHxe(k, e, ec);
const Hyk = buildHyk(k, e, ec);
const Hye = buildHye(k, e, ec);
// 4) geometry pairs
const { x31, y31, x12, y12 } = ec;
// 5) assemble B
let B = zeros(3, 9) as Matrix;
for (let i = 0; i < 9; i++) {
// row 0 => kappa_x
const val0 = (y31 * Hxk[i] + y12 * Hxe[i]) / twoA;
B.set([0, i], val0);
// row 1 => kappa_y
const val1 = (-x31 * Hyk[i] - x12 * Hye[i]) / twoA;
B.set([1, i], val1);
// row 2 => kappa_xy
const val2 =
(-x31 * Hxk[i] - x12 * Hxe[i] + y31 * Hyk[i] + y12 * Hye[i]) / twoA;
B.set([2, i], val2);
}
return B;
}
function buildIsoDb(E: number, nu: number, t: number): Matrix {
const factor = (E * t ** 3) / (12 * (1 - nu * nu));
const data = [
[1, nu, 0],
[nu, 1, 0],
[0, 0, (1 - nu) / 2],
].map((row) => row.map((val) => val * factor));
return matrix(data);
}
/**
* Expand the 9x9 DKT stiffness matrix to a full 18x18 matrix with all 6 DOFs per node
* @param {Array<Array<number>>} K9 - The 9x9 DKT stiffness matrix
* @returns {Array<Array<number>>} The expanded 18x18 stiffness matrix
*/
function expandStiffnessMatrix(K9) {
// Initialize 18x18 matrix with zeros
const K18 = Array(18)
.fill(0)
.map(() => Array(18).fill(0));
// Mapping from 9x9 to 18x18
// Original DOF order: [Node1-DZ, Node1-DRX, Node1-DRY, Node2-DZ, Node2-DRX, Node2-DRY, Node3-DZ, Node3-DRX, Node3-DRY]
// New DOF order: [Node1-DX, Node1-DY, Node1-DZ, Node1-DRX, Node1-DRY, Node1-DRZ,
// Node2-DX, Node2-DY, Node2-DZ, Node2-DRX, Node2-DRY, Node2-DRZ,
// Node3-DX, Node3-DY, Node3-DZ, Node3-DRX, Node3-DRY, Node3-DRZ]
// Create mapping from old indices to new indices
const mapping = [
2, // Node1-DZ -> index 2
3, // Node1-DRX -> index 3
4, // Node1-DRY -> index 4
8, // Node2-DZ -> index 8
9, // Node2-DRX -> index 9
10, // Node2-DRY -> index 10
14, // Node3-DZ -> index 14
15, // Node3-DRX -> index 15
16, // Node3-DRY -> index 16
];
// Copy values from K9 to K18 using the mapping
for (let i = 0; i < 9; i++) {
for (let j = 0; j < 9; j++) {
K18[mapping[i]][mapping[j]] = K9[i][j];
}
}
return K18;
}
}