Skip to content
Merged
1 change: 0 additions & 1 deletion .github/workflows/TagBot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ on:
jobs:
TagBot:
if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
# ubuntu-slim doesn't support containers, and thus won't work
runs-on: ubuntu-latest
steps:
- uses: JuliaRegistries/TagBot@v1.25.11
Expand Down
27 changes: 27 additions & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Benchmark this PR

on:
pull_request:
branches: [main]

permissions:
pull-requests: write
contents: read

jobs:
benchmark:
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
version: ["1"]
env:
# Force consistent Julia depot path for self-hosted runners
JULIA_DEPOT_PATH: ~/.julia
steps:
- uses: MilesCranmer/AirspeedVelocity.jl@action-v1
with:
julia-version: ${{ matrix.version }}
script: "benchmark/benchmarks.jl"
job-summary: true
35 changes: 35 additions & 0 deletions benchmark/benchmarks.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using BenchmarkTools
using Random, LinearAlgebra
using NearestCorrelationMatrix
using NearestCorrelationMatrix.Internals: symmetrize!

Random.seed!(0x00c0ffee)

const SUITE = BenchmarkGroup()

function rand_negdef(n::Int)
while true
A = 2.0 * rand(Float64, n, n) .- 1.0
symmetrize!(A)
A[diagind(A)] .= 1.0
!isposdef(A) && return A
end
return zeros(Float64, 0, 0)
end

function create_benchmarkable(n, alg; evals, samples, seconds)
return @benchmarkable nearest_cor(A, $alg) evals = evals samples = samples seconds = seconds setup = (A = rand_negdef($n))
end

function create_benchmark_group(n::Int; evals = 5, samples = 100, seconds = 60)
grp = BenchmarkGroup()
grp["Nw"] = create_benchmarkable(n, Newton; evals, samples, seconds)
grp["AP"] = create_benchmarkable(n, AlternatingProjections; evals, samples, seconds)
grp["AA"] = create_benchmarkable(n, AcceleratedAP; evals, samples, seconds)
grp["Di"] = create_benchmarkable(n, DirectProjection; evals, samples, seconds)
return grp
end

SUITE["n=10"] = create_benchmark_group(10; samples = 30)
SUITE["n=100"] = create_benchmark_group(100; samples = 30)
SUITE["n=1000"] = create_benchmark_group(1000; samples = 30, seconds = 180)
1 change: 1 addition & 0 deletions src/internals/Internals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ using LinearAlgebra
include("common.jl")
include("projections.jl")
include("checks.jl")
include("gen_cor.jl")

include("newton_internals.jl")

Expand Down
51 changes: 51 additions & 0 deletions src/internals/gen_cor.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
export default_negdef, rand_negdef

"""
default_negdef([T=Float64]; include_mask=false)

Returns a 4×4 invalid correlation matrix and an optional mask.
"""
function default_negdef(::Type{T}; include_mask::Bool = false) where {T <: AbstractFloat}
# !WARNING! The data in this generator must never be edited to ensure consistency with
# future releases.

A = Float64[
1.0 -0.2188 -0.79 0.7773
-0.2188 1.0 0.2559 -0.5977
-0.79 0.2559 1.0 0.2266
0.7773 -0.5977 0.2266 1.0
]

mask = [
1 1 0 0
1 1 0 0
0 0 1 1
0 0 1 1
]

X = convert(AbstractMatrix{T}, A)
m = convert(BitMatrix, mask)

return include_mask ? (X, m) : X
end

default_negdef(; include_mask::Bool = false) = default_negdef(Float64; include_mask)

"""
rand_negdef([T=Float64], n; max_attempts=100)

Generates a random pseudo-correlation matrix that is negative definite.
"""
function rand_negdef(::Type{T}, n::Int; max_attempts::Int = 100) where {T <: AbstractFloat}
attempts = 0
while attempts < max_attempts
r = 2 * rand(T, n, n) .- one(T)
symmetrize!(r)
r[diagind(r)] .= one(T)
!isposdef(r) && return r
attempts += 1
end
return Matrix{T}(undef, 0, 0)
end

rand_negdef(n::Int; max_attempts::Int = 100) = rand_negdef(Float64, n; max_attempts)
53 changes: 17 additions & 36 deletions test/Datasets.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ using LinearAlgebra
import MAT

export
default_negdef,
bccd16,
beyu11,
bhwi01,
Expand Down Expand Up @@ -48,24 +47,6 @@ function vec_to_mat(x::AbstractVector{T}; diag_val::Real = 1) where {T}
return A
end

"""
default_negdef(T)

Gets a 4×4 invalid correlation matrix for testing.
"""
function default_negdef(::Type{T}) where {T}
r = [
1.0 -0.2188 -0.79 0.7773
-0.2188 1.0 0.2559 -0.5977
-0.79 0.2559 1.0 0.2266
0.7773 -0.5977 0.2266 1.0
]

return convert(Matrix{T}, r)
end

default_negdef() = default_negdef(Float64)

"""
BCCD16 is a 3250×3250 invalid correlation matrix constructed from data for banks in 27 EU member states.

Expand Down Expand Up @@ -146,13 +127,13 @@ Source:
"""
function fing97()
A = Float64[
1.0 0.18 -0.13 -0.26 0.19 -0.25 -0.12
0.18 1.0 0.22 -0.14 0.31 0.16 0.09
-0.13 0.22 1.0 0.06 -0.08 0.04 0.04
-0.26 -0.14 0.06 1.0 0.85 0.85 0.85
0.19 0.31 -0.08 0.85 1.0 0.85 0.85
-0.25 0.16 0.04 0.85 0.85 1.0 0.85
-0.12 0.09 0.04 0.85 0.85 0.85 1.0
1.0 0.18 -0.13 -0.26 0.19 -0.25 -0.12
0.18 1.0 0.22 -0.14 0.31 0.16 0.09
-0.13 0.22 1.0 0.06 -0.08 0.04 0.04
-0.26 -0.14 0.06 1.0 0.85 0.85 0.85
0.19 0.31 -0.08 0.85 1.0 0.85 0.85
-0.25 0.16 0.04 0.85 0.85 1.0 0.85
-0.12 0.09 0.04 0.85 0.85 0.85 1.0
]

mask = [trues(3, 3) falses(3, 4); falses(4, 3) I(4)]
Expand Down Expand Up @@ -182,12 +163,12 @@ St. Petersburg, Russia, 2013. Revised June 2014.
"""
function mmb13()
A = Float64[
0.010712 0.000654 0.002391 0.010059 -0.008321 0.001738
0.000654 0.000004 0.002917 0.00065 0.002263 0.002913
0.002391 0.002917 0.013225 -0.000525 0.010834 0.010309
0.010059 0.00065 -0.000525 0.009409 -0.010584 -0.001175
-0.008321 0.002263 0.010834 -0.010584 0.019155 0.008571
0.001738 0.002913 0.010309 -0.001175 0.008571 0.007396
0.010712 0.000654 0.002391 0.010059 -0.008321 0.001738
0.000654 0.000004 0.002917 0.00065 0.002263 0.002913
0.002391 0.002917 0.013225 -0.000525 0.010834 0.010309
0.010059 0.00065 -0.000525 0.009409 -0.010584 -0.001175
-0.008321 0.002263 0.010834 -0.010584 0.019155 0.008571
0.001738 0.002913 0.010309 -0.001175 0.008571 0.007396
]

d = sqrt.(diag(A))
Expand All @@ -206,10 +187,10 @@ stress testing for value-at-risk. Journal of Risk, 5(4):75-89, 2003
"""
function tec03()
return Float64[
1 -0.55 -0.15 -0.1
-0.55 1 0.9 0.9
-0.15 0.9 1 0.9
-0.1 0.9 0.9 1
1 -0.55 -0.15 -0.1
-0.55 1 0.9 0.9
-0.15 0.9 1 0.9
-0.1 0.9 0.9 1
]
end

Expand Down
10 changes: 4 additions & 6 deletions test/test_api.jl
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
using Test
using InteractiveUtils
using NearestCorrelationMatrix

include("Datasets.jl")
using .Datasets
using NearestCorrelationMatrix.Internals: default_negdef

include("CustomTestMacros.jl")
using .CustomTestMacros

internal_algtypes = setdiff(subtypes(NCMAlgorithm), (JuMPAlgorithm,))

r0 = default_negdef(Float64)
A = default_negdef()

# variants of NCMProblem
@test_isdefined NCMProblem
@test_isimplemented NCMProblem(r0)
prob = NCMProblem(r0)
@test_isimplemented NCMProblem(A)
prob = NCMProblem(A)

# variations of init
@test_isdefined init
Expand Down
16 changes: 7 additions & 9 deletions test/test_convergence.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,18 @@ using Test
using InteractiveUtils
using LinearAlgebra
using NearestCorrelationMatrix
using NearestCorrelationMatrix.Internals: default_negdef
import NearestCorrelationMatrix as NCM

include("Datasets.jl")
using .Datasets

include("CustomTestMacros.jl")
using .CustomTestMacros

internal_algtypes = setdiff(subtypes(NCMAlgorithm), (JuMPAlgorithm,))

function test_simple(algtype)
return @testset "$(NCM.alg_name(algtype))" begin
r0 = default_negdef(Float64)
prob = NCMProblem(r0)
A = default_negdef()
prob = NCMProblem(A)
alg = autotune(algtype, prob)
cache = init(prob, alg)
sol = solve!(cache)
Expand All @@ -26,14 +24,14 @@ function test_simple(algtype)
@test sol.X isa Symmetric

# Handle Symmetric type matrices
r0 = default_negdef(Float64)
prob = NCMProblem(Symmetric(r0))
A = default_negdef()
prob = NCMProblem(Symmetric(A))
alg = autotune(algtype, prob)
@test_nothrow solve(prob, alg)

# Handle Float16 input matrices
r0 = default_negdef(Float16)
prob = NCMProblem(r0)
A = default_negdef(Float16)
prob = NCMProblem(A)
alg = autotune(algtype, prob)
if NCM.supports_float16(alg)
@test_nothrow solve(prob, alg)
Expand Down
8 changes: 3 additions & 5 deletions test/test_jump.jl
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
using Test
using NearestCorrelationMatrix
using NearestCorrelationMatrix.Internals: default_negdef
using JuMP, COSMO

include("Datasets.jl")
using .Datasets

include("CustomTestMacros.jl")
using .CustomTestMacros

r0 = default_negdef(Float64)
prob = NCMProblem(r0)
A = default_negdef()
prob = NCMProblem(A)

@test_isdefined JuMPAlgorithm
@test_isimplemented JuMPAlgorithm(COSMO.Optimizer)
Expand Down
7 changes: 2 additions & 5 deletions test/test_masking.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ using Test
using LinearAlgebra
using InteractiveUtils
using NearestCorrelationMatrix
using NearestCorrelationMatrix.Internals: default_negdef
import NearestCorrelationMatrix as NCM

include("Datasets.jl")
using .Datasets

include("CustomTestMacros.jl")
using .CustomTestMacros

Expand All @@ -15,8 +13,7 @@ supported_types = (Float64, Float32, Float16)

for algtype in masking_algs, T in supported_types
@testset "$(NCM.alg_name(algtype)) - $T" begin
A, m = usgs13()
A = convert(Matrix{T}, A)
A, m = default_negdef(T; include_mask = true)
X = copy(A)
prob = NCMProblem(X; mask = m)
alg = autotune(algtype, prob)
Expand Down
25 changes: 11 additions & 14 deletions test/test_robustness.jl
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
function rand_negdef(::Type{T}, n) where {T}
while true
r = 2 * rand(T, n, n) .- one(T)
symmetrize!(r)
r[diagind(r)] .= one(T)

!isposdef(r) && return r
end
return
end
using Test
using LinearAlgebra
using NearestCorrelationMatrix
using NearestCorrelationMatrix.Internals: rand_negdef
using JuMP, COSMO

include("CustomTestMacros.jl")
using .CustomTestMacros

function test_robust_reps(algtype::Type, nreps, size, T, test_pd; kwargs...)
return @testset "$(size)×$(size)" begin
for _ in 1:nreps
r0 = rand_negdef(T, size)
prob = NCMProblem(r0)
A = rand_negdef(T, size)
prob = NCMProblem(A)
alg = autotune(algtype, prob)
cache = init(prob, alg; kwargs...)
sol = solve!(cache)
Expand All @@ -31,8 +28,8 @@ end
function test_robust_reps(alg::NCMAlgorithm, nreps, size, T, test_pd; kwargs...)
return @testset "$(size)×$(size)" begin
for _ in 1:nreps
r0 = rand_negdef(T, size)
prob = NCMProblem(r0)
A = rand_negdef(T, size)
prob = NCMProblem(A)
cache = init(prob, alg; kwargs...)
sol = solve!(cache)

Expand Down
Loading
Loading