@geotechcli/core 0.4.110 → 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.
Files changed (35) hide show
  1. package/dist/agents/brain.d.ts.map +1 -1
  2. package/dist/agents/brain.js +31 -2
  3. package/dist/agents/brain.js.map +1 -1
  4. package/dist/agents/fem-tools.js +5 -0
  5. package/dist/agents/fem-tools.js.map +1 -1
  6. package/dist/agents/safety.d.ts +1 -0
  7. package/dist/agents/safety.d.ts.map +1 -1
  8. package/dist/agents/safety.js +62 -0
  9. package/dist/agents/safety.js.map +1 -1
  10. package/dist/fem/engineering-evidence.d.ts +21 -2
  11. package/dist/fem/engineering-evidence.d.ts.map +1 -1
  12. package/dist/fem/engineering-evidence.js +257 -6
  13. package/dist/fem/engineering-evidence.js.map +1 -1
  14. package/dist/fem/index.d.ts +3 -2
  15. package/dist/fem/index.d.ts.map +1 -1
  16. package/dist/fem/index.js +1 -0
  17. package/dist/fem/index.js.map +1 -1
  18. package/dist/fem/nonlinear-plane-strain-solver.d.ts +10 -0
  19. package/dist/fem/nonlinear-plane-strain-solver.d.ts.map +1 -0
  20. package/dist/fem/nonlinear-plane-strain-solver.js +358 -0
  21. package/dist/fem/nonlinear-plane-strain-solver.js.map +1 -0
  22. package/dist/fem/plane-strain-assembly.d.ts +37 -0
  23. package/dist/fem/plane-strain-assembly.d.ts.map +1 -1
  24. package/dist/fem/plane-strain-assembly.js +209 -43
  25. package/dist/fem/plane-strain-assembly.js.map +1 -1
  26. package/dist/fem/production-readiness.js +2 -2
  27. package/dist/fem/production-readiness.js.map +1 -1
  28. package/dist/fem/types.d.ts +52 -3
  29. package/dist/fem/types.d.ts.map +1 -1
  30. package/dist/fem/validation.d.ts.map +1 -1
  31. package/dist/fem/validation.js +281 -10
  32. package/dist/fem/validation.js.map +1 -1
  33. package/dist/ingest/document-evidence-packet.d.ts +6 -6
  34. package/dist/meta/metadata.json +1 -1
  35. package/package.json +1 -1
@@ -1773,6 +1773,42 @@ function normalizeLoadStepFractions(options) {
1773
1773
  }
1774
1774
  return fractions;
1775
1775
  }
1776
+ function normalizeAdaptiveDruckerPragerLoadStepping(value) {
1777
+ if (value == null || value === false) {
1778
+ return {
1779
+ enabled: false,
1780
+ strategy: 'explicit-only',
1781
+ minLoadFactorIncrement: 0,
1782
+ maxCutbacks: 0,
1783
+ };
1784
+ }
1785
+ const options = value === true ? {} : value;
1786
+ const enabled = options.enabled ?? true;
1787
+ if (!enabled) {
1788
+ return {
1789
+ enabled: false,
1790
+ strategy: 'explicit-only',
1791
+ minLoadFactorIncrement: 0,
1792
+ maxCutbacks: 0,
1793
+ };
1794
+ }
1795
+ const strategy = options.strategy ?? 'cutback-bisection';
1796
+ if (strategy !== 'cutback-bisection') {
1797
+ throw new Error('adaptiveLoadStepping.strategy must be cutback-bisection.');
1798
+ }
1799
+ const minLoadFactorIncrement = options.minLoadFactorIncrement ?? 1 / 64;
1800
+ if (!Number.isFinite(minLoadFactorIncrement) || minLoadFactorIncrement <= 0 || minLoadFactorIncrement >= 1) {
1801
+ throw new Error('adaptiveLoadStepping.minLoadFactorIncrement must be finite and between 0 and 1.');
1802
+ }
1803
+ const maxCutbacks = Math.floor(options.maxCutbacks ?? 24);
1804
+ assertPositiveInteger(maxCutbacks, 'adaptiveLoadStepping.maxCutbacks');
1805
+ return {
1806
+ enabled: true,
1807
+ strategy,
1808
+ minLoadFactorIncrement,
1809
+ maxCutbacks,
1810
+ };
1811
+ }
1776
1812
  function isDruckerPragerStepConverged(evaluation, policy) {
1777
1813
  return evaluation.residualNormRatio <= policy.forceBalanceTolerance &&
1778
1814
  evaluation.maxYieldResidualRatio <= policy.residualTolerance;
@@ -1780,6 +1816,19 @@ function isDruckerPragerStepConverged(evaluation, policy) {
1780
1816
  function createInitialDruckerPragerStateGrid(system) {
1781
1817
  return system.elementGaussCache.map((entry) => entry.gauss.map(() => initialDruckerPragerState()));
1782
1818
  }
1819
+ function druckerPragerStateSignature(states) {
1820
+ return states
1821
+ .flatMap((elementStates) => elementStates)
1822
+ .map((state) => [
1823
+ ...state.strain,
1824
+ ...state.stressKpa,
1825
+ state.sigmaZKpa,
1826
+ state.equivalentPlasticStrain,
1827
+ ...state.plasticStrainPrincipal,
1828
+ state.volumetricPlasticStrain,
1829
+ ].map((value) => round(value, 12)).join(','))
1830
+ .join('|');
1831
+ }
1783
1832
  function druckerPragerResidualHistoryEntry(iteration, evaluation, policy) {
1784
1833
  return {
1785
1834
  iteration,
@@ -1895,6 +1944,7 @@ export function runPlaneStrainDruckerPragerLoadSteps(model, options = {}) {
1895
1944
  }
1896
1945
  const linearSolverMaxIterations = options.linearSolverMaxIterations ?? Math.max(100, system.freeDofs.length * 10);
1897
1946
  assertPositiveInteger(linearSolverMaxIterations, 'linearSolverMaxIterations');
1947
+ const adaptiveOptions = normalizeAdaptiveDruckerPragerLoadStepping(options.adaptiveLoadStepping);
1898
1948
  const reducedDenseK = linearSolver === 'dense-gaussian'
1899
1949
  ? system.freeDofs.map((row) => system.freeDofs.map((col) => system.stiffness[row][col]))
1900
1950
  : undefined;
@@ -1908,14 +1958,22 @@ export function runPlaneStrainDruckerPragerLoadSteps(model, options = {}) {
1908
1958
  let committedStates = createInitialDruckerPragerStateGrid(system);
1909
1959
  let finalEvaluation;
1910
1960
  const loadSteps = [];
1911
- for (const [stepIndex, loadFactor] of loadStepFractions.entries()) {
1961
+ let currentLoadFactor = 0;
1962
+ let attemptedStepCount = 0;
1963
+ let cutbackCount = 0;
1964
+ let maxCutbackDepth = 0;
1965
+ const acceptedLoadFactors = [];
1966
+ const adaptiveAttemptAudits = [];
1967
+ const adaptiveBlockerCodes = [];
1968
+ function solveDruckerPragerStepAttempt(input) {
1969
+ const trialDisplacement = [...input.startingDisplacement];
1912
1970
  for (const [index, value] of system.prescribed)
1913
- displacement[index] = value * loadFactor;
1971
+ trialDisplacement[index] = value * input.loadFactor;
1914
1972
  let evaluation = evaluatePlaneStrainDruckerPragerState({
1915
1973
  system,
1916
- displacement,
1917
- loadFactor,
1918
- committedStates,
1974
+ displacement: trialDisplacement,
1975
+ loadFactor: input.loadFactor,
1976
+ committedStates: input.startingCommittedStates,
1919
1977
  });
1920
1978
  let iterations = 0;
1921
1979
  let converged = isDruckerPragerStepConverged(evaluation, system.policy);
@@ -1929,7 +1987,7 @@ export function runPlaneStrainDruckerPragerLoadSteps(model, options = {}) {
1929
1987
  if (system.freeDofs.length === 0)
1930
1988
  break;
1931
1989
  const correctionRhs = system.freeDofs.map((index) => -evaluation.residual[index]);
1932
- const currentFreeDisplacement = system.freeDofs.map((index) => displacement[index]);
1990
+ const currentFreeDisplacement = system.freeDofs.map((index) => trialDisplacement[index]);
1933
1991
  const solved = linearSolver === 'sparse-csr-cg'
1934
1992
  ? solveSparseDruckerPragerCorrection({
1935
1993
  reducedK: reducedSparseK,
@@ -1952,51 +2010,155 @@ export function runPlaneStrainDruckerPragerLoadSteps(model, options = {}) {
1952
2010
  }
1953
2011
  const correction = solved.correction;
1954
2012
  for (const [correctionIndex, dof] of system.freeDofs.entries()) {
1955
- displacement[dof] += correction[correctionIndex];
2013
+ trialDisplacement[dof] += correction[correctionIndex];
1956
2014
  }
1957
2015
  for (const [index, value] of system.prescribed)
1958
- displacement[index] = value * loadFactor;
2016
+ trialDisplacement[index] = value * input.loadFactor;
1959
2017
  evaluation = evaluatePlaneStrainDruckerPragerState({
1960
2018
  system,
1961
- displacement,
1962
- loadFactor,
1963
- committedStates,
2019
+ displacement: trialDisplacement,
2020
+ loadFactor: input.loadFactor,
2021
+ committedStates: input.startingCommittedStates,
1964
2022
  });
1965
2023
  converged = isDruckerPragerStepConverged(evaluation, system.policy);
1966
2024
  residualHistory.push(druckerPragerResidualHistoryEntry(iterations, evaluation, system.policy));
1967
2025
  }
1968
- finalEvaluation = evaluation;
1969
- if (converged)
1970
- committedStates = evaluation.trialStates;
1971
2026
  const terminationReason = druckerPragerTerminationReason(evaluation, system.policy, converged, iterations, linearSolverFailure);
1972
2027
  const lastLinearAudit = linearSolverAudits.at(-1);
1973
- loadSteps.push({
1974
- step: stepIndex + 1,
1975
- loadFactor: round(loadFactor, 8),
1976
- iterations,
1977
- maxFreeResidualKn: round(evaluation.maxFreeResidualKn, 12),
1978
- residualNormRatio: round(evaluation.residualNormRatio, 12),
1979
- reactionBalanceRatio: round(evaluation.reactionBalanceRatio, 12),
1980
- maxYieldResidualRatio: round(evaluation.maxYieldResidualRatio, 12),
1981
- maxEquivalentPlasticStrain: round(evaluation.maxEquivalentPlasticStrain, 12),
1982
- maxEquivalentPlasticStrainIncrement: round(evaluation.maxEquivalentPlasticStrainIncrement, 12),
1983
- plasticGaussPointCount: evaluation.plasticGaussPointCount,
1984
- linearSolver,
1985
- linearIterations: linearSolverAudits.reduce((sum, audit) => sum + audit.iterations, 0),
1986
- linearResidualNormRatio: round(lastLinearAudit?.residualNormRatio ?? 0, 12),
1987
- correctionNormRatio: round(lastLinearAudit?.correctionNormRatio ?? 0, 12),
1988
- linearSolverAudits,
2028
+ return {
2029
+ evaluation,
2030
+ displacement: trialDisplacement,
1989
2031
  converged,
1990
- terminationReason,
1991
- ...(linearSolverFailure?.failureReason ? { failureReason: linearSolverFailure.failureReason } : {}),
1992
- residualHistory,
2032
+ step: {
2033
+ step: input.step,
2034
+ loadFactor: round(input.loadFactor, 8),
2035
+ ...(Math.abs(input.requestedLoadFactor - input.loadFactor) > 1e-12
2036
+ ? { requestedLoadFactor: round(input.requestedLoadFactor, 8) }
2037
+ : {}),
2038
+ ...(input.cutbackDepth > 0 ? { cutbackDepth: input.cutbackDepth, adaptiveCutback: true } : {}),
2039
+ iterations,
2040
+ maxFreeResidualKn: round(evaluation.maxFreeResidualKn, 12),
2041
+ residualNormRatio: round(evaluation.residualNormRatio, 12),
2042
+ reactionBalanceRatio: round(evaluation.reactionBalanceRatio, 12),
2043
+ maxYieldResidualRatio: round(evaluation.maxYieldResidualRatio, 12),
2044
+ maxEquivalentPlasticStrain: round(evaluation.maxEquivalentPlasticStrain, 12),
2045
+ maxEquivalentPlasticStrainIncrement: round(evaluation.maxEquivalentPlasticStrainIncrement, 12),
2046
+ plasticGaussPointCount: evaluation.plasticGaussPointCount,
2047
+ linearSolver,
2048
+ linearIterations: linearSolverAudits.reduce((sum, audit) => sum + audit.iterations, 0),
2049
+ linearResidualNormRatio: round(lastLinearAudit?.residualNormRatio ?? 0, 12),
2050
+ correctionNormRatio: round(lastLinearAudit?.correctionNormRatio ?? 0, 12),
2051
+ linearSolverAudits,
2052
+ converged,
2053
+ terminationReason,
2054
+ ...(linearSolverFailure?.failureReason ? { failureReason: linearSolverFailure.failureReason } : {}),
2055
+ residualHistory,
2056
+ },
2057
+ };
2058
+ }
2059
+ function solveToLoadFactor(targetLoadFactor, requestedLoadFactor, cutbackDepth) {
2060
+ const startLoadFactor = currentLoadFactor;
2061
+ const committedStateSignatureBefore = druckerPragerStateSignature(committedStates);
2062
+ const attempt = solveDruckerPragerStepAttempt({
2063
+ step: loadSteps.length + 1,
2064
+ loadFactor: targetLoadFactor,
2065
+ requestedLoadFactor,
2066
+ cutbackDepth,
2067
+ startingDisplacement: displacement,
2068
+ startingCommittedStates: committedStates,
2069
+ });
2070
+ attemptedStepCount += 1;
2071
+ maxCutbackDepth = Math.max(maxCutbackDepth, cutbackDepth);
2072
+ if (attempt.converged) {
2073
+ adaptiveAttemptAudits.push({
2074
+ attempt: adaptiveAttemptAudits.length + 1,
2075
+ startLoadFactor: round(startLoadFactor, 8),
2076
+ targetLoadFactor: round(targetLoadFactor, 8),
2077
+ requestedLoadFactor: round(requestedLoadFactor, 8),
2078
+ cutbackDepth,
2079
+ accepted: true,
2080
+ rollbackApplied: false,
2081
+ terminationReason: attempt.step.terminationReason,
2082
+ committedStateSignatureBefore,
2083
+ committedStateSignatureAfter: druckerPragerStateSignature(attempt.evaluation.trialStates),
2084
+ });
2085
+ displacement.splice(0, displacement.length, ...attempt.displacement);
2086
+ committedStates = attempt.evaluation.trialStates;
2087
+ finalEvaluation = attempt.evaluation;
2088
+ currentLoadFactor = targetLoadFactor;
2089
+ acceptedLoadFactors.push(round(targetLoadFactor, 8));
2090
+ loadSteps.push({
2091
+ ...attempt.step,
2092
+ step: loadSteps.length + 1,
2093
+ });
2094
+ return true;
2095
+ }
2096
+ adaptiveAttemptAudits.push({
2097
+ attempt: adaptiveAttemptAudits.length + 1,
2098
+ startLoadFactor: round(startLoadFactor, 8),
2099
+ targetLoadFactor: round(targetLoadFactor, 8),
2100
+ requestedLoadFactor: round(requestedLoadFactor, 8),
2101
+ cutbackDepth,
2102
+ accepted: false,
2103
+ rollbackApplied: adaptiveOptions.enabled,
2104
+ terminationReason: attempt.step.terminationReason,
2105
+ committedStateSignatureBefore,
2106
+ committedStateSignatureAfter: committedStateSignatureBefore,
1993
2107
  });
2108
+ if (adaptiveOptions.enabled) {
2109
+ const increment = targetLoadFactor - startLoadFactor;
2110
+ const midpoint = startLoadFactor + increment / 2;
2111
+ if (cutbackCount >= adaptiveOptions.maxCutbacks) {
2112
+ adaptiveBlockerCodes.push('adaptive-load-step-max-cutbacks-exhausted');
2113
+ }
2114
+ else if (Math.abs(midpoint - startLoadFactor) < adaptiveOptions.minLoadFactorIncrement) {
2115
+ adaptiveBlockerCodes.push('adaptive-load-step-min-increment-reached');
2116
+ }
2117
+ else if (Math.abs(midpoint - targetLoadFactor) < 1e-12) {
2118
+ adaptiveBlockerCodes.push('adaptive-load-step-cutback-stalled');
2119
+ }
2120
+ else {
2121
+ cutbackCount += 1;
2122
+ const firstHalfAccepted = solveToLoadFactor(midpoint, requestedLoadFactor, cutbackDepth + 1);
2123
+ if (!firstHalfAccepted)
2124
+ return false;
2125
+ return solveToLoadFactor(targetLoadFactor, requestedLoadFactor, cutbackDepth + 1);
2126
+ }
2127
+ }
2128
+ displacement.splice(0, displacement.length, ...attempt.displacement);
2129
+ finalEvaluation = attempt.evaluation;
2130
+ loadSteps.push({
2131
+ ...attempt.step,
2132
+ step: loadSteps.length + 1,
2133
+ });
2134
+ return false;
2135
+ }
2136
+ for (const loadFactor of loadStepFractions) {
2137
+ const accepted = solveToLoadFactor(loadFactor, loadFactor, 0);
2138
+ if (!accepted && adaptiveOptions.enabled)
2139
+ break;
1994
2140
  }
1995
2141
  if (!finalEvaluation) {
1996
2142
  throw new Error('Plane-strain nonlinear load-step solver requires at least one load step.');
1997
2143
  }
2144
+ const acceptedFinalEvaluation = finalEvaluation;
1998
2145
  const failedStep = loadSteps.find((step) => !step.converged);
1999
2146
  const status = failedStep ? 'nonconverged' : 'converged';
2147
+ const adaptiveLoadStepping = {
2148
+ schemaVersion: 'fem-plane-strain-dp-adaptive-load-stepping.v1',
2149
+ enabled: adaptiveOptions.enabled,
2150
+ strategy: adaptiveOptions.strategy,
2151
+ requestedStepCount: loadStepFractions.length,
2152
+ attemptedStepCount,
2153
+ acceptedStepCount: loadSteps.filter((step) => step.converged).length,
2154
+ cutbackCount,
2155
+ maxCutbackDepth,
2156
+ minLoadFactorIncrement: round(adaptiveOptions.minLoadFactorIncrement, 12),
2157
+ requestedLoadFactors: loadStepFractions.map((factor) => round(factor, 8)),
2158
+ acceptedLoadFactors,
2159
+ attempts: adaptiveAttemptAudits,
2160
+ blockerCodes: [...new Set(adaptiveBlockerCodes)],
2161
+ };
2000
2162
  return {
2001
2163
  schemaVersion: 'fem-plane-strain-drucker-prager-result.v1',
2002
2164
  method: 'quad4-plane-strain-drucker-prager-modified-newton',
@@ -2004,10 +2166,10 @@ export function runPlaneStrainDruckerPragerLoadSteps(model, options = {}) {
2004
2166
  ...node,
2005
2167
  uxM: round(displacement[dofIndex(index, 'ux')], 12),
2006
2168
  uyM: round(displacement[dofIndex(index, 'uy')], 12),
2007
- rxnXKn: round(finalEvaluation.residual[dofIndex(index, 'ux')], 8),
2008
- rxnYKn: round(finalEvaluation.residual[dofIndex(index, 'uy')], 8),
2169
+ rxnXKn: round(acceptedFinalEvaluation.residual[dofIndex(index, 'ux')], 8),
2170
+ rxnYKn: round(acceptedFinalEvaluation.residual[dofIndex(index, 'uy')], 8),
2009
2171
  })),
2010
- elements: finalEvaluation.elements,
2172
+ elements: acceptedFinalEvaluation.elements,
2011
2173
  dofCount: system.dofCount,
2012
2174
  freeDofCount: system.freeDofs.length,
2013
2175
  constrainedDofCount: system.prescribed.size,
@@ -2016,14 +2178,15 @@ export function runPlaneStrainDruckerPragerLoadSteps(model, options = {}) {
2016
2178
  globalTangent: 'elastic',
2017
2179
  materialIntegration: 'incremental-committed-drucker-prager-return-mapping',
2018
2180
  stateStorage: 'committed-gauss-point-history',
2181
+ adaptiveLoadStepping,
2019
2182
  loadSteps,
2020
- maxFreeResidualKn: round(finalEvaluation.maxFreeResidualKn, 12),
2021
- residualNormRatio: round(finalEvaluation.residualNormRatio, 12),
2022
- reactionBalanceRatio: round(finalEvaluation.reactionBalanceRatio, 12),
2023
- maxYieldResidualRatio: round(finalEvaluation.maxYieldResidualRatio, 12),
2024
- maxEquivalentPlasticStrain: round(finalEvaluation.maxEquivalentPlasticStrain, 12),
2025
- maxEquivalentPlasticStrainIncrement: round(finalEvaluation.maxEquivalentPlasticStrainIncrement, 12),
2026
- plasticGaussPointCount: finalEvaluation.plasticGaussPointCount,
2183
+ maxFreeResidualKn: round(acceptedFinalEvaluation.maxFreeResidualKn, 12),
2184
+ residualNormRatio: round(acceptedFinalEvaluation.residualNormRatio, 12),
2185
+ reactionBalanceRatio: round(acceptedFinalEvaluation.reactionBalanceRatio, 12),
2186
+ maxYieldResidualRatio: round(acceptedFinalEvaluation.maxYieldResidualRatio, 12),
2187
+ maxEquivalentPlasticStrain: round(acceptedFinalEvaluation.maxEquivalentPlasticStrain, 12),
2188
+ maxEquivalentPlasticStrainIncrement: round(acceptedFinalEvaluation.maxEquivalentPlasticStrainIncrement, 12),
2189
+ plasticGaussPointCount: acceptedFinalEvaluation.plasticGaussPointCount,
2027
2190
  converged: status === 'converged',
2028
2191
  status,
2029
2192
  ...(failedStep ? {
@@ -2040,6 +2203,9 @@ export function runPlaneStrainDruckerPragerLoadSteps(model, options = {}) {
2040
2203
  limitations: [
2041
2204
  'Benchmark-scale modified-Newton plane-strain plasticity evidence kernel only.',
2042
2205
  ...(failedStep ? ['Nonconverged load-step result is reported fail-closed and must not be treated as an accepted engineering solve.'] : []),
2206
+ ...(adaptiveOptions.enabled
2207
+ ? ['Adaptive cutback-bisection load stepping can subdivide rejected nonlinear increments with rollback audit, but it is still not an arc-length or production consistent-tangent strategy.']
2208
+ : []),
2043
2209
  linearSolver === 'sparse-csr-cg'
2044
2210
  ? '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.'
2045
2211
  : '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.',