Skip to content

[DamApplication] Restore apply-once semantics of scalar processes - #14683

Merged
djvicente merged 14 commits into
masterfrom
dam/initialization-regression
Aug 17, 2026
Merged

[DamApplication] Restore apply-once semantics of scalar processes#14683
djvicente merged 14 commits into
masterfrom
dam/initialization-regression

Conversation

@joaquinirazabal

Copy link
Copy Markdown
Member

📝 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.

…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
djvicente merged commit c59336d into master Aug 17, 2026
10 checks passed
@djvicente
djvicente deleted the dam/initialization-regression branch August 17, 2026 13:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants