Convex.jl icon indicating copy to clipboard operation
Convex.jl copied to clipboard

Add summary printing to show(::IO, ::Problem)

Open odow opened this issue 9 months ago • 11 comments

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;;]

odow avatar May 10 '24 03:05 odow

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 the size one commented?"
    • also flux uses # to inject counts as comments (see below), so I probably had that expectation
  • 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.

ericphanson avatar May 10 '24 11:05 ericphanson

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).

ericphanson avatar May 10 '24 11:05 ericphanson

What if it said "summary statistics" or "problem statistics"?

odow avatar May 10 '24 23:05 odow

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)).

ericphanson avatar May 11 '24 13:05 ericphanson

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;;]

odow avatar May 12 '24 22:05 odow

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.

codecov[bot] avatar May 12 '24 23:05 codecov[bot]

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

ericphanson avatar May 12 '24 23:05 ericphanson

memory allocated is potentially confusing

:+1:

What about explicitly quantifying the size of the expression graph and the size of the MOI backend?

odow avatar May 12 '24 23:05 odow

One issue with optimization model : XXX bytes is that it doesn't count the memory allocated in C by the solver

odow avatar May 13 '24 01:05 odow

Maybe we should just remove the memory stuff. The rest is useful enough, and specifying the memory is just going to mislead people.

odow avatar May 13 '24 01:05 odow

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

odow avatar May 13 '24 02:05 odow