Skip to content

Commit

Permalink
Remove PersistenceDiagramsBase dependency, update Julia version (#53)
Browse files Browse the repository at this point in the history
* Remove PersistenceDiagramsBase dependency, update Julia version

* Update doctests and formatting

* fix Test.yml
  • Loading branch information
mtsch authored May 25, 2022
1 parent 772a43e commit e761e58
Show file tree
Hide file tree
Showing 9 changed files with 600 additions and 9 deletions.
9 changes: 4 additions & 5 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
name = "PersistenceDiagrams"
uuid = "90b4794c-894b-4756-a0f8-5efeb5ddf7ae"
authors = ["mtsch <[email protected]>"]
version = "0.9.6"
version = "0.9.7"

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

[compat]
Compat = "^3.10.0"
Compat = "^3.10, 4"
Hungarian = "0.6"
MLJModelInterface = "1"
PersistenceDiagramsBase = "^0.1.1"
RecipesBase = "1"
ScientificTypes = "3"
Tables = "1"
julia = "1"

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

[targets]
test = ["Aqua", "Documenter", "MLJBase", "SafeTestsets", "Suppressor", "Test"]
test = ["Aqua", "DataFrames", "Documenter", "MLJBase", "SafeTestsets", "Suppressor", "Test"]
4 changes: 3 additions & 1 deletion src/PersistenceDiagrams.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,14 @@ export PersistenceDiagram,

using Compat
using Hungarian
using PersistenceDiagramsBase
using RecipesBase
using ScientificTypes
using Statistics
using Tables

include("intervals.jl")
include("diagrams.jl")
include("tables.jl")
include("matching.jl")

include("persistencecurves.jl")
Expand Down
137 changes: 137 additions & 0 deletions src/diagrams.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"""
PersistenceDiagram <: AbstractVector{PersistenceInterval}
Type for representing persistence diagrams. Behaves exactly like a vector of
`PersistenceInterval`s, but can have additional metadata attached to it. It supports pretty
printing and plotting.
Can be used as a table with any function that uses the
[`Tables.jl`](https://github.com/JuliaData/Tables.jl) interface. Note that using it as a
table will only keep interval endpoints and the `dim` and `threshold` attributes.
# Example
```jldoctest
julia> diagram = PersistenceDiagram([(1, 3), (3, 4), (1, Inf)]; dim=1, custom_metadata=:a)
3-element 1-dimensional PersistenceDiagram:
[1.0, 3.0)
[3.0, 4.0)
[1.0, ∞)
julia> diagram[1]
[1.0, 3.0)
julia> sort(diagram; by=persistence, rev=true)
3-element 1-dimensional PersistenceDiagram:
[1.0, ∞)
[1.0, 3.0)
[3.0, 4.0)
julia> propertynames(diagram)
(:intervals, :dim, :custom_metadata)
julia> dim(diagram)
1
julia> diagram.custom_metadata
:a
```
"""
struct PersistenceDiagram <: AbstractVector{PersistenceInterval}
intervals::Vector{PersistenceInterval}
meta::NamedTuple
end

function PersistenceDiagram(intervals::Vector{PersistenceInterval}; kwargs...)
meta = (; kwargs...)
return PersistenceDiagram(intervals, meta)
end
function PersistenceDiagram(intervals::AbstractVector{PersistenceInterval}; kwargs...)
return PersistenceDiagram(collect(intervals); kwargs...)
end
function PersistenceDiagram(pairs::AbstractVector{<:Tuple}; kwargs...)
return PersistenceDiagram(PersistenceInterval.(pairs); kwargs...)
end
function PersistenceDiagram(table)
rows = Tables.rows(table)
if isempty(rows)
return PersistenceDiagram(PersistenceInterval[])
else
firstrow = first(rows)
dim = hasproperty(firstrow, :dim) ? firstrow.dim : missing
threshold = hasproperty(firstrow, :threshold) ? firstrow.threshold : missing
intervals = map(rows) do row
d = hasproperty(row, :dim) ? row.dim : missing
t = hasproperty(row, :threshold) ? row.threshold : missing
if !isequal(d, dim)
error("different `dim`s detected. Try splitting the table first.")
end
if !isequal(t, threshold)
error("different `threshold`s detected. Try splitting the table first.")
end
PersistenceInterval(row.birth, row.death)
end
return PersistenceDiagram(intervals; dim=dim, threshold=threshold)
end
end

function Base.show(io::IO, diag::PersistenceDiagram)
return summary(io, diag)
end
function Base.summary(io::IO, diag::PersistenceDiagram)
if haskey(diag.meta, :dim)
print(io, length(diag), "-element ", dim(diag), "-dimensional PersistenceDiagram")
else
print(io, length(diag), "-element PersistenceDiagram")
end
end

###
### Array interface
###
Base.size(diag::PersistenceDiagram) = size(diag.intervals)
Base.getindex(diag::PersistenceDiagram, i::Integer) = diag.intervals[i]
Base.setindex!(diag::PersistenceDiagram, x, i::Integer) = diag.intervals[i] = x
Base.firstindex(diag::PersistenceDiagram) = 1
Base.lastindex(diag::PersistenceDiagram) = length(diag.intervals)

function Base.similar(diag::PersistenceDiagram)
return PersistenceDiagram(similar(diag.intervals); diag.meta...)
end
function Base.similar(diag::PersistenceDiagram, dims::Tuple)
return PersistenceDiagram(similar(diag.intervals, dims); diag.meta...)
end

###
### Meta
###
function Base.getproperty(diag::PersistenceDiagram, key::Symbol)
if hasfield(typeof(diag), key)
return getfield(diag, key)
elseif haskey(diag.meta, key)
return diag.meta[key]
else
error("$diag has no $key")
end
end
function Base.propertynames(diag::PersistenceDiagram, private::Bool=false)
if private
return tuple(:intervals, :meta, propertynames(diag.meta)...)
else
return tuple(:intervals, propertynames(diag.meta)...)
end
end

"""
threshold(diagram::PersistenceDiagram)
Get the threshold of persistence diagram. Equivalent to `diagram.threshold`.
"""
threshold(diag::PersistenceDiagram) = diag.threshold

"""
dim(diagram::PersistenceDiagram)
Get the dimension of persistence diagram. Equivalent to `diagram.dim`.
"""
dim(diag::PersistenceDiagram) = diag.dim
182 changes: 182 additions & 0 deletions src/intervals.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
"""
PersistenceInterval
Type for representing persistence intervals. It behaves exactly like a `Tuple{Float64,
Float64}`, but can have meta data attached to it. The metadata is accessible with
`getproperty` or the dot syntax.
# Example
```jldoctest
julia> interval = PersistenceInterval(1, Inf; meta1=:a, meta2=:b)
[1.0, ∞) with:
meta1: Symbol
meta2: Symbol
julia> birth(interval), death(interval), persistence(interval)
(1.0, Inf, Inf)
julia> isfinite(interval)
false
julia> propertynames(interval)
(:birth, :death, :meta1, :meta2)
julia> interval.meta1
:a
```
"""
struct PersistenceInterval
birth::Float64
death::Float64
meta::NamedTuple
end
function PersistenceInterval(birth, death; kwargs...)
meta = (; kwargs...)
return PersistenceInterval(Float64(birth), Float64(death), meta)
end
function PersistenceInterval(t::Tuple{<:Any,<:Any}; kwargs...)
meta = (; kwargs...)
return PersistenceInterval(Float64(t[1]), Float64(t[2]), meta)
end
function PersistenceInterval(int::PersistenceInterval; kwargs...)
meta = (; kwargs...)
return PersistenceInterval(Float64(int[1]), Float64(int[2]), meta)
end

"""
birth(interval)
Get the birth time of `interval`.
"""
birth(int::PersistenceInterval) = getfield(int, 1)
"""
death(interval)
Get the death time of `interval`.
"""
death(int::PersistenceInterval) = getfield(int, 2)
"""
persistence(interval)
Get the persistence of `interval`, which is equal to `death - birth`.
"""
persistence(int::PersistenceInterval) = death(int) - birth(int)

"""
midlife(interval)
Get the midlife of the `interval`, which is equal to `(birth + death) / 2`.
"""
midlife(int::PersistenceInterval) = (birth(int) + death(int)) / 2

Base.isfinite(int::PersistenceInterval) = isfinite(death(int))

###
### Iteration
###
function Base.iterate(int::PersistenceInterval, i=1)
if i == 1
return birth(int), i + 1
elseif i == 2
return death(int), i + 1
else
return nothing
end
end

Base.length(::PersistenceInterval) = 2
Base.IteratorSize(::Type{<:PersistenceInterval}) = Base.HasLength()
Base.IteratorEltype(::Type{<:PersistenceInterval}) = Base.HasEltype()
Base.eltype(::Type{<:PersistenceInterval}) = Float64

function Base.getindex(int::PersistenceInterval, i)
if i == 1
return birth(int)
elseif i == 2
return death(int)
else
throw(BoundsError(int, i))
end
end

Base.firstindex(int::PersistenceInterval) = 1
Base.lastindex(int::PersistenceInterval) = 2

###
### Equality and ordering
###
function Base.:(==)(int1::PersistenceInterval, int2::PersistenceInterval)
return birth(int1) == birth(int2) && death(int1) == death(int2)
end
Base.:(==)(int::PersistenceInterval, (b, d)::Tuple) = birth(int) == b && death(int) == d
Base.:(==)((b, d)::Tuple, int::PersistenceInterval) = birth(int) == b && death(int) == d

function Base.isless(int1::PersistenceInterval, int2::PersistenceInterval)
return (birth(int1), death(int1)) < (birth(int2), death(int2))
end

###
### Printing
###
function Base.show(io::IO, int::PersistenceInterval)
b = round(birth(int); sigdigits=3)
d = isfinite(death(int)) ? round(death(int); sigdigits=3) : ""
return print(io, "[$b, $d)")
end

function Base.show(io::IO, ::MIME"text/plain", int::PersistenceInterval)
b = round(birth(int); sigdigits=3)
d = isfinite(death(int)) ? round(death(int); sigdigits=3) : ""
print(io, "[$b, $d)")
if !isempty(int.meta)
print(io, " with:")
for (k, v) in zip(keys(int.meta), int.meta)
print(io, "\n ", k, ": ", summary(v))
end
end
end

###
### Metadata
###
function Base.getproperty(int::PersistenceInterval, key::Symbol)
if hasfield(typeof(int), key)
return getfield(int, key)
elseif haskey(int.meta, key)
return int.meta[key]
else
error("interval $int has no $key")
end
end
function Base.propertynames(int::PersistenceInterval, private::Bool=false)
if private
return tuple(:birth, :death, propertynames(int.meta)..., :meta)
else
return (:birth, :death, propertynames(int.meta)...)
end
end

"""
representative(interval::PersistenceInterval)
Get the representative (co)cycle attached to `interval`, if it has one.
"""
representative(int::PersistenceInterval) = int.representative

"""
birth_simplex(interval::PersistenceInterval)
Get the critical birth simplex of `interval`, if it has one.
"""
birth_simplex(int::PersistenceInterval) = int.birth_simplex

"""
death_simplex(interval::PersistenceInterval)
Get the critical death simplex of `interval`, if it has one.
!!! note
An infinite interval's death simplex is `nothing`.
"""
death_simplex(int::PersistenceInterval) = int.death_simplex
2 changes: 1 addition & 1 deletion src/persistencecurves.jl
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ persistence diagrams. [arXiv preprint arXiv:1904.07768](https://arxiv.org/abs/19
function Midlife(args...; kwargs...)
return PersistenceCurve(midlife, sum, args...; kwargs...)
end
PersistenceDiagramsBase.midlife((b, d), _, _) = (b + d) / 2
midlife((b, d), _, _) = (b + d) / 2

"""
LifeEntropy
Expand Down
Loading

2 comments on commit e761e58

@mtsch
Copy link
Owner Author

@mtsch mtsch commented on e761e58 May 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/60991

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.9.7 -m "<description of version>" e761e58d8552e57679d22c72eb3851d87b9a8bbf
git push origin v0.9.7

Please sign in to comment.