Repository files navigation

spatial-graph

LicensePyPIPython VersionCIcodecovCodSpeed

spatial_graph provides a data structure for directed and undirected graphs, where each node has an nD position (in time or space).

Design Principles

Goals

  • support for arbitrary number of dimensions
  • typed node identifiers and attributes
    • any fixed-length type that is supported by numpy
  • efficient node/edge queries by
    • ROI
    • kNN (by points / lines)
  • numpy-like interface for efficient:
    • graph population and manipulation
    • query results
    • attribute access
  • minimal memory footprint
  • minimal dependencies
    • cython / witty / cheetah3, used only when something has to be compiled at runtime (see Cross-Platform Support)
    • numpy for array interfaces
  • PYX API for graph algorithms in C/C++

Non-Goals

  • graph algorithms
  • I/O
  • non-typed arguments
  • non-spatial graphs
  • out-of-memory support
  • networkx compatibility

Python API

Graph creation:

graph=sg.SpatialGraph(
ndims=3,
node_dtype="uint64",
node_attr_dtypes={"position": "double[3]"},
edge_attr_dtypes={"score": "float32"},
position_attr="position",
)

Adding nodes/edges:

graph.add_nodes(
np.array([1, 2, 3, 4, 5], dtype="uint64"),
position=np.array(
[
[0.1, 0.1, 0.1],
[0.2, 0.2, 0.2],
[0.3, 0.3, 0.3],
[0.4, 0.4, 0.4],
[0.5, 0.5, 0.5],
],
dtype="double",
),
)
graph.add_edges(
np.array([[1, 2], [3, 4], [5, 1]], dtype="uint64"),
score=np.array([0.2, 0.3, 0.4], dtype="float32"),
)

Query nodes/edges in ROI:

# nodes/edges will be numpy arrays of dtype uint64 and shape (n,)/(n, 2)nodes=graph.query_nodes_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))
edges=graph.query_edges_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))

Query nodes/edges by position:

nodes=graph.query_nearest_nodes(np.array([0.3, 0.3, 0.3]), k=3)
edges=graph.query_nearest_edges(np.array([0.3, 0.3, 0.3]), k=3)

Access node/edge attributes:

node_positions=graph.node_attrs[nodes].positionedge_scores=graph.edge_attrs[edges].score

Delete nodes/edges:

graph.remove_nodes(nodes[:1000])

Implementation Details

A SpatialGraph consists of three data structures:

  • The Graph itself, holding nodes, edges, and their attributes (graphlite).
  • Two R-trees for spatial node and edge queries (based on rtree.c). We modified the original code to also include a fast kNN search.

Cross-Platform Support

spatial_graph generates specialized C/C++ for the exact data types you ask for. Where those types can be known in advance we compile them ahead of time and ship them in the wheels; everything else is compiled on your machine the first time it is used, which needs a C compiler.

No compiler needed. The PyPI wheels contain prebuilt PointRTree and LineRTree variants for the common combinations: float32/float64 coordinates, 2 to 5 dimensions, and int64/uint64 items -- as int64[2] / uint64[2] for LineRTree, whose items are node pairs. If your R-tree matches one of those -- as most do -- nothing is compiled, on any supported Python.

Compiler needed. Two cases fall back to compiling at runtime:

  1. Graph, DiGraph, SpatialGraph and SpatialDiGraph. Their node and edge attribute types are only known when you construct the graph, so they cannot be enumerated ahead of time.
  2. R-trees outside the prebuilt set above (an int32 item type, say, or 6 dimensions).

If you or your users need those without a compiler, you can still install spatial_graph from conda-forge, where we include a compiler (clang) in its dependencies.

The wheels are abi3 (stable ABI) and require Python 3.11 or newer, so one wheel per platform covers every supported CPython. Python 3.10 users should pin to a release before this one.

Why can't everything be prebuilt?

There is no cross-platform C/C++ compiler that we can install using pip. numba is maybe the closest to having solved that problem: numba does compile during runtime even if you don't have a compiler locally installed. This works because numba is generating LLVM IR, an intermediate representation language that LLVM can compile into machine code. numba depends on llvmlite, which provides a subset of the LLVM API, statically linked into the binaries in that package. This is just enough to compile the numba generated LLVM IR into machine code. We can't use this strategy, because we compile general C/C++ code. Converting that into LLVM IR is exactly what we need a compiler for.

For Developers

To create a new release, tag the current commit with a version number and push it to the upstream remote:

git tag -a "vX.Y.Z" -m "vX.Y.Z"
git push upstream --follow-tags

This will trigger the CI workflow, which will build the package and upload it to PyPI.

Testing in a conda environment

To simulate a naive user environment, with no assumptions made about the availability of a C/C++ compiler, you can run the included Dockerfile (where the key part of the conda env is the compilers package):

docker build -t spatial_graph .
docker run --rm spatial_graph

About

A spatial graph datastructure for python

Resources

Stars

11 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

spatial-graph

LicensePyPIPython VersionCIcodecovCodSpeed

spatial_graph provides a data structure for directed and undirected graphs, where each node has an nD position (in time or space).

Design Principles

Goals

  • support for arbitrary number of dimensions
  • typed node identifiers and attributes
    • any fixed-length type that is supported by numpy
  • efficient node/edge queries by
    • ROI
    • kNN (by points / lines)
  • numpy-like interface for efficient:
    • graph population and manipulation
    • query results
    • attribute access
  • minimal memory footprint
  • minimal dependencies
    • cython / witty / cheetah3, used only when something has to be compiled at runtime (see Cross-Platform Support)
    • numpy for array interfaces
  • PYX API for graph algorithms in C/C++

Non-Goals

  • graph algorithms
  • I/O
  • non-typed arguments
  • non-spatial graphs
  • out-of-memory support
  • networkx compatibility

Python API

Graph creation:

graph=sg.SpatialGraph(
ndims=3,
node_dtype="uint64",
node_attr_dtypes={"position": "double[3]"},
edge_attr_dtypes={"score": "float32"},
position_attr="position",
)

Adding nodes/edges:

graph.add_nodes(
np.array([1, 2, 3, 4, 5], dtype="uint64"),
position=np.array(
[
[0.1, 0.1, 0.1],
[0.2, 0.2, 0.2],
[0.3, 0.3, 0.3],
[0.4, 0.4, 0.4],
[0.5, 0.5, 0.5],
],
dtype="double",
),
)
graph.add_edges(
np.array([[1, 2], [3, 4], [5, 1]], dtype="uint64"),
score=np.array([0.2, 0.3, 0.4], dtype="float32"),
)

Query nodes/edges in ROI:

# nodes/edges will be numpy arrays of dtype uint64 and shape (n,)/(n, 2)nodes=graph.query_nodes_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))
edges=graph.query_edges_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))

Query nodes/edges by position:

nodes=graph.query_nearest_nodes(np.array([0.3, 0.3, 0.3]), k=3)
edges=graph.query_nearest_edges(np.array([0.3, 0.3, 0.3]), k=3)

Access node/edge attributes:

node_positions=graph.node_attrs[nodes].positionedge_scores=graph.edge_attrs[edges].score

Delete nodes/edges:

graph.remove_nodes(nodes[:1000])

Implementation Details

A SpatialGraph consists of three data structures:

  • The Graph itself, holding nodes, edges, and their attributes (graphlite).
  • Two R-trees for spatial node and edge queries (based on rtree.c). We modified the original code to also include a fast kNN search.

Cross-Platform Support

spatial_graph generates specialized C/C++ for the exact data types you ask for. Where those types can be known in advance we compile them ahead of time and ship them in the wheels; everything else is compiled on your machine the first time it is used, which needs a C compiler.

No compiler needed. The PyPI wheels contain prebuilt PointRTree and LineRTree variants for the common combinations: float32/float64 coordinates, 2 to 5 dimensions, and int64/uint64 items -- as int64[2] / uint64[2] for LineRTree, whose items are node pairs. If your R-tree matches one of those -- as most do -- nothing is compiled, on any supported Python.

Compiler needed. Two cases fall back to compiling at runtime:

  1. Graph, DiGraph, SpatialGraph and SpatialDiGraph. Their node and edge attribute types are only known when you construct the graph, so they cannot be enumerated ahead of time.
  2. R-trees outside the prebuilt set above (an int32 item type, say, or 6 dimensions).

If you or your users need those without a compiler, you can still install spatial_graph from conda-forge, where we include a compiler (clang) in its dependencies.

The wheels are abi3 (stable ABI) and require Python 3.11 or newer, so one wheel per platform covers every supported CPython. Python 3.10 users should pin to a release before this one.

Why can't everything be prebuilt?

There is no cross-platform C/C++ compiler that we can install using pip. numba is maybe the closest to having solved that problem: numba does compile during runtime even if you don't have a compiler locally installed. This works because numba is generating LLVM IR, an intermediate representation language that LLVM can compile into machine code. numba depends on llvmlite, which provides a subset of the LLVM API, statically linked into the binaries in that package. This is just enough to compile the numba generated LLVM IR into machine code. We can't use this strategy, because we compile general C/C++ code. Converting that into LLVM IR is exactly what we need a compiler for.

For Developers

To create a new release, tag the current commit with a version number and push it to the upstream remote:

git tag -a "vX.Y.Z" -m "vX.Y.Z"
git push upstream --follow-tags

This will trigger the CI workflow, which will build the package and upload it to PyPI.

Testing in a conda environment

To simulate a naive user environment, with no assumptions made about the availability of a C/C++ compiler, you can run the included Dockerfile (where the key part of the conda env is the compilers package):

docker build -t spatial_graph .
docker run --rm spatial_graph

About

A spatial graph datastructure for python

Resources

Stars

11 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

spatial-graph

LicensePyPIPython VersionCIcodecovCodSpeed

spatial_graph provides a data structure for directed and undirected graphs, where each node has an nD position (in time or space).

Design Principles

Goals

  • support for arbitrary number of dimensions
  • typed node identifiers and attributes
    • any fixed-length type that is supported by numpy
  • efficient node/edge queries by
    • ROI
    • kNN (by points / lines)
  • numpy-like interface for efficient:
    • graph population and manipulation
    • query results
    • attribute access
  • minimal memory footprint
  • minimal dependencies
    • cython / witty / cheetah3, used only when something has to be compiled at runtime (see Cross-Platform Support)
    • numpy for array interfaces
  • PYX API for graph algorithms in C/C++

Non-Goals

  • graph algorithms
  • I/O
  • non-typed arguments
  • non-spatial graphs
  • out-of-memory support
  • networkx compatibility

Python API

Graph creation:

graph=sg.SpatialGraph(
ndims=3,
node_dtype="uint64",
node_attr_dtypes={"position": "double[3]"},
edge_attr_dtypes={"score": "float32"},
position_attr="position",
)

Adding nodes/edges:

graph.add_nodes(
np.array([1, 2, 3, 4, 5], dtype="uint64"),
position=np.array(
[
[0.1, 0.1, 0.1],
[0.2, 0.2, 0.2],
[0.3, 0.3, 0.3],
[0.4, 0.4, 0.4],
[0.5, 0.5, 0.5],
],
dtype="double",
),
)
graph.add_edges(
np.array([[1, 2], [3, 4], [5, 1]], dtype="uint64"),
score=np.array([0.2, 0.3, 0.4], dtype="float32"),
)

Query nodes/edges in ROI:

# nodes/edges will be numpy arrays of dtype uint64 and shape (n,)/(n, 2)nodes=graph.query_nodes_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))
edges=graph.query_edges_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))

Query nodes/edges by position:

nodes=graph.query_nearest_nodes(np.array([0.3, 0.3, 0.3]), k=3)
edges=graph.query_nearest_edges(np.array([0.3, 0.3, 0.3]), k=3)

Access node/edge attributes:

node_positions=graph.node_attrs[nodes].positionedge_scores=graph.edge_attrs[edges].score

Delete nodes/edges:

graph.remove_nodes(nodes[:1000])

Implementation Details

A SpatialGraph consists of three data structures:

  • The Graph itself, holding nodes, edges, and their attributes (graphlite).
  • Two R-trees for spatial node and edge queries (based on rtree.c). We modified the original code to also include a fast kNN search.

Cross-Platform Support

spatial_graph generates specialized C/C++ for the exact data types you ask for. Where those types can be known in advance we compile them ahead of time and ship them in the wheels; everything else is compiled on your machine the first time it is used, which needs a C compiler.

No compiler needed. The PyPI wheels contain prebuilt PointRTree and LineRTree variants for the common combinations: float32/float64 coordinates, 2 to 5 dimensions, and int64/uint64 items -- as int64[2] / uint64[2] for LineRTree, whose items are node pairs. If your R-tree matches one of those -- as most do -- nothing is compiled, on any supported Python.

Compiler needed. Two cases fall back to compiling at runtime:

  1. Graph, DiGraph, SpatialGraph and SpatialDiGraph. Their node and edge attribute types are only known when you construct the graph, so they cannot be enumerated ahead of time.
  2. R-trees outside the prebuilt set above (an int32 item type, say, or 6 dimensions).

If you or your users need those without a compiler, you can still install spatial_graph from conda-forge, where we include a compiler (clang) in its dependencies.

The wheels are abi3 (stable ABI) and require Python 3.11 or newer, so one wheel per platform covers every supported CPython. Python 3.10 users should pin to a release before this one.

Why can't everything be prebuilt?

There is no cross-platform C/C++ compiler that we can install using pip. numba is maybe the closest to having solved that problem: numba does compile during runtime even if you don't have a compiler locally installed. This works because numba is generating LLVM IR, an intermediate representation language that LLVM can compile into machine code. numba depends on llvmlite, which provides a subset of the LLVM API, statically linked into the binaries in that package. This is just enough to compile the numba generated LLVM IR into machine code. We can't use this strategy, because we compile general C/C++ code. Converting that into LLVM IR is exactly what we need a compiler for.

For Developers

To create a new release, tag the current commit with a version number and push it to the upstream remote:

git tag -a "vX.Y.Z" -m "vX.Y.Z"
git push upstream --follow-tags

This will trigger the CI workflow, which will build the package and upload it to PyPI.

Testing in a conda environment

To simulate a naive user environment, with no assumptions made about the availability of a C/C++ compiler, you can run the included Dockerfile (where the key part of the conda env is the compilers package):

docker build -t spatial_graph .
docker run --rm spatial_graph

About

A spatial graph datastructure for python

Resources

Stars

11 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

spatial-graph

LicensePyPIPython VersionCIcodecovCodSpeed

spatial_graph provides a data structure for directed and undirected graphs, where each node has an nD position (in time or space).

Design Principles

Goals

  • support for arbitrary number of dimensions
  • typed node identifiers and attributes
    • any fixed-length type that is supported by numpy
  • efficient node/edge queries by
    • ROI
    • kNN (by points / lines)
  • numpy-like interface for efficient:
    • graph population and manipulation
    • query results
    • attribute access
  • minimal memory footprint
  • minimal dependencies
    • cython / witty / cheetah3, used only when something has to be compiled at runtime (see Cross-Platform Support)
    • numpy for array interfaces
  • PYX API for graph algorithms in C/C++

Non-Goals

  • graph algorithms
  • I/O
  • non-typed arguments
  • non-spatial graphs
  • out-of-memory support
  • networkx compatibility

Python API

Graph creation:

graph=sg.SpatialGraph(
ndims=3,
node_dtype="uint64",
node_attr_dtypes={"position": "double[3]"},
edge_attr_dtypes={"score": "float32"},
position_attr="position",
)

Adding nodes/edges:

graph.add_nodes(
np.array([1, 2, 3, 4, 5], dtype="uint64"),
position=np.array(
[
[0.1, 0.1, 0.1],
[0.2, 0.2, 0.2],
[0.3, 0.3, 0.3],
[0.4, 0.4, 0.4],
[0.5, 0.5, 0.5],
],
dtype="double",
),
)
graph.add_edges(
np.array([[1, 2], [3, 4], [5, 1]], dtype="uint64"),
score=np.array([0.2, 0.3, 0.4], dtype="float32"),
)

Query nodes/edges in ROI:

# nodes/edges will be numpy arrays of dtype uint64 and shape (n,)/(n, 2)nodes=graph.query_nodes_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))
edges=graph.query_edges_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))

Query nodes/edges by position:

nodes=graph.query_nearest_nodes(np.array([0.3, 0.3, 0.3]), k=3)
edges=graph.query_nearest_edges(np.array([0.3, 0.3, 0.3]), k=3)

Access node/edge attributes:

node_positions=graph.node_attrs[nodes].positionedge_scores=graph.edge_attrs[edges].score

Delete nodes/edges:

graph.remove_nodes(nodes[:1000])

Implementation Details

A SpatialGraph consists of three data structures:

  • The Graph itself, holding nodes, edges, and their attributes (graphlite).
  • Two R-trees for spatial node and edge queries (based on rtree.c). We modified the original code to also include a fast kNN search.

Cross-Platform Support

spatial_graph generates specialized C/C++ for the exact data types you ask for. Where those types can be known in advance we compile them ahead of time and ship them in the wheels; everything else is compiled on your machine the first time it is used, which needs a C compiler.

No compiler needed. The PyPI wheels contain prebuilt PointRTree and LineRTree variants for the common combinations: float32/float64 coordinates, 2 to 5 dimensions, and int64/uint64 items -- as int64[2] / uint64[2] for LineRTree, whose items are node pairs. If your R-tree matches one of those -- as most do -- nothing is compiled, on any supported Python.

Compiler needed. Two cases fall back to compiling at runtime:

  1. Graph, DiGraph, SpatialGraph and SpatialDiGraph. Their node and edge attribute types are only known when you construct the graph, so they cannot be enumerated ahead of time.
  2. R-trees outside the prebuilt set above (an int32 item type, say, or 6 dimensions).

If you or your users need those without a compiler, you can still install spatial_graph from conda-forge, where we include a compiler (clang) in its dependencies.

The wheels are abi3 (stable ABI) and require Python 3.11 or newer, so one wheel per platform covers every supported CPython. Python 3.10 users should pin to a release before this one.

Why can't everything be prebuilt?

There is no cross-platform C/C++ compiler that we can install using pip. numba is maybe the closest to having solved that problem: numba does compile during runtime even if you don't have a compiler locally installed. This works because numba is generating LLVM IR, an intermediate representation language that LLVM can compile into machine code. numba depends on llvmlite, which provides a subset of the LLVM API, statically linked into the binaries in that package. This is just enough to compile the numba generated LLVM IR into machine code. We can't use this strategy, because we compile general C/C++ code. Converting that into LLVM IR is exactly what we need a compiler for.

For Developers

To create a new release, tag the current commit with a version number and push it to the upstream remote:

git tag -a "vX.Y.Z" -m "vX.Y.Z"
git push upstream --follow-tags

This will trigger the CI workflow, which will build the package and upload it to PyPI.

Testing in a conda environment

To simulate a naive user environment, with no assumptions made about the availability of a C/C++ compiler, you can run the included Dockerfile (where the key part of the conda env is the compilers package):

docker build -t spatial_graph .
docker run --rm spatial_graph

About

A spatial graph datastructure for python

Resources

Stars

11 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

spatial-graph

LicensePyPIPython VersionCIcodecovCodSpeed

spatial_graph provides a data structure for directed and undirected graphs, where each node has an nD position (in time or space).

Design Principles

Goals

  • support for arbitrary number of dimensions
  • typed node identifiers and attributes
    • any fixed-length type that is supported by numpy
  • efficient node/edge queries by
    • ROI
    • kNN (by points / lines)
  • numpy-like interface for efficient:
    • graph population and manipulation
    • query results
    • attribute access
  • minimal memory footprint
  • minimal dependencies
    • cython / witty / cheetah3, used only when something has to be compiled at runtime (see Cross-Platform Support)
    • numpy for array interfaces
  • PYX API for graph algorithms in C/C++

Non-Goals

  • graph algorithms
  • I/O
  • non-typed arguments
  • non-spatial graphs
  • out-of-memory support
  • networkx compatibility

Python API

Graph creation:

graph=sg.SpatialGraph(
ndims=3,
node_dtype="uint64",
node_attr_dtypes={"position": "double[3]"},
edge_attr_dtypes={"score": "float32"},
position_attr="position",
)

Adding nodes/edges:

graph.add_nodes(
np.array([1, 2, 3, 4, 5], dtype="uint64"),
position=np.array(
[
[0.1, 0.1, 0.1],
[0.2, 0.2, 0.2],
[0.3, 0.3, 0.3],
[0.4, 0.4, 0.4],
[0.5, 0.5, 0.5],
],
dtype="double",
),
)
graph.add_edges(
np.array([[1, 2], [3, 4], [5, 1]], dtype="uint64"),
score=np.array([0.2, 0.3, 0.4], dtype="float32"),
)

Query nodes/edges in ROI:

# nodes/edges will be numpy arrays of dtype uint64 and shape (n,)/(n, 2)nodes=graph.query_nodes_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))
edges=graph.query_edges_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))

Query nodes/edges by position:

nodes=graph.query_nearest_nodes(np.array([0.3, 0.3, 0.3]), k=3)
edges=graph.query_nearest_edges(np.array([0.3, 0.3, 0.3]), k=3)

Access node/edge attributes:

node_positions=graph.node_attrs[nodes].positionedge_scores=graph.edge_attrs[edges].score

Delete nodes/edges:

graph.remove_nodes(nodes[:1000])

Implementation Details

A SpatialGraph consists of three data structures:

  • The Graph itself, holding nodes, edges, and their attributes (graphlite).
  • Two R-trees for spatial node and edge queries (based on rtree.c). We modified the original code to also include a fast kNN search.

Cross-Platform Support

spatial_graph generates specialized C/C++ for the exact data types you ask for. Where those types can be known in advance we compile them ahead of time and ship them in the wheels; everything else is compiled on your machine the first time it is used, which needs a C compiler.

No compiler needed. The PyPI wheels contain prebuilt PointRTree and LineRTree variants for the common combinations: float32/float64 coordinates, 2 to 5 dimensions, and int64/uint64 items -- as int64[2] / uint64[2] for LineRTree, whose items are node pairs. If your R-tree matches one of those -- as most do -- nothing is compiled, on any supported Python.

Compiler needed. Two cases fall back to compiling at runtime:

  1. Graph, DiGraph, SpatialGraph and SpatialDiGraph. Their node and edge attribute types are only known when you construct the graph, so they cannot be enumerated ahead of time.
  2. R-trees outside the prebuilt set above (an int32 item type, say, or 6 dimensions).

If you or your users need those without a compiler, you can still install spatial_graph from conda-forge, where we include a compiler (clang) in its dependencies.

The wheels are abi3 (stable ABI) and require Python 3.11 or newer, so one wheel per platform covers every supported CPython. Python 3.10 users should pin to a release before this one.

Why can't everything be prebuilt?

There is no cross-platform C/C++ compiler that we can install using pip. numba is maybe the closest to having solved that problem: numba does compile during runtime even if you don't have a compiler locally installed. This works because numba is generating LLVM IR, an intermediate representation language that LLVM can compile into machine code. numba depends on llvmlite, which provides a subset of the LLVM API, statically linked into the binaries in that package. This is just enough to compile the numba generated LLVM IR into machine code. We can't use this strategy, because we compile general C/C++ code. Converting that into LLVM IR is exactly what we need a compiler for.

For Developers

To create a new release, tag the current commit with a version number and push it to the upstream remote:

git tag -a "vX.Y.Z" -m "vX.Y.Z"
git push upstream --follow-tags

This will trigger the CI workflow, which will build the package and upload it to PyPI.

Testing in a conda environment

To simulate a naive user environment, with no assumptions made about the availability of a C/C++ compiler, you can run the included Dockerfile (where the key part of the conda env is the compilers package):

docker build -t spatial_graph .
docker run --rm spatial_graph

About

A spatial graph datastructure for python

Resources

Stars

11 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

spatial-graph

LicensePyPIPython VersionCIcodecovCodSpeed

spatial_graph provides a data structure for directed and undirected graphs, where each node has an nD position (in time or space).

Design Principles

Goals

  • support for arbitrary number of dimensions
  • typed node identifiers and attributes
    • any fixed-length type that is supported by numpy
  • efficient node/edge queries by
    • ROI
    • kNN (by points / lines)
  • numpy-like interface for efficient:
    • graph population and manipulation
    • query results
    • attribute access
  • minimal memory footprint
  • minimal dependencies
    • cython / witty / cheetah3, used only when something has to be compiled at runtime (see Cross-Platform Support)
    • numpy for array interfaces
  • PYX API for graph algorithms in C/C++

Non-Goals

  • graph algorithms
  • I/O
  • non-typed arguments
  • non-spatial graphs
  • out-of-memory support
  • networkx compatibility

Python API

Graph creation:

graph=sg.SpatialGraph(
ndims=3,
node_dtype="uint64",
node_attr_dtypes={"position": "double[3]"},
edge_attr_dtypes={"score": "float32"},
position_attr="position",
)

Adding nodes/edges:

graph.add_nodes(
np.array([1, 2, 3, 4, 5], dtype="uint64"),
position=np.array(
[
[0.1, 0.1, 0.1],
[0.2, 0.2, 0.2],
[0.3, 0.3, 0.3],
[0.4, 0.4, 0.4],
[0.5, 0.5, 0.5],
],
dtype="double",
),
)
graph.add_edges(
np.array([[1, 2], [3, 4], [5, 1]], dtype="uint64"),
score=np.array([0.2, 0.3, 0.4], dtype="float32"),
)

Query nodes/edges in ROI:

# nodes/edges will be numpy arrays of dtype uint64 and shape (n,)/(n, 2)nodes=graph.query_nodes_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))
edges=graph.query_edges_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))

Query nodes/edges by position:

nodes=graph.query_nearest_nodes(np.array([0.3, 0.3, 0.3]), k=3)
edges=graph.query_nearest_edges(np.array([0.3, 0.3, 0.3]), k=3)

Access node/edge attributes:

node_positions=graph.node_attrs[nodes].positionedge_scores=graph.edge_attrs[edges].score

Delete nodes/edges:

graph.remove_nodes(nodes[:1000])

Implementation Details

A SpatialGraph consists of three data structures:

  • The Graph itself, holding nodes, edges, and their attributes (graphlite).
  • Two R-trees for spatial node and edge queries (based on rtree.c). We modified the original code to also include a fast kNN search.

Cross-Platform Support

spatial_graph generates specialized C/C++ for the exact data types you ask for. Where those types can be known in advance we compile them ahead of time and ship them in the wheels; everything else is compiled on your machine the first time it is used, which needs a C compiler.

No compiler needed. The PyPI wheels contain prebuilt PointRTree and LineRTree variants for the common combinations: float32/float64 coordinates, 2 to 5 dimensions, and int64/uint64 items -- as int64[2] / uint64[2] for LineRTree, whose items are node pairs. If your R-tree matches one of those -- as most do -- nothing is compiled, on any supported Python.

Compiler needed. Two cases fall back to compiling at runtime:

  1. Graph, DiGraph, SpatialGraph and SpatialDiGraph. Their node and edge attribute types are only known when you construct the graph, so they cannot be enumerated ahead of time.
  2. R-trees outside the prebuilt set above (an int32 item type, say, or 6 dimensions).

If you or your users need those without a compiler, you can still install spatial_graph from conda-forge, where we include a compiler (clang) in its dependencies.

The wheels are abi3 (stable ABI) and require Python 3.11 or newer, so one wheel per platform covers every supported CPython. Python 3.10 users should pin to a release before this one.

Why can't everything be prebuilt?

There is no cross-platform C/C++ compiler that we can install using pip. numba is maybe the closest to having solved that problem: numba does compile during runtime even if you don't have a compiler locally installed. This works because numba is generating LLVM IR, an intermediate representation language that LLVM can compile into machine code. numba depends on llvmlite, which provides a subset of the LLVM API, statically linked into the binaries in that package. This is just enough to compile the numba generated LLVM IR into machine code. We can't use this strategy, because we compile general C/C++ code. Converting that into LLVM IR is exactly what we need a compiler for.

For Developers

To create a new release, tag the current commit with a version number and push it to the upstream remote:

git tag -a "vX.Y.Z" -m "vX.Y.Z"
git push upstream --follow-tags

This will trigger the CI workflow, which will build the package and upload it to PyPI.

Testing in a conda environment

To simulate a naive user environment, with no assumptions made about the availability of a C/C++ compiler, you can run the included Dockerfile (where the key part of the conda env is the compilers package):

docker build -t spatial_graph .
docker run --rm spatial_graph

About

A spatial graph datastructure for python

Resources

Stars

11 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

spatial-graph

LicensePyPIPython VersionCIcodecovCodSpeed

spatial_graph provides a data structure for directed and undirected graphs, where each node has an nD position (in time or space).

Design Principles

Goals

  • support for arbitrary number of dimensions
  • typed node identifiers and attributes
    • any fixed-length type that is supported by numpy
  • efficient node/edge queries by
    • ROI
    • kNN (by points / lines)
  • numpy-like interface for efficient:
    • graph population and manipulation
    • query results
    • attribute access
  • minimal memory footprint
  • minimal dependencies
    • cython / witty / cheetah3, used only when something has to be compiled at runtime (see Cross-Platform Support)
    • numpy for array interfaces
  • PYX API for graph algorithms in C/C++

Non-Goals

  • graph algorithms
  • I/O
  • non-typed arguments
  • non-spatial graphs
  • out-of-memory support
  • networkx compatibility

Python API

Graph creation:

graph=sg.SpatialGraph(
ndims=3,
node_dtype="uint64",
node_attr_dtypes={"position": "double[3]"},
edge_attr_dtypes={"score": "float32"},
position_attr="position",
)

Adding nodes/edges:

graph.add_nodes(
np.array([1, 2, 3, 4, 5], dtype="uint64"),
position=np.array(
[
[0.1, 0.1, 0.1],
[0.2, 0.2, 0.2],
[0.3, 0.3, 0.3],
[0.4, 0.4, 0.4],
[0.5, 0.5, 0.5],
],
dtype="double",
),
)
graph.add_edges(
np.array([[1, 2], [3, 4], [5, 1]], dtype="uint64"),
score=np.array([0.2, 0.3, 0.4], dtype="float32"),
)

Query nodes/edges in ROI:

# nodes/edges will be numpy arrays of dtype uint64 and shape (n,)/(n, 2)nodes=graph.query_nodes_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))
edges=graph.query_edges_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))

Query nodes/edges by position:

nodes=graph.query_nearest_nodes(np.array([0.3, 0.3, 0.3]), k=3)
edges=graph.query_nearest_edges(np.array([0.3, 0.3, 0.3]), k=3)

Access node/edge attributes:

node_positions=graph.node_attrs[nodes].positionedge_scores=graph.edge_attrs[edges].score

Delete nodes/edges:

graph.remove_nodes(nodes[:1000])

Implementation Details

A SpatialGraph consists of three data structures:

  • The Graph itself, holding nodes, edges, and their attributes (graphlite).
  • Two R-trees for spatial node and edge queries (based on rtree.c). We modified the original code to also include a fast kNN search.

Cross-Platform Support

spatial_graph generates specialized C/C++ for the exact data types you ask for. Where those types can be known in advance we compile them ahead of time and ship them in the wheels; everything else is compiled on your machine the first time it is used, which needs a C compiler.

No compiler needed. The PyPI wheels contain prebuilt PointRTree and LineRTree variants for the common combinations: float32/float64 coordinates, 2 to 5 dimensions, and int64/uint64 items -- as int64[2] / uint64[2] for LineRTree, whose items are node pairs. If your R-tree matches one of those -- as most do -- nothing is compiled, on any supported Python.

Compiler needed. Two cases fall back to compiling at runtime:

  1. Graph, DiGraph, SpatialGraph and SpatialDiGraph. Their node and edge attribute types are only known when you construct the graph, so they cannot be enumerated ahead of time.
  2. R-trees outside the prebuilt set above (an int32 item type, say, or 6 dimensions).

If you or your users need those without a compiler, you can still install spatial_graph from conda-forge, where we include a compiler (clang) in its dependencies.

The wheels are abi3 (stable ABI) and require Python 3.11 or newer, so one wheel per platform covers every supported CPython. Python 3.10 users should pin to a release before this one.

Why can't everything be prebuilt?

There is no cross-platform C/C++ compiler that we can install using pip. numba is maybe the closest to having solved that problem: numba does compile during runtime even if you don't have a compiler locally installed. This works because numba is generating LLVM IR, an intermediate representation language that LLVM can compile into machine code. numba depends on llvmlite, which provides a subset of the LLVM API, statically linked into the binaries in that package. This is just enough to compile the numba generated LLVM IR into machine code. We can't use this strategy, because we compile general C/C++ code. Converting that into LLVM IR is exactly what we need a compiler for.

For Developers

To create a new release, tag the current commit with a version number and push it to the upstream remote:

git tag -a "vX.Y.Z" -m "vX.Y.Z"
git push upstream --follow-tags

This will trigger the CI workflow, which will build the package and upload it to PyPI.

Testing in a conda environment

To simulate a naive user environment, with no assumptions made about the availability of a C/C++ compiler, you can run the included Dockerfile (where the key part of the conda env is the compilers package):

docker build -t spatial_graph .
docker run --rm spatial_graph

About

A spatial graph datastructure for python

Resources

Stars

11 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

spatial-graph

LicensePyPIPython VersionCIcodecovCodSpeed

spatial_graph provides a data structure for directed and undirected graphs, where each node has an nD position (in time or space).

Design Principles

Goals

  • support for arbitrary number of dimensions
  • typed node identifiers and attributes
    • any fixed-length type that is supported by numpy
  • efficient node/edge queries by
    • ROI
    • kNN (by points / lines)
  • numpy-like interface for efficient:
    • graph population and manipulation
    • query results
    • attribute access
  • minimal memory footprint
  • minimal dependencies
    • cython / witty / cheetah3, used only when something has to be compiled at runtime (see Cross-Platform Support)
    • numpy for array interfaces
  • PYX API for graph algorithms in C/C++

Non-Goals

  • graph algorithms
  • I/O
  • non-typed arguments
  • non-spatial graphs
  • out-of-memory support
  • networkx compatibility

Python API

Graph creation:

graph=sg.SpatialGraph(
ndims=3,
node_dtype="uint64",
node_attr_dtypes={"position": "double[3]"},
edge_attr_dtypes={"score": "float32"},
position_attr="position",
)

Adding nodes/edges:

graph.add_nodes(
np.array([1, 2, 3, 4, 5], dtype="uint64"),
position=np.array(
[
[0.1, 0.1, 0.1],
[0.2, 0.2, 0.2],
[0.3, 0.3, 0.3],
[0.4, 0.4, 0.4],
[0.5, 0.5, 0.5],
],
dtype="double",
),
)
graph.add_edges(
np.array([[1, 2], [3, 4], [5, 1]], dtype="uint64"),
score=np.array([0.2, 0.3, 0.4], dtype="float32"),
)

Query nodes/edges in ROI:

# nodes/edges will be numpy arrays of dtype uint64 and shape (n,)/(n, 2)nodes=graph.query_nodes_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))
edges=graph.query_edges_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))

Query nodes/edges by position:

nodes=graph.query_nearest_nodes(np.array([0.3, 0.3, 0.3]), k=3)
edges=graph.query_nearest_edges(np.array([0.3, 0.3, 0.3]), k=3)

Access node/edge attributes:

node_positions=graph.node_attrs[nodes].positionedge_scores=graph.edge_attrs[edges].score

Delete nodes/edges:

graph.remove_nodes(nodes[:1000])

Implementation Details

A SpatialGraph consists of three data structures:

  • The Graph itself, holding nodes, edges, and their attributes (graphlite).
  • Two R-trees for spatial node and edge queries (based on rtree.c). We modified the original code to also include a fast kNN search.

Cross-Platform Support

spatial_graph generates specialized C/C++ for the exact data types you ask for. Where those types can be known in advance we compile them ahead of time and ship them in the wheels; everything else is compiled on your machine the first time it is used, which needs a C compiler.

No compiler needed. The PyPI wheels contain prebuilt PointRTree and LineRTree variants for the common combinations: float32/float64 coordinates, 2 to 5 dimensions, and int64/uint64 items -- as int64[2] / uint64[2] for LineRTree, whose items are node pairs. If your R-tree matches one of those -- as most do -- nothing is compiled, on any supported Python.

Compiler needed. Two cases fall back to compiling at runtime:

  1. Graph, DiGraph, SpatialGraph and SpatialDiGraph. Their node and edge attribute types are only known when you construct the graph, so they cannot be enumerated ahead of time.
  2. R-trees outside the prebuilt set above (an int32 item type, say, or 6 dimensions).

If you or your users need those without a compiler, you can still install spatial_graph from conda-forge, where we include a compiler (clang) in its dependencies.

The wheels are abi3 (stable ABI) and require Python 3.11 or newer, so one wheel per platform covers every supported CPython. Python 3.10 users should pin to a release before this one.

Why can't everything be prebuilt?

There is no cross-platform C/C++ compiler that we can install using pip. numba is maybe the closest to having solved that problem: numba does compile during runtime even if you don't have a compiler locally installed. This works because numba is generating LLVM IR, an intermediate representation language that LLVM can compile into machine code. numba depends on llvmlite, which provides a subset of the LLVM API, statically linked into the binaries in that package. This is just enough to compile the numba generated LLVM IR into machine code. We can't use this strategy, because we compile general C/C++ code. Converting that into LLVM IR is exactly what we need a compiler for.

For Developers

To create a new release, tag the current commit with a version number and push it to the upstream remote:

git tag -a "vX.Y.Z" -m "vX.Y.Z"
git push upstream --follow-tags

This will trigger the CI workflow, which will build the package and upload it to PyPI.

Testing in a conda environment

To simulate a naive user environment, with no assumptions made about the availability of a C/C++ compiler, you can run the included Dockerfile (where the key part of the conda env is the compilers package):

docker build -t spatial_graph .
docker run --rm spatial_graph

About

A spatial graph datastructure for python

Resources

Stars

11 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages