Skip to content

Support immutable output types in out-of-place scalar→vector gradient#212

Merged
ChrisRackauckas merged 1 commit intoJuliaDiff:masterfrom
ChrisRackauckas-Claude:fix-immutable-gradient-scalar-input
Apr 16, 2026
Merged

Support immutable output types in out-of-place scalar→vector gradient#212
ChrisRackauckas merged 1 commit intoJuliaDiff:masterfrom
ChrisRackauckas-Claude:fix-immutable-gradient-scalar-input

Conversation

@ChrisRackauckas-Claude
Copy link
Copy Markdown

Summary

  • finite_difference_gradient (out-of-place, cached) for scalar x previously delegated to finite_difference_gradient!, which uses @. df = result / epsilon. This in-place broadcast calls copyto!setindex! on the output buffer, which fails for immutable array types like SVector or ArrayPartition{SVector}.
  • Extract the scalar→vector case into _scalar_gradient_oop which computes the finite difference purely out-of-place using @. (a - b) / h (allocating a new array via copy rather than mutating via copyto!).
  • Add regression test using a ReadOnlyVec wrapper that blocks setindex! to simulate immutable array types.

Motivation

When solving a SecondOrderODEProblem with SVector state in OrdinaryDiffEq.jl, the solver creates an ArrayPartition(SVector, SVector) state. If the solver switches to Rosenbrock23 with finite differences, calc_tderivative calls DI.derivativefinite_difference_gradient, which allocates a buffer via zero(cache.c1). Since similar(::ArrayPartition{SVector}) preserves the immutable SVector inner parts (unlike similar(::SVector) which returns MVector), the in-place @. df = ... fails with setindex!(::SVector{N,T}, ...) is not defined.

The out-of-place finite_difference_gradient API should never require mutation of the result — it already allocates a return value. This PR makes it compute directly without mutation for the scalar input case.

Fixes SciML/OrdinaryDiffEq.jl#3444

Test plan

  • Existing tests pass (all 161 passed, 1 pre-existing broken)
  • New test verifies finite_difference_gradient works with immutable output arrays (forward and central)
  • Verified fix resolves the original OrdinaryDiffEq.jl issue: SecondOrderODEProblem with SVector state + Rosenbrock23(autodiff=false) now solves successfully

🤖 Generated with Claude Code

`finite_difference_gradient` (out-of-place, cached) for scalar `x` previously
delegated to `finite_difference_gradient!` which uses `@. df = result / epsilon`.
This in-place broadcast fails when the output buffer contains immutable array
types (e.g. `ArrayPartition{SVector}` from `SecondOrderODEProblem` in
OrdinaryDiffEq.jl) because `setindex!` is not defined for `SVector`.

Extract the scalar→vector case into `_scalar_gradient_oop` which computes the
finite difference result purely out-of-place using `@. (a - b) / h` (which
allocates a new array via `copy` rather than mutating via `copyto!`).

Fixes SciML/OrdinaryDiffEq.jl#3444

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ChrisRackauckas ChrisRackauckas merged commit d480d7b into JuliaDiff:master Apr 16, 2026
5 of 6 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.

solve attempts to modify a StaticArray halfway through the solution

2 participants