graph-matrix-analysis 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.
- graph_matrix_analysis-0.1.0/LICENSE +21 -0
- graph_matrix_analysis-0.1.0/PKG-INFO +136 -0
- graph_matrix_analysis-0.1.0/README.md +120 -0
- graph_matrix_analysis-0.1.0/graph_matrix_analysis/__init__.py +7 -0
- graph_matrix_analysis-0.1.0/graph_matrix_analysis/core.py +233 -0
- graph_matrix_analysis-0.1.0/graph_matrix_analysis.egg-info/PKG-INFO +136 -0
- graph_matrix_analysis-0.1.0/graph_matrix_analysis.egg-info/SOURCES.txt +10 -0
- graph_matrix_analysis-0.1.0/graph_matrix_analysis.egg-info/dependency_links.txt +1 -0
- graph_matrix_analysis-0.1.0/graph_matrix_analysis.egg-info/requires.txt +2 -0
- graph_matrix_analysis-0.1.0/graph_matrix_analysis.egg-info/top_level.txt +1 -0
- graph_matrix_analysis-0.1.0/pyproject.toml +27 -0
- graph_matrix_analysis-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aitor Merino, Xabier Labraga
|
|
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,136 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: graph_matrix_analysis
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Library for analyzing graphs based on their adjacency matrix
|
|
5
|
+
Author-email: Aitor Merino <aitor.merino@alumni.mondragon.edu>, Xabier Labraga <xabier.labraga@alumni.mondragon.edu>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: graphs,adjacency matrix,euler,dijkstra,bellman-ford,isomorphism
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: networkx
|
|
14
|
+
Requires-Dist: matplotlib
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# graph_matrix_analysis
|
|
18
|
+
|
|
19
|
+
Python library to analyze graphs from their **adjacency matrix**.
|
|
20
|
+
|
|
21
|
+
With a matrix you can:
|
|
22
|
+
|
|
23
|
+
- check whether the graph is **simple, complete, a multigraph, a pseudograph or directed**,
|
|
24
|
+
- check whether it is **Eulerian** or **semi-Eulerian**,
|
|
25
|
+
- check whether it is **connected** (using the connectivity matrix `C = A + A² + … + Aⁿ⁻¹`),
|
|
26
|
+
- compute the **shortest path** between two vertices with **Bellman-Ford** and draw the graph,
|
|
27
|
+
- check whether two matrices represent **isomorphic** graphs.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install graph_matrix_analysis
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
It also installs `networkx` and `matplotlib`, which are used to draw the graph.
|
|
36
|
+
|
|
37
|
+
## The matrix
|
|
38
|
+
|
|
39
|
+
The matrix is a square list of lists. `A[i][j]` is the number of edges from vertex `i` to vertex `j`, and vertices are numbered starting at 0. If the matrix is not square, `InvalidMatrixError` is raised.
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from graph_matrix_analysis import Properties, Connectivity, WeightedGraph, Isomorphism
|
|
45
|
+
|
|
46
|
+
A = [
|
|
47
|
+
[0, 1, 1, 0],
|
|
48
|
+
[1, 0, 1, 1],
|
|
49
|
+
[1, 1, 0, 1],
|
|
50
|
+
[0, 1, 1, 0],
|
|
51
|
+
]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Properties (`Properties`)
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
p = Properties(A)
|
|
58
|
+
|
|
59
|
+
p.count_vertex() # 4
|
|
60
|
+
p.count_edges() # 5
|
|
61
|
+
p.simple() # True
|
|
62
|
+
p.complete() # False
|
|
63
|
+
p.pseudograph() # False
|
|
64
|
+
p.multigraph() # False
|
|
65
|
+
p.digraph() # False
|
|
66
|
+
p.eulerian() # False
|
|
67
|
+
p.semieulerian() # True
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
| Method | Returns `True` if... |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `simple()` | it has no loops or multiple edges |
|
|
73
|
+
| `complete()` | there is an edge between every pair of vertices |
|
|
74
|
+
| `pseudograph()` | it has at least one loop |
|
|
75
|
+
| `multigraph()` | it has multiple edges between distinct vertices |
|
|
76
|
+
| `digraph()` | the matrix is not symmetric (directed graph) |
|
|
77
|
+
| `eulerian()` | all vertices have even degree |
|
|
78
|
+
| `semieulerian()` | exactly two vertices have odd degree |
|
|
79
|
+
|
|
80
|
+
### Connectivity (`Connectivity`)
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
c = Connectivity(A)
|
|
84
|
+
|
|
85
|
+
c.connected() # True
|
|
86
|
+
c.connectivity_matrix() # [[4, 7, 7, 4], [7, 7, 8, 7], [7, 8, 7, 7], [4, 7, 7, 4]]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`connectivity_matrix()` computes `C = A + A² + … + Aⁿ⁻¹`. The graph is connected if all off-diagonal entries of `C` are non-zero.
|
|
90
|
+
|
|
91
|
+
### Shortest path (`WeightedGraph`)
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
W = [
|
|
95
|
+
[0, 4, 5, 0],
|
|
96
|
+
[0, 0, -3, 6],
|
|
97
|
+
[0, 0, 0, 2],
|
|
98
|
+
[0, 0, 0, 0],
|
|
99
|
+
]
|
|
100
|
+
g = WeightedGraph(W)
|
|
101
|
+
|
|
102
|
+
g.bellman_ford(0, 3) # ([0, 1, 2, 3], 3) -> (path, distance)
|
|
103
|
+
g.bellman_ford(3, 0) # 'No path exists'
|
|
104
|
+
g.plot_graph() # draws the graph with its weights
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Here the matrix is a **weight** matrix: `W[i][j]` is the weight of the edge from `i` to `j`, and 0 means there is no edge. Negative weights are allowed; if there is a negative cycle, it returns `'Negative cycle detected'`.
|
|
108
|
+
|
|
109
|
+
### Isomorphism (`Isomorphism`)
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
B = [
|
|
113
|
+
[0, 1, 1, 1],
|
|
114
|
+
[1, 0, 1, 0],
|
|
115
|
+
[1, 1, 0, 1],
|
|
116
|
+
[1, 0, 1, 0],
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
Isomorphism(A, B).isomorphic() # True
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
It tries every permutation of the vertices, so it is only practical for small graphs.
|
|
123
|
+
|
|
124
|
+
## Development
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
git clone https://github.com/tuusuario/graph_matrix_analysis
|
|
128
|
+
cd grafomatriz
|
|
129
|
+
pip install -e .
|
|
130
|
+
pip install pytest
|
|
131
|
+
pytest
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
MIT
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# graph_matrix_analysis
|
|
2
|
+
|
|
3
|
+
Python library to analyze graphs from their **adjacency matrix**.
|
|
4
|
+
|
|
5
|
+
With a matrix you can:
|
|
6
|
+
|
|
7
|
+
- check whether the graph is **simple, complete, a multigraph, a pseudograph or directed**,
|
|
8
|
+
- check whether it is **Eulerian** or **semi-Eulerian**,
|
|
9
|
+
- check whether it is **connected** (using the connectivity matrix `C = A + A² + … + Aⁿ⁻¹`),
|
|
10
|
+
- compute the **shortest path** between two vertices with **Bellman-Ford** and draw the graph,
|
|
11
|
+
- check whether two matrices represent **isomorphic** graphs.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install graph_matrix_analysis
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
It also installs `networkx` and `matplotlib`, which are used to draw the graph.
|
|
20
|
+
|
|
21
|
+
## The matrix
|
|
22
|
+
|
|
23
|
+
The matrix is a square list of lists. `A[i][j]` is the number of edges from vertex `i` to vertex `j`, and vertices are numbered starting at 0. If the matrix is not square, `InvalidMatrixError` is raised.
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from graph_matrix_analysis import Properties, Connectivity, WeightedGraph, Isomorphism
|
|
29
|
+
|
|
30
|
+
A = [
|
|
31
|
+
[0, 1, 1, 0],
|
|
32
|
+
[1, 0, 1, 1],
|
|
33
|
+
[1, 1, 0, 1],
|
|
34
|
+
[0, 1, 1, 0],
|
|
35
|
+
]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Properties (`Properties`)
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
p = Properties(A)
|
|
42
|
+
|
|
43
|
+
p.count_vertex() # 4
|
|
44
|
+
p.count_edges() # 5
|
|
45
|
+
p.simple() # True
|
|
46
|
+
p.complete() # False
|
|
47
|
+
p.pseudograph() # False
|
|
48
|
+
p.multigraph() # False
|
|
49
|
+
p.digraph() # False
|
|
50
|
+
p.eulerian() # False
|
|
51
|
+
p.semieulerian() # True
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
| Method | Returns `True` if... |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `simple()` | it has no loops or multiple edges |
|
|
57
|
+
| `complete()` | there is an edge between every pair of vertices |
|
|
58
|
+
| `pseudograph()` | it has at least one loop |
|
|
59
|
+
| `multigraph()` | it has multiple edges between distinct vertices |
|
|
60
|
+
| `digraph()` | the matrix is not symmetric (directed graph) |
|
|
61
|
+
| `eulerian()` | all vertices have even degree |
|
|
62
|
+
| `semieulerian()` | exactly two vertices have odd degree |
|
|
63
|
+
|
|
64
|
+
### Connectivity (`Connectivity`)
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
c = Connectivity(A)
|
|
68
|
+
|
|
69
|
+
c.connected() # True
|
|
70
|
+
c.connectivity_matrix() # [[4, 7, 7, 4], [7, 7, 8, 7], [7, 8, 7, 7], [4, 7, 7, 4]]
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`connectivity_matrix()` computes `C = A + A² + … + Aⁿ⁻¹`. The graph is connected if all off-diagonal entries of `C` are non-zero.
|
|
74
|
+
|
|
75
|
+
### Shortest path (`WeightedGraph`)
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
W = [
|
|
79
|
+
[0, 4, 5, 0],
|
|
80
|
+
[0, 0, -3, 6],
|
|
81
|
+
[0, 0, 0, 2],
|
|
82
|
+
[0, 0, 0, 0],
|
|
83
|
+
]
|
|
84
|
+
g = WeightedGraph(W)
|
|
85
|
+
|
|
86
|
+
g.bellman_ford(0, 3) # ([0, 1, 2, 3], 3) -> (path, distance)
|
|
87
|
+
g.bellman_ford(3, 0) # 'No path exists'
|
|
88
|
+
g.plot_graph() # draws the graph with its weights
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Here the matrix is a **weight** matrix: `W[i][j]` is the weight of the edge from `i` to `j`, and 0 means there is no edge. Negative weights are allowed; if there is a negative cycle, it returns `'Negative cycle detected'`.
|
|
92
|
+
|
|
93
|
+
### Isomorphism (`Isomorphism`)
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
B = [
|
|
97
|
+
[0, 1, 1, 1],
|
|
98
|
+
[1, 0, 1, 0],
|
|
99
|
+
[1, 1, 0, 1],
|
|
100
|
+
[1, 0, 1, 0],
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
Isomorphism(A, B).isomorphic() # True
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
It tries every permutation of the vertices, so it is only practical for small graphs.
|
|
107
|
+
|
|
108
|
+
## Development
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
git clone https://github.com/tuusuario/graph_matrix_analysis
|
|
112
|
+
cd grafomatriz
|
|
113
|
+
pip install -e .
|
|
114
|
+
pip install pytest
|
|
115
|
+
pytest
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import networkx as nx
|
|
2
|
+
import matplotlib as plt
|
|
3
|
+
|
|
4
|
+
class InvalidMatrixError(Exception):
|
|
5
|
+
pass
|
|
6
|
+
|
|
7
|
+
def check_matrix(A):
|
|
8
|
+
n = len(A)
|
|
9
|
+
for i in range(n):
|
|
10
|
+
if len(A[i]) != n:
|
|
11
|
+
raise InvalidMatrixError('The matrix is not square')
|
|
12
|
+
|
|
13
|
+
class Properties:
|
|
14
|
+
def __init__(self, A):
|
|
15
|
+
check_matrix(A)
|
|
16
|
+
self.A = A
|
|
17
|
+
self.n = len(A)
|
|
18
|
+
|
|
19
|
+
def count_vertex(self): # Number of vertices
|
|
20
|
+
return self.n
|
|
21
|
+
|
|
22
|
+
def count_edges(self): # Number of edges
|
|
23
|
+
edges = 0
|
|
24
|
+
for i in range(self.n):
|
|
25
|
+
for j in range(self.n):
|
|
26
|
+
edges += self.A[i][j]
|
|
27
|
+
return edges // 2
|
|
28
|
+
|
|
29
|
+
def simple(self): # Simple graph
|
|
30
|
+
for i in range(self.n):
|
|
31
|
+
if self.A[i][i] != 0:
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
for j in range(self.n):
|
|
35
|
+
if self.A[i][j] > 1:
|
|
36
|
+
return False
|
|
37
|
+
return True
|
|
38
|
+
|
|
39
|
+
def complete(self): # Complete graph
|
|
40
|
+
for i in range(self.n):
|
|
41
|
+
for j in range(self.n):
|
|
42
|
+
if i != j and self.A[i][j] == 0:
|
|
43
|
+
return False
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
def pseudograph(self): # Pseudograph
|
|
47
|
+
for i in range(self.n):
|
|
48
|
+
if self.A[i][i] > 0:
|
|
49
|
+
return True
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
def multigraph(self): # Multigraph
|
|
53
|
+
for i in range(self.n):
|
|
54
|
+
for j in range(self.n):
|
|
55
|
+
if i != j and self.A[i][j] > 1:
|
|
56
|
+
return True
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
def digraph(self): # Directed graph
|
|
60
|
+
for i in range(self.n):
|
|
61
|
+
for j in range(self.n):
|
|
62
|
+
if self.A[i][j] != self.A[j][i]:
|
|
63
|
+
return True
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
def eulerian(self): # Eulerian graph
|
|
67
|
+
for i in range(self.n):
|
|
68
|
+
degree = sum(self.A[i])
|
|
69
|
+
if degree % 2 != 0:
|
|
70
|
+
return False
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
def semieulerian(self): # Semi-Eulerian graph
|
|
74
|
+
odd = 0
|
|
75
|
+
for i in range(self.n):
|
|
76
|
+
degree = sum(self.A[i])
|
|
77
|
+
if degree % 2 != 0:
|
|
78
|
+
odd += 1
|
|
79
|
+
return odd == 2
|
|
80
|
+
|
|
81
|
+
class Connectivity:
|
|
82
|
+
def __init__(self, A):
|
|
83
|
+
check_matrix(A)
|
|
84
|
+
self.A = A
|
|
85
|
+
self.n = len(A)
|
|
86
|
+
|
|
87
|
+
def multiply(self, A, B):
|
|
88
|
+
C = []
|
|
89
|
+
for i in range(self.n):
|
|
90
|
+
row = []
|
|
91
|
+
for j in range(self.n):
|
|
92
|
+
row.append(0)
|
|
93
|
+
C.append(row)
|
|
94
|
+
|
|
95
|
+
for i in range(self.n):
|
|
96
|
+
for j in range(self.n):
|
|
97
|
+
for k in range(self.n):
|
|
98
|
+
C[i][j] += A[i][k] * B[k][j]
|
|
99
|
+
return C
|
|
100
|
+
|
|
101
|
+
def connectivity_matrix(self):
|
|
102
|
+
C = []
|
|
103
|
+
for i in range(self.n):
|
|
104
|
+
row = []
|
|
105
|
+
for j in range(self.n):
|
|
106
|
+
row.append(0)
|
|
107
|
+
C.append(row)
|
|
108
|
+
|
|
109
|
+
P = self.A
|
|
110
|
+
for k in range(1, self.n):
|
|
111
|
+
for i in range(self.n):
|
|
112
|
+
for j in range(self.n):
|
|
113
|
+
C[i][j] += P[i][j]
|
|
114
|
+
P = self.multiply(P, self.A)
|
|
115
|
+
return C
|
|
116
|
+
|
|
117
|
+
def connected(self):
|
|
118
|
+
C = self.connectivity_matrix()
|
|
119
|
+
for i in range(self.n):
|
|
120
|
+
for j in range(self.n):
|
|
121
|
+
if i != j and C[i][j] == 0:
|
|
122
|
+
return False
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
class WeightedGraph:
|
|
126
|
+
def __init__(self, W):
|
|
127
|
+
check_matrix(W)
|
|
128
|
+
self.W = W
|
|
129
|
+
self.n = len(W)
|
|
130
|
+
|
|
131
|
+
def bellman_ford(self, start, end):
|
|
132
|
+
distance = []
|
|
133
|
+
for i in range(self.n): # Initialize distances
|
|
134
|
+
distance.append(float('inf'))
|
|
135
|
+
distance[start] = 0
|
|
136
|
+
|
|
137
|
+
predecessor = []
|
|
138
|
+
for i in range(self.n): # Initialize predecessors
|
|
139
|
+
predecessor.append(None)
|
|
140
|
+
|
|
141
|
+
# Bellman-Ford algorithm
|
|
142
|
+
for k in range(self.n - 1):
|
|
143
|
+
for i in range(self.n):
|
|
144
|
+
for j in range(self.n):
|
|
145
|
+
if self.W[i][j] != 0:
|
|
146
|
+
if distance[i] != float('inf'):
|
|
147
|
+
if distance[i] + self.W[i][j] < distance[j]:
|
|
148
|
+
distance[j] = distance[i] + self.W[i][j]
|
|
149
|
+
predecessor[j] = i
|
|
150
|
+
|
|
151
|
+
# Check negative cycles
|
|
152
|
+
for i in range(self.n):
|
|
153
|
+
for j in range(self.n):
|
|
154
|
+
if self.W[i][j] != 0:
|
|
155
|
+
if distance[i] != float('inf'):
|
|
156
|
+
if distance[i] + self.W[i][j] < distance[j]:
|
|
157
|
+
return "Negative cycle detected"
|
|
158
|
+
|
|
159
|
+
if distance[end] == float('inf'): # Check if destination is reachable
|
|
160
|
+
return "No path exists"
|
|
161
|
+
|
|
162
|
+
# Reconstruct shortest path
|
|
163
|
+
path = []
|
|
164
|
+
current = end
|
|
165
|
+
|
|
166
|
+
while current != None:
|
|
167
|
+
path.append(current)
|
|
168
|
+
current = predecessor[current]
|
|
169
|
+
path.reverse()
|
|
170
|
+
|
|
171
|
+
return path, distance[end]
|
|
172
|
+
|
|
173
|
+
def plot_graph(self):
|
|
174
|
+
G = nx.DiGraph()
|
|
175
|
+
for i in range(self.n): # Add vertices
|
|
176
|
+
G.add_node(i)
|
|
177
|
+
|
|
178
|
+
# Add edges and weights
|
|
179
|
+
for i in range(self.n):
|
|
180
|
+
for j in range(self.n):
|
|
181
|
+
if self.W[i][j] != 0:
|
|
182
|
+
G.add_edge(i, j, weight=self.W[i][j])
|
|
183
|
+
|
|
184
|
+
# Draw graph
|
|
185
|
+
pos = nx.spring_layout(G)
|
|
186
|
+
nx.draw(G, pos, with_labels = True, node_color = 'lightblue', node_size = 800, font_size = 12, arrows = True)
|
|
187
|
+
|
|
188
|
+
# Draw edge weights
|
|
189
|
+
labels = nx.get_edge_attributes(G, 'weight')
|
|
190
|
+
|
|
191
|
+
nx.draw_networkx_edge_labels(G, pos, edge_labels=labels)
|
|
192
|
+
|
|
193
|
+
plt.title("Weighted Graph")
|
|
194
|
+
plt.show()
|
|
195
|
+
|
|
196
|
+
class Isomorphism:
|
|
197
|
+
def __init__(self, A, B):
|
|
198
|
+
check_matrix(A)
|
|
199
|
+
check_matrix(B)
|
|
200
|
+
self.A = A
|
|
201
|
+
self.B = B
|
|
202
|
+
self.n = len(A)
|
|
203
|
+
|
|
204
|
+
def check(self, p):
|
|
205
|
+
for i in range(self.n):
|
|
206
|
+
for j in range(self.n):
|
|
207
|
+
if self.A[i][j] != self.B[p[i]][p[j]]:
|
|
208
|
+
return False
|
|
209
|
+
return True
|
|
210
|
+
|
|
211
|
+
def generate(self, p, used):
|
|
212
|
+
if len(p) == self.n:
|
|
213
|
+
return self.check(p)
|
|
214
|
+
|
|
215
|
+
for i in range(self.n):
|
|
216
|
+
if not used[i]:
|
|
217
|
+
p.append(i)
|
|
218
|
+
used[i] = True
|
|
219
|
+
if self.generate(p, used):
|
|
220
|
+
return True
|
|
221
|
+
p.pop()
|
|
222
|
+
used[i] = False
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
def isomorphic(self):
|
|
226
|
+
if len(self.A) != len(self.B):
|
|
227
|
+
return False
|
|
228
|
+
p = []
|
|
229
|
+
used = []
|
|
230
|
+
|
|
231
|
+
for i in range(self.n):
|
|
232
|
+
used.append(False)
|
|
233
|
+
return self.generate(p, used)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: graph_matrix_analysis
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Library for analyzing graphs based on their adjacency matrix
|
|
5
|
+
Author-email: Aitor Merino <aitor.merino@alumni.mondragon.edu>, Xabier Labraga <xabier.labraga@alumni.mondragon.edu>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: graphs,adjacency matrix,euler,dijkstra,bellman-ford,isomorphism
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: networkx
|
|
14
|
+
Requires-Dist: matplotlib
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# graph_matrix_analysis
|
|
18
|
+
|
|
19
|
+
Python library to analyze graphs from their **adjacency matrix**.
|
|
20
|
+
|
|
21
|
+
With a matrix you can:
|
|
22
|
+
|
|
23
|
+
- check whether the graph is **simple, complete, a multigraph, a pseudograph or directed**,
|
|
24
|
+
- check whether it is **Eulerian** or **semi-Eulerian**,
|
|
25
|
+
- check whether it is **connected** (using the connectivity matrix `C = A + A² + … + Aⁿ⁻¹`),
|
|
26
|
+
- compute the **shortest path** between two vertices with **Bellman-Ford** and draw the graph,
|
|
27
|
+
- check whether two matrices represent **isomorphic** graphs.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install graph_matrix_analysis
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
It also installs `networkx` and `matplotlib`, which are used to draw the graph.
|
|
36
|
+
|
|
37
|
+
## The matrix
|
|
38
|
+
|
|
39
|
+
The matrix is a square list of lists. `A[i][j]` is the number of edges from vertex `i` to vertex `j`, and vertices are numbered starting at 0. If the matrix is not square, `InvalidMatrixError` is raised.
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from graph_matrix_analysis import Properties, Connectivity, WeightedGraph, Isomorphism
|
|
45
|
+
|
|
46
|
+
A = [
|
|
47
|
+
[0, 1, 1, 0],
|
|
48
|
+
[1, 0, 1, 1],
|
|
49
|
+
[1, 1, 0, 1],
|
|
50
|
+
[0, 1, 1, 0],
|
|
51
|
+
]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Properties (`Properties`)
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
p = Properties(A)
|
|
58
|
+
|
|
59
|
+
p.count_vertex() # 4
|
|
60
|
+
p.count_edges() # 5
|
|
61
|
+
p.simple() # True
|
|
62
|
+
p.complete() # False
|
|
63
|
+
p.pseudograph() # False
|
|
64
|
+
p.multigraph() # False
|
|
65
|
+
p.digraph() # False
|
|
66
|
+
p.eulerian() # False
|
|
67
|
+
p.semieulerian() # True
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
| Method | Returns `True` if... |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `simple()` | it has no loops or multiple edges |
|
|
73
|
+
| `complete()` | there is an edge between every pair of vertices |
|
|
74
|
+
| `pseudograph()` | it has at least one loop |
|
|
75
|
+
| `multigraph()` | it has multiple edges between distinct vertices |
|
|
76
|
+
| `digraph()` | the matrix is not symmetric (directed graph) |
|
|
77
|
+
| `eulerian()` | all vertices have even degree |
|
|
78
|
+
| `semieulerian()` | exactly two vertices have odd degree |
|
|
79
|
+
|
|
80
|
+
### Connectivity (`Connectivity`)
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
c = Connectivity(A)
|
|
84
|
+
|
|
85
|
+
c.connected() # True
|
|
86
|
+
c.connectivity_matrix() # [[4, 7, 7, 4], [7, 7, 8, 7], [7, 8, 7, 7], [4, 7, 7, 4]]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`connectivity_matrix()` computes `C = A + A² + … + Aⁿ⁻¹`. The graph is connected if all off-diagonal entries of `C` are non-zero.
|
|
90
|
+
|
|
91
|
+
### Shortest path (`WeightedGraph`)
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
W = [
|
|
95
|
+
[0, 4, 5, 0],
|
|
96
|
+
[0, 0, -3, 6],
|
|
97
|
+
[0, 0, 0, 2],
|
|
98
|
+
[0, 0, 0, 0],
|
|
99
|
+
]
|
|
100
|
+
g = WeightedGraph(W)
|
|
101
|
+
|
|
102
|
+
g.bellman_ford(0, 3) # ([0, 1, 2, 3], 3) -> (path, distance)
|
|
103
|
+
g.bellman_ford(3, 0) # 'No path exists'
|
|
104
|
+
g.plot_graph() # draws the graph with its weights
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Here the matrix is a **weight** matrix: `W[i][j]` is the weight of the edge from `i` to `j`, and 0 means there is no edge. Negative weights are allowed; if there is a negative cycle, it returns `'Negative cycle detected'`.
|
|
108
|
+
|
|
109
|
+
### Isomorphism (`Isomorphism`)
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
B = [
|
|
113
|
+
[0, 1, 1, 1],
|
|
114
|
+
[1, 0, 1, 0],
|
|
115
|
+
[1, 1, 0, 1],
|
|
116
|
+
[1, 0, 1, 0],
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
Isomorphism(A, B).isomorphic() # True
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
It tries every permutation of the vertices, so it is only practical for small graphs.
|
|
123
|
+
|
|
124
|
+
## Development
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
git clone https://github.com/tuusuario/graph_matrix_analysis
|
|
128
|
+
cd grafomatriz
|
|
129
|
+
pip install -e .
|
|
130
|
+
pip install pytest
|
|
131
|
+
pytest
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
graph_matrix_analysis/__init__.py
|
|
5
|
+
graph_matrix_analysis/core.py
|
|
6
|
+
graph_matrix_analysis.egg-info/PKG-INFO
|
|
7
|
+
graph_matrix_analysis.egg-info/SOURCES.txt
|
|
8
|
+
graph_matrix_analysis.egg-info/dependency_links.txt
|
|
9
|
+
graph_matrix_analysis.egg-info/requires.txt
|
|
10
|
+
graph_matrix_analysis.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
graph_matrix_analysis
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
[project]
|
|
7
|
+
name = "graph_matrix_analysis" # nombre único en PyPI
|
|
8
|
+
version = "0.1.0"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name="Aitor Merino", email="aitor.merino@alumni.mondragon.edu" },
|
|
11
|
+
{ name="Xabier Labraga", email="xabier.labraga@alumni.mondragon.edu" }
|
|
12
|
+
]
|
|
13
|
+
description = "Library for analyzing graphs based on their adjacency matrix"
|
|
14
|
+
readme = "README.md"
|
|
15
|
+
requires-python = ">=3.8"
|
|
16
|
+
license = "MIT"
|
|
17
|
+
license-files = ["LICENSE"]
|
|
18
|
+
keywords = ["graphs", "adjacency matrix", "euler", "dijkstra", "bellman-ford", "isomorphism"]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"networkx",
|
|
25
|
+
"matplotlib",
|
|
26
|
+
]
|
|
27
|
+
|