logbook/review/estimate.go
2025-05-09 14:37:57 -04:00

76 lines
2.2 KiB
Go

package review
import (
"math"
"time"
"gonum.org/v1/gonum/optimize"
)
// PowerLawFunc models weight as a function of reps: w = a * reps^b
func PowerLawFunc(a, b, reps float64) float64 {
return a * math.Pow(reps, b)
}
// WeightedResiduals computes weighted residuals for curve fitting
func WeightedResiduals(params []float64, weight, reps []float64, dates []time.Time, now time.Time, halfLifeDays float64) float64 {
a, b := params[0], params[1]
var sum float64
for i := range weight {
// Exponential time decay weighting
daysAgo := now.Sub(dates[i]).Hours() / 24
weightDecay := math.Exp(-math.Ln2 * daysAgo / halfLifeDays) // Half-life decay
predicted := PowerLawFunc(a, b, reps[i])
residual := weight[i] - predicted
sum += weightDecay * residual * residual
}
return sum
}
// FitPowerLaw fits the power law curve with time weighting
func FitPowerLaw(weight, reps []float64, dates []time.Time, halfLifeDays float64) (a, b float64) {
now := time.Now()
// Initial guess: a = max(weight), b = -0.1
params := []float64{max(weight), -0.1}
problem := optimize.Problem{
Func: func(x []float64) float64 {
return WeightedResiduals(x, weight, reps, dates, now, halfLifeDays)
},
}
result, err := optimize.Minimize(problem, params, nil, nil)
if err != nil {
panic(err)
}
return result.X[0], result.X[1]
}
// max returns the maximum value in a slice
func max(slice []float64) float64 {
m := slice[0]
for _, v := range slice {
if v > m {
m = v
}
}
return m
}
// Estimate1RM estimates the current 1RM (reps=1) using fitted parameters
func Estimate1RM(a, b float64) float64 {
return PowerLawFunc(a, b, 1)
}
// EstimateReps returns the predicted number of reps at a given weight
func EstimateReps(a, b, targetWeight float64) float64 {
// Avoid division by zero or negative exponent issues
if a == 0 || b == 0 {
return 0
}
return math.Pow(targetWeight/a, 1/b)
}
// EstimateMaxWeight returns the predicted max weight for a given number of reps
func EstimateMaxWeight(a, b, nReps float64) float64 {
return a * math.Pow(nReps, b)
}