Skip to content

Commit

Permalink
compatibility with v0.6: mostly, added .s for broadcasting, and chang…
Browse files Browse the repository at this point in the history
…ed syntax for .*, ./, and .^ to dot(*), dot(/), and dot(^) in the file broadcast.jl
  • Loading branch information
madeleineudell committed Jun 21, 2017
1 parent 96bc050 commit ba00f76
Show file tree
Hide file tree
Showing 29 changed files with 150 additions and 126 deletions.
11 changes: 8 additions & 3 deletions docs/operations.rst
Expand Up @@ -48,9 +48,9 @@ An optimization problem using only these functions can be solved by any LP solve
+---------------------------+----------------------------+------------+---------------+---------------------------------+
|:code:`x/y` | division | affine |increasing | PR: :math:`y` is scalar constant|
+---------------------------+----------------------------+------------+---------------+---------------------------------+
|:code:`x .* y` | elementwise multiplication | affine |increasing | PR: one argument is constant |
|:code:`dot(*)(x, y)` | elementwise multiplication | affine |increasing | PR: one argument is constant |
+---------------------------+----------------------------+------------+---------------+---------------------------------+
|:code:`x ./ y` | elementwise division | affine |increasing | PR: one argument is constant |
|:code:`dot(/)(x, y)` | elementwise division | affine |increasing | PR: one argument is constant |
+---------------------------+----------------------------+------------+---------------+---------------------------------+
|:code:`x[1:4, 2:3]` | indexing and slicing | affine |increasing | none |
+---------------------------+----------------------------+------------+---------------+---------------------------------+
Expand Down Expand Up @@ -190,12 +190,17 @@ Of course, if an optimization problem has both LP and SOCP representable functio
+----------------------------+-------------------------------------+------------+---------------+--------------------------+
|:code:`sqrt(x)` | :math:`\sqrt{x}` | concave |decreasing | IC: :math:`x>0` |
+----------------------------+-------------------------------------+------------+---------------+--------------------------+
|:code:`square(x), x.^2, x^2`| :math:`x^2` | convex |increasing on | elementwise |
|:code:`square(x), x^2` | :math:`x^2` | convex |increasing on | PR : :math:`x` is scalar |
| | | |:math:`x \ge 0`| |
| | | | | |
| | | |decreasing on | |
| | | |:math:`x \le 0`| |
+----------------------------+-------------------------------------+------------+---------------+--------------------------+
|:code:`dot(^)(x,2)` | :math:`x.^2` | convex |increasing on | elementwise |
| | | |:math:`x \ge 0`| |
| | | |decreasing on | |
| | | |:math:`x \le 0`| |
+----------------------------+-------------------------------------+------------+---------------+--------------------------+
|:code:`geomean(x, y)` | :math:`\sqrt{xy}` | concave |increasing | IC: :math:`x\ge0`, |
| | | | | :math:`y\ge0` |
+----------------------------+-------------------------------------+------------+---------------+--------------------------+
Expand Down
1 change: 1 addition & 0 deletions src/Convex.jl
Expand Up @@ -84,6 +84,7 @@ include("atoms/exp_+_sdp_cone/logdet.jl")
include("utilities/show.jl")
include("utilities/iteration.jl")
include("utilities/deprecated.jl")
include("utilities/broadcast.jl")

#Temporary workaround for memory leak (https://github.com/JuliaOpt/Convex.jl/issues/83)
function clearmemory()
Expand Down
11 changes: 2 additions & 9 deletions src/atoms/affine/add_subtract.jl
Expand Up @@ -6,8 +6,8 @@
# Please read expressions.jl first.
#############################################################################

import Base.+, Base.-, Base.(.+), Base.(.-)
export +, -, .+, .-
import Base.broadcast
export +, -, broadcast
export sign, curvature, monotonicity, evaluate

### Unary Negation
Expand Down Expand Up @@ -122,10 +122,3 @@ end
-(x::AbstractExpr, y::AbstractExpr) = x + (-y)
-(x::Value, y::AbstractExpr) = Constant(x) + (-y)
-(x::AbstractExpr, y::Value) = x + Constant(-y)

.+(x::AbstractExpr, y::AbstractExpr) = AdditionAtom(x, y)
.+(x::Value, y::AbstractExpr) = AdditionAtom(Constant(x), y)
.+(x::AbstractExpr, y::Value) = AdditionAtom(x, Constant(y))
.-(x::AbstractExpr, y::AbstractExpr) = x + (-y)
.-(x::Value, y::AbstractExpr) = Constant(x) + (-y)
.-(x::AbstractExpr, y::Value) = x + Constant(-y)
7 changes: 3 additions & 4 deletions src/atoms/affine/dot.jl
Expand Up @@ -2,9 +2,9 @@ import Base.dot, Base.vecdot
export vecdot, dot


vecdot(x::AbstractExpr, y::AbstractExpr) = sum(x .* y)
vecdot(x::Value, y::AbstractExpr) = sum(Constant(x) .* y)
vecdot(x::AbstractExpr, y::Value) = sum(x .* Constant(y))
vecdot(x::AbstractExpr, y::AbstractExpr) = sum(broadcast(*, x, y))
vecdot(x::Value, y::AbstractExpr) = sum(broadcast(*, Constant(x), y))
vecdot(x::AbstractExpr, y::Value) = sum(broadcast(*, x, Constant(y)))

dot(x::AbstractExpr, y::AbstractExpr) = (ismatrix(x) || ismatrix(y)) ? error("dot not implemented for matrices. perhaps you're looking for vecdot?") : vecdot(x, y)
dot(x::Value, y::AbstractExpr) = (ismatrix(x) || ismatrix(y)) ? error("dot not implemented for matrices. perhaps you're looking for vecdot?") : vecdot(x, y)
Expand All @@ -24,4 +24,3 @@ function ismatrix(x)
end
return true
end

2 changes: 1 addition & 1 deletion src/atoms/affine/index.jl
Expand Up @@ -54,7 +54,7 @@ function conic_form!(x::IndexAtom, unique_conic_forms::UniqueConicForms)

if x.inds == nothing
sz = length(x.cols) * length(x.rows)
J = Array(Int, sz)
J = Array{Int}(sz)
k = 1

num_rows = x.children[1].size[1]
Expand Down
36 changes: 18 additions & 18 deletions src/atoms/affine/multiply_divide.jl
Expand Up @@ -6,8 +6,8 @@
# Please read expressions.jl first.
#############################################################################

import Base.*, Base.(.*), Base./, Base.(./)
export *, .*, /, ./
import Base.broadcast
export broadcast
export sign, monotonicity, curvature, evaluate, conic_form!

### Scalar and matrix multiplication
Expand Down Expand Up @@ -178,21 +178,21 @@ function conic_form!(x::DotMultiplyAtom, unique_conic_forms::UniqueConicForms)
return get_conic_form(unique_conic_forms, x)
end

# function .*(x::Constant, y::AbstractExpr)
# if x.size == (1, 1) || y.size == (1, 1)
# return x * y
# elseif size(y,1) < size(x,1) && size(y,1) == 1
# return DotMultiplyAtom(x, ones(size(x,1))*y)
# elseif size(y,2) < size(x,2) && size(y,2) == 1
# return DotMultiplyAtom(x, y*ones(1,size(x,1)))
# else
# return DotMultiplyAtom(x, y)
# end
# end
# .*(y::AbstractExpr, x::Constant) = .*(x,y)
function broadcast(::typeof(*), x::Constant, y::AbstractExpr)
if x.size == (1, 1) || y.size == (1, 1)
return x * y
elseif size(y,1) < size(x,1) && size(y,1) == 1
return DotMultiplyAtom(x, ones(size(x,1))*y)
elseif size(y,2) < size(x,2) && size(y,2) == 1
return DotMultiplyAtom(x, y*ones(1,size(x,1)))
else
return DotMultiplyAtom(x, y)
end
end
broadcast(::typeof(*), y::AbstractExpr, x::Constant) = DotMultiplyAtom(x, y)

# if neither is a constant it's not DCP, but might be nice to support anyway for eg MultiConvex
function .*(x::AbstractExpr, y::AbstractExpr)
function broadcast(::typeof(*), x::AbstractExpr, y::AbstractExpr)
if x.size == (1, 1) || y.size == (1, 1)
return x * y
elseif vexity(x) == ConstVexity()
Expand All @@ -203,7 +203,7 @@ function .*(x::AbstractExpr, y::AbstractExpr)
return DotMultiplyAtom(y, x)
end
end
.*(x::Value, y::AbstractExpr) = DotMultiplyAtom(Constant(x), y)
.*(x::AbstractExpr, y::Value) = DotMultiplyAtom(Constant(y), x)
./(x::AbstractExpr, y::Value) = DotMultiplyAtom(Constant(1./y), x)
broadcast(::typeof(*), x::Value, y::AbstractExpr) = DotMultiplyAtom(Constant(x), y)
broadcast(::typeof(*), x::AbstractExpr, y::Value) = DotMultiplyAtom(Constant(y), x)
broadcast(::typeof(/), x::AbstractExpr, y::Value) = DotMultiplyAtom(Constant(1./y), x)
# x ./ y and x / y for x constant, y variable is defined in second_order_cone.qol_elemwise.jl
16 changes: 8 additions & 8 deletions src/atoms/affine/real_imag.jl
Expand Up @@ -25,7 +25,7 @@ end
function sign(x::RealAtom)
if sign(x.children[1]) == ComplexSign()
return NoSign()
else
else
return sign(x.children[1])
end
end
Expand All @@ -39,7 +39,7 @@ function curvature(x::RealAtom)
end

function evaluate(x::RealAtom)
return real(evaluate(x.children[1]))
return real.(evaluate(x.children[1]))
end

function conic_form!(x::RealAtom, unique_conic_forms::UniqueConicForms)
Expand All @@ -49,8 +49,8 @@ function conic_form!(x::RealAtom, unique_conic_forms::UniqueConicForms)


for var in keys(objective)
re = real(objective[var][1])
im = real(objective[var][2])
re = real.(objective[var][1])
im = real.(objective[var][2])
new_objective[var] = (re,im)
end

Expand Down Expand Up @@ -91,18 +91,18 @@ function curvature(x::ImaginaryAtom)
end

function evaluate(x::ImaginaryAtom)
return imag(evaluate(x.children[1]))
return imag.(evaluate(x.children[1]))
end

function conic_form!(x::ImaginaryAtom, unique_conic_forms::UniqueConicForms)
if !has_conic_form(unique_conic_forms, x)
new_objective = ConicObj()
objective = conic_form!(x.children[1], unique_conic_forms)


for var in keys(objective)
re = imag(objective[var][1])
im = imag(objective[var][2])
re = imag.(objective[var][1])
im = imag.(objective[var][2])
new_objective[var] = (re,im)
end
cache_conic_form!(unique_conic_forms, x, new_objective)
Expand Down
8 changes: 4 additions & 4 deletions src/atoms/affine/transpose.jl
Expand Up @@ -48,8 +48,8 @@ function conic_form!(x::TransposeAtom, unique_conic_forms::UniqueConicForms)
num_rows = x.size[1]
num_cols = x.size[2]

I = Array(Int, sz)
J = Array(Int, sz)
I = Array{Int}(sz)
J = Array{Int}(sz)

k = 1
for r = 1:num_rows
Expand Down Expand Up @@ -111,8 +111,8 @@ function conic_form!(x::CTransposeAtom, unique_conic_forms::UniqueConicForms)
num_rows = x.size[1]
num_cols = x.size[2]

I = Array(Int, sz)
J = Array(Int, sz)
I = Array{Int}(sz)
J = Array{Int}(sz)

k = 1
for r = 1:num_rows
Expand Down
2 changes: 1 addition & 1 deletion src/atoms/exp_cone/entropy.jl
Expand Up @@ -44,7 +44,7 @@ end

function evaluate(x::EntropyAtom)
c = evaluate(x.children[1])
return map(y -> -y * log(y), c)
return -c .* log.(c)
end

function conic_form!(e::EntropyAtom, unique_conic_forms::UniqueConicForms)
Expand Down
4 changes: 2 additions & 2 deletions src/atoms/exp_cone/exp.jl
Expand Up @@ -20,7 +20,7 @@ type ExpAtom <: AbstractExpr
function ExpAtom(x::AbstractExpr)
if sign(x)==ComplexSign()
error("The argument should be real but it's instead complex")
else
else
children = (x,)
return new(:exp, hash(children), children, x.size)
end
Expand All @@ -40,7 +40,7 @@ function curvature(x::ExpAtom)
end

function evaluate(x::ExpAtom)
return exp(evaluate(x.children[1]))
return exp.(evaluate(x.children[1]))
end

exp(x::AbstractExpr) = ExpAtom(x)
Expand Down
4 changes: 2 additions & 2 deletions src/atoms/exp_cone/log.jl
Expand Up @@ -20,7 +20,7 @@ type LogAtom <: AbstractExpr
function LogAtom(x::AbstractExpr)
if sign(x)==ComplexSign()
error("The argument should be real but it's instead complex")
else
else
children = (x,)
return new(:log, hash(children), children, x.size)
end
Expand All @@ -40,7 +40,7 @@ function curvature(x::LogAtom)
end

function evaluate(x::LogAtom)
return log(evaluate(x.children[1]))
return log.(evaluate(x.children[1]))
end

log(x::AbstractExpr) = LogAtom(x)
Expand Down
4 changes: 2 additions & 2 deletions src/atoms/exp_cone/logsumexp.jl
Expand Up @@ -21,7 +21,7 @@ type LogSumExpAtom <: AbstractExpr
function LogSumExpAtom(x::AbstractExpr)
if sign(x)==ComplexSign()
error("The argument should be real but it's instead complex")
else
else
children = (x,)
return new(:logsumexp, hash(children), children, (1,1))
end
Expand All @@ -41,7 +41,7 @@ function curvature(x::LogSumExpAtom)
end

function evaluate(x::LogSumExpAtom)
return log(sum(exp(evaluate(x.children[1]))))
return log(sum(exp.(evaluate(x.children[1]))))
end

logsumexp(x::AbstractExpr) = LogSumExpAtom(x)
Expand Down
2 changes: 1 addition & 1 deletion src/atoms/exp_cone/relative_entropy.jl
Expand Up @@ -43,7 +43,7 @@ function evaluate(e::RelativeEntropyAtom)
y = evaluate(e.children[2])
if any(@compat isnan.(y)) return Inf end

out = x.*log(x./y)
out = x.*log.(x./y)
# fix value when x=0:
# out will only be NaN if x=0, in which case the correct value is 0
out[@compat isnan.(out)] = 0
Expand Down
10 changes: 4 additions & 6 deletions src/atoms/lp_cone/abs.jl
Expand Up @@ -36,11 +36,9 @@ function curvature(x::AbsAtom)
end

function evaluate(x::AbsAtom)
return abs(evaluate(x.children[1]))
return abs.(evaluate(x.children[1]))
end



function conic_form!(x::AbsAtom, unique_conic_forms::UniqueConicForms)
if !has_conic_form(unique_conic_forms, x)
c = x.children[1]
Expand All @@ -50,14 +48,14 @@ function conic_form!(x::AbsAtom, unique_conic_forms::UniqueConicForms)
for i in 1:length(vec(t))
conic_form!(t[i]>=norm2([real(c[i]);imag(c[i])]), unique_conic_forms)
end
else
else
conic_form!(c<=t, unique_conic_forms)
conic_form!(c>=-t, unique_conic_forms)
end
cache_conic_form!(unique_conic_forms, x, objective)
end
return get_conic_form(unique_conic_forms, x)
end
end

abs(x::AbstractExpr) = AbsAtom(x)
abs2(x::AbstractExpr) = square(abs(x))
abs2(x::AbstractExpr) = square(abs(x))
6 changes: 3 additions & 3 deletions src/atoms/lp_cone/max.jl
Expand Up @@ -18,7 +18,7 @@ type MaxAtom <: AbstractExpr
function MaxAtom(x::AbstractExpr, y::AbstractExpr)
if sign(x)==ComplexSign() || sign(y)==ComplexSign()
error("Both the arguments should be real instead they are $(sign(x)) and $(sign(y))")
else
else
if x.size == y.size
sz = x.size
elseif x.size == (1, 1)
Expand All @@ -29,7 +29,7 @@ type MaxAtom <: AbstractExpr
error("Got different sizes for x as $(x.size) and y as $(y.size)")
end
end

children = (x, y)
return new(:max, hash(children), children, sz)
end
Expand Down Expand Up @@ -59,7 +59,7 @@ function curvature(x::MaxAtom)
end

function evaluate(x::MaxAtom)
return max(evaluate(x.children[1]), evaluate(x.children[2]))
return max.(evaluate(x.children[1]), evaluate(x.children[2]))
end

# x <= this and y <= this if max(x, y) = this
Expand Down
2 changes: 1 addition & 1 deletion src/atoms/lp_cone/min.jl
Expand Up @@ -59,7 +59,7 @@ function curvature(x::MinAtom)
end

function evaluate(x::MinAtom)
return min(evaluate(x.children[1]), evaluate(x.children[2]))
return min.(evaluate(x.children[1]), evaluate(x.children[2]))
end

# x >= this and y >= this if min(x, y) = this
Expand Down
4 changes: 2 additions & 2 deletions src/atoms/second_order_cone/geomean.jl
Expand Up @@ -13,7 +13,7 @@ type GeoMeanAtom <: AbstractExpr
error("geo mean must take two arguments of the same size")
elseif sign(x)==ComplexSign() || sign(y)==ComplexSign()
error("Both the arguments should be real instead they are $(sign(x)) and $(sign(y))")
else
else
children = (x, y)
return new(:geomean, hash(children), children, x.size)
end
Expand All @@ -33,7 +33,7 @@ function curvature(q::GeoMeanAtom)
end

function evaluate(q::GeoMeanAtom)
return sqrt(evaluate(q.children[1]) .* evaluate(q.children[2]))
return sqrt.(evaluate(q.children[1]) .* evaluate(q.children[2]))
end

function conic_form!(q::GeoMeanAtom, unique_conic_forms::UniqueConicForms)
Expand Down
3 changes: 2 additions & 1 deletion src/atoms/second_order_cone/power_to_socp.jl
Expand Up @@ -12,6 +12,7 @@
# This reduction is documented in the pdf available at
# https://github.com/JuliaOpt/Convex.jl/raw/master/docs/supplementary/rational_to_socp.pdf

using Compat
module psocp

type InequalityExpression
Expand Down Expand Up @@ -77,7 +78,7 @@ function ProductToSimpleInequalities(first_power::Int, second_power::Int)
var_list = [1, 2, 3];
init_inequality = InequalityExpression(first_power, second_power, 0,
1, 2, 3, -1);
return ReducePowers(init_inequality, Array(SimpleInequalityExpression, 0),
return ReducePowers(init_inequality, Array{SimpleInequalityExpression}(0),
var_list);
end

Expand Down

14 comments on commit ba00f76

@tkelman
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.* does not go through broadcast on 0.5, so this could break code there

@madeleineudell
Copy link
Contributor Author

@madeleineudell madeleineudell commented on ba00f76 Jun 21, 2017 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tkelman
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that were the case you wouldn't have to use dot(*)(x, y)

@madeleineudell
Copy link
Contributor Author

@madeleineudell madeleineudell commented on ba00f76 Jun 21, 2017 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tkelman
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. The infix syntax .* works on Julia v0.5

.* is a distinct operator on Julia 0.5, on Julia 0.6 it goes through broadcast. So when called on custom types, it will go through different code paths. If you had been a user of Convex.jl on Julia 0.5, previously working code would likely be broken by this update until people change .* to dot(*). I'd recommend adding deprecations on Julia 0.5 if you intend for that to be the syntax going forward and want everyone to change previously working code even on Julia 0.5.

there is no way to make .* call ElementwiseMultiplicationAtom on Julia v0.6.

With .* going through broadcast now on Julia 0.6, the multiplication operation would work on individual elements, not entire array variables.

@madeleineudell
Copy link
Contributor Author

@madeleineudell madeleineudell commented on ba00f76 Jun 21, 2017 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tkelman
Copy link
Contributor

@tkelman tkelman commented on ba00f76 Jun 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.*(x::AbstractExpr, y) gets rewritten to broadcast(typeof(*), x::AbstractExpr, y) which you could define to call ElementwiseMultiplyAtom(x,y) but due to broadcast fusion, x .* y .* z will call a different method, broadcast(typeof(*), x, y, z)

@mlubin
Copy link
Member

@mlubin mlubin commented on ba00f76 Jun 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't it concluded in JuliaLang/julia#22053 that there's no way to properly intercept dot operations in 0.6? Convex.jl has no way to (and shouldn't need to) catch and disentangle all possible cases that could arise from broadcast fusion.

@tkelman
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the repeated symbol and literal optimizations in that pass are kind of a mistake IMO but we're stuck with them on 0.6 for now. Are you sure you also want to force 0.5 users of Convex.jl to update the syntax they're using due to a constraint that only applies to 0.6?

@madeleineudell
Copy link
Contributor Author

@madeleineudell madeleineudell commented on ba00f76 Jun 21, 2017 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mlubin
Copy link
Member

@mlubin mlubin commented on ba00f76 Jun 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just restrict this release to 0.6 or later?

@tkelman
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either make this release 0.6-only, or do

@static if VERSION >= v"0.6.0-dev.1632"
    # 0.6 code
else
    # 0.5 code
end

if you want to keep supporting the old syntax on 0.5 with the new release

@madeleineudell
Copy link
Contributor Author

@madeleineudell madeleineudell commented on ba00f76 Jun 21, 2017 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tkelman
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjust the julia lower bound in REQUIRE to 0.6 (and remove 0.5 testing from CI), then re-tag https://github.com/attobot/attobot#updatingdeleting-a-release

Please sign in to comment.