From bf74d3fb675405847e834d0c18df8d3042b55df7 Mon Sep 17 00:00:00 2001 From: Alex Arslan Date: Tue, 8 Jan 2019 09:51:08 -0800 Subject: [PATCH 1/3] Parameterize Constant on the type of its value This allows for more expressive condition checking using dispatch, as complex numbers, real numbers, or arrays thereof can be handled as separate methods. This also limits the types that can be accepted as constant values to scalars, vectors, and matrices. Previously this was assumed but not actually checked. --- src/constant.jl | 64 ++++++++++++++++++++---------------------- test/test_utilities.jl | 16 +++++++++++ 2 files changed, 47 insertions(+), 33 deletions(-) diff --git a/src/constant.jl b/src/constant.jl index ec1dddbf4..ba8fe3ec9 100644 --- a/src/constant.jl +++ b/src/constant.jl @@ -5,54 +5,52 @@ export Constant export vexity, evaluate, sign, conic_form! -struct Constant <: AbstractExpr +const ComplexValue = Union{Complex,AbstractVecOrMat{<:Complex}} + +ispos(x::Real) = x >= 0 +ispos(v::AbstractVecOrMat{<:Real}) = all(ispos, v) +isneg(x::Real) = x <= 0 +isneg(v::AbstractVecOrMat{<:Real}) = all(isneg, v) + +_size(x::Number) = (1, 1) +_size(x::AbstractVector) = (length(x), 1) +_size(x::AbstractMatrix) = size(x) + +_sign(x::ComplexValue) = ComplexSign() +_sign(x::Value) = ispos(x) ? Positive() : + isneg(x) ? Negative() : NoSign() + +struct Constant{T<:Value} <: AbstractExpr head::Symbol id_hash::UInt64 - value::Value + value::T size::Tuple{Int, Int} - vexity::Vexity sign::Sign - function Constant(x::Value, sign::Sign) - sz = (size(x, 1), size(x, 2)) - return new(:constant, objectid(x), x, sz, ConstVexity(), sign) + function Constant(x::T, sign::Union{ComplexSign,NoSign}) where T<:ComplexValue + new{T}(:constant, objectid(x), x, _size(x), sign) end - function Constant(x::Value, check_sign::Bool=true) - if check_sign - if !isreal(x) - return Constant(x, ComplexSign()) - elseif all(xi >= 0 for xi in x) - return Constant(x, Positive()) - elseif all(xi <= 0 for xi in x) - return Constant(x, Negative()) - end - end - return Constant(x, NoSign()) + function Constant(x::T, sign::Union{Positive,Negative,NoSign}) where T<:Value + new{T}(:constant, objectid(x), x, _size(x), sign) end -end -#### Constant Definition end ##### -function vexity(x::Constant) - return x.vexity + Constant(x::Value, check_sign::Bool=true) = Constant(x, check_sign ? _sign(x) : NoSign()) end -function evaluate(x::Constant) - return x.value -end +#### Constant Definition end ##### -function sign(x::Constant) - return x.sign -end +vexity(::Constant) = ConstVexity() +evaluate(x::Constant) = x.value -function real_conic_form(x::Constant) - return vec([real(x.value);]) -end +sign(x::Constant) = x.sign -function imag_conic_form(x::Constant) - return im*vec([imag(x.value);]) -end +real_conic_form(x::Constant{<:Number}) = [real(x.value)] +real_conic_form(x::Constant{<:AbstractVecOrMat}) = vec(real(x.value)) + +imag_conic_form(x::Constant{<:Number}) = [im * imag(x.value)] +imag_conic_form(x::Constant{<:AbstractVecOrMat}) = im * vec(imag(x.value)) # We can more efficiently get the length of a constant by asking for the length of its # value, which Julia can get via Core.arraylen for arrays and knows is 1 for scalars diff --git a/test/test_utilities.jl b/test/test_utilities.jl index 674ac3b90..824ca842d 100644 --- a/test/test_utilities.jl +++ b/test/test_utilities.jl @@ -42,6 +42,22 @@ @test size(y) == (2, 1) end + @testset "Parametric constants" begin + z = Constant([1.0 0.0im; 0.0 1.0]) + @test z isa Constant{Matrix{Complex{Float64}}} + + # Helper functions + @test Convex._size(3) == (1, 1) + @test Convex._sign(3) == Positive() + @test Convex._size([-1,1,1]) == (3, 1) + @test Convex._sign([-1,1,1]) == NoSign() + @test Convex._sign([-1,-1,-1]) == Negative() + @test Convex._size([0 0; 0 0]) == (2, 2) + @test Convex._sign([0 0; 0 0]) == Positive() + @test Convex._size(0+1im) == (1, 1) + @test Convex._sign(0+1im) == ComplexSign() + end + # returns [21]; not sure why # context("iteration") do # x = Variable(2,3) From 9e14301d26583e8f5d8747a8d7fe56f31c0880f9 Mon Sep 17 00:00:00 2001 From: Alex Arslan Date: Tue, 8 Jan 2019 15:25:30 -0800 Subject: [PATCH 2/3] Allow but check for mismatched values and signs Currently you can construct a Constant with any sign regardless of whether it logically matches the given value. This change enforces consistency to ensure that types, values, and signs agree. --- src/constant.jl | 18 ++++++++++++++++++ test/test_utilities.jl | 14 ++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/constant.jl b/src/constant.jl index ba8fe3ec9..31b13b99d 100644 --- a/src/constant.jl +++ b/src/constant.jl @@ -5,6 +5,7 @@ export Constant export vexity, evaluate, sign, conic_form! +const RealValue = Union{Real,AbstractVecOrMat{<:Real}} const ComplexValue = Union{Complex,AbstractVecOrMat{<:Complex}} ispos(x::Real) = x >= 0 @@ -38,6 +39,23 @@ struct Constant{T<:Value} <: AbstractExpr Constant(x::Value, check_sign::Bool=true) = Constant(x, check_sign ? _sign(x) : NoSign()) end +# Allow mismatched signs and values, but check for logical consistency + +Constant(x::RealValue, sign::ComplexSign) = Constant(complex(x), sign) + +function Constant(x::ComplexValue, sign::S) where S<:Union{Positive,Negative} + msg = "Cannot construct $S() constant from " + r = real(x) + if !iszero(imag(x)) + throw(ArgumentError(msg * "complex value; use ComplexSign() instead")) + elseif S <: Positive && !ispos(r) + throw(ArgumentError(msg * "non-positive value")) + elseif S <: Negative && !isneg(r) + throw(ArgumentError(msg * "non-negative value")) + end + Constant(r, sign) +end + #### Constant Definition end ##### vexity(::Constant) = ConstVexity() diff --git a/test/test_utilities.jl b/test/test_utilities.jl index 824ca842d..6ef788cbd 100644 --- a/test/test_utilities.jl +++ b/test/test_utilities.jl @@ -56,6 +56,20 @@ @test Convex._sign([0 0; 0 0]) == Positive() @test Convex._size(0+1im) == (1, 1) @test Convex._sign(0+1im) == ComplexSign() + + x = Constant(1, ComplexSign()) + @test x isa Constant{Complex{Int}} + @test evaluate(x) === 1+0im + @test sign(x) == ComplexSign() + + y = Constant(0+0im, Positive()) + @test y isa Constant{Int} + @test evaluate(y) === 0 + @test sign(y) == Positive() + + @test_throws ArgumentError Constant(1+2im, Positive()) + @test_throws ArgumentError Constant([-1,0], Positive()) + @test_throws ArgumentError Constant([1,0], Negative()) end # returns [21]; not sure why From 86245cd69536ebb23a449cf139d09884e19d6a52 Mon Sep 17 00:00:00 2001 From: Alex Arslan Date: Thu, 17 Jan 2019 14:05:32 -0800 Subject: [PATCH 3/3] Undo checking --- src/constant.jl | 41 ++++++++++------------------------------- test/test_utilities.jl | 14 -------------- 2 files changed, 10 insertions(+), 45 deletions(-) diff --git a/src/constant.jl b/src/constant.jl index 31b13b99d..a02859e13 100644 --- a/src/constant.jl +++ b/src/constant.jl @@ -5,9 +5,6 @@ export Constant export vexity, evaluate, sign, conic_form! -const RealValue = Union{Real,AbstractVecOrMat{<:Real}} -const ComplexValue = Union{Complex,AbstractVecOrMat{<:Complex}} - ispos(x::Real) = x >= 0 ispos(v::AbstractVecOrMat{<:Real}) = all(ispos, v) isneg(x::Real) = x <= 0 @@ -17,7 +14,7 @@ _size(x::Number) = (1, 1) _size(x::AbstractVector) = (length(x), 1) _size(x::AbstractMatrix) = size(x) -_sign(x::ComplexValue) = ComplexSign() +_sign(x::Union{Complex,AbstractVecOrMat{<:Complex}}) = ComplexSign() _sign(x::Value) = ispos(x) ? Positive() : isneg(x) ? Negative() : NoSign() @@ -28,34 +25,10 @@ struct Constant{T<:Value} <: AbstractExpr size::Tuple{Int, Int} sign::Sign - function Constant(x::T, sign::Union{ComplexSign,NoSign}) where T<:ComplexValue - new{T}(:constant, objectid(x), x, _size(x), sign) - end - - function Constant(x::T, sign::Union{Positive,Negative,NoSign}) where T<:Value - new{T}(:constant, objectid(x), x, _size(x), sign) - end - + Constant(x::Value, sign::Sign) = new{T}(:constant, objectid(x), x, _size(x), sign) Constant(x::Value, check_sign::Bool=true) = Constant(x, check_sign ? _sign(x) : NoSign()) end -# Allow mismatched signs and values, but check for logical consistency - -Constant(x::RealValue, sign::ComplexSign) = Constant(complex(x), sign) - -function Constant(x::ComplexValue, sign::S) where S<:Union{Positive,Negative} - msg = "Cannot construct $S() constant from " - r = real(x) - if !iszero(imag(x)) - throw(ArgumentError(msg * "complex value; use ComplexSign() instead")) - elseif S <: Positive && !ispos(r) - throw(ArgumentError(msg * "non-positive value")) - elseif S <: Negative && !isneg(r) - throw(ArgumentError(msg * "non-negative value")) - end - Constant(r, sign) -end - #### Constant Definition end ##### vexity(::Constant) = ConstVexity() @@ -64,11 +37,17 @@ evaluate(x::Constant) = x.value sign(x::Constant) = x.sign +# `real(::Real)` is a no-op and should be optimized out for `Constant{<:Real}` real_conic_form(x::Constant{<:Number}) = [real(x.value)] real_conic_form(x::Constant{<:AbstractVecOrMat}) = vec(real(x.value)) -imag_conic_form(x::Constant{<:Number}) = [im * imag(x.value)] -imag_conic_form(x::Constant{<:AbstractVecOrMat}) = im * vec(imag(x.value)) +# `imag(::Real)` always returns 0, so we can avoid the implicit conversion to `Complex` +# by multiplication with `im` and just use an explicit call to `zeros` with the appropriate +# length +imag_conic_form(x::Constant{<:Real}) = zeros(Complex{typeof(x.value)}, 1) +imag_conic_form(x::Constant{<:AbstractVecOrMat{<:Real}}) = zeros(Complex{eltype(x.value)}, length(x)) +imag_conic_form(x::Constant{<:Complex}) = [im * imag(x.value)] +imag_conic_form(x::Constant{<:AbstractVecOrMat{<:Complex}}) = im * vec(imag(x.value)) # We can more efficiently get the length of a constant by asking for the length of its # value, which Julia can get via Core.arraylen for arrays and knows is 1 for scalars diff --git a/test/test_utilities.jl b/test/test_utilities.jl index 6ef788cbd..824ca842d 100644 --- a/test/test_utilities.jl +++ b/test/test_utilities.jl @@ -56,20 +56,6 @@ @test Convex._sign([0 0; 0 0]) == Positive() @test Convex._size(0+1im) == (1, 1) @test Convex._sign(0+1im) == ComplexSign() - - x = Constant(1, ComplexSign()) - @test x isa Constant{Complex{Int}} - @test evaluate(x) === 1+0im - @test sign(x) == ComplexSign() - - y = Constant(0+0im, Positive()) - @test y isa Constant{Int} - @test evaluate(y) === 0 - @test sign(y) == Positive() - - @test_throws ArgumentError Constant(1+2im, Positive()) - @test_throws ArgumentError Constant([-1,0], Positive()) - @test_throws ArgumentError Constant([1,0], Negative()) end # returns [21]; not sure why