diff --git a/README.md b/README.md index b19e81aa..4c96c120 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ add("LinearAlgebraicRepresentation") ## Documentation -Go to [the documentation page](https://cvdlab.github.io/LinearAlgebraicRepresentation.jl/) +Go to [the documentation page](https://marteresagh.github.io/LinearAlgebraicRepresentation.jl/) ## Authors * [Giulio Martella](https://github.com/giuliom95) diff --git a/docs/src/arrangement.md b/docs/src/arrangement.md index 528460ef..ee9aeeeb 100644 --- a/docs/src/arrangement.md +++ b/docs/src/arrangement.md @@ -9,12 +9,25 @@ This operation can be seen as a boolean union of two cellular complexes. Here an ![10 x 10 Cube](./images/cube10x10.jpg) > **Figure 1:** Arrangement of ``2000=2\times10\times10\times10`` cubes +## Graph of recall functions +A graph is an ordered pair ``G(V,E)`` comprising a set ``V`` of vertices, nodes or points together with a set ``E`` of edges, arcs or lines, which are ``2``-elements subsets of ``V``. In this case, ``V`` is the set of functions of this sub-module and let ``v1``, ``v2`` be elements of ``V``, then ``e=v1v2`` ``\in`` ``E`` if ``v1`` calls ``v2``. +In the following graph the functions are arranged in colored boxes: +![dependency graph](./images/dependency_graph.png) +- yellow boxes is for `spatial_arrangement` +- red boxes is for `planar_arrangement` +- green boxes is for `minimal_cycles` +- blue boxes is for `dimension_travel` + +Functions in darker boxes are declared only in the local scope of its father. + ## API Every function strictly relative to the arrangement has been collected in the `Lar.Arrangement` sub-module but the two main functions are accessible directly from the `LinearAlgebraicRepresentation` namespace. + !!! warning `Lar.Arrangement` is the only place in `LinearAlgebraicRepresentation` where `Point` matrices store points per row and not per column as described in the documentation of `Lar.Points` + ```@docs Lar.spatial_arrangement Lar.planar_arrangement @@ -47,14 +60,14 @@ the spatial index: it is a mapping ``\mathcal{I}(\sigma)`` from a cell where the ``box`` function provides the axis aligned bounding box (AABB) of a cell [fig. 2, c, ``\sigma`` in red and ``\mathcal{I}(\sigma)`` in blue]. The spatial arrangement calculation is speeded up by storing the AABBs as dimensional wise intervals -into an interval tree \cite{interval_trees}. +into an interval tree ``\cite{interval_trees}``. Now for each cell ``\sigma`` we transform ``\sigma \cup \mathcal{I}(\sigma)`` -in a way that ``\sigma`` lays on the ``x_3=0`` plane [fig. 2, d] and we find the intersections -of the ``\mathcal{I}(\sigma)`` cells with ``x_3=0`` plane. So we have a "soup" +in a way that ``\sigma`` lays on the ``x_3=0`` plane [fig. 2, d] and we find the intersections +of the ``\mathcal{I}(\sigma)`` cells with ``x_3=0`` plane. So we have a "soup" of 1-cells in ``\mathbb{E}^2`` [fig. 2, e], and we fragment each 1-cell with every other cell obtaining a valid 1-skeleton [fig. 2, f]. From this data it is possible to build the 2-cells using the ALGORITHM 1 -presented and explored by Paoluzzi et al. \cite{Paoluzzi} +presented and explored by Paoluzzi et al. ``\cite{Paoluzzi}`` [fig. 2, g, exploded]. The procedure to fragment 1-cells on a plane and return a 2-complex is called *planar arrangement*. When the planar arrangement is complete, fragmented ``\sigma`` can be transformed back to its original position @@ -97,4 +110,78 @@ cell 2 has cell 1 as an hole]. !!! note A 2-cell with a non-intersecting shell can be trivially defined as a "face with holes"; the correct definition is that it cannot - be shrunk to the dimension of a point. \ No newline at end of file + be shrunk to the dimension of a point. + +## Main Interface + +### Dimension travel + +### Minimal cycles + +```@docs +Lar.Arrangement.minimal_2cycles +``` + +```@docs +Lar.Arrangement.minimal_3cycles +``` + +### Planar arrangement + +```@docs +Lar.Arrangement.intersect_edges +``` + +```@docs +Lar.Arrangement.frag_edge +``` + +```@docs +Lar.Arrangement.merge_vertices! +``` + +```@docs +Lar.Arrangement.biconnected_components +``` + +```@docs +Lar.Arrangement.get_external_cycle +``` + +```@docs +Lar.Arrangement.pre_containment_test +``` + +```@docs +Lar.Arrangement.prune_containment_graph +``` + +```@docs +Lar.Arrangement.transitive_reduction! +``` + +```@docs +Lar.Arrangement.componentgraph +``` + +```@docs +Lar.Arrangement.cell_merging +``` + +```@docs +Lar.Arrangement.cleandecomposition +``` + +```@docs +Lar.Arrangement.planar_arrangement_1 +``` + +```@docs +Lar.Arrangement.planar_arrangement_2 +``` + +### Spatial arrangement + + + + diff --git a/docs/src/images/dependency_graph.png b/docs/src/images/dependency_graph.png new file mode 100644 index 00000000..7bafb21d Binary files /dev/null and b/docs/src/images/dependency_graph.png differ diff --git a/src/arrangement/minimal_cycles.jl b/src/arrangement/minimal_cycles.jl index 6ff94735..ce57cc0d 100644 --- a/src/arrangement/minimal_cycles.jl +++ b/src/arrangement/minimal_cycles.jl @@ -1,5 +1,11 @@ Lar = LinearAlgebraicRepresentation +""" + minimal_2cycles(V::Lar.Points, EV::Lar.ChainOp) + +Return all cycles of a 2D graph define by its vertices and edges. + +""" function minimal_2cycles(V::Lar.Points, EV::Lar.ChainOp) function edge_angle(v::Int, e::Int) @@ -14,12 +20,17 @@ function minimal_2cycles(V::Lar.Points, EV::Lar.ChainOp) EV[i, j] = -1 end VE = convert(Lar.ChainOp, SparseArrays.transpose(EV)) - +@show V +@show VE EF = Lar.Arrangement.minimal_cycles(edge_angle)(V, VE) return convert(Lar.ChainOp, SparseArrays.transpose(EF)) end +""" + minimal_3cycles(V::Lar.Points, EV::Lar.ChainOp, FE::Lar.ChainOp) + +""" function minimal_3cycles(V::Lar.Points, EV::Lar.ChainOp, FE::Lar.ChainOp) triangulated_faces = Array{Any, 1}(undef, FE.m) diff --git a/src/arrangement/planar_arrangement.jl b/src/arrangement/planar_arrangement.jl index ddcf7790..47f0af98 100644 --- a/src/arrangement/planar_arrangement.jl +++ b/src/arrangement/planar_arrangement.jl @@ -13,6 +13,42 @@ function frag_edge_channel(in_chan, out_chan, V, EV, bigPI) end end + +""" + frag_edge(V::Lar.Points, EV::Lar.ChainOp, edge_idx::Int, bigPI) + +Return vertices and edges after intersection. +# Example +```julia +julia> V = [ 0 0; 1 1; 1 0; 0 1]; + +julia> EV = Int8[ 1 1 0 0; + 0 0 1 1; + ]; + +julia> EV = sparse(EV); + +julia> model = (convert(Lar.Points,V'),Lar.cop2lar(EV)); + +julia> bigPI = Lar.spaceindex(model::Lar.LAR); + +julia> Lar.Arrangement.frag_edge(V, EV, 1, bigPI) +([0.0 0.0; 1.0 1.0; 0.5 0.5], + [1, 1] = 1 + [2, 2] = 1 + [1, 3] = 1 + [2, 3] = 1) + +julia> Lar.Arrangement.frag_edge(V, EV, 2, bigPI) +([1.0 0.0; 0.0 1.0; 0.5 0.5], + [1, 1] = 1 + [2, 2] = 1 + [1, 3] = 1 + [2, 3] = 1) + + +``` +""" function frag_edge(V::Lar.Points, EV::Lar.ChainOp, edge_idx::Int, bigPI) alphas = Dict{Float64, Int}() edge = EV[edge_idx, :] @@ -39,6 +75,27 @@ function frag_edge(V::Lar.Points, EV::Lar.ChainOp, edge_idx::Int, bigPI) return verts, ev end + +""" + intersect_edges(V::Lar.Points, edge1::Lar.Cell, edge2::Lar.Cell) + +Intersection of two edges. Return, if exist, points of intersection and parameter. + +# Example +```julia +julia> V=[0 0 ; 1 1; 1/2 0; 1/2 1]; + +julia> EV = SparseArrays.sparse(Array{Int8, 2}([ + [1 1 0 0] #1->1,2 + [0 0 1 1] #2->3,4 + ])); + +julia> Lar.Arrangement.intersect_edges(V, EV[1, :], EV[2, :]) +1-element Array{Tuple{Array{T,2} where T,Float64},1}: + ([0.5 0.5], 0.5) + +``` +""" function intersect_edges(V::Lar.Points, edge1::Lar.Cell, edge2::Lar.Cell) err = 10e-8 @@ -83,6 +140,36 @@ function intersect_edges(V::Lar.Points, edge1::Lar.Cell, edge2::Lar.Cell) return ret end +""" + merge_vertices!(V::Lar.Points, EV::Lar.ChainOp, edge_map, err=1e-4) + +If two or more vertices are very close, return one vertex and right edges. + +# Example +```julia +julia> p0 = 1e-2; + +julia> pm = 1-p0; + +julia> pp = 1+p0; + +julia> V = [ p0 p0; p0 -p0; + pp pm; pp pp + ]; + +julia> EV = Int8[1 0 1 0 ; + 0 1 0 1 ; + 1 0 0 1 ; + 0 1 1 0 ]; + +julia> EV = sparse(EV); + +julia> Lar.Arrangement.merge_vertices!(V, EV, [],1e-1) +([0.01 0.01; 1.01 0.99], + [1, 1] = 1 + [1, 2] = 1) +``` +""" function merge_vertices!(V::Lar.Points, EV::Lar.ChainOp, edge_map, err=1e-4) vertsnum = size(V, 1) edgenum = size(EV, 1) @@ -144,6 +231,29 @@ function merge_vertices!(V::Lar.Points, EV::Lar.ChainOp, edge_map, err=1e-4) return Lar.Points(nV), nEV end +""" + biconnected_components(EV::Lar.ChainOp) + +Find the biconnected components of a graph define by its edges. A biconnected component is a maximal biconnected subgraph. A biconnected graph has no **articulation vertices**. + +# Example +```julia +julia> EV = Int8[1 1 0 0 0 0; + 0 1 1 0 0 0; + 1 0 1 0 0 0; + 1 0 0 0 1 0; + 0 0 0 1 1 0; + 0 0 0 1 0 1; + 0 0 0 0 1 1] ; + +julia> EV = sparse(EV); + +julia> bc = Lar.Arrangement.biconnected_components(EV) +2-element Array{Array{Int64,1},1}: + [3, 2, 1] + [7, 6, 5] +``` +""" function biconnected_components(EV::Lar.ChainOp) ps = Array{Tuple{Int, Int, Int}, 1}() es = Array{Tuple{Int, Int}, 1}() @@ -240,6 +350,11 @@ function biconnected_components(EV::Lar.ChainOp) return bicon_comps end +""" + get_external_cycle(V::Lar.Points, EV::Lar.ChainOp, FE::Lar.ChainOp) + +Get the face's index of external cell in FE. +""" function get_external_cycle(V::Lar.Points, EV::Lar.ChainOp, FE::Lar.ChainOp) FV = abs.(FE)*EV vs = sparsevec(mapslices(sum, abs.(EV), dims=1)').nzind @@ -272,6 +387,13 @@ function get_external_cycle(V::Lar.Points, EV::Lar.ChainOp, FE::Lar.ChainOp) end end end + +""" + pre_containment_test(bboxes) + +Return containment graph. An element **(i,j)** is **1** if the **i-th** cell is contained in the **boundary box** of the **j-th** cell. + +""" function pre_containment_test(bboxes) n = length(bboxes) containment_graph = spzeros(Int8, n, n) @@ -286,6 +408,12 @@ function pre_containment_test(bboxes) return containment_graph end + +""" + prune_containment_graph(n, V, EVs, shells, graph) + +Check if the origin point of a cell is inside the face area of other cell in the graph. +""" function prune_containment_graph(n, V, EVs, shells, graph) for i in 1:n @@ -309,6 +437,27 @@ function prune_containment_graph(n, V, EVs, shells, graph) end return graph end + +""" + transitive_reduction!(graph) + +Remove elements from containment graph that can be compute for transitivity. + +# Example +```julia +julia> graph = [0 1 1 1 ; 0 0 1 1 ; 0 0 0 1 ; 0 0 0 0 ]; + +julia> Lar.Arrangement.transitive_reduction!(graph) + +julia> graph +4×4 Array{Int64,2}: + 0 1 0 0 + 0 0 1 0 + 0 0 0 1 + 0 0 0 0 + +``` +""" function transitive_reduction!(graph) n = size(graph, 1) for j in 1:n @@ -324,6 +473,12 @@ function transitive_reduction!(graph) end end +""" + cell_merging(n, containment_graph, V, EVs, boundaries, shells, shell_bboxes) + +Merge all cells. + +""" function cell_merging(n, containment_graph, V, EVs, boundaries, shells, shell_bboxes) function bboxes(V::Lar.Points, indexes::Lar.ChainOp) boxes = Array{Tuple{Any, Any}}(undef, indexes.n) @@ -379,7 +534,11 @@ function cell_merging(n, containment_graph, V, EVs, boundaries, shells, shell_bb return EV, FE end +""" + componentgraph(V, copEV, bicon_comps) +Return some properties of a graph, in order: `n`, `containment_graph`, `V`, `EVs`, `boundaries`, `shells`, `shell_bboxes`. +""" function componentgraph(V, copEV, bicon_comps) # arrangement of isolated components @@ -425,7 +584,12 @@ function componentgraph(V, copEV, bicon_comps) return n, containment_graph, V, EVs, boundaries, shells, shell_bboxes end +""" + cleandecomposition(V, copEV, sigma) + +Delete edges outside sigma area and remove dangling edges in 3D arrangement. +""" function cleandecomposition(V, copEV, sigma) # Deletes edges outside sigma area todel = [] @@ -578,7 +742,7 @@ function planar_arrangement_2(V, copEV, bicon_comps, # Topological Gift Wrapping n, containment_graph, V, EVs, boundaries, shells, shell_bboxes = - componentgraph(V, copEV, bicon_comps) + Lar.Arrangement.componentgraph(V, copEV, bicon_comps) @show containment_graph # only in the context of 3D arrangement if sigma.n > 0 diff --git a/test/planar_arr_mytest.jl b/test/planar_arr_mytest.jl new file mode 100644 index 00000000..2deb6c57 --- /dev/null +++ b/test/planar_arr_mytest.jl @@ -0,0 +1,222 @@ +using Test +using LinearAlgebraicRepresentation +Lar = LinearAlgebraicRepresentation +using Plasm,SparseArrays + + +@testset "intersect edges" begin + V=[0 0 ; 1 1; 1/2 0; 1/2 1]; + EV = SparseArrays.sparse(Array{Int8, 2}([ + [1 1 0 0] #1->1,2 + [0 0 1 1] #2->3,4 + ])); + + W=Lar.Arrangement.intersect_edges(V, EV[1, :], EV[2, :]) + @test W[1][1]==[0.5 0.5] + @test W[1][2]==0.5 + +end + +@testset "frag_edge" begin + V = [ 3 1; 6 8;2 -2;9 6;1 6;10 1] + + EV = Int8[ 1 1 0 0 0 0 ; + 0 0 1 1 0 0 ; + 0 0 0 0 1 1 ] + EV = sparse(EV) + edgenum = size(EV, 1) + rV = Lar.Points(zeros(0, 2)) + rEV = SparseArrays.spzeros(Int8, 0, 0) + model = (convert(Lar.Points,V'),Lar.cop2lar(EV)) + bigPI = Lar.spaceindex(model::Lar.LAR) + for i in 1:edgenum + v, ev = Lar.Arrangement.frag_edge(V, EV, i, bigPI) + rV, rEV = Lar.skel_merge(rV, rEV, v, ev) + end + + @test Lar.cop2lar(rEV) == [[1,3],[2,3],[4,6],[5,6],[7,10],[9,10],[8,9]] +end + + +@testset "merge_vertices!" begin + + @testset "close vertices" begin + V = [ 0.01 0.01; 0.01 -0.01; 1.01 0.99; 1.01 1.01] + EV = Int8[1 0 1 0 ; + 0 1 0 1 ; + 1 0 0 1 ; + 0 1 1 0 ] + EV = sparse(EV) + V, EV = Lar.Arrangement.merge_vertices!(V, EV, [],1e-1) + @test size(V,1)==2 + @test size(EV,1)==1 + end + + @testset "double vertices" begin + V=[0 0; 0 0; 1 1; 1 1; 2 2 ;2 2] + EV=Int8[1 0 1 0; + 0 1 0 1] + EV = sparse(EV) + V, EV = Lar.Arrangement.merge_vertices!(V, EV, []) + @test size(V,1)==3 + @test size(EV,1)==1 + end +end + + +@testset "biconnected_components" begin + @testset "Cycle graph" begin + EV = Int8[1 1 0 0; + 0 1 1 0; + 0 0 1 1; + 1 0 0 1] + EV = sparse(EV) + + bc = Lar.Arrangement.biconnected_components(EV) + @test size(bc,1)==1 + end + + @testset "Graph with 2 biconnected components" begin + EV = Int8[1 1 0 0 0 0; + 0 1 1 0 0 0; + 1 0 1 0 0 0; + 1 0 0 0 1 0; + 0 0 0 1 1 0; + 0 0 0 1 0 1; + 0 0 0 0 1 1] ; + EV = sparse(EV); + + bc = Lar.Arrangement.biconnected_components(EV) + @test size(bc,1)==2 + end +end + + +@testset "get_external_cycle" begin + @testset "Example 1" begin + + V = [ 0 0; 1 0;2 0;2 1;1 1;0 1] + + EV = Int8[-1 1 0 0 0 0 ; + 0 -1 1 0 0 0 ; + 0 0 -1 1 0 0 ; + 0 0 0 -1 1 0 ; + 0 0 0 0 -1 1 ; + 1 0 0 0 0 -1 ; + 0 -1 0 0 1 0 + ] + EV = sparse(EV) + + FE = Int8[ 1 1 1 1 1 1 0; + 1 0 0 0 1 1 -1; + 0 1 1 1 0 0 -1] + FE = sparse(FE) + @test Lar.Arrangement.get_external_cycle(V, EV, FE) == 1 + end + + @testset "Example 2" begin + + V = [ 0 0; 2 0; 2 2; 0 2; + 1 0; 3 0; 3 3; 1 2] + + EV = Int8[-1 1 0 0 0 0 0 0; + 0 -1 1 0 0 0 0 0; + 0 0 -1 1 0 0 0 0; + -1 0 0 1 0 0 0 0; + 0 0 0 0 -1 1 0 0; + 0 0 0 0 0 -1 1 0; + 0 0 0 0 0 0 -1 1; + 0 0 0 0 -1 0 0 1; + ] + EV = sparse(EV) + + FE = Int8[-1 -1 -1 1 0 0 0 0; + 0 0 0 0 -1 -1 -1 1; + 0 0 0 1 -1 -1 -1 0] + FE = sparse(FE) + @test Lar.Arrangement.get_external_cycle(V, EV, FE) == 3 + end + +end + + + +@testset "Containment graph" begin + @testset "pre_containment and prune_containment" begin + + V = [ 0 0; 2 0; 0 2; 3/2 3/2; 1/2 3/2;1/2 1/2 ; 3/2 1/2] + + EV = Int8[ -1 1 0 0 0 0 0; + 0 -1 1 0 0 0 0; + -1 0 1 0 0 0 0] + + EV1=Int8[ 0 0 0 -1 1 0 0; + 0 0 0 0 -1 1 0; + 0 0 0 0 0 -1 1; + 0 0 0 -1 0 0 1 + ] + EVs = map(sparse, [EV, EV1]) + + shell1=Int8[-1 -1 1] + shell2=Int8[-1 -1 -1 1] + shells = map(sparsevec, [shell1, shell2]) + + shell_bboxes = [] + n = 2 + for i in 1:n + vs_indexes = (abs.(EVs[i]')*abs.(shells[i])).nzind + push!(shell_bboxes, Lar.bbox(V[vs_indexes, :])) + end + + graph = Lar.Arrangement.pre_containment_test(shell_bboxes) + @test graph == [0 0;1 0]; + + graph = Lar.Arrangement.prune_containment_graph(n, V, EVs, shells, graph) + @test graph == [0 0;0 0] + end + + @testset "transitive_reduction" begin + + graph = [0 1 1 1 ; 0 0 1 1 ; 0 0 0 1 ; 0 0 0 0 ] + Lar.Arrangement.transitive_reduction!(graph) + @test graph == [0 1 0 0 ; 0 0 1 0; 0 0 0 1 ; 0 0 0 0 ] + end + +end + +@testset "component graph" begin + + V = [ 0 0; 2 0; 0 1; 3/2 1/2; 3/2 1/2; 1/2 3/2; 3/2 3/2] + + EV = Int8[ 1 1 0 0 0 0 0; + 0 1 1 0 0 0 0; + 1 0 1 0 0 0 0; + 0 0 0 1 1 0 0; + 0 0 0 0 1 0 1; + 0 0 0 0 0 1 1; + 0 0 0 1 0 1 0 + ] + EV = sparse(EV) + + bc = Lar.Arrangement.biconnected_components(EV) + + n, containment_graph, V, EVs, boundaries, shells, shell_bboxes=Lar.Arrangement.componentgraph(V, EV, bc) + + @test n==2 + @test boundaries==[[1 1 -1 -1],[1 1 -1]] + @test shell_bboxes==[([0.5 0.5], [1.5 1.5]), ([0.0 0.0], [2.0 1.0])] +end + +@testset "Planar Arrangement" begin + + V = [ 0 0; 1 0; 1 1; 0 1; 2 1] + + EV=[[1,2],[2,3],[3,4],[1,4],[3,5]] + + cop_EV = Lar.coboundary_0(EV::Lar.Cells) + cop_EW = convert(Lar.ChainOp, cop_EV) + + V, copEV, copFE = Lar.Arrangement.planar_arrangement(V::Lar.Points, cop_EW::Lar.ChainOp) + + @test Lar.cop2lar(copEV)==[[1,2],[2,3],[3,4],[1,4]] +end