sameer-graph-lib 0.1.0__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.
- sameer_graph_lib/__init__.py +28 -0
- sameer_graph_lib/_h3.py +123 -0
- sameer_graph_lib/affinity_graph.py +775 -0
- sameer_graph_lib/corridor_extractor.py +82 -0
- sameer_graph_lib/geometry.py +75 -0
- sameer_graph_lib/hex_graph.py +15 -0
- sameer_graph_lib/plotting.py +211 -0
- sameer_graph_lib/spatial_ingestor.py +170 -0
- sameer_graph_lib/topology_analyzer.py +174 -0
- sameer_graph_lib-0.1.0.dist-info/METADATA +197 -0
- sameer_graph_lib-0.1.0.dist-info/RECORD +14 -0
- sameer_graph_lib-0.1.0.dist-info/WHEEL +5 -0
- sameer_graph_lib-0.1.0.dist-info/licenses/LICENSE +21 -0
- sameer_graph_lib-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Topology analysis for route affinity graphs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Iterable, List, Sequence
|
|
6
|
+
|
|
7
|
+
import networkx as nx
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TopologyAnalyzer:
|
|
11
|
+
"""Split a graph into its main trunk and residual minor branches."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, graph_or_affinity) -> None:
|
|
14
|
+
self.graph = graph_or_affinity if isinstance(graph_or_affinity, nx.Graph) else graph_or_affinity.graph
|
|
15
|
+
|
|
16
|
+
def decompose_branches(self, seed_hexes: Sequence[str] | None = None) -> dict:
|
|
17
|
+
if self.graph.number_of_nodes() == 0:
|
|
18
|
+
return {
|
|
19
|
+
"main_branch": {
|
|
20
|
+
"node_count": 0,
|
|
21
|
+
"total_count": 0,
|
|
22
|
+
"total_value": 0,
|
|
23
|
+
"hexes": [],
|
|
24
|
+
},
|
|
25
|
+
"minor_branches": [],
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
forest = self._maximum_spanning_forest()
|
|
29
|
+
main_branch = self._choose_main_branch(forest, seed_hexes=seed_hexes)
|
|
30
|
+
main_set = set(main_branch)
|
|
31
|
+
|
|
32
|
+
residual = self.graph.copy()
|
|
33
|
+
residual.remove_nodes_from(main_set)
|
|
34
|
+
|
|
35
|
+
branches = []
|
|
36
|
+
for component in nx.connected_components(residual):
|
|
37
|
+
hexes = self._stable_component_order(component)
|
|
38
|
+
branches.append(
|
|
39
|
+
{
|
|
40
|
+
"node_count": len(hexes),
|
|
41
|
+
"total_count": self._sum_node_attr(hexes, "count"),
|
|
42
|
+
"total_value": self._sum_node_attr(hexes, "value"),
|
|
43
|
+
"hexes": hexes,
|
|
44
|
+
"connects_to_main_at": self._main_attachment(component, main_set),
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
branches.sort(key=lambda item: (item["total_count"], item["node_count"]), reverse=True)
|
|
49
|
+
for idx, branch in enumerate(branches, start=1):
|
|
50
|
+
branch["branch_id"] = idx
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
"main_branch": {
|
|
54
|
+
"node_count": len(main_branch),
|
|
55
|
+
"total_count": self._sum_node_attr(main_branch, "count"),
|
|
56
|
+
"total_value": self._sum_node_attr(main_branch, "value"),
|
|
57
|
+
"hexes": main_branch,
|
|
58
|
+
},
|
|
59
|
+
"minor_branches": branches,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
def _maximum_spanning_forest(self) -> nx.Graph:
|
|
63
|
+
weighted = nx.Graph()
|
|
64
|
+
weighted.add_nodes_from(self.graph.nodes(data=True))
|
|
65
|
+
|
|
66
|
+
for u, v, data in self.graph.edges(data=True):
|
|
67
|
+
copied = dict(data)
|
|
68
|
+
copied["_trunk_strength"] = self._edge_strength(u, v, data)
|
|
69
|
+
weighted.add_edge(u, v, **copied)
|
|
70
|
+
|
|
71
|
+
if weighted.number_of_edges() == 0:
|
|
72
|
+
return weighted
|
|
73
|
+
|
|
74
|
+
return nx.maximum_spanning_tree(weighted, weight="_trunk_strength")
|
|
75
|
+
|
|
76
|
+
def _choose_main_branch(
|
|
77
|
+
self,
|
|
78
|
+
forest: nx.Graph,
|
|
79
|
+
seed_hexes: Sequence[str] | None = None,
|
|
80
|
+
) -> List[str]:
|
|
81
|
+
if forest.number_of_nodes() == 1:
|
|
82
|
+
return list(forest.nodes)
|
|
83
|
+
|
|
84
|
+
candidates = []
|
|
85
|
+
seed_path = self._seed_path(forest, seed_hexes)
|
|
86
|
+
if seed_path:
|
|
87
|
+
candidates.append(seed_path)
|
|
88
|
+
|
|
89
|
+
for component in nx.connected_components(forest):
|
|
90
|
+
subgraph = forest.subgraph(component)
|
|
91
|
+
candidates.append(self._diameter_path(subgraph))
|
|
92
|
+
|
|
93
|
+
return max(
|
|
94
|
+
candidates,
|
|
95
|
+
key=lambda path: (len(path), self._sum_node_attr(path, "count")),
|
|
96
|
+
default=[],
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def _seed_path(self, forest: nx.Graph, seed_hexes: Sequence[str] | None) -> List[str]:
|
|
100
|
+
if not seed_hexes:
|
|
101
|
+
return []
|
|
102
|
+
|
|
103
|
+
present = [cell for cell in seed_hexes if cell in forest]
|
|
104
|
+
if len(present) < 2:
|
|
105
|
+
return []
|
|
106
|
+
|
|
107
|
+
best_path: List[str] = []
|
|
108
|
+
# Limit pair scans for very long seed routes while keeping endpoint intent.
|
|
109
|
+
candidates = present[:25] + present[-25:]
|
|
110
|
+
for idx, source in enumerate(candidates):
|
|
111
|
+
for target in candidates[idx + 1 :]:
|
|
112
|
+
if not nx.has_path(forest, source, target):
|
|
113
|
+
continue
|
|
114
|
+
path = nx.shortest_path(forest, source, target)
|
|
115
|
+
if len(path) > len(best_path):
|
|
116
|
+
best_path = path
|
|
117
|
+
|
|
118
|
+
return best_path
|
|
119
|
+
|
|
120
|
+
def _diameter_path(self, graph: nx.Graph) -> List[str]:
|
|
121
|
+
if graph.number_of_nodes() == 0:
|
|
122
|
+
return []
|
|
123
|
+
if graph.number_of_nodes() == 1:
|
|
124
|
+
return list(graph.nodes)
|
|
125
|
+
|
|
126
|
+
start = next(iter(graph.nodes))
|
|
127
|
+
first = self._farthest_by_hops(graph, start)
|
|
128
|
+
second = self._farthest_by_hops(graph, first)
|
|
129
|
+
return nx.shortest_path(graph, first, second)
|
|
130
|
+
|
|
131
|
+
def _farthest_by_hops(self, graph: nx.Graph, source: str) -> str:
|
|
132
|
+
lengths = nx.single_source_shortest_path_length(graph, source)
|
|
133
|
+
return max(lengths, key=lambda node: (lengths[node], self._node_metric(node)))
|
|
134
|
+
|
|
135
|
+
def _edge_strength(self, u: str, v: str, data: dict) -> float:
|
|
136
|
+
traversal_count = float(data.get("count", 0) or 0)
|
|
137
|
+
endpoint_strength = (self._node_metric(u) + self._node_metric(v)) / 2.0
|
|
138
|
+
distance = max(float(data.get("distance", data.get("weight", 1)) or 1), 1.0)
|
|
139
|
+
return traversal_count * 1000.0 + endpoint_strength - distance * 0.001
|
|
140
|
+
|
|
141
|
+
def _node_metric(self, node: str) -> float:
|
|
142
|
+
return float(self.graph.nodes[node].get("count", 0) or 0)
|
|
143
|
+
|
|
144
|
+
def _sum_node_attr(self, nodes: Iterable[str], attr: str) -> float:
|
|
145
|
+
return sum(float(self.graph.nodes[node].get(attr, 0) or 0) for node in nodes)
|
|
146
|
+
|
|
147
|
+
def _stable_component_order(self, component: Iterable[str]) -> List[str]:
|
|
148
|
+
return sorted(
|
|
149
|
+
component,
|
|
150
|
+
key=lambda node: (
|
|
151
|
+
-float(self.graph.nodes[node].get("count", 0) or 0),
|
|
152
|
+
str(node),
|
|
153
|
+
),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def _main_attachment(self, component: Iterable[str], main_set: set[str]) -> str | None:
|
|
157
|
+
options = []
|
|
158
|
+
for node in component:
|
|
159
|
+
for neighbor in self.graph.neighbors(node):
|
|
160
|
+
if neighbor not in main_set:
|
|
161
|
+
continue
|
|
162
|
+
data = self.graph[node][neighbor]
|
|
163
|
+
options.append(
|
|
164
|
+
(
|
|
165
|
+
-float(data.get("count", 0) or 0),
|
|
166
|
+
float(data.get("distance", data.get("weight", 0)) or 0),
|
|
167
|
+
neighbor,
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
if not options:
|
|
172
|
+
return None
|
|
173
|
+
options.sort()
|
|
174
|
+
return options[0][2]
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sameer-graph-lib
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: H3 and NetworkX based route affinity graph toolkit.
|
|
5
|
+
Author-email: Sameer <sameerkumarroy073@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/iams31/sameer_graph_lib
|
|
8
|
+
Project-URL: Repository, https://github.com/iams31/sameer_graph_lib
|
|
9
|
+
Project-URL: Issues, https://github.com/iams31/sameer_graph_lib/issues
|
|
10
|
+
Keywords: h3,networkx,geospatial,routing,graph
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: h3>=4.0.0
|
|
22
|
+
Requires-Dist: networkx>=3.0
|
|
23
|
+
Provides-Extra: plot
|
|
24
|
+
Requires-Dist: matplotlib>=3.7; extra == "plot"
|
|
25
|
+
Provides-Extra: geo
|
|
26
|
+
Requires-Dist: contextily>=1.5; extra == "geo"
|
|
27
|
+
Requires-Dist: geopandas>=0.14; extra == "geo"
|
|
28
|
+
Requires-Dist: shapely>=2.0; extra == "geo"
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
31
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
32
|
+
Requires-Dist: twine>=5.1; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# sameer-graph-lib
|
|
36
|
+
|
|
37
|
+
`sameer-graph-lib` is an editable Python library for building H3-based route affinity graphs with NetworkX.
|
|
38
|
+
|
|
39
|
+
It turns H3 arrays, latitude/longitude sequences, and encoded polylines into connected hex chains, inserts them into a weighted graph, extracts high-affinity corridors, and decomposes the graph into a main trunk plus minor branches.
|
|
40
|
+
|
|
41
|
+
## Editable install
|
|
42
|
+
|
|
43
|
+
```powershell
|
|
44
|
+
python -m pip install -e ".[dev,plot]"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Because the install is editable, changes you make inside `src/sameer_graph_lib` are picked up immediately by Python without reinstalling.
|
|
48
|
+
|
|
49
|
+
If your machine uses `uv`, run commands through the managed environment:
|
|
50
|
+
|
|
51
|
+
```powershell
|
|
52
|
+
uv run --extra dev --extra plot python -c "import sameer_graph_lib; print(sameer_graph_lib.__version__)"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
From PyPI after publication:
|
|
58
|
+
|
|
59
|
+
```powershell
|
|
60
|
+
pip install sameer-graph-lib
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
With optional plotting and geospatial extras:
|
|
64
|
+
|
|
65
|
+
```powershell
|
|
66
|
+
pip install "sameer-graph-lib[plot,geo]"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Quick start
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from sameer_graph_lib import HexGraph
|
|
73
|
+
|
|
74
|
+
graph = HexGraph(hex_resolution=9)
|
|
75
|
+
|
|
76
|
+
route = graph.add_latlng_sequence([
|
|
77
|
+
(12.9716, 77.5946),
|
|
78
|
+
(12.9760, 77.5990),
|
|
79
|
+
])
|
|
80
|
+
|
|
81
|
+
print(graph.get_graph_stats())
|
|
82
|
+
selected = graph.get_appropriate_hexes(cutoff=0.8)
|
|
83
|
+
fig = graph.visualize_graph(title="80% compact cluster", highlight_hexes=selected)
|
|
84
|
+
print(graph.decompose_branches())
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Main APIs
|
|
88
|
+
|
|
89
|
+
- `SpatialIngestor`: converts H3 arrays, lat/lng sequences, and encoded polylines into contiguous H3 chains.
|
|
90
|
+
- `AffinityGraph`: NetworkX wrapper for array-based insertion, nearest attachment, affinity scoring, editing, and JSON persistence.
|
|
91
|
+
- `CorridorExtractor`: uses exact all-node Dijkstra selection to extract the most compact cluster covering a target percentage of graph traversal volume.
|
|
92
|
+
- `TopologyAnalyzer`: separates the main branch from residual minor branches.
|
|
93
|
+
- `HexGraph`: backwards-compatible convenience class for your original code style.
|
|
94
|
+
|
|
95
|
+
Graph creation follows the original per-node procedure: H3 arrays are normalized, then each hex is inserted with `add_node`/`add_hex`. Lat/lng sequences and encoded polylines are first converted into H3 arrays at the requested resolution, then inserted the same way.
|
|
96
|
+
|
|
97
|
+
For QC, use:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
fig = graph.visualize_graph(highlight_hexes=selected)
|
|
101
|
+
fig.savefig("graph_qc.png", dpi=150, bbox_inches="tight")
|
|
102
|
+
|
|
103
|
+
fig = graph.visualize_step_by_step(route[:5], labels=["A", "B", "C", "D", "E"])
|
|
104
|
+
fig.savefig("insertion_steps.png", dpi=150, bbox_inches="tight")
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
To plot actual H3 hex boundaries as geospatial polygons:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from sameer_graph_lib import plot_h3_cells, plot_h3_cells_map
|
|
111
|
+
|
|
112
|
+
fig = plot_h3_cells("88618c4f29fffff", label_full_hex=True)
|
|
113
|
+
fig.savefig("single_h3_cell.png", dpi=150, bbox_inches="tight")
|
|
114
|
+
|
|
115
|
+
fig = graph.plot_h3_cells(highlight_hexes=selected, show_labels=False)
|
|
116
|
+
fig.savefig("h3_cell_footprint.png", dpi=150, bbox_inches="tight")
|
|
117
|
+
|
|
118
|
+
fig = plot_h3_cells_map(route, selected_cells=selected)
|
|
119
|
+
fig.savefig("h3_cell_basemap.png", dpi=150, bbox_inches="tight")
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`plot_h3_cells_map` uses GeoPandas + Contextily. Install it with:
|
|
123
|
+
|
|
124
|
+
```powershell
|
|
125
|
+
python -m pip install -e ".[plot,geo]"
|
|
126
|
+
uv run --extra plot --extra geo python -c "from sameer_graph_lib import plot_h3_cells_map"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
To get H3 centers and a convex hull:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from sameer_graph_lib import getLatLng, h3_convex_hull
|
|
133
|
+
|
|
134
|
+
points = getLatLng(route) # [(lat, lng), ...]
|
|
135
|
+
hull = h3_convex_hull(route) # Shapely geometry in (lng, lat)
|
|
136
|
+
graph_hull = graph.convex_hull() # Same, using graph nodes
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Useful commands
|
|
140
|
+
|
|
141
|
+
```powershell
|
|
142
|
+
python -m pytest
|
|
143
|
+
python -m build
|
|
144
|
+
uv run --extra dev pytest -q
|
|
145
|
+
uv run --extra dev python -m build
|
|
146
|
+
uv run --extra dev python -m twine check dist/*
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Build artifacts will appear in `dist/` after `python -m build`.
|
|
150
|
+
|
|
151
|
+
## Publish To PyPI
|
|
152
|
+
|
|
153
|
+
1. Build the package:
|
|
154
|
+
|
|
155
|
+
```powershell
|
|
156
|
+
uv run --extra dev python -m build
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
2. Validate the package metadata:
|
|
160
|
+
|
|
161
|
+
```powershell
|
|
162
|
+
uv run --extra dev python -m twine check dist/*
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
3. Upload to PyPI:
|
|
166
|
+
|
|
167
|
+
```powershell
|
|
168
|
+
uv run --extra dev python -m twine upload dist/*
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
After upload, users can install it with:
|
|
172
|
+
|
|
173
|
+
```powershell
|
|
174
|
+
pip install sameer-graph-lib
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Publish From GitHub
|
|
178
|
+
|
|
179
|
+
This repo also includes a Trusted Publishing workflow in
|
|
180
|
+
[.github/workflows/publish.yml](C:/Users/rrran/Desktop/sameer_graph_lib/.github/workflows/publish.yml:1).
|
|
181
|
+
|
|
182
|
+
To finish that setup:
|
|
183
|
+
|
|
184
|
+
1. Create the project on PyPI, or reserve the name `sameer-graph-lib`.
|
|
185
|
+
2. On PyPI, open the project settings and add a Trusted Publisher for:
|
|
186
|
+
`owner`: `iams31`
|
|
187
|
+
`repository`: `sameer_graph_lib`
|
|
188
|
+
`workflow`: `publish.yml`
|
|
189
|
+
`environment`: `pypi`
|
|
190
|
+
3. Create a GitHub Release, or run the workflow manually from the Actions tab.
|
|
191
|
+
|
|
192
|
+
After that, GitHub Actions can publish without storing a long-lived PyPI token.
|
|
193
|
+
|
|
194
|
+
Official references:
|
|
195
|
+
|
|
196
|
+
- PyPI Trusted Publishing: https://docs.pypi.org/trusted-publishers/
|
|
197
|
+
- Packaging guide upload flow: https://packaging.python.org/tutorials/packaging-projects/
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
sameer_graph_lib/__init__.py,sha256=rfzOeoVj_WmdLCH1j2Is2p6mXlvg9ohJ1CAvlH-uFbM,791
|
|
2
|
+
sameer_graph_lib/_h3.py,sha256=Vlch4zX9SR6hg4S-5lYGDNEwEH9IVosRIEg_n2Emq0I,3655
|
|
3
|
+
sameer_graph_lib/affinity_graph.py,sha256=DCFLbRJ3rTmHR5oBtxNAyAZWE7sxn-rFwf3pPgmfptk,28681
|
|
4
|
+
sameer_graph_lib/corridor_extractor.py,sha256=V77VtxPHWBAJYzQz553TdoRpuo3KdnEeI6H3iPex998,2667
|
|
5
|
+
sameer_graph_lib/geometry.py,sha256=unCcwqZU96kewJTNpaym31B2o32v6xsmiP2hdv2Y6VY,2288
|
|
6
|
+
sameer_graph_lib/hex_graph.py,sha256=51VHsD5_xVS95UW4viiBjffHdr9xziDg_YQeIHplWsU,454
|
|
7
|
+
sameer_graph_lib/plotting.py,sha256=DPuUdSmoJMUQloWqEV-bauZXJjUFvKWUCtTs2WI8dvM,6597
|
|
8
|
+
sameer_graph_lib/spatial_ingestor.py,sha256=3GDaTWT5etbC55fIzSTB5D0gS3HzyRzyMRKR0tjEsw0,5608
|
|
9
|
+
sameer_graph_lib/topology_analyzer.py,sha256=JfZtbBeIrTZaZdzG41fsnoNZgcssml4iSZWVWAevBSc,6367
|
|
10
|
+
sameer_graph_lib-0.1.0.dist-info/licenses/LICENSE,sha256=5VuvbSI3M1-d6PVSlQm5_zjokepvD9eFuWOvXS0udls,1069
|
|
11
|
+
sameer_graph_lib-0.1.0.dist-info/METADATA,sha256=8UElHhnErBPw92gnA7-P8uHFsHd1S2tRdSKdMr9qmTs,6282
|
|
12
|
+
sameer_graph_lib-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
13
|
+
sameer_graph_lib-0.1.0.dist-info/top_level.txt,sha256=tgFV-BCa4PLQQ0AF9nOM1VscPuEX4LlO-DTe6H_Xnv8,17
|
|
14
|
+
sameer_graph_lib-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sameer Kumar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sameer_graph_lib
|