simulation-alg 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.
@@ -0,0 +1,31 @@
1
+ name: CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ matrix:
10
+ python-version: ["3.10"]
11
+
12
+ steps:
13
+ - uses: actions/checkout@v2
14
+
15
+ - name: Set up Python ${{ matrix.python-version }}
16
+ uses: actions/setup-python@v2
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+
20
+ - name: Install dependencies
21
+ run: |
22
+ python -m pip install --upgrade pip
23
+ pip install maturin pytest
24
+
25
+ - name: Build and install
26
+ run: |
27
+ maturin develop
28
+
29
+ - name: Run tests
30
+ run: |
31
+ pytest tests/
@@ -0,0 +1,2 @@
1
+ /target
2
+ /.vscode
@@ -0,0 +1,3 @@
1
+ [submodule "lib/graph-simulation"]
2
+ path = lib/graph-simulation
3
+ url = https://github.com/RaVincentHuang/graph-simulation.git
@@ -0,0 +1,240 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "autocfg"
7
+ version = "1.4.0"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
10
+
11
+ [[package]]
12
+ name = "cfg-if"
13
+ version = "1.0.0"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
16
+
17
+ [[package]]
18
+ name = "crossbeam-deque"
19
+ version = "0.8.6"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
22
+ dependencies = [
23
+ "crossbeam-epoch",
24
+ "crossbeam-utils",
25
+ ]
26
+
27
+ [[package]]
28
+ name = "crossbeam-epoch"
29
+ version = "0.9.18"
30
+ source = "registry+https://github.com/rust-lang/crates.io-index"
31
+ checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
32
+ dependencies = [
33
+ "crossbeam-utils",
34
+ ]
35
+
36
+ [[package]]
37
+ name = "crossbeam-utils"
38
+ version = "0.8.21"
39
+ source = "registry+https://github.com/rust-lang/crates.io-index"
40
+ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
41
+
42
+ [[package]]
43
+ name = "either"
44
+ version = "1.14.0"
45
+ source = "registry+https://github.com/rust-lang/crates.io-index"
46
+ checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d"
47
+
48
+ [[package]]
49
+ name = "graph-base"
50
+ version = "0.1.2"
51
+ source = "registry+https://github.com/rust-lang/crates.io-index"
52
+ checksum = "97818a84e65f02545f38aae643f16779e54c186b442f084f138c53e1c981e32c"
53
+
54
+ [[package]]
55
+ name = "graph-simulation"
56
+ version = "0.1.2"
57
+ source = "registry+https://github.com/rust-lang/crates.io-index"
58
+ checksum = "2b06d26f28d2b6d5504d69507ed5460c55a4368e66dcd767e7c8f189615c3643"
59
+ dependencies = [
60
+ "graph-base",
61
+ ]
62
+
63
+ [[package]]
64
+ name = "heck"
65
+ version = "0.5.0"
66
+ source = "registry+https://github.com/rust-lang/crates.io-index"
67
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
68
+
69
+ [[package]]
70
+ name = "indoc"
71
+ version = "2.0.5"
72
+ source = "registry+https://github.com/rust-lang/crates.io-index"
73
+ checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
74
+
75
+ [[package]]
76
+ name = "libc"
77
+ version = "0.2.169"
78
+ source = "registry+https://github.com/rust-lang/crates.io-index"
79
+ checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
80
+
81
+ [[package]]
82
+ name = "memoffset"
83
+ version = "0.9.1"
84
+ source = "registry+https://github.com/rust-lang/crates.io-index"
85
+ checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
86
+ dependencies = [
87
+ "autocfg",
88
+ ]
89
+
90
+ [[package]]
91
+ name = "once_cell"
92
+ version = "1.20.2"
93
+ source = "registry+https://github.com/rust-lang/crates.io-index"
94
+ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
95
+
96
+ [[package]]
97
+ name = "portable-atomic"
98
+ version = "1.11.0"
99
+ source = "registry+https://github.com/rust-lang/crates.io-index"
100
+ checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
101
+
102
+ [[package]]
103
+ name = "proc-macro2"
104
+ version = "1.0.92"
105
+ source = "registry+https://github.com/rust-lang/crates.io-index"
106
+ checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
107
+ dependencies = [
108
+ "unicode-ident",
109
+ ]
110
+
111
+ [[package]]
112
+ name = "pyo3"
113
+ version = "0.23.5"
114
+ source = "registry+https://github.com/rust-lang/crates.io-index"
115
+ checksum = "7778bffd85cf38175ac1f545509665d0b9b92a198ca7941f131f85f7a4f9a872"
116
+ dependencies = [
117
+ "cfg-if",
118
+ "indoc",
119
+ "libc",
120
+ "memoffset",
121
+ "once_cell",
122
+ "portable-atomic",
123
+ "pyo3-build-config",
124
+ "pyo3-ffi",
125
+ "pyo3-macros",
126
+ "unindent",
127
+ ]
128
+
129
+ [[package]]
130
+ name = "pyo3-build-config"
131
+ version = "0.23.5"
132
+ source = "registry+https://github.com/rust-lang/crates.io-index"
133
+ checksum = "94f6cbe86ef3bf18998d9df6e0f3fc1050a8c5efa409bf712e661a4366e010fb"
134
+ dependencies = [
135
+ "once_cell",
136
+ "target-lexicon",
137
+ ]
138
+
139
+ [[package]]
140
+ name = "pyo3-ffi"
141
+ version = "0.23.5"
142
+ source = "registry+https://github.com/rust-lang/crates.io-index"
143
+ checksum = "e9f1b4c431c0bb1c8fb0a338709859eed0d030ff6daa34368d3b152a63dfdd8d"
144
+ dependencies = [
145
+ "libc",
146
+ "pyo3-build-config",
147
+ ]
148
+
149
+ [[package]]
150
+ name = "pyo3-macros"
151
+ version = "0.23.5"
152
+ source = "registry+https://github.com/rust-lang/crates.io-index"
153
+ checksum = "fbc2201328f63c4710f68abdf653c89d8dbc2858b88c5d88b0ff38a75288a9da"
154
+ dependencies = [
155
+ "proc-macro2",
156
+ "pyo3-macros-backend",
157
+ "quote",
158
+ "syn",
159
+ ]
160
+
161
+ [[package]]
162
+ name = "pyo3-macros-backend"
163
+ version = "0.23.5"
164
+ source = "registry+https://github.com/rust-lang/crates.io-index"
165
+ checksum = "fca6726ad0f3da9c9de093d6f116a93c1a38e417ed73bf138472cf4064f72028"
166
+ dependencies = [
167
+ "heck",
168
+ "proc-macro2",
169
+ "pyo3-build-config",
170
+ "quote",
171
+ "syn",
172
+ ]
173
+
174
+ [[package]]
175
+ name = "quote"
176
+ version = "1.0.38"
177
+ source = "registry+https://github.com/rust-lang/crates.io-index"
178
+ checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
179
+ dependencies = [
180
+ "proc-macro2",
181
+ ]
182
+
183
+ [[package]]
184
+ name = "rayon"
185
+ version = "1.10.0"
186
+ source = "registry+https://github.com/rust-lang/crates.io-index"
187
+ checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
188
+ dependencies = [
189
+ "either",
190
+ "rayon-core",
191
+ ]
192
+
193
+ [[package]]
194
+ name = "rayon-core"
195
+ version = "1.12.1"
196
+ source = "registry+https://github.com/rust-lang/crates.io-index"
197
+ checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
198
+ dependencies = [
199
+ "crossbeam-deque",
200
+ "crossbeam-utils",
201
+ ]
202
+
203
+ [[package]]
204
+ name = "simulation"
205
+ version = "0.1.0"
206
+ dependencies = [
207
+ "graph-base",
208
+ "graph-simulation",
209
+ "pyo3",
210
+ "rayon",
211
+ ]
212
+
213
+ [[package]]
214
+ name = "syn"
215
+ version = "2.0.98"
216
+ source = "registry+https://github.com/rust-lang/crates.io-index"
217
+ checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
218
+ dependencies = [
219
+ "proc-macro2",
220
+ "quote",
221
+ "unicode-ident",
222
+ ]
223
+
224
+ [[package]]
225
+ name = "target-lexicon"
226
+ version = "0.12.16"
227
+ source = "registry+https://github.com/rust-lang/crates.io-index"
228
+ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
229
+
230
+ [[package]]
231
+ name = "unicode-ident"
232
+ version = "1.0.14"
233
+ source = "registry+https://github.com/rust-lang/crates.io-index"
234
+ checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
235
+
236
+ [[package]]
237
+ name = "unindent"
238
+ version = "0.2.3"
239
+ source = "registry+https://github.com/rust-lang/crates.io-index"
240
+ checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce"
@@ -0,0 +1,25 @@
1
+ [package]
2
+ name = "simulation"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ license = "MIT"
6
+ description = "A Python package for simulation algorithms"
7
+ homepage = "https://github.com/RaVincentHuang/simulation"
8
+ repository = "https://github.com/RaVincentHuang/simulation"
9
+
10
+
11
+ [lib]
12
+ name = "simulation"
13
+ crate-type = ["cdylib"]
14
+
15
+ [dependencies]
16
+ pyo3 = { version = "0.23", features = ["extension-module"] }
17
+ graph-simulation = "0.1.2"
18
+ graph-base = "0.1.2"
19
+ rayon = "1.10.0"
20
+ # [tool.maturin]
21
+ # include-pyi = true # 自动打包生成的 .pyi 文件
22
+ # python-source = "python"
23
+
24
+ [package.metadata.maturin]
25
+ bindings = "pyo3"
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Zixiao Huang
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,54 @@
1
+ Metadata-Version: 2.4
2
+ Name: simulation-alg
3
+ Version: 0.1.0
4
+ Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: Implementation :: CPython
6
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
7
+ License-File: LICENSE
8
+ Summary: A Python package for simulation algorithms
9
+ Home-Page: https://github.com/RaVincentHuang/simulation
10
+ License: MIT
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
13
+ Project-URL: Source Code, https://github.com/RaVincentHuang/simulation
14
+
15
+ # Python Simulation Package
16
+
17
+
18
+ **Install**
19
+ ```bash
20
+ git submodule update --init --recursive
21
+ pip install maturin
22
+ pip install -r requirements.txt
23
+ maturin develop --release
24
+ ```
25
+
26
+ **How to use**
27
+ ```python
28
+ import networkx as nx
29
+ import simulation
30
+
31
+ def random_graph_gen(n, p, k):
32
+ G: DiGraph = nx.fast_gnp_random_graph(n, p, directed=True)
33
+ for node in G.nodes:
34
+ G.nodes[node]['label'] = random.randint(1, k)
35
+ return G
36
+
37
+ def graph_permutation(g: DiGraph) -> DiGraph:
38
+ nodes = list(g.nodes)
39
+ random.shuffle(nodes)
40
+ mapping = dict(zip(g.nodes, nodes))
41
+ g_perm = nx.relabel_nodes(g, mapping)
42
+ return g_perm
43
+
44
+ def attr_same(attr1: dict, attr2: dict):
45
+ label1 = attr1.get("label")
46
+ label2 = attr2.get("label")
47
+ return label1 == label2
48
+
49
+ n, p = ...
50
+ g1 = random_graph_gen(n, p, directed=True)
51
+ g2 = graph_permutation(g1)
52
+ print(simulation.is_simulation_isomorphic_fn(g1, g2, attr_same))
53
+ ```
54
+
@@ -0,0 +1,39 @@
1
+ # Python Simulation Package
2
+
3
+
4
+ **Install**
5
+ ```bash
6
+ git submodule update --init --recursive
7
+ pip install maturin
8
+ pip install -r requirements.txt
9
+ maturin develop --release
10
+ ```
11
+
12
+ **How to use**
13
+ ```python
14
+ import networkx as nx
15
+ import simulation
16
+
17
+ def random_graph_gen(n, p, k):
18
+ G: DiGraph = nx.fast_gnp_random_graph(n, p, directed=True)
19
+ for node in G.nodes:
20
+ G.nodes[node]['label'] = random.randint(1, k)
21
+ return G
22
+
23
+ def graph_permutation(g: DiGraph) -> DiGraph:
24
+ nodes = list(g.nodes)
25
+ random.shuffle(nodes)
26
+ mapping = dict(zip(g.nodes, nodes))
27
+ g_perm = nx.relabel_nodes(g, mapping)
28
+ return g_perm
29
+
30
+ def attr_same(attr1: dict, attr2: dict):
31
+ label1 = attr1.get("label")
32
+ label2 = attr2.get("label")
33
+ return label1 == label2
34
+
35
+ n, p = ...
36
+ g1 = random_graph_gen(n, p, directed=True)
37
+ g2 = graph_permutation(g1)
38
+ print(simulation.is_simulation_isomorphic_fn(g1, g2, attr_same))
39
+ ```
@@ -0,0 +1,13 @@
1
+ [build-system]
2
+ requires = ["maturin>=1.0,<2.0"]
3
+ build-backend = "maturin"
4
+
5
+ [project]
6
+ name = "simulation-alg"
7
+ version = "0.1.0"
8
+ requires-python = ">=3.10"
9
+ classifiers = [
10
+ "Programming Language :: Rust",
11
+ "Programming Language :: Python :: Implementation :: CPython",
12
+ "Programming Language :: Python :: Implementation :: PyPy",
13
+ ]
File without changes
@@ -0,0 +1 @@
1
+ networkx = 3.4.2
@@ -0,0 +1,34 @@
1
+
2
+ from typing import Callable, Dict
3
+ import networkx
4
+
5
+
6
+ def get_simulation_inter(nx_graph1: networkx.DiGraph, nx_graph2: networkx.DiGraph, is_label_cached=False) -> Dict:
7
+ """
8
+ Get the simulation between two graphs.
9
+ """
10
+
11
+ def is_simulation_isomorphic(nx_graph1: networkx.DiGraph, nx_graph2: networkx.DiGraph, is_label_cached=False) -> bool:
12
+ """
13
+ Check if two graphs are isomorphic by graph simulation.
14
+ """
15
+
16
+ def get_simulation_inter_fn(nx_graph1: networkx.DiGraph, nx_graph2: networkx.DiGraph, compare_fn: Callable, is_label_cached=False) -> Dict:
17
+ """
18
+ Get the simulation between two graphs.
19
+ """
20
+
21
+ def is_simulation_isomorphic_fn(nx_graph1: networkx.DiGraph, nx_graph2: networkx.DiGraph, compare_fn: Callable, is_label_cached=False) -> bool:
22
+ """
23
+ Check if two graphs are isomorphic by graph simulation.
24
+ """
25
+
26
+ def is_simulation_isomorphic_of_node_edge_fn(nx_graph1: networkx.DiGraph, nx_graph2: networkx.DiGraph, node_compare_fn: Callable, edge_compare_fn: Callable, is_label_cached=False) -> bool:
27
+ """
28
+ Check if two graphs are isomorphic by graph simulation.
29
+ """
30
+
31
+ def is_simulation_isomorphic_of_edge_fn(nx_graph1: networkx.DiGraph, nx_graph2: networkx.DiGraph, node_edge_compare_fn: Callable, is_label_cached=False) -> bool:
32
+ """
33
+ Check if two graphs are isomorphic by graph simulation.
34
+ """
@@ -0,0 +1,13 @@
1
+
2
+ pub mod networkx_graph;
3
+
4
+ use pyo3::prelude::*;
5
+
6
+ #[pymodule]
7
+ pub fn register_graph_module(parent_module: &Bound<'_, PyModule>) -> PyResult<()> {
8
+ let child_module = PyModule::new(parent_module.py(), "graph")?;
9
+ child_module.add_class::<networkx_graph::NetworkXGraph>()?;
10
+ child_module.add_function(wrap_pyfunction!(networkx_graph::get_simulation_inter, &child_module)?)?;
11
+ child_module.add_function(wrap_pyfunction!(networkx_graph::is_simulation_isomorphic, &child_module)?)?;
12
+ parent_module.add_submodule(&child_module)
13
+ }
@@ -0,0 +1,609 @@
1
+ use graph_simulation::algorithm::simulation::Simulation;
2
+ use pyo3::types::PySet;
3
+ use pyo3::{prelude::*, types::PyDict};
4
+ use graph_base::interfaces::labeled::{Label, Labeled, LabeledAdjacency};
5
+ use graph_base::interfaces::graph::{Graph, Directed, Adjacency, AdjacencyInv, SingleId, IdPair};
6
+
7
+ use pyo3::prelude::*;
8
+ use std::collections::{HashMap, HashSet};
9
+ use std::hash::{Hash, Hasher};
10
+ use std::path::Display;
11
+ use std::sync::Arc;
12
+
13
+ type SharedRustFn = Arc<dyn Fn(&Attributes, &Attributes) -> bool + Send + Sync>;
14
+
15
+
16
+ // 自定义图结构
17
+
18
+ #[derive(Debug)]
19
+ struct Attributes(HashMap<String, Py<PyAny>>);
20
+
21
+ impl Clone for Attributes {
22
+ fn clone(&self) -> Self {
23
+ Python::with_gil(|py| {
24
+ let cloned_map = self.0.iter()
25
+ .map(|(k, v)| (k.clone(), v.clone_ref(py)))
26
+ .collect();
27
+ Attributes(cloned_map)
28
+ })
29
+ }
30
+ }
31
+
32
+ impl std::fmt::Display for Attributes {
33
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34
+ let mut entries: Vec<_> = self.0.iter().collect();
35
+ entries.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
36
+
37
+ write!(f, "{{")?;
38
+ for (key, value) in entries {
39
+ write!(f, "{}: {}, ", key, value)?;
40
+ }
41
+ write!(f, "}}")
42
+ }
43
+ }
44
+
45
+ impl PartialEq for Attributes {
46
+ fn eq(&self, other: &Self) -> bool {
47
+ // 首先比较长度
48
+ if self.0.len() != other.0.len() {
49
+ return false;
50
+ }
51
+
52
+ Python::with_gil(|py| {
53
+ self.0.iter().all(|(key, value)| {
54
+ match other.0.get(key) {
55
+ Some(other_value) => {
56
+ match value.call_method1(py, "__eq__", (other_value,)) {
57
+ Ok(result) => result.extract::<bool>(py).unwrap_or(false),
58
+ Err(_) => false,
59
+ }
60
+ },
61
+ None => false,
62
+ }
63
+ })
64
+ })
65
+ }
66
+ }
67
+
68
+ impl Eq for Attributes {}
69
+
70
+ impl Hash for Attributes {
71
+ fn hash<H: Hasher>(&self, state: &mut H) {
72
+ // 确保相同的字典产生相同的哈希值
73
+ let mut entries: Vec<_> = self.0.iter().collect();
74
+ entries.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
75
+
76
+ Python::with_gil(|py| {
77
+ for (key, value) in entries {
78
+ key.hash(state);
79
+ // 使用 Python 对象的 __hash__ 方法
80
+ match value.call_method0(py, "__hash__") {
81
+ Ok(hash_result) => {
82
+ if let Ok(hash_value) = hash_result.extract::<isize>(py) {
83
+ hash_value.hash(state);
84
+ }
85
+ },
86
+ Err(_) => {
87
+ // 如果对象不可哈希,使用默认值
88
+ 0isize.hash(state);
89
+ }
90
+ }
91
+ }
92
+ });
93
+ }
94
+ }
95
+
96
+ impl Label for Attributes {
97
+ fn label(&self) -> &str {
98
+ ""
99
+ }
100
+ }
101
+
102
+ #[derive(Clone, Debug, Hash, PartialEq, Eq)]
103
+ pub struct Node {
104
+ id: usize,
105
+ attributes: Attributes,
106
+ }
107
+
108
+ impl std::fmt::Display for Node {
109
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110
+ write!(f, "Node({})", self.id)
111
+ }
112
+ }
113
+
114
+ impl SingleId for Node {
115
+ fn id(&self) -> usize {
116
+ self.id
117
+ }
118
+ }
119
+
120
+ #[derive(Clone, Debug, Hash, PartialEq, Eq)]
121
+ pub struct Edge {
122
+ source: usize,
123
+ target: usize,
124
+ attributes: Attributes,
125
+ }
126
+
127
+ impl IdPair for Edge {
128
+ fn pair(&self) -> (usize, usize) {
129
+ (self.source, self.target)
130
+ }
131
+ }
132
+
133
+ #[pyclass]
134
+ pub struct NetworkXGraph {
135
+ nodes: Vec<Node>,
136
+ edges: Vec<Edge>,
137
+ node_indices: HashMap<String, usize>,
138
+ same_label_fn: Option<Py<PyAny>>,
139
+ same_edge_fn: Option<Py<PyAny>>,
140
+ same_node_edge_fn: Option<Py<PyAny>>,
141
+ same_label_cache: Option<HashSet<(usize, usize)>>
142
+ }
143
+
144
+ impl Clone for NetworkXGraph {
145
+ fn clone(&self) -> Self {
146
+ Python::with_gil(|py| {
147
+ NetworkXGraph {
148
+ nodes: self.nodes.clone(),
149
+ edges: self.edges.clone(),
150
+ node_indices: self.node_indices.clone(),
151
+ same_label_fn: self.same_label_fn.as_ref().map(|f| f.clone_ref(py)),
152
+ same_edge_fn: self.same_edge_fn.as_ref().map(|f| f.clone_ref(py)),
153
+ same_node_edge_fn: self.same_node_edge_fn.as_ref().map(|f| f.clone_ref(py)),
154
+ same_label_cache: self.same_label_cache.clone(),
155
+ }
156
+ })
157
+ }
158
+ }
159
+
160
+ fn convert_to_string(obj: &PyObject) -> PyResult<String> {
161
+ Python::with_gil(|py| {
162
+ // Try direct conversion first
163
+ obj.call_method0(py, "__str__")?.extract::<String>(py)
164
+ .or_else(|_| {
165
+ // If that fails, try to convert to a string using repr
166
+ obj.call_method0(py, "__repr__")?.extract::<String>(py)
167
+ })
168
+ })
169
+ }
170
+
171
+ #[pymethods]
172
+ impl NetworkXGraph {
173
+ #[new]
174
+ fn new() -> Self {
175
+ NetworkXGraph {
176
+ nodes: Vec::new(),
177
+ edges: Vec::new(),
178
+ node_indices: HashMap::new(),
179
+ same_label_fn: None,
180
+ same_edge_fn: None,
181
+ same_node_edge_fn: None,
182
+ same_label_cache: None,
183
+ }
184
+ }
185
+
186
+ // 从NetworkX图转换的静态方法
187
+ #[staticmethod]
188
+ fn from_networkx(nx_graph: &Bound<'_, PyAny>) -> PyResult<Self> {
189
+ let nodes = nx_graph.getattr("nodes")?.call_method1("items", ())?;
190
+ let edges = nx_graph.getattr("edges")?.call_method1("data", ())?;
191
+
192
+ let mut graph = NetworkXGraph::new();
193
+
194
+ for node in nodes.try_iter()? {
195
+ let node = node?;
196
+ let id = node.call_method1("__getitem__", (0, ))?.extract::<PyObject>()?;
197
+ let id = convert_to_string(&id)?;
198
+ let attrs = node.call_method1("__getitem__", (1, ))?.extract::<HashMap<String, PyObject>>()?;
199
+ graph.add_node(id, attrs);
200
+ }
201
+ for edge in edges.try_iter()? {
202
+ let edge = edge?;
203
+ let source = edge.call_method1("__getitem__", (0, ))?.extract::<PyObject>()?;
204
+ let source = convert_to_string(&source)?;
205
+ let target = edge.call_method1("__getitem__", (1, ))?.extract::<PyObject>()?;
206
+ let target = convert_to_string(&target)?;
207
+ let attrs = edge.call_method1("__getitem__", (2, ))?.extract::<HashMap<String, PyObject>>()?;
208
+ graph.add_edge(source, target, attrs);
209
+ }
210
+
211
+ Ok(graph)
212
+ }
213
+
214
+ // 转回NetworkX图的方法
215
+ fn to_networkx(&self, py: Python) -> PyResult<PyObject> {
216
+ let nx = py.import("networkx")?;
217
+ let graph = nx.getattr("Graph")?.call0()?;
218
+
219
+ // 添加节点
220
+ for node in &self.nodes {
221
+ let attrs_dict = PyDict::new(py);
222
+ for (k, v) in &node.attributes.0 {
223
+ attrs_dict.set_item(k, v.clone_ref(py))?;
224
+ }
225
+ graph.call_method1(
226
+ "add_node",
227
+ (node.id.clone(), attrs_dict),
228
+ )?;
229
+ }
230
+
231
+ // 添加边
232
+ for edge in &self.edges {
233
+ let attrs_dict = PyDict::new(py);
234
+ for (k, v) in &edge.attributes.0 {
235
+ attrs_dict.set_item(k, v.clone_ref(py))?;
236
+ }
237
+ graph.call_method1(
238
+ "add_edge",
239
+ (
240
+ edge.source.clone(),
241
+ edge.target.clone(),
242
+ attrs_dict,
243
+ ),
244
+ )?;
245
+ }
246
+
247
+ Ok(graph.into())
248
+ }
249
+
250
+ // 其他有用的方法
251
+ fn add_node(&mut self, id: String, attributes: HashMap<String, PyObject>) {
252
+ let index = self.nodes.len();
253
+ self.node_indices.insert(id.clone(), index);
254
+ let attributes = Attributes(attributes);
255
+ self.nodes.push(Node { id: index, attributes });
256
+ }
257
+
258
+ fn add_edge(
259
+ &mut self,
260
+ source: String,
261
+ target: String,
262
+ attributes: HashMap<String, PyObject>,
263
+ ) {
264
+ let attributes = Attributes(attributes);
265
+ let source = *self.node_indices.get(&source).unwrap();
266
+ let target = *self.node_indices.get(&target).unwrap();
267
+ self.edges.push(Edge {
268
+ source,
269
+ target,
270
+ attributes,
271
+ });
272
+ }
273
+
274
+ fn node_count(&self) -> usize {
275
+ self.nodes.len()
276
+ }
277
+
278
+ fn edge_count(&self) -> usize {
279
+ self.edges.len()
280
+ }
281
+
282
+ // 获取节点属性
283
+ fn get_node_attributes(&self, node_id: &str) -> Option<HashMap<String, PyObject>> {
284
+ self.node_indices.get(node_id).map(|&index| {
285
+ Python::with_gil(|py| {
286
+ self.nodes[index].attributes.0.iter()
287
+ .map(|(k, v)| (k.clone(), v.clone_ref(py)))
288
+ .collect()
289
+ })
290
+ })
291
+ }
292
+
293
+ // 获取边属性
294
+ fn get_edge_attributes(
295
+ &self,
296
+ source: &str,
297
+ target: &str,
298
+ ) -> Option<HashMap<String, PyObject>> {
299
+ self.edges
300
+ .iter()
301
+ .find(|e| e.source == *self.node_indices.get(source).unwrap()
302
+ && e.target == *self.node_indices.get(target).unwrap())
303
+ .map(|e| Python::with_gil(|py| {
304
+ e.attributes.0.iter()
305
+ .map(|(k, v)| (k.clone(), v.clone_ref(py)))
306
+ .collect()
307
+ }))
308
+ }
309
+
310
+ fn register_compare_fn(&mut self, compare: Py<PyAny>) {
311
+ self.same_label_fn = Some(compare);
312
+ }
313
+
314
+ fn register_edge_compare_fn(&mut self, compare: Py<PyAny>) {
315
+ self.same_edge_fn = Some(compare);
316
+ }
317
+
318
+ fn register_node_edge_compare_fn(&mut self, compare: Py<PyAny>) {
319
+ self.same_node_edge_fn = Some(compare);
320
+ }
321
+
322
+ fn build_compare_cache(&mut self, other: &NetworkXGraph) {
323
+ // use rayon::prelude::*;
324
+ // let cache: HashSet<_> = self.nodes.par_iter().flat_map(|node1| {
325
+ // let local: HashSet<_> = other.nodes.par_iter().filter_map(|node2| {
326
+ // if self.label_same(node1, node2) {
327
+ // Some((node1.id, node2.id))
328
+ // } else {
329
+ // None
330
+ // }
331
+ // }).collect();
332
+ // local
333
+ // }).collect();
334
+
335
+ let cache: HashSet<_> = self.nodes.iter().flat_map(|node1| {
336
+ other.nodes.iter().filter_map(|node2| {
337
+ if self.label_same(node1, node2) {
338
+ Some((node1.id, node2.id))
339
+ } else {
340
+ None
341
+ }
342
+ }).collect::<HashSet<_>>()
343
+ }).collect();
344
+
345
+ self.same_label_cache = Some(cache);
346
+ }
347
+ }
348
+
349
+ impl<'a> Graph<'a> for NetworkXGraph {
350
+ type Node = Node;
351
+
352
+ type Edge = Edge;
353
+
354
+ fn nodes(&'a self) -> impl Iterator<Item = &'a Self::Node> {
355
+ self.nodes.iter()
356
+ }
357
+
358
+ fn edges(&'a self) -> impl Iterator<Item = &'a Self::Edge> {
359
+ self.edges.iter()
360
+ }
361
+
362
+ fn get_edges_pair(&'a self) -> impl Iterator<Item = (&'a Self::Node, &'a Self::Node)> {
363
+ let id_map: HashMap<_, _, std::collections::hash_map::RandomState> = HashMap::from_iter(self.nodes.iter().map(|node| (node.id, node)));
364
+ self.edges.iter().map(|edge| (id_map.get(&edge.source).unwrap().clone(), id_map.get(&edge.target).unwrap().clone()) ).collect::<Vec<_>>().into_iter()
365
+ }
366
+
367
+ fn add_node(&mut self, node: Self::Node) {
368
+ let index = self.nodes.len();
369
+ self.node_indices.insert(format!("Node{}.", index), index);
370
+ self.nodes.push(node);
371
+ }
372
+
373
+ fn add_edge(&mut self, edge: Self::Edge) {
374
+ self.edges.push(edge);
375
+ }
376
+ }
377
+
378
+ fn test_eq(a: &Py<PyAny>, b: &Py<PyAny>) -> bool {
379
+ Python::with_gil(|py| {
380
+ match a.call_method1(py, "__eq__", (b,)) {
381
+ Ok(result) => result.extract::<bool>(py).unwrap_or(false),
382
+ Err(_) => false
383
+ }
384
+ })
385
+ }
386
+
387
+ fn native_same_label_fn(a: &Attributes, b: &Attributes) -> bool {
388
+ for (k, v) in &a.0 {
389
+ if let Some(other_v) = b.0.get(k) {
390
+ if test_eq(v, other_v) {
391
+ continue;
392
+ }
393
+ } else {
394
+ return false;
395
+ }
396
+ }
397
+ true
398
+ }
399
+
400
+ impl<'a> Labeled<'a> for NetworkXGraph {
401
+ fn label_same(&self, node: &Self::Node, label: &Self::Node) -> bool {
402
+
403
+ if let Some(cache) = self.same_label_cache.as_ref() {
404
+ return cache.contains(&(node.id, label.id));
405
+ }
406
+
407
+ if let Some(compare_fn) = self.same_label_fn.as_ref() {
408
+ Python::with_gil(|py| {
409
+ let attr1 = node.attributes.0.iter().map(|(k, v)| (k.clone(), v.clone_ref(py))).collect::<HashMap<_, _>>();
410
+ let attr2 = label.attributes.0.iter().map(|(k, v)| (k.clone(), v.clone_ref(py))).collect::<HashMap<_, _>>();
411
+ compare_fn.call1(py,(attr1, attr2)).unwrap().extract::<bool>(py).unwrap()
412
+ })
413
+ } else {
414
+ return native_same_label_fn(&node.attributes, &label.attributes);
415
+ }
416
+ }
417
+
418
+ fn get_label(&'a self, node: &'a Self::Node) -> &'a impl Label {
419
+ &node.attributes
420
+ }
421
+
422
+ fn get_edges_pair_label(&'a self) -> impl Iterator<Item = (&'a Self::Node, &'a Self::Node, &'a impl Label)> {
423
+ let id_map: HashMap<_, _, std::collections::hash_map::RandomState> = HashMap::from_iter(self.nodes.iter().map(|node| (node.id, node)));
424
+ self.edges.iter().map(move |edge| (id_map.get(&edge.source).unwrap().clone(), id_map.get(&edge.target).unwrap().clone(), &edge.attributes)).collect::<Vec<_>>().into_iter()
425
+ }
426
+
427
+ fn edge_label_same(&self, edge1: &Self::Edge, edge2: &Self::Edge) -> bool {
428
+ if let Some(compare_fn) = self.same_edge_fn.as_ref() {
429
+ Python::with_gil(|py| {
430
+ let attr1 = edge1.attributes.0.iter().map(|(k, v)| (k.clone(), v.clone_ref(py))).collect::<HashMap<_, _>>();
431
+ let attr2 = edge2.attributes.0.iter().map(|(k, v)| (k.clone(), v.clone_ref(py))).collect::<HashMap<_, _>>();
432
+ compare_fn.call1(py,(attr1, attr2)).unwrap().extract::<bool>(py).unwrap()
433
+ })
434
+ } else {
435
+ native_same_label_fn(&edge1.attributes, &edge2.attributes)
436
+ }
437
+ }
438
+
439
+ fn edge_node_label_same(&self, src1: &Self::Node, edge1: &Self::Edge, dst1: &Self::Node, src2: &Self::Node, edge2: &Self::Edge, dst2: &Self::Node) -> bool {
440
+ if let Some(compare_fn) = self.same_node_edge_fn.as_ref() {
441
+ Python::with_gil(|py| {
442
+ let src1_attr = src1.attributes.0.iter().map(|(k, v)| (k.clone(), v.clone_ref(py))).collect::<HashMap<_, _>>();
443
+ let dst1_attr = dst1.attributes.0.iter().map(|(k, v)| (k.clone(), v.clone_ref(py))).collect::<HashMap<_, _>>();
444
+ let edge1_attr = edge1.attributes.0.iter().map(|(k, v)| (k.clone(), v.clone_ref(py))).collect::<HashMap<_, _>>();
445
+ let src2_attr = src2.attributes.0.iter().map(|(k, v)| (k.clone(), v.clone_ref(py))).collect::<HashMap<_, _>>();
446
+ let dst2_attr = dst2.attributes.0.iter().map(|(k, v)| (k.clone(), v.clone_ref(py))).collect::<HashMap<_, _>>();
447
+ let edge2_attr = edge2.attributes.0.iter().map(|(k, v)| (k.clone(), v.clone_ref(py))).collect::<HashMap<_, _>>();
448
+ compare_fn.call1(py,(src1_attr, edge1_attr, dst1_attr, src2_attr, edge2_attr, dst2_attr)).unwrap().extract::<bool>(py).unwrap()
449
+ })
450
+ } else {
451
+ native_same_label_fn(&src1.attributes, &src2.attributes)
452
+ && native_same_label_fn(&dst1.attributes, &dst2.attributes)
453
+ && native_same_label_fn(&edge1.attributes, &edge2.attributes)
454
+ }
455
+ }
456
+ }
457
+
458
+ impl LabeledAdjacency<'_> for NetworkXGraph {}
459
+
460
+ impl std::fmt::Display for NetworkXGraph {
461
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
462
+ write!(f, "NetworkXGraph(\n")?;
463
+ write!(f, "Nodes: [\n")?;
464
+ for node in &self.nodes {
465
+ write!(f, " {},\n", node)?;
466
+ }
467
+ write!(f, "],\n")?;
468
+ write!(f, "Edges: [\n")?;
469
+ for edge in &self.edges {
470
+ write!(f, "{} -> {}, ", edge.source, edge.target)?;
471
+ }
472
+ write!(f, "]\n")?;
473
+ write!(f, ")")
474
+ }
475
+ }
476
+
477
+ fn to_nx_node(py: Python, node: &Node) -> PyResult<PyObject> {
478
+ let attrs_dict = PyDict::new(py);
479
+ for (k, v) in &node.attributes.0 {
480
+ attrs_dict.set_item(k, v.clone_ref(py))?;
481
+ }
482
+ let nx = py.import("networkx")?;
483
+ let node = nx.getattr("Node")?.call1((node.id.clone(), attrs_dict))?;
484
+ Ok(node.into())
485
+ }
486
+
487
+ #[pyfunction]
488
+ #[pyo3(signature = (nx_graph1, nx_graph2, is_label_cached = false))]
489
+ pub fn get_simulation_inter(nx_graph1: &Bound<'_, PyAny>, nx_graph2: &Bound<'_, PyAny>, is_label_cached: bool) -> PyResult<PyObject> {
490
+ let mut graph1 = NetworkXGraph::from_networkx(nx_graph1)?;
491
+ let graph2 = NetworkXGraph::from_networkx(nx_graph2)?;
492
+
493
+ if is_label_cached {
494
+ graph1.build_compare_cache(&graph2);
495
+ }
496
+
497
+ let sim = graph1.get_simulation_inter(&graph2);
498
+
499
+
500
+ // Convert simulation to a list of pairs (i, j) where i is a node in graph1, j is a node in graph2
501
+ Python::with_gil(|py| {
502
+ let map = PyDict::new(py);
503
+
504
+ for (node, set) in sim.iter() {
505
+ let py_set = PySet::new(py, set.iter().map(|node| to_nx_node(py, node)).collect::<PyResult<Vec<_>>>()?)?;
506
+ map.set_item(to_nx_node(py, node)?, py_set)?;
507
+ }
508
+
509
+ Ok(map.into())
510
+ })
511
+ }
512
+
513
+ #[pyfunction]
514
+ #[pyo3(signature = (nx_graph1, nx_graph2, is_label_cached = false))]
515
+ pub fn is_simulation_isomorphic(nx_graph1: &Bound<'_, PyAny>, nx_graph2: &Bound<'_, PyAny>, is_label_cached: bool) -> PyResult<bool> {
516
+ let mut graph1 = NetworkXGraph::from_networkx(nx_graph1)?;
517
+ let graph2 = NetworkXGraph::from_networkx(nx_graph2)?;
518
+
519
+ if is_label_cached {
520
+ graph1.build_compare_cache(&graph2);
521
+ }
522
+
523
+ Ok(NetworkXGraph::has_simulation(graph1.get_simulation_inter(&graph2)))
524
+ }
525
+
526
+ #[pyfunction]
527
+ #[pyo3(signature = (nx_graph1, nx_graph2, compare, is_label_cached = false))]
528
+ pub fn get_simulation_inter_fn(nx_graph1: &Bound<'_, PyAny>, nx_graph2: &Bound<'_, PyAny>, compare: Py<PyAny>, is_label_cached: bool) -> PyResult<PyObject> {
529
+ let mut graph1 = NetworkXGraph::from_networkx(nx_graph1)?;
530
+ let graph2 = NetworkXGraph::from_networkx(nx_graph2)?;
531
+
532
+ graph1.register_compare_fn(compare);
533
+
534
+ if is_label_cached {
535
+ graph1.build_compare_cache(&graph2);
536
+ }
537
+
538
+ let sim = graph1.get_simulation_inter(&graph2);
539
+
540
+ Python::with_gil(|py| {
541
+ let map = PyDict::new(py);
542
+
543
+ for (node, set) in sim.iter() {
544
+ let py_set = PySet::new(py, set.iter().map(|node| to_nx_node(py, node)).collect::<PyResult<Vec<_>>>()?)?;
545
+ map.set_item(to_nx_node(py, node)?, py_set)?;
546
+ }
547
+
548
+ Ok(map.into())
549
+ })
550
+ }
551
+
552
+ #[pyfunction]
553
+ #[pyo3(signature = (nx_graph1, nx_graph2, compare, is_label_cached = false))]
554
+ pub fn is_simulation_isomorphic_fn(nx_graph1: &Bound<'_, PyAny>, nx_graph2: &Bound<'_, PyAny>, compare: Py<PyAny>, is_label_cached: bool) -> PyResult<bool> {
555
+ let mut graph1 = NetworkXGraph::from_networkx(nx_graph1)?;
556
+ let graph2 = NetworkXGraph::from_networkx(nx_graph2)?;
557
+
558
+ graph1.register_compare_fn(compare);
559
+
560
+ if is_label_cached {
561
+ graph1.build_compare_cache(&graph2);
562
+ }
563
+
564
+ Ok(NetworkXGraph::has_simulation(graph1.get_simulation_inter(&graph2)))
565
+ }
566
+
567
+ #[pyfunction]
568
+ #[pyo3(signature = (nx_graph1, nx_graph2, node_compare, edge_compare, is_label_cached = false))]
569
+ pub fn is_simulation_isomorphic_of_node_edge_fn(nx_graph1: &Bound<'_, PyAny>, nx_graph2: &Bound<'_, PyAny>, node_compare: Py<PyAny>, edge_compare: Py<PyAny>, is_label_cached: bool) -> PyResult<bool> {
570
+ let mut graph1 = NetworkXGraph::from_networkx(nx_graph1)?;
571
+ let graph2 = NetworkXGraph::from_networkx(nx_graph2)?;
572
+
573
+ graph1.register_compare_fn(node_compare);
574
+ graph1.register_edge_compare_fn(edge_compare);
575
+
576
+ if is_label_cached {
577
+ graph1.build_compare_cache(&graph2);
578
+ }
579
+
580
+ Ok(NetworkXGraph::has_simulation(graph1.get_simulation_of_node_edge(&graph2)))
581
+ }
582
+
583
+ #[pyfunction]
584
+ #[pyo3(signature = (nx_graph1, nx_graph2, node_edge_compare, is_label_cached = false))]
585
+ pub fn is_simulation_isomorphic_of_edge_fn(nx_graph1: &Bound<'_, PyAny>, nx_graph2: &Bound<'_, PyAny>, node_edge_compare: Py<PyAny>, is_label_cached: bool) -> PyResult<bool> {
586
+ let mut graph1 = NetworkXGraph::from_networkx(nx_graph1)?;
587
+ let graph2 = NetworkXGraph::from_networkx(nx_graph2)?;
588
+
589
+ graph1.register_node_edge_compare_fn(node_edge_compare);
590
+
591
+ if is_label_cached {
592
+ graph1.build_compare_cache(&graph2);
593
+ }
594
+
595
+ Ok(NetworkXGraph::has_simulation(graph1.get_simulation_of_edge(&graph2)))
596
+ }
597
+
598
+ impl Directed for NetworkXGraph {}
599
+
600
+ impl Adjacency<'_> for NetworkXGraph {}
601
+
602
+ impl AdjacencyInv<'_> for NetworkXGraph {}
603
+
604
+ // 模块定义
605
+ // #[pymodule]
606
+ // pub fn networkx_graph(_py: Python, m: &PyModule) -> PyResult<()> {
607
+ // m.add_class::<NetworkXGraph>()?;
608
+ // Ok(())
609
+ // }
@@ -0,0 +1,17 @@
1
+ pub mod graph;
2
+ pub mod utils;
3
+
4
+ use pyo3::prelude::*;
5
+
6
+
7
+ #[pymodule]
8
+ fn simulation(m: &Bound<'_, PyModule>) -> PyResult<()> {
9
+ graph::register_graph_module(m)?;
10
+ m.add_function(wrap_pyfunction!(graph::networkx_graph::get_simulation_inter, m)?)?;
11
+ m.add_function(wrap_pyfunction!(graph::networkx_graph::get_simulation_inter_fn, m)?)?;
12
+ m.add_function(wrap_pyfunction!(graph::networkx_graph::is_simulation_isomorphic, m)?)?;
13
+ m.add_function(wrap_pyfunction!(graph::networkx_graph::is_simulation_isomorphic_fn, m)?)?;
14
+ m.add_function(wrap_pyfunction!(graph::networkx_graph::is_simulation_isomorphic_of_edge_fn, m)?)?;
15
+ m.add_function(wrap_pyfunction!(graph::networkx_graph::is_simulation_isomorphic_of_node_edge_fn, m)?)?;
16
+ Ok(())
17
+ }
@@ -0,0 +1,2 @@
1
+ use pyo3::prelude::*;
2
+
@@ -0,0 +1,75 @@
1
+ from math import e
2
+ import time
3
+ import simulation
4
+ import networkx as nx
5
+ import os
6
+
7
+ # Get the directory where the current file is located
8
+ current_dir = os.path.dirname(os.path.abspath(__file__))
9
+
10
+ simulation_test_path = os.path.join(current_dir, "lib/graph-simulation/data/label_graph/simulation_test")
11
+ # Read all files in simulation_test_path
12
+ files = []
13
+ if os.path.exists(simulation_test_path):
14
+ for file_name in os.listdir(simulation_test_path):
15
+ file_path = os.path.join(simulation_test_path, file_name)
16
+ if os.path.isfile(file_path):
17
+ files.append(file_path)
18
+
19
+ def attr_same(attr1: dict, attr2: dict):
20
+ label1 = attr1.get("label")
21
+ label2 = attr2.get("label")
22
+ return label1 == label2
23
+
24
+ times_cost = 0
25
+
26
+ for path in files:
27
+ with open(path, "r") as f:
28
+ lines = f.readlines()
29
+ # Get the first line of the file
30
+ is_isomorphic = True if lines[0].strip() == "t" else False
31
+
32
+ index = 1
33
+ # Get the first graph
34
+ graph1 = nx.DiGraph()
35
+ graph1_nodes, graph1_edges, graph1_labels = map(int, lines[index].strip().split())
36
+ index += 1
37
+ for i in range(index, index + graph1_nodes):
38
+ idx, label = lines[i].strip().split()
39
+ graph1.add_node(idx, label=label)
40
+
41
+ index += graph1_nodes
42
+
43
+ for i in range(index, index + graph1_edges):
44
+ u, v = map(int, lines[i].strip().split())
45
+ graph1.add_edge(u, v)
46
+
47
+ index += graph1_edges
48
+
49
+ # Get the second graph
50
+
51
+ graph2 = nx.DiGraph()
52
+
53
+ graph2_nodes, graph2_edges, graph2_labels = map(int, lines[index].strip().split())
54
+ index += 1
55
+ for i in range(index, index + graph2_nodes):
56
+ idx, label = lines[i].strip().split()
57
+ graph2.add_node(idx, label=label)
58
+
59
+ index += graph2_nodes
60
+
61
+ for i in range(index, index + graph2_edges):
62
+ u, v = map(int, lines[i].strip().split())
63
+ graph2.add_edge(u, v)
64
+
65
+
66
+ start = time.time()
67
+ is_simulation = simulation.is_simulation_isomorphic_fn(graph1, graph2, attr_same)
68
+ end = time.time()
69
+ times_cost += end - start
70
+
71
+ if is_isomorphic != is_simulation and is_isomorphic:
72
+ print("Test failed for file", path)
73
+
74
+
75
+ print("Time taken:", times_cost)