@geotechcli/core 0.4.104 → 0.4.106

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.
@@ -3,6 +3,8 @@ export declare function buildRaftDemoAnalysisCase(now?: Date): FemAnalysisCase;
3
3
  export declare function buildExcavationDemoAnalysisCase(now?: Date): FemAnalysisCase;
4
4
  export declare function buildTunnelVolumeLossDemoAnalysisCase(now?: Date): FemAnalysisCase;
5
5
  export declare function buildStagedSettlementConsolidationDemoAnalysisCase(now?: Date): FemAnalysisCase;
6
+ export declare function buildSeepageBiotPlaneStrainDemoAnalysisCase(now?: Date): FemAnalysisCase;
7
+ export declare function runBuiltinBiotUpPlaneStrainPreview(caseFile?: FemAnalysisCase): FemResultManifest;
6
8
  export declare function runBuiltinElasticRaftDemo(caseFile?: FemAnalysisCase): FemResultManifest;
7
9
  export declare function runBuiltinTunnelVolumeLossDemo(caseFile?: FemAnalysisCase): FemResultManifest;
8
10
  export declare function runBuiltinStagedSettlementConsolidationDemo(caseFile?: FemAnalysisCase): FemResultManifest;
@@ -1 +1 @@
1
- {"version":3,"file":"demo.d.ts","sourceRoot":"","sources":["../../src/fem/demo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAIf,iBAAiB,EAIlB,MAAM,YAAY,CAAC;AA2DpB,wBAAgB,yBAAyB,CAAC,GAAG,OAAuC,GAAG,eAAe,CAuHrG;AAED,wBAAgB,+BAA+B,CAAC,GAAG,OAAuC,GAAG,eAAe,CAuI3G;AAED,wBAAgB,qCAAqC,CAAC,GAAG,OAAuC,GAAG,eAAe,CA+GjH;AAED,wBAAgB,kDAAkD,CAChE,GAAG,OAAuC,GACzC,eAAe,CA4KjB;AA2pBD,wBAAgB,yBAAyB,CAAC,QAAQ,kBAA8B,GAAG,iBAAiB,CAkEnG;AAED,wBAAgB,8BAA8B,CAC5C,QAAQ,kBAA0C,GACjD,iBAAiB,CAiEnB;AAED,wBAAgB,2CAA2C,CACzD,QAAQ,kBAAuD,GAC9D,iBAAiB,CAuJnB;AAED,wBAAgB,+BAA+B,CAC7C,QAAQ,kBAAoC,GAC3C,iBAAiB,CA4EnB"}
1
+ {"version":3,"file":"demo.d.ts","sourceRoot":"","sources":["../../src/fem/demo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAIf,iBAAiB,EAIlB,MAAM,YAAY,CAAC;AAkFpB,wBAAgB,yBAAyB,CAAC,GAAG,OAAuC,GAAG,eAAe,CAuHrG;AAED,wBAAgB,+BAA+B,CAAC,GAAG,OAAuC,GAAG,eAAe,CAuI3G;AAED,wBAAgB,qCAAqC,CAAC,GAAG,OAAuC,GAAG,eAAe,CA+GjH;AAED,wBAAgB,kDAAkD,CAChE,GAAG,OAAuC,GACzC,eAAe,CA4KjB;AAED,wBAAgB,2CAA2C,CACzD,GAAG,OAAuC,GACzC,eAAe,CA8GjB;AAm0BD,wBAAgB,kCAAkC,CAChD,QAAQ,kBAAgD,GACvD,iBAAiB,CAiInB;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,kBAA8B,GAAG,iBAAiB,CAkEnG;AAED,wBAAgB,8BAA8B,CAC5C,QAAQ,kBAA0C,GACjD,iBAAiB,CAiEnB;AAED,wBAAgB,2CAA2C,CACzD,QAAQ,kBAAuD,GAC9D,iBAAiB,CAuJnB;AAED,wBAAgB,+BAA+B,CAC7C,QAAQ,kBAAoC,GAC3C,iBAAiB,CA4EnB"}
package/dist/fem/demo.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { runMohrCoulombMaterialPoint, runTerzaghiConsolidationTimeStepper, } from './engineering-evidence.js';
2
+ import { buildPlaneStrainRectangularMesh, runPlaneStrainBiotConsolidation, } from './plane-strain-assembly.js';
2
3
  import { validateFemAnalysisCase } from './validation.js';
3
4
  const DEFAULT_UNITS = {
4
5
  length: 'm',
@@ -48,6 +49,24 @@ function movementColor(t) {
48
49
  return round(value / 255, 4);
49
50
  });
50
51
  }
52
+ function porePressureColor(t) {
53
+ const stops = [
54
+ [247, 252, 240],
55
+ [186, 228, 188],
56
+ [123, 204, 196],
57
+ [67, 162, 202],
58
+ [8, 104, 172],
59
+ [8, 48, 107],
60
+ ];
61
+ const scaled = Math.min(Math.max(t, 0), 1) * (stops.length - 1);
62
+ const left = Math.floor(scaled);
63
+ const right = Math.min(left + 1, stops.length - 1);
64
+ const local = scaled - left;
65
+ return [0, 1, 2].map((index) => {
66
+ const value = stops[left][index] + (stops[right][index] - stops[left][index]) * local;
67
+ return round(value / 255, 4);
68
+ });
69
+ }
51
70
  export function buildRaftDemoAnalysisCase(now = new Date('2026-05-16T00:00:00.000Z')) {
52
71
  const assumptions = [
53
72
  {
@@ -585,6 +604,116 @@ export function buildStagedSettlementConsolidationDemoAnalysisCase(now = new Dat
585
604
  ],
586
605
  };
587
606
  }
607
+ export function buildSeepageBiotPlaneStrainDemoAnalysisCase(now = new Date('2026-06-03T00:00:00.000Z')) {
608
+ const finalTimeSeconds = 19.7;
609
+ const assumptions = [
610
+ {
611
+ id: 'biot-up-preview-scope',
612
+ parameter: 'solver scope',
613
+ value: 'saturated linear-elastic plane-strain Biot u-p preview',
614
+ basis: 'Strong-beta route-backed preview for reviewed pore-pressure dissipation and hydro-mechanical evidence only.',
615
+ confidence: 'review',
616
+ reviewRequired: true,
617
+ },
618
+ {
619
+ id: 'excess-pore-pressure-positive',
620
+ parameter: 'pore pressure convention',
621
+ value: 'positive excess pore pressure in kPa',
622
+ basis: 'The benchmark-scale Biot evidence kernel rejects unsupported negative free pore-pressure solves.',
623
+ confidence: 'review',
624
+ reviewRequired: true,
625
+ },
626
+ {
627
+ id: 'top-drained-boundary',
628
+ parameter: 'hydraulic boundary',
629
+ value: 'top drained to zero excess pore pressure',
630
+ unit: 'kPa',
631
+ basis: 'Representative Terzaghi-style top-drained column fixture for transient pressure-dissipation review.',
632
+ confidence: 'review',
633
+ reviewRequired: true,
634
+ },
635
+ ];
636
+ return {
637
+ schemaVersion: 'fem-analysis-case.v0',
638
+ caseId: 'seepage-biot-plane-strain-demo',
639
+ title: 'Experimental plane-strain Biot u-p pore-pressure dissipation preview',
640
+ createdBy: 'geotechcli-fem-demo',
641
+ createdAt: now.toISOString(),
642
+ experimental: true,
643
+ objective: 'seepage_groundwater_coupling',
644
+ analysisType: 'time_dependent_2d_biot_consolidation',
645
+ units: DEFAULT_UNITS,
646
+ geometry: {
647
+ domain: {
648
+ type: 'box',
649
+ lengthM: 1,
650
+ widthM: 1,
651
+ depthM: 1,
652
+ },
653
+ biot: {
654
+ type: 'plane_strain_biot_column',
655
+ widthM: 1,
656
+ heightM: 1,
657
+ thicknessM: 1,
658
+ initialPorePressureKpa: 100,
659
+ timeStepsSeconds: Array.from({ length: 20 }, (_, index) => round(finalTimeSeconds * ((index + 1) / 20), 6)),
660
+ porePressureBoundaries: [
661
+ { id: 'top-drained', boundary: 'top', porePressureKpa: 0 },
662
+ ],
663
+ },
664
+ },
665
+ materials: [
666
+ {
667
+ id: 'biot-clay',
668
+ name: 'Representative saturated clay for Biot u-p preview',
669
+ model: 'linear_elastic',
670
+ elasticModulusKpa: 30_000,
671
+ poissonRatio: 0.3,
672
+ unitWeightKnM3: 18,
673
+ hydraulicConductivityMPerS: 1e-6,
674
+ hydraulicConductivityXMPerS: 1e-6,
675
+ hydraulicConductivityYMPerS: 1e-6,
676
+ biotCoefficient: 0.8,
677
+ specificStorage1PerM: 1e-4,
678
+ evidenceRefs: [],
679
+ assumptions,
680
+ },
681
+ ],
682
+ loads: [],
683
+ boundaryConditions: [
684
+ {
685
+ id: 'base-fixed',
686
+ type: 'fixed_base',
687
+ description: 'Bottom boundary fixed in both plane-strain displacement directions for reviewed column preview.',
688
+ },
689
+ {
690
+ id: 'side-rollers',
691
+ type: 'side_rollers',
692
+ description: 'Side boundaries treated as rollers in the plane-strain preview interpretation.',
693
+ },
694
+ ],
695
+ mesh: {
696
+ elementType: 'quad4_plane_strain',
697
+ divisionsX: 1,
698
+ divisionsY: 16,
699
+ divisionsZ: 1,
700
+ },
701
+ groundwater: {
702
+ condition: 'specified',
703
+ depthM: 0,
704
+ note: 'Saturated top-drained excess-pore-pressure dissipation fixture; uplift, pumping, unsaturated flow, and piping are not design-checked.',
705
+ reviewRequired: true,
706
+ },
707
+ assumptions,
708
+ evidenceRefs: [],
709
+ limitations: [
710
+ 'Experimental benchmark-scale plane-strain Biot u-p preview only; not a production nonlinear geotechnical FEM solver.',
711
+ 'Uses dense linear-elastic Quad4 displacement/pore-pressure coupling and reviewed excess-pore-pressure boundary assumptions.',
712
+ 'No unsaturated flow, pumping/dewatering design, uplift/piping verification, support design, nonlinear plasticity coupling, staged activation, or production sparse solver is provided.',
713
+ 'Independent published/commercial solver benchmarks and enforced approval workflows are still required before production design use.',
714
+ ],
715
+ };
716
+ }
588
717
  function buildVisualizationMesh(caseFile, maxSettlementMm) {
589
718
  const { domain, raft } = caseFile.geometry;
590
719
  if (!raft) {
@@ -1170,6 +1299,287 @@ function buildTunnelResultDatasets(visualization) {
1170
1299
  },
1171
1300
  ];
1172
1301
  }
1302
+ function buildBiotPlaneStrainVisualizationMesh(caseFile, result) {
1303
+ const biot = caseFile.geometry.biot;
1304
+ if (!biot) {
1305
+ throw new Error('Biot visualization mesh requires Biot plane-strain geometry.');
1306
+ }
1307
+ const nx = caseFile.mesh.divisionsX;
1308
+ const ny = caseFile.mesh.divisionsY;
1309
+ const maxPressure = Math.max(...result.nodes.map((node) => node.porePressureKpa), biot.initialPorePressureKpa, 1e-9);
1310
+ const base = [];
1311
+ const disp = [];
1312
+ const color = [];
1313
+ const scalarValues = [];
1314
+ const tri = [];
1315
+ const edge = [];
1316
+ for (const node of result.nodes) {
1317
+ base.push(round(node.xM - biot.widthM / 2, 6), 0, round(-node.yM, 6));
1318
+ disp.push(round(node.uxM, 9), 0, round(node.uyM, 9));
1319
+ scalarValues.push(round(node.porePressureKpa, 8));
1320
+ color.push(...porePressureColor(node.porePressureKpa / maxPressure));
1321
+ }
1322
+ const idx = (ix, iy) => iy * (nx + 1) + ix;
1323
+ for (let iy = 0; iy < ny; iy += 1) {
1324
+ for (let ix = 0; ix < nx; ix += 1) {
1325
+ const a = idx(ix, iy);
1326
+ const b = idx(ix + 1, iy);
1327
+ const c = idx(ix + 1, iy + 1);
1328
+ const d = idx(ix, iy + 1);
1329
+ tri.push(a, b, c, a, c, d);
1330
+ }
1331
+ }
1332
+ for (let iy = 0; iy <= ny; iy += 1) {
1333
+ for (let ix = 0; ix < nx; ix += 1) {
1334
+ edge.push(idx(ix, iy), idx(ix + 1, iy));
1335
+ }
1336
+ }
1337
+ for (let ix = 0; ix <= nx; ix += 1) {
1338
+ for (let iy = 0; iy < ny; iy += 1) {
1339
+ edge.push(idx(ix, iy), idx(ix, iy + 1));
1340
+ }
1341
+ }
1342
+ const finalStep = result.timeSteps[result.timeSteps.length - 1];
1343
+ const stageLabel = finalStep ? `Final Biot step at ${finalStep.timeSeconds.toFixed(3)} s` : 'Final Biot step';
1344
+ return {
1345
+ base,
1346
+ disp,
1347
+ color,
1348
+ tri,
1349
+ edge,
1350
+ outlineBase: [
1351
+ -biot.widthM / 2, 0, 0,
1352
+ biot.widthM / 2, 0, 0,
1353
+ biot.widthM / 2, 0, -biot.heightM,
1354
+ -biot.widthM / 2, 0, -biot.heightM,
1355
+ ],
1356
+ outlineDisp: new Array(12).fill(0),
1357
+ outlineIdx: [0, 1, 1, 2, 2, 3, 3, 0],
1358
+ frames: [
1359
+ {
1360
+ field: 'excess_pore_pressure',
1361
+ fieldLabel: 'Excess pore pressure',
1362
+ stageIndex: 0,
1363
+ stageLabel,
1364
+ disp,
1365
+ color,
1366
+ scalarValues,
1367
+ },
1368
+ ],
1369
+ };
1370
+ }
1371
+ function buildBiotResultFields() {
1372
+ return [
1373
+ {
1374
+ id: 'vertical_settlement',
1375
+ label: 'Vertical settlement',
1376
+ unit: 'mm',
1377
+ location: 'surface_nodes',
1378
+ quantity: 'displacement',
1379
+ component: 'z',
1380
+ signConvention: 'Negative z displacement is downward in the viewer; values are generated from plane-strain uy.',
1381
+ },
1382
+ {
1383
+ id: 'excess_pore_pressure',
1384
+ label: 'Excess pore pressure',
1385
+ unit: 'kPa',
1386
+ location: 'surface_nodes',
1387
+ quantity: 'pore_pressure',
1388
+ signConvention: 'Positive compression excess pore pressure in the Biot u-p kernel.',
1389
+ },
1390
+ {
1391
+ id: 'max_excess_pore_pressure',
1392
+ label: 'Maximum excess pore pressure',
1393
+ unit: 'kPa',
1394
+ location: 'envelope',
1395
+ quantity: 'pore_pressure',
1396
+ },
1397
+ {
1398
+ id: 'pore_pressure_mass_balance_error_ratio',
1399
+ label: 'Pore-pressure mass-balance error ratio',
1400
+ unit: 'ratio',
1401
+ location: 'envelope',
1402
+ quantity: 'degree_of_consolidation',
1403
+ signConvention: 'Residual ratio from free pore-pressure equation audit; smaller is better.',
1404
+ },
1405
+ ];
1406
+ }
1407
+ function buildBiotResultSteps(result) {
1408
+ const finalStep = result.timeSteps[result.timeSteps.length - 1];
1409
+ return [
1410
+ {
1411
+ id: 'final',
1412
+ label: finalStep ? `Final Biot step at ${finalStep.timeSeconds.toFixed(3)} s` : 'Final Biot step',
1413
+ index: 0,
1414
+ timeSeconds: finalStep?.timeSeconds ?? 0,
1415
+ deltaTimeSeconds: finalStep?.deltaTimeSeconds ?? 0,
1416
+ },
1417
+ ];
1418
+ }
1419
+ function buildBiotResultDatasets(visualization, envelope) {
1420
+ const frame = visualization.frames?.[0];
1421
+ return [
1422
+ {
1423
+ id: 'vertical_settlement-final',
1424
+ fieldId: 'vertical_settlement',
1425
+ stepId: 'final',
1426
+ values: visualization.disp,
1427
+ stride: 3,
1428
+ source: 'visualization.disp',
1429
+ },
1430
+ {
1431
+ id: 'excess_pore_pressure-final',
1432
+ fieldId: 'excess_pore_pressure',
1433
+ stepId: 'final',
1434
+ values: frame?.scalarValues ?? [],
1435
+ stride: 1,
1436
+ source: 'visualization.scalar-frame',
1437
+ },
1438
+ {
1439
+ id: 'max_excess_pore_pressure-final',
1440
+ fieldId: 'max_excess_pore_pressure',
1441
+ values: [envelope.maxExcessPorePressureKpa],
1442
+ stride: 1,
1443
+ source: 'envelope',
1444
+ },
1445
+ {
1446
+ id: 'pore_pressure_mass_balance_error_ratio-final',
1447
+ fieldId: 'pore_pressure_mass_balance_error_ratio',
1448
+ values: [envelope.porePressureMassBalanceErrorRatio],
1449
+ stride: 1,
1450
+ source: 'envelope',
1451
+ },
1452
+ ];
1453
+ }
1454
+ export function runBuiltinBiotUpPlaneStrainPreview(caseFile = buildSeepageBiotPlaneStrainDemoAnalysisCase()) {
1455
+ const validation = validateFemAnalysisCase(caseFile);
1456
+ const biot = caseFile.geometry.biot;
1457
+ const material = caseFile.materials[0];
1458
+ if (!biot || !material) {
1459
+ throw new Error('Biot u-p preview requires Biot geometry and one coupled material.');
1460
+ }
1461
+ if (validation.status === 'blocked') {
1462
+ throw new Error(`FEM case is blocked: ${validation.findings.map((item) => item.message).join('; ')}`);
1463
+ }
1464
+ const mesh = buildPlaneStrainRectangularMesh({
1465
+ widthM: biot.widthM,
1466
+ heightM: biot.heightM,
1467
+ divisionsX: caseFile.mesh.divisionsX,
1468
+ divisionsY: caseFile.mesh.divisionsY,
1469
+ materialId: material.id,
1470
+ });
1471
+ const eps = 1e-9;
1472
+ const boundaryConditions = mesh.nodes.flatMap((node) => {
1473
+ const conditions = [];
1474
+ if (Math.abs(node.yM) <= eps) {
1475
+ conditions.push({ nodeId: node.id, dof: 'ux', valueM: 0 }, { nodeId: node.id, dof: 'uy', valueM: 0 });
1476
+ }
1477
+ else if (Math.abs(node.xM) <= eps || Math.abs(node.xM - biot.widthM) <= eps) {
1478
+ conditions.push({ nodeId: node.id, dof: 'ux', valueM: 0 });
1479
+ }
1480
+ return conditions;
1481
+ });
1482
+ const porePressureBoundaryConditions = biot.porePressureBoundaries.flatMap((boundary) => (mesh.nodes
1483
+ .filter((node) => {
1484
+ if (boundary.boundary === 'top')
1485
+ return Math.abs(node.yM - biot.heightM) <= eps;
1486
+ if (boundary.boundary === 'bottom')
1487
+ return Math.abs(node.yM) <= eps;
1488
+ if (boundary.boundary === 'left')
1489
+ return Math.abs(node.xM) <= eps;
1490
+ return Math.abs(node.xM - biot.widthM) <= eps;
1491
+ })
1492
+ .map((node) => ({ nodeId: node.id, porePressureKpa: boundary.porePressureKpa }))));
1493
+ const result = runPlaneStrainBiotConsolidation({
1494
+ schemaVersion: 'fem-plane-strain-biot-consolidation-model.v1',
1495
+ nodes: mesh.nodes,
1496
+ elements: mesh.elements,
1497
+ materials: [
1498
+ {
1499
+ id: material.id,
1500
+ elasticModulusKpa: material.elasticModulusKpa,
1501
+ poissonRatio: material.poissonRatio,
1502
+ unitWeightKnM3: material.unitWeightKnM3,
1503
+ hydraulicConductivityXMPerS: material.hydraulicConductivityXMPerS ?? material.hydraulicConductivityMPerS,
1504
+ hydraulicConductivityYMPerS: material.hydraulicConductivityYMPerS ?? material.hydraulicConductivityMPerS,
1505
+ biotCoefficient: material.biotCoefficient,
1506
+ specificStorage1PerM: material.specificStorage1PerM,
1507
+ },
1508
+ ],
1509
+ boundaryConditions,
1510
+ porePressureBoundaryConditions,
1511
+ timeStepsSeconds: biot.timeStepsSeconds,
1512
+ initialPorePressureKpa: biot.initialPorePressureKpa,
1513
+ defaultThicknessM: biot.thicknessM,
1514
+ });
1515
+ const visualization = buildBiotPlaneStrainVisualizationMesh(caseFile, result);
1516
+ const finalStep = result.timeSteps[result.timeSteps.length - 1];
1517
+ const maxSettlementMm = round(Math.max(...result.nodes.map((node) => Math.max(0, -node.uyM))) * 1000, 6);
1518
+ const envelope = {
1519
+ maxSettlementMm,
1520
+ minSettlementMm: 0,
1521
+ totalLoadKn: 0,
1522
+ reactionKn: 0,
1523
+ reactionBalanceRatio: 1,
1524
+ finalSettlementMm: maxSettlementMm,
1525
+ maxExcessPorePressureKpa: round(result.maxPorePressureKpa, 8),
1526
+ timeStepCount: result.timeSteps.length,
1527
+ minPorePressureKpa: round(result.minPorePressureKpa, 8),
1528
+ maxPorePressureKpa: round(result.maxPorePressureKpa, 8),
1529
+ maxBiotCouplingKpa: round(result.maxBiotCouplingKpa, 8),
1530
+ porePressureMassBalanceErrorRatio: finalStep?.massBalanceErrorRatio ?? result.massBalanceErrorRatio,
1531
+ maxFreePorePressureResidualM3PerS: result.maxFreePorePressureResidualM3PerS,
1532
+ freePorePressureResidualL1M3PerS: result.freePorePressureResidualL1M3PerS,
1533
+ prescribedPorePressureResidualL1M3PerS: result.pressureAudit.prescribedPorePressureResidualL1M3PerS,
1534
+ coupledUnknownCount: result.coupledUnknownCount,
1535
+ displacementDofCount: result.displacementDofCount,
1536
+ porePressureDofCount: result.porePressureDofCount,
1537
+ };
1538
+ return {
1539
+ schemaVersion: 'fem-result-manifest.v0',
1540
+ caseId: caseFile.caseId,
1541
+ title: caseFile.title,
1542
+ generatedAt: new Date().toISOString(),
1543
+ backend: {
1544
+ id: 'builtin-biot-up-plane-strain-v0',
1545
+ label: 'Built-in experimental Biot u-p plane-strain seepage preview',
1546
+ deterministic: true,
1547
+ version: '0.1.0',
1548
+ },
1549
+ analysisCase: caseFile,
1550
+ validation,
1551
+ mesh: {
1552
+ nodes: mesh.nodes.length,
1553
+ elements: mesh.elements.length,
1554
+ elementType: caseFile.mesh.elementType,
1555
+ divisions: [caseFile.mesh.divisionsX, caseFile.mesh.divisionsY, caseFile.mesh.divisionsZ],
1556
+ visualizationNodes: visualization.base.length / 3,
1557
+ visualizationTriangles: visualization.tri.length / 3,
1558
+ visualizationEdges: visualization.edge.length / 2,
1559
+ },
1560
+ envelope,
1561
+ pressureAudit: result.pressureAudit,
1562
+ visualization,
1563
+ resultFields: buildBiotResultFields(),
1564
+ steps: buildBiotResultSteps(result),
1565
+ datasets: buildBiotResultDatasets(visualization, envelope),
1566
+ assumptions: [
1567
+ ...caseFile.assumptions,
1568
+ {
1569
+ id: 'biot-numerical-contract',
1570
+ parameter: 'Biot numerical contract',
1571
+ value: result.numericalContract.pressureKind,
1572
+ basis: `${result.numericalContract.darcyFluxRelation}; ${result.numericalContract.storageConvention}.`,
1573
+ confidence: 'measured',
1574
+ reviewRequired: true,
1575
+ },
1576
+ ],
1577
+ limitations: [
1578
+ ...caseFile.limitations,
1579
+ ...result.limitations,
1580
+ ],
1581
+ };
1582
+ }
1173
1583
  export function runBuiltinElasticRaftDemo(caseFile = buildRaftDemoAnalysisCase()) {
1174
1584
  const validation = validateFemAnalysisCase(caseFile);
1175
1585
  const material = caseFile.materials[0];