Skip to content

Commit acb3ce7

Browse files
mtorpeyjames-d-mitchell
authored andcommitted
Add AbsorptionExpectedSteps
1 parent e4e272f commit acb3ce7

5 files changed

Lines changed: 160 additions & 36 deletions

File tree

doc/attr.xml

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1138,7 +1138,8 @@ gap> ArticulationPoints(D);
11381138

11391139
Strongly connected components are indexed in the order given by
11401140
<Ref Attr="DigraphStronglyConnectedComponents"/>. See also
1141-
<Ref Oper="DigraphRandomWalk"/>.
1141+
<Ref Oper="DigraphRandomWalk"/> and
1142+
<Ref Attr="DigraphAbsorptionExpectedSteps"/>.
11421143
<P/>
11431144
<Example><![CDATA[
11441145
gap> gr := Digraph([[2, 3, 4], [3], [2], []]);
@@ -1152,6 +1153,38 @@ gap> DigraphAbsorptionProbabilities(gr);
11521153
</ManSection>
11531154
<#/GAPDoc>
11541155

1156+
<#GAPDoc Label="DigraphAbsorptionExpectedSteps">
1157+
<ManSection>
1158+
<Attr Name="DigraphAbsorptionExpectedSteps" Arg="digraph"/>
1159+
<Returns>A list of rational numbers.</Returns>
1160+
<Description>
1161+
A random walk of unbounded length through a finite digraph may pass through
1162+
several different strongly connected components (SCCs). However, with
1163+
probability 1 it must eventually reach an SCC which it can never leave,
1164+
because the SCC has no out-edges leading to other SCCs. When this happens,
1165+
we say the walk has been <E>absorbed</E>.
1166+
<P/>
1167+
1168+
<C>DigraphAbsorptionExpectedSteps</C> returns a list <C>L</C> with length
1169+
equal to the number of vertices of <A>digraph</A>, where <C>L[v]</C>
1170+
is the expected number of steps a random walk starting at vertex <C>v</C>
1171+
should take before it is absorbed – that is, the expected number of steps to
1172+
reach an SCC that it can never leave.
1173+
<P/>
1174+
1175+
See also <Ref Oper="DigraphRandomWalk"/> and
1176+
<Ref Attr="DigraphAbsorptionProbabilities"/>.
1177+
<P/>
1178+
<Example><![CDATA[
1179+
gap> gr := Digraph([[2], [3, 4], [], [2]]);
1180+
<immutable digraph with 4 vertices, 4 edges>
1181+
gap> DigraphAbsorptionExpectedSteps(gr);
1182+
[ 4, 3, 0, 4 ]
1183+
]]></Example>
1184+
</Description>
1185+
</ManSection>
1186+
<#/GAPDoc>
1187+
11551188
<#GAPDoc Label="StrongOrientation">
11561189
<ManSection>
11571190
<Oper Name="StrongOrientation" Arg="D"/>

doc/z-chap4.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
<#Include Label="DigraphShortestPath">
7171
<#Include Label="DigraphRandomWalk">
7272
<#Include Label="DigraphAbsorptionProbabilities">
73+
<#Include Label="DigraphAbsorptionExpectedSteps">
7374
<#Include Label="Dominators">
7475
<#Include Label="DominatorTree">
7576
<#Include Label="IteratorOfPaths">

gap/attr.gd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ DeclareAttribute("DigraphOddGirth", IsDigraph);
5151
DeclareAttribute("DigraphUndirectedGirth", IsDigraph);
5252
DeclareAttribute("ArticulationPoints", IsDigraph);
5353
DeclareSynonymAttr("CutVertices", ArticulationPoints);
54+
DeclareAttribute("DIGRAPHS_AbsorbingMarkovChain", IsDigraph);
5455
DeclareAttribute("DigraphAbsorptionProbabilities", IsDigraph);
56+
DeclareAttribute("DigraphAbsorptionExpectedSteps", IsDigraph);
5557

5658
DeclareAttribute("DigraphAllSimpleCircuits", IsDigraph);
5759
DeclareAttribute("DigraphLongestSimpleCircuit", IsDigraph);

gap/attr.gi

Lines changed: 74 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -192,26 +192,16 @@ function(I, subtracted_set, size_bound)
192192
end
193193
);
194194

195-
InstallMethod(DigraphAbsorptionProbabilities,
195+
InstallMethod(DIGRAPHS_AbsorbingMarkovChain,
196196
"for a digraph",
197197
[IsDigraph],
198198
function(D)
199+
# Helper for DigraphAbsorptionProbabilities and DigraphAbsorptionExpectedSteps
199200
local scc, is_sink_comp, transient_vertices, i, comp, v, sink_comps,
200201
nr_transients, transient_mat, absorption_mat, neighbours, chance, w,
201-
w_comp, sink_comp_index, j, N, chances_of_absorption, output, comp_no,
202-
c;
203-
# No vertices: trivial matrix
204-
if DigraphNrVertices(D) = 0 then
205-
return [];
206-
fi;
207-
202+
w_comp, sink_comp_index, j, fundamental_mat;
208203
scc := DigraphStronglyConnectedComponents(D);
209204

210-
# One SCC only: all vertices stay in it with probability 1.
211-
if Length(scc.comps) = 1 then
212-
return ListWithIdenticalEntries(DigraphNrVertices(D), [1]);
213-
fi;
214-
215205
# Find the "sink components" (components from which there is no escape)
216206
# We could use QuotientDigraph and DigraphSinks here, but this avoids copying.
217207
is_sink_comp := ListWithIdenticalEntries(Length(scc.comps), true);
@@ -229,6 +219,14 @@ function(D)
229219
sink_comps := Positions(is_sink_comp, true);
230220
nr_transients := Length(transient_vertices);
231221

222+
# If no transient vertices, then we can't return anything interesting.
223+
if nr_transients = 0 then
224+
return rec(transient_vertices := transient_vertices,
225+
fundamental_mat := [],
226+
sink_comps := sink_comps,
227+
absorption_mat := []);
228+
fi;
229+
232230
# transient_mat[i][j] is the chance of going
233231
# from transient vertex i to transient vertex j
234232
transient_mat := NullMat(nr_transients, nr_transients);
@@ -257,21 +255,57 @@ function(D)
257255
od;
258256
od;
259257

260-
# Compute the chances as t tends to infinity using formula (I - Q)^-1 * R
261-
N := Inverse(IdentityMat(nr_transients) - transient_mat);
262-
chances_of_absorption := N * absorption_mat;
258+
# "Fundamental matrix": mat[i][j] is the expected number of visits to each
259+
# transient vertex j given a random walk starting at transient vertex i.
260+
# Compute this using formula (I - Q)^-1
261+
fundamental_mat := Inverse(IdentityMat(nr_transients) - transient_mat);
263262

264-
# Rows are vertices, columns are SCCs
263+
# Return all info needed for further computation in DigraphAbsorption* methods
264+
return rec(transient_vertices := transient_vertices,
265+
fundamental_mat := fundamental_mat,
266+
sink_comps := sink_comps,
267+
absorption_mat := absorption_mat);
268+
end);
269+
270+
InstallMethod(DigraphAbsorptionProbabilities,
271+
"for a digraph",
272+
[IsDigraph],
273+
function(D)
274+
local scc, markov, chances_of_absorption, output, sink_comps, comp_no, v,
275+
transient_vertices, i, j, c;
276+
# Strongly connected components are an important part of definition
277+
scc := DigraphStronglyConnectedComponents(D);
278+
279+
# One SCC only (or none): all vertices stay in it with probability 1.
280+
if Length(scc.comps) <= 1 then
281+
return ListWithIdenticalEntries(DigraphNrVertices(D), [1]);
282+
fi;
283+
284+
# Get data about absorbing Markov chain represented by this digraph
285+
markov := DIGRAPHS_AbsorbingMarkovChain(D);
286+
287+
# Calculate chances of absorption
288+
# Rows are transient vertices, columns are absorbing SCCs
289+
if Length(markov.transient_vertices) = 0 then
290+
chances_of_absorption := [];
291+
else
292+
chances_of_absorption := markov.fundamental_mat * markov.absorption_mat;
293+
fi;
294+
295+
# Convert to output format
296+
# Rows are all vertices, columns are all SCCs
265297
output := NullMat(DigraphNrVertices(D), Length(scc.comps));
266298

267299
# Non-transient vertices stay in their own SCC with probability 1
300+
sink_comps := markov.sink_comps;
268301
for comp_no in sink_comps do
269302
for v in scc.comps[comp_no] do
270303
output[v][comp_no] := 1;
271304
od;
272305
od;
273306

274-
# Transient vertices have chances as calculated above
307+
# Transient vertices have chances as calculated in Markov code
308+
transient_vertices := markov.transient_vertices;
275309
for i in [1 .. Length(transient_vertices)] do
276310
v := transient_vertices[i];
277311
for j in [1 .. Length(sink_comps)] do
@@ -283,6 +317,28 @@ function(D)
283317
return output;
284318
end);
285319

320+
InstallMethod(DigraphAbsorptionExpectedSteps,
321+
"for a digraph",
322+
[IsDigraph],
323+
function(D)
324+
local markov, N, transient_expected_steps, out, i;
325+
if DigraphNrVertices(D) = 0 then
326+
return [];
327+
fi;
328+
markov := DIGRAPHS_AbsorbingMarkovChain(D);
329+
N := markov.fundamental_mat;
330+
if Length(N) = 0 then # no transient vertices
331+
transient_expected_steps := [];
332+
else
333+
transient_expected_steps := N * ListWithIdenticalEntries(Length(N), 1);
334+
fi;
335+
out := ListWithIdenticalEntries(DigraphNrVertices(D), 0);
336+
for i in [1 .. Length(transient_expected_steps)] do
337+
out[markov.transient_vertices[i]] := transient_expected_steps[i];
338+
od;
339+
return out;
340+
end);
341+
286342
BindGlobal("DIGRAPHS_ChromaticNumberLawler",
287343
function(D)
288344
local n, vertices, subset_colours, s, S, i, I, subset_iter, x,

tst/standard/attr.tst

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2883,23 +2883,6 @@ gap> DigraphStronglyConnectedComponents(gr).comps; # this ordering is used
28832883
[ [ 2, 3 ], [ 4 ], [ 1 ] ]
28842884
gap> DigraphAbsorptionProbabilities(gr);
28852885
[ [ 2/3, 1/3, 0 ], [ 1, 0, 0 ], [ 1, 0, 0 ], [ 0, 1, 0 ] ]
2886-
gap> soccer := Digraph([[7, 2, 3, 5, 1, 4], # Motivating example:
2887-
> [7, 2, 3, 5, 5, 4], # game of 'Soccer Dice'
2888-
> [7, 5, 5, 5, 5, 1],
2889-
> [7, 7, 2, 5, 5, 5],
2890-
> [6, 6, 7, 7, 7, 4],
2891-
> [], []]);;
2892-
gap> DigraphStronglyConnectedComponents(soccer).comps; # this ordering is used
2893-
[ [ 7 ], [ 6 ], [ 1, 2, 3, 5, 4 ] ]
2894-
gap> DigraphAbsorptionProbabilities(soccer) =
2895-
> [[3473 / 4493, 1020 / 4493, 0],
2896-
> [3365 / 4493, 1128 / 4493, 0],
2897-
> [3211 / 4493, 1282 / 4493, 0],
2898-
> [3471 / 4493, 1022 / 4493, 0],
2899-
> [2825 / 4493, 1668 / 4493, 0],
2900-
> [0, 1, 0],
2901-
> [1, 0, 0]];
2902-
true
29032886
gap> DigraphAbsorptionProbabilities(EmptyDigraph(0));
29042887
[ ]
29052888
gap> DigraphAbsorptionProbabilities(EmptyDigraph(1));
@@ -2908,6 +2891,8 @@ gap> DigraphAbsorptionProbabilities(CompleteDigraph(5));
29082891
[ [ 1 ], [ 1 ], [ 1 ], [ 1 ], [ 1 ] ]
29092892
gap> DigraphAbsorptionProbabilities(ChainDigraph(4));
29102893
[ [ 0, 0, 0, 1 ], [ 0, 0, 0, 1 ], [ 0, 0, 0, 1 ], [ 0, 0, 0, 1 ] ]
2894+
gap> DigraphAbsorptionProbabilities(CycleDigraph(5));
2895+
[ [ 1 ], [ 1 ], [ 1 ], [ 1 ], [ 1 ] ]
29112896
gap> gr := ChainDigraph(250);;
29122897
gap> probs := DigraphAbsorptionProbabilities(gr);;
29132898
gap> scc := DigraphStronglyConnectedComponents(gr);;
@@ -2917,6 +2902,53 @@ gap> ForAll(probs, # all zeros except for the sink
29172902
> comp -> v[comp] = 0
29182903
> or (v[comp] = 1 and scc.id[comp] = sink)));
29192904
true
2905+
gap> gr := Digraph([[1], []]);;
2906+
gap> DigraphAbsorptionProbabilities(gr);
2907+
[ [ 1, 0 ], [ 0, 1 ] ]
2908+
2909+
# DigraphAbsorptionExpectedSteps
2910+
gap> DigraphAbsorptionExpectedSteps(Digraph([[2], [3], [2]]));
2911+
[ 1, 0, 0 ]
2912+
gap> DigraphAbsorptionExpectedSteps(Digraph([]));
2913+
[ ]
2914+
gap> DigraphAbsorptionExpectedSteps(Digraph([[1]]));
2915+
[ 0 ]
2916+
gap> DigraphAbsorptionExpectedSteps(Digraph([[1, 2], [2]]));
2917+
[ 2, 0 ]
2918+
gap> DigraphAbsorptionExpectedSteps(ChainDigraph(5));
2919+
[ 4, 3, 2, 1, 0 ]
2920+
gap> DigraphAbsorptionExpectedSteps(CompleteDigraph(5));
2921+
[ 0, 0, 0, 0, 0 ]
2922+
2923+
# Absorption motivating example: game of 'Soccer Dice'
2924+
gap> soccer := Digraph([[7, 2, 3, 5, 1, 4],
2925+
> [7, 2, 3, 5, 5, 4],
2926+
> [7, 5, 5, 5, 5, 1],
2927+
> [7, 7, 2, 5, 5, 5],
2928+
> [6, 6, 7, 7, 7, 4],
2929+
> [], []]);;
2930+
gap> DigraphStronglyConnectedComponents(soccer).comps; # this ordering is used
2931+
[ [ 7 ], [ 6 ], [ 1, 2, 3, 5, 4 ] ]
2932+
gap> DigraphAbsorptionProbabilities(soccer) =
2933+
> [[3473 / 4493, 1020 / 4493, 0],
2934+
> [3365 / 4493, 1128 / 4493, 0],
2935+
> [3211 / 4493, 1282 / 4493, 0],
2936+
> [3471 / 4493, 1022 / 4493, 0],
2937+
> [2825 / 4493, 1668 / 4493, 0],
2938+
> [0, 1, 0],
2939+
> [1, 0, 0]];
2940+
true
2941+
gap> DigraphAbsorptionExpectedSteps(soccer);
2942+
[ 13026/4493, 11868/4493, 10716/4493, 9510/4493, 6078/4493, 0, 0 ]
2943+
2944+
# Absorption motivating example: a flea on the vertices of a dodecahedron.
2945+
gap> gr := Digraph("dodecahedral");;
2946+
gap> gr := DigraphRemoveEdges(gr, List(OutNeighbours(gr)[1], v -> [1, v]));
2947+
<immutable digraph with 20 vertices, 57 edges>
2948+
gap> DigraphAbsorptionExpectedSteps(gr)[1];
2949+
0
2950+
gap> DigraphAbsorptionExpectedSteps(gr)[2];
2951+
19
29202952

29212953
# Unbind local variables, auto-generated by etc/tst-unbind-local-vars.py
29222954
gap> Unbind(A);

0 commit comments

Comments
 (0)