Go: матрицы и векторы

Gonum — это коллекция библиотек на языке Go для работы с математическими методам линейной алгебры, теории оптимизации и т.д. Дизайн библиотеки сделан таким образом, чтобы было легко создавать эффективные алгоритмы для вычислений различной степени сложности. В данной статье мы познакомимся с тем как работать с векторами и матрицами.

Для начала давайте установим необходимые библиотеки. Это можно сделать следующей командой:

go get -u gonum.org/v1/gonum/...

После в коде импортируем пакет gonum/mat

import "gonum.org/v1/gonum/mat"

gonum/mat предоставляет методы для создания матриц/векторов и управления ими через интерфейсы mat.VecDense и mat.Dense соответственно. Внутренняя реализация остается под капотом. В качестве базового класса элементов используется float64. Создадим два вектора из трех элементов каждый:

u := mat.NewVecDense(3, []float64{1, 2, 3})
v := mat.NewVecDense(3, []float64{4, 5, 6})

Чтобы задать/получить значение ячейки придется использовать методы SetVec/AtVec соответственно, что может показаться не очень удобным в сравнение с оператором [] в языках типа С++/Python.

a := u.AtVec(1)
a := u.At(1, 0)
u.SetVec(1, 33.2)

Большинство операций применяется к объекту и не возвращают результата. Это сделано для того, чтобы минимизировать работу с памятью. Пример ниже покажет, как использовать основные векторные операции:

package main

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

func main() {
 u := mat.NewVecDense(3, []float64{1, 2, 3})
 println("u: ")
 matPrint(u)

 v := mat.NewVecDense(3, []float64{4, 5, 6})
 println("v: ")
 matPrint(v)

 w := mat.NewVecDense(3, nil)
 w.AddVec(u, v)
 println("u + v: ")
 matPrint(w)

 // текущий вектор можно переписать, сделав 
 // его результатом операции
 u.AddVec(u, v)
 println("u (overwritten):")
 matPrint(u)

 // операция u + alpha * v, alpha - скаляр
 w.AddScaledVec(u, 2, v)
 println("u + 2 * v: ")
 matPrint(w)

 // вычитание v - u
 w.SubVec(u, v)
 println("v - u: ")
 matPrint(w)
 
 // умножение вектора на число
 w.ScaleVec(23, u)
 println("u * 23: ")
 matPrint(w)

 // скалярное произведение векторов
 d := mat.Dot(u, v)
 println("u dot v: ", d)
 
 // получение размера вектора
 l := v.Len()
 println("Length of v: ", l)

 // вычисление евклидовой нормы вектора
 println(mat.Norm(v, 2))
}

func matPrint(X mat.Matrix) {
 fa := mat.Formatted(X, mat.Prefix(""), mat.Squeeze())
 fmt.Printf("%v\n", fa)
}

Особо отметим, что мы работаем с VecDense. Внутреннее представление вектора это матрица. Поэтому, если вы передадите вектор в метод, который будет ожидать интерфейс матрицы, то он будет использоваться как столбец. Для того, чтобы работать с ним как со строкой нужно его транспонировать методом T().

Давайте теперь рассмотрим как можно работать с матрицами в gonum/mat. К сожаления, Gonum не поддерживает загрузку данных например из CSV и других форматов, а работает только с массивами в памяти, поэтому матрица передается как слайс []float64.

v := []float64{1,2,3,4,5,6,7,8,9,10,11,12}
A := mat.NewDense(3, 4, v)

Ниже приведен пример наиболее важных операций при работе с матрицами.

package main

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

func matPrint(X mat.Matrix) {
 fa := mat.Formatted(X, mat.Prefix(""), mat.Squeeze())
 fmt.Printf("%v\n", fa)
}

func main() {

 // инициализация исходных данных
 v := make([]float64, 12)
 for i := 0; i < 12; i++ {
  v[i] = float64(i)
 }

 // создание новой матрицы размером 3x4
 A := mat.NewDense(3, 4, v)
 println("A:")
 matPrint(A)

 // получение/установка элемента матрицы
 a := A.At(0, 2)
 println("A[0, 2]: ", a)
 A.Set(0, 2, -1.5)
 matPrint(A)
 
 // получение строки/столбца матрицы
 println("Row 1 of A:")
 matPrint(A.RowView(1))
 
 println("Column 0 of A:")
 matPrint(A.ColView(0))

 // установка значений строки/столбца из []float64
 row := []float64{10, 9, 8, 7}
 A.SetRow(0, row)
 matPrint(A)

 col := []float64{3, 2, 1}
 A.SetCol(0, col)
 matPrint(A)

 // сложение/вычитание матриц. в случае ошибки panic
 B := mat.NewDense(3, 4, nil)
 B.Add(A, A)
 println("B:")
 matPrint(B)

 C := mat.NewDense(3, 4, nil)
 C.Sub(A, B)
 println("A - B:")
 matPrint(C)

 // умножение на скаляр всех элементов матрицы
 C.Scale(3.5, B)
 println("3.5 * B:")
 matPrint(C)

 // транспонирование матрицы
 println("A'")
 matPrint(A.T())
 
 // перемножение матриц
 D := mat.NewDense(3, 3, nil)
 D.Product(A, B.T())
 println("A * B'")
 matPrint(D)

 // перемножение нескольких матриц
 println("D * A * B' * D")
 matPrint(D)
 
 // применение функции ко всем элементам матрицы
 C.Apply(sumOfIndices, A)
 println("C:")
 matPrint(C)

 // получение подматрицы по диапазонам и 
 // вычисление ее детерминанта
 E := A.Slice(0, 3, 0, 3)
 d := mat.Det(E)
 println("det(E): ", d)

 // след матрицы
 t := mat.Trace(E)
 println("tr(E)", t)

 // вычисление обратной матрицы
 src := []float64{ 2, 0, 0, 0, 3, 0, 0, 0, 1}
 E = mat.NewDense( 3, 3, src )
 In := mat.NewDense( 3, 3, nil)
 In.Inverse(E)
 matPrint(In)
}

func sumOfIndices(i, j int, v float64) float64 {
 return float64(i + j)
}

Этим возможности пакета gonum/mat не ограничиваются. Для получения большей информации посетите сайт проекта.

 

02.11.2018









 
архив

подписка