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.
- relationalai/__init__.py +4 -0
- relationalai/clients/snowflake.py +34 -13
- relationalai/early_access/lqp/constructors/__init__.py +2 -2
- relationalai/early_access/metamodel/rewrite/__init__.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/README.md +2 -2
- relationalai/experimental/paths/__init__.py +14 -309
- relationalai/{semantics/reasoners/graph → experimental}/paths/examples/basic_example.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/examples/paths_benchmark.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/examples/paths_example.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/examples/pattern_to_automaton.py +1 -1
- relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/one_sided_ball_repetition.py +1 -1
- relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/single.py +3 -3
- relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/two_sided_balls_repetition.py +1 -1
- relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/two_sided_balls_upto.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/usp-old.py +3 -3
- relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/usp-tuple.py +3 -3
- relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/usp.py +3 -3
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_limit_sp_max_length.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_limit_sp_multiple.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_limit_sp_single.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_limit_walks_multiple.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_limit_walks_single.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_one_sided_ball_repetition_multiple.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_one_sided_ball_repetition_single.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_one_sided_ball_upto_multiple.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_one_sided_ball_upto_single.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_single_paths.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_single_walks.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_single_walks_undirected.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_two_sided_balls_repetition_multiple.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_two_sided_balls_repetition_single.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_two_sided_balls_upto_multiple.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_two_sided_balls_upto_single.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_usp_nsp_multiple.py +2 -2
- relationalai/{semantics/reasoners/graph → experimental}/paths/tests/tests_usp_nsp_single.py +2 -2
- relationalai/semantics/__init__.py +4 -0
- relationalai/semantics/internal/annotations.py +1 -0
- relationalai/semantics/internal/internal.py +3 -4
- relationalai/semantics/internal/snowflake.py +14 -1
- relationalai/semantics/lqp/builtins.py +1 -0
- relationalai/semantics/lqp/constructors.py +0 -5
- relationalai/semantics/lqp/executor.py +34 -10
- relationalai/semantics/lqp/intrinsics.py +2 -2
- relationalai/semantics/lqp/model2lqp.py +105 -9
- relationalai/semantics/lqp/passes.py +27 -8
- relationalai/semantics/lqp/primitives.py +18 -15
- relationalai/semantics/lqp/rewrite/__init__.py +2 -2
- relationalai/semantics/lqp/rewrite/{fd_constraints.py → function_annotations.py} +4 -4
- relationalai/semantics/lqp/utils.py +17 -13
- relationalai/semantics/metamodel/builtins.py +50 -1
- relationalai/semantics/metamodel/typer/typer.py +3 -0
- relationalai/semantics/reasoners/__init__.py +4 -0
- relationalai/semantics/reasoners/experimental/__init__.py +7 -0
- relationalai/semantics/reasoners/graph/core.py +1154 -122
- relationalai/semantics/rel/builtins.py +3 -1
- relationalai/semantics/rel/compiler.py +2 -2
- relationalai/semantics/rel/executor.py +30 -8
- relationalai/semantics/rel/rel_utils.py +5 -0
- relationalai/semantics/snowflake/__init__.py +2 -2
- relationalai/semantics/sql/compiler.py +6 -0
- relationalai/semantics/sql/executor/snowflake.py +6 -2
- relationalai/semantics/tests/test_snapshot_abstract.py +5 -4
- {relationalai-0.12.4.dist-info → relationalai-0.12.7.dist-info}/METADATA +2 -2
- {relationalai-0.12.4.dist-info → relationalai-0.12.7.dist-info}/RECORD +99 -115
- {relationalai-0.12.4.dist-info → relationalai-0.12.7.dist-info}/WHEEL +1 -1
- relationalai/early_access/paths/__init__.py +0 -22
- relationalai/early_access/paths/api/__init__.py +0 -12
- relationalai/early_access/paths/benchmarks/__init__.py +0 -13
- relationalai/early_access/paths/graph/__init__.py +0 -12
- relationalai/early_access/paths/path_algorithms/find_paths/__init__.py +0 -12
- relationalai/early_access/paths/path_algorithms/one_sided_ball_repetition/__init__.py +0 -12
- relationalai/early_access/paths/path_algorithms/one_sided_ball_upto/__init__.py +0 -12
- relationalai/early_access/paths/path_algorithms/single/__init__.py +0 -12
- relationalai/early_access/paths/path_algorithms/two_sided_balls_repetition/__init__.py +0 -12
- relationalai/early_access/paths/path_algorithms/two_sided_balls_upto/__init__.py +0 -12
- relationalai/early_access/paths/path_algorithms/usp/__init__.py +0 -12
- relationalai/early_access/paths/rpq/__init__.py +0 -13
- relationalai/early_access/paths/utilities/iterators/__init__.py +0 -12
- relationalai/experimental/paths/pathfinder.rel +0 -2560
- relationalai/semantics/reasoners/graph/paths/__init__.py +0 -16
- relationalai/semantics/reasoners/graph/paths/path_algorithms/__init__.py +0 -3
- relationalai/semantics/reasoners/graph/paths/utilities/__init__.py +0 -3
- /relationalai/{semantics/reasoners/graph → experimental}/paths/api.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/benchmarks/__init__.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/benchmarks/grid_graph.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/code_organization.md +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/Movies.ipynb +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/minimal_engine_warmup.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movie_example.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/actedin.csv +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/directed.csv +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/follows.csv +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/movies.csv +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/person.csv +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/produced.csv +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/ratings.csv +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/examples/movies_data/wrote.csv +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/find_paths_via_automaton.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/graph.py +0 -0
- /relationalai/{early_access → experimental}/paths/path_algorithms/__init__.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/find_paths.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/path_algorithms/one_sided_ball_upto.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/product_graph.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/__init__.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/automaton.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/diagnostics.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/filter.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/glushkov.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/rpq.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/rpq/transition.py +0 -0
- /relationalai/{early_access → experimental}/paths/utilities/__init__.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/utilities/iterators.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/utilities/prefix_sum.py +0 -0
- /relationalai/{semantics/reasoners/graph → experimental}/paths/utilities/utilities.py +0 -0
- {relationalai-0.12.4.dist-info → relationalai-0.12.7.dist-info}/entry_points.txt +0 -0
- {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
|