From b546d13fc47209e3a555539160b3cd79b5477cb7 Mon Sep 17 00:00:00 2001 From: "Michael J. Roberts" <84131395+michealroberts@users.noreply.github.com> Date: Sun, 17 Dec 2023 15:32:29 +0000 Subject: [PATCH] feat: Added NewHyperbolicVCurve() *VCurveParams to vcurve IRIS module. Includes associated test suite for expected output and API struct definitions. feat: Added NewHyperbolicVCurve() *VCurveParams to vcurve IRIS module. Includes associated test suite for expected output and API struct definitions. --- go.mod | 7 +++++ go.sum | 6 ++++ pkg/vcurve/vcurve.go | 44 ++++++++++++++++++++++++++++ pkg/vcurve/vcurve_test.go | 60 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 go.sum create mode 100644 pkg/vcurve/vcurve_test.go diff --git a/go.mod b/go.mod index 652a33a..cd40fbb 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,10 @@ module github.com/observerly/iris go 1.19 + +require gonum.org/v1/gonum v0.14.0 + +require ( + golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect + golang.org/x/tools v0.7.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..61dc257 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= +gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= diff --git a/pkg/vcurve/vcurve.go b/pkg/vcurve/vcurve.go index 6be55db..ebaf932 100644 --- a/pkg/vcurve/vcurve.go +++ b/pkg/vcurve/vcurve.go @@ -1,5 +1,12 @@ package vcurve +import ( + "math" + + "gonum.org/v1/gonum/floats" + "gonum.org/v1/gonum/stat" +) + // Point is a data point with x and y coordinates. type Point struct { x float64 @@ -20,3 +27,40 @@ type VCurveParams struct { x []float64 y []float64 } + +/* +NewHyperbolicVCurve + +Creates a new VCurve object ready for applying the Levenberg-Marquardt iterative optimization technique. + +The VCurve object is initialized with the data points, and initial guesses for the parameters are calculated from the input data. +*/ +func NewHyperbolicVCurve(data VCurve) *VCurveParams { + // Preallocate slices with the exact required capacity + dataX := make([]float64, 0, len(data.Points)) + dataY := make([]float64, 0, len(data.Points)) + + // A single loop to populate the slices + for _, point := range data.Points { + dataX = append(dataX, point.x) + dataY = append(dataY, point.y) + } + + // Get the initial guess for the parameter, for A, we take the mean value of the yData: + A := stat.Mean(dataY, nil) + + // Get the initial guess for B, for B, we take the min value of the yData: + B := floats.Min(dataY) + + // Get the initial guess for C, for C, we take the mean value of the xData: + C := stat.Mean(dataX, nil) + + return &VCurveParams{ + A: math.Round(A), + B: B, + C: C, + D: 0, + x: dataX, + y: dataY, + } +} diff --git a/pkg/vcurve/vcurve_test.go b/pkg/vcurve/vcurve_test.go new file mode 100644 index 0000000..d8fc11c --- /dev/null +++ b/pkg/vcurve/vcurve_test.go @@ -0,0 +1,60 @@ +package vcurve + +import ( + "testing" +) + +var ( + points = []Point{ + {x: 29000, y: 40.5}, + {x: 29100, y: 36.2}, + {x: 29200, y: 31.4}, + {x: 29300, y: 28.6}, + {x: 29400, y: 23.1}, + {x: 29500, y: 21.2}, + {x: 29600, y: 16.6}, + {x: 29700, y: 13.7}, + {x: 29800, y: 6.21}, + {x: 29900, y: 4.21}, + {x: 30000, y: 3.98}, + {x: 30100, y: 4.01}, + {x: 30200, y: 4.85}, + {x: 30300, y: 11.1}, + {x: 30400, y: 15.3}, + {x: 30500, y: 22.1}, + {x: 30600, y: 21.9}, + {x: 30700, y: 27.4}, + {x: 30800, y: 32.1}, + {x: 30900, y: 36.5}, + {x: 31000, y: 39.7}, + } +) + +func TestNewHyperbolicVCurve(t *testing.T) { + v := NewHyperbolicVCurve(VCurve{ + Points: points, + }) + + // Deconstruct the VCurveParams struct + a, b, c, d := v.A, v.B, v.C, v.D + + // Expect the initial guess for A to be the mean value of the yData: + if a != 21 { + t.Errorf("A should be 21, but got %v", a) + } + + // Expect the initial guess for B to be the min value of the yData: + if b != 3.98 { + t.Errorf("B should be 3.98, but got %v", b) + } + + // Expect the initial guess for C to be the mean value of the xData: + if c != 30000 { + t.Errorf("C should be 30000, but got %v", c) + } + + // Expect the initial guess for D to be 0: + if d != 0 { + t.Errorf("D should be 0, but got %v", d) + } +}