multimodalrouter 0.1.3__py3-none-any.whl → 0.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

@@ -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,8 @@ class Hub:
49
50
  self.coords: list[float] = coords
50
51
  self.id = id
51
52
  self.hubType = hubType
52
- self.outgoing = {}
53
+ # dict like {mode -> {dest_id -> EdgeMetadata}}
54
+ self.outgoing: dict[str, dict[str, EdgeMetadata]] = {}
53
55
 
54
56
  def addOutgoing(self, mode: str, dest_id: str, metrics: EdgeMetadata):
55
57
  if mode not in self.outgoing:
@@ -104,3 +106,35 @@ class Route:
104
106
  class VerboseRoute(Route):
105
107
  """Uses base Route class but adds additional info to hold the edge metadata for every leg"""
106
108
  path: list[tuple[str, str, EdgeMetadata]]
109
+
110
+
111
+ class Filter(ABC):
112
+
113
+ @abstractmethod
114
+ def filterEdge(self, edge: EdgeMetadata) -> bool:
115
+ """
116
+ Return True if you want to keep the edge else False
117
+
118
+ Args:
119
+ edge (EdgeMetadata): Edge to filter
120
+
121
+ Returns:
122
+ bool: True if you want to keep the edge
123
+ """
124
+ pass
125
+
126
+ @abstractmethod
127
+ def filterHub(self, hub: Hub) -> bool:
128
+ """
129
+ Return True if you want to keep the hub else False
130
+
131
+ Args:
132
+ hub (Hub): Hub to filter
133
+
134
+ Returns:
135
+ bool: True if you want to keep the hub
136
+ """
137
+ pass
138
+
139
+ def filter(self, start: Hub, end: Hub, edge: EdgeMetadata) -> bool:
140
+ 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,
@@ -0,0 +1 @@
1
+ from .graphicsWrapper import GraphDisplay # noqa: F401
@@ -0,0 +1,323 @@
1
+ # dataclasses.py
2
+ # Copyright (c) 2025 Tobias Karusseit
3
+ # Licensed under the MIT License. See LICENSE file in the project root for full license information.
4
+
5
+
6
+ from ..graph import RouteGraph
7
+ import plotly.graph_objects as go
8
+
9
+
10
+ class GraphDisplay():
11
+
12
+ def __init__(self, graph: RouteGraph, name: str = "Graph", iconSize: int = 10) -> None:
13
+ self.graph: RouteGraph = graph
14
+ self.name: str = name
15
+ self.iconSize: int = iconSize
16
+
17
+ def _toPlotlyFormat(
18
+ self,
19
+ nodeTransform=None,
20
+ edgeTransform=None
21
+ ):
22
+ """
23
+ transform the graph data into plotly format.to use the display function
24
+
25
+ args:
26
+ - nodeTransform: function to transform the node coordinates (default = None)
27
+ - edgeTransform: function to transform the edge coordinates (default = None)
28
+ returns:
29
+ - None (modifies self.nodes and self.edges)
30
+ """
31
+ self.nodes = {
32
+ f"{hub.hubType}-{hub.id}": {
33
+ "coords": hub.coords,
34
+ "hubType": hub.hubType,
35
+ "id": hub.id
36
+ }
37
+ for hub in self.graph._allHubs()
38
+ }
39
+
40
+ self.edges = [
41
+ {
42
+ "from": f"{hub.hubType}-{hub.id}",
43
+ "to": f"{self.graph.getHubById(dest).hubType}-{dest}",
44
+ **edge.allMetrics
45
+ }
46
+ for hub in self.graph._allHubs()
47
+ for _, edge in hub.outgoing.items()
48
+ for dest, edge in edge.items()
49
+ ]
50
+ self.dim = max(len(node.get("coords")) for node in self.nodes.values())
51
+
52
+ if nodeTransform is not None:
53
+ expandedCoords = [node.get("coords") + [0] * (self.dim - len(node.get("coords"))) for node in self.nodes.values()]
54
+ transformedCoords = nodeTransform(expandedCoords)
55
+ for node, coords in zip(self.nodes.values(), transformedCoords):
56
+ node["coords"] = coords
57
+
58
+ self.dim = max(len(node.get("coords")) for node in self.nodes.values())
59
+
60
+ if edgeTransform is not None:
61
+ starts = [edge["from"] for edge in self.edges]
62
+ startCoords = [self.nodes[start]["coords"] for start in starts]
63
+ ends = [edge["to"] for edge in self.edges]
64
+ endCoords = [self.nodes[end]["coords"] for end in ends]
65
+
66
+ transformedEdges = edgeTransform(startCoords, endCoords)
67
+ for edge, transformedEdge in zip(self.edges, transformedEdges):
68
+ edge["curve"] = transformedEdge
69
+
70
+ def display(
71
+ self,
72
+ nodeTransform=None,
73
+ edgeTransform=None,
74
+ displayEarth=False
75
+ ):
76
+ """
77
+ function to display any 2D or 3D RouteGraph
78
+
79
+ args:
80
+ - nodeTransform: function to transform the node coordinates (default = None)
81
+ - edgeTransform: function to transform the edge coordinates (default = None)
82
+ - displayEarth: whether to display the earth as a background (default = False, only in 3D)
83
+
84
+ returns:
85
+ - None (modifies self.nodes and self.edges opens the plot in a browser)
86
+
87
+ """
88
+ # transform the graph
89
+ self._toPlotlyFormat(nodeTransform, edgeTransform)
90
+ # init plotly placeholders
91
+ node_x, node_y, node_z, text, colors = [], [], [], [], []
92
+ edge_x, edge_y, edge_z, edge_text = [], [], [], []
93
+
94
+ # add all the nodes
95
+ for node_key, node_data in self.nodes.items():
96
+ x, y, *rest = node_data["coords"]
97
+ node_x.append(x)
98
+ node_y.append(y)
99
+ if self.dim == 3:
100
+ node_z.append(node_data["coords"][2])
101
+ text.append(f"{node_data['id']}<br>Type: {node_data['hubType']}")
102
+ colors.append(hash(node_data['hubType']) % 10)
103
+
104
+ # add all the edges
105
+ for edge in self.edges:
106
+ # check if edge has been transformed
107
+ if "curve" in edge:
108
+ curve = edge["curve"]
109
+ # add all the points of the edge
110
+ for point in curve:
111
+ edge_x.append(point[0])
112
+ edge_y.append(point[1])
113
+ if self.dim == 3:
114
+ edge_z.append(point[2])
115
+ edge_x.append(None)
116
+ edge_y.append(None)
117
+ # if 3d add the extra none to close the edge
118
+ if self.dim == 3:
119
+ edge_z.append(None)
120
+ else:
121
+ source = self.nodes[edge["from"]]["coords"]
122
+ target = self.nodes[edge["to"]]["coords"]
123
+
124
+ edge_x += [source[0], target[0], None]
125
+ edge_y += [source[1], target[1], None]
126
+
127
+ if self.dim == 3:
128
+ edge_z += [source[2], target[2], None]
129
+
130
+ # add text and hover display
131
+ hover = f"{edge['from']} → {edge['to']}"
132
+ metrics = {k: v for k, v in edge.items() if k not in ("from", "to", "curve")}
133
+ if metrics:
134
+ hover += "<br>" + "<br>".join(f"{k}: {v}" for k, v in metrics.items())
135
+ edge_text.append(hover)
136
+
137
+ if self.dim == 2:
138
+ # ceate the plot in 2d
139
+ node_trace = go.Scatter(
140
+ x=node_x,
141
+ y=node_y,
142
+ mode="markers",
143
+ hoverinfo="text",
144
+ text=text,
145
+ marker=dict(
146
+ size=self.iconSize,
147
+ color=colors,
148
+ colorscale="Viridis",
149
+ showscale=True
150
+ )
151
+ )
152
+
153
+ edge_trace = go.Scatter(
154
+ x=edge_x,
155
+ y=edge_y,
156
+ line=dict(width=2, color="#888"),
157
+ hoverinfo="text",
158
+ text=edge_text,
159
+ mode="lines"
160
+ )
161
+
162
+ elif self.dim == 3:
163
+ # create the plot in 3d
164
+ node_trace = go.Scatter3d(
165
+ x=node_x,
166
+ y=node_y,
167
+ z=node_z,
168
+ mode="markers",
169
+ hoverinfo="text",
170
+ text=text,
171
+ marker=dict(
172
+ size=self.iconSize,
173
+ color=colors,
174
+ colorscale="Viridis",
175
+ showscale=True
176
+ )
177
+ )
178
+
179
+ edge_trace = go.Scatter3d(
180
+ x=edge_x,
181
+ y=edge_y,
182
+ z=edge_z,
183
+ line=dict(width=1, color="#888"),
184
+ hoverinfo="text",
185
+ text=edge_text,
186
+ mode="lines",
187
+ opacity=0.6
188
+ )
189
+
190
+ # create the plotly figure
191
+ fig = go.Figure(data=[edge_trace, node_trace])
192
+ # render earth / sphere in 3d
193
+ if self.dim == 3 and displayEarth:
194
+ try:
195
+ import numpy as np
196
+ R = 6369.9 # sphere radius
197
+ u = np.linspace(0, 2 * np.pi, 50) # azimuthal angle
198
+ v = np.linspace(0, np.pi, 50) # polar angle
199
+ u, v = np.meshgrid(u, v)
200
+
201
+ # Cartesian coordinates
202
+ x = R * np.cos(u) * np.sin(v)
203
+ y = R * np.sin(u) * np.sin(v)
204
+ z = R * np.cos(v)
205
+ except ImportError:
206
+ raise ImportError("numpy is required to display the earth")
207
+
208
+ sphere_surface = go.Surface(
209
+ x=x, y=y, z=z,
210
+ colorscale='Blues',
211
+ opacity=1,
212
+ showscale=False,
213
+ hoverinfo='skip'
214
+ )
215
+
216
+ fig.add_trace(sphere_surface)
217
+
218
+ fig.update_layout(title="Interactive Graph", showlegend=False, hovermode="closest")
219
+ fig.show()
220
+
221
+ @staticmethod
222
+ def degreesToCartesian3D(coords):
223
+ try:
224
+ import torch
225
+ C = torch.tensor(coords)
226
+ if C.dim() == 1:
227
+ C = C.unsqueeze(0)
228
+ R = 6371.0
229
+ lat = torch.deg2rad(C[:, 0])
230
+ lng = torch.deg2rad(C[:, 1])
231
+ x = R * torch.cos(lat) * torch.cos(lng)
232
+ y = R * torch.cos(lat) * torch.sin(lng)
233
+ z = R * torch.sin(lat)
234
+ return list(torch.stack((x, y, z), dim=1).numpy())
235
+ except ImportError:
236
+ import math
237
+ R = 6371.0
238
+ output = []
239
+ for lat, lng in coords:
240
+ lat = math.radians(lat)
241
+ lng = math.radians(lng)
242
+ x = R * math.cos(lat) * math.cos(lng)
243
+ y = R * math.cos(lat) * math.sin(lng)
244
+ z = R * math.sin(lat)
245
+ output.append([x, y, z])
246
+ return output
247
+
248
+ @staticmethod
249
+ def curvedEdges(start, end, R=6371.0, H=0.05, n=20):
250
+ try:
251
+ # if torch and np are available calc vectorized graeter circle curves
252
+ import numpy as np
253
+ import torch
254
+
255
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
256
+
257
+ start_np = np.array(start, dtype=np.float32)
258
+ end_np = np.array(end, dtype=np.float32)
259
+
260
+ start = torch.tensor(start_np, device=device)
261
+ end = torch.tensor(end_np, device=device)
262
+ start = start.float()
263
+ end = end.float()
264
+
265
+ # normalize to sphere
266
+ start_norm = R * start / start.norm(dim=1, keepdim=True)
267
+ end_norm = R * end / end.norm(dim=1, keepdim=True)
268
+
269
+ # compute angle between vectors
270
+ dot = (start_norm * end_norm).sum(dim=1, keepdim=True) / (R**2)
271
+ dot = torch.clamp(dot, -1.0, 1.0)
272
+ theta = torch.acos(dot).unsqueeze(2) # shape: (num_edges,1,1)
273
+
274
+ # linear interpolation along great circle
275
+ t = torch.linspace(0, 1, n, device=device).view(1, n, 1)
276
+ one_minus_t = 1 - t
277
+ sin_theta = torch.sin(theta)
278
+ sin_theta[sin_theta == 0] = 1e-6
279
+
280
+ factor_start = torch.sin(one_minus_t * theta) / sin_theta
281
+ factor_end = torch.sin(t * theta) / sin_theta
282
+
283
+ curve = factor_start * start_norm.unsqueeze(1) + factor_end * end_norm.unsqueeze(1)
284
+
285
+ # normalize to radius
286
+ curve = R * curve / curve.norm(dim=2, keepdim=True)
287
+
288
+ # apply radial lift at curve center using sin weight
289
+ weight = torch.sin(torch.pi * t) # 0 at endpoints, 1 at center
290
+ curve = curve * (1 + H * weight)
291
+
292
+ return curve
293
+ except ImportError:
294
+ # fallback to calculating quadratic bezier curves with math
295
+ import math
296
+ curves_all = []
297
+
298
+ def multiply_vec(vec, factor):
299
+ return [factor * x for x in vec]
300
+
301
+ def add_vec(*vecs):
302
+ return [sum(items) for items in zip(*vecs)]
303
+
304
+ for startP, endP in zip(start, end):
305
+ mid = [(s + e) / 2 for s, e in zip(startP, endP)]
306
+ norm = math.sqrt(sum(c ** 2 for c in mid))
307
+ mid_proj = [R * c / norm for c in mid]
308
+ mid_arch = [c * (1 + H) for c in mid_proj]
309
+
310
+ curve = []
311
+ for i in range(n):
312
+ t_i = i / (n - 1)
313
+ one_minus_t = 1 - t_i
314
+ point = add_vec(
315
+ multiply_vec(startP, one_minus_t ** 2),
316
+ multiply_vec(mid_arch, 2 * one_minus_t * t_i),
317
+ multiply_vec(endP, t_i ** 2)
318
+ )
319
+ curve.append(point)
320
+
321
+ curves_all.append(curve)
322
+
323
+ return curves_all
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multimodalrouter
3
- Version: 0.1.3
3
+ Version: 0.1.5
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
@@ -19,6 +19,7 @@ Project-URL: Repository, https://github.com/K-T0BIAS/MultiModalRouter
19
19
  Requires-Python: >=3.11
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE.md
22
+ License-File: NOTICE.md
22
23
  Requires-Dist: colorama>=0.4.6
23
24
  Requires-Dist: dill>=0.4.0
24
25
  Requires-Dist: filelock>=3.19.1
@@ -45,6 +46,9 @@ Provides-Extra: torch
45
46
  Requires-Dist: torch>=2.8.0; extra == "torch"
46
47
  Provides-Extra: dev
47
48
  Requires-Dist: pytest>=8.0; extra == "dev"
49
+ Requires-Dist: plotly>=6.3.0; extra == "dev"
50
+ Provides-Extra: plotly
51
+ Requires-Dist: plotly>=6.3.0; extra == "plotly"
48
52
  Dynamic: license-file
49
53
 
50
54
  # Multi Modal Router
@@ -95,6 +99,12 @@ The graph can be build from any data aslong as the required fields are present (
95
99
 
96
100
  ![example from the maze solver](./docs/solvedMaze1.png)
97
101
 
102
+ ## graph visualizations
103
+
104
+ Use the build-in [visualization](./docs/visualization.md) tool to plot any `2D` or `3D` Graph.
105
+
106
+ ![example plot of flight paths](./docs/FlightPathPlot.png)
107
+
98
108
  ## Important considerations for your usecase
99
109
 
100
110
  Depending on your usecase and datasets some features may not be usable see solutions below
@@ -116,4 +126,6 @@ Depending on your usecase and datasets some features may not be usable see solut
116
126
 
117
127
  [see here](./LICENSE.md)
118
128
 
129
+ [dependencies](./NOTICE.md)
130
+
119
131
 
@@ -0,0 +1,18 @@
1
+ multimodalrouter/__init__.py,sha256=Kva-wGkYdCi4GveSLe6emkTjd13C3K_ynhihADTQxZY,256
2
+ multimodalrouter/graph/__init__.py,sha256=zvKvSpZ8PbuOpG6DL14CJPV34ZLYderNJV4Amw1jsLI,150
3
+ multimodalrouter/graph/dataclasses.py,sha256=-vCA3VRGBeLTEOZpYyYrGM5O24jnGRS36eGwZWDKISQ,4464
4
+ multimodalrouter/graph/graph.py,sha256=k20ynHXPwVdZcjjp8a3dzCOuEHBPtEjnpauHtcvry9Y,23314
5
+ multimodalrouter/graphics/__init__.py,sha256=F_KttIwUySzJs_C9OCDR2Zptu-tsuaF3gauSZyZyimo,56
6
+ multimodalrouter/graphics/graphicsWrapper.py,sha256=8k6XidKINOVpe7sPQqjGkRNVsYgS78_ex4l-4iKCFGk,11850
7
+ multimodalrouter/router/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ multimodalrouter/router/build.py,sha256=jrGcFyVS7-Qg6CXAVVwiXM_krLBHBoRH85Slknmutns,2843
9
+ multimodalrouter/router/route.py,sha256=5RZGFgrTYm930RJX6i26d8f8_k7CGnp5lKZJBM1gcKk,1956
10
+ multimodalrouter/utils/__init__.py,sha256=jsZ7cBB-9otxzMiN9FVviGuknxnOPmaG2RBbZuiezeg,53
11
+ multimodalrouter/utils/preprocessor.py,sha256=45ya0cg0PGCV3YMk680_HZUve1QGJ7JHPSHoRvYdleY,6333
12
+ multimodalrouter-0.1.5.dist-info/licenses/LICENSE.md,sha256=CRtvaQsLnzHvSIzusV5sHHw-e8w8gytXq8R7AP1GBmE,1092
13
+ multimodalrouter-0.1.5.dist-info/licenses/NOTICE.md,sha256=Fv3AK0KmlZGgmf0_4yM8ZtOTbv2mIfQVHQkZ4hFZXb8,3313
14
+ multimodalrouter-0.1.5.dist-info/METADATA,sha256=gjV64EK-1p_dUxfW02Y0bJqEbzpvAx4GNU6xvQ74vek,6333
15
+ multimodalrouter-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
+ multimodalrouter-0.1.5.dist-info/entry_points.txt,sha256=vp177Z2KMWPGJkS_dVpX05LVtBYssdlyYhTCG0kYmjo,138
17
+ multimodalrouter-0.1.5.dist-info/top_level.txt,sha256=4RYMG9hyl8mDNJ_gTrlh8QdYjZNXLDBzFVK1PcTpAYg,17
18
+ multimodalrouter-0.1.5.dist-info/RECORD,,
@@ -0,0 +1,44 @@
1
+ # Dependencies and Licenses
2
+
3
+ This project `MultiModalRouter` depends on the following libraries. All licenses are permissive and compatible with MIT licensing for this project.
4
+
5
+ | Package | Version | License | License Link |
6
+ |---------|---------|---------|--------------|
7
+ | colorama | >=0.4.6 | BSD 3-Clause | [License](https://github.com/tartley/colorama/blob/master/LICENSE) |
8
+ | dill | >=0.4.0 | BSD | [License](https://github.com/uqfoundation/dill/blob/main/LICENSE) |
9
+ | filelock | >=3.19.1 | MIT | [License](https://github.com/tox-dev/py-filelock/blob/main/LICENSE) |
10
+ | fsspec | >=2025.9.0 | Apache 2.0 | [License](https://github.com/fsspec/filesystem_spec/blob/main/LICENSE) |
11
+ | Jinja2 | >=3.1.6 | BSD-3-Clause | [License](https://github.com/pallets/jinja/blob/main/LICENSE) |
12
+ | MarkupSafe | >=3.0.2 | BSD-3-Clause | [License](https://github.com/pallets/markupsafe/blob/main/LICENSE) |
13
+ | mpmath | >=1.3.0 | BSD | [License](https://github.com/fredrik-johansson/mpmath/blob/master/LICENSE) |
14
+ | networkx | >=3.5 | BSD | [License](https://github.com/networkx/networkx/blob/main/LICENSE.txt) |
15
+ | numpy | >=2.3.3 | BSD | [License](https://github.com/numpy/numpy/blob/main/LICENSE.txt) |
16
+ | pandas | >=2.3.2 | BSD-3-Clause | [License](https://github.com/pandas-dev/pandas/blob/main/LICENSE) |
17
+ | parquet | >=1.3.1 | Apache 2.0 | [License](https://github.com/urschrei/parquet-python/blob/master/LICENSE) |
18
+ | ply | >=3.11 | BSD | [License](https://github.com/dabeaz/ply/blob/master/LICENSE.txt) |
19
+ | pyarrow | >=21.0.0 | Apache 2.0 | [License](https://github.com/apache/arrow/blob/master/LICENSE) |
20
+ | python-dateutil | >=2.9.0.post0 | BSD | [License](https://github.com/dateutil/dateutil/blob/master/LICENSE.txt) |
21
+ | pytz | >=2025.2 | MIT | [License](https://github.com/stub42/pytz/blob/master/LICENSE) |
22
+ | setuptools | >=80.9.0 | MIT | [License](https://github.com/pypa/setuptools/blob/main/LICENSE) |
23
+ | six | >=1.17.0 | MIT | [License](https://github.com/benjaminp/six/blob/master/LICENSE) |
24
+ | sympy | >=1.14.0 | BSD | [License](https://github.com/sympy/sympy/blob/master/LICENSE) |
25
+ | thriftpy2 | >=0.5.3 | MIT | [License](https://github.com/Thriftpy/thriftpy2/blob/master/LICENSE) |
26
+ | tqdm | >=4.67.1 | MPL 2.0 | [License](https://github.com/tqdm/tqdm/blob/master/LICENSE) |
27
+ | typing_extensions | >=4.15.0 | PSF | [License](https://github.com/python/typing_extensions/blob/main/LICENSE) |
28
+ | tzdata | >=2025.2 | Public Domain | [License](https://github.com/python/tzdata) |
29
+
30
+ ## Optional Dependencies
31
+
32
+ | Package | Version | License | License Link |
33
+ |---------|---------|---------|--------------|
34
+ | torch | >=2.8.0 | BSD | [License](https://github.com/pytorch/pytorch/blob/master/LICENSE) |
35
+ | plotly | >=6.3.0 | MIT | [License](https://github.com/plotly/plotly.py/blob/master/LICENSE) |
36
+ | pytest | >=8.0 | MIT | [License](https://github.com/pytest-dev/pytest/blob/main/LICENSE) |
37
+
38
+ ---
39
+
40
+ ### Notes
41
+
42
+ 1. All packages listed above are permissively licensed (MIT, BSD, Apache 2.0, or Public Domain), so they are compatible with MIT licensing for this project.
43
+ 2. If distributing this library, include this `DEPENDENCIES.md` file and your own MIT license file to give proper attribution.
44
+ 3. Optional dependencies should be listed in documentation or `pyproject.toml` extras.
@@ -1,15 +0,0 @@
1
- multimodalrouter/__init__.py,sha256=3O9ft058b5x09ORYD8Luw1qyEYoAx4kFPMjBs2HkfBM,238
2
- multimodalrouter/graph/__init__.py,sha256=FSIR6ZN-IM0r-NRcM81AiPn2FVe9fA8rkc9r2746Lr4,142
3
- multimodalrouter/graph/dataclasses.py,sha256=Bmcg3NFfFyPJS2sJO-IO0TnNCJ_pCG_n9lfJKVvFjxI,3535
4
- multimodalrouter/graph/graph.py,sha256=UPFcFIxhlJ1gfpHG8mPWAGiRjxVgEy_VTv16AneSjYM,19944
5
- multimodalrouter/router/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- multimodalrouter/router/build.py,sha256=jrGcFyVS7-Qg6CXAVVwiXM_krLBHBoRH85Slknmutns,2843
7
- multimodalrouter/router/route.py,sha256=5RZGFgrTYm930RJX6i26d8f8_k7CGnp5lKZJBM1gcKk,1956
8
- multimodalrouter/utils/__init__.py,sha256=jsZ7cBB-9otxzMiN9FVviGuknxnOPmaG2RBbZuiezeg,53
9
- multimodalrouter/utils/preprocessor.py,sha256=45ya0cg0PGCV3YMk680_HZUve1QGJ7JHPSHoRvYdleY,6333
10
- multimodalrouter-0.1.3.dist-info/licenses/LICENSE.md,sha256=CRtvaQsLnzHvSIzusV5sHHw-e8w8gytXq8R7AP1GBmE,1092
11
- multimodalrouter-0.1.3.dist-info/METADATA,sha256=5nQMehbzF0Hg6LUBHdTGUiwRpdO5ss7LXjbpsM98Y2w,5971
12
- multimodalrouter-0.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
- multimodalrouter-0.1.3.dist-info/entry_points.txt,sha256=vp177Z2KMWPGJkS_dVpX05LVtBYssdlyYhTCG0kYmjo,138
14
- multimodalrouter-0.1.3.dist-info/top_level.txt,sha256=4RYMG9hyl8mDNJ_gTrlh8QdYjZNXLDBzFVK1PcTpAYg,17
15
- multimodalrouter-0.1.3.dist-info/RECORD,,