/ proof-of-concept / steelsim21
Sign in
proof-of-concept / steelsim21
steelsim21 / src / sim / cpuRef.ts
205 lines · 8.5 KBBlameHistoryRaw
1// CPU reference implementation of the spatial discretization (float64).
2// It mirrors step.wgsl exactly and is used for the stable time-step estimate
3// and for the tests. It is not used in the time loop of the browser simulation.
5import { type Grid, type Material, trapWeight } from "../params.ts";
7export interface Fields {
8 u: Float64Array;
9 v: Float64Array;
10 w: Float64Array;
13const G3 = 1 / Math.sqrt(3);
15/**
16 * Accelerations (au, av, aw) of every node for displacement fields (u, v, w),
17 * without damping. Bending forces come from the discrete Kirchhoff energy
18 * (natural free-edge conditions); membrane forces come from bilinear (Q4)
19 * elements with 2x2 Gauss integration and the von Karman strains.
20 */
21export function accelerations(g: Grid, m: Material, f: Fields, out: Fields, w0?: Float64Array): void {
22 const { nx, ny, hx, hy } = g;
23 const { u, v, w } = f;
24 const hx2 = hx * hx;
25 const hy2 = hy * hy;
26 const nu = m.nu;
27 const id = (i: number, j: number) => j * nx + i;
29 // Bending moment-like quantities at nodes (zero where the second difference is undefined).
30 const Ma = (i: number, j: number): number => {
31 if (i < 1 || i > nx - 2 || j < 0 || j > ny - 1) return 0;
32 const a = (w[id(i + 1, j)] - 2 * w[id(i, j)] + w[id(i - 1, j)]) / hx2;
33 let b = 0;
34 if (j >= 1 && j <= ny - 2) b = (w[id(i, j + 1)] - 2 * w[id(i, j)] + w[id(i, j - 1)]) / hy2;
35 return trapWeight(g, i, j) * (a + nu * b);
36 };
37 const Mb = (i: number, j: number): number => {
38 if (j < 1 || j > ny - 2 || i < 0 || i > nx - 1) return 0;
39 const b = (w[id(i, j + 1)] - 2 * w[id(i, j)] + w[id(i, j - 1)]) / hy2;
40 let a = 0;
41 if (i >= 1 && i <= nx - 2) a = (w[id(i + 1, j)] - 2 * w[id(i, j)] + w[id(i - 1, j)]) / hx2;
42 return trapWeight(g, i, j) * (b + nu * a);
43 };
44 // Mixed second difference at a cell center (zero for cells outside the plate).
45 const Cc = (ci: number, cj: number): number => {
46 if (ci < 0 || ci > nx - 2 || cj < 0 || cj > ny - 2) return 0;
47 return (w[id(ci + 1, cj + 1)] - w[id(ci + 1, cj)] - w[id(ci, cj + 1)] + w[id(ci, cj)]) / (hx * hy);
48 };
50 const q = (hx * hy) / 4; // area per Gauss point
51 const U = new Float64Array(4);
52 const V = new Float64Array(4);
53 const Wn = new Float64Array(4);
54 const W0 = new Float64Array(4);
55 const dNdx = new Float64Array(4);
56 const dNdy = new Float64Array(4);
58 // Membrane force on local node `a` of cell (ci, cj); returns [Fu, Fv, Fw].
59 const cellForce = (ci: number, cj: number, a: number, F: Float64Array): void => {
60 if (ci < 0 || ci > nx - 2 || cj < 0 || cj > ny - 2) return;
61 const n0 = id(ci, cj), n1 = id(ci + 1, cj), n2 = id(ci + 1, cj + 1), n3 = id(ci, cj + 1);
62 U[0] = u[n0]; U[1] = u[n1]; U[2] = u[n2]; U[3] = u[n3];
63 V[0] = v[n0]; V[1] = v[n1]; V[2] = v[n2]; V[3] = v[n3];
64 Wn[0] = w[n0]; Wn[1] = w[n1]; Wn[2] = w[n2]; Wn[3] = w[n3];
65 if (w0) { W0[0] = w0[n0]; W0[1] = w0[n1]; W0[2] = w0[n2]; W0[3] = w0[n3]; }
66 for (let gp = 0; gp < 4; gp++) {
67 const xi = gp & 1 ? G3 : -G3;
68 const eta = gp & 2 ? G3 : -G3;
69 dNdx[0] = (-(1 - eta) / 4) * (2 / hx);
70 dNdx[1] = ((1 - eta) / 4) * (2 / hx);
71 dNdx[2] = ((1 + eta) / 4) * (2 / hx);
72 dNdx[3] = (-(1 + eta) / 4) * (2 / hx);
73 dNdy[0] = (-(1 - xi) / 4) * (2 / hy);
74 dNdy[1] = (-(1 + xi) / 4) * (2 / hy);
75 dNdy[2] = ((1 + xi) / 4) * (2 / hy);
76 dNdy[3] = ((1 - xi) / 4) * (2 / hy);
77 // Marguerre strains: total slope (w0 + w) minus the stress-free slope of w0.
78 let ux = 0, uy = 0, vx = 0, vy = 0, wx = 0, wy = 0, w0x = 0, w0y = 0;
79 for (let b = 0; b < 4; b++) {
80 ux += dNdx[b] * U[b]; uy += dNdy[b] * U[b];
81 vx += dNdx[b] * V[b]; vy += dNdy[b] * V[b];
82 wx += dNdx[b] * (Wn[b] + W0[b]); wy += dNdy[b] * (Wn[b] + W0[b]);
83 w0x += dNdx[b] * W0[b]; w0y += dNdy[b] * W0[b];
84 }
85 const exx = ux + 0.5 * (wx * wx - w0x * w0x);
86 const eyy = vy + 0.5 * (wy * wy - w0y * w0y);
87 const gxy = uy + vx + wx * wy - w0x * w0y;
88 const Nxx = m.C * (exx + nu * eyy);
89 const Nyy = m.C * (eyy + nu * exx);
90 const Nxy = m.G * gxy;
91 F[0] -= q * (Nxx * dNdx[a] + Nxy * dNdy[a]);
92 F[1] -= q * (Nyy * dNdy[a] + Nxy * dNdx[a]);
93 F[2] -= q * ((Nxx * wx + Nxy * wy) * dNdx[a] + (Nxy * wx + Nyy * wy) * dNdy[a]);
94 }
95 };
97 const F = new Float64Array(3);
98 for (let j = 0; j < ny; j++) {
99 for (let i = 0; i < nx; i++) {
100 const k = id(i, j);
101 const bend =
102 (Ma(i - 1, j) - 2 * Ma(i, j) + Ma(i + 1, j)) / hx2 +
103 (Mb(i, j - 1) - 2 * Mb(i, j) + Mb(i, j + 1)) / hy2 +
104 (2 * (1 - nu) * (Cc(i, j) - Cc(i - 1, j) - Cc(i, j - 1) + Cc(i - 1, j - 1))) / (hx * hy);
105 F[0] = 0; F[1] = 0; F[2] = -m.D * hx * hy * bend;
106 cellForce(i, j, 0, F);
107 cellForce(i - 1, j, 1, F);
108 cellForce(i - 1, j - 1, 2, F);
109 cellForce(i, j - 1, 3, F);
110 const mass = m.rhoh * hx * hy * trapWeight(g, i, j);
111 out.u[k] = F[0] / mass;
112 out.v[k] = F[1] / mass;
113 out.w[k] = F[2] / mass;
114 }
115 }
118/**
119 * Largest angular frequency of the linearized discrete system, by power
120 * iteration on a small grid with the same spacing (the extreme mode is a
121 * grid-scale mode, so the plate size does not matter).
122 */
123export function maxAngularFrequency(g: Grid, m: Material, nSmall = 12, iters = 300): number {
124 const gs: Grid = { nx: nSmall, ny: nSmall, hx: g.hx, hy: g.hy, n: nSmall * nSmall };
125 const n = gs.n;
126 const x: Fields = { u: new Float64Array(n), v: new Float64Array(n), w: new Float64Array(n) };
127 const y: Fields = { u: new Float64Array(n), v: new Float64Array(n), w: new Float64Array(n) };
128 let seed = 12345;
129 const rnd = () => {
130 seed = (seed * 1664525 + 1013904223) >>> 0;
131 return seed / 4294967296 - 0.5;
132 };
133 for (let k = 0; k < n; k++) {
134 x.u[k] = rnd(); x.v[k] = rnd(); x.w[k] = rnd();
135 }
136 const eps = 1e-9; // keeps the nonlinear terms negligible
137 let lambda = 0;
138 for (let it = 0; it < iters; it++) {
139 let norm = 0;
140 for (let k = 0; k < n; k++) norm += x.u[k] ** 2 + x.v[k] ** 2 + x.w[k] ** 2;
141 norm = Math.sqrt(norm);
142 for (let k = 0; k < n; k++) {
143 x.u[k] *= eps / norm; x.v[k] *= eps / norm; x.w[k] *= eps / norm;
144 }
145 accelerations(gs, m, x, y);
146 let dot = 0;
147 for (let k = 0; k < n; k++) dot += x.u[k] * y.u[k] + x.v[k] * y.v[k] + x.w[k] * y.w[k];
148 lambda = -dot / (eps * eps); // Rayleigh quotient of -M^{-1}K
149 for (let k = 0; k < n; k++) {
150 x.u[k] = -y.u[k]; x.v[k] = -y.v[k]; x.w[k] = -y.w[k];
151 }
152 }
153 return Math.sqrt(Math.max(lambda, 0));
156/** Discrete potential energy (bending + membrane) matching `accelerations`. */
157export function potentialEnergy(g: Grid, m: Material, f: Fields, w0?: Float64Array): number {
158 const { nx, ny, hx, hy } = g;
159 const { u, v, w } = f;
160 const nu = m.nu;
161 const id = (i: number, j: number) => j * nx + i;
162 let Ub = 0;
163 for (let j = 0; j < ny; j++) {
164 for (let i = 0; i < nx; i++) {
165 let a = 0, b = 0;
166 if (i >= 1 && i <= nx - 2) a = (w[id(i + 1, j)] - 2 * w[id(i, j)] + w[id(i - 1, j)]) / (hx * hx);
167 if (j >= 1 && j <= ny - 2) b = (w[id(i, j + 1)] - 2 * w[id(i, j)] + w[id(i, j - 1)]) / (hy * hy);
168 Ub += trapWeight(g, i, j) * (a * a + b * b + 2 * nu * a * b);
169 }
170 }
171 for (let cj = 0; cj < ny - 1; cj++) {
172 for (let ci = 0; ci < nx - 1; ci++) {
173 const c = (w[id(ci + 1, cj + 1)] - w[id(ci + 1, cj)] - w[id(ci, cj + 1)] + w[id(ci, cj)]) / (hx * hy);
174 Ub += 2 * (1 - nu) * c * c;
175 }
176 }
177 Ub *= 0.5 * m.D * hx * hy;
179 let Um = 0;
180 const q = (hx * hy) / 4;
181 for (let cj = 0; cj < ny - 1; cj++) {
182 for (let ci = 0; ci < nx - 1; ci++) {
183 const nn = [id(ci, cj), id(ci + 1, cj), id(ci + 1, cj + 1), id(ci, cj + 1)];
184 for (let gp = 0; gp < 4; gp++) {
185 const xi = gp & 1 ? G3 : -G3;
186 const eta = gp & 2 ? G3 : -G3;
187 const dNdx = [-(1 - eta), 1 - eta, 1 + eta, -(1 + eta)].map((s) => (s / 4) * (2 / hx));
188 const dNdy = [-(1 - xi), -(1 + xi), 1 + xi, 1 - xi].map((s) => (s / 4) * (2 / hy));
189 let ux = 0, uy = 0, vx = 0, vy = 0, wx = 0, wy = 0, w0x = 0, w0y = 0;
190 for (let b = 0; b < 4; b++) {
191 const z0 = w0 ? w0[nn[b]] : 0;
192 ux += dNdx[b] * u[nn[b]]; uy += dNdy[b] * u[nn[b]];
193 vx += dNdx[b] * v[nn[b]]; vy += dNdy[b] * v[nn[b]];
194 wx += dNdx[b] * (w[nn[b]] + z0); wy += dNdy[b] * (w[nn[b]] + z0);
195 w0x += dNdx[b] * z0; w0y += dNdy[b] * z0;
196 }
197 const exx = ux + 0.5 * (wx * wx - w0x * w0x);
198 const eyy = vy + 0.5 * (wy * wy - w0y * w0y);
199 const gxy = uy + vx + wx * wy - w0x * w0y;
200 Um += q * 0.5 * (m.C * (exx * exx + eyy * eyy + 2 * nu * exx * eyy) + m.G * gxy * gxy);
201 }
202 }
203 }
204 return Ub + Um;
↑↓ move↵openescclose