Skip to content

Commit ec3e9d9

Browse files
authored
Memory estimation (#22)
* update * estimate memory
1 parent 83c25b4 commit ec3e9d9

File tree

8 files changed

+138
-26
lines changed

8 files changed

+138
-26
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ end
5252

5353
## Examples
5454

55-
You can find many examples in the documentation, a good one to start with is [solving the independent set problem](https://psychic-meme-f4d866f8.pages.github.io/dev/tutorials/IndependentSet/)
55+
You can find many examples in the documentation, a good one to start with is [solving the independent set problem](https://psychic-meme-f4d866f8.pages.github.io/dev/tutorials/IndependentSet/).
5656

5757
## Supporting and Citing
5858

docs/src/performancetips.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,17 @@ julia> sizeof(TruncatedPoly{5,Float64,Float64})
4949
48
5050
```
5151

52+
One can use [`estimate_memory`](@ref) to get a good estimation of peak memory in bytes.
53+
```julia
54+
julia> estimate_memory(problem, GraphPolynomial(; method=:finitefield))
55+
297616
56+
57+
julia> estimate_memory(problem, GraphPolynomial(; method=:polynomial))
58+
71427840
59+
```
60+
It means one only needs 298 KB memory to find the graph polynomial with the finite field approach,
61+
but needs 71 MB memory to find the graph polynomial using the [`Polynomial`](@ref) type.
62+
5263
!!! note
5364
* The actual run time memory can be several times larger than the size of the maximum tensor.
5465
There is no constant bound for the factor, an empirical value for it is 3x.

docs/src/ref.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ getixsv
105105
getiyv
106106
timespace_complexity
107107
timespacereadwrite_complexity
108+
estimate_memory
108109
@ein_str
109110
GreedyMethod
110111
TreeSA

src/GraphTensorNetworks.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ using Graphs
1111
export timespace_complexity, timespacereadwrite_complexity, @ein_str, getixsv, getiyv
1212
export GreedyMethod, TreeSA, SABipartite, KaHyParBipartite, MergeVectors, MergeGreedy
1313

14+
# estimate memory
15+
export estimate_memory
16+
1417
# Algebras
1518
export StaticBitVector, StaticElementVector, @bv_str
1619
export is_commutative_semiring

src/configurations.jl

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,19 @@ Find optimal solutions with bounding.
2020
* If `invert` is true, find the minimum.
2121
* If `tree_storage` is true, use [`TreeConfigEnumerator`](@ref) as the storage of solutions.
2222
"""
23-
function best_solutions(gp::GraphProblem; all=false, usecuda=false, invert=false, tree_storage::Bool=false)
23+
function best_solutions(gp::GraphProblem; all=false, usecuda=false, invert=false, tree_storage::Bool=false, T=Float64)
2424
if all && usecuda
2525
throw(ArgumentError("ConfigEnumerator can not be computed on GPU!"))
2626
end
27-
xst = generate_tensors(_x(TropicalF64; invert), gp)
27+
xst = generate_tensors(_x(Tropical{T}; invert), gp)
2828
ymask = trues(fill(2, length(getiyv(gp.code)))...)
2929
if usecuda
3030
xst = CuArray.(xst)
3131
ymask = CuArray(ymask)
3232
end
3333
if all
34-
# we use `Float64` types because we want to support weighted graphs.
35-
T = config_type(CountingTropical{Float64,Float64}, length(labels(gp)), nflavor(gp); all, tree_storage)
34+
# we use `Float64` as default because we want to support weighted graphs.
35+
T = config_type(CountingTropical{T,T}, length(labels(gp)), nflavor(gp); all, tree_storage)
3636
xs = generate_tensors(_x(T; invert), gp)
3737
ret = bounding_contract(AllConfigs{1}(), gp.code, xst, ymask, xs)
3838
return invert ? post_invert_exponent.(ret) : ret
@@ -71,12 +71,12 @@ end
7171
7272
Finding optimal and suboptimal solutions.
7373
"""
74-
best2_solutions(gp::GraphProblem; all=true, usecuda=false, invert::Bool=false) = solutions(gp, Max2Poly{Float64,Float64}; all, usecuda, invert)
74+
best2_solutions(gp::GraphProblem; all=true, usecuda=false, invert::Bool=false, T=Float64) = solutions(gp, Max2Poly{T,T}; all, usecuda, invert)
7575

76-
function bestk_solutions(gp::GraphProblem, k::Int; invert::Bool=false, tree_storage::Bool=false)
77-
xst = generate_tensors(_x(TropicalF64; invert), gp)
76+
function bestk_solutions(gp::GraphProblem, k::Int; invert::Bool=false, tree_storage::Bool=false, T=Float64)
77+
xst = generate_tensors(_x(Tropical{T}; invert), gp)
7878
ymask = trues(fill(2, length(getiyv(gp.code)))...)
79-
T = config_type(TruncatedPoly{k,Float64,Float64}, length(labels(gp)), nflavor(gp); all=true, tree_storage)
79+
T = config_type(TruncatedPoly{k,T,T}, length(labels(gp)), nflavor(gp); all=true, tree_storage)
8080
xs = generate_tensors(_x(T; invert), gp)
8181
ret = bounding_contract(AllConfigs{k}(), gp.code, xst, ymask, xs)
8282
return invert ? post_invert_exponent.(ret) : ret
@@ -88,7 +88,7 @@ end
8888
Finding all solutions grouped by size.
8989
e.g. when the problem is [`MaximalIS`](@ref), it computes all maximal independent sets, or the maximal cliques of it complement.
9090
"""
91-
all_solutions(gp::GraphProblem) = solutions(gp, Polynomial{Float64,:x}, all=true, usecuda=false, tree_storage=false)
91+
all_solutions(gp::GraphProblem; T=Float64) = solutions(gp, Polynomial{T,:x}, all=true, usecuda=false, tree_storage=false)
9292

9393
function _onehotv(::Type{Polynomial{BS,X}}, x, v) where {BS,X}
9494
Polynomial{BS,X}([onehotv(BS, x, v)])

src/graph_polynomials.jl

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ using FFTW
44
using Graphs
55

66
"""
7-
graph_polynomial(problem, method; usecuda=false, kwargs...)
7+
graph_polynomial(problem, method; usecuda=false, T=Float64, kwargs...)
88
99
Computing the graph polynomial for specific problem.
1010
11+
Positional Arguments
12+
----------------------------------
1113
* `problem` can be one of the following instances,
1214
* `IndependentSet` for the independence polynomial,
1315
* `MaximalIS` for the maximal independence polynomial,
@@ -23,27 +25,32 @@ Computing the graph polynomial for specific problem.
2325
It Consumes additional kwargs [`maxorder`, `r`]. The larger `r` is,
2426
the more accurate the factors of high order terms, and the less accurate the factors of low order terms.
2527
* `Val(:fitting)`, compute with the polynomial fitting approach, fast but inaccurate for large graphs.
28+
29+
Keyword Arguments
30+
----------------------------------
31+
* `usecuda` is true if one wants to compute with CUDA arrays,
32+
* `T` is the base type
2633
"""
2734
function graph_polynomial end
2835

29-
function graph_polynomial(gp::GraphProblem, ::Val{:fft}; usecuda=false,
36+
function graph_polynomial(gp::GraphProblem, ::Val{:fft}; usecuda=false, T=Float64,
3037
maxorder=max_size(gp; usecuda=usecuda), r=1.0)
3138
ω = exp(-2im*π/(maxorder+1))
3239
xs = r .* collect.^ (0:maxorder))
33-
ys = [Array(contractx(gp, x; usecuda=usecuda)) for x in xs]
40+
ys = [Array(contractx(gp, Complex{T}(x); usecuda=usecuda)) for x in xs]
3441
map(ci->Polynomial(ifft(getindex.(ys, Ref(ci))) ./ (r .^ (0:maxorder))), CartesianIndices(ys[1]))
3542
end
3643

37-
function graph_polynomial(gp::GraphProblem, ::Val{:fitting}; usecuda=false,
44+
function graph_polynomial(gp::GraphProblem, ::Val{:fitting}; usecuda=false, T=Float64,
3845
maxorder = max_size(gp; usecuda=usecuda))
3946
xs = (0:maxorder)
40-
ys = [Array(contractx(gp, x; usecuda=usecuda)) for x in xs]
47+
ys = [Array(contractx(gp, T(x); usecuda=usecuda)) for x in xs]
4148
map(ci->fit(xs, getindex.(ys, Ref(ci))), CartesianIndices(ys[1]))
4249
end
4350

44-
function graph_polynomial(gp::GraphProblem, ::Val{:polynomial}; usecuda=false)
51+
function graph_polynomial(gp::GraphProblem, ::Val{:polynomial}; usecuda=false, T=Float64)
4552
@assert !usecuda "Polynomial type can not be computed on GPU!"
46-
contractx(gp::GraphProblem, Polynomial([0, 1.0]))
53+
contractx(gp::GraphProblem, Polynomial(T[0, 1]))
4754
end
4855

4956
function _polynomial_single(gp::GraphProblem, ::Type{T}; usecuda, maxorder) where T
@@ -60,7 +67,8 @@ function _polynomial_single(gp::GraphProblem, ::Type{T}; usecuda, maxorder) wher
6067
return res
6168
end
6269

63-
function graph_polynomial(gp::GraphProblem, ::Val{:finitefield}; usecuda=false,
70+
# T is not used in finitefield approach
71+
function graph_polynomial(gp::GraphProblem, ::Val{:finitefield}; usecuda=false, T=Float64,
6472
maxorder=max_size(gp; usecuda=usecuda), max_iter=100)
6573
return map(Polynomial, big_integer_solve(T->_polynomial_single(gp, T; usecuda=usecuda, maxorder=maxorder), Int32, max_iter))
6674
end

src/interfaces.jl

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ Method Argument
9090
* It accepts keyword arguments `maxorder` (optional) and `r`,
9191
if `r > 1`, one has better precision for coefficients of large order, if `r < 1`,
9292
one has better precision for coefficients of small order.
93+
* `:fitting`, fit the polynomial directly.
94+
* The corresponding tensor element type is floating point numbers like `Base.Float64`.
95+
* It has round-off error.
96+
* BLAS and GPU are supported, it is the fastest among all methods.
9397
9498
Graph polynomials are not defined for weighted graph problems.
9599
"""
@@ -237,7 +241,7 @@ function solve(gp::GraphProblem, property::AbstractProperty; T=Float64, usecuda=
237241
elseif property isa CountingMin
238242
return post_invert_exponent.(contractx(gp, pre_invert_exponent(TruncatedPoly(ntuple(i->i == min_k(property) ? one(T) : zero(T), min_k(property)), one(T))); usecuda=usecuda))
239243
elseif property isa GraphPolynomial
240-
return graph_polynomial(gp, Val(graph_polynomial_method(property)); usecuda=usecuda, property.kwargs...)
244+
return graph_polynomial(gp, Val(graph_polynomial_method(property)); usecuda=usecuda, T=T, property.kwargs...)
241245
elseif property isa SingleConfigMax{false}
242246
return solutions(gp, CountingTropical{T,T}; all=false, usecuda=usecuda, )
243247
elseif property isa SingleConfigMin{false}
@@ -253,19 +257,19 @@ function solve(gp::GraphProblem, property::AbstractProperty; T=Float64, usecuda=
253257
elseif property isa ConfigsAll
254258
return solutions(gp, Real; all=true, usecuda=usecuda, tree_storage=tree_storage(property))
255259
elseif property isa SingleConfigMax{true}
256-
return best_solutions(gp; all=false, usecuda=usecuda)
260+
return best_solutions(gp; all=false, usecuda=usecuda, T=T)
257261
elseif property isa SingleConfigMin{true}
258-
return best_solutions(gp; all=false, usecuda=usecuda, invert=true)
262+
return best_solutions(gp; all=false, usecuda=usecuda, invert=true, T=T)
259263
elseif property isa ConfigsMax{1,true}
260-
return best_solutions(gp; all=true, usecuda=usecuda, tree_storage=tree_storage(property))
264+
return best_solutions(gp; all=true, usecuda=usecuda, tree_storage=tree_storage(property), T=T)
261265
elseif property isa ConfigsMin{1,true}
262-
return best_solutions(gp; all=true, usecuda=usecuda, invert=true, tree_storage=tree_storage(property))
266+
return best_solutions(gp; all=true, usecuda=usecuda, invert=true, tree_storage=tree_storage(property), T=T)
263267
elseif property isa (ConfigsMax{K,true} where K)
264-
return bestk_solutions(gp, max_k(property), tree_storage=tree_storage(property))
268+
return bestk_solutions(gp, max_k(property), tree_storage=tree_storage(property), T=T)
265269
elseif property isa (ConfigsMin{K,true} where K)
266-
return bestk_solutions(gp, min_k(property), invert=true, tree_storage=tree_storage(property))
270+
return bestk_solutions(gp, min_k(property), invert=true, tree_storage=tree_storage(property), T=T)
267271
else
268-
error("unknown property $property.")
272+
error("unknown property: `$property`.")
269273
end
270274
end
271275

@@ -379,3 +383,61 @@ end
379383
# convert to Matrix
380384
Base.Matrix(ce::ConfigEnumerator) = plain_matrix(ce)
381385
Base.Vector(ce::StaticElementVector) = collect(ce)
386+
387+
########## memory estimation ###############
388+
"""
389+
estimate_memory(problem, property; T=Float64)
390+
391+
Memory estimation in number of bytes to compute certain `property` of a `problem`.
392+
`T` is the base type.
393+
"""
394+
function estimate_memory(problem::GraphProblem, property::AbstractProperty; T=Float64)
395+
_estimate_memory(tensor_element_type(T, length(labels(problem)), nflavor(problem), property), problem)
396+
end
397+
function estimate_memory(problem::GraphProblem, ::Union{SingleConfigMax{true},SingleConfigMin{true}}; T=Float64)
398+
tc, sc, rw = timespacereadwrite_complexity(problem.code, _size_dict(problem))
399+
# caching all tensors is equivalent to counting the total number of writes
400+
return ceil(Int, exp2(rw - 1)) * sizeof(Tropical{T})
401+
end
402+
function estimate_memory(problem::GraphProblem, ::GraphPolynomial{:polynomial}; T=Float64)
403+
# this is the upper bound
404+
return peak_memory(problem.code, _size_dict(problem)) * (sizeof(T) * length(labels(problem)))
405+
end
406+
407+
function _size_dict(problem)
408+
lbs = labels(problem)
409+
nf = nflavor(problem)
410+
return Dict([lb=>nf for lb in lbs])
411+
end
412+
413+
function _estimate_memory(::Type{ET}, problem::GraphProblem) where ET
414+
if !isbitstype(ET) && !(ET <: Mod)
415+
@warn "Target tensor element type `$ET` is not a bits type, the estimation of memory might be unreliable."
416+
end
417+
return peak_memory(problem.code, _size_dict(problem)) * sizeof(ET)
418+
end
419+
420+
for (PROP, ET) in [(:SizeMax, :(Tropical{T})), (:SizeMin, :(Tropical{T})),
421+
(:(SingleConfigMax{true}), :(Tropical{T})), (:(SingleConfigMin{true}), :(Tropical{T})),
422+
(:(CountingAll), :T), (:(CountingMax{1}), :(CountingTropical{T,T})), (:(CountingMin{1}), :(CountingTropical{T,T})),
423+
(:(CountingMax{K}), :(TruncatedPoly{K,T,T})), (:(CountingMin{K}), :(TruncatedPoly{K,T,T})),
424+
(:(GraphPolynomial{:finitefield}), :(Mod{N,Int32} where N)), (:(GraphPolynomial{:fft}), :(Complex{T})),
425+
(:(GraphPolynomial{:polynomial}), :(Polynomial{T, :x})), (:(GraphPolynomial{:fitting}), :T),
426+
]
427+
@eval tensor_element_type(::Type{T}, n::Int, nflavor::Int, ::$PROP) where {T,K} = $ET
428+
end
429+
430+
for (PROP, ET) in [(:(SingleConfigMax{false}), :(CountingTropical{T,T})), (:(SingleConfigMin{false}), :(CountingTropical{T,T}))]
431+
@eval function tensor_element_type(::Type{T}, n::Int, nflavor::Int, ::$PROP) where {T}
432+
sampler_type($ET, n, nflavor)
433+
end
434+
end
435+
for (PROP, ET) in [
436+
(:(ConfigsMax{1}), :(CountingTropical{T,T})), (:(ConfigsMin{1}), :(CountingTropical{T,T})),
437+
(:(ConfigsMax{K}), :(TruncatedPoly{K,T,T})), (:(ConfigsMin{K}), :(TruncatedPoly{K,T,T})),
438+
(:(ConfigsAll), :(Real))
439+
]
440+
@eval function tensor_element_type(::Type{T}, n::Int, nflavor::Int, ::$PROP) where {T,K}
441+
set_type($ET, n, nflavor)
442+
end
443+
end

test/interfaces.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,4 +179,31 @@ end
179179
@test length(res1) == length(res2)
180180
@test Set(res2 |> collect) == Set(res1 |> collect)
181181
end
182+
end
183+
184+
@testset "memory estimation" begin
185+
gp = IndependentSet(smallgraph(:petersen))
186+
for property in [
187+
SizeMax(), SizeMin(), CountingMax(), CountingMin(), CountingMax(2), CountingMin(2),
188+
ConfigsMax(;bounded=true), ConfigsMin(;bounded=true), ConfigsMax(2;bounded=true), ConfigsMin(2;bounded=true),
189+
ConfigsMax(;bounded=false), ConfigsMin(;bounded=false), ConfigsMax(2;bounded=false), ConfigsMin(2;bounded=false), SingleConfigMax(;bounded=false), SingleConfigMin(;bounded=false),
190+
CountingAll(), ConfigsAll(),
191+
]
192+
@show property
193+
ET = GraphTensorNetworks.tensor_element_type(Float32, 10, 2, property)
194+
@test eltype(solve(gp, property, T=Float32)) <: ET
195+
@test estimate_memory(gp, property) isa Integer
196+
end
197+
@test GraphTensorNetworks.tensor_element_type(Float32, 10, 2, GraphPolynomial(method=:polynomial)) == Polynomial{Float32, :x}
198+
@test sizeof(GraphTensorNetworks.tensor_element_type(Float32, 10, 2, GraphPolynomial(method=:fitting))) == 4
199+
@test sizeof(GraphTensorNetworks.tensor_element_type(Float32, 10, 2, GraphPolynomial(method=:fft))) == 8
200+
@test sizeof(GraphTensorNetworks.tensor_element_type(Float64, 10, 2, GraphPolynomial(method=:finitefield))) == 4
201+
@test GraphTensorNetworks.tensor_element_type(Float32, 10, 2, SingleConfigMax(;bounded=true)) == Tropical{Float32}
202+
@test GraphTensorNetworks.tensor_element_type(Float32, 10, 2, SingleConfigMin(;bounded=true)) == Tropical{Float32}
203+
204+
@test estimate_memory(gp, SizeMax()) * 2 == estimate_memory(gp, CountingMax())
205+
@test estimate_memory(gp, SingleConfigMax(bounded=true)) > estimate_memory(gp, SingleConfigMax(bounded=false))
206+
@test estimate_memory(gp, ConfigsMax(bounded=true)) == estimate_memory(gp, SingleConfigMax(bounded=false))
207+
@test estimate_memory(gp, GraphPolynomial(method=:fitting); T=Float32) * 4 == estimate_memory(gp, GraphPolynomial(method=:fft))
208+
@test estimate_memory(gp, GraphPolynomial(method=:finitefield)) * 10 == estimate_memory(gp, GraphPolynomial(method=:polynomial); T=Float32)
182209
end

0 commit comments

Comments
 (0)