@geotechcli/core 0.4.109 → 0.4.111
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/agents/brain.d.ts.map +1 -1
- package/dist/agents/brain.js +31 -2
- package/dist/agents/brain.js.map +1 -1
- package/dist/agents/fem-tools.js +5 -0
- package/dist/agents/fem-tools.js.map +1 -1
- package/dist/agents/safety.d.ts +1 -0
- package/dist/agents/safety.d.ts.map +1 -1
- package/dist/agents/safety.js +62 -0
- package/dist/agents/safety.js.map +1 -1
- package/dist/fem/demo.d.ts.map +1 -1
- package/dist/fem/demo.js +4 -0
- package/dist/fem/demo.js.map +1 -1
- package/dist/fem/engineering-evidence.d.ts +21 -2
- package/dist/fem/engineering-evidence.d.ts.map +1 -1
- package/dist/fem/engineering-evidence.js +354 -6
- package/dist/fem/engineering-evidence.js.map +1 -1
- package/dist/fem/index.d.ts +4 -2
- package/dist/fem/index.d.ts.map +1 -1
- package/dist/fem/index.js +2 -0
- package/dist/fem/index.js.map +1 -1
- package/dist/fem/nonlinear-plane-strain-solver.d.ts +10 -0
- package/dist/fem/nonlinear-plane-strain-solver.d.ts.map +1 -0
- package/dist/fem/nonlinear-plane-strain-solver.js +358 -0
- package/dist/fem/nonlinear-plane-strain-solver.js.map +1 -0
- package/dist/fem/plane-strain-assembly.d.ts +54 -0
- package/dist/fem/plane-strain-assembly.d.ts.map +1 -1
- package/dist/fem/plane-strain-assembly.js +277 -46
- package/dist/fem/plane-strain-assembly.js.map +1 -1
- package/dist/fem/production-readiness.js +5 -5
- package/dist/fem/production-readiness.js.map +1 -1
- package/dist/fem/support-design.d.ts +124 -0
- package/dist/fem/support-design.d.ts.map +1 -0
- package/dist/fem/support-design.js +380 -0
- package/dist/fem/support-design.js.map +1 -0
- package/dist/fem/types.d.ts +68 -3
- package/dist/fem/types.d.ts.map +1 -1
- package/dist/fem/validation.d.ts.map +1 -1
- package/dist/fem/validation.js +346 -13
- package/dist/fem/validation.js.map +1 -1
- package/dist/ingest/document-evidence-packet.d.ts +30 -30
- package/dist/ingest/job-worker.d.ts.map +1 -1
- package/dist/ingest/job-worker.js +4 -0
- package/dist/ingest/job-worker.js.map +1 -1
- package/dist/meta/metadata.json +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { calculateLateralEarthPressure } from '../geo/lateral-earth-pressure.js';
|
|
2
2
|
import { buildPlaneStrainRectangularMesh, runPlaneStrainBiotConsolidation, runPlaneStrainDruckerPragerLoadSteps, runPlaneStrainQuad4Assembly, runPlaneStrainSteadySeepage, } from './plane-strain-assembly.js';
|
|
3
|
+
import { runFemSupportMemberDesignCheck } from './support-design.js';
|
|
3
4
|
export const DEFAULT_FEM_CONVERGENCE_POLICY = {
|
|
4
5
|
schemaVersion: 'fem-convergence-policy.v1',
|
|
5
6
|
residualTolerance: 1e-6,
|
|
@@ -68,6 +69,81 @@ const DEFAULT_EXTERNAL_BENCHMARK_REQUIRED_QUANTITIES = [
|
|
|
68
69
|
requiredReferenceSourceTypes: REQUIRED_EXTERNAL_BENCHMARK_SOURCE_TYPES,
|
|
69
70
|
},
|
|
70
71
|
];
|
|
72
|
+
const DEFAULT_EXTERNAL_BENCHMARK_REFERENCES = [
|
|
73
|
+
{
|
|
74
|
+
id: 'terzaghi-1943-theoretical-soil-mechanics',
|
|
75
|
+
sourceType: 'published-source',
|
|
76
|
+
label: 'Terzaghi 1D consolidation and effective-stress source reference',
|
|
77
|
+
citation: 'Terzaghi, K. (1943). Theoretical Soil Mechanics. John Wiley & Sons. doi:10.1002/9780470172766.',
|
|
78
|
+
publishedSource: {
|
|
79
|
+
title: 'Theoretical Soil Mechanics',
|
|
80
|
+
authors: ['Karl Terzaghi'],
|
|
81
|
+
year: 1943,
|
|
82
|
+
publication: 'John Wiley & Sons',
|
|
83
|
+
doi: '10.1002/9780470172766',
|
|
84
|
+
url: 'https://doi.org/10.1002/9780470172766',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'biot-1941-three-dimensional-consolidation',
|
|
89
|
+
sourceType: 'published-source',
|
|
90
|
+
label: 'Biot three-dimensional consolidation theory source reference',
|
|
91
|
+
citation: 'Biot, M. A. (1941). General Theory of Three-Dimensional Consolidation. Journal of Applied Physics, 12(2), 155-164. doi:10.1063/1.1712886.',
|
|
92
|
+
publishedSource: {
|
|
93
|
+
title: 'General Theory of Three-Dimensional Consolidation',
|
|
94
|
+
authors: ['Maurice A. Biot'],
|
|
95
|
+
year: 1941,
|
|
96
|
+
publication: 'Journal of Applied Physics',
|
|
97
|
+
doi: '10.1063/1.1712886',
|
|
98
|
+
url: 'https://doi.org/10.1063/1.1712886',
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'opensees-drucker-prager-material',
|
|
103
|
+
sourceType: 'open-source-solver',
|
|
104
|
+
label: 'OpenSees Drucker-Prager material and triaxial example reference',
|
|
105
|
+
citation: 'OpenSees Documentation, Drucker Prager Material, nDMaterial DruckerPrager plane-strain/3D formulation and confined triaxial compression example.',
|
|
106
|
+
referenceSolver: {
|
|
107
|
+
name: 'OpenSees',
|
|
108
|
+
version: 'documentation-current',
|
|
109
|
+
vendor: 'OpenSees project',
|
|
110
|
+
analysisProcedure: 'Drucker-Prager material point and confined triaxial compression example',
|
|
111
|
+
elementType: 'nDMaterial DruckerPrager',
|
|
112
|
+
url: 'https://opensees.github.io/OpenSeesDocumentation/user/manual/material/ndMaterials/DruckerPrager.html',
|
|
113
|
+
retrievedAt: '2026-06-04',
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: 'opengeosys-hydro-mechanics-benchmarks',
|
|
118
|
+
sourceType: 'open-source-solver',
|
|
119
|
+
label: 'OpenGeoSys hydro-mechanics benchmark suite reference',
|
|
120
|
+
citation: 'OpenGeoSys stable documentation, Hydro Mechanics benchmark suite including consolidation, Mandel-Cryer, injection/production, and HM drainage excavation examples.',
|
|
121
|
+
referenceSolver: {
|
|
122
|
+
name: 'OpenGeoSys',
|
|
123
|
+
version: 'stable documentation',
|
|
124
|
+
vendor: 'OpenGeoSys project',
|
|
125
|
+
analysisProcedure: 'hydro-mechanics benchmark suite',
|
|
126
|
+
elementType: 'HM and LIE/HM finite elements',
|
|
127
|
+
url: 'https://www.opengeosys.org/docs/benchmarks/hydro-mechanics/',
|
|
128
|
+
retrievedAt: '2026-06-04',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: 'opengeosys-richards-flow-benchmarks',
|
|
133
|
+
sourceType: 'open-source-solver',
|
|
134
|
+
label: 'OpenGeoSys Richards flow benchmark reference',
|
|
135
|
+
citation: 'OpenGeoSys documentation, Richards Flow benchmark suite for saturated/unsaturated transient flow verification.',
|
|
136
|
+
referenceSolver: {
|
|
137
|
+
name: 'OpenGeoSys',
|
|
138
|
+
version: '6.x documentation',
|
|
139
|
+
vendor: 'OpenGeoSys project',
|
|
140
|
+
analysisProcedure: 'Richards flow benchmark',
|
|
141
|
+
elementType: 'Richards flow finite elements',
|
|
142
|
+
url: 'https://www.opengeosys.org/docs/benchmarks/richards-flow/',
|
|
143
|
+
retrievedAt: '2026-06-04',
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
];
|
|
71
147
|
function degToRad(degrees) {
|
|
72
148
|
return (degrees * Math.PI) / 180;
|
|
73
149
|
}
|
|
@@ -104,6 +180,9 @@ function hasReferenceSolverCitation(citation) {
|
|
|
104
180
|
isNonEmptyString(citation.name) &&
|
|
105
181
|
isNonEmptyString(citation.version);
|
|
106
182
|
}
|
|
183
|
+
function hasValidSha256(value) {
|
|
184
|
+
return typeof value === 'string' && /^[a-f0-9]{64}$/i.test(value);
|
|
185
|
+
}
|
|
107
186
|
function copyExternalBenchmarkReference(reference) {
|
|
108
187
|
return {
|
|
109
188
|
...reference,
|
|
@@ -128,11 +207,22 @@ function copyExternalBenchmarkQuantityRequirement(requirement) {
|
|
|
128
207
|
requiredReferenceSourceTypes: [...new Set(requirement.requiredReferenceSourceTypes)],
|
|
129
208
|
};
|
|
130
209
|
}
|
|
210
|
+
function copyExternalBenchmarkComparisonResult(result) {
|
|
211
|
+
return {
|
|
212
|
+
...result,
|
|
213
|
+
...(result.notes ? { notes: [...result.notes] } : {}),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
131
216
|
export function buildFemExternalBenchmarkAcceptanceContract(options = {}) {
|
|
132
|
-
const references = (options.references ??
|
|
217
|
+
const references = (options.references ?? DEFAULT_EXTERNAL_BENCHMARK_REFERENCES)
|
|
218
|
+
.map(copyExternalBenchmarkReference);
|
|
133
219
|
const requiredQuantities = (options.requiredQuantities ?? DEFAULT_EXTERNAL_BENCHMARK_REQUIRED_QUANTITIES)
|
|
134
220
|
.map(copyExternalBenchmarkQuantityRequirement);
|
|
221
|
+
const comparisonResults = (options.comparisonResults ?? [])
|
|
222
|
+
.map(copyExternalBenchmarkComparisonResult);
|
|
135
223
|
const blockerCodes = [];
|
|
224
|
+
const referenceById = new Map(references.map((reference) => [reference.id, reference]));
|
|
225
|
+
const quantityById = new Map(requiredQuantities.map((requirement) => [requirement.id, requirement]));
|
|
136
226
|
if (references.length === 0) {
|
|
137
227
|
blockerCodes.push('external-benchmark-reference-corpus-missing');
|
|
138
228
|
}
|
|
@@ -164,6 +254,9 @@ export function buildFemExternalBenchmarkAcceptanceContract(options = {}) {
|
|
|
164
254
|
const hasPublishedReference = references.some((reference) => reference.sourceType === 'published-source' &&
|
|
165
255
|
isNonEmptyString(reference.citation) &&
|
|
166
256
|
hasPublishedCitation(reference.publishedSource));
|
|
257
|
+
const hasCommercialReference = references.some((reference) => reference.sourceType === 'commercial-solver' &&
|
|
258
|
+
isNonEmptyString(reference.citation) &&
|
|
259
|
+
hasReferenceSolverCitation(reference.referenceSolver));
|
|
167
260
|
const hasReferenceSolver = references.some((reference) => (reference.sourceType === 'commercial-solver' || reference.sourceType === 'open-source-solver') &&
|
|
168
261
|
isNonEmptyString(reference.citation) &&
|
|
169
262
|
hasReferenceSolverCitation(reference.referenceSolver));
|
|
@@ -173,9 +266,15 @@ export function buildFemExternalBenchmarkAcceptanceContract(options = {}) {
|
|
|
173
266
|
if (!hasReferenceSolver) {
|
|
174
267
|
blockerCodes.push('external-benchmark-reference-solver-citation-missing');
|
|
175
268
|
}
|
|
269
|
+
if (!hasCommercialReference) {
|
|
270
|
+
blockerCodes.push('external-benchmark-commercial-solver-citation-missing');
|
|
271
|
+
}
|
|
176
272
|
if (requiredQuantities.length === 0) {
|
|
177
273
|
blockerCodes.push('external-benchmark-required-quantities-missing');
|
|
178
274
|
}
|
|
275
|
+
if (comparisonResults.length === 0) {
|
|
276
|
+
blockerCodes.push('external-benchmark-comparison-results-missing');
|
|
277
|
+
}
|
|
179
278
|
for (const [index, requirement] of requiredQuantities.entries()) {
|
|
180
279
|
const quantityCode = isNonEmptyString(requirement.id)
|
|
181
280
|
? requirement.id
|
|
@@ -208,25 +307,119 @@ export function buildFemExternalBenchmarkAcceptanceContract(options = {}) {
|
|
|
208
307
|
}
|
|
209
308
|
}
|
|
210
309
|
}
|
|
310
|
+
for (const [index, result] of comparisonResults.entries()) {
|
|
311
|
+
const resultCode = isNonEmptyString(result.id) ? result.id : String(index);
|
|
312
|
+
if (!isNonEmptyString(result.id)) {
|
|
313
|
+
blockerCodes.push(`external-benchmark.comparison-results.${index}.id-missing`);
|
|
314
|
+
}
|
|
315
|
+
if (!isNonEmptyString(result.quantityRequirementId) || !quantityById.has(result.quantityRequirementId)) {
|
|
316
|
+
blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.quantity-requirement-missing`);
|
|
317
|
+
}
|
|
318
|
+
if (!isNonEmptyString(result.referenceId) || !referenceById.has(result.referenceId)) {
|
|
319
|
+
blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.reference-missing`);
|
|
320
|
+
}
|
|
321
|
+
if (!isNonEmptyString(result.caseId)) {
|
|
322
|
+
blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.case-id-missing`);
|
|
323
|
+
}
|
|
324
|
+
if (!isNonEmptyString(result.quantity)) {
|
|
325
|
+
blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.quantity-missing`);
|
|
326
|
+
}
|
|
327
|
+
if (!isNonEmptyString(result.unit)) {
|
|
328
|
+
blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.unit-missing`);
|
|
329
|
+
}
|
|
330
|
+
if (!Number.isFinite(result.actual) || !Number.isFinite(result.expected)) {
|
|
331
|
+
blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.value-invalid`);
|
|
332
|
+
}
|
|
333
|
+
if (!Number.isFinite(result.tolerance) || result.tolerance < 0) {
|
|
334
|
+
blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.tolerance-invalid`);
|
|
335
|
+
}
|
|
336
|
+
if (result.toleranceType !== 'absolute' &&
|
|
337
|
+
result.toleranceType !== 'relative' &&
|
|
338
|
+
result.toleranceType !== 'absolute-or-relative') {
|
|
339
|
+
blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.tolerance-type-invalid`);
|
|
340
|
+
}
|
|
341
|
+
if (!hasValidSha256(result.evidenceHashSha256)) {
|
|
342
|
+
blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.evidence-hash-missing`);
|
|
343
|
+
}
|
|
344
|
+
const requirement = quantityById.get(result.quantityRequirementId);
|
|
345
|
+
if (requirement) {
|
|
346
|
+
if (result.quantity !== requirement.quantity) {
|
|
347
|
+
blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.quantity-mismatch`);
|
|
348
|
+
}
|
|
349
|
+
if (result.unit !== requirement.unit) {
|
|
350
|
+
blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.unit-mismatch`);
|
|
351
|
+
}
|
|
352
|
+
if (result.toleranceType !== requirement.toleranceType) {
|
|
353
|
+
blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.tolerance-type-mismatch`);
|
|
354
|
+
}
|
|
355
|
+
if (result.tolerance > requirement.tolerance) {
|
|
356
|
+
blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.tolerance-too-loose`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (Number.isFinite(result.actual) &&
|
|
360
|
+
Number.isFinite(result.expected) &&
|
|
361
|
+
Number.isFinite(result.tolerance) &&
|
|
362
|
+
result.tolerance >= 0) {
|
|
363
|
+
const acceptanceTolerance = requirement?.tolerance ?? result.tolerance;
|
|
364
|
+
const acceptanceToleranceType = requirement?.toleranceType ?? result.toleranceType;
|
|
365
|
+
const acceptanceUnit = requirement?.unit ?? result.unit;
|
|
366
|
+
const tolerance = evaluateFemTolerance(requirement?.quantity ?? result.quantity, result.actual, result.expected, acceptanceToleranceType === 'relative' ? 0 : acceptanceTolerance, acceptanceToleranceType === 'absolute'
|
|
367
|
+
? { unit: acceptanceUnit }
|
|
368
|
+
: { relativeTolerance: acceptanceTolerance, unit: acceptanceUnit });
|
|
369
|
+
if (!result.accepted || !tolerance.accepted) {
|
|
370
|
+
blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.not-accepted`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
for (const requirement of requiredQuantities) {
|
|
375
|
+
if (!isNonEmptyString(requirement.id))
|
|
376
|
+
continue;
|
|
377
|
+
for (const sourceType of requirement.requiredReferenceSourceTypes) {
|
|
378
|
+
const hasAcceptedComparison = comparisonResults.some((result) => {
|
|
379
|
+
const reference = referenceById.get(result.referenceId);
|
|
380
|
+
if (!reference || reference.sourceType !== sourceType)
|
|
381
|
+
return false;
|
|
382
|
+
if (result.quantityRequirementId !== requirement.id || !result.accepted)
|
|
383
|
+
return false;
|
|
384
|
+
if (!Number.isFinite(result.actual) || !Number.isFinite(result.expected))
|
|
385
|
+
return false;
|
|
386
|
+
if (!hasValidSha256(result.evidenceHashSha256))
|
|
387
|
+
return false;
|
|
388
|
+
if (result.quantity !== requirement.quantity || result.unit !== requirement.unit)
|
|
389
|
+
return false;
|
|
390
|
+
if (result.toleranceType !== requirement.toleranceType || result.tolerance > requirement.tolerance) {
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
const tolerance = evaluateFemTolerance(requirement.quantity, result.actual, result.expected, requirement.toleranceType === 'relative' ? 0 : requirement.tolerance, requirement.toleranceType === 'absolute'
|
|
394
|
+
? { unit: requirement.unit }
|
|
395
|
+
: { relativeTolerance: requirement.tolerance, unit: requirement.unit });
|
|
396
|
+
return tolerance.accepted;
|
|
397
|
+
});
|
|
398
|
+
if (!hasAcceptedComparison) {
|
|
399
|
+
blockerCodes.push(`external-benchmark.required-quantities.${requirement.id}.accepted-comparison-missing.${sourceType}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
211
403
|
const uniqueBlockerCodes = [...new Set(blockerCodes)];
|
|
212
404
|
const productionReadinessBlocked = uniqueBlockerCodes.length > 0;
|
|
213
405
|
return {
|
|
214
|
-
schemaVersion: 'fem-external-benchmark-acceptance
|
|
215
|
-
status: productionReadinessBlocked ? 'blocked' : '
|
|
406
|
+
schemaVersion: 'fem-external-benchmark-acceptance.v2',
|
|
407
|
+
status: productionReadinessBlocked ? 'blocked' : 'accepted-comparisons-ready',
|
|
216
408
|
productionReadinessBlocked,
|
|
217
409
|
requiredSourceTypes: [...REQUIRED_EXTERNAL_BENCHMARK_SOURCE_TYPES],
|
|
218
410
|
references,
|
|
219
411
|
requiredQuantities,
|
|
412
|
+
comparisonResults,
|
|
220
413
|
blockerCodes: uniqueBlockerCodes,
|
|
221
414
|
acceptanceStatement: productionReadinessBlocked
|
|
222
|
-
? 'External benchmark
|
|
223
|
-
: 'External benchmark
|
|
415
|
+
? 'External benchmark acceptance is incomplete; production readiness remains blocked until source citations, commercial solver references, and accepted comparison results cover every required FEM quantity.'
|
|
416
|
+
: 'External benchmark references and comparison results cover the required FEM quantities; this still does not approve production design without solver-route and reviewer-workflow acceptance.',
|
|
224
417
|
};
|
|
225
418
|
}
|
|
226
419
|
export function evaluateFemTolerance(quantity, actual, expected, absoluteTolerance, options = {}) {
|
|
227
420
|
const error = Math.abs(actual - expected);
|
|
228
421
|
const relativeError = Math.abs(expected) > 0 ? error / Math.abs(expected) : error;
|
|
229
|
-
const relativeAccepted = options.relativeTolerance
|
|
422
|
+
const relativeAccepted = options.relativeTolerance != null && relativeError <= options.relativeTolerance;
|
|
230
423
|
const accepted = error <= absoluteTolerance || relativeAccepted;
|
|
231
424
|
return {
|
|
232
425
|
quantity,
|
|
@@ -1133,6 +1326,65 @@ export function runFemEngineeringEvidenceSuite(policy = DEFAULT_FEM_CONVERGENCE_
|
|
|
1133
1326
|
loadStepFractions: [0.25, 0.5, 0.75, 1],
|
|
1134
1327
|
});
|
|
1135
1328
|
benchmarks.push(benchmark('quad4-plane-strain-dp-collapse-detection', 'coupled-nonlinear-plane-strain', 'internal-balance', 'collapseDetected', dpCollapse.converged ? 0 : 1, 1, 0, 'Low-strength/high-load plane-strain fixture must report nonconvergence instead of a clean accepted nonlinear solve.'));
|
|
1329
|
+
const adaptiveCutbackPolicy = {
|
|
1330
|
+
...policy,
|
|
1331
|
+
maxIterations: Math.min(policy.maxIterations, 8),
|
|
1332
|
+
minAcceptedSteps: 1,
|
|
1333
|
+
};
|
|
1334
|
+
const dpAdaptiveDirect = runPlaneStrainDruckerPragerLoadSteps({
|
|
1335
|
+
schemaVersion: 'fem-plane-strain-model.v1',
|
|
1336
|
+
nodes: dpElasticMesh.nodes,
|
|
1337
|
+
elements: dpElasticMesh.elements,
|
|
1338
|
+
materials: [{
|
|
1339
|
+
id: 'soil',
|
|
1340
|
+
elasticModulusKpa: 25_000,
|
|
1341
|
+
poissonRatio: 0.28,
|
|
1342
|
+
frictionAngleDeg: 32,
|
|
1343
|
+
cohesionKpa: 5,
|
|
1344
|
+
dilationAngleDeg: 0,
|
|
1345
|
+
}],
|
|
1346
|
+
boundaryConditions: dpElasticBottomNodes.flatMap((node) => [
|
|
1347
|
+
{ nodeId: node.id, dof: 'ux' },
|
|
1348
|
+
{ nodeId: node.id, dof: 'uy' },
|
|
1349
|
+
]),
|
|
1350
|
+
nodalLoads: dpElasticTopNodes.map((node) => ({ nodeId: node.id, fyKn: -30 })),
|
|
1351
|
+
policy: adaptiveCutbackPolicy,
|
|
1352
|
+
}, {
|
|
1353
|
+
loadStepFractions: [1],
|
|
1354
|
+
});
|
|
1355
|
+
const dpAdaptiveRecovered = runPlaneStrainDruckerPragerLoadSteps({
|
|
1356
|
+
schemaVersion: 'fem-plane-strain-model.v1',
|
|
1357
|
+
nodes: dpElasticMesh.nodes,
|
|
1358
|
+
elements: dpElasticMesh.elements,
|
|
1359
|
+
materials: [{
|
|
1360
|
+
id: 'soil',
|
|
1361
|
+
elasticModulusKpa: 25_000,
|
|
1362
|
+
poissonRatio: 0.28,
|
|
1363
|
+
frictionAngleDeg: 32,
|
|
1364
|
+
cohesionKpa: 5,
|
|
1365
|
+
dilationAngleDeg: 0,
|
|
1366
|
+
}],
|
|
1367
|
+
boundaryConditions: dpElasticBottomNodes.flatMap((node) => [
|
|
1368
|
+
{ nodeId: node.id, dof: 'ux' },
|
|
1369
|
+
{ nodeId: node.id, dof: 'uy' },
|
|
1370
|
+
]),
|
|
1371
|
+
nodalLoads: dpElasticTopNodes.map((node) => ({ nodeId: node.id, fyKn: -30 })),
|
|
1372
|
+
policy: adaptiveCutbackPolicy,
|
|
1373
|
+
}, {
|
|
1374
|
+
loadStepFractions: [1],
|
|
1375
|
+
adaptiveLoadStepping: {
|
|
1376
|
+
minLoadFactorIncrement: 1 / 64,
|
|
1377
|
+
maxCutbacks: 20,
|
|
1378
|
+
},
|
|
1379
|
+
});
|
|
1380
|
+
const adaptiveRollbackAccepted = dpAdaptiveRecovered.converged &&
|
|
1381
|
+
!dpAdaptiveDirect.converged &&
|
|
1382
|
+
dpAdaptiveRecovered.adaptiveLoadStepping.cutbackCount > 0 &&
|
|
1383
|
+
dpAdaptiveRecovered.adaptiveLoadStepping.attempts.some((attempt) => !attempt.accepted &&
|
|
1384
|
+
attempt.rollbackApplied &&
|
|
1385
|
+
attempt.committedStateSignatureAfter === attempt.committedStateSignatureBefore) &&
|
|
1386
|
+
dpAdaptiveRecovered.adaptiveLoadStepping.acceptedLoadFactors.at(-1) === 1;
|
|
1387
|
+
benchmarks.push(benchmark('quad4-plane-strain-dp-adaptive-cutback-rollback-recovery', 'solver-convergence-and-tolerance', 'internal-balance', 'adaptiveCutbackRollbackAccepted', adaptiveRollbackAccepted ? 1 : 0, 1, 0, 'Adaptive cutback-bisection load stepping must recover a nonlinear Drucker-Prager plane-strain solve that fails as one full increment, while rejected attempts leave committed Gauss-point state unchanged.'));
|
|
1136
1388
|
const finalTimeYears = 0.197 * 25;
|
|
1137
1389
|
const consolidationTimes = Array.from({ length: 80 }, (_, index) => finalTimeYears * ((index + 1) / 80));
|
|
1138
1390
|
const consolidation = runTerzaghiConsolidationTimeStepper({
|
|
@@ -1244,6 +1496,14 @@ export function runFemEngineeringEvidenceSuite(policy = DEFAULT_FEM_CONVERGENCE_
|
|
|
1244
1496
|
benchmarks.push(benchmark('quad4-plane-strain-biot-u-p-effective-stress-coupling', 'seepage-pore-pressure-coupling', 'internal-balance', 'effectiveMinusTotalStressError', biotStressSignError, 0, 1e-7, 'Biot u-p evidence kernel must apply positive pore pressure as total-stress reduction in the tension-positive plane-strain convention.', 'kPa'));
|
|
1245
1497
|
benchmarks.push(benchmark('quad4-plane-strain-biot-u-p-free-residual', 'solver-convergence-and-tolerance', 'internal-balance', 'residualNormRatio', biot.residualNormRatio, 0, policy.forceBalanceTolerance, 'Biot u-p evidence kernel must satisfy the coupled mechanical free-DOF residual policy.'));
|
|
1246
1498
|
benchmarks.push(benchmark('quad4-plane-strain-biot-u-p-mass-residual', 'coupled-biot-plane-strain', 'internal-balance', 'massBalanceErrorRatio', biot.massBalanceErrorRatio, 0, policy.porePressureMassBalanceTolerance, 'Biot u-p evidence kernel must satisfy the free pore-pressure residual policy for the backward-Euler pressure equation.'));
|
|
1499
|
+
benchmarks.push(benchmark('quad4-plane-strain-biot-u-p-transient-acceptance-policy', 'solver-convergence-and-tolerance', 'internal-balance', 'transientAcceptance', biot.transientAcceptance.accepted &&
|
|
1500
|
+
biot.transientAcceptance.acceptedStepCount >= policy.minAcceptedSteps &&
|
|
1501
|
+
biot.transientAcceptance.dissipationCheckMode === 'prescribed-gradient-relaxation' &&
|
|
1502
|
+
biot.transientAcceptance.maxResidualNormRatio <= policy.forceBalanceTolerance &&
|
|
1503
|
+
biot.transientAcceptance.maxMassBalanceErrorRatio <= policy.porePressureMassBalanceTolerance &&
|
|
1504
|
+
biot.transientAcceptance.maxPressureOvershootKpa === 0
|
|
1505
|
+
? 1
|
|
1506
|
+
: 0, 1, 0, 'Biot u-p evidence kernel must report an aggregate transient acceptance audit for residual, mass-balance, pressure-envelope, and prescribed-gradient relaxation checks.'));
|
|
1247
1507
|
const biotPatchMesh = buildPlaneStrainRectangularMesh({
|
|
1248
1508
|
widthM: 2,
|
|
1249
1509
|
heightM: 1,
|
|
@@ -1371,6 +1631,13 @@ export function runFemEngineeringEvidenceSuite(policy = DEFAULT_FEM_CONVERGENCE_
|
|
|
1371
1631
|
}, 0) / biotTerzaghiVolumeM3;
|
|
1372
1632
|
const biotTerzaghiDegreeOfConsolidation = 1 - (biotTerzaghiAveragePressureKpa / biotTerzaghiInitialPressureKpa);
|
|
1373
1633
|
benchmarks.push(benchmark('quad4-plane-strain-biot-u-p-terzaghi-pressure-dissipation', 'consolidation', 'closed-form', 'degreeOfConsolidation', biotTerzaghiDegreeOfConsolidation, terzaghiAverageConsolidation(biotTerzaghiTimeFactor), 0.005, 'Alpha-zero Quad4 Biot pressure diffusion must match Terzaghi average consolidation at Tv = 0.197 for a top-drained column.'));
|
|
1634
|
+
benchmarks.push(benchmark('quad4-plane-strain-biot-u-p-drained-dissipation-acceptance', 'solver-convergence-and-tolerance', 'internal-balance', 'drainedDissipationAccepted', biotTerzaghi.transientAcceptance.accepted &&
|
|
1635
|
+
biotTerzaghi.transientAcceptance.dissipationCheckMode === 'drained-dissipation' &&
|
|
1636
|
+
biotTerzaghi.transientAcceptance.monotonicAverageFreePressureDissipationRequired &&
|
|
1637
|
+
biotTerzaghi.transientAcceptance.monotonicAverageFreePressureDissipation &&
|
|
1638
|
+
biotTerzaghi.transientAcceptance.monotonicMaxPressureEnvelope
|
|
1639
|
+
? 1
|
|
1640
|
+
: 0, 1, 0, 'Top-drained alpha-zero Biot Terzaghi fixture must pass the stricter drained-dissipation transient acceptance gate.'));
|
|
1374
1641
|
const coupling = runHydroMechanicalCoupling1D({
|
|
1375
1642
|
totalVerticalStressKpa: 200,
|
|
1376
1643
|
porePressureBeforeKpa: 80,
|
|
@@ -1401,6 +1668,87 @@ export function runFemEngineeringEvidenceSuite(policy = DEFAULT_FEM_CONVERGENCE_
|
|
|
1401
1668
|
support.productionBlockers.includes('jurisdiction-specific-wall-strut-anchor-structural-design-not-implemented')
|
|
1402
1669
|
? 1
|
|
1403
1670
|
: 0, 1, 0, 'Support screening must tie support reaction demand to staged excavation depths while preserving jurisdiction-specific structural design as a production blocker.'));
|
|
1671
|
+
const supportMember = runFemSupportMemberDesignCheck({
|
|
1672
|
+
schemaVersion: 'fem-support-member-design-input.v1',
|
|
1673
|
+
units: {
|
|
1674
|
+
length: 'm',
|
|
1675
|
+
force: 'kN',
|
|
1676
|
+
stress: 'MPa',
|
|
1677
|
+
area: 'm2',
|
|
1678
|
+
moment: 'kN-m',
|
|
1679
|
+
momentOfInertia: 'm4',
|
|
1680
|
+
sectionModulus: 'm3',
|
|
1681
|
+
utilization: 'ratio',
|
|
1682
|
+
},
|
|
1683
|
+
member: {
|
|
1684
|
+
id: 'support-strut-fixture',
|
|
1685
|
+
kind: 'strut',
|
|
1686
|
+
label: 'Benchmark excavation strut fixture',
|
|
1687
|
+
sectionLabel: 'Reviewed circular hollow strut proxy',
|
|
1688
|
+
unbracedLengthM: 4,
|
|
1689
|
+
effectiveLengthFactor: 1,
|
|
1690
|
+
areaM2: 0.015,
|
|
1691
|
+
weakAxisMomentOfInertiaM4: 1.2e-4,
|
|
1692
|
+
sectionModulusM3: 0.0012,
|
|
1693
|
+
yieldStrengthMpa: 250,
|
|
1694
|
+
elasticModulusMpa: 200_000,
|
|
1695
|
+
},
|
|
1696
|
+
demand: {
|
|
1697
|
+
axialCompressionDemandKn: 900,
|
|
1698
|
+
bendingMomentDemandKnM: 40,
|
|
1699
|
+
source: {
|
|
1700
|
+
source: 'support-reaction-screening',
|
|
1701
|
+
loadCombination: 'temporary-support-envelope',
|
|
1702
|
+
description: 'Fixture support reaction demand from deterministic excavation screening envelope.',
|
|
1703
|
+
caseId: 'support-fixture',
|
|
1704
|
+
stageId: 'stage-2',
|
|
1705
|
+
resultHashSha256: 'd'.repeat(64),
|
|
1706
|
+
},
|
|
1707
|
+
},
|
|
1708
|
+
factors: {
|
|
1709
|
+
demandFactor: 1.2,
|
|
1710
|
+
resistanceFactorCompression: 0.9,
|
|
1711
|
+
resistanceFactorFlexure: 0.9,
|
|
1712
|
+
maximumSlendernessRatio: 160,
|
|
1713
|
+
},
|
|
1714
|
+
review: {
|
|
1715
|
+
schemaVersion: 'fem-support-design-review-metadata.v1',
|
|
1716
|
+
reviewer: {
|
|
1717
|
+
name: 'Licensed Reviewer',
|
|
1718
|
+
licenseId: 'PE-12345',
|
|
1719
|
+
jurisdiction: 'US-CA',
|
|
1720
|
+
},
|
|
1721
|
+
reviewedAt: '2026-06-04T00:00:00.000Z',
|
|
1722
|
+
assumptions: [
|
|
1723
|
+
{
|
|
1724
|
+
id: 'support-effective-length',
|
|
1725
|
+
parameter: 'effective length factor',
|
|
1726
|
+
value: 1,
|
|
1727
|
+
unit: 'ratio',
|
|
1728
|
+
basis: 'Pinned temporary works end-restraint assumption for deterministic benchmark fixture.',
|
|
1729
|
+
confidence: 'review',
|
|
1730
|
+
reviewRequired: true,
|
|
1731
|
+
},
|
|
1732
|
+
{
|
|
1733
|
+
id: 'support-demand-source',
|
|
1734
|
+
parameter: 'support demand source',
|
|
1735
|
+
value: 'support-reaction-screening',
|
|
1736
|
+
basis: 'Demand is supplied explicitly from a reviewed support reaction screening envelope.',
|
|
1737
|
+
confidence: 'review',
|
|
1738
|
+
reviewRequired: true,
|
|
1739
|
+
},
|
|
1740
|
+
],
|
|
1741
|
+
limitations: ['Connection, local buckling, and jurisdiction-specific code checks remain outside this benchmark fixture.'],
|
|
1742
|
+
},
|
|
1743
|
+
policy,
|
|
1744
|
+
});
|
|
1745
|
+
benchmarks.push(benchmark('support-member-yield-buckling-interaction', 'support-design', 'internal-balance', 'supportMemberAccepted', supportMember.status === 'accepted' &&
|
|
1746
|
+
supportMember.productionClaim === false &&
|
|
1747
|
+
supportMember.limitStates.every((limitState) => limitState.status === 'accepted') &&
|
|
1748
|
+
supportMember.controllingLimitState.id === 'combined-axial-flexure' &&
|
|
1749
|
+
supportMember.review.reviewer.licenseId.length > 0
|
|
1750
|
+
? 1
|
|
1751
|
+
: 0, 1, 0, 'Support member fixture must pass deterministic axial yield, Euler buckling, flexural yield, slenderness, and combined utilization checks with reviewer metadata.'));
|
|
1404
1752
|
const reviewerValidation = validateFemReviewerApprovalRecord({
|
|
1405
1753
|
schemaVersion: 'fem-reviewer-approval.v1',
|
|
1406
1754
|
recordId: 'review-fem-001',
|