@geotechcli/core 0.4.123 → 0.4.124
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/fem-tools.js +43 -4
- package/dist/agents/fem-tools.js.map +1 -1
- package/dist/agents/tool-normalization.d.ts.map +1 -1
- package/dist/agents/tool-normalization.js +109 -0
- package/dist/agents/tool-normalization.js.map +1 -1
- package/dist/fem/index.d.ts +1 -1
- package/dist/fem/index.d.ts.map +1 -1
- package/dist/fem/index.js +1 -1
- package/dist/fem/index.js.map +1 -1
- package/dist/fem/nonlinear-plane-strain-solver.d.ts +6 -0
- package/dist/fem/nonlinear-plane-strain-solver.d.ts.map +1 -1
- package/dist/fem/nonlinear-plane-strain-solver.js +251 -3
- package/dist/fem/nonlinear-plane-strain-solver.js.map +1 -1
- package/dist/fem/routing.d.ts +1 -1
- package/dist/fem/routing.d.ts.map +1 -1
- package/dist/fem/routing.js +130 -3
- package/dist/fem/routing.js.map +1 -1
- package/dist/fem/types.d.ts +19 -1
- package/dist/fem/types.d.ts.map +1 -1
- package/dist/fem/validation.d.ts.map +1 -1
- package/dist/fem/validation.js +271 -10
- package/dist/fem/validation.js.map +1 -1
- package/dist/meta/metadata.json +1 -1
- package/package.json +1 -1
package/dist/fem/validation.js
CHANGED
|
@@ -22,6 +22,7 @@ const FEM_MAX_PREVIEW_MESH_NODES = FEM_WEBGL_UINT16_INDEX_LIMIT + 1;
|
|
|
22
22
|
const FEM_MIN_BIOT_TRANSIENT_STEPS = 3;
|
|
23
23
|
const FEM_MAX_BIOT_TIME_STEP_GROWTH_RATIO = 8;
|
|
24
24
|
const FEM_PLANE_STRAIN_DP_ADAPTIVE_BACKEND_ID = 'builtin-plane-strain-dp-adaptive-v0';
|
|
25
|
+
const FEM_PLANE_STRAIN_DP_BIOT_REPLAY_BACKEND_ID = 'builtin-plane-strain-dp-biot-replay-v0';
|
|
25
26
|
const FEM_PLANE_STRAIN_DP_ANALYSIS_TYPE = 'static_2d_plane_strain_drucker_prager';
|
|
26
27
|
function expectedMeshCounts(mesh) {
|
|
27
28
|
if (mesh.elementType === 'quad4_plane_strain') {
|
|
@@ -719,7 +720,9 @@ export function validateFemAnalysisCase(caseFile) {
|
|
|
719
720
|
findings.push(finding('review', 'stages.final-depth-review', 'Last excavation stage does not exactly match the final depth; staging requires review.'));
|
|
720
721
|
}
|
|
721
722
|
findings.push(isPlaneStrainDruckerPragerAnalysis
|
|
722
|
-
?
|
|
723
|
+
? biot
|
|
724
|
+
? finding('review', 'excavation.plane-strain-dp-biot-replay-preview', 'Excavation preview uses experimental plane-strain Drucker-Prager plasticity with sequential Biot pressure replay; it still excludes retaining wall design, basal heave, monolithic hydro-mechanical coupling, and production design acceptance.')
|
|
725
|
+
: finding('review', 'excavation.plane-strain-dp-preview', 'Excavation preview uses experimental plane-strain Drucker-Prager plasticity and still excludes retaining wall design, basal heave, seepage, consolidation, and production design acceptance.')
|
|
723
726
|
: finding('review', 'excavation.design-excluded', 'Excavation preview excludes retaining wall design, basal heave, seepage, consolidation, and nonlinear soil response.'));
|
|
724
727
|
}
|
|
725
728
|
}
|
|
@@ -799,7 +802,9 @@ export function validateFemAnalysisCase(caseFile) {
|
|
|
799
802
|
findings.push(finding('review', 'consolidation.1d-preview', 'Staged consolidation preview uses a 1D Terzaghi column and Mohr-Coulomb material-point review gate; it is not a full 2D/3D coupled FEM consolidation solver.'));
|
|
800
803
|
}
|
|
801
804
|
}
|
|
802
|
-
|
|
805
|
+
const validatesBiotPressureSourceGeometry = caseFile.objective === 'seepage_groundwater_coupling' ||
|
|
806
|
+
(caseFile.objective === 'excavation_deformation' && isPlaneStrainDruckerPragerAnalysis && biot != null);
|
|
807
|
+
if (validatesBiotPressureSourceGeometry) {
|
|
803
808
|
if (!biot) {
|
|
804
809
|
findings.push(finding('blocker', 'geometry.biot-missing', 'Seepage/groundwater coupling cases require plane-strain Biot geometry.'));
|
|
805
810
|
}
|
|
@@ -865,7 +870,9 @@ export function validateFemAnalysisCase(caseFile) {
|
|
|
865
870
|
pushFiniteNumberFinding(findings, boundary.porePressureKpa, `${prefix}.pore-pressure`, 'Prescribed pore pressure', { nonNegative: true });
|
|
866
871
|
}
|
|
867
872
|
}
|
|
868
|
-
findings.push(
|
|
873
|
+
findings.push(caseFile.objective === 'seepage_groundwater_coupling'
|
|
874
|
+
? finding('review', 'biot.up-preview-only', 'Seepage/groundwater coupling uses an experimental deterministic 2D Biot u-p preview and is not production design evidence.')
|
|
875
|
+
: finding('review', 'biot.pressure-replay-source-preview-only', 'Plane-strain Drucker-Prager excavation uses Biot geometry only as an experimental sequential pressure-replay source; this is not monolithic hydro-mechanical production evidence.'));
|
|
869
876
|
}
|
|
870
877
|
}
|
|
871
878
|
if (caseFile.materials.length === 0) {
|
|
@@ -923,8 +930,10 @@ export function validateFemAnalysisCase(caseFile) {
|
|
|
923
930
|
}
|
|
924
931
|
pushFiniteNumberFinding(findings, material.cohesionKpa, `${prefix}.plane-strain-dp-cohesion`, 'Plane-strain Drucker-Prager cohesion', { nonNegative: true });
|
|
925
932
|
}
|
|
926
|
-
|
|
927
|
-
|
|
933
|
+
const requiresBiotMaterial = caseFile.objective === 'seepage_groundwater_coupling' ||
|
|
934
|
+
(isPlaneStrainDruckerPragerAnalysis && biot != null);
|
|
935
|
+
if (requiresBiotMaterial) {
|
|
936
|
+
if (caseFile.objective === 'seepage_groundwater_coupling' && material.model !== 'linear_elastic') {
|
|
928
937
|
findings.push(finding('blocker', `${prefix}.biot-model-required`, 'Biot u-p preview requires a linear_elastic material with hydraulic coupling parameters.'));
|
|
929
938
|
}
|
|
930
939
|
const hasHydraulicX = material.hydraulicConductivityXMPerS != null || material.hydraulicConductivityMPerS != null;
|
|
@@ -1526,6 +1535,249 @@ function validatePlaneStrainDpAdaptiveAcceptance(findings, manifest, solverToler
|
|
|
1526
1535
|
findings.push(finding('blocker', 'result.envelope.dp.yield-residual-too-large', 'DP yield residual exceeds the material return-map tolerance.'));
|
|
1527
1536
|
}
|
|
1528
1537
|
}
|
|
1538
|
+
function validatePressureAuditEnvelope(findings, manifest, context) {
|
|
1539
|
+
const { envelope } = manifest;
|
|
1540
|
+
const pressureAudit = manifest.pressureAudit;
|
|
1541
|
+
const codePrefix = context === 'biot' ? 'result.pressure-audit' : 'result.dp-biot-replay.pressure-audit';
|
|
1542
|
+
if (!pressureAudit) {
|
|
1543
|
+
findings.push(finding('blocker', `${codePrefix}.missing`, context === 'biot'
|
|
1544
|
+
? 'Biot result manifests must include a pressure audit.'
|
|
1545
|
+
: 'DP Biot pressure-replay manifests must include the upstream Biot pressure audit.'));
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
const pressureAuditEntries = [
|
|
1549
|
+
['freePorePressureResidualL1M3PerS', 'free-pore-pressure-residual-l1', 'Free pore-pressure residual L1 norm'],
|
|
1550
|
+
['prescribedPorePressureResidualL1M3PerS', 'prescribed-pore-pressure-residual-l1', 'Prescribed pore-pressure residual L1 norm'],
|
|
1551
|
+
['netPrescribedPressureBoundaryFlowM3PerS', 'net-prescribed-pressure-boundary-flow', 'Net prescribed pressure boundary flow'],
|
|
1552
|
+
['appliedNodalFluxSumM3PerS', 'applied-nodal-flux-sum', 'Applied nodal flux sum'],
|
|
1553
|
+
['storageRateSumM3PerS', 'storage-rate-sum', 'Storage-rate sum'],
|
|
1554
|
+
['couplingRateSumM3PerS', 'coupling-rate-sum', 'Coupling-rate sum'],
|
|
1555
|
+
['darcyFlowRateSumM3PerS', 'darcy-flow-rate-sum', 'Darcy flow-rate sum'],
|
|
1556
|
+
];
|
|
1557
|
+
for (const [key, code, label] of pressureAuditEntries) {
|
|
1558
|
+
pushFiniteNumberFinding(findings, pressureAudit[key], `${codePrefix}.${code}`, label);
|
|
1559
|
+
}
|
|
1560
|
+
pushApproximateMatchFinding(findings, pressureAudit.freePorePressureResidualL1M3PerS, envelope.freePorePressureResidualL1M3PerS, `${codePrefix}.free-residual-envelope-mismatch`, 'Pressure-audit free residual', 1e-12);
|
|
1561
|
+
pushApproximateMatchFinding(findings, pressureAudit.prescribedPorePressureResidualL1M3PerS, envelope.prescribedPorePressureResidualL1M3PerS, `${codePrefix}.prescribed-residual-envelope-mismatch`, 'Pressure-audit prescribed residual', 1e-12);
|
|
1562
|
+
}
|
|
1563
|
+
function validateBiotTransientAcceptanceEnvelope(findings, manifest, options) {
|
|
1564
|
+
const { envelope } = manifest;
|
|
1565
|
+
const transientAcceptance = manifest.biotTransientAcceptance;
|
|
1566
|
+
const codePrefix = options.context === 'biot'
|
|
1567
|
+
? 'result.biot-transient-acceptance'
|
|
1568
|
+
: 'result.dp-biot-replay.biot-transient-acceptance';
|
|
1569
|
+
if (!transientAcceptance) {
|
|
1570
|
+
findings.push(finding('blocker', `${codePrefix}.missing`, options.context === 'biot'
|
|
1571
|
+
? 'Biot result manifests must include transient acceptance metadata.'
|
|
1572
|
+
: 'DP Biot pressure-replay manifests must include upstream Biot transient acceptance metadata.'));
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
if (transientAcceptance.schemaVersion !== 'fem-plane-strain-biot-transient-acceptance.v1') {
|
|
1576
|
+
findings.push(finding('blocker', `${codePrefix}.schema.unsupported`, 'Unsupported Biot transient acceptance schema.'));
|
|
1577
|
+
}
|
|
1578
|
+
if (transientAcceptance.accepted !== true) {
|
|
1579
|
+
findings.push(finding('blocker', `${codePrefix}.not-accepted`, 'Biot transient acceptance must be accepted before publishing a preview manifest.'));
|
|
1580
|
+
}
|
|
1581
|
+
if (transientAcceptance.dissipationCheckMode !== 'drained-dissipation' &&
|
|
1582
|
+
transientAcceptance.dissipationCheckMode !== 'prescribed-gradient-relaxation' &&
|
|
1583
|
+
transientAcceptance.dissipationCheckMode !== 'load-generated-consolidation') {
|
|
1584
|
+
findings.push(finding('blocker', `${codePrefix}.mode.invalid`, 'Biot transient acceptance mode is invalid.'));
|
|
1585
|
+
}
|
|
1586
|
+
const pressureEnvelopeMode = transientAcceptance.pressureEnvelopeMode ??
|
|
1587
|
+
(transientAcceptance.dissipationCheckMode === 'load-generated-consolidation'
|
|
1588
|
+
? 'load-generated-positive-pressure'
|
|
1589
|
+
: 'initial-prescribed-bound');
|
|
1590
|
+
if (pressureEnvelopeMode !== 'initial-prescribed-bound' &&
|
|
1591
|
+
pressureEnvelopeMode !== 'load-generated-positive-pressure') {
|
|
1592
|
+
findings.push(finding('blocker', `${codePrefix}.pressure-envelope-mode.invalid`, 'Biot transient pressure envelope mode is invalid.'));
|
|
1593
|
+
}
|
|
1594
|
+
const acceptedStepCountOk = pushFiniteNumberFinding(findings, transientAcceptance.acceptedStepCount, `${codePrefix}.accepted-step-count`, 'Biot transient accepted step count', { positive: true });
|
|
1595
|
+
const requiredStepCountOk = pushFiniteNumberFinding(findings, transientAcceptance.requiredStepCount, `${codePrefix}.required-step-count`, 'Biot transient required step count', { positive: true });
|
|
1596
|
+
const maxResidualOk = pushFiniteNumberFinding(findings, transientAcceptance.maxResidualNormRatio, `${codePrefix}.max-residual-ratio`, 'Biot transient maximum residual ratio', { nonNegative: true });
|
|
1597
|
+
const maxMassBalanceOk = pushFiniteNumberFinding(findings, transientAcceptance.maxMassBalanceErrorRatio, `${codePrefix}.max-mass-balance-ratio`, 'Biot transient maximum mass-balance ratio', { nonNegative: true });
|
|
1598
|
+
pushFiniteNumberFinding(findings, transientAcceptance.maxPressureOvershootKpa, `${codePrefix}.max-pressure-overshoot`, 'Biot transient maximum pressure overshoot', { nonNegative: true });
|
|
1599
|
+
const finalDissipationOk = pushFiniteNumberFinding(findings, transientAcceptance.finalPorePressureDissipationRatio, `${codePrefix}.final-dissipation-ratio`, 'Biot transient final pore-pressure dissipation ratio', { nonNegative: true });
|
|
1600
|
+
if (acceptedStepCountOk && isFiniteNumber(envelope.timeStepCount) && transientAcceptance.acceptedStepCount !== envelope.timeStepCount) {
|
|
1601
|
+
findings.push(finding('blocker', `${codePrefix}.accepted-step-count-mismatch`, 'Biot accepted step count must match the envelope time-step count.'));
|
|
1602
|
+
}
|
|
1603
|
+
if (acceptedStepCountOk &&
|
|
1604
|
+
options.expectedAcceptedStepCount != null &&
|
|
1605
|
+
transientAcceptance.acceptedStepCount !== options.expectedAcceptedStepCount) {
|
|
1606
|
+
findings.push(finding('blocker', `${codePrefix}.accepted-step-count-audit-mismatch`, 'Biot accepted step count must match the pressure-replay audit source step count.'));
|
|
1607
|
+
}
|
|
1608
|
+
if (requiredStepCountOk && transientAcceptance.requiredStepCount < FEM_MIN_BIOT_TRANSIENT_STEPS) {
|
|
1609
|
+
findings.push(finding('blocker', `${codePrefix}.required-step-count-too-small`, 'Biot required transient step count is below the preview policy.'));
|
|
1610
|
+
}
|
|
1611
|
+
if (acceptedStepCountOk && requiredStepCountOk && transientAcceptance.acceptedStepCount < transientAcceptance.requiredStepCount) {
|
|
1612
|
+
findings.push(finding('blocker', `${codePrefix}.accepted-step-count-too-small`, 'Biot accepted step count is below the required transient step count.'));
|
|
1613
|
+
}
|
|
1614
|
+
if (maxResidualOk && transientAcceptance.maxResidualNormRatio > 1e-3) {
|
|
1615
|
+
findings.push(finding('blocker', `${codePrefix}.max-residual-too-large`, 'Biot transient maximum residual ratio exceeds the preview tolerance.'));
|
|
1616
|
+
}
|
|
1617
|
+
if (maxMassBalanceOk && transientAcceptance.maxMassBalanceErrorRatio > 1e-3) {
|
|
1618
|
+
findings.push(finding('blocker', `${codePrefix}.max-mass-balance-too-large`, 'Biot transient maximum mass-balance ratio exceeds the preview tolerance.'));
|
|
1619
|
+
}
|
|
1620
|
+
if (finalDissipationOk && isFiniteNumber(envelope.porePressureDissipationRatio)) {
|
|
1621
|
+
pushApproximateMatchFinding(findings, transientAcceptance.finalPorePressureDissipationRatio, envelope.porePressureDissipationRatio, `${codePrefix}.final-dissipation-envelope-mismatch`, 'Biot transient final dissipation ratio', 1e-12);
|
|
1622
|
+
}
|
|
1623
|
+
if (options.requireDrainedMonotonicChecks &&
|
|
1624
|
+
transientAcceptance.dissipationCheckMode === 'drained-dissipation' &&
|
|
1625
|
+
transientAcceptance.monotonicAverageFreePressureDissipationRequired !== true) {
|
|
1626
|
+
findings.push(finding('blocker', `${codePrefix}.drained-monotonic-required`, 'Drained-dissipation Biot acceptance must require monotonic average free pore-pressure dissipation.'));
|
|
1627
|
+
}
|
|
1628
|
+
if (transientAcceptance.monotonicAverageFreePressureDissipationRequired &&
|
|
1629
|
+
transientAcceptance.monotonicAverageFreePressureDissipation !== true) {
|
|
1630
|
+
findings.push(finding('blocker', `${codePrefix}.average-free-pressure-not-monotonic`, 'Required average free pore-pressure dissipation was not monotonic.'));
|
|
1631
|
+
}
|
|
1632
|
+
if (options.requireDrainedMonotonicChecks &&
|
|
1633
|
+
pressureEnvelopeMode === 'initial-prescribed-bound' &&
|
|
1634
|
+
transientAcceptance.monotonicMaxPressureEnvelope !== true) {
|
|
1635
|
+
findings.push(finding('blocker', `${codePrefix}.max-pressure-not-monotonic`, 'Biot maximum pore-pressure envelope must be monotonic non-increasing.'));
|
|
1636
|
+
}
|
|
1637
|
+
if (!Array.isArray(transientAcceptance.blockerCodes)) {
|
|
1638
|
+
findings.push(finding('blocker', `${codePrefix}.blocker-codes.invalid`, 'Biot transient acceptance blocker codes must be an array.'));
|
|
1639
|
+
}
|
|
1640
|
+
else if (transientAcceptance.accepted && transientAcceptance.blockerCodes.length > 0) {
|
|
1641
|
+
findings.push(finding('blocker', `${codePrefix}.blocker-codes-not-empty`, 'Accepted Biot transient metadata must not include blocker codes.'));
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
function validatePlaneStrainDpBiotPressureReplayAcceptance(findings, manifest) {
|
|
1645
|
+
const { envelope, analysisCase } = manifest;
|
|
1646
|
+
const biot = analysisCase.geometry.biot;
|
|
1647
|
+
if (!biot) {
|
|
1648
|
+
findings.push(finding('blocker', 'result.dp-biot-replay.geometry-biot-missing', 'DP Biot pressure-replay manifests must embed the reviewed Biot pressure-source geometry.'));
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
const audit = manifest.pressureReplayAudit;
|
|
1652
|
+
if (!isRecord(audit)) {
|
|
1653
|
+
findings.push(finding('blocker', 'result.dp-biot-replay.audit.missing', 'DP Biot pressure-replay manifests must include pressureReplayAudit metadata.'));
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
if (audit.schemaVersion !== 'fem-plane-strain-dp-biot-pressure-replay-audit.v1') {
|
|
1657
|
+
findings.push(finding('blocker', 'result.dp-biot-replay.audit.schema.unsupported', 'Unsupported DP Biot pressure-replay audit schema.'));
|
|
1658
|
+
}
|
|
1659
|
+
if (audit.mode !== 'sequential-one-way-biot-pressure-replay') {
|
|
1660
|
+
findings.push(finding('blocker', 'result.dp-biot-replay.audit.mode.invalid', 'DP Biot pressure replay must declare sequential one-way replay mode.'));
|
|
1661
|
+
}
|
|
1662
|
+
if (audit.pressureFrameSource !== 'final-biot-step') {
|
|
1663
|
+
findings.push(finding('blocker', 'result.dp-biot-replay.audit.pressure-frame-source.invalid', 'DP Biot pressure replay must use the final Biot step as the pressure-frame source.'));
|
|
1664
|
+
}
|
|
1665
|
+
if (!isNonEmptyString(audit.sourceMethod)) {
|
|
1666
|
+
findings.push(finding('blocker', 'result.dp-biot-replay.audit.source-method.missing', 'DP Biot pressure replay must record the upstream Biot source method.'));
|
|
1667
|
+
}
|
|
1668
|
+
if (audit.sourceTransientAccepted !== true) {
|
|
1669
|
+
findings.push(finding('blocker', 'result.dp-biot-replay.audit.source-transient-not-accepted', 'DP Biot pressure replay requires an accepted upstream transient source.'));
|
|
1670
|
+
}
|
|
1671
|
+
if (!Array.isArray(audit.sourceTransientBlockerCodes)) {
|
|
1672
|
+
findings.push(finding('blocker', 'result.dp-biot-replay.audit.source-blockers.invalid', 'DP Biot pressure replay source blocker codes must be an array.'));
|
|
1673
|
+
}
|
|
1674
|
+
else if (audit.sourceTransientBlockerCodes.length > 0) {
|
|
1675
|
+
findings.push(finding('blocker', 'result.dp-biot-replay.audit.source-blockers-not-empty', 'Accepted DP Biot pressure replay source must not include blocker codes.'));
|
|
1676
|
+
}
|
|
1677
|
+
const sourceStepsOk = pushFiniteNumberFinding(findings, audit.sourceAcceptedStepCount, 'result.dp-biot-replay.audit.source-accepted-step-count', 'Pressure-replay source accepted step count', { positive: true });
|
|
1678
|
+
const sourcePressureDofsOk = pushFiniteNumberFinding(findings, audit.sourcePorePressureDofCount, 'result.dp-biot-replay.audit.source-pore-pressure-dof-count', 'Pressure-replay source pore-pressure DOF count', { positive: true });
|
|
1679
|
+
const replayNodeCountOk = pushFiniteNumberFinding(findings, audit.replayNodeCount, 'result.dp-biot-replay.audit.replay-node-count', 'Pressure-replay node count', { positive: true });
|
|
1680
|
+
const sourceMassBalanceOk = pushFiniteNumberFinding(findings, audit.sourceMassBalanceErrorRatio, 'result.dp-biot-replay.audit.source-mass-balance-ratio', 'Pressure-replay source mass-balance ratio', { nonNegative: true });
|
|
1681
|
+
const sourceResidualOk = pushFiniteNumberFinding(findings, audit.sourceResidualNormRatio, 'result.dp-biot-replay.audit.source-residual-ratio', 'Pressure-replay source residual ratio', { nonNegative: true });
|
|
1682
|
+
pushFiniteNumberFinding(findings, audit.pressureScale, 'result.dp-biot-replay.audit.pressure-scale', 'Pressure-replay scale', { nonNegative: true });
|
|
1683
|
+
pushFiniteNumberFinding(findings, audit.maxInputPorePressureKpa, 'result.dp-biot-replay.audit.max-input-pore-pressure', 'Pressure-replay maximum input pore pressure', { nonNegative: true });
|
|
1684
|
+
const auditCouplingOk = pushFiniteNumberFinding(findings, audit.maxAppliedEffectiveStressReductionKpa, 'result.dp-biot-replay.audit.max-applied-effective-stress-reduction', 'Pressure-replay maximum applied effective-stress reduction', { nonNegative: true });
|
|
1685
|
+
if (!Array.isArray(audit.limitations) || audit.limitations.length === 0) {
|
|
1686
|
+
findings.push(finding('blocker', 'result.dp-biot-replay.audit.limitations.missing', 'DP Biot pressure-replay audit must include explicit limitations.'));
|
|
1687
|
+
}
|
|
1688
|
+
else {
|
|
1689
|
+
const limitationText = audit.limitations.join(' ').toLowerCase();
|
|
1690
|
+
if (!limitationText.includes('sequential one-way') || !limitationText.includes('no pore-pressure dofs')) {
|
|
1691
|
+
findings.push(finding('blocker', 'result.dp-biot-replay.audit.limitations.incomplete', 'DP Biot pressure-replay audit limitations must disclose sequential one-way replay and no pore-pressure DOFs.'));
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
for (const [ok, value, code, label] of [
|
|
1695
|
+
[sourceStepsOk, audit.sourceAcceptedStepCount, 'source-accepted-step-count', 'source accepted step count'],
|
|
1696
|
+
[sourcePressureDofsOk, audit.sourcePorePressureDofCount, 'source-pore-pressure-dof-count', 'source pore-pressure DOF count'],
|
|
1697
|
+
[replayNodeCountOk, audit.replayNodeCount, 'replay-node-count', 'replay node count'],
|
|
1698
|
+
]) {
|
|
1699
|
+
if (ok && !Number.isInteger(value)) {
|
|
1700
|
+
findings.push(finding('blocker', `result.dp-biot-replay.audit.${code}.integer`, `Pressure-replay ${label} must be an integer.`));
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
if (replayNodeCountOk && audit.replayNodeCount !== manifest.mesh.nodes) {
|
|
1704
|
+
findings.push(finding('blocker', 'result.dp-biot-replay.audit.replay-node-count-mismatch', 'Pressure-replay node count must match the manifest mesh node count.'));
|
|
1705
|
+
}
|
|
1706
|
+
if (sourcePressureDofsOk && audit.sourcePorePressureDofCount !== manifest.mesh.nodes) {
|
|
1707
|
+
findings.push(finding('blocker', 'result.dp-biot-replay.audit.source-pore-pressure-dof-count-mismatch', 'Pressure-replay source pore-pressure DOF count must match the pressure-source mesh node count.'));
|
|
1708
|
+
}
|
|
1709
|
+
if (sourceStepsOk && audit.sourceAcceptedStepCount !== biot.timeStepsSeconds.length) {
|
|
1710
|
+
findings.push(finding('blocker', 'result.dp-biot-replay.audit.source-step-count-mismatch', 'Pressure-replay source accepted step count must match Biot time steps.'));
|
|
1711
|
+
}
|
|
1712
|
+
if (sourceMassBalanceOk && audit.sourceMassBalanceErrorRatio > 1e-3) {
|
|
1713
|
+
findings.push(finding('blocker', 'result.dp-biot-replay.audit.source-mass-balance-too-large', 'Pressure-replay source mass-balance ratio exceeds the preview tolerance.'));
|
|
1714
|
+
}
|
|
1715
|
+
if (sourceResidualOk && audit.sourceResidualNormRatio > 1e-3) {
|
|
1716
|
+
findings.push(finding('blocker', 'result.dp-biot-replay.audit.source-residual-too-large', 'Pressure-replay source residual ratio exceeds the preview tolerance.'));
|
|
1717
|
+
}
|
|
1718
|
+
const minPorePressureOk = pushFiniteNumberFinding(findings, envelope.minPorePressureKpa, 'result.envelope.dp-biot-replay.min-pore-pressure', 'Pressure-replay envelope minimum pore pressure', { nonNegative: true });
|
|
1719
|
+
const maxPorePressureOk = pushFiniteNumberFinding(findings, envelope.maxPorePressureKpa, 'result.envelope.dp-biot-replay.max-pore-pressure', 'Pressure-replay envelope maximum pore pressure', { nonNegative: true });
|
|
1720
|
+
const maxExcessOk = pushFiniteNumberFinding(findings, envelope.maxExcessPorePressureKpa, 'result.envelope.dp-biot-replay.max-excess-pore-pressure', 'Pressure-replay envelope maximum excess pore pressure', { nonNegative: true });
|
|
1721
|
+
const maxCouplingOk = pushFiniteNumberFinding(findings, envelope.maxBiotCouplingKpa, 'result.envelope.dp-biot-replay.max-biot-coupling', 'Pressure-replay envelope maximum Biot coupling', { nonNegative: true });
|
|
1722
|
+
const massBalanceOk = pushFiniteNumberFinding(findings, envelope.porePressureMassBalanceErrorRatio, 'result.envelope.dp-biot-replay.mass-balance-ratio', 'Pressure-replay envelope pore-pressure mass-balance ratio', { nonNegative: true });
|
|
1723
|
+
pushFiniteNumberFinding(findings, envelope.maxFreePorePressureResidualM3PerS, 'result.envelope.dp-biot-replay.max-free-pore-pressure-residual', 'Pressure-replay maximum free pore-pressure residual', { nonNegative: true });
|
|
1724
|
+
pushFiniteNumberFinding(findings, envelope.freePorePressureResidualL1M3PerS, 'result.envelope.dp-biot-replay.free-pore-pressure-residual-l1', 'Pressure-replay free pore-pressure residual L1 norm', { nonNegative: true });
|
|
1725
|
+
pushFiniteNumberFinding(findings, envelope.prescribedPorePressureResidualL1M3PerS, 'result.envelope.dp-biot-replay.prescribed-pore-pressure-residual-l1', 'Pressure-replay prescribed pore-pressure residual L1 norm', { nonNegative: true });
|
|
1726
|
+
const averagePorePressureOk = pushFiniteNumberFinding(findings, envelope.averagePorePressureKpa, 'result.envelope.dp-biot-replay.average-pore-pressure', 'Pressure-replay average pore pressure', { nonNegative: true });
|
|
1727
|
+
const averageFreePorePressureOk = pushFiniteNumberFinding(findings, envelope.averageFreePorePressureKpa, 'result.envelope.dp-biot-replay.average-free-pore-pressure', 'Pressure-replay average free pore pressure', { nonNegative: true });
|
|
1728
|
+
const dissipationRatioOk = pushFiniteNumberFinding(findings, envelope.porePressureDissipationRatio, 'result.envelope.dp-biot-replay.pore-pressure-dissipation-ratio', 'Pressure-replay pore-pressure dissipation ratio', { nonNegative: true });
|
|
1729
|
+
pushFiniteNumberFinding(findings, envelope.maxPorePressureChangeRateKpaPerS, 'result.envelope.dp-biot-replay.max-pore-pressure-change-rate', 'Pressure-replay maximum pore-pressure change rate', { nonNegative: true });
|
|
1730
|
+
const timeStepCountOk = pushFiniteNumberFinding(findings, envelope.timeStepCount, 'result.envelope.dp-biot-replay.time-step-count', 'Pressure-replay envelope time-step count', { positive: true });
|
|
1731
|
+
const coupledUnknownsOk = pushFiniteNumberFinding(findings, envelope.coupledUnknownCount, 'result.envelope.dp-biot-replay.coupled-unknown-count', 'Pressure-replay coupled unknown count', { positive: true });
|
|
1732
|
+
const displacementDofsOk = pushFiniteNumberFinding(findings, envelope.displacementDofCount, 'result.envelope.dp-biot-replay.displacement-dof-count', 'Pressure-replay displacement DOF count', { positive: true });
|
|
1733
|
+
const pressureDofsOk = pushFiniteNumberFinding(findings, envelope.porePressureDofCount, 'result.envelope.dp-biot-replay.pore-pressure-dof-count', 'Pressure-replay nonlinear pore-pressure DOF count', { nonNegative: true });
|
|
1734
|
+
if (minPorePressureOk && maxPorePressureOk && envelope.minPorePressureKpa > envelope.maxPorePressureKpa) {
|
|
1735
|
+
findings.push(finding('blocker', 'result.envelope.dp-biot-replay.pore-pressure-range-invalid', 'Pressure-replay minimum pore pressure cannot exceed maximum pore pressure.'));
|
|
1736
|
+
}
|
|
1737
|
+
if (maxExcessOk && maxPorePressureOk) {
|
|
1738
|
+
pushApproximateMatchFinding(findings, envelope.maxExcessPorePressureKpa, envelope.maxPorePressureKpa, 'result.envelope.dp-biot-replay.max-excess-pore-pressure-mismatch', 'Pressure-replay maximum excess pore pressure', 1e-6);
|
|
1739
|
+
}
|
|
1740
|
+
if (averagePorePressureOk && minPorePressureOk && maxPorePressureOk && (envelope.averagePorePressureKpa < envelope.minPorePressureKpa - 1e-6 ||
|
|
1741
|
+
envelope.averagePorePressureKpa > envelope.maxPorePressureKpa + 1e-6)) {
|
|
1742
|
+
findings.push(finding('blocker', 'result.envelope.dp-biot-replay.average-pore-pressure-range-invalid', 'Pressure-replay average pore pressure must stay within the reported range.'));
|
|
1743
|
+
}
|
|
1744
|
+
if (averageFreePorePressureOk && maxPorePressureOk && envelope.averageFreePorePressureKpa > envelope.maxPorePressureKpa + 1e-6) {
|
|
1745
|
+
findings.push(finding('blocker', 'result.envelope.dp-biot-replay.average-free-pore-pressure-range-invalid', 'Pressure-replay average free pore pressure cannot exceed the maximum pore pressure.'));
|
|
1746
|
+
}
|
|
1747
|
+
if (dissipationRatioOk && envelope.porePressureDissipationRatio > 1) {
|
|
1748
|
+
findings.push(finding('blocker', 'result.envelope.dp-biot-replay.dissipation-ratio-invalid', 'Pressure-replay pore-pressure dissipation ratio must be between 0 and 1.'));
|
|
1749
|
+
}
|
|
1750
|
+
if (timeStepCountOk && (!Number.isInteger(envelope.timeStepCount) || envelope.timeStepCount !== biot.timeStepsSeconds.length)) {
|
|
1751
|
+
findings.push(finding('blocker', 'result.envelope.dp-biot-replay.time-step-count-mismatch', 'Pressure-replay envelope time-step count must match Biot time steps.'));
|
|
1752
|
+
}
|
|
1753
|
+
if (massBalanceOk && envelope.porePressureMassBalanceErrorRatio > 1e-3) {
|
|
1754
|
+
findings.push(finding('blocker', 'result.envelope.dp-biot-replay.mass-balance-too-large', 'Pressure-replay mass-balance ratio exceeds the preview tolerance.'));
|
|
1755
|
+
}
|
|
1756
|
+
for (const [ok, value, code, label] of [
|
|
1757
|
+
[coupledUnknownsOk, envelope.coupledUnknownCount, 'coupled-unknown-count', 'coupled unknown count'],
|
|
1758
|
+
[displacementDofsOk, envelope.displacementDofCount, 'displacement-dof-count', 'displacement DOF count'],
|
|
1759
|
+
[pressureDofsOk, envelope.porePressureDofCount, 'pore-pressure-dof-count', 'pore-pressure DOF count'],
|
|
1760
|
+
]) {
|
|
1761
|
+
if (ok && !Number.isInteger(value)) {
|
|
1762
|
+
findings.push(finding('blocker', `result.envelope.dp-biot-replay.${code}.integer`, `Pressure-replay ${label} must be an integer.`));
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
if (pressureDofsOk && envelope.porePressureDofCount !== 0) {
|
|
1766
|
+
findings.push(finding('blocker', 'result.envelope.dp-biot-replay.pore-pressure-dof-count-not-zero', 'DP Biot pressure replay must report zero pore-pressure DOFs in the nonlinear mechanical iterations.'));
|
|
1767
|
+
}
|
|
1768
|
+
if (coupledUnknownsOk && displacementDofsOk && envelope.coupledUnknownCount !== envelope.displacementDofCount) {
|
|
1769
|
+
findings.push(finding('blocker', 'result.envelope.dp-biot-replay.coupled-unknown-count-mismatch', 'DP Biot pressure replay coupled unknown count must equal displacement DOFs only.'));
|
|
1770
|
+
}
|
|
1771
|
+
if (maxCouplingOk && auditCouplingOk) {
|
|
1772
|
+
pushApproximateMatchFinding(findings, envelope.maxBiotCouplingKpa, audit.maxAppliedEffectiveStressReductionKpa, 'result.envelope.dp-biot-replay.max-coupling-audit-mismatch', 'Pressure-replay maximum Biot coupling', 1e-8);
|
|
1773
|
+
}
|
|
1774
|
+
validatePressureAuditEnvelope(findings, manifest, 'dp-biot-replay');
|
|
1775
|
+
validateBiotTransientAcceptanceEnvelope(findings, manifest, {
|
|
1776
|
+
context: 'dp-biot-replay',
|
|
1777
|
+
requireDrainedMonotonicChecks: false,
|
|
1778
|
+
expectedAcceptedStepCount: sourceStepsOk ? audit.sourceAcceptedStepCount : undefined,
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1529
1781
|
function validateResultEnvelopeSemantics(findings, manifest) {
|
|
1530
1782
|
const { envelope, analysisCase } = manifest;
|
|
1531
1783
|
const maxSettlementOk = pushFiniteNumberFinding(findings, envelope.maxSettlementMm, 'result.envelope.max-settlement', 'Envelope max settlement', { nonNegative: true });
|
|
@@ -1538,7 +1790,7 @@ function validateResultEnvelopeSemantics(findings, manifest) {
|
|
|
1538
1790
|
}
|
|
1539
1791
|
const expectedBackendByObjective = new Map([
|
|
1540
1792
|
['foundation_settlement', ['builtin-elastic3d-demo']],
|
|
1541
|
-
['excavation_deformation', ['builtin-staged-excavation-demo', FEM_PLANE_STRAIN_DP_ADAPTIVE_BACKEND_ID]],
|
|
1793
|
+
['excavation_deformation', ['builtin-staged-excavation-demo', FEM_PLANE_STRAIN_DP_ADAPTIVE_BACKEND_ID, FEM_PLANE_STRAIN_DP_BIOT_REPLAY_BACKEND_ID]],
|
|
1542
1794
|
['tunnel_volume_loss_settlement', ['builtin-tunnel-volume-loss-demo']],
|
|
1543
1795
|
['staged_settlement_consolidation', ['builtin-staged-consolidation-1d', 'builtin-nonlinear-column-v0']],
|
|
1544
1796
|
['seepage_groundwater_coupling', ['builtin-biot-up-plane-strain-v0']],
|
|
@@ -1547,7 +1799,10 @@ function validateResultEnvelopeSemantics(findings, manifest) {
|
|
|
1547
1799
|
if (expectedBackends && !expectedBackends.includes(manifest.backend.id)) {
|
|
1548
1800
|
findings.push(finding('blocker', 'result.backend.objective-mismatch', 'Result backend must match the embedded FEM objective.'));
|
|
1549
1801
|
}
|
|
1550
|
-
|
|
1802
|
+
const isPlaneStrainDpAdaptiveManifest = manifest.backend.id === FEM_PLANE_STRAIN_DP_ADAPTIVE_BACKEND_ID;
|
|
1803
|
+
const isPlaneStrainDpBiotReplayManifest = manifest.backend.id === FEM_PLANE_STRAIN_DP_BIOT_REPLAY_BACKEND_ID;
|
|
1804
|
+
const isPlaneStrainDpManifest = isPlaneStrainDpAdaptiveManifest || isPlaneStrainDpBiotReplayManifest;
|
|
1805
|
+
if (analysisCase.objective !== 'seepage_groundwater_coupling' && !isPlaneStrainDpBiotReplayManifest) {
|
|
1551
1806
|
if (manifest.pressureAudit != null) {
|
|
1552
1807
|
findings.push(finding('blocker', 'result.pressure-audit.unexpected', 'Pressure audit is only valid for Biot u-p seepage result manifests.'));
|
|
1553
1808
|
}
|
|
@@ -1555,11 +1810,13 @@ function validateResultEnvelopeSemantics(findings, manifest) {
|
|
|
1555
1810
|
findings.push(finding('blocker', 'result.biot-transient-acceptance.unexpected', 'Biot transient acceptance metadata is only valid for Biot u-p seepage result manifests.'));
|
|
1556
1811
|
}
|
|
1557
1812
|
}
|
|
1558
|
-
|
|
1559
|
-
if (!isPlaneStrainDpAdaptiveManifest && manifest.adaptiveLoadStepping != null) {
|
|
1813
|
+
if (!isPlaneStrainDpManifest && manifest.adaptiveLoadStepping != null) {
|
|
1560
1814
|
findings.push(finding('blocker', 'result.dp-adaptive.unexpected', 'Drucker-Prager adaptive metadata is only valid for plane-strain DP adaptive result manifests.'));
|
|
1561
1815
|
}
|
|
1562
|
-
if (
|
|
1816
|
+
if (!isPlaneStrainDpBiotReplayManifest && manifest.pressureReplayAudit != null) {
|
|
1817
|
+
findings.push(finding('blocker', 'result.dp-biot-replay.unexpected', 'Biot pressure-replay audit metadata is only valid for the plane-strain DP Biot replay backend.'));
|
|
1818
|
+
}
|
|
1819
|
+
if (isPlaneStrainDpManifest) {
|
|
1563
1820
|
if (analysisCase.analysisType !== FEM_PLANE_STRAIN_DP_ANALYSIS_TYPE) {
|
|
1564
1821
|
findings.push(finding('blocker', 'result.dp-adaptive.analysis-type-mismatch', 'Plane-strain DP adaptive manifests require static_2d_plane_strain_drucker_prager analysis cases.'));
|
|
1565
1822
|
}
|
|
@@ -1623,6 +1880,9 @@ function validateResultEnvelopeSemantics(findings, manifest) {
|
|
|
1623
1880
|
const solverTolerances = validateNonlinearSolverConvergenceReport(findings, manifest, expectedDpLoadSteps);
|
|
1624
1881
|
validatePlaneStrainDpAdaptiveAcceptance(findings, manifest, solverTolerances);
|
|
1625
1882
|
}
|
|
1883
|
+
if (isPlaneStrainDpBiotReplayManifest) {
|
|
1884
|
+
validatePlaneStrainDpBiotPressureReplayAcceptance(findings, manifest);
|
|
1885
|
+
}
|
|
1626
1886
|
return;
|
|
1627
1887
|
}
|
|
1628
1888
|
if (analysisCase.objective === 'tunnel_volume_loss_settlement') {
|
|
@@ -1897,6 +2157,7 @@ export function validateFemResultManifest(manifest) {
|
|
|
1897
2157
|
'builtin-nonlinear-column-v0',
|
|
1898
2158
|
'builtin-biot-up-plane-strain-v0',
|
|
1899
2159
|
FEM_PLANE_STRAIN_DP_ADAPTIVE_BACKEND_ID,
|
|
2160
|
+
FEM_PLANE_STRAIN_DP_BIOT_REPLAY_BACKEND_ID,
|
|
1900
2161
|
]);
|
|
1901
2162
|
if (!validBackendIds.has(manifest.backend.id)) {
|
|
1902
2163
|
findings.push(finding('blocker', 'result.backend.id-invalid', `Unsupported FEM result backend: ${String(manifest.backend.id)}.`));
|