jump-dev / Convex.jl

A Julia package for disciplined convex programming
https://jump.dev/Convex.jl/stable/
Other
559 stars 119 forks source link

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

Closed odow closed 2 months ago

odow commented 2 months ago

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;;]
ericphanson commented 2 months ago

hm, interesting. Some thoughts

ericphanson commented 2 months ago

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

odow commented 2 months ago

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

ericphanson commented 2 months ago

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

odow commented 2 months ago

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[bot] commented 2 months ago

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 ```diff @@ 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.

ericphanson commented 2 months ago

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

odow commented 2 months ago

memory allocated is potentially confusing

:+1:

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

odow commented 2 months ago

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

odow commented 2 months ago

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

odow commented 2 months ago

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