Skip to content

Commit e761e58

Browse files
authored
Remove PersistenceDiagramsBase dependency, update Julia version (#53)
* Remove PersistenceDiagramsBase dependency, update Julia version * Update doctests and formatting * fix Test.yml
1 parent 772a43e commit e761e58

File tree

9 files changed

+600
-9
lines changed

9 files changed

+600
-9
lines changed

Project.toml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,34 @@
11
name = "PersistenceDiagrams"
22
uuid = "90b4794c-894b-4756-a0f8-5efeb5ddf7ae"
33
authors = ["mtsch <matijacufar@gmail.com>"]
4-
version = "0.9.6"
4+
version = "0.9.7"
55

66
[deps]
77
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
88
Hungarian = "e91730f6-4275-51fb-a7a0-7064cfbd3b39"
99
MLJModelInterface = "e80e1ace-859a-464e-9ed9-23947d8ae3ea"
10-
PersistenceDiagramsBase = "b1ad91c1-539c-4ace-90bd-ea06abc420fa"
1110
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
1211
ScientificTypes = "321657f4-b219-11e9-178b-2701a2544e81"
1312
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
1413
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
1514

1615
[compat]
17-
Compat = "^3.10.0"
16+
Compat = "^3.10, 4"
1817
Hungarian = "0.6"
1918
MLJModelInterface = "1"
20-
PersistenceDiagramsBase = "^0.1.1"
2119
RecipesBase = "1"
2220
ScientificTypes = "3"
2321
Tables = "1"
2422
julia = "1"
2523

2624
[extras]
2725
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
26+
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
2827
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
2928
MLJBase = "a7f614a8-145f-11e9-1d2a-a57a1082229d"
3029
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
3130
Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
3231
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
3332

3433
[targets]
35-
test = ["Aqua", "Documenter", "MLJBase", "SafeTestsets", "Suppressor", "Test"]
34+
test = ["Aqua", "DataFrames", "Documenter", "MLJBase", "SafeTestsets", "Suppressor", "Test"]

src/PersistenceDiagrams.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,14 @@ export PersistenceDiagram,
4040

4141
using Compat
4242
using Hungarian
43-
using PersistenceDiagramsBase
4443
using RecipesBase
4544
using ScientificTypes
4645
using Statistics
4746
using Tables
4847

48+
include("intervals.jl")
49+
include("diagrams.jl")
50+
include("tables.jl")
4951
include("matching.jl")
5052

5153
include("persistencecurves.jl")

src/diagrams.jl

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"""
2+
PersistenceDiagram <: AbstractVector{PersistenceInterval}
3+
4+
Type for representing persistence diagrams. Behaves exactly like a vector of
5+
`PersistenceInterval`s, but can have additional metadata attached to it. It supports pretty
6+
printing and plotting.
7+
8+
Can be used as a table with any function that uses the
9+
[`Tables.jl`](https://github.com/JuliaData/Tables.jl) interface. Note that using it as a
10+
table will only keep interval endpoints and the `dim` and `threshold` attributes.
11+
12+
# Example
13+
14+
```jldoctest
15+
julia> diagram = PersistenceDiagram([(1, 3), (3, 4), (1, Inf)]; dim=1, custom_metadata=:a)
16+
3-element 1-dimensional PersistenceDiagram:
17+
[1.0, 3.0)
18+
[3.0, 4.0)
19+
[1.0, ∞)
20+
21+
julia> diagram[1]
22+
[1.0, 3.0)
23+
24+
julia> sort(diagram; by=persistence, rev=true)
25+
3-element 1-dimensional PersistenceDiagram:
26+
[1.0, ∞)
27+
[1.0, 3.0)
28+
[3.0, 4.0)
29+
30+
julia> propertynames(diagram)
31+
(:intervals, :dim, :custom_metadata)
32+
33+
julia> dim(diagram)
34+
1
35+
36+
julia> diagram.custom_metadata
37+
:a
38+
```
39+
"""
40+
struct PersistenceDiagram <: AbstractVector{PersistenceInterval}
41+
intervals::Vector{PersistenceInterval}
42+
meta::NamedTuple
43+
end
44+
45+
function PersistenceDiagram(intervals::Vector{PersistenceInterval}; kwargs...)
46+
meta = (; kwargs...)
47+
return PersistenceDiagram(intervals, meta)
48+
end
49+
function PersistenceDiagram(intervals::AbstractVector{PersistenceInterval}; kwargs...)
50+
return PersistenceDiagram(collect(intervals); kwargs...)
51+
end
52+
function PersistenceDiagram(pairs::AbstractVector{<:Tuple}; kwargs...)
53+
return PersistenceDiagram(PersistenceInterval.(pairs); kwargs...)
54+
end
55+
function PersistenceDiagram(table)
56+
rows = Tables.rows(table)
57+
if isempty(rows)
58+
return PersistenceDiagram(PersistenceInterval[])
59+
else
60+
firstrow = first(rows)
61+
dim = hasproperty(firstrow, :dim) ? firstrow.dim : missing
62+
threshold = hasproperty(firstrow, :threshold) ? firstrow.threshold : missing
63+
intervals = map(rows) do row
64+
d = hasproperty(row, :dim) ? row.dim : missing
65+
t = hasproperty(row, :threshold) ? row.threshold : missing
66+
if !isequal(d, dim)
67+
error("different `dim`s detected. Try splitting the table first.")
68+
end
69+
if !isequal(t, threshold)
70+
error("different `threshold`s detected. Try splitting the table first.")
71+
end
72+
PersistenceInterval(row.birth, row.death)
73+
end
74+
return PersistenceDiagram(intervals; dim=dim, threshold=threshold)
75+
end
76+
end
77+
78+
function Base.show(io::IO, diag::PersistenceDiagram)
79+
return summary(io, diag)
80+
end
81+
function Base.summary(io::IO, diag::PersistenceDiagram)
82+
if haskey(diag.meta, :dim)
83+
print(io, length(diag), "-element ", dim(diag), "-dimensional PersistenceDiagram")
84+
else
85+
print(io, length(diag), "-element PersistenceDiagram")
86+
end
87+
end
88+
89+
###
90+
### Array interface
91+
###
92+
Base.size(diag::PersistenceDiagram) = size(diag.intervals)
93+
Base.getindex(diag::PersistenceDiagram, i::Integer) = diag.intervals[i]
94+
Base.setindex!(diag::PersistenceDiagram, x, i::Integer) = diag.intervals[i] = x
95+
Base.firstindex(diag::PersistenceDiagram) = 1
96+
Base.lastindex(diag::PersistenceDiagram) = length(diag.intervals)
97+
98+
function Base.similar(diag::PersistenceDiagram)
99+
return PersistenceDiagram(similar(diag.intervals); diag.meta...)
100+
end
101+
function Base.similar(diag::PersistenceDiagram, dims::Tuple)
102+
return PersistenceDiagram(similar(diag.intervals, dims); diag.meta...)
103+
end
104+
105+
###
106+
### Meta
107+
###
108+
function Base.getproperty(diag::PersistenceDiagram, key::Symbol)
109+
if hasfield(typeof(diag), key)
110+
return getfield(diag, key)
111+
elseif haskey(diag.meta, key)
112+
return diag.meta[key]
113+
else
114+
error("$diag has no $key")
115+
end
116+
end
117+
function Base.propertynames(diag::PersistenceDiagram, private::Bool=false)
118+
if private
119+
return tuple(:intervals, :meta, propertynames(diag.meta)...)
120+
else
121+
return tuple(:intervals, propertynames(diag.meta)...)
122+
end
123+
end
124+
125+
"""
126+
threshold(diagram::PersistenceDiagram)
127+
128+
Get the threshold of persistence diagram. Equivalent to `diagram.threshold`.
129+
"""
130+
threshold(diag::PersistenceDiagram) = diag.threshold
131+
132+
"""
133+
dim(diagram::PersistenceDiagram)
134+
135+
Get the dimension of persistence diagram. Equivalent to `diagram.dim`.
136+
"""
137+
dim(diag::PersistenceDiagram) = diag.dim

src/intervals.jl

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
"""
2+
PersistenceInterval
3+
4+
Type for representing persistence intervals. It behaves exactly like a `Tuple{Float64,
5+
Float64}`, but can have meta data attached to it. The metadata is accessible with
6+
`getproperty` or the dot syntax.
7+
8+
# Example
9+
10+
```jldoctest
11+
julia> interval = PersistenceInterval(1, Inf; meta1=:a, meta2=:b)
12+
[1.0, ∞) with:
13+
meta1: Symbol
14+
meta2: Symbol
15+
16+
julia> birth(interval), death(interval), persistence(interval)
17+
(1.0, Inf, Inf)
18+
19+
julia> isfinite(interval)
20+
false
21+
22+
julia> propertynames(interval)
23+
(:birth, :death, :meta1, :meta2)
24+
25+
julia> interval.meta1
26+
:a
27+
```
28+
"""
29+
struct PersistenceInterval
30+
birth::Float64
31+
death::Float64
32+
meta::NamedTuple
33+
end
34+
function PersistenceInterval(birth, death; kwargs...)
35+
meta = (; kwargs...)
36+
return PersistenceInterval(Float64(birth), Float64(death), meta)
37+
end
38+
function PersistenceInterval(t::Tuple{<:Any,<:Any}; kwargs...)
39+
meta = (; kwargs...)
40+
return PersistenceInterval(Float64(t[1]), Float64(t[2]), meta)
41+
end
42+
function PersistenceInterval(int::PersistenceInterval; kwargs...)
43+
meta = (; kwargs...)
44+
return PersistenceInterval(Float64(int[1]), Float64(int[2]), meta)
45+
end
46+
47+
"""
48+
birth(interval)
49+
50+
Get the birth time of `interval`.
51+
"""
52+
birth(int::PersistenceInterval) = getfield(int, 1)
53+
"""
54+
death(interval)
55+
56+
Get the death time of `interval`.
57+
"""
58+
death(int::PersistenceInterval) = getfield(int, 2)
59+
"""
60+
persistence(interval)
61+
62+
Get the persistence of `interval`, which is equal to `death - birth`.
63+
"""
64+
persistence(int::PersistenceInterval) = death(int) - birth(int)
65+
66+
"""
67+
midlife(interval)
68+
69+
Get the midlife of the `interval`, which is equal to `(birth + death) / 2`.
70+
"""
71+
midlife(int::PersistenceInterval) = (birth(int) + death(int)) / 2
72+
73+
Base.isfinite(int::PersistenceInterval) = isfinite(death(int))
74+
75+
###
76+
### Iteration
77+
###
78+
function Base.iterate(int::PersistenceInterval, i=1)
79+
if i == 1
80+
return birth(int), i + 1
81+
elseif i == 2
82+
return death(int), i + 1
83+
else
84+
return nothing
85+
end
86+
end
87+
88+
Base.length(::PersistenceInterval) = 2
89+
Base.IteratorSize(::Type{<:PersistenceInterval}) = Base.HasLength()
90+
Base.IteratorEltype(::Type{<:PersistenceInterval}) = Base.HasEltype()
91+
Base.eltype(::Type{<:PersistenceInterval}) = Float64
92+
93+
function Base.getindex(int::PersistenceInterval, i)
94+
if i == 1
95+
return birth(int)
96+
elseif i == 2
97+
return death(int)
98+
else
99+
throw(BoundsError(int, i))
100+
end
101+
end
102+
103+
Base.firstindex(int::PersistenceInterval) = 1
104+
Base.lastindex(int::PersistenceInterval) = 2
105+
106+
###
107+
### Equality and ordering
108+
###
109+
function Base.:(==)(int1::PersistenceInterval, int2::PersistenceInterval)
110+
return birth(int1) == birth(int2) && death(int1) == death(int2)
111+
end
112+
Base.:(==)(int::PersistenceInterval, (b, d)::Tuple) = birth(int) == b && death(int) == d
113+
Base.:(==)((b, d)::Tuple, int::PersistenceInterval) = birth(int) == b && death(int) == d
114+
115+
function Base.isless(int1::PersistenceInterval, int2::PersistenceInterval)
116+
return (birth(int1), death(int1)) < (birth(int2), death(int2))
117+
end
118+
119+
###
120+
### Printing
121+
###
122+
function Base.show(io::IO, int::PersistenceInterval)
123+
b = round(birth(int); sigdigits=3)
124+
d = isfinite(death(int)) ? round(death(int); sigdigits=3) : ""
125+
return print(io, "[$b, $d)")
126+
end
127+
128+
function Base.show(io::IO, ::MIME"text/plain", int::PersistenceInterval)
129+
b = round(birth(int); sigdigits=3)
130+
d = isfinite(death(int)) ? round(death(int); sigdigits=3) : ""
131+
print(io, "[$b, $d)")
132+
if !isempty(int.meta)
133+
print(io, " with:")
134+
for (k, v) in zip(keys(int.meta), int.meta)
135+
print(io, "\n ", k, ": ", summary(v))
136+
end
137+
end
138+
end
139+
140+
###
141+
### Metadata
142+
###
143+
function Base.getproperty(int::PersistenceInterval, key::Symbol)
144+
if hasfield(typeof(int), key)
145+
return getfield(int, key)
146+
elseif haskey(int.meta, key)
147+
return int.meta[key]
148+
else
149+
error("interval $int has no $key")
150+
end
151+
end
152+
function Base.propertynames(int::PersistenceInterval, private::Bool=false)
153+
if private
154+
return tuple(:birth, :death, propertynames(int.meta)..., :meta)
155+
else
156+
return (:birth, :death, propertynames(int.meta)...)
157+
end
158+
end
159+
160+
"""
161+
representative(interval::PersistenceInterval)
162+
163+
Get the representative (co)cycle attached to `interval`, if it has one.
164+
"""
165+
representative(int::PersistenceInterval) = int.representative
166+
167+
"""
168+
birth_simplex(interval::PersistenceInterval)
169+
170+
Get the critical birth simplex of `interval`, if it has one.
171+
"""
172+
birth_simplex(int::PersistenceInterval) = int.birth_simplex
173+
174+
"""
175+
death_simplex(interval::PersistenceInterval)
176+
177+
Get the critical death simplex of `interval`, if it has one.
178+
179+
!!! note
180+
An infinite interval's death simplex is `nothing`.
181+
"""
182+
death_simplex(int::PersistenceInterval) = int.death_simplex

src/persistencecurves.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ persistence diagrams. [arXiv preprint arXiv:1904.07768](https://arxiv.org/abs/19
272272
function Midlife(args...; kwargs...)
273273
return PersistenceCurve(midlife, sum, args...; kwargs...)
274274
end
275-
PersistenceDiagramsBase.midlife((b, d), _, _) = (b + d) / 2
275+
midlife((b, d), _, _) = (b + d) / 2
276276

277277
"""
278278
LifeEntropy

0 commit comments

Comments
 (0)