mirror of
https://github.com/photoprism/photoprism.git
synced 2026-01-23 02:24:24 +00:00
CI: Apply Go linter recommendations to remaining "pkg/..." code #5330
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
90ab65a9b0
commit
149f5e5731
116 changed files with 541 additions and 351 deletions
|
|
@ -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]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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++
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
//nolint:revive,staticcheck,gocritic,gosec // estimator retains legacy style and pseudo-random seeding
|
||||
package alg
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue