Ring statistics
Definitions
In this section, we use the terminology recommended by Blatov, O’Keeffe and Proserpio. In particular:
- a cycle is a sequence of vertices
v₁, v₂, ..., vₙ
such that for alli
between 2 andn
,vᵢ₋₁
andvᵢ
are neighbors, as well asv₁
andvₙ
, and novᵢ
occurs more than once in the sequence. It can be equivalently represented by the sequence of edges between two consecutive vertices and betweenv₁
andvₙ
. - the sum of two or more cycles is the set of edges occurring only an odd number of times in the set of input cycles. The sum of two cycles is thus the symmetric difference of their edges. Note that the sum of cycles may be empty, or may be the juxtaposition of several edge-disjoint cycles.
- the length of a cycle is its number of edges. It is also equal to its number of vertices since no vertex is repeated in the sequence. A cycle
a
is strictly smaller than anotherb
when the length ofa
is strictly lower than that ofb
. - a ring is a cycle which is not the sum of two strictly smaller cycles. An equivalent definition is that a ring is a cycle which does not admit a short-circuit: between two vertices of the ring, there is no path of the graph strictly smaller than both branches of the ring linking the two vertices.
- a strong ring is a cycle which is not the sum of any number of strictly smaller cycles.
Manual
PeriodicGraphs.jl
provides an algorithm for the determination of all rings and strong rings up to a given size in a PeriodicGraph
. It can also be used on finite graphs by converting them to PeriodicGraph{0}
, although the code is not optimized for this case.
The rings
(respectively strong_rings
) function returns the list of all rings (respectively strong rings) in the graph up to a given size:
PeriodicGraphs.rings
— Functionrings(g::PeriodicGraph{D}, [depth::Integer=15,] symmetries::AbstractSymmetryGroup=NoSymmetryGroup(g), dist::DistanceRecord=DistanceRecord(g,depth)) where D
Compute the list of rings in g
, up to length 2*depth+3
. Return the list of Vector{Int}
where each sublist is a ring whose vertices are the reverse_hash_position
s of the sublist elements. Also return an AbstractSymmetryGroup
acting on the returned rings.
A ring is a cycle of the graph for which there is no shortcut, i.e. no path in the graph between two vertices of the cycle that is shorter than either path connecting the vertices in the cycle.
If provided, symmetries
should represent the symmetries of the graph as a AbstractSymmetryGroup
object respecting its documented interface.
A PeriodicGraphs.DistanceRecord
dist
can be optionally provided to track the distances between pairs of vertices in the graph.
PeriodicGraphs.strong_rings
— Functionstrong_rings([rs::Vector{Vector{Int}},] g::PeriodicGraph{D}, [depth::Integer=15,] symmetries::AbstractSymmetryGroup=NoSymmetryGroup(g), dist::DistanceRecord=DistanceRecord(g,depth)) where D
Compute the list of strong rings in g
, up to length 2*depth+3
. Return them with their symmetry group. Each ring is represented by the list of hash_position
of its vertices.
The optional first argument rs
is the list of rings which can be provided if previously computed.
See rings
for the meaning of the other arguments.
A strong ring is a cycle of the graph which cannot be decomposed into a sum of any number of smaller cycles. By comparison, a ring is a cycle which cannot be decomposed into a sum of two smaller cycles. In particular, all strong rings are rings.
See also strong_erings
to obtain the rings as a list of integers representing the edges of the ring, instead of a list of integers representing its vertices.
A few notes on the output:
- The output is a pair
(rs, symm)
wherers
is the list of rings (respectively strong rings) andsymm
is anAbstractSymmetryGroup
which contains symmetries ofrs
.symm
is always aNoSymmetryGroup
if the optional argumentsymmetries
is not provided. - The returned list of rings
rs
is generally unsorted. - All translations of the same ring are represented by a unique ring, which means that a ring crossing through different unit cells will only appear once in the list, even though it may appear several times in a single unit cell.
- The
symmetries
optional argument reduces the computational cost of the algorithm. The output listsrs
with and without the optional argument are identical except for the order of their elements.
The optional argument depth
defaults to 15, which means that rings containing up to 33 edges will be considered. This default value is chosen to accomodate the vast majority of periodic nets encountered as crystal nets, for which the ring size rarely exceeds 20.
Let's take as example an aperiodic graph representing a small house, made of a cube with a pyramid on top:
julia> house = PeriodicGraph{0}("0 "*
"1 2 2 3 3 4 4 1 "* # square base of the house
"1 5 2 6 3 7 4 8 "* # 4 vertical pillars
"5 6 6 7 7 8 8 5 "* # square ceiling
"5 9 6 9 7 9 8 9 " # pyramidal roof
);
julia> sort!(first(rings(house)))
14-element Vector{Vector{Int64}}:
[1, 2, 3, 4]
[1, 2, 3, 7, 8, 5]
[1, 2, 6, 5]
[1, 2, 6, 7, 8, 4]
[1, 4, 3, 7, 6, 5]
[1, 4, 8, 5]
[2, 3, 4, 8, 5, 6]
[2, 3, 7, 6]
[3, 4, 8, 7]
[5, 6, 7, 8]
[5, 6, 9]
[5, 8, 9]
[6, 7, 9]
[7, 8, 9]
julia> sort!(first(strong_rings(house)))
9-element Vector{Vector{Int64}}:
[1, 2, 3, 4]
[1, 2, 6, 5]
[1, 4, 8, 5]
[2, 3, 7, 6]
[3, 4, 8, 7]
[5, 6, 9]
[5, 8, 9]
[6, 7, 9]
[7, 8, 9]
We can see that the house has four weak rings of size 6, six rings of size 4 among which five are strong, and four strong rings of size 3.
The strong rings are the faces of the house: there are four triangles that make the roof, four squares that make the walls and one last square for the base of the house. The square corresponding to the ceiling is actually the sum of the four triangles of the roof, which is why it is not a strong ring. The four weak rings of size 6 are those that go through each vertex of the cube except for one of the four opposite pairs.
To explore the ring distributions around individual vertices, the RingAttributions
struct factors the ring distribution by vertex. The list of rings including a particular vertex is factored into a RingIncluding
struct:
PeriodicGraphs.RingAttributions
— TypeRingAttributions{D} <: AbstractVector{RingIncluding{D}}
Represent a set of rings of a PeriodicGraph{D}
.
For ra
of type RingAttributions{D}
, ra[i]
is a RingIncluding{D}
object representing the set of rings including PeriodicVertex{D}(i)
.
PeriodicGraphs.RingIncluding
— TypeRingIncluding{D} <: AbstractVector{OffsetVertexIterator{D}}
The list of rings of a PeriodicGraph{D}
including a particular vertex PeriodicVertex{D}(i)
.
The object is iterable and indexable by an integer: for ri
of type RingIncluding{D}
, ri[j]
is an iterable over the vertices of the j
-th ring including vertex i
.
To avoid useless computations, the lists of rings and the rings themselves are returned as iterables instead of Vector{PeriodicVertex{N}}
and such, so they should be collect
ed if required.
Let's look all the rings on our little house:
julia> ras = RingAttributions(house)
RingAttributions{0}(rings per node: [6, 6, 6, 6, 8, 8, 8, 8, 4])
julia> roofpeak = ras[9] # the list of rings including the top of the roof
4-element RingIncluding{0}:
[5, 8, 9]
[5, 6, 9]
[7, 8, 9]
[6, 7, 9]
julia> roofpeak[2] # the second ring including the top of the roof
3-element PeriodicGraphs.OffsetVertexIterator{0}:
5
6
9
julia> rasstrong = RingAttributions(house, true)
RingAttributions{0}(rings per node: [3, 3, 3, 3, 4, 4, 4, 4, 4])
julia> rasstrong[1] # the base and two walls make the strong rings around vertex 1
3-element RingIncluding{0}:
[1, 2, 6, 5]
[1, 4, 8, 5]
[1, 2, 3, 4]
julia> collect(rasstrong[5]) # two rooftiles and two walls make the strong rings around vertex 5
4-element Vector{PeriodicGraphs.OffsetVertexIterator{0}}:
[5, 8, 9]
[5, 6, 9]
[1, 2, 6, 5]
[1, 4, 8, 5]
Internal API
Here is a collection of internal utilities used for the algorithms of rings
and strong_rings
:
PeriodicGraphs.ConstMiniBitSet
— TypeConstMiniBitSet{T} <: AbstractSet{Int}
Fixed-size bitset stored on a single word of type T
, typically a UInt64
or a UInt32
.
PeriodicGraphs.DistanceRecord
— TypeDistanceRecord{D}
Record of the computed distances between vertices of a graph.
PeriodicGraphs.JunctionNode
— TypeJunctionNode{T}
Element of the DAG representing the set of arcs linking each vertex x
to a fixed vertex i
of graph g
. Each JunctionNode
contains information on the arcs passing through a particular vertex x
:
num
is the length of the shortest path betweenx
andi
. Since we only collect rings, only the shortest paths are of interest, as well as the path of lengthnum+1
which may form an odd-length ring when combined with a shortest path.heads
is a list of neighbors ofx
such that thelastshort
first are at distancenum - 1
fromi
, and the rest are at distancenum
and have a lower value thanx
.shortroots
is the set of roots reachable on a shortest path fromx
toi
. A root is the neighbor ofi
on that path, a.k.a. the second-to-last vertex on that path.longroots
is the set of roots reachable on a path of lengthnum+1
fromx
toi
.
PeriodicGraphs.PhantomJunctionNode
— TypePhantomJunctionNode{D}
Element of the phantom DAG.
Similarly to the DAG of JunctionNode
, the phantom DAG tracks arcs linking vertices x
to a fixed vertex i
, except that the vertices x
are those that should be ignored in the returned list of rings. Thus, only the shortest distance between x
and i
needs to be recorded, since the arcs themselves will be discarded eventually.
PeriodicGraphs.arcs_list
— Functionarcs_list(g::PeriodicGraph{D}, i, depth::T, ringavoid=nothing, cycleavoid=nothing) where {D,T}
Compute the list of shortest arcs starting from vertex i
up to length depth+1
. Vertices in ringavoid
are not included in the returned arcs, but considered still part of the graph for distance computations. Vertices in cycleavoid
are considered removed from the graph completely.
Return (dag, vertexnums)
where dag
is a Vector{JunctionNode{T}}
representing, for each visited node, the DAG of all arcs from that node back to i
, in a compact representation. vertexnums
is a Vector{PeriodicVertex{D}}
whose k
-th value is the vertex represented by number k
in dag
.
If ringavoid !== nothing
, dag
will also not include arcs that pass through nodes of the form PeriodicVertex{D}(j, ofs)
with j == i
and ofs < zero(SVector{Int,D})
: this allows eagerly pruning cycles that are translations of others. Note that this can result in missing cycles if those pass through at least three nodes with j == i
, but that situation should be exceptionally rare.
The type T
of depth
is used as type parameter to the JunctionNode
, just to avoid having a dedicated argument (since depth
should be at most 62 for the rest of the algorithm to work). This size controls the maximal degree a vertex of g
should have : for example, T == UInt32
indicates that all vertices must have degree at most 31.
PeriodicGraphs.RingsEndingAt
— TypeRingsEndingAt(dag, midnode, record)
Iterable over the rings of graph g
around node i
with midnode
as vertex furthest from i
. If there are two such vertices (odd ring), midnode
is the higher of the two.
record
should be set to (dist, vertexnums)
where dist == DistanceRecord(g, depth)
and dag, vertexnums == first(arcs_list(g, i, depth, ...))
, otherwise the iterator will return many more cycles that may not be rings.
In order to efficiently cycle through the rings, the iterator reuses a buffer on which the rings are written. This means that performing an iteration will change the value of the previously returned result: for example, collect(RingsEndingAt(...))
will yield a list containing the same sublist (unlikely to be an actual ring) repeated over. To actually obtain the list of rings, copy the result as they arrive by doing map(copy, RingsEndingAt(...))
or [copy(x) for x in RingsEndingAt(...)]
for example.
This also means that the list returned at each iteration should never be modified directly: copy it before.
PeriodicGraphs.normalize_cycle!
— Functionnormalize_cycle!(cycle::Vector{Int}, n, v::Val{D}) where D
In-place rotate and possibly reverse cycle
, a Vector{Int}
whose elements are the hash_position
of vertices of g
so that the result is the same for all such vectors that represent the same cycle, possibly translated to a different unit cell or rotated.
The graph g::PeriodicGraph{D}
is represented as n = nv(g)
and v = Val(D)
PeriodicGraphs.symdiff_cycles!
— Functionsymdiff_cycles!(c::Vector{T}, a::Vector{T}, b::Vector{T}) where T
Like PeriodicGraphs.symdiff_cycles
but stores the result in c
.
c
will be resized accordingly so its initial length does not matter.
PeriodicGraphs.symdiff_cycles
— Functionsymdiff_cycles(a, b)
Symmetric difference between two sorted lists a
and b
. Return the sorted list of elements belonging to a
or to b
but not to both.
Use PeriodicGraphs.symdiff_cycles!
to provide a pre-allocated destination.
Example
julia> PeriodicGraphs.symdiff_cycles([3,4,5], [4,5,6])
2-element Vector{Int64}:
3
6
PeriodicGraphs.IterativeGaussianElimination
— TypeIterativeGaussianElimination{T}
Struct containing the list of sparse columns of the matrix under gaussian elimination on the 𝔽₂ finite field.
To be used with PeriodicGraphs.gaussian_elimination!
as one of the three concrete types:
PeriodicGraphs.IterativeGaussianEliminationNone
for simple gaussian elimination,PeriodicGraphs.IterativeGaussianEliminationLength
to detect when a new column can be expressed as a sum of strictly smaller columns of the matrix.PeriodicGraphs.IterativeGaussianEliminationDecomposition
to detect when a new column can be expressed as a sum of other columns of the matrix and keep track of which.
PeriodicGraphs.gaussian_elimination!
— Functiongaussian_elimination!(gauss::IterativeGaussianElimination, r::Vector{Int})
Test whether r
can be expressed as a sum of vectors stored in gauss
, and store r
if not. "sum" refers to the symmetric difference of boolean vectors, represented in sparse format as the ordered list of non-zero indices.
If gauss isa IterativeGaussianEliminationLength
, return whether r
can be expressed as a sum of strictly smaller vectors.
Otherwise, return true
when r
is a sum of any previously encoutered vectors. If gauss
isa IterativeGaussianEliminationDecomposition
, query retrieve_track(gauss)
to obtain the sorted list of indices of such previously encountered vectors.
See also gaussian_elimination
to test r
without storing it.
PeriodicGraphs.gaussian_elimination
— Functiongaussian_elimination(gauss::IterativeGaussianElimination, r::Vector{Int})
Return notindependent, info
where notindependent
is true
if r
can be expressed as a sum of vectors stored in gauss
.
See PeriodicGraphs.gaussian_elimination!
to store r
in gauss
if not, and for more details dependending on the type of gauss
.
Call PeriodicGraphs.gaussian_elimination!(gauss, r, notindependent, info)
to obtain the result of PeriodicGraphs.gaussian_elimination!(gauss, r)
without duplicating computation.
PeriodicGraphs.intersect_cycles!
— Functionintersect_cycles!(c::Vector{T}, a::Vector{T}, b::Vector{T}) where T
Like PeriodicGraphs.intersect_cycles
but stores the result in c
.
c
will be resized accordingly so its initial length does not matter as long as it is at least as large as the resulting list.
PeriodicGraphs.intersect_cycles
— Functionintersect_cycles(a, b)
Intersection between two sorted lists a
and b
. Return the sorted list of elements belonging to both a
and b
.
Use PeriodicGraphs.intersect_cycles!
to provide a pre-allocated destination.
Example
julia> PeriodicGraphs.intersect_cycles([3,4,5], [4,5,6])
2-element Vector{Int64}:
4
5
PeriodicGraphs.union_cycles!
— Functionunion_cycles!(c::Vector{T}, a::Vector{T}, b::Vector{T}) where T
Like PeriodicGraphs.union_cycles
but stores the result in c
.
c
will be resized accordingly so its initial length does not matter as long as it is at least as large as the resulting list.
PeriodicGraphs.union_cycles
— Functionunion_cycles(a, b)
Union between two sorted lists a
and b
. Return the sorted list of elements belonging to a
or b
or both.
Use PeriodicGraphs.union_cycles!
to provide a pre-allocated destination.
Example
julia> PeriodicGraphs.union_cycles([3,4,5], [4,5,6])
4-element Vector{Int64}:
3
4
5
6
PeriodicGraphs.retrieve_track!
— Functionretrieve_track!([ret::Vector{Int32}, buffer::Vector{Int32},] gauss::IterativeGaussianEliminationDecomposition)
To be called consecutive to a call to gaussian_elimination!(gauss, x)
that returned true
. In that case, x
was found to be the sum of previously encountered vectors: return the (reverse-sorted) list of their indices.
Calling retrieve_track!
after a call to gaussian_elimination!
that returned false
will produce an invalid result. Calling it twice will also produce an invalid result.
PeriodicGraphs.rings_around
— Functionrings_around(g::PeriodicGraph{D}, i, depth=15, dist::DistanceRecord=DistanceRecord(g,depth), visited=nothing) where D
Return the list of all rings around node i
in graph g
up to length 2*depth+3
.
The returned rings are the list of hash_position
of the corresponding vertices. To get back the list of actual PeriodicVertex
of a returned ring
in the list, do
[reverse_hash_position(x, g) for x in ring]
If the offsets of the corresponding vertices are not needed, simply do
[mod1(x, n) for x in ring] # n should be nv(g)
visited
is interpreted as the ringavoid
argument of arcs_list
unless dist === nothing
, in which case it is interpreted as the cycleavoid
argument. In particular, unless dist === nothing
, only one ring will appear in the list even if some of its translated images also pass through PeriodicVertex{D}(i)
.
PeriodicGraphs.EdgeDict
— TypeEdgeDict{D}
Map from pairs of PeriodicVertex{D}
to the identifier of the corresponding edge.
kp::EdgeDict{D}
should be queried by either get!(kp, minmax(v1, v2))
where v1
and v2
are PeriodicVertex{D}
to obtain the identifier of the edge and store a new identifier if absent, or by kp[i]
where i
is an Integer
to obtain the pair of vertices corresponding to identifier i
.
kp
is built by calling EdgeDict(g)
where g
is a PeriodicGraph
.
PeriodicGraphs.strong_erings
— Functionstrong_erings([rs::Vector{Vector{Int}},], g::PeriodicGraph{D}, [depth=15,] ringsymms::AbstractSymmetryGroup=NoSymmetryGroup(length(rs))) where D
Compute the list of strong edge rings in g
, up to length 2*depth+3
. See strong_rings
and rings
for the meaning of the optional arguments.
Return a quadruplet of values:
- the two first values are the list of rings and their symmetry group, identical to the result of
strong_rings
, unlessrs
is provided (see below). - the third is the list of edge rings: each edge of the periodic graph is mapped to an integer and each ring is represented by the sorted list of its edges.
- the last is the mapping from edges to integers, given as an
EdgeDict
.
If rs
is provided, the first returned value is the list of indices keep
of rs
such that rs[keep]
is the list of strong rings.
PeriodicGraphs.convert_to_ering!
— Functionconvert_to_ering!(buffer::Vector{Int}, ring::Vector{PeriodicVertex{D}}, kp::EdgeDict{D}, ofs, len) where D
Return the edge ring corresponding to OffsetVertexIterator{D}(ring[1:len], ofs)
inside buffer
, resized to length len
.
See also PeriodicGraphs.convert_to_ering
.
PeriodicGraphs.convert_to_ering
— Functionconvert_to_ering(ring::Vector{PeriodicVertex{D}}, kp::EdgeDict{D}, ofs=zero(SVector{D,Int}), len=length(ring)) where D
Return the edge ring corresponding to OffsetVertexIterator{D}(ring[1:len], ofs)
.
See also PeriodicGraphs.convert_to_ering!
to provide a buffer.