[DamApplication] Restore apply-once semantics of scalar processes - #14683
Merged
Conversation
…and lifecycle instrumentation Small deterministic 2D thermomechanical case (10x20 m section, 18 nodes, 10 quads) using DamBofangConditionTemperatureProcess on the upstream face, plus an instrumented DamAnalysis that records nodal TEMPERATURE at each lifecycle stage and per-step results/stresses as CSV. The patches/ directory contains the exact diffs used for the legacy-lifecycle variants of the Bofang initialization regression experiment (PR #13472).
…ression compare_results.py: variant pairwise comparison (lifecycle temperature, step results, stresses) printing raw numerical differences. analytical_bofang.py: analytical reproduction of the Bofang expression. analytical_verification.py: analytical vs Kratos-assigned temperature table.
Checks that after the full analysis initialization the reservoir nodes below the water level carry the analytical Bofang temperature and that no temperature is assigned above the water level or outside the reservoir model part. This locks the physically meaningful invariant (correct initial condition) and guards against a dead-lifecycle regression of the Bofang process, without depending on the internal lifecycle mechanism.
… and results Records the lifecycle observation, source-code analysis and the numerical comparison for the PR #13472 lifecycle change (variants A current master, B process-only legacy revert, B2 faithful pre-PR lifecycle, C historical Kratos at ece5cfe). A vs B2 and A vs C are bit-identical; only the process-only revert (B) breaks the initialization.
…rapper integration test - Clarify variant B (dead-callback negative control) vs B2 (faithful legacy lifecycle) and that A vs B2 is the causal lifecycle comparison. - Correct the byte-identical claim: limit it to the Dam subsystems actually exercised by the case (custom_elements, custom_constitutive, custom_strategies, thermo-mechanic/P solver scripts) that show an empty git diff between ece5cfe and HEAD; dam_analysis.py differs only in the two lifecycle call sites. - Add lifecycle_fixity recording and a production-chain integration test (dam_analysis.py -> process_factory -> impose_reservoir_temperature_ condition_process.py -> C++ DamBofangConditionTemperatureProcess) that verifies the analytical Bofang temperature and the fixity pattern at the post-initialization checkpoint. Documents that the production wrapper forces constrained=true and does not forward ExecuteInitialize.
…ecycle harness Audit of every DamApplication file changed by PR #13472: 20 C++ processes, 17 Python wrappers, dam_analysis.py and 16 test JSON files. Per process it records the lifecycle callbacks (historical vs current), assignment mechanism, is_fixed/constrained semantics, consumer trace and risk. Findings: - All 19 renamed C++ processes are L0 (lifecycle equivalent): the value is assigned before solver.Initialize in both versions, bodies are byte-identical apart from the rename, and every consumer reads the value only during the solve (CalculateLocalSystem / CalculateMaterialResponse). - Assignment-semantics audit of ApplyConstantScalarValueProcess vs AssignScalarVariableProcess: the default fixity differs (is_fixed=false vs constrained=true) and ApplyFixity throws for non-DOF variables, which is the root cause of the vector-load regression (#14617, already fixed and tested). - NEW: impose_nodal_young_modulus_process.py cannot be imported since #13472 (class base 'Process' left undefined when the star import was removed). Reproduced on current master (NameError). A canary regression test pins it; the one-line fix is reported separately per the decision rules (not applied). Tests: test_dam_process_lifecycle.py (generic production-wrapper lifecycle harness for representative R1 processes + the nodal-young canary); registered in the small suite. Small suite: 20 tests OK.
PR #13472 removed 'from KratosMultiphysics import *' from impose_nodal_young_modulus_process.py but left the class base as the bare 'Process', which is now undefined, so the module raised NameError at import time and the nodal-Young-modulus feature was unusable. Minimal fix: class base Process -> KratosMultiphysics.Process (one line). No unrelated changes. The regression test in test_dam_process_lifecycle.py was converted from a canary that pinned the NameError into a positive test verifying: module import, production-factory instantiation, Process base class, execution of the underlying C++ process on a minimal ModelPart, expected NODAL_YOUNG_MODULUS assignment, and the documented fixity with no unrelated DOF/fixity changes.
Add focused idempotence tests for the double pre-loop callback (ExecuteBeforeSolutionLoop is invoked twice around solver.Initialize in dam_analysis.py): deterministic thermal (nodal reference temperature), material-evolution (nodal Young modulus), load (hydrostatic pressure), DamRandomFieldsVariableProcess (table-read; random field is generated once in the wrapper) and DamAddedMassConditionProcess. Each test runs the callback twice at the same TIME and asserts assigned values, fixity and DOF set are unchanged (test_idempotent_* in test_dam_process_lifecycle.py). No production code changed.
Refine the lifecycle conclusion: the historical and current effective lifecycles both reach an equivalent solver-visible initialization state, but the callback-count is NOT literally identical — the current analysis executes ExecuteBeforeSolutionLoop() twice around solver.Initialize() (idempotently). Record per process: current_preloop_call_count_before_first_solve, same_time_idempotent, idempotence_reason, state_accumulated, risk_if_non_idempotent and an evidence level (N/P/S). - N (numerical proof): Bofang (A == B2 == C). - P (process-level regression): 18 processes. - S (source-level equivalence only, NOT numerically proven): 8 processes (azenha, noorzai, chemo_mechanical_aging, temperature_by_device, uplift, uplift_circular, westergaard, apply_component_table). Idempotence section: no affected callback accumulates, randomizes, mutates tables, creates entities or modifies constitutive state; all are deterministic overwrites (verified by source + representative runtime double-call tests). Add the architectural note on the double pre-loop call (dam_analysis.py left unchanged; any cleanup must be a separate change). Document that the nodal-Young-modulus import bug is now FIXED on this branch with a positive regression test, and confirm no unguarded AssignScalarVariableProcess use with the default constrained=true remains in DamApplication.
… suite The Bofang and process-lifecycle regression classes were registered only in the small/nightly suites; add them to the 'all' suite so every permanent Dam test class is exercised there too.
…nment processes PR #13472 migrated several Dam wrappers from ApplyConstantScalarValueProcess (applied once at ExecuteInitialize) to AssignScalarVariableProcess without forwarding an interval. AssignScalarVariableProcess defaults to interval [0,1e30] (always active), so the values were re-applied at every ExecuteInitializeSolutionStep. For the uniform initial temperature this is a causal physical regression (identified with the real La Baells dam model, evidence level N/CAUSAL): the initial condition became a persistent override, overwriting the thermally evolved field from day 2 onward. The same interval-omission pattern affected the thermal-parameter process and the UniformFlux branch of the heat-flux process (numerically idempotent there because the values are constant, but semantically changed: later updates were silently overwritten). Fix: forward an initial-only interval [0,0] to AssignScalarVariableProcess in - impose_uniform_temperature_process.py - impose_thermal_parameters_scalar_value_process.py - impose_face_heat_flux_process.py (UniformFlux branch) Regression tests (test_dam_process_lifecycle.py): - test_uniform_initial_temperature_is_initial_condition_only: initial 12.712 assigned once; a later step must not restore it after manual evolution to 8.0. - test_thermal_parameters_are_initial_condition_only: conductivity 2.2 assigned once; a later step must not restore it after a manual update to 5.0. construction_results.json regenerated: the fix restores the pre-#13472 apply-once behaviour, matching the pre-#13472 results to ~1e-15; the stored values had been updated by #13472 to the persistent-override behaviour. Validation on the La Baells case (5 steps, same mesh/input): fixed branch matches the pre-#13472 checkpoint (ece5cfe) to ~1e-12 (619-3D temperature day1-5 identical: 9.9486, 9.1925, 8.8525, 7.8823, 7.3543); day 1 unchanged, day 2 no reset, days 2-5 follow pre-#13472 behaviour.
…fetime semantics Add per-row value-lifetime fields (historical_value_lifetime, current_value_lifetime, interval_forwarded, default_interval, per_step_reassignment, lifetime_semantics_equivalent, interval_regression_risk) to the PR #13472 audit. Correct the risk classification following the La Baells causal investigation: - impose_uniform_temperature_process.py: R3 causal physical regression (La Baells, first divergent step day 2) -- FIXED on this branch. - impose_thermal_parameters_scalar_value_process.py: R2 semantic regression (same interval pattern; numerically idempotent for constant values) -- FIXED. - impose_face_heat_flux_process.py (UniformFlux branch): R1 (latent pattern, constant flux) -- FIXED. - apply_load_vector / _table / constraint wrappers: documented as LOW (constant values; tables use ApplyComponentTableProcessDam). Record evidence level N/CAUSAL for the uniform-temperature regression and add the La Baells causal evidence summary to the audit.
- Renamed `TestDamProcessLifecycle` to `TestDamProcessLifetime` to better reflect its purpose. - Removed `TestBofangInitializationRegression` and its associated test file as it is no longer needed. - Updated test suite assembly to reflect the removal and renaming of tests. - Adjusted tests in `test_dam_process_lifecycle.py` to ensure they validate the correct application of initial conditions without reapplication in subsequent solution steps.
djvicente
approved these changes
Aug 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📝 Description
PR #13472 migrated several DamApplication wrappers from ApplyConstantScalarValueProcess to AssignScalarVariableProcess, changing some apply-once initializations into values re-applied at every solution step.
This PR restores the previous behavior using interval: [0.0, 0.0] for:
uniform initial temperature;
thermal parameters;
uniform face heat flux.
It also fixes the import of ImposeNodalYoungModulusProcess.
Validation
Added focused regression tests for the four fixes.
DamApplication small, nightly and all suites pass.
Construction results reproduce the pre-#13472 behavior.
A large thermomechanical dam case reproduces the pre-#13472 results over 5 steps within ~1e-12.
🆕 Changelog
Restore apply-once scalar process semantics in DamApplication.
Fix nodal Young modulus process import.