Vector Search with HNSW
This notebook demonstrates the vector indexing and nearest-neighbor search capabilities of NetworkXGraph. It uses hnswlib for approximate nearest-neighbor (ANN) queries.
Key methods covered:
enable_vector_index()— create an ANN index on a node propertyquery_vector_index()— find the k nearest nodes to a query vectorSupported distance spaces:
cosine,l2,ip(inner product)
[ ]:
import importlib.util
package_to_check = 'drm'
spec = importlib.util.find_spec(package_to_check)
if spec is None:
print(f'⚠️ {package_to_check} no està instal·lat. Iniciant instal·lació...')
%pip install -q --upgrade drm-tools
print("✅ Instal·lació completada. L'estat del kernel PODRIA requerir un reinici.")
else:
print(f'✅ {package_to_check} ja està present al sistema. Saltant instal·lació.')
[ ]:
import numpy as np
try:
import hnswlib
print("✅ hnswlib is available")
except ImportError:
print("⚠️ hnswlib not installed. Run: pip install hnswlib")
hnswlib = None
from drm import NetworkXGraph, Node
graph = NetworkXGraph()
# Create a small set of items with 4-dimensional feature vectors
# Each item has a 'features' property that will be indexed for vector search
items = [
{"name": "apple", "features": [0.9, 0.1, 0.05, 0.0], "category": "fruit"},
{"name": "banana", "features": [0.85, 0.15, 0.0, 0.0], "category": "fruit"},
{"name": "carrot", "features": [0.0, 0.9, 0.05, 0.05], "category": "vegetable"},
{"name": "broccoli", "features": [0.05, 0.85, 0.05, 0.05], "category": "vegetable"},
{"name": "salmon", "features": [0.1, 0.1, 0.7, 0.1], "category": "protein"},
{"name": "chicken", "features": [0.1, 0.1, 0.75, 0.05], "category": "protein"},
{"name": "rice", "features": [0.2, 0.2, 0.1, 0.5], "category": "grain"},
]
node_ids = {}
for item in items:
node = Node(pk={"name": item["name"]}, main_label="Food", features=item["features"], category=item["category"])
nid = graph.insertNode(node)
node_ids[item["name"]] = nid
print(f"Inserted {len(items)} food items")
print("Node ids:", node_ids)
Enabling a vector index
Call enable_vector_index(property_name, dimensions, space) to create an HNSW index on a specific node property. The property values must be 1D vectors of the given dimensionality.
Supported space values:
"cosine"— cosine similarity (default, recommended for normalized vectors)"l2"— Euclidean distance"ip"— inner product
[ ]:
graph.enable_vector_index(
property_name="features",
dimensions=4,
space="cosine",
)
print("Vector index enabled for property 'features' (4D, cosine distance)")
Querying nearest neighbors
query_vector_index(property_name, vector, top_k) returns a list of (node_id, distance) tuples sorted by ascending distance.
We’ll query for the nearest neighbors of a “mystery” food item.
[ ]:
def lookup_name(node_id):
"""Reverse lookup: find the food name for a node id."""
for name, nid in node_ids.items():
if nid == node_id:
return name
return f"id={node_id}"
def show_results(query_vec, top_k=3):
results = graph.query_vector_index(
property_name="features",
vector=query_vec,
top_k=top_k,
)
print(f"Query: {query_vec}")
print(f"Top {top_k} nearest neighbors:")
for nid, dist in results:
name = lookup_name(nid)
print(f" {name:10s} distance={dist:.4f}")
return results
# Query 1: Find fruits (similar to apple)
print("=== Query 1: Find fruits ===")
show_results([0.9, 0.05, 0.0, 0.0], top_k=3)
print()
# Query 2: Find vegetables
print("=== Query 2: Find vegetables ===")
show_results([0.0, 0.8, 0.1, 0.1], top_k=3)
print()
# Query 3: Find protein
print("=== Query 3: Find protein ===")
show_results([0.05, 0.05, 0.8, 0.1], top_k=3)
Adding new nodes updates the index automatically
When you insert a new node with the indexed property, it is automatically added to the HNSW index.
[ ]:
# Add a new food item
mango = Node(pk={"name": "mango"}, main_label="Food", features=[0.88, 0.1, 0.02, 0.0], category="fruit")
graph.insertNode(mango)
node_ids["mango"] = graph.checkNode(mango)
print("Added 'mango' to the graph")
# Query again — mango should appear near apple and banana
print()
print("=== Query after adding mango ===")
show_results([0.9, 0.1, 0.0, 0.0], top_k=4)
Multiple vector indexes
You can create separate indexes on different properties.
[ ]:
# Create a second index on a different property (if you had 2D embedding)
# For this demo, we'll just show the API:
# graph.enable_vector_index(
# property_name="other_features",
# dimensions=8,
# space="l2",
# )
print("Multiple vector indexes are supported. See the API for details.")
Cleanup
[ ]:
graph.close()
print("Graph closed.")