Skip to content

Add tests, docs, and generalization to #79 - #84

Merged
KnutAM merged 8 commits into
KnutAM:mainfrom
knutambot:JG-set_new_to_old_state-tests-docs
Aug 4, 2026
Merged

Add tests, docs, and generalization to #79#84
KnutAM merged 8 commits into
KnutAM:mainfrom
knutambot:JG-set_new_to_old_state-tests-docs

Conversation

@knutambot

Copy link
Copy Markdown
Contributor

Add tests, docs, and generalization to #79

Jacob Gunnarsson and others added 3 commits June 10, 2026 09:34
…ndling

Builds on PR KnutAM#79 (JG/set_new_to_old_state) from GunnarssonJ, which adds
set_new_to_old_states! to reset the current (new) state variables back to
the last converged (old) state without reassembling.

While writing tests, found that the PR's implementation used copyto!
unconditionally, which throws a MethodError for any per-cell state that
isn't an AbstractArray (e.g. a single mutable struct per cell). Fixed
src/states.jl to fall back to a deepcopy-based assignment for such states,
keeping the zero-allocation copyto! path for the common Vector{T} case.

Added a docstring for set_new_to_old_states! (mirroring update_states!),
exported it from FerriteAssembly.jl, and documented it in
docs/src/DomainBuffers/StateVariables.md and Setup.md.

Added tests in test/states.jl covering: bits-vector states (MatA),
non-bitstype mutable-struct-per-cell states (MatB, exercises the deepcopy
fallback), accumulating states (MatC), nothing-states, Simulation
forwarding, and Dict{String}-domain forwarding. Tests check correctness,
non-aliasing with old_states, and allocation behavior.

Test results: Pkg.test() passes (335/335 state-variable tests, full suite
green). docs/make.jl (which executes all tutorials/howtos via Literate and
builds the full doc site) completes with no errors.
set_new_to_old_states! previously used a hardcoded deepcopy fallback for
per-cell states that aren't AbstractArrays (e.g. a single mutable struct
per cell). Introduce FerriteAssembly.copy_state(state), defaulting to
deepcopy, as the dispatch point for that fallback so users can overload
it for a custom cell state type and avoid the allocation. AbstractArray
states are unaffected and continue to use the zero-allocation copyto!
path.

Documented copy_state in the set_new_to_old_states! docstring and in
docs/src/DomainBuffers/StateVariables.md (added to the API list).

Added a MatD/StateD test case in test/states.jl (mutable struct per cell,
like MatB) that overloads copy_state and uses a call counter to confirm
the custom method is dispatched for every cell during
set_new_to_old_states!, while values and non-aliasing with old_states
remain correct. MatB (no overload) continues to test the default deepcopy
fallback.

Test results: Pkg.test() "state variables" testset passes cleanly at
341/341 (up from 335) across repeated runs, with all other testsets
matching a previously fully-completed clean run (the test tool has a
~170s cap that truncates the last few testsets' output, unrelated to
these changes). docs/make.jl (executes every tutorial/howto and builds
the full doc site) completes with no errors.
@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.89%. Comparing base (0b63017) to head (738a8e2).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #84      +/-   ##
==========================================
+ Coverage   96.82%   96.89%   +0.06%     
==========================================
  Files          30       30              
  Lines        1166     1190      +24     
==========================================
+ Hits         1129     1153      +24     
  Misses         37       37              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

KnutAM and others added 5 commits August 4, 2026 10:14
The copy_state/set_new_to_old_states! interface in src/states.jl was
redesigned: copy_state no longer has a default deepcopy fallback (it's
now `function copy_state end`), isbits values are copied by identity
internally via _copy_state, and AbstractArray states are copied
element-wise via map! instead of copyto!, dispatching copy_state per
non-isbits element.

Updated test/states.jl to match:
- Added FerriteAssembly.copy_state(s::StateB) = deepcopy(s): StateB
  previously relied on the removed default fallback and would now throw
  a MethodError.
- Added MatE/StateE, a Vector{StateE} of non-bits elements, to exercise
  the per-element copy_state dispatch inside map! (with a call counter
  confirming it's invoked once per array element, and a non-aliasing
  check).
- Added MatF/StateF with no copy_state overload at all, asserting
  set_new_to_old_states! throws MethodError, to directly test that the
  old deepcopy fallback is gone.
- Updated stale comments on MatB/MatD referring to the old fallback.

Updated docstrings in src/states.jl (copy_state), src/DomainBuffers.jl
and docs/src/DomainBuffers/StateVariables.md (set_new_to_old_states!) to
accurately describe: per-value (not per-declared-type) isbits identity
copying, the mandatory copy_state overload with no default, and that
AbstractArray states must be mutable and keep matching axes between
old/new since they're updated in-place via map!.

Reviewed via dual-review with Codex (independent read-only reviewer) at
both the plan and diff stage; addressed all findings (docstring
precision issues, missing no-fallback regression test, missing array
mutability note) except the map! axes/mutability requirement itself,
which is now documented as an invariant rather than enforced in code,
since the underlying set_new_to_old_states! implementation was the
user's own redesign and out of scope for this change.

Test results: Pkg.test() "state variables" testset passes 360/360 (up
from 341). docs/make.jl builds cleanly (fixed an unrelated @ref link
break introduced while editing StateVariables.md).
set_new_to_old_states! now additionally checks ismutable(old_val) before
taking the element-wise map! path: only a mutable AbstractArray cell
state is updated element-wise; anything else, including an immutable
AbstractArray (e.g. NTuple- or StaticArrays-backed), is now copied as a
whole via copy_state, same as any other non-array cell state. It also
now throws ArgumentError on an axes mismatch between the old and new
mutable-array states, instead of silently truncating via map!.

Updated docstrings in src/states.jl, src/DomainBuffers.jl, and
docs/src/DomainBuffers/StateVariables.md to describe this: mutable vs.
immutable AbstractArray handling, and the new ArgumentError on axes
mismatch.

Added test/states.jl coverage:
- MatG/StateG/ImmutableStates: an immutable AbstractVector wrapping
  mutable-but-non-isbits StateG elements, with a copy_state overload for
  the whole array. Confirms the whole-array path is taken (copy_state
  called once per cell, not once per element) and that values are
  copied correctly without aliasing.
- MatH/ImmutableStatesNoOverload: same immutable-array shape but with no
  copy_state overload, confirming MethodError (not a map!-related
  error) for an immutable array cell state without an overload.
- A regression test using MatA with an artificially resized "new" state
  vector, confirming set_new_to_old_states! now throws ArgumentError on
  a mutable-array axes mismatch.

Test results: Pkg.test() "state variables" testset passes 368/368 (up
from 360). docs/make.jl builds cleanly.
`update_states!` swaps the old and new state containers, so directly after it the
new states hold the values from the previous time step. The phase-field fracture
tutorial couples its two parts through the states: the `:u` part reads the *new*
phase-field state via `get_state(cb_d)`, and it is assembled first in the staggered
loop. Without a copy back, the first displacement solve of each time step therefore
used a phase field that was one time step too old.

Added `set_new_to_old_states!(sim_d)` after `update_states!(sim_d)`, plus an inline
comment and a new section explaining the swap-vs-copy semantics, that the old states
(the irreversibility bound) are unaffected, and that no `copy_state` overload is
needed for the `Vector{Float64}` cell state. The copy also prevents the
irreversibility bound from moving backwards in the case where the `:d` part is never
assembled during a time step.

Verified by running the tutorial's solve with and without the call, with fresh
buffers, simulations, matrices and constraint handler per variant (65 load steps,
threading, autodiffbuffer):
 * sent_fine.inp (the tutorial's grid): staggered iterations 713 -> 687, Newton
   iterations 2033 -> 1922, wall time 472.8 s -> 404.5 s. Same solution:
   max|dd| = 6.7e-6 (max|d| = 0.91), max|dphi_old| = 8.8e-6, reaction-force curve
   relative difference 1.3e-5.
 * sent_coarse.inp: staggered 618 -> 588, Newton 1700 -> 1578, wall 58.4 s -> 47.3 s,
   max|dd| = 1.5e-5, reaction-force curve relative difference 4.2e-6.

Test results: Pkg.test() passes (all testsets, "state variables" 368/368). Every
literate tutorial and how-to script runs. docs/make.jl builds with no cross-reference
errors (only the pre-existing viscoelasticity example_size_threshold warning).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WazatEo5GFZNfTZ4gwRLfj
@KnutAM
KnutAM merged commit d64fccd into KnutAM:main Aug 4, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants