From fa6a9dbcaa87c937b482c5c5496103e693f3a23e Mon Sep 17 00:00:00 2001 From: Hamid Arian Date: Fri, 19 Jun 2026 23:28:11 -0400 Subject: [PATCH] fix(optimization): symmetrize hrp distance matrix; bump v0.5.1 --- CHANGELOG.md | 11 +++++++++++ Project.toml | 2 +- src/Optimization/HierarchicalRiskParity.jl | 1 + test/runtests.jl | 12 ++++++++++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da504f1..0a4481f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to `RiskLabAI.jl` are documented here. The format is based o [Semantic Versioning](https://semver.org/spec/v2.0.0.html) (pre-1.0: minor versions may include breaking changes). +## [0.5.1] — 2026-06-19 + +### Fixed + +- `Optimization.hrp` now symmetrises the correlation-distance matrix before + single-linkage clustering, so it accepts correlation matrices with the tiny + floating-point asymmetry that real sample covariances always have (previously + `Clustering.hclust` threw `ArgumentError: Distance matrix should be symmetric`). + Added a regression test covering an asymmetric correlation input. + ## [0.5.0] — 2026-06-19 First substantive release. The package was reconstructed from the ground up to @@ -67,5 +77,6 @@ machine-learning pieces are validated structurally. The parity ledger lives in - Initial package skeleton. +[0.5.1]: https://github.com/RiskLabAI/RiskLabAI.jl/releases/tag/v0.5.1 [0.5.0]: https://github.com/RiskLabAI/RiskLabAI.jl/releases/tag/v0.5.0 [0.0.1]: https://github.com/RiskLabAI/RiskLabAI.jl/releases/tag/v0.0.1 diff --git a/Project.toml b/Project.toml index 3f0d6b1..5421bd8 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "RiskLabAI" uuid = "a72881da-fdaa-49c1-8962-99caf4ccfee8" -version = "0.5.0" +version = "0.5.1" authors = ["RiskLab AI "] [deps] diff --git a/src/Optimization/HierarchicalRiskParity.jl b/src/Optimization/HierarchicalRiskParity.jl index 4fd0f3c..753dd27 100644 --- a/src/Optimization/HierarchicalRiskParity.jl +++ b/src/Optimization/HierarchicalRiskParity.jl @@ -131,6 +131,7 @@ function hrp( correlation::AbstractMatrix{<:Real}, ) distance = distance_corr(correlation) + distance = (distance .+ distance') ./ 2 # enforce exact symmetry for hclust order = hclust(distance; linkage = :single).order bisection = recursive_bisection(covariance, order) weights = zeros(Float64, length(order)) diff --git a/test/runtests.jl b/test/runtests.jl index e27d952..7ab7273 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -940,6 +940,18 @@ end @test length(w_hrp) == 4 @test sum(w_hrp) ≈ 1.0 @test all(w_hrp .> 0) + + # Regression: hrp() must accept a correlation with floating-point asymmetry + # (real sample covariances are not bit-exactly symmetric → hclust would throw + # "Distance matrix should be symmetric" without the symmetrisation guard). + g = randn(MersenneTwister(7), 6, 6) + psd = g * g' + sym_corr = RiskLabAI.Cluster.covariance_to_correlation(psd) + asym_corr = copy(sym_corr) + asym_corr[1, 2] += 1e-12 # break exact symmetry, as FP does + w_sample = O.hrp(psd, asym_corr) + @test length(w_sample) == 6 + @test sum(w_sample) ≈ 1.0 end @testset "Validation — cross-validators (parity with Python)" begin