-
Notifications
You must be signed in to change notification settings - Fork 195
Graph6 DiGaph6 Sparse6 support #1500
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 12 commits
ac1afb8
2a09e57
c6e5e7c
826a8e4
efab5f1
d6ed94f
f47a0ff
1f00fd1
d9a2c6b
42b158b
f8241fe
d6be2f2
ffd809d
a5c6a90
ed32f8a
5bd2a65
8a2ae09
84f202c
cd3d88f
6ecd347
886b893
aca2137
d8b13d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| features: | ||
| - | | ||
| This note documents the graph6 family of ASCII formats and the helpers | ||
| added to the codebase. The summary below is a concise description of the | ||
| formats (based on the canonical formats document) and a few developer | ||
| notes to help maintainers and tests. | ||
|
|
||
| references: | ||
| - https://users.cecs.anu.edu.au/~bdm/data/formats.txt | ||
| issue: | ||
| - https://github.com/Qiskit/rustworkx/issues/1496 | ||
|
|
||
| graph6 | ||
| - A compact ASCII-based encoding for simple undirected graphs. | ||
| - Encodes the graph order (n) using a variable-length size field and packs | ||
| the upper-triangular adjacency bits into 6-bit chunks. Each 6-bit value | ||
| is mapped into a printable ASCII character by adding an offset (so the | ||
| encoded bytes are printable ASCII). | ||
| - Typical uses: small-to-moderately-dense graphs where the adjacency | ||
| matrix can be packed efficiently. | ||
|
|
||
| digraph6 | ||
| - A variant of the graph6 scheme adapted for directed graphs. It encodes | ||
| a representation of the full adjacency matrix (row-major) using the | ||
| same 6-bit packing and ASCII mapping as graph6. | ||
| - Useful for representing directed graphs where directionality matters. | ||
|
|
||
| sparse6 | ||
| - An alternate encoding designed for very sparse graphs. Instead of | ||
| packing the full adjacency matrix, sparse6 records adjacency in a more | ||
| compact variable-length integer form (adjacency lists / runs), which | ||
| yields much smaller files for low edge-density graphs. | ||
|
|
||
| Developer notes | ||
| - Parsers must correctly handle variable-length size fields, 6-bit packing | ||
| and padding to 6-bit boundaries. Edge cases around very small and very | ||
| large n should be handled robustly. | ||
| - Tests should prefer roundtrip and structural checks (parse -> graph -> | ||
| serialize -> parse) and verify canonical encodings where the format | ||
| features: | ||
| - title: Add graph6/digraph6/sparse6 support and format summary | ||
| release: unreleased | ||
| notes: | | ||
| This note documents the graph6 family of ASCII formats and the helpers | ||
| added to the codebase. The summary below is a concise description of the | ||
| formats (based on the canonical formats document) and a few developer | ||
| notes to help maintainers and tests. | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,30 @@ | ||||||
| """digraph6 format helpers. | ||||||
|
||||||
| def write_graphml(graph, path, /, keys=None, compression=None): |
Line 1248 in 7318a80
| pub fn read_graphml<'py>( |
digraph_read_graph6 and graph_read_graph6There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed graph6.py, digraph6.py, sparse6.py and use a write_graph6 function in init with _rustworkx_dispatch
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| """graph6 format helpers. | ||
|
|
||
| This module provides a namespace for working with undirected graph6 strings | ||
| as described in: https://users.cecs.anu.edu.au/~bdm/data/formats.txt | ||
|
|
||
| It wraps the low-level functions exported from the compiled extension | ||
| (`read_graph6_str`, `write_graph6_from_pygraph`) and offers convenience | ||
| helpers. Backwards compatibility: existing top-level functions in | ||
| `rustworkx` remain valid; this is a thin façade only. | ||
| """ | ||
| from __future__ import annotations | ||
|
|
||
| from . import read_graph6_str as _read_graph6_str | ||
| from . import write_graph6_from_pygraph as _write_graph6_from_pygraph | ||
|
|
||
| __all__ = [ | ||
| "read_graph6_str", | ||
| "write_graph6_from_pygraph", | ||
| "read", | ||
| "write", | ||
| ] | ||
|
|
||
|
|
||
| def read_graph6_str(repr: str): | ||
| """Parse a graph6 representation into a PyGraph. | ||
|
|
||
| Accepts either raw graph6, header form (>>graph6<<:), or directed strings. | ||
| For clarity, use digraph6.read_graph6_str for directed graphs. This wrapper | ||
| leaves behavior unchanged (delegates to the core function) but documents | ||
| intent that this namespace targets undirected graphs. | ||
| """ | ||
| g = _read_graph6_str(repr) | ||
| return g | ||
|
|
||
| # Short aliases | ||
| read = read_graph6_str | ||
|
|
||
|
|
||
| def write_graph6_from_pygraph(graph): | ||
| """Serialize a PyGraph to a graph6 string.""" | ||
| return _write_graph6_from_pygraph(graph) | ||
|
|
||
| write = write_graph6_from_pygraph |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| """sparse6 format helpers (placeholder). | ||
|
|
||
| The sparse6 format is related to graph6/digraph6 but optimized for sparse | ||
| graphs. Parsing is currently not implemented in the Rust core; the Rust | ||
| layer returns an UnsupportedFormat error when an explicit sparse6 header is | ||
| encountered. | ||
|
|
||
| This module centralizes the placeholder so future implementation can add | ||
| real parsing while giving users a discoverable namespace today. | ||
| """ | ||
| from __future__ import annotations | ||
|
|
||
| from . import read_sparse6_str as _read_sparse6_str | ||
| from . import write_sparse6_from_pygraph as _write_sparse6_from_pygraph | ||
|
|
||
| __all__ = ["read_sparse6_str", "write_sparse6_from_pygraph"] | ||
|
|
||
|
|
||
| def read_sparse6_str(repr: str): | ||
| return _read_sparse6_str(repr) | ||
|
|
||
|
|
||
| def write_sparse6_from_pygraph(pygraph, header: bool = True): | ||
| return _write_sparse6_from_pygraph(pygraph, header) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| use crate::{get_edge_iter_with_weights, StablePyGraph}; | ||
| use crate::graph6::{utils, IOError, GraphConversion}; | ||
| use pyo3::prelude::*; | ||
| use pyo3::types::PyAny; | ||
| use petgraph::graph::NodeIndex; | ||
| use petgraph::algo; | ||
|
|
||
| /// Directed graph implementation (extracted from graph6.rs) | ||
| #[derive(Debug)] | ||
| pub struct DiGraph { | ||
hsunwenfang marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| pub bit_vec: Vec<usize>, | ||
| pub n: usize, | ||
| } | ||
| impl DiGraph { | ||
| /// Creates a new DiGraph from a graph6 representation string | ||
| pub fn from_d6(repr: &str) -> Result<Self, IOError> { | ||
| let bytes = repr.as_bytes(); | ||
| Self::valid_digraph(bytes)?; | ||
| let (n, n_len) = utils::parse_size(bytes, 1)?; | ||
| let Some(bit_vec) = Self::build_bitvector(bytes, n, 1 + n_len) else { | ||
| return Err(IOError::NonCanonicalEncoding); | ||
| }; | ||
| Ok(Self { bit_vec, n }) | ||
| } | ||
|
|
||
| /// Creates a new DiGraph from a flattened adjacency matrix | ||
| #[cfg(test)] | ||
|
||
| pub fn from_adj(adj: &[usize]) -> Result<Self, IOError> { | ||
| let n2 = adj.len(); | ||
| let n = (n2 as f64).sqrt() as usize; | ||
| if n * n != n2 { | ||
| return Err(IOError::InvalidAdjacencyMatrix); | ||
| } | ||
| let bit_vec = adj.to_vec(); | ||
| Ok(Self { bit_vec, n }) | ||
| } | ||
|
|
||
| /// Validates graph6 directed representation | ||
| pub(crate) fn valid_digraph(repr: &[u8]) -> Result<bool, IOError> { | ||
| if repr[0] == b'&' { | ||
| Ok(true) | ||
| } else { | ||
| Err(IOError::InvalidDigraphHeader) | ||
| } | ||
| } | ||
|
|
||
| /// Iteratores through the bytes and builds a bitvector | ||
| /// representing the adjaceny matrix of the graph | ||
| fn build_bitvector(bytes: &[u8], n: usize, offset: usize) -> Option<Vec<usize>> { | ||
| let bv_len = n * n; | ||
| utils::fill_bitvector(bytes, bv_len, offset) | ||
| } | ||
| } | ||
|
|
||
| impl GraphConversion for DiGraph { | ||
| fn bit_vec(&self) -> &[usize] { | ||
| &self.bit_vec | ||
| } | ||
|
|
||
| fn size(&self) -> usize { | ||
| self.n | ||
| } | ||
|
|
||
| fn is_directed(&self) -> bool { | ||
| true | ||
| } | ||
| } | ||
|
|
||
| /// Convert internal DiGraph to PyDiGraph | ||
| pub fn digraph_to_pydigraph<'py>(py: Python<'py>, g: &DiGraph) -> PyResult<Bound<'py, PyAny>> { | ||
| use crate::graph6::GraphConversion as _; | ||
| let mut graph = StablePyGraph::<petgraph::Directed>::with_capacity(g.size(), 0); | ||
| for _ in 0..g.size() { | ||
| graph.add_node(py.None()); | ||
| } | ||
| for i in 0..g.size() { | ||
| for j in 0..g.size() { | ||
| if g.bit_vec[i * g.size() + j] == 1 { | ||
| let u = NodeIndex::new(i); | ||
| let v = NodeIndex::new(j); | ||
| graph.add_edge(u, v, py.None()); | ||
| } | ||
| } | ||
| } | ||
| let out = crate::digraph::PyDiGraph { | ||
| graph, | ||
| cycle_state: algo::DfsSpace::default(), | ||
| check_cycle: false, | ||
| node_removed: false, | ||
| multigraph: true, | ||
| attrs: py.None(), | ||
| }; | ||
| Ok(out.into_pyobject(py)?.into_any()) | ||
| } | ||
|
|
||
| #[pyfunction] | ||
| #[pyo3(signature=(pydigraph))] | ||
| pub fn write_graph6_from_pydigraph(pydigraph: Py<crate::digraph::PyDiGraph>) -> PyResult<String> { | ||
| Python::with_gil(|py| { | ||
| let g = pydigraph.borrow(py); | ||
| let n = g.graph.node_count(); | ||
| let mut bit_vec = vec![0usize; n * n]; | ||
| for (i, j, _w) in get_edge_iter_with_weights(&g.graph) { | ||
| bit_vec[i * n + j] = 1; | ||
| } | ||
| let graph6 = crate::graph6::write::write_graph6(bit_vec, n, true); | ||
| Ok(graph6) | ||
| }) | ||
| } | ||
|
|
||
| #[pyfunction] | ||
| #[pyo3(signature=(digraph, path))] | ||
| pub fn digraph_write_graph6_file(digraph: Py<crate::digraph::PyDiGraph>, path: &str) -> PyResult<()> { | ||
|
||
| let s = write_graph6_from_pydigraph(digraph)?; | ||
| crate::graph6::to_file(path, &s) | ||
| .map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("IO error: {}", e)))?; | ||
| Ok(()) | ||
| } | ||
|
|
||
| // Enable write_graph() in tests for DiGraph via the WriteGraph trait | ||
|
||
| #[cfg(test)] | ||
| impl crate::graph6::write::WriteGraph for DiGraph {} | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please read https://docs.openstack.org/reno/latest/user/usage.html#editing-a-release-note for the correct format of release notes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
used reno to generate one with
nox -e docscompatible doc