Skip to content

Commit 8541638

Browse files
authored
Extended tropical for computing spectrums (#24)
* update * tests pass * fix docs * slow but works
1 parent 513181b commit 8541638

File tree

7 files changed

+211
-25
lines changed

7 files changed

+211
-25
lines changed

docs/src/ref.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ is_commutative_semiring
8080
```@docs
8181
TropicalNumbers.Tropical
8282
TropicalNumbers.CountingTropical
83+
ExtendedTropical
8384
Mods.Mod
8485
Polynomials.Polynomial
8586
TruncatedPoly

src/GraphTensorNetworks.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export estimate_memory
1818
export StaticBitVector, StaticElementVector, @bv_str
1919
export is_commutative_semiring
2020
export Max2Poly, TruncatedPoly, Polynomial, Tropical, CountingTropical, StaticElementVector, Mod, ConfigEnumerator, onehotv, ConfigSampler, TreeConfigEnumerator
21-
export CountingTropicalF64, CountingTropicalF32, TropicalF64, TropicalF32
21+
export CountingTropicalF64, CountingTropicalF32, TropicalF64, TropicalF32, ExtendedTropical
2222

2323
# Lower level APIs
2424
export AllConfigs, SingleConfig

src/arithematics.jl

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ function is_commutative_semiring(a::T, b::T, c::T) where T
7979
return true
8080
end
8181

82+
######################## Truncated Polynomial ######################
83+
# TODO: store orders to support non-integer weights
84+
# (↑) Maybe not so nessesary, no use case for counting degeneracy when using floating point weights.
8285
"""
8386
TruncatedPoly{K,T,TO} <: Number
8487
TruncatedPoly(coeffs::Tuple, maxorder)
@@ -165,6 +168,124 @@ function Base.show(io::IO, ::MIME"text/plain", x::TruncatedPoly{K}) where K
165168
end
166169
end
167170

171+
############################ ExtendedTropical #####################
172+
"""
173+
ExtendedTropical{K,TO} <: Number
174+
ExtendedTropical(orders)
175+
176+
Extended Tropical numbers with largest `K` orders keeped,
177+
or the [`TruncatedPoly`](@ref) without coefficients,
178+
`TO` is the element type of orders.
179+
180+
Example
181+
------------------------------
182+
```jldoctest; setup=(using GraphTensorNetworks)
183+
julia> x = ExtendedTropical{3}([1.0, 2, 3])
184+
ExtendedTropical{3, Float64}([1.0, 2.0, 3.0])
185+
186+
julia> y = ExtendedTropical{3}([-Inf, 2, 5])
187+
ExtendedTropical{3, Float64}([-Inf, 2.0, 5.0])
188+
189+
julia> x * y
190+
ExtendedTropical{3, Float64}([6.0, 7.0, 8.0])
191+
192+
julia> x + y
193+
ExtendedTropical{3, Float64}([2.0, 3.0, 5.0])
194+
195+
julia> one(x)
196+
ExtendedTropical{3, Float64}([-Inf, -Inf, 0.0])
197+
198+
julia> zero(x)
199+
ExtendedTropical{3, Float64}([-Inf, -Inf, -Inf])
200+
```
201+
"""
202+
struct ExtendedTropical{K,TO} <: Number
203+
orders::Vector{TO}
204+
end
205+
function ExtendedTropical{K}(x::Vector{T}) where {T, K}
206+
@assert length(x) == K
207+
@assert issorted(x)
208+
ExtendedTropical{K,T}(x)
209+
end
210+
Base.:(==)(a::ExtendedTropical{K}, b::ExtendedTropical{K}) where K = all(i->a.orders[i] == b.orders[i], 1:K)
211+
212+
function Base.:*(a::ExtendedTropical{K,TO}, b::ExtendedTropical{K,TO}) where {K,TO}
213+
res = Vector{TO}(undef, K)
214+
return ExtendedTropical{K,TO}(sorted_sum_combination!(res, a.orders, b.orders))
215+
end
216+
217+
function sorted_sum_combination!(res::AbstractVector{TO}, A::AbstractVector{TO}, B::AbstractVector{TO}) where TO
218+
K = length(res)
219+
@assert length(B) == length(A) == K
220+
maxval = A[K] + B[K]
221+
ptr = K
222+
res[ptr] = maxval
223+
queue = [(K,K-1,A[K]+B[K-1]), (K-1,K,A[K-1]+B[K])]
224+
for k = 1:K-1
225+
(i, j, res[K-k]) = _pop_max_sum!(queue) # TODO: do not enumerate, use better data structures
226+
_push_if_not_exists!(queue, i, j-1, A, B)
227+
_push_if_not_exists!(queue, i-1, j, A, B)
228+
end
229+
return res
230+
end
231+
232+
function _push_if_not_exists!(queue, i, j, A, B)
233+
@inbounds if j>=1 && i>=1 && !any(x->x[1] >= i && x[2] >= j, queue)
234+
push!(queue, (i, j, A[i] + B[j]))
235+
end
236+
end
237+
238+
function _pop_max_sum!(queue)
239+
maxsum = first(queue)[3]
240+
maxloc = 1
241+
@inbounds for i=2:length(queue)
242+
m = queue[i][3]
243+
if m > maxsum
244+
maxsum = m
245+
maxloc = i
246+
end
247+
end
248+
@inbounds data = queue[maxloc]
249+
deleteat!(queue, maxloc)
250+
return data
251+
end
252+
253+
function Base.:+(a::ExtendedTropical{K,TO}, b::ExtendedTropical{K,TO}) where {K,TO}
254+
res = Vector{TO}(undef, K)
255+
ptr1, ptr2 = K, K
256+
@inbounds va, vb = a.orders[ptr1], b.orders[ptr2]
257+
@inbounds for i=K:-1:1
258+
if va > vb
259+
res[i] = va
260+
if ptr1 != 1
261+
ptr1 -= 1
262+
va = a.orders[ptr1]
263+
end
264+
else
265+
res[i] = vb
266+
if ptr2 != 1
267+
ptr2 -= 1
268+
vb = b.orders[ptr2]
269+
end
270+
end
271+
end
272+
return ExtendedTropical{K,TO}(res)
273+
end
274+
275+
Base.:^(a::ExtendedTropical, b::Integer) = Base.invoke(^, Tuple{ExtendedTropical, Real}, a, b)
276+
function Base.:^(a::ExtendedTropical{K,TO}, b::Real) where {K,TO}
277+
if iszero(b) # to avoid NaN
278+
return one(ExtendedTropical{K,promote_type(TO,typeof(b))})
279+
else
280+
return ExtendedTropical{K,TO}(a.orders .* b)
281+
end
282+
end
283+
284+
Base.zero(::Type{ExtendedTropical{K,TO}}) where {K,TO} = ExtendedTropical{K,TO}(fill(zero(Tropical{TO}).n, K))
285+
Base.one(::Type{ExtendedTropical{K,TO}}) where {K,TO} = ExtendedTropical{K,TO}(map(i->i==K ? one(Tropical{TO}).n : zero(Tropical{TO}).n, 1:K))
286+
Base.zero(::ExtendedTropical{K,TO}) where {K,TO} = zero(ExtendedTropical{K,TO})
287+
Base.one(::ExtendedTropical{K,TO}) where {K,TO} = one(ExtendedTropical{K,TO})
288+
168289
############################ SET Numbers ##########################
169290
abstract type AbstractSetNumber end
170291

@@ -501,7 +622,7 @@ function Base.copy(c::TreeConfigEnumerator{N,S,C}) where {N,S,C}
501622
end
502623

503624
# Handle boolean, this is a patch for CUDA matmul
504-
for TYPE in [:AbstractSetNumber, :TruncatedPoly]
625+
for TYPE in [:AbstractSetNumber, :TruncatedPoly, :ExtendedTropical]
505626
@eval Base.:*(a::Bool, y::$TYPE) = a ? y : zero(y)
506627
@eval Base.:*(y::$TYPE, a::Bool) = a ? y : zero(y)
507628
end
@@ -550,9 +671,22 @@ function _x(::Type{Tropical{TV}}; invert) where {TV}
550671
ret = Tropical{TV}(one(TV))
551672
invert ? pre_invert_exponent(ret) : ret
552673
end
674+
function _x(::Type{ExtendedTropical{K,TO}}; invert) where {K,TO}
675+
ret =ExtendedTropical{K,TO}(map(i->i==K ? one(TO) : zero(Tropical{TO}).n, 1:K))
676+
invert ? pre_invert_exponent(ret) : ret
677+
end
553678

554679
# for finding all solutions
555680
function _x(::Type{T}; invert) where {T<:AbstractSetNumber}
556681
ret = one(T)
557682
invert ? pre_invert_exponent(ret) : ret
558683
end
684+
685+
# negate the exponents before entering the solver
686+
pre_invert_exponent(t::TruncatedPoly{K}) where K = TruncatedPoly(t.coeffs, -t.maxorder)
687+
pre_invert_exponent(t::TropicalNumbers.TropicalTypes) = inv(t)
688+
pre_invert_exponent(t::ExtendedTropical{K}) where K = ExtendedTropical{K}(map(i->i==K ? -t.orders[i] : t.orders[i], 1:K))
689+
# negate the exponents after entering the solver
690+
post_invert_exponent(t::TruncatedPoly{K}) where K = TruncatedPoly(ntuple(i->t.coeffs[K-i+1], K), -t.maxorder+(K-1))
691+
post_invert_exponent(t::TropicalNumbers.TropicalTypes) = inv(t)
692+
post_invert_exponent(t::ExtendedTropical{K}) where K = ExtendedTropical{K}(map(i->-t.orders[i], K:-1:1))

src/interfaces.jl

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,33 @@
11
abstract type AbstractProperty end
22

33
"""
4-
SizeMax <: AbstractProperty
5-
SizeMax()
4+
SizeMax{K} <: AbstractProperty
5+
SizeMax(k::Int)
66
7-
The maximum set size. e.g. the largest size of the [`IndependentSet`](@ref) problem is also know as the independence number.
7+
The maximum-K set sizes. e.g. the largest size of the [`IndependentSet`](@ref) problem is also know as the independence number.
88
9-
* The corresponding tensor element type is max-plus tropical number [`Tropical`](@ref).
9+
* The corresponding tensor element type are max-plus tropical number [`Tropical`](@ref) for `K == 1` and [`ExtendedTropical`](@ref) for `K > 1`.
1010
* It is compatible with weighted graph problems.
11-
* BLAS (on CPU) and GPU are supported,
11+
* BLAS (on CPU) and GPU are supported only for `K == 1`,
1212
"""
13-
struct SizeMax <: AbstractProperty end
13+
struct SizeMax{K} <: AbstractProperty end
14+
SizeMax(k::Int=1) = SizeMax{k}()
15+
max_k(::SizeMax{K}) where K = K
1416

1517
"""
16-
SizeMin <: AbstractProperty
17-
SizeMin()
18+
SizeMin{K} <: AbstractProperty
19+
SizeMin(k::Int)
1820
19-
The maximum set size. e.g. the smallest size ofthe [`MaximalIS`](@ref) problem is also known as the independent domination number.
21+
The minimum-K set sizes. e.g. the smallest size ofthe [`MaximalIS`](@ref) problem is also known as the independent domination number.
2022
21-
* The corresponding tensor element type inverted max-plus tropical number [`Tropical`](@ref), which is equivalent to the min-plus tropical number.
23+
* The corresponding tensor element type are inverted max-plus tropical number [`Tropical`](@ref) for `K == 1` and inverted [`ExtendedTropical`](@ref) for `K > 1`.
24+
The inverted Tropical number emulates the min-plus tropical number.
2225
* It is compatible with weighted graph problems.
23-
* BLAS (on CPU) and GPU are supported,
26+
* BLAS (on CPU) and GPU are supported only for `K == 1`,
2427
"""
25-
struct SizeMin <: AbstractProperty end
28+
struct SizeMin{K} <: AbstractProperty end
29+
SizeMin(k::Int=1) = SizeMin{k}()
30+
max_k(::SizeMin{K}) where K = K
2631

2732
"""
2833
CountingAll <: AbstractProperty
@@ -226,10 +231,14 @@ function solve(gp::GraphProblem, property::AbstractProperty; T=Float64, usecuda=
226231
if !_solvable(gp, property)
227232
throw(ArgumentError("Graph property `$(typeof(property))` is not computable for graph problem of type `$(typeof(gp))`."))
228233
end
229-
if property isa SizeMax
234+
if property isa SizeMax{1}
230235
return contractx(gp, _x(Tropical{T}; invert=false); usecuda=usecuda)
231-
elseif property isa SizeMin
236+
elseif property isa SizeMin{1}
232237
return post_invert_exponent.(contractx(gp, _x(Tropical{T}; invert=true); usecuda=usecuda))
238+
elseif property isa SizeMax
239+
return contractx(gp, _x(ExtendedTropical{max_k(property), T}; invert=false); usecuda=usecuda)
240+
elseif property isa SizeMin
241+
return post_invert_exponent.(contractx(gp, _x(ExtendedTropical{max_k(property), T}; invert=true); usecuda=usecuda))
233242
elseif property isa CountingAll
234243
return contractx(gp, one(T); usecuda=usecuda)
235244
elseif property isa CountingMax{1}
@@ -276,13 +285,6 @@ end
276285
# raise an error if the property for problem can not be computed
277286
_solvable(::Any, ::Any) = true
278287

279-
# negate the exponents before entering the solver
280-
pre_invert_exponent(t::TruncatedPoly{K}) where K = TruncatedPoly(t.coeffs, -t.maxorder)
281-
pre_invert_exponent(t::TropicalNumbers.TropicalTypes) = inv(t)
282-
# negate the exponents after entering the solver
283-
post_invert_exponent(t::TruncatedPoly{K}) where K = TruncatedPoly(ntuple(i->t.coeffs[K-i+1], K), -t.maxorder+(K-1))
284-
post_invert_exponent(t::TropicalNumbers.TropicalTypes) = inv(t)
285-
286288
"""
287289
max_size(problem; usecuda=false)
288290
@@ -403,6 +405,9 @@ function estimate_memory(problem::GraphProblem, ::GraphPolynomial{:polynomial};
403405
# this is the upper bound
404406
return peak_memory(problem.code, _size_dict(problem)) * (sizeof(T) * length(labels(problem)))
405407
end
408+
function estimate_memory(problem::GraphProblem, ::Union{SizeMax{K},SizeMin{K}}; T=Float64) where K
409+
return peak_memory(problem.code, _size_dict(problem)) * (sizeof(T) * K)
410+
end
406411

407412
function _size_dict(problem)
408413
lbs = labels(problem)
@@ -417,7 +422,9 @@ function _estimate_memory(::Type{ET}, problem::GraphProblem) where ET
417422
return peak_memory(problem.code, _size_dict(problem)) * sizeof(ET)
418423
end
419424

420-
for (PROP, ET) in [(:SizeMax, :(Tropical{T})), (:SizeMin, :(Tropical{T})),
425+
for (PROP, ET) in [
426+
(:(SizeMax{1}), :(Tropical{T})), (:(SizeMin{1}), :(Tropical{T})),
427+
(:(SizeMax{K}), :(ExtendedTropical{K,T})), (:(SizeMin{K}), :(ExtendedTropical{K,T})),
421428
(:(SingleConfigMax{true}), :(Tropical{T})), (:(SingleConfigMin{true}), :(Tropical{T})),
422429
(:(CountingAll), :T), (:(CountingMax{1}), :(CountingTropical{T,T})), (:(CountingMin{1}), :(CountingTropical{T,T})),
423430
(:(CountingMax{K}), :(TruncatedPoly{K,T,T})), (:(CountingMin{K}), :(TruncatedPoly{K,T,T})),

test/arithematics.jl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ end
3434
(Max2Poly(1,2,3.0), Max2Poly(3,2,2.0), Max2Poly(4,7,1.0)),
3535
(TruncatedPoly((1,2,3),3.0), TruncatedPoly((7,3,2),2.0), TruncatedPoly((1,4,7),1.0)),
3636
(TropicalF64(5), TropicalF64(3), TropicalF64(-9)),
37+
(ExtendedTropical{2}([2.2,3.1]), ExtendedTropical{2}([-1.0, 4.0]), ExtendedTropical{2}([-Inf, 0.6])),
3738
(CountingTropicalF64(5, 3), CountingTropicalF64(3, 9), CountingTropicalF64(-3, 2)),
3839
(CountingTropical(5.0, ConfigSampler(StaticBitVector(rand(Bool, 10)))), CountingTropical(3.0, ConfigSampler(StaticBitVector(rand(Bool, 10)))), CountingTropical(-3.0, ConfigSampler(StaticBitVector(rand(Bool, 10))))),
3940
(CountingTropical(5.0, ConfigEnumerator([StaticBitVector(rand(Bool, 10)) for j=1:3])), CountingTropical(3.0, ConfigEnumerator([StaticBitVector(rand(Bool, 10)) for j=1:4])), CountingTropical(-3.0, ConfigEnumerator([StaticBitVector(rand(Bool, 10)) for j=1:5]))),
@@ -97,6 +98,14 @@ end
9798
x = ConfigSampler(bv"00111")
9899
@test x ^ 0 == one(x)
99100
@test x ^ 2.0 == x
101+
102+
x = ExtendedTropical{3}([1.0, 2.0, 3.0])
103+
@test x ^ 1 == x
104+
@test x ^ 0 == one(x)
105+
@test x ^ 1.0 == x
106+
@test x ^ 0.0 == one(x)
107+
@test x ^ 2 == ExtendedTropical{3}([2.0, 4.0, 6.0])
108+
@test x ^ 2.0 == ExtendedTropical{3}([2.0, 4.0, 6.0])
100109
end
101110

102111
@testset "push coverage" begin
@@ -113,4 +122,32 @@ end
113122
@test copy(y) !== y
114123
@test !iszero(y)
115124
println((x * x) * zero(x))
125+
end
126+
127+
@testset "Truncated Tropical" begin
128+
# +
129+
a = ExtendedTropical{3}([1,2,3])
130+
b = ExtendedTropical{3}([4,5,6])
131+
c = ExtendedTropical{3}([0,1,2])
132+
@test a + b == ExtendedTropical{3}([4,5,6])
133+
@test b + a == ExtendedTropical{3}([4,5,6])
134+
@test c + a == ExtendedTropical{3}([2,2,3])
135+
@test a + c == ExtendedTropical{3}([2,2,3])
136+
137+
# *
138+
function naive_mul(a, b)
139+
K = length(a)
140+
return sort!(vec([x+y for x in a, y in b]))[end-K+1:end]
141+
end
142+
d = ExtendedTropical{3}([0,1,20])
143+
@test naive_mul(a.orders, b.orders) == (a * b).orders
144+
@test naive_mul(b.orders, a.orders) == (b * a).orders
145+
@test naive_mul(a.orders, d.orders) == (a * d).orders
146+
@test naive_mul(d.orders, a.orders) == (d * a).orders
147+
@test naive_mul(d.orders, d.orders) == (d * d).orders
148+
for i=1:20
149+
a = ExtendedTropical{100}(sort!(randn(100)))
150+
b = ExtendedTropical{100}(sort!(randn(100)))
151+
@test naive_mul(a.orders, b.orders) == (a * b).orders
152+
end
116153
end

test/interfaces.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,11 @@ end
141141
res4b = solve(gp, ConfigsMax(1; bounded=true))[]
142142
@test res4 == res4b
143143
@test res4.c.data[] == res3.c.data
144+
145+
g = Graphs.smallgraph("petersen")
146+
gp = IndependentSet(g; weights=fill(0.5, 10))
147+
res5 = solve(gp, SizeMax(6))[]
148+
@test res5.orders == [3.0,4,4,4,4,4] ./ 2
144149
end
145150

146151
@testset "tree storage" begin
@@ -184,7 +189,7 @@ end
184189
@testset "memory estimation" begin
185190
gp = IndependentSet(smallgraph(:petersen))
186191
for property in [
187-
SizeMax(), SizeMin(), CountingMax(), CountingMin(), CountingMax(2), CountingMin(2),
192+
SizeMax(), SizeMin(), SizeMax(3), SizeMin(3), CountingMax(), CountingMin(), CountingMax(2), CountingMin(2),
188193
ConfigsMax(;bounded=true), ConfigsMin(;bounded=true), ConfigsMax(2;bounded=true), ConfigsMin(2;bounded=true),
189194
ConfigsMax(;bounded=false), ConfigsMin(;bounded=false), ConfigsMax(2;bounded=false), ConfigsMin(2;bounded=false), SingleConfigMax(;bounded=false), SingleConfigMin(;bounded=false),
190195
CountingAll(), ConfigsAll(),

test/networks/MaximalIS.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ end
2323
@testset "counting minimum maximal IS" begin
2424
g = smallgraph(:tutte)
2525
res = solve(MaximalIS(g), SizeMin())[]
26+
res2 = solve(MaximalIS(g), SizeMin(10))[]
2627
poly = solve(MaximalIS(g), GraphPolynomial())[]
2728
@test poly == Polynomial([fill(0.0, 13)..., 2, 150, 7510, 71669, 66252, 14925, 571])
2829
@test res.n == 13
30+
@test res2.orders == [13, 13, fill(14, 8)...]
2931
@test solve(MaximalIS(g), CountingMin())[].c == 2
3032
min2 = solve(MaximalIS(g), CountingMin(3))[]
3133
@test min2.maxorder == 15

0 commit comments

Comments
 (0)