edsger 0.1.2__cp312-cp312-win32.whl → 0.1.4__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 CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.2"
1
+ __version__ = "0.1.4"
Binary file
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,7 +35,7 @@ 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
 
26
- # Memory prefetching support (x86/x64 only)
38
+ # memory prefetching support (x86/x64 only)
27
39
  cdef extern from "prefetch_compat.h":
28
40
  void prefetch_hint(char*, int) nogil
29
41
  int PREFETCH_T0
@@ -89,14 +101,14 @@ cpdef cnp.ndarray compute_sssp(
89
101
 
90
102
  head_vert_idx = <size_t>csr_indices[idx]
91
103
 
92
- # Prefetch next iteration data to improve cache performance
104
+ # prefetch next iteration data to improve cache performance
93
105
  if idx + 1 < <size_t>csr_indptr[tail_vert_idx + 1]:
94
106
  prefetch_hint(<char*>&csr_indices[idx + 1], PREFETCH_T0)
95
107
  prefetch_hint(<char*>&csr_data[idx + 1], PREFETCH_T0)
96
108
 
97
109
  vert_state = pqueue.Elements[head_vert_idx].state
98
110
  if vert_state != SCANNED:
99
- # Prefetch priority queue element data for the vertex
111
+ # prefetch priority queue element data for the vertex
100
112
  prefetch_hint(<char*>&pqueue.Elements[head_vert_idx], PREFETCH_T0)
101
113
 
102
114
  head_vert_val = tail_vert_val + csr_data[idx]
@@ -114,6 +126,109 @@ cpdef cnp.ndarray compute_sssp(
114
126
  return path_lengths
115
127
 
116
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
+
117
232
  cpdef cnp.ndarray compute_sssp_w_path(
118
233
  cnp.uint32_t[::1] csr_indptr,
119
234
  cnp.uint32_t[::1] csr_indices,
@@ -179,14 +294,14 @@ cpdef cnp.ndarray compute_sssp_w_path(
179
294
 
180
295
  head_vert_idx = <size_t>csr_indices[idx]
181
296
 
182
- # Prefetch next iteration data to improve cache performance
297
+ # prefetch next iteration data to improve cache performance
183
298
  if idx + 1 < <size_t>csr_indptr[tail_vert_idx + 1]:
184
299
  prefetch_hint(<char*>&csr_indices[idx + 1], PREFETCH_T0)
185
300
  prefetch_hint(<char*>&csr_data[idx + 1], PREFETCH_T0)
186
301
 
187
302
  vert_state = pqueue.Elements[head_vert_idx].state
188
303
  if vert_state != SCANNED:
189
- # Prefetch priority queue element data for the vertex
304
+ # prefetch priority queue element data for the vertex
190
305
  prefetch_hint(<char*>&pqueue.Elements[head_vert_idx], PREFETCH_T0)
191
306
 
192
307
  head_vert_val = tail_vert_val + csr_data[idx]
@@ -206,6 +321,115 @@ cpdef cnp.ndarray compute_sssp_w_path(
206
321
  return path_lengths
207
322
 
208
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
+
209
433
  cpdef cnp.ndarray compute_stsp(
210
434
  cnp.uint32_t[::1] csc_indptr,
211
435
  cnp.uint32_t[::1] csc_indices,
@@ -265,8 +489,17 @@ cpdef cnp.ndarray compute_stsp(
265
489
  <size_t>csc_indptr[head_vert_idx + 1]):
266
490
 
267
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
+
268
498
  vert_state = pqueue.Elements[tail_vert_idx].state
269
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
+
270
503
  tail_vert_val = head_vert_val + csc_data[idx]
271
504
  if vert_state == UNLABELED:
272
505
  pq.insert(&pqueue, tail_vert_idx, tail_vert_val)
@@ -342,8 +575,17 @@ cpdef cnp.ndarray compute_stsp_w_path(
342
575
  <size_t>csc_indptr[head_vert_idx + 1]):
343
576
 
344
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
+
345
584
  vert_state = pqueue.Elements[tail_vert_idx].state
346
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
+
347
589
  tail_vert_val = head_vert_val + csc_data[idx]
348
590
  if vert_state == UNLABELED:
349
591
  pq.insert(&pqueue, tail_vert_idx, tail_vert_val)
@@ -361,6 +603,216 @@ cpdef cnp.ndarray compute_stsp_w_path(
361
603
  return path_lengths
362
604
 
363
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
+
364
816
  # ============================================================================ #
365
817
  # tests #
366
818
  # ============================================================================ #
@@ -521,6 +973,56 @@ cpdef compute_stsp_02():
521
973
  assert np.allclose(path_lengths_ref, path_lengths)
522
974
 
523
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
+
524
1026
  # author : Francois Pacull
525
1027
  # copyright : Architecture & Performance
526
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 self._orientation == "in":
362
- path_length_values = compute_stsp(
363
- self.__indptr,
364
- self.__indices,
365
- self.__edge_weights,
366
- vertex_new,
367
- self._n_vertices,
368
- heap_length,
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
- path_length_values = compute_sssp(
372
- self.__indptr,
373
- self.__indices,
374
- self.__edge_weights,
375
- vertex_new,
376
- self._n_vertices,
377
- heap_length,
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 self._orientation == "in":
382
- path_length_values = compute_stsp_w_path(
383
- self.__indptr,
384
- self.__indices,
385
- self.__edge_weights,
386
- self._path_links,
387
- vertex_new,
388
- self._n_vertices,
389
- heap_length,
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
- path_length_values = compute_sssp_w_path(
393
- self.__indptr,
394
- self.__indices,
395
- self.__edge_weights,
396
- self._path_links,
397
- vertex_new,
398
- self._n_vertices,
399
- heap_length,
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
Binary file
Binary file
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edsger
3
- Version: 0.1.2
3
+ Version: 0.1.4
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,8 @@ Dynamic: license-file
42
42
 
43
43
  ![Tests Status](https://github.com/aetperf/edsger/actions/workflows/tests.yml/badge.svg?branch=release)
44
44
  [![codecov](https://codecov.io/gh/aetperf/edsger/branch/release/graph/badge.svg)](https://codecov.io/gh/aetperf/edsger)
45
- [![PyPI version](https://img.shields.io/pypi/v/edsger.svg)](https://pypi.org/project/edsger/)
45
+ [![Documentation Status](https://readthedocs.org/projects/edsger/badge/?version=latest)](https://edsger.readthedocs.io/en/latest/?badge=latest)
46
+ [![PyPI version](https://img.shields.io/pypi/v/edsger.svg?refresh=1)](https://pypi.org/project/edsger/)
46
47
  [![Downloads](https://static.pepy.tech/badge/edsger)](https://pepy.tech/project/edsger)
47
48
  [![Python 3.9 | 3.10 | 3.11 | 3.12 | 3.13](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue)](https://pypi.org/project/edsger/)
48
49
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
@@ -75,14 +76,14 @@ edges = pd.DataFrame({
75
76
  edges
76
77
  ```
77
78
 
78
- | | tail | head | weight |
79
- |---:|-------:|-------:|---------:|
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 |
79
+ | | tail | head | weight |
80
+ |---:|-------:|-------:|---------:|
81
+ | 0 | 0 | 1 | 1.0 |
82
+ | 1 | 0 | 2 | 4.0 |
83
+ | 2 | 1 | 2 | 2.0 |
84
+ | 3 | 2 | 3 | 1.5 |
85
+ | 4 | 2 | 4 | 3.0 |
86
+ | 5 | 3 | 4 | 1.0 |
86
87
 
87
88
  ```python
88
89
  # Initialize the Dijkstra object
@@ -97,13 +98,19 @@ print("Shortest paths:", shortest_paths)
97
98
 
98
99
  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.
99
100
 
100
- ## Why Use Edsger?
101
+ ## Installation
102
+
103
+ ### Standard Installation
101
104
 
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:
105
+ ```bash
106
+ pip install edsger
107
+ ```
108
+
109
+ ## Why Use Edsger?
103
110
 
104
- <img src="docs/source/assets/dijkstra_benchmark_comparison.png" alt="Dijkstra Performance Comparison" width="700">
111
+ Edsger is designed to be **dataframe-friendly**, providing seamless integration with pandas workflows for graph algorithms. Also it is rather efficient on Linux. Our benchmarks on the USA road network (23.9M vertices, 57.7M edges) demonstrate nice performance:
105
112
 
106
- *Benchmark performed on Intel i9-12900H laptop.*
113
+ <img src="https://raw.githubusercontent.com/aetperf/edsger/release/docs/source/assets/dijkstra_benchmark_comparison.png" alt="Dijkstra Performance Comparison" width="700">
107
114
 
108
115
  ## Contributing
109
116
 
@@ -0,0 +1,27 @@
1
+ edsger/.gitignore,sha256=mr9Izcwvjgv215xjRKhWEZ7vsyrKWhMqvWjSLHRYDjk,13
2
+ edsger/__init__.py,sha256=lgtGe3cqdwWdO1DLEOx7fX3i8D4Z_2rXHSq7Xecf-NM,41
3
+ edsger/_version.py,sha256=JMD28FXYHc_TM03visyUSd3UA9FZAaJMRStnfZoq50Y,21
4
+ edsger/commons.cp312-win32.pyd,sha256=Ij8OCS8r0EYdUGeU8sokLJRYItAKYa9boIJ_PTPPKnw,20480
5
+ edsger/commons.pxd,sha256=UshKjr5ve3Or9u75xGyGPKRz1RwCCb5N-xgNevXZ4j4,464
6
+ edsger/commons.pyx,sha256=6Ze22eE_zwXPRAe550DEhEvu-b7hvKmwQu73rzzWMUE,839
7
+ edsger/dijkstra.cp312-win32.pyd,sha256=TCNFPOoVvGeRGn_n_G00zWWRwl9PWGWonD5Iydyv6h8,214016
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=gjsAtjRWQ4itlrdmklw2H-pS-q4OcKNl7jJFvlF_SqY,116736
12
+ edsger/path_tracking.pyx,sha256=H24TLmC53I8LjbM1S5E7gS8WEb5uE_PZ8nhG6BteMYA,1900
13
+ edsger/pq_4ary_dec_0b.cp312-win32.pyd,sha256=1_pXQ1Xtxwx1rCo9AzFvKFUFW9rryU7by4GD0sYIERk,142848
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=KHt94IJ_Xvhs9a2VAtPS1cMamFypM7VdnGJDtDybmus,162304
18
+ edsger/spiess_florian.pyx,sha256=tjfF9Iv8nqpp4lnv4KAjF-37ij0_SgQ0fnacVVKx-CE,9934
19
+ edsger/star.cp312-win32.pyd,sha256=8LFDWdmxlNrI_PfdRcFzJz4IEBceYrZYtqJi8UqrpF0,154112
20
+ edsger/star.pyx,sha256=9LAIXhlccEeDgT41ico7n57FJ7PKCzhPv4f22Lphn78,9602
21
+ edsger/utils.py,sha256=xYfOFIbYpAiZljhUOgGWy0TVNvWaMFCwbCLPBkzdVos,2097
22
+ edsger-0.1.4.dist-info/licenses/AUTHORS.rst,sha256=8udN6bgZHdHYcVzV38y6SPnF-x6Ks0uXxxFsic6Aces,110
23
+ edsger-0.1.4.dist-info/licenses/LICENSE,sha256=w7gRlruGxK3_4KTZAyJsOR2tML4UQgB-GNm2LerwJS0,1132
24
+ edsger-0.1.4.dist-info/METADATA,sha256=fl_OoQXbo9d0B0sIkuOSDD5JAB-CgxWA6dRNujINONc,5494
25
+ edsger-0.1.4.dist-info/WHEEL,sha256=LwxTQZ0gyDP_uaeNCLm-ZIktY9hv6x0e22Q-hgFd-po,97
26
+ edsger-0.1.4.dist-info/top_level.txt,sha256=QvhzFORJIIot6GzSnDrtGa9KQt9iifCbOC5ULlzY5dg,7
27
+ edsger-0.1.4.dist-info/RECORD,,
@@ -1,27 +0,0 @@
1
- edsger/.gitignore,sha256=mr9Izcwvjgv215xjRKhWEZ7vsyrKWhMqvWjSLHRYDjk,13
2
- edsger/__init__.py,sha256=lgtGe3cqdwWdO1DLEOx7fX3i8D4Z_2rXHSq7Xecf-NM,41
3
- edsger/_version.py,sha256=K5SiDdEGYMpdqXThrqwTqECJJBOQNTQDrnpc2K5mzKs,21
4
- edsger/commons.cp312-win32.pyd,sha256=A4VhAzKWRKdaOm2qAydr-Yw2K4JRz1k2A9CLGhBGqHs,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=rwRaxfFz0SUpuKKoMNlk-0KTRkqMdCTq9PCg1M85uZ0,171008
8
- edsger/dijkstra.pyx,sha256=5yjCC2NqXrfBe8tp1sN_XSsWF92IHFrOu-QdbOV8Rvo,17915
9
- edsger/networks.py,sha256=hH9sgT5Ic4TLVCjxPNzMDWNjNDbqpXMxXxLeWxCpdLE,10730
10
- edsger/path.py,sha256=OnXbP8Mf1rcO_9m6XsDrdXCRcLMeOb2vOAb1eHcZfM8,30395
11
- edsger/path_tracking.cp312-win32.pyd,sha256=zKOdWTiOOLtihPEsKDMRcSMnygp0v6MjQ1uEDwjday4,113664
12
- edsger/path_tracking.pyx,sha256=H24TLmC53I8LjbM1S5E7gS8WEb5uE_PZ8nhG6BteMYA,1900
13
- edsger/pq_4ary_dec_0b.cp312-win32.pyd,sha256=_g51tHNv46-1KGSuFYR1Od8MvBn9GnDK9XKu3rB32mM,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=mF_MRkXyjQJfWzQr-EiJoBIOPqT6-UFJJLFgEjNU2rE,159232
18
- edsger/spiess_florian.pyx,sha256=tjfF9Iv8nqpp4lnv4KAjF-37ij0_SgQ0fnacVVKx-CE,9934
19
- edsger/star.cp312-win32.pyd,sha256=sLn5npOoHmJJXTZW8-rNM7kX_yP8g67CZPBoy4wamGs,151040
20
- edsger/star.pyx,sha256=9LAIXhlccEeDgT41ico7n57FJ7PKCzhPv4f22Lphn78,9602
21
- edsger/utils.py,sha256=xYfOFIbYpAiZljhUOgGWy0TVNvWaMFCwbCLPBkzdVos,2097
22
- edsger-0.1.2.dist-info/licenses/AUTHORS.rst,sha256=8udN6bgZHdHYcVzV38y6SPnF-x6Ks0uXxxFsic6Aces,110
23
- edsger-0.1.2.dist-info/licenses/LICENSE,sha256=w7gRlruGxK3_4KTZAyJsOR2tML4UQgB-GNm2LerwJS0,1132
24
- edsger-0.1.2.dist-info/METADATA,sha256=Z2KnsYE4vh_m5tvRYU-LIyDso2Kpdl7iQQeFYH_zfhM,5273
25
- edsger-0.1.2.dist-info/WHEEL,sha256=LwxTQZ0gyDP_uaeNCLm-ZIktY9hv6x0e22Q-hgFd-po,97
26
- edsger-0.1.2.dist-info/top_level.txt,sha256=QvhzFORJIIot6GzSnDrtGa9KQt9iifCbOC5ULlzY5dg,7
27
- edsger-0.1.2.dist-info/RECORD,,
File without changes