Skip to content

Commit 98d5720

Browse files
allow arrays of arrays of stuff in build_function iip
Fixes #262
1 parent fc03508 commit 98d5720

File tree

3 files changed

+220
-1
lines changed

3 files changed

+220
-1
lines changed

src/build_function.jl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,15 @@ function _build_function(target::JuliaTarget, rhss, args...;
107107
fargs = Expr(:tuple,argnames...)
108108

109109
X = gensym(:MTIIPVar)
110-
if rhss isa SparseMatrixCSC
110+
if eltype(eltype(rhss)) <: AbstractArray # Array of arrays of arrays
111+
ip_sys_exprs = reduce(vcat,[vec(reduce(vcat,[vec([:($X[$i][$j][$k] = $(conv(rhs))) for (k, rhs) enumerate(rhsel2)]) for (j, rhsel2) enumerate(rhsel)],init=Expr[])) for (i,rhsel) enumerate(rhss)],init=Expr[])
112+
elseif eltype(eltype(rhss)) <: SparseMatrixCSC # Array of arrays of arrays
113+
ip_sys_exprs = reduce(vcat,[vec(reduce(vcat,[vec([:($X[$i][$j].nzval[$k] = $(conv(rhs))) for (k, rhs) enumerate(rhsel2)]) for (j, rhsel2) enumerate(rhsel)])) for (i,rhsel) enumerate(rhss)])
114+
elseif eltype(rhss) <: SparseMatrixCSC # Array of sparse matrices
115+
ip_sys_exprs = reduce(vcat,[vec([:($X[$i].nzval[$j] = $(conv(rhs))) for (j, rhs) enumerate(rhsel)]) for (i,rhsel) enumerate(rhss)])
116+
elseif eltype(rhss) <: AbstractArray # Array of arrays
117+
ip_sys_exprs = reduce(vcat,[vec([:($X[$i][$j] = $(conv(rhs))) for (j, rhs) enumerate(rhsel)]) for (i,rhsel) enumerate(rhss)])
118+
elseif rhss isa SparseMatrixCSC
111119
ip_sys_exprs = [:($X.nzval[$i] = $(conv(rhs))) for (i, rhs) enumerate(rhss.nzval)]
112120
else
113121
ip_sys_exprs = [:($X[$i] = $(conv(rhs))) for (i, rhs) enumerate(rhss)]

src/direct.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,5 @@ simplified_expr(c::Constant) = c.value
5454
function simplified_expr(eq::Equation)
5555
Expr(:(=), simplified_expr(eq.lhs), simplified_expr(eq.rhs))
5656
end
57+
58+
simplified_expr(eq::AbstractArray) = simplified_expr.(eq)
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
using ModelingToolkit, Test, SparseArrays
2+
@variables a b c
3+
4+
# Auxiliary Functions and Constants
5+
get_sparsity_pattern(h::Array{Expression}) = sparse(Int64.(map(~, h .=== ModelingToolkit.Constant(0))))
6+
get_sparsity_pattern(h::SparseMatrixCSC{Expression,Int64}) = sparse(Int64.(map(~, h .=== ModelingToolkit.Constant(0))))
7+
get_sparsity_pattern(h::SparseVector{Expression,Int64}) = sparse(Int64.(map(~, h .=== ModelingToolkit.Constant(0))))
8+
9+
input = [1, 2, 3]
10+
11+
# ===== Dense tests =====
12+
# Arrays of Matrices
13+
h_dense_arraymat = [[a 1; b 0], [0 0; 0 0], [a c; 1 0]] # empty array support required
14+
function h_dense_arraymat_julia(x)
15+
a, b, c = x
16+
return [[a[1] 1; b[1] 0], [0 0; 0 0], [a[1] c[1]; 1 0]]
17+
end
18+
function h_dense_arraymat_julia!(out, x)
19+
a, b, c = x
20+
out[1] .= [a[1] 1; b[1] 0]
21+
out[2] .= [0 0; 0 0]
22+
out[3] .= [a[1] c[1]; 1 0]
23+
end
24+
25+
h_dense_arraymat_str = ModelingToolkit.build_function(h_dense_arraymat, [a, b, c])
26+
h_dense_arraymat_oop = eval(h_dense_arraymat_str[1])
27+
h_dense_arraymat_ip! = eval(h_dense_arraymat_str[2])
28+
out_1_arraymat = [Array{Int64}(undef, 2, 2) for i in 1:3]
29+
out_2_arraymat = [similar(x) for x in out_1_arraymat]
30+
julia_dense_arraymat = h_dense_arraymat_julia(input)
31+
mtk_dense_arraymat = h_dense_arraymat_oop(input)
32+
@test_broken julia_dense_arraymat == mtk_dense_arraymat
33+
h_dense_arraymat_julia!(out_1_arraymat, input)
34+
h_dense_arraymat_ip!(out_2_arraymat, input)
35+
@test out_1_arraymat == out_2_arraymat
36+
37+
# Arrays of 1D Vectors
38+
h_dense_arrayvec = [[a, 0, c], [0, 0, 0], [1, a, b]] # same for empty vectors, etc.
39+
function h_dense_arrayvec_julia(x)
40+
a, b, c = x
41+
return [[a[1], 0, c[1]], [0, 0, 0], [1, a[1], b[1]]]
42+
end
43+
function h_dense_arrayvec_julia!(out, x)
44+
a, b, c = x
45+
out[1] .= [a[1], 0, c[1]]
46+
out[2] .= [0, 0, 0]
47+
out[3] .= [1, a[1], b[1]]
48+
end
49+
50+
h_dense_arrayvec_str = ModelingToolkit.build_function(h_dense_arrayvec, [a, b, c])
51+
h_dense_arrayvec_oop = eval(h_dense_arrayvec_str[1])
52+
h_dense_arrayvec_ip! = eval(h_dense_arrayvec_str[2])
53+
out_1_arrayvec = [Vector{Int64}(undef, 3) for i in 1:3]
54+
out_2_arrayvec = [Vector{Int64}(undef, 3) for i in 1:3]
55+
julia_dense_arrayvec = h_dense_arrayvec_julia(input)
56+
mtk_dense_arrayvec = h_dense_arrayvec_oop(input)
57+
@test_broken julia_dense_arrayvec == mtk_dense_arrayvec
58+
mtk_dense_arrayvec = @test_broken h_dense_arrayvec_oop(input)
59+
h_dense_arrayvec_julia!(out_1_arrayvec, input)
60+
h_dense_arrayvec_ip!(out_2_arrayvec, input)
61+
@test out_1_arrayvec == out_2_arrayvec
62+
63+
# Arrays of Arrays of Matrices
64+
h_dense_arrayNestedMat = [[[a 1; b 0], [0 0; 0 0]], [[b 1; a 0], [b c; 0 1]]]
65+
function h_dense_arrayNestedMat_julia(x)
66+
a, b, c = x
67+
return [[[a[1] 1; b[1] 0], [0 0; 0 0]], [[b[1] 1; a[1] 0], [b[1] c[1]; 0 1]]]
68+
end
69+
function h_dense_arrayNestedMat_julia!(out, x)
70+
a, b, c = x
71+
out[1][1] .= [a[1] 1; b[1] 0]
72+
out[1][2] .= [0 0; 0 0]
73+
out[2][1] .= [b[1] 1; a[1] 0]
74+
out[2][2] .= [b[1] c[1]; 0 1]
75+
end
76+
77+
h_dense_arrayNestedMat_str = ModelingToolkit.build_function(h_dense_arrayNestedMat, [a, b, c])
78+
h_dense_arrayNestedMat_oop = eval(h_dense_arrayNestedMat_str[1])
79+
h_dense_arrayNestedMat_ip! = eval(h_dense_arrayNestedMat_str[2])
80+
out_1_arrayNestedMat = [[rand(Int64, 2, 2), rand(Int64, 2, 2)], [rand(Int64, 2, 2), rand(Int64, 2, 2)]] # avoid undef broadcasting issue
81+
out_2_arrayNestedMat = [[rand(Int64, 2, 2), rand(Int64, 2, 2)], [rand(Int64, 2, 2), rand(Int64, 2, 2)]]
82+
julia_dense_arrayNestedMat = h_dense_arrayNestedMat_julia(input)
83+
mtk_dense_arrayNestedMat = h_dense_arrayNestedMat_oop(input)
84+
@test_broken julia_dense_arrayNestedMat == mtk_dense_arrayNestedMat
85+
h_dense_arrayNestedMat_julia!(out_1_arrayNestedMat, input)
86+
h_dense_arrayNestedMat_ip!(out_2_arrayNestedMat, input)
87+
@test out_1_arrayNestedMat == out_2_arrayNestedMat
88+
89+
# ===== Sparse tests =====
90+
# Array of Matrices
91+
h_sparse_arraymat = sparse.([[a 1; b 0], [0 0; 0 0], [a c; 1 0]])
92+
function h_sparse_arraymat_julia(x)
93+
a, b, c = x
94+
return [sparse([a[1] 1; b[1] 0]), sparse([0 0; 0 0]), sparse([a[1] c[1]; 1 0])] # necessary because sparse([]) is a SparseVector, not SparseMatrix
95+
end
96+
function h_sparse_arraymat_julia!(out, x)
97+
a, b, c = x
98+
out[1][1, 1] = a[1]
99+
out[1][1, 2] = 1
100+
out[1][2, 1] = b[1]
101+
out[2] = sparse([0 0; 0 0]) # no undef constructor for SparseMatrixCSC
102+
out[3][1, 1] = a[1]
103+
out[3][1, 2] = c[1]
104+
out[3][2, 1] = 1
105+
end
106+
107+
h_sparse_arraymat_str = ModelingToolkit.build_function(h_sparse_arraymat, [a, b, c])
108+
h_sparse_arraymat_oop = eval(h_sparse_arraymat_str[1])
109+
h_sparse_arraymat_ip! = eval(h_sparse_arraymat_str[2])
110+
h_sparse_arraymat_sparsity_patterns = map(get_sparsity_pattern, h_sparse_arraymat)
111+
out_1_arraymat = [similar(h) for h in h_sparse_arraymat_sparsity_patterns]
112+
out_2_arraymat = [similar(h) for h in h_sparse_arraymat_sparsity_patterns] # can't do similar() because it will just be #undef, with the wrong sparsity pattern
113+
julia_sparse_arraymat = h_sparse_arraymat_julia(input)
114+
mtk_sparse_arraymat = h_sparse_arraymat_oop(input)
115+
@test_broken julia_sparse_arraymat == mtk_sparse_arraymat
116+
h_sparse_arraymat_julia!(out_1_arraymat, input)
117+
h_sparse_arraymat_ip!(out_2_arraymat, input)
118+
@test out_1_arraymat == out_2_arraymat
119+
120+
# Array of 1D Vectors
121+
h_sparse_arrayvec = sparse.([[a, 0, c], [0, 0, 0], [1, a, b]])
122+
function h_sparse_arrayvec_julia(x)
123+
a, b, c = x
124+
return sparse.([[a[1], 0, c[1]], [0, 0, 0], [1, a[1], b[1]]])
125+
end
126+
function h_sparse_arrayvec_julia!(out, x)
127+
a, b, c = x
128+
out[1][1] = a[1]
129+
out[1][3] = c[1]
130+
out[2] = sparse([0, 0, 0]) # necessary because sparsity pattern is 3 elements with 0 stored, not 0 elements
131+
out[3][1] = 1
132+
out[3][2] = a[1]
133+
out[3][3] = b[1]
134+
end
135+
136+
h_sparse_arrayvec_str = ModelingToolkit.build_function(h_sparse_arrayvec, [a, b, c])
137+
h_sparse_arrayvec_oop = eval(h_sparse_arrayvec_str[1])
138+
h_sparse_arrayvec_ip! = eval(h_sparse_arrayvec_str[2])
139+
h_sparse_arrayvec_sparsity_patterns = map(get_sparsity_pattern, h_sparse_arrayvec)
140+
out_1_arrayvec = [similar(h) for h in h_sparse_arrayvec_sparsity_patterns]
141+
out_2_arrayvec = [similar(h) for h in h_sparse_arrayvec_sparsity_patterns]
142+
julia_sparse_arrayvec = h_sparse_arrayvec_julia(input)
143+
mtk_sparse_arrayvec = h_sparse_arrayvec_oop(input)
144+
@test_broken julia_sparse_arrayvec == mtk_sparse_arrayvec
145+
h_sparse_arrayvec_julia!(out_1_arrayvec, input)
146+
h_sparse_arrayvec_ip!(out_2_arrayvec, input)
147+
@test out_1_arrayvec == out_2_arrayvec
148+
149+
# Arrays of Arrays of Matrices
150+
h_sparse_arrayNestedMat = [[[a 1; b 0], [0 0; 0 0]], [[b 1; a 0], [b c; 0 1]]]
151+
function h_sparse_arrayNestedMat_julia(x)
152+
a, b, c = x
153+
return [sparse.([[a[1] 1; b[1] 0], [0 0; 0 0]]), sparse.([[b[1] 1; a[1] 0], [b[1] c[1]; 0 1]])]
154+
end
155+
function h_sparse_arrayNestedMat_julia!(out, x)
156+
a, b, c = x
157+
out[1][1][1, 1] = a[1]
158+
out[1][1][1, 2] = 1
159+
out[1][1][2, 1] = b[1]
160+
out[1][2] = sparse([0 0; 0 0])
161+
out[2][1][1, 1] = b[1]
162+
out[2][1][1, 2] = 1
163+
out[2][1][2, 1] = a[1]
164+
out[2][2][1, 1] = b[1]
165+
out[2][2][1, 2] = c[1]
166+
out[2][2][2, 2] = 1
167+
end
168+
169+
h_sparse_arrayNestedMat_str = ModelingToolkit.build_function(h_sparse_arrayNestedMat, [a, b, c])
170+
h_sparse_arrayNestedMat_oop = eval(h_sparse_arrayNestedMat_str[1])
171+
h_sparse_arrayNestedMat_ip! = eval(h_sparse_arrayNestedMat_str[2])
172+
h_sparse_arrayNestedMat_sparsity_patterns = [map(get_sparsity_pattern, h) for h in h_sparse_arrayNestedMat]
173+
out_1_arrayNestedMat = [[similar(h_sub) for h_sub in h] for h in h_sparse_arrayNestedMat_sparsity_patterns]
174+
out_2_arrayNestedMat = [[similar(h_sub) for h_sub in h] for h in h_sparse_arrayNestedMat_sparsity_patterns]
175+
julia_sparse_arrayNestedMat = h_sparse_arrayNestedMat_julia(input)
176+
mtk_sparse_arrayNestedMat = h_sparse_arrayNestedMat_oop(input)
177+
@test_broken julia_sparse_arrayNestedMat == mtk_sparse_arrayNestedMat
178+
h_sparse_arrayNestedMat_julia!(out_1_arrayNestedMat, input)
179+
h_sparse_arrayNestedMat_ip!(out_2_arrayNestedMat, input)
180+
@test out_1_arrayNestedMat == out_2_arrayNestedMat
181+
182+
# Additional Tests
183+
# Returning 0-element structures (corresponding to empty Jacobians)
184+
# Arrays of Matrices
185+
h_empty = [[a b; c 0], Array{Expression,2}(undef, 0,0)]
186+
h_empty_str = ModelingToolkit.build_function(h_empty, [a, b, c])
187+
h_empty_oop = eval(h_empty_str[1])
188+
h_empty_ip! = eval(h_empty_str[2])
189+
@test_broken h_empty_oop(input) == [[1 2; 3 0], Array{Int64,2}(undef,0,0)]
190+
out = [Matrix{Int64}(undef, 2, 2), Matrix{Int64}(undef, 0, 0)]
191+
h_empty_ip!(out, input) # should just not fail
192+
193+
# Array of Vectors
194+
h_empty_vec = [[a, b, c, 0], Vector{Expression}(undef,0)]
195+
h_empty_vec_str = ModelingToolkit.build_function(h_empty_vec, [a, b, c])
196+
h_empty_vec_oop = eval(h_empty_vec_str[1])
197+
h_empty_vec_ip! = eval(h_empty_vec_str[2])
198+
@test_broken h_empty_vec_oop(input) == [[1, 2, 3, 0], Vector{Int64}(undef,0)]
199+
out = [Vector{Int64}(undef, 4), Vector{Int64}(undef, 0)]
200+
h_empty_vec_ip!(out, input) # should just not fail
201+
202+
# Arrays of Arrays of Matrices
203+
h_emptyNested = [[[a b; c 0]], Array{Array{Expression, 2}}(undef, 0)] # emptyNested array of arrays
204+
h_emptyNested_str = ModelingToolkit.build_function(h_emptyNested, [a, b, c])
205+
h_emptyNested_oop = eval(h_emptyNested_str[1])
206+
h_emptyNested_ip! = eval(h_emptyNested_str[2])
207+
@test_broken h_emptyNested_oop(input) == [[[1 2; 3 0]], Array{Array{Int64, 2}}(undef, 0)]
208+
out = [[[1 2;3 4]], Array{Array{Int64,2},1}(undef, 0)]
209+
h_emptyNested_ip!(out, input) # should just not fail

0 commit comments

Comments
 (0)