From 1ca2cef1419b6e885ab254055ee3d88233f3a64e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 3 Mar 2026 17:08:58 +0100 Subject: [PATCH 1/5] Add neural network example --- perf/array_expr.jl | 11 ++++++++ perf/array_of_variables.jl | 53 ++++++++++++++++++++++++++++++++++++++ perf/neural.jl | 12 +++++++++ test/Project.toml | 5 ++++ 4 files changed, 81 insertions(+) create mode 100644 perf/array_expr.jl create mode 100644 perf/array_of_variables.jl create mode 100644 perf/neural.jl diff --git a/perf/array_expr.jl b/perf/array_expr.jl new file mode 100644 index 0000000..b1c0c82 --- /dev/null +++ b/perf/array_expr.jl @@ -0,0 +1,11 @@ +struct GenericArrayExpr{N,V<:AbstractVariableRef} + head::Symbol + args::Vector{Any} + size::NTuple{N,Int} +end + +const ArrayExpr{N} = GenericArrayExpr{N,JuMP.VariableRef} + +function LinearAlgebra.mul(A::MatrixOfVariables, B::Matrix) + return GenericArrayExpr{2,variable_ref_type(A.model)}(:*, Any[A, B], (size(A, 1), size(B, 2))) +end diff --git a/perf/array_of_variables.jl b/perf/array_of_variables.jl new file mode 100644 index 0000000..3a3a1d1 --- /dev/null +++ b/perf/array_of_variables.jl @@ -0,0 +1,53 @@ +# Taken out of GenOpt, to be moved inside ArrayDiff.jl and then add ArrayDiff as dependency to GenOpt +import JuMP +import LinearAlgebra + +struct ArrayOfVariables{T,N} <: AbstractArray{JuMP.GenericVariableRef{T},N} + model::JuMP.GenericModel{T} + offset::Int64 + size::NTuple{N,Int64} +end + +const MatrixOfVariables{T} = ArrayOfVariables{T,2} + +Base.size(array::ArrayOfVariables) = array.size +function Base.getindex(A::ArrayOfVariables{T}, I...) where {T} + index = + A.offset + Base._to_linear_index(Base.CartesianIndices(A.size), I...) + return JuMP.GenericVariableRef{T}(A.model, MOI.VariableIndex(index)) +end + +function JuMP.Containers.container( + f::Function, + indices::JuMP.Containers.VectorizedProductIterator{NTuple{N,Base.OneTo{Int}}}, + ::Type{ArrayOfVariables}, +) where {N} + return to_generator(JuMP.Containers.container(f, indices, Array)) +end + +JuMP._is_real(::ArrayOfVariables) = true + +function Base.convert( + ::Type{ArrayOfVariables{T,N}}, + array::Array{JuMP.GenericVariableRef{T},N}, +) where {T,N} + model = JuMP.owner_model(array[1]) + offset = JuMP.index(array[1]).value - 1 + for i in eachindex(array) + @assert JuMP.owner_model(array[i]) === model + @assert JuMP.index(array[i]).value == offset + i + end + return ArrayOfVariables{T,N}(model, offset, size(array)) +end + +function to_generator(array::Array{JuMP.GenericVariableRef{T},N}) where {T,N} + return convert(ArrayOfVariables{T,N}, array) +end + +function Base.show(io::IO, ::MIME"text/plain", v::ArrayOfVariables) + return println(io, Base.summary(v), " with offset ", v.offset) +end + +function Base.show(io::IO, v::ArrayOfVariables) + return show(io, MIME"text/plain"(), v) +end diff --git a/perf/neural.jl b/perf/neural.jl new file mode 100644 index 0000000..7dd44f1 --- /dev/null +++ b/perf/neural.jl @@ -0,0 +1,12 @@ +# Needs https://github.com/jump-dev/JuMP.jl/pull/3451 +using JuMP + +include(joinpath(@__DIR__, "array_of_variables.jl")) +include(joinpath(@__DIR__, "array_expr.jl")) + +n = 2 +X = rand(n, n) +model = Model() +@variable(model, W[1:n, 1:n], container = ArrayOfVariables) +W * X +tanh.(W * X) diff --git a/test/Project.toml b/test/Project.toml index d768ff2..0b5a41e 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,6 +1,8 @@ [deps] ArrayDiff = "c45fa1ca-6901-44ac-ae5b-5513a4852d50" Calculus = "49dc2e85-a5d0-5ad3-a950-438e2897f1b9" +GenOpt = "f2c049d8-7489-4223-990c-4f1c121a4cde" +JuMP = "4076af6c-e467-56ae-b986-b466b2749572" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" @@ -8,3 +10,6 @@ Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[sources] +ArrayDiff = {path = ".."} From 02898c0d0cb46244d61d2b1bad56dbf84d58fbb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 3 Mar 2026 20:09:40 +0100 Subject: [PATCH 2/5] Remove from perf --- perf/array_expr.jl | 11 -------- perf/array_of_variables.jl | 53 -------------------------------------- 2 files changed, 64 deletions(-) delete mode 100644 perf/array_expr.jl delete mode 100644 perf/array_of_variables.jl diff --git a/perf/array_expr.jl b/perf/array_expr.jl deleted file mode 100644 index b1c0c82..0000000 --- a/perf/array_expr.jl +++ /dev/null @@ -1,11 +0,0 @@ -struct GenericArrayExpr{N,V<:AbstractVariableRef} - head::Symbol - args::Vector{Any} - size::NTuple{N,Int} -end - -const ArrayExpr{N} = GenericArrayExpr{N,JuMP.VariableRef} - -function LinearAlgebra.mul(A::MatrixOfVariables, B::Matrix) - return GenericArrayExpr{2,variable_ref_type(A.model)}(:*, Any[A, B], (size(A, 1), size(B, 2))) -end diff --git a/perf/array_of_variables.jl b/perf/array_of_variables.jl deleted file mode 100644 index 3a3a1d1..0000000 --- a/perf/array_of_variables.jl +++ /dev/null @@ -1,53 +0,0 @@ -# Taken out of GenOpt, to be moved inside ArrayDiff.jl and then add ArrayDiff as dependency to GenOpt -import JuMP -import LinearAlgebra - -struct ArrayOfVariables{T,N} <: AbstractArray{JuMP.GenericVariableRef{T},N} - model::JuMP.GenericModel{T} - offset::Int64 - size::NTuple{N,Int64} -end - -const MatrixOfVariables{T} = ArrayOfVariables{T,2} - -Base.size(array::ArrayOfVariables) = array.size -function Base.getindex(A::ArrayOfVariables{T}, I...) where {T} - index = - A.offset + Base._to_linear_index(Base.CartesianIndices(A.size), I...) - return JuMP.GenericVariableRef{T}(A.model, MOI.VariableIndex(index)) -end - -function JuMP.Containers.container( - f::Function, - indices::JuMP.Containers.VectorizedProductIterator{NTuple{N,Base.OneTo{Int}}}, - ::Type{ArrayOfVariables}, -) where {N} - return to_generator(JuMP.Containers.container(f, indices, Array)) -end - -JuMP._is_real(::ArrayOfVariables) = true - -function Base.convert( - ::Type{ArrayOfVariables{T,N}}, - array::Array{JuMP.GenericVariableRef{T},N}, -) where {T,N} - model = JuMP.owner_model(array[1]) - offset = JuMP.index(array[1]).value - 1 - for i in eachindex(array) - @assert JuMP.owner_model(array[i]) === model - @assert JuMP.index(array[i]).value == offset + i - end - return ArrayOfVariables{T,N}(model, offset, size(array)) -end - -function to_generator(array::Array{JuMP.GenericVariableRef{T},N}) where {T,N} - return convert(ArrayOfVariables{T,N}, array) -end - -function Base.show(io::IO, ::MIME"text/plain", v::ArrayOfVariables) - return println(io, Base.summary(v), " with offset ", v.offset) -end - -function Base.show(io::IO, v::ArrayOfVariables) - return show(io, MIME"text/plain"(), v) -end From 298ecae0cadce045edc3825b7be3fe7b2c13607e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 3 Mar 2026 20:10:23 +0100 Subject: [PATCH 3/5] Add tests --- Project.toml | 4 ++++ perf/neural.jl | 7 ++----- src/ArrayDiff.jl | 2 ++ src/JuMP/JuMP.jl | 11 +++++++++++ src/JuMP/nlp_expr.jl | 13 +++++++++++++ src/JuMP/operators.jl | 3 +++ src/JuMP/print.jl | 11 +++++++++++ src/JuMP/variables.jl | 43 +++++++++++++++++++++++++++++++++++++++++++ test/JuMP.jl | 39 +++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 1 + 10 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 src/JuMP/JuMP.jl create mode 100644 src/JuMP/nlp_expr.jl create mode 100644 src/JuMP/operators.jl create mode 100644 src/JuMP/print.jl create mode 100644 src/JuMP/variables.jl create mode 100644 test/JuMP.jl diff --git a/Project.toml b/Project.toml index d941fb8..729a584 100644 --- a/Project.toml +++ b/Project.toml @@ -7,6 +7,8 @@ authors = ["Sophie Lequeu ", "Benoît Legat Date: Wed, 4 Mar 2026 12:21:08 +0100 Subject: [PATCH 4/5] Fix --- src/JuMP/nlp_expr.jl | 7 +++++-- src/JuMP/operators.jl | 6 +++++- src/JuMP/variables.jl | 4 +++- test/JuMP.jl | 10 +++++++--- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/JuMP/nlp_expr.jl b/src/JuMP/nlp_expr.jl index 1cc9337..76ef5ad 100644 --- a/src/JuMP/nlp_expr.jl +++ b/src/JuMP/nlp_expr.jl @@ -1,4 +1,5 @@ -struct GenericArrayExpr{V<:JuMP.AbstractVariableRef,N} <: AbstractJuMPArray{JuMP.GenericNonlinearExpr{V},N} +struct GenericArrayExpr{V<:JuMP.AbstractVariableRef,N} <: + AbstractJuMPArray{JuMP.GenericNonlinearExpr{V},N} head::Symbol args::Vector{Any} size::NTuple{N,Int} @@ -7,7 +8,9 @@ end const ArrayExpr{N} = GenericArrayExpr{JuMP.VariableRef,N} function Base.getindex(::GenericArrayExpr, args...) - error("`getindex` not implemented, build vectorized expression instead") + return error( + "`getindex` not implemented, build vectorized expression instead", + ) end Base.size(expr::GenericArrayExpr) = expr.size diff --git a/src/JuMP/operators.jl b/src/JuMP/operators.jl index 2eeae6d..d81bd35 100644 --- a/src/JuMP/operators.jl +++ b/src/JuMP/operators.jl @@ -1,3 +1,7 @@ function Base.:(*)(A::MatrixOfVariables, B::Matrix) - return GenericArrayExpr{JuMP.variable_ref_type(A.model),2}(:*, Any[A, B], (size(A, 1), size(B, 2))) + return GenericArrayExpr{JuMP.variable_ref_type(A.model),2}( + :*, + Any[A, B], + (size(A, 1), size(B, 2)), + ) end diff --git a/src/JuMP/variables.jl b/src/JuMP/variables.jl index ef563a7..f70de6a 100644 --- a/src/JuMP/variables.jl +++ b/src/JuMP/variables.jl @@ -17,7 +17,9 @@ end function JuMP.Containers.container( f::Function, - indices::JuMP.Containers.VectorizedProductIterator{NTuple{N,Base.OneTo{Int}}}, + indices::JuMP.Containers.VectorizedProductIterator{ + NTuple{N,Base.OneTo{Int}}, + }, ::Type{ArrayOfVariables}, ) where {N} return to_generator(JuMP.Containers.container(f, indices, Array)) diff --git a/test/JuMP.jl b/test/JuMP.jl index 4846000..e0de6c8 100644 --- a/test/JuMP.jl +++ b/test/JuMP.jl @@ -25,11 +25,15 @@ function test_array_product() @test JuMP.index(W[1, 1]) == MOI.VariableIndex(1) @test JuMP.index(W[2, 1]) == MOI.VariableIndex(2) @test JuMP.index(W[2]) == MOI.VariableIndex(2) - @test sprint(show, W) == "2×2 ArrayDiff.ArrayOfVariables{Float64, 2} with offset 0" + @test sprint(show, W) == + "2×2 ArrayDiff.ArrayOfVariables{Float64, 2} with offset 0" prod = W * X @test prod isa ArrayDiff.ArrayExpr{2} - @test sprint(show, prod) == "2×2 ArrayDiff.GenericArrayExpr{VariableRef, 2}" - err = ErrorException("`getindex` not implemented, build vectorized expression instead") + @test sprint(show, prod) == + "2×2 ArrayDiff.GenericArrayExpr{JuMP.VariableRef, 2}" + err = ErrorException( + "`getindex` not implemented, build vectorized expression instead", + ) @test_throws err prod[1, 1] return end From c5fb26f8e4dd6494857c3b19c12f07c7784e8703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 4 Mar 2026 12:33:58 +0100 Subject: [PATCH 5/5] remove LA --- Project.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Project.toml b/Project.toml index 729a584..5670237 100644 --- a/Project.toml +++ b/Project.toml @@ -8,7 +8,6 @@ Calculus = "49dc2e85-a5d0-5ad3-a950-438e2897f1b9" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" -LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" @@ -20,7 +19,6 @@ Calculus = "0.5.2" DataStructures = "0.18, 0.19" ForwardDiff = "1" JuMP = "1.29.4" -LinearAlgebra = "1.12.0" MathOptInterface = "1.40" NaNMath = "1" SparseArrays = "1.10"