Convex.jl
Convex.jl copied to clipboard
Add summary printing to show(::IO, ::Problem)
Alternative for #643
Closes #640
Examples
julia> A = [1 2im 3 4; 4im 3im 2 1; 4 5 6 7]
3×4 Matrix{Complex{Int64}}:
1+0im 0+2im 3+0im 4+0im
0+4im 0+3im 2+0im 1+0im
4+0im 5+0im 6+0im 7+0im
julia> y = ComplexVariable(3, 4)
Variable
size: (3, 4)
sign: complex
vexity: affine
id: 120…842
julia> p = minimize(nuclearnorm(y), y == A)
Problem statistics
number of variables : 1 (24 scalar elements)
number of constraints : 1 (24 scalar elements)
number of coefficients : 24
number of atoms : 2
Solution summary
termination status : OPTIMIZE_NOT_CALLED
primal status : NO_SOLUTION
dual status : NO_SOLUTION
Expression graph
minimize
└─ nuclearnorm (convex; positive)
└─ 3×4 complex variable (id: 120…842)
subject to
└─ == constraint (affine)
└─ + (affine; complex)
├─ 3×4 complex variable (id: 120…842)
└─ 3×4 complex constant
julia> x = Variable(1000)
Variable
size: (1000, 1)
sign: real
vexity: affine
id: 153…767
julia> for i in 1:1000
add_constraint!(x, x[i] >= 0)
end
julia> p = minimize(sum(x))
Problem statistics
number of variables : 1 (1_000 scalar elements)
number of constraints : 1_000 (1_000 scalar elements)
number of coefficients : 1000
number of atoms : 2001
Solution summary
termination status : OPTIMIZE_NOT_CALLED
primal status : NO_SOLUTION
dual status : NO_SOLUTION
Expression graph
minimize
└─ sum (affine; real)
└─ 1000-element real variable (id: 245…329)
subject to
├─ ≥ constraint (affine)
│ └─ + (affine; real)
│ ├─ index (affine; real)
│ │ └─ …
│ └─ [0;;]
hm, interesting. Some thoughts
- I agree with merging number of dense elements and sparse to just number of coefficients
- I agree with deferring MOI stuff to MOI. Could be nice if we could just
show
the model post-formulation or something. - this printing is less compact, but has more alignment, so maybe it is easier to read
- this
Counts
implementation looks less "elegant" to me (more direct/manual, less composition of abstractions), but maybe that's for the best - using
#
for "number" is confusing to me, since it gets highlighted like a comment (for awhile I was wondering "why isn't thesize
one commented?"- also flux uses
#
to inject counts as comments (see below), so I probably had that expectation
- also flux uses
- the re-use of the tree-printing characters to show the summary is kinda interesting - makes me think maybe we should merge the counts with the tree, like Flux does (though they have flatter structures usually):
Chain(
Dropout(0.0, active=false),
Conv((3, 3), 1 => 32, relu), # 320 parameters
BatchNorm(32, relu, active=false), # 64 parameters, plus 64
MaxPool((2, 2)),
Dropout(0.0, active=false),
Conv((3, 3), 32 => 16, relu), # 4_624 parameters
Dropout(0.0, active=false),
MaxPool((2, 2)),
Dropout(0.0, active=false),
Conv((3, 3), 16 => 10, relu), # 1_450 parameters
Dropout(0.0, active=false),
var"#22#23"(),
Dropout(0.0, active=false),
Dense(90 => 10), # 910 parameters
Dense(10 => 10), # 110 parameters
Dense(10 => 10), # 110 parameters
NNlib.softmax,
) # Total: 12 trainable arrays, 7_478 parameters,
# plus 4 non-trainable, 174 parameters, summarysize 31.431 KiB.
I think
julia> problem
summary
├─ # variables : 1 (1_000 scalar elements)
├─ # constraints : 1_000 (1_000 scalar elements)
├─ # coefficients : 1000
├─ # atoms : 2001
└─ size : 265.836 KiB
minimize
└─ sum (affine; real)
└─ 1000-element real variable (id: 153…767)
subject to
├─ ≥ constraint (affine)
│ └─ + (affine; real)
would make more sense to me if you could do problem.summary
or something to get a ProblemSummary
object, and that object had those numbers and nice printing. Because to me it looks like we are saying: "this problem has a summary
with these entries, and minimize
with these children".
(In terms of implementation, I don't think a field totally makes sense, bc I don't think we want to actually cache the summary since then we have to worry about invalidation, but it could be get_summary(problem)
or something).
What if it said "summary statistics" or "problem statistics"?
what about something more distinct from the tree printing?
julia> p = minimize(nuclearnorm(y), y == A)
┌──────────────┬────────────────────────┐
│ variables │ 1 (24 scalar elements) │
│ constraints │ 1 (24 scalar elements) │
│ coefficients │ 24 │
│ atoms │ 2 │
│ size │ 704 bytes │
└──────────────┴────────────────────────┘
minimize
└─ nuclearnorm (convex; positive)
└─ 3×4 complex variable (id: 446…935)
subject to
└─ == constraint (affine)
└─ + (affine; complex)
├─ 3×4 complex variable (id: 446…935)
└─ 3×4 complex constant
status: `solve!` not called yet
(this I mocked up with a dataframe and PrettyTables.pretty_table(stdout, df; alignment=[:r, :l], show_subheader=false, show_header=false, tf=tf_unicode)
).
What about:
julia> p
Problem statistics
number of variables : 1 (4 scalar elements)
number of constraints : 5 (14 scalar elements)
number of coefficients : 36
number of atoms : 19
memory allocated : 2.727 KiB
Solution summary
Termination status : OPTIMAL
Primal status : FEASIBLE_POINT
Dual status : FEASIBLE_POINT
objective value : 9.9998
Expression graph
minimize
└─ sum (affine; real)
└─ reshape (affine; real)
└─ * (affine; real)
├─ …
└─ …
subject to
├─ ≤ constraint (affine)
│ └─ + (affine; real)
│ ├─ * (affine; real)
│ │ ├─ …
│ │ └─ …
│ └─ 4×1 Matrix{Int64}
├─ ≥ constraint (affine)
│ └─ + (affine; real)
│ ├─ 4-element real variable (id: 104…638)
│ └─ Convex.NegateAtom (constant; negative)
│ └─ …
├─ ≤ constraint (affine)
│ └─ + (affine; real)
│ ├─ 4-element real variable (id: 104…638)
│ └─ Convex.NegateAtom (constant; negative)
│ └─ …
├─ ≤ constraint (affine)
│ └─ + (affine; real)
│ ├─ index (affine; real)
│ │ └─ …
│ └─ [-5;;]
└─ ≤ constraint (affine)
└─ + (affine; real)
├─ index (affine; real)
│ └─ …
├─ index (affine; real)
│ └─ …
├─ Convex.NegateAtom (affine; real)
│ └─ …
└─ [-10;;]
Codecov Report
All modified and coverable lines are covered by tests :white_check_mark:
Project coverage is 97.94%. Comparing base (
379f7e7
) to head (1515175
). Report is 2 commits behind head on master.
Additional details and impacted files
@@ Coverage Diff @@
## master #650 +/- ##
==========================================
+ Coverage 97.89% 97.94% +0.05%
==========================================
Files 88 88
Lines 5125 5208 +83
==========================================
+ Hits 5017 5101 +84
+ Misses 108 107 -1
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
I like it!
One tiny nitpick is that memory allocated is potentially confusing, since more memory was likely allocated on the way, but this what we’re still using.
otherwise lgtm
memory allocated is potentially confusing
:+1:
What about explicitly quantifying the size of the expression graph and the size of the MOI backend?
One issue with optimization model : XXX bytes
is that it doesn't count the memory allocated in C by the solver
Maybe we should just remove the memory stuff. The rest is useful enough, and specifying the memory is just going to mislead people.
Okay, this now yields:
julia> A = [1 2im 3 4; 4im 3im 2 1; 4 5 6 7]
3×4 Matrix{Complex{Int64}}:
1+0im 0+2im 3+0im 4+0im
0+4im 0+3im 2+0im 1+0im
4+0im 5+0im 6+0im 7+0im
julia> y = ComplexVariable(3, 4)
Variable
size: (3, 4)
sign: complex
vexity: affine
id: 120…842
julia> p = minimize(nuclearnorm(y), y == A)
Problem statistics
number of variables : 1 (24 scalar elements)
number of constraints : 1 (24 scalar elements)
number of coefficients : 24
number of atoms : 2
Solution summary
termination status : OPTIMIZE_NOT_CALLED
primal status : NO_SOLUTION
dual status : NO_SOLUTION
Expression graph
minimize
└─ nuclearnorm (convex; positive)
└─ 3×4 complex variable (id: 120…842)
subject to
└─ == constraint (affine)
└─ + (affine; complex)
├─ 3×4 complex variable (id: 120…842)
└─ 3×4 complex constant