diff --git a/docs/Project.toml b/docs/Project.toml index 1814eb33..639f6766 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,5 +1,7 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244" [compat] Documenter = "1" +DocumenterCitations = "1.3" diff --git a/docs/make.jl b/docs/make.jl index 631b3226..e8f1c3bd 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,4 +1,6 @@ -using Documenter, ReachabilityBase +using Documenter, ReachabilityBase, DocumenterCitations + +bib = CitationBibliography(joinpath(@__DIR__, "src", "refs.bib"); style=:alpha) DocMeta.setdocmeta!(ReachabilityBase, :DocTestSetup, :(using ReachabilityBase); recursive=true) @@ -12,6 +14,7 @@ makedocs(; sitename="ReachabilityBase.jl", ReachabilityBase.Distribution, ReachabilityBase.Subtypes, ReachabilityBase.Arrays, ReachabilityBase.Timing], pagesonly=true, + plugins=[bib], pages=["Home" => "index.md", "Library" => Any[ # @@ -28,6 +31,7 @@ makedocs(; sitename="ReachabilityBase.jl", "Basetype" => "lib/Basetype.md" # ], + "Bibliography" => "bibliography.md", "About" => "about.md"]) deploydocs(; repo="github.com/JuliaReach/ReachabilityBase.jl.git", diff --git a/docs/src/bibliography.md b/docs/src/bibliography.md new file mode 100644 index 00000000..a987675a --- /dev/null +++ b/docs/src/bibliography.md @@ -0,0 +1,4 @@ +# Bibliography + +```@bibliography +``` diff --git a/docs/src/lib/Arrays.md b/docs/src/lib/Arrays.md index 2dbd7a0a..34cf6e80 100644 --- a/docs/src/lib/Arrays.md +++ b/docs/src/lib/Arrays.md @@ -29,6 +29,7 @@ ismultiple ispermutation issquare iswellconditioned +logarithmic_norm matrix_type nonzero_columns nonzero_indices diff --git a/docs/src/refs.bib b/docs/src/refs.bib new file mode 100644 index 00000000..7152b218 --- /dev/null +++ b/docs/src/refs.bib @@ -0,0 +1,6 @@ +@book{DesoerV09, + title={Feedback Systems: Input-Output Properties}, + author={Desoer, Charles A. and Vidyasagar, Mathukumalli}, + year={2009}, + publisher={SIAM} +} diff --git a/src/Arrays/Arrays.jl b/src/Arrays/Arrays.jl index ff28931e..f167675c 100644 --- a/src/Arrays/Arrays.jl +++ b/src/Arrays/Arrays.jl @@ -6,7 +6,7 @@ This module provides machinery for vectors and matrices. module Arrays using Base: \, /, transpose -using LinearAlgebra: Diagonal, cond, det, diag, eigvals, norm, qr +using LinearAlgebra: Diagonal, Hermitian, cond, det, diag, eigmax, norm, qr using Random: AbstractRNG, GLOBAL_RNG using Requires: @require using SparseArrays: AbstractSparseArray, AbstractSparseMatrix, diff --git a/src/Arrays/logarithmic_norm.jl b/src/Arrays/logarithmic_norm.jl index fa826ff2..b8469407 100644 --- a/src/Arrays/logarithmic_norm.jl +++ b/src/Arrays/logarithmic_norm.jl @@ -1,27 +1,38 @@ -# ======================================================================= -# Logarithmic norms of matrices (a.k.a. matrix measures) -# -# Reference: -# -# C. Desoer and M. Vidyasagar, Feedback Systems: Input-Output Properties. -# Society for Industrial and Applied Mathematics, 2009. -# ======================================================================= - -# logarithmic norm (also known as matrix measure) for commonly used p-norms +""" + logarithmic_norm(A::AbstractMatrix, p::Real=Inf) + +Compute the logarithmic norm (also known as matrix measure) for commonly used p-norms. + +### Input + +- `A` -- matrix +- `p` -- (optional, default: `Inf`) p-norm + +### Output + +A number representing the logarithmic norm. + +### Notes + +The implementation currently supports the following values for `p`: `1`, `2`, `Inf`. + +See [DesoerV09](@citet) for a reference. +""" function logarithmic_norm(A::AbstractMatrix, p::Real=Inf) if p == Inf - return logarithmic_norm_inf(A) + return _logarithmic_norm_inf(A) elseif p == 1 - return logarithmic_norm_1(A) + return _logarithmic_norm_1(A) elseif p == 2 - return logarithmic_norm_2(A) + return _logarithmic_norm_2(A) else - throw(ArgumentError("logarithmic norm only implemented for p = 1, 2, or Inf, got p = $p")) + throw(ArgumentError("`logarithmic_norm` is only implemented for p = 1, 2, or Inf, " * + "but got p = $p")) end end # max_j a_jj + ∑_{i ≠ j} |a_ij| -function logarithmic_norm_1(A::AbstractMatrix{N}) where {N} +function _logarithmic_norm_1(A::AbstractMatrix) out = -Inf @inbounds for j in axes(A, 2) α = A[j, j] @@ -38,7 +49,7 @@ function logarithmic_norm_1(A::AbstractMatrix{N}) where {N} end # max_i a_ii + ∑_{j ≠ i} |a_ij| -function logarithmic_norm_inf(A::AbstractMatrix{N}) where {N} +function _logarithmic_norm_inf(A::AbstractMatrix) out = -Inf @inbounds for i in axes(A, 1) α = A[i, i] @@ -54,9 +65,9 @@ function logarithmic_norm_inf(A::AbstractMatrix{N}) where {N} return out end -# max_j 1/2 * λⱼ(A + A^T) -function logarithmic_norm_2(A::AbstractMatrix) - B = A + A' - λ = eigvals(B) - return maximum(λ) / 2 +# max_j 1/2 * λⱼ(A + Aᵀ) +function _logarithmic_norm_2(A::AbstractMatrix) + B = Hermitian(A + A') # type information that all eigenvalues are real + λ = eigmax(B) + return λ / 2 end diff --git a/test/Arrays/logarithmic_norm.jl b/test/Arrays/logarithmic_norm.jl index 55e7baa3..d0355f31 100644 --- a/test/Arrays/logarithmic_norm.jl +++ b/test/Arrays/logarithmic_norm.jl @@ -1,23 +1,26 @@ -using ReachabilityBase.Arrays +using ReachabilityBase.Arrays, Test +using ReachabilityBase.Comparison: isidentical -@testset "Logarithmic norm" begin - # Example 1 - A = [1 2; 3 -4] - - @test logarithmic_norm(A, 1) == 4 - @test logarithmic_norm(A, Inf) == 3 - - # Example 2 (see Example 2 in "Explicit Error Bounds for Carleman Linearization. - # Marcelo Forets and Amaury Pouly (2018)"). - A = [0 1; -1 -2] +for N in [Float64, Float32, Rational{Int}] + A = N[1 2; 3 -4] + @test_throws ArgumentError logarithmic_norm(A, 3) + @test isidentical(logarithmic_norm(A), N(3)) # default norm is `Inf` + @test isidentical(logarithmic_norm(A, Inf), N(3)) + @test isidentical(logarithmic_norm(A, 1), N(4)) + # see Example 2 in: + # "Marcelo Forets and Amaury Pouly: Explicit Error Bounds for Carleman Linearization (2018)" + A = N[0 1; -1 -2] Aop2 = kron(A, A) + @test isidentical(logarithmic_norm(A, Inf), N(1)) + @test isidentical(logarithmic_norm(Aop2, Inf), N(9)) + @test isidentical(logarithmic_norm(A, 1), N(1)) + @test isidentical(logarithmic_norm(Aop2, 1), N(9)) +end - @test logarithmic_norm(A, Inf) == 1 - @test logarithmic_norm(Aop2, Inf) == 9 - - @test logarithmic_norm(A, 1) == 1 - @test logarithmic_norm(Aop2, 1) == 9 - - @test logarithmic_norm(A, 2) == 0 - @test logarithmic_norm(Aop2, 2) ≈ 4.23606797749979 +for N in [Float64, Float32] + A = N[0 1; -1 -2] # see above + Aop2 = kron(A, A) + @test isidentical(logarithmic_norm(A, 2), N(0)) + res = logarithmic_norm(Aop2, 2) + @test res isa N && res ≈ 4.23606797749979 end diff --git a/test/runtests.jl b/test/runtests.jl index 1ff0c66a..c870b8e6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -11,9 +11,6 @@ seed!(1234) # fix RNG seed for reproducibility @testset "arrays" begin include("Arrays/arrays.jl") end - @testset "logarithmic_norm" begin - include("Arrays/logarithmic_norm.jl") - end @testset "matrix_operations" begin include("Arrays/matrix_operations.jl") end @@ -26,6 +23,9 @@ seed!(1234) # fix RNG seed for reproducibility @testset "SingleEntryVector" begin include("Arrays/SingleEntryVector.jl") end + @testset "logarithmic_norm" begin + include("Arrays/logarithmic_norm.jl") + end end @testset "Comparison" begin include("Comparison/comparison.jl")