Skip to content

Commit 19eb40e

Browse files
authored
Maintain order of expression generators when calling flatten! (#4196)
1 parent d0db0e5 commit 19eb40e

2 files changed

Lines changed: 28 additions & 9 deletions

File tree

src/nlp_expr.jl

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@ a single n-ary operation.
511511
512512
Nonlinear expressions created using operator overloading can be deeply nested
513513
and unbalanced. For example, `prod(x for i in 1:4)` creates
514-
`*(x, *(x, *(x, x)))` instead of the more preferable `*(x, x, x, x)`.
514+
`*(*(*(x, x), x, x)` instead of the more preferable `*(x, x, x, x)`.
515515
516516
## Example
517517
@@ -536,27 +536,35 @@ function flatten!(expr::GenericNonlinearExpr{V}) where {V}
536536
return expr
537537
end
538538
stack = Tuple{GenericNonlinearExpr{V},Int,GenericNonlinearExpr{V}}[]
539-
for i in length(expr.args):-1:1
539+
for i in 1:length(expr.args)
540540
if _needs_flatten(expr, expr.args[i])
541541
push!(stack, (expr, i, expr.args[i]))
542542
end
543543
end
544544
while !isempty(stack)
545545
parent, i, arg = pop!(stack)
546546
if parent.head in (:+, :*) && arg.head == parent.head
547-
n = length(parent.args)
548-
resize!(parent.args, n + length(arg.args) - 1)
549-
for j in length(arg.args):-1:1
550-
parent_index = j == 1 ? i : n + j - 1
547+
m, n = length(arg.args), length(parent.args)
548+
# We're going to splice the `m` args of the child into
549+
# `parent.args[i:i+m-1]`. To do so, extend the args to make space:
550+
resize!(parent.args, n + (m - 1))
551+
# and then shift the parent args along to make room:
552+
for j in n:-1:(i+1)
553+
parent.args[m+j-1] = parent.args[j]
554+
end
555+
# Now we can put each child arg into the parent.
556+
for j in 1:m
557+
parent_index = i + j - 1
551558
if _needs_flatten(parent, arg.args[j])
559+
# The child needs flattening itself
552560
push!(stack, (parent, parent_index, arg.args[j]))
553561
else
554562
parent.args[parent_index] = arg.args[j]
555563
end
556564
end
557565
else
558566
parent.args[i] = arg
559-
for j in length(arg.args):-1:1
567+
for j in 1:length(arg.args)
560568
if _needs_flatten(arg, arg.args[j])
561569
push!(stack, (arg, j, arg.args[j]))
562570
end

test/test_nlp_expr.jl

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,17 @@ function test_extension_flatten_nary(
124124
return
125125
end
126126

127+
function test_extension_flatten_nary_order_maintained(
128+
ModelType = Model,
129+
VariableRefType = VariableRef,
130+
)
131+
model = ModelType()
132+
@variable(model, x[1:3])
133+
@test sum(log(x[i]) for i in 1:3) |> flatten! |> string ==
134+
"log(x[1]) + log(x[2]) + log(x[3])"
135+
return
136+
end
137+
127138
function test_extension_zero_one(
128139
ModelType = Model,
129140
VariableRefType = VariableRef,
@@ -1070,11 +1081,11 @@ function test_printing_truncation()
10701081
@variable(model, x[1:100])
10711082
y = @expression(model, sum(sin.(x) .* 2))
10721083
@test occursin(
1073-
"(sin(x[72]) * 2) + [[...40 terms omitted...]] + (sin(x[31]) * 2)",
1084+
"(sin(x[30]) * 2) + [[...40 terms omitted...]] + (sin(x[71]) * 2)",
10741085
function_string(MIME("text/plain"), y),
10751086
)
10761087
@test occursin(
1077-
"{\\left({\\textsf{sin}\\left({x_{72}}\\right)} * {2}\\right) + {[[\\ldots\\text{40 terms omitted}\\ldots]]} + {\\left({\\textsf{sin}\\left({x_{31}}\\right)} * {2}\\right)}",
1088+
"{\\left({\\textsf{sin}\\left({x_{30}}\\right)} * {2}\\right) + {[[\\ldots\\text{40 terms omitted}\\ldots]]} + {\\left({\\textsf{sin}\\left({x_{71}}\\right)} * {2}\\right)}",
10781089
function_string(MIME("text/latex"), y),
10791090
)
10801091
return

0 commit comments

Comments
 (0)