Skip to content

Commit febc192

Browse files
authored
Merge pull request #72 from JuliaGaussianProcesses/positive-semidefinite
Add epsilon to `PositiveDefinite`
2 parents 902fbe2 + 0c5a728 commit febc192

File tree

3 files changed

+29
-20
lines changed

3 files changed

+29
-20
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ParameterHandling"
22
uuid = "2412ca09-6db7-441c-8e3a-88d5709968c5"
33
authors = ["Invenia Technical Computing Corporation"]
4-
version = "0.4.10"
4+
version = "0.5.0"
55

66
[deps]
77
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"

src/parameters_matrix.jl

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,31 +39,36 @@ function flatten(::Type{T}, X::Orthogonal) where {T<:Real}
3939
end
4040

4141
"""
42-
positive_definite(X::AbstractMatrix{<:Real})
42+
positive_definite(X::AbstractMatrix{<:Real}, ε = eps(T))
4343
44-
Produce a parameter whose `value` is constrained to be a positive-definite matrix. The argument `X` needs to
45-
be a positive-definite matrix (see https://en.wikipedia.org/wiki/Definite_matrix).
44+
Produce a parameter whose `value` is constrained to be a strictly positive-definite
45+
matrix. The argument `X` minus `ε` times the identity needs to be a positive-definite matrix
46+
(see https://en.wikipedia.org/wiki/Definite_matrix). The optional second argument `ε` must
47+
be a positive real number.
4648
4749
The unconstrained parameter is a `LowerTriangular` matrix, stored as a vector.
4850
"""
49-
function positive_definite(X::AbstractMatrix{<:Real})
50-
isposdef(X) || throw(ArgumentError("X is not positive-definite"))
51-
return PositiveDefinite(tril_to_vec(cholesky(X).L))
51+
function positive_definite(X::AbstractMatrix{T}, ε=eps(T)) where {T<:Real}
52+
ε > 0 || throw(ArgumentError("ε must be positive."))
53+
_X = X - ε * I
54+
isposdef(_X) || throw(ArgumentError("X-ε*I is not positive-definite for ε="))
55+
return PositiveDefinite(tril_to_vec(cholesky(_X).L), ε)
5256
end
5357

54-
struct PositiveDefinite{TL<:AbstractVector{<:Real}} <: AbstractParameter
58+
struct PositiveDefinite{TL<:AbstractVector{<:Real},Tε<:Real} <: AbstractParameter
5559
L::TL
60+
ε::Tε
5661
end
5762

58-
Base.:(==)(X::PositiveDefinite, Y::PositiveDefinite) = X.L == Y.L
59-
6063
A_At(X) = X * X'
6164

62-
value(X::PositiveDefinite) = A_At(vec_to_tril(X.L))
65+
Base.:(==)(X::PositiveDefinite, Y::PositiveDefinite) = X.L == Y.L && X.ε == Y.ε
66+
67+
value(X::PositiveDefinite) = A_At(vec_to_tril(X.L)) + X.ε * I
6368

6469
function flatten(::Type{T}, X::PositiveDefinite) where {T<:Real}
6570
v, unflatten_v = flatten(T, X.L)
66-
unflatten_PositiveDefinite(v_new::Vector{T}) = PositiveDefinite(unflatten_v(v_new))
71+
unflatten_PositiveDefinite(v_new::Vector{T}) = PositiveDefinite(unflatten_v(v_new), X.ε)
6772
return v, unflatten_PositiveDefinite
6873
end
6974

test/parameters_matrix.jl

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,21 @@ using ParameterHandling: vec_to_tril, tril_to_vec
3232
end
3333
X_mat = ParameterHandling.A_At(rand(3, 3)) # Create a positive definite object
3434
X = positive_definite(X_mat)
35-
@test X == X
3635
@test value(X) X_mat
3736
@test isposdef(value(X))
3837
@test vec_to_tril(X.L) cholesky(X_mat).L
39-
@test_throws ArgumentError positive_definite(rand(3, 3))
38+
39+
# Zeroing the unconstrained value preserve positivity
40+
X.L .= 0
41+
@test isposdef(value(X))
42+
43+
# Check that zeroing the unconstrained value does not affect the original array
44+
@test X != positive_definite(X_mat)
45+
46+
@test positive_definite(X_mat, 1e-5) != positive_definite(X_mat, 1e-6)
47+
@test_throws ArgumentError positive_definite(zeros(3, 3))
48+
@test_throws ArgumentError positive_definite(X_mat, 0.0)
49+
4050
test_parameter_interface(X)
4151

4252
x, re = flatten(X)
@@ -46,12 +56,6 @@ using ParameterHandling: vec_to_tril, tril_to_vec
4656
return logdet(value(X))
4757
end,
4858
)
49-
ΔL = first(
50-
Zygote.gradient(vec_to_tril(X.L)) do L
51-
return logdet(L * L')
52-
end,
53-
)
54-
@test vec_to_tril(Δl) == tril(ΔL)
5559
ChainRulesTestUtils.test_rrule(vec_to_tril, x)
5660
end
5761
end

0 commit comments

Comments
 (0)