Skip to content

Support Adapt.adapt and copy for GradientOp - #48

Merged
nHackel merged 4 commits into
JuliaImageRecon:mainfrom
shukebeta:feat/issue-33-gradient-adapt
Nov 3, 2026
Merged

nHackel merged 4 commits into
JuliaImageRecon:mainfrom
shukebeta:feat/issue-33-gradient-adapt

Conversation

@shukebeta

Copy link
Copy Markdown
Contributor

Adapt.adapt(target, op) and copy(op) now work for GradientOp, also inside ProdOp, normalOperator and DiagOp. Both return a new operator that is rebuilt from the shape and the directions taken at construction, with a new LinearOperator, closures and temporary vector for the target storage type; op is not modified.

G = GradientOp(Float32; shape=(64, 64))   # CPU
G_gpu = Adapt.adapt(CuArray, G)
  • GradientOp now returns a GradientOpImpl <: GradientOp{T} that wraps the LinearOperator and keeps shape, dims and the adjoint's temporary vector, like the other wrapper operators. Properties of the LinearOperator are forwarded.
  • dims is iterated once at construction and stored as a tuple, or as an Int for a single direction. Rebuilding does not depend on the caller's container: a vector changed later, or a consumed iterator, has no effect.
  • The adapted storage must have the operator's element type. An element-typed target such as CuArray{Float32} for a Float64 operator throws an ArgumentError, and so does an operator built with an S of another element type, whose adjoint rounds through its temporary vector. copy keeps such an operator as it is.
  • The GPU gradient kernels no longer set cpu = false, so they also run on CPU array types such as JLArray. Their computation is unchanged.
  • test/testAdapt.jl moves GradientOp between Array and JLArray (or the configured GPU array type) for Float32, Float64, ComplexF32, ComplexF64 and Int32, and for single, several, repeated and no directions. It compares forward, transpose and adjoint, with 3- and 5-argument mul!, against the source. It also checks that the source's buffers, temporary vector and counters are unchanged, and that copies get their own temporary vector. It further covers a mutated dims vector, a consumed iterator, createLinearOperator, ProdOp, normalOperator, DiagOp, and the element-type errors.

The tests pass on the CPU with Julia 1.10.12. With two threads, the only failure is the existing DiagOp((op, op)) FFT test from #44.

shukebeta and others added 4 commits November 3, 2026 03:45
GradientOp now returns a GradientOpImpl wrapper that keeps the shape, the
directions taken from `dims` at construction, and the adjoint scratch vector.
Adapt.adapt and copy rebuild the operator, its closures and scratch storage
for the target storage type instead of sharing the source's state. `dims` is
iterated once, so a later mutation of the caller's vector or a consumed
iterator does not change a rebuilt operator.

The GPU gradient kernels drop `cpu = false` so they also run on
KernelAbstractions' CPU backend (JLArray).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… type

Rebuilding keeps the operator's element type, so a target such as
Array{Float32} for a Float64 operator would mix precisions in the scratch
and buffers. Throw an ArgumentError instead and document the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Compare the adapted storage with eltype(op) rather than the source storage,
so an operator built with an S of another element type is rejected for a
target without an element type instead of returning an operator whose
storage disagrees with its element type. A target naming the operator's
element type still rebuilds consistent storage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A rebuild with storage of the operator's element type would drop the
rounding through the source's lower-precision temporary vector and change
the adjoint, so such operators cannot be adapted to any target. copy keeps
them unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Nov 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.66667% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.23%. Comparing base (454257e) to head (9fe52df).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
ext/LinearOperatorGPUArraysExt/GradientOp.jl 33.33% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #48      +/-   ##
==========================================
+ Coverage   73.01%   81.23%   +8.22%     
==========================================
  Files          15       15              
  Lines         593      613      +20     
==========================================
+ Hits          433      498      +65     
+ Misses        160      115      -45     

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

@nHackel
nHackel merged commit 363af08 into JuliaImageRecon:main Nov 3, 2026
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.

2 participants