TGW3D.TGW3D
— ModuleL’algoritmo Topological Gift Wrapping calcola le d-celle di una partizione di spazio generate da loro partendo da un oggetto geometrico d-1 dimensionale.
TGW prende una matrice sparsa di dimensione d-1 in input e produce in output la matrice sparsa di dimensione d sconosciuta aumentata dalle celle esterne.
Riferimenti
Questa pagina ha i riferimenti a tutti i tipi, metodi e funzioni utilizzati.
Tipi
TGW3D.ChainOp
— TypeChainOp = SparseArrays.SparseMatrixCSC{Int8,Int}
Dichiarazione Alias di specifiche strutture dati di LAR.
La SparseMatrix
nel formato Colonne sparse compresse, contiene la rappresentazione in coordinate di un operatore tra lo spazio lineare delle P-chains
.
Operatori $P-Boundary : P-Chain -> (P-1)-Chain$ e $P-Coboundary : P-Chain -> (P+1)-Chain$ sono tipicamente immagazinati come ChainOp
con elementi in ${-1,0,1}$ oppure in ${0,1}$, per operatori assegnati e non-assegnati rispettivamente.
TGW3D.Points
— TypePoints = Array{Number,2}
Dichiarazione Alias di specifiche strutture dati di LAR.
Array{Number,2,1}
$M x N$ compatto per immagazzinare la posizione dei vertici (0-cells) di un complesso cellulare. Il numero delle righe $M$ è la dimensione dello spazio di inclusione. Il numero delle colonne $N$ è il numero dei vertici.
Funzioni
Dati utilizzati per l'esecuzione degli esempi:
using SparseArrays
V = Float64[
0 0 0; 0 1 0;
1 1 0; 1 0 0;
0 0 1; 0 1 1;
1 1 1; 1 0 1
]
EV = sparse(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;
-1 0 0 0 1 0 0 0;
0 -1 0 0 0 1 0 0;
0 0 -1 0 0 0 1 0;
0 0 0 -1 0 0 0 1;
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;
])
FE = sparse(Int8[
1 1 1 -1 0 0 0 0 0 0 0 0;
0 0 0 0 0 0 0 0 -1 -1 -1 1;
-1 0 0 0 1 -1 0 0 1 0 0 0;
0 -1 0 0 0 1 -1 0 0 1 0 0;
0 0 -1 0 0 0 1 -1 0 0 1 0;
0 0 0 1 -1 0 0 1 0 0 0 -1;
])
TGW3D.filter_fn
— Methodfunction filter_fn(face)
Funzione di filtro per la funzione merge_vertices
. La funzione filter_fn
prende in input una faccia e restituisce true
se i vertici della faccia non sono stati visitati, false
altrimenti.
Input
face::Vector{Tuple{Int64, Int64}}
Output
true/false::Bool
TGW3D.frag_face
— Methodfunction frag_face(
V::Points,
EV::ChainOp,
FE::ChainOp,
sp_idx::Vector{Vector{Int64}},
sigma::Int64)
Prende la faccia sigma
e la trasforma in 2D per poter calcolare le intersezioni con le facce in sp_idx[sigma]
ed ottenere la disposizione 2D della faccia sigma
.
Input
V::Points
EV::ChainOp
FE::ChainOp
sp_idx::Vector{Vector{Int64}}
sigma::Int64
Output
nV::Points
nEV::ChainOp
nFE::ChainOp
Esempi
julia> nV, nEV, nFE = frag_face(V, EV, FE, [[2,3,4,5]], 2)
([0.0 0.0 1.0; 0.0 1.0 1.0; 1.0 1.0 1.0; 1.0 0.0 1.0], sparse([1, 4, 1, 2, 2, 3, 3, 4],
[1, 1, 2, 2, 3, 3, 4, 4], Int8[-1, -1, 1, -1, 1, -1, 1, 1], 4, 4), sparse([1, 1, 1, 1],
[1, 2, 3, 4], Int8[1, 1, 1, -1], 1, 4))
julia> nV
4×3 Matrix{Float64}:
0.0 0.0 1.0
0.0 1.0 1.0
1.0 1.0 1.0
1.0 0.0 1.0
julia> nEV
4×4 SparseMatrixCSC{Int8, Int64} with 8 stored entries:
-1 1 ⋅ ⋅
⋅ -1 1 ⋅
⋅ ⋅ -1 1
-1 ⋅ ⋅ 1
julia> nFE
1×4 SparseMatrixCSC{Int8, Int64} with 4 stored entries:
1 1 1 -1
TGW3D.frag_face_channel
— Methodfunction frag_face_channel(
in_chan,
out_chan,
V::Points,
EV::ChainOp,
FE::ChainOp,
sp_idx::Vector{Int64})
Funziona che parallelizza, con l'utilizzo dei canali, la frammentazione delle facce in FE
rispetto le facce in sp_idx
.
Input
in_chan
out_chan
V::Points
EV::ChainOp
FE::ChainOp
sp_idx::Vector{Int64}
Output
V::Points
EV::ChainOp
TGW3D.merge_vertices
— Functionfunction merge_vertices(
V::Points,
EV::ChainOp,
FE::ChainOp,
[err=1e-4])
Rimuove i vertici congruenti ad un singolo rappresentatante, traduce i lati per tener conto della congruenza ed otteniene nuove facce congruenti.
Argomenti addizionali:
err
: Limite di errore massimo che si vuole utilizzare. Di Defaults a1e-4
.
Input
V::Points
EV::ChainOp
FE::ChainOp
err=1e-4
Output
nV::Points
nEV::ChainOp
nFE::ChainOp
Esempi
julia> nV, nEV, nFE = merge_vertices(V, EV, FE)
([0.0 0.0 0.0; 0.0 1.0 0.0; … ; 1.0 1.0 1.0; 1.0 0.0 1.0], sparse([1, 4, 5, 1, 2, 6, 2, 3, 7, 3
… 12, 6, 9, 10, 7, 10, 11, 8, 11, 12], [1, 1, 1, 2, 2, 2, 3, 3, 3, 4 … 5, 6, 6, 6, 7, 7, 7,
8, 8, 8], Int8[-1, -1, -1, 1, -1, -1, 1, -1, -1, 1 … -1, 1, 1, -1, 1, 1, -1, 1, 1, 1], 12, 8),
sparse([1, 3, 1, 4, 1, 5, 1, 6, 3, 6 … 5, 6, 2, 3, 2, 4, 2, 5, 2, 6], [1, 1, 2, 2, 3, 3, 4, 4,
5, 5 … 8, 8, 9, 9, 10, 10, 11, 11, 12, 12], Int8[1, -1, 1, -1, 1, -1, -1, 1, 1, -1 … -1, 1,
-1, 1, -1, 1, -1, 1, 1, -1], 6, 12))
julia> nV
8×3 Matrix{Float64}:
0.0 0.0 0.0
0.0 1.0 0.0
1.0 1.0 0.0
1.0 0.0 0.0
0.0 0.0 1.0
0.0 1.0 1.0
1.0 1.0 1.0
1.0 0.0 1.0
julia> nEV
12×8 SparseMatrixCSC{Int8, Int64} with 24 stored entries:
-1 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅
⋅ -1 1 ⋅ ⋅ ⋅ ⋅ ⋅
⋅ ⋅ -1 1 ⋅ ⋅ ⋅ ⋅
-1 ⋅ ⋅ 1 ⋅ ⋅ ⋅ ⋅
-1 ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅
⋅ -1 ⋅ ⋅ ⋅ 1 ⋅ ⋅
⋅ ⋅ -1 ⋅ ⋅ ⋅ 1 ⋅
⋅ ⋅ ⋅ -1 ⋅ ⋅ ⋅ 1
⋅ ⋅ ⋅ ⋅ -1 1 ⋅ ⋅
⋅ ⋅ ⋅ ⋅ ⋅ -1 1 ⋅
⋅ ⋅ ⋅ ⋅ ⋅ ⋅ -1 1
⋅ ⋅ ⋅ ⋅ -1 ⋅ ⋅ 1
julia> nFE
6×12 SparseMatrixCSC{Int8, Int64} with 24 stored entries:
1 1 1 -1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅
⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ -1 -1 -1 1
-1 ⋅ ⋅ ⋅ 1 -1 ⋅ ⋅ 1 ⋅ ⋅ ⋅
⋅ -1 ⋅ ⋅ ⋅ 1 -1 ⋅ ⋅ 1 ⋅ ⋅
⋅ ⋅ -1 ⋅ ⋅ ⋅ 1 -1 ⋅ ⋅ 1 ⋅
⋅ ⋅ ⋅ 1 -1 ⋅ ⋅ 1 ⋅ ⋅ ⋅ -1
TGW3D.removeinnerloops
— Methodfunction removeinnerloops(
g::Int64,
nFE::ChainOp)
Rimuove le facce all'interno dei cicli interni dalla matrice sparsa nFE. Il valore restituito ha g
righe in meno rispetto all'input nFE
.
Input
g::Int
nFE::ChainOp
Output
nFE::ChainOp
Esempio
julia> nFE = removeinnerloops(2, FE)
4×12 SparseMatrixCSC{Int8, Int64} with 16 stored entries:
1 1 1 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅
⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1 1 1 1
1 ⋅ ⋅ ⋅ 1 1 ⋅ ⋅ 1 ⋅ ⋅ ⋅
⋅ 1 ⋅ ⋅ ⋅ 1 1 ⋅ ⋅ 1 ⋅ ⋅
TGW3D.spatial_arrangement
— Functionfunction spatial_arrangement(
V::Points,
copEV::ChainOp,
copFE::ChainOp;
[multiproc::Bool])
Calcola la disposizione sulle cellule complesse 2-skeleton date in 3D.
Un complesso cellulare è disposto quando l'intersezione di ogni coppia di celle del complesso è vuota e l'unione di tutte le celle rappresenta l'intero spazio Euclideo. La funzione ritorna la piena disposizione complessa come una lista di vertici V e una catena di lati EV, FE, CF.
Argomenti addizionali:
multiproc::Bool
: Esegue la computazione in modalità parallela. Di Defaults afalse
.
Input
V::Points
copEV::ChainOp
copFE::ChainOp
multiproc::Bool=false
Output
rV::Points
rEV::ChainOp
rFE::ChainOp
rCF::ChainOp
Esempi
julia> rV, rEV, rFE, rCF = spatial_arrangement(Points(V), ChainOp(EV), ChainOp(FE))
([0.0 0.0 0.0; 0.0 1.0 0.0; … ; 1.0 1.0 1.0; 1.0 0.0 1.0], sparse([1, 4, 9, 1, 2, 10,
2, 3, 11, 3 … 9, 5, 6, 10, 6, 7, 11, 7, 8, 12], [1, 1, 1, 2, 2, 2, 3, 3, 3, 4 … 5,
6, 6, 6, 7, 7, 7, 8, 8, 8], Int8[-1, -1, -1, 1, -1, -1, 1, -1, -1, 1 … 1, 1, -1, 1, 1,
-1, 1, 1, 1, 1], 12, 8), sparse([1, 3, 1, 4, 1, 5, 1, 6, 2, 3 … 2, 6, 3, 6, 3, 4, 4, 5,
5, 6], [1, 1, 2, 2, 3, 3, 4, 4, 5, 5 … 8, 8, 9, 9, 10, 10, 11, 11, 12, 12], Int8[1, 1,
1, 1, 1, 1, -1, 1, 1, -1 … -1, -1, -1, -1, 1, -1, 1, -1, 1, 1], 6, 12), sparse([1, 7,
2, 8, 3, 9, 4, 10, 5, 11, 6, 12], [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6], Int8[-1, 1, -1,
1, -1, 1, -1, 1, -1, 1, -1, 1], 12, 6))
julia> rV
8×3 Matrix{Float64}:
0.0 0.0 0.0
0.0 1.0 0.0
1.0 1.0 0.0
1.0 0.0 0.0
0.0 0.0 1.0
0.0 1.0 1.0
1.0 1.0 1.0
1.0 0.0 1.0
julia> rEV
12×8 SparseMatrixCSC{Int8, Int64} with 24 stored entries:
-1 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅
⋅ -1 1 ⋅ ⋅ ⋅ ⋅ ⋅
⋅ ⋅ -1 1 ⋅ ⋅ ⋅ ⋅
-1 ⋅ ⋅ 1 ⋅ ⋅ ⋅ ⋅
⋅ ⋅ ⋅ ⋅ -1 1 ⋅ ⋅
⋅ ⋅ ⋅ ⋅ ⋅ -1 1 ⋅
⋅ ⋅ ⋅ ⋅ ⋅ ⋅ -1 1
⋅ ⋅ ⋅ ⋅ -1 ⋅ ⋅ 1
-1 ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅
⋅ -1 ⋅ ⋅ ⋅ 1 ⋅ ⋅
⋅ ⋅ -1 ⋅ ⋅ ⋅ 1 ⋅
⋅ ⋅ ⋅ -1 ⋅ ⋅ ⋅ 1
julia> rFE
6×12 SparseMatrixCSC{Int8, Int64} with 24 stored entries:
1 1 1 -1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅
⋅ ⋅ ⋅ ⋅ 1 1 1 -1 ⋅ ⋅ ⋅ ⋅
1 ⋅ ⋅ ⋅ -1 ⋅ ⋅ ⋅ -1 1 ⋅ ⋅
⋅ 1 ⋅ ⋅ ⋅ -1 ⋅ ⋅ ⋅ -1 1 ⋅
⋅ ⋅ 1 ⋅ ⋅ ⋅ -1 ⋅ ⋅ ⋅ -1 1
⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅ -1 -1 ⋅ ⋅ 1
julia> rCF
12×6 SparseMatrixCSC{Int8, Int64} with 12 stored entries:
-1 ⋅ ⋅ ⋅ ⋅ ⋅
⋅ -1 ⋅ ⋅ ⋅ ⋅
⋅ ⋅ -1 ⋅ ⋅ ⋅
⋅ ⋅ ⋅ -1 ⋅ ⋅
⋅ ⋅ ⋅ ⋅ -1 ⋅
⋅ ⋅ ⋅ ⋅ ⋅ -1
1 ⋅ ⋅ ⋅ ⋅ ⋅
⋅ 1 ⋅ ⋅ ⋅ ⋅
⋅ ⋅ 1 ⋅ ⋅ ⋅
⋅ ⋅ ⋅ 1 ⋅ ⋅
⋅ ⋅ ⋅ ⋅ 1 ⋅
⋅ ⋅ ⋅ ⋅ ⋅ 1
TGW3D.spatial_arrangement_1
— Functionfunction spatial_arrangement_1(
V::Points,
copEV::ChainOp,
copFE::ChainOp,
[multiproc::Bool=false])
Si occupa del processo di frammentazione delle facce per l'utilizzo del planar arrangement. Richiama le funzioni frag_face
e `merge_vertices' per ritornare i nuovi vertici, lati e facce.
Argomenti addizionali:
multiproc::Bool
: Esegue la computazione in modalità parallela. Di Defaults afalse
.
Input
V::Points
copEV::ChainOp
copFE::ChainOp
multiproc::Bool=false
Output
rV::Points
rEV::ChainOp
rFE::ChainOp
Esempi
julia> rV, rEV, rFE = spatial_arrangement_1(Points(V), ChainOp(EV), ChainOp(FE))
([0.0 0.0 0.0; 0.0 1.0 0.0; … ; 1.0 1.0 1.0; 1.0 0.0 1.0], sparse([1, 4, 9, 1, 2,
10, 2, 3, 11, 3 … 9, 5, 6, 10, 6, 7, 11, 7, 8, 12], [1, 1, 1, 2, 2, 2, 3, 3, 3,
4 … 5, 6, 6, 6, 7, 7, 7, 8, 8, 8], Int8[-1, -1, -1, 1, -1, -1, 1, -1, -1, 1 …
1, 1, -1, 1, 1, -1, 1, 1, 1, 1], 12, 8), sparse([1, 3, 1, 4, 1, 5, 1, 6, 2, 3 …
2, 6, 3, 6, 3, 4, 4, 5, 5, 6], [1, 1, 2, 2, 3, 3, 4, 4, 5, 5 … 8, 8, 9, 9, 10,
10, 11, 11, 12, 12], Int8[1, 1, 1, 1, 1, 1, -1, 1, 1, -1 … -1, -1, -1, -1, 1,
-1, 1, -1, 1, 1], 6, 12))
julia> rV
8×3 Matrix{Float64}:
0.0 0.0 0.0
0.0 1.0 0.0
1.0 1.0 0.0
1.0 0.0 0.0
0.0 0.0 1.0
0.0 1.0 1.0
1.0 1.0 1.0
1.0 0.0 1.0
julia> rEV
12×8 SparseMatrixCSC{Int8, Int64} with 24 stored entries:
-1 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅
⋅ -1 1 ⋅ ⋅ ⋅ ⋅ ⋅
⋅ ⋅ -1 1 ⋅ ⋅ ⋅ ⋅
-1 ⋅ ⋅ 1 ⋅ ⋅ ⋅ ⋅
⋅ ⋅ ⋅ ⋅ -1 1 ⋅ ⋅
⋅ ⋅ ⋅ ⋅ ⋅ -1 1 ⋅
⋅ ⋅ ⋅ ⋅ ⋅ ⋅ -1 1
⋅ ⋅ ⋅ ⋅ -1 ⋅ ⋅ 1
-1 ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅
⋅ -1 ⋅ ⋅ ⋅ 1 ⋅ ⋅
⋅ ⋅ -1 ⋅ ⋅ ⋅ 1 ⋅
⋅ ⋅ ⋅ -1 ⋅ ⋅ ⋅ 1
julia> rFE
6×12 SparseMatrixCSC{Int8, Int64} with 24 stored entries:
1 1 1 -1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅
⋅ ⋅ ⋅ ⋅ 1 1 1 -1 ⋅ ⋅ ⋅ ⋅
1 ⋅ ⋅ ⋅ -1 ⋅ ⋅ ⋅ -1 1 ⋅ ⋅
⋅ 1 ⋅ ⋅ ⋅ -1 ⋅ ⋅ ⋅ -1 1 ⋅
⋅ ⋅ 1 ⋅ ⋅ ⋅ -1 ⋅ ⋅ ⋅ -1 1
⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅ -1 -1 ⋅ ⋅ 1
TGW3D.spatial_arrangement_2
— Functionfunction spatial_arrangement_2(
rV::Points,
rcopEV::ChainOp,
rcopFE::ChainOp,
[multiproc::Bool=false])
Effettua la ricostruzione delle facce permettendo il wrapping spaziale 3D.
Argomenti addizionali:
multiproc::Bool
: Esegue la computazione in modalità parallela. Di Defaults afalse
.
Input
rV::Points
rcopEV::ChainOp
rcopFE::ChainOp
multiproc::Bool=false
Output
rV::Points
rcopEV::ChainOp
rcopFE::ChainOp
rcopCF::ChainOp
multiproc::Bool=false
Esempi
julia> rV, rcopEV, rcopFE, rcopCF = spatial_arrangement_2(rV, rEV, rFE)
([0.0 0.0 0.0; 0.0 1.0 0.0; … ; 1.0 1.0 1.0; 1.0 0.0 1.0], sparse([1, 4,
9, 1, 2, 10, 2, 3, 11, 3 … 9, 5, 6, 10, 6, 7, 11, 7, 8, 12], [1, 1, 1,
2, 2, 2, 3, 3, 3, 4 … 5, 6, 6, 6, 7, 7, 7, 8, 8, 8], Int8[-1, -1, -1,
1, -1, -1, 1, -1, -1, 1 … 1, 1, -1, 1, 1, -1, 1, 1, 1, 1], 12, 8), sparse(
[1, 3, 1, 4, 1, 5, 1, 6, 2, 3 … 2, 6, 3, 6, 3, 4, 4, 5, 5, 6], [1,
1, 2, 2, 3, 3, 4, 4, 5, 5 … 8, 8, 9, 9, 10, 10, 11, 11, 12, 12], Int8[1,
1, 1, 1, 1, 1, -1, 1, 1, -1 … -1, -1, -1, -1, 1, -1, 1, -1, 1, 1],
6, 12), sparse([1, 7, 2, 8, 3, 9, 4, 10, 5, 11, 6, 12], [1, 1, 2, 2, 3,
3, 4, 4, 5, 5, 6, 6], Int8[-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1], 12, 6))
julia> rV
8×3 Matrix{Float64}:
0.0 0.0 0.0
0.0 1.0 0.0
1.0 1.0 0.0
1.0 0.0 0.0
0.0 0.0 1.0
0.0 1.0 1.0
1.0 1.0 1.0
1.0 0.0 1.0
julia> rcopEV
12×8 SparseMatrixCSC{Int8, Int64} with 24 stored entries:
-1 1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅
⋅ -1 1 ⋅ ⋅ ⋅ ⋅ ⋅
⋅ ⋅ -1 1 ⋅ ⋅ ⋅ ⋅
-1 ⋅ ⋅ 1 ⋅ ⋅ ⋅ ⋅
⋅ ⋅ ⋅ ⋅ -1 1 ⋅ ⋅
⋅ ⋅ ⋅ ⋅ ⋅ -1 1 ⋅
⋅ ⋅ ⋅ ⋅ ⋅ ⋅ -1 1
⋅ ⋅ ⋅ ⋅ -1 ⋅ ⋅ 1
-1 ⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅
⋅ -1 ⋅ ⋅ ⋅ 1 ⋅ ⋅
⋅ ⋅ -1 ⋅ ⋅ ⋅ 1 ⋅
⋅ ⋅ ⋅ -1 ⋅ ⋅ ⋅ 1
julia> rcopFE
6×12 SparseMatrixCSC{Int8, Int64} with 24 stored entries:
1 1 1 -1 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅
⋅ ⋅ ⋅ ⋅ 1 1 1 -1 ⋅ ⋅ ⋅ ⋅
1 ⋅ ⋅ ⋅ -1 ⋅ ⋅ ⋅ -1 1 ⋅ ⋅
⋅ 1 ⋅ ⋅ ⋅ -1 ⋅ ⋅ ⋅ -1 1 ⋅
⋅ ⋅ 1 ⋅ ⋅ ⋅ -1 ⋅ ⋅ ⋅ -1 1
⋅ ⋅ ⋅ 1 ⋅ ⋅ ⋅ -1 -1 ⋅ ⋅ 1
julia> rcopCF
12×6 SparseMatrixCSC{Int8, Int64} with 12 stored entries:
-1 ⋅ ⋅ ⋅ ⋅ ⋅
⋅ -1 ⋅ ⋅ ⋅ ⋅
⋅ ⋅ -1 ⋅ ⋅ ⋅
⋅ ⋅ ⋅ -1 ⋅ ⋅
⋅ ⋅ ⋅ ⋅ -1 ⋅
⋅ ⋅ ⋅ ⋅ ⋅ -1
1 ⋅ ⋅ ⋅ ⋅ ⋅
⋅ 1 ⋅ ⋅ ⋅ ⋅
⋅ ⋅ 1 ⋅ ⋅ ⋅
⋅ ⋅ ⋅ 1 ⋅ ⋅
⋅ ⋅ ⋅ ⋅ 1 ⋅
⋅ ⋅ ⋅ ⋅ ⋅ 1