From de77c9786871b5906275590bca477d6ca3c0003c Mon Sep 17 00:00:00 2001 From: Collin Wittenstein Date: Fri, 18 Jul 2025 23:36:47 +0200 Subject: [PATCH 1/9] small changes which make it ~1.5x faster --- src/array_partition.jl | 40 ++++++++++++++++++++++++++++-------- src/named_array_partition.jl | 1 + 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/array_partition.jl b/src/array_partition.jl index b0325fe0..978f3427 100644 --- a/src/array_partition.jl +++ b/src/array_partition.jl @@ -364,15 +364,37 @@ end ArrayPartition(f, N) end +# old version +# @inline function Base.copyto!(dest::ArrayPartition, +# bc::Broadcast.Broadcasted{ArrayPartitionStyle{Style}}) where { +# Style, +# } +# N = npartitions(dest, bc) +# @inline function f(i) +# copyto!(dest.x[i], unpack(bc, i)) +# end +# ntuple(f, Val(N)) +# dest +# end + +# new version @inline function Base.copyto!(dest::ArrayPartition, - bc::Broadcast.Broadcasted{ArrayPartitionStyle{Style}}) where { - Style, -} + bc::Broadcast.Broadcasted{ArrayPartitionStyle{Style}}) where {Style} N = npartitions(dest, bc) - @inline function f(i) - copyto!(dest.x[i], unpack(bc, i)) + # Check if this is a simple enough broadcast that we can optimize + if bc.f isa Union{typeof(+), typeof(*), typeof(muladd)} + # @show "hey", bc, N + @inbounds for i in 1:N + # Use materialize! which is more efficient than copyto! for simple broadcasts + Base.Broadcast.materialize!(dest.x[i], unpack(bc, i)) + end + else + # Fall back to original implementation for complex broadcasts + @inline function f(i) + copyto!(dest.x[i], unpack(bc, i)) + end + ntuple(f, Val(N)) end - ntuple(f, Val(N)) dest end @@ -411,8 +433,10 @@ end i) where {Style <: Broadcast.DefaultArrayStyle} Broadcast.Broadcasted{Style}(bc.f, unpack_args(i, bc.args)) end -unpack(x, ::Any) = x -unpack(x::ArrayPartition, i) = x.x[i] + +@inline unpack(x, ::Any) = x +@inline unpack(x::ArrayPartition, i) = x.x[i] + @inline function unpack_args(i, args::Tuple) (unpack(args[1], i), unpack_args(i, Base.tail(args))...) diff --git a/src/named_array_partition.jl b/src/named_array_partition.jl index de8fa91a..75460016 100644 --- a/src/named_array_partition.jl +++ b/src/named_array_partition.jl @@ -135,6 +135,7 @@ end NamedArrayPartition(f, N, getfield(x, :names_to_indices)) end +# TODO: has this also performance problems and can be improved? @inline function Base.copyto!(dest::NamedArrayPartition, bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{NamedArrayPartition}}) N = npartitions(dest, bc) From f0f51c3c0e81733a1a17b4cb962fa999bafbbd36 Mon Sep 17 00:00:00 2001 From: Collin Wittenstein Date: Sat, 19 Jul 2025 17:33:08 +0200 Subject: [PATCH 2/9] new example to readme --- README.md | 21 +++++++++++++++++++++ src/array_partition.jl | 1 - 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index af34685c..cc53aa52 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ the documentation, which contains the unreleased features. ## Example +### VectorOfArray + ```julia using RecursiveArrayTools a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] @@ -30,11 +32,30 @@ vA = VectorOfArray(a) vB = VectorOfArray(b) vA .* vB # Now all standard array stuff works! +``` +### ArrayPartition + +```julia a = (rand(5), rand(5)) b = (rand(5), rand(5)) pA = ArrayPartition(a) pB = ArrayPartition(b) pA .* pB # Now all standard array stuff works! + + +x0 = rand(3,3) +v0 = rand(3,3) +a0 = rand(3,3) +u0 = ArrayPartition(x0, v0, a0) +u0.x[1] == x0 # true + +u0 .+= 1 +u0.x[2] == v0 # still true + +# do some calculations creating a new partitioned array +unew = u0 * 10 +# easily access the individual components without having to rely on complicated indexing +xnew, vnew, anew = unew.x ``` diff --git a/src/array_partition.jl b/src/array_partition.jl index 978f3427..210b69fc 100644 --- a/src/array_partition.jl +++ b/src/array_partition.jl @@ -383,7 +383,6 @@ end N = npartitions(dest, bc) # Check if this is a simple enough broadcast that we can optimize if bc.f isa Union{typeof(+), typeof(*), typeof(muladd)} - # @show "hey", bc, N @inbounds for i in 1:N # Use materialize! which is more efficient than copyto! for simple broadcasts Base.Broadcast.materialize!(dest.x[i], unpack(bc, i)) From b08a940ec6e3b7c5c444f746a6b3e10979dffb5b Mon Sep 17 00:00:00 2001 From: Collin Wittenstein Date: Mon, 21 Jul 2025 13:40:51 +0200 Subject: [PATCH 3/9] new copyto! function --- src/array_partition.jl | 21 +++------------------ src/named_array_partition.jl | 6 ++---- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/src/array_partition.jl b/src/array_partition.jl index 210b69fc..d693c6b4 100644 --- a/src/array_partition.jl +++ b/src/array_partition.jl @@ -364,28 +364,13 @@ end ArrayPartition(f, N) end -# old version -# @inline function Base.copyto!(dest::ArrayPartition, -# bc::Broadcast.Broadcasted{ArrayPartitionStyle{Style}}) where { -# Style, -# } -# N = npartitions(dest, bc) -# @inline function f(i) -# copyto!(dest.x[i], unpack(bc, i)) -# end -# ntuple(f, Val(N)) -# dest -# end - -# new version @inline function Base.copyto!(dest::ArrayPartition, bc::Broadcast.Broadcasted{ArrayPartitionStyle{Style}}) where {Style} N = npartitions(dest, bc) - # Check if this is a simple enough broadcast that we can optimize - if bc.f isa Union{typeof(+), typeof(*), typeof(muladd)} + # If dest is all the same underlying array type, use for-loop + if all(x isa typeof(first(dest.x)) for x in dest.x) @inbounds for i in 1:N - # Use materialize! which is more efficient than copyto! for simple broadcasts - Base.Broadcast.materialize!(dest.x[i], unpack(bc, i)) + copyto!(dest.x[i], unpack(bc, i)) end else # Fall back to original implementation for complex broadcasts diff --git a/src/named_array_partition.jl b/src/named_array_partition.jl index 75460016..f77fa049 100644 --- a/src/named_array_partition.jl +++ b/src/named_array_partition.jl @@ -135,14 +135,12 @@ end NamedArrayPartition(f, N, getfield(x, :names_to_indices)) end -# TODO: has this also performance problems and can be improved? @inline function Base.copyto!(dest::NamedArrayPartition, bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{NamedArrayPartition}}) N = npartitions(dest, bc) - @inline function f(i) - copyto!(ArrayPartition(dest).x[i], unpack(bc, i)) + @inbounds for i in 1:N + copyto!(dest.x[i], unpack(bc, i)) end - ntuple(f, Val(N)) return dest end From 02ac939b95069dcf6b4208ee325f5e1a1df9944c Mon Sep 17 00:00:00 2001 From: Collin Wittenstein Date: Mon, 21 Jul 2025 13:42:15 +0200 Subject: [PATCH 4/9] deleted spaces --- src/array_partition.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/array_partition.jl b/src/array_partition.jl index d693c6b4..89261f4b 100644 --- a/src/array_partition.jl +++ b/src/array_partition.jl @@ -417,11 +417,9 @@ end i) where {Style <: Broadcast.DefaultArrayStyle} Broadcast.Broadcasted{Style}(bc.f, unpack_args(i, bc.args)) end - @inline unpack(x, ::Any) = x @inline unpack(x::ArrayPartition, i) = x.x[i] - @inline function unpack_args(i, args::Tuple) (unpack(args[1], i), unpack_args(i, Base.tail(args))...) end From 71942b991e598ccb69fe3e72a420c98e0d85329a Mon Sep 17 00:00:00 2001 From: Collin Wittenstein Date: Mon, 21 Jul 2025 13:47:01 +0200 Subject: [PATCH 5/9] changed readme back to original --- README.md | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/README.md b/README.md index cc53aa52..af34685c 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,6 @@ the documentation, which contains the unreleased features. ## Example -### VectorOfArray - ```julia using RecursiveArrayTools a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] @@ -32,30 +30,11 @@ vA = VectorOfArray(a) vB = VectorOfArray(b) vA .* vB # Now all standard array stuff works! -``` -### ArrayPartition - -```julia a = (rand(5), rand(5)) b = (rand(5), rand(5)) pA = ArrayPartition(a) pB = ArrayPartition(b) pA .* pB # Now all standard array stuff works! - - -x0 = rand(3,3) -v0 = rand(3,3) -a0 = rand(3,3) -u0 = ArrayPartition(x0, v0, a0) -u0.x[1] == x0 # true - -u0 .+= 1 -u0.x[2] == v0 # still true - -# do some calculations creating a new partitioned array -unew = u0 * 10 -# easily access the individual components without having to rely on complicated indexing -xnew, vnew, anew = unew.x ``` From 92065274dc719497c670fd05b8767002690ee391 Mon Sep 17 00:00:00 2001 From: Collin Wittenstein <126870995+cwittens@users.noreply.github.com> Date: Mon, 21 Jul 2025 18:02:56 +0200 Subject: [PATCH 6/9] Update README.md --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index af34685c..bf041dfc 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ the documentation, which contains the unreleased features. ## Example +### VectorOfArray + ```julia using RecursiveArrayTools a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] @@ -30,11 +32,30 @@ vA = VectorOfArray(a) vB = VectorOfArray(b) vA .* vB # Now all standard array stuff works! +``` +### ArrayPartition + +```julia a = (rand(5), rand(5)) b = (rand(5), rand(5)) pA = ArrayPartition(a) pB = ArrayPartition(b) pA .* pB # Now all standard array stuff works! + +# or do: +x0 = rand(3,3) +v0 = rand(3,3) +a0 = rand(3,3) +u0 = ArrayPartition(x0, v0, a0) +u0.x[1] == x0 # true + +u0 .+= 1 +u0.x[2] == v0 # still true + +# do some calculations creating a new partitioned array +unew = u0 * 10 +# easily access the individual components without having to rely on complicated indexing +xnew, vnew, anew = unew.x ``` From dd1ad4c3f07e3434a3bb28f2ad42eaf258af5048 Mon Sep 17 00:00:00 2001 From: Hendrik Ranocha Date: Fri, 25 Jul 2025 11:42:04 +0200 Subject: [PATCH 7/9] bump version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index c8e5752b..1380b7dd 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "RecursiveArrayTools" uuid = "731186ca-8d62-57ce-b412-fbd966d074cd" authors = ["Chris Rackauckas "] -version = "3.35.0" +version = "3.36.0" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" From f28e96fa19502090ddaea59c34ff2d88b5c6baaf Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 26 Jul 2025 22:51:19 -0400 Subject: [PATCH 8/9] Update Downgrade.yml --- .github/workflows/Downgrade.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/Downgrade.yml b/.github/workflows/Downgrade.yml index d6d5a4e4..3eef5129 100644 --- a/.github/workflows/Downgrade.yml +++ b/.github/workflows/Downgrade.yml @@ -15,18 +15,22 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - version: ['1'] group: - Core - - Downstream + downgrade_mode: ['alldeps'] + julia-version: ['1.10'] steps: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v2 with: - version: ${{ matrix.version }} - - uses: julia-actions/julia-downgrade-compat@v1 + version: ${{ matrix.julia-version }} + - uses: julia-actions/julia-downgrade-compat@v2 # if: ${{ matrix.version == '1.6' }} with: skip: Pkg,TOML - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 + with: + ALLOW_RERESOLVE: false + env: + GROUP: ${{ matrix.group }} From 10a3a15a72dcf29749ec786dc5a972e8abbd9bfe Mon Sep 17 00:00:00 2001 From: ChrisRackauckas Date: Sun, 27 Jul 2025 00:27:25 -0400 Subject: [PATCH 9/9] Bump minimum versions for downgrade CI compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bump ArrayInterface minimum to 7.10 - Bump SymbolicIndexingInterface minimum to 0.3.30 - Fix copyto\! bug in NamedArrayPartition that was accessing non-existent field These changes ensure the package works with the minimal dependency versions as determined by the downgrade CI resolver. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- Project.toml | 4 ++-- src/named_array_partition.jl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index c8e5752b..4297fd3c 100644 --- a/Project.toml +++ b/Project.toml @@ -43,7 +43,7 @@ RecursiveArrayToolsZygoteExt = "Zygote" [compat] Adapt = "3.4, 4" Aqua = "0.8" -ArrayInterface = "7.6" +ArrayInterface = "7.10" DocStringExtensions = "0.9" FastBroadcast = "0.2.8, 0.3" ForwardDiff = "0.10.19, 1" @@ -65,7 +65,7 @@ StaticArrays = "1.6" StaticArraysCore = "1.4" Statistics = "1.10, 1.11" StructArrays = "0.6.11, 0.7" -SymbolicIndexingInterface = "0.3.25" +SymbolicIndexingInterface = "0.3.30" Tables = "1.11" Test = "1" Tracker = "0.2.15" diff --git a/src/named_array_partition.jl b/src/named_array_partition.jl index f77fa049..1db2b981 100644 --- a/src/named_array_partition.jl +++ b/src/named_array_partition.jl @@ -139,7 +139,7 @@ end bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{NamedArrayPartition}}) N = npartitions(dest, bc) @inbounds for i in 1:N - copyto!(dest.x[i], unpack(bc, i)) + copyto!(getfield(dest, :array_partition).x[i], unpack(bc, i)) end return dest end