Basic Usage¶
This guide covers the fundamental operations you can perform with HNSW, including creating graphs, adding vectors, searching, and deleting nodes.
Creating a Graph¶
To create a new HNSW graph with default parameters:
You can also create a graph with custom parameters:
graph := hnsw.NewGraph[int]()
// Configure the graph
graph.M = 16 // Maximum number of connections per node
graph.Ml = 0.25 // Level generation factor
graph.EfConstruction = 200 // Size of the dynamic candidate list during construction
graph.EfSearch = 100 // Size of the dynamic candidate list during search
graph.Distance = hnsw.CosineDistance // Distance function to use
Adding Vectors¶
Adding a Single Vector¶
// Create a vector
vector := []float32{0.1, 0.2, 0.3, 0.4, 0.5}
// Create a node with a unique key
node := hnsw.Node[int]{
Key: 1,
Value: vector,
}
// Add the node to the graph
err := graph.Add(node)
if err != nil {
// Handle error
log.Fatalf("Failed to add node: %v", err)
}
Adding Multiple Vectors¶
For better performance when adding many vectors, use the BatchAdd
method:
// Create multiple nodes
nodes := []hnsw.Node[int]{
{Key: 1, Value: []float32{0.1, 0.2, 0.3, 0.4, 0.5}},
{Key: 2, Value: []float32{0.2, 0.3, 0.4, 0.5, 0.6}},
{Key: 3, Value: []float32{0.3, 0.4, 0.5, 0.6, 0.7}},
}
// Add the nodes to the graph in a batch
err := graph.BatchAdd(nodes)
if err != nil {
// Handle error
log.Fatalf("Failed to add nodes: %v", err)
}
Searching¶
Basic Search¶
To find the nearest neighbors to a query vector:
// Create a query vector
query := []float32{0.15, 0.25, 0.35, 0.45, 0.55}
// Search for the 5 nearest neighbors
results, err := graph.Search(query, 5)
if err != nil {
// Handle error
log.Fatalf("Failed to search: %v", err)
}
// Process the results
for i, result := range results {
fmt.Printf("%d. Key: %d, Distance: %f\n", i+1, result.Key, result.Dist)
}
Parallel Search¶
For large graphs or high-dimensional data, you can use parallel search for better performance:
// Search for the 5 nearest neighbors using 4 worker goroutines
results, err := graph.ParallelSearch(query, 5, 4)
if err != nil {
// Handle error
log.Fatalf("Failed to search: %v", err)
}
Search with Negative Examples¶
To find vectors that are similar to a positive example but dissimilar to a negative example:
// Create a positive query vector
positiveQuery := []float32{0.1, 0.2, 0.3, 0.4, 0.5}
// Create a negative query vector
negativeQuery := []float32{0.5, 0.4, 0.3, 0.2, 0.1}
// Search for the 5 nearest neighbors that are similar to the positive query
// but dissimilar to the negative query, with a negative weight of 0.5
results, err := graph.SearchWithNegative(positiveQuery, negativeQuery, 5, 0.5)
if err != nil {
// Handle error
log.Fatalf("Failed to search: %v", err)
}
You can also search with multiple negative examples:
// Create multiple negative query vectors
negativeQueries := [][]float32{
{0.5, 0.4, 0.3, 0.2, 0.1},
{0.6, 0.5, 0.4, 0.3, 0.2},
}
// Search with multiple negative examples
results, err := graph.SearchWithNegatives(positiveQuery, negativeQueries, 5, 0.5)
if err != nil {
// Handle error
log.Fatalf("Failed to search: %v", err)
}
Deleting Nodes¶
Deleting a Single Node¶
Deleting Multiple Nodes¶
// Delete multiple nodes by key
keys := []int{1, 2, 3}
results := graph.BatchDelete(keys)
// Check which nodes were deleted
for i, deleted := range results {
if deleted {
fmt.Printf("Node %d was deleted\n", keys[i])
} else {
fmt.Printf("Node %d was not found\n", keys[i])
}
}
Looking Up Nodes¶
To retrieve a vector by its key:
// Look up a node by key
vector, found := graph.Lookup(1)
if !found {
fmt.Println("Node not found")
} else {
fmt.Printf("Vector: %v\n", vector)
}
Getting Graph Information¶
To get the number of nodes in the graph:
// Get the number of nodes in the graph
count := graph.Len()
fmt.Printf("The graph contains %d nodes\n", count)
Saving and Loading Graphs¶
To save a graph to a file:
// Open a file for writing
file, err := os.Create("graph.bin")
if err != nil {
log.Fatalf("Failed to create file: %v", err)
}
defer file.Close()
// Export the graph
err = graph.Export(file)
if err != nil {
log.Fatalf("Failed to export graph: %v", err)
}
To load a graph from a file:
// Open a file for reading
file, err := os.Open("graph.bin")
if err != nil {
log.Fatalf("Failed to open file: %v", err)
}
defer file.Close()
// Create a new graph
graph := hnsw.NewGraph[int]()
// Import the graph
err = graph.Import(file)
if err != nil {
log.Fatalf("Failed to import graph: %v", err)
}
Next Steps¶
Now that you understand the basic usage of HNSW, you can explore:
- Examples: See practical examples of HNSW in action
- Advanced Techniques: Learn about more sophisticated usage patterns
- Performance Tuning: Optimize HNSW for your specific use case