relationalai 0.12.4__py3-none-any.whl → 0.12.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. relationalai/__init__.py +4 -0
  2. relationalai/clients/snowflake.py +34 -13
  3. relationalai/early_access/lqp/constructors/__init__.py +2 -2
  4. relationalai/early_access/metamodel/rewrite/__init__.py +2 -2
  5. relationalai/{semantics/reasoners/graph → experimental}/paths/README.md +2 -2
  6. relationalai/experimental/paths/__init__.py +14 -309
  7. relationalai/{semantics/reasoners/graph → experimental}/paths/examples/basic_example.py +2 -2
  8. relationalai/{semantics/reasoners/graph → experimental}/paths/examples/paths_benchmark.py +2 -2
  9. relationalai/{semantics/reasoners/graph → experimental}/paths/examples/paths_example.py +2 -2
  10. relationalai/{semantics/reasoners/graph → experimental}/paths/examples/pattern_to_automaton.py +1 -1
  11. relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/one_sided_ball_repetition.py +1 -1
  12. relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/single.py +3 -3
  13. relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/two_sided_balls_repetition.py +1 -1
  14. relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/two_sided_balls_upto.py +2 -2
  15. relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/usp-old.py +3 -3
  16. relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/usp-tuple.py +3 -3
  17. relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/usp.py +3 -3
  18. relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_limit_sp_max_length.py +2 -2
  19. relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_limit_sp_multiple.py +2 -2
  20. relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_limit_sp_single.py +2 -2
  21. relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_limit_walks_multiple.py +2 -2
  22. relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_limit_walks_single.py +2 -2
  23. relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_one_sided_ball_repetition_multiple.py +2 -2
  24. relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_one_sided_ball_repetition_single.py +2 -2
  25. relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_one_sided_ball_upto_multiple.py +2 -2
  26. relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_one_sided_ball_upto_single.py +2 -2
  27. relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_single_paths.py +2 -2
  28. relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_single_walks.py +2 -2
  29. relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_single_walks_undirected.py +2 -2
  30. relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_two_sided_balls_repetition_multiple.py +2 -2
  31. relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_two_sided_balls_repetition_single.py +2 -2
  32. relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_two_sided_balls_upto_multiple.py +2 -2
  33. relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_two_sided_balls_upto_single.py +2 -2
  34. relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_usp_nsp_multiple.py +2 -2
  35. relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_usp_nsp_single.py +2 -2
  36. relationalai/semantics/__init__.py +4 -0
  37. relationalai/semantics/internal/annotations.py +1 -0
  38. relationalai/semantics/internal/internal.py +3 -4
  39. relationalai/semantics/internal/snowflake.py +14 -1
  40. relationalai/semantics/lqp/builtins.py +1 -0
  41. relationalai/semantics/lqp/constructors.py +0 -5
  42. relationalai/semantics/lqp/executor.py +34 -10
  43. relationalai/semantics/lqp/intrinsics.py +2 -2
  44. relationalai/semantics/lqp/model2lqp.py +105 -9
  45. relationalai/semantics/lqp/passes.py +27 -8
  46. relationalai/semantics/lqp/primitives.py +18 -15
  47. relationalai/semantics/lqp/rewrite/__init__.py +2 -2
  48. relationalai/semantics/lqp/rewrite/{fd_constraints.py → function_annotations.py} +4 -4
  49. relationalai/semantics/lqp/utils.py +17 -13
  50. relationalai/semantics/metamodel/builtins.py +50 -1
  51. relationalai/semantics/metamodel/typer/typer.py +3 -0
  52. relationalai/semantics/reasoners/__init__.py +4 -0
  53. relationalai/semantics/reasoners/experimental/__init__.py +7 -0
  54. relationalai/semantics/reasoners/graph/core.py +1154 -122
  55. relationalai/semantics/rel/builtins.py +3 -1
  56. relationalai/semantics/rel/compiler.py +2 -2
  57. relationalai/semantics/rel/executor.py +30 -8
  58. relationalai/semantics/rel/rel_utils.py +5 -0
  59. relationalai/semantics/snowflake/__init__.py +2 -2
  60. relationalai/semantics/sql/compiler.py +6 -0
  61. relationalai/semantics/sql/executor/snowflake.py +6 -2
  62. relationalai/semantics/tests/test_snapshot_abstract.py +5 -4
  63. {relationalai-0.12.4.dist-info → relationalai-0.12.7.dist-info}/METADATA +2 -2
  64. {relationalai-0.12.4.dist-info → relationalai-0.12.7.dist-info}/RECORD +99 -115
  65. {relationalai-0.12.4.dist-info → relationalai-0.12.7.dist-info}/WHEEL +1 -1
  66. relationalai/early_access/paths/__init__.py +0 -22
  67. relationalai/early_access/paths/api/__init__.py +0 -12
  68. relationalai/early_access/paths/benchmarks/__init__.py +0 -13
  69. relationalai/early_access/paths/graph/__init__.py +0 -12
  70. relationalai/early_access/paths/path_algorithms/find_paths/__init__.py +0 -12
  71. relationalai/early_access/paths/path_algorithms/one_sided_ball_repetition/__init__.py +0 -12
  72. relationalai/early_access/paths/path_algorithms/one_sided_ball_upto/__init__.py +0 -12
  73. relationalai/early_access/paths/path_algorithms/single/__init__.py +0 -12
  74. relationalai/early_access/paths/path_algorithms/two_sided_balls_repetition/__init__.py +0 -12
  75. relationalai/early_access/paths/path_algorithms/two_sided_balls_upto/__init__.py +0 -12
  76. relationalai/early_access/paths/path_algorithms/usp/__init__.py +0 -12
  77. relationalai/early_access/paths/rpq/__init__.py +0 -13
  78. relationalai/early_access/paths/utilities/iterators/__init__.py +0 -12
  79. relationalai/experimental/paths/pathfinder.rel +0 -2560
  80. relationalai/semantics/reasoners/graph/paths/__init__.py +0 -16
  81. relationalai/semantics/reasoners/graph/paths/path_algorithms/__init__.py +0 -3
  82. relationalai/semantics/reasoners/graph/paths/utilities/__init__.py +0 -3
  83. /relationalai/{semantics/reasoners/graph → experimental}/paths/api.py +0 -0
  84. /relationalai/{semantics/reasoners/graph → experimental}/paths/benchmarks/__init__.py +0 -0
  85. /relationalai/{semantics/reasoners/graph → experimental}/paths/benchmarks/grid_graph.py +0 -0
  86. /relationalai/{semantics/reasoners/graph → experimental}/paths/code_organization.md +0 -0
  87. /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/Movies.ipynb +0 -0
  88. /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/minimal_engine_warmup.py +0 -0
  89. /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movie_example.py +0 -0
  90. /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/actedin.csv +0 -0
  91. /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/directed.csv +0 -0
  92. /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/follows.csv +0 -0
  93. /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/movies.csv +0 -0
  94. /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/person.csv +0 -0
  95. /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/produced.csv +0 -0
  96. /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/ratings.csv +0 -0
  97. /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/wrote.csv +0 -0
  98. /relationalai/{semantics/reasoners/graph → experimental}/paths/find_paths_via_automaton.py +0 -0
  99. /relationalai/{semantics/reasoners/graph → experimental}/paths/graph.py +0 -0
  100. /relationalai/{early_access → experimental}/paths/path_algorithms/__init__.py +0 -0
  101. /relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/find_paths.py +0 -0
  102. /relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/one_sided_ball_upto.py +0 -0
  103. /relationalai/{semantics/reasoners/graph → experimental}/paths/product_graph.py +0 -0
  104. /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/__init__.py +0 -0
  105. /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/automaton.py +0 -0
  106. /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/diagnostics.py +0 -0
  107. /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/filter.py +0 -0
  108. /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/glushkov.py +0 -0
  109. /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/rpq.py +0 -0
  110. /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/transition.py +0 -0
  111. /relationalai/{early_access → experimental}/paths/utilities/__init__.py +0 -0
  112. /relationalai/{semantics/reasoners/graph → experimental}/paths/utilities/iterators.py +0 -0
  113. /relationalai/{semantics/reasoners/graph → experimental}/paths/utilities/prefix_sum.py +0 -0
  114. /relationalai/{semantics/reasoners/graph → experimental}/paths/utilities/utilities.py +0 -0
  115. {relationalai-0.12.4.dist-info → relationalai-0.12.7.dist-info}/entry_points.txt +0 -0
  116. {relationalai-0.12.4.dist-info → relationalai-0.12.7.dist-info}/licenses/LICENSE +0 -0
@@ -1,2560 +0,0 @@
1
- // Pathfinder package
2
-
3
- // pathfinder/utilities (from model/utilities.rel)
4
- doc"""
5
- Contains utility methods for working with paths and methods identified as
6
- of potential general interest (for consideration to be moved to other
7
- libraries).
8
- """
9
- namespace pathfinder::utilities
10
-
11
- doc"""
12
- prefix_sum[{List}]
13
-
14
- Computes sums of each prefix of an enumerated list `List(i, val)` i.e.,
15
- ```
16
- prefix_sum[List, i] = List[1] + ... + List[i].
17
- ```
18
- Assumes that `List` is a binary predicate: the first attribute takes consecutive integers
19
- values starting from 1, the second attribute supports addition. Uses an efficient
20
- divide-and-conquer approach.
21
- """
22
- @inline
23
- def prefix_sum({List}, i, val): _prefix_sum(List, :result, i, val)
24
-
25
- // Implementation of prefix sums using divide-and-conquer approach
26
- @outline
27
- module _prefix_sum[{List}]
28
- // Backward partial sums of exponentially growing lengths.
29
- // B[i, j] = List[i - j + 1] + ... + List[i] for j∈{1,2,4,...} s.t. i|j
30
- def B(i, 1, val): List(i, val)
31
- def B(i, j, val):
32
- exists((k) |
33
- val = B[i, k] + B[i - k, k] and
34
- j = 2 * k and
35
- modulo[i, j] = 0
36
- )
37
-
38
- // Longest backward partial sum
39
- def L[i, j]: (B[i, j], j = max[(k) : B(i, k, _)])
40
-
41
- // Final result
42
- def result(i, val): L(i, i, val)
43
- def result(i, val): exists((j) | val = L[i, j] + result[i - j])
44
- end
45
-
46
- doc"""
47
- linear_prefix_sum[{List}]
48
-
49
- Computes sums of each prefix of an enumerated list `List(i, val)` i.e.,
50
- ```
51
- prefix_sum[List, i] = List[1] + ... + List[i].
52
- ```
53
- Assumes that `List` is a binary predicate: the first attribute takes consecutive integers
54
- values starting from 1, the second attribute supports addition. This implementation uses
55
- the natural linear recursion approach.
56
- """
57
- @inline
58
- def linear_prefix_sum({List}, i, val): _linear_prefix_sum(List, :result, i, val)
59
-
60
- // Implementation of prefix sums using divide-and-conquer approach
61
- @outline
62
- module _linear_prefix_sum[{List}]
63
- @function
64
- def result(i, val) : List(i, val) and i = 1
65
- def result(i, val) : exists( (w, v) | result(i-1, w) and List(i, v) and val = w+v )
66
- end
67
-
68
- doc"""
69
- hash_paths[{Paths}]
70
-
71
- Replaces the edge identifier of every path in the input collection `Paths` with a unique
72
- _canonical_ identifier of the path. The canonical identifier of a path is computed by
73
- hashing the path contents. In particular, if the input collection contains repeated
74
- paths, they will be collapsed into a single path.
75
- """
76
- @inline
77
- def hash_paths({Paths}, new_id, x...):
78
- exists((old_id) | Paths(old_id, x...) and _path_hash_reduce(Paths[old_id], new_id))
79
-
80
- // Computes the hash of a path ensuring proper handling of empty (single node) paths
81
- // and explicitly determining the order of hashing node ids and edge labels.
82
- @inline
83
- def _path_hash_reduce[{Path}] :
84
- rel_primitive_reduce[
85
- murmurhash3f_with_seed,
86
- uint128[0],
87
- {(1, hash_reduce[Path[:node]]); (2, hash_reduce[Path[:edge_label]])}
88
- ]
89
-
90
-
91
- doc"""
92
- hash_reduce[{R}]
93
-
94
- Reduces a relation `R` into a single number (UInt128) representing its hash value.
95
- """
96
- @inline
97
- def hash_reduce[{R}]: rel_primitive_reduce[murmurhash3f_with_seed, uint128[0], hash[R]]
98
-
99
-
100
- doc"""
101
- pivot_paths[{R}, {L}]
102
-
103
- Pivots paths represented horizontally in a relation to a vertical representation. The
104
- input relation `R` must have a fixed arity and the relation `L` is a relation
105
- with a single tuple of strings to be used as labels for the edges in the path. Naturally,
106
- `arity(L) = arity(R)-1`. Example usage:
107
-
108
- ```
109
- pivot_paths[{ (1,2,3); (3,2,4) }, {"a", "b"}]
110
- ```
111
-
112
- The implementation does not check that `R` is a relation with uniform arity, and that `L`
113
- is a relation of arity one lesser with a single tuple of strings. Should those
114
- assumptions be violated, the behavior is undefined.
115
- """
116
- @inline
117
- def pivot_paths[{R}, {L}]: {
118
- hash_paths[_pivot_paths[R, pivot[L], :paths]]
119
- }
120
-
121
- // Builds a path set from a horizontal relation of node identifiers and a (vertical) edge of
122
- // labels relation. The relation `R` must have a fixed arity and the relation `Labels` is a
123
- // ordered list of strings to be used as labels for the edges in the path.
124
- @outline
125
- module _pivot_paths[{R}, {Labels}]
126
- def path_count { count[R] }
127
- def path_length { arity[R] }
128
- def sorted_paths { sort[R] }
129
- def dom_paths { range[1, path_count, 1] }
130
- module paths[i in dom_paths]
131
- def node(j, n): {
132
- range(1, path_length, 1, j) and
133
- pivot(sorted_paths[i], j, n)
134
- }
135
- def edge_label(j, l): {
136
- range(1, path_length - 1, 1, j) and
137
- Labels(j, l)
138
- }
139
- end
140
- end
141
-
142
-
143
- doc"""
144
- canonical_index[i, j]
145
-
146
- Defines the canonical index of a pair of positive integers `(i, j)` by mapping it
147
- (bijectively) onto the set of positive integers. Its formulation proves that ℕ₊ ~ ℕ₊×ℕ₊.
148
-
149
- The principle of the enumeration is visualized on the fragment below.
150
-
151
- ```
152
-
153
- +--+
154
- 5 |11|
155
- +--+--+
156
- 4 | 7|12|
157
- +--+--+--+
158
- m 3 | 4| 8|13|
159
- +--+--+--+--+
160
- 2 | 2| 5| 9|14|
161
- +--+--+--+--+--+
162
- 1 | 1| 3| 6|10|15|
163
- ∙--+--+--+--+--+-→
164
- 1 2 3 4 5
165
- n
166
- ```
167
- """
168
- @inline
169
- def canonical_index[m, n]: {
170
- m + ((m + n - 2) * (m + n - 1 ) ÷ 2)
171
- }
172
-
173
-
174
- doc"""
175
- KleeneStar[{R}]
176
-
177
- Higher-order Kleene star operator defining the transitive and reflexive closure of the
178
- given binary relation `R`.
179
- """
180
- @outline
181
- def KleeneStar({R}, x, y):
182
- (x = y and (R(x, _) or R(_, x))) or
183
- exists((z) | KleeneStar(R, x, z) and R(z, y))
184
-
185
-
186
- doc"""
187
- KleenePlus[{R}]
188
-
189
- Higher-order Kleene star operator defining the transitive closure of the given binary
190
- relation `R`.
191
- """
192
- @outline
193
- def KleenePlus({R}, x, y):
194
- R(x, y) or exists((z) | KleenePlus(R, x, z) and R(z, y))
195
-
196
- end // namespace pathfinder::utilities
197
-
198
-
199
- // pathfinder (from model/pathfinder.rel)
200
- namespace pathfinder
201
-
202
- from ::pathfinder::utilities import hash_paths
203
-
204
- def version { "0.2.0" }
205
-
206
- doc"""
207
- shortest_paths[group, selection, {PathQuery}, {Source}, {Target}(, {K})]
208
-
209
- Returns the shortest paths connecting `Source` nodes to `Target` nodes that follow the
210
- `PathQuery` predicate. The `group` parameter, can be either `:any` or `:for_each`, and it
211
- controls whether the output consists of paths whose length is the minimum length of a path
212
- connecting `:any` pair of source and target nodes, or whether `:for_each` pair of source
213
- and target nodes their connecting shortest paths are constructed. The `selection` parameter
214
- can be either `:single`, `:all`, or `:limit`, and it controls the set of paths that are
215
- returned. The `:single` selection returns a single shortest path (and in `:for_each`
216
- grouping a single path per source-target pair). The `:all` selection returns all shortest
217
- paths. The `:limit` allows to indicate the upper bound `K` on the number of paths to return;
218
- Note that the `shortest_paths` is overloaded for the selection `:limit` and expects the
219
- value `K` to be provided.
220
-
221
- The output is a set of paths represented vertically with each path having a canonical
222
- identifier. Namely, the output is a relation `paths` with two specializations:
223
- * `paths(path_id, :node, i, v)` indicating that the `i`-th node of the path `path_id` is `v`.
224
- * `paths(path_id, :edge_label, i, lab)` indicating that the `i`-th edge of the path `path_id`
225
- is labeled with `lab` (the edge connects the `i`-th and `i+1`-th node of the path).
226
- """
227
-
228
- @inline
229
- def shortest_paths[:any, :all, :two_sided, {PQ}, {S}, {T}]:
230
- hash_paths[::pathfinder::_internal::any::all[PQ, S, T]]
231
- @inline
232
- def shortest_paths[:any, :all, :from_source, {PQ}, {S}, {T}]:
233
- ::pathfinder::_internal::any::from_source::all[PQ, S, T]
234
- @inline
235
- def shortest_paths[:for_each, :all, :from_source, {PQ}, {S}, {T}]:
236
- ::pathfinder::_internal::for_each::from_source::all[PQ, S, T]
237
-
238
-
239
- end // namespace pathfinder
240
-
241
- // pathfinder/_internal/utilities (from model/_internal/utilities.rel)
242
- namespace pathfinder::_internal::utilities
243
-
244
- from ::pathfinder::utilities import hash_paths
245
-
246
- //
247
- // Return a single tuple deterministically from the relation `R`.
248
- //
249
- @inline
250
- def some[{R}]: top[1, R, 1]
251
-
252
- //
253
- // Auxiliary hash value used in recursive path construction to indicate a dummy automaton
254
- // state. The value is chosen to be different from the hash values of the actual states.
255
- //
256
- @function
257
- def aux_hash { uint128_hash_value_convert[murmurhash3f[murmurhash3f["__auxiliary__"]]] }
258
-
259
- //
260
- // Implementations of the shortest_paths methods number the nodes by their distance from the
261
- // source node, thus enumerating them starting from 0. In Rel, however, it is customary to
262
- // enumerate lists starting from index 1. Refactoring of the present code has been deemed
263
- // to introduce too much risk if attempted without a good coverage of the test.
264
- // Consequently, we introduce a post-processing step that reindexes the nodes, starting
265
- // from 1 instead of 0. This transformation does introduce an overhead and we plan to remove
266
- // it in the future by refactoring the code constructing the paths.
267
- //
268
- @outline
269
- module reindex_nodes[{Paths}, path]
270
- def node(i, v): Paths(path, :node, i-1, v)
271
- def edge_label(i, lab): Paths(path, :edge_label, i, lab)
272
- end
273
-
274
-
275
- end // namespace pathfinder::_internal::utilities
276
-
277
- // pathfinder/_internal/any/balls (from model/_internal/any/balls.rel)
278
- namespace pathfinder::_internal::any
279
-
280
- //
281
- // balls[{Conn}, {S}, {T}]
282
- //
283
- // Constructs a subgraph of the product graph, defined by Conn, that consists of two
284
- // "balls", one growing from the source nodes S and the other from the target nodes T. The
285
- // balls are constructed by iteratively extending their diameter until they meet at common
286
- // middle node(s). To avoid explosion of the size of the balls, at each iteration we extend
287
- // only one of ball choosing the smallest one. There is a hard coded limit on the number of
288
- // iterations (up to 100).
289
- //
290
- // Input parameters:
291
- // - Conn: a connectivity predicate that defines a path query in the knowledge graph; used
292
- // to extract a finite automaton FA with a set of states that represents the query.
293
- // - S: a set of source nodes; Type: (Node)
294
- // - T: a set of target nodes; Type: (Node)
295
- //
296
- // Output (most relevant):
297
- // - pg: the product graph defined by the FA extracted from the predicate Conn. A _node_ of
298
- // the product graph is a pair (p, n) that represents FA reaching node n of the KG in a
299
- // state p. The edge between two nodes of the product graph is labeled with the (hash
300
- // value) of the edge label in the KG that connects the two nodes and has a
301
- // corresponding transition in A. An a-labeled edge from (p, u) to (q, v) is
302
- // represented as pg(p, q, a, u, v). Type: (State,State,#Label,Node,Node)
303
- // - label: a function that maps hashed edge labels to their string representation. Using
304
- // hashes improves performance drastically. Type: (#Label, String).
305
- // - source: source nodes of the product graph. Type: (Node,State).
306
- // - middle: the set of the nodes of the pg where the two balls meet. Type: (Node,State).
307
- // - target: target nodes of the product graph. Type: (Node,State).
308
- // - dist_from_S: a relation that holds the distance between a product node (v, q) in the
309
- // S-ball to its closest source node. Type: (Int,Node,State).
310
- // - dist_to_T: a relation that holds the distance between a product node (v, q) in the
311
- // T-ball to its closest target node. Type: (Int,Node,State).
312
- // - radius_X: the diameter of the source X-ball (X is S or T). Type:Int.
313
- // - dist_S_to_T: the length of the shortest path between the source and target nodes.
314
- // Type:Int .
315
- //
316
- @outline
317
- module balls[{Conn}, {S}, {T}]
318
-
319
- // The S-ball starts from the source nodes in the initial state 1 of the automaton
320
- @inline
321
- def initial_state { 1 }
322
-
323
- @inline
324
- def source(u, p): S(u) and p = initial_state
325
-
326
- // The T-ball starts from the target nodes in the final state of the automaton
327
- // which are provided by the FFI rel_primitive_product_graph_targets
328
- @pipeline
329
- def pg_target { rel_primitive_product_graph_targets[Conn] }
330
-
331
- @force_dnf
332
- def target(u, p): T(u) and pg_target(p, u)
333
-
334
- // The FFI rel_primitive_product_graph compiles the definition of the product graph
335
- @inline
336
- def pg { rel_primitive_product_graph[Conn] }
337
-
338
- // This FFI provides the mapping from hash values to the string labels.
339
- // (hash values of string labels are used for efficiency reasons)
340
- @function
341
- def label { rel_primitive_product_graph_labels[Conn] }
342
-
343
- // Hard-coded bound on the maximal number of iterations to avoid runaway computations.
344
- @inline
345
- def max_number_iterations { 100 }
346
-
347
-
348
- // Iterative process of constructing the balls. Uses recursion with non-stratified
349
- // negation to simulate while loop iteration: finish_iteration() and finish_loop()
350
- // control the execution of the loop.
351
- @function
352
- def radius_S {
353
- minimum[
354
- max[0;
355
- radius_S;
356
- {(radius_S + 1, // increment the diameter only if
357
- delta_S_size < delta_T_size and // the ball size increase is smaller
358
- finish_iteration() and
359
- not finish_loop())
360
- }
361
- ],
362
- max_number_iterations]
363
- }
364
-
365
- @function
366
- def radius_T {
367
- minimum[
368
- max[0;
369
- radius_T;
370
- {(radius_T + 1, // increment the diameter only if
371
- delta_S_size >= delta_T_size and // the ball size increase is smaller
372
- finish_iteration() and
373
- not finish_loop())}
374
- ],
375
- max_number_iterations]
376
- }
377
-
378
- // The sizes of the outermost layers of the respective balls at the current iteration
379
- @inline
380
- def delta_S_size { count[dist_from_S[radius_S]] }
381
- @inline
382
- def delta_T_size { count[dist_to_T[radius_T]] }
383
-
384
- // Predecessor nodes of the source ball
385
- @force_dnf
386
- def prev_dist_from_S(v, q):
387
- exists((u, p) |
388
- dist_from_S(radius_S - 1, u, p) and pg(p, q, _, u, v)
389
- )
390
-
391
- // Extending the source ball (if radius_S hash been incremented)
392
- @force_dnf
393
- def dist_from_S(d, v, q): source(v, q) and d = 0
394
- def dist_from_S(d, v, q):
395
- d = radius_S and
396
- prev_dist_from_S(v, q) and
397
- not exists((j) | range(0, radius_S - 1, 1, j) and dist_from_S(j, v, q))
398
- def dist_from_S(d, v, q): dist_from_S(d, v, q)
399
-
400
- // Predecessor nodes of the target ball
401
- @force_dnf
402
- def prev_dist_to_T(u, p):
403
- exists((v, q) |
404
- dist_to_T(radius_T - 1, v, q) and pg(p, q, _, u, v)
405
- )
406
-
407
- // Extending the target ball (if radius_T hash been incremented)
408
- @force_dnf
409
- def dist_to_T(d, u, p): target(u, p) and d = 0
410
- def dist_to_T(d, u, p):
411
- d = radius_T and
412
- prev_dist_to_T(u, p) and
413
- not exists((j) | range(0, radius_T - 1, 1, j) and dist_to_T(j, u, p))
414
- def dist_to_T(d, u, p): dist_to_T(d, u, p)
415
-
416
- // Iteration is complete if extending both balls has been considered
417
- def finish_iteration(): delta_S_size > 0 and delta_T_size > 0
418
-
419
- // Loop is terminated when the two balls meet
420
- def finish_loop():
421
- exists((u, p) |
422
- dist_from_S(radius_S, u, p) and dist_to_T(radius_T, u, p)
423
- )
424
-
425
- // Nodes of the product graph where the two balls meet.
426
- def middle(u, p): dist_from_S(radius_S, u, p) and dist_to_T(radius_T, u, p)
427
-
428
- // The shortest path length is precisely the sum of the two diameters because we
429
- // terminate the loop precisely when the two balls meet.
430
- @function
431
- def dist_S_to_T { radius_S + radius_T }
432
-
433
- end // module balls[{Conn}, {S}, {T}]
434
-
435
-
436
- end // namespace pathfinder::_internal::any
437
-
438
- // pathfinder/_internal/any/single (from model/_internal/any/single.rel)
439
- namespace pathfinder::_internal::any
440
-
441
- from ::pathfinder::_internal::utilities import ...
442
-
443
- // _Single indexes the nodes based on their distance from the source (starting from 0). We
444
- // reindex them to start from 1, as is common practice in Rel.
445
- @inline
446
- def single[{Conn}, {S}, {T}]: { reindex_nodes[_single[Conn, S, T, :paths]] }
447
-
448
- //
449
- // Computes a single shortest path from the set of source nodes S to the set of target nodes
450
- // T that follows the predicate Conn. The :any grouping selects only the shortest paths
451
- // among any paths satisfying Conn and connecting a source node to a target node. The path
452
- // is computed by selecting a middle node and building the two path fragments from the
453
- // source to the middle node and from the middle node to the target.
454
- //
455
- @outline
456
- module _single[{Conn}, {S}, {T}]
457
-
458
- with balls[Conn, S, T] use
459
- pg, // product graph. Type: (State,State,#Label,Node,Node)
460
- dist_from_S, // distance from S. Type: (Int,Node,State)
461
- dist_to_T, // distance to T. Type: (Int,Node,State)
462
- dist_S_to_T, // length of shortest path from S to T. Type: Int
463
- radius_S, // S-ball radius. Type: Int
464
- middle, // middle nodes in pg. Type: (Node,State)
465
- label // edge label: Type: (#Label,String)
466
-
467
- def paths(1, :node, i, v): path_from_S(i, v, _, _) or path_to_T(i, v, _, _)
468
- def paths[1, :edge_label, i]: label[path_from_S[i-1, _, _]]
469
- def paths[1, :edge_label, i]: label[path_to_T[i, _, _]]
470
-
471
- // Pick a single middle node
472
- @function
473
- def single_candidate { some[middle] }
474
-
475
- // Build the path_from_S(Int, Node, Node, State) from the source to the midway node
476
- // going backwards, following edges in the product graph. With each path node keep the
477
- // corresponding node of the product graph.
478
- @function
479
- def path_from_S[i]:
480
- (single_candidate, aux_hash, i = radius_S) // use dummy pg node `aux_hash`
481
- @force_dnf
482
- def path_from_S[i]:
483
- some[
484
- (u, p, a): exists((q, v) |
485
- dist_from_S(i, u, p) and
486
- pg(p, q, a, u, v) and
487
- path_from_S(i + 1, v, q, _))
488
- ]
489
-
490
- // Build the path fragment from the selected middle node to the target node going
491
- // forwards following edges in the product graph (with each path node keep the
492
- // corresponding node of the product graph)
493
- @function
494
- def path_to_T[i]:
495
- (single_candidate, aux_hash, i = radius_S) // use dummy pg node
496
-
497
- @force_dnf
498
- def path_to_T[i]:
499
- some[
500
- (v, q, a): exists((p, u) |
501
- dist_to_T(dist_S_to_T - i, v, q) and
502
- pg(p, q, a, u, v) and
503
- path_to_T(i - 1, u, p, _))
504
- ]
505
-
506
- end // with balls[Conn, S, T]
507
-
508
- end // module _single[{Conn}, {S}, {T}]
509
-
510
-
511
- end // namespace pathfinder::_internal::any
512
-
513
- // pathfinder/_internal/any/usp (from model/_internal/any/usp.rel)
514
- namespace pathfinder::_internal::any
515
-
516
- from ::pathfinder::utilities import prefix_sum
517
-
518
- //
519
- // The union of shortest paths (USP) is a subgraph of the product graph that contains only
520
- // the shortest paths from the set of source nodes to the set of target nodes. Recall that
521
- // a node in a product graph is represented as a pair of values (u, p) where u is a node in
522
- // the source graph and p is a state of the finite automaton capturing the connectivity path
523
- // query. USP is decorated with additional information that compactly represents the
524
- // enumeration of the shortest paths that allows an efficient path retrieval.
525
- //
526
- // Because of performance considerations, we do not construct the full USP, but only its
527
- // fragments, and depending on the case we use
528
- // - usp_cut to construct a part of USP traversing only selected k middle nodes; used
529
- // for constructing limit-k shortest paths.
530
- // - usp_segment to construct a fragment of USP, either from the source nodes
531
- // to the middle nodes or from the target nodes to the middle nodes (in inverted
532
- // direction).
533
-
534
- //
535
- // Constructs a part of the USP containing the shortest paths going through K middle points,
536
- // used for limit-k queries. The USP fragment is obtained by identifying the paths
537
- // traversing K selected middle nodes (obtained from the balls construction).
538
- //
539
- // Input:
540
- // - Conn: a connectivity predicate that defines a path query in the knowledge graph
541
- // - S: a set of source nodes. Type: (Node)
542
- // - T: a set of target nodes. Type: (Node)
543
- // - K: the number of middle nodes. Type: Int
544
- //
545
- // Output (that we use):
546
- // - limit_middle: the set of (up to) K middle nodes. Type: (Node,State)
547
- // - usp_edge: the edges of the USP fragment. Note that it has different signature than
548
- // product graph. Namely, Type: (Node,State,Node,State,#Label)
549
- // Compact representation of the enumeration of the shortest paths
550
- // - neighbor: enumeration of the outbound edges of USP node.
551
- // Type: (Node,State,Int,Node,State,#Label)
552
- // - nsp: the number of shortest paths from a USP node to target nodes.
553
- // Type: (Node,State,Int)
554
- // - interval: the interval of numbers of shortest paths for source nodes.
555
- // Type: (Node,Int,Int)
556
- // - acc_nsp: the enumeration of the shortest paths going through the outbound edges of a
557
- // USP node. Type: (Node,State,Int)
558
- // - total: the total number of shortest paths from S to T (may be <= K). Type: Int
559
- //
560
- @outline
561
- module usp_cut[{Conn}, {S}, {T}, {K}]
562
-
563
- with balls[Conn, S, T] use
564
- pg, // product graph. Type: (State,State,#Label,Node,Node)
565
- dist_from_S, // distance from S. Type: (Int,Node,State)
566
- dist_to_T, // distance to T. Type: (Int,Node,State)
567
- dist_S_to_T, // length of shortest path from S to T. Type: Int
568
- radius_S, // S-ball radius. Type: Int
569
- radius_T, // T-ball radius. Type: Int
570
- initial_state, // initial state of the automaton. Type: Int
571
- target, // target nodes. Type: (Node,State)
572
- middle // middle nodes in pg. Type: (Node,State)
573
-
574
- // Pick k middle nodes
575
- def limit_middle { top[K, middle, range[1, K, 1]] }
576
-
577
- // The USP fragment is obtained by constructing the paths from the source nodes to the
578
- // middle nodes and from the middle nodes to the target nodes.
579
- def usp_edge { usp_edge_back[_]; usp_edge_fwd[_] }
580
-
581
- // Build the paths backward from the middle to the source nodes. We materialize the USP
582
- // fragment "on the fly", using recursion where we control the order of evaluation by
583
- // introducing additional intermediate predicates.
584
- @force_dnf
585
- def start_usp_edge_back(u, p, v, q, a):
586
- pg(p, q, a, u, v) and
587
- limit_middle(v, q)
588
-
589
- @force_dnf
590
- def step_usp_edge_back(i, u, p, v, q, a):
591
- usp_edge_back(i + 1, v, q, _, _, _) and
592
- pg(p, q, a, u, v)
593
-
594
- def usp_edge_back(i, u, p, v, q, a):
595
- dist_from_S(radius_S - 1, u, p) and
596
- start_usp_edge_back(u, p, v, q, a) and
597
- i = radius_S - 1
598
- def usp_edge_back(i, u, p, v, q, a):
599
- step_usp_edge_back(i, u, p, v, q, a) and
600
- dist_from_S(i, u, p)
601
-
602
- // Build the paths forward from the middle to the target nodes. We materialize the USP
603
- // fragment "on the fly", using recursion where we control the order of evaluation by
604
- // introducing additional intermediate predicates.
605
- @force_dnf
606
- def start_usp_edge_fwd(u, p, v, q, a):
607
- limit_middle(u, p) and
608
- pg(p, q, a, u, v)
609
-
610
- @force_dnf
611
- def step_usp_edge_fwd(i, u, p, v, q, a):
612
- usp_edge_fwd(i - 1, _, _, u, p, _) and
613
- pg(p, q, a, u, v)
614
-
615
- def usp_edge_fwd(i, u, p, v, q, a):
616
- start_usp_edge_fwd(u, p, v, q, a) and
617
- dist_to_T(radius_T - 1, v, q) and
618
- i = radius_S
619
- def usp_edge_fwd(i, u, p, v, q, a):
620
- step_usp_edge_fwd(i, u, p, v, q, a) and
621
- dist_to_T(dist_S_to_T - i - 1, v, q)
622
-
623
- //
624
- // An enumeration of relevant shortest paths, represented in a compact manner.
625
- //
626
-
627
- // Order the outbound edges of every pg node
628
- def neighbor(u, p, i, v, q, a):
629
- enumerate({(w, _q, _a): usp_edge(u, p, w, _q, _a)}, i, v, q, a)
630
-
631
- // Compute the number of shortest paths from the given USP node to target nodes
632
- def nsp[u, p]: (1, target(u, p))
633
- def nsp[u, p]: sum[(v, q, a, w): w = nsp[v, q] and usp_edge(u, p, v, q, a)]
634
-
635
- // Filter out the source nodes that are not connected to a target node with a shortest
636
- // path visiting limit_middle. If S and T coincide, USP has no edges and all relevant
637
- // source nodes are in limit_middle (those are also target nodes).
638
- def relevant_source(u): S(u) and usp_edge(u, initial_state, _, _, _)
639
- def relevant_source(u): S(u) and limit_middle(u, initial_state)
640
-
641
- // Order the relevant source nodes
642
- def ordered_source { sort[relevant_source] }
643
-
644
- // Decomposition of number of shortest paths that traverses nodes (and edges).
645
- // Edge case is simple and we keep the relative position i of the edge.
646
- def edge_nsp(u, p, i, val): exists((v, q) | val = nsp[v, q] and neighbor(u, p, i, v, q, _))
647
-
648
- // Node case: an enumeration of the shortest paths going through its outbound edges
649
- def acc_nsp[u, p, 0]: (0, usp_edge(u, p, _, _, _))
650
- def acc_nsp[u, p]: prefix_sum[edge_nsp[u, p]]
651
-
652
- // Also, relevant source nodes get an interval [a, b] of the shortest paths (up to K)
653
- def interval(u, a, b) :
654
- ordered_source(1, u) and a = 1 and b = minimum[nsp[u, initial_state], K]
655
-
656
- def interval(u, a, b) :
657
- exists((i) |
658
- ordered_source(i, u) and
659
- a = running_sum[i - 1] + 1 and
660
- a <= K and
661
- b = minimum[running_sum[i], K])
662
-
663
- def value_source(i, val):
664
- exists((u) | ordered_source(i, u) and val = nsp[u, initial_state] and i <= K)
665
-
666
- def running_sum { prefix_sum[value_source] }
667
-
668
-
669
- // The total number of shortest paths from S to T (that go through the K middle nodes)
670
- def total { sum[(u, val): relevant_source(u) and val = nsp[u, initial_state]] }
671
-
672
-
673
- end // with balls[Conn, S, T]
674
-
675
- end // module usp_cut[{Conn}, {S}, {T}, {K}]
676
-
677
- //
678
- // Constructs a fragment of the USP containing the shortest paths from the set of source
679
- // nodes to the set of target nodes guided by the distance function and the product graph
680
- // definition.
681
- //
682
- // Input:
683
- // - Source: a set of pg source nodes. Type: (Node,State)
684
- // - Target: a set of pg target nodes. Type: (Node,State)
685
- // - Distance: distance of a pg node from the source. Type: (Int,Node,State)
686
- // - Length: the length of the shortest path in the USP fragment:
687
- // - PG: the product graph definition. Type: (State,State,#Label,Node,Node)
688
- //
689
- // Output:
690
- // - usp_edge: the edges of the USP fragment. Note that it has different signature than
691
- // product graph. Type: (Node,State,Node,State,#Label)
692
- // - neighbor: enumeration of the outbound edges of USP node.
693
- // Type: (Node,State,Int,Node,State,#Label)
694
- // - nsp: the number of shortest paths from a USP node to target nodes.
695
- // Type: (Node,State,Int)
696
- // - interval: the interval of numbers of shortest paths for source nodes.
697
- // Type: (Node,Int,Int)
698
- // - acc_nsp: the enumeration of the shortest paths going through consecutive outbound edges
699
- // of a USP node. Type: (Node,State,Int,Int)
700
- //
701
- @outline
702
- module usp_segment[{Source}, {Target}, {Distance}, {Length}, {PG}]
703
-
704
- // The USP fragment is obtained by constructing the edges from the source to the target
705
- // following the indications of the distance function to capture only the shortest paths
706
- def usp_edge(u, p, v, q, a): _usp_edge(_, u, p, v, q, a)
707
-
708
- // The edges are build "on the fly", using recursion where we control the order of
709
- // evaluation by introducing additional intermediate predicates.
710
- @force_dnf
711
- def start_usp_edge(u, p, v, q, a): Distance(Length - 1, u, p) and PG(p, q, a, u, v)
712
-
713
- @force_dnf
714
- def step_usp_edge(i, u, p, v, q, a): _usp_edge(i + 1, v, q, _, _, _) and PG(p, q, a, u, v)
715
-
716
- def _usp_edge(i, u, p, v, q, a): start_usp_edge(u, p, v, q, a) and Target(v, q) and i = Length - 1
717
- def _usp_edge(i, u, p, v, q, a): step_usp_edge(i, u, p, v, q, a) and Distance(i, u, p)
718
-
719
- //
720
- // An enumeration of relevant shortest paths, represented in a compact manner.
721
- //
722
-
723
- // Order the outbound edges of every pg node
724
- def neighbor(u, p, i, v, q, a):
725
- enumerate({(w, s, b): usp_edge(u, p, w, s, b)}, i, v, q, a)
726
-
727
- // Compute the number of shortest paths from the given USP node to target nodes
728
- def nsp[u, p]: (1, Target(u, p))
729
- def nsp[u, p]: sum[(v, q, a, w): w = nsp[v, q] and usp_edge(u, p, v, q, a)]
730
-
731
- // Filter out the source nodes that are not connected to a target node with a shortest
732
- // path. If source and target nodes coincide, USP has no edges.
733
- def _ordered_source(u, p): Source(u, p) and usp_edge(u, p, _, _, _)
734
- def _ordered_source(u, p): Source(u, p) and Target(u, p)
735
-
736
- def ordered_source { sort[_ordered_source] }
737
-
738
- // Decomposition of number of shortest paths that traverses nodes (and edges).
739
- // Edge case is simple and we keep the relative position i of the edge.
740
- def edge_nsp(u, p, i, val):
741
- exists((v, q) |
742
- val = nsp[v, q] and
743
- neighbor(u, p, i, v, q, _)
744
- )
745
-
746
- // Node case: an enumeration of the shortest paths going through its outbound edges
747
- def acc_nsp[u, p, 0]: (0, usp_edge(u, p, _, _, _))
748
- def acc_nsp[u, p]: prefix_sum[edge_nsp[u, p]]
749
-
750
- // Relevant source nodes get an interval [a, b] of the shortest paths they originate
751
- def interval(u, p, a, b) :
752
- ordered_source(1, u, p) and a = 1 and b = nsp[u, p]
753
-
754
- def interval(u, p, a, b) :
755
- exists((i) |
756
- ordered_source(i, u, p) and
757
- a = running_sum[i - 1] + 1 and
758
- b = running_sum[i])
759
-
760
- def value_source(i, val):
761
- exists((u, p) | ordered_source(i, u, p) and val = nsp[u, p])
762
-
763
- def running_sum { prefix_sum[value_source] }
764
-
765
- end // module usp_segment[{Source}, {Target}, {Distance}, {Length}, {PG}]
766
-
767
-
768
- end // namespace pathfinder::_internal::any
769
-
770
- // pathfinder/_internal/any/limit (from model/_internal/any/limit.rel)
771
- namespace pathfinder::_internal::any
772
-
773
- from ::pathfinder::_internal::utilities import reindex_nodes, aux_hash
774
-
775
- // _limit indexes the nodes based on their distance from the source (starting from 0). We
776
- // reindex them to start from 1, as is common practice in Rel.
777
- @inline
778
- def limit[{Conn}, {S}, {T}, {K}]: { reindex_nodes[_limit[Conn, S, T, K, :paths]] }
779
-
780
- //
781
- // Computes at most K shortest paths from the set of source nodes S to the set of target
782
- // nodes T that follow the predicate Conn. The :any grouping selects only the shortest paths
783
- // among any paths satisfying Conn and connecting a source node to a target node. The paths
784
- // are computed by selecting K middle node and materializing a fragment of the USP with
785
- // the shortest paths visiting the selected middle nodes.
786
- //
787
- @outline
788
- module _limit[{Conn}, {S}, {T}, {K}]
789
-
790
- // Use balls to get the automaton's initial state and the edge label map
791
- with balls[Conn, S, T] use
792
- initial_state, // initial state of the automaton corresponding to Conn. Type: Int
793
- label // edge label. Type: (#Label,String)
794
-
795
- // Compute USP fragment containing only paths traversing K middle nodes
796
- with usp_cut[Conn, S, T, K] use
797
- limit_middle, // selected middle nodes: (Node,State)
798
- nsp, // number of shortest paths from a USP node to. Type: (Node,State,Int)
799
- neighbor, // enum of outbound USP edges. Type: (Node,State,Int,Node,State,#Label)
800
- usp_edge, // the USP fragment (edges). Type: (Node,State,Int,Node,State,#Label)
801
- interval, // interval of shortest path numbers for source nodes. Type: (Node,Int,Int)
802
- acc_nsp // enum of shortest paths numbers following the order of
803
- // outbound edges, for a given USP node. Type: (Node,State,Int)
804
-
805
- def paths(path_num, :node, i, v): _paths(i, v, _, path_num, _, _)
806
- def paths(path_num, :edge_label, i, lab):
807
- exists((h) |
808
- _paths(i, _, _, path_num, h, _) and label(h, lab)
809
- )
810
-
811
-
812
- // Paths are constructed using their enumeration in the USP fragment. The path is
813
- // routed with the help on the interval assigned to source nodes, and with the neighbor
814
- // enumeration and nsp values for subsequent nodes.
815
- def _paths(0, v, q, path_num, a, n):
816
- exists((c, d) |
817
- a = aux_hash and
818
- q = initial_state and
819
- interval(v, c, d) and
820
- range(c, d, 1, path_num) and
821
- n = path_num - c + 1
822
- )
823
- def _paths(i, v, q, path_num, a, n):
824
- exists((n2, u, p) |
825
- _paths(i - 1, u, p, path_num, _, n2) and
826
- route_path(u, p, n2, v, q, a, n)
827
- )
828
-
829
- // The path numbers assigned to the i-th outbound edge of u start at this number.
830
- def prev_acc_nsp[u, p, i]: acc_nsp[u, p, i - 1]
831
-
832
- // Route the n-th shortest path from (u,p) through (v,q) using an a-edge. Holds for
833
- // every path with relative number m among the paths routed through the edge.
834
- def route_path(u, p, m, v, q, a, n):
835
- exists((i) |
836
- range(prev_acc_nsp[u, p, i] + 1, acc_nsp[u, p, i], 1, m) and
837
- neighbor(u, p, i, v, q, a) and
838
- n = m - prev_acc_nsp[u, p, i]
839
- )
840
-
841
- end // with usp_cut[Conn, S, T, K]
842
- end // with balls[Conn, S, T]
843
-
844
- end // module _limit[{Conn}, {S}, {T}, {K}]
845
-
846
-
847
- end // namespace pathfinder::_internal::any
848
-
849
- // pathfinder/_internal/any/utilities (from model/_internal/any/utilities.rel)
850
- namespace pathfinder::_internal::any
851
-
852
- //
853
- // Inverts the edges of the product graph, allowing to reuse path construction code for
854
- // forward and backward fragments. An edge (p, q, a, u, v) is inverted to (q, p, a, v, u).
855
- //
856
- @inline
857
- def invert_pg({PG}, q, p, a, v, u): PG(p, q, a, u, v)
858
-
859
-
860
- end // namespace pathfinder::_internal::any
861
-
862
- // pathfinder/_internal/any/all (from model/_internal/any/all.rel)
863
- namespace pathfinder::_internal::any
864
-
865
-
866
- from ::pathfinder::utilities import canonical_index
867
- from ::pathfinder::_internal::utilities import reindex_nodes, aux_hash
868
-
869
- // _all indexes the nodes based on their distance from the source (starting from 0). We
870
- // reindex them to start from 1, as is common practice in Rel.
871
- @inline
872
- def all[{Conn}, {S}, {T}]: reindex_nodes[_all[Conn, S, T, :paths]]
873
-
874
- //
875
- // Computes all shortest paths from the set of source nodes S to the set of target nodes T
876
- // that follow the predicate Conn. The :any grouping selects only the shortest paths among
877
- // any paths satisfying Conn and connecting a source node to a target node. The paths are
878
- // computed by selecting a middle node and building the two path fragments from the source
879
- // to the middle node and from the middle node to the target.
880
- //
881
- @outline
882
- module _all[{Conn}, {S}, {T}]
883
-
884
- //
885
- // Combine source-to-middle and middle-to-target paths
886
- //
887
- with balls[Conn, S, T] use
888
- radius_S, // S-ball radius. Type: Int
889
- label // edge label. Type: (#Label,String)
890
-
891
- // Each shortest path is obtained by concatenating a fragment from a source node to a
892
- // middle node and a fragment from the middle node to the target.
893
- def paths(path_num, :node, i, v):
894
- exists((path_num_S, path_num_T) |
895
- path_num = canonical_index[path_num_S, path_num_T] and
896
- (
897
- paths_from_S(i, v, _, path_num_S, _, _)
898
- or
899
- paths_to_T(i, v, _, path_num_T, _, _)
900
- ) and
901
- connected_fragments(path_num_S, path_num_T)
902
- )
903
- def paths(path_num, :edge_label, i, lab):
904
- exists((path_num_S, path_num_T) |
905
- connected_fragments(path_num_S, path_num_T) and
906
- path_num = canonical_index[path_num_S, path_num_T] and
907
- (
908
-
909
- exists((h) |
910
- paths_from_S(i, _, _, path_num_S, h, _) and
911
- label(h, lab)
912
- )
913
- or
914
- exists((h) |
915
- paths_to_T(i - 1, _, _, path_num_T, h, _) and
916
- label(h, lab)
917
- )
918
- )
919
- )
920
-
921
- // A source path fragment is compatible with a target path fragment if they meet at a
922
- // middle node. Note: the computation of the balls terminates when a middle is reached.
923
- def connected_fragments(path_num_S, path_num_T):
924
- exists((u, p) |
925
- paths_from_S(radius_S, u, p, path_num_S, _, _) and
926
- paths_to_T(radius_S, u, p, path_num_T, _, _)
927
- )
928
-
929
- end // balls[Conn, S, T]
930
-
931
-
932
- //
933
- // Construct source-to-middle path fragments
934
- //
935
- with balls[Conn, S, T] use
936
- source, // source nodes. Type: (Node,State)
937
- middle, // middle nodes. Type :(Node,State)
938
- dist_from_S, // distance from S. Type: (Int,Node,State)
939
- radius_S, // S-ball radius. Type: Int
940
- pg, // product graph. Signature: (State,State,#Label,Node,Node)
941
- initial_state // initial state of the automaton. Type: Int
942
-
943
- with usp_segment[source, middle, dist_from_S, radius_S, pg] use
944
- neighbor, // enum of outbound USP edges. Type: (Node,State,Int,Node,State,#Label)
945
- nsp, // number of shortest paths from a USP node to. Type: (Node,State,Int)
946
- interval, // interval of shortest path numbers for source nodes. Type: (Node,Int,Int)
947
- acc_nsp // enum of shortest paths numbers following the order of
948
- // outbound edges, for a given USP node. Type: (Node,State,Int)
949
-
950
- // Paths are constructed using their enumeration in the USP fragment. The path is
951
- // routed with the help on the interval assigned to source nodes, and with the neighbor
952
- // enumeration and nsp values for subsequent nodes.
953
- def paths_from_S(i, v, q, path_num, a, n):
954
- exists((c, d) |
955
- i = 0 and
956
- a = aux_hash and
957
- q = initial_state and
958
- interval(v, q, c, d) and
959
- range(c, d, 1, path_num) and
960
- n = path_num - c + 1
961
- )
962
- def paths_from_S(i, v, q, path_num, a, n):
963
- exists((m, u, p) |
964
- paths_from_S(i - 1, u, p, path_num, _, m) and
965
- route_paths_from_S(u, p, m, v, q, a, n)
966
- )
967
-
968
- // The path numbers assigned to the i-th outbound edge of u start at this number.
969
- def prev_acc_nsp[u, p, i]: acc_nsp[u, p, i - 1]
970
-
971
- // Route the n-th shortest path from (u,p) through (v,q) using an a-edge. Holds for
972
- // every path with relative number m among the paths routed through the edge.
973
- def route_paths_from_S(u, p, m, v, q, a, n):
974
- exists((i) |
975
- range(prev_acc_nsp[u, p, i] + 1, acc_nsp[u, p, i], 1, m) and
976
- neighbor(u, p, i, v, q, a) and
977
- n = m - prev_acc_nsp[u, p, i]
978
- )
979
-
980
- end // compute_usp_segment[source, middle, dist_from_S, radius_S, pg]
981
- end // balls[Conn, S, T]
982
-
983
-
984
- //
985
- // Construct middle-to-target path fragments
986
- //
987
- with balls[Conn, S, T] use
988
- target, // target nodes. Type: (Node,State)
989
- middle, // middle nodes. Type: (Node,State)
990
- dist_to_T, // distance to T. Type: (Int,Node,State)
991
- radius_T, // T-ball radius: Int
992
- pg, // product graph. Type: (State,State,#Label,Node,Node)
993
- dist_S_to_T // length of shortest path from S to T: Int
994
-
995
- with usp_segment[target, middle, dist_to_T, radius_T, invert_pg[pg]] use
996
- neighbor, // enum of outbound USP edges. Type: (Node,State,Int,Node,State,#Label)
997
- nsp, // number of shortest paths from a USP node to. Type: (Node,State,Int)
998
- interval, // interval of shortest path numbers for source nodes. Type: (Node,Int,Int)
999
- acc_nsp // enum of shortest paths numbers following the order of
1000
- // outbound edges, for a given USP node. Type: (Node,State,Int)
1001
-
1002
- // Paths are constructed using their enumeration in the USP fragment. The path is
1003
- // routed with the help on the interval assigned to source nodes, and with the neighbor
1004
- // enumeration and nsp values for subsequent nodes.
1005
- def paths_to_T(i, v, q, path_num, a, n):
1006
- exists((c, d) |
1007
- i = dist_S_to_T and
1008
- a = aux_hash and
1009
- interval(v, q, c, d) and
1010
- range(c, d, 1, path_num) and
1011
- n = path_num - c + 1
1012
- )
1013
- def paths_to_T(i, v, q, path_num, a, n):
1014
- exists((m, u, p) |
1015
- paths_to_T(i + 1, u, p, path_num, _, m) and
1016
- route_paths_to_T(u, p, m, v, q, a, n)
1017
- )
1018
-
1019
- // The path numbers assigned to the i-th outbound edge of u start at this number.
1020
- def prev_acc_nsp_inv[u, p, i]: acc_nsp[u, p, i - 1]
1021
-
1022
- // Route the n-th shortest path from (u,p) through (v,q) using an a-edge. Holds for
1023
- // every path with relative number m among the paths routed through the edge.
1024
- def route_paths_to_T(u, p, m, v, q, a, n):
1025
- exists((i) |
1026
- range(prev_acc_nsp_inv[u, p, i] + 1, acc_nsp[u, p, i], 1, m) and
1027
- neighbor(u, p, i, v, q, a) and
1028
- n = m - prev_acc_nsp_inv[u, p, i]
1029
- )
1030
-
1031
- end // with usp_segment[target, middle, dist_to_T, radius_T, invert_pg[pg]]
1032
- end // with balls[Conn, S, T]
1033
-
1034
- end // module _all[{Conn}, {S}, {T}]
1035
-
1036
-
1037
- end // namespace pathfinder::_internal::any
1038
-
1039
- // pathfinder/_internal/for_each/utilities (from model/_internal/for_each/utilities.rel)
1040
- namespace pathfinder::_internal::for_each::utilities
1041
-
1042
- from ::pathfinder::utilities import canonical_index
1043
-
1044
- // `index_target(T, t, i)` holds if `t` is the `i`-th tuple of `T`
1045
- @function @outline
1046
- def index_target({T}, t, i): sort(T, i, t)
1047
-
1048
-
1049
- @function @outline
1050
- def index_target_pair({T}, t, j, val):
1051
- exists((i) | val = canonical_index[i, j] and i = index_target[T, t])
1052
-
1053
- end // namespace pathfinder::_internal::for_each::utilities
1054
-
1055
-
1056
- // pathfinder/_internal/for_each/balls (from model/_internal/for_each/balls.rel)
1057
- namespace pathfinder::_internal::for_each
1058
-
1059
- //
1060
- // compute_balls[{Conn}, {S}, {T}]
1061
- //
1062
- // Assuming that S consists of a single node and T = {t_1, ..., t_k}, constructs
1063
- // a subgraph of the product graph, defined by Conn, that consists of "balls" B,
1064
- // B_1, ..., B_k such that B grows from the source node S and B_i grows from
1065
- // target node t_i for each 1 <= i <= k. The balls are constructed by iteratively
1066
- // extending their radius until the intersection of B with each B_i is not empty.
1067
- // To avoid an explosion in the size of the balls, at each iteration, we extend only
1068
- // the source ball or the union of the target balls depending on which is smaller,
1069
- // considering the target nodes whose balls have not yet intersected with the source
1070
- // ball (referred to as "active" target nodes). There is a hardcoded limit on the
1071
- // number of iterations (up to 100).
1072
- //
1073
- // Input parameters:
1074
- // - Conn: a connectivity predicate that defines a path query in the knowledge graph
1075
- // - S: a source node
1076
- // - T: a set of target nodes
1077
- //
1078
- // Output (most relevant):
1079
- // - pg: the product graph defined by the finite automaton A extracted from the
1080
- // predicate Conn. A node of the product graph is a pair (u, p) that represents
1081
- // A reaching node u of the KG in a state p. The edge between two nodes of
1082
- // the product graph is labeled with the (hash value) of the edge label in the KG
1083
- // that connects the two nodes and has a corresponding transition in A. An
1084
- // a-labeled edge from (u, p) to (v, q) is represented as pg(p, q, a, u, v).
1085
- // Type: pg(State,State,#Label,Node,Node)
1086
- // - label: a function that maps hashed edge labels to their string representation.
1087
- // Using hashes improves performance drastically. Type: label(#Label,String)
1088
- // - middle: the set of the nodes of the product graph where the two balls meet. In
1089
- // particular, middle(t, i, u, p) holds if (u, p) is a node in the intersection
1090
- // of the ball centered at S with the ball centered at t for the target node t.
1091
- // Besides, i is the distance in the product graph between the source node and
1092
- // (u, p). Type: middle(Node,Int,Node,State)
1093
- // - dist_from_S: a relation that holds the distance from the source node
1094
- // to a product node (v, q). Type: dist_from_S(Int,Node,State).
1095
- // - dist_to_T: a relation that holds the distance of a product node (v, q) to a
1096
- // target node t, for each t in T, which is denoted as
1097
- // dist_from_S(d, t, v, q). Type: dist_to_T(Int,Node,Node,State).
1098
- // - radius_S: the radius of the ball centered at S. Type: Int.
1099
- // - radius_T: the maximum among the radii of the balls centered at t, for each
1100
- // target node t. Tyoe: Int.
1101
- // - dist_S_to_T: the length of the shortest path between the source node and a target
1102
- // node t, for each t in T. Type: dist_S_to_T(Node,Int).
1103
- //
1104
- @outline
1105
- module balls[{Conn}, {S}, {T}]
1106
-
1107
- // The source ball starts from the source node in the initial state 1 of the automaton
1108
- @inline
1109
- def initial_state { 1 }
1110
-
1111
- @inline
1112
- def source(u, p): S(u) and p = initial_state
1113
-
1114
-
1115
- // The target ball starts from the target nodes in the final states of the automaton
1116
- // which are provided by the FFI rel_primitive_product_graph_targets
1117
- @pipeline
1118
- def pg_target { rel_primitive_product_graph_targets[Conn] }
1119
-
1120
- @force_dnf
1121
- def target(u, p): T(u) and pg_target(p, u)
1122
-
1123
-
1124
- // The FFI rel_primitive_product_graph compiles the definition of the product graph
1125
- @inline
1126
- def pg { rel_primitive_product_graph[Conn] }
1127
-
1128
-
1129
- // This FFI provides the mapping from hash values to the string labels
1130
- // (hash values of string labels are used for efficiency reasons)
1131
- @function
1132
- def label { rel_primitive_product_graph_labels[Conn] }
1133
-
1134
-
1135
- // Hard-coded bound on the maximum number of iterations to avoid runaway computations
1136
- @inline
1137
- def max_number_iterations { 100 }
1138
-
1139
-
1140
- // Iterative process of constructing the balls. Uses recursion with non-stratified
1141
- // negation to simulate while loop iteration: finish_iteration() and finish_loop()
1142
- // control the execution of the loop.
1143
- @function
1144
- def radius_S {
1145
- minimum[
1146
- max[0;
1147
- radius_S;
1148
- (radius_S + 1, // increment the radius only if
1149
- delta_S_size < delta_T_size and // the ball size increase is smaller
1150
- finish_iteration() and
1151
- not finish_loop()
1152
- )
1153
- ],
1154
- max_number_iterations
1155
- ]
1156
- }
1157
-
1158
-
1159
- @function
1160
- def radius_T {
1161
- minimum[
1162
- max[0;
1163
- radius_T;
1164
- (radius_T + 1, // increment the radius only if
1165
- delta_S_size >= delta_T_size and // the ball size increase is smaller
1166
- finish_iteration() and
1167
- not finish_loop()
1168
- )
1169
- ],
1170
- max_number_iterations
1171
- ]
1172
- }
1173
-
1174
-
1175
- // delta_S_size is the size of the layer at distance radius_S from the pair
1176
- // (s, 1), where s is the source node
1177
- @inline
1178
- def delta_S_size { count[dist_from_S[radius_S]] }
1179
-
1180
-
1181
- // delta_T_size is the size of the level set at distance radius_T from
1182
- // {(v, q) | v is a target node, q is a final state and v is active}
1183
- @inline
1184
- def delta_T_size { count[dist_to_T[radius_T]] }
1185
-
1186
-
1187
- // active(d, t) holds if t is a target node and there is no final state
1188
- // q such that the intersection of the ball of radius d from (v, q)
1189
- // with the ball of radius radius_S from (s, 1) is not empty, where s is
1190
- // the source node.
1191
- def active(d, t): T(t) and d = 0
1192
- def active(d, t):
1193
- d = radius_T and
1194
- active(radius_T - 1, t) and
1195
- not exists((u, p) |
1196
- dist_from_S(_, u, p) and dist_to_T(_, t, u, p)) and
1197
- forall((u) |
1198
- active(radius_T, t) implies dist_to_T(radius_T, u, _, _))
1199
- def active(d, t): active(d, t)
1200
-
1201
-
1202
- // prev_dist_from_S(v, q) holds if the distance from the source node
1203
- // to (v, q) is at most radius_S
1204
- @force_dnf
1205
- def prev_dist_from_S(v, q):
1206
- exists((u, p) |
1207
- dist_from_S(radius_S - 1, u, p) and pg(p, q, _, u, v)
1208
- )
1209
-
1210
-
1211
- // dist_from_S(d, v, q) holds if the distance between the source node
1212
- // and (v, q) is d.
1213
- @force_dnf
1214
- def dist_from_S(d, v, q): source(v, q) and d = 0
1215
- def dist_from_S(d, v, q):
1216
- d = radius_S and
1217
- prev_dist_from_S(v, q) and
1218
- not exists((j) |
1219
- range(0, radius_S - 1, 1, j) and dist_from_S(j, v, q))
1220
- def dist_from_S(d, v, q): dist_from_S(d, v, q)
1221
-
1222
-
1223
- // prev_dist_to_T(t, u, p) holds if the distance from (u, p)
1224
- // to the target node t is at most radius_T
1225
- @force_dnf
1226
- def prev_dist_to_T(t, u, p):
1227
- exists((v, q) |
1228
- dist_to_T(radius_T - 1, t, v, q) and pg(p, q, _, u, v)
1229
- )
1230
-
1231
-
1232
- // Given a target node t, dist_to_T(d, t, u, p) holds if the distance
1233
- // between (u, p) and { (t, q) | q is a final state } is d.
1234
- // Notice that the distance from a node n to a set of nodes N is
1235
- // defined as the minimum of the distances from n to each node in N
1236
- @force_dnf
1237
- def dist_to_T(d, t, u, p): target(u, p) and d = 0 and t = u
1238
- def dist_to_T(d, t, u, p):
1239
- d = radius_T and
1240
- active(radius_T, t) and
1241
- prev_dist_to_T(t, u, p) and
1242
- not exists((j) |
1243
- range(0, radius_T - 1, 1, j) and dist_to_T(j, t, u, p))
1244
- def dist_to_T(d, t, u, p): dist_to_T(d, t, u, p)
1245
-
1246
-
1247
- // Iteration is complete if the extensions of source and target balls
1248
- // have been completed
1249
- def finish_iteration():
1250
- delta_S_size > 0 and
1251
- delta_T_size > 0 and
1252
- active(radius_T, _) and
1253
- forall((t) |
1254
- active(radius_T, t) implies dist_to_T(radius_T, t, _, _))
1255
-
1256
-
1257
- // Loop is terminated when the interesection of the source ball with each ball
1258
- // centered at t is not empty, where t is a target node.
1259
- def finish_loop():
1260
- forall((t) |
1261
- T(t) implies exists((u, p) | dist_from_S(_, u, p) and dist_to_T(_, t, u, p))
1262
- )
1263
-
1264
-
1265
- // This auxilary predicate is used for efficiency purposes
1266
- def pre_middle(t, i, j, u, p): dist_from_S(i, u, p) and dist_to_T(j, t, u, p)
1267
-
1268
-
1269
- // Given a target node t, dist_S_to_T[t] is the distance from
1270
- // the source node to t
1271
- @function
1272
- def dist_S_to_T[t]:
1273
- min[(k) : exists((i,j) | pre_middle(t, i, j, _, _) and k = i + j)]
1274
-
1275
-
1276
- // For each target node t, compute the set of pairs (u, p) in the
1277
- // intersection of the ball centered at (s, 1) with the ball centered at
1278
- // { (t, q) | q is a final state }, where s is the source node.
1279
- // Besides, for each such pair (u, p), it stores the distance i
1280
- // from (s, 1) to (u, p).
1281
- def middle(t, i, u, p):
1282
- exists((j) | pre_middle(t, i, j, u, p) and i + j = dist_S_to_T[t])
1283
-
1284
-
1285
- def max_dist_to_T[t, u, p]: max[(j) : pre_middle(t, _, j, u, p)]
1286
-
1287
- end // module balls[{Conn}, {S}, {T}]
1288
-
1289
-
1290
- end // namespace pathfinder::for_each
1291
-
1292
- // pathfinder/_internal/for_each/all (from model/_internal/for_each/all.rel)
1293
- namespace pathfinder::_internal::for_each
1294
-
1295
- from ::pathfinder::utilities import prefix_sum
1296
- from ::pathfinder::_internal::utilities import reindex_nodes, aux_hash
1297
-
1298
-
1299
- // _all indexes the nodes based on their distance from the source (starting from 0).
1300
- // We reindex them to start from 1, as is common practice in Rel.
1301
- @inline
1302
- def all[{Conn}, {S}, {T}]: { reindex_nodes[_all[Conn, S, T, :paths]] }
1303
-
1304
-
1305
- //
1306
- // Given a connectivity predicate Conn, a source node S and a set of
1307
- // target nodes T, all[{Conn}, {S}, {T}] computes all shortest paths from S
1308
- // to t that conform to Conn, for each target node t in T.
1309
- //
1310
- @outline
1311
- module _all[{Conn}, {S}, {T}]
1312
-
1313
- with balls[Conn, S, T] use
1314
- pg, // product graph. Type: (State,State,#Label,Node,Node)
1315
- middle, // middle nodes in the paths from S to each target node in pg
1316
- // Type: (Node,Int,Node,State)
1317
- dist_from_S, // distance from S. Type: (Int,Node,State)
1318
- radius_S, // radius of the ball centered at S. Type: Int
1319
- dist_to_T, // distance to each node in T. Type: (Int,Node,Node,State)
1320
- max_dist_to_T, // maximum of the distances in dist_to_T. Type: Int
1321
- dist_S_to_T, // length of shortest path from S to each node in T.
1322
- // Type: (Node,Int)
1323
- source, // source node in pg. Type: (Node,State)
1324
- label // edge label. Type: (#Label,String)
1325
-
1326
-
1327
- // Listing all shortest paths from the source node to the target nodes.
1328
- def paths(path_num, :node, i, v): _paths(i, v, _, path_num, _, _)
1329
- def paths(path_num, :edge_label, i, lab):
1330
- exists((h) | _paths(i, _, _, path_num, h, _) and label(h, lab))
1331
-
1332
- // _paths(i, v, q, path_num, a, n) holds if (v, q) is the node of
1333
- // the product graph at position i of a path, a is the label of the
1334
- // i-th edge of the path, and n is the path-number of the sub-problem of
1335
- // generating a path from (v, q) to the target nodes in the product graph.
1336
- def _paths(i, v, q, path_num, a, n):
1337
- i = 0 and
1338
- source(v, q) and
1339
- a = aux_hash and
1340
- range(1, nsp[0, v, q], 1, path_num) and
1341
- n = path_num
1342
- def _paths(i, v, q, path_num, a, n):
1343
- exists((m, u, p) |
1344
- _paths(i - 1, u, p, path_num, _, m) and
1345
- route_path(i - 1, u, p, m, v, q, a, n) and
1346
- not relevant_target(i - 1, u, p)
1347
- )
1348
- def _paths(i, v, q, path_num, a, n):
1349
- exists((m, u, p) |
1350
- _paths(i - 1, u, p, path_num, _, m) and
1351
- route_path(i - 1, u, p, m - 1, v, q, a, n) and
1352
- relevant_target(i - 1, u, p) and
1353
- m > 1
1354
- )
1355
-
1356
-
1357
- // The union of shortest paths (USP) is a subgraph of the product graph that
1358
- // is defined as the union of the shortest paths from the source node to each
1359
- // target node. Recall that a node in a product graph is represented as a pair of
1360
- // values (u, p) where u is a node in the source graph and p is a state of
1361
- // the finite automaton representing the connectivity path query. USP is decorated
1362
- // with additional information that compactly represents the enumeration of the
1363
- // shortest paths that allows an efficient path retrieval.
1364
-
1365
- // The USP structure is obtained by constructing the paths from the source node to
1366
- // the middle nodes and from the middle nodes to the target nodes.
1367
- // usp_edge(i, u, p, v, q, a) holds if there is an edge in the product graph from
1368
- // (u, p) to (v, q) with label a, and the distance from the source node in the
1369
- // product graph to (u, p) is i.
1370
- def usp_edge(i, u, p, v, q, a): usp_edge_back(i, u, p, v, q, a)
1371
- def usp_edge(i, u, p, v, q, a): usp_edge_fwd(_, i, u, p, v, q, a)
1372
-
1373
-
1374
- // Build the paths backward from the middle to the source node. We materialize the
1375
- // USP fragment "on the fly", using recursion where we control the order of
1376
- // evaluation by introducing additional intermediate predicates.
1377
- @force_dnf
1378
- def start_usp_edge_back(i, u, p, v, q, a):
1379
- dist_from_S(i, u, p) and pg(p, q, a, u, v) and i <= radius_S - 1
1380
-
1381
- @force_dnf
1382
- def step_usp_edge_back(i, u, p, v, q, a):
1383
- usp_edge_back(i + 1, v, q, _, _, _) and pg(p, q, a, u, v)
1384
-
1385
- // usp_edge_back(i, u, p, v, q, a) holds if there is an edge in
1386
- // the product graph from (u, p) to (v, q) with label a, and the
1387
- // distance from the source node in the product graph to (u, p) is i.
1388
- def usp_edge_back(i, u, p, v, q, a):
1389
- start_usp_edge_back(i, u, p, v, q, a) and middle(_, i + 1, v, q)
1390
- def usp_edge_back(i, u, p, v, q, a):
1391
- step_usp_edge_back(i, u, p, v, q, a) and dist_from_S(i, u, p)
1392
-
1393
-
1394
- // Build the paths forward from the middle to the target nodes. We materialize the
1395
- // USP structure "on the fly", using recursion where we control the order of
1396
- // evaluation by introducing additional intermediate predicates.
1397
- @force_dnf
1398
- def start_usp_edge_fwd(t, j, u, p, v, q, a):
1399
- dist_to_T(j, t, v, q) and
1400
- pg(p, q, a, u, v) and
1401
- j = max_dist_to_T[t, u, p] - 1
1402
-
1403
- @force_dnf
1404
- def step_usp_edge_fwd(t, i, u, p, v, q, a):
1405
- usp_edge_fwd(t, i - 1, _, _, u, p, _) and pg(p, q, a, u, v)
1406
-
1407
- // Assuming that t is a target node and d is the distance from the
1408
- // source node to { (t, r) | r is a final state } in the product graph,
1409
- // usp_edge_fwd(t, i, u, p, v, q, a) holds if there is an edge in the
1410
- // product graph from (u, p) to (v, q) with label a, and the distance
1411
- // from (v, q) to { (t, r) | r is a final state } is d - (i + 1).
1412
- // Recall that the distance between a node n and a set of nodes N is
1413
- // defined as the minimum of the distances between n and each node in N.
1414
- def usp_edge_fwd(t, i, u, p, v, q, a):
1415
- exists((j) |
1416
- middle(t, i, u, p) and
1417
- start_usp_edge_fwd(t, j, u, p, v, q, a) and
1418
- i + j + 1 = dist_S_to_T[t]
1419
- )
1420
- def usp_edge_fwd(t, i, u, p, v, q, a):
1421
- exists((j) |
1422
- step_usp_edge_fwd(t, i, u, p, v, q, a) and
1423
- dist_to_T(j, t, v, q) and
1424
- i + j + 1 = dist_S_to_T[t]
1425
- )
1426
-
1427
-
1428
- // Order the outbound edges of every USP node
1429
- def neighbor(i, u, p, j, v, q, a):
1430
- sort({(w, r, b) : usp_edge(i, u, p, w, r, b)}, j, v, q, a)
1431
-
1432
-
1433
- // Filter out the target nodes for which there is no path from the source node.
1434
- def relevant_target(i, u, p):
1435
- source(u, p) and dist_S_to_T[u] = 0 and i = 0
1436
- def relevant_target(i, u, p):
1437
- T(u) and i = dist_S_to_T[u] and usp_edge(i - 1, _, _, u, p, _)
1438
-
1439
-
1440
- // Compute the number of shortest paths from the given USP node to the
1441
- // target nodes
1442
- def nsp[i, u, p]:
1443
- (
1444
- sum[(v, q, a, w) : w = nsp[i + 1, v, q] and usp_edge(i, u, p, v, q, a)],
1445
- not relevant_target(i, u, p)
1446
- )
1447
- def nsp[i, u, p]:
1448
- (
1449
- (sum[(v, q, a, w) :
1450
- w = nsp[i + 1, v, q] and usp_edge(i, u, p, v, q, a)
1451
- ]<++0) + 1,
1452
- relevant_target(i, u, p)
1453
- )
1454
-
1455
-
1456
- // Given a number j, acc_nsp computes the accumulated number of shortest
1457
- // paths from a node in the USP structure to the target nodes that pass
1458
- // through the first j successors of the node.
1459
- def acc_nsp[i, u, p, 0]: (0, usp_edge(i, u, p, _, _, _))
1460
- def acc_nsp[i, u, p]: prefix_sum[_acc_nsp[i, u, p]]
1461
-
1462
- // Auxiliary predicate used for efficiency reasons.
1463
- def _acc_nsp(i, u, p, j, val):
1464
- exists((v, q) |
1465
- val = nsp[i + 1, v, q] and neighbor(i, u, p, j, v, q, _)
1466
- )
1467
-
1468
-
1469
- // Routes the m-th shortest path from (u, p) through (v, q) using an
1470
- // a-edge, and computes the number n for rerouting from (v, q)
1471
- // in the construction of the shortest path.
1472
- def route_path(i, u, p, m, v, q, a, n):
1473
- exists((j) |
1474
- range(
1475
- _route_path[i, u, p, j] + 1,
1476
- acc_nsp[i, u, p, j],
1477
- 1,
1478
- m
1479
- ) and
1480
- neighbor(i, u, p, j, v, q, a) and
1481
- n = m - _route_path[i, u, p, j]
1482
- )
1483
-
1484
- // Auxiliary predicate used for efficiency reasons.
1485
- def _route_path[i, u, p, j]: acc_nsp[i, u, p, j - 1]
1486
-
1487
- end // with balls[Conn, S, T] use
1488
-
1489
- end // module _all[{Conn}, {S}, {T}]
1490
-
1491
-
1492
- end // pathfinder::_internal::for_each
1493
-
1494
- // pathfinder/_internal/for_each/single (from model/_internal/for_each/single.rel)
1495
- namespace pathfinder::_internal::for_each
1496
-
1497
- from ::pathfinder::_internal::utilities import some, aux_hash, reindex_nodes
1498
- from ::pathfinder::_internal::for_each::utilities import index_target
1499
-
1500
-
1501
- //
1502
- // Given a connectivity predicate Conn, a source node S and a set of
1503
- // target nodes T, single[{Conn}, {S}, {T}] computes a single shortest path
1504
- // from S to t for each target node t in T.
1505
- // _single indexes the nodes based on their distance from the source (starting from 0).
1506
- // We reindex them to start from 1, as is common practice in Rel.
1507
- //
1508
- @inline
1509
- def single[{Conn}, {S}, {T}]: { reindex_nodes[_single[Conn, S, T, :paths]] }
1510
-
1511
-
1512
- @outline
1513
- module _single[{Conn}, {S}, {T}]
1514
-
1515
- with balls[Conn, S, T] use
1516
- pg, // product graph. Type: (State,State,#Label,Node,Node)
1517
- dist_from_S, // distance from S. Type: (Int,Node,State)
1518
- dist_to_T, // distance to each node in T. Type: (Int,Node,Node,State)
1519
- dist_S_to_T, // length of shortest path from S to each node in T.
1520
- // Type: (Node,Int)
1521
- middle, // middle nodes in the paths from S to each target node in pg
1522
- // Type: (Node,Int,Node,State)
1523
- label // edge label. Type: (#Label,String)
1524
-
1525
- // For each target node t, paths combines the results of
1526
- // path_from_S and path_to_T into a single path from the source
1527
- // node to t
1528
- def paths(path_num, :node, i, v):
1529
- exists((t) |
1530
- (path_from_S(t, i, v, _, _) or path_to_T(t, i, v, _, _)) and
1531
- index_target(T, t, path_num)
1532
- )
1533
- def paths(path_num, :edge_label, i, lab):
1534
- exists((t, h) |
1535
- (path_from_S(t, i - 1, _, _, h) or path_to_T(t, i, _, _, h)) and
1536
- label(h, lab) and
1537
- index_target(T, t, path_num)
1538
- )
1539
-
1540
-
1541
- // For each target node t, choose a pair (u, p) in the intersection
1542
- // of the ball centered at (s, 1) and the ball centered at
1543
- // { (t, q) | q is a final state }, where s is the source node.
1544
- @function
1545
- def candidate(t, i, u, p):
1546
- { some[(j, v, q): middle(t, j, v, q)] }(i, u, p)
1547
-
1548
-
1549
- // For each target node t, path_from_S constructs a path from
1550
- // (s, 1) to a pair (v, q) in the middle, where where s is the
1551
- // source node.
1552
- def path_from_S(t, i, u, p, a): candidate(t, i, u, p) and a = aux_hash
1553
- @force_dnf
1554
- def path_from_S[t, i]:
1555
- some[(u, p, a) :
1556
- exists((q, v) |
1557
- dist_from_S(i, u, p) and
1558
- pg(p, q, a, u, v) and
1559
- path_from_S(t, i + 1, v, q, _)
1560
- )
1561
- ]
1562
-
1563
-
1564
- // For each target node t, path_to_T constructs a path from
1565
- // a pair (u, p) in the middle to (t, q), where q is a final
1566
- // state
1567
- def path_to_T(t, i, u, p, a): candidate(t, i, u, p) and a = aux_hash
1568
- @force_dnf
1569
- def path_to_T[t, i]:
1570
- some[(v, q, a) :
1571
- dist_to_T(dist_S_to_T[t] - i, t, v, q) and
1572
- exists((p, u) |
1573
- pg(p, q, a, u, v) and path_to_T(t, i - 1, u, p, _))
1574
- ]
1575
-
1576
- end // with balls[Conn, S, T]
1577
-
1578
- end // module _single[{Conn}, {S}, {T}]
1579
-
1580
-
1581
- end // pathfinder::_internal::for_each
1582
-
1583
- // pathfinder/_internal/for_each/usp (from model/_internal/for_each/usp.rel)
1584
- namespace pathfinder::_internal::for_each
1585
-
1586
- from ::pathfinder::utilities import prefix_sum
1587
-
1588
- // The union of shortest paths (USP) is a subgraph of the product graph that
1589
- // is defined as the union of the shortest paths from the source node to each
1590
- // target node. Recall that a node in a product graph is represented as a pair of
1591
- // values (u, p) where u is a node in the source graph and p is a state of
1592
- // the finite automaton representing the connectivity path query. USP is decorated
1593
- // with additional information that compactly represents the enumeration of the
1594
- // shortest paths that allows an efficient path retrieval.
1595
-
1596
-
1597
- //
1598
- // Constructs a fragment of the USP containing the shortest paths from the source node
1599
- // to each target node going through K middle points, used for limit-k queries.
1600
- // The USP fragment is obtained by identifying the paths traversing K selected
1601
- // middle nodes (obtained from the balls construction).
1602
- //
1603
- // Input:
1604
- // - Conn: a connectivity predicate that defines a path query in the knowledge graph
1605
- // - S: a source node
1606
- // - T: a set of target nodes
1607
- // - K: the number of middle nodes (single int)
1608
- //
1609
- // Output (that we use):
1610
- // - limit_middle: the set of (up to) K middle nodes for each target node t.
1611
- // Type: (Node,Node,State)
1612
- // - usp_edge: the edges of the USP fragment. Note that it has different signature
1613
- // than product graph. Type: (Node,Node,State,Node,State,#Label)
1614
- // [Compact representation of the enumeration of the shortest paths]
1615
- // - neighbor: enumeration of the outbound edges of USP node.
1616
- // Type: (Node,Node,State,Int,Node,State,#Label)
1617
- // - nsp: the number of shortest paths from a USP node to target nodes. Type:
1618
- // (Node,Node,State,Int)
1619
- // - relevant_target: target nodes in pg that are reachable from the source node.
1620
- // Type: (Node,State)
1621
- // - acc_nsp: the enumeration of the shortest paths going through the outbound edges
1622
- // of a USP node. Type: (Node,Node,State,Int)
1623
- // - total: the total number of shortest paths from the source node to each target
1624
- // node (may be smaller than K, or even 0, for some target nodes).
1625
- // Type: (Node,Int)
1626
- //
1627
- @outline
1628
- module usp_cut[{Conn}, {S}, {T}, {K}]
1629
-
1630
- with balls[Conn, S, T] use
1631
- middle, // middle nodes in the paths from S to each target node in pg.
1632
- // Type: (Node,Int,Node,State)
1633
- pg, // product graph. Type: (State,State,#Label,Node,Node)
1634
- dist_from_S, // distance from S. Type: (Int,Node,State)
1635
- dist_to_T, // distance to each node in T. Type: (Int,Node,Node,State)
1636
- dist_S_to_T, // length of shortest path from S to each node in T.
1637
- // Type: (Node,Int)
1638
- radius_S, // radius of the ball centered at S. Type: Int
1639
- source, // source node in pg. Type: (Node,State)
1640
- target // target nodes in pg. Type: (Node,State)
1641
-
1642
-
1643
- // Pick K middle nodes for each target node t
1644
- def limit_middle[t]: top[K, {(j, v, q) : middle(t, j, v, q)}, range[1, K, 1]]
1645
-
1646
-
1647
- // The USP structure is obtained by constructing the paths from the source node to
1648
- // the middle nodes and from the middle nodes to the target nodes.
1649
- // usp_edge(t, u, p, v, q, a) holds if there exists a path from the source node
1650
- // to the target node t that contains an edge from (u, p) to (v, q)
1651
- // with label a
1652
- def usp_edge(t, u, p, v, q, a): usp_edge_back(t, _, u, p, v, q, a)
1653
- def usp_edge(t, u, p, v, q, a): usp_edge_fwd(t, _, u, p, v, q, a)
1654
-
1655
-
1656
- // Build the paths backward from the middle to the source node. We materialize the
1657
- // USP fragment "on the fly", using recursion where we control the order of
1658
- // evaluation by introducing additional intermediate predicates.
1659
- @force_dnf
1660
- def start_usp_edge_back(t, i, u, p, v, q, a):
1661
- pg(p, q, a, u, v) and
1662
- limit_middle(t, i + 1, v, q)
1663
-
1664
- @force_dnf
1665
- def step_usp_edge_back(t, i, u, p, v, q, a):
1666
- usp_edge_back(t, i + 1, v, q, _, _, _) and
1667
- pg(p, q, a, u, v)
1668
-
1669
- // usp_edge_back(t, i, u, p, v, q, a) holds if there exists a path
1670
- // from the source node to the target node t that contains an edge from
1671
- // (u, p) to (v, q) with label a, and the distance from the source
1672
- // node to (u, p) in this path is i.
1673
- def usp_edge_back(t, i, u, p, v, q, a):
1674
- start_usp_edge_back(t, i, u, p, v, q, a) and
1675
- dist_from_S(i, u, p) and
1676
- i <= radius_S - 1
1677
- def usp_edge_back(t, i, u, p, v, q, a):
1678
- step_usp_edge_back(t, i, u, p, v, q, a) and
1679
- dist_from_S(i, u, p)
1680
-
1681
-
1682
- // Build the paths forward from the middle to the target nodes. We materialize the
1683
- // USP structure "on the fly", using recursion where we control the order of
1684
- // evaluation by introducing additional intermediate predicates.
1685
- @force_dnf
1686
- def start_usp_edge_fwd(t, i, u, p, v, q, a):
1687
- limit_middle(t, i, u, p) and
1688
- pg(p, q, a, u, v)
1689
-
1690
- @force_dnf
1691
- def step_usp_edge_fwd(t, i, u, p, v, q, a):
1692
- usp_edge_fwd(t, i - 1, _, _, u, p, _) and
1693
- pg(p, q, a, u, v)
1694
-
1695
- // usp_edge_fwd(t, i, u, p, v, q, a) holds if there exists a path
1696
- // from the source node to the target node t that contains an edge from
1697
- // (u, p) to (v, q) with label a, and the distance from the source
1698
- // node to (u, p) in this path is i.
1699
- def usp_edge_fwd(t, i, u, p, v, q, a):
1700
- exists((j) |
1701
- start_usp_edge_fwd(t, i, u, p, v, q, a) and
1702
- dist_to_T(j, t, v, q) and
1703
- i + j + 1 = dist_S_to_T[t]
1704
- )
1705
- def usp_edge_fwd(t, i, u, p, v, q, a):
1706
- exists((j) |
1707
- step_usp_edge_fwd(t, i, u, p, v, q, a) and
1708
- dist_to_T(j, t, v, q) and
1709
- i + j + 1 = dist_S_to_T[t]
1710
- )
1711
-
1712
-
1713
- // Order the outbound edges of every USP node, considering for each
1714
- // target node t the fragment of the USP structure consisting of the
1715
- // shortest paths form the source node to t.
1716
- def neighbor(t, u, p, j, v, q, a):
1717
- sort({(w, r, b) : usp_edge(t, u, p, w, r, b)}, j, v, q, a)
1718
-
1719
-
1720
- // Filter out the target nodes for which there is no path from the source node.
1721
- def relevant_target(u, p): source(u, p) and dist_S_to_T[u] = 0
1722
- def relevant_target(u, p): target(u, p) and usp_edge(u, _, _, u, p, _)
1723
-
1724
-
1725
- // Compute the number of shortest paths from a given USP node to the
1726
- // target node t, considering the fragment of the USP structure
1727
- // consisting of the shortest paths form the source node to t.
1728
- def nsp[t, u, p]: (1, relevant_target(u, p) and t = u)
1729
- def nsp[t, u, p]:
1730
- sum[(v, q, a, w) : w = nsp[t, v, q] and usp_edge(t, u, p, v, q, a)]
1731
-
1732
-
1733
- // Given a number j, acc_nsp computes the accumulated number of shortest
1734
- // paths from a node in the USP structure to the target node t that pass
1735
- // through the first j successors of the node, considering the fragment
1736
- // of the USP structure consisting of the shortest paths form the source
1737
- // node to t.
1738
- def acc_nsp[t, u, p, 0]: (0, usp_edge(t, u, p, _, _, _))
1739
- def acc_nsp[t, u, p]: prefix_sum[_acc_nsp[t, u, p]]
1740
-
1741
- // Auxiliary predicate used for efficiency reasons.
1742
- def _acc_nsp(t, u, p, j, val):
1743
- exists((v, q) |
1744
- val = nsp[t, v, q] and neighbor(t, u, p, j, v, q, _)
1745
- )
1746
-
1747
-
1748
- // The maximum index of a successor of a node in the USP structure,
1749
- // considering the fragment of the USP structure consisting of the
1750
- // shortest paths form the source node to t.
1751
- @inline
1752
- def max_pos[t, u, p]: max[(i) : neighbor(t, u, p, i, _, _, _)]
1753
-
1754
-
1755
- // The total number of shortest paths from the source node to
1756
- // each target node (that go through the K middle nodes)
1757
- def total(t, val):
1758
- exists((u, p) |
1759
- val = (nsp[t, u, p]<++0) and T(t) and source(u, p)
1760
- )
1761
-
1762
- end // balls[Conn, S, T] use
1763
-
1764
- end // module usp_cut[{Conn}, {S}, {T}, {K}]
1765
-
1766
-
1767
- end // namespace pathfinder::_internal::for_each
1768
-
1769
- // pathfinder/_internal/for_each/limit (from model/_internal/for_each/limit.rel)
1770
- namespace pathfinder::_internal::for_each
1771
-
1772
- from ::pathfinder::utilities import canonical_index
1773
- from ::pathfinder::_internal::utilities import reindex_nodes, aux_hash
1774
- from ::pathfinder::_internal::for_each::utilities import index_target
1775
-
1776
-
1777
- // _limit indexes the nodes based on their distance from the source (starting from 0). We
1778
- // reindex them to start from 1, as is common practice in Rel.
1779
- @inline
1780
- def limit[{Conn}, {S}, {T}, {K}]: { reindex_nodes[_limit[Conn, S, T, K, :paths]] }
1781
-
1782
-
1783
- //
1784
- // Given a connectivity predicate Conn, a source node S, a set of
1785
- // target nodes T, and a natural number K, all[{Conn}, {S}, {T}, {K}] computes
1786
- // K shortest paths from S to t that conform to Conn, for each target node t in T.
1787
- //
1788
- @outline
1789
- module _limit[{Conn}, {S}, {T}, {K}]
1790
-
1791
- with balls[Conn, S, T] use
1792
- source, // source node in pg. Type: (Node,State)
1793
- target, // target nodes in pg. Type: (Node,State)
1794
- dist_S_to_T, // length of shortest path from S to each node in T.
1795
- // Type: (Node,Int)
1796
- label // edge label. Type: (#Label,String)
1797
-
1798
- with usp_cut[Conn, S, T, K] use
1799
- acc_nsp, // enum of shortest paths numbers following the order of
1800
- // outbound edges, for a given USP node.
1801
- // Type: (Node,Node,State,Int)
1802
- neighbor, // enum of outbound USP edges.
1803
- // Type: (Node,Node,State,Int,Node,State,#Label)
1804
- usp_edge, // edges the USP fragment.
1805
- // Type: (Node,Node,State,Node,State,#Label)
1806
- nsp, // number of shortest paths from a USP node.
1807
- // Type: (Node,Node,State,Int)
1808
- relevant_target // target nodes reachable from the source.
1809
- // Type: (Node,State)
1810
-
1811
-
1812
- // For each target node t, construct K shortest paths from the
1813
- // source node to t
1814
- def paths(path_num, :node, i, v):
1815
- exists((t, pn) |
1816
- _paths(i, t, v, _, pn, _, _) and
1817
- path_num = canonical_index[index_target[T, t], pn]
1818
- )
1819
- def paths(path_num, :edge_label, i, lab):
1820
- exists((t, pn, h) |
1821
- _paths(i, t, _, _, pn, h, _) and
1822
- label(h, lab) and
1823
- path_num = canonical_index[index_target[T, t], pn]
1824
- )
1825
-
1826
- // _paths(i, t, v, q, path_num, a, n) holds if (v, q) is the node
1827
- // of the product graph at position i of a shortest path from the source
1828
- // node to the target node t, a is the label of the i-th edge of
1829
- // the path, and n is the path-number of the sub-problem of generating
1830
- // a path from (v, q) to the target nodes in the product graph.
1831
- def _paths(i, t, v, q, path_num, a, n):
1832
- relevant_target(t, _) and
1833
- source(v, q) and
1834
- a = aux_hash and
1835
- i = 0 and
1836
- range(1, minimum[nsp[t, v, q], K], 1, path_num) and
1837
- n = path_num
1838
- def _paths(i, t, v, q, path_num, a, n):
1839
- exists((m, u, p) |
1840
- _paths(i - 1, t, u, p, path_num, _, m) and
1841
- route_path(t, u, p, m, v, q, a, n)
1842
- )
1843
-
1844
-
1845
- // Considering the shortest paths from the source node to the target
1846
- // node t, routes the m-th shortest path from (u, p) through
1847
- // (v, q) using an a-edge, and computes the number n for
1848
- // rerouting from (v, q) in the construction of the shortest path.
1849
- def route_path(t, u, p, m, v, q, a, n):
1850
- exists((i) |
1851
- range(
1852
- _route_path[t, u, p, i] + 1,
1853
- acc_nsp[t, u, p, i],
1854
- 1,
1855
- m
1856
- ) and
1857
- neighbor(t, u, p, i, v, q, a) and
1858
- n = m - _route_path[t, u, p, i]
1859
- )
1860
-
1861
- // Auxiliary predicate used for efficiency reasons.
1862
- def _route_path[t, u, p, i]: acc_nsp[t, u, p, i - 1]
1863
-
1864
- end // with usp_cut[Conn, S, T, K] use
1865
-
1866
- end // with balls[Conn, S, T] use
1867
-
1868
- end // module _limit[{Conn}, {S}, {T}, {K}]
1869
-
1870
-
1871
- end // namespace pathfinder::_internal::for_each
1872
-
1873
- // pathfinder/_internal/any/from_source/all (from model/_internal/any/from_source/all.rel)
1874
- namespace pathfinder::_internal::any::from_source
1875
-
1876
- from ::pathfinder::utilities import canonical_index
1877
- from ::pathfinder::_internal::utilities import reindex_nodes, aux_hash
1878
-
1879
- // _all indexes the nodes based on their distance from the source (starting from 0). We
1880
- // reindex them to start from 1, as is common practice in Rel.
1881
- @inline
1882
- def all[{Conn}, {S}, {T}]: reindex_nodes[_all[Conn, S, T, :paths]]
1883
-
1884
- //
1885
- // Computes all shortest paths from the set of source nodes S to the set of target nodes T
1886
- // that follow the predicate Conn. The :any grouping selects only the shortest paths among
1887
- // any paths satisfying Conn and connecting a source node to a target node.
1888
- //
1889
- @outline
1890
- module _all[{Conn}, {S}, {T}]
1891
-
1892
- @function
1893
- def label { rel_primitive_product_graph_labels[Conn] }
1894
-
1895
- with ball[Conn, S, T] use
1896
- index_to_state
1897
-
1898
- with usp[Conn, S, T] use
1899
- relevant_source,
1900
- neighbor, // enum of outbound USP edges. Type: (Node,State,Int,Node,State,#Label)
1901
- nsp, // number of shortest paths from a USP node to. Type: (Node,State,Int)
1902
- interval, // interval of shortest path numbers for source nodes. Type: (Node,Int,Int)
1903
- acc_nsp // enum of shortest paths numbers following the order of
1904
- // outbound edges, for a given USP node. Type: (Node,State,Int)
1905
-
1906
-
1907
- // Computes all shortest paths from S to T that conform to Conn
1908
- def paths(path_num, :node, i, v): exists((vv) |
1909
- paths_from_S(vv, _, i, path_num, _) and index_to_state(vv, v, _)
1910
- )
1911
-
1912
- def paths(path_num, :edge_label, i, lab):
1913
- exists((h) |
1914
- paths_from_S(_, _, i, path_num, h) and
1915
- label(h, lab)
1916
- )
1917
-
1918
-
1919
- // Paths are constructed using their enumeration in the USP fragment. The path is
1920
- // routed with the help on the interval assigned to source nodes, and with the neighbor
1921
- // enumeration and nsp values for subsequent nodes.
1922
- def paths_from_S(vv, n, i, path_num, a):
1923
- exists((c, d) |
1924
- i = 0 and
1925
- a = aux_hash and
1926
- relevant_source(vv) and
1927
- interval(vv, c, d) and
1928
- range(c, d, 1, path_num) and
1929
- n = path_num - c + 1
1930
- )
1931
-
1932
- // TODO: this was disabled because the faqtorizer created an unnecessary intermediate
1933
- // We will remove this once the faqtorizer is fixed
1934
- @disable(:faq)
1935
- def paths_from_S(vv, n, i, path_num, a):
1936
- exists((m, uu) |
1937
- paths_from_S(uu, m, i - 1, path_num, _) and
1938
- route_paths_from_S(uu, m, vv, a, n)
1939
- )
1940
-
1941
- // The path numbers assigned to the i-th outbound edge of u start at this number.
1942
- @no_inline
1943
- def prev_acc_nsp[uu, i]: acc_nsp[uu, i - 1]
1944
-
1945
- // Route the n-th shortest path from (u, p) through (v, q) using an a-edge. Holds for
1946
- // every path with relative number m among the paths routed through the edge.
1947
- def route_paths_from_S(uu, m, vv, a, n):
1948
- exists((i) |
1949
- range(prev_acc_nsp[uu, i] + 1, acc_nsp[uu, i], 1, m) and
1950
- neighbor(uu, i, vv, a) and
1951
- n = m - prev_acc_nsp[uu, i]
1952
- )
1953
-
1954
- end // usp[Conn, S, T]
1955
-
1956
- end // ball[Conn, S, T]
1957
-
1958
- end // module _all[{Conn}, {S}, {T}]
1959
-
1960
- end // namespace pathfinder::_internal::any::from_source
1961
-
1962
- // pathfinder/_internal/any/from_source/balls (from model/_internal/any/from_source/balls.rel)
1963
- namespace pathfinder::_internal::any::from_source
1964
-
1965
- //
1966
- // ball[{Conn}, {S}, {T}]
1967
- //
1968
- // Constructs a subgraph of the product graph, defined by Conn, that consists of one
1969
- // "ball", growing from the source nodes S. The ball is constructed by iteratively extending
1970
- // its radius until it reaches a target node. There is a hard coded limit on the number of
1971
- // iterations (up to 100).
1972
- //
1973
- // Input parameters:
1974
- // - Conn: a connectivity predicate that defines a path query in the knowledge graph; used
1975
- // to extract a finite automaton FA with a set of states that represents the query.
1976
- // - S: a set of source nodes; Type: (Node)
1977
- // - T: a set of target nodes; Type: (Node)
1978
- //
1979
- // Output (most relevant):
1980
- // - pg: the product graph defined by the FA extracted from the predicate Conn. A _node_ of
1981
- // the product graph is a pair (n, p) that represents FA reaching node n of the KG in a
1982
- // state p. The edge between two nodes of the product graph is labeled with the (hash
1983
- // value) of the edge label in the KG that connects the two nodes and has a
1984
- // corresponding transition in A. An a-labeled edge from (u, p) to (v, q) is
1985
- // represented as pg(p, q, a, u, v). Type: (State,State,#Label,Node,Node)
1986
- // - label: a function that maps hashed edge labels to their string representation. Using
1987
- // hashes improves performance drastically. Type: (#Label, String).
1988
- // - state_to_index: a function that maps a pair (Node, State) to a distinct integer index.
1989
- // - index_to_state: a function that maps back from an integer index to a pair (Node, State)
1990
- // - source: source nodes of the product graph. Type: (Index), which is an integer.
1991
- // - target: target nodes of the product graph. Type: (Index), which is an integer.
1992
- // - dist_from_S: a relation that holds the distance between a product node (Index) in the
1993
- // S-ball to its closest source node. Type: (Int, Index).
1994
- // - radius_S: the diameter of the source S-ball. Type:Int.
1995
- //
1996
- @outline
1997
- module ball[{Conn}, {S}, {T}]
1998
-
1999
- // The S-ball starts from the source nodes in the initial state 1 of the automaton
2000
- @inline
2001
- def initial_state { 1 }
2002
-
2003
- @inline
2004
- def _source(u, p): S(u) and p = initial_state
2005
-
2006
- // The S-ball grows until it reaches a target node in the final state of the
2007
- // automaton, which are provided by the FFI rel_primitive_product_graph_targets
2008
- @pipeline
2009
- def pg_target { rel_primitive_product_graph_targets[Conn] }
2010
-
2011
- @force_dnf
2012
- def _target(u, p): T(u) and pg_target(p, u)
2013
-
2014
- // The FFI rel_primitive_product_graph compiles the definition of the product graph
2015
- @inline
2016
- def pg { rel_primitive_product_graph[Conn] }
2017
-
2018
- // This FFI provides the mapping from hash values to the string labels.
2019
- // (hash values of string labels are used for efficiency reasons)
2020
- @function
2021
- def label { rel_primitive_product_graph_labels[Conn] }
2022
-
2023
- // Hard-coded bound on the maximal number of iterations to avoid runaway computations.
2024
- @inline
2025
- def max_number_iterations { 100 }
2026
-
2027
-
2028
- // Iterative process of constructing the S-ball. Uses recursion with non-stratified
2029
- // negation to simulate while loop iteration: finish_iteration() and finish_loop()
2030
- // control the execution of the loop.
2031
- @function
2032
- def radius_S {
2033
- minimum[
2034
- max[0;
2035
- radius_S;
2036
- {(radius_S + 1,
2037
- finish_iteration() and
2038
- not finish_loop())
2039
- }
2040
- ],
2041
- max_number_iterations]
2042
- }
2043
-
2044
-
2045
- // Predecessor nodes of the source ball
2046
- @force_dnf
2047
- def _prev_dist_from_S(v, q):
2048
- exists((u, p) |
2049
- _dist_from_S(radius_S - 1, u, p) and pg(p, q, _, u, v)
2050
- )
2051
-
2052
- // Extending the source ball (if radius_S hash been incremented)
2053
- @force_dnf
2054
- def _dist_from_S(d, v, q): _source(v, q) and d = 0
2055
- def _dist_from_S(d, v, q):
2056
- d = radius_S and
2057
- _prev_dist_from_S(v, q) and
2058
- not exists((j) | range(0, radius_S - 1, 1, j) and _dist_from_S(j, v, q))
2059
- def _dist_from_S(d, v, q): _dist_from_S(d, v, q)
2060
-
2061
-
2062
- // Iteration is complete if extending the S-ball has been considered
2063
- @inline
2064
- def finish_iteration(): _dist_from_S(radius_S, _, _)
2065
-
2066
- // Loop is terminated when the S-ball reaches a target node
2067
- @inline
2068
- def finish_loop():
2069
- exists((u, p) |
2070
- _dist_from_S(radius_S, u, p) and _target(u, p))
2071
-
2072
- // Gather all nodes and give each of them a number
2073
- def all_nodes(v, q) : _dist_from_S(_, v, q)
2074
-
2075
- // index_to_state[uu] = (u, p) if uu-th node in all_nodes is (u, p)
2076
- def index_to_state { enumerate[all_nodes]}
2077
-
2078
- // state_to_index[u,p] = uu if uu-th node in all_nodes is (u, p)
2079
- @no_inline
2080
- def state_to_index(u, p, uu) : index_to_state(uu, u, p)
2081
-
2082
- def dist_from_S(d, uu):
2083
- exists ( (u, p) | _dist_from_S(d, u, p) and state_to_index(u, p, uu) )
2084
-
2085
- def source(uu) : exists((u, p) | _source(u, p) and uu = state_to_index[u, p])
2086
-
2087
- def target(uu) : exists((u, p) | _target(u, p) and uu = state_to_index[u, p])
2088
-
2089
- end // module ball[{Conn}, {S}, {T}]
2090
-
2091
- end // namespace pathfinder::_internal::any::from_source
2092
-
2093
- // pathfinder/_internal/any/from_source/usp (from model/_internal/any/from_source/usp.rel)
2094
- namespace pathfinder::_internal::any::from_source
2095
-
2096
- from ::pathfinder::utilities import prefix_sum, linear_prefix_sum
2097
-
2098
- //
2099
- // The union of shortest paths (USP) is a subgraph of the product graph that contains only
2100
- // the shortest paths from the set of source nodes to the set of target nodes. Recall that
2101
- // a node in a product graph is represented as a pair of values (u, p) where u is a node in
2102
- // the source graph and p is a state of the finite automaton capturing the connectivity path
2103
- // query. USP is decorated with additional information that compactly represents the
2104
- // enumeration of the shortest paths that allows an efficient path retrieval.
2105
- //
2106
-
2107
- //
2108
- // Constructs the USP structure
2109
- //
2110
- // Input:
2111
- // - Conn: a connectivity predicate that defines a path query in the knowledge graph
2112
- // - S: a set of source nodes. Type: (Node)
2113
- // - T: a set of target nodes. Type: (Node)
2114
- //
2115
- // Output (that we use):
2116
- // - usp_edge: the edges of the USP fragment. Note that it has different signature than
2117
- // product graph. Namely, Type: (Index, Index, #Label)
2118
- // Compact representation of the enumeration of the shortest paths
2119
- // - neighbor: enumeration of the outbound edges of USP node.
2120
- // Type: (Index, Int, Index, #Label)
2121
- // - nsp: the number of shortest paths from a USP node to target nodes.
2122
- // Type: (Index, Int)
2123
- // - interval: the interval of numbers of shortest paths for source nodes.
2124
- // Type: (Index, Int, Int)
2125
- // - acc_nsp: the enumeration of the shortest paths going through the outbound edges of a
2126
- // USP node. Type: (Index, Int)
2127
- // - total: the total number of shortest paths from S to T (may be <= K). Type: Int
2128
- //
2129
- @outline
2130
- module usp[{Conn}, {S}, {T}]
2131
-
2132
- with ball[Conn, S, T] use
2133
- pg, // product graph. Type: (State,State,#Label,Node,Node)
2134
- dist_from_S, // distance from S. Type: (Int,Node,State)
2135
- radius_S, // S-ball radius. Type: Int
2136
- initial_state, // initial state of the automaton. Type: Int
2137
- target, // target nodes. Type: (Node,State)
2138
- state_to_index,
2139
- source
2140
-
2141
-
2142
- // The USP fragment is obtained by constructing the paths from the source nodes
2143
- // to the target nodes.
2144
- def usp_edge { usp_edge_back[_] }
2145
-
2146
-
2147
- // Build the paths backward from the target to the source nodes. We materialize the USP
2148
- // fragment "on the fly", using recursion where we control the order of evaluation by
2149
- // introducing additional intermediate predicates.
2150
- @force_dnf
2151
- def start_usp_edge_back(uu, vv, a): exists ( (u, p, v, q) |
2152
- pg(p, q, a, u, v) and
2153
- target(vv) and
2154
- state_to_index[v, q] = vv and
2155
- state_to_index[u, p] = uu
2156
- )
2157
-
2158
- @force_dnf
2159
- def step_usp_edge_back(i, uu, vv, a): exists ( (u, p, v, q) |
2160
- usp_edge_back(i + 1, vv, _, _) and
2161
- pg(p, q, a, u, v) and
2162
- state_to_index[v, q] = vv and
2163
- state_to_index[u, p] = uu
2164
- )
2165
-
2166
- def usp_edge_back(i, uu, vv, a):
2167
- dist_from_S(i, uu) and
2168
- start_usp_edge_back(uu, vv, a) and
2169
- i = radius_S - 1
2170
-
2171
- def usp_edge_back(i, uu, vv, a):
2172
- step_usp_edge_back(i, uu, vv, a) and
2173
- dist_from_S(i, uu)
2174
-
2175
- //
2176
- // An enumeration of relevant shortest paths, represented in a compact manner.
2177
- //
2178
-
2179
- // Order the outbound edges of every pg node
2180
- def neighbor(uu, i, vv, a):
2181
- enumerate({(ww, _a): usp_edge(uu, ww, _a)}, i, vv, a)
2182
-
2183
- // Compute the number of shortest paths from the given USP node to target nodes
2184
- def nsp[uu]: (1, target(uu))
2185
- def nsp[uu]: sum[(vv, a, w): w = nsp[vv] and usp_edge(uu, vv, a)]
2186
-
2187
- // Filter out the source nodes that are not connected to a target node with a
2188
- // shortest path. If S and T coincide, USP has no edges and all relevant
2189
- // source nodes are also target nodes.
2190
- def relevant_source(uu): source(uu) and usp_edge(uu, _, _)
2191
- def relevant_source(uu): source(uu) and target(uu)
2192
-
2193
- // Order the relevant source nodes
2194
- def ordered_source { sort[relevant_source] }
2195
-
2196
- // Decomposition of number of shortest paths that traverses nodes (and edges).
2197
- // Edge case is simple and we keep the relative position i of the edge.
2198
- def edge_nsp(uu, i, val): exists((vv) | val = nsp[vv] and neighbor(uu, i, vv, _))
2199
-
2200
- // Node case: an enumeration of the shortest paths going through its outbound edges
2201
- // We use linear prefix sum here because the expected degree of nodes in the USP graph
2202
- // is small, and the number of nodes is large. Linear prefix sum has less overhead than
2203
- // the binary search prefix sum.
2204
- def acc_nsp[uu, 0]: (0, usp_edge(uu, _, _))
2205
- def acc_nsp[uu]: linear_prefix_sum[edge_nsp[uu]]
2206
-
2207
- // Also, relevant source nodes get an interval [a, b] of the shortest paths (up to K)
2208
- def interval(uu, a, b) :
2209
- ordered_source(1, uu) and a = 1 and b = nsp[uu]
2210
-
2211
- def interval(uu, a, b) :
2212
- exists((i) |
2213
- ordered_source(i, uu) and
2214
- a = running_sum[i - 1] + 1 and
2215
- b = running_sum[i])
2216
-
2217
- def value_source(i, val):
2218
- exists((uu) | ordered_source(i, uu) and val = nsp[uu])
2219
-
2220
- // Here binary-search prefix-sum is used because the array we are taking the prefix sum
2221
- // of is expected to be large.
2222
- def running_sum { prefix_sum[value_source] }
2223
-
2224
- // The total number of shortest paths from S to T
2225
- def total { sum[(uu, val): relevant_source(uu) and val = nsp[uu]] }
2226
-
2227
-
2228
- end // with ball[Conn, S, T]
2229
-
2230
- end // module usp[{Conn}, {S}, {T}]
2231
-
2232
- end // namespace pathfinder::_internal::any::from_source
2233
-
2234
- // pathfinder/_internal/for_each/from_source/all (from model/_internal/for_each/from_source/all.rel)
2235
- namespace pathfinder::_internal::for_each::from_source
2236
-
2237
- from ::pathfinder::utilities import canonical_index
2238
- from ::pathfinder::_internal::utilities import reindex_nodes, aux_hash
2239
-
2240
- // _all indexes the nodes based on their distance from the source (starting from 0). We
2241
- // reindex them to start from 1, as is common practice in Rel.
2242
- @inline
2243
- def all[{Conn}, {S}, {T}]: reindex_nodes[_all[Conn, S, T, :paths]]
2244
-
2245
- //
2246
- // Computes all shortest paths from the set of source nodes S to the set of target nodes T
2247
- // that follow the predicate Conn. The :any grouping selects only the shortest paths among
2248
- // any paths satisfying Conn and connecting a source node to a target node.
2249
- //
2250
- @outline
2251
- module _all[{Conn}, {S}, {T}]
2252
-
2253
- @function
2254
- def label { rel_primitive_product_graph_labels[Conn] }
2255
-
2256
- with ball[Conn, S, T] use
2257
- index_to_state,
2258
- target
2259
-
2260
- with usp[Conn, S, T] use
2261
- relevant_source,
2262
- neighbor, // enum of outbound USP edges. Type: (Node,State,Int,Node,State,#Label)
2263
- nsp, // number of shortest paths from a USP node to. Type: (Node,State,Int)
2264
- interval, // interval of shortest path numbers for source nodes. Type: (Node,Int,Int)
2265
- acc_nsp // enum of shortest paths numbers following the order of
2266
- // outbound edges, for a given USP node. Type: (Node,State,Int)
2267
-
2268
-
2269
- // Computes all shortest paths from S to T that conform to Conn
2270
- def paths(path_num, :node, i, v): exists((vv) |
2271
- paths_from_S(vv, _, i, path_num, _) and index_to_state(vv, v, _)
2272
- )
2273
-
2274
- def paths(path_num, :edge_label, i, lab):
2275
- exists((h) |
2276
- paths_from_S(_, _, i, path_num, h) and
2277
- label(h, lab)
2278
- )
2279
-
2280
-
2281
- // Paths are constructed using their enumeration in the USP fragment. The path is
2282
- // routed with the help on the interval assigned to source nodes, and with the neighbor
2283
- // enumeration and nsp values for subsequent nodes.
2284
- def paths_from_S(vv, n, i, path_num, a):
2285
- exists((c, d) |
2286
- i = 0 and
2287
- a = aux_hash and
2288
- relevant_source(vv) and
2289
- interval(vv, c, d) and
2290
- range(c, d, 1, path_num) and
2291
- n = path_num - c + 1
2292
- )
2293
-
2294
- // TODO: this was disabled because the faqtorizer created an unnecessary intermediate
2295
- // We will remove this once the faqtorizer is fixed
2296
- @disable(:faq)
2297
- def paths_from_S(vv, n, i, path_num, a):
2298
- exists((m, uu) |
2299
- paths_from_S(uu, m, i - 1, path_num, _) and
2300
- route_paths_from_S(uu, m, vv, a, n)
2301
- )
2302
-
2303
- // The path numbers assigned to the i-th outbound edge of u start at this number.
2304
- @no_inline
2305
- def prev_acc_nsp[uu, i]: acc_nsp[uu, i - 1]
2306
-
2307
- // Route the n-th shortest path from (u, p) through (v, q) using an a-edge. Holds for
2308
- // every path with relative number m among the paths routed through the edge.
2309
- def route_paths_from_S(uu, m, vv, a, n):
2310
- exists((i) |
2311
- range(prev_acc_nsp[uu, i] + 1, acc_nsp[uu, i], 1, m) and
2312
- neighbor(uu, i, vv, a) and
2313
- n = m - prev_acc_nsp[uu, i]
2314
- )
2315
-
2316
- end // usp[Conn, S, T]
2317
-
2318
- end // ball[Conn, S, T]
2319
-
2320
- end // module _all[{Conn}, {S}, {T}]
2321
-
2322
- end // namespace pathfinder::_internal::for_each::from_source
2323
-
2324
- // pathfinder/_internal/for_each/from_source/balls (from model/_internal/for_each/from_source/balls.rel)
2325
- namespace pathfinder::_internal::for_each::from_source
2326
-
2327
- //
2328
- // ball[{Conn}, {S}, {T}]
2329
- //
2330
- // Constructs a subgraph of the product graph, defined by Conn, that consists of one
2331
- // "ball", growing from the source nodes S. The ball is constructed by iteratively extending
2332
- // its radius until it reaches a target node. There is a hard coded limit on the number of
2333
- // iterations (up to 100).
2334
- //
2335
- // Input parameters:
2336
- // - Conn: a connectivity predicate that defines a path query in the knowledge graph; used
2337
- // to extract a finite automaton FA with a set of states that represents the query.
2338
- // - S: a set of source nodes; Type: (Node)
2339
- // - T: a set of target nodes; Type: (Node)
2340
- //
2341
- // Output (most relevant):
2342
- // - pg: the product graph defined by the FA extracted from the predicate Conn. A _node_ of
2343
- // the product graph is a pair (n, p) that represents FA reaching node n of the KG in a
2344
- // state p. The edge between two nodes of the product graph is labeled with the (hash
2345
- // value) of the edge label in the KG that connects the two nodes and has a
2346
- // corresponding transition in A. An a-labeled edge from (u, p) to (v, q) is
2347
- // represented as pg(p, q, a, u, v). Type: (State,State,#Label,Node,Node)
2348
- // - label: a function that maps hashed edge labels to their string representation. Using
2349
- // hashes improves performance drastically. Type: (#Label, String).
2350
- // - state_to_index: a function that maps a pair (Node, State) to a distinct integer index.
2351
- // - index_to_state: a function that maps back from an integer index to a pair (Node, State)
2352
- // - source: source nodes of the product graph. Type: (Index), which is an integer.
2353
- // - target: target nodes of the product graph. Type: (Index), which is an integer.
2354
- // - dist_from_S: a relation that holds the distance between a product node (Index) in the
2355
- // S-ball to its closest source node. Type: (Int, Index).
2356
- //
2357
- @outline
2358
- module ball[{Conn}, {S}, {T}]
2359
-
2360
- // The S-ball starts from the source nodes in the initial state 1 of the automaton
2361
- @inline
2362
- def initial_state { 1 }
2363
-
2364
- @inline
2365
- def _source(u, p): S(u) and p = initial_state
2366
-
2367
- // The S-ball grows until it reaches a target node in the final state of the
2368
- // automaton, which are provided by the FFI rel_primitive_product_graph_targets
2369
- @pipeline
2370
- def pg_target { rel_primitive_product_graph_targets[Conn] }
2371
-
2372
- @force_dnf
2373
- def _target(u, p): T(u) and pg_target(p, u)
2374
-
2375
- // The FFI rel_primitive_product_graph compiles the definition of the product graph
2376
- @inline
2377
- def pg { rel_primitive_product_graph[Conn] }
2378
-
2379
- // This FFI provides the mapping from hash values to the string labels.
2380
- // (hash values of string labels are used for efficiency reasons)
2381
- @function
2382
- def label { rel_primitive_product_graph_labels[Conn] }
2383
-
2384
-
2385
- @force_dnf @function
2386
- def _dist_from_S[v, q]: {
2387
- min[
2388
- [d] : (
2389
- (
2390
- exists((u, p) |
2391
- d = _dist_from_S[u, p] + 1 and
2392
- pg(p, q, _, u, v)
2393
- )
2394
- ) or
2395
- (_source(v, q) and d = 0)
2396
- )
2397
- ]
2398
- }
2399
-
2400
-
2401
- // Gather all nodes and give each of them a number
2402
- def all_nodes(v, q) : _dist_from_S(v, q, _)
2403
-
2404
- // index_to_state[uu] = (u, p) if uu-th node in all_nodes is (u, p)
2405
- def index_to_state { enumerate[all_nodes]}
2406
-
2407
- // state_to_index[u,p] = uu if uu-th node in all_nodes is (u, p)
2408
- @no_inline
2409
- def state_to_index(u, p, uu) : index_to_state(uu, u, p)
2410
-
2411
- def dist_from_S(d, uu):
2412
- exists ( (u, p) | _dist_from_S(u, p, d) and state_to_index(u, p, uu) )
2413
-
2414
- def source(uu) : exists((u, p) | _source(u, p) and uu = state_to_index[u, p])
2415
-
2416
- def target(uu) : exists((u, p) | _target(u, p) and uu = state_to_index[u, p])
2417
-
2418
- end // module ball[{Conn}, {S}, {T}]
2419
-
2420
- end // namespace pathfinder::_internal::for_each::from_source
2421
-
2422
- // pathfinder/_internal/for_each/from_source/usp (from model/_internal/for_each/from_source/usp.rel)
2423
- namespace pathfinder::_internal::for_each::from_source
2424
-
2425
- from ::pathfinder::utilities import prefix_sum, linear_prefix_sum
2426
-
2427
- //
2428
- // The union of shortest paths (USP) is a subgraph of the product graph that contains only
2429
- // the shortest paths from the set of source nodes to the set of target nodes. Recall that
2430
- // a node in a product graph is represented as a pair of values (u, p) where u is a node in
2431
- // the source graph and p is a state of the finite automaton capturing the connectivity path
2432
- // query. USP is decorated with additional information that compactly represents the
2433
- // enumeration of the shortest paths that allows an efficient path retrieval.
2434
- //
2435
-
2436
- //
2437
- // Constructs the USP structure
2438
- //
2439
- // Input:
2440
- // - Conn: a connectivity predicate that defines a path query in the knowledge graph
2441
- // - S: a set of source nodes. Type: (Node)
2442
- // - T: a set of target nodes. Type: (Node)
2443
- //
2444
- // Output (that we use):
2445
- // - usp_edge: the edges of the USP fragment. Note that it has different signature than
2446
- // product graph. Namely, Type: (Index, Index, #Label)
2447
- // Compact representation of the enumeration of the shortest paths
2448
- // - neighbor: enumeration of the outbound edges of USP node.
2449
- // Type: (Index, Int, Index, #Label)
2450
- // - nsp: the number of shortest paths from a USP node to target nodes.
2451
- // Type: (Index, Int)
2452
- // - interval: the interval of numbers of shortest paths for source nodes.
2453
- // Type: (Index, Int, Int)
2454
- // - acc_nsp: the enumeration of the shortest paths going through the outbound edges of a
2455
- // USP node. Type: (Index, Int)
2456
- // - total: the total number of shortest paths from S to T (may be <= K). Type: Int
2457
- //
2458
- @outline
2459
- module usp[{Conn}, {S}, {T}]
2460
-
2461
- with ball[Conn, S, T] use
2462
- pg, // product graph. Type: (State,State,#Label,Node,Node)
2463
- dist_from_S, // distance from S. Type: (Int,Node,State)
2464
- radius_S, // S-ball radius. Type: Int
2465
- initial_state, // initial state of the automaton. Type: Int
2466
- target, // target nodes. Type: (Node,State)
2467
- state_to_index,
2468
- source
2469
-
2470
-
2471
- // The USP fragment is obtained by constructing the paths from the source nodes
2472
- // to the target nodes.
2473
- def usp_edge { usp_edge_back[_] }
2474
-
2475
-
2476
- // Build the paths backward from the target to the source nodes. We materialize the USP
2477
- // fragment "on the fly", using recursion where we control the order of evaluation by
2478
- // introducing additional intermediate predicates.
2479
- @force_dnf
2480
- def start_usp_edge_back(uu, vv, a): exists ( (u, p, v, q) |
2481
- pg(p, q, a, u, v) and
2482
- target(vv) and
2483
- state_to_index[v, q] = vv and
2484
- state_to_index[u, p] = uu
2485
- )
2486
-
2487
- @force_dnf
2488
- def step_usp_edge_back(i, uu, vv, a): exists ( (u, p, v, q) |
2489
- usp_edge_back(i + 1, vv, _, _) and
2490
- pg(p, q, a, u, v) and
2491
- state_to_index[v, q] = vv and
2492
- state_to_index[u, p] = uu
2493
- )
2494
-
2495
- def usp_edge_back(i, uu, vv, a):
2496
- dist_from_S(i, uu) and
2497
- start_usp_edge_back(uu, vv, a)
2498
-
2499
- def usp_edge_back(i, uu, vv, a):
2500
- step_usp_edge_back(i, uu, vv, a) and
2501
- dist_from_S(i, uu)
2502
-
2503
- //
2504
- // An enumeration of relevant shortest paths, represented in a compact manner.
2505
- //
2506
-
2507
- // Order the outbound edges of every pg node
2508
- def neighbor(uu, i, vv, a):
2509
- enumerate({(ww, _a): usp_edge(uu, ww, _a)}, i, vv, a)
2510
-
2511
- // Compute the number of shortest paths from the given USP node to target nodes
2512
- def nsp[uu]: (1, target(uu))
2513
- def nsp[uu]: sum[(vv, a, w): w = nsp[vv] and usp_edge(uu, vv, a)]
2514
-
2515
- // Filter out the source nodes that are not connected to a target node with a
2516
- // shortest path. If S and T coincide, USP has no edges and all relevant
2517
- // source nodes are also target nodes.
2518
- def relevant_source(uu): source(uu) and usp_edge(uu, _, _)
2519
- def relevant_source(uu): source(uu) and target(uu)
2520
-
2521
- // Order the relevant source nodes
2522
- def ordered_source { sort[relevant_source] }
2523
-
2524
- // Decomposition of number of shortest paths that traverses nodes (and edges).
2525
- // Edge case is simple and we keep the relative position i of the edge.
2526
- def edge_nsp(uu, i, val): exists((vv) | val = nsp[vv] and neighbor(uu, i, vv, _))
2527
-
2528
- // Node case: an enumeration of the shortest paths going through its outbound edges
2529
- // We use linear prefix sum here because the expected degree of nodes in the USP graph
2530
- // is small, and the number of nodes is large. Linear prefix sum has less overhead than
2531
- // the binary search prefix sum.
2532
- def acc_nsp[uu, 0]: (0, usp_edge(uu, _, _))
2533
- def acc_nsp[uu]: linear_prefix_sum[edge_nsp[uu]]
2534
-
2535
- // Also, relevant source nodes get an interval [a, b] of the shortest paths (up to K)
2536
- def interval(uu, a, b) :
2537
- ordered_source(1, uu) and a = 1 and b = nsp[uu]
2538
-
2539
- def interval(uu, a, b) :
2540
- exists((i) |
2541
- ordered_source(i, uu) and
2542
- a = running_sum[i - 1] + 1 and
2543
- b = running_sum[i])
2544
-
2545
- def value_source(i, val):
2546
- exists((uu) | ordered_source(i, uu) and val = nsp[uu])
2547
-
2548
- // Here binary-search prefix-sum is used because the array we are taking the prefix sum
2549
- // of is expected to be large.
2550
- def running_sum { prefix_sum[value_source] }
2551
-
2552
- // The total number of shortest paths from S to T
2553
- def total { sum[(uu, val): relevant_source(uu) and val = nsp[uu]] }
2554
-
2555
-
2556
- end // with ball[Conn, S, T]
2557
-
2558
- end // module usp[{Conn}, {S}, {T}]
2559
-
2560
- end // namespace pathfinder::_internal::for_each::from_source