simulation-alg 0.1.9__tar.gz → 0.1.11__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.
Files changed (22) hide show
  1. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/.gitignore +1 -0
  2. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/Cargo.lock +3 -3
  3. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/Cargo.toml +3 -3
  4. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/PKG-INFO +1 -1
  5. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/pyproject.toml +1 -1
  6. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/simulation.pyi +16 -0
  7. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/src/graph/hypergraph.rs +9 -0
  8. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/src/graph/networkx_graph.rs +125 -21
  9. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/src/lib.rs +1 -0
  10. simulation_alg-0.1.11/test_bounded_simulation.py +84 -0
  11. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/.github/workflows/ci.yml +0 -0
  12. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/.gitmodules +0 -0
  13. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/LICENSE +0 -0
  14. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/README.md +0 -0
  15. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/hyper-simulation.log +0 -0
  16. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/python/__init__.py +0 -0
  17. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/requirements.txt +0 -0
  18. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/src/graph/mod.rs +0 -0
  19. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/src/utils.rs +0 -0
  20. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/test.py +0 -0
  21. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/tests/test_hyper_simulation.py +0 -0
  22. {simulation_alg-0.1.9 → simulation_alg-0.1.11}/tests/test_simulation.py +0 -0
@@ -1,2 +1,3 @@
1
1
  /target
2
2
  /.vscode
3
+ /logs
@@ -188,9 +188,9 @@ dependencies = [
188
188
 
189
189
  [[package]]
190
190
  name = "graph-simulation"
191
- version = "0.1.24"
191
+ version = "0.1.25"
192
192
  source = "registry+https://github.com/rust-lang/crates.io-index"
193
- checksum = "3744a0d1d724cad58fef5eee7383bcdc3cd318952f4611ca9f3916fe0bf01e46"
193
+ checksum = "f56f1442ddd23a716bdc42a3d1a53a08a4dd07dc9801e2ea102cbd1b1ff6677d"
194
194
  dependencies = [
195
195
  "bincode",
196
196
  "env_logger",
@@ -543,7 +543,7 @@ dependencies = [
543
543
 
544
544
  [[package]]
545
545
  name = "simulation"
546
- version = "0.1.8"
546
+ version = "0.1.11"
547
547
  dependencies = [
548
548
  "graph-base",
549
549
  "graph-simulation",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "simulation"
3
- version = "0.1.8"
3
+ version = "0.1.11"
4
4
  edition = "2021"
5
5
  license = "MIT"
6
6
  description = "A Python package for simulation algorithms"
@@ -14,8 +14,8 @@ crate-type = ["cdylib"]
14
14
 
15
15
  [dependencies]
16
16
  pyo3 = { version = "0.27.1", features = ["extension-module"] }
17
- graph-simulation = "0.1.24"
18
- graph-base = "0.1.13"
17
+ graph-simulation = "0.1.25"
18
+ graph-base = "0.1.14"
19
19
  rayon = "1.10.0"
20
20
  # [tool.maturin]
21
21
  # include-pyi = true # 自动打包生成的 .pyi 文件
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simulation-alg
3
- Version: 0.1.9
3
+ Version: 0.1.11
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "simulation-alg"
7
- version = "0.1.9"
7
+ version = "0.1.11"
8
8
  requires-python = ">=3.10"
9
9
  classifiers = [
10
10
  "Programming Language :: Rust",
@@ -33,6 +33,16 @@ def is_simulation_isomorphic_of_edge_fn(nx_graph1: networkx.DiGraph, nx_graph2:
33
33
  Check if two graphs are isomorphic by graph simulation.
34
34
  """
35
35
 
36
+ def get_bounded_simulation(nx_graph1: networkx.DiGraph, nx_graph2: networkx.DiGraph, compare: Callable, bound: Callable, is_label_cached=False) -> Dict:
37
+ """
38
+ Get the bounded simulation between two graphs.
39
+
40
+ The compare function should take two node attribute dictionaries and return True if they have the same label.
41
+ The bound function should take a node attribute dictionary and return an integer representing the bound value for that node.
42
+
43
+ Returns a dictionary mapping nodes from nx_graph1 to sets of nodes from nx_graph2 that satisfy the bounded simulation.
44
+ """
45
+
36
46
  class Node:
37
47
  """
38
48
  Node for hypergraph.
@@ -123,4 +133,10 @@ class Hypergraph:
123
133
  """
124
134
  Hyper Simulation
125
135
  """
136
+
137
+ @staticmethod
138
+ def get_hyper_simulation_strict(query: 'Hypergraph', data: 'Hypergraph', delta: Delta, d_match: DMatch) -> dict[int, set[int]]:
139
+ """
140
+ Strict Hyper Simulation
141
+ """
126
142
 
@@ -174,6 +174,15 @@ impl Hypergraph {
174
174
  .collect()
175
175
  }
176
176
 
177
+ #[staticmethod]
178
+ pub fn get_hyper_simulation_strict(query: PyRef<Hypergraph>, data: PyRef<Hypergraph>, delta: PyRef<DeltaPy>, d_match: PyRef<DMatchImpl>) -> HashMap<usize, HashSet<usize>> {
179
+ let delta_inner = DeltaImpl::from(delta.clone(), &*query, &*data);
180
+ let sim = HyperSimulation::get_hyper_simulation_strict(&*query, &*data, &delta_inner, &*d_match);
181
+ sim.into_iter()
182
+ .map(|(k, v)| (k.id(), v.into_iter().map(|n| n.id()).collect()))
183
+ .collect()
184
+ }
185
+
177
186
  pub fn get_hyper_simulation_trace(&self) -> Vec<Event> {
178
187
  let trace = HyperSimulationTrace::get_trace("hyper_simulation.trace").unwrap();
179
188
  let events = trace.into_iter().map(|event| {
@@ -1,9 +1,10 @@
1
1
  use graph_base::interfaces::vertex::Vertex;
2
2
  use graph_simulation::algorithm::simulation::Simulation;
3
+ use graph_simulation::algorithm::bounded::{BoundedSimulation, Bounded};
3
4
  use pyo3::types::PySet;
4
5
  use pyo3::{prelude::*, types::PyDict};
5
6
  use graph_base::interfaces::labeled::{Label, Labeled, LabeledAdjacency};
6
- use graph_base::interfaces::graph::{Graph, Directed, Adjacency, AdjacencyInv, SingleId, IdPair};
7
+ use graph_base::interfaces::graph::{Graph, Directed, Adjacency, AdjacencyInv, SingleId, IdPair, Degree, DegreeList};
7
8
 
8
9
  use pyo3::prelude::*;
9
10
  use std::collections::{HashMap, HashSet};
@@ -139,7 +140,8 @@ pub struct NetworkXGraph {
139
140
  same_label_fn: Option<Py<PyAny>>,
140
141
  same_edge_fn: Option<Py<PyAny>>,
141
142
  same_node_edge_fn: Option<Py<PyAny>>,
142
- same_label_cache: Option<HashSet<(usize, usize)>>
143
+ same_label_cache: Option<HashSet<(usize, usize)>>,
144
+ bound_values: HashMap<usize, usize>, // 节点 ID 到 bound 值的映射
143
145
  }
144
146
 
145
147
  impl Clone for NetworkXGraph {
@@ -153,6 +155,7 @@ impl Clone for NetworkXGraph {
153
155
  same_edge_fn: self.same_edge_fn.as_ref().map(|f| f.clone_ref(py)),
154
156
  same_node_edge_fn: self.same_node_edge_fn.as_ref().map(|f| f.clone_ref(py)),
155
157
  same_label_cache: self.same_label_cache.clone(),
158
+ bound_values: self.bound_values.clone(),
156
159
  }
157
160
  })
158
161
  }
@@ -181,6 +184,7 @@ impl NetworkXGraph {
181
184
  same_edge_fn: None,
182
185
  same_node_edge_fn: None,
183
186
  same_label_cache: None,
187
+ bound_values: HashMap::new(),
184
188
  }
185
189
  }
186
190
 
@@ -321,17 +325,6 @@ impl NetworkXGraph {
321
325
  }
322
326
 
323
327
  fn build_compare_cache(&mut self, other: &NetworkXGraph) {
324
- // use rayon::prelude::*;
325
- // let cache: HashSet<_> = self.nodes.par_iter().flat_map(|node1| {
326
- // let local: HashSet<_> = other.nodes.par_iter().filter_map(|node2| {
327
- // if self.label_same(node1, node2) {
328
- // Some((node1.id, node2.id))
329
- // } else {
330
- // None
331
- // }
332
- // }).collect();
333
- // local
334
- // }).collect();
335
328
 
336
329
  let cache: HashSet<_> = self.nodes.iter().flat_map(|node1| {
337
330
  other.nodes.iter().filter_map(|node2| {
@@ -345,6 +338,22 @@ impl NetworkXGraph {
345
338
 
346
339
  self.same_label_cache = Some(cache);
347
340
  }
341
+
342
+ fn set_bound_values(&mut self, bound_fn: Py<PyAny>) {
343
+ // 使用 bound 函数为每个节点设置 bound 值
344
+ for node in &self.nodes {
345
+ let bound_value = Python::attach(|py| {
346
+ let node_attrs = node.attributes.0.iter()
347
+ .map(|(k, v)| (k.clone(), v.clone_ref(py)))
348
+ .collect::<HashMap<_, _>>();
349
+ bound_fn.call1(py, (node_attrs,))
350
+ .ok()
351
+ .and_then(|result| result.extract::<usize>(py).ok())
352
+ .unwrap_or(0)
353
+ });
354
+ self.bound_values.insert(node.id, bound_value);
355
+ }
356
+ }
348
357
  }
349
358
 
350
359
  impl Vertex for Node {}
@@ -363,6 +372,7 @@ impl<'a> Graph<'a> for NetworkXGraph {
363
372
  same_edge_fn: None,
364
373
  same_node_edge_fn: None,
365
374
  same_label_cache: None,
375
+ bound_values: HashMap::new(),
366
376
  }
367
377
  }
368
378
 
@@ -489,14 +499,14 @@ impl std::fmt::Display for NetworkXGraph {
489
499
  }
490
500
  }
491
501
 
492
- fn to_nx_node(py: Python, node: &Node) -> PyResult<Py<PyAny>> {
493
- let attrs_dict = PyDict::new(py);
494
- for (k, v) in &node.attributes.0 {
495
- attrs_dict.set_item(k, v.clone_ref(py))?;
496
- }
497
- let nx = py.import("networkx")?;
498
- let node = nx.getattr("Node")?.call1((node.id.clone(), attrs_dict))?;
499
- Ok(node.into())
502
+ fn to_nx_node(_py: Python, node: &Node) -> PyResult<Py<PyAny>> {
503
+ // Return the node id as a Python object
504
+ Python::attach(|py| {
505
+ let builtins = py.import("builtins")?;
506
+ let int_fn = builtins.getattr("int")?;
507
+ let val = int_fn.call1((node.id,))?;
508
+ Ok(val.into())
509
+ })
500
510
  }
501
511
 
502
512
  #[pyfunction]
@@ -616,6 +626,100 @@ impl Adjacency<'_> for NetworkXGraph {}
616
626
 
617
627
  impl AdjacencyInv<'_> for NetworkXGraph {}
618
628
 
629
+ impl<'a> Degree<'a> for NetworkXGraph {
630
+ fn get_out_degree(&'a self) -> DegreeList<'a, Self> {
631
+ let mut degree_map = HashMap::new();
632
+
633
+ for node in &self.nodes {
634
+ let out_degree = self.edges.iter()
635
+ .filter(|edge| edge.source == node.id)
636
+ .count();
637
+ degree_map.insert(node, out_degree);
638
+ }
639
+
640
+ // 使用 unsafe 代码来创建 DegreeList,因为其字段是私有的
641
+ unsafe { std::mem::transmute::<HashMap<&'a Self::Node, usize>, DegreeList<'a, Self>>(degree_map) }
642
+ }
643
+
644
+ fn get_in_degree(&'a self) -> DegreeList<'a, Self> {
645
+ let mut degree_map = HashMap::new();
646
+
647
+ for node in &self.nodes {
648
+ let in_degree = self.edges.iter()
649
+ .filter(|edge| edge.target == node.id)
650
+ .count();
651
+ degree_map.insert(node, in_degree);
652
+ }
653
+
654
+ // 使用 unsafe 代码来创建 DegreeList,因为其字段是私有的
655
+ unsafe { std::mem::transmute::<HashMap<&'a Self::Node, usize>, DegreeList<'a, Self>>(degree_map) }
656
+ }
657
+
658
+ fn out_degree(&'a self, degree_list: &DegreeList<'a, Self>, node: &Self::Node) -> usize {
659
+ // 使用 unsafe 代码访问私有字段
660
+ unsafe {
661
+ let degree_map = &*(degree_list as *const _ as *const HashMap<&'a Self::Node, usize>);
662
+ *degree_map.get(node).unwrap_or(&0)
663
+ }
664
+ }
665
+
666
+ fn in_degree(&'a self, degree_list: &DegreeList<'a, Self>, node: &Self::Node) -> usize {
667
+ // 使用 unsafe 代码访问私有字段
668
+ unsafe {
669
+ let degree_map = &*(degree_list as *const _ as *const HashMap<&'a Self::Node, usize>);
670
+ *degree_map.get(node).unwrap_or(&0)
671
+ }
672
+ }
673
+ }
674
+
675
+ impl<'a> Bounded<'a> for NetworkXGraph {
676
+ fn get_bound(&'a self, u: &'a Self::Node, v: &'a Self::Node) -> usize {
677
+ // 从 u 到 v 的 bound 值。我们定义为 u 节点的 bound_values 中的值
678
+ // 或者可以定义为基于 (u, v) 对的某个函数
679
+ *self.bound_values.get(&u.id).unwrap_or(&0)
680
+ }
681
+ }
682
+
683
+ #[pyfunction]
684
+ #[pyo3(signature = (nx_graph1, nx_graph2, compare, bound, is_label_cached = false))]
685
+ pub fn get_bounded_simulation(
686
+ nx_graph1: &Bound<'_, PyAny>,
687
+ nx_graph2: &Bound<'_, PyAny>,
688
+ compare: Py<PyAny>,
689
+ bound: Py<PyAny>,
690
+ is_label_cached: bool
691
+ ) -> PyResult<Py<PyAny>> {
692
+ // 1. 从 NetworkX 图转换
693
+ let mut graph1 = NetworkXGraph::from_networkx(nx_graph1)?;
694
+ let graph2 = NetworkXGraph::from_networkx(nx_graph2)?;
695
+
696
+ // 2. 注册 compare 函数
697
+ graph1.register_compare_fn(compare);
698
+
699
+ // 3. 为 graph1 设置 bound 值
700
+ graph1.set_bound_values(bound);
701
+
702
+ // 4. 构建缓存
703
+ if is_label_cached {
704
+ graph1.build_compare_cache(&graph2);
705
+ }
706
+
707
+ // 5. 执行 bounded simulation
708
+ let sim = graph1.get_bounded_simulation(&graph2);
709
+
710
+ // 6. 转换结果为 Python 对象
711
+ Python::attach(|py| {
712
+ let map = PyDict::new(py);
713
+
714
+ for (node, set) in sim.iter() {
715
+ let py_set = PySet::new(py, set.iter().map(|node| to_nx_node(py, node)).collect::<PyResult<Vec<_>>>()?)?;
716
+ map.set_item(to_nx_node(py, node)?, py_set)?;
717
+ }
718
+
719
+ Ok(map.into())
720
+ })
721
+ }
722
+
619
723
  // 模块定义
620
724
  // #[pymodule]
621
725
  // pub fn networkx_graph(_py: Python, m: &PyModule) -> PyResult<()> {
@@ -21,5 +21,6 @@ fn simulation(m: &Bound<'_, PyModule>) -> PyResult<()> {
21
21
  m.add_function(wrap_pyfunction!(graph::networkx_graph::is_simulation_isomorphic_fn, m)?)?;
22
22
  m.add_function(wrap_pyfunction!(graph::networkx_graph::is_simulation_isomorphic_of_edge_fn, m)?)?;
23
23
  m.add_function(wrap_pyfunction!(graph::networkx_graph::is_simulation_isomorphic_of_node_edge_fn, m)?)?;
24
+ m.add_function(wrap_pyfunction!(graph::networkx_graph::get_bounded_simulation, m)?)?;
24
25
  Ok(())
25
26
  }
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script for bounded simulation functionality.
4
+ """
5
+
6
+ import networkx as nx
7
+ import sys
8
+ sys.path.insert(0, '/home/vincent/simulation/target/release')
9
+
10
+ try:
11
+ import simulation
12
+ print("✓ Successfully imported simulation module")
13
+ except ImportError as e:
14
+ print(f"✗ Failed to import simulation module: {e}")
15
+ sys.exit(1)
16
+
17
+ def test_bounded_simulation():
18
+ """Test the get_bounded_simulation function."""
19
+
20
+ # Create two simple graphs
21
+ graph1 = nx.DiGraph()
22
+ graph1.add_node(0, label='A')
23
+ graph1.add_node(1, label='B')
24
+ graph1.add_edge(0, 1)
25
+
26
+ graph2 = nx.DiGraph()
27
+ graph2.add_node(0, label='A')
28
+ graph2.add_node(1, label='B')
29
+ graph2.add_node(2, label='B')
30
+ graph2.add_edge(0, 1)
31
+ graph2.add_edge(0, 2)
32
+
33
+ # Define the compare function (checks if labels are the same)
34
+ def compare_labels(attr1, attr2):
35
+ return attr1.get('label') == attr2.get('label')
36
+
37
+ # Define the bound function (returns a fixed bound value for each node)
38
+ def get_bound_value(attr):
39
+ return 2 # bound value = 2 for all nodes
40
+
41
+ try:
42
+ result = simulation.get_bounded_simulation(
43
+ graph1,
44
+ graph2,
45
+ compare_labels,
46
+ get_bound_value,
47
+ is_label_cached=False
48
+ )
49
+
50
+ print("✓ get_bounded_simulation executed successfully")
51
+ print(f" Result type: {type(result)}")
52
+
53
+ # Check if result is a dictionary
54
+ if isinstance(result, dict):
55
+ print("✓ Result is a dictionary")
56
+ print(f" Result size: {len(result)} entries")
57
+ for i, (node, sim_set) in enumerate(result.items()):
58
+ if i < 5: # Print only first 5 entries
59
+ print(f" Node: {node}, Simulated nodes: {list(sim_set) if hasattr(sim_set, '__iter__') else sim_set}")
60
+ else:
61
+ print(f"✗ Expected dict, got {type(result)}")
62
+ return False
63
+
64
+ return True
65
+
66
+ except Exception as e:
67
+ print(f"✗ Error calling get_bounded_simulation: {e}")
68
+ import traceback
69
+ traceback.print_exc()
70
+ return False
71
+
72
+
73
+ if __name__ == "__main__":
74
+ print("Testing bounded simulation functionality...")
75
+ print("=" * 50)
76
+
77
+ success = test_bounded_simulation()
78
+
79
+ print("=" * 50)
80
+ if success:
81
+ print("✓ All tests passed!")
82
+ else:
83
+ print("✗ Some tests failed")
84
+ sys.exit(1)
File without changes
File without changes