Also at Deasil Works · txn2 · Plexara
Profiles GitHub · X · LinkedIn
Theme Light · Auto · Dark
Professional notes by Craig Johnston
long-form, short-form, working drafts · since 2008
VOL. XIX · MMXXVI
106 NOTES IN PRINT
FOLIO LV 2020-10-10 · 7 MIN · SHORT-FORM

Linear Algebra in Go: Vectors and Basic Operations

Linear Algebra in Go Part 1

Diagram · folio lv
mindmap
  root((Vectors in Go))
    Construction
      mat.NewVecDense
      from float64 slice
      zero vector
      unit vector
    Arithmetic
      add
      subtract
      scalar multiply
    Inner products
      dot product
      magnitude
      angle between
    Spatial
      cross product
      projection
      orthogonal basis
    Linear Algebra
      span
      linear combination
      basis vectors

This article begins a new series on linear algebra in Go, demonstrating how to perform numerical computations using the gonum library. If you’ve followed the Linear Algebra Crash Course in Python, this series provides a parallel implementation in Go with performance comparisons.

Linear Algebra: Golang Series - View all articles in this series.

Go offers advantages for linear algebra workloads: strong concurrency support, compile-time type checking, and excellent performance. The gonum ecosystem provides production-ready linear algebra, statistics, and plotting capabilities.


§2026 Update

Gonum is still the library for this, actively maintained and the right choice for linear algebra in Go. The code below still compiles and runs unchanged on current Go. One thing has changed: how you pull in the dependency.

These posts predate Go modules being universal. The go get gonum.org/v1/gonum/... wildcard is the old style. Today you work inside a module: run go mod init yourmodule once, add the gonum imports to your file, and run go mod tidy to fetch them and pin versions in go.mod. You rarely call go get by hand anymore; go mod tidy reads what you actually import and gets exactly that. Everything else, mat.VecDense, mat.Dot, the floats package, behaves the same.


The article continues below. The 2026 Update above covers what’s worth knowing today.

§Setting Up Gonum

Work inside a Go module. Initialize one, then let go mod tidy fetch gonum once you have added the imports:

go mod init example
go get gonum.org/v1/gonum
go get gonum.org/v1/plot

Import the necessary packages:

package main

import (
    "fmt"
    "math"

    "gonum.org/v1/gonum/floats"
    "gonum.org/v1/gonum/mat"
)

§Creating Vectors

In gonum, vectors are represented using mat.VecDense:

package main

import (
    "fmt"
    "gonum.org/v1/gonum/mat"
)

func main() {
    // Create a vector from a slice
    data := []float64{1.0, 2.0, 3.0, 4.0}
    v := mat.NewVecDense(len(data), data)

    fmt.Printf("Vector v:\n%v\n", mat.Formatted(v))
    fmt.Printf("Length: %d\n", v.Len())

    // Access individual elements
    fmt.Printf("v[0] = %.2f\n", v.AtVec(0))
    fmt.Printf("v[2] = %.2f\n", v.AtVec(2))
}

Output:

Vector v:
⎡1⎤
⎢2⎥
⎢3⎥
⎣4⎦
Length: 4
v[0] = 1.00
v[2] = 3.00

§Vector Operations

§Addition and Subtraction

func vectorAddition() {
    v1 := mat.NewVecDense(3, []float64{1, 2, 3})
    v2 := mat.NewVecDense(3, []float64{4, 5, 6})

    // Addition: result = v1 + v2
    result := mat.NewVecDense(3, nil)
    result.AddVec(v1, v2)
    fmt.Printf("v1 + v2 = %v\n", mat.Formatted(result.T()))

    // Subtraction: result = v1 - v2
    result.SubVec(v1, v2)
    fmt.Printf("v1 - v2 = %v\n", mat.Formatted(result.T()))
}

Output:

v1 + v2 = [5  7  9]
v1 - v2 = [-3  -3  -3]

§Scalar Multiplication

func scalarMultiplication() {
    v := mat.NewVecDense(3, []float64{1, 2, 3})
    scalar := 2.5

    // Scale the vector
    result := mat.NewVecDense(3, nil)
    result.ScaleVec(scalar, v)
    fmt.Printf("%.1f * v = %v\n", scalar, mat.Formatted(result.T()))
}

Output:

2.5 * v = [2.5    5  7.5]

§Dot Product

The dot product (inner product) of two vectors:

func dotProduct() {
    v1 := mat.NewVecDense(4, []float64{1, 2, 3, 4})
    v2 := mat.NewVecDense(4, []float64{2, 3, 4, 5})

    // Dot product
    dot := mat.Dot(v1, v2)
    fmt.Printf("v1 · v2 = %.2f\n", dot)

    // Manual calculation: 1*2 + 2*3 + 3*4 + 4*5 = 2 + 6 + 12 + 20 = 40
}

Output:

v1 · v2 = 40.00

§Vector Norms

The norm (magnitude) of a vector measures its length:

import "gonum.org/v1/gonum/floats"

func vectorNorms() {
    data := []float64{3, 4}
    v := mat.NewVecDense(2, data)

    // L2 norm (Euclidean)
    l2 := floats.Norm(data, 2)
    fmt.Printf("L2 norm: %.2f\n", l2)  // sqrt(3² + 4²) = 5

    // L1 norm (Manhattan)
    l1 := floats.Norm(data, 1)
    fmt.Printf("L1 norm: %.2f\n", l1)  // |3| + |4| = 7

    // Using mat.Norm
    l2Alt := mat.Norm(v, 2)
    fmt.Printf("L2 norm (mat): %.2f\n", l2Alt)
}

Output:

L2 norm: 5.00
L1 norm: 7.00
L2 norm (mat): 5.00

§Normalizing Vectors

Create a unit vector (length = 1):

func normalizeVector() {
    data := []float64{3, 4}
    v := mat.NewVecDense(2, data)

    // Compute norm
    norm := mat.Norm(v, 2)

    // Normalize
    normalized := mat.NewVecDense(2, nil)
    normalized.ScaleVec(1/norm, v)

    fmt.Printf("Original: %v\n", mat.Formatted(v.T()))
    fmt.Printf("Normalized: %v\n", mat.Formatted(normalized.T()))
    fmt.Printf("Norm of normalized: %.6f\n", mat.Norm(normalized, 2))
}

Output:

Original: [3  4]
Normalized: [0.6  0.8]
Norm of normalized: 1.000000

§Angle Between Vectors

The angle θ between vectors can be computed using the dot product:

func angleBetweenVectors() {
    v1 := mat.NewVecDense(2, []float64{1, 0})
    v2 := mat.NewVecDense(2, []float64{1, 1})

    // cos(θ) = (v1 · v2) / (||v1|| * ||v2||)
    dot := mat.Dot(v1, v2)
    norm1 := mat.Norm(v1, 2)
    norm2 := mat.Norm(v2, 2)

    cosTheta := dot / (norm1 * norm2)
    theta := math.Acos(cosTheta)
    thetaDegrees := theta * 180 / math.Pi

    fmt.Printf("Angle: %.2f radians (%.2f degrees)\n", theta, thetaDegrees)
}

Output:

Angle: 0.79 radians (45.00 degrees)

§Working with Raw Slices

Sometimes it’s more efficient to work with raw slices using the floats package:

import "gonum.org/v1/gonum/floats"

func sliceOperations() {
    a := []float64{1, 2, 3, 4}
    b := []float64{5, 6, 7, 8}

    // Element-wise operations modify in place
    result := make([]float64, len(a))
    copy(result, a)

    floats.Add(result, b)
    fmt.Printf("a + b = %v\n", result)

    // Dot product
    dot := floats.Dot(a, b)
    fmt.Printf("a · b = %.2f\n", dot)

    // Sum
    sum := floats.Sum(a)
    fmt.Printf("sum(a) = %.2f\n", sum)

    // Max
    max := floats.Max(a)
    fmt.Printf("max(a) = %.2f\n", max)
}

§Visualizing Vector Operations

One of Go’s strengths is the gonum/plot library for creating publication-quality visualizations. Here’s how to visualize vector addition:

package main

import (
    "image/color"
    "math"

    "gonum.org/v1/plot"
    "gonum.org/v1/plot/plotter"
    "gonum.org/v1/plot/vg"
)

func main() {
    p := plot.New()
    p.Title.Text = "Vector Addition"
    p.X.Label.Text = "X"
    p.Y.Label.Text = "Y"
    p.X.Min, p.X.Max = -1, 8
    p.Y.Min, p.Y.Max = -1, 8

    // Vectors: v1 = [3,2], v2 = [1,4], sum = [4,6]
    drawArrow(p, 0, 0, 3, 2, color.RGBA{R: 66, G: 133, B: 244, A: 255})   // v1 blue
    drawArrow(p, 0, 0, 1, 4, color.RGBA{R: 234, G: 67, B: 53, A: 255})    // v2 red
    drawArrow(p, 0, 0, 4, 6, color.RGBA{R: 52, G: 168, B: 83, A: 255})    // sum green

    p.Save(6*vg.Inch, 6*vg.Inch, "vectors.png")
}

func drawArrow(p *plot.Plot, x1, y1, x2, y2 float64, c color.Color) {
    // Line
    pts := plotter.XYs{{X: x1, Y: y1}, {X: x2, Y: y2}}
    line, _ := plotter.NewLine(pts)
    line.Color = c
    line.Width = vg.Points(2)
    p.Add(line)

    // Arrowhead
    angle := math.Atan2(y2-y1, x2-x1)
    arrowLen := 0.3
    for _, da := range []float64{math.Pi / 6, -math.Pi / 6} {
        ax := x2 - arrowLen*math.Cos(angle-da)
        ay := y2 - arrowLen*math.Sin(angle-da)
        arrow, _ := plotter.NewLine(plotter.XYs{{X: x2, Y: y2}, {X: ax, Y: ay}})
        arrow.Color = c
        arrow.Width = vg.Points(2)
        p.Add(arrow)
    }
}

Vector addition visualization showing v1, v2, and their sum

The blue vector v1=[3,2], red vector v2=[1,4], and their sum in green [4,6] form a parallelogram - a fundamental property of vector addition.

§Practical Example: Cosine Similarity

Cosine similarity is commonly used in NLP and recommendation systems:

func cosineSimilarity(v1, v2 *mat.VecDense) float64 {
    dot := mat.Dot(v1, v2)
    norm1 := mat.Norm(v1, 2)
    norm2 := mat.Norm(v2, 2)

    if norm1 == 0 || norm2 == 0 {
        return 0
    }
    return dot / (norm1 * norm2)
}

func cosineSimilarityExample() {
    // Word embeddings (simplified)
    king := mat.NewVecDense(4, []float64{0.5, 0.3, 0.8, 0.1})
    queen := mat.NewVecDense(4, []float64{0.5, 0.3, 0.7, 0.2})
    apple := mat.NewVecDense(4, []float64{0.9, 0.1, 0.1, 0.9})

    fmt.Printf("Similarity(king, queen) = %.4f\n",
        cosineSimilarity(king, queen))
    fmt.Printf("Similarity(king, apple) = %.4f\n",
        cosineSimilarity(king, apple))
}

Output:

Similarity(king, queen) = 0.9913
Similarity(king, apple) = 0.5101

§Complete Working Example

Here’s a complete program demonstrating all vector operations:

package main

import (
    "fmt"
    "math"

    "gonum.org/v1/gonum/floats"
    "gonum.org/v1/gonum/mat"
)

func main() {
    // Create vectors
    v1 := mat.NewVecDense(3, []float64{1, 2, 3})
    v2 := mat.NewVecDense(3, []float64{4, 5, 6})

    fmt.Println("=== Vector Operations in Go ===")
    fmt.Printf("v1 = %v\n", rawData(v1))
    fmt.Printf("v2 = %v\n", rawData(v2))

    // Addition
    sum := mat.NewVecDense(3, nil)
    sum.AddVec(v1, v2)
    fmt.Printf("v1 + v2 = %v\n", rawData(sum))

    // Dot product
    dot := mat.Dot(v1, v2)
    fmt.Printf("v1 · v2 = %.2f\n", dot)

    // Norms
    fmt.Printf("||v1|| = %.4f\n", mat.Norm(v1, 2))

    // Normalize
    norm := mat.Norm(v1, 2)
    unit := mat.NewVecDense(3, nil)
    unit.ScaleVec(1/norm, v1)
    fmt.Printf("unit(v1) = %v\n", rawData(unit))
    fmt.Printf("||unit(v1)|| = %.6f\n", mat.Norm(unit, 2))
}

func rawData(v *mat.VecDense) []float64 {
    data := make([]float64, v.Len())
    for i := 0; i < v.Len(); i++ {
        data[i] = v.AtVec(i)
    }
    return data
}

§Summary

This article covered:

  • Setting up gonum for linear algebra in Go
  • Creating vectors with mat.VecDense
  • Vector operations: addition, subtraction, scalar multiplication
  • Dot product using mat.Dot
  • Vector norms (L1, L2)
  • Normalizing vectors to unit length
  • Angle computation between vectors
  • Practical application: Cosine similarity

§Resources


Linear Algebra: Golang Series - View all articles in this series.

← back to all notes