Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ require (
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.5-0.20231225225746-43d5d4cd4e0e // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorse-io/goat v0.2.1-0.20260618151728-201cbcf325ad // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/asmfmt v1.3.2 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
Expand All @@ -49,9 +52,14 @@ require (
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.10.1 // indirect
github.com/rogpeppe/go-internal v1.16.0 // indirect
github.com/samber/lo v1.53.0 // indirect
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/twpayne/go-geom v1.6.1 // indirect
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/text v0.36.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

tool github.com/gorse-io/goat
20 changes: 18 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ github.com/cockroachdb/swiss v0.0.0-20251224182025-b0f6560f979b h1:VXvSNzmr8hMj8
github.com/cockroachdb/swiss v0.0.0-20251224182025-b0f6560f979b/go.mod h1:yBRu/cnL4ks9bgy4vAASdjIW+/xMlFwuHKqtmh3GZQg=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand All @@ -67,10 +68,16 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorse-io/goat v0.2.1-0.20260618151728-201cbcf325ad h1:HESEie1ivHg8yI9Qnej4Cu+AP8w8gQ9gqpYcnV5zC7Q=
github.com/gorse-io/goat v0.2.1-0.20260618151728-201cbcf325ad/go.mod h1:NjwKvGIyhFveMjnUdYrngJYvYLqK0aK7Qdp7QJWkBLo=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4=
github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
Expand Down Expand Up @@ -109,6 +116,14 @@ github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPH
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/twpayne/go-geom v1.6.1 h1:iLE+Opv0Ihm/ABIcvQFGIiFBXd76oBIar9drAwHFhR4=
Expand All @@ -117,6 +132,7 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
Expand All @@ -141,8 +157,8 @@ golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
Expand Down
69 changes: 32 additions & 37 deletions internal/ailego/vector_math.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ package ailego
import (
"errors"
"math"

"github.com/gorse-io/xvec/internal/floats"
)

var (
Expand All @@ -26,38 +28,26 @@ var (
ErrInvalidSparseOrder = errors.New("ailego: sparse indices must be strictly increasing")
)

// L2Squared computes squared Euclidean distance using float64 accumulation and
// returns the float32 score used by indexes.
// L2Squared computes squared Euclidean distance using float32 accumulation.
func L2Squared(left, right []float32) (float32, error) {
if err := validateDensePair(left, right); err != nil {
return 0, err
}
var sum float64
for index, leftValue := range left {
rightValue := right[index]
if !finite32(leftValue) || !finite32(rightValue) {
return 0, ErrNonFiniteVector
}
difference := float64(leftValue) - float64(rightValue)
sum += difference * difference
if err := validateDenseFinite(left, right); err != nil {
return 0, err
}
return finiteScore(sum)
return finiteScore(float64(floats.L2Squared(left, right)))
}

// InnerProduct computes the dot-product similarity. Higher scores are better.
func InnerProduct(left, right []float32) (float32, error) {
if err := validateDensePair(left, right); err != nil {
return 0, err
}
var sum float64
for index, leftValue := range left {
rightValue := right[index]
if !finite32(leftValue) || !finite32(rightValue) {
return 0, ErrNonFiniteVector
}
sum += float64(leftValue) * float64(rightValue)
if err := validateDenseFinite(left, right); err != nil {
return 0, err
}
return finiteScore(sum)
return finiteScore(float64(floats.InnerProduct(left, right)))
}

// CosineDistance computes 1-cos(left,right). Lower scores are better. Two zero
Expand All @@ -66,19 +56,25 @@ func CosineDistance(left, right []float32) (float32, error) {
if err := validateDensePair(left, right); err != nil {
return 0, err
}
inner, leftNorm, rightNorm, err := denseProducts(left, right)
if err != nil {
if err := validateDenseFinite(left, right); err != nil {
return 0, err
}
return finiteScore(float64(cosineDistance(left, right)))
}

func cosineDistance(left, right []float32) float32 {
inner, leftNorm, rightNorm := floats.DotNorms(left, right)
if leftNorm == 0 && rightNorm == 0 {
return 0, nil
return 0
}
if leftNorm == 0 || rightNorm == 0 {
return 1, nil
return 1
}
cosine := inner / math.Sqrt(leftNorm*rightNorm)
leftMagnitude := float32(math.Sqrt(float64(leftNorm)))
rightMagnitude := float32(math.Sqrt(float64(rightNorm)))
cosine := inner / (leftMagnitude * rightMagnitude)
cosine = min(1, max(-1, cosine))
return finiteScore(1 - cosine)
return 1 - cosine
}

// MIPSL2Squared computes the baseline localized-spherical MIPS-to-L2
Expand All @@ -88,15 +84,19 @@ func MIPSL2Squared(left, right []float32) (float32, error) {
if err := validateDensePair(left, right); err != nil {
return 0, err
}
inner, leftNorm, rightNorm, err := denseProducts(left, right)
if err != nil {
if err := validateDenseFinite(left, right); err != nil {
return 0, err
}
return finiteScore(float64(mipsL2Squared(left, right)))
}

func mipsL2Squared(left, right []float32) float32 {
inner, leftNorm, rightNorm := floats.DotNorms(left, right)
denominator := max(leftNorm, rightNorm)
if denominator == 0 {
return 0, nil
return 0
}
return finiteScore(2 - 2*inner/denominator)
return 2 - 2*inner/denominator
}

// SparseInnerProduct computes the dot product of canonical sparse vectors.
Expand Down Expand Up @@ -139,19 +139,14 @@ func validateDensePair(left, right []float32) error {
return nil
}

func denseProducts(left, right []float32) (inner, leftNorm, rightNorm float64, err error) {
func validateDenseFinite(left, right []float32) error {
for index, leftValue := range left {
rightValue := right[index]
if !finite32(leftValue) || !finite32(rightValue) {
return 0, 0, 0, ErrNonFiniteVector
return ErrNonFiniteVector
}
leftFloat := float64(leftValue)
rightFloat := float64(rightValue)
inner += leftFloat * rightFloat
leftNorm += leftFloat * leftFloat
rightNorm += rightFloat * rightFloat
}
return inner, leftNorm, rightNorm, nil
return nil
}

func validateSparse(indices []uint32, values []float32) error {
Expand Down
43 changes: 43 additions & 0 deletions internal/ailego/vector_math_prevalidated.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2026-present the xvec project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ailego

import "github.com/gorse-io/xvec/internal/floats"

// DenseDistance computes a score for two already validated dense vectors.
// Callers must guarantee equal, non-zero dimensions and finite components.
type DenseDistance func(left, right []float32) (float32, error)

// L2SquaredPrevalidated computes squared Euclidean distance without validating
// its inputs. It is intended for index hot paths whose storage boundary has
// already validated every vector.
func L2SquaredPrevalidated(left, right []float32) (float32, error) {
return finiteScore(float64(floats.L2Squared(left, right)))
}

// InnerProductPrevalidated computes inner product without validating inputs.
func InnerProductPrevalidated(left, right []float32) (float32, error) {
return finiteScore(float64(floats.InnerProduct(left, right)))
}

// CosineDistancePrevalidated computes cosine distance without validating inputs.
func CosineDistancePrevalidated(left, right []float32) (float32, error) {
return finiteScore(float64(cosineDistance(left, right)))
}

// MIPSL2SquaredPrevalidated computes MIPS-to-L2 distance without validating inputs.
func MIPSL2SquaredPrevalidated(left, right []float32) (float32, error) {
return finiteScore(float64(mipsL2Squared(left, right)))
}
110 changes: 0 additions & 110 deletions internal/ailego/vector_math_scalar.go

This file was deleted.

Loading
Loading