arx-net 0.1.0__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.
- arx_net-0.1.0/LICENSE +21 -0
- arx_net-0.1.0/PKG-INFO +98 -0
- arx_net-0.1.0/README.md +84 -0
- arx_net-0.1.0/pyproject.toml +28 -0
- arx_net-0.1.0/setup.cfg +4 -0
- arx_net-0.1.0/src/arx_net/__init__.py +9 -0
- arx_net-0.1.0/src/arx_net/convert.py +197 -0
- arx_net-0.1.0/src/arx_net/parser.py +100 -0
- arx_net-0.1.0/src/arx_net.egg-info/PKG-INFO +98 -0
- arx_net-0.1.0/src/arx_net.egg-info/SOURCES.txt +10 -0
- arx_net-0.1.0/src/arx_net.egg-info/dependency_links.txt +1 -0
- arx_net-0.1.0/src/arx_net.egg-info/top_level.txt +1 -0
arx_net-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dwarakesh
|
|
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.
|
arx_net-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: arx-net
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Graph parsing for arx net (https://dwarakesh-v.github.io/arx-net/) and graph representation conversion library.
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: arx-net,graph,network,adjacency,graph-parser
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# Arx-Net
|
|
16
|
+
|
|
17
|
+
A lightweight Python library for parsing graph edge strings and converting
|
|
18
|
+
between common graph representations.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install arx-net
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- Parse edge strings
|
|
29
|
+
- Directed and undirected graphs
|
|
30
|
+
- Weighted and unweighted graphs
|
|
31
|
+
- Convert between
|
|
32
|
+
|
|
33
|
+
- adjacency lists
|
|
34
|
+
- edge lists
|
|
35
|
+
- adjacency matrices
|
|
36
|
+
|
|
37
|
+
## Example
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import arx_net
|
|
41
|
+
|
|
42
|
+
graph = arx_net.parse_edges(
|
|
43
|
+
"(A,B,4),(B,C,2),(C,D)"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
print(graph)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Output
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
{
|
|
53
|
+
"A": [("B",4)],
|
|
54
|
+
"B": [("C",2)],
|
|
55
|
+
"C": [("D",1)],
|
|
56
|
+
"D": []
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Convert formats
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
edges = arx_net.convert_type(graph, "edge")
|
|
64
|
+
matrix, order = arx_net.convert_type(graph, "matrix")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Supported formats
|
|
68
|
+
|
|
69
|
+
### Adjacency List
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
{
|
|
73
|
+
"A": ["B", "C"]
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Edge List
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
[
|
|
81
|
+
("A","B"),
|
|
82
|
+
("A","C")
|
|
83
|
+
]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Matrix
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
[
|
|
90
|
+
[0,1,1],
|
|
91
|
+
[0,0,1],
|
|
92
|
+
[0,0,0]
|
|
93
|
+
]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
MIT
|
arx_net-0.1.0/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Arx-Net
|
|
2
|
+
|
|
3
|
+
A lightweight Python library for parsing graph edge strings and converting
|
|
4
|
+
between common graph representations.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install arx-net
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- Parse edge strings
|
|
15
|
+
- Directed and undirected graphs
|
|
16
|
+
- Weighted and unweighted graphs
|
|
17
|
+
- Convert between
|
|
18
|
+
|
|
19
|
+
- adjacency lists
|
|
20
|
+
- edge lists
|
|
21
|
+
- adjacency matrices
|
|
22
|
+
|
|
23
|
+
## Example
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import arx_net
|
|
27
|
+
|
|
28
|
+
graph = arx_net.parse_edges(
|
|
29
|
+
"(A,B,4),(B,C,2),(C,D)"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
print(graph)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Output
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
{
|
|
39
|
+
"A": [("B",4)],
|
|
40
|
+
"B": [("C",2)],
|
|
41
|
+
"C": [("D",1)],
|
|
42
|
+
"D": []
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Convert formats
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
edges = arx_net.convert_type(graph, "edge")
|
|
50
|
+
matrix, order = arx_net.convert_type(graph, "matrix")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Supported formats
|
|
54
|
+
|
|
55
|
+
### Adjacency List
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
{
|
|
59
|
+
"A": ["B", "C"]
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Edge List
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
[
|
|
67
|
+
("A","B"),
|
|
68
|
+
("A","C")
|
|
69
|
+
]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Matrix
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
[
|
|
76
|
+
[0,1,1],
|
|
77
|
+
[0,0,1],
|
|
78
|
+
[0,0,0]
|
|
79
|
+
]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
MIT
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=69"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "arx-net"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Graph parsing for arx net (https://dwarakesh-v.github.io/arx-net/) and graph representation conversion library."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
|
|
13
|
+
keywords = [
|
|
14
|
+
"arx-net",
|
|
15
|
+
"graph",
|
|
16
|
+
"network",
|
|
17
|
+
"adjacency",
|
|
18
|
+
"graph-parser"
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[tool.setuptools.packages.find]
|
|
28
|
+
where = ["src"]
|
arx_net-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
from warnings import warn
|
|
2
|
+
|
|
3
|
+
# Type conversion
|
|
4
|
+
# Parse to canonical edges
|
|
5
|
+
def _parse_adj(adj):
|
|
6
|
+
nodes = set(adj.keys())
|
|
7
|
+
edges = []
|
|
8
|
+
for src, neighbors in adj.items():
|
|
9
|
+
for nb in neighbors:
|
|
10
|
+
if isinstance(nb, (tuple, list)) and len(nb) == 2:
|
|
11
|
+
tgt, w = nb
|
|
12
|
+
else:
|
|
13
|
+
tgt, w = nb, None
|
|
14
|
+
nodes.add(tgt)
|
|
15
|
+
edges.append((src, tgt, w))
|
|
16
|
+
return edges, nodes
|
|
17
|
+
|
|
18
|
+
def _parse_edge_list(edge_list):
|
|
19
|
+
nodes = set()
|
|
20
|
+
edges = []
|
|
21
|
+
for e in edge_list:
|
|
22
|
+
if isinstance(e, dict):
|
|
23
|
+
src = e.get('source', e.get('src'))
|
|
24
|
+
tgt = e.get('target', e.get('tgt'))
|
|
25
|
+
w = e.get('weight', e.get('w'))
|
|
26
|
+
elif len(e) == 3:
|
|
27
|
+
src, tgt, w = e
|
|
28
|
+
elif len(e) == 2:
|
|
29
|
+
src, tgt, w = *e, None
|
|
30
|
+
else:
|
|
31
|
+
warn(f"Skipping malformed edge: {e}")
|
|
32
|
+
continue
|
|
33
|
+
if src is None or tgt is None:
|
|
34
|
+
warn(f"Skipping edge with missing src/tgt: {e}")
|
|
35
|
+
continue
|
|
36
|
+
nodes.update([src, tgt])
|
|
37
|
+
edges.append((src, tgt, w))
|
|
38
|
+
return edges, nodes
|
|
39
|
+
|
|
40
|
+
def _parse_matrix(matrix):
|
|
41
|
+
edges = []
|
|
42
|
+
if isinstance(matrix, dict):
|
|
43
|
+
nodes = set(matrix.keys())
|
|
44
|
+
for src, row in matrix.items():
|
|
45
|
+
for tgt, val in row.items():
|
|
46
|
+
nodes.add(tgt)
|
|
47
|
+
if val != 0:
|
|
48
|
+
edges.append((src, tgt, val))
|
|
49
|
+
else:
|
|
50
|
+
n = len(matrix)
|
|
51
|
+
nodes = set(range(n))
|
|
52
|
+
for i, row in enumerate(matrix):
|
|
53
|
+
for j, val in enumerate(row):
|
|
54
|
+
if val != 0:
|
|
55
|
+
edges.append((i, j, val))
|
|
56
|
+
return edges, nodes
|
|
57
|
+
|
|
58
|
+
def convert_type(input_graph, to, directed=None, weighted=None, from_type=None):
|
|
59
|
+
"""
|
|
60
|
+
Convert between graph representations seamlessly.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
input_graph : Input graph in any supported format -
|
|
64
|
+
Adjacency list : { node: [neighbor, ...] } or { node: [(neighbor, weight), ...] }
|
|
65
|
+
Edge list : [(src, tgt), ...] or [(src, tgt, weight), ...]
|
|
66
|
+
or list of dicts with 'source'/'target'/'weight' keys (parse_edges output)
|
|
67
|
+
Matrix : 2D list (nodes = row/col indices)
|
|
68
|
+
or dict-of-dicts { src: { tgt: weight } }
|
|
69
|
+
to : Target format - 'adj', 'edge', or 'matrix'
|
|
70
|
+
directed : bool. Auto-detected from input symmetry if None.
|
|
71
|
+
weighted : bool. Auto-detected from input structure if None.
|
|
72
|
+
from_type : Input format - 'adj', 'edge', or 'matrix'. Auto-detected if None.
|
|
73
|
+
('from' is a Python keyword, so from_type is used instead.)
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
'adj'
|
|
77
|
+
{node: [neighbor, ...]} or
|
|
78
|
+
{node: [(neighbor, weight), ...]}
|
|
79
|
+
|
|
80
|
+
'edge'
|
|
81
|
+
[(src, tgt), ...] or
|
|
82
|
+
[(src, tgt, weight), ...]
|
|
83
|
+
|
|
84
|
+
'matrix'
|
|
85
|
+
(2D list, node_order)
|
|
86
|
+
node_order[i] gives the label for row/column i.
|
|
87
|
+
|
|
88
|
+
Notes:
|
|
89
|
+
- For binary (0/1) matrices, weighted is inferred as False since 1 = "edge exists".
|
|
90
|
+
If your graph genuinely has weight=1 on all edges, pass weighted=True explicitly.
|
|
91
|
+
- Directed detection is a heuristic: if every edge (u,v) has a reverse (v,u),
|
|
92
|
+
the graph is assumed undirected. Pass directed=True/False to override.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
FORMAT_ALIASES = {
|
|
96
|
+
'adj': 'adj', 'adjacency_list': 'adj', 'adjacency': 'adj',
|
|
97
|
+
'edge': 'edge', 'edge_list': 'edge', 'edges': 'edge',
|
|
98
|
+
'matrix': 'matrix', 'adjacency_matrix': 'matrix', 'mat': 'matrix',
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
to = FORMAT_ALIASES.get(to, to)
|
|
102
|
+
if to not in ('adj', 'edge', 'matrix'):
|
|
103
|
+
raise ValueError(f"Unknown target format '{to}'. Use 'adj', 'edge', or 'matrix'.")
|
|
104
|
+
|
|
105
|
+
if from_type is not None:
|
|
106
|
+
from_type = FORMAT_ALIASES.get(from_type, from_type)
|
|
107
|
+
|
|
108
|
+
# Auto-detect input format
|
|
109
|
+
def detect_format(g):
|
|
110
|
+
if isinstance(g, dict):
|
|
111
|
+
if not g:
|
|
112
|
+
return 'adj'
|
|
113
|
+
first_val = next(iter(g.values()))
|
|
114
|
+
return 'matrix' if isinstance(first_val, dict) else 'adj'
|
|
115
|
+
if isinstance(g, list):
|
|
116
|
+
if not g:
|
|
117
|
+
return 'edge'
|
|
118
|
+
return 'matrix' if isinstance(g[0], list) else 'edge'
|
|
119
|
+
raise ValueError("Cannot auto-detect format. Specify `from_type` explicitly.")
|
|
120
|
+
|
|
121
|
+
fmt = from_type if from_type is not None else detect_format(input_graph)
|
|
122
|
+
if fmt not in ('adj', 'edge', 'matrix'):
|
|
123
|
+
raise ValueError(f"Unknown input format '{fmt}'.")
|
|
124
|
+
|
|
125
|
+
parsers = {'adj': _parse_adj, 'edge': _parse_edge_list, 'matrix': _parse_matrix}
|
|
126
|
+
edges, nodes = parsers[fmt](input_graph)
|
|
127
|
+
|
|
128
|
+
# Auto-detect weighted
|
|
129
|
+
if weighted is None:
|
|
130
|
+
if fmt == 'matrix':
|
|
131
|
+
# Binary 0/1 matrix → unweighted; any other value → weighted
|
|
132
|
+
weighted = any(w not in (0, 1) for _, _, w in edges)
|
|
133
|
+
else:
|
|
134
|
+
weighted = any(w is not None for _, _, w in edges)
|
|
135
|
+
|
|
136
|
+
# Auto-detect directed
|
|
137
|
+
if directed is None:
|
|
138
|
+
if fmt == 'matrix' and isinstance(input_graph, list):
|
|
139
|
+
n = len(input_graph)
|
|
140
|
+
directed = any(
|
|
141
|
+
input_graph[i][j] != input_graph[j][i]
|
|
142
|
+
for i in range(n) for j in range(i, n)
|
|
143
|
+
)
|
|
144
|
+
else:
|
|
145
|
+
edge_set = {(s, t) for s, t, _ in edges}
|
|
146
|
+
directed = not all(
|
|
147
|
+
(t, s) in edge_set
|
|
148
|
+
for s, t, _ in edges if s != t
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Normalize
|
|
152
|
+
if weighted:
|
|
153
|
+
edges = [(s, t, w if w is not None else 1) for s, t, w in edges]
|
|
154
|
+
else:
|
|
155
|
+
edges = [(s, t, None) for s, t, _ in edges]
|
|
156
|
+
|
|
157
|
+
if not directed:
|
|
158
|
+
seen = set()
|
|
159
|
+
deduped = []
|
|
160
|
+
for s, t, w in edges:
|
|
161
|
+
key = frozenset([s, t])
|
|
162
|
+
if key not in seen:
|
|
163
|
+
seen.add(key)
|
|
164
|
+
deduped.append((s, t, w))
|
|
165
|
+
edges = deduped
|
|
166
|
+
|
|
167
|
+
# Build target format
|
|
168
|
+
|
|
169
|
+
def build_adj(edges, nodes, directed, weighted):
|
|
170
|
+
adj = {n: [] for n in nodes}
|
|
171
|
+
for s, t, w in edges:
|
|
172
|
+
adj.setdefault(s, []).append((t, w) if weighted else t)
|
|
173
|
+
if not directed:
|
|
174
|
+
adj.setdefault(t, []).append((s, w) if weighted else s)
|
|
175
|
+
return adj
|
|
176
|
+
|
|
177
|
+
def build_edge_list(edges, weighted):
|
|
178
|
+
return [(s, t, w) if weighted else (s, t) for s, t, w in edges]
|
|
179
|
+
|
|
180
|
+
def build_matrix(edges, nodes, directed, weighted):
|
|
181
|
+
node_order = sorted(nodes, key=str)
|
|
182
|
+
idx = {n: i for i, n in enumerate(node_order)}
|
|
183
|
+
size = len(node_order)
|
|
184
|
+
matrix = [[0] * size for _ in range(size)]
|
|
185
|
+
for s, t, w in edges:
|
|
186
|
+
val = w if weighted else 1
|
|
187
|
+
matrix[idx[s]][idx[t]] = val
|
|
188
|
+
if not directed:
|
|
189
|
+
matrix[idx[t]][idx[s]] = val
|
|
190
|
+
return matrix, node_order
|
|
191
|
+
|
|
192
|
+
builders = {
|
|
193
|
+
'adj': lambda: build_adj(edges, nodes, directed, weighted),
|
|
194
|
+
'edge': lambda: build_edge_list(edges, weighted),
|
|
195
|
+
'matrix': lambda: build_matrix(edges, nodes, directed, weighted),
|
|
196
|
+
}
|
|
197
|
+
return builders[to]()
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
# Parse Arx-Net style edges
|
|
4
|
+
def _split_top_level(input_str):
|
|
5
|
+
result = []
|
|
6
|
+
depth = 0
|
|
7
|
+
current = ''
|
|
8
|
+
for char in input_str:
|
|
9
|
+
if char == '(':
|
|
10
|
+
depth += 1
|
|
11
|
+
if char == ')':
|
|
12
|
+
depth -= 1
|
|
13
|
+
if char == ',' and depth == 0:
|
|
14
|
+
result.append(current)
|
|
15
|
+
current = ''
|
|
16
|
+
else:
|
|
17
|
+
current += char
|
|
18
|
+
result.append(current)
|
|
19
|
+
return [s for s in result if s.strip() != '']
|
|
20
|
+
|
|
21
|
+
def parse_edges(edges_input, directed=True, strict=False):
|
|
22
|
+
"""
|
|
23
|
+
Parse the arx net application format into a python compatible adjacency list format.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
edges_input : Arx-Tet type input edge format
|
|
27
|
+
directed : Directed or Undirected
|
|
28
|
+
strict : Continue or terminate for invalid cases
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Python compatible adjacency list of type dict.
|
|
32
|
+
"""
|
|
33
|
+
simple_format = re.compile(r'^([a-zA-Z0-9]{2})(-?\d*\.?\d*)$')
|
|
34
|
+
paren_format = re.compile(r'^\(\s*([a-zA-Z0-9]+)\s*,\s*([a-zA-Z0-9]+)\s*(?:,\s*(-?\d*\.?\d*)\s*)?\)$')
|
|
35
|
+
|
|
36
|
+
edges_raw = []
|
|
37
|
+
for edge in _split_top_level(edges_input):
|
|
38
|
+
edge = edge.strip()
|
|
39
|
+
|
|
40
|
+
source = target = weight = None
|
|
41
|
+
|
|
42
|
+
if m := simple_format.match(edge):
|
|
43
|
+
source = m.group(1)[0]
|
|
44
|
+
target = m.group(1)[1]
|
|
45
|
+
w = m.group(2)
|
|
46
|
+
weight = float(w) if w else None
|
|
47
|
+
elif m := paren_format.match(edge):
|
|
48
|
+
source = m.group(1)
|
|
49
|
+
target = m.group(2)
|
|
50
|
+
w = m.group(3)
|
|
51
|
+
weight = float(w) if w else None
|
|
52
|
+
elif len(edge) == 1:
|
|
53
|
+
source = edge
|
|
54
|
+
target = None
|
|
55
|
+
weight = None
|
|
56
|
+
else:
|
|
57
|
+
if strict:
|
|
58
|
+
raise ValueError(f"Invalid edge format: {edge}")
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
if weight is None or (isinstance(weight, float) and weight != weight): # NaN check
|
|
62
|
+
weight = None # keep as None rather than defaulting to 1, so we know if weights are absent
|
|
63
|
+
|
|
64
|
+
edges_raw.append({'source': source, 'target': target, 'weight': weight})
|
|
65
|
+
|
|
66
|
+
# Remove duplicates, keeping last occurrence
|
|
67
|
+
edge_map = {}
|
|
68
|
+
for edge in edges_raw:
|
|
69
|
+
if edge['source'] and edge['target']:
|
|
70
|
+
key = f"{edge['source']}_{edge['target']}"
|
|
71
|
+
edge_map[key] = edge
|
|
72
|
+
if not directed:
|
|
73
|
+
reverse_key = f"{edge['target']}_{edge['source']}"
|
|
74
|
+
if reverse_key in edge_map:
|
|
75
|
+
del edge_map[reverse_key]
|
|
76
|
+
|
|
77
|
+
edges_raw = list(edge_map.values())
|
|
78
|
+
|
|
79
|
+
has_weights = any(e['weight'] is not None for e in edges_raw)
|
|
80
|
+
|
|
81
|
+
adj = {}
|
|
82
|
+
for edge in edges_raw:
|
|
83
|
+
src, tgt, w = edge['source'], edge['target'], edge['weight']
|
|
84
|
+
if src is None:
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
for node in ([src, tgt] if tgt else [src]):
|
|
88
|
+
if node not in adj:
|
|
89
|
+
adj[node] = []
|
|
90
|
+
|
|
91
|
+
if tgt is not None:
|
|
92
|
+
neighbor = (tgt, w if w is not None else 1) if has_weights else tgt
|
|
93
|
+
adj[src].append(neighbor)
|
|
94
|
+
|
|
95
|
+
if not directed:
|
|
96
|
+
neighbor_rev = (src, w if w is not None else 1) if has_weights else src
|
|
97
|
+
adj[tgt].append(neighbor_rev)
|
|
98
|
+
|
|
99
|
+
return adj
|
|
100
|
+
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: arx-net
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Graph parsing for arx net (https://dwarakesh-v.github.io/arx-net/) and graph representation conversion library.
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: arx-net,graph,network,adjacency,graph-parser
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# Arx-Net
|
|
16
|
+
|
|
17
|
+
A lightweight Python library for parsing graph edge strings and converting
|
|
18
|
+
between common graph representations.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install arx-net
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- Parse edge strings
|
|
29
|
+
- Directed and undirected graphs
|
|
30
|
+
- Weighted and unweighted graphs
|
|
31
|
+
- Convert between
|
|
32
|
+
|
|
33
|
+
- adjacency lists
|
|
34
|
+
- edge lists
|
|
35
|
+
- adjacency matrices
|
|
36
|
+
|
|
37
|
+
## Example
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import arx_net
|
|
41
|
+
|
|
42
|
+
graph = arx_net.parse_edges(
|
|
43
|
+
"(A,B,4),(B,C,2),(C,D)"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
print(graph)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Output
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
{
|
|
53
|
+
"A": [("B",4)],
|
|
54
|
+
"B": [("C",2)],
|
|
55
|
+
"C": [("D",1)],
|
|
56
|
+
"D": []
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Convert formats
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
edges = arx_net.convert_type(graph, "edge")
|
|
64
|
+
matrix, order = arx_net.convert_type(graph, "matrix")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Supported formats
|
|
68
|
+
|
|
69
|
+
### Adjacency List
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
{
|
|
73
|
+
"A": ["B", "C"]
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Edge List
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
[
|
|
81
|
+
("A","B"),
|
|
82
|
+
("A","C")
|
|
83
|
+
]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Matrix
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
[
|
|
90
|
+
[0,1,1],
|
|
91
|
+
[0,0,1],
|
|
92
|
+
[0,0,0]
|
|
93
|
+
]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
arx_net
|