@geotechcli/core 0.4.107 → 0.4.109
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/fem/engineering-evidence.d.ts +65 -0
- package/dist/fem/engineering-evidence.d.ts.map +1 -1
- package/dist/fem/engineering-evidence.js +315 -10
- package/dist/fem/engineering-evidence.js.map +1 -1
- package/dist/fem/index.d.ts +3 -2
- package/dist/fem/index.d.ts.map +1 -1
- package/dist/fem/index.js +2 -1
- package/dist/fem/index.js.map +1 -1
- package/dist/fem/plane-strain-assembly.d.ts +44 -4
- package/dist/fem/plane-strain-assembly.d.ts.map +1 -1
- package/dist/fem/plane-strain-assembly.js +334 -52
- package/dist/fem/plane-strain-assembly.js.map +1 -1
- package/dist/fem/production-readiness.d.ts.map +1 -1
- package/dist/fem/production-readiness.js +8 -3
- package/dist/fem/production-readiness.js.map +1 -1
- package/dist/fem/routing.js +4 -4
- package/dist/fem/routing.js.map +1 -1
- package/dist/fem/sparse-linear-algebra.d.ts +47 -0
- package/dist/fem/sparse-linear-algebra.d.ts.map +1 -0
- package/dist/fem/sparse-linear-algebra.js +290 -0
- package/dist/fem/sparse-linear-algebra.js.map +1 -0
- package/dist/meta/metadata.json +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DEFAULT_FEM_CONVERGENCE_POLICY } from './engineering-evidence.js';
|
|
2
|
+
import { buildCsrFromTriplets, solveCsrConjugateGradient, } from './sparse-linear-algebra.js';
|
|
2
3
|
const GAUSS_POINTS = [
|
|
3
4
|
[-1 / Math.sqrt(3), -1 / Math.sqrt(3), 1],
|
|
4
5
|
[1 / Math.sqrt(3), -1 / Math.sqrt(3), 1],
|
|
@@ -6,6 +7,7 @@ const GAUSS_POINTS = [
|
|
|
6
7
|
[-1 / Math.sqrt(3), 1 / Math.sqrt(3), 1],
|
|
7
8
|
];
|
|
8
9
|
const MAX_DENSE_DOF_COUNT = 800;
|
|
10
|
+
const MAX_SPARSE_EXPERIMENTAL_DOF_COUNT = 5_000;
|
|
9
11
|
const MAX_BIOT_TIME_STEP_GROWTH_RATIO = 8;
|
|
10
12
|
function assertFinite(value, label) {
|
|
11
13
|
if (!Number.isFinite(value))
|
|
@@ -178,52 +180,126 @@ function deviatoricNorm(values) {
|
|
|
178
180
|
const mean = (values[0] + values[1] + values[2]) / 3;
|
|
179
181
|
return Math.hypot(values[0] - mean, values[1] - mean, values[2] - mean);
|
|
180
182
|
}
|
|
181
|
-
function
|
|
183
|
+
function principalTrace(values) {
|
|
184
|
+
return values[0] + values[1] + values[2];
|
|
185
|
+
}
|
|
186
|
+
function principalDeviator(values) {
|
|
187
|
+
const mean = principalTrace(values) / 3;
|
|
188
|
+
return [values[0] - mean, values[1] - mean, values[2] - mean];
|
|
189
|
+
}
|
|
190
|
+
function addPrincipal(a, b) {
|
|
191
|
+
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
|
192
|
+
}
|
|
193
|
+
function scalePrincipal(values, scale) {
|
|
194
|
+
return [values[0] * scale, values[1] * scale, values[2] * scale];
|
|
195
|
+
}
|
|
196
|
+
function initialDruckerPragerState() {
|
|
197
|
+
return {
|
|
198
|
+
strain: [0, 0, 0],
|
|
199
|
+
stressKpa: [0, 0, 0],
|
|
200
|
+
sigmaZKpa: 0,
|
|
201
|
+
equivalentPlasticStrain: 0,
|
|
202
|
+
plasticStrainPrincipal: [0, 0, 0],
|
|
203
|
+
volumetricPlasticStrain: 0,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function integrateDruckerPragerStress(input) {
|
|
182
207
|
const { material, strain } = input;
|
|
183
208
|
const params = druckerPragerParameters(material);
|
|
184
|
-
const
|
|
209
|
+
const d = planeStrainD(material);
|
|
210
|
+
const strainIncrement = strain.map((value, index) => value - input.previous.strain[index]);
|
|
211
|
+
const stressIncrement = d.map((row) => row.reduce((sum, value, col) => sum + value * strainIncrement[col], 0));
|
|
212
|
+
const trialStress = [
|
|
213
|
+
input.previous.stressKpa[0] + stressIncrement[0],
|
|
214
|
+
input.previous.stressKpa[1] + stressIncrement[1],
|
|
215
|
+
input.previous.stressKpa[2] + stressIncrement[2],
|
|
216
|
+
];
|
|
217
|
+
const trialSigmaZKpa = input.previous.sigmaZKpa + planeStrainSigmaZ(material, strainIncrement);
|
|
218
|
+
const principal = principalCompressionFromPlaneStress(trialStress, trialSigmaZKpa);
|
|
185
219
|
const principalStress = principal.values;
|
|
186
|
-
const trace = principalStress
|
|
220
|
+
const trace = principalTrace(principalStress);
|
|
187
221
|
const mean = trace / 3;
|
|
188
222
|
const q = deviatoricNorm(principalStress);
|
|
189
|
-
const
|
|
223
|
+
const hardeningModulusKpa = material.hardeningModulusKpa ?? 0;
|
|
224
|
+
const intercept = params.compressionInterceptKpa +
|
|
225
|
+
hardeningModulusKpa * input.previous.equivalentPlasticStrain;
|
|
190
226
|
const yieldValue = q - params.rho * trace - intercept;
|
|
191
227
|
const yieldScale = Math.max(q, Math.abs(params.rho * trace), Math.abs(intercept), 1);
|
|
192
228
|
if (yieldValue <= 0 || Math.abs(yieldValue) / yieldScale <= input.policy.residualTolerance) {
|
|
193
|
-
|
|
229
|
+
const nextState = {
|
|
194
230
|
strain,
|
|
195
|
-
stressKpa:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
231
|
+
stressKpa: trialStress,
|
|
232
|
+
sigmaZKpa: trialSigmaZKpa,
|
|
233
|
+
equivalentPlasticStrain: input.previous.equivalentPlasticStrain,
|
|
234
|
+
plasticStrainPrincipal: input.previous.plasticStrainPrincipal,
|
|
235
|
+
volumetricPlasticStrain: input.previous.volumetricPlasticStrain,
|
|
236
|
+
};
|
|
237
|
+
return {
|
|
238
|
+
nextState,
|
|
239
|
+
result: {
|
|
240
|
+
strain,
|
|
241
|
+
stressKpa: trialStress,
|
|
242
|
+
outOfPlaneStressKpa: trialSigmaZKpa,
|
|
243
|
+
compressionPositivePrincipalStressKpa: principalStress,
|
|
244
|
+
strainIncrement,
|
|
245
|
+
previousEquivalentPlasticStrain: input.previous.equivalentPlasticStrain,
|
|
246
|
+
equivalentPlasticStrainIncrement: 0,
|
|
247
|
+
plasticStrainPrincipal: nextState.plasticStrainPrincipal,
|
|
248
|
+
volumetricPlasticStrain: nextState.volumetricPlasticStrain,
|
|
249
|
+
yieldValueKpa: yieldValue,
|
|
250
|
+
yieldResidualRatio: Math.max(0, yieldValue) / yieldScale,
|
|
251
|
+
plasticMultiplier: 0,
|
|
252
|
+
equivalentPlasticStrain: nextState.equivalentPlasticStrain,
|
|
253
|
+
state: 'elastic',
|
|
254
|
+
},
|
|
203
255
|
};
|
|
204
256
|
}
|
|
205
|
-
const targetQ = Math.max(0, params.rho * trace + intercept);
|
|
206
|
-
const scale = q > 0 ? targetQ / q : 0;
|
|
207
|
-
const correctedPrincipal = principalStress.map((value) => mean + (value - mean) * scale);
|
|
208
|
-
const corrected = planeStressFromPrincipalCompression(correctedPrincipal, principal.angleRad);
|
|
209
|
-
const correctedQ = deviatoricNorm(correctedPrincipal);
|
|
210
|
-
const correctedYieldValue = correctedQ - params.rho * trace - intercept;
|
|
211
|
-
const correctedScale = Math.max(correctedQ, Math.abs(params.rho * trace), Math.abs(intercept), 1);
|
|
212
257
|
const moduli = elasticModuli(material);
|
|
213
258
|
const denominator = 2 * moduli.shearModulusKpa +
|
|
214
259
|
9 * moduli.bulkModulusKpa * params.rho * params.rhoBar +
|
|
215
|
-
|
|
260
|
+
hardeningModulusKpa;
|
|
216
261
|
const plasticMultiplier = Math.max(0, yieldValue / Math.max(denominator, 1e-12));
|
|
217
|
-
|
|
262
|
+
const updatedEquivalentPlasticStrain = input.previous.equivalentPlasticStrain + plasticMultiplier;
|
|
263
|
+
const updatedIntercept = params.compressionInterceptKpa +
|
|
264
|
+
hardeningModulusKpa * updatedEquivalentPlasticStrain;
|
|
265
|
+
const trialDeviator = principalDeviator(principalStress);
|
|
266
|
+
const flowDirection = q > 0 ? scalePrincipal(trialDeviator, 1 / q) : [0, 0, 0];
|
|
267
|
+
const plasticStrainIncrement = addPrincipal(scalePrincipal(flowDirection, plasticMultiplier), [-params.rhoBar * plasticMultiplier, -params.rhoBar * plasticMultiplier, -params.rhoBar * plasticMultiplier]);
|
|
268
|
+
const correctedDeviator = addPrincipal(trialDeviator, scalePrincipal(flowDirection, -2 * moduli.shearModulusKpa * plasticMultiplier));
|
|
269
|
+
const correctedTrace = trace + 9 * moduli.bulkModulusKpa * params.rhoBar * plasticMultiplier;
|
|
270
|
+
const correctedPrincipal = addPrincipal(correctedDeviator, [correctedTrace / 3, correctedTrace / 3, correctedTrace / 3]);
|
|
271
|
+
const corrected = planeStressFromPrincipalCompression(correctedPrincipal, principal.angleRad);
|
|
272
|
+
const correctedQ = deviatoricNorm(correctedPrincipal);
|
|
273
|
+
const correctedYieldValue = correctedQ - params.rho * correctedTrace - updatedIntercept;
|
|
274
|
+
const correctedScale = Math.max(correctedQ, Math.abs(params.rho * correctedTrace), Math.abs(updatedIntercept), 1);
|
|
275
|
+
const plasticStrainPrincipal = addPrincipal(input.previous.plasticStrainPrincipal, plasticStrainIncrement);
|
|
276
|
+
const volumetricPlasticStrain = input.previous.volumetricPlasticStrain + principalTrace(plasticStrainIncrement);
|
|
277
|
+
const nextState = {
|
|
218
278
|
strain,
|
|
219
279
|
stressKpa: corrected.stress,
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
280
|
+
sigmaZKpa: corrected.sigmaZKpa,
|
|
281
|
+
equivalentPlasticStrain: updatedEquivalentPlasticStrain,
|
|
282
|
+
plasticStrainPrincipal,
|
|
283
|
+
volumetricPlasticStrain,
|
|
284
|
+
};
|
|
285
|
+
return {
|
|
286
|
+
nextState,
|
|
287
|
+
result: {
|
|
288
|
+
strain,
|
|
289
|
+
stressKpa: corrected.stress,
|
|
290
|
+
outOfPlaneStressKpa: corrected.sigmaZKpa,
|
|
291
|
+
compressionPositivePrincipalStressKpa: correctedPrincipal,
|
|
292
|
+
strainIncrement,
|
|
293
|
+
previousEquivalentPlasticStrain: input.previous.equivalentPlasticStrain,
|
|
294
|
+
equivalentPlasticStrainIncrement: plasticMultiplier,
|
|
295
|
+
plasticStrainPrincipal,
|
|
296
|
+
volumetricPlasticStrain,
|
|
297
|
+
yieldValueKpa: correctedYieldValue,
|
|
298
|
+
yieldResidualRatio: Math.abs(correctedYieldValue) / correctedScale,
|
|
299
|
+
plasticMultiplier,
|
|
300
|
+
equivalentPlasticStrain: updatedEquivalentPlasticStrain,
|
|
301
|
+
state: 'plastic',
|
|
302
|
+
},
|
|
227
303
|
};
|
|
228
304
|
}
|
|
229
305
|
function shapeDerivativesNatural(xi, eta) {
|
|
@@ -284,6 +360,9 @@ function matVec(matrix, vector) {
|
|
|
284
360
|
function dot(a, b) {
|
|
285
361
|
return a.reduce((sum, value, index) => sum + value * b[index], 0);
|
|
286
362
|
}
|
|
363
|
+
function vectorNorm(values) {
|
|
364
|
+
return Math.sqrt(values.reduce((sum, value) => sum + value * value, 0));
|
|
365
|
+
}
|
|
287
366
|
function elementMatrices(input) {
|
|
288
367
|
const { nodes, material, thicknessM } = input;
|
|
289
368
|
const d = planeStrainD(material);
|
|
@@ -1344,7 +1423,7 @@ export function runPlaneStrainQuad4Assembly(model) {
|
|
|
1344
1423
|
policy,
|
|
1345
1424
|
};
|
|
1346
1425
|
}
|
|
1347
|
-
function assemblePlaneStrainSystem(model) {
|
|
1426
|
+
function assemblePlaneStrainSystem(model, options = {}) {
|
|
1348
1427
|
if (model.schemaVersion !== 'fem-plane-strain-model.v1') {
|
|
1349
1428
|
throw new Error('Only fem-plane-strain-model.v1 is supported.');
|
|
1350
1429
|
}
|
|
@@ -1370,10 +1449,17 @@ function assemblePlaneStrainSystem(model) {
|
|
|
1370
1449
|
const nodeIndexById = new Map(model.nodes.map((node, index) => [node.id, index]));
|
|
1371
1450
|
const materialById = new Map(model.materials.map((material) => [material.id, material]));
|
|
1372
1451
|
const dofCount = model.nodes.length * 2;
|
|
1373
|
-
|
|
1452
|
+
const storage = options.storage ?? 'dense-and-triplets';
|
|
1453
|
+
if (storage === 'dense-and-triplets' && dofCount > MAX_DENSE_DOF_COUNT) {
|
|
1374
1454
|
throw new Error(`Plane-strain dense assembly is capped at ${MAX_DENSE_DOF_COUNT} DOFs for benchmark-scale evidence runs.`);
|
|
1375
1455
|
}
|
|
1376
|
-
|
|
1456
|
+
if (storage === 'triplets-only' && dofCount > MAX_SPARSE_EXPERIMENTAL_DOF_COUNT) {
|
|
1457
|
+
throw new Error(`Plane-strain sparse experimental assembly is capped at ${MAX_SPARSE_EXPERIMENTAL_DOF_COUNT} DOFs until production solver benchmarks are approved.`);
|
|
1458
|
+
}
|
|
1459
|
+
const stiffness = storage === 'dense-and-triplets'
|
|
1460
|
+
? Array.from({ length: dofCount }, () => new Array(dofCount).fill(0))
|
|
1461
|
+
: undefined;
|
|
1462
|
+
const stiffnessTriplets = [];
|
|
1377
1463
|
const loads = new Array(dofCount).fill(0);
|
|
1378
1464
|
for (const node of model.nodes) {
|
|
1379
1465
|
assertFinite(node.xM, `node ${node.id} xM`);
|
|
@@ -1422,7 +1508,12 @@ function assemblePlaneStrainSystem(model) {
|
|
|
1422
1508
|
const globalDofs = nodeIndices.flatMap((index) => [dofIndex(index, 'ux'), dofIndex(index, 'uy')]);
|
|
1423
1509
|
for (let localRow = 0; localRow < 8; localRow += 1) {
|
|
1424
1510
|
for (let localCol = 0; localCol < 8; localCol += 1) {
|
|
1425
|
-
|
|
1511
|
+
const row = globalDofs[localRow];
|
|
1512
|
+
const col = globalDofs[localCol];
|
|
1513
|
+
const value = elementData.stiffness[localRow][localCol];
|
|
1514
|
+
stiffnessTriplets.push({ row, col, value });
|
|
1515
|
+
if (stiffness)
|
|
1516
|
+
stiffness[row][col] += value;
|
|
1426
1517
|
}
|
|
1427
1518
|
}
|
|
1428
1519
|
elementGaussCache.push({
|
|
@@ -1463,6 +1554,7 @@ function assemblePlaneStrainSystem(model) {
|
|
|
1463
1554
|
policy,
|
|
1464
1555
|
materialById,
|
|
1465
1556
|
stiffness,
|
|
1557
|
+
stiffnessTriplets,
|
|
1466
1558
|
loads,
|
|
1467
1559
|
prescribed,
|
|
1468
1560
|
freeDofs,
|
|
@@ -1471,27 +1563,30 @@ function assemblePlaneStrainSystem(model) {
|
|
|
1471
1563
|
};
|
|
1472
1564
|
}
|
|
1473
1565
|
function evaluatePlaneStrainDruckerPragerState(input) {
|
|
1474
|
-
const { system, displacement, loadFactor } = input;
|
|
1566
|
+
const { system, displacement, loadFactor, committedStates } = input;
|
|
1475
1567
|
const internal = new Array(system.dofCount).fill(0);
|
|
1476
1568
|
let maxYieldResidualRatio = 0;
|
|
1477
1569
|
let maxEquivalentPlasticStrain = 0;
|
|
1570
|
+
let maxEquivalentPlasticStrainIncrement = 0;
|
|
1478
1571
|
let plasticGaussPointCount = 0;
|
|
1479
|
-
const
|
|
1572
|
+
const trialStates = [];
|
|
1573
|
+
const elements = system.elementGaussCache.map((entry, elementIndex) => {
|
|
1480
1574
|
const material = system.materialById.get(entry.element.materialId);
|
|
1481
|
-
const d = planeStrainD(material);
|
|
1482
1575
|
const elementDisplacement = entry.globalDofs.map((index) => displacement[index]);
|
|
1483
1576
|
const gaussPoints = entry.gauss.map((point, index) => {
|
|
1484
1577
|
const strain = point.b.map((row) => row.reduce((sum, value, col) => sum + value * elementDisplacement[col], 0));
|
|
1485
|
-
const
|
|
1486
|
-
const projected = projectDruckerPragerStress({
|
|
1578
|
+
const integrated = integrateDruckerPragerStress({
|
|
1487
1579
|
material,
|
|
1488
1580
|
policy: system.policy,
|
|
1489
1581
|
strain,
|
|
1490
|
-
|
|
1491
|
-
sigmaZKpa: planeStrainSigmaZ(material, strain),
|
|
1582
|
+
previous: committedStates[elementIndex]?.[index] ?? initialDruckerPragerState(),
|
|
1492
1583
|
});
|
|
1584
|
+
const projected = integrated.result;
|
|
1585
|
+
trialStates[elementIndex] = trialStates[elementIndex] ?? [];
|
|
1586
|
+
trialStates[elementIndex][index] = integrated.nextState;
|
|
1493
1587
|
maxYieldResidualRatio = Math.max(maxYieldResidualRatio, projected.yieldResidualRatio);
|
|
1494
1588
|
maxEquivalentPlasticStrain = Math.max(maxEquivalentPlasticStrain, projected.equivalentPlasticStrain);
|
|
1589
|
+
maxEquivalentPlasticStrainIncrement = Math.max(maxEquivalentPlasticStrainIncrement, projected.equivalentPlasticStrainIncrement);
|
|
1495
1590
|
if (projected.state === 'plastic')
|
|
1496
1591
|
plasticGaussPointCount += 1;
|
|
1497
1592
|
for (let localDof = 0; localDof < 8; localDof += 1) {
|
|
@@ -1520,6 +1615,19 @@ function evaluatePlaneStrainDruckerPragerState(input) {
|
|
|
1520
1615
|
round(projected.compressionPositivePrincipalStressKpa[1], 8),
|
|
1521
1616
|
round(projected.compressionPositivePrincipalStressKpa[2], 8),
|
|
1522
1617
|
],
|
|
1618
|
+
strainIncrement: [
|
|
1619
|
+
round(projected.strainIncrement[0], 12),
|
|
1620
|
+
round(projected.strainIncrement[1], 12),
|
|
1621
|
+
round(projected.strainIncrement[2], 12),
|
|
1622
|
+
],
|
|
1623
|
+
previousEquivalentPlasticStrain: round(projected.previousEquivalentPlasticStrain, 12),
|
|
1624
|
+
equivalentPlasticStrainIncrement: round(projected.equivalentPlasticStrainIncrement, 12),
|
|
1625
|
+
plasticStrainPrincipal: [
|
|
1626
|
+
round(projected.plasticStrainPrincipal[0], 12),
|
|
1627
|
+
round(projected.plasticStrainPrincipal[1], 12),
|
|
1628
|
+
round(projected.plasticStrainPrincipal[2], 12),
|
|
1629
|
+
],
|
|
1630
|
+
volumetricPlasticStrain: round(projected.volumetricPlasticStrain, 12),
|
|
1523
1631
|
yieldValueKpa: round(projected.yieldValueKpa, 10),
|
|
1524
1632
|
yieldResidualRatio: round(projected.yieldResidualRatio, 12),
|
|
1525
1633
|
plasticMultiplier: round(projected.plasticMultiplier, 12),
|
|
@@ -1560,13 +1668,33 @@ function evaluatePlaneStrainDruckerPragerState(input) {
|
|
|
1560
1668
|
reactionBalanceRatio,
|
|
1561
1669
|
maxYieldResidualRatio,
|
|
1562
1670
|
maxEquivalentPlasticStrain,
|
|
1671
|
+
maxEquivalentPlasticStrainIncrement,
|
|
1563
1672
|
plasticGaussPointCount,
|
|
1673
|
+
trialStates,
|
|
1564
1674
|
elements,
|
|
1565
1675
|
};
|
|
1566
1676
|
}
|
|
1567
|
-
function normalizeLoadStepFractions(
|
|
1568
|
-
|
|
1569
|
-
|
|
1677
|
+
function normalizeLoadStepFractions(options) {
|
|
1678
|
+
if (options.loadStepFractions != null && options.loadHistoryFactors != null) {
|
|
1679
|
+
throw new Error('Specify either loadStepFractions or loadHistoryFactors, not both.');
|
|
1680
|
+
}
|
|
1681
|
+
if (options.loadHistoryFactors != null) {
|
|
1682
|
+
if (!Array.isArray(options.loadHistoryFactors) || options.loadHistoryFactors.length === 0) {
|
|
1683
|
+
throw new Error('loadHistoryFactors must contain at least one load factor when provided.');
|
|
1684
|
+
}
|
|
1685
|
+
const history = [...options.loadHistoryFactors];
|
|
1686
|
+
for (const [index, factor] of history.entries()) {
|
|
1687
|
+
if (!Number.isFinite(factor) || factor < 0 || factor > 1) {
|
|
1688
|
+
throw new Error(`loadHistoryFactors.${index} must be finite and between 0 and 1.`);
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
if (history[history.length - 1] !== 1) {
|
|
1692
|
+
throw new Error('loadHistoryFactors must end at 1.');
|
|
1693
|
+
}
|
|
1694
|
+
return history;
|
|
1695
|
+
}
|
|
1696
|
+
const fractions = options.loadStepFractions && options.loadStepFractions.length > 0
|
|
1697
|
+
? [...options.loadStepFractions]
|
|
1570
1698
|
: [0.25, 0.5, 0.75, 1];
|
|
1571
1699
|
let previous = 0;
|
|
1572
1700
|
for (const [index, fraction] of fractions.entries()) {
|
|
@@ -1584,6 +1712,9 @@ function isDruckerPragerStepConverged(evaluation, policy) {
|
|
|
1584
1712
|
return evaluation.residualNormRatio <= policy.forceBalanceTolerance &&
|
|
1585
1713
|
evaluation.maxYieldResidualRatio <= policy.residualTolerance;
|
|
1586
1714
|
}
|
|
1715
|
+
function createInitialDruckerPragerStateGrid(system) {
|
|
1716
|
+
return system.elementGaussCache.map((entry) => entry.gauss.map(() => initialDruckerPragerState()));
|
|
1717
|
+
}
|
|
1587
1718
|
function druckerPragerResidualHistoryEntry(iteration, evaluation, policy) {
|
|
1588
1719
|
return {
|
|
1589
1720
|
iteration,
|
|
@@ -1596,48 +1727,184 @@ function druckerPragerResidualHistoryEntry(iteration, evaluation, policy) {
|
|
|
1596
1727
|
converged: isDruckerPragerStepConverged(evaluation, policy),
|
|
1597
1728
|
};
|
|
1598
1729
|
}
|
|
1599
|
-
function druckerPragerTerminationReason(evaluation, policy, converged, iterations) {
|
|
1730
|
+
function druckerPragerTerminationReason(evaluation, policy, converged, iterations, linearSolverFailure) {
|
|
1600
1731
|
if (converged)
|
|
1601
1732
|
return 'converged';
|
|
1733
|
+
if (linearSolverFailure)
|
|
1734
|
+
return 'linear_solver_nonconverged';
|
|
1602
1735
|
if (iterations >= policy.maxIterations)
|
|
1603
1736
|
return 'max_iterations';
|
|
1604
1737
|
if (evaluation.residualNormRatio > policy.forceBalanceTolerance)
|
|
1605
1738
|
return 'force_residual_exceeded';
|
|
1606
1739
|
return 'yield_residual_exceeded';
|
|
1607
1740
|
}
|
|
1741
|
+
function normalizeLinearSolverKind(solver) {
|
|
1742
|
+
const resolved = solver ?? 'dense-gaussian';
|
|
1743
|
+
if (resolved !== 'dense-gaussian' && resolved !== 'sparse-csr-cg') {
|
|
1744
|
+
throw new Error('linearSolver must be dense-gaussian or sparse-csr-cg.');
|
|
1745
|
+
}
|
|
1746
|
+
return resolved;
|
|
1747
|
+
}
|
|
1748
|
+
function denseNonzeroCount(matrix) {
|
|
1749
|
+
return matrix.reduce((count, row) => count + row.filter((value) => Math.abs(value) > 0).length, 0);
|
|
1750
|
+
}
|
|
1751
|
+
function buildReducedCsr(input) {
|
|
1752
|
+
const freeIndexByDof = new Map(input.freeDofs.map((dof, index) => [dof, index]));
|
|
1753
|
+
const reducedTriplets = input.triplets.flatMap((entry) => {
|
|
1754
|
+
const row = freeIndexByDof.get(entry.row);
|
|
1755
|
+
const col = freeIndexByDof.get(entry.col);
|
|
1756
|
+
return row != null && col != null ? [{ row, col, value: entry.value }] : [];
|
|
1757
|
+
});
|
|
1758
|
+
return buildCsrFromTriplets({
|
|
1759
|
+
rowCount: input.freeDofs.length,
|
|
1760
|
+
colCount: input.freeDofs.length,
|
|
1761
|
+
triplets: reducedTriplets,
|
|
1762
|
+
dropTolerance: 0,
|
|
1763
|
+
});
|
|
1764
|
+
}
|
|
1765
|
+
function solveDenseDruckerPragerCorrection(input) {
|
|
1766
|
+
const correction = solveDenseLinearSystem(input.reducedK, [...input.rhs]);
|
|
1767
|
+
const solvedRhs = matVec(input.reducedK, correction);
|
|
1768
|
+
const residual = solvedRhs.map((value, index) => value - input.rhs[index]);
|
|
1769
|
+
const rhsNorm = Math.max(vectorNorm(input.rhs), 1);
|
|
1770
|
+
const finalResidualNorm = vectorNorm(residual);
|
|
1771
|
+
const correctionNorm = vectorNorm(correction);
|
|
1772
|
+
return {
|
|
1773
|
+
correction,
|
|
1774
|
+
audit: {
|
|
1775
|
+
schemaVersion: 'fem-plane-strain-linear-solver-audit.v1',
|
|
1776
|
+
solver: 'dense-gaussian',
|
|
1777
|
+
matrixDofCount: input.rhs.length,
|
|
1778
|
+
nonzeroCount: denseNonzeroCount(input.reducedK),
|
|
1779
|
+
iterations: input.rhs.length,
|
|
1780
|
+
tolerance: input.tolerance,
|
|
1781
|
+
maxIterations: input.maxIterations,
|
|
1782
|
+
initialResidualNorm: vectorNorm(input.rhs),
|
|
1783
|
+
finalResidualNorm,
|
|
1784
|
+
residualNormRatio: finalResidualNorm / rhsNorm,
|
|
1785
|
+
correctionNormM: correctionNorm,
|
|
1786
|
+
correctionNormRatio: correctionNorm / Math.max(vectorNorm(input.currentFreeDisplacement), 1e-12),
|
|
1787
|
+
converged: true,
|
|
1788
|
+
},
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
function solveSparseDruckerPragerCorrection(input) {
|
|
1792
|
+
const solved = solveCsrConjugateGradient(input.reducedK, input.rhs, {
|
|
1793
|
+
tolerance: input.tolerance,
|
|
1794
|
+
maxIterations: input.maxIterations,
|
|
1795
|
+
preconditioner: 'jacobi',
|
|
1796
|
+
});
|
|
1797
|
+
const correctionNorm = vectorNorm(solved.solution);
|
|
1798
|
+
return {
|
|
1799
|
+
correction: solved.solution,
|
|
1800
|
+
audit: {
|
|
1801
|
+
schemaVersion: 'fem-plane-strain-linear-solver-audit.v1',
|
|
1802
|
+
solver: 'sparse-csr-cg',
|
|
1803
|
+
matrixDofCount: input.rhs.length,
|
|
1804
|
+
nonzeroCount: input.reducedK.nonzeroCount,
|
|
1805
|
+
iterations: solved.iterations,
|
|
1806
|
+
tolerance: solved.tolerance,
|
|
1807
|
+
maxIterations: solved.maxIterations,
|
|
1808
|
+
initialResidualNorm: solved.initialResidualNorm,
|
|
1809
|
+
finalResidualNorm: solved.finalResidualNorm,
|
|
1810
|
+
residualNormRatio: solved.residualNormRatio,
|
|
1811
|
+
correctionNormM: correctionNorm,
|
|
1812
|
+
correctionNormRatio: correctionNorm / Math.max(vectorNorm(input.currentFreeDisplacement), 1e-12),
|
|
1813
|
+
converged: solved.converged,
|
|
1814
|
+
...(solved.failureReason ? { failureReason: solved.failureReason } : {}),
|
|
1815
|
+
},
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1608
1818
|
export function runPlaneStrainDruckerPragerLoadSteps(model, options = {}) {
|
|
1609
|
-
const
|
|
1610
|
-
const
|
|
1611
|
-
|
|
1819
|
+
const linearSolver = normalizeLinearSolverKind(options.linearSolver);
|
|
1820
|
+
const system = assemblePlaneStrainSystem(model, {
|
|
1821
|
+
storage: linearSolver === 'sparse-csr-cg' ? 'triplets-only' : 'dense-and-triplets',
|
|
1822
|
+
});
|
|
1823
|
+
const loadStepFractions = normalizeLoadStepFractions({
|
|
1824
|
+
loadStepFractions: options.loadStepFractions,
|
|
1825
|
+
loadHistoryFactors: options.loadHistoryFactors,
|
|
1826
|
+
});
|
|
1827
|
+
const linearSolverTolerance = options.linearSolverTolerance ?? Math.min(1e-10, system.policy.forceBalanceTolerance / 10);
|
|
1828
|
+
if (!Number.isFinite(linearSolverTolerance) || linearSolverTolerance <= 0) {
|
|
1829
|
+
throw new Error('linearSolverTolerance must be a finite positive number.');
|
|
1830
|
+
}
|
|
1831
|
+
const linearSolverMaxIterations = options.linearSolverMaxIterations ?? Math.max(100, system.freeDofs.length * 10);
|
|
1832
|
+
assertPositiveInteger(linearSolverMaxIterations, 'linearSolverMaxIterations');
|
|
1833
|
+
const reducedDenseK = linearSolver === 'dense-gaussian'
|
|
1834
|
+
? system.freeDofs.map((row) => system.freeDofs.map((col) => system.stiffness[row][col]))
|
|
1835
|
+
: undefined;
|
|
1836
|
+
const reducedSparseK = linearSolver === 'sparse-csr-cg' && system.freeDofs.length > 0
|
|
1837
|
+
? buildReducedCsr({
|
|
1838
|
+
freeDofs: system.freeDofs,
|
|
1839
|
+
triplets: system.stiffnessTriplets,
|
|
1840
|
+
})
|
|
1841
|
+
: undefined;
|
|
1612
1842
|
const displacement = new Array(system.dofCount).fill(0);
|
|
1843
|
+
let committedStates = createInitialDruckerPragerStateGrid(system);
|
|
1613
1844
|
let finalEvaluation;
|
|
1614
1845
|
const loadSteps = [];
|
|
1615
1846
|
for (const [stepIndex, loadFactor] of loadStepFractions.entries()) {
|
|
1616
1847
|
for (const [index, value] of system.prescribed)
|
|
1617
1848
|
displacement[index] = value * loadFactor;
|
|
1618
|
-
let evaluation = evaluatePlaneStrainDruckerPragerState({
|
|
1849
|
+
let evaluation = evaluatePlaneStrainDruckerPragerState({
|
|
1850
|
+
system,
|
|
1851
|
+
displacement,
|
|
1852
|
+
loadFactor,
|
|
1853
|
+
committedStates,
|
|
1854
|
+
});
|
|
1619
1855
|
let iterations = 0;
|
|
1620
1856
|
let converged = isDruckerPragerStepConverged(evaluation, system.policy);
|
|
1621
1857
|
const residualHistory = [
|
|
1622
1858
|
druckerPragerResidualHistoryEntry(iterations, evaluation, system.policy),
|
|
1623
1859
|
];
|
|
1860
|
+
const linearSolverAudits = [];
|
|
1861
|
+
let linearSolverFailure;
|
|
1624
1862
|
while (!converged && iterations < system.policy.maxIterations) {
|
|
1625
1863
|
iterations += 1;
|
|
1626
1864
|
if (system.freeDofs.length === 0)
|
|
1627
1865
|
break;
|
|
1628
1866
|
const correctionRhs = system.freeDofs.map((index) => -evaluation.residual[index]);
|
|
1629
|
-
const
|
|
1867
|
+
const currentFreeDisplacement = system.freeDofs.map((index) => displacement[index]);
|
|
1868
|
+
const solved = linearSolver === 'sparse-csr-cg'
|
|
1869
|
+
? solveSparseDruckerPragerCorrection({
|
|
1870
|
+
reducedK: reducedSparseK,
|
|
1871
|
+
rhs: correctionRhs,
|
|
1872
|
+
currentFreeDisplacement,
|
|
1873
|
+
tolerance: linearSolverTolerance,
|
|
1874
|
+
maxIterations: linearSolverMaxIterations,
|
|
1875
|
+
})
|
|
1876
|
+
: solveDenseDruckerPragerCorrection({
|
|
1877
|
+
reducedK: reducedDenseK,
|
|
1878
|
+
rhs: correctionRhs,
|
|
1879
|
+
currentFreeDisplacement,
|
|
1880
|
+
tolerance: linearSolverTolerance,
|
|
1881
|
+
maxIterations: linearSolverMaxIterations,
|
|
1882
|
+
});
|
|
1883
|
+
linearSolverAudits.push(solved.audit);
|
|
1884
|
+
if (!solved.audit.converged) {
|
|
1885
|
+
linearSolverFailure = solved.audit;
|
|
1886
|
+
break;
|
|
1887
|
+
}
|
|
1888
|
+
const correction = solved.correction;
|
|
1630
1889
|
for (const [correctionIndex, dof] of system.freeDofs.entries()) {
|
|
1631
1890
|
displacement[dof] += correction[correctionIndex];
|
|
1632
1891
|
}
|
|
1633
1892
|
for (const [index, value] of system.prescribed)
|
|
1634
1893
|
displacement[index] = value * loadFactor;
|
|
1635
|
-
evaluation = evaluatePlaneStrainDruckerPragerState({
|
|
1894
|
+
evaluation = evaluatePlaneStrainDruckerPragerState({
|
|
1895
|
+
system,
|
|
1896
|
+
displacement,
|
|
1897
|
+
loadFactor,
|
|
1898
|
+
committedStates,
|
|
1899
|
+
});
|
|
1636
1900
|
converged = isDruckerPragerStepConverged(evaluation, system.policy);
|
|
1637
1901
|
residualHistory.push(druckerPragerResidualHistoryEntry(iterations, evaluation, system.policy));
|
|
1638
1902
|
}
|
|
1639
1903
|
finalEvaluation = evaluation;
|
|
1640
|
-
|
|
1904
|
+
if (converged)
|
|
1905
|
+
committedStates = evaluation.trialStates;
|
|
1906
|
+
const terminationReason = druckerPragerTerminationReason(evaluation, system.policy, converged, iterations, linearSolverFailure);
|
|
1907
|
+
const lastLinearAudit = linearSolverAudits.at(-1);
|
|
1641
1908
|
loadSteps.push({
|
|
1642
1909
|
step: stepIndex + 1,
|
|
1643
1910
|
loadFactor: round(loadFactor, 8),
|
|
@@ -1647,9 +1914,16 @@ export function runPlaneStrainDruckerPragerLoadSteps(model, options = {}) {
|
|
|
1647
1914
|
reactionBalanceRatio: round(evaluation.reactionBalanceRatio, 12),
|
|
1648
1915
|
maxYieldResidualRatio: round(evaluation.maxYieldResidualRatio, 12),
|
|
1649
1916
|
maxEquivalentPlasticStrain: round(evaluation.maxEquivalentPlasticStrain, 12),
|
|
1917
|
+
maxEquivalentPlasticStrainIncrement: round(evaluation.maxEquivalentPlasticStrainIncrement, 12),
|
|
1650
1918
|
plasticGaussPointCount: evaluation.plasticGaussPointCount,
|
|
1919
|
+
linearSolver,
|
|
1920
|
+
linearIterations: linearSolverAudits.reduce((sum, audit) => sum + audit.iterations, 0),
|
|
1921
|
+
linearResidualNormRatio: round(lastLinearAudit?.residualNormRatio ?? 0, 12),
|
|
1922
|
+
correctionNormRatio: round(lastLinearAudit?.correctionNormRatio ?? 0, 12),
|
|
1923
|
+
linearSolverAudits,
|
|
1651
1924
|
converged,
|
|
1652
1925
|
terminationReason,
|
|
1926
|
+
...(linearSolverFailure?.failureReason ? { failureReason: linearSolverFailure.failureReason } : {}),
|
|
1653
1927
|
residualHistory,
|
|
1654
1928
|
});
|
|
1655
1929
|
}
|
|
@@ -1672,12 +1946,18 @@ export function runPlaneStrainDruckerPragerLoadSteps(model, options = {}) {
|
|
|
1672
1946
|
dofCount: system.dofCount,
|
|
1673
1947
|
freeDofCount: system.freeDofs.length,
|
|
1674
1948
|
constrainedDofCount: system.prescribed.size,
|
|
1949
|
+
linearSolver,
|
|
1950
|
+
nonlinearAlgorithm: 'modified-newton',
|
|
1951
|
+
globalTangent: 'elastic',
|
|
1952
|
+
materialIntegration: 'incremental-committed-drucker-prager-return-mapping',
|
|
1953
|
+
stateStorage: 'committed-gauss-point-history',
|
|
1675
1954
|
loadSteps,
|
|
1676
1955
|
maxFreeResidualKn: round(finalEvaluation.maxFreeResidualKn, 12),
|
|
1677
1956
|
residualNormRatio: round(finalEvaluation.residualNormRatio, 12),
|
|
1678
1957
|
reactionBalanceRatio: round(finalEvaluation.reactionBalanceRatio, 12),
|
|
1679
1958
|
maxYieldResidualRatio: round(finalEvaluation.maxYieldResidualRatio, 12),
|
|
1680
1959
|
maxEquivalentPlasticStrain: round(finalEvaluation.maxEquivalentPlasticStrain, 12),
|
|
1960
|
+
maxEquivalentPlasticStrainIncrement: round(finalEvaluation.maxEquivalentPlasticStrainIncrement, 12),
|
|
1681
1961
|
plasticGaussPointCount: finalEvaluation.plasticGaussPointCount,
|
|
1682
1962
|
converged: status === 'converged',
|
|
1683
1963
|
status,
|
|
@@ -1695,7 +1975,9 @@ export function runPlaneStrainDruckerPragerLoadSteps(model, options = {}) {
|
|
|
1695
1975
|
limitations: [
|
|
1696
1976
|
'Benchmark-scale modified-Newton plane-strain plasticity evidence kernel only.',
|
|
1697
1977
|
...(failedStep ? ['Nonconverged load-step result is reported fail-closed and must not be treated as an accepted engineering solve.'] : []),
|
|
1698
|
-
|
|
1978
|
+
linearSolver === 'sparse-csr-cg'
|
|
1979
|
+
? 'Uses an experimental CSR Conjugate Gradient linear solve audit, elastic global tangent, and committed Gauss-point Drucker-Prager return mapping; no production consistent tangent, hardening calibration, staged activation, pore-pressure DOF, or route-backed result manifest is provided.'
|
|
1980
|
+
: 'Uses elastic global tangent with committed Gauss-point Drucker-Prager return mapping; no production consistent tangent, production sparse solver, hardening calibration, staged activation, pore-pressure DOF, or route-backed result manifest is provided.',
|
|
1699
1981
|
'Use for deterministic evidence and regression tests only until independent published/commercial benchmark comparison and licensed production approval gates are complete.',
|
|
1700
1982
|
],
|
|
1701
1983
|
};
|