diff --git a/README.md b/README.md index 317eef2..45e7c0d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![CI](https://github.com/guo-yong-zhi/Stuffing.jl/actions/workflows/ci.yml/badge.svg)](https://github.com/guo-yong-zhi/Stuffing.jl/actions/workflows/ci.yml) [![CI-nightly](https://github.com/guo-yong-zhi/Stuffing.jl/actions/workflows/ci-nightly.yml/badge.svg)](https://github.com/guo-yong-zhi/Stuffing.jl/actions/workflows/ci-nightly.yml) [![codecov](https://codecov.io/gh/guo-yong-zhi/Stuffing.jl/branch/main/graph/badge.svg?token=43TOrL25V7)](https://codecov.io/gh/guo-yong-zhi/Stuffing.jl) [![DOI](https://zenodo.org/badge/349631351.svg)](https://zenodo.org/badge/latestdoi/349631351) This's an algorithm for solving **2D irregular nesting problems** (also known as cutting problems or packing problems). The algorithm accepts arbitrary **binary raster masks** as inputs and is good at handling the nesting problems of many gadgets. The implementation is based on quadtree & gradient optimization. Also, it can be parallelized if you start `julia` with `julia --threads k`. This package is used by [WordCloud.jl](https://github.com/guo-yong-zhi/WordCloud.jl). -Examples: [collision detection](./examples/collision.jl), [packing](./examples/packing.jl) +Examples: [collision detection](./examples/collision.jl), [dynamic collision detection](./examples/dynamiccollisions.jl), [packing](./examples/packing.jl) Benchmark: [benchmark](https://github.com/guo-yong-zhi/WordCloud/blob/master/examples/benchmark.jl) *** ``` diff --git a/examples/dynamiccollisions.jl b/examples/dynamiccollisions.jl new file mode 100644 index 0000000..9ec2d1e --- /dev/null +++ b/examples/dynamiccollisions.jl @@ -0,0 +1,40 @@ +using Stuffing +qts = Stuffing.randqtrees(300); +println("visualization:") +println(repr("text/plain", overlap(qts))) + +println("""To get all collisions via `partialcollisions`, the set `updated` should contains all collided labels in last round. + And the labels of other updated objects should be contained too. + `partialcollisions` is faster than `totalcollisions` when `updated` is small. + """) +updated = Set(1:length(qts)); +sptree = linked_spacial_qtree(qts); +for i in 1:10 + C1 = partialcollisions(qts, sptree, updated); + union!(updated, first.(C1) |> Iterators.flatten) #all collided labels + C2 = QTrees.totalcollisions_native(qts); + @assert length(C1) == length(C2) + @assert Set(Set.(first.(C1))) == Set(Set.(first.(C2))) + Stuffing.Trainer.batchsteps!(qts, C1) + QTrees.shift!(qts[3], 1, 1, 1) + QTrees.shift!(qts[7], 1, -1, -1) + union!(updated, [3, 7]) #other updated labels +end +println("And things are similar for `dynamiccollisions`. ") +println("`dynamiccollisions` is faster than `partialcollisions` when `updated` is not that small.") +updated = UpdatedSet(length(qts)); +sptree = linked_spacial_qtree(qts); +for i in 1:10 + C1 = dynamiccollisions(qts, sptree, updated); + union!(updated, first.(C1) |> Iterators.flatten) #all collided labels + C2 = QTrees.totalcollisions_native(qts); + C3 = QTrees.totalcollisions(qts); + #sometimes C2!=C3. When objects are out of the scope, the `totalcollisions_native` will miss them. + #But `totalcollisions` may not (not promise). + @assert length(C1) == length(C2) || length(C1) == length(C3) + @assert Set(Set.(first.(C1))) == Set(Set.(first.(C2))) || Set(Set.(first.(C1))) == Set(Set.(first.(C3))) + Stuffing.Trainer.batchsteps!(qts, C1) + QTrees.shift!(qts[3], 1, -1, -1) + QTrees.shift!(qts[7], 1, 1, 1) + union!(updated, [3, 7]) #other updated labels +end \ No newline at end of file diff --git a/src/Stuffing.jl b/src/Stuffing.jl index c9e6792..a0a77fc 100644 --- a/src/Stuffing.jl +++ b/src/Stuffing.jl @@ -1,7 +1,7 @@ module Stuffing export qtree, maskqtree, qtrees, place!, overlap!, overlap, getpositions, setpositions!, packing, packing! export QTrees, getshift, getcenter, setshift!, setcenter!, outofbounds, outofkernelbounds, - partialcollisions, totalcollisions, collision, findroom_uniform, findroom_gathering, + dynamiccollisions, partialcollisions, totalcollisions, collision, findroom_uniform, findroom_gathering, UpdatedSet, locate!, linked_spacial_qtree, hash_spacial_qtree export Trainer, train!, fit!, Momentum, SGD include("linkedlist.jl") diff --git a/src/fit.jl b/src/fit.jl index c016741..fd8d7be 100644 --- a/src/fit.jl +++ b/src/fit.jl @@ -66,7 +66,7 @@ function step_index!(qtrees, i1, i2, collisionpoint, optimiser) end end -function batchsteps!(qtrees, colist::Vector{QTrees.CoItem}, optimiser) +function batchsteps!(qtrees, colist::Vector{QTrees.CoItem}, optimiser=(t, Δ) -> Δ ./ 6) for ((i1, i2), cp) in shuffle!(colist) # @show cp # @assert cp[1] > 0 diff --git a/test/test_qtrees.jl b/test/test_qtrees.jl index ed1716c..a3aeef1 100644 --- a/test/test_qtrees.jl +++ b/test/test_qtrees.jl @@ -67,7 +67,34 @@ testqtree = Stuffing.testqtree c1 = QTrees.partialcollisions(qts, sptree, Set(labels)) c2set = Set([Set(p) for p in first.(QTrees.totalcollisions(qts)) if !isdisjoint(p, labels)]) @test c2set == Set(Set.(first.(c1))) - + # dynamic + updated = Set(1:length(qts)); + sptree = linked_spacial_qtree(qts); + for i in 1:10 + C1 = partialcollisions(qts, sptree, updated); + union!(updated, first.(C1) |> Iterators.flatten) #all collided labels + C2 = QTrees.totalcollisions_native(qts); + @test length(C1) == length(C2) + @test Set(Set.(first.(C1))) == Set(Set.(first.(C2))) + Stuffing.Trainer.batchsteps!(qts, C1) + QTrees.shift!(qts[3], 1, 1, 1) + QTrees.shift!(qts[7], 1, -1, -1) + union!(updated, [3, 7]) #other updated labels + end + updated = UpdatedSet(length(qts)); + sptree = linked_spacial_qtree(qts); + for i in 1:10 + C1 = dynamiccollisions(qts, sptree, updated); + union!(updated, first.(C1) |> Iterators.flatten) #all collided labels + C2 = QTrees.totalcollisions_native(qts); + C3 = QTrees.totalcollisions(qts); + @test length(C1) == length(C2) || length(C1) == length(C3) + @test Set(Set.(first.(C1))) == Set(Set.(first.(C2))) || Set(Set.(first.(C1))) == Set(Set.(first.(C3))) + Stuffing.Trainer.batchsteps!(qts, C1) + QTrees.shift!(qts[3], 1, -1, -1) + QTrees.shift!(qts[7], 1, 1, 1) + union!(updated, [3, 7]) #other updated labels + end #edge cases # batch @test QTrees.totalcollisions_spacial(Vector{QTrees.U8SQTree}()) |> isempty