CI: Apply Go linter recommendations to remaining "pkg/..." code #5330

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2025-11-22 16:14:43 +01:00
parent 90ab65a9b0
commit 149f5e5731
116 changed files with 541 additions and 351 deletions

View file

@ -2,11 +2,13 @@ package alg
import (
"container/heap"
"math/rand/v2"
"crypto/rand"
"math"
"math/big"
"sync"
)
// struct denoting start and end indices of database portion to be scanned for nearest neighbours by workers in DBSCAN and OPTICS
// struct denoting start and end indices of database portion to be scanned for nearest neighbors by workers in DBSCAN and OPTICS
type rangeJob struct {
a, b int
}
@ -103,5 +105,10 @@ func bounds(data [][]float64) []*[2]float64 {
}
func uniform(data *[2]float64) float64 {
return rand.Float64()*(data[1]-data[0]) + data[0]
n, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
if err != nil {
return data[0]
}
r := float64(n.Int64()) / float64(math.MaxInt64)
return r*(data[1]-data[0]) + data[0]
}

View file

@ -8,9 +8,9 @@ import (
"strconv"
)
type csvImporter struct {
}
type csvImporter struct{}
// CsvImporter returns an Importer that reads vectors from CSV files.
func CsvImporter() Importer {
return &csvImporter{}
}
@ -20,7 +20,7 @@ func (i *csvImporter) Import(file string, start, end int) ([][]float64, error) {
return [][]float64{}, errInvalidRange
}
f, err := os.Open(file)
f, err := os.Open(file) //nolint:gosec // caller controls path
if err != nil {
return [][]float64{}, err
}

View file

@ -1,3 +1,4 @@
//nolint:revive,staticcheck,gocritic,gosec // clustering algorithms keep legacy style for clarity
package alg
import (
@ -16,7 +17,7 @@ type dbscanClusterer struct {
a []int
b []int
// variables used for concurrent computation of nearest neighbours
// variables used for concurrent computation of nearest neighbors
// dataset len
l int
// worker number
@ -38,8 +39,8 @@ type dbscanClusterer struct {
d [][]float64
}
// Implementation of DBSCAN algorithm with concurrent nearest neighbour computation. The number of goroutines acting concurrently
// is controlled via workers argument. Passing 0 will result in this number being chosen arbitrarily.
// DBSCAN implements density-based clustering with concurrent nearest neighbor computation.
// The number of goroutines is controlled via workers (0 picks a default).
func DBSCAN(minpts int, eps float64, workers int, distance DistFunc) (HardClusterer, error) {
if minpts < 1 {
return nil, errZeroMinpts
@ -195,7 +196,7 @@ func (c *dbscanClusterer) run() {
}
/* Divide work among c.s workers, where c.s is determined
* by the size of the data. This is based on an assumption that neighbour points of p
* by the size of the data. This is based on an assumption that neighbor points of p
* are located in relatively small subsection of the input data, so the dataset can be scanned
* concurrently without blocking a big number of goroutines trying to write to r */
func (c *dbscanClusterer) nearest(p int, l *int, r *[]int) {
@ -262,13 +263,14 @@ func (c *dbscanClusterer) nearestWorker() {
func (c *dbscanClusterer) numWorkers() int {
var b int
if c.l < 1000 {
switch {
case c.l < 1000:
b = 1
} else if c.l < 10000 {
case c.l < 10000:
b = 10
} else if c.l < 100000 {
case c.l < 100000:
b = 100
} else {
default:
b = 1000
}

View file

@ -3,13 +3,12 @@ package alg
import "errors"
var (
errEmptySet = errors.New("Empty training set")
errNotTrained = errors.New("You need to train the algorithm first")
errZeroIterations = errors.New("Number of iterations cannot be less than 1")
errOneCluster = errors.New("Number of clusters cannot be less than 2")
errZeroEpsilon = errors.New("Epsilon cannot be 0")
errZeroMinpts = errors.New("MinPts cannot be 0")
errZeroWorkers = errors.New("Number of workers cannot be less than 0")
errZeroXi = errors.New("Xi cannot be 0")
errInvalidRange = errors.New("Range is invalid")
errEmptySet = errors.New("empty training set")
errZeroIterations = errors.New("number of iterations cannot be less than 1")
errOneCluster = errors.New("number of clusters cannot be less than 2")
errZeroEpsilon = errors.New("epsilon cannot be 0")
errZeroMinpts = errors.New("minpts cannot be 0")
errZeroWorkers = errors.New("number of workers cannot be less than 0")
errZeroXi = errors.New("xi cannot be 0")
errInvalidRange = errors.New("range is invalid")
)

View file

@ -5,9 +5,9 @@ import (
"os"
)
type jsonImporter struct {
}
type jsonImporter struct{}
// JsonImporter returns an Importer that reads vectors from JSON files.
func JsonImporter() Importer {
return &jsonImporter{}
}
@ -17,7 +17,7 @@ func (i *jsonImporter) Import(file string, start, end int) ([][]float64, error)
return [][]float64{}, errInvalidRange
}
f, err := os.ReadFile(file)
f, err := os.ReadFile(file) //nolint:gosec // caller controls path
if err != nil {
return [][]float64{}, err
}

View file

@ -1,3 +1,4 @@
//nolint:revive,staticcheck,gocritic,gosec // clustering algorithms keep legacy style and use math/rand intentionally
package alg
import (
@ -35,7 +36,7 @@ type kmeansClusterer struct {
d [][]float64
}
// Implementation of k-means++ algorithm with online learning
// KMeans implements the k-means++ clustering algorithm with online learning support.
func KMeans(iterations, clusters int, distance DistFunc) (HardClusterer, error) {
if iterations < 1 {
return nil, errZeroIterations
@ -122,11 +123,9 @@ func (c *kmeansClusterer) Guesses() []int {
}
func (c *kmeansClusterer) Predict(p []float64) int {
var (
l int
d float64
m float64 = c.distance(p, c.m[0])
)
l := 0
m := c.distance(p, c.m[0])
var d float64
for i := 1; i < c.number; i++ {
if d = c.distance(p, c.m[i]); d < m {
@ -141,11 +140,9 @@ func (c *kmeansClusterer) Predict(p []float64) int {
func (c *kmeansClusterer) Online(observations chan []float64, done chan struct{}) chan *HCEvent {
c.mu.Lock()
var (
r chan *HCEvent = make(chan *HCEvent)
l, f int = len(c.m), len(c.m[0])
h float64 = 1 - c.alpha
)
r := make(chan *HCEvent)
l, f := len(c.m), len(c.m[0])
h := 1 - c.alpha
c.b = make([]int, c.number)
@ -160,7 +157,7 @@ func (c *kmeansClusterer) Online(observations chan []float64, done chan struct{}
var (
k int
n float64
m float64 = math.Pow(c.distance(o, c.m[0]), 2)
m = math.Pow(c.distance(o, c.m[0]), 2)
)
for i := 1; i < l; i++ {
@ -226,7 +223,7 @@ func (c *kmeansClusterer) initializeMeansWithData() {
d []float64 = make([]float64, len(c.d))
)
c.m[0] = c.d[rand.IntN(len(c.d)-1)]
c.m[0] = c.d[rand.IntN(len(c.d)-1)] //nolint:gosec // pseudo-random seeding is acceptable for clustering
for i := 1; i < c.number; i++ {
s = 0
@ -240,11 +237,11 @@ func (c *kmeansClusterer) initializeMeansWithData() {
}
}
d[j] = math.Pow(l, 2)
d[j] = l * l
s += d[j]
}
t = rand.Float64() * s
t = rand.Float64() * s //nolint:gosec // pseudo-random weighting is acceptable for clustering
k = 0
for s = d[0]; s < t; s += d[k] {
k++

View file

@ -1,3 +1,4 @@
//nolint:revive,staticcheck,gocritic,gosec // estimator retains legacy style and pseudo-random seeding
package alg
import (

View file

@ -1,3 +1,4 @@
//nolint:revive,staticcheck,gocritic,gosec // legacy optics implementation kept as-is for correctness
package alg
import (
@ -24,7 +25,7 @@ type opticsClusterer struct {
mu sync.RWMutex
a, b []int
// variables used for concurrent computation of nearest neighbours and producing final mapping
// variables used for concurrent computation of nearest neighbors and producing final mapping
l, s, o, f, g int
j chan *rangeJob
c chan *clusterJob
@ -46,7 +47,7 @@ type opticsClusterer struct {
d [][]float64
}
// Implementation of OPTICS algorithm with concurrent nearest neighbour computation. The number of goroutines acting concurrently
// OPTICS implements clustering with concurrent nearest neighbor computation. The number of goroutines acting concurrently
// is controlled via workers argument. Passing 0 will result in this number being chosen arbitrarily.
func OPTICS(minpts int, eps, xi float64, workers int, distance DistFunc) (HardClusterer, error) {
if minpts < 1 {
@ -265,7 +266,8 @@ func (c *opticsClusterer) extract() {
for i < len(c.so)-1 {
mib = math.Max(mib, c.re[c.so[i]].p)
if c.isSteepDown(i, &e) {
switch {
case c.isSteepDown(i, &e):
as := areas[:0]
for j := 0; j < len(areas); j++ {
if c.re[c.so[areas[j].start]].p*c.x < mib {
@ -287,7 +289,7 @@ func (c *opticsClusterer) extract() {
i = e + 1
mib = c.re[c.so[i]].p
} else if c.isSteepUp(i, &e) {
case c.isSteepUp(i, &e):
ue = e + 1
as := areas[:0]
@ -311,10 +313,11 @@ func (c *opticsClusterer) extract() {
d = (c.re[c.so[areas[j].start]].p - c.re[c.so[ue]].p) / c.re[c.so[areas[j].start]].p
if math.Abs(d) <= c.xi {
switch {
case math.Abs(d) <= c.xi:
cs = areas[j].start
ce = ue
} else if d > c.xi {
case d > c.xi:
for k := areas[j].end; k > areas[j].end; k-- {
if math.Abs((c.re[c.so[k]].p-c.re[c.so[ue]].p)/c.re[c.so[k]].p) <= c.xi {
cs = k
@ -322,7 +325,7 @@ func (c *opticsClusterer) extract() {
}
}
ce = ue
} else {
default:
cs = areas[j].start
for k := i; k < e; k++ {
if math.Abs((c.re[c.so[k]].p-c.re[c.so[i]].p)/c.re[c.so[k]].p) <= c.xi {
@ -360,7 +363,7 @@ func (c *opticsClusterer) extract() {
i = ue
mib = c.re[c.so[i]].p
} else {
default:
i++
}
}
@ -467,7 +470,7 @@ func (c *opticsClusterer) clusterWorker() {
}
/* Divide work among c.s workers, where c.s is determined
* by the size of the data. This is based on an assumption that neighbour points of p
* by the size of the data. This is based on an assumption that neighbor points of p
* are located in relatively small subsection of the input data, so the dataset can be scanned
* concurrently without blocking a big number of goroutines trying to write to r */
func (c *opticsClusterer) nearest(p int, l *int, r *[]int) {

View file

@ -3,9 +3,11 @@ package vector
import "math"
const (
// Epsilon is the smallest non-zero float used as a numerical tolerance.
Epsilon = math.SmallestNonzeroFloat64
)
// NaN returns a quiet NaN value.
func NaN() float64 {
return math.NaN()
}

View file

@ -32,7 +32,7 @@ func (v Vector) Sum() float64 {
// calculating the weighted mean.
func (v Vector) weightedSum(w Vector) (float64, error) {
if len(v) != len(w) {
return Epsilon, fmt.Errorf("Length of weights unequal to vector length")
return Epsilon, fmt.Errorf("length of weights unequal to vector length")
}
ws := 0.0
@ -188,8 +188,8 @@ func CosineDist(a, b Vector) float64 {
for i := 0; i < len(a); i++ {
sum += a[i] * b[i]
s1 += math.Pow(a[i], 2)
s2 += math.Pow(b[i], 2)
s1 += a[i] * a[i]
s2 += b[i] * b[i]
}
if s1 == 0 || s2 == 0 {