edsger 0.1.4__cp311-cp311-macosx_11_0_arm64.whl → 0.1.6__cp311-cp311-macosx_11_0_arm64.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/bellman_ford.c +35284 -0
- edsger/bellman_ford.cpython-311-darwin.so +0 -0
- edsger/bellman_ford.pyx +551 -0
- edsger/bfs.c +33575 -0
- edsger/bfs.cpython-311-darwin.so +0 -0
- edsger/bfs.pyx +243 -0
- edsger/commons.c +286 -278
- edsger/commons.cpython-311-darwin.so +0 -0
- edsger/commons.pyx +7 -0
- edsger/dijkstra.c +2433 -1857
- edsger/dijkstra.cpython-311-darwin.so +0 -0
- edsger/dijkstra.pyx +7 -0
- edsger/graph_importer.py +340 -0
- edsger/networks.py +4 -2
- edsger/path.py +1410 -264
- edsger/path_tracking.c +423 -302
- edsger/path_tracking.cpython-311-darwin.so +0 -0
- edsger/path_tracking.pyx +7 -0
- edsger/pq_4ary_dec_0b.c +1175 -1016
- edsger/pq_4ary_dec_0b.cpython-311-darwin.so +0 -0
- edsger/pq_4ary_dec_0b.pyx +7 -0
- edsger/spiess_florian.c +1410 -1140
- edsger/spiess_florian.cpython-311-darwin.so +0 -0
- edsger/spiess_florian.pyx +7 -0
- edsger/star.c +1240 -767
- edsger/star.cpython-311-darwin.so +0 -0
- edsger/star.pyx +7 -0
- edsger/utils.py +69 -4
- edsger-0.1.6.dist-info/METADATA +304 -0
- edsger-0.1.6.dist-info/RECORD +40 -0
- edsger-0.1.4.dist-info/METADATA +0 -125
- edsger-0.1.4.dist-info/RECORD +0 -33
- {edsger-0.1.4.dist-info → edsger-0.1.6.dist-info}/WHEEL +0 -0
- {edsger-0.1.4.dist-info → edsger-0.1.6.dist-info}/licenses/AUTHORS.rst +0 -0
- {edsger-0.1.4.dist-info → edsger-0.1.6.dist-info}/licenses/LICENSE +0 -0
- {edsger-0.1.4.dist-info → edsger-0.1.6.dist-info}/top_level.txt +0 -0
Binary file
|
edsger/dijkstra.pyx
CHANGED
@@ -29,6 +29,13 @@ cpdef functions:
|
|
29
29
|
are reached. Compute successors.
|
30
30
|
"""
|
31
31
|
|
32
|
+
# cython: language_level=3
|
33
|
+
# cython: boundscheck=False
|
34
|
+
# cython: wraparound=False
|
35
|
+
# cython: embedsignature=False
|
36
|
+
# cython: cdivision=True
|
37
|
+
# cython: initializedcheck=False
|
38
|
+
|
32
39
|
cimport numpy as cnp
|
33
40
|
|
34
41
|
from edsger.commons cimport (
|
edsger/graph_importer.py
ADDED
@@ -0,0 +1,340 @@
|
|
1
|
+
"""
|
2
|
+
Graph importer module for converting various DataFrame formats to NumPy-backed pandas DataFrames.
|
3
|
+
|
4
|
+
This module provides a unified interface for importing graph data from different DataFrame libraries
|
5
|
+
(pandas with NumPy backend, pandas with Arrow backend, Polars, etc.) and converting them to a
|
6
|
+
standardized NumPy-backed pandas DataFrame format that is optimal for the graph algorithms.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from abc import ABC, abstractmethod
|
10
|
+
from typing import Optional, List
|
11
|
+
import warnings
|
12
|
+
|
13
|
+
import numpy as np
|
14
|
+
import pandas as pd
|
15
|
+
|
16
|
+
|
17
|
+
class GraphImporter(ABC):
|
18
|
+
"""
|
19
|
+
Abstract base class for importing graph data from various DataFrame libraries.
|
20
|
+
|
21
|
+
All importers convert their input format to a NumPy-backed pandas DataFrame
|
22
|
+
with contiguous memory layout for optimal performance in Cython algorithms.
|
23
|
+
"""
|
24
|
+
|
25
|
+
def __init__(self, edges_df):
|
26
|
+
"""
|
27
|
+
Initialize the importer with a DataFrame.
|
28
|
+
|
29
|
+
Parameters
|
30
|
+
----------
|
31
|
+
edges_df : DataFrame-like
|
32
|
+
The edges DataFrame in the specific library format.
|
33
|
+
"""
|
34
|
+
self.edges_df = edges_df
|
35
|
+
|
36
|
+
@staticmethod
|
37
|
+
def from_dataframe( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
38
|
+
edges,
|
39
|
+
tail: str = "tail",
|
40
|
+
head: str = "head",
|
41
|
+
weight: Optional[str] = None,
|
42
|
+
trav_time: Optional[str] = None,
|
43
|
+
freq: Optional[str] = None,
|
44
|
+
) -> "GraphImporter":
|
45
|
+
"""
|
46
|
+
Factory method to create the appropriate importer based on DataFrame type.
|
47
|
+
|
48
|
+
Parameters
|
49
|
+
----------
|
50
|
+
edges : DataFrame-like
|
51
|
+
The edges DataFrame to import.
|
52
|
+
tail : str
|
53
|
+
Column name for tail vertices.
|
54
|
+
head : str
|
55
|
+
Column name for head vertices.
|
56
|
+
weight : str, optional
|
57
|
+
Column name for edge weights (for shortest path algorithms).
|
58
|
+
trav_time : str, optional
|
59
|
+
Column name for travel time (for hyperpath algorithms).
|
60
|
+
freq : str, optional
|
61
|
+
Column name for frequency (for hyperpath algorithms).
|
62
|
+
|
63
|
+
Returns
|
64
|
+
-------
|
65
|
+
GraphImporter
|
66
|
+
An instance of the appropriate importer subclass.
|
67
|
+
"""
|
68
|
+
# Note: tail, head, weight, trav_time, freq are part of API but not used here
|
69
|
+
# They're used by calling code after factory creates the importer
|
70
|
+
# pylint: disable=unused-argument
|
71
|
+
try:
|
72
|
+
# Check for Polars DataFrame
|
73
|
+
if hasattr(edges, "__class__") and edges.__class__.__module__.startswith(
|
74
|
+
"polars"
|
75
|
+
):
|
76
|
+
return PolarsImporter(edges)
|
77
|
+
except (AttributeError, TypeError):
|
78
|
+
# If __class__ or __module__ access fails, continue to other checks
|
79
|
+
pass
|
80
|
+
|
81
|
+
# Check for pandas DataFrame
|
82
|
+
if isinstance(edges, pd.DataFrame):
|
83
|
+
try:
|
84
|
+
# Check if any column has Arrow backend
|
85
|
+
has_arrow = any(
|
86
|
+
hasattr(dtype, "pyarrow_dtype") for dtype in edges.dtypes
|
87
|
+
)
|
88
|
+
|
89
|
+
if has_arrow:
|
90
|
+
return PandasArrowImporter(edges)
|
91
|
+
return PandasNumpyImporter(edges)
|
92
|
+
except (AttributeError, TypeError):
|
93
|
+
# If dtype checking fails, assume NumPy backend
|
94
|
+
return PandasNumpyImporter(edges)
|
95
|
+
|
96
|
+
# Unknown type - try to convert to pandas
|
97
|
+
warnings.warn(
|
98
|
+
f"Unknown DataFrame type {type(edges)}. Attempting to convert to pandas.",
|
99
|
+
UserWarning,
|
100
|
+
)
|
101
|
+
return PandasNumpyImporter(pd.DataFrame(edges))
|
102
|
+
|
103
|
+
@abstractmethod
|
104
|
+
def to_numpy_edges(self, columns: List[str]) -> pd.DataFrame:
|
105
|
+
"""
|
106
|
+
Convert the DataFrame to a NumPy-backed pandas DataFrame.
|
107
|
+
|
108
|
+
Parameters
|
109
|
+
----------
|
110
|
+
columns : List[str]
|
111
|
+
List of column names to extract.
|
112
|
+
|
113
|
+
Returns
|
114
|
+
-------
|
115
|
+
pd.DataFrame
|
116
|
+
A pandas DataFrame with NumPy backend and contiguous memory.
|
117
|
+
"""
|
118
|
+
|
119
|
+
def _ensure_contiguous(self, array: np.ndarray) -> np.ndarray:
|
120
|
+
"""
|
121
|
+
Ensure the array is C-contiguous.
|
122
|
+
|
123
|
+
Parameters
|
124
|
+
----------
|
125
|
+
array : np.ndarray
|
126
|
+
Input array.
|
127
|
+
|
128
|
+
Returns
|
129
|
+
-------
|
130
|
+
np.ndarray
|
131
|
+
C-contiguous array.
|
132
|
+
"""
|
133
|
+
if not array.flags["C_CONTIGUOUS"]:
|
134
|
+
return np.ascontiguousarray(array)
|
135
|
+
return array
|
136
|
+
|
137
|
+
|
138
|
+
class PandasNumpyImporter(GraphImporter):
|
139
|
+
"""
|
140
|
+
Importer for pandas DataFrames with NumPy backend.
|
141
|
+
|
142
|
+
This is the most efficient case as it requires minimal conversion.
|
143
|
+
"""
|
144
|
+
|
145
|
+
def to_numpy_edges(self, columns: List[str]) -> pd.DataFrame:
|
146
|
+
"""
|
147
|
+
Extract columns and ensure they are NumPy-backed.
|
148
|
+
|
149
|
+
For NumPy-backed pandas, this is mostly a pass-through operation
|
150
|
+
with validation to ensure contiguous memory.
|
151
|
+
"""
|
152
|
+
# Extract only the needed columns
|
153
|
+
result_df = self.edges_df[columns].copy(deep=True)
|
154
|
+
|
155
|
+
# Ensure all columns are contiguous NumPy arrays
|
156
|
+
for col in columns:
|
157
|
+
if not isinstance(result_df[col].values, np.ndarray):
|
158
|
+
# Convert to NumPy if somehow not already
|
159
|
+
result_df[col] = result_df[col].to_numpy()
|
160
|
+
|
161
|
+
return result_df
|
162
|
+
|
163
|
+
|
164
|
+
class PandasArrowImporter(GraphImporter):
|
165
|
+
"""
|
166
|
+
Importer for pandas DataFrames with Arrow backend.
|
167
|
+
|
168
|
+
Converts Arrow-backed columns to NumPy arrays with proper data types.
|
169
|
+
"""
|
170
|
+
|
171
|
+
def to_numpy_edges(self, columns: List[str]) -> pd.DataFrame:
|
172
|
+
"""
|
173
|
+
Convert Arrow-backed columns to NumPy arrays.
|
174
|
+
|
175
|
+
Uses to_numpy() method which ensures contiguous memory layout.
|
176
|
+
"""
|
177
|
+
result_data = {}
|
178
|
+
|
179
|
+
for col in columns:
|
180
|
+
series = self.edges_df[col]
|
181
|
+
|
182
|
+
# Determine target dtype based on column values
|
183
|
+
if col in columns[:2]: # Assume first two are vertex indices (tail, head)
|
184
|
+
# Try to use uint32 for vertex indices if possible
|
185
|
+
max_val = series.max()
|
186
|
+
if max_val < np.iinfo(np.uint32).max:
|
187
|
+
target_dtype = np.uint32
|
188
|
+
else:
|
189
|
+
target_dtype = np.uint64
|
190
|
+
else:
|
191
|
+
# Use float64 for weights/times/frequencies
|
192
|
+
target_dtype = np.float64
|
193
|
+
|
194
|
+
# Convert to NumPy with specified dtype
|
195
|
+
if hasattr(series, "to_numpy"):
|
196
|
+
# Use to_numpy() for Arrow-backed series
|
197
|
+
result_data[col] = series.to_numpy(dtype=target_dtype, copy=True)
|
198
|
+
else:
|
199
|
+
# Fallback for older pandas versions
|
200
|
+
result_data[col] = series.values.astype(target_dtype)
|
201
|
+
|
202
|
+
# Ensure contiguous
|
203
|
+
result_data[col] = self._ensure_contiguous(result_data[col])
|
204
|
+
|
205
|
+
return pd.DataFrame(result_data)
|
206
|
+
|
207
|
+
|
208
|
+
class PolarsImporter(GraphImporter):
|
209
|
+
"""
|
210
|
+
Importer for Polars DataFrames.
|
211
|
+
|
212
|
+
Converts Polars DataFrames to NumPy-backed pandas DataFrames.
|
213
|
+
"""
|
214
|
+
|
215
|
+
def to_numpy_edges(
|
216
|
+
self, columns: List[str]
|
217
|
+
) -> pd.DataFrame: # pylint: disable=too-many-branches
|
218
|
+
"""
|
219
|
+
Convert Polars DataFrame to NumPy-backed pandas DataFrame.
|
220
|
+
|
221
|
+
Uses Polars' to_pandas() method or to_numpy() depending on what's available.
|
222
|
+
"""
|
223
|
+
try:
|
224
|
+
import polars # pylint: disable=import-outside-toplevel,unused-import
|
225
|
+
except ImportError as exc:
|
226
|
+
raise ImportError(
|
227
|
+
"Polars is required to import Polars DataFrames. "
|
228
|
+
"Install it with: pip install polars"
|
229
|
+
) from exc
|
230
|
+
|
231
|
+
# Select only needed columns
|
232
|
+
selected_df = self.edges_df.select(columns)
|
233
|
+
|
234
|
+
# Method 1: Direct to_pandas() conversion (simplest)
|
235
|
+
if hasattr(selected_df, "to_pandas"):
|
236
|
+
result_df = selected_df.to_pandas()
|
237
|
+
|
238
|
+
# Handle empty DataFrames
|
239
|
+
if len(result_df) == 0:
|
240
|
+
return result_df
|
241
|
+
|
242
|
+
# Ensure proper dtypes
|
243
|
+
for i, col in enumerate(columns):
|
244
|
+
if i < 2: # Vertex indices
|
245
|
+
# Check if column contains numeric data
|
246
|
+
if np.issubdtype(result_df[col].dtype, np.integer):
|
247
|
+
# Try to use uint32 for efficiency
|
248
|
+
max_val = result_df[col].max()
|
249
|
+
if not pd.isna(max_val) and max_val < np.iinfo(np.uint32).max:
|
250
|
+
result_df[col] = result_df[col].astype(np.uint32)
|
251
|
+
# If not numeric (e.g., strings), leave as is
|
252
|
+
else:
|
253
|
+
# Weights/times/frequencies
|
254
|
+
result_df[col] = result_df[col].astype(np.float64)
|
255
|
+
|
256
|
+
return result_df
|
257
|
+
|
258
|
+
# Method 2: Column-by-column conversion
|
259
|
+
result_data = {}
|
260
|
+
|
261
|
+
# Handle empty DataFrames
|
262
|
+
if len(selected_df) == 0:
|
263
|
+
return selected_df.to_pandas()
|
264
|
+
|
265
|
+
for i, col in enumerate(columns):
|
266
|
+
series = selected_df[col]
|
267
|
+
|
268
|
+
# Determine target dtype
|
269
|
+
if i < 2: # Vertex indices
|
270
|
+
# Check if the series contains numeric data
|
271
|
+
if hasattr(series, "dtype") and series.dtype.is_integer():
|
272
|
+
max_val = series.max()
|
273
|
+
if max_val is not None and max_val < np.iinfo(np.uint32).max:
|
274
|
+
target_dtype = np.uint32
|
275
|
+
else:
|
276
|
+
target_dtype = np.uint64
|
277
|
+
else:
|
278
|
+
# Non-numeric columns, convert to pandas as is
|
279
|
+
result_data[col] = series.to_pandas()
|
280
|
+
continue
|
281
|
+
else:
|
282
|
+
target_dtype = np.float64
|
283
|
+
|
284
|
+
# Convert to NumPy
|
285
|
+
if hasattr(series, "to_numpy"):
|
286
|
+
np_array = series.to_numpy().astype(target_dtype)
|
287
|
+
else:
|
288
|
+
# Fallback for older Polars versions
|
289
|
+
np_array = series.to_list()
|
290
|
+
np_array = np.array(np_array, dtype=target_dtype)
|
291
|
+
|
292
|
+
# Ensure contiguous
|
293
|
+
result_data[col] = self._ensure_contiguous(np_array)
|
294
|
+
|
295
|
+
return pd.DataFrame(result_data)
|
296
|
+
|
297
|
+
|
298
|
+
def standardize_graph_dataframe( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
299
|
+
edges,
|
300
|
+
tail: str = "tail",
|
301
|
+
head: str = "head",
|
302
|
+
weight: Optional[str] = None,
|
303
|
+
trav_time: Optional[str] = None,
|
304
|
+
freq: Optional[str] = None,
|
305
|
+
) -> pd.DataFrame:
|
306
|
+
"""
|
307
|
+
Convenience function to standardize any DataFrame format to NumPy-backed pandas.
|
308
|
+
|
309
|
+
Parameters
|
310
|
+
----------
|
311
|
+
edges : DataFrame-like
|
312
|
+
Input edges DataFrame in any supported format.
|
313
|
+
tail : str
|
314
|
+
Column name for tail vertices.
|
315
|
+
head : str
|
316
|
+
Column name for head vertices.
|
317
|
+
weight : str, optional
|
318
|
+
Column name for edge weights.
|
319
|
+
trav_time : str, optional
|
320
|
+
Column name for travel time.
|
321
|
+
freq : str, optional
|
322
|
+
Column name for frequency.
|
323
|
+
|
324
|
+
Returns
|
325
|
+
-------
|
326
|
+
pd.DataFrame
|
327
|
+
NumPy-backed pandas DataFrame with only the specified columns.
|
328
|
+
"""
|
329
|
+
# Determine which columns to extract
|
330
|
+
columns = [tail, head]
|
331
|
+
if weight is not None:
|
332
|
+
columns.append(weight)
|
333
|
+
if trav_time is not None:
|
334
|
+
columns.append(trav_time)
|
335
|
+
if freq is not None:
|
336
|
+
columns.append(freq)
|
337
|
+
|
338
|
+
# Create appropriate importer and convert
|
339
|
+
importer = GraphImporter.from_dataframe(edges, tail, head, weight, trav_time, freq)
|
340
|
+
return importer.to_numpy_edges(columns)
|
edsger/networks.py
CHANGED
@@ -31,7 +31,7 @@ class SiouxFalls:
|
|
31
31
|
"""
|
32
32
|
|
33
33
|
@property
|
34
|
-
def edges(self):
|
34
|
+
def edges(self) -> pd.DataFrame:
|
35
35
|
"""
|
36
36
|
A DataFrame containing the edges of the Sioux Falls network.
|
37
37
|
|
@@ -130,7 +130,9 @@ class SiouxFalls:
|
|
130
130
|
return graph_edges
|
131
131
|
|
132
132
|
|
133
|
-
def create_sf_network(
|
133
|
+
def create_sf_network(
|
134
|
+
dwell_time: float = 1.0e-6, board_alight_ratio: float = 0.5
|
135
|
+
) -> pd.DataFrame:
|
134
136
|
"""
|
135
137
|
Example network from Spiess, H. and Florian, M. (1989).
|
136
138
|
Optimal strategies: A new assignment model for transit networks.
|