Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ad6d2ca
Score dimensional covers without promoting operands
timholy Jul 18, 2026
cb2693e
Check the solve status before reading a solution
timholy Jul 18, 2026
e0ce23f
Scale internal tolerances by eps of the element type
timholy Jul 18, 2026
0236a42
State that the support callback's return value is ignored
timholy Jul 18, 2026
6a8e335
Record the Unitful representation this extension reads
timholy Jul 18, 2026
9d275c8
Require a penalty in the heuristic covers' penalty slot
timholy Jul 18, 2026
7b863a0
Give complex Hermitian sparse input the sparse traversal
timholy Jul 18, 2026
306e45b
Report an empty strategy menu as such in the sym drivers
timholy Jul 18, 2026
0cb5729
Accept row and column scales of differing element types
timholy Jul 18, 2026
6896012
Check docstrings for the public bindings, not just the exports
timholy Jul 18, 2026
a36ee0c
Reject unit types that carry no multiplicative scale
timholy Jul 18, 2026
4aeb758
Check that abs.(A) is symmetric at the sym entry points
timholy Jul 18, 2026
b851c0e
Document the full-grid weighting of the symmetric cover objective
timholy Jul 18, 2026
e9f33a5
Read the cover objective through the support traversal
timholy Jul 18, 2026
5b21f00
Gather the support once for the soft-cover kernels
timholy Jul 18, 2026
aae8603
Size the native AbsLog{2} solvers by the support
timholy Jul 18, 2026
0d1aeed
Size the cover initializers by the support
timholy Jul 18, 2026
98533cb
Build the solver models from the support
timholy Jul 18, 2026
399ebe0
Weight the Ipopt sym objectives over the full grid
timholy Jul 18, 2026
a593760
Use JuMP's unified nonlinear interface in the Ipopt extension
timholy Jul 19, 2026
275ca54
Suppress Ipopt's startup banner
timholy Jul 19, 2026
665f98c
Make the prose examples executable
timholy Jul 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Aqua = "0.8"
ExplicitImports = "1.15"
HiGHS = "1"
Ipopt = "1"
JuMP = "1"
JuMP = "1.15"
LinearAlgebra = "1.10.0"
OffsetArrays = "1"
PrecompileTools = "1"
Expand Down
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,53 @@ rather than forbid it. Objective-minimal hard covers (`symcover_min`,
squared-log-excess penalty is solved natively, with no external solver, while
the other penalties are available when JuMP and HiGHS (or Ipopt) are loaded.

## Example

```julia
julia> using MatrixCovers, LinearAlgebra

julia> A = [4.0 2.0; 2.0 16.0];

julia> a = symcover(A) # a[i] * a[j] >= abs(A[i, j])
2-element Vector{Float64}:
2.0
4.0

julia> a * a' # dominates A entrywise
2×2 Matrix{Float64}:
4.0 8.0
8.0 16.0

julia> iscover(a, A)
true
```

Covers are scale-covariant: rescaling the matrix rescales the cover the same way.

```julia
julia> D = Diagonal([10.0, 0.5]);

julia> symcover(D * A * D) ≈ D * a
true
```

Non-symmetric matrices get separate row and column scales from `cover`, and
`cover_min`/`symcover_min` trade the fast heuristic for a cover that minimizes a
penalty subject to the same constraint:

```julia
julia> M = [1.0 2.0 3.0; 6.0 5.0 4.0];

julia> a, b = cover(M);

julia> iscover(a, b, M)
true

julia> aq, bq = cover_min(AbsLog{2}(), M); # minimal, solved natively

julia> cover_objective(AbsLog{2}(), aq, bq, M) <= cover_objective(AbsLog{2}(), a, b, M)
true
```

See the [documentation](https://HolyLab.github.io/MatrixCovers.jl/dev/)
for motivation, examples, and a full API reference.
4 changes: 3 additions & 1 deletion docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
MatrixCovers = "727e6139-ff52-4636-a344-ed1d23e73ffc"
Expand All @@ -9,6 +10,7 @@ Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
[compat]
Documenter = "1"
HiGHS = "1"
JuMP = "1"
Ipopt = "1"
JuMP = "1.15"
LinearAlgebra = "1"
Unitful = "1"
7 changes: 5 additions & 2 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using MatrixCovers
using Documenter
using JuMP, HiGHS
using JuMP, HiGHS, Ipopt
using Unitful # loaded here so the doctests' own `using` cannot emit precompilation output

DocMeta.setdocmeta!(MatrixCovers, :DocTestSetup, :(using MatrixCovers); recursive=true)
Expand All @@ -14,7 +14,10 @@ makedocs(;
edit_link="main",
assets=String[],
),
checkdocs=:exports,
# `:public` also covers the bindings marked `public` but not exported (the
# extension hooks). It rests on `Base.ispublic`, which Julia 1.10 lacks, so
# there the check falls back to the exported set.
checkdocs=(VERSION >= v"1.11" ? :public : :exports),
pages=[
"Home" => "index.md",
],
Expand Down
57 changes: 45 additions & 12 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,14 +217,30 @@ faster than a general-purpose convex solver. The other penalties — `AbsLog{1}
(a linear program) and the non-convex `AbsLinear` variants — are solved through
[JuMP](https://jump.dev/) and are loaded on demand as a package extension:

```julia
using JuMP, HiGHS # HiGHS for AbsLog penalties
using MatrixCovers
```jldoctest jumpmin
julia> using MatrixCovers, JuMP, HiGHS # HiGHS for the AbsLog penalties

a = symcover_min(AbsLog{1}(), A) # L1-minimal symmetric hard cover
a, b = cover_min(AbsLog{1}(), A) # L1-minimal general hard cover
julia> S = [4 1 0; 1 1 5; 0 5 2]; # symmetric

julia> round.(symcover_min(AbsLog{1}(), S); digits=6) # L1-minimal symmetric hard cover
3-element Vector{Float64}:
2.0
1.0
5.0

julia> A = [1 2 3; 6 5 4];

julia> a, b = cover_min(AbsLog{1}(), A); # L1-minimal general hard cover

julia> round.(a * b'; digits=6) # tight on four of the six entries
2×3 Matrix{Float64}:
2.4 2.0 3.0
6.0 5.0 7.5
```

The solver returns values good to roughly solver tolerance, so these examples
round before displaying.

The soft `*_min` solvers divide along the same line, but not at the same place:
[`soft_symcover_min`](@ref) and [`soft_cover_min`](@ref) solve `AbsLog{2}` natively and
reach for JuMP with Ipopt only for the `AbsLinear` penalties. They do not accept
Expand Down Expand Up @@ -291,15 +307,32 @@ from scratch rather than reading the vector passed in.

For finer control, you can run these manually:

```julia
using JuMP, Ipopt # Ipopt for the AbsLinear penalties
using MatrixCovers
```jldoctest manualstart
julia> using MatrixCovers, JuMP, Ipopt # Ipopt for the AbsLinear penalties

a = symcover_min(AbsLinear{2}(), A) # multistart over the whole menu
a = symcover_min(AbsLinear{2}(), A; strategies=(:geomean,)) # or commit to one start
julia> S = [4 1 0; 1 1 5; 0 5 2];

a0 = initialize_symcover(A; strategy=:geomean) # or drive it yourself
symcover_min!(AbsLinear{2}(), a0, A)
julia> round.(symcover_min(AbsLinear{2}(), S); digits=6) # multistart over the whole menu
3-element Vector{Float64}:
2.0
1.0
5.0

julia> round.(symcover_min(AbsLinear{2}(), S; strategies=(:geomean,)); digits=6) # or commit to one start
3-element Vector{Float64}:
2.0
1.0
5.0

julia> a0 = initialize_symcover(S; strategy=:geomean); # or drive it yourself

julia> symcover_min!(AbsLinear{2}(), a0, S);

julia> round.(a0; digits=6)
3-element Vector{Float64}:
2.0
1.0
5.0
```

The same menu supplies the starting points of the [`soft_symcover`](@ref) and
Expand Down
Loading
Loading