visiter-native 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,180 @@
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.5.1"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
10
+
11
+ [[package]]
12
+ name = "cfg-if"
13
+ version = "1.0.4"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
16
+
17
+ [[package]]
18
+ name = "heck"
19
+ version = "0.5.0"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
22
+
23
+ [[package]]
24
+ name = "indoc"
25
+ version = "2.0.7"
26
+ source = "registry+https://github.com/rust-lang/crates.io-index"
27
+ checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
28
+ dependencies = [
29
+ "rustversion",
30
+ ]
31
+
32
+ [[package]]
33
+ name = "libc"
34
+ version = "0.2.186"
35
+ source = "registry+https://github.com/rust-lang/crates.io-index"
36
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
37
+
38
+ [[package]]
39
+ name = "memoffset"
40
+ version = "0.9.1"
41
+ source = "registry+https://github.com/rust-lang/crates.io-index"
42
+ checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
43
+ dependencies = [
44
+ "autocfg",
45
+ ]
46
+
47
+ [[package]]
48
+ name = "once_cell"
49
+ version = "1.21.4"
50
+ source = "registry+https://github.com/rust-lang/crates.io-index"
51
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
52
+
53
+ [[package]]
54
+ name = "portable-atomic"
55
+ version = "1.13.1"
56
+ source = "registry+https://github.com/rust-lang/crates.io-index"
57
+ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
58
+
59
+ [[package]]
60
+ name = "proc-macro2"
61
+ version = "1.0.106"
62
+ source = "registry+https://github.com/rust-lang/crates.io-index"
63
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
64
+ dependencies = [
65
+ "unicode-ident",
66
+ ]
67
+
68
+ [[package]]
69
+ name = "pyo3"
70
+ version = "0.23.5"
71
+ source = "registry+https://github.com/rust-lang/crates.io-index"
72
+ checksum = "7778bffd85cf38175ac1f545509665d0b9b92a198ca7941f131f85f7a4f9a872"
73
+ dependencies = [
74
+ "cfg-if",
75
+ "indoc",
76
+ "libc",
77
+ "memoffset",
78
+ "once_cell",
79
+ "portable-atomic",
80
+ "pyo3-build-config",
81
+ "pyo3-ffi",
82
+ "pyo3-macros",
83
+ "unindent",
84
+ ]
85
+
86
+ [[package]]
87
+ name = "pyo3-build-config"
88
+ version = "0.23.5"
89
+ source = "registry+https://github.com/rust-lang/crates.io-index"
90
+ checksum = "94f6cbe86ef3bf18998d9df6e0f3fc1050a8c5efa409bf712e661a4366e010fb"
91
+ dependencies = [
92
+ "once_cell",
93
+ "target-lexicon",
94
+ ]
95
+
96
+ [[package]]
97
+ name = "pyo3-ffi"
98
+ version = "0.23.5"
99
+ source = "registry+https://github.com/rust-lang/crates.io-index"
100
+ checksum = "e9f1b4c431c0bb1c8fb0a338709859eed0d030ff6daa34368d3b152a63dfdd8d"
101
+ dependencies = [
102
+ "libc",
103
+ "pyo3-build-config",
104
+ ]
105
+
106
+ [[package]]
107
+ name = "pyo3-macros"
108
+ version = "0.23.5"
109
+ source = "registry+https://github.com/rust-lang/crates.io-index"
110
+ checksum = "fbc2201328f63c4710f68abdf653c89d8dbc2858b88c5d88b0ff38a75288a9da"
111
+ dependencies = [
112
+ "proc-macro2",
113
+ "pyo3-macros-backend",
114
+ "quote",
115
+ "syn",
116
+ ]
117
+
118
+ [[package]]
119
+ name = "pyo3-macros-backend"
120
+ version = "0.23.5"
121
+ source = "registry+https://github.com/rust-lang/crates.io-index"
122
+ checksum = "fca6726ad0f3da9c9de093d6f116a93c1a38e417ed73bf138472cf4064f72028"
123
+ dependencies = [
124
+ "heck",
125
+ "proc-macro2",
126
+ "pyo3-build-config",
127
+ "quote",
128
+ "syn",
129
+ ]
130
+
131
+ [[package]]
132
+ name = "quote"
133
+ version = "1.0.45"
134
+ source = "registry+https://github.com/rust-lang/crates.io-index"
135
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
136
+ dependencies = [
137
+ "proc-macro2",
138
+ ]
139
+
140
+ [[package]]
141
+ name = "rustversion"
142
+ version = "1.0.22"
143
+ source = "registry+https://github.com/rust-lang/crates.io-index"
144
+ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
145
+
146
+ [[package]]
147
+ name = "syn"
148
+ version = "2.0.117"
149
+ source = "registry+https://github.com/rust-lang/crates.io-index"
150
+ checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
151
+ dependencies = [
152
+ "proc-macro2",
153
+ "quote",
154
+ "unicode-ident",
155
+ ]
156
+
157
+ [[package]]
158
+ name = "target-lexicon"
159
+ version = "0.12.16"
160
+ source = "registry+https://github.com/rust-lang/crates.io-index"
161
+ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
162
+
163
+ [[package]]
164
+ name = "unicode-ident"
165
+ version = "1.0.24"
166
+ source = "registry+https://github.com/rust-lang/crates.io-index"
167
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
168
+
169
+ [[package]]
170
+ name = "unindent"
171
+ version = "0.2.4"
172
+ source = "registry+https://github.com/rust-lang/crates.io-index"
173
+ checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
174
+
175
+ [[package]]
176
+ name = "visiter_native"
177
+ version = "0.1.0"
178
+ dependencies = [
179
+ "pyo3",
180
+ ]
@@ -0,0 +1,21 @@
1
+ [package]
2
+ name = "visiter_native"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ description = "Optional native BFS engine for visiter (Path A: Python callbacks via PyO3)."
6
+ license = "MIT"
7
+ readme = "README.md"
8
+
9
+ [lib]
10
+ name = "visiter_native"
11
+ crate-type = ["cdylib"]
12
+
13
+ [dependencies]
14
+ # abi3-py311: one stable-ABI wheel per platform works on CPython 3.11+,
15
+ # so distribution doesn't need a wheel per Python version.
16
+ pyo3 = { version = "0.23", features = ["abi3-py311"] }
17
+
18
+ [profile.release]
19
+ opt-level = 3
20
+ lto = "fat"
21
+ codegen-units = 1
@@ -0,0 +1,36 @@
1
+ Metadata-Version: 2.4
2
+ Name: visiter_native
3
+ Version: 0.1.0
4
+ Classifier: License :: OSI Approved :: MIT License
5
+ Classifier: Programming Language :: Python :: 3 :: Only
6
+ Classifier: Programming Language :: Rust
7
+ Classifier: Topic :: Scientific/Engineering :: Visualization
8
+ Summary: Optional native BFS engine for visiter (engine='native').
9
+ Keywords: visiter,graph,bfs,native,pyo3
10
+ Author-email: Jakob Stemberger <jakob.stemberger@gmail.com>
11
+ License: MIT
12
+ Requires-Python: >=3.11
13
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
14
+ Project-URL: Homepage, https://github.com/yaccob/visiter
15
+ Project-URL: Issues, https://github.com/yaccob/visiter/issues
16
+
17
+ # visiter_native
18
+
19
+ The optional native BFS engine for [visiter](https://pypi.org/project/visiter/)
20
+ (Path A): a PyO3 extension that runs visiter's graph build natively while keeping
21
+ your Python callbacks.
22
+
23
+ You normally don't install this directly — use the extra:
24
+
25
+ ```bash
26
+ pip install "visiter[native]"
27
+ ```
28
+
29
+ Once `visiter_native` is importable, `viter(...).build()` uses it automatically
30
+ for unbounded builds (`engine="auto"`, the default) and produces a graph
31
+ byte-identical to the pure-Python build. visiter works without it — pure Python
32
+ is the always-available baseline.
33
+
34
+ Distributed as `abi3` wheels (one per platform, CPython 3.11+). MIT licensed.
35
+ See the [visiter manual](https://github.com/yaccob/visiter/blob/main/docs/manual.md#8-optional-native-acceleration-and-columnar-storage).
36
+
@@ -0,0 +1,19 @@
1
+ # visiter_native
2
+
3
+ The optional native BFS engine for [visiter](https://pypi.org/project/visiter/)
4
+ (Path A): a PyO3 extension that runs visiter's graph build natively while keeping
5
+ your Python callbacks.
6
+
7
+ You normally don't install this directly — use the extra:
8
+
9
+ ```bash
10
+ pip install "visiter[native]"
11
+ ```
12
+
13
+ Once `visiter_native` is importable, `viter(...).build()` uses it automatically
14
+ for unbounded builds (`engine="auto"`, the default) and produces a graph
15
+ byte-identical to the pure-Python build. visiter works without it — pure Python
16
+ is the always-available baseline.
17
+
18
+ Distributed as `abi3` wheels (one per platform, CPython 3.11+). MIT licensed.
19
+ See the [visiter manual](https://github.com/yaccob/visiter/blob/main/docs/manual.md#8-optional-native-acceleration-and-columnar-storage).
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["maturin>=1.0,<2.0"]
3
+ build-backend = "maturin"
4
+
5
+ [project]
6
+ name = "visiter_native"
7
+ version = "0.1.0"
8
+ description = "Optional native BFS engine for visiter (engine='native')."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [{ name = "Jakob Stemberger", email = "jakob.stemberger@gmail.com" }]
12
+ requires-python = ">=3.11"
13
+ keywords = ["visiter", "graph", "bfs", "native", "pyo3"]
14
+ classifiers = [
15
+ "License :: OSI Approved :: MIT License",
16
+ "Programming Language :: Python :: 3 :: Only",
17
+ "Programming Language :: Rust",
18
+ "Topic :: Scientific/Engineering :: Visualization",
19
+ ]
20
+
21
+ [project.urls]
22
+ Homepage = "https://github.com/yaccob/visiter"
23
+ Issues = "https://github.com/yaccob/visiter/issues"
24
+
25
+ [tool.maturin]
26
+ features = ["pyo3/extension-module"]
@@ -0,0 +1,149 @@
1
+ //! Optional native BFS engine for visiter (Path A / ①).
2
+ //!
3
+ //! `build_raw` mirrors the pure-Python `visiter.iteration.build` BFS for the
4
+ //! **unbounded** subset (no max_depth / max_nodes / time_limit / bound). It
5
+ //! calls the Python condition/op callables per node, so the callbacks stay
6
+ //! Python — only the BFS expansion, deduplication and interning run natively.
7
+ //!
8
+ //! It returns the raw discovered structure (node objects in BFS order, their
9
+ //! depths, and deduplicated edges as `(from_idx, to_idx, op_idx, label?)`).
10
+ //! The Python shim assembles the final Graph dict (string keys, key_type,
11
+ //! tags, op labels) from this, so the output is byte-identical to the
12
+ //! pure-Python build for the supported subset.
13
+ //!
14
+ //! Node order, edge order, depth assignment and dedup semantics are kept
15
+ //! faithful to the Python loop so a Graph-level parity test passes exactly.
16
+
17
+ use pyo3::prelude::*;
18
+ use std::collections::{HashMap, HashSet};
19
+
20
+ /// A Python object used as a node key: hashed by the value's Python `hash()`
21
+ /// (computed once), equality resolved by Python `==` only on hash collision.
22
+ struct Key {
23
+ obj: Py<PyAny>,
24
+ hash: isize,
25
+ }
26
+
27
+ impl std::hash::Hash for Key {
28
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
29
+ state.write_isize(self.hash);
30
+ }
31
+ }
32
+
33
+ impl PartialEq for Key {
34
+ fn eq(&self, other: &Self) -> bool {
35
+ if self.hash != other.hash {
36
+ return false;
37
+ }
38
+ Python::with_gil(|py| {
39
+ self.obj.bind(py).eq(other.obj.bind(py)).unwrap_or(false)
40
+ })
41
+ }
42
+ }
43
+ impl Eq for Key {}
44
+
45
+ type Edge = (u32, u32, usize, Option<String>);
46
+
47
+ /// Run the unbounded BFS natively. `op_result_type` is the Python `OpResult`
48
+ /// class, used to detect per-call label overrides.
49
+ #[pyfunction]
50
+ #[pyo3(signature = (starts, conditions, ops, exclusive, default_op, op_result_type))]
51
+ fn build_raw(
52
+ py: Python<'_>,
53
+ starts: Vec<Py<PyAny>>,
54
+ conditions: Vec<Py<PyAny>>,
55
+ ops: Vec<Py<PyAny>>,
56
+ exclusive: Vec<bool>,
57
+ default_op: Option<Py<PyAny>>,
58
+ op_result_type: Py<PyAny>,
59
+ ) -> PyResult<(Vec<Py<PyAny>>, Vec<u32>, Vec<Edge>)> {
60
+ let n_rules = conditions.len();
61
+ let default_idx = n_rules; // op index reserved for the default op
62
+ let ort = op_result_type.bind(py);
63
+
64
+ let mut id_of: HashMap<Key, u32> = HashMap::new();
65
+ let mut values: Vec<Py<PyAny>> = Vec::new();
66
+ let mut depths: Vec<u32> = Vec::new();
67
+ let mut edges: Vec<Edge> = Vec::new();
68
+ let mut seen_edges: HashSet<(u32, u32)> = HashSet::new();
69
+
70
+ // `fire` is a macro (not a fn) so it can mutate the locals in place without
71
+ // fighting the borrow checker over a dozen &mut parameters.
72
+ macro_rules! fire {
73
+ ($xid:expr, $xb:expr, $op_idx:expr, $op_func:expr, $next:expr) => {{
74
+ let result = $op_func.bind(py).call1(($xb,))?;
75
+ let (nv, label): (Py<PyAny>, Option<String>) =
76
+ if result.is_instance(ort)? {
77
+ let v = result.getattr("value")?.unbind();
78
+ let l: Option<String> = result.getattr("label")?.extract()?;
79
+ (v, l)
80
+ } else {
81
+ (result.unbind(), None)
82
+ };
83
+ let h = nv.bind(py).hash()?;
84
+ let key = Key { obj: nv.clone_ref(py), hash: h };
85
+ let (nid, is_new) = match id_of.get(&key) {
86
+ Some(&i) => (i, false),
87
+ None => {
88
+ let i = values.len() as u32;
89
+ values.push(nv);
90
+ depths.push(depths[$xid as usize] + 1);
91
+ id_of.insert(key, i);
92
+ (i, true)
93
+ }
94
+ };
95
+ if seen_edges.insert(($xid, nid)) {
96
+ edges.push(($xid, nid, $op_idx, label));
97
+ }
98
+ if is_new {
99
+ $next.push(nid);
100
+ }
101
+ }};
102
+ }
103
+
104
+ let mut frontier: Vec<u32> = Vec::new();
105
+ for s in starts {
106
+ let h = s.bind(py).hash()?;
107
+ let key = Key { obj: s.clone_ref(py), hash: h };
108
+ if !id_of.contains_key(&key) {
109
+ let id = values.len() as u32;
110
+ values.push(s);
111
+ depths.push(0);
112
+ id_of.insert(key, id);
113
+ frontier.push(id);
114
+ }
115
+ }
116
+
117
+ while !frontier.is_empty() {
118
+ let mut next: Vec<u32> = Vec::new();
119
+ for &xid in &frontier {
120
+ let x = values[xid as usize].clone_ref(py);
121
+ let xb = x.bind(py);
122
+ let mut any_matched = false;
123
+ for ri in 0..n_rules {
124
+ if conditions[ri].bind(py).call1((xb,))?.is_truthy()? {
125
+ any_matched = true;
126
+ fire!(xid, xb, ri, ops[ri], next);
127
+ if exclusive[ri] {
128
+ break;
129
+ }
130
+ }
131
+ }
132
+ if !any_matched {
133
+ if let Some(ref dop) = default_op {
134
+ fire!(xid, xb, default_idx, dop, next);
135
+ }
136
+ }
137
+ }
138
+ frontier = next;
139
+ }
140
+
141
+ Ok((values, depths, edges))
142
+ }
143
+
144
+ #[pymodule]
145
+ fn visiter_native(m: &Bound<'_, PyModule>) -> PyResult<()> {
146
+ m.add_function(wrap_pyfunction!(build_raw, m)?)?;
147
+ m.add("__version__", "0.0.0")?;
148
+ Ok(())
149
+ }