Skip to content

Commit b98580c

Browse files
authored
Merge pull request #322 from ericphanson/remove-id-to-variables
Remove global variables
2 parents b4f89f3 + 26f73e9 commit b98580c

File tree

9 files changed

+68
-76
lines changed

9 files changed

+68
-76
lines changed

src/Convex.jl

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ global DEFAULT_SOLVER = nothing
99
### modeling framework
1010
include("dcp.jl")
1111
include("expressions.jl")
12-
include("conic_form.jl")
12+
# need to define `Variable` before `UniqueConicForms`
1313
include("variable.jl")
14+
include("conic_form.jl")
15+
# need to define `conic_form!` for `Variable`s after `UniqueConicForms`
16+
include("variable_conic_form.jl")
1417
include("constant.jl")
1518
include("constraints/constraints.jl")
1619
include("constraints/signs_and_sets.jl")
@@ -85,13 +88,9 @@ include("utilities/show.jl")
8588
include("utilities/iteration.jl")
8689
include("utilities/broadcast.jl")
8790

88-
#Temporary workaround for memory leak (https://github.com/JuliaOpt/Convex.jl/issues/83)
91+
# Deprecated workaround for memory leak (https://github.com/JuliaOpt/Convex.jl/issues/83)
8992
function clearmemory()
90-
global id_to_variables
91-
empty!(id_to_variables)
92-
global conic_constr_to_constr
93-
empty!(conic_constr_to_constr)
94-
GC.gc()
93+
Base.depwarn("Convex.clearmemory() is deprecated, as the memory leak it works around has been closed (in https://github.com/JuliaOpt/Convex.jl/pull/322). This function no longer does anything and will be removed in a future Convex.jl release.", :clearmemory )
9594
end
9695

9796
end

src/atoms/affine/stack.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ function conic_form!(x::HcatAtom, unique_conic_forms::UniqueConicForms=UniqueCon
5454
if id == objectid(:constant)
5555
variable_to_sizes[id] = 1
5656
else
57-
variable_to_sizes[id] = length(id_to_variables[id])
57+
variable_to_sizes[id] = length(unique_conic_forms.id_to_variables[id])
5858
end
5959
end
6060
end

src/conic_form.jl

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,19 @@ const UniqueExpMap = OrderedDict{Tuple{Symbol, UInt64}, ConicObj}
106106
const UniqueConstrMap = OrderedDict{Tuple{Symbol, UInt64}, Int}
107107
# records each ConicConstr created
108108
const UniqueConstrList = Vector{ConicConstr}
109-
109+
# map variables' hash to the variable itself
110+
const IdToVariables = OrderedDict{UInt64, Variable}
111+
const ConicConstrToConstr = Dict{ConicConstr, Constraint}
110112
# UniqueConicForms caches all the conic forms of expressions we've parsed so far
111113
struct UniqueConicForms
112114
exp_map::UniqueExpMap
113115
constr_map::UniqueConstrMap
114116
constr_list::UniqueConstrList
117+
id_to_variables::IdToVariables
118+
conic_constr_to_constr::ConicConstrToConstr
115119
end
116120

117-
UniqueConicForms() = UniqueConicForms(UniqueExpMap(), UniqueConstrMap(), ConicConstr[])
121+
UniqueConicForms() = UniqueConicForms(UniqueExpMap(), UniqueConstrMap(), ConicConstr[], IdToVariables(), ConicConstrToConstr())
118122

119123
function has_conic_form(conic_forms::UniqueConicForms, exp::AbstractExpr)
120124
return haskey(conic_forms.exp_map, (exp.head, exp.id_hash))
@@ -145,3 +149,7 @@ function cache_conic_form!(conic_forms::UniqueConicForms, constr::Constraint, ne
145149
conic_forms.constr_map[(constr.head, constr.id_hash)] = 0
146150
append!(conic_forms.constr_list, new_conic_forms)
147151
end
152+
153+
function add_to_id_to_variables!(conic_forms::UniqueConicForms, var::Variable)
154+
conic_forms.id_to_variables[var.id_hash] = var
155+
end

src/constraints/constraints.jl

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import Base.==, Base.<=, Base.>=, Base.<, Base.>
22
export EqConstraint, LtConstraint, GtConstraint
33
export ==, <=, >=
44

5-
const conic_constr_to_constr = Dict{ConicConstr, Constraint}()
6-
75
### Linear equality constraint
86
mutable struct EqConstraint <: Constraint
97
head::Symbol
@@ -42,14 +40,14 @@ function conic_form!(c::EqConstraint, unique_conic_forms::UniqueConicForms=Uniqu
4240
expr = c.lhs - c.rhs
4341
objective = conic_form!(expr, unique_conic_forms)
4442
new_constraint = ConicConstr([objective], :Zero, [c.size[1] * c.size[2]])
45-
conic_constr_to_constr[new_constraint] = c
43+
unique_conic_forms.conic_constr_to_constr[new_constraint] = c
4644
else
4745
real_expr = real(c.lhs - c.rhs)
4846
imag_expr = imag(c.lhs - c.rhs)
4947
real_objective = conic_form!(real_expr, unique_conic_forms)
5048
imag_objective = conic_form!(imag_expr, unique_conic_forms)
5149
new_constraint = ConicConstr([real_objective, imag_objective], :Zero, [c.size[1] * c.size[2], c.size[1] * c.size[2]])
52-
conic_constr_to_constr[new_constraint] = c
50+
unique_conic_forms.conic_constr_to_constr[new_constraint] = c
5351
end
5452
cache_conic_form!(unique_conic_forms, c, new_constraint)
5553
end
@@ -100,7 +98,7 @@ function conic_form!(c::LtConstraint, unique_conic_forms::UniqueConicForms=Uniqu
10098
expr = c.rhs - c.lhs
10199
objective = conic_form!(expr, unique_conic_forms)
102100
new_constraint = ConicConstr([objective], :NonNeg, [c.size[1] * c.size[2]])
103-
conic_constr_to_constr[new_constraint] = c
101+
unique_conic_forms.conic_constr_to_constr[new_constraint] = c
104102
cache_conic_form!(unique_conic_forms, c, new_constraint)
105103
end
106104
return get_conic_form(unique_conic_forms, c)
@@ -152,7 +150,7 @@ function conic_form!(c::GtConstraint, unique_conic_forms::UniqueConicForms=Uniqu
152150
expr = c.lhs - c.rhs
153151
objective = conic_form!(expr, unique_conic_forms)
154152
new_constraint = ConicConstr([objective], :NonNeg, [c.size[1] * c.size[2]])
155-
conic_constr_to_constr[new_constraint] = c
153+
unique_conic_forms.conic_constr_to_constr[new_constraint] = c
156154
cache_conic_form!(unique_conic_forms, c, new_constraint)
157155
end
158156
return get_conic_form(unique_conic_forms, c)

src/problems.jl

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ end
5252
# constr_size: m
5353
# var_to_ranges a dictionary mapping from variable id to (start_index, end_index)
5454
# where start_index and end_index are the start and end indexes of the variable in A
55-
function find_variable_ranges(constraints)
55+
function find_variable_ranges(constraints, id_to_variables)
5656
index = 0
5757
constr_size = 0
5858
var_to_ranges = Dict{UInt64, Tuple{Int, Int}}()
@@ -127,10 +127,13 @@ function conic_problem(p::Problem)
127127
unique_conic_forms = UniqueConicForms()
128128
objective, objective_var_id = conic_form!(p, unique_conic_forms)
129129
constraints = unique_conic_forms.constr_list
130+
conic_constr_to_constr = unique_conic_forms.conic_constr_to_constr
131+
id_to_variables = unique_conic_forms.id_to_variables
132+
130133
# var_to_ranges maps from variable id to the (start_index, stop_index) pairs of the columns of A corresponding to that variable
131134
# var_size is the sum of the lengths of all variables in the problem
132135
# constr_size is the sum of the lengths of all constraints in the problem
133-
var_size, constr_size, var_to_ranges = find_variable_ranges(constraints)
136+
var_size, constr_size, var_to_ranges = find_variable_ranges(constraints, id_to_variables)
134137
c = spzeros(var_size, 1)
135138
objective_range = var_to_ranges[objective_var_id]
136139
c[objective_range[1]:objective_range[2]] .= 1
@@ -188,7 +191,7 @@ function conic_problem(p::Problem)
188191
c = -c
189192
end
190193

191-
return c, A, b, cones, var_to_ranges, vartypes, constraints
194+
return c, A, b, cones, var_to_ranges, vartypes, constraints, id_to_variables, conic_constr_to_constr
192195
end
193196

194197
Problem(head::Symbol, objective::AbstractExpr, constraints::Constraint...) =

src/solution.jl

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,20 @@ function solve!(problem::Problem;
3333
vex = vexity(problem)
3434
end
3535

36-
c, A, b, cones, var_to_ranges, vartypes, conic_constraints = conic_problem(problem)
36+
c, A, b, cones, var_to_ranges, vartypes, conic_constraints, id_to_variables, conic_constr_to_constr = conic_problem(problem)
3737

3838
# load MPB conic problem
3939
m = problem.model
4040
load_problem!(m, c, A, b, cones, vartypes)
4141
if warmstart
42-
set_warmstart!(m, problem, length(c), var_to_ranges)
42+
set_warmstart!(m, problem, length(c), var_to_ranges, id_to_variables)
4343
end
4444
# optimize MPB conic problem
4545
MathProgBase.optimize!(m)
4646

4747
# populate the status, the primal (and possibly dual) solution
4848
# and the primal (and possibly dual) variables with values
49-
populate_solution!(m, problem, var_to_ranges, conic_constraints)
49+
populate_solution!(m, problem, var_to_ranges, conic_constraints, id_to_variables, conic_constr_to_constr)
5050
if problem.status != :Optimal && verbose
5151
@warn "Problem status $(problem.status); solution may be inaccurate."
5252
end
@@ -55,7 +55,8 @@ end
5555
function set_warmstart!(m::MathProgBase.AbstractConicModel,
5656
problem::Problem,
5757
n::Int, # length of primal (conic) solution
58-
var_to_ranges)
58+
var_to_ranges,
59+
id_to_variables)
5960
# use previously cached solution, if any,
6061
try
6162
primal = problem.solution.primal
@@ -73,7 +74,7 @@ function set_warmstart!(m::MathProgBase.AbstractConicModel,
7374
end
7475

7576
# grab any variables whose values the user may be trying to set
76-
load_primal_solution!(primal, var_to_ranges)
77+
load_primal_solution!(primal, var_to_ranges, id_to_variables)
7778

7879
# notify the model that we're trying to warmstart
7980
try
@@ -105,7 +106,9 @@ end
105106
function populate_solution!(m::MathProgBase.AbstractConicModel,
106107
problem::Problem,
107108
var_to_ranges,
108-
conic_constraints)
109+
conic_constraints,
110+
id_to_variables,
111+
conic_constr_to_constr)
109112
dual = try
110113
MathProgBase.getdual(m)
111114
catch
@@ -130,10 +133,10 @@ function populate_solution!(m::MathProgBase.AbstractConicModel,
130133
problem.solution = Solution(solution, dual, MathProgBase.status(m), objective)
131134
end
132135

133-
populate_variables!(problem, var_to_ranges)
136+
populate_variables!(problem, var_to_ranges, id_to_variables)
134137

135138
if problem.solution.has_dual
136-
populate_duals!(conic_constraints, problem.solution.dual)
139+
populate_duals!(conic_constraints, problem.solution.dual, conic_constr_to_constr)
137140
end
138141

139142
# minimize -> maximize
@@ -148,7 +151,7 @@ function populate_solution!(m::MathProgBase.AbstractConicModel,
148151
problem
149152
end
150153

151-
function populate_variables!(problem::Problem, var_to_ranges::Dict{UInt64, Tuple{Int, Int}})
154+
function populate_variables!(problem::Problem, var_to_ranges::Dict{UInt64, Tuple{Int, Int}}, id_to_variables)
152155
x = problem.solution.primal
153156
for (id, (start_index, end_index)) in var_to_ranges
154157
var = id_to_variables[id]
@@ -174,7 +177,7 @@ end
174177
# TODO: it would be super cool to grab the other expressions that appear in the primal solution vector,
175178
# get their `expression_to_range`,
176179
# and populate them too using `evaluate`
177-
function load_primal_solution!(primal::Array{Float64,1}, var_to_ranges::Dict{UInt64, Tuple{Int, Int}})
180+
function load_primal_solution!(primal::Array{Float64,1}, var_to_ranges::Dict{UInt64, Tuple{Int, Int}}, id_to_variables)
178181
for (id, (start_index, end_index)) in var_to_ranges
179182
var = id_to_variables[id]
180183
if var.value !== nothing
@@ -188,7 +191,7 @@ function load_primal_solution!(primal::Array{Float64,1}, var_to_ranges::Dict{UIn
188191
end
189192
end
190193

191-
function populate_duals!(constraints::Array{ConicConstr}, dual::Vector)
194+
function populate_duals!(constraints::Array{ConicConstr}, dual::Vector, conic_constr_to_constr)
192195
constr_index = 1
193196
for constraint in constraints
194197
# conic_constr_to_constr only has keys for conic constraints with a single objective

src/variable.jl

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ mutable struct Variable <: AbstractExpr
1919
function Variable(size::Tuple{Int, Int}, sign::Sign=NoSign(), sets::Symbol...)
2020
this = new(:variable, 0, nothing, size, AffineVexity(), sign, Symbol[sets...])
2121
this.id_hash = objectid(this)
22-
id_to_variables[this.id_hash] = this
2322
return this
2423
end
2524

@@ -53,11 +52,6 @@ function HermitianSemidefinite(m::Integer, n::Integer)
5352
end
5453
end
5554

56-
# global map from unique variable ids to variables.
57-
# the expression tree will only utilize variable ids during construction
58-
# full information of the variables will be needed during stuffing
59-
# and after solving to populate the variables with values
60-
const id_to_variables = Dict{UInt64, Variable}()
6155

6256
function vexity(x::Variable)
6357
return x.vexity
@@ -86,32 +80,6 @@ function imag_conic_form(x::Variable)
8680
end
8781
end
8882

89-
function conic_form!(x::Variable, unique_conic_forms::UniqueConicForms=UniqueConicForms())
90-
if !has_conic_form(unique_conic_forms, x)
91-
if vexity(x) == ConstVexity()
92-
# do exactly what we would for a constant
93-
objective = ConicObj()
94-
objective[objectid(:constant)] = (vec([real(x.value);]),vec([imag(x.value);]))
95-
cache_conic_form!(unique_conic_forms, x, objective)
96-
else
97-
objective = ConicObj()
98-
vec_size = length(x)
99-
100-
objective[x.id_hash] = (real_conic_form(x), imag_conic_form(x))
101-
objective[objectid(:constant)] = (spzeros(vec_size, 1), spzeros(vec_size, 1))
102-
# placeholder values in unique constraints prevent infinite recursion depth
103-
cache_conic_form!(unique_conic_forms, x, objective)
104-
if !(x.sign == NoSign() || x.sign == ComplexSign())
105-
conic_form!(x.sign, x, unique_conic_forms)
106-
end
107-
for set in x.sets
108-
conic_form!(set, x, unique_conic_forms)
109-
end
110-
end
111-
end
112-
return get_conic_form(unique_conic_forms, x)
113-
end
114-
11583
# fix variables to hold them at their current value, and free them afterwards
11684
function fix!(x::Variable)
11785
x.value === nothing && error("This variable has no value yet; cannot fix value to nothing!")

src/variable_conic_form.jl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
function conic_form!(x::Variable, unique_conic_forms::UniqueConicForms=UniqueConicForms())
2+
if !has_conic_form(unique_conic_forms, x)
3+
add_to_id_to_variables!(unique_conic_forms, x)
4+
if vexity(x) == ConstVexity()
5+
# do exactly what we would for a constant
6+
objective = ConicObj()
7+
objective[objectid(:constant)] = (vec([real(x.value);]),vec([imag(x.value);]))
8+
cache_conic_form!(unique_conic_forms, x, objective)
9+
else
10+
objective = ConicObj()
11+
vec_size = length(x)
12+
13+
objective[x.id_hash] = (real_conic_form(x), imag_conic_form(x))
14+
objective[objectid(:constant)] = (spzeros(vec_size, 1), spzeros(vec_size, 1))
15+
# placeholder values in unique constraints prevent infinite recursion depth
16+
cache_conic_form!(unique_conic_forms, x, objective)
17+
if !(x.sign == NoSign() || x.sign == ComplexSign())
18+
conic_form!(x.sign, x, unique_conic_forms)
19+
end
20+
for set in x.sets
21+
conic_form!(set, x, unique_conic_forms)
22+
end
23+
end
24+
end
25+
return get_conic_form(unique_conic_forms, x)
26+
end

test/test_utilities.jl

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,20 +55,7 @@
5555
end
5656

5757
@testset "clearmemory" begin
58-
# solve a problem to populate globals
59-
x = Variable()
60-
p = minimize(-x, [x <= 0])
61-
@test vexity(p) == AffineVexity()
62-
solve!(p, solvers[1])
63-
64-
@test !isempty(Convex.id_to_variables)
65-
@test !isempty(Convex.conic_constr_to_constr)
66-
67-
Convex.clearmemory()
68-
69-
# check they are cleared
70-
@test isempty(Convex.id_to_variables)
71-
@test isempty(Convex.conic_constr_to_constr)
58+
@test_deprecated Convex.clearmemory()
7259
end
7360

7461
@testset "ConicObj" for T = [UInt32, UInt64]

0 commit comments

Comments
 (0)