diff --git a/.travis.yml b/.travis.yml index b23ad684..98fd0b72 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,4 +32,4 @@ deploy: keep-history: true local-dir: docs/build on: - branch: master + branch: julia-1.0 diff --git a/README.md b/README.md index b19e81aa..45cae86c 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,20 @@ add("LinearAlgebraicRepresentation") ## Documentation -Go to [the documentation page](https://cvdlab.github.io/LinearAlgebraicRepresentation.jl/) +Building EOnofri julia-1.0 [![Build Status](https://travis-ci.org/eOnofri04/LinearAlgebraicRepresentation.jl.svg?branch=master)](https://travis-ci.org/eOnofri04/LinearAlgebraicRepresentation.jl) + +Go to [eonofri documentation page](https://eonofri04.github.io/LinearAlgebraicRepresentation.jl/) + + +Go to [cvdlab documentation page](https://cvdlab.github.io/LinearAlgebraicRepresentation.jl/) + ## Authors * [Giulio Martella](https://github.com/giuliom95) * [Alberto Paoluzzi](https://github.com/apaoluzzi) * [Francesco Furiani](https://github.com/furio) + +## Additional Documenters +* [Elia Onofri](https://github.com/eonofri04) +* [Maria Teresa Graziano](https://github.com/marteresagh) +* [Luca Angelini]() diff --git a/REQUIRE b/REQUIRE index ff1f67ea..672a74c0 100644 --- a/REQUIRE +++ b/REQUIRE @@ -2,4 +2,4 @@ julia 1.0 NearestNeighbors IntervalTrees DataStructures -Triangle \ No newline at end of file +Triangle diff --git a/docs/make.jl b/docs/make.jl index 2c0ea8ca..8d8a8830 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -12,7 +12,10 @@ makedocs( "Home" => "index.md", "L.A.R. Intro" => "lar.md", "Interface" => "interface.md", - "Arrangement" => "arrangement.md", + "Arrangement" => [ + "Arrangement module" => "arrangement.md", + "Planar arrangement" => "planar_arrangement.md" + ], "Parametric primitives" => [ "Mapper" => "mapper.md", "Assemblies" => "struct.md" diff --git a/docs/src/arrangement.md b/docs/src/arrangement.md index 528460ef..73b2205c 100644 --- a/docs/src/arrangement.md +++ b/docs/src/arrangement.md @@ -4,22 +4,19 @@ The **arrangement** is an algorithm which gets two general ``d``-dimensional cel * ``\sigma_1 \cap \sigma_2 = \emptyset,\quad \forall`` couple of cells ``(\sigma_1, \sigma_2)`` * ``\bigcup_i\sigma_i = \mathbb{E}^d`` -This operation can be seen as a boolean union of two cellular complexes. Here an exploded visualization of the final result of the arrangement algorithm ran on 2 cubes made by ``10\times10\times10`` smaller cubes. +This operation can be seen as a boolean union of two cellular complexes. Here an exploded visualization of the final result of the arrangement algorithm run on 2 cubes made by ``10\times10\times10`` smaller cubes. ![10 x 10 Cube](./images/cube10x10.jpg) > **Figure 1:** Arrangement of ``2000=2\times10\times10\times10`` cubes -## API +## Organisation -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. +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. They are: + - [`Lar.planar_arrangement`]() that evaluates the arrangements in 2D spaces. + - [`Lar.spatial_arrangement`]() that evaluates the arrangements in 3D spaces. !!! 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 -``` - !!! note Even if the arrangement algorithm is theoretically dimension independent, we implemented "only" the ``d=2`` and ``d=3`` version of it. @@ -47,14 +44,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" 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 @@ -83,14 +80,14 @@ are to discard: the ones outside the area of ``\sigma`` and the ones which are not part of a maximal biconnected component (We can talk about biconnected components because we can consider the 1-skeleton as a graph: 0-cells are nodes, 1-cells are edges and the boundary operator is -a incidence matrix.). +a incidence matrix). The result of this edge pruning outputs a 1-skeleton [fig. 3, c, exploded]. After this, 2-cells must be computed: For each connected component we build a containment tree, which indicates which component is spatially inside an other component. -Computing these relations lets us launch the ALGORITHM 1 \cite{Paoluzzi} +Computing these relations lets us launch the ALGORITHM 1 ``\cite{Paoluzzi}`` on each component and then combine the results to create 2-cells with non-intersecting shells [fig. 3, d, 2-cells numbered in green; please note that cell 2 has cell 1 as an hole]. diff --git a/docs/src/images/2d-arrangement-pipeline.jpg b/docs/src/images/2d-arrangement-pipeline.jpg new file mode 100644 index 00000000..8aae7cb9 Binary files /dev/null and b/docs/src/images/2d-arrangement-pipeline.jpg differ diff --git a/docs/src/planar_arrangement.md b/docs/src/planar_arrangement.md new file mode 100644 index 00000000..1e31c8c8 --- /dev/null +++ b/docs/src/planar_arrangement.md @@ -0,0 +1,262 @@ +# Arrangement of cellular complexes in two-dimensional spaces + +The module Arangement provides two dimensional space arrangements via the following method (accesible from the `LinearAlgebraicRepresentation` namespace): + +```@docs +Lar.planar_arrangement +``` + +In general we recall the notation we have used in source code: + - `V::Lar.Points` is the 1-cells (Vertices) complex by Columns. + - `W::Lar.Points` is the 1-cells (Vertices) complex by Rows. + - `EV::Lar.Cells` is the 2-cells (Edges) complex. + - `FV::Lar.Cells` is the 3-cells (Faces) complex. + - `copEV::Lar.ChainOp`: is the Chain Coboundary of the 2-cells. + - `copFE::Lar.ChainOp`: is the Chain Coboundary of the 3-cells. + - `bigPI::Array{Array{Int64,1},1}`: is the bounding box intersection array: + each row is associated to a 2-cell and contains the indices of the other 2-cells intersecting its bounding box. + +!!! warning + Do remember that in `planar_arrangement` (`Lar.Arrangement` module) matrices store points per row and not per column as described in the documentation of `Lar.Points`. + +## The Arrangement Algorithm + +In this section we will provide a general overview of how the `planar_arrangement` is meant to work. + +The algorithm is divided into the following pipeline: + - Fragmentation of the 2-Cells. + - Spatial Indexing. + - Pairwise 2-Cells Fragmentation. + - Vertices Identification. + - Biconnected Components Detection. + - 3-Cells Evaluation and Dangling 2-Cells Elimination via Topological Gift Wrapping (TGW) algorithm. + - Component graph evaluation (TGW offline part). + - Evalution of the external cicle. + - Containment graph evaluation. + - Pruning of the containment graph. + - Transitive ``R`` reduction of ``S`` and generation of forest of flat trees + - Decomposition Cleaning (if a boundary has been specified). + - Shell poset aggregation (TGW onloine part). + +### Fragmentation of the 2-Cells + +This part of the pipeline is covered by: +```@docs +Lar.Arrangement.planar_arrangement_1 +``` +A small set of optional parameters could be choosen in order to personalize the computation: + - `sigma::Lar.Chain`: if specified, the arrangement will delete from the output every edge outside this cell + (*by defaults* = empty cell, no boundary). + - `return_edge_map::Bool`: If set to true, the function will also return an `edge_map` that maps the input edges to + the corresponding output ones. + - `multiproc::Bool`: If set to true, execute the arrangement in parallel (*by default* = false, sequential). + + + +Once the data have been aquired, a spatial indexing between the 2-cells is made up in order to speed up the computation. +```@docs +Lar.spaceindex +``` + + + +Then each single 2-cell is fragmented by intersecting it with the other 2-cells that shares the same bounding box. +This process is either done in a parralel or a sequtial way via: +```@docs +Lar.Arrangement.frag_edge_channel +Lar.Arrangement.frag_edge +``` +if the parallel way is choosen (namely `frag_edge_channel` is used) then a few more parameters must be specified: + - `in_chan::Distributed.RemoteChannel{Channel{Int64}}`: an input channel made of the edges indices to be intersected; + the channel must also hold at the end an EoW (`-1`) indicator for each worker thread in use. + - `out_chan::Distributed.RemoteChannel{Channel{Int64}}`: a ready-to-use output channel. + + + +In order to split the edge, at a lower level, each pair of possible intersecting 2-cells are compared via: +```@docs +Lar.Arrangement.intersect_edges +``` + + + +In the end, the 1-cells are identified if they are located spatially very close one to the other (_i.e_ ``\epsilon = 10^{-4}``). +Of course if two 2-cells share the same endpoints then they are also identified in a unique 2-cell. +```@docs +Lar.Arrangement.merge_vertices! +``` +Here also two optional parameters could be specified: + - `edge_map::Array{Array{Int64,1},1}`: Mapping from a set of edges to the edges of the given cochain. + If it is given in input than a copy of it would be returned in output, with its values coherently rearranged with the vertices merging (*by default* = ``[[-1]]``). + - `err::Float64`: Range of the vertex identification (*by default* = ``1e-4``). + + + +!!! note + Choosing a good value for `err` is a very important issue. + Note that the identification is sequentially made; + therefore the following situation could happend: + if three vertices are collinear and evenly spaciated, then + - if the second is identified in the third, + then the first and the third won't be identified; + - if the third is identified with the second, + then the first and the second will be identified; + + This situation could be seen in the secon example given by the function documentation. + + + +### Biconnected Components Detection. + +This part of the pipeline is covered by +```@docs +Lar.Arrangement.biconnected_components +``` +As a reader can see the components are evaluated regardless the model geometrical structure; +in fact the 1-cells are not considered during the computation. This means that the abstract +graph made by the edges is the only input needed by the function. + + + +### 3-Cells Evaluation and Dangling 2-Cells Elimination. + +This part of the pipeline is covered by +```@docs +Lar.Arrangement.planar_arrangement_2 +``` + + +First of all the components of the graph are evaluated via +```@docs +Lar.Arrangement.componentgraph +``` + + +Then, if a special chain ``\sigma`` has been specified, the given decomposition is cleaned +from all the 2-cells (and consequently from alle the 1-cells) that are located ouside its boundary via +```@docs +Lar.Arrangement.cleandecomposition +``` + + +Lastly the 2-cells are merged in order to retrieve the 3-cell of the complex. +By doing so, all the 2-cells that are not linked to any 3-cell (_i.e._ dangling edges) +are lost. +!!! note + Do note that the 1-cells are not pruned during this computation. + Therefore the vertices associated with dangling edges will still be there. + +This last part of the computation is done via: +```@docs +Lar.Arrangement.cell_merging +``` + + +#### Component graph evaluation +Component Graph evaluation relies on different functions that could be use separatelly in order to obtain different results. + +First of all we use the following function on each biconnected component +in order to gain the faces of the complex: +```@docs +Lar.Arrangement.get_external_cycle +``` + + + +Then a containment graph is build in order to clarify if some biconnected component +relies in another biconnected component. To do so, the approach is similiar to what +we did with the 2-cells decomposition. We firstly build the bounding box of each +component and we check whether they are one inside the other via: +```@docs +Lar.Arrangement.pre_containment_test +``` + + + +Then we prune the resulting mapping using: +```@docs +Lar.Arrangement.prune_containment_graph +``` +In order to do so we take each biconnected component ``\phi`` whose bounding box is inside +the bounding box of ``\psi`` (``\forall \psi``) and we check if a point of ``phi`` is or is not +on the possible ``\psi`` 3-cells. +Do note that it is sufficient checking one single point since we know in advance +that each couple of biconnected components is intersection free. Therefore if a single +point of ``\phi`` is inside a biconnected component ``\psi`` then ``\phi \subset \psi``. + + +However if three or more faces are nested one into the other, then the mapping +is redundant. We therefore eliminate this redundany by applying: +```@docs +Lar.Arrangement.transitive_reduction! +``` + +## Examples + +Lastly we close this section by giving a small example of how Planar Arrangement Works. +Using `Plasm` we are able to visualize the step of the computation grafically. + + +```julia +using Plasm +using LinearAlgebraicRepresentation +Lar = LinearAlgebraicRepresentation; + +# Data Reading +V = [ + 0.0 0.5 3.0 5.0 2.0 2.0 0.0 1.0 0.0 3.0; + 1.0 1.0 1.0 1.0 2.0 0.0 3.0 1.5 0.0 1.5 + ]; +EV = [[1, 4], [2, 3], [5, 6], [2, 5], [3, 6], [7, 10], [8, 10], [9, 10]]; + +## 1 - Input Reading +Plasm.view(Plasm.numbering(0.5)((V,[[[k] for k=1:size(V,2)], EV]))); + +# Planar Arrangement 1 +W = convert(Lar.Points, V'); # Infering type for W = V' +copEV = Lar.coboundary_0(EV::Lar.Cells); +W1, copEV1 = Lar.planar_arrangement_1(W::Lar.Points, copEV::Lar.ChainOp); + +## 2 - 2-cells fragmentation +EV1 = Lar.cop2lar(copEV1); +V1 = convert(Lar.Points, W1'); +Plasm.view(Plasm.numbering(0.5)((V1,[[[k] for k=1:size(V1,2)], EV1]))); + +# Biconnected COmponent Evaluation +bicon_comps = Lar.Arrangement.biconnected_components(copEV1); + +## 3 - Biconnected Components +hpcs = [ Plasm.lar2hpc(V1,[EV1[e] for e in comp]) for comp in bicon_comps ] +Plasm.view([ + Plasm.color(Plasm.colorkey[(k%12)==0 ? 12 : k%12])(hpcs[k]) + for k = 1 : (length(hpcs)) +]) + +# computation of 2-cells and 2-boundary +W2, copEV2, copFE2 = Lar.planar_arrangement_2(W1, copEV1, bicon_comps) + +## 4 - 3-cells identification & dangling 1-cells elimination +Plasm.view( Plasm.numbering1(0.5)((W2, copEV2, copFE2)) ) + + +# 5 - Colorfull Representation +triangulated_faces = Lar.triangulate2D(W2, [copEV2, copFE2]) +V2 = convert(Lar.Points, W2') +FVs2 = convert(Array{Lar.Cells}, triangulated_faces) +Plasm.viewcolor(V2::Lar.Points, FVs2::Array{Lar.Cells}) + +# polygonal face boundaries +EVs2 = Lar.FV2EVs(copEV2, copFE2) +EVs2 = convert(Array{Array{Array{Int64,1},1},1}, EVs2) +Plasm.viewcolor(V2::Lar.Points, EVs2::Array{Lar.Cells}) + +# 6 - Exploded Representation +model = V2,EVs2 +Plasm.view(Plasm.lar_exploded(model)(1.2,1.2,1.2)) +``` + +The described commands gives the following representation via Plasm. Here we have reunited all the images in order to clarify the example. It could be seen in (4) that the 1-cells are not removed via the Pipeline. +![Pipeline visualization](./images/2d-arrangement-pipeline.jpg) +> **Figure 1:** Pipeline visualization over a sample structure. + +More examples could be find in the `test` and `examples` directory. diff --git a/examples/2d/planar_arrangement/Triforce.jl b/examples/2d/planar_arrangement/Triforce.jl new file mode 100644 index 00000000..fc7bf50e --- /dev/null +++ b/examples/2d/planar_arrangement/Triforce.jl @@ -0,0 +1,61 @@ +using LinearAlgebraicRepresentation +using Plasm +using SparseArrays +Lar = LinearAlgebraicRepresentation +Lara = Lar.Arrangement + +# Data Reading +V = [ + 0.0 1.5 3.0 0.0 3.0 0.0 1.5 3.0; + 0.0 0.0 0.0 1.5 1.5 3.0 3.0 3.0 + ]; +EV = [ + [1, 3], [4, 5], # Orizontal + [1, 7], [2, 8], # Diag 1 + [2, 6], [3, 7] # Diag 2 + ]; + +## 1 - Input Reading +Plasm.view(Plasm.numbering(0.5)((V,[[[k] for k=1:size(V,2)], EV]))); + +# Planar Arrangement 1 +W = convert(Lar.Points, V'); # Infering type for W = V' +copEV = Lar.coboundary_0(EV::Lar.Cells); +W1, copEV1 = Lar.planar_arrangement_1(W::Lar.Points, copEV::Lar.ChainOp); + +## 2 - 2-cells fragmentation +EV1 = Lar.cop2lar(copEV1); +V1 = convert(Lar.Points, W1'); +Plasm.view(Plasm.numbering(0.5)((V1,[[[k] for k=1:size(V1,2)], EV1]))); + +# Biconnected COmponent Evaluation +bicon_comps = Lar.Arrangement.biconnected_components(copEV1); + +## 3 - Biconnected Components +hpcs = [ Plasm.lar2hpc(V1,[EV1[e] for e in comp]) for comp in bicon_comps ] +Plasm.view([ + Plasm.color(Plasm.colorkey[(k%12)==0 ? 12 : k%12])(hpcs[k]) + for k = 1 : (length(hpcs)) +]) + +# computation of 2-cells and 2-boundary +W2, copEV2, copFE2 = Lar.planar_arrangement_2(W1, copEV1, bicon_comps) + +## 4 - 3-cells identification & dangling 1-cells elimination +Plasm.view( Plasm.numbering1(0.5)((W2, copEV2, copFE2)) ) + + +# 5 - Colorfull Representation +triangulated_faces = Lar.triangulate2D(W2, [copEV2, copFE2]) +V2 = convert(Lar.Points, W2') +FVs2 = convert(Array{Lar.Cells}, triangulated_faces) +Plasm.viewcolor(V2::Lar.Points, FVs2::Array{Lar.Cells}) + +# polygonal face boundaries +EVs2 = Lar.FV2EVs(copEV2, copFE2) +EVs2 = convert(Array{Array{Array{Int64,1},1},1}, EVs2) +Plasm.viewcolor(V2::Lar.Points, EVs2::Array{Lar.Cells}) + +# 6 - Exploded Representation +model = V2,EVs2 +Plasm.view(Plasm.lar_exploded(model)(1.2,1.2,1.2)) \ No newline at end of file diff --git a/examples/2d/planar_arrangement/planar_tile.jl b/examples/2d/planar_arrangement/planar_tile.jl new file mode 100644 index 00000000..8edcebd4 --- /dev/null +++ b/examples/2d/planar_arrangement/planar_tile.jl @@ -0,0 +1,100 @@ +using LinearAlgebraicRepresentation +using Plasm +using SparseArrays +Lar = LinearAlgebraicRepresentation +Lara = Lar.Arrangement + + +V = [ + 0.0 0.4 0.5 0.6 1.0 0.0 1.0 0.0 1.0 0.0 1.0 0.0 0.4 0.5 0.6 1.0; + 0.0 0.0 0.0 0.0 0.0 0.4 0.4 0.5 0.5 0.6 0.6 1.0 1.0 1.0 1.0 1.0 +] + +EV = [ + [ 1, 2], [ 1, 6], [ 2, 6], + [ 4, 5], [ 4, 7], [ 5, 7], + [10, 12], [10, 13], [12, 13], + [11, 15], [11, 16], [15, 16], + [ 3, 8], [ 3, 9], [ 3, 14], [ 8, 9], [ 8, 14], [ 9, 14] +] + +## 1 - Input Reading +Plasm.view(Plasm.numbering(0.5)((V,[[[k] for k=1:size(V,2)], EV]))); + + +# Planar Arrangement 1 +W = convert(Lar.Points, V'); # Infering type for W = V' +copEV = Lar.coboundary_0(EV::Lar.Cells); +W1, copEV1 = Lar.planar_arrangement_1(W::Lar.Points, copEV::Lar.ChainOp); + + +## 2 - 2-cells fragmentation +EV1 = Lar.cop2lar(copEV1); +V1 = convert(Lar.Points, W1'); +Plasm.view(Plasm.numbering(0.5)((V1,[[[k] for k=1:size(V1,2)], EV1]))); + + +# Biconnected COmponent Evaluation +bicon_comps = Lar.Arrangement.biconnected_components(copEV1); + + +## 3 - Biconnected Components +hpcs = [ Plasm.lar2hpc(V1,[EV1[e] for e in comp]) for comp in bicon_comps ] +Plasm.view([ + Plasm.color(Plasm.colorkey[(k%12)==0 ? 12 : k%12])(hpcs[k]) + for k = 1 : (length(hpcs)) +]) + + +# computation of 2-cells and 2-boundary + +n = size(bicon_comps, 1) +shells = Array{Lar.Chain, 1}(undef, n) +boundaries = Array{Lar.ChainOp, 1}(undef, n) +EVs = Array{Lar.ChainOp, 1}(undef, n) +# for each component +for p=1:n + ev = copEV1[sort(bicon_comps[p]), :] + fe = Lara.minimal_2cycles(W1, ev) + global shell_num = Lara.get_external_cycle(W1, ev, fe) + EVs[p] = ev + global tokeep = setdiff(1:fe.m, shell_num) + boundaries[p] = fe[tokeep, :] + shells[p] = fe[shell_num, :] +end + +shell_bboxes = [] +for i in 1:n + vs_indexes = (abs.(EVs[i]')*abs.(shells[i])).nzind + push!(shell_bboxes, Lar.bbox(W1[vs_indexes, :])) +end + +containment_graph = Lara.pre_containment_test(shell_bboxes) +containment_graph = Lara.prune_containment_graph( + n, W1, EVs, shells, containment_graph +) +Lara.transitive_reduction!(containment_graph) +W2 = W1; +copEV2, copFE2 = Lara.cell_merging( + n, containment_graph, W2, EVs, boundaries, shells, shell_bboxes +) + + +## 4 - 3-cells identification & dangling 1-cells elimination +Plasm.view( Plasm.numbering1(0.5)((W2, copEV2, copFE2)) ) + + +# 5 - Colorfull Representation +triangulated_faces = Lar.triangulate2D(W2, [copEV2, copFE2]) +V2 = convert(Lar.Points, W2') +FVs2 = convert(Array{Lar.Cells}, triangulated_faces) +Plasm.viewcolor(V2::Lar.Points, FVs2::Array{Lar.Cells}) + +# polygonal face boundaries +EVs2 = Lar.FV2EVs(copEV2, copFE2) +EVs2 = convert(Array{Array{Array{Int64,1},1},1}, EVs2) +Plasm.viewcolor(V2::Lar.Points, EVs2::Array{Lar.Cells}) + +# 6 - Exploded Representation +model = V2,EVs2 +Plasm.view(Plasm.lar_exploded(model)(1.2,1.2,1.2)) diff --git a/examples/2d/planar_arrangement/triangulation.jl b/examples/2d/planar_arrangement/triangulation.jl new file mode 100644 index 00000000..887e1e38 --- /dev/null +++ b/examples/2d/planar_arrangement/triangulation.jl @@ -0,0 +1,57 @@ +using LinearAlgebraicRepresentation +using Plasm +using SparseArrays +Lar = LinearAlgebraicRepresentation +Lara = Lar.Arrangement + +# Data Reading +V = [ + 0.0 0.5 3.0 5.0 2.0 2.0 0.0 1.0 0.0 3.0; + 1.0 1.0 1.0 1.0 2.0 0.0 3.0 1.5 0.0 1.5 + ]; +EV = [[1, 4], [2, 3], [5, 6], [2, 5], [3, 6], [7, 10], [8, 10], [9, 10]]; + +## 1 - Input Reading +Plasm.view(Plasm.numbering(0.5)((V,[[[k] for k=1:size(V,2)], EV]))); + +# Planar Arrangement 1 +W = convert(Lar.Points, V'); # Infering type for W = V' +copEV = Lar.coboundary_0(EV::Lar.Cells); +W1, copEV1 = Lar.planar_arrangement_1(W::Lar.Points, copEV::Lar.ChainOp); + +## 2 - 2-cells fragmentation +EV1 = Lar.cop2lar(copEV1); +V1 = convert(Lar.Points, W1'); +Plasm.view(Plasm.numbering(0.5)((V1,[[[k] for k=1:size(V1,2)], EV1]))); + +# Biconnected COmponent Evaluation +bicon_comps = Lar.Arrangement.biconnected_components(copEV1); + +## 3 - Biconnected Components +hpcs = [ Plasm.lar2hpc(V1,[EV1[e] for e in comp]) for comp in bicon_comps ] +Plasm.view([ + Plasm.color(Plasm.colorkey[(k%12)==0 ? 12 : k%12])(hpcs[k]) + for k = 1 : (length(hpcs)) +]) + +# computation of 2-cells and 2-boundary +W2, copEV2, copFE2 = Lar.planar_arrangement_2(W1, copEV1, bicon_comps) + +## 4 - 3-cells identification & dangling 1-cells elimination +Plasm.view( Plasm.numbering1(0.5)((W2, copEV2, copFE2)) ) + + +# 5 - Colorfull Representation +triangulated_faces = Lar.triangulate2D(W2, [copEV2, copFE2]) +V2 = convert(Lar.Points, W2') +FVs2 = convert(Array{Lar.Cells}, triangulated_faces) +Plasm.viewcolor(V2::Lar.Points, FVs2::Array{Lar.Cells}) + +# polygonal face boundaries +EVs2 = Lar.FV2EVs(copEV2, copFE2) +EVs2 = convert(Array{Array{Array{Int64,1},1},1}, EVs2) +Plasm.viewcolor(V2::Lar.Points, EVs2::Array{Lar.Cells}) + +# 6 - Exploded Representation +model = V2,EVs2 +Plasm.view(Plasm.lar_exploded(model)(1.2,1.2,1.2)) \ No newline at end of file diff --git a/src/arrangement/planar_arrangement.jl b/src/arrangement/planar_arrangement.jl index ddcf7790..a6af963e 100644 --- a/src/arrangement/planar_arrangement.jl +++ b/src/arrangement/planar_arrangement.jl @@ -1,26 +1,90 @@ using LinearAlgebraicRepresentation Lar = LinearAlgebraicRepresentation +Lara = Lar.Arrangement -function frag_edge_channel(in_chan, out_chan, V, EV, bigPI) + +#-------------------------------------------------------------------------------------------------------------------------------- +# PIPELINE PART 1 +#-------------------------------------------------------------------------------------------------------------------------------- + +""" + frag_edge_channel(in_chan, out_chan, V, EV, bigPI) + +Utility function for parallel edge fragmentation. + +This function handles the edge fragmentation in the first part of arrangement's algorithmic pipeline +(see also [`Lar.Arrangement.planar_arrangement_1`](@ref)) during multiprocessing computation. +In order to do so it needs two `Distributed.RemoteChannel`s, one with the inputs and one for outputs. + +See also: [`Lar.Arrangement.frag_edge`](@ref) +""" +function frag_edge_channel( in_chan::Distributed.RemoteChannel{Channel{Int64}}, + out_chan::Distributed.RemoteChannel{Channel{Tuple}}, + V::Lar.Points, + EV::Lar.ChainOp, + bigPI::Array{Array{Int64,1},1}) run_loop = true while run_loop edgenum = take!(in_chan) if edgenum != -1 - put!(out_chan, (edgenum, frag_edge(V, EV, edgenum, bigPI))) + put!(out_chan, (edgenum, Lar.Arrangement.frag_edge(V, EV, edgenum, bigPI))) else run_loop = false end end end -function frag_edge(V::Lar.Points, EV::Lar.ChainOp, edge_idx::Int, bigPI) + +""" + frag_edge(V, EV, edge_idx, bigPI) + +Splits the `edge_idx`-th edge in `EV`. + +This method splits the `edge_idx`-th edge in `EV` into several parts by confronting it +with the others that intersect its bounding box (see also [`Lar.Arrangement.intersect_edges`](@ref)). + +The method returns a set of the new vertices that the segment is made of (with redundancies) and +the associated cochain (with no redundancies). + +See also: + - [`Lar.Arrangement.planar_arrangement_1`](@ref) + - [`Lar.Arrangement.frag_edge_channel`](@ref) + +--- + +# Examples +```jldoctest +julia> V = [1.0 0.0; 0.0 1.0; 0.0 0.5; 0.5 1.0; 1.0 1.0]; # By Rows! + +julia> EV = [[1, 2], [2, 5], [3, 4], [4, 5]]; + +julia> copEV = Lar.coboundary_0(EV::Lar.Cells); + +julia> bigPI = Lar.spaceindex((convert(Lar.Points, V'), EV)); + +julia> Lara.frag_edge(V, copEV, 1, bigPI)[1] +5×2 Array{Float64,2}: + 1.0 0.0 + 0.0 1.0 + 1.0 0.0 + 0.25 0.75 + 0.0 1.0 + +julia> Lara.frag_edge(V, copEV, 1, bigPI)[2] +2×5 SparseMatrixCSC{Int8,Int64} with 4 stored entries: + [1, 1] = 1 + [2, 2] = 1 + [1, 4] = 1 + [2, 4] = 1 +``` +""" +function frag_edge(V::Lar.Points, EV::Lar.ChainOp, edge_idx::Int, bigPI::Array{Array{Int64,1},1})::Tuple{Lar.Points, Lar.ChainOp} alphas = Dict{Float64, Int}() edge = EV[edge_idx, :] verts = V[edge.nzind, :] for i in bigPI[edge_idx] - if i != edge_idx - intersection = Lar.Arrangement.intersect_edges( - V, edge, EV[i, :]) + if i != edge_idx && edge_idx in bigPI[i] #<------------------------- Could be usefull? + intersection = Lar.Arrangement.intersect_edges(V, edge, EV[i, :]) for (point, alpha) in intersection verts = [verts; point] alphas[alpha] = size(verts, 1) @@ -39,7 +103,51 @@ function frag_edge(V::Lar.Points, EV::Lar.ChainOp, edge_idx::Int, bigPI) return verts, ev end -function intersect_edges(V::Lar.Points, edge1::Lar.Cell, edge2::Lar.Cell) + +""" + intersect_edges(V, edge1, edge2) + +Finds the intersection point (if there exists) between the two given edges. + +This method compute the points where `edge2` intersect `edge1`. +If they are collinear only the vertices of `edge2` are considered (see the second example). + +See also: [`Lar.Arrangement.frag_edge`](@ref) + +--- + +# Examples +```jldoctest +# Cross +julia> V = [1.0 0.0; 0.0 1.0; 0.0 0.5; 0.5 1.0]; # By Rows! + +julia> EV = [[1, 2], [3, 4]]; + +julia> copEV = Lar.coboundary_0(EV::Lar.Cells); + +julia> Lara.intersect_edges(V, copEV[1, :], copEV[2, :]) +1-element Array{Tuple{Array{T,2} where T,Float64},1}: + ([0.25 0.75], 0.75) +``` + +```jldoctest +# Collinear +julia> V = [1.0 0.0; 0.0 1.0; 0.75 0.25; 0.5 0.5]; # By Rows! + +julia> EV = [[1, 2], [3, 4]]; + +julia> copEV = Lar.coboundary_0(EV::Lar.Cells); + +julia> Lar.Arrangement.intersect_edges(V, copEV[1, :], copEV[2, :]) +2-element Array{Tuple{Array{T,2} where T,Float64},1}: + ([0.75 0.25], 0.25) + ([0.5 0.5], 0.5) + +julia> Lar.Arrangement.intersect_edges(V, copEV[2, :], copEV[1, :]) +0-element Array{Tuple{Array{T,2} where T,Float64},1} +``` +""" +function intersect_edges(V::Lar.Points, edge1::Lar.Cell, edge2::Lar.Cell)::Array{Tuple{Lar.Points, Float64}, 1} err = 10e-8 x1, y1, x2, y2 = vcat(map(c->V[c, :], edge1.nzind)...) @@ -50,20 +158,20 @@ function intersect_edges(V::Lar.Points, edge1::Lar.Cell, edge2::Lar.Cell) v2 = [x4-x3, y4-y3]; v3 = [x3-x1, y3-y1]; - ang1 = dot(normalize(v1), normalize(v2)) - ang2 = dot(normalize(v1), normalize(v3)) + ang1 = Lar.dot(Lar.normalize(v1), Lar.normalize(v2)) + ang2 = Lar.dot(Lar.normalize(v1), Lar.normalize(v3)) parallel = 1-err < abs(ang1) < 1+err - colinear = parallel && (1-err < abs(ang2) < 1+err || -err < norm(v3) < err) + colinear = parallel && (1-err < abs(ang2) < 1+err || -err < Lar.norm(v3) < err) if colinear o = [x1 y1] v = [x2 y2] - o - alpha = 1/dot(v,v') + alpha = 1/Lar.dot(v,v') ps = [x3 y3; x4 y4] for i in 1:2 - a = alpha*dot(v',(reshape(ps[i, :], 1, 2)-o)) + a = alpha*Lar.dot(v',(reshape(ps[i, :], 1, 2)-o)) if 0 < a < 1 push!(ret, (ps[i:i, :], a)) end @@ -83,7 +191,45 @@ function intersect_edges(V::Lar.Points, edge1::Lar.Cell, edge2::Lar.Cell) return ret end -function merge_vertices!(V::Lar.Points, EV::Lar.ChainOp, edge_map, err=1e-4) + +""" + merge_vertices!(V, EV[, edge_map[, err]]) + +Compact the vertices closer than `err` in a single one. + +This method check one at time each vertex ``v`` in `V` and identifies each other vertex within `err` with ``v`` itself. +The cochain `EV` is coherently modified (multiple edges between two vertices are not allowed). +If an `edge_map` is given in input (this could be usefull during the planar arrangements), then also +the map is coherently modified and given back in output. + +See also: [`Lar.Arrangement.planar_arrangement_1`](@ref) + +--- + +# Examples +```jldoctest +# Collinear +julia> V = [0.5 0.5; 0.0 0.0; 0.5 0.5; 1.0 1.0; 0.5 0.5; 1.0 1.0]; # By Rows! + +julia> EV = [[1, 4], [3, 2], [5, 6], [1, 6], [5, 3]]; + +julia> copEV = Lar.coboundary_0(EV::Lar.Cells); + +julia> Lara.merge_vertices!(V, copEV)[1] +3×2 Array{Float64,2}: + 0.5 0.5 + 0.0 0.0 + 1.0 1.0 + +julia> Lara.merge_vertices!(V, copEV)[2] +2×3 SparseArrays.SparseMatrixCSC{Int8,Int64} with 4 stored entries: + [1, 1] = 1 + [2, 1] = 1 + [2, 2] = 1 + [1, 3] = 1 +``` +""" +function merge_vertices!(V::Lar.Points, EV::Lar.ChainOp, edge_map::Array{Array{Int64,1},1}=[[-1]], err=1e-4) vertsnum = size(V, 1) edgenum = size(EV, 1) newverts = zeros(Int, vertsnum) @@ -132,19 +278,72 @@ function merge_vertices!(V::Lar.Points, EV::Lar.ChainOp, edge_map, err=1e-4) etuple2idx[nedges[ei]] = ei end - for i in 1:length(edge_map) - row = edge_map[i] - row = map(x->edges[x], row) - row = filter(t->t[1]!=t[2], row) - row = map(x->etuple2idx[x], row) - edge_map[i] = row + if edge_map != [[-1]] + for i in 1:length(edge_map) + row = edge_map[i] + row = map(x->edges[x], row) + row = filter(t->t[1]!=t[2], row) + row = map(x->etuple2idx[x], row) + edge_map[i] = row + end + return Lar.Points(nV), nEV, edge_map end return Lar.Points(nV), nEV end -function biconnected_components(EV::Lar.ChainOp) + + +#-------------------------------------------------------------------------------------------------------------------------------- +# PIPELINE BICONNECTED COMPONENTS +#-------------------------------------------------------------------------------------------------------------------------------- + +""" + biconnected_components(EV) + +Compute sets of 2-cells on the same 3-cells biconnected components. + +The method evaluate the 2-cells ``σ_i`` wich lies on the same 3-cells biconnected +component ``χ_j`` and gives back an array that contains an array of ``σ_i`` for each ``j``. +Do note that if the cochain EV contains more copies of the same 2-cell then +it will be considered like a 3-cell. + +Intermediate part of Planar Arrangement's algorithmic pipeline. + +See also: [`Lar.planar_arrangement`](@ref) for the complete pipeline. + +--- + +# Examples +```jldoctest +julia> copEV = SparseArrays.sparse(Array{Int8, 2}([ + [1 1 0 0 0 0] #1 -> 1,2 | + [1 0 1 0 0 0] #2 -> 1,3 | + [1 0 0 1 0 0] #3 -> 1,4 | + [1 0 0 0 1 0] #4 -> 1,5 | + [1 0 0 0 0 1] #5 -> 1,6 + [0 1 1 0 0 0] #6 -> 2,3 | + [0 0 0 1 1 0] #7 -> 4,5 | + ])); +julia> Lara.biconnected_components(copEV) +2-element Array{Array{Int64,1},1}: + [2, 6, 1] + [4, 7, 3] +``` + +```jldoctest +julia> copEV = SparseArrays.sparse(Array{Int8, 2}([ + [1 1 0] #1 -> 1,2 | + [1 1 0] #2 -> 1,2 | + [1 0 1] #3 -> 1,2 + ])); +julia> Lara.biconnected_components(copEV) +1-element Array{Array{Int64,1},1}: + [2, 1] +``` +""" +function biconnected_components(EV::Lar.ChainOp)::Array{Array{Int, 1}, 1} ps = Array{Tuple{Int, Int, Int}, 1}() es = Array{Tuple{Int, Int}, 1}() todel = Array{Int, 1}() @@ -240,6 +439,50 @@ function biconnected_components(EV::Lar.ChainOp) return bicon_comps end + + +#-------------------------------------------------------------------------------------------------------------------------------- +# PIPELINE PART 2 +#-------------------------------------------------------------------------------------------------------------------------------- + +""" + get_external_cycle(V, EV, FE) + +Evaluates the index of the external 3D-cell. + +This method looks for and retrieve the external cycle of the model `(V, EV, FE)`. +If the cell does not exist then it return a `Nothing` element. + +See also: [`Lar.Arrangement.componentgraph`](@ref). + +--- + +# Examples +```jldoctest +# ``K^4`` graph +julia> V = [0.0 0.0; 4.0 0.0; 2.0 3.0; 2.0 1.5]; +julia> copEV = SparseArrays.sparse(Array{Int8, 2}([ + [1 1 0 0] #1 -> 1,2 + [0 1 1 0] #2 -> 2,3 + [1 0 1 0] #3 -> 3,1 + [1 0 0 1] #4 -> 1,2 + [0 1 0 1] #5 -> 2,3 + [0 0 1 1] #6 -> 3,1 + ])); +julia> copFE = SparseArrays.sparse(Array{Int8, 2}([ + [1 0 0 1 1 0] #1 -> 1,4,5 + [0 1 0 0 1 1] #2 -> 2,5,6 + [0 0 1 1 0 1] #3 -> 3,4,6 + [1 1 1 0 0 0] #4 -> 1,2,3 External + ])); + +julia> Lara.get_external_cycle(V, copEV, copFE) +4 + +julia> typeof(Lara.get_external_cycle(V, copEV, copFE[1:3,:])) +Nothing +``` +""" 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,9 +515,46 @@ function get_external_cycle(V::Lar.Points, EV::Lar.ChainOp, FE::Lar.ChainOp) end end end + + +""" + pre_containment_test(bboxes) + +Generate the containment graph associated to `bboxes`. + +The function evaluate a `SparseArrays{Int8, n, n}` PRE-containmnet graph in the sense +that it stores `1` when the bbox associated to the `i`-th component contains entirely +the bbox associated to the `j`-th one. (where `n = length(bboxes)`) + +See also: + - [`Lar.Arrangement.componentgraph`](@ref). + - [`Lar.bbox_contains`](@ref). + +--- + +# Examples +```jldoctest +# Planar Tiles +julia> bboxes = [ + ([0.0 0.0], [1.0 1.0]) + ([0.0 0.0], [0.4 0.4]) + ([0.6 0.0], [1.0 0.4]) + ([0.0 0.6], [0.4 1.0]) + ([0.6 0.6], [1.0 1.0]) + ([2.0 3.0], [2.0 3.0]) # no intersection + ]; + +julia> Lara.pre_containment_test(bboxes) +5×5 SparseMatrixCSC{Int8,Int64} with 4 stored entries: + [2, 1] = 1 + [3, 1] = 1 + [4, 1] = 1 + [5, 1] = 1 +``` +""" function pre_containment_test(bboxes) n = length(bboxes) - containment_graph = spzeros(Int8, n, n) + containment_graph = SparseArrays.spzeros(Int8, n, n) for i in 1:n for j in 1:n @@ -286,6 +566,23 @@ function pre_containment_test(bboxes) return containment_graph end + + +""" + prune_containment_graph(n, V, EVs, shells, graph) + +Prunes the containment `graph` from the non-included faces. + +This method prunes the containment graph eliminating the included bouning boxes +that not corresponds to included faces. + +Do note that this method expects to work on biconnectet clusters of faces. + +See also: + - [`Lar.Arrangement.componentgraph`](@ref). + - [`Lar.point_in_face`](@ref). +``` +""" function prune_containment_graph(n, V, EVs, shells, graph) for i in 1:n @@ -309,6 +606,43 @@ function prune_containment_graph(n, V, EVs, shells, graph) end return graph end + + +""" + transitive_reduction!(graph) + +Prune the redundancies in containment `graph`. + +The ``m``-layer containment `graph` is reduced to the 1-layer containment `graph` +where only the first level nested components are considered. + +See also: [`Lar.Arrangement.componentgraph`](@ref). + +--- + +# Examples +```jldoctest +julia> containment = [ + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 1 0 0 0 0 0 + 1 0 0 0 0 0 + 1 0 1 0 0 0 + 1 0 1 0 1 0 + ]; + +julia> transitive_reduction!(containment) + +julia> containment +6×6 Array{Int64,2}: + 0 0 0 0 0 0 + 0 0 0 0 0 0 + 1 0 0 0 0 0 + 1 0 0 0 0 0 + 0 0 1 0 0 0 + 0 0 0 0 1 0 +``` +""" function transitive_reduction!(graph) n = size(graph, 1) for j in 1:n @@ -324,6 +658,16 @@ function transitive_reduction!(graph) end end + +""" + cell_merging(n, containment_graph, V, EVs, boundaries, shells, shell_bboxes) + +Cells composing for the Topological Gift Wrapping algorithm. + +This is the online part of the TGW algorithm. + +See also: [`Lar.Arrangement.planar_arrangement_2`](@ref). +""" 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) @@ -333,7 +677,7 @@ function cell_merging(n, containment_graph, V, EVs, boundaries, shells, shell_bb end boxes end - # initiolization + # initialization sums = Array{Tuple{Int, Int, Int}}(undef, 0); # assembling child components with father components for father in 1:n @@ -380,6 +724,97 @@ function cell_merging(n, containment_graph, V, EVs, boundaries, shells, shell_bb end +""" + componentgraph(V, copEV, bicon_comps) + +Topological Gift Wrapping algorithm on 2D skeletons. + +This is the offline part of the TGW algorithm. It takes in input a model and its +biconnected components mapping and evaluates usefull informations: + 1. Number of biconnected components. + 2. Component Graph of the biconnected structure. + 3. The 1-cells structure (UNMODIFIED). <----------------------------- Could be removed? + 4. Association between non-dangling 2-cells and their orientation (for each component). + 5. Association between 3-cells and 2-cells (with orientation, for each component). + 6. Association between 3-cells and their orientation (for each component). + 7. Shell bounding boxes of the components. + + +See also: [`Lar.Arrangement.planar_arrangement_2`](@ref) for the TGW. + +--- + +# Examples +```jldoctest +# Papillon +julia> V = [0.0 0.0; 0.0 3.0; 2.0 1.5; 4.0 0.0; 4.0 3.0]; + +julia> copEV = SparseArrays.sparse(Array{Int8, 2}([ + [1 1 0 0 0] #1 -> 1,2 + [0 1 1 0 0] #2 -> 2,3 + [1 0 1 0 0] #3 -> 3,1 + [0 0 1 1 0] #4 -> 3,4 + [0 0 0 1 1] #5 -> 4,5 + [0 0 1 0 1] #6 -> 3,5 + ])); + +julia> copFE = SparseArrays.sparse(Array{Int8, 2}([ + [1 1 1 0 0 0] #1 -> 1,2,3 + [0 0 0 1 1 1] #2 -> 4,5,6 + [1 1 1 1 1 1] #3 -> 1,2,3,4,5,6 External + ])); + +julia> bicon_comps = [[1, 2, 3], [4, 5, 6]]; + +julia> Lara.componentgraph(V, copEV, bicon_comps)[1] +2 + +julia> Lara.componentgraph(V, copEV, bicon_comps)[2] +2×2 SparseMatrixCSC{Int8,Int64} with 0 stored entries + +julia> Lara.componentgraph(V, copEV, bicon_comps)[4] +2-element Array{SparseMatrixCSC{Int8,Int64},1}: + + [1, 1] = -1 + [3, 1] = -1 + [1, 2] = 1 + [2, 2] = -1 + [2, 3] = 1 + [3, 3] = 1 + + [1, 3] = -1 + [3, 3] = -1 + [1, 4] = 1 + [2, 4] = -1 + [2, 5] = 1 + [3, 5] = 1 + +julia> Lara.componentgraph(V, copEV, bicon_comps)[5] +2-element Array{SparseMatrixCSC{Int8,Int64},1}: + + [1, 1] = -1 + [1, 2] = -1 + [1, 3] = 1 + + [1, 1] = 1 + [1, 2] = 1 + [1, 3] = -1 + +julia> Lara.componentgraph(V, copEV, bicon_comps)[6] +2-element Array{SparseVector{Int8,Int64},1}: + [1] = 1 + [2] = 1 + [3] = -1 + [1] = -1 + [2] = -1 + [3] = 1 + +julia> Lara.componentgraph(V, copEV, bicon_comps)[7] +2-element Array{Any,1}: + ([0.0 0.0], [2.0 3.0]) + ([2.0 0.0], [4.0 3.0]) +``` +""" function componentgraph(V, copEV, bicon_comps) # arrangement of isolated components @@ -426,7 +861,47 @@ function componentgraph(V, copEV, bicon_comps) end -function cleandecomposition(V, copEV, sigma) +""" + cleandecomposition(V, copEV, sigma) + +!!! NOT WORKING + +This function clears the model `(V, copEV)` from all edges outside ``σ``. + +This function takes a model `(V, copEV)` and a `Lar.Chain` ``σ`` and +gives back the dropped model and and a vector `todel` of the 2-cells to drop. + +--- + +# Examples +```jldoctest +# Nested Triangles +julia> V = [ + 0.0 0.0; 2.0 0.0; 4.0 0.0; + 1.0 1.5; 3.0 1.5; 2.0 3.0; + 2.0 -3.; -2. 3.0; 6.0 3.0 + ]; + +julia> copEV = SparseArrays.sparse(Array{Int8, 2}([ + [1 0 1 0 0 0 0 0 0] #1 -> 1,3 + [1 0 0 0 0 1 0 0 0] #2 -> 1,6 + [0 0 1 0 0 1 0 0 0] #3 -> 3,6 + [0 1 0 1 0 0 0 0 0] #4 -> 2,4 + [0 1 0 0 1 0 0 0 0] #5 -> 2,5 + [0 0 0 1 1 0 0 0 0] #6 -> 4,5 + [0 0 0 0 0 0 1 1 0] #7 -> 7,8 + [0 0 0 0 0 0 1 0 1] #8 -> 7,9 + [0 0 0 0 0 0 0 1 1] #9 -> 8,9 + ])); + +julia> σ = SparseArrays.sparse([0; 0; 0; 1; 1; 1; 0; 0; 0]); + +todel, V, copEV = Lara.cleandecomposition(V, copEV, convert(Lar.Chain, σ)) + +Plasm.view(convert(Lar.Points, V'), Lar.cop2lar(copEV)); +``` +""" +function cleandecomposition(V::Lar.Points, copEV::Lar.ChainOp, sigma::Lar.Chain) # Deletes edges outside sigma area todel = [] new_edges = [] @@ -489,19 +964,56 @@ function cleandecomposition(V, copEV, sigma) end - +#-------------------------------------------------------------------------------------------------------------------------------- +# MAIN PIPELINE +#-------------------------------------------------------------------------------------------------------------------------------- + """ - function planar_arrangement_1( V::Lar.Points, copEV::Lar.ChainOp, - sigma::Lar.Chain=spzeros(Int8, 0), - return_edge_map::Bool=false, - multiproc::Bool=false) + function planar_arrangement_1(V, copEV[, sigma[, return_edge_map[, multiproc]]]) -Compute the arrangement on the given cellular complex 1-skeleton in 2D. -First part of arrangement's algorithmic pipeline. +First part of arrangement's algorithmic pipeline. + +This function computes the pairwise intersection between each edge of a given 2D cellular complex 1-skeleton. +The computation is speeded up via the evaluation of the Spatial Index. See [`Lar.spaceindex`](@ref). + +See also: [`Lar.planar_arrangement`](@ref) for the complete pipeline. + +--- + +# Examples +```jldoctest +julia> EV = [[1, 2], [3, 4], [1, 3], [2, 4], [5, 6], [7, 8], [5, 7], [6, 8]]; + +julia> V = [ + 0.0 0.5 0.0 0.5 0.3 1.0 0.3 1.0; + 0.0 0.0 1.0 1.0 0.5 0.5 1.0 1.0 + ]; + +julia> W = convert(Lar.Points, V'); # Infering type for W = V' + +julia> cop_EV = Lar.coboundary_0(EV::Lar.Cells); + +julia> W1, copEV1 = Lar.planar_arrangement_1(W::Lar.Points, cop_EV::Lar.ChainOp) +([0.0 0.0; 0.5 0.0; … ; 1.0 0.5; 1.0 1.0], + [1 , 1] = 1 + [4 , 1] = 1 + [1 , 2] = 1 + ⋮ + [11, 8] = 1 + [9 , 9] = 1 + [11, 9] = 1) + +julia> EV1 = Lar.cop2lar(copEV1); + +julia> V1 = convert(Lar.Points, W1'); + +julia> Plasm.view(Plasm.numbering(0.1)((V,[[[k] for k=1:size(V,2)], EV]))); +julia> Plasm.view(Plasm.numbering(0.1)((V1,[[[k] for k=1:size(V1,2)], EV1]))); +``` """ -function planar_arrangement_1( V, copEV, - sigma::Lar.Chain=spzeros(Int8, 0), +function planar_arrangement_1(V::Lar.Points, copEV::Lar.ChainOp, + sigma::Lar.Chain=SparseArrays.spzeros(Int8, 0), return_edge_map::Bool=false, multiproc::Bool=false) @@ -556,64 +1068,112 @@ function planar_arrangement_1( V, copEV, end # merging of close vertices and edges (2D congruence) V, copEV = rV, rEV + if return_edge_map + V, copEV, edge_map = Lar.Arrangement.merge_vertices!(V, copEV, edge_map) + return V, copEV, edge_map + end V, copEV = Lar.Arrangement.merge_vertices!(V, copEV, edge_map) return V, copEV -end - +end + + """ - function planar_arrangement_2(V, copEV, bicon_comps, - sigma::Lar.Chain=spzeros(Int8, 0), - return_edge_map::Bool=false, - multiproc::Bool=false) + function planar_arrangement_2(V, copEV, bicon_comps + [, sigma[, return_edge_map[, multiproc]]] + ) +Second part of arrangement's algorithmic pipeline. -Compute the arrangement on the given cellular complex 1-skeleton in 2D. -Second part of arrangement's algorithmic pipeline. +This function is the complete Topological Gift Wrapping (TGW) algorithm that is firstly +locally used in order to decompose the 2-cells and then globally to generate the 3-cells +of the arrangement of the ambient space ``E^3``. +During this process each dangling 2-cell is removed. +Do note that the isolated 1-cells are not removed by this procedure. + +See also: [`Lar.planar_arrangement`](@ref) for the complete pipeline. + +--- + +# Examples +```jldoctest +# Triforce +julia> V = [0.0 0.0; 2.0 0.0; 4.0 0.0; 1.0 1.5; 3.0 1.5; 2.0 3.0; 3.0 3.0]; +julia> W = convert(Lar.Points, V'); +julia> EV = SparseArrays.sparse(Array{Int8, 2}([ + [1 1 0 0 0 0 0] #1 -> 1,2 + [0 1 1 0 0 0 0] #2 -> 2,3 + [1 0 0 1 0 0 0] #3 -> 1,4 + [0 0 0 1 0 1 0] #4 -> 4,6 + [0 0 1 0 1 0 0] #5 -> 3,5 + [0 0 0 0 1 1 0] #6 -> 5,6 + [0 1 0 1 0 0 0] #7 -> 2,4 + [0 1 0 0 1 0 0] #8 -> 2,5 + [0 0 0 1 1 0 0] #9 -> 4,5 + [0 0 0 0 0 1 1] + ])); + +julia> Plasm.view(Plasm.numbering(0.5)( + (W,[[[k] for k=1:size(W,2)], Lar.cop2lar(EV)]) + )); + +julia> bicon_comps = Lar.Arrangement.biconnected_components(EV); + +julia> V, EV, FE = Lar.Arrangement.planar_arrangement_2(V, EV, bicon_comps); + +julia> Plasm.view( Plasm.numbering1(0.5)((V, EV, FE)) ); +``` """ -function planar_arrangement_2(V, copEV, bicon_comps, +function planar_arrangement_2(V::Lar.Points, copEV::Lar.ChainOp, bicon_comps::Array{Array{Int, 1}, 1}, sigma::Lar.Chain=spzeros(Int8, 0), return_edge_map::Bool=false, multiproc::Bool=false) # 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 - todel, V, copEV = cleandecomposition(V, copEV, sigma) - V, copEV = Lar.delete_edges(todel, V, copEV) + if sigma.n > 0 ## number of columns of sigma > 0 + todel, V, copEV = Lar.Arrangement.cleandecomposition(V, copEV, sigma) + V, copEV = Lar.delete_edges(todel, V, copEV) # <----------------------non mi è chiaro perchè non è dentro la fun end # final shell poset aggregation and FE output copEV, FE = Lar.Arrangement.cell_merging( n, containment_graph, V, EVs, boundaries, shells, shell_bboxes) if (return_edge_map) return V, copEV, FE, edge_map - else - return V, copEV, FE end return V, copEV, FE end - """ - planar_arrangement(V::Points, copEV::ChainOp, - [sigma::Chain], [return_edge_map::Bool], [multiproc::Bool]) + planar_arrangement(V::Points, copEV::ChainOp + [, sigma::Chain[, return_edge_map::Bool[, multiproc::Bool]]] + ) Compute the arrangement on the given cellular complex 1-skeleton in 2D. Whole arrangement's algorithmic pipeline. A cellular complex is arranged when the intersection of every possible pair of cell of the complex is empty and the union of all the cells is the whole Euclidean space. -The basic method of the function without the `sigma`, `return_edge_map` and `multiproc` arguments -returns the full arranged complex `V`, `EV` and `FE`. +The method with no additional arguments specified gives back the full arranged complex +`V`, `EV` and `FE`. + +See also: + - [`Lar.Arrangement.planar_arrangement_1`](@ref) + - [`Lar.Arrangement.biconnected_components`](@ref) + - [`Lar.Arrangement.planar_arrangement_2`](@ref) ## Additional arguments: -- `sigma::Chain`: if specified, `planar_arrangement` will delete from the output every edge and face outside this cell. Defaults to an empty cell. -- `return_edge_map::Bool`: makes the function return also an `edge_map` which maps the edges of the imput to the one of the output. Defaults to `false`. -- `multiproc::Bool`: Runs the computation in parallel mode. Defaults to `false`. + - `sigma::Chain`: if specified, `planar_arrangement` will delete from + the output every edge and face outside this cell. Defaults to an empty cell. + - `return_edge_map::Bool`: makes the function return also an `edge_map` which + maps the edges of the imput to the one of the output. Defaults to `false`. + - `multiproc::Bool`: Runs the computation in parallel mode. Defaults to `false`. + +Many examples could be found in Lar Documentation and `examples` directory. """ function planar_arrangement( V::Lar.Points, copEV::Lar.ChainOp, sigma::Lar.Chain=spzeros(Int8, 0), @@ -621,10 +1181,10 @@ function planar_arrangement( V::Lar.Points, copEV::Lar.ChainOp, multiproc::Bool=false) # edge subdivision - V, copEV = Lar.planar_arrangement_1(V::Lar.Points, copEV::Lar.ChainOp) + V, copEV = Lar.Arrangement.planar_arrangement_1(V::Lar.Points, copEV::Lar.ChainOp) # biconnected components - bicon_comps = Lar.Arrangement.biconnected_components(copEV) + bicon_comps = Lar.Arrangement.biconnected_components(copEV::Lar.ChainOp) # 2-complex and containment graph - V, copEV, copFE = Lar.planar_arrangement_2(V, copEV, bicon_comps) + V, copEV, copFE = Lar.Arrangement.planar_arrangement_2(V::Lar.Points, copEV::Lar.ChainOp, bicon_comps) return V, copEV, copFE -end +end \ No newline at end of file diff --git a/src/refactoring.jl b/src/refactoring.jl index 0ed47a7b..6af7e85e 100644 --- a/src/refactoring.jl +++ b/src/refactoring.jl @@ -238,7 +238,7 @@ end Generation of *space indexes* for all ``(d-1)``-dim cell members of `model`. *Spatial index* made by ``d`` *interval-trees* on -bounding boxes of ``sigma in S_{d−1}``. Spatial queries solved by +bounding boxes of ``σ ∈ S_{d−1}``. Spatial queries solved by intersection of ``d`` queries on IntervalTrees generated by bounding-boxes of geometric objects (LAR cells). diff --git a/test/refactor_planar_arrangement.jl b/test/refactor_planar_arrangement.jl new file mode 100644 index 00000000..3c743a7c --- /dev/null +++ b/test/refactor_planar_arrangement.jl @@ -0,0 +1,369 @@ +using Test +using SparseArrays +using LinearAlgebra +using LinearAlgebraicRepresentation +using LinearAlgebraicRepresentation.Arrangement +Lar = LinearAlgebraicRepresentation +Lara = LinearAlgebraicRepresentation.Arrangement + +@testset "Pipeline Arrangement 1" begin + V = [0.0 1.0; 1.0 1.0; 3.0 1.0; 4.0 1.0; 2.0 2.0; 2.0 0.0]; + EV = SparseArrays.sparse(Array{Int8, 2}([ + [1 0 0 1 0 0] #1->1,4 + [0 1 1 0 0 0] #2->2,3 + [0 0 0 0 1 1] #3->5,6 + [0 1 0 0 1 0] #4->2,5 + [0 0 1 0 0 1] #5->3,6 + ])) + bigPI = Lar.spaceindex((convert(Lar.Points, V'), Lar.cop2lar(EV))); + + @testset "intersect_edges" begin + @test Lara.intersect_edges(V, EV[1, :], EV[3, :]) == [([2.0 1.0], 0.5)]; + @test Lara.intersect_edges(V, EV[1, :], EV[3, :]) == Lara.intersect_edges(V, EV[2, :], EV[3, :]) + @test Lara.intersect_edges(V, EV[1, :], EV[2, :]) == [([1.0 1.0], 0.25); ([3.0 1.0], 0.75)]; + @test Lara.intersect_edges(V, EV[2, :], EV[1, :]) == Lara.intersect_edges(V, EV[4, :], EV[5, :]); + @test Lara.intersect_edges(V, EV[1, :], EV[4, :]) == [([1.0 1.0], 0.25)]; + @test Lara.intersect_edges(V, EV[2, :], EV[4, :]) == [([1.0 1.0], 0.0)]; + + end + + @testset "merge_vertices" begin + V1, EV1 = Lara.frag_edge(V, EV, 1, bigPI); + ans1 = SparseArrays.sparse(Array{Int8, 2}([ + [1 0 1 0 0] #1->1,3 + [0 0 1 0 1] #2->3,5 + [0 0 0 1 1] #3->4,5 + [0 1 0 1 0] #4->2,4 + ])); + ans2 = SparseArrays.sparse(Array{Int8, 2}([ + [1 0 1] #1->1,3 + [0 1 1] #2->2,3 + ])); + @test Lara.merge_vertices!(V1, EV1)[1] == [0.0 1.0; 4.0 1.0; 1.0 1.0; 3.0 1.0; 2.0 1.0]; + @test Lara.merge_vertices!(V1, EV1)[2] == ans1; + + @test Lara.merge_vertices!(V1, EV1, [[-1]], 1)[1] == [0.0 1.0; 4.0 1.0; 2.0 1.0]; + @test Lara.merge_vertices!(V1, EV1, [[-1]], 1)[2] == ans2; + end + + @testset "Complete Pipeline 1" begin + Vans = [0.0 1.0; 4.0 1.0; 1.0 1.0; 3.0 1.0; 2.0 1.0; 2.0 2.0; 2.0 0.0]; + EVans = SparseArrays.sparse(Array{Int8, 2}([ + [1 0 1 0 0 0 0] # 1->3 + [0 0 1 0 1 0 0] # 3->4 + [0 0 0 1 1 0 0] # 4->5 + [0 1 0 1 0 0 0] # 2->4 + [0 0 0 0 1 1 0] # 5->6 + [0 0 0 0 1 0 1] # 5->7 + [0 0 1 0 0 1 0] # 3->6 + [0 0 0 1 0 0 1] # 4->7 + ])); + @test Lara.planar_arrangement_1(V, EV)[1] == Vans + @test Lara.planar_arrangement_1(V, EV)[2] == EVans + end +end + +#-------------------------------------------------------------------------------------------------------------------------------- + +@testset "Biconnected Components" begin + @testset "Normal Execution" begin + EV = SparseArrays.sparse(Array{Int8, 2}([ + [1 1 0 0 0 0 0 0 0 0] #1 -> 1,2 | + [0 1 1 0 0 0 0 0 0 0] #2 -> 2,3 | + [1 0 1 0 0 0 0 0 0 0] #3 -> 3,1 | + [0 0 1 1 0 0 0 0 0 0] #4 -> 3,4 | + [0 0 1 0 0 1 0 0 0 0] #5 -> 3,6 | + [0 0 1 0 0 0 1 0 0 0] #6 -> 3,7 - + [0 0 0 1 1 0 0 0 0 0] #7 -> 4,5 | + [0 0 0 0 1 1 0 0 0 0] #8 -> 5,6 | + [0 0 0 0 0 0 0 1 1 0] #9 -> 8,9 | + [0 0 0 0 0 0 0 0 1 1] #10-> 9,10 | + [0 0 0 0 0 0 0 1 0 1] #11-> 8,10 | + ])); + bicomp = sort(Lara.biconnected_components(EV)); + + @test length(Lara.biconnected_components(EV)) == 3; + @test sort(bicomp[1]) == [1, 2, 3]; + @test sort(bicomp[2]) == [4, 5, 7, 8]; + @test sort(bicomp[3]) == [9, 10, 11]; + end + + @testset "Void Components" begin + EV = SparseArrays.sparse(Array{Int8, 2}([ + [1 1 0 0] #1 -> 1,2 + [1 0 1 0] #2 -> 1,3 + [1 0 0 1] #3 -> 1,4 + ])); + + @test length(Lara.biconnected_components(EV)) == 0; + end + + @testset "Couple of Components" begin + EV = SparseArrays.sparse(Array{Int8, 2}([ + [1 1 0] #1 -> 1,2 | + [1 1 0] #2 -> 1,2 | + [1 0 1] #3 -> 1,2 + ])); + + @test length(Lara.biconnected_components(EV)) == 1; + @test sort(Lara.biconnected_components(EV)[1]) == [1, 2]; + end +end + +#-------------------------------------------------------------------------------------------------------------------------------- + +@testset "Pipeline Arrangement 2" begin + + @testset "Containment Boxes on Nested Triangular" begin + W = [ + 0.0 0.0; 8.0 0.0; 4.0 6.0; + 2.0 2.0; 6.0 2.0; 4.0 5.0; + 8.0 1.0; 5.0 6.0; 8.0 6.0; + 3.0 3.0; 5.0 3.0; 4.0 4.0]; + copEV = SparseArrays.sparse(Array{Int8, 2}([ + [1 1 0 0 0 0 0 0 0 0 0 0] # 1 -> 1,2 + [0 1 1 0 0 0 0 0 0 0 0 0] # 2 -> 2,3 + [1 0 1 0 0 0 0 0 0 0 0 0] # 3 -> 3,1 + [0 0 0 1 1 0 0 0 0 0 0 0] # 4 -> 4,5 + [0 0 0 0 1 1 0 0 0 0 0 0] # 5 -> 5,6 + [0 0 0 1 0 1 0 0 0 0 0 0] # 6 -> 4,6 + [0 0 0 0 0 0 1 1 0 0 0 0] # 7 -> 7,8 + [0 0 0 0 0 0 0 1 1 0 0 0] # 8 -> 8,9 + [0 0 0 0 0 0 1 0 1 0 0 0] # 9 -> 7,9 + [0 0 0 0 0 0 0 0 0 1 1 0] #10 -> 10,11 + [0 0 0 0 0 0 0 0 0 0 1 1] #11 -> 11,12 + [0 0 0 0 0 0 0 0 0 1 0 1] #12 -> 10,12 + ])); + + EVs = [ + SparseArrays.sparse(Array{Int8, 2}([ + [-1 1 0 0 0 0 0 0 0 0 0 0] #1 -> 1,2 + [ 0 -1 1 0 0 0 0 0 0 0 0 0] #2 -> 2,3 + [-1 0 1 0 0 0 0 0 0 0 0 0] #3 -> 3,1 + ])), + SparseArrays.sparse(Array{Int8, 2}([ + [0 0 0 -1 1 0 0 0 0 0 0 0] #4 -> 4,5 + [0 0 0 0 -1 1 0 0 0 0 0 0] #5 -> 5,6 + [0 0 0 -1 0 1 0 0 0 0 0 0] #6 -> 4,6 + ])), + SparseArrays.sparse(Array{Int8, 2}([ + [0 0 0 0 0 0 -1 1 0 0 0 0] #7 -> 7,8 + [0 0 0 0 0 0 0 -1 1 0 0 0] #8 -> 8,9 + [0 0 0 0 0 0 -1 0 1 0 0 0] #9 -> 7,9 + ])), + SparseArrays.sparse(Array{Int8, 2}([ + [0 0 0 0 0 0 0 0 0 -1 1 0] #10 -> 10,11 + [0 0 0 0 0 0 0 0 0 0 -1 1] #11 -> 11,12 + [0 0 0 0 0 0 0 0 0 -1 0 1] #12 -> 10,12 + ])) + ]; + + shells = [ + SparseArrays.sparse(Array{Int8}([-1; -1; 1])), + SparseArrays.sparse(Array{Int8}([-1; -1; 1])), + SparseArrays.sparse(Array{Int8}([-1; -1; 1])), + SparseArrays.sparse(Array{Int8}([-1; -1; 1])), + ]; + + bicon_comps = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]; + shell_bboxes = [ + ([0.0 0.0], [8.0 6.0]), + ([2.0 2.0], [6.0 5.0]), + ([5.0 1.0], [8.0 6.0]), + ([3.0 3.0], [5.0 4.0]) + ]; + precontainment = sparse(Array{Int8,2}([ + [0 0 0 0] + [1 0 0 0] + [1 0 0 0] + [1 1 0 0] + ])); + containment_graph = sparse(Array{Int8,2}([ + [0 0 0 0] + [1 0 0 0] + [0 0 0 0] + [1 1 0 0] + ])); + transitive = sparse(Array{Int8,2}([ + [0 0 0 0] + [1 0 0 0] + [0 0 0 0] + [0 1 0 0] + ])); + + @test Lara.pre_containment_test(shell_bboxes) == precontainment; + @test Lara.prune_containment_graph(3, W, EVs, shells, containment_graph) == containment_graph; + + Lar.Arrangement.transitive_reduction!(containment_graph) + + @test containment_graph == transitive; + end + + @testset "Pipeline Arrangement 2 over K^4" begin + + # K^4 + edge + W = [0.0 0.0; 4.0 0.0; 2.0 3.0; 2.0 1.5]; + copEV = SparseArrays.sparse(Array{Int8, 2}([ + [1 1 0 0] #1 -> 1,2 + [0 1 1 0] #2 -> 2,3 + [1 0 1 0] #3 -> 3,1 + [1 0 0 1] #4 -> 1,2 + [0 1 0 1] #5 -> 2,3 + [0 0 1 1] #6 -> 3,1 + ])); + + copFE = SparseArrays.sparse(Array{Int8, 2}([ + [1 0 0 1 1 0] #1 -> 1,4,5 + [0 1 0 0 1 1] #2 -> 2,5,6 + [0 0 1 1 0 1] #3 -> 3,4,6 + [1 1 1 0 0 0] #4 -> 1,2,3 External + ])); + + bicon_comps = [[1, 2, 3, 4, 5, 6]]; + + @testset "External Cycle" begin + @test Lara.get_external_cycle(W, copEV, copFE) == 4; + @test typeof(Lara.get_external_cycle(W, copEV, copFE[1:3,:])) == Nothing + end + + @testset "Biconnected Components" begin + @test length(Lara.biconnected_components(copEV)) == 1; + @test sort(Lara.biconnected_components(copEV)[1]) == bicon_comps[1]; + end + + @testset "Complete Component Graph" begin + EVs = [SparseArrays.sparse(Array{Int8, 2}([ + [-1 1 0 0] #1 -> 1,2 + [ 0 -1 1 0] #2 -> 2,3 + [-1 0 1 0] #3 -> 3,1 + [-1 0 0 1] #4 -> 1,2 + [ 0 -1 0 1] #5 -> 2,3 + [ 0 0 -1 1] #6 -> 3,1 + ]))]; + + boundaries = [SparseArrays.sparse(Array{Int8, 2}([ + [ 1 0 0 -1 1 0] #1 -> 1,4,5 + [ 0 1 0 0 -1 1] #2 -> 2,5,6 + [ 0 0 -1 1 0 -1] #3 -> 3,4,6 + ]))]; + + @test Lara.componentgraph(W, copEV, bicon_comps)[1] == 1; + @test size(Lara.componentgraph(W, copEV, bicon_comps)[2]) == (1, 1); + @test Lara.componentgraph(W, copEV, bicon_comps)[3] == W; + @test Lara.componentgraph(W, copEV, bicon_comps)[4] == EVs; + @test Lara.componentgraph(W, copEV, bicon_comps)[5] == boundaries; + @test Lara.componentgraph(W, copEV, bicon_comps)[6] == [sparse(Array{Int8}([-1; -1; 1; 0; 0; 0]))]; + @test Lara.componentgraph(W, copEV, bicon_comps)[7] == [([0.0 0.0], [4.0 3.0])]; + end + end + + @testset "Pipeline Arrangement 2 over planar_tile" begin + W = [ + 0.0 0.0; 0.4 0.0; 0.5 0.0; 0.6 0.0; 1.0 0.0; + 0.0 0.4; 1.0 0.4; + 0.0 0.5; 1.0 0.5; + 0.0 0.6; 1.0 0.6; + 0.0 1.0; 0.4 1.0; 0.5 1.0; 0.6 1.0; 1.0 1.0; + 0.5 0.5 + ]; + copEV = SparseArrays.sparse(Array{Int8, 2}([ + [1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] #1 -> 1 2 + [1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0] #2 -> 1 6 + [0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0] #3 -> 2 6 + + [0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0] #4 -> 4 5 + [0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0] #5 -> 4 7 + [0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0] #6 -> 5 7 + + [0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0] #7 -> 10 12 + [0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0] #8 -> 10 13 + [0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0] #9 -> 12 13 + + [0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0] #10 -> 11 15 + [0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0] #11 -> 11 16 + [0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0] #12 -> 15 16 + + [0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0] #13 -> 3 8 + [0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0] #14 -> 3 9 + [0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1] #15 -> 3 17 + [0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1] #16 -> 8 17 + [0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0] #17 -> 8 14 + [0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1] #18 -> 9 17 + [0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0] #19 -> 9 14 + [0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1] #20 -> 14 17 + ])); + + copFE = SparseArrays.sparse(Array{Int8, 2}([ + [1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] #1 -> 1,2,3 + [0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0] #2 -> 4,5,6 + [0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0] #3 -> 7,8,9 + [0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0] #4 -> 10,11,12 + [0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 0 0 0 0] #5 -> 13,15,16 + [0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 0 0] #6 -> 14,15,18 + [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1] #7 -> 16,17,20 + [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1] #8 -> 18,19,20 + [1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 0 1 0] #9 -> external + ])); + + bicon_comps = [ + [ 1, 2, 3], + [ 4, 5, 6], + [ 7, 8, 9], + [10, 11, 12], + [13, 14, 15, 16, 17, 18, 19, 20] + ]; + + bboxes = [ + ([0.0 0.0], [0.4 0.4]) + ([0.6 0.0], [1.0 0.4]) + ([0.0 0.6], [0.4 1.0]) + ([0.6 0.6], [1.0 1.0]) + ([0.0 0.0], [1.0 1.0]) + ]; + + @testset "External Cycle" begin + @test Lara.get_external_cycle(W, copEV, copFE) == 9; + @test typeof(Lara.get_external_cycle(W, copEV, copFE[1:8,:])) == Nothing + end + + @testset "Biconnected Components" begin + @test length(Lara.biconnected_components(copEV)) == 5; + @test sort(sort(Lara.biconnected_components(copEV))[1]) == bicon_comps[1]; + @test sort(sort(Lara.biconnected_components(copEV))[2]) == bicon_comps[2]; + @test sort(sort(Lara.biconnected_components(copEV))[3]) == bicon_comps[3]; + @test sort(sort(Lara.biconnected_components(copEV))[4]) == bicon_comps[4]; + @test sort(sort(Lara.biconnected_components(copEV))[5]) == bicon_comps[5]; + end + + @testset "Containment Boxes" begin + pre_containment = SparseArrays.sparse(Array{Int8, 2}([ + [0 0 0 0 1] + [0 0 0 0 1] + [0 0 0 0 1] + [0 0 0 0 1] + [0 0 0 0 0] + ])); + @test Lara.pre_containment_test(bboxes) == pre_containment; + end + + @testset "Complete Component Graph" begin + shells = [ + sparse(Array{Int8}([-1; 1; -1])), + sparse(Array{Int8}([-1; 1; -1])), + sparse(Array{Int8}([ 1; -1; 1])), + sparse(Array{Int8}([ 1; -1; 1])), + sparse(Array{Int8}([ 1; -1; 0; 0; 1; 0; -1; 0])) + ]; + + @test Lara.componentgraph(W, copEV, bicon_comps)[1] == 5; + @test Lara.componentgraph(W, copEV, bicon_comps)[2] == SparseArrays.spzeros(Int8, 5, 5); + @test Lara.componentgraph(W, copEV, bicon_comps)[3] == W; + # @test abs(Lara.componentgraph(W, copEV, bicon_comps)[4]) == EV; + # @test abs(Lara.componentgraph(W, copEV, bicon_comps)[5]) == boundaries; + @test Lara.componentgraph(W, copEV, bicon_comps)[6] == shells; + @test Lara.componentgraph(W, copEV, bicon_comps)[7] == bboxes; + end + + + end +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 4cb0243a..d18732d4 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,7 +2,8 @@ #Lar = LinearAlgebraicRepresentation include("./utilities.jl") -include("./planar_arrangement.jl") +#include("./planar_arrangement.jl") +include("./refactor_planar_arrangement.jl") include("./dimension_travel.jl") include("./largrid.jl")