Skip to content

Quick Start

This guide will help you get started with HNSW by walking through a simple example.

Basic Usage

Here's a complete example that demonstrates how to create a graph, add vectors, and perform a search:

package main

import (
 "fmt"

 "github.com/TFMV/hnsw"
)

func main() {
 // Create a new HNSW graph with default parameters
 graph := hnsw.NewGraph[int]()

 // Add some vectors to the graph
 for i := 0; i < 1000; i++ {
  // Create a vector with 10 dimensions
  vector := make([]float32, 10)
  for j := 0; j < 10; j++ {
   vector[j] = float32(i) * 0.01 * float32(j+1)
  }

  // Add the vector to the graph
  node := hnsw.Node[int]{
   Key:   i,
   Value: vector,
  }
  err := graph.Add(node)
  if err != nil {
   fmt.Printf("Error adding node: %v\n", err)
  }
 }

 // Create a query vector
 query := []float32{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}

 // Search for the 5 nearest neighbors
 results, err := graph.Search(query, 5)
 if err != nil {
  fmt.Printf("Error searching: %v\n", err)
  return
 }

 // Print the results
 fmt.Println("Search results:")
 for i, result := range results {
  fmt.Printf("%d. Key: %d, Distance: %f\n", i+1, result.Key, result.Dist)
 }
}

Step-by-Step Explanation

1. Creating a Graph

graph := hnsw.NewGraph[int]()

This creates a new HNSW graph with default parameters. The type parameter int specifies the type of the keys used to identify vectors in the graph.

You can also create a graph with custom parameters:

graph := hnsw.NewGraph[int]()
graph.M = 16              // Maximum number of connections per node
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

2. Adding Vectors

node := hnsw.Node[int]{
    Key:   i,
    Value: vector,
}
err := graph.Add(node)

Each vector is added to the graph as a Node with a unique key and a vector value. The vector is a slice of float32 values.

You can also add multiple vectors in a batch:

nodes := []hnsw.Node[int]{
    {Key: 1, Value: []float32{0.1, 0.2, 0.3}},
    {Key: 2, Value: []float32{0.2, 0.3, 0.4}},
    {Key: 3, Value: []float32{0.3, 0.4, 0.5}},
}
err := graph.BatchAdd(nodes)

3. Searching

results, err := graph.Search(query, 5)

This searches for the 5 nearest neighbors to the query vector. The results are returned as a slice of SearchResult structs, which contain the key of the node and the distance to the query vector.

4. Using Different Distance Functions

HNSW provides several built-in distance functions:

// Euclidean distance
graph.Distance = hnsw.EuclideanDistance

// Cosine distance
graph.Distance = hnsw.CosineDistance

// Dot product distance
graph.Distance = hnsw.DotProductDistance

You can also define your own distance function:

graph.Distance = func(a, b []float32) float32 {
    // Custom distance calculation
    var sum float32
    for i := range a {
        sum += (a[i] - b[i]) * (a[i] - b[i])
    }
    return sum
}

Next Steps

Now that you've seen the basic usage of HNSW, you can explore more features: