QuVINE#
Quantum View-based Network Embeddings
QuVINE turns a graph into low-dimensional node embeddings by combining
classical and quantum random walks with SGNS-based representation learning. It
is vendored into QBioCode as an in-tree app (qbiocode.apps.quvine).
Installation#
QuVINEβs Python modules ship with QBioCode, but its third-party dependencies (gensim, hiperwalk, node2vec, torch-geometric, python-louvain, ripser, omegaconf) are heavy, so they are behind an optional extra:
pip install "qbiocode[quvine]"
A plain pip install qbiocode leaves QuVINEβs dependencies out. import
qbiocode and every classical embedding still work; requesting a QuVINE method
then raises a QuvineDependencyError that names
the extra, the missing module, and the command above. To check an existing
environment:
from qbiocode.apps.quvine import describe_environment
print(describe_environment())
- π¬ What QuVINE Does
Builds multiple views of a single input graph
Runs walks: random walk with restart (RWR) and discrete-/continuous-time quantum walks (DTQW/CTQW)
Learns embeddings via skip-gram negative sampling (SGNS), with quantum-calibrated filter / GAT / GraphGPS variants
Compares against classical baselines: node2vec, NetMF, APPNP
Fuses views into a single embedding when requested
Note
Before you start, make sure that you have installed QBioCode correctly by following the Installation guide.
Important
Graph-complexity metrics are not part of the embedding app. They live in
QBioCodeβs own qbiocode.evaluate_graph()
(qbiocode.evaluation.graph_evaluation), which you can run on the same
graph to characterize it.
How QuVINE Works#
QuVINE does not walk the input graph directly. It builds a family of bounded views around each node, walks each view with a mix of classical and quantum walks, treats the resulting node sequences as a corpus, and learns one embedding per walk kind with skip-gram negative sampling β optionally fusing them into a single matrix.
Preprocess (research pipeline only)
Radius-bounded subsample around the seed set
Local-score sparsification, degree-capped
Views
views.num_viewsconstrained subgraphs per rootBounded by
max_nodes/max_edges/max_degreeHubs down-weighted by
degree_alpha
Walks β one corpus per kind in walks.kinds
rwrclassical random walk with restartctqwcontinuous-time quantum walkdtqwdiscrete-time quantum walk
SGNS
Skip-gram over the walk token sequences
One
(n_nodes, dim)matrix per walk kindFused across kinds by
fusion.method
The Embedding Pipeline#
networkx.Graph (or a 2-/3-column edge list)
β
[Preprocess] subsample β sparsify (Hydra pipeline only)
β
Views: num_views constrained subgraphs per root
β
Walks: rwr ββββ ctqw ββββ dtqw ββββ (each kind, independently)
β β β β
Corpus: node-sequence tokens per walk kind
β β β β
SGNS: E_rwr β E_ctqw β E_dtqw β (n_nodes Γ dim each)
βββββββ΄ββββββββββββ΄ββββββββββββ
Fusion: fusion.method (svd | graphreg | attention | hybrid | ...)
β
EmbedResult.embedding β (n_nodes, dim)
Stages in detail#
1. Views instead of the whole graph. For each root node QuVINE samples
views.num_views bounded subgraphs, each capped at max_nodes nodes,
max_edges edges and max_degree degree. This is what makes quantum walks
tractable: a CTQW or DTQW needs an operator over the walked subgraph, so the caps
bound that cost per root rather than by the size of the whole network. Sampling
is degree-normalized (degree_norm, degree_alpha) so a view is not
swallowed by whichever hub it happened to touch.
2. Walks, one corpus per kind. Every entry in walks.kinds produces its
own token corpus over the views. rwr is the classical control;
ctqw evolves for walks.time; dtqw takes walks.steps steps with the
walks.coin operator. Walks are seeded per root and derived from
experiment.base_seed, so a run reproduces regardless of how the roots were
scheduled across workers.
3. SGNS. Each corpus is trained with skip-gram negative sampling
(train.sg, train.negative, train.window, train.epochs) into a
(n_nodes, train.embedding_dim) matrix.
4. Fusion. The *_fused methods combine the per-kind matrices through
fusion.method at rank fusion.k. The single-kind methods
(quvine_rwr, quvine_ctqw, quvine_dtqw) skip this and return one
matrix directly.
5. Quantum-calibrated baselines. The gat_*, graphgps_* and
filter_* families are classical architectures whose spectral filter is fitted
against a quantum walkβs profile (heat_qcal_ctqw, poly_qcal_dtqw, β¦).
They exist so a quantum walk can be compared against a classical model that has
been given the same spectral information β which is a much harder baseline
than an unmodified GAT.
See also
Every knob named above is documented in the QuVINE Configuration Guide, including which of the three entry points reads it.
Usage#
QuVINE can be used as a command-line tool or as a Python library.
Command-Line Interface#
After installing QBioCode, the quvine command embeds the nodes of a graph
given as a 2- or 3-column edge list (source,target[,weight]). A conventional
header row is recognized and skipped; # comment lines are ignored:
# List all available methods
quvine --list-methods
# Embed a graph with the default fused method
quvine --edgelist edges.csv --method quvine_fused --output out/
# Use a classical baseline on a tab-separated, weighted edge list
quvine --edgelist edges.tsv --sep '\t' --weighted --method node2vec --output out/
Command-Line Options#
Flag |
Default |
Description |
|---|---|---|
|
required |
Path to the edge-list file: |
|
required |
Output directory. Created if it does not exist. |
|
|
Embedding method. Any name from |
|
|
Field separator of the edge list. Use |
|
off |
Read a third column as the edge weight. Without this the third column is ignored and every edge weighs 1. |
|
|
Whether the first row is column names. |
|
from config |
Integer seed, overriding |
|
packaged default |
Path to a YAML config. See the Configuration Guide. |
|
off |
Additionally write the raw array as |
|
off |
Print every available method and exit. Methods whose dependency is missing are reported as unavailable rather than omitted silently. |
The tool validates before it computes: a missing, nonexistent or directory
--edgelist, a --config that is not a file, an empty --sep, an
unknown --method, an edge list that is only a header, or a graph that parses
to zero nodes each exit non-zero with a message naming the problem. In particular a mistyped --config path is
an error β earlier versions silently fell back to the packaged default and
produced a run configured by something other than what you asked for.
Outputs#
A quvine run writes three things into --output:
File |
Contents |
|---|---|
|
The embedding matrix. Index column |
|
The same matrix as a raw NumPy array, written only with |
|
Run metadata: |
requested_method and method differ whenever an alias or a config choice
resolves: quvine_fused runs as fused:svd when fusion.method is
svd. Both are recorded, so a result file says exactly what produced it.
Note
For a fused method dim is fusion.k, not train.embedding_dim β
fusion reduces the stacked per-walk-kind embeddings to its own rank. With the
shipped defaults that is 10 rather than 64. Single-kind methods return
train.embedding_dim.
Note
The research pipeline (python -m qbiocode.apps.quvine.main) writes a
larger result set β ranking_results.csv, embedding_comparison.csv,
the resolved config.yaml, summary.json, per-iteration
embeddings/embeddings_iter_{n}.npz and plots β into
runtime.output_dir. See the
Configuration Guide.
Python Library Usage#
import networkx as nx
from qbiocode.apps.quvine import embed, list_methods
# Inspect the available methods (83 in total)
print(list_methods())
# Build (or load) a graph
G = nx.karate_club_graph()
# Embed its nodes; result.embedding has shape (n_nodes, dim)
result = embed(G, "quvine_fused", base_seed=0)
print(result.embedding.shape)
The embed() function accepts the graph, a method name, and optional
configuration/override arguments (config, overrides, base_seed,
fuse, n_jobs, β¦). It returns an EmbedResult whose embedding
attribute is the (n_nodes, dim) embedding matrix.
Evaluating the input graph:
from qbiocode import evaluate_graph
metrics = evaluate_graph(G, name="karate") # -> pandas.DataFrame
As a QBioCode Embedding#
QuVINE is also wired into QBioCodeβs embedding layer, so any of its method names
is usable wherever pca, nmf or umap is β including QProfilerβs
embeddings: config list. The call shape is identical:
from qbiocode import get_embeddings
X_train_emb, X_test_emb = get_embeddings(
"quvine_rwr", X_train, X_test, n_components=8
)
A symmetric k-nearest-neighbour graph is built over the rows of
vstack([X_train, X_test]) with edge weight 1/(1+d), embedded with the
requested method, reduced to n_components if the method returned a different
width, and split back into the train and test blocks.
Warning
QuVINE methods are transductive: no QuVINE method has an out-of-sample
transform, so the test rows take part in building the graph. Test
features therefore influence the geometry; test labels never enter at any
point. get_embeddings emits one UserWarning per call saying so,
and qbiocode.is_transductive() lets downstream code branch on it. The
classical spectral mode has exactly the same property.
Discovering what is available:
import qbiocode
qbiocode.SKLEARN_METHODS # the classical modes, always present
qbiocode.QUVINE_HEADLINE_METHODS # the QuVINE names worth trying first
qbiocode.QUVINE_METHODS # all 83; empty if the extra is absent
netmf, appnp and the gat_* family need nothing beyond the base
install. The rest raise a message naming the missing dependency and the exact
install command β see the installation note above.
Available Methods#
list_methods() returns 83 method names spanning several families. Pass
kind="sgns", kind="registry" or kind="fused" to narrow the list:
Family |
Examples |
|---|---|
Fused (all walk kinds combined) |
|
QuVINE (quantum-calibrated) |
|
Filter variants |
|
GAT variants |
|
GraphGPS variants |
|
Walks |
|
Classical baselines |
|
Tip
Small quantum-walk step counts often work best: high step counts can over-mix the walk, so a modest number of steps tends to give comparable or better embeddings.
Configuration#
Every stage above is driven by a YAML config. QuVINE ships a working default and finds it automatically, so nothing below is required to get started β but the three entry points read different parts of it, which is the one thing worth knowing before you edit it:
Entry point |
Reads |
|---|---|
|
|
|
all of the above plus |
Point QuVINE at your own file with embed(..., config=path), the CLIβs
--config, or the QUVINE_DEFAULT_CONFIG environment variable; override
individual keys with overrides={"train": {"epochs": 20}} or the dotlist form
overrides=["train.epochs=20"].
Graph-Complexity Measures#
qbiocode.evaluate_graph() is the graph analogue of QProfilerβs
data-complexity measures: it summarizes a graph
as a single-row pandas.DataFrame you can correlate against embedding
quality, use to choose a method, or report alongside a result.
from qbiocode import evaluate_graph
metrics = evaluate_graph(G, name="karate") # 1 x 88 DataFrame
Important
evaluate_graph is core QBioCode, not part of the QuVINE app β it is
documented here because this is where you are most likely to want it. It needs
only the base install; QuVINEβs optional extra is irrelevant to it.
It merges two families of metrics, and each degrades independently: if one
family cannot be computed for a graph, that familyβs columns are dropped with a
UserWarning and the rest are still returned. An empty graph yields a
size-only summary. Passing None raises TypeError.
The 88 columns#
Family |
Count |
Metrics |
|---|---|---|
Size and density |
8 |
|
Laplacian spectrum |
16 |
|
Eigenvector localization |
8 |
|
Diffusion and walk spectra |
4 |
|
Quantum composites |
5 |
|
Paths and connectivity |
5 |
|
Degree and centrality concentration |
12 |
|
Community and clustering |
6 |
|
Ollivier-Ricci curvature |
10 |
|
Effective resistance |
3 |
|
Persistent homology |
8 |
|
Label- and feature-aware |
2 |
|
Identifier |
1 |
|
Tip
To compare graphs, concatenate the single-row frames:
import pandas as pd
from qbiocode import evaluate_graph
table = pd.concat([evaluate_graph(g, name=n) for n, g in graphs.items()])
That is exactly what the Getting Started notebook does before correlating complexity against macro-F1.
The individual functions behind these columns are documented in
qbiocode.evaluation.graph_evaluation.
Tutorials#
Four notebooks, in reading order β the synthetic walkthrough introduces the API and the method registry, two single-cell notebooks apply it to real data, and the last drives QuVINE through QProfiler:
QuVINE - Getting Started - benchmark 12 methods across three stochastic block models of increasing difficulty, then correlate each graphβs complexity against node-classification macro-F1. Fully synthetic, so no data files are needed.
QuVINE on Single-Cell Data - build a kNN graph from PBMC single-cell data, inspect it with
qbiocode.evaluate_graph(), and compare classical against quantum-calibrated walk embeddings on a CD4-vs-CD8 task.QuVINE on T vs. Monocyte - a transductive, semi-supervised task on an 800-cell two-view graph with soft seed labels, evaluated both by training on the seed embeddings and by ranking non-seed nodes against degree- and distance-matched null controls.
QuVINE Embeddings in QProfiler - drive QuVINE through
qbiocode.get_embeddings()like any other embedding and let QProfiler benchmark the 2x2 classical/quantum design.The Tutorials page for the full gallery.
See also
QuVINE Configuration Guide - the shipped config, section by section
qbiocode.apps.quvine- App packageqbiocode.evaluate_graph()- Graph-complexity metrics