multimodalrouter 0.1.2__tar.gz → 0.1.4__tar.gz

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.

Potentially problematic release.


This version of multimodalrouter might be problematic. Click here for more details.

Files changed (32) hide show
  1. {multimodalrouter-0.1.2/src/multiModalRouter.egg-info → multimodalrouter-0.1.4}/PKG-INFO +4 -1
  2. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/README.md +2 -0
  3. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/docs/graph.md +108 -0
  4. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/docs/installation.md +9 -6
  5. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/pyproject.toml +2 -1
  6. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4/src/multiModalRouter.egg-info}/PKG-INFO +4 -1
  7. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/src/multimodalrouter/__init__.py +2 -2
  8. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/src/multimodalrouter/graph/__init__.py +1 -1
  9. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/src/multimodalrouter/graph/dataclasses.py +34 -1
  10. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/src/multimodalrouter/graph/graph.py +76 -3
  11. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/LICENSE.md +0 -0
  12. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/MANIFEST.in +0 -0
  13. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/docs/cli.md +0 -0
  14. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/docs/examples/demoData.csv +0 -0
  15. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/docs/examples/flightRouter/data/fullDataset.csv +0 -0
  16. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/docs/examples/flightRouter/main.py +0 -0
  17. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/docs/examples/mazePathfinder/data/createMaze.py +0 -0
  18. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/docs/examples/mazePathfinder/data/maze.csv +0 -0
  19. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/docs/examples/mazePathfinder/main.py +0 -0
  20. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/docs/solvedMaze1.png +0 -0
  21. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/docs/utils.md +0 -0
  22. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/setup.cfg +0 -0
  23. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/src/multiModalRouter.egg-info/SOURCES.txt +0 -0
  24. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/src/multiModalRouter.egg-info/dependency_links.txt +0 -0
  25. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/src/multiModalRouter.egg-info/entry_points.txt +0 -0
  26. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/src/multiModalRouter.egg-info/requires.txt +0 -0
  27. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/src/multiModalRouter.egg-info/top_level.txt +0 -0
  28. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/src/multimodalrouter/router/__init__.py +0 -0
  29. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/src/multimodalrouter/router/build.py +0 -0
  30. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/src/multimodalrouter/router/route.py +0 -0
  31. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/src/multimodalrouter/utils/__init__.py +0 -0
  32. {multimodalrouter-0.1.2 → multimodalrouter-0.1.4}/src/multimodalrouter/utils/preprocessor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multimodalrouter
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: A graph-based routing library for dynamic routing.
5
5
  Author-email: Tobias Karusseit <karusseittobi@gmail.com>
6
6
  License: MIT License
@@ -14,6 +14,7 @@ License: MIT License
14
14
  THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15
15
 
16
16
 
17
+ Project-URL: Homepage, https://github.com/K-T0BIAS/MultiModalRouter
17
18
  Project-URL: Repository, https://github.com/K-T0BIAS/MultiModalRouter
18
19
  Requires-Python: >=3.11
19
20
  Description-Content-Type: text/markdown
@@ -50,6 +51,8 @@ Dynamic: license-file
50
51
 
51
52
  The Multi Modal Router is a graph-based routing engine that allows you to build and query any hub-based network. It supports multiple transport modes like driving, flying, or shipping, and lets you optimize routes by distance, time, or custom metrics. It can be expanded to any n-dimensional space making it versatile in any coordinate space
52
53
 
54
+ > NEWS: v0.1.3 now on pypi ([installation guide](./docs/installation.md))
55
+
53
56
  > NOTE: This project is a work in progress and features might be added and or changed
54
57
 
55
58
  # In depth Documentation
@@ -2,6 +2,8 @@
2
2
 
3
3
  The Multi Modal Router is a graph-based routing engine that allows you to build and query any hub-based network. It supports multiple transport modes like driving, flying, or shipping, and lets you optimize routes by distance, time, or custom metrics. It can be expanded to any n-dimensional space making it versatile in any coordinate space
4
4
 
5
+ > NEWS: v0.1.3 now on pypi ([installation guide](./docs/installation.md))
6
+
5
7
  > NOTE: This project is a work in progress and features might be added and or changed
6
8
 
7
9
  # In depth Documentation
@@ -146,6 +146,31 @@ def find_shortest_path(
146
146
 
147
147
  **returns** : [Route](#route) or None if no route was found
148
148
 
149
+ ### radial search /finding all hubs inside a radius
150
+
151
+ > Note: this doesn't search a direct radius but rather a reachablity distance (e.g.: A and B may have a distance $x \leq r$, but the shortest connecting path has distance $y \geq r$)
152
+
153
+ ```python
154
+ def radial_search(
155
+ self,
156
+ hub_id: str,
157
+ radius: float,
158
+ optimization_metric: OptimizationMetric | str = OptimizationMetric.DISTANCE,
159
+ allowed_modes: list[str] = None,
160
+ custom_filter: Filter = None
161
+ ) -> list[float, Hub]:
162
+ ```
163
+
164
+ #### args
165
+
166
+ - `hub_id`: str = the id of the center hub the search starts at
167
+ - `radius`: float = the maximum value the search metric is allowed to have from the start
168
+ - `optimization_metric`: str = the target metric you want to use for the distance (default='distance')
169
+ - `allowed_modes`: list[str] = the types of edges that are considered (default= None => all edges are checked)
170
+ - `custom_filter`: Filter = a [filter](#filter) object you can pass to add filters for Hubs and edgeMetadata
171
+
172
+ **returns:** list[ tuple[float, [Hub](#hub)] ] = a list of all reachable hubs with the 'distance' to the start
173
+
149
174
  ### save
150
175
 
151
176
  ```python
@@ -359,7 +384,51 @@ nDimGraph = RouteGraph(
359
384
 
360
385
  > It is theoretically possible to combine hubs from differnt dimensions as long as a distance metric is given or the distance is pre calculated
361
386
 
387
+ #### custom filters in searches
388
+
389
+ To add custom rulesets to searches like [`find_shortest_path`](#routing--finding-the-shortest-path-form-a-to-b) you can add your own [`Filter`](#filter) objects
390
+
391
+ #### example
392
+
393
+ Imagine one of your datasets has the following keys
394
+
395
+ ```csv
396
+ source, destination, distance, cost, sx, sy, dx, dy, namex, namey
397
+ ```
398
+
399
+ You have now build your graph with the extra keys: `cost`, `namex`,`namey`, and you want to start a shortest path search that excludes edges where `cost` > `C` and the where the destination `namey` = `N`. Additionally you want to exclude a list of `hub Ids` = `I`
400
+
401
+ **create Filter:**
402
+
403
+ ```python
404
+ from multimodalrouter import Filter
405
+
406
+ class CustomFilter(Filter):
407
+
408
+ def __init__(self, C: float, N: str, I: list[str]):
409
+ self.C = C
410
+ self.N = N
411
+ self.I = I
412
+
413
+ def filterHub(self, hub: Hub):
414
+ return hub.id not in self.I
415
+
416
+ def filterEdge(self, edge: EdgeMetadata):
417
+ return (edge.getMetric('cost') < self.C
418
+ and egde-getMetric('namey') != self.N
419
+ )
420
+ ```
421
+
422
+ **use filter**
423
+
424
+ ```python
425
+ # graph creation code here
362
426
 
427
+ route = graph.find_shortest_path(
428
+ **kwargs,
429
+ custom_filter=CustomFilter(c, n, i) # your filter instance
430
+ )
431
+ ```
363
432
  ---
364
433
  ---
365
434
  ---
@@ -509,6 +578,45 @@ Start: GOM
509
578
  -> LOK
510
579
  ```
511
580
 
581
+ ### Filter
582
+
583
+ The `Filter` class is an abstract class you can implement to add custom filter to you searches
584
+
585
+ #### example
586
+
587
+ ```python
588
+ class ExampleFilter(Filter):
589
+
590
+ def __init__(
591
+ self,
592
+ forbiddenHubs: list[str],
593
+ filterVal: str | float
594
+ ):
595
+ self.forbiddenHubs = forbiddenHubs
596
+ self.filterVal = filterVal
597
+
598
+ def filterHub(self, hub: Hub) -> bool:
599
+ return hub.id not in self.forbiddenHubs
600
+
601
+ def filterEdge(self, edge: EdgeMetadata) -> bool:
602
+ return edge.getMetric('distance') < 3 and edge.getMetric('yourCustomMetric') != self.filterVal
603
+ ```
604
+
605
+ This `ExampleFilter` will remove all hubs with Ids in the forbidden hubs list and ignore all edges where: $distance > 3 \lor customMetric = filterVal $
606
+
607
+ To make your own `Filter` just implement the ``__init__``, `filterHUb` & `filterEdge` functions and pass an object to the search (custom_filter = your flter object)
608
+
609
+ > Tipp: if you want to only add a filter for either Hubs or Edges set the function that shouldn't filter to return `True`
610
+
611
+ **example**
612
+ ```python
613
+ def filterHub(self, hub: Hub) -> bool:
614
+ return True
615
+ ```
616
+
617
+ will let any hub pass through the filter
618
+
619
+
512
620
 
513
621
 
514
622
 
@@ -7,12 +7,15 @@
7
7
  First check if your data comes with precomputed distances and if you are going to want to use the default [driving connections](./graph.md) when building your graph.
8
8
  Depending on your choices you will need to install the library with torch. To see what your use case requires check the table below and copy the command.
9
9
 
10
- | data has distances | use driving edges | installation mode |
11
- |--------------------|-------------------|-------------------------------------|
12
- | YES | YES |`pip install .[torch]`|
13
- | YES | NO |`pip install .[torch]`|
14
- | NO | YES |`pip install .[torch]`|
15
- | NO | NO | `pip install .` |
10
+ | data has distances | use driving edges | planning to use build-in [Haversine distance](../src/multimodalrouter/graph/graph.py) | installation mode |
11
+ |--------------------|-------------------|---------------------------------------------|-------------------------------------|
12
+ | YES | YES | / |`pip install multimodalrouter[torch]`|
13
+ | YES | NO | / |`pip install multimodalrouter[torch]`|
14
+ | NO | YES | / |`pip install multimodalrouter[torch]`|
15
+ | NO | NO | NO | `pip install multimodalrouter` |
16
+ | / | / | YES | `pip install multimodalrouter[torch]`
17
+
18
+ > Tip: if unsure whether you will need torch or not, install without it first and install torch later if necessary
16
19
 
17
20
  ## Step 2
18
21
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "multimodalrouter"
7
- version = "0.1.2"
7
+ version = "0.1.4"
8
8
  description = "A graph-based routing library for dynamic routing."
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE.md" }
@@ -39,6 +39,7 @@ dependencies = [
39
39
  requires-python = ">=3.11"
40
40
 
41
41
  [project.urls]
42
+ Homepage = "https://github.com/K-T0BIAS/MultiModalRouter"
42
43
  Repository = "https://github.com/K-T0BIAS/MultiModalRouter"
43
44
 
44
45
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multimodalrouter
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: A graph-based routing library for dynamic routing.
5
5
  Author-email: Tobias Karusseit <karusseittobi@gmail.com>
6
6
  License: MIT License
@@ -14,6 +14,7 @@ License: MIT License
14
14
  THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15
15
 
16
16
 
17
+ Project-URL: Homepage, https://github.com/K-T0BIAS/MultiModalRouter
17
18
  Project-URL: Repository, https://github.com/K-T0BIAS/MultiModalRouter
18
19
  Requires-Python: >=3.11
19
20
  Description-Content-Type: text/markdown
@@ -50,6 +51,8 @@ Dynamic: license-file
50
51
 
51
52
  The Multi Modal Router is a graph-based routing engine that allows you to build and query any hub-based network. It supports multiple transport modes like driving, flying, or shipping, and lets you optimize routes by distance, time, or custom metrics. It can be expanded to any n-dimensional space making it versatile in any coordinate space
52
53
 
54
+ > NEWS: v0.1.3 now on pypi ([installation guide](./docs/installation.md))
55
+
53
56
  > NOTE: This project is a work in progress and features might be added and or changed
54
57
 
55
58
  # In depth Documentation
@@ -1,4 +1,4 @@
1
- from .graph import RouteGraph, Hub, EdgeMetadata, OptimizationMetric, Route, VerboseRoute
1
+ from .graph import RouteGraph, Hub, EdgeMetadata, OptimizationMetric, Route, VerboseRoute, Filter
2
2
  from .utils import preprocessor
3
3
 
4
- __all__ = ["RouteGraph", "Hub", "EdgeMetadata", "OptimizationMetric", "Route", "VerboseRoute", "preprocessor"]
4
+ __all__ = ["RouteGraph", "Hub", "EdgeMetadata", "OptimizationMetric", "Route", "VerboseRoute", "preprocessor", "Filter"]
@@ -1,2 +1,2 @@
1
1
  from .graph import RouteGraph # noqa: F401
2
- from .dataclasses import Hub, EdgeMetadata, OptimizationMetric, Route, VerboseRoute # noqa: F401
2
+ from .dataclasses import Hub, EdgeMetadata, OptimizationMetric, Route, VerboseRoute, Filter # noqa: F401
@@ -5,6 +5,7 @@
5
5
 
6
6
  from dataclasses import dataclass
7
7
  from enum import Enum
8
+ from abc import abstractmethod, ABC
8
9
 
9
10
 
10
11
  class OptimizationMetric(Enum):
@@ -49,7 +50,7 @@ class Hub:
49
50
  self.coords: list[float] = coords
50
51
  self.id = id
51
52
  self.hubType = hubType
52
- self.outgoing = {}
53
+ self.outgoing: dict[str, dict[str, EdgeMetadata]] = {}
53
54
 
54
55
  def addOutgoing(self, mode: str, dest_id: str, metrics: EdgeMetadata):
55
56
  if mode not in self.outgoing:
@@ -104,3 +105,35 @@ class Route:
104
105
  class VerboseRoute(Route):
105
106
  """Uses base Route class but adds additional info to hold the edge metadata for every leg"""
106
107
  path: list[tuple[str, str, EdgeMetadata]]
108
+
109
+
110
+ class Filter(ABC):
111
+
112
+ @abstractmethod
113
+ def filterEdge(self, edge: EdgeMetadata) -> bool:
114
+ """
115
+ Return True if you want to keep the edge else False
116
+
117
+ Args:
118
+ edge (EdgeMetadata): Edge to filter
119
+
120
+ Returns:
121
+ bool: True if you want to keep the edge
122
+ """
123
+ pass
124
+
125
+ @abstractmethod
126
+ def filterHub(self, hub: Hub) -> bool:
127
+ """
128
+ Return True if you want to keep the hub else False
129
+
130
+ Args:
131
+ hub (Hub): Hub to filter
132
+
133
+ Returns:
134
+ bool: True if you want to keep the hub
135
+ """
136
+ pass
137
+
138
+ def filter(self, start: Hub, end: Hub, edge: EdgeMetadata) -> bool:
139
+ return self.filterHub(start) and self.filterHub(end) and self.filterEdge(edge)
@@ -9,8 +9,9 @@ import dill
9
9
  import heapq
10
10
  import os
11
11
  import pandas as pd
12
- from .dataclasses import Hub, EdgeMetadata, OptimizationMetric, Route
12
+ from .dataclasses import Hub, EdgeMetadata, OptimizationMetric, Route, Filter
13
13
  from threading import Lock
14
+ from collections import deque
14
15
 
15
16
 
16
17
  class RouteGraph:
@@ -371,10 +372,11 @@ class RouteGraph:
371
372
  self,
372
373
  start_id: str,
373
374
  end_id: str,
374
- allowed_modes: list[str],
375
+ allowed_modes: list[str] = None,
375
376
  optimization_metric: OptimizationMetric | str = OptimizationMetric.DISTANCE,
376
377
  max_segments: int = 10,
377
- verbose: bool = False
378
+ verbose: bool = False,
379
+ custom_filter: Filter = None,
378
380
  ) -> Route | None:
379
381
  """
380
382
  Find the optimal path between two hubs using Dijkstra
@@ -399,6 +401,9 @@ class RouteGraph:
399
401
  if end_hub is None:
400
402
  raise ValueError(f"End hub '{end_id}' not found in graph")
401
403
 
404
+ if allowed_modes is None:
405
+ allowed_modes = list(self.TransportModes.values())
406
+
402
407
  if start_id == end_id:
403
408
  # create a route with only the start hub
404
409
  # no verbose since no edges are needed
@@ -460,6 +465,15 @@ class RouteGraph:
460
465
  if connection_metrics is None: # skip if the connection has no metrics
461
466
  continue
462
467
 
468
+ try:
469
+ next_hub = self.getHubById(next_hub_id)
470
+ except KeyError:
471
+ raise ValueError(
472
+ f"Hub with ID '{next_hub_id}' not found in graph! But it is connected to hub '{current_hub_id}' via mode '{mode}'." # noqa: E501
473
+ )
474
+ if custom_filter is not None and not custom_filter.filter(current_hub, next_hub, connection_metrics):
475
+ continue
476
+
463
477
  # get the selected metric alue for this connection
464
478
  connection_value = connection_metrics.getMetric(optimization_metric)
465
479
  new_metric_value = current_metric_value + connection_value
@@ -488,6 +502,65 @@ class RouteGraph:
488
502
 
489
503
  return None
490
504
 
505
+ def radial_search(
506
+ self,
507
+ hub_id: str,
508
+ radius: float,
509
+ optimization_metric: OptimizationMetric | str = OptimizationMetric.DISTANCE,
510
+ allowed_modes: list[str] = None,
511
+ custom_filter: Filter = None,
512
+ ) -> list[float, Hub]:
513
+ """
514
+ Find all hubs within a given radius of a given hub
515
+ (Note: distance is measured from the connecting paths not direct)
516
+
517
+ Args:
518
+ hub_id: ID of the center hub
519
+ radius: maximum distance from the center hub
520
+ optimization_metric: metric to optimize for (e.g. distance, time, cost)
521
+ allowed_modes: list of allowed transport modes (default: None => all modes)
522
+
523
+ Returns:
524
+ list of tuples containing the metric value and the corresponding hub object
525
+ """
526
+
527
+ center = self.getHubById(hub_id)
528
+ if center is None:
529
+ return [center]
530
+
531
+ if allowed_modes is None:
532
+ allowed_modes = list(self.TransportModes.values())
533
+
534
+ hubsToSearch = deque([center])
535
+ queued = set([hub_id])
536
+ reachableHubs: dict[str, tuple[float, Hub]] = {hub_id: (0.0, center)}
537
+
538
+ while hubsToSearch:
539
+ hub = hubsToSearch.popleft() # get the current hub to search
540
+ currentMetricVal, _ = reachableHubs[hub.id] # get the current metric value
541
+ for mode in allowed_modes:
542
+ outgoing = hub.outgoing.get(mode, {}) # find all outgoing connections
543
+ # dict like {dest_id: EdgeMetadata}
544
+ for id, edgemetadata in outgoing.items(): # iter over outgoing connections
545
+ thisMetricVal = edgemetadata.getMetric(optimization_metric)
546
+ if thisMetricVal is None:
547
+ continue
548
+ nextMetricVal = currentMetricVal + thisMetricVal
549
+ if nextMetricVal > radius:
550
+ continue
551
+ knownMetric = reachableHubs.get(id, None)
552
+ destHub = self.getHubById(id)
553
+ if custom_filter is not None and not custom_filter.filter(hub, destHub, edgemetadata):
554
+ continue
555
+ # only save smaller metric values
556
+ if knownMetric is None or knownMetric[0] > nextMetricVal:
557
+ reachableHubs.update({id: (nextMetricVal, destHub)})
558
+ if id not in queued:
559
+ queued.add(id)
560
+ hubsToSearch.append(destHub)
561
+
562
+ return [v for v in reachableHubs.values()]
563
+
491
564
  def compare_routes(
492
565
  self,
493
566
  start_id: str,