Skip to content

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:

import "github.com/TFMV/hnsw"

// Create a new graph with int keys
graph := hnsw.NewGraph[int]()

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

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)
}

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

// Delete a node by key
deleted := graph.Delete(1)
if !deleted {
    fmt.Println("Node not found")
}

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: