From 49ed886c5aa8697d222bb0cc5f9df95cd87957d9 Mon Sep 17 00:00:00 2001 From: ClaudeBot Date: Wed, 9 Sep 2026 08:22:31 -0400 Subject: [PATCH] Fix stale time increment in coupled buffers (BUG-003) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit reinit_coupled! reinitialized a coupled CellBuffer's cellvalues, dofs, and state each cell, but never refreshed its Δt. The coupled buffer therefore kept the initial NaN, or whatever value the partner had after it was last worked on its own, regardless of any set_time_increment! calls made on the partner in the meantime. Refresh each coupled buffer's Δt from its corresponding partner Simulation's base itembuffer on every reinit, before copying dofs and state. This reads the partner's current value directly rather than relying on stale scatter!'d task-local copies, and works uniformly for sequential and threaded, ordinary and autodiff buffers. Extended the "couple_buffers" regression test (test/replacements.jl) across its existing threading/autodiff/multidomain matrix to assert the coupled buffer's time increment matches the partner's value across two successive set_time_increment!+work! calls, plus a third call using an independently coupled buffer copy (via replace_material, mirroring the fracture tutorial's usage) to confirm the fix reads from the currently supplied simulation rather than any object baked in at couple_buffers time. Testing: full Pkg.test() suite passes (couple_buffers: 336/336, all other testsets green). docs/src/literate_tutorials/phasefield_fracture.jl (the package's real coupled-buffer usage) runs cleanly with no errors. Reviewed by Codex (dual-review skill, read-only independent reviewer): plan review flagged that the initial regression test only reused the same coupled buffer object already linked via couple_buffers and did not cover independently coupled copies as used in the fracture tutorial; addressed by adding the replace_material-based case above. Final diff review returned no findings. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01ND6mqzQ1JLuWdC8LPAhkvY --- src/ItemBuffers/CellBuffer.jl | 6 +++++- test/replacements.jl | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/ItemBuffers/CellBuffer.jl b/src/ItemBuffers/CellBuffer.jl index 211df395..0bf4eecb 100644 --- a/src/ItemBuffers/CellBuffer.jl +++ b/src/ItemBuffers/CellBuffer.jl @@ -150,7 +150,11 @@ function reinit_coupled!(coupled_buffers::NamedTuple, coupled::CoupledSimulation if length(coupled_buffers) != length(coupled.sims) throw(ArgumentError("When using coupled simulations, the coupled buffers must match the coupled simulations")) end - tuple((reinit_buffer!(cb, coupled.sims[k], CoupledSimulations(), cellnum) for (k, cb) in pairs(coupled_buffers))...) + for (k, cb) in pairs(coupled_buffers) + sim = coupled.sims[k] + set_time_increment!(cb, get_time_increment(get_base(get_itembuffer(sim)))) + reinit_buffer!(cb, sim, CoupledSimulations(), cellnum) + end return nothing end diff --git a/test/replacements.jl b/test/replacements.jl index 65d7e061..ace0e78c 100644 --- a/test/replacements.jl +++ b/test/replacements.jl @@ -59,6 +59,7 @@ end FerriteAssembly.create_cell_state(::MA, cv, x, ae, args...) = [function_value(cv, i, ae) for i in 1:getnquadpoints(cv)] FerriteAssembly.create_cell_state(::MB, cv, x, ae, args...) = [2 * function_value(cv, i, ae)[1] for i in 1:getnquadpoints(cv)] + Δt2 = 0.25 # Test case to check that values have been updated correctly function FerriteAssembly.element_routine!(Ke, re, state, ae, m::MA, cv, buffer) cb_b = FerriteAssembly.get_coupled_buffer(buffer, :b) @@ -72,6 +73,8 @@ end @test FerriteAssembly.get_aeold(buffer) ≈ FerriteAssembly.get_aeold(cb_b)[2:2:end] # Check that state variables have been updated @test 6 * state ≈ FerriteAssembly.get_state(cb_b) + # Check that the coupled buffer's time increment reflects the partner's current value + @test FerriteAssembly.get_time_increment(cb_b) == Δt2 end a1 = rand(ndofs(dh1)) @@ -101,7 +104,25 @@ end K = allocate_matrix(dh1) r = zeros(ndofs(dh1)) assembler = start_assemble(K, r) + Δt2 = 0.25 + set_time_increment!(d2, Δt2) work!(assembler, sim1, CoupledSimulations(b = sim2)) # Test + # Change the partner's time increment before the next staggered iteration + # and check it isn't stale (BUG-003 regression) + Δt2 = 0.75 + set_time_increment!(d2, Δt2) + assembler = start_assemble(K, r) + work!(assembler, sim1, CoupledSimulations(b = sim2)) # Test + # An independently coupled copy (a distinct buffer object from the one + # captured by `couple_buffers(d1; b = d2)` above, as occurs e.g. after + # `replace_material`, cf. the fracture tutorial) must also give a fresh, + # non-stale time increment (BUG-003 regression) + d2_indep = FerriteAssembly.replace_material(d2, identity) + sim2_indep = Simulation(d2_indep, a2, aold2) + Δt2 = 0.4 + set_time_increment!(d2_indep, Δt2) + assembler = start_assemble(K, r) + work!(assembler, sim1, CoupledSimulations(b = sim2_indep)) # Test end end end