@jtgtools/xbeam 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,649 @@
1
+ // src/constants.ts
2
+ var HALF_BANDWIDTH = 3;
3
+ var TOLERANCE = 1e-9;
4
+ var DEFAULT_MAX_ELEMENTS = 200;
5
+ var DEFAULT_MIN_ELEMENTS_PER_SEGMENT = 2;
6
+ var DEFAULT_DIAGRAM_POINTS_PER_ELEMENT = 12;
7
+
8
+ // src/mesh.ts
9
+ function generateMesh(beam, options = {}) {
10
+ const eps = options.eps ?? TOLERANCE;
11
+ const maxElements = options.maxElements ?? DEFAULT_MAX_ELEMENTS;
12
+ const minElementsPerSegment = options.minElementsPerSegment ?? DEFAULT_MIN_ELEMENTS_PER_SEGMENT;
13
+ const critical = collectCriticalPoints(beam);
14
+ critical.sort((a, b) => a - b);
15
+ const unique = [];
16
+ for (const x of critical) {
17
+ if (unique.length === 0) {
18
+ unique.push(x);
19
+ continue;
20
+ }
21
+ const last = unique[unique.length - 1];
22
+ if (Math.abs(x - last) >= eps) unique.push(x);
23
+ }
24
+ const nodes = [];
25
+ const L = beam.length;
26
+ if (unique.length < 2) {
27
+ nodes.push(0, L);
28
+ return {
29
+ nodes: new Float64Array(nodes),
30
+ findNodeIndex: (xQuery, epsQuery) => findClosestNodeIndex(nodes, xQuery, epsQuery)
31
+ };
32
+ }
33
+ const targetDensity = maxElements / L;
34
+ for (let i = 0; i < unique.length - 1; i++) {
35
+ const xStart = unique[i];
36
+ const xEnd = unique[i + 1];
37
+ const segLen = xEnd - xStart;
38
+ if (segLen <= eps) continue;
39
+ const nElem = Math.max(Math.floor(segLen * targetDensity), minElementsPerSegment);
40
+ for (let j = 0; j < nElem; j++) {
41
+ const s = j / nElem;
42
+ nodes.push(xStart + s * segLen);
43
+ }
44
+ }
45
+ nodes.push(L);
46
+ const nodeArray = new Float64Array(nodes);
47
+ return {
48
+ nodes: nodeArray,
49
+ findNodeIndex: (xQuery, epsQuery) => findClosestNodeIndex(nodeArray, xQuery, epsQuery)
50
+ };
51
+ }
52
+ function collectCriticalPoints(beam) {
53
+ const points = [0, beam.length];
54
+ for (const s of beam.supports) points.push(s.position);
55
+ for (const pl of beam.pointLoads) points.push(pl.position);
56
+ for (const dl of beam.distributedLoads) {
57
+ points.push(dl.startPosition, dl.endPosition);
58
+ }
59
+ for (const am of beam.appliedMoments ?? []) points.push(am.position);
60
+ return points;
61
+ }
62
+ function findClosestNodeIndex(nodes, x, eps) {
63
+ let lo = 0;
64
+ let hi = nodes.length - 1;
65
+ while (lo <= hi) {
66
+ const mid = lo + hi >>> 1;
67
+ const v = nodes[mid];
68
+ if (x < v - eps) hi = mid - 1;
69
+ else if (x > v + eps) lo = mid + 1;
70
+ else return mid;
71
+ }
72
+ const candidates = [];
73
+ if (lo >= 0 && lo < nodes.length) candidates.push(lo);
74
+ if (lo - 1 >= 0 && lo - 1 < nodes.length) candidates.push(lo - 1);
75
+ let bestIdx = -1;
76
+ let bestDist = Number.POSITIVE_INFINITY;
77
+ for (const idx of candidates) {
78
+ const d = Math.abs(nodes[idx] - x);
79
+ if (d <= eps && d < bestDist) {
80
+ bestDist = d;
81
+ bestIdx = idx;
82
+ }
83
+ }
84
+ return bestIdx;
85
+ }
86
+
87
+ // src/hermite.ts
88
+ function hermiteShapeInto(s, l, out) {
89
+ const s2 = s * s;
90
+ const s3 = s2 * s;
91
+ out[0] = 1 - 3 * s2 + 2 * s3;
92
+ out[1] = l * (s - 2 * s2 + s3);
93
+ out[2] = 3 * s2 - 2 * s3;
94
+ out[3] = l * (-s2 + s3);
95
+ }
96
+ function hermiteD2Into(s, l, out) {
97
+ const l2 = l * l;
98
+ const l2Inv = 1 / l2;
99
+ const sixS = 6 * s;
100
+ out[0] = (-6 + 12 * s) * l2Inv;
101
+ out[1] = l * (-4 + sixS) * l2Inv;
102
+ out[2] = (6 - 12 * s) * l2Inv;
103
+ out[3] = l * (-2 + sixS) * l2Inv;
104
+ }
105
+
106
+ // src/element.ts
107
+ var GAUSS_POINTS = [-Math.sqrt(3 / 5), 0, Math.sqrt(3 / 5)];
108
+ var GAUSS_WEIGHTS = [5 / 9, 8 / 9, 5 / 9];
109
+ function stiffnessCoefficients(le, E, I) {
110
+ const EI = E * I;
111
+ const le2 = le * le;
112
+ const le3 = le2 * le;
113
+ return {
114
+ k11: 12 * EI / le3,
115
+ k12: 6 * EI / le2,
116
+ k22: 4 * EI / le,
117
+ k24: 2 * EI / le
118
+ };
119
+ }
120
+ function consistentDistributedLoadGaussInto(qa, qb, sa, sb, le, out, shapeScratch) {
121
+ const mid = (sa + sb) / 2;
122
+ const hw = (sb - sa) / 2;
123
+ const dq = sb > sa ? (qb - qa) / (sb - sa) : 0;
124
+ for (let g = 0; g < 3; g++) {
125
+ const s = mid + hw * GAUSS_POINTS[g];
126
+ const q = qa + dq * (s - sa);
127
+ hermiteShapeInto(s, le, shapeScratch);
128
+ const w = GAUSS_WEIGHTS[g] * hw * le * q;
129
+ out[0] += w * shapeScratch[0];
130
+ out[1] += w * shapeScratch[1];
131
+ out[2] += w * shapeScratch[2];
132
+ out[3] += w * shapeScratch[3];
133
+ }
134
+ }
135
+
136
+ // src/utils.ts
137
+ function distributedIntensityAt(x, dl) {
138
+ const a = dl.startPosition;
139
+ const b = dl.endPosition;
140
+ const len = b - a;
141
+ if (Math.abs(len) < TOLERANCE) return dl.startMagnitude;
142
+ if (x < Math.min(a, b) - TOLERANCE) return 0;
143
+ if (x > Math.max(a, b) + TOLERANCE) return 0;
144
+ const t = Math.max(0, Math.min(1, (x - a) / len));
145
+ return dl.startMagnitude + (dl.endMagnitude - dl.startMagnitude) * t;
146
+ }
147
+
148
+ // src/assembler.ts
149
+ function assembleSystem(beam, mesh) {
150
+ const nodes = mesh.nodes;
151
+ const nNodes = nodes.length;
152
+ const nDof = 2 * nNodes;
153
+ const bp1 = HALF_BANDWIDTH + 1;
154
+ const bandedK = new Float64Array(nDof * bp1);
155
+ const forceVector = new Float64Array(nDof);
156
+ assembleStiffness(beam, nodes, bandedK, bp1);
157
+ assembleForces(beam, mesh, nodes, forceVector);
158
+ return { bandedK, forceVector, nodePositions: nodes };
159
+ }
160
+ function getBoundaryConditions(beam, mesh) {
161
+ const nodes = mesh.nodes;
162
+ const nNodes = nodes.length;
163
+ const nDof = 2 * nNodes;
164
+ const restrained = new Uint8Array(nDof);
165
+ for (const s of beam.supports) {
166
+ const idx = mesh.findNodeIndex(s.position, TOLERANCE);
167
+ if (idx < 0) continue;
168
+ const wDof = 2 * idx;
169
+ switch (s.kind) {
170
+ case "PINNED":
171
+ case "ROLLER":
172
+ restrained[wDof] = 1;
173
+ break;
174
+ case "FIXED":
175
+ restrained[wDof] = 1;
176
+ restrained[wDof + 1] = 1;
177
+ break;
178
+ }
179
+ }
180
+ let nFree = 0;
181
+ let nRest = 0;
182
+ for (let dof = 0; dof < nDof; dof++) {
183
+ if (restrained[dof]) nRest++;
184
+ else nFree++;
185
+ }
186
+ const freeDofs = new Int32Array(nFree);
187
+ const restrainedDofs = new Int32Array(nRest);
188
+ let fi = 0;
189
+ let ri = 0;
190
+ for (let dof = 0; dof < nDof; dof++) {
191
+ if (restrained[dof]) restrainedDofs[ri++] = dof;
192
+ else freeDofs[fi++] = dof;
193
+ }
194
+ return { freeDofs, restrainedDofs };
195
+ }
196
+ function assembleStiffness(beam, nodes, bandedK, bp1) {
197
+ const E = beam.modulusOfElasticity;
198
+ const I = beam.momentOfInertia;
199
+ const nNodes = nodes.length;
200
+ for (let e = 0; e < nNodes - 1; e++) {
201
+ const le = nodes[e + 1] - nodes[e];
202
+ if (le <= TOLERANCE) continue;
203
+ const { k11, k12, k22, k24 } = stiffnessCoefficients(le, E, I);
204
+ const a = 2 * e;
205
+ const b = 2 * e + 1;
206
+ const c = 2 * (e + 1);
207
+ const d = 2 * (e + 1) + 1;
208
+ bandedK[a * bp1 + 0] += k11;
209
+ bandedK[a * bp1 + 1] += k12;
210
+ bandedK[a * bp1 + 2] += -k11;
211
+ bandedK[a * bp1 + 3] += k12;
212
+ bandedK[b * bp1 + 0] += k22;
213
+ bandedK[b * bp1 + 1] += -k12;
214
+ bandedK[b * bp1 + 2] += k24;
215
+ bandedK[c * bp1 + 0] += k11;
216
+ bandedK[c * bp1 + 1] += -k12;
217
+ bandedK[d * bp1 + 0] += k22;
218
+ }
219
+ }
220
+ function assembleForces(beam, mesh, nodes, forceVector) {
221
+ const nNodes = nodes.length;
222
+ const nDof = 2 * nNodes;
223
+ if (forceVector.length !== nDof) throw new Error("Unexpected forceVector size.");
224
+ const loadScratch = new Float64Array(4);
225
+ const shapeScratch = new Float64Array(4);
226
+ for (const pl of beam.pointLoads) {
227
+ const idx = mesh.findNodeIndex(pl.position, TOLERANCE);
228
+ if (idx < 0) continue;
229
+ forceVector[2 * idx] += pl.magnitude;
230
+ }
231
+ for (const dl of beam.distributedLoads) {
232
+ const xMin = Math.min(dl.startPosition, dl.endPosition);
233
+ const xMax = Math.max(dl.startPosition, dl.endPosition);
234
+ if (xMax - xMin <= TOLERANCE) continue;
235
+ for (let e = 0; e < nNodes - 1; e++) {
236
+ const xa = nodes[e];
237
+ const xb = nodes[e + 1];
238
+ const le = xb - xa;
239
+ if (le <= TOLERANCE) continue;
240
+ const overlapStart = Math.max(xa, xMin);
241
+ const overlapEnd = Math.min(xb, xMax);
242
+ if (overlapEnd <= overlapStart + TOLERANCE) continue;
243
+ const sa = (overlapStart - xa) / le;
244
+ const sb = (overlapEnd - xa) / le;
245
+ const qa = distributedIntensityAt(overlapStart, dl);
246
+ const qb = distributedIntensityAt(overlapEnd, dl);
247
+ loadScratch.fill(0);
248
+ consistentDistributedLoadGaussInto(qa, qb, sa, sb, le, loadScratch, shapeScratch);
249
+ const wA = 2 * e;
250
+ const thA = 2 * e + 1;
251
+ const wB = 2 * (e + 1);
252
+ const thB = 2 * (e + 1) + 1;
253
+ forceVector[wA] += loadScratch[0];
254
+ forceVector[thA] += loadScratch[1];
255
+ forceVector[wB] += loadScratch[2];
256
+ forceVector[thB] += loadScratch[3];
257
+ }
258
+ }
259
+ for (const am of beam.appliedMoments ?? []) {
260
+ const idx = mesh.findNodeIndex(am.position, TOLERANCE);
261
+ if (idx < 0) continue;
262
+ forceVector[2 * idx + 1] -= am.magnitude;
263
+ }
264
+ }
265
+
266
+ // src/solver.ts
267
+ var BP1 = HALF_BANDWIDTH + 1;
268
+ function solveLinearSystem(system, bc) {
269
+ const nDof = system.forceVector.length;
270
+ const u = new Float64Array(nDof);
271
+ const { freeDofs, restrainedDofs } = bc;
272
+ const n = freeDofs.length;
273
+ if (n === 0) throw new Error("All DOFs restrained; nothing to solve.");
274
+ const globalOf = freeDofs;
275
+ const compactOf = new Int32Array(nDof);
276
+ compactOf.fill(-1);
277
+ for (let ii = 0; ii < n; ii++) compactOf[globalOf[ii]] = ii;
278
+ const Kff = new Float64Array(n * n);
279
+ for (let ii = 0; ii < n; ii++) {
280
+ const gi = globalOf[ii];
281
+ for (let jj = ii; jj < n; jj++) {
282
+ const gj = globalOf[jj];
283
+ const d = gj - gi;
284
+ if (d <= HALF_BANDWIDTH) {
285
+ Kff[ii * n + jj] = system.bandedK[gi * BP1 + d];
286
+ Kff[jj * n + ii] = Kff[ii * n + jj];
287
+ }
288
+ }
289
+ }
290
+ const ff = new Float64Array(n);
291
+ for (let ii = 0; ii < n; ii++) ff[ii] = system.forceVector[globalOf[ii]];
292
+ const L = Kff;
293
+ for (let i = 0; i < n; i++) {
294
+ for (let k = 0; k < i; k++) {
295
+ let sum2 = L[i * n + k];
296
+ for (let j = 0; j < k; j++) sum2 -= L[i * n + j] * L[k * n + j];
297
+ L[i * n + k] = sum2 / L[k * n + k];
298
+ }
299
+ let sum = L[i * n + i];
300
+ for (let j = 0; j < i; j++) sum -= L[i * n + j] * L[i * n + j];
301
+ if (sum <= 0) throw new Error(`Matrix not SPD near compact DOF ${i}`);
302
+ L[i * n + i] = Math.sqrt(sum);
303
+ }
304
+ const y = new Float64Array(n);
305
+ y[0] = ff[0] / L[0];
306
+ for (let i = 1; i < n; i++) {
307
+ let sum = ff[i];
308
+ for (let j = 0; j < i; j++) sum -= L[i * n + j] * y[j];
309
+ y[i] = sum / L[i * n + i];
310
+ }
311
+ const uf = new Float64Array(n);
312
+ uf[n - 1] = y[n - 1] / L[(n - 1) * n + (n - 1)];
313
+ for (let i = n - 2; i >= 0; i--) {
314
+ let sum = y[i];
315
+ for (let j = i + 1; j < n; j++) sum -= L[j * n + i] * uf[j];
316
+ uf[i] = sum / L[i * n + i];
317
+ }
318
+ for (let ii = 0; ii < n; ii++) u[globalOf[ii]] = uf[ii];
319
+ const nNodes = system.nodePositions.length;
320
+ const reactionVertical = new Float64Array(nNodes);
321
+ const reactionMoment = new Float64Array(nNodes);
322
+ for (let ri = 0; ri < restrainedDofs.length; ri++) {
323
+ const dof = restrainedDofs[ri];
324
+ let Ku = 0;
325
+ for (let d = 0; d <= HALF_BANDWIDTH; d++) {
326
+ const j = dof + d;
327
+ if (j >= nDof) break;
328
+ Ku += system.bandedK[dof * BP1 + d] * u[j];
329
+ }
330
+ for (let d = 1; d <= HALF_BANDWIDTH; d++) {
331
+ const j = dof - d;
332
+ if (j < 0) break;
333
+ Ku += system.bandedK[j * BP1 + d] * u[j];
334
+ }
335
+ const reaction = Ku - system.forceVector[dof];
336
+ const nodeIdx = dof >> 1;
337
+ if ((dof & 1) === 0) reactionVertical[nodeIdx] = reaction;
338
+ else reactionMoment[nodeIdx] = reaction;
339
+ }
340
+ return { displacements: u, reactions: { vertical: reactionVertical, moment: reactionMoment } };
341
+ }
342
+ function createSolverBuffers(nDof) {
343
+ return {
344
+ nDof,
345
+ compactOf: new Int32Array(nDof),
346
+ Kff: new Float64Array(nDof * BP1),
347
+ Lband: new Float64Array(nDof * BP1),
348
+ ff: new Float64Array(nDof),
349
+ y: new Float64Array(nDof),
350
+ uf: new Float64Array(nDof),
351
+ u: new Float64Array(nDof)
352
+ };
353
+ }
354
+ function solveLinearSystemBuffered(system, bc, buffers) {
355
+ const nDof = system.forceVector.length;
356
+ if (buffers.nDof !== nDof) throw new Error("Buffer size mismatch");
357
+ const { freeDofs, restrainedDofs } = bc;
358
+ const n = freeDofs.length;
359
+ if (n === 0) throw new Error("All DOFs restrained; nothing to solve.");
360
+ const globalOf = freeDofs;
361
+ const compactOf = buffers.compactOf;
362
+ compactOf.fill(-1);
363
+ for (let ii = 0; ii < n; ii++) compactOf[globalOf[ii]] = ii;
364
+ const Kff = new Float64Array(n * n);
365
+ for (let ii = 0; ii < n; ii++) {
366
+ const gi = globalOf[ii];
367
+ for (let jj = ii; jj < n; jj++) {
368
+ const gj = globalOf[jj];
369
+ const d = gj - gi;
370
+ if (d <= HALF_BANDWIDTH) {
371
+ Kff[ii * n + jj] = system.bandedK[gi * BP1 + d];
372
+ Kff[jj * n + ii] = Kff[ii * n + jj];
373
+ }
374
+ }
375
+ }
376
+ const ff = buffers.ff;
377
+ for (let ii = 0; ii < n; ii++) ff[ii] = system.forceVector[globalOf[ii]];
378
+ const L = Kff;
379
+ for (let i = 0; i < n; i++) {
380
+ for (let k = 0; k < i; k++) {
381
+ let sum2 = L[i * n + k];
382
+ for (let j = 0; j < k; j++) sum2 -= L[i * n + j] * L[k * n + j];
383
+ L[i * n + k] = sum2 / L[k * n + k];
384
+ }
385
+ let sum = L[i * n + i];
386
+ for (let j = 0; j < i; j++) sum -= L[i * n + j] * L[i * n + j];
387
+ if (sum <= 0) throw new Error(`Matrix not SPD near compact DOF ${i}`);
388
+ L[i * n + i] = Math.sqrt(sum);
389
+ }
390
+ const y = buffers.y;
391
+ y[0] = ff[0] / L[0];
392
+ for (let i = 1; i < n; i++) {
393
+ let sum = ff[i];
394
+ for (let j = 0; j < i; j++) sum -= L[i * n + j] * y[j];
395
+ y[i] = sum / L[i * n + i];
396
+ }
397
+ const uf = buffers.uf;
398
+ uf[n - 1] = y[n - 1] / L[(n - 1) * n + (n - 1)];
399
+ for (let i = n - 2; i >= 0; i--) {
400
+ let sum = y[i];
401
+ for (let j = i + 1; j < n; j++) sum -= L[j * n + i] * uf[j];
402
+ uf[i] = sum / L[i * n + i];
403
+ }
404
+ const u = buffers.u;
405
+ u.fill(0);
406
+ for (let ii = 0; ii < n; ii++) u[globalOf[ii]] = uf[ii];
407
+ const nNodes = system.nodePositions.length;
408
+ const reactionVertical = new Float64Array(nNodes);
409
+ const reactionMoment = new Float64Array(nNodes);
410
+ for (let ri = 0; ri < restrainedDofs.length; ri++) {
411
+ const dof = restrainedDofs[ri];
412
+ let Ku = 0;
413
+ for (let d = 0; d <= HALF_BANDWIDTH; d++) {
414
+ const j = dof + d;
415
+ if (j >= nDof) break;
416
+ Ku += system.bandedK[dof * BP1 + d] * u[j];
417
+ }
418
+ for (let d = 1; d <= HALF_BANDWIDTH; d++) {
419
+ const j = dof - d;
420
+ if (j < 0) break;
421
+ Ku += system.bandedK[j * BP1 + d] * u[j];
422
+ }
423
+ const reaction = Ku - system.forceVector[dof];
424
+ const nodeIdx = dof >> 1;
425
+ if ((dof & 1) === 0) reactionVertical[nodeIdx] = reaction;
426
+ else reactionMoment[nodeIdx] = reaction;
427
+ }
428
+ return { displacements: u.slice(), reactions: { vertical: reactionVertical, moment: reactionMoment } };
429
+ }
430
+
431
+ // src/postprocess.ts
432
+ function computeNodalResponses(beam, mesh, displacements, reactions) {
433
+ const nodes = mesh.nodes;
434
+ const nNodes = nodes.length;
435
+ const nElems = nNodes - 1;
436
+ const w = new Float64Array(nNodes);
437
+ const theta = new Float64Array(nNodes);
438
+ for (let i = 0; i < nNodes; i++) {
439
+ w[i] = displacements[2 * i];
440
+ theta[i] = displacements[2 * i + 1];
441
+ }
442
+ const EI = beam.modulusOfElasticity * beam.momentOfInertia;
443
+ const momentSum = new Float64Array(nNodes);
444
+ const momentCount = new Uint16Array(nNodes);
445
+ for (let e = 0; e < nElems; e++) {
446
+ const xa = nodes[e];
447
+ const xb = nodes[e + 1];
448
+ const le = xb - xa;
449
+ if (le <= TOLERANCE) continue;
450
+ const wA = w[e];
451
+ const thA = theta[e];
452
+ const wB = w[e + 1];
453
+ const thB = theta[e + 1];
454
+ const invL = 1 / le;
455
+ const invL2 = invL * invL;
456
+ const kappaLeft = -6 * invL2 * wA + -4 * invL * thA + 6 * invL2 * wB + -2 * invL * thB;
457
+ const mLeft = EI * kappaLeft;
458
+ const kappaRight = 6 * invL2 * wA + 2 * invL * thA + -6 * invL2 * wB + 4 * invL * thB;
459
+ const mRight = EI * kappaRight;
460
+ momentSum[e] += mLeft;
461
+ momentCount[e] += 1;
462
+ momentSum[e + 1] += mRight;
463
+ momentCount[e + 1] += 1;
464
+ }
465
+ const moments = new Float64Array(nNodes);
466
+ for (let i = 0; i < nNodes; i++) {
467
+ moments[i] = momentCount[i] > 0 ? momentSum[i] / momentCount[i] : 0;
468
+ }
469
+ const concAtNode = new Float64Array(nNodes);
470
+ for (let i = 0; i < nNodes; i++) concAtNode[i] = reactions.vertical[i];
471
+ for (const pl of beam.pointLoads) {
472
+ const idx = mesh.findNodeIndex(pl.position, TOLERANCE);
473
+ if (idx >= 0) concAtNode[idx] += pl.magnitude;
474
+ }
475
+ const qAtNodes = new Float64Array(nNodes);
476
+ for (let i = 0; i < nNodes; i++) {
477
+ const x = nodes[i];
478
+ let sum = 0;
479
+ for (const dl of beam.distributedLoads) sum += distributedIntensityAt(x, dl);
480
+ qAtNodes[i] = sum;
481
+ }
482
+ const distPrefix = new Float64Array(nNodes);
483
+ distPrefix[0] = 0;
484
+ for (let e = 0; e < nElems; e++) {
485
+ const le = nodes[e + 1] - nodes[e];
486
+ distPrefix[e + 1] = distPrefix[e] + (qAtNodes[e] + qAtNodes[e + 1]) * 0.5 * le;
487
+ }
488
+ const prefixConc = new Float64Array(nNodes + 1);
489
+ prefixConc[0] = 0;
490
+ for (let i = 0; i < nNodes; i++) prefixConc[i + 1] = prefixConc[i] + concAtNode[i];
491
+ const shears = new Float64Array(nNodes);
492
+ for (let i = 0; i < nNodes; i++) shears[i] = prefixConc[i + 1] + distPrefix[i];
493
+ return {
494
+ nodePositions: nodes,
495
+ deflections: w,
496
+ rotations: theta,
497
+ moments,
498
+ shears,
499
+ reactions: { vertical: reactions.vertical, moment: reactions.moment }
500
+ };
501
+ }
502
+
503
+ // src/diagrams.ts
504
+ function makeSeries(len) {
505
+ return { x: new Float64Array(len), y: new Float64Array(len) };
506
+ }
507
+ function pointLoadsAtNodes(beam, mesh, eps) {
508
+ const nNodes = mesh.nodes.length;
509
+ const out = new Float64Array(nNodes);
510
+ for (const pl of beam.pointLoads) {
511
+ const idx = mesh.findNodeIndex(pl.position, eps);
512
+ if (idx >= 0) out[idx] += pl.magnitude;
513
+ }
514
+ return out;
515
+ }
516
+ function sampleDiagrams(beam, mesh, results, options = {}) {
517
+ const nodes = results.nodePositions;
518
+ const nNodes = nodes.length;
519
+ const nElems = nNodes - 1;
520
+ const pointsPerElement = options.pointsPerElement ?? DEFAULT_DIAGRAM_POINTS_PER_ELEMENT;
521
+ const eps = options.eps ?? TOLERANCE;
522
+ const includeOutsideEnds = options.includeOutsideEnds ?? true;
523
+ if (nElems <= 0) {
524
+ const len = includeOutsideEnds ? 2 : 1;
525
+ const deflection2 = makeSeries(len);
526
+ const moment2 = makeSeries(len);
527
+ const shear2 = makeSeries(len);
528
+ deflection2.x[0] = nodes[0];
529
+ deflection2.y[0] = results.deflections[0];
530
+ if (includeOutsideEnds) {
531
+ deflection2.x[1] = nodes[0];
532
+ deflection2.y[1] = results.deflections[0];
533
+ moment2.x[1] = nodes[0];
534
+ shear2.x[1] = nodes[0];
535
+ }
536
+ moment2.x[0] = nodes[0];
537
+ shear2.x[0] = nodes[0];
538
+ return { deflection: deflection2, moment: moment2, shear: shear2 };
539
+ }
540
+ const EI = beam.modulusOfElasticity * beam.momentOfInertia;
541
+ const pointAtNode = pointLoadsAtNodes(beam, mesh, eps);
542
+ const concAtNode = new Float64Array(nNodes);
543
+ for (let i = 0; i < nNodes; i++) {
544
+ concAtNode[i] = results.reactions.vertical[i] + pointAtNode[i];
545
+ }
546
+ const prefixConc = new Float64Array(nNodes + 1);
547
+ prefixConc[0] = 0;
548
+ for (let i = 0; i < nNodes; i++) prefixConc[i + 1] = prefixConc[i] + concAtNode[i];
549
+ const qAtNodes = new Float64Array(nNodes);
550
+ for (let i = 0; i < nNodes; i++) {
551
+ const x = nodes[i];
552
+ let sum = 0;
553
+ for (const dl of beam.distributedLoads) sum += distributedIntensityAt(x, dl);
554
+ qAtNodes[i] = sum;
555
+ }
556
+ const distPrefix = new Float64Array(nNodes);
557
+ distPrefix[0] = 0;
558
+ for (let e = 0; e < nElems; e++) {
559
+ const le = nodes[e + 1] - nodes[e];
560
+ distPrefix[e + 1] = distPrefix[e] + (qAtNodes[e] + qAtNodes[e + 1]) * 0.5 * le;
561
+ }
562
+ const internalLen = nElems * (pointsPerElement + 1);
563
+ const totalLen = internalLen + (includeOutsideEnds ? 2 : 0);
564
+ const deflection = makeSeries(totalLen);
565
+ const moment = makeSeries(totalLen);
566
+ const shear = makeSeries(totalLen);
567
+ let idx = 0;
568
+ if (includeOutsideEnds) {
569
+ deflection.x[idx] = nodes[0];
570
+ deflection.y[idx] = results.deflections[0];
571
+ moment.x[idx] = nodes[0];
572
+ moment.y[idx] = 0;
573
+ shear.x[idx] = nodes[0];
574
+ shear.y[idx] = 0;
575
+ idx++;
576
+ }
577
+ const N = new Float64Array(4);
578
+ const d2N = new Float64Array(4);
579
+ for (let e = 0; e < nElems; e++) {
580
+ const xa = nodes[e];
581
+ const xb = nodes[e + 1];
582
+ const le = xb - xa;
583
+ if (le <= eps) continue;
584
+ const wA = results.deflections[e];
585
+ const thA = results.rotations[e];
586
+ const wB = results.deflections[e + 1];
587
+ const thB = results.rotations[e + 1];
588
+ const qA = qAtNodes[e];
589
+ const qB = qAtNodes[e + 1];
590
+ const VStart = prefixConc[e + 1] + distPrefix[e];
591
+ for (let j = 0; j <= pointsPerElement; j++) {
592
+ const s = j / pointsPerElement;
593
+ const x = xa + s * le;
594
+ hermiteShapeInto(s, le, N);
595
+ const w = N[0] * wA + N[1] * thA + N[2] * wB + N[3] * thB;
596
+ hermiteD2Into(s, le, d2N);
597
+ const kappa = d2N[0] * wA + d2N[1] * thA + d2N[2] * wB + d2N[3] * thB;
598
+ const M = EI * kappa;
599
+ const s2 = s * s;
600
+ const Vdist = le * (qA * s + (qB - qA) * 0.5 * s2);
601
+ const V = VStart + Vdist;
602
+ deflection.x[idx] = x;
603
+ deflection.y[idx] = w;
604
+ moment.x[idx] = x;
605
+ moment.y[idx] = M;
606
+ shear.x[idx] = x;
607
+ shear.y[idx] = V;
608
+ idx++;
609
+ }
610
+ }
611
+ if (includeOutsideEnds) {
612
+ const last = nNodes - 1;
613
+ deflection.x[idx] = nodes[last];
614
+ deflection.y[idx] = results.deflections[last];
615
+ moment.x[idx] = nodes[last];
616
+ moment.y[idx] = 0;
617
+ shear.x[idx] = nodes[last];
618
+ shear.y[idx] = 0;
619
+ idx++;
620
+ }
621
+ return { deflection, moment, shear };
622
+ }
623
+
624
+ // src/index.ts
625
+ function analyzeBeam(beam, meshOptions) {
626
+ const mesh = generateMesh(beam, meshOptions);
627
+ const system = assembleSystem(beam, mesh);
628
+ const bc = getBoundaryConditions(beam, mesh);
629
+ const solved = solveLinearSystem(system, bc);
630
+ return computeNodalResponses(beam, mesh, solved.displacements, solved.reactions);
631
+ }
632
+ function analyzeBeamChartReady(beam, meshOptions, diagramOptions) {
633
+ const mesh = generateMesh(beam, meshOptions);
634
+ const system = assembleSystem(beam, mesh);
635
+ const bc = getBoundaryConditions(beam, mesh);
636
+ const solved = solveLinearSystem(system, bc);
637
+ const results = computeNodalResponses(beam, mesh, solved.displacements, solved.reactions);
638
+ const diagrams = sampleDiagrams(beam, mesh, results, diagramOptions);
639
+ return { results, diagrams };
640
+ }
641
+ export {
642
+ analyzeBeam,
643
+ analyzeBeamChartReady,
644
+ assembleSystem,
645
+ createSolverBuffers,
646
+ generateMesh,
647
+ getBoundaryConditions,
648
+ solveLinearSystemBuffered
649
+ };
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@jtgtools/xbeam",
3
+ "version": "0.0.1",
4
+ "description": "Fast Euler-Bernoulli beam FEM solver with chart-ready results",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
15
+ "files": ["dist"],
16
+ "scripts": {
17
+ "prepare": "tsup src/index.ts --format esm,cjs --dts --clean",
18
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
19
+ "demo": "npm run build && npx serve . --listen 3000",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest",
22
+ "bench": "vitest bench"
23
+ },
24
+ "dependencies": {
25
+ "tsup": "^8.5.1",
26
+ "typescript": "^6.0.2"
27
+ },
28
+ "devDependencies": {
29
+ "vitest": "^4.1.2"
30
+ }
31
+ }