diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f5bca19..468b554 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -66,6 +66,6 @@ jobs: shell: julia --project=docs --color=yes {0} run: | using Documenter: DocMeta, doctest - using ScaleInvariantAnalysis - DocMeta.setdocmeta!(ScaleInvariantAnalysis, :DocTestSetup, :(using ScaleInvariantAnalysis); recursive=true) - doctest(ScaleInvariantAnalysis) + using MatrixCovers + DocMeta.setdocmeta!(MatrixCovers, :DocTestSetup, :(using MatrixCovers); recursive=true) + doctest(MatrixCovers) diff --git a/Project.toml b/Project.toml index 36abf67..104c3cd 100644 --- a/Project.toml +++ b/Project.toml @@ -1,4 +1,4 @@ -name = "ScaleInvariantAnalysis" +name = "MatrixCovers" uuid = "727e6139-ff52-4636-a344-ed1d23e73ffc" version = "1.0.0-DEV" authors = ["Tim Holy and contributors"] @@ -16,11 +16,11 @@ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" [extensions] -SIAIpopt = ["Ipopt", "JuMP"] -SIAJuMP = ["HiGHS", "JuMP"] -SIASparseArrays = "SparseArrays" -SIASparseArraysUnitful = ["SparseArrays", "Unitful"] -SIAUnitful = "Unitful" +MatrixCoversIpoptExt = ["Ipopt", "JuMP"] +MatrixCoversJuMPExt = ["HiGHS", "JuMP"] +MatrixCoversSparseArraysExt = "SparseArrays" +MatrixCoversSparseArraysUnitfulExt = ["SparseArrays", "Unitful"] +MatrixCoversUnitfulExt = "Unitful" [compat] Aqua = "0.8" diff --git a/README.md b/README.md index aee5a00..03f521b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# ScaleInvariantAnalysis +# MatrixCovers - -[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://HolyLab.github.io/ScaleInvariantAnalysis.jl/dev/) -[![Build Status](https://github.com/HolyLab/ScaleInvariantAnalysis.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/HolyLab/ScaleInvariantAnalysis.jl/actions/workflows/CI.yml?query=branch%3Amain) -[![Coverage](https://codecov.io/gh/HolyLab/ScaleInvariantAnalysis.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/HolyLab/ScaleInvariantAnalysis.jl) + +[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://HolyLab.github.io/MatrixCovers.jl/dev/) +[![Build Status](https://github.com/HolyLab/MatrixCovers.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/HolyLab/MatrixCovers.jl/actions/workflows/CI.yml?query=branch%3Amain) +[![Coverage](https://codecov.io/gh/HolyLab/MatrixCovers.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/HolyLab/MatrixCovers.jl) [![Aqua QA](https://juliatesting.github.io/Aqua.jl/dev/assets/badge.svg)](https://github.com/JuliaTesting/Aqua.jl) This package computes **covers** of matrices: non-negative vectors `a` (and `b`) @@ -19,5 +19,5 @@ 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. -See the [documentation](https://HolyLab.github.io/ScaleInvariantAnalysis.jl/dev/) +See the [documentation](https://HolyLab.github.io/MatrixCovers.jl/dev/) for motivation, examples, and a full API reference. diff --git a/bench/Project.toml b/bench/Project.toml index c99a54c..dd185dc 100644 --- a/bench/Project.toml +++ b/bench/Project.toml @@ -3,7 +3,7 @@ HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" -ScaleInvariantAnalysis = "727e6139-ff52-4636-a344-ed1d23e73ffc" +MatrixCovers = "727e6139-ff52-4636-a344-ed1d23e73ffc" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" diff --git a/bench/sizesweep.jl b/bench/sizesweep.jl index 565b5c4..2cfb2ce 100644 --- a/bench/sizesweep.jl +++ b/bench/sizesweep.jl @@ -7,8 +7,8 @@ # julia --project=bench bench/sizesweep.jl # First use (creates bench/Manifest*.toml, which is gitignored): # julia --project=bench -e 'using Pkg; Pkg.develop(path="."); Pkg.instantiate()' -using ScaleInvariantAnalysis, SparseArrays -using JuMP, HiGHS, Ipopt # triggers the SIAJuMP and SIAIpopt solver extensions +using MatrixCovers, SparseArrays +using JuMP, HiGHS, Ipopt # triggers the MatrixCoversJuMPExt and MatrixCoversIpoptExt solver extensions using StableRNGs: StableRNG using Statistics: median using Printf diff --git a/docs/Project.toml b/docs/Project.toml index 236ec75..a34d103 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -3,7 +3,7 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -ScaleInvariantAnalysis = "727e6139-ff52-4636-a344-ed1d23e73ffc" +MatrixCovers = "727e6139-ff52-4636-a344-ed1d23e73ffc" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" [compat] diff --git a/docs/make.jl b/docs/make.jl index 677f415..5f372da 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,16 +1,16 @@ -using ScaleInvariantAnalysis +using MatrixCovers using Documenter using JuMP, HiGHS using Unitful # loaded here so the doctests' own `using` cannot emit precompilation output -DocMeta.setdocmeta!(ScaleInvariantAnalysis, :DocTestSetup, :(using ScaleInvariantAnalysis); recursive=true) +DocMeta.setdocmeta!(MatrixCovers, :DocTestSetup, :(using MatrixCovers); recursive=true) makedocs(; - modules=[ScaleInvariantAnalysis], + modules=[MatrixCovers], authors="Tim Holy and contributors", - sitename="ScaleInvariantAnalysis.jl", + sitename="MatrixCovers.jl", format=Documenter.HTML(; - canonical="https://HolyLab.github.io/ScaleInvariantAnalysis.jl", + canonical="https://HolyLab.github.io/MatrixCovers.jl", edit_link="main", assets=String[], ), @@ -21,6 +21,6 @@ makedocs(; ) deploydocs(; - repo="github.com/HolyLab/ScaleInvariantAnalysis.jl", + repo="github.com/HolyLab/MatrixCovers.jl", devbranch="main", ) diff --git a/docs/src/index.md b/docs/src/index.md index c43bc3f..b17da09 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,8 +1,8 @@ ```@meta -CurrentModule = ScaleInvariantAnalysis +CurrentModule = MatrixCovers ``` -# ScaleInvariantAnalysis +# MatrixCovers This package computes *covers* of matrices. Given a matrix `A`, a cover (more specifically, a *hard cover*) is a matrix `C` that can be defined as @@ -41,7 +41,7 @@ Newtons. Loading [Unitful](https://github.com/PainterQubits/Unitful.jl) lets the matrix carry those units itself: ```jldoctest coverunits -julia> using ScaleInvariantAnalysis, Unitful +julia> using MatrixCovers, Unitful julia> L, V, F = u"m", u"m/s", u"N"; # length, velocity, force @@ -112,7 +112,7 @@ which the solvers minimize. Two penalty families are provided: [`cover_objective`](@ref) evaluates either penalty for a given cover: ```jldoctest quality; filter = r"(\d+\.\d{6})\d+" => s"\1" -julia> using ScaleInvariantAnalysis +julia> using MatrixCovers julia> A = [4.0 2.0; 2.0 16.0]; @@ -186,7 +186,7 @@ penalty they are solved natively (no external solver) by penalty-continuation with a damped semismooth Newton iteration: ```jldoctest qmin; filter = r"(\d+\.\d{6})\d+" => s"\1" -julia> using ScaleInvariantAnalysis +julia> using MatrixCovers julia> A = [1 2 3; 6 5 4]; @@ -219,7 +219,7 @@ faster than a general-purpose convex solver. The other penalties — `AbsLog{1} ```julia using JuMP, HiGHS # HiGHS for AbsLog penalties -using ScaleInvariantAnalysis +using MatrixCovers a = symcover_min(AbsLog{1}(), A) # L1-minimal symmetric hard cover a, b = cover_min(AbsLog{1}(), A) # L1-minimal general hard cover @@ -293,7 +293,7 @@ For finer control, you can run these manually: ```julia using JuMP, Ipopt # Ipopt for the AbsLinear penalties -using ScaleInvariantAnalysis +using MatrixCovers a = symcover_min(AbsLinear{2}(), A) # multistart over the whole menu a = symcover_min(AbsLinear{2}(), A; strategies=(:geomean,)) # or commit to one start @@ -323,7 +323,7 @@ That quantity can be estimated from the magnitudes of `A` and `b` alone, without forming `x` at all: ```jldoctest roundoff -julia> using ScaleInvariantAnalysis, LinearAlgebra +julia> using MatrixCovers, LinearAlgebra julia> A = [1e6 1e3; 1e3 4.0]; @@ -410,12 +410,12 @@ true ## Index of available tools ```@index -Modules = [ScaleInvariantAnalysis] +Modules = [MatrixCovers] ``` ## Reference documentation ```@autodocs -Modules = [ScaleInvariantAnalysis] +Modules = [MatrixCovers] Private = false ``` diff --git a/ext/SIAIpopt.jl b/ext/MatrixCoversIpoptExt.jl similarity index 90% rename from ext/SIAIpopt.jl rename to ext/MatrixCoversIpoptExt.jl index e42eac9..5842043 100644 --- a/ext/SIAIpopt.jl +++ b/ext/MatrixCoversIpoptExt.jl @@ -1,9 +1,9 @@ -module SIAIpopt +module MatrixCoversIpoptExt using JuMP: JuMP, @variable, @objective, @constraint, @NLobjective, @NLconstraint using Ipopt: Ipopt -using ScaleInvariantAnalysis -using ScaleInvariantAnalysis: AbsLinear +using MatrixCovers +using MatrixCovers: AbsLinear # The models are built over 1-based positions 1:n; `pr`/`pc` map each position to the # corresponding axis index of `A`, and results are scattered back onto vectors @@ -34,8 +34,8 @@ end # Variables: α[i] = log(a[i]); constraint: α[i]+α[j] ≥ log|A[i,j]|. # ============================================================ -function ScaleInvariantAnalysis.symcover_min!(::AbsLinear{2}, a::AbstractVector, A) - ScaleInvariantAnalysis._prepare_symcover_start!(a, A) +function MatrixCovers.symcover_min!(::AbsLinear{2}, a::AbstractVector, A) + MatrixCovers._prepare_symcover_start!(a, A) axr = axes(A, 1) T = float(real(eltype(A))) pr = collect(axr) @@ -63,8 +63,8 @@ function ScaleInvariantAnalysis.symcover_min!(::AbsLinear{2}, a::AbstractVector, return a end -function ScaleInvariantAnalysis.symcover_min!(::AbsLinear{1}, a::AbstractVector, A) - ScaleInvariantAnalysis._prepare_symcover_start!(a, A) +function MatrixCovers.symcover_min!(::AbsLinear{1}, a::AbstractVector, A) + MatrixCovers._prepare_symcover_start!(a, A) axr = axes(A, 1) T = float(real(eltype(A))) pr = collect(axr) @@ -108,8 +108,8 @@ end # would be arbitrary. # ============================================================ -function ScaleInvariantAnalysis.cover_min!(::AbsLinear{2}, a::AbstractVector, b::AbstractVector, A) - ScaleInvariantAnalysis._prepare_cover_start!(a, b, A) +function MatrixCovers.cover_min!(::AbsLinear{2}, a::AbstractVector, b::AbstractVector, A) + MatrixCovers._prepare_cover_start!(a, b, A) axr, axc = axes(A, 1), axes(A, 2) T = float(real(eltype(A))) pr, pc = collect(axr), collect(axc) @@ -143,8 +143,8 @@ function ScaleInvariantAnalysis.cover_min!(::AbsLinear{2}, a::AbstractVector, b: return a, b end -function ScaleInvariantAnalysis.cover_min!(::AbsLinear{1}, a::AbstractVector, b::AbstractVector, A) - ScaleInvariantAnalysis._prepare_cover_start!(a, b, A) +function MatrixCovers.cover_min!(::AbsLinear{1}, a::AbstractVector, b::AbstractVector, A) + MatrixCovers._prepare_cover_start!(a, b, A) axr, axc = axes(A, 1), axes(A, 2) T = float(real(eltype(A))) pr, pc = collect(axr), collect(axc) @@ -190,8 +190,8 @@ end # driver over these kernels is native; see soft_symcover_min. # ============================================================ -function ScaleInvariantAnalysis.soft_symcover_min!(::AbsLinear{2}, a::AbstractVector, A) - ScaleInvariantAnalysis._prepare_soft_symcover_start!(a, A) +function MatrixCovers.soft_symcover_min!(::AbsLinear{2}, a::AbstractVector, A) + MatrixCovers._prepare_soft_symcover_start!(a, A) axr = axes(A, 1) T = float(real(eltype(A))) pr = collect(axr) @@ -217,8 +217,8 @@ function ScaleInvariantAnalysis.soft_symcover_min!(::AbsLinear{2}, a::AbstractVe return a end -function ScaleInvariantAnalysis.soft_symcover_min!(::AbsLinear{1}, a::AbstractVector, A) - ScaleInvariantAnalysis._prepare_soft_symcover_start!(a, A) +function MatrixCovers.soft_symcover_min!(::AbsLinear{1}, a::AbstractVector, A) + MatrixCovers._prepare_soft_symcover_start!(a, A) axr = axes(A, 1) T = float(real(eltype(A))) pr = collect(axr) @@ -256,8 +256,8 @@ end # scales, so the count of zeros enters the objective as a constant, matching cover_objective. # ============================================================ -function ScaleInvariantAnalysis.soft_cover_min!(::AbsLinear{2}, a::AbstractVector, b::AbstractVector, A) - ScaleInvariantAnalysis._prepare_soft_cover_start!(a, b, A) +function MatrixCovers.soft_cover_min!(::AbsLinear{2}, a::AbstractVector, b::AbstractVector, A) + MatrixCovers._prepare_soft_cover_start!(a, b, A) axr, axc = axes(A, 1), axes(A, 2) T = float(real(eltype(A))) pr, pc = collect(axr), collect(axc) @@ -289,8 +289,8 @@ function ScaleInvariantAnalysis.soft_cover_min!(::AbsLinear{2}, a::AbstractVecto return a, b end -function ScaleInvariantAnalysis.soft_cover_min!(::AbsLinear{1}, a::AbstractVector, b::AbstractVector, A) - ScaleInvariantAnalysis._prepare_soft_cover_start!(a, b, A) +function MatrixCovers.soft_cover_min!(::AbsLinear{1}, a::AbstractVector, b::AbstractVector, A) + MatrixCovers._prepare_soft_cover_start!(a, b, A) axr, axc = axes(A, 1), axes(A, 2) T = float(real(eltype(A))) pr, pc = collect(axr), collect(axc) @@ -327,4 +327,4 @@ function ScaleInvariantAnalysis.soft_cover_min!(::AbsLinear{1}, a::AbstractVecto return a, b end -end # module SIAIpopt +end # module MatrixCoversIpoptExt diff --git a/ext/SIAJuMP.jl b/ext/MatrixCoversJuMPExt.jl similarity index 92% rename from ext/SIAJuMP.jl rename to ext/MatrixCoversJuMPExt.jl index 837807c..b440234 100644 --- a/ext/SIAJuMP.jl +++ b/ext/MatrixCoversJuMPExt.jl @@ -1,9 +1,9 @@ -module SIAJuMP +module MatrixCoversJuMPExt using JuMP: JuMP, @variable, @objective, @constraint using HiGHS: HiGHS -using ScaleInvariantAnalysis -using ScaleInvariantAnalysis: AbsLog +using MatrixCovers +using MatrixCovers: AbsLog using LinearAlgebra: dot # The models are built over 1-based positions 1:n; `pr`/`pc` map each position to @@ -14,7 +14,7 @@ using LinearAlgebra: dot # Exact reference for the native `symcover_min(::AbsLog{2})`: same QP, solved by # HiGHS. Not exported; used by the test suite to cross-check the native solver. -function ScaleInvariantAnalysis.symcover_min_jump(::AbsLog{2}, A) +function MatrixCovers.symcover_min_jump(::AbsLog{2}, A) axr = axes(A, 1) axes(A, 2) == axr || throw(ArgumentError("symcover_min_jump requires a square matrix")) T = float(real(eltype(A))) @@ -38,10 +38,10 @@ function ScaleInvariantAnalysis.symcover_min_jump(::AbsLog{2}, A) return a end -ScaleInvariantAnalysis.symcover_min(::AbsLog{1}, A) = _symcover_min_abslog1(A, nothing) +MatrixCovers.symcover_min(::AbsLog{1}, A) = _symcover_min_abslog1(A, nothing) -function ScaleInvariantAnalysis.symcover_min!(::AbsLog{1}, a::AbstractVector, A) - ScaleInvariantAnalysis._prepare_symcover_start!(a, A) +function MatrixCovers.symcover_min!(::AbsLog{1}, a::AbstractVector, A) + MatrixCovers._prepare_symcover_start!(a, A) a .= _symcover_min_abslog1(A, a) return a end @@ -114,7 +114,7 @@ function _symcover_min_abslog1(A, start) return a end -function ScaleInvariantAnalysis.cover_min_jump(::AbsLog{2}, A) +function MatrixCovers.cover_min_jump(::AbsLog{2}, A) axr = axes(A, 1) axc = axes(A, 2) T = float(real(eltype(A))) @@ -146,10 +146,10 @@ function ScaleInvariantAnalysis.cover_min_jump(::AbsLog{2}, A) return a, b end -ScaleInvariantAnalysis.cover_min(::AbsLog{1}, A) = _cover_min_abslog1(A, nothing) +MatrixCovers.cover_min(::AbsLog{1}, A) = _cover_min_abslog1(A, nothing) -function ScaleInvariantAnalysis.cover_min!(::AbsLog{1}, a::AbstractVector, b::AbstractVector, A) - ScaleInvariantAnalysis._prepare_cover_start!(a, b, A) +function MatrixCovers.cover_min!(::AbsLog{1}, a::AbstractVector, b::AbstractVector, A) + MatrixCovers._prepare_cover_start!(a, b, A) anew, bnew = _cover_min_abslog1(A, (a, b)) a .= anew b .= bnew diff --git a/ext/SIASparseArrays.jl b/ext/MatrixCoversSparseArraysExt.jl similarity index 73% rename from ext/SIASparseArrays.jl rename to ext/MatrixCoversSparseArraysExt.jl index 0a3500b..e9d9148 100644 --- a/ext/SIASparseArrays.jl +++ b/ext/MatrixCoversSparseArraysExt.jl @@ -1,15 +1,15 @@ -module SIASparseArrays +module MatrixCoversSparseArraysExt using LinearAlgebra: LinearAlgebra, Hermitian, Symmetric using SparseArrays: SparseArrays, SparseMatrixCSC, nonzeros, nzrange, rowvals -using ScaleInvariantAnalysis -using ScaleInvariantAnalysis: AbsLog, _symcover_min_abslog2, _cover_min_abslog2 +using MatrixCovers +using MatrixCovers: AbsLog, _symcover_min_abslog2, _cover_min_abslog2 # ============================================================ # Support traversal # ============================================================ -function ScaleInvariantAnalysis.foreach_support(f, A::SparseMatrixCSC) +function MatrixCovers.foreach_support(f, A::SparseMatrixCSC) rv, nzs = rowvals(A), nonzeros(A) for j in axes(A, 2) for k in nzrange(A, j) @@ -20,7 +20,7 @@ function ScaleInvariantAnalysis.foreach_support(f, A::SparseMatrixCSC) return nothing end -function ScaleInvariantAnalysis.foreach_support_sym(f, A::SparseMatrixCSC) +function MatrixCovers.foreach_support_sym(f, A::SparseMatrixCSC) ax = axes(A, 1) axes(A, 2) == ax || throw(DimensionMismatch("foreach_support_sym requires a square matrix, got axes $(axes(A))")) rv, nzs = rowvals(A), nonzeros(A) @@ -37,7 +37,7 @@ end # Emitted pairs are canonical (row <= col) regardless of uplo: for uplo='L' # the stored (i, j) with i >= j is reported as (j, i). -function ScaleInvariantAnalysis.foreach_support_sym(f, +function MatrixCovers.foreach_support_sym(f, S::Union{Symmetric{<:Any,<:SparseMatrixCSC},Hermitian{<:Real,<:SparseMatrixCSC}}) P = parent(S) ax = axes(P, 1) @@ -71,43 +71,43 @@ end # is the intended path when nnz ≪ n²; pass `linsolve=:auto`/`:dense` to force the # dense factorization. Only AbsLog{2} is native; other penalties dispatch to the # JuMP extension. -function ScaleInvariantAnalysis.symcover_min(ϕ::AbsLog{2}, A::SparseMatrixCSC; linsolve::Symbol=:lsqr, kwargs...) +function MatrixCovers.symcover_min(ϕ::AbsLog{2}, A::SparseMatrixCSC; linsolve::Symbol=:lsqr, kwargs...) a, _ = _symcover_min_abslog2(A; linsolve, kwargs...) return a end -function ScaleInvariantAnalysis.cover_min(ϕ::AbsLog{2}, A::SparseMatrixCSC; linsolve::Symbol=:lsqr, kwargs...) +function MatrixCovers.cover_min(ϕ::AbsLog{2}, A::SparseMatrixCSC; linsolve::Symbol=:lsqr, kwargs...) a, b, _ = _cover_min_abslog2(A; linsolve, kwargs...) return a, b end -function ScaleInvariantAnalysis.symcover_min(ϕ::AbsLog{2}, S::Symmetric{<:Any, <:SparseMatrixCSC}; linsolve::Symbol=:lsqr, kwargs...) +function MatrixCovers.symcover_min(ϕ::AbsLog{2}, S::Symmetric{<:Any, <:SparseMatrixCSC}; linsolve::Symbol=:lsqr, kwargs...) a, _ = _symcover_min_abslog2(S; linsolve, kwargs...) return a end -function ScaleInvariantAnalysis.symcover_min(ϕ::AbsLog{2}, H::Hermitian{<:Any, <:SparseMatrixCSC}; linsolve::Symbol=:lsqr, kwargs...) +function MatrixCovers.symcover_min(ϕ::AbsLog{2}, H::Hermitian{<:Any, <:SparseMatrixCSC}; linsolve::Symbol=:lsqr, kwargs...) a, _ = _symcover_min_abslog2(H; linsolve, kwargs...) return a end # The refiners take the same sparse `linsolve` default as the solvers above. -function ScaleInvariantAnalysis.symcover_min!(ϕ::AbsLog{2}, a::AbstractVector, +function MatrixCovers.symcover_min!(ϕ::AbsLog{2}, a::AbstractVector, S::Union{SparseMatrixCSC,Symmetric{<:Any,<:SparseMatrixCSC},Hermitian{<:Any,<:SparseMatrixCSC}}; linsolve::Symbol=:lsqr, kwargs...) - ScaleInvariantAnalysis._prepare_symcover_start!(a, S) + MatrixCovers._prepare_symcover_start!(a, S) anew, _ = _symcover_min_abslog2(S; start=a, linsolve, kwargs...) a .= anew return a end -function ScaleInvariantAnalysis.cover_min!(ϕ::AbsLog{2}, a::AbstractVector, b::AbstractVector, +function MatrixCovers.cover_min!(ϕ::AbsLog{2}, a::AbstractVector, b::AbstractVector, A::SparseMatrixCSC; linsolve::Symbol=:lsqr, kwargs...) - ScaleInvariantAnalysis._prepare_cover_start!(a, b, A) + MatrixCovers._prepare_cover_start!(a, b, A) anew, bnew, _ = _cover_min_abslog2(A; start=(a, b), linsolve, kwargs...) a .= anew b .= bnew return a, b end -end # module SIASparseArrays +end # module MatrixCoversSparseArraysExt diff --git a/ext/SIASparseArraysUnitful.jl b/ext/MatrixCoversSparseArraysUnitfulExt.jl similarity index 53% rename from ext/SIASparseArraysUnitful.jl rename to ext/MatrixCoversSparseArraysUnitfulExt.jl index 9706d0d..cda665e 100644 --- a/ext/SIASparseArraysUnitful.jl +++ b/ext/MatrixCoversSparseArraysUnitfulExt.jl @@ -1,17 +1,17 @@ -module SIASparseArraysUnitful +module MatrixCoversSparseArraysUnitfulExt -# `SIASparseArrays` types the matrix slot of its refiners, where `SIAUnitful` types +# `MatrixCoversSparseArraysExt` types the matrix slot of its refiners, where `MatrixCoversUnitfulExt` types # the element: neither is more specific for a sparse matrix of quantities, so the two # are ambiguous there. These methods resolve that pair. They are the only overlap -- # every other sparse method leaves its matrix slot untyped. using LinearAlgebra: LinearAlgebra, Hermitian, Symmetric -using ScaleInvariantAnalysis -using ScaleInvariantAnalysis: AbsLog +using MatrixCovers +using MatrixCovers: AbsLog using SparseArrays: SparseArrays, SparseMatrixCSC using Unitful: Quantity -const SIA = ScaleInvariantAnalysis +const MC = MatrixCovers # Sparse storage synthesizes structural zeros with `zero(eltype)`, so the element # type is concrete and every entry carries the same unit. @@ -20,16 +20,16 @@ const QSparseSym = Union{QSparse, Symmetric{<:Quantity,<:SparseMatrixCSC}, Hermitian{<:Quantity,<:SparseMatrixCSC}} -# `Unitful` triggers `SIAUnitful` as well as this extension, so it is loaded whenever +# `Unitful` triggers `MatrixCoversUnitfulExt` as well as this extension, so it is loaded whenever # these methods can be called. Reaching for it here rather than at load time leaves # the two extensions' load order free. -siaunitful() = Base.get_extension(ScaleInvariantAnalysis, :SIAUnitful)::Module +unitfulext() = Base.get_extension(MatrixCovers, :MatrixCoversUnitfulExt)::Module -SIA.symcover_min!(ϕ::AbsLog{2}, a::AbstractVector{<:Quantity}, A::QSparseSym; kwargs...) = - siaunitful().symstart!(SIA.symcover_min!, a, A, ϕ; kwargs...) +MC.symcover_min!(ϕ::AbsLog{2}, a::AbstractVector{<:Quantity}, A::QSparseSym; kwargs...) = + unitfulext().symstart!(MC.symcover_min!, a, A, ϕ; kwargs...) -SIA.cover_min!(ϕ::AbsLog{2}, a::AbstractVector{<:Quantity}, b::AbstractVector{<:Quantity}, +MC.cover_min!(ϕ::AbsLog{2}, a::AbstractVector{<:Quantity}, b::AbstractVector{<:Quantity}, A::QSparse; kwargs...) = - siaunitful().asymstart!(SIA.cover_min!, a, b, A, ϕ; kwargs...) + unitfulext().asymstart!(MC.cover_min!, a, b, A, ϕ; kwargs...) -end # module SIASparseArraysUnitful +end # module MatrixCoversSparseArraysUnitfulExt diff --git a/ext/SIAUnitful.jl b/ext/MatrixCoversUnitfulExt.jl similarity index 74% rename from ext/SIAUnitful.jl rename to ext/MatrixCoversUnitfulExt.jl index e5c20e3..f6d5ef6 100644 --- a/ext/SIAUnitful.jl +++ b/ext/MatrixCoversUnitfulExt.jl @@ -1,11 +1,11 @@ -module SIAUnitful +module MatrixCoversUnitfulExt using LinearAlgebra: LinearAlgebra -using ScaleInvariantAnalysis -using ScaleInvariantAnalysis: AbsLog, AbsLinear +using MatrixCovers +using MatrixCovers: AbsLog, AbsLinear using Unitful: Unitful, FreeUnits, Quantity, Unit, unit, ustrip -const SIA = ScaleInvariantAnalysis +const MC = MatrixCovers # Exponents of a unit, keyed by atomic unit. Rational, because a symmetric cover # halves the diagonal's exponents. @@ -244,7 +244,7 @@ function asymstart!(f, a::QVector, b::QVector, A::QMatrix, ϕ...; kwargs...) return a, b end -# Every penalty slot below mirrors ScaleInvariantAnalysis's own method table: where +# Every penalty slot below mirrors MatrixCovers's own method table: where # it leaves `ϕ` untyped these do too, and where it dispatches on concrete penalties # these enumerate the same ones. Each method is then strictly more specific than the # one it shadows -- including those in the JuMP and Ipopt extensions, which leave the @@ -253,57 +253,57 @@ end const PENALTIES = (:(AbsLog{1}), :(AbsLog{2}), :(AbsLinear{1}), :(AbsLinear{2})) # Heuristic covers and initializers: `ϕ` is untyped upstream and ignored. -SIA.symcover(A::QMatrix; kwargs...) = sym(SIA.symcover, A; kwargs...) -SIA.symcover(ϕ, A::QMatrix; kwargs...) = sym(SIA.symcover, A, ϕ; kwargs...) -SIA.symcover!(a::QVector, A::QMatrix; kwargs...) = sym!(SIA.symcover!, a, A; kwargs...) -SIA.symcover!(ϕ, a::QVector, A::QMatrix; kwargs...) = sym!(SIA.symcover!, a, A, ϕ; kwargs...) +MC.symcover(A::QMatrix; kwargs...) = sym(MC.symcover, A; kwargs...) +MC.symcover(ϕ, A::QMatrix; kwargs...) = sym(MC.symcover, A, ϕ; kwargs...) +MC.symcover!(a::QVector, A::QMatrix; kwargs...) = sym!(MC.symcover!, a, A; kwargs...) +MC.symcover!(ϕ, a::QVector, A::QMatrix; kwargs...) = sym!(MC.symcover!, a, A, ϕ; kwargs...) -SIA.cover(A::QMatrix; kwargs...) = asym(SIA.cover, A; kwargs...) -SIA.cover(ϕ, A::QMatrix; kwargs...) = asym(SIA.cover, A, ϕ; kwargs...) -SIA.cover!(a::QVector, b::QVector, A::QMatrix; kwargs...) = asym!(SIA.cover!, a, b, A; kwargs...) -SIA.cover!(ϕ, a::QVector, b::QVector, A::QMatrix; kwargs...) = asym!(SIA.cover!, a, b, A, ϕ; kwargs...) +MC.cover(A::QMatrix; kwargs...) = asym(MC.cover, A; kwargs...) +MC.cover(ϕ, A::QMatrix; kwargs...) = asym(MC.cover, A, ϕ; kwargs...) +MC.cover!(a::QVector, b::QVector, A::QMatrix; kwargs...) = asym!(MC.cover!, a, b, A; kwargs...) +MC.cover!(ϕ, a::QVector, b::QVector, A::QMatrix; kwargs...) = asym!(MC.cover!, a, b, A, ϕ; kwargs...) # `cover`/`cover!` dispatch on `Adjoint`/`Transpose` upstream without an eltype # bound, so a wrapped `Quantity` matrix needs these to stay unambiguous. for W in (:(LinearAlgebra.Adjoint{<:Quantity}), :(LinearAlgebra.Transpose{<:Quantity})) @eval begin - SIA.cover(A::$W; kwargs...) = asym(SIA.cover, A; kwargs...) - SIA.cover!(a::QVector, b::QVector, A::$W; kwargs...) = asym!(SIA.cover!, a, b, A; kwargs...) + MC.cover(A::$W; kwargs...) = asym(MC.cover, A; kwargs...) + MC.cover!(a::QVector, b::QVector, A::$W; kwargs...) = asym!(MC.cover!, a, b, A; kwargs...) end end -SIA.initialize_symcover(A::QMatrix; kwargs...) = sym(SIA.initialize_symcover, A; kwargs...) -SIA.initialize_symcover!(a::QVector, A::QMatrix; kwargs...) = sym!(SIA.initialize_symcover!, a, A; kwargs...) -SIA.initialize_cover(A::QMatrix; kwargs...) = asym(SIA.initialize_cover, A; kwargs...) -SIA.initialize_cover!(a::QVector, b::QVector, A::QMatrix; kwargs...) = asym!(SIA.initialize_cover!, a, b, A; kwargs...) +MC.initialize_symcover(A::QMatrix; kwargs...) = sym(MC.initialize_symcover, A; kwargs...) +MC.initialize_symcover!(a::QVector, A::QMatrix; kwargs...) = sym!(MC.initialize_symcover!, a, A; kwargs...) +MC.initialize_cover(A::QMatrix; kwargs...) = asym(MC.initialize_cover, A; kwargs...) +MC.initialize_cover!(a::QVector, b::QVector, A::QMatrix; kwargs...) = asym!(MC.initialize_cover!, a, b, A; kwargs...) # Soft covers and the `*_min` family: `ϕ` is dispatched on upstream. -SIA.soft_symcover(A::QMatrix; kwargs...) = sym(SIA.soft_symcover, A; kwargs...) -SIA.soft_cover(A::QMatrix; kwargs...) = asym(SIA.soft_cover, A; kwargs...) -SIA.symcover_min(A::QMatrix; kwargs...) = sym(SIA.symcover_min, A; kwargs...) -SIA.symcover_min!(a::QVector, A::QMatrix; kwargs...) = symstart!(SIA.symcover_min!, a, A; kwargs...) -SIA.cover_min(A::QMatrix; kwargs...) = asym(SIA.cover_min, A; kwargs...) -SIA.cover_min!(a::QVector, b::QVector, A::QMatrix; kwargs...) = asymstart!(SIA.cover_min!, a, b, A; kwargs...) -SIA.soft_symcover_min(A::QMatrix; kwargs...) = sym(SIA.soft_symcover_min, A; kwargs...) -SIA.soft_symcover_min!(a::QVector, A::QMatrix; kwargs...) = symstart!(SIA.soft_symcover_min!, a, A; kwargs...) -SIA.soft_cover_min(A::QMatrix; kwargs...) = asym(SIA.soft_cover_min, A; kwargs...) -SIA.soft_cover_min!(a::QVector, b::QVector, A::QMatrix; kwargs...) = asymstart!(SIA.soft_cover_min!, a, b, A; kwargs...) +MC.soft_symcover(A::QMatrix; kwargs...) = sym(MC.soft_symcover, A; kwargs...) +MC.soft_cover(A::QMatrix; kwargs...) = asym(MC.soft_cover, A; kwargs...) +MC.symcover_min(A::QMatrix; kwargs...) = sym(MC.symcover_min, A; kwargs...) +MC.symcover_min!(a::QVector, A::QMatrix; kwargs...) = symstart!(MC.symcover_min!, a, A; kwargs...) +MC.cover_min(A::QMatrix; kwargs...) = asym(MC.cover_min, A; kwargs...) +MC.cover_min!(a::QVector, b::QVector, A::QMatrix; kwargs...) = asymstart!(MC.cover_min!, a, b, A; kwargs...) +MC.soft_symcover_min(A::QMatrix; kwargs...) = sym(MC.soft_symcover_min, A; kwargs...) +MC.soft_symcover_min!(a::QVector, A::QMatrix; kwargs...) = symstart!(MC.soft_symcover_min!, a, A; kwargs...) +MC.soft_cover_min(A::QMatrix; kwargs...) = asym(MC.soft_cover_min, A; kwargs...) +MC.soft_cover_min!(a::QVector, b::QVector, A::QMatrix; kwargs...) = asymstart!(MC.soft_cover_min!, a, b, A; kwargs...) for P in PENALTIES @eval begin - SIA.soft_symcover(ϕ::$P, A::QMatrix; kwargs...) = sym(SIA.soft_symcover, A, ϕ; kwargs...) - SIA.soft_cover(ϕ::$P, A::QMatrix; kwargs...) = asym(SIA.soft_cover, A, ϕ; kwargs...) - - SIA.symcover_min(ϕ::$P, A::QMatrix; kwargs...) = sym(SIA.symcover_min, A, ϕ; kwargs...) - SIA.symcover_min!(ϕ::$P, a::QVector, A::QMatrix; kwargs...) = symstart!(SIA.symcover_min!, a, A, ϕ; kwargs...) - SIA.cover_min(ϕ::$P, A::QMatrix; kwargs...) = asym(SIA.cover_min, A, ϕ; kwargs...) - SIA.cover_min!(ϕ::$P, a::QVector, b::QVector, A::QMatrix; kwargs...) = asymstart!(SIA.cover_min!, a, b, A, ϕ; kwargs...) - - SIA.soft_symcover_min(ϕ::$P, A::QMatrix; kwargs...) = sym(SIA.soft_symcover_min, A, ϕ; kwargs...) - SIA.soft_symcover_min!(ϕ::$P, a::QVector, A::QMatrix; kwargs...) = symstart!(SIA.soft_symcover_min!, a, A, ϕ; kwargs...) - SIA.soft_cover_min(ϕ::$P, A::QMatrix; kwargs...) = asym(SIA.soft_cover_min, A, ϕ; kwargs...) - SIA.soft_cover_min!(ϕ::$P, a::QVector, b::QVector, A::QMatrix; kwargs...) = asymstart!(SIA.soft_cover_min!, a, b, A, ϕ; kwargs...) + MC.soft_symcover(ϕ::$P, A::QMatrix; kwargs...) = sym(MC.soft_symcover, A, ϕ; kwargs...) + MC.soft_cover(ϕ::$P, A::QMatrix; kwargs...) = asym(MC.soft_cover, A, ϕ; kwargs...) + + MC.symcover_min(ϕ::$P, A::QMatrix; kwargs...) = sym(MC.symcover_min, A, ϕ; kwargs...) + MC.symcover_min!(ϕ::$P, a::QVector, A::QMatrix; kwargs...) = symstart!(MC.symcover_min!, a, A, ϕ; kwargs...) + MC.cover_min(ϕ::$P, A::QMatrix; kwargs...) = asym(MC.cover_min, A, ϕ; kwargs...) + MC.cover_min!(ϕ::$P, a::QVector, b::QVector, A::QMatrix; kwargs...) = asymstart!(MC.cover_min!, a, b, A, ϕ; kwargs...) + + MC.soft_symcover_min(ϕ::$P, A::QMatrix; kwargs...) = sym(MC.soft_symcover_min, A, ϕ; kwargs...) + MC.soft_symcover_min!(ϕ::$P, a::QVector, A::QMatrix; kwargs...) = symstart!(MC.soft_symcover_min!, a, A, ϕ; kwargs...) + MC.soft_cover_min(ϕ::$P, A::QMatrix; kwargs...) = asym(MC.soft_cover_min, A, ϕ; kwargs...) + MC.soft_cover_min!(ϕ::$P, a::QVector, b::QVector, A::QMatrix; kwargs...) = asymstart!(MC.soft_cover_min!, a, b, A, ϕ; kwargs...) end end -end # module SIAUnitful +end # module MatrixCoversUnitfulExt diff --git a/src/ScaleInvariantAnalysis.jl b/src/MatrixCovers.jl similarity index 98% rename from src/ScaleInvariantAnalysis.jl rename to src/MatrixCovers.jl index c7396c7..de8a318 100644 --- a/src/ScaleInvariantAnalysis.jl +++ b/src/MatrixCovers.jl @@ -1,4 +1,4 @@ -module ScaleInvariantAnalysis +module MatrixCovers using LinearAlgebra: LinearAlgebra, Adjoint, Bidiagonal, Diagonal, SymTridiagonal, Symmetric, Transpose, Tridiagonal, dot, norm diff --git a/src/minimal_covers.jl b/src/minimal_covers.jl index 116bf8a..8f87b16 100644 --- a/src/minimal_covers.jl +++ b/src/minimal_covers.jl @@ -1,5 +1,5 @@ # Objective-minimal hard covers. The default AbsLog{2} penalty is solved natively -# here; the other penalties are provided by the SIAJuMP and SIAIpopt extensions, +# here; the other penalties are provided by the MatrixCoversJuMPExt and MatrixCoversIpoptExt extensions, # whose entry points are declared as stubs below. # ============================================================ @@ -194,7 +194,7 @@ end # basin its start lies in. The drivers below therefore refine every start on a menu and keep # the best, which is what makes the result of the non-mutating entry point a property of `A` # rather than of an initialization the caller never chose. The kernels they call — the -# `*_min!` refiners for the AbsLinear penalties — live in the SIAIpopt extension, but the +# `*_min!` refiners for the AbsLinear penalties — live in the MatrixCoversIpoptExt extension, but the # menu and the selection are native, so the two families need only one description. # # The winner is picked by `_multistart_select`, the same scale-covariant rule the soft-cover @@ -743,10 +743,10 @@ _soft_symcover_min_abslog2(A::AbstractMatrix; kwargs...) = _soft_cover_min_abslog2(A::AbstractMatrix; kwargs...) = _cover_min_abslog2(A; κs=(), boost=false, kwargs...) -# Internal exact reference implemented by the SIAJuMP extension; used only to +# Internal exact reference implemented by the MatrixCoversJuMPExt extension; used only to # cross-check the native `symcover_min(::AbsLog{2})` in the test suite. function symcover_min_jump end -# Internal exact reference implemented by the SIAJuMP extension; used only to +# Internal exact reference implemented by the MatrixCoversJuMPExt extension; used only to # cross-check the native `cover_min(::AbsLog{2})` in the test suite. function cover_min_jump end diff --git a/src/soft_covers.jl b/src/soft_covers.jl index 19b4540..92ab001 100644 --- a/src/soft_covers.jl +++ b/src/soft_covers.jl @@ -332,7 +332,7 @@ function soft_symcover_min(::AbsLog{2}, A::AbstractMatrix; kwargs...) end # Multistart driver for the non-convex soft AbsLinear covers, the unconstrained counterpart -# of `symcover_min(::AbsLinear)`. The kernels (`soft_symcover_min!`) live in SIAIpopt; the +# of `symcover_min(::AbsLinear)`. The kernels (`soft_symcover_min!`) live in MatrixCoversIpoptExt; the # menu and the selection are native. Starts are taken raw: a soft cover is under no # obligation to cover `A`. function soft_symcover_min(ϕ::AbsLinear, A::AbstractMatrix; strategies=SYMCOVER_MIN_STRATEGIES) @@ -450,7 +450,7 @@ function soft_cover_min(::AbsLog{2}, A::AbstractMatrix; kwargs...) end # Multistart driver for the non-convex asymmetric soft covers, the unconstrained -# counterpart of `cover_min(::AbsLinear)`. The kernels (`soft_cover_min!`) live in SIAIpopt. +# counterpart of `cover_min(::AbsLinear)`. The kernels (`soft_cover_min!`) live in MatrixCoversIpoptExt. function soft_cover_min(ϕ::AbsLinear, A::AbstractMatrix; strategies=COVER_MIN_STRATEGIES) isempty(strategies) && throw(ArgumentError("soft_cover_min: `strategies` must name at least one starting cover")) diff --git a/src/support.jl b/src/support.jl index b5e9cdf..b436346 100644 --- a/src/support.jl +++ b/src/support.jl @@ -24,7 +24,7 @@ walk `nzrange` instead of the full grid, are the model. To support a new matrix type, define - ScaleInvariantAnalysis.foreach_support(f, A::MyMatrix) + MatrixCovers.foreach_support(f, A::MyMatrix) which must call `f(i, j, abs(A[i, j]))` exactly once for each `(i, j)` with `abs(A[i, j]) != 0`, must not call `f` for any other entry (a stored zero is @@ -70,7 +70,7 @@ robustness, not a promise.) To support a new matrix type, define - ScaleInvariantAnalysis.foreach_support_sym(f, A::MyMatrix) + MatrixCovers.foreach_support_sym(f, A::MyMatrix) which must call `f(i, j, v)` exactly once for each pair `i <= j` with `v = abs(A[i, j]) != 0`, must not call `f` for zero pairs, and must return diff --git a/test/extensions.jl b/test/extensions.jl index 06a10e5..aaf5235 100644 --- a/test/extensions.jl +++ b/test/extensions.jl @@ -369,7 +369,7 @@ end # JuMP/HiGHS/Ipopt unloaded (this test file loads them itself, which # would otherwise mask the failure), so the hint should fire. script = """ - using ScaleInvariantAnalysis + using MatrixCovers A = [4.0 1.0; 1.0 4.0] try symcover_min(AbsLog{1}(), A) @@ -381,11 +381,11 @@ end @test occursin("loading JuMP", out) # The no-ϕ wrapper `soft_symcover_min(A)` exists in the base package, but the - # `AbsLinear{2}` method it forwards to lives in the SIAIpopt extension. The + # `AbsLinear{2}` method it forwards to lives in the MatrixCoversIpoptExt extension. The # MethodError raised (and hinted on) is for the inner call, so the hint must # still fire even though the outer, no-ϕ call is what the user wrote. script_noϕ = """ - using ScaleInvariantAnalysis + using MatrixCovers A = [4.0 1.0; 1.0 4.0] try soft_symcover_min(A) @@ -414,7 +414,7 @@ end # call reaches it directly (AbsLog{1}) or through the AbsLinear multistart driver, # whose MethodError is raised on the cover_min! kernel it calls. script_cover = """ - using ScaleInvariantAnalysis + using MatrixCovers A = [4.0 1.0; 1.0 4.0] for call in (() -> cover_min(AbsLog{1}(), A), () -> cover_min(AbsLinear{2}(), A)) try @@ -430,7 +430,7 @@ end # The refiners are themselves the extension's entry points, so a caller who reaches # for one directly must be advised of the load just as the drivers' callers are. script_bang = """ - using ScaleInvariantAnalysis + using MatrixCovers A = [4.0 1.0; 1.0 4.0] for call in (() -> symcover_min!(AbsLinear{2}(), [2.0, 2.0], A), () -> cover_min!(AbsLinear{2}(), [2.0, 2.0], [2.0, 2.0], A)) diff --git a/test/generator/buildmatrices.jl b/test/generator/buildmatrices.jl index 80a288e..e6db166 100644 --- a/test/generator/buildmatrices.jl +++ b/test/generator/buildmatrices.jl @@ -11,7 +11,7 @@ using Random using ProgressMeter # Reference AbsLog{2} minimal covers, the same formulation as `symcover_min_jump` and -# `cover_min_jump` in ext/SIAJuMP.jl, but built on ParametricOptInterface so a single +# `cover_min_jump` in ext/MatrixCoversJuMPExt.jl, but built on ParametricOptInterface so a single # model is re-solved across many candidate matrices. using JuMP: JuMP, @variable, @objective, @constraint, @expression, Parameter using HiGHS: HiGHS diff --git a/test/initializers.jl b/test/initializers.jl index d406c55..26fb4c1 100644 --- a/test/initializers.jl +++ b/test/initializers.jl @@ -73,7 +73,7 @@ ag, bg = initialize_cover(B; strategy=:geomean, feasible=:boost) a0, b0 = cover(B; maxiter=0) @test ag * bg' ≈ a0 * b0' - at, bt = ScaleInvariantAnalysis.tighten_cover!(copy(ag), copy(bg), B) + at, bt = MatrixCovers.tighten_cover!(copy(ag), copy(bg), B) ac, bc = cover(B) @test at * bt' ≈ ac * bc' end @@ -169,7 +169,7 @@ @testset "inflate_feasible! rejects a zero scale on a supported row" begin A = [2.0 1.0; 1.0 3.0] - @test_throws "positive scale on every supported row" ScaleInvariantAnalysis.inflate_feasible!([0.0, 1.0], A) - @test_throws "positive scale on every supported row/column" ScaleInvariantAnalysis.inflate_feasible!([0.0, 1.0], [1.0, 1.0], A) + @test_throws "positive scale on every supported row" MatrixCovers.inflate_feasible!([0.0, 1.0], A) + @test_throws "positive scale on every supported row/column" MatrixCovers.inflate_feasible!([0.0, 1.0], [1.0, 1.0], A) end end diff --git a/test/minimal_covers.jl b/test/minimal_covers.jl index 2b9f84c..d254b73 100644 --- a/test/minimal_covers.jl +++ b/test/minimal_covers.jl @@ -15,7 +15,7 @@ end for (_, A) in symmetric_matrices Af = Float64.(A) a = symcover_min(AbsLog{2}(), Af) - aj = ScaleInvariantAnalysis.symcover_min_jump(AbsLog{2}(), Af) + aj = MatrixCovers.symcover_min_jump(AbsLog{2}(), Af) @test iscover(a, Af; atol=1e-8) oj = cover_objective(AbsLog{2}(), aj, Af) o = cover_objective(AbsLog{2}(), a, Af) @@ -57,7 +57,7 @@ end a, b = cover_min(AbsLog{2}(), Af) @test iscover(a, b, Af; atol=1e-7) if k in idx_sub - aj, bj = ScaleInvariantAnalysis.cover_min_jump(AbsLog{2}(), Af) + aj, bj = MatrixCovers.cover_min_jump(AbsLog{2}(), Af) oj = cover_objective(AbsLog{2}(), aj, bj, Af) o = cover_objective(AbsLog{2}(), a, b, Af) @test o <= oj * (1 + 1e-6) + 1e-10 @@ -112,7 +112,7 @@ end for (_, A) in symmetric_matrices Af = Float64.(A) a = symcover_min(AbsLog{2}(), Af; linsolve=:lsqr) - aj = ScaleInvariantAnalysis.symcover_min_jump(AbsLog{2}(), Af) + aj = MatrixCovers.symcover_min_jump(AbsLog{2}(), Af) @test iscover(a, Af; atol=1e-8) @test cover_objective(AbsLog{2}(), a, Af) <= cover_objective(AbsLog{2}(), aj, Af) * (1 + 1e-6) + 1e-10 @@ -124,7 +124,7 @@ end Af = Float64.(A) a, b = cover_min(AbsLog{2}(), Af; linsolve=:lsqr) @test iscover(a, b, Af; atol=1e-7) - aj, bj = ScaleInvariantAnalysis.cover_min_jump(AbsLog{2}(), Af) + aj, bj = MatrixCovers.cover_min_jump(AbsLog{2}(), Af) @test cover_objective(AbsLog{2}(), a, b, Af) <= cover_objective(AbsLog{2}(), aj, bj, Af) * (1 + 1e-6) + 1e-10 end @@ -198,7 +198,7 @@ end A = [1.0 2.0 3.0; 6.0 5.0 4.0] a, b = soft_cover_min(AbsLog{2}(), A) a_ref, b_ref = similar(a), similar(b) - ScaleInvariantAnalysis.unconstrained_min!(AbsLog{2}(), a_ref, b_ref, A) + MatrixCovers.unconstrained_min!(AbsLog{2}(), a_ref, b_ref, A) @test a ≈ a_ref && b ≈ b_ref # It's the unconstrained minimum: any perturbation can only raise the objective. diff --git a/test/runtests.jl b/test/runtests.jl index 4390217..1e43759 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,8 +1,8 @@ -using ScaleInvariantAnalysis -using ScaleInvariantAnalysis: foreach_support, foreach_support_sym, unconstrained_min!, tighten_cover! -using JuMP, HiGHS, Ipopt # triggers SIAJuMP and SIAIpopt extensions -using SparseArrays # triggers SIASparseArrays extension -using Unitful # triggers SIAUnitful extension +using MatrixCovers +using MatrixCovers: foreach_support, foreach_support_sym, unconstrained_min!, tighten_cover! +using JuMP, HiGHS, Ipopt # triggers MatrixCoversJuMPExt and MatrixCoversIpoptExt extensions +using SparseArrays # triggers MatrixCoversSparseArraysExt extension +using Unitful # triggers MatrixCoversUnitfulExt extension using LinearAlgebra using OffsetArrays using Statistics: median @@ -14,7 +14,7 @@ using Test include("helpers.jl") # isbalanced, covaries, PENALTIES -@testset "ScaleInvariantAnalysis.jl" begin +@testset "MatrixCovers.jl" begin include("penalties.jl") # cover_objective include("support.jl") # foreach_support(_sym) traversal @@ -28,7 +28,7 @@ include("helpers.jl") # isbalanced, covaries, PENALTIES include("unitful.jl") # dimensional covers via the Unitful extension include("invariants.jl") # shared conventions checked across every notion - Aqua.test_all(ScaleInvariantAnalysis) + Aqua.test_all(MatrixCovers) @testset "ExplicitImports" begin # The public-ness checks consult `Base.ispublic` only on 1.11+; before that they @@ -49,7 +49,7 @@ include("helpers.jl") # isbalanced, covaries, PENALTIES # names, and `register_error_hint` is Base-internal. foreign = (:FreeUnits, :Unit, :Optimizer, :Experimental, :register_error_hint) test_explicit_imports( - ScaleInvariantAnalysis; + MatrixCovers; all_explicit_imports_are_public = VERSION >= v"1.11" ? (; ignore = (internals..., foreign...)) : false, all_qualified_accesses_are_public = VERSION >= v"1.11" ? @@ -59,11 +59,11 @@ include("helpers.jl") # isbalanced, covaries, PENALTIES # Aqua checks the package alone; the extensions need their own sweep. @testset "method ambiguities" begin - @test isempty(detect_ambiguities(ScaleInvariantAnalysis; recursive=true)) - for extname in (:SIASparseArrays, :SIAJuMP, :SIAIpopt, :SIAUnitful, :SIASparseArraysUnitful) - ext = Base.get_extension(ScaleInvariantAnalysis, extname) + @test isempty(detect_ambiguities(MatrixCovers; recursive=true)) + for extname in (:MatrixCoversSparseArraysExt, :MatrixCoversJuMPExt, :MatrixCoversIpoptExt, :MatrixCoversUnitfulExt, :MatrixCoversSparseArraysUnitfulExt) + ext = Base.get_extension(MatrixCovers, extname) @test ext !== nothing - @test isempty(detect_ambiguities(ScaleInvariantAnalysis, ext; recursive=true)) + @test isempty(detect_ambiguities(MatrixCovers, ext; recursive=true)) end end diff --git a/test/soft_covers.jl b/test/soft_covers.jl index 29cbaff..5916d4c 100644 --- a/test/soft_covers.jl +++ b/test/soft_covers.jl @@ -295,8 +295,8 @@ end # so the instrumented call reproduces the public entry point exactly (asserted below). function provenance(A; rng=StableRNG(0)) labels = String[]; objs = Float64[] - a = ScaleInvariantAnalysis._soft_symcover_abslinear2(A, 32, 5, 2.0, rng; labels, objs) - return a, labels[ScaleInvariantAnalysis._multistart_select(objs)], labels, objs + a = MatrixCovers._soft_symcover_abslinear2(A, 32, 5, 2.0, rng; labels, objs) + return a, labels[MatrixCovers._multistart_select(objs)], labels, objs end # The greedy feasible cover (`init_feasible_diag!`) is offered as a start only when `A` has a @@ -329,10 +329,10 @@ end # As above, the positional arguments mirror `soft_cover`'s defaults. Ag = [1.0 2.0 3.0; 6.0 5.0 4.0] albs = String[]; aobjs = Float64[] - ab = ScaleInvariantAnalysis._soft_cover_abslinear2(Ag, 200, 4, 2.0, StableRNG(0); + ab = MatrixCovers._soft_cover_abslinear2(Ag, 200, 4, 2.0, StableRNG(0); labels=albs, objs=aobjs) @test ab == soft_cover(Ag; rng=StableRNG(0)) - @test albs[ScaleInvariantAnalysis._multistart_select(aobjs)] in albs + @test albs[MatrixCovers._multistart_select(aobjs)] in albs @test "feasible" ∉ albs end diff --git a/test/unitful.jl b/test/unitful.jl index b4165bc..6e6907f 100644 --- a/test/unitful.jl +++ b/test/unitful.jl @@ -100,7 +100,7 @@ @test all(unit.(v) == fill(u"m^-1", 3) for v in cover(S)) @test unit.(symcover(Symmetric(S))) == fill(u"m^-1", 3) - # The refiners are where SIASparseArrays and SIAUnitful overlap. + # The refiners are where MatrixCoversSparseArraysExt and MatrixCoversUnitfulExt overlap. a = initialize_symcover(S) @test unit.(symcover_min!(AbsLog{2}(), a, S)) == fill(u"m^-1", 3) @test unit.(symcover_min!(AbsLog{2}(), a, Symmetric(S))) == fill(u"m^-1", 3)