edsger 0.1.1__cp312-cp312-win32.whl → 0.1.3__cp312-cp312-win32.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.
- edsger/_version.py +1 -1
- edsger/commons.cp312-win32.pyd +0 -0
- edsger/dijkstra.cp312-win32.pyd +0 -0
- edsger/dijkstra.pyx +525 -0
- edsger/path.py +145 -37
- edsger/path_tracking.cp312-win32.pyd +0 -0
- edsger/path_tracking.pyx +2 -2
- edsger/pq_4ary_dec_0b.cp312-win32.pyd +0 -0
- edsger/pq_4ary_dec_0b.pxd +7 -7
- edsger/pq_4ary_dec_0b.pyx +14 -16
- edsger/prefetch_compat.h +21 -0
- edsger/spiess_florian.cp312-win32.pyd +0 -0
- edsger/star.cp312-win32.pyd +0 -0
- edsger/star.pyx +3 -3
- {edsger-0.1.1.dist-info → edsger-0.1.3.dist-info}/METADATA +18 -11
- edsger-0.1.3.dist-info/RECORD +27 -0
- edsger-0.1.1.dist-info/RECORD +0 -26
- {edsger-0.1.1.dist-info → edsger-0.1.3.dist-info}/WHEEL +0 -0
- {edsger-0.1.1.dist-info → edsger-0.1.3.dist-info}/licenses/AUTHORS.rst +0 -0
- {edsger-0.1.1.dist-info → edsger-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {edsger-0.1.1.dist-info → edsger-0.1.3.dist-info}/top_level.txt +0 -0
edsger/_version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.1.
|
1
|
+
__version__ = "0.1.3"
|
edsger/commons.cp312-win32.pyd
CHANGED
Binary file
|
edsger/dijkstra.cp312-win32.pyd
CHANGED
Binary file
|
edsger/dijkstra.pyx
CHANGED
@@ -9,12 +9,24 @@ cpdef functions:
|
|
9
9
|
- compute_sssp_w_path
|
10
10
|
Compute single-source shortest path (from one vertex to all vertices).
|
11
11
|
Compute predecessors.
|
12
|
+
- compute_sssp_early_termination
|
13
|
+
Compute single-source shortest path with early termination when target nodes
|
14
|
+
are reached. Does not return predecessors.
|
15
|
+
- compute_sssp_w_path_early_termination
|
16
|
+
Compute single-source shortest path with early termination when target nodes
|
17
|
+
are reached. Compute predecessors.
|
12
18
|
- compute_stsp
|
13
19
|
Compute single-target shortest path (from all vertices to one vertex). Does
|
14
20
|
not return successors.
|
15
21
|
- compute_stsp_w_path
|
16
22
|
Compute single-target shortest path (from all vertices to one vertex).
|
17
23
|
Compute successors.
|
24
|
+
- compute_stsp_early_termination
|
25
|
+
Compute single-target shortest path with early termination when target nodes
|
26
|
+
are reached. Does not return successors.
|
27
|
+
- compute_stsp_w_path_early_termination
|
28
|
+
Compute single-target shortest path with early termination when target nodes
|
29
|
+
are reached. Compute successors.
|
18
30
|
"""
|
19
31
|
|
20
32
|
cimport numpy as cnp
|
@@ -23,6 +35,11 @@ from edsger.commons cimport (
|
|
23
35
|
DTYPE_INF, UNLABELED, SCANNED, DTYPE_t, ElementState)
|
24
36
|
cimport edsger.pq_4ary_dec_0b as pq # priority queue
|
25
37
|
|
38
|
+
# memory prefetching support (x86/x64 only)
|
39
|
+
cdef extern from "prefetch_compat.h":
|
40
|
+
void prefetch_hint(char*, int) nogil
|
41
|
+
int PREFETCH_T0
|
42
|
+
|
26
43
|
|
27
44
|
cpdef cnp.ndarray compute_sssp(
|
28
45
|
cnp.uint32_t[::1] csr_indptr,
|
@@ -83,8 +100,17 @@ cpdef cnp.ndarray compute_sssp(
|
|
83
100
|
<size_t>csr_indptr[tail_vert_idx + 1]):
|
84
101
|
|
85
102
|
head_vert_idx = <size_t>csr_indices[idx]
|
103
|
+
|
104
|
+
# prefetch next iteration data to improve cache performance
|
105
|
+
if idx + 1 < <size_t>csr_indptr[tail_vert_idx + 1]:
|
106
|
+
prefetch_hint(<char*>&csr_indices[idx + 1], PREFETCH_T0)
|
107
|
+
prefetch_hint(<char*>&csr_data[idx + 1], PREFETCH_T0)
|
108
|
+
|
86
109
|
vert_state = pqueue.Elements[head_vert_idx].state
|
87
110
|
if vert_state != SCANNED:
|
111
|
+
# prefetch priority queue element data for the vertex
|
112
|
+
prefetch_hint(<char*>&pqueue.Elements[head_vert_idx], PREFETCH_T0)
|
113
|
+
|
88
114
|
head_vert_val = tail_vert_val + csr_data[idx]
|
89
115
|
if vert_state == UNLABELED:
|
90
116
|
pq.insert(&pqueue, head_vert_idx, head_vert_val)
|
@@ -100,6 +126,109 @@ cpdef cnp.ndarray compute_sssp(
|
|
100
126
|
return path_lengths
|
101
127
|
|
102
128
|
|
129
|
+
cpdef cnp.ndarray compute_sssp_early_termination(
|
130
|
+
cnp.uint32_t[::1] csr_indptr,
|
131
|
+
cnp.uint32_t[::1] csr_indices,
|
132
|
+
DTYPE_t[::1] csr_data,
|
133
|
+
cnp.uint32_t[::1] termination_nodes,
|
134
|
+
int source_vert_idx,
|
135
|
+
int vertex_count,
|
136
|
+
int heap_length):
|
137
|
+
"""
|
138
|
+
Compute single-source shortest path with early termination when target
|
139
|
+
nodes are reached.
|
140
|
+
Parameters
|
141
|
+
----------
|
142
|
+
csr_indices : cnp.uint32_t[::1]
|
143
|
+
indices in the CSR format
|
144
|
+
csr_indptr : cnp.uint32_t[::1]
|
145
|
+
pointers in the CSR format
|
146
|
+
csr_data DTYPE_t[::1]
|
147
|
+
data (edge weights) in the CSR format
|
148
|
+
termination_nodes : cnp.uint32_t[::1]
|
149
|
+
target node indices for early termination
|
150
|
+
source_vert_idx : int
|
151
|
+
source vertex index
|
152
|
+
vertex_count : int
|
153
|
+
vertex count
|
154
|
+
heap_length : int
|
155
|
+
heap length
|
156
|
+
|
157
|
+
Returns
|
158
|
+
-------
|
159
|
+
path_lengths : cnp.ndarray
|
160
|
+
shortest path length for each termination node
|
161
|
+
"""
|
162
|
+
|
163
|
+
cdef:
|
164
|
+
size_t tail_vert_idx, head_vert_idx, idx, i
|
165
|
+
DTYPE_t tail_vert_val, head_vert_val
|
166
|
+
pq.PriorityQueue pqueue
|
167
|
+
ElementState vert_state
|
168
|
+
size_t source = <size_t>source_vert_idx
|
169
|
+
size_t target_count = termination_nodes.shape[0]
|
170
|
+
size_t visited_targets = 0
|
171
|
+
size_t iteration_count = 0
|
172
|
+
size_t check_frequency = 16
|
173
|
+
|
174
|
+
with nogil:
|
175
|
+
|
176
|
+
# initialization of the heap elements
|
177
|
+
# all nodes have INFINITY key and UNLABELED state
|
178
|
+
pq.init_pqueue(&pqueue, <size_t>heap_length, <size_t>vertex_count)
|
179
|
+
|
180
|
+
# the key is set to zero for the source vertex,
|
181
|
+
# which is inserted into the heap
|
182
|
+
pq.insert(&pqueue, source, 0.0)
|
183
|
+
|
184
|
+
# main loop
|
185
|
+
while pqueue.size > 0:
|
186
|
+
tail_vert_idx = pq.extract_min(&pqueue)
|
187
|
+
tail_vert_val = pqueue.Elements[tail_vert_idx].key
|
188
|
+
|
189
|
+
# check for early termination every check_frequency iterations
|
190
|
+
iteration_count += 1
|
191
|
+
if iteration_count % check_frequency == 0:
|
192
|
+
visited_targets = 0
|
193
|
+
for i in range(target_count):
|
194
|
+
if pqueue.Elements[termination_nodes[i]].state == SCANNED:
|
195
|
+
visited_targets += 1
|
196
|
+
if visited_targets == target_count:
|
197
|
+
break
|
198
|
+
|
199
|
+
# loop on outgoing edges
|
200
|
+
for idx in range(<size_t>csr_indptr[tail_vert_idx],
|
201
|
+
<size_t>csr_indptr[tail_vert_idx + 1]):
|
202
|
+
|
203
|
+
head_vert_idx = <size_t>csr_indices[idx]
|
204
|
+
|
205
|
+
# prefetch next iteration data to improve cache performance
|
206
|
+
if idx + 1 < <size_t>csr_indptr[tail_vert_idx + 1]:
|
207
|
+
prefetch_hint(<char*>&csr_indices[idx + 1], PREFETCH_T0)
|
208
|
+
prefetch_hint(<char*>&csr_data[idx + 1], PREFETCH_T0)
|
209
|
+
|
210
|
+
vert_state = pqueue.Elements[head_vert_idx].state
|
211
|
+
if vert_state != SCANNED:
|
212
|
+
# prefetch priority queue element data for the vertex
|
213
|
+
prefetch_hint(<char*>&pqueue.Elements[head_vert_idx], PREFETCH_T0)
|
214
|
+
|
215
|
+
head_vert_val = tail_vert_val + csr_data[idx]
|
216
|
+
if vert_state == UNLABELED:
|
217
|
+
pq.insert(&pqueue, head_vert_idx, head_vert_val)
|
218
|
+
elif pqueue.Elements[head_vert_idx].key > head_vert_val:
|
219
|
+
pq.decrease_key(&pqueue, head_vert_idx, head_vert_val)
|
220
|
+
|
221
|
+
# copy only the termination nodes' results into a numpy array
|
222
|
+
cdef cnp.ndarray path_lengths = np.empty(target_count, dtype=DTYPE_PY)
|
223
|
+
for i in range(target_count):
|
224
|
+
path_lengths[i] = pqueue.Elements[termination_nodes[i]].key
|
225
|
+
|
226
|
+
# cleanup
|
227
|
+
pq.free_pqueue(&pqueue)
|
228
|
+
|
229
|
+
return path_lengths
|
230
|
+
|
231
|
+
|
103
232
|
cpdef cnp.ndarray compute_sssp_w_path(
|
104
233
|
cnp.uint32_t[::1] csr_indptr,
|
105
234
|
cnp.uint32_t[::1] csr_indices,
|
@@ -164,8 +293,17 @@ cpdef cnp.ndarray compute_sssp_w_path(
|
|
164
293
|
<size_t>csr_indptr[tail_vert_idx + 1]):
|
165
294
|
|
166
295
|
head_vert_idx = <size_t>csr_indices[idx]
|
296
|
+
|
297
|
+
# prefetch next iteration data to improve cache performance
|
298
|
+
if idx + 1 < <size_t>csr_indptr[tail_vert_idx + 1]:
|
299
|
+
prefetch_hint(<char*>&csr_indices[idx + 1], PREFETCH_T0)
|
300
|
+
prefetch_hint(<char*>&csr_data[idx + 1], PREFETCH_T0)
|
301
|
+
|
167
302
|
vert_state = pqueue.Elements[head_vert_idx].state
|
168
303
|
if vert_state != SCANNED:
|
304
|
+
# prefetch priority queue element data for the vertex
|
305
|
+
prefetch_hint(<char*>&pqueue.Elements[head_vert_idx], PREFETCH_T0)
|
306
|
+
|
169
307
|
head_vert_val = tail_vert_val + csr_data[idx]
|
170
308
|
if vert_state == UNLABELED:
|
171
309
|
pq.insert(&pqueue, head_vert_idx, head_vert_val)
|
@@ -183,6 +321,115 @@ cpdef cnp.ndarray compute_sssp_w_path(
|
|
183
321
|
return path_lengths
|
184
322
|
|
185
323
|
|
324
|
+
cpdef cnp.ndarray compute_sssp_w_path_early_termination(
|
325
|
+
cnp.uint32_t[::1] csr_indptr,
|
326
|
+
cnp.uint32_t[::1] csr_indices,
|
327
|
+
DTYPE_t[::1] csr_data,
|
328
|
+
cnp.uint32_t[::1] predecessor,
|
329
|
+
cnp.uint32_t[::1] termination_nodes,
|
330
|
+
int source_vert_idx,
|
331
|
+
int vertex_count,
|
332
|
+
int heap_length):
|
333
|
+
"""
|
334
|
+
Compute single-source shortest path with path tracking and early termination.
|
335
|
+
Parameters
|
336
|
+
----------
|
337
|
+
csr_indices : cnp.uint32_t[::1]
|
338
|
+
indices in the CSR format
|
339
|
+
csr_indptr : cnp.uint32_t[::1]
|
340
|
+
pointers in the CSR format
|
341
|
+
csr_data : DTYPE_t[::1]
|
342
|
+
data (edge weights) in the CSR format
|
343
|
+
predecessor : cnp.uint32_t[::1]
|
344
|
+
array of indices, one for each vertex of the graph. Each vertex'
|
345
|
+
entry contains the index of its predecessor in a path from the
|
346
|
+
source, through the graph.
|
347
|
+
termination_nodes : cnp.uint32_t[::1]
|
348
|
+
target node indices for early termination
|
349
|
+
source_vert_idx : int
|
350
|
+
source vertex index
|
351
|
+
vertex_count : int
|
352
|
+
vertex count
|
353
|
+
heap_length : int
|
354
|
+
heap length
|
355
|
+
|
356
|
+
Returns
|
357
|
+
-------
|
358
|
+
path_lengths : cnp.ndarray
|
359
|
+
shortest path length for each termination node
|
360
|
+
"""
|
361
|
+
|
362
|
+
cdef:
|
363
|
+
size_t tail_vert_idx, head_vert_idx, idx, i
|
364
|
+
DTYPE_t tail_vert_val, head_vert_val
|
365
|
+
pq.PriorityQueue pqueue
|
366
|
+
ElementState vert_state
|
367
|
+
size_t source = <size_t>source_vert_idx
|
368
|
+
size_t target_count = termination_nodes.shape[0]
|
369
|
+
size_t visited_targets = 0
|
370
|
+
size_t iteration_count = 0
|
371
|
+
size_t check_frequency = 16
|
372
|
+
|
373
|
+
with nogil:
|
374
|
+
|
375
|
+
# initialization of the heap elements
|
376
|
+
# all nodes have INFINITY key and UNLABELED state
|
377
|
+
pq.init_pqueue(&pqueue, <size_t>heap_length, <size_t>vertex_count)
|
378
|
+
|
379
|
+
# the key is set to zero for the source vertex,
|
380
|
+
# which is inserted into the heap
|
381
|
+
pq.insert(&pqueue, source, 0.0)
|
382
|
+
|
383
|
+
# main loop
|
384
|
+
while pqueue.size > 0:
|
385
|
+
tail_vert_idx = pq.extract_min(&pqueue)
|
386
|
+
tail_vert_val = pqueue.Elements[tail_vert_idx].key
|
387
|
+
|
388
|
+
# check for early termination every check_frequency iterations
|
389
|
+
iteration_count += 1
|
390
|
+
if iteration_count % check_frequency == 0:
|
391
|
+
visited_targets = 0
|
392
|
+
for i in range(target_count):
|
393
|
+
if pqueue.Elements[termination_nodes[i]].state == SCANNED:
|
394
|
+
visited_targets += 1
|
395
|
+
if visited_targets == target_count:
|
396
|
+
break
|
397
|
+
|
398
|
+
# loop on outgoing edges
|
399
|
+
for idx in range(<size_t>csr_indptr[tail_vert_idx],
|
400
|
+
<size_t>csr_indptr[tail_vert_idx + 1]):
|
401
|
+
|
402
|
+
head_vert_idx = <size_t>csr_indices[idx]
|
403
|
+
|
404
|
+
# prefetch next iteration data to improve cache performance
|
405
|
+
if idx + 1 < <size_t>csr_indptr[tail_vert_idx + 1]:
|
406
|
+
prefetch_hint(<char*>&csr_indices[idx + 1], PREFETCH_T0)
|
407
|
+
prefetch_hint(<char*>&csr_data[idx + 1], PREFETCH_T0)
|
408
|
+
|
409
|
+
vert_state = pqueue.Elements[head_vert_idx].state
|
410
|
+
if vert_state != SCANNED:
|
411
|
+
# prefetch priority queue element data for the vertex
|
412
|
+
prefetch_hint(<char*>&pqueue.Elements[head_vert_idx], PREFETCH_T0)
|
413
|
+
|
414
|
+
head_vert_val = tail_vert_val + csr_data[idx]
|
415
|
+
if vert_state == UNLABELED:
|
416
|
+
pq.insert(&pqueue, head_vert_idx, head_vert_val)
|
417
|
+
predecessor[head_vert_idx] = tail_vert_idx
|
418
|
+
elif pqueue.Elements[head_vert_idx].key > head_vert_val:
|
419
|
+
pq.decrease_key(&pqueue, head_vert_idx, head_vert_val)
|
420
|
+
predecessor[head_vert_idx] = tail_vert_idx
|
421
|
+
|
422
|
+
# copy only the termination nodes' results into a numpy array
|
423
|
+
cdef cnp.ndarray path_lengths = np.empty(target_count, dtype=DTYPE_PY)
|
424
|
+
for i in range(target_count):
|
425
|
+
path_lengths[i] = pqueue.Elements[termination_nodes[i]].key
|
426
|
+
|
427
|
+
# cleanup
|
428
|
+
pq.free_pqueue(&pqueue)
|
429
|
+
|
430
|
+
return path_lengths
|
431
|
+
|
432
|
+
|
186
433
|
cpdef cnp.ndarray compute_stsp(
|
187
434
|
cnp.uint32_t[::1] csc_indptr,
|
188
435
|
cnp.uint32_t[::1] csc_indices,
|
@@ -242,8 +489,17 @@ cpdef cnp.ndarray compute_stsp(
|
|
242
489
|
<size_t>csc_indptr[head_vert_idx + 1]):
|
243
490
|
|
244
491
|
tail_vert_idx = <size_t>csc_indices[idx]
|
492
|
+
|
493
|
+
# prefetch next iteration data to improve cache performance
|
494
|
+
if idx + 1 < <size_t>csc_indptr[head_vert_idx + 1]:
|
495
|
+
prefetch_hint(<char*>&csc_indices[idx + 1], PREFETCH_T0)
|
496
|
+
prefetch_hint(<char*>&csc_data[idx + 1], PREFETCH_T0)
|
497
|
+
|
245
498
|
vert_state = pqueue.Elements[tail_vert_idx].state
|
246
499
|
if vert_state != SCANNED:
|
500
|
+
# prefetch priority queue element data for the vertex
|
501
|
+
prefetch_hint(<char*>&pqueue.Elements[tail_vert_idx], PREFETCH_T0)
|
502
|
+
|
247
503
|
tail_vert_val = head_vert_val + csc_data[idx]
|
248
504
|
if vert_state == UNLABELED:
|
249
505
|
pq.insert(&pqueue, tail_vert_idx, tail_vert_val)
|
@@ -319,8 +575,17 @@ cpdef cnp.ndarray compute_stsp_w_path(
|
|
319
575
|
<size_t>csc_indptr[head_vert_idx + 1]):
|
320
576
|
|
321
577
|
tail_vert_idx = <size_t>csc_indices[idx]
|
578
|
+
|
579
|
+
# prefetch next iteration data to improve cache performance
|
580
|
+
if idx + 1 < <size_t>csc_indptr[head_vert_idx + 1]:
|
581
|
+
prefetch_hint(<char*>&csc_indices[idx + 1], PREFETCH_T0)
|
582
|
+
prefetch_hint(<char*>&csc_data[idx + 1], PREFETCH_T0)
|
583
|
+
|
322
584
|
vert_state = pqueue.Elements[tail_vert_idx].state
|
323
585
|
if vert_state != SCANNED:
|
586
|
+
# prefetch priority queue element data for the vertex
|
587
|
+
prefetch_hint(<char*>&pqueue.Elements[tail_vert_idx], PREFETCH_T0)
|
588
|
+
|
324
589
|
tail_vert_val = head_vert_val + csc_data[idx]
|
325
590
|
if vert_state == UNLABELED:
|
326
591
|
pq.insert(&pqueue, tail_vert_idx, tail_vert_val)
|
@@ -338,6 +603,216 @@ cpdef cnp.ndarray compute_stsp_w_path(
|
|
338
603
|
return path_lengths
|
339
604
|
|
340
605
|
|
606
|
+
cpdef cnp.ndarray compute_stsp_early_termination(
|
607
|
+
cnp.uint32_t[::1] csc_indptr,
|
608
|
+
cnp.uint32_t[::1] csc_indices,
|
609
|
+
DTYPE_t[::1] csc_data,
|
610
|
+
cnp.uint32_t[::1] termination_nodes,
|
611
|
+
int target_vert_idx,
|
612
|
+
int vertex_count,
|
613
|
+
int heap_length):
|
614
|
+
"""
|
615
|
+
Compute single-target shortest path with early termination when target
|
616
|
+
nodes are reached.
|
617
|
+
Parameters
|
618
|
+
----------
|
619
|
+
csc_indices : cnp.uint32_t[::1]
|
620
|
+
indices in the CSC format
|
621
|
+
csc_indptr : cnp.uint32_t[::1]
|
622
|
+
pointers in the CSC format
|
623
|
+
csc_data : DTYPE_t[::1]
|
624
|
+
data (edge weights) in the CSC format
|
625
|
+
termination_nodes : cnp.uint32_t[::1]
|
626
|
+
target node indices for early termination
|
627
|
+
target_vert_idx : int
|
628
|
+
target vertex index
|
629
|
+
vertex_count : int
|
630
|
+
vertex count
|
631
|
+
heap_length : int
|
632
|
+
heap length
|
633
|
+
|
634
|
+
Returns
|
635
|
+
-------
|
636
|
+
path_lengths : cnp.ndarray
|
637
|
+
shortest path length for each termination node
|
638
|
+
"""
|
639
|
+
|
640
|
+
cdef:
|
641
|
+
size_t tail_vert_idx, head_vert_idx, idx, i
|
642
|
+
DTYPE_t tail_vert_val, head_vert_val
|
643
|
+
pq.PriorityQueue pqueue
|
644
|
+
ElementState vert_state
|
645
|
+
size_t target = <size_t>target_vert_idx
|
646
|
+
size_t target_count = termination_nodes.shape[0]
|
647
|
+
size_t visited_targets = 0
|
648
|
+
size_t iteration_count = 0
|
649
|
+
size_t check_frequency = 16
|
650
|
+
|
651
|
+
with nogil:
|
652
|
+
|
653
|
+
# initialization of the heap elements
|
654
|
+
# all nodes have INFINITY key and UNLABELED state
|
655
|
+
pq.init_pqueue(&pqueue, <size_t>heap_length, <size_t>vertex_count)
|
656
|
+
|
657
|
+
# the key is set to zero for the target vertex,
|
658
|
+
# which is inserted into the heap
|
659
|
+
pq.insert(&pqueue, target, 0.0)
|
660
|
+
|
661
|
+
# main loop
|
662
|
+
while pqueue.size > 0:
|
663
|
+
head_vert_idx = pq.extract_min(&pqueue)
|
664
|
+
head_vert_val = pqueue.Elements[head_vert_idx].key
|
665
|
+
|
666
|
+
# check for early termination every check_frequency iterations
|
667
|
+
iteration_count += 1
|
668
|
+
if iteration_count % check_frequency == 0:
|
669
|
+
visited_targets = 0
|
670
|
+
for i in range(target_count):
|
671
|
+
if pqueue.Elements[termination_nodes[i]].state == SCANNED:
|
672
|
+
visited_targets += 1
|
673
|
+
if visited_targets == target_count:
|
674
|
+
break
|
675
|
+
|
676
|
+
# loop on incoming edges
|
677
|
+
for idx in range(<size_t>csc_indptr[head_vert_idx],
|
678
|
+
<size_t>csc_indptr[head_vert_idx + 1]):
|
679
|
+
|
680
|
+
tail_vert_idx = <size_t>csc_indices[idx]
|
681
|
+
|
682
|
+
# prefetch next iteration data to improve cache performance
|
683
|
+
if idx + 1 < <size_t>csc_indptr[head_vert_idx + 1]:
|
684
|
+
prefetch_hint(<char*>&csc_indices[idx + 1], PREFETCH_T0)
|
685
|
+
prefetch_hint(<char*>&csc_data[idx + 1], PREFETCH_T0)
|
686
|
+
|
687
|
+
vert_state = pqueue.Elements[tail_vert_idx].state
|
688
|
+
if vert_state != SCANNED:
|
689
|
+
# prefetch priority queue element data for the vertex
|
690
|
+
prefetch_hint(<char*>&pqueue.Elements[tail_vert_idx], PREFETCH_T0)
|
691
|
+
|
692
|
+
tail_vert_val = head_vert_val + csc_data[idx]
|
693
|
+
if vert_state == UNLABELED:
|
694
|
+
pq.insert(&pqueue, tail_vert_idx, tail_vert_val)
|
695
|
+
elif pqueue.Elements[tail_vert_idx].key > tail_vert_val:
|
696
|
+
pq.decrease_key(&pqueue, tail_vert_idx, tail_vert_val)
|
697
|
+
|
698
|
+
# copy only the termination nodes' results into a numpy array
|
699
|
+
cdef cnp.ndarray path_lengths = np.empty(target_count, dtype=DTYPE_PY)
|
700
|
+
for i in range(target_count):
|
701
|
+
path_lengths[i] = pqueue.Elements[termination_nodes[i]].key
|
702
|
+
|
703
|
+
# cleanup
|
704
|
+
pq.free_pqueue(&pqueue)
|
705
|
+
|
706
|
+
return path_lengths
|
707
|
+
|
708
|
+
|
709
|
+
cpdef cnp.ndarray compute_stsp_w_path_early_termination(
|
710
|
+
cnp.uint32_t[::1] csc_indptr,
|
711
|
+
cnp.uint32_t[::1] csc_indices,
|
712
|
+
DTYPE_t[::1] csc_data,
|
713
|
+
cnp.uint32_t[::1] successor,
|
714
|
+
cnp.uint32_t[::1] termination_nodes,
|
715
|
+
int target_vert_idx,
|
716
|
+
int vertex_count,
|
717
|
+
int heap_length):
|
718
|
+
"""
|
719
|
+
Compute single-target shortest path with path tracking and early termination.
|
720
|
+
Parameters
|
721
|
+
----------
|
722
|
+
csc_indices : cnp.uint32_t[::1]
|
723
|
+
Indices in the CSC format.
|
724
|
+
csc_indptr : cnp.uint32_t[::1]
|
725
|
+
Pointers in the CSC format.
|
726
|
+
csc_data : DTYPE_t[::1]
|
727
|
+
Data (edge weights) in the CSC format.
|
728
|
+
successor : cnp.uint32_t[::1]
|
729
|
+
Array of successor indices for path reconstruction.
|
730
|
+
termination_nodes : cnp.uint32_t[::1]
|
731
|
+
target node indices for early termination
|
732
|
+
target_vert_idx : int
|
733
|
+
Target vertex index.
|
734
|
+
vertex_count : int
|
735
|
+
Vertex count.
|
736
|
+
heap_length : int
|
737
|
+
heap_length.
|
738
|
+
|
739
|
+
Returns
|
740
|
+
-------
|
741
|
+
path_lengths : cnp.ndarray
|
742
|
+
shortest path length for each termination node
|
743
|
+
"""
|
744
|
+
|
745
|
+
cdef:
|
746
|
+
size_t tail_vert_idx, head_vert_idx, idx, i
|
747
|
+
DTYPE_t tail_vert_val, head_vert_val
|
748
|
+
pq.PriorityQueue pqueue
|
749
|
+
ElementState vert_state
|
750
|
+
size_t target = <size_t>target_vert_idx
|
751
|
+
size_t target_count = termination_nodes.shape[0]
|
752
|
+
size_t visited_targets = 0
|
753
|
+
size_t iteration_count = 0
|
754
|
+
size_t check_frequency = 16
|
755
|
+
|
756
|
+
with nogil:
|
757
|
+
|
758
|
+
# initialization of the heap elements
|
759
|
+
# all nodes have INFINITY key and UNLABELED state
|
760
|
+
pq.init_pqueue(&pqueue, <size_t>heap_length, <size_t>vertex_count)
|
761
|
+
|
762
|
+
# the key is set to zero for the target vertex,
|
763
|
+
# which is inserted into the heap
|
764
|
+
pq.insert(&pqueue, target, 0.0)
|
765
|
+
|
766
|
+
# main loop
|
767
|
+
while pqueue.size > 0:
|
768
|
+
head_vert_idx = pq.extract_min(&pqueue)
|
769
|
+
head_vert_val = pqueue.Elements[head_vert_idx].key
|
770
|
+
|
771
|
+
# check for early termination every check_frequency iterations
|
772
|
+
iteration_count += 1
|
773
|
+
if iteration_count % check_frequency == 0:
|
774
|
+
visited_targets = 0
|
775
|
+
for i in range(target_count):
|
776
|
+
if pqueue.Elements[termination_nodes[i]].state == SCANNED:
|
777
|
+
visited_targets += 1
|
778
|
+
if visited_targets == target_count:
|
779
|
+
break
|
780
|
+
|
781
|
+
# loop on incoming edges
|
782
|
+
for idx in range(<size_t>csc_indptr[head_vert_idx],
|
783
|
+
<size_t>csc_indptr[head_vert_idx + 1]):
|
784
|
+
|
785
|
+
tail_vert_idx = <size_t>csc_indices[idx]
|
786
|
+
|
787
|
+
# prefetch next iteration data to improve cache performance
|
788
|
+
if idx + 1 < <size_t>csc_indptr[head_vert_idx + 1]:
|
789
|
+
prefetch_hint(<char*>&csc_indices[idx + 1], PREFETCH_T0)
|
790
|
+
prefetch_hint(<char*>&csc_data[idx + 1], PREFETCH_T0)
|
791
|
+
|
792
|
+
vert_state = pqueue.Elements[tail_vert_idx].state
|
793
|
+
if vert_state != SCANNED:
|
794
|
+
# prefetch priority queue element data for the vertex
|
795
|
+
prefetch_hint(<char*>&pqueue.Elements[tail_vert_idx], PREFETCH_T0)
|
796
|
+
|
797
|
+
tail_vert_val = head_vert_val + csc_data[idx]
|
798
|
+
if vert_state == UNLABELED:
|
799
|
+
pq.insert(&pqueue, tail_vert_idx, tail_vert_val)
|
800
|
+
successor[tail_vert_idx] = head_vert_idx
|
801
|
+
elif pqueue.Elements[tail_vert_idx].key > tail_vert_val:
|
802
|
+
pq.decrease_key(&pqueue, tail_vert_idx, tail_vert_val)
|
803
|
+
successor[tail_vert_idx] = head_vert_idx
|
804
|
+
|
805
|
+
# copy only the termination nodes' results into a numpy array
|
806
|
+
cdef cnp.ndarray path_lengths = np.empty(target_count, dtype=DTYPE_PY)
|
807
|
+
for i in range(target_count):
|
808
|
+
path_lengths[i] = pqueue.Elements[termination_nodes[i]].key
|
809
|
+
|
810
|
+
# cleanup
|
811
|
+
pq.free_pqueue(&pqueue)
|
812
|
+
|
813
|
+
return path_lengths
|
814
|
+
|
815
|
+
|
341
816
|
# ============================================================================ #
|
342
817
|
# tests #
|
343
818
|
# ============================================================================ #
|
@@ -498,6 +973,56 @@ cpdef compute_stsp_02():
|
|
498
973
|
assert np.allclose(path_lengths_ref, path_lengths)
|
499
974
|
|
500
975
|
|
976
|
+
cpdef compute_sssp_early_termination_01():
|
977
|
+
"""
|
978
|
+
Test SSSP early termination on Braess-like network.
|
979
|
+
"""
|
980
|
+
|
981
|
+
csr_indptr, csr_indices, csr_data = generate_braess_network_csr()
|
982
|
+
|
983
|
+
# from vertex 0, stop when vertices 1 and 3 are reached
|
984
|
+
target_nodes = np.array([1, 3], dtype=np.uint32)
|
985
|
+
path_lengths = compute_sssp_early_termination(
|
986
|
+
csr_indptr, csr_indices, csr_data, target_nodes, 0, 4, 4
|
987
|
+
)
|
988
|
+
|
989
|
+
# should return path lengths only for termination nodes [1, 3]
|
990
|
+
path_lengths_ref = np.array([1., 2.], dtype=DTYPE_PY) # lengths to nodes 1 and 3
|
991
|
+
assert np.allclose(path_lengths_ref, path_lengths)
|
992
|
+
|
993
|
+
# test with path tracking
|
994
|
+
predecessor = np.arange(0, 4, dtype=np.uint32)
|
995
|
+
path_lengths_w_path = compute_sssp_w_path_early_termination(
|
996
|
+
csr_indptr, csr_indices, csr_data, predecessor, target_nodes, 0, 4, 4
|
997
|
+
)
|
998
|
+
assert np.allclose(path_lengths_ref, path_lengths_w_path)
|
999
|
+
|
1000
|
+
|
1001
|
+
cpdef compute_stsp_early_termination_01():
|
1002
|
+
"""
|
1003
|
+
Test STSP early termination on Braess-like network.
|
1004
|
+
"""
|
1005
|
+
|
1006
|
+
csc_indptr, csc_indices, csc_data = generate_braess_network_csc()
|
1007
|
+
|
1008
|
+
# to vertex 3, stop when vertices 0 and 2 are reached
|
1009
|
+
target_nodes = np.array([0, 2], dtype=np.uint32)
|
1010
|
+
path_lengths = compute_stsp_early_termination(
|
1011
|
+
csc_indptr, csc_indices, csc_data, target_nodes, 3, 4, 4
|
1012
|
+
)
|
1013
|
+
|
1014
|
+
# should return path lengths only for termination nodes [0, 2]
|
1015
|
+
path_lengths_ref = np.array([2., 1.], dtype=DTYPE_PY) # lengths to nodes 0 and 2
|
1016
|
+
assert np.allclose(path_lengths_ref, path_lengths)
|
1017
|
+
|
1018
|
+
# test with path tracking
|
1019
|
+
successor = np.arange(0, 4, dtype=np.uint32)
|
1020
|
+
path_lengths_w_path = compute_stsp_w_path_early_termination(
|
1021
|
+
csc_indptr, csc_indices, csc_data, successor, target_nodes, 3, 4, 4
|
1022
|
+
)
|
1023
|
+
assert np.allclose(path_lengths_ref, path_lengths_w_path)
|
1024
|
+
|
1025
|
+
|
501
1026
|
# author : Francois Pacull
|
502
1027
|
# copyright : Architecture & Performance
|
503
1028
|
# email: francois.pacull@architecture-performance.fr
|
edsger/path.py
CHANGED
@@ -17,8 +17,12 @@ from edsger.commons import (
|
|
17
17
|
from edsger.dijkstra import (
|
18
18
|
compute_sssp,
|
19
19
|
compute_sssp_w_path,
|
20
|
+
compute_sssp_early_termination,
|
21
|
+
compute_sssp_w_path_early_termination,
|
20
22
|
compute_stsp,
|
21
23
|
compute_stsp_w_path,
|
24
|
+
compute_stsp_early_termination,
|
25
|
+
compute_stsp_w_path_early_termination,
|
22
26
|
)
|
23
27
|
from edsger.path_tracking import compute_path
|
24
28
|
from edsger.spiess_florian import compute_SF_in
|
@@ -285,6 +289,7 @@ class Dijkstra:
|
|
285
289
|
return_inf=True,
|
286
290
|
return_series=False,
|
287
291
|
heap_length_ratio=1.0,
|
292
|
+
termination_nodes=None,
|
288
293
|
):
|
289
294
|
"""
|
290
295
|
Runs shortest path algorithm between a given vertex and all other vertices in the graph.
|
@@ -303,6 +308,11 @@ class Dijkstra:
|
|
303
308
|
as values.
|
304
309
|
heap_length_ratio : float, optional (default=1.0)
|
305
310
|
The heap length as a fraction of the number of vertices. Must be in the range (0, 1].
|
311
|
+
termination_nodes : array-like, optional (default=None)
|
312
|
+
List or array of vertex indices for early termination. For SSSP (orientation='out'),
|
313
|
+
these are target nodes to reach. For STSP (orientation='in'), these are source nodes
|
314
|
+
to find paths from. When provided, the algorithm stops once all specified nodes have
|
315
|
+
been processed, potentially improving performance. If None, runs to completion.
|
306
316
|
|
307
317
|
Returns
|
308
318
|
-------
|
@@ -355,49 +365,140 @@ class Dijkstra:
|
|
355
365
|
)
|
356
366
|
heap_length = int(np.rint(heap_length_ratio * self._n_vertices))
|
357
367
|
|
368
|
+
# validate and process termination_nodes
|
369
|
+
termination_nodes_array = None
|
370
|
+
if termination_nodes is not None:
|
371
|
+
try:
|
372
|
+
termination_nodes_array = np.array(termination_nodes, dtype=np.uint32)
|
373
|
+
except (ValueError, TypeError):
|
374
|
+
raise TypeError(
|
375
|
+
"argument 'termination_nodes' must be array-like of integers"
|
376
|
+
)
|
377
|
+
|
378
|
+
if termination_nodes_array.ndim != 1:
|
379
|
+
raise ValueError("argument 'termination_nodes' must be 1-dimensional")
|
380
|
+
|
381
|
+
if len(termination_nodes_array) == 0:
|
382
|
+
raise ValueError("argument 'termination_nodes' must not be empty")
|
383
|
+
|
384
|
+
# handle vertex permutation if needed
|
385
|
+
if self._permute:
|
386
|
+
termination_nodes_permuted = []
|
387
|
+
for termination_node in termination_nodes_array:
|
388
|
+
if termination_node not in self._permutation.vert_idx_old.values:
|
389
|
+
raise ValueError(
|
390
|
+
f"termination node {termination_node} not found in graph"
|
391
|
+
)
|
392
|
+
termination_node_new = self._permutation.loc[
|
393
|
+
self._permutation.vert_idx_old == termination_node,
|
394
|
+
"vert_idx_new",
|
395
|
+
].iloc[0]
|
396
|
+
termination_nodes_permuted.append(termination_node_new)
|
397
|
+
termination_nodes_array = np.array(
|
398
|
+
termination_nodes_permuted, dtype=np.uint32
|
399
|
+
)
|
400
|
+
else:
|
401
|
+
# validate that termination nodes exist
|
402
|
+
if np.any(termination_nodes_array >= self._n_vertices) or np.any(
|
403
|
+
termination_nodes_array < 0
|
404
|
+
):
|
405
|
+
raise ValueError(
|
406
|
+
"termination_nodes contains invalid vertex indices"
|
407
|
+
)
|
408
|
+
|
358
409
|
# compute path length
|
359
410
|
if not path_tracking:
|
360
411
|
self._path_links = None
|
361
|
-
if
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
412
|
+
if termination_nodes_array is not None:
|
413
|
+
# use early termination algorithms
|
414
|
+
if self._orientation == "in":
|
415
|
+
path_length_values = compute_stsp_early_termination(
|
416
|
+
self.__indptr,
|
417
|
+
self.__indices,
|
418
|
+
self.__edge_weights,
|
419
|
+
termination_nodes_array,
|
420
|
+
vertex_new,
|
421
|
+
self._n_vertices,
|
422
|
+
heap_length,
|
423
|
+
)
|
424
|
+
else:
|
425
|
+
path_length_values = compute_sssp_early_termination(
|
426
|
+
self.__indptr,
|
427
|
+
self.__indices,
|
428
|
+
self.__edge_weights,
|
429
|
+
termination_nodes_array,
|
430
|
+
vertex_new,
|
431
|
+
self._n_vertices,
|
432
|
+
heap_length,
|
433
|
+
)
|
370
434
|
else:
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
435
|
+
# use standard algorithms
|
436
|
+
if self._orientation == "in":
|
437
|
+
path_length_values = compute_stsp(
|
438
|
+
self.__indptr,
|
439
|
+
self.__indices,
|
440
|
+
self.__edge_weights,
|
441
|
+
vertex_new,
|
442
|
+
self._n_vertices,
|
443
|
+
heap_length,
|
444
|
+
)
|
445
|
+
else:
|
446
|
+
path_length_values = compute_sssp(
|
447
|
+
self.__indptr,
|
448
|
+
self.__indices,
|
449
|
+
self.__edge_weights,
|
450
|
+
vertex_new,
|
451
|
+
self._n_vertices,
|
452
|
+
heap_length,
|
453
|
+
)
|
379
454
|
else:
|
380
455
|
self._path_links = np.arange(0, self._n_vertices, dtype=np.uint32)
|
381
|
-
if
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
456
|
+
if termination_nodes_array is not None:
|
457
|
+
# use early termination algorithms with path tracking
|
458
|
+
if self._orientation == "in":
|
459
|
+
path_length_values = compute_stsp_w_path_early_termination(
|
460
|
+
self.__indptr,
|
461
|
+
self.__indices,
|
462
|
+
self.__edge_weights,
|
463
|
+
self._path_links,
|
464
|
+
termination_nodes_array,
|
465
|
+
vertex_new,
|
466
|
+
self._n_vertices,
|
467
|
+
heap_length,
|
468
|
+
)
|
469
|
+
else:
|
470
|
+
path_length_values = compute_sssp_w_path_early_termination(
|
471
|
+
self.__indptr,
|
472
|
+
self.__indices,
|
473
|
+
self.__edge_weights,
|
474
|
+
self._path_links,
|
475
|
+
termination_nodes_array,
|
476
|
+
vertex_new,
|
477
|
+
self._n_vertices,
|
478
|
+
heap_length,
|
479
|
+
)
|
391
480
|
else:
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
481
|
+
# use standard algorithms with path tracking
|
482
|
+
if self._orientation == "in":
|
483
|
+
path_length_values = compute_stsp_w_path(
|
484
|
+
self.__indptr,
|
485
|
+
self.__indices,
|
486
|
+
self.__edge_weights,
|
487
|
+
self._path_links,
|
488
|
+
vertex_new,
|
489
|
+
self._n_vertices,
|
490
|
+
heap_length,
|
491
|
+
)
|
492
|
+
else:
|
493
|
+
path_length_values = compute_sssp_w_path(
|
494
|
+
self.__indptr,
|
495
|
+
self.__indices,
|
496
|
+
self.__edge_weights,
|
497
|
+
self._path_links,
|
498
|
+
vertex_new,
|
499
|
+
self._n_vertices,
|
500
|
+
heap_length,
|
501
|
+
)
|
401
502
|
|
402
503
|
if self._permute:
|
403
504
|
# permute back the path vertex indices
|
@@ -445,7 +546,7 @@ class Dijkstra:
|
|
445
546
|
|
446
547
|
# reorder path lengths
|
447
548
|
if return_series:
|
448
|
-
if self._permute:
|
549
|
+
if self._permute and termination_nodes_array is None:
|
449
550
|
self._permutation["path_length"] = path_length_values
|
450
551
|
path_lengths_df = self._permutation[
|
451
552
|
["vert_idx_old", "path_length"]
|
@@ -457,9 +558,16 @@ class Dijkstra:
|
|
457
558
|
path_lengths_series = pd.Series(path_length_values)
|
458
559
|
path_lengths_series.index.name = "vertex_idx"
|
459
560
|
path_lengths_series.name = "path_length"
|
561
|
+
if self._permute and termination_nodes_array is not None:
|
562
|
+
# For early termination with permutation, use original termination node indices
|
563
|
+
path_lengths_series.index = termination_nodes
|
460
564
|
|
461
565
|
return path_lengths_series
|
462
566
|
|
567
|
+
# For early termination, return results directly (already in correct order)
|
568
|
+
if termination_nodes_array is not None:
|
569
|
+
return path_length_values
|
570
|
+
|
463
571
|
if self._permute:
|
464
572
|
self._permutation["path_length"] = path_length_values
|
465
573
|
if return_inf:
|
Binary file
|
edsger/path_tracking.pyx
CHANGED
@@ -43,7 +43,7 @@ cpdef cnp.ndarray compute_path(cnp.uint32_t[::1] path_links, int vertex_idx):
|
|
43
43
|
cdef int _compute_path_first_pass(
|
44
44
|
cnp.uint32_t[::1] path_links,
|
45
45
|
int vertex_idx
|
46
|
-
) nogil
|
46
|
+
) noexcept nogil:
|
47
47
|
"""Returns the path length.
|
48
48
|
"""
|
49
49
|
|
@@ -69,7 +69,7 @@ cdef void _compute_path_second_pass(
|
|
69
69
|
cnp.uint32_t[::1] path_links,
|
70
70
|
cnp.uint32_t[::1] path_vertices,
|
71
71
|
int vertex_idx
|
72
|
-
) nogil
|
72
|
+
) noexcept nogil:
|
73
73
|
"""Compute the sequence of vertices forming a path.
|
74
74
|
"""
|
75
75
|
cdef size_t i, j, k
|
Binary file
|
edsger/pq_4ary_dec_0b.pxd
CHANGED
@@ -17,13 +17,13 @@ cdef struct PriorityQueue:
|
|
17
17
|
size_t* A # array storing the binary tree
|
18
18
|
Element* Elements # array storing the elements
|
19
19
|
|
20
|
-
cdef void init_pqueue(PriorityQueue*, size_t, size_t) nogil
|
21
|
-
cdef void free_pqueue(PriorityQueue*) nogil
|
22
|
-
cdef void insert(PriorityQueue*, size_t, DTYPE_t) nogil
|
23
|
-
cdef DTYPE_t peek(PriorityQueue*) nogil
|
24
|
-
cdef size_t extract_min(PriorityQueue*) nogil
|
25
|
-
cdef bint is_empty(PriorityQueue*) nogil
|
26
|
-
cdef void decrease_key(PriorityQueue*, size_t, DTYPE_t) nogil
|
20
|
+
cdef void init_pqueue(PriorityQueue*, size_t, size_t) noexcept nogil
|
21
|
+
cdef void free_pqueue(PriorityQueue*) noexcept nogil
|
22
|
+
cdef void insert(PriorityQueue*, size_t, DTYPE_t) noexcept nogil
|
23
|
+
cdef DTYPE_t peek(PriorityQueue*) noexcept nogil
|
24
|
+
cdef size_t extract_min(PriorityQueue*) noexcept nogil
|
25
|
+
cdef bint is_empty(PriorityQueue*) noexcept nogil
|
26
|
+
cdef void decrease_key(PriorityQueue*, size_t, DTYPE_t) noexcept nogil
|
27
27
|
cdef cnp.ndarray copy_keys_to_numpy(PriorityQueue*, size_t)
|
28
28
|
|
29
29
|
|
edsger/pq_4ary_dec_0b.pyx
CHANGED
@@ -47,7 +47,7 @@ from edsger.commons cimport (
|
|
47
47
|
cdef void init_pqueue(
|
48
48
|
PriorityQueue* pqueue,
|
49
49
|
size_t heap_length,
|
50
|
-
size_t element_count) nogil
|
50
|
+
size_t element_count) noexcept nogil:
|
51
51
|
"""
|
52
52
|
Initialize the priority queue.
|
53
53
|
|
@@ -77,7 +77,7 @@ cdef void init_pqueue(
|
|
77
77
|
|
78
78
|
cdef void _initialize_element(
|
79
79
|
PriorityQueue* pqueue,
|
80
|
-
size_t element_idx) nogil
|
80
|
+
size_t element_idx) noexcept nogil:
|
81
81
|
"""
|
82
82
|
Initialize a single element.
|
83
83
|
|
@@ -92,7 +92,7 @@ cdef void _initialize_element(
|
|
92
92
|
|
93
93
|
|
94
94
|
cdef void free_pqueue(
|
95
|
-
PriorityQueue* pqueue) nogil
|
95
|
+
PriorityQueue* pqueue) noexcept nogil:
|
96
96
|
"""
|
97
97
|
Free the priority queue.
|
98
98
|
|
@@ -107,7 +107,7 @@ cdef void free_pqueue(
|
|
107
107
|
cdef void insert(
|
108
108
|
PriorityQueue* pqueue,
|
109
109
|
size_t element_idx,
|
110
|
-
DTYPE_t key) nogil
|
110
|
+
DTYPE_t key) noexcept nogil:
|
111
111
|
"""
|
112
112
|
Insert an element into the heap and reorder the heap.
|
113
113
|
|
@@ -134,7 +134,7 @@ cdef void insert(
|
|
134
134
|
cdef void decrease_key(
|
135
135
|
PriorityQueue* pqueue,
|
136
136
|
size_t element_idx,
|
137
|
-
DTYPE_t key_new) nogil
|
137
|
+
DTYPE_t key_new) noexcept nogil:
|
138
138
|
"""
|
139
139
|
Decrease the key of a element in the heap, given its element index.
|
140
140
|
|
@@ -154,7 +154,7 @@ cdef void decrease_key(
|
|
154
154
|
key_new)
|
155
155
|
|
156
156
|
|
157
|
-
cdef DTYPE_t peek(PriorityQueue* pqueue) nogil
|
157
|
+
cdef DTYPE_t peek(PriorityQueue* pqueue) noexcept nogil:
|
158
158
|
"""
|
159
159
|
Find heap min key.
|
160
160
|
|
@@ -174,7 +174,7 @@ cdef DTYPE_t peek(PriorityQueue* pqueue) nogil noexcept:
|
|
174
174
|
return pqueue.Elements[pqueue.A[0]].key
|
175
175
|
|
176
176
|
|
177
|
-
cdef bint is_empty(PriorityQueue* pqueue) nogil
|
177
|
+
cdef bint is_empty(PriorityQueue* pqueue) noexcept nogil:
|
178
178
|
"""
|
179
179
|
Check if the heap is empty.
|
180
180
|
|
@@ -190,7 +190,7 @@ cdef bint is_empty(PriorityQueue* pqueue) nogil noexcept:
|
|
190
190
|
return isempty
|
191
191
|
|
192
192
|
|
193
|
-
cdef size_t extract_min(PriorityQueue* pqueue) nogil
|
193
|
+
cdef size_t extract_min(PriorityQueue* pqueue) noexcept nogil:
|
194
194
|
"""
|
195
195
|
Extract element with min keay from the heap,
|
196
196
|
and return its element index.
|
@@ -229,7 +229,7 @@ cdef size_t extract_min(PriorityQueue* pqueue) nogil noexcept:
|
|
229
229
|
cdef cnp.ndarray copy_keys_to_numpy(
|
230
230
|
PriorityQueue* pqueue,
|
231
231
|
size_t vertex_count
|
232
|
-
)
|
232
|
+
):
|
233
233
|
"""
|
234
234
|
Copy the keys into a numpy array.
|
235
235
|
|
@@ -261,7 +261,7 @@ cdef cnp.ndarray copy_keys_to_numpy(
|
|
261
261
|
cdef void _exchange_nodes(
|
262
262
|
PriorityQueue* pqueue,
|
263
263
|
size_t node_i,
|
264
|
-
size_t node_j) nogil
|
264
|
+
size_t node_j) noexcept nogil:
|
265
265
|
"""
|
266
266
|
Exchange two nodes in the heap.
|
267
267
|
|
@@ -286,7 +286,7 @@ cdef void _exchange_nodes(
|
|
286
286
|
|
287
287
|
cdef void _min_heapify(
|
288
288
|
PriorityQueue* pqueue,
|
289
|
-
size_t node_idx) nogil
|
289
|
+
size_t node_idx) noexcept nogil:
|
290
290
|
"""
|
291
291
|
Re-order sub-tree under a given node (given its node index)
|
292
292
|
until it satisfies the heap property.
|
@@ -363,7 +363,7 @@ cdef void _min_heapify(
|
|
363
363
|
cdef void _decrease_key_from_node_index(
|
364
364
|
PriorityQueue* pqueue,
|
365
365
|
size_t node_idx,
|
366
|
-
DTYPE_t key_new) nogil
|
366
|
+
DTYPE_t key_new) noexcept nogil:
|
367
367
|
"""
|
368
368
|
Decrease the key of an element in the heap, given its tree index.
|
369
369
|
|
@@ -594,7 +594,7 @@ cpdef is_empty_01():
|
|
594
594
|
assert is_empty(&pqueue) == 1
|
595
595
|
insert(&pqueue, 1, 3.0)
|
596
596
|
assert is_empty(&pqueue) == 0
|
597
|
-
|
597
|
+
_ = extract_min(&pqueue)
|
598
598
|
assert is_empty(&pqueue) == 1
|
599
599
|
|
600
600
|
free_pqueue(&pqueue)
|
@@ -652,7 +652,7 @@ cpdef decrease_key_01():
|
|
652
652
|
free_pqueue(&pqueue)
|
653
653
|
|
654
654
|
|
655
|
-
cdef void heapsort(DTYPE_t[::1] values_in, DTYPE_t[::1] values_out) nogil:
|
655
|
+
cdef void heapsort(DTYPE_t[::1] values_in, DTYPE_t[::1] values_out) noexcept nogil:
|
656
656
|
"""
|
657
657
|
Heap sort by inerting all the values into the priority queue,
|
658
658
|
and extracting them.
|
@@ -676,8 +676,6 @@ cpdef sort_01(int n, random_seed=124):
|
|
676
676
|
and with the numpy default sort function, compare the results.
|
677
677
|
"""
|
678
678
|
|
679
|
-
cdef PriorityQueue pqueue
|
680
|
-
|
681
679
|
rng = np.random.default_rng(random_seed)
|
682
680
|
values_in = rng.random(size=n)
|
683
681
|
values_out = np.empty_like(values_in, dtype=DTYPE_PY)
|
edsger/prefetch_compat.h
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#ifndef PREFETCH_COMPAT_H
|
2
|
+
#define PREFETCH_COMPAT_H
|
3
|
+
|
4
|
+
// Cross-platform memory prefetching compatibility header
|
5
|
+
|
6
|
+
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm64__)
|
7
|
+
// ARM64 platforms - use ARM-specific prefetch
|
8
|
+
#define prefetch_hint(addr, hint) __builtin_prefetch((const void*)(addr), 0, 3)
|
9
|
+
#define PREFETCH_T0 0
|
10
|
+
#elif defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86)
|
11
|
+
// x86/x64 platforms - use SSE intrinsics
|
12
|
+
#include <xmmintrin.h>
|
13
|
+
#define prefetch_hint(addr, hint) _mm_prefetch((const char*)(addr), hint)
|
14
|
+
#define PREFETCH_T0 _MM_HINT_T0
|
15
|
+
#else
|
16
|
+
// Other platforms - no-op (compile time optimization will remove calls)
|
17
|
+
#define prefetch_hint(addr, hint) ((void)0)
|
18
|
+
#define PREFETCH_T0 0
|
19
|
+
#endif
|
20
|
+
|
21
|
+
#endif // PREFETCH_COMPAT_H
|
Binary file
|
edsger/star.cp312-win32.pyd
CHANGED
Binary file
|
edsger/star.pyx
CHANGED
@@ -204,7 +204,7 @@ cdef void _coo_to_csr_uint32(
|
|
204
204
|
cnp.uint32_t [::1] Ax,
|
205
205
|
cnp.uint32_t [::1] Bp,
|
206
206
|
cnp.uint32_t [::1] Bj,
|
207
|
-
cnp.uint32_t [::1] Bx) nogil
|
207
|
+
cnp.uint32_t [::1] Bx) noexcept nogil:
|
208
208
|
|
209
209
|
cdef:
|
210
210
|
size_t i, row, dest
|
@@ -280,7 +280,7 @@ cpdef void _coo_to_csr_float64(
|
|
280
280
|
cnp.float64_t [::1] Ax,
|
281
281
|
cnp.uint32_t [::1] Bp,
|
282
282
|
cnp.uint32_t [::1] Bj,
|
283
|
-
cnp.float64_t [::1] Bx) nogil
|
283
|
+
cnp.float64_t [::1] Bx) noexcept nogil:
|
284
284
|
|
285
285
|
cdef:
|
286
286
|
size_t i, row, dest
|
@@ -318,7 +318,7 @@ cpdef void _coo_to_csc_float64(
|
|
318
318
|
cnp.float64_t [::1] Ax,
|
319
319
|
cnp.uint32_t [::1] Bp,
|
320
320
|
cnp.uint32_t [::1] Bi,
|
321
|
-
cnp.float64_t [::1] Bx) nogil
|
321
|
+
cnp.float64_t [::1] Bx) noexcept nogil:
|
322
322
|
|
323
323
|
cdef:
|
324
324
|
size_t i, col, dest
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: edsger
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.3
|
4
4
|
Summary: Graph algorithms in Cython.
|
5
5
|
Author-email: François Pacull <francois.pacull@architecture-performance.fr>
|
6
6
|
Maintainer-email: François Pacull <francois.pacull@architecture-performance.fr>
|
@@ -42,7 +42,7 @@ Dynamic: license-file
|
|
42
42
|
|
43
43
|

|
44
44
|
[](https://codecov.io/gh/aetperf/edsger)
|
45
|
-
[](https://pypi.org/project/edsger/)
|
45
|
+
[](https://pypi.org/project/edsger/)
|
46
46
|
[](https://pepy.tech/project/edsger)
|
47
47
|
[](https://pypi.org/project/edsger/)
|
48
48
|
[](https://github.com/psf/black)
|
@@ -70,20 +70,19 @@ from edsger.path import Dijkstra
|
|
70
70
|
edges = pd.DataFrame({
|
71
71
|
'tail': [0, 0, 1, 2, 2, 3],
|
72
72
|
'head': [1, 2, 2, 3, 4, 4],
|
73
|
-
'weight': [1, 4, 2, 1, 3, 1]
|
73
|
+
'weight': [1, 4, 2, 1.5, 3, 1]
|
74
74
|
})
|
75
75
|
edges
|
76
76
|
```
|
77
77
|
|
78
78
|
| | tail | head | weight |
|
79
79
|
|---:|-------:|-------:|---------:|
|
80
|
-
| 0 | 0 | 1 |
|
81
|
-
| 1 | 0 | 2 |
|
82
|
-
| 2 | 1 | 2 |
|
83
|
-
| 3 | 2 | 3 |
|
84
|
-
| 4 | 2 | 4 |
|
85
|
-
| 5 | 3 | 4 |
|
86
|
-
|
80
|
+
| 0 | 0 | 1 | 1.0 |
|
81
|
+
| 1 | 0 | 2 | 4.0 |
|
82
|
+
| 2 | 1 | 2 | 2.0 |
|
83
|
+
| 3 | 2 | 3 | 1.5 |
|
84
|
+
| 4 | 2 | 4 | 3.0 |
|
85
|
+
| 5 | 3 | 4 | 1.0 |
|
87
86
|
|
88
87
|
```python
|
89
88
|
# Initialize the Dijkstra object
|
@@ -94,10 +93,18 @@ shortest_paths = dijkstra.run(vertex_idx=0)
|
|
94
93
|
print("Shortest paths:", shortest_paths)
|
95
94
|
```
|
96
95
|
|
97
|
-
Shortest paths: [0.
|
96
|
+
Shortest paths: [0. 1. 3. 4.5 5.5]
|
98
97
|
|
99
98
|
We get the shortest paths from the source node 0 to all other nodes in the graph. The output is an array with the shortest path length to each node. A path length is the sum of the weights of the edges in the path.
|
100
99
|
|
100
|
+
## Why Use Edsger?
|
101
|
+
|
102
|
+
Edsger is designed to be **dataframe-friendly**, providing seamless integration with pandas workflows for graph algorithms. Also it is rather efficient. Our benchmarks on the USA road network (23.9M vertices, 57.7M edges) demonstrate nice performance:
|
103
|
+
|
104
|
+
<img src="https://raw.githubusercontent.com/aetperf/edsger/release/docs/source/assets/dijkstra_benchmark_comparison.png" alt="Dijkstra Performance Comparison" width="700">
|
105
|
+
|
106
|
+
*Benchmark performed on Intel i9-12900H Linux laptop.*
|
107
|
+
|
101
108
|
## Contributing
|
102
109
|
|
103
110
|
We welcome contributions to the Edsger library. If you have any suggestions, bug reports, or feature requests, please open an issue on our [GitHub repository](https://github.com/aetperf/Edsger).
|
@@ -0,0 +1,27 @@
|
|
1
|
+
edsger/.gitignore,sha256=mr9Izcwvjgv215xjRKhWEZ7vsyrKWhMqvWjSLHRYDjk,13
|
2
|
+
edsger/__init__.py,sha256=lgtGe3cqdwWdO1DLEOx7fX3i8D4Z_2rXHSq7Xecf-NM,41
|
3
|
+
edsger/_version.py,sha256=R5TtpJu7Qu6sOarfDpp-5Oyy8Pi2Ir3VewCvsCQiAgo,21
|
4
|
+
edsger/commons.cp312-win32.pyd,sha256=h0ZL_HSXe3Wr1v6KgCpNkzCKqtDcU8RahQxMa_S6YdI,19968
|
5
|
+
edsger/commons.pxd,sha256=UshKjr5ve3Or9u75xGyGPKRz1RwCCb5N-xgNevXZ4j4,464
|
6
|
+
edsger/commons.pyx,sha256=6Ze22eE_zwXPRAe550DEhEvu-b7hvKmwQu73rzzWMUE,839
|
7
|
+
edsger/dijkstra.cp312-win32.pyd,sha256=UzIZ1VwG3A-IHA9lMc2C14CQkZ5u6Tni0NtdiY04ZY4,210432
|
8
|
+
edsger/dijkstra.pyx,sha256=kBXFya0bugjp97xas145sZEUXtb89_Sg9v8IdWiURoE,37542
|
9
|
+
edsger/networks.py,sha256=hH9sgT5Ic4TLVCjxPNzMDWNjNDbqpXMxXxLeWxCpdLE,10730
|
10
|
+
edsger/path.py,sha256=2NtkhwN2HQUsoZn0Sl6UbFKWIcWVTvnE6D8IH-xEG88,35768
|
11
|
+
edsger/path_tracking.cp312-win32.pyd,sha256=7MgkmgJl8G13zc0DBB4tAknqMosJASBWdP2doQcZioc,113664
|
12
|
+
edsger/path_tracking.pyx,sha256=H24TLmC53I8LjbM1S5E7gS8WEb5uE_PZ8nhG6BteMYA,1900
|
13
|
+
edsger/pq_4ary_dec_0b.cp312-win32.pyd,sha256=Vg73UUeqK45T7e6guJOyqD1iRxgn0Zhkc9kS5W-bFRA,139776
|
14
|
+
edsger/pq_4ary_dec_0b.pxd,sha256=VvXcQzJq3OGBptrbawtemagPimuqSCayGQ91Jrad894,1098
|
15
|
+
edsger/pq_4ary_dec_0b.pyx,sha256=IzvzQerf-LYy7weQpgI0f28Q8gUrR4ENaedekXs1Jeg,18486
|
16
|
+
edsger/prefetch_compat.h,sha256=AyAYq_ZHKk5ChaJDrZOAOYe6SprL0_2byjRbjcBGrsU,826
|
17
|
+
edsger/spiess_florian.cp312-win32.pyd,sha256=M2aIY0VlYK2HCxdEy8d2RdeI4-neKv07P324p3qg4DE,159232
|
18
|
+
edsger/spiess_florian.pyx,sha256=tjfF9Iv8nqpp4lnv4KAjF-37ij0_SgQ0fnacVVKx-CE,9934
|
19
|
+
edsger/star.cp312-win32.pyd,sha256=4bLqerB1V2Vv0bclUIkWoMk4ERkIL1TO9lOACjFALTw,151040
|
20
|
+
edsger/star.pyx,sha256=9LAIXhlccEeDgT41ico7n57FJ7PKCzhPv4f22Lphn78,9602
|
21
|
+
edsger/utils.py,sha256=xYfOFIbYpAiZljhUOgGWy0TVNvWaMFCwbCLPBkzdVos,2097
|
22
|
+
edsger-0.1.3.dist-info/licenses/AUTHORS.rst,sha256=8udN6bgZHdHYcVzV38y6SPnF-x6Ks0uXxxFsic6Aces,110
|
23
|
+
edsger-0.1.3.dist-info/licenses/LICENSE,sha256=w7gRlruGxK3_4KTZAyJsOR2tML4UQgB-GNm2LerwJS0,1132
|
24
|
+
edsger-0.1.3.dist-info/METADATA,sha256=pd11DVCVJaNAwtzqYHZQu3HffnIgBUmwWwfuzgNTAF0,5314
|
25
|
+
edsger-0.1.3.dist-info/WHEEL,sha256=LwxTQZ0gyDP_uaeNCLm-ZIktY9hv6x0e22Q-hgFd-po,97
|
26
|
+
edsger-0.1.3.dist-info/top_level.txt,sha256=QvhzFORJIIot6GzSnDrtGa9KQt9iifCbOC5ULlzY5dg,7
|
27
|
+
edsger-0.1.3.dist-info/RECORD,,
|
edsger-0.1.1.dist-info/RECORD
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
edsger/.gitignore,sha256=mr9Izcwvjgv215xjRKhWEZ7vsyrKWhMqvWjSLHRYDjk,13
|
2
|
-
edsger/__init__.py,sha256=lgtGe3cqdwWdO1DLEOx7fX3i8D4Z_2rXHSq7Xecf-NM,41
|
3
|
-
edsger/_version.py,sha256=8oAxKUG747GUokmxjkrWejyJa5yPNEsoJDlXxoedxTw,21
|
4
|
-
edsger/commons.cp312-win32.pyd,sha256=720fDz6moZ1HNdMwRXnaAIQKC_YbuUxU4mGZEa-TG0c,19968
|
5
|
-
edsger/commons.pxd,sha256=UshKjr5ve3Or9u75xGyGPKRz1RwCCb5N-xgNevXZ4j4,464
|
6
|
-
edsger/commons.pyx,sha256=6Ze22eE_zwXPRAe550DEhEvu-b7hvKmwQu73rzzWMUE,839
|
7
|
-
edsger/dijkstra.cp312-win32.pyd,sha256=wb1jq5O_IKhDceS0JwQhUGlZgGZzXcG_E5l4XueB6e4,171520
|
8
|
-
edsger/dijkstra.pyx,sha256=0Hs3ccSGWpKx53md3kHye344u63KaunHj_gmG3_T8EE,16830
|
9
|
-
edsger/networks.py,sha256=hH9sgT5Ic4TLVCjxPNzMDWNjNDbqpXMxXxLeWxCpdLE,10730
|
10
|
-
edsger/path.py,sha256=OnXbP8Mf1rcO_9m6XsDrdXCRcLMeOb2vOAb1eHcZfM8,30395
|
11
|
-
edsger/path_tracking.cp312-win32.pyd,sha256=eeF2_dhKpGqOrUJ440ack8MPlAdv54ChshmdXYFklCs,113664
|
12
|
-
edsger/path_tracking.pyx,sha256=ZyIJQucG4-pyltgjsfGuZ7B6d0bFeovCuNh34LrBSvM,1900
|
13
|
-
edsger/pq_4ary_dec_0b.cp312-win32.pyd,sha256=ZlxIKW-KTSnZqflae6q8LTcRg-hn3mQBIx3ouO7aXJo,139776
|
14
|
-
edsger/pq_4ary_dec_0b.pxd,sha256=s0IsejKiG95SPgS3EX8rG86w1gzEyAkXJMaq5zj3HzE,1035
|
15
|
-
edsger/pq_4ary_dec_0b.pyx,sha256=x9rDOYMAjS6uCyp_VQYrI1yFBY4va4sTLXn05be4rTo,18521
|
16
|
-
edsger/spiess_florian.cp312-win32.pyd,sha256=JY2a55UAeHo3SynjF97HUQb39lsu9q5dJkM4yGkHcT8,159744
|
17
|
-
edsger/spiess_florian.pyx,sha256=tjfF9Iv8nqpp4lnv4KAjF-37ij0_SgQ0fnacVVKx-CE,9934
|
18
|
-
edsger/star.cp312-win32.pyd,sha256=8ML2pVvxO9YYBstyO8nZyasPNAFZlx7scpSbHG9NUGU,151040
|
19
|
-
edsger/star.pyx,sha256=50sopL56DnCQokHORsJ4IydQv_Zqs39W3WZLnadwgfk,9602
|
20
|
-
edsger/utils.py,sha256=xYfOFIbYpAiZljhUOgGWy0TVNvWaMFCwbCLPBkzdVos,2097
|
21
|
-
edsger-0.1.1.dist-info/licenses/AUTHORS.rst,sha256=8udN6bgZHdHYcVzV38y6SPnF-x6Ks0uXxxFsic6Aces,110
|
22
|
-
edsger-0.1.1.dist-info/licenses/LICENSE,sha256=w7gRlruGxK3_4KTZAyJsOR2tML4UQgB-GNm2LerwJS0,1132
|
23
|
-
edsger-0.1.1.dist-info/METADATA,sha256=I6IBbA-l70HNaSOT9dlFffY90wm0eeXqbukb_-no2qo,4789
|
24
|
-
edsger-0.1.1.dist-info/WHEEL,sha256=LwxTQZ0gyDP_uaeNCLm-ZIktY9hv6x0e22Q-hgFd-po,97
|
25
|
-
edsger-0.1.1.dist-info/top_level.txt,sha256=QvhzFORJIIot6GzSnDrtGa9KQt9iifCbOC5ULlzY5dg,7
|
26
|
-
edsger-0.1.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|