package fit
import "go-hep.org/x/hep/fit"
Package fit provides functions to fit data.
Index ¶
- func Curve1D(f Func1D, settings *optimize.Settings, m optimize.Method) (*optimize.Result, error)
- func CurveND(f FuncND, settings *optimize.Settings, m optimize.Method) (*optimize.Result, error)
- func H1D(h *hbook.H1D, f Func1D, settings *optimize.Settings, m optimize.Method) (*optimize.Result, error)
- func Poly(xs, ys []float64, degree int) ([]float64, error)
- type Func1D
- type FuncND
Examples ¶
- Curve1D (Exponential)
- Curve1D (Gaussian)
- Curve1D (Hessian)
- Curve1D (Poly)
- Curve1D (Powerlaw)
- CurveND (Plane)
- H1D (Gaussian)
- Poly
Functions ¶
func Curve1D ¶
Curve1D returns the result of a non-linear least squares to fit
a function f to the underlying data with method m.
Code:
Code:
Code:
Output: Code:
Code:
Example (Exponential)¶
{
const (
a = 0.3
b = 0.1
ndf = 2.0
)
xdata, ydata, err := readXY("testdata/exp-data.txt")
if err != nil {
log.Fatal(err)
}
exp := func(x, a, b float64) float64 {
return math.Exp(a*x + b)
}
res, err := fit.Curve1D(
fit.Func1D{
F: func(x float64, ps []float64) float64 {
return exp(x, ps[0], ps[1])
},
X: xdata,
Y: ydata,
N: 2,
},
nil, &optimize.NelderMead{},
)
if err != nil {
log.Fatal(err)
}
if err := res.Status.Err(); err != nil {
log.Fatal(err)
}
if got, want := res.X, []float64{a, b}; !floats.EqualApprox(got, want, 0.1) {
log.Fatalf("got= %v\nwant=%v\n", got, want)
}
{
p := hplot.New()
p.X.Label.Text = "exp(a*x+b)"
p.Y.Label.Text = "y-data"
p.Y.Min = 0
p.Y.Max = 5
p.X.Min = 0
p.X.Max = 5
s := hplot.NewS2D(hplot.ZipXY(xdata, ydata))
s.Color = color.RGBA{0, 0, 255, 255}
p.Add(s)
f := plotter.NewFunction(func(x float64) float64 {
return exp(x, res.X[0], res.X[1])
})
f.Color = color.RGBA{255, 0, 0, 255}
f.Samples = 1000
p.Add(f)
p.Add(plotter.NewGrid())
err := p.Save(20*vg.Centimeter, -1, "testdata/exp-plot.png")
if err != nil {
log.Fatal(err)
}
}
}
Example (Gaussian)¶
{
var (
cst = 3.0
mean = 30.0
sigma = 20.0
want = []float64{cst, mean, sigma}
)
xdata, ydata, err := readXY("testdata/gauss-data.txt")
if err != nil {
log.Fatal(err)
}
gauss := func(x, cst, mu, sigma float64) float64 {
v := (x - mu)
return cst * math.Exp(-v*v/sigma)
}
res, err := fit.Curve1D(
fit.Func1D{
F: func(x float64, ps []float64) float64 {
return gauss(x, ps[0], ps[1], ps[2])
},
X: xdata,
Y: ydata,
Ps: []float64{10, 10, 10},
},
nil, &optimize.NelderMead{},
)
if err != nil {
log.Fatal(err)
}
if err := res.Status.Err(); err != nil {
log.Fatal(err)
}
if got := res.X; !floats.EqualApprox(got, want, 1e-3) {
log.Fatalf("got= %v\nwant=%v\n", got, want)
}
{
p := hplot.New()
p.X.Label.Text = "Gauss"
p.Y.Label.Text = "y-data"
s := hplot.NewS2D(hplot.ZipXY(xdata, ydata))
s.Color = color.RGBA{0, 0, 255, 255}
p.Add(s)
f := plotter.NewFunction(func(x float64) float64 {
return gauss(x, res.X[0], res.X[1], res.X[2])
})
f.Color = color.RGBA{255, 0, 0, 255}
f.Samples = 1000
p.Add(f)
p.Add(plotter.NewGrid())
err := p.Save(20*vg.Centimeter, -1, "testdata/gauss-plot.png")
if err != nil {
log.Fatal(err)
}
}
}
Example (Hessian)¶
{
var (
cst = 3.0
mean = 30.0
sigma = 20.0
want = []float64{cst, mean, sigma}
)
xdata, ydata, err := readXY("testdata/gauss-data.txt")
if err != nil {
log.Fatal(err)
}
// use a small sample
xdata = xdata[:min(25, len(xdata))]
ydata = ydata[:min(25, len(ydata))]
gauss := func(x, cst, mu, sigma float64) float64 {
v := (x - mu)
return cst * math.Exp(-v*v/sigma)
}
f1d := fit.Func1D{
F: func(x float64, ps []float64) float64 {
return gauss(x, ps[0], ps[1], ps[2])
},
X: xdata,
Y: ydata,
Ps: []float64{10, 10, 10},
}
res, err := fit.Curve1D(f1d, nil, &optimize.NelderMead{})
if err != nil {
log.Fatal(err)
}
if err := res.Status.Err(); err != nil {
log.Fatal(err)
}
if got := res.X; !floats.EqualApprox(got, want, 1e-3) {
log.Fatalf("got= %v\nwant=%v\n", got, want)
}
inv := mat.NewSymDense(len(res.Location.X), nil)
f1d.Hessian(inv, res.Location.X)
// fmt.Printf("hessian: %1.2e\n", mat.Formatted(inv, mat.Prefix(" ")))
popt := res.Location.X
pcov := mat.NewDense(len(popt), len(popt), nil)
{
var chol mat.Cholesky
if ok := chol.Factorize(inv); !ok {
log.Fatalf("cov-matrix not positive semi-definite")
}
err := chol.InverseTo(inv)
if err != nil {
log.Fatalf("could not inverse matrix: %+v", err)
}
pcov.Copy(inv)
}
// compute goodness-of-fit.
gof := newGoF(f1d.X, f1d.Y, popt, func(x float64) float64 {
return f1d.F(x, popt)
})
pcov.Scale(gof.SSE/float64(len(f1d.X)-len(popt)), pcov)
// fmt.Printf("pcov: %1.2e\n", mat.Formatted(pcov, mat.Prefix(" ")))
var (
n = float64(len(f1d.X)) // number of data points
ndf = n - float64(len(popt)) // number of degrees of freedom
t = distuv.StudentsT{
Mu: 0,
Sigma: 1,
Nu: ndf,
}.Quantile(0.5 * (1 + 0.95))
)
for i, p := range popt {
sigma := math.Sqrt(pcov.At(i, i))
fmt.Printf("c%d: %1.5e [%1.5e, %1.5e] -- truth: %g\n", i, p, p-sigma*t, p+sigma*t, want[i])
}
// Output:
//c0: 2.99999e+00 [2.99999e+00, 3.00000e+00] -- truth: 3
//c1: 3.00000e+01 [3.00000e+01, 3.00000e+01] -- truth: 30
//c2: 2.00000e+01 [2.00000e+01, 2.00000e+01] -- truth: 20
}
c0: 2.99999e+00 [2.99999e+00, 3.00000e+00] -- truth: 3
c1: 3.00000e+01 [3.00000e+01, 3.00000e+01] -- truth: 30
c2: 2.00000e+01 [2.00000e+01, 2.00000e+01] -- truth: 20
Example (Poly)¶
{
var (
a = 1.0
b = 2.0
ps = []float64{a, b}
want = []float64{1.38592513, 1.98485122} // from scipy.curve_fit
)
poly := func(x float64, ps []float64) float64 {
return ps[0] + ps[1]*x*x
}
xdata, ydata := genXY(100, poly, ps, -10, 10)
res, err := fit.Curve1D(
fit.Func1D{
F: poly,
X: xdata,
Y: ydata,
Ps: []float64{1, 1},
},
nil, &optimize.NelderMead{},
)
if err != nil {
log.Fatal(err)
}
if err := res.Status.Err(); err != nil {
log.Fatal(err)
}
if got := res.X; !floats.EqualApprox(got, want, 1e-6) {
log.Fatalf("got= %v\nwant=%v\n", got, want)
}
{
p := hplot.New()
p.X.Label.Text = "f(x) = a + b*x*x"
p.Y.Label.Text = "y-data"
p.X.Min = -10
p.X.Max = +10
p.Y.Min = 0
p.Y.Max = 220
s := hplot.NewS2D(hplot.ZipXY(xdata, ydata))
s.Color = color.RGBA{0, 0, 255, 255}
p.Add(s)
f := plotter.NewFunction(func(x float64) float64 {
return poly(x, res.X)
})
f.Color = color.RGBA{255, 0, 0, 255}
f.Samples = 1000
p.Add(f)
p.Add(plotter.NewGrid())
err := p.Save(20*vg.Centimeter, -1, "testdata/poly-plot.png")
if err != nil {
log.Fatal(err)
}
}
}
Example (Powerlaw)¶
{
var (
amp = 11.021171432949746
index = -2.027389113217428
want = []float64{amp, index}
)
xdata, ydata, yerrs, err := readXYerr("testdata/powerlaw-data.txt")
if err != nil {
log.Fatal(err)
}
plaw := func(x, amp, index float64) float64 {
return amp * math.Pow(x, index)
}
res, err := fit.Curve1D(
fit.Func1D{
F: func(x float64, ps []float64) float64 {
return plaw(x, ps[0], ps[1])
},
X: xdata,
Y: ydata,
Err: yerrs,
Ps: []float64{1, 1},
},
nil, &optimize.NelderMead{},
)
if err != nil {
log.Fatal(err)
}
if err := res.Status.Err(); err != nil {
log.Fatal(err)
}
if got := res.X; !floats.EqualApprox(got, want, 1e-3) {
log.Fatalf("got= %v\nwant=%v\n", got, want)
}
{
p := hplot.New()
p.X.Label.Text = "f(x) = a * x^b"
p.Y.Label.Text = "y-data"
p.X.Min = 0
p.X.Max = 10
p.Y.Min = 0
p.Y.Max = 10
pts := make([]hbook.Point2D, len(xdata))
for i := range pts {
pts[i].X = xdata[i]
pts[i].Y = ydata[i]
pts[i].ErrY.Min = 0.5 * yerrs[i]
pts[i].ErrY.Max = 0.5 * yerrs[i]
}
s := hplot.NewS2D(hbook.NewS2D(pts...), hplot.WithYErrBars(true))
s.Color = color.RGBA{0, 0, 255, 255}
p.Add(s)
f := plotter.NewFunction(func(x float64) float64 {
return plaw(x, res.X[0], res.X[1])
})
f.Color = color.RGBA{255, 0, 0, 255}
f.Samples = 1000
p.Add(f)
p.Add(plotter.NewGrid())
err := p.Save(20*vg.Centimeter, -1, "testdata/powerlaw-plot.png")
if err != nil {
log.Fatal(err)
}
}
}
func CurveND ¶
CurveND returns the result of a non-linear least squares to fit
a function f to the underlying data with method m, where there
is more than one independent variable.
Code:
Example (Plane)¶
{
var (
m1 = 0.3
m2 = 0.1
c = 0.2
ps = []float64{m1, m2, c}
n0 = 10
n1 = 10
x0min = -1.
x0max = 1.
x1min = -1.
x1max = 1.
)
plane := func(x, ps []float64) float64 {
return ps[0]*x[0] + ps[1]*x[1] + ps[2]
}
xData, yData := genData2D(n0, n1, plane, ps, x0min, x0max, x1min, x1max)
res, err := fit.CurveND(
fit.FuncND{
F: func(x []float64, ps []float64) float64 {
return plane(x, ps)
},
X: xData,
Y: yData,
N: 3,
},
nil, &optimize.NelderMead{},
)
if err != nil {
log.Fatal(err)
}
if err := res.Status.Err(); err != nil {
log.Fatal(err)
}
if got, want := res.X, []float64{m1, m2, c}; !floats.EqualApprox(got, want, 0.1) {
log.Fatalf("got= %v\nwant=%v\n", got, want)
}
{
// slicing for a particular x0 value to plot y as a function of x1,
// to visualise how well the fit is working for a given x0.
x0Selection := 8
if 0 > x0Selection || x0Selection > n0 {
log.Fatalf("x0 slice, %d, is not in valid range [0 - %d]", x0Selection, n0)
}
x0SlicePos := x0min + ((x0max-x0min)/float64(n0))*float64(x0Selection)
var x1Slice []float64
var ySlice []float64
for i := range xData {
if xData[i][0] == x0SlicePos {
x1Slice = append(x1Slice, xData[i][1])
ySlice = append(ySlice, yData[i])
}
}
p := hplot.New()
p.Title.Text = fmt.Sprintf("Slice of plane at x0 = %.2f", x0SlicePos)
p.X.Label.Text = "x1"
p.Y.Label.Text = "y"
p.Y.Min = x1min
p.Y.Max = x1max
p.X.Min = x0min
p.X.Max = x0max
s := hplot.NewS2D(hplot.ZipXY(x1Slice, ySlice))
s.Color = color.RGBA{B: 255, A: 255}
p.Add(s)
shiftLine := func(x, m, c, mxOtherAxis float64) float64 {
return m*x + c + mxOtherAxis
}
f := plotter.NewFunction(func(x float64) float64 {
return shiftLine(x, res.X[1], res.X[2], res.X[0]*x0SlicePos)
})
f.Color = color.RGBA{R: 255, A: 255}
f.Samples = 1000
p.Add(f)
p.Add(plotter.NewGrid())
err := p.Save(20*vg.Centimeter, -1, "testdata/2d-plane-plot.png")
if err != nil {
log.Fatal(err)
}
}
}
func H1D ¶
func H1D(h *hbook.H1D, f Func1D, settings *optimize.Settings, m optimize.Method) (*optimize.Result, error)
H1D returns the fit of histogram h with function f and optimization method m.
Only bins with at least an entry are considered for the fit.
In case settings is nil, the optimize.DefaultSettingsLocal is used.
In case m is nil, the same default optimization method than for Curve1D is used.
Code:
Example (Gaussian)¶
{
var (
mean = 2.0
sigma = 4.0
// Values from gonum/optimize:
want = []float64{450.56454241860934, 2.0146898541006277, 3.9613086671267466}
// Values from ROOT:
// want = []float64{4.53720e+02, 1.93218e+00, 3.93188e+00}
)
const npoints = 10000
// Create a normal distribution.
dist := distuv.Normal{
Mu: mean,
Sigma: sigma,
Src: rand.New(rand.NewPCG(0, 0)),
}
// Draw some random values from the standard
// normal distribution.
hist := hbook.NewH1D(100, -20, +25)
for range npoints {
v := dist.Rand()
hist.Fill(v, 1)
}
gauss := func(x, cst, mu, sigma float64) float64 {
v := (x - mu) / sigma
return cst * math.Exp(-0.5*v*v)
}
res, err := fit.H1D(
hist,
fit.Func1D{
F: func(x float64, ps []float64) float64 {
return gauss(x, ps[0], ps[1], ps[2])
},
N: len(want),
},
nil, &optimize.NelderMead{},
)
if err != nil {
log.Fatal(err)
}
if err := res.Status.Err(); err != nil {
log.Fatal(err)
}
if got := res.X; !floats.EqualApprox(got, want, 1e-3) {
log.Fatalf("got= %v\nwant=%v\n", got, want)
}
{
p := hplot.New()
p.X.Label.Text = "f(x) = cst * exp(-0.5 * ((x-mu)/sigma)^2)"
p.Y.Label.Text = "y-data"
p.Y.Min = 0
h := hplot.NewH1D(hist)
h.Color = color.RGBA{0, 0, 255, 255}
p.Add(h)
f := plotter.NewFunction(func(x float64) float64 {
return gauss(x, res.X[0], res.X[1], res.X[2])
})
f.Color = color.RGBA{255, 0, 0, 255}
f.Samples = 1000
p.Add(f)
p.Add(plotter.NewGrid())
err := p.Save(20*vg.Centimeter, -1, "testdata/h1d-gauss-plot.png")
if err != nil {
log.Fatal(err)
}
}
}
func Poly ¶
Poly fits a polynomial p of degree `degree` to points (x, y):
p(x) = p[0] * x^deg + ... + p[deg]
Example¶
Code:play
package main import ( "fmt" "log" "go-hep.org/x/hep/fit" ) func main() { var ( xs = []float64{0.0, 1.0, 2.0, 3.0, +4.0, +5.0} ys = []float64{0.0, 0.8, 0.9, 0.1, -0.8, -1.0} degree = 3 zs, err = fit.Poly(xs, ys, degree) ) if err != nil { log.Fatalf("could not fit polynomial: %v", err) } fmt.Printf("z = %+.5f\n", zs) }
Output:
z = [+0.08704 -0.81349 +1.69312 -0.03968]
Types ¶
type Func1D ¶
type Func1D struct { // F is the function to minimize. // ps is the slice of parameters to optimize during the fit. F func(x float64, ps []float64) float64 // N is the number of parameters to optimize during the fit. // If N is 0, Ps must not be nil. N int // Ps is the initial values for the parameters. // If Ps is nil, the set of initial parameters values is a slice of // length N filled with zeros. Ps []float64 X []float64 Y []float64 Err []float64 // contains filtered or unexported fields }
Func1D describes a 1D function to fit some data.
func (*Func1D) Hessian ¶
Hessian computes the hessian matrix at the provided x point.
type FuncND ¶
type FuncND struct { // F is the function to minimize. // ps is the slice of parameters to optimize during the fit. // x is the slice of independent variables. F func(x []float64, ps []float64) float64 // N is the number of parameters to optimize during the fit. // If N is 0, Ps must not be nil. N int // Ps is the initial values for the parameters. // If Ps is nil, the set of initial parameters values is a slice of // length N filled with zeros. Ps []float64 // X is the multidimensional slice of the independent variables, // it must be structured so that the X[i] is a list of values for the // independent variables that corresponds to a single Y value. // In other words, the sequence of rows must correspond to the sequence // of independent variable values. X [][]float64 Y []float64 Err []float64 // contains filtered or unexported fields }
FuncND describes a multivariate function F(x0, x1... xn; p0, p1... pn) for which the parameters ps can be found with a fit.
Source Files ¶
curve1d.go curve_nd.go fit.go hist.go poly.go
- Version
- v0.37.1 (latest)
- Published
- Jun 3, 2025
- Platform
- linux/amd64
- Imports
- 6 packages
- Last checked
- 4 hours ago –
Tools for package owners.