go-hep.org/x/hep@v0.38.1/fit/README.md (about)

     1  # fit
     2  
     3  [![GoDoc](https://godoc.org/go-hep.org/x/hep/fit?status.svg)](https://godoc.org/go-hep.org/x/hep/fit)
     4  
     5  `fit` is a WIP package to provide easy fitting models and curve fitting functions.
     6  
     7  ## H1D
     8  
     9  ### Fit a gaussian
    10  
    11  ![h1d-gaussian-example](https://codeberg.org/go-hep/hep/raw/branch/main/fit/testdata/h1d-gauss-plot_golden.png)
    12  
    13  [embedmd]:# (hist_example_test.go go /func ExampleH1D_gaussian/ /\n}/)
    14  ```go
    15  func ExampleH1D_gaussian() {
    16  	var (
    17  		mean  = 2.0
    18  		sigma = 4.0
    19  		// Values from gonum/optimize:
    20  		want = []float64{450.56454241860934, 2.0146898541006277, 3.9613086671267466}
    21  		// Values from ROOT:
    22  		// want  = []float64{4.53720e+02, 1.93218e+00, 3.93188e+00}
    23  	)
    24  
    25  	const npoints = 10000
    26  
    27  	// Create a normal distribution.
    28  	dist := distuv.Normal{
    29  		Mu:    mean,
    30  		Sigma: sigma,
    31  		Src:   rand.New(rand.NewPCG(0, 0)),
    32  	}
    33  
    34  	// Draw some random values from the standard
    35  	// normal distribution.
    36  	hist := hbook.NewH1D(100, -20, +25)
    37  	for range npoints {
    38  		v := dist.Rand()
    39  		hist.Fill(v, 1)
    40  	}
    41  
    42  	gauss := func(x, cst, mu, sigma float64) float64 {
    43  		v := (x - mu) / sigma
    44  		return cst * math.Exp(-0.5*v*v)
    45  	}
    46  
    47  	res, err := fit.H1D(
    48  		hist,
    49  		fit.Func1D{
    50  			F: func(x float64, ps []float64) float64 {
    51  				return gauss(x, ps[0], ps[1], ps[2])
    52  			},
    53  			N: len(want),
    54  		},
    55  		nil, &optimize.NelderMead{},
    56  	)
    57  	if err != nil {
    58  		log.Fatal(err)
    59  	}
    60  
    61  	if err := res.Status.Err(); err != nil {
    62  		log.Fatal(err)
    63  	}
    64  	if got := res.X; !floats.EqualApprox(got, want, 1e-3) {
    65  		log.Fatalf("got= %v\nwant=%v\n", got, want)
    66  	}
    67  
    68  	{
    69  		p := hplot.New()
    70  		p.X.Label.Text = "f(x) = cst * exp(-0.5 * ((x-mu)/sigma)^2)"
    71  		p.Y.Label.Text = "y-data"
    72  		p.Y.Min = 0
    73  
    74  		h := hplot.NewH1D(hist)
    75  		h.Color = color.RGBA{0, 0, 255, 255}
    76  		p.Add(h)
    77  
    78  		f := plotter.NewFunction(func(x float64) float64 {
    79  			return gauss(x, res.X[0], res.X[1], res.X[2])
    80  		})
    81  		f.Color = color.RGBA{255, 0, 0, 255}
    82  		f.Samples = 1000
    83  		p.Add(f)
    84  
    85  		p.Add(plotter.NewGrid())
    86  
    87  		err := p.Save(20*vg.Centimeter, -1, "testdata/h1d-gauss-plot.png")
    88  		if err != nil {
    89  			log.Fatal(err)
    90  		}
    91  	}
    92  }
    93  ```
    94  
    95  ## Curve1D
    96  
    97  ### Fit a gaussian
    98  
    99  ![func1d-gaussian-example](https://codeberg.org/go-hep/hep/raw/branch/main/fit/testdata/gauss-plot_golden.png)
   100  
   101  [embedmd]:# (curve1d_example_test.go go /func ExampleCurve1D_gaussian/ /\n}/)
   102  ```go
   103  func ExampleCurve1D_gaussian() {
   104  	var (
   105  		cst   = 3.0
   106  		mean  = 30.0
   107  		sigma = 20.0
   108  		want  = []float64{cst, mean, sigma}
   109  	)
   110  
   111  	xdata, ydata, err := readXY("testdata/gauss-data.txt")
   112  	if err != nil {
   113  		log.Fatal(err)
   114  	}
   115  
   116  	gauss := func(x, cst, mu, sigma float64) float64 {
   117  		v := (x - mu)
   118  		return cst * math.Exp(-v*v/sigma)
   119  	}
   120  
   121  	res, err := fit.Curve1D(
   122  		fit.Func1D{
   123  			F: func(x float64, ps []float64) float64 {
   124  				return gauss(x, ps[0], ps[1], ps[2])
   125  			},
   126  			X:  xdata,
   127  			Y:  ydata,
   128  			Ps: []float64{10, 10, 10},
   129  		},
   130  		nil, &optimize.NelderMead{},
   131  	)
   132  	if err != nil {
   133  		log.Fatal(err)
   134  	}
   135  
   136  	if err := res.Status.Err(); err != nil {
   137  		log.Fatal(err)
   138  	}
   139  	if got := res.X; !floats.EqualApprox(got, want, 1e-3) {
   140  		log.Fatalf("got= %v\nwant=%v\n", got, want)
   141  	}
   142  
   143  	{
   144  		p := hplot.New()
   145  		p.X.Label.Text = "Gauss"
   146  		p.Y.Label.Text = "y-data"
   147  
   148  		s := hplot.NewS2D(hplot.ZipXY(xdata, ydata))
   149  		s.Color = color.RGBA{0, 0, 255, 255}
   150  		p.Add(s)
   151  
   152  		f := plotter.NewFunction(func(x float64) float64 {
   153  			return gauss(x, res.X[0], res.X[1], res.X[2])
   154  		})
   155  		f.Color = color.RGBA{255, 0, 0, 255}
   156  		f.Samples = 1000
   157  		p.Add(f)
   158  
   159  		p.Add(plotter.NewGrid())
   160  
   161  		err := p.Save(20*vg.Centimeter, -1, "testdata/gauss-plot.png")
   162  		if err != nil {
   163  			log.Fatal(err)
   164  		}
   165  	}
   166  }
   167  ```
   168  
   169  ### Fit a powerlaw (with Y-errors)
   170  
   171  ![func1d-powerlaw-example](https://codeberg.org/go-hep/hep/raw/branch/main/fit/testdata/powerlaw-plot_golden.png)
   172  
   173  [embedmd]:# (curve1d_example_test.go go /func ExampleCurve1D_powerlaw/ /\n}/)
   174  ```go
   175  func ExampleCurve1D_powerlaw() {
   176  	var (
   177  		amp   = 11.021171432949746
   178  		index = -2.027389113217428
   179  		want  = []float64{amp, index}
   180  	)
   181  
   182  	xdata, ydata, yerrs, err := readXYerr("testdata/powerlaw-data.txt")
   183  	if err != nil {
   184  		log.Fatal(err)
   185  	}
   186  
   187  	plaw := func(x, amp, index float64) float64 {
   188  		return amp * math.Pow(x, index)
   189  	}
   190  
   191  	res, err := fit.Curve1D(
   192  		fit.Func1D{
   193  			F: func(x float64, ps []float64) float64 {
   194  				return plaw(x, ps[0], ps[1])
   195  			},
   196  			X:   xdata,
   197  			Y:   ydata,
   198  			Err: yerrs,
   199  			Ps:  []float64{1, 1},
   200  		},
   201  		nil, &optimize.NelderMead{},
   202  	)
   203  	if err != nil {
   204  		log.Fatal(err)
   205  	}
   206  
   207  	if err := res.Status.Err(); err != nil {
   208  		log.Fatal(err)
   209  	}
   210  	if got := res.X; !floats.EqualApprox(got, want, 1e-3) {
   211  		log.Fatalf("got= %v\nwant=%v\n", got, want)
   212  	}
   213  
   214  	{
   215  		p := hplot.New()
   216  		p.X.Label.Text = "f(x) = a * x^b"
   217  		p.Y.Label.Text = "y-data"
   218  		p.X.Min = 0
   219  		p.X.Max = 10
   220  		p.Y.Min = 0
   221  		p.Y.Max = 10
   222  
   223  		pts := make([]hbook.Point2D, len(xdata))
   224  		for i := range pts {
   225  			pts[i].X = xdata[i]
   226  			pts[i].Y = ydata[i]
   227  			pts[i].ErrY.Min = 0.5 * yerrs[i]
   228  			pts[i].ErrY.Max = 0.5 * yerrs[i]
   229  		}
   230  
   231  		s := hplot.NewS2D(hbook.NewS2D(pts...), hplot.WithYErrBars(true))
   232  		s.Color = color.RGBA{0, 0, 255, 255}
   233  		p.Add(s)
   234  
   235  		f := plotter.NewFunction(func(x float64) float64 {
   236  			return plaw(x, res.X[0], res.X[1])
   237  		})
   238  		f.Color = color.RGBA{255, 0, 0, 255}
   239  		f.Samples = 1000
   240  		p.Add(f)
   241  
   242  		p.Add(plotter.NewGrid())
   243  
   244  		err := p.Save(20*vg.Centimeter, -1, "testdata/powerlaw-plot.png")
   245  		if err != nil {
   246  			log.Fatal(err)
   247  		}
   248  	}
   249  }
   250  ```
   251  
   252  ### Fit an exponential
   253  
   254  ![func1d-exp-example](https://codeberg.org/go-hep/hep/raw/branch/main/fit/testdata/exp-plot_golden.png)
   255  
   256  [embedmd]:# (curve1d_example_test.go go /func ExampleCurve1D_exponential/ /\n}/)
   257  ```go
   258  func ExampleCurve1D_exponential() {
   259  	const (
   260  		a   = 0.3
   261  		b   = 0.1
   262  		ndf = 2.0
   263  	)
   264  
   265  	xdata, ydata, err := readXY("testdata/exp-data.txt")
   266  	if err != nil {
   267  		log.Fatal(err)
   268  	}
   269  
   270  	exp := func(x, a, b float64) float64 {
   271  		return math.Exp(a*x + b)
   272  	}
   273  
   274  	res, err := fit.Curve1D(
   275  		fit.Func1D{
   276  			F: func(x float64, ps []float64) float64 {
   277  				return exp(x, ps[0], ps[1])
   278  			},
   279  			X: xdata,
   280  			Y: ydata,
   281  			N: 2,
   282  		},
   283  		nil, &optimize.NelderMead{},
   284  	)
   285  	if err != nil {
   286  		log.Fatal(err)
   287  	}
   288  
   289  	if err := res.Status.Err(); err != nil {
   290  		log.Fatal(err)
   291  	}
   292  	if got, want := res.X, []float64{a, b}; !floats.EqualApprox(got, want, 0.1) {
   293  		log.Fatalf("got= %v\nwant=%v\n", got, want)
   294  	}
   295  
   296  	{
   297  		p := hplot.New()
   298  		p.X.Label.Text = "exp(a*x+b)"
   299  		p.Y.Label.Text = "y-data"
   300  		p.Y.Min = 0
   301  		p.Y.Max = 5
   302  		p.X.Min = 0
   303  		p.X.Max = 5
   304  
   305  		s := hplot.NewS2D(hplot.ZipXY(xdata, ydata))
   306  		s.Color = color.RGBA{0, 0, 255, 255}
   307  		p.Add(s)
   308  
   309  		f := plotter.NewFunction(func(x float64) float64 {
   310  			return exp(x, res.X[0], res.X[1])
   311  		})
   312  		f.Color = color.RGBA{255, 0, 0, 255}
   313  		f.Samples = 1000
   314  		p.Add(f)
   315  
   316  		p.Add(plotter.NewGrid())
   317  
   318  		err := p.Save(20*vg.Centimeter, -1, "testdata/exp-plot.png")
   319  		if err != nil {
   320  			log.Fatal(err)
   321  		}
   322  	}
   323  }
   324  ```
   325  
   326  ### Fit a polynomial
   327  
   328  ![func1d-poly-example](https://codeberg.org/go-hep/hep/raw/branch/main/fit/testdata/poly-plot_golden.png)
   329  
   330  [embedmd]:# (curve1d_example_test.go go /func ExampleCurve1D_poly/ /\n}/)
   331  ```go
   332  func ExampleCurve1D_poly() {
   333  	var (
   334  		a    = 1.0
   335  		b    = 2.0
   336  		ps   = []float64{a, b}
   337  		want = []float64{1.38592513, 1.98485122} // from scipy.curve_fit
   338  	)
   339  
   340  	poly := func(x float64, ps []float64) float64 {
   341  		return ps[0] + ps[1]*x*x
   342  	}
   343  
   344  	xdata, ydata := genXY(100, poly, ps, -10, 10)
   345  
   346  	res, err := fit.Curve1D(
   347  		fit.Func1D{
   348  			F:  poly,
   349  			X:  xdata,
   350  			Y:  ydata,
   351  			Ps: []float64{1, 1},
   352  		},
   353  		nil, &optimize.NelderMead{},
   354  	)
   355  	if err != nil {
   356  		log.Fatal(err)
   357  	}
   358  
   359  	if err := res.Status.Err(); err != nil {
   360  		log.Fatal(err)
   361  	}
   362  
   363  	if got := res.X; !floats.EqualApprox(got, want, 1e-6) {
   364  		log.Fatalf("got= %v\nwant=%v\n", got, want)
   365  	}
   366  
   367  	{
   368  		p := hplot.New()
   369  		p.X.Label.Text = "f(x) = a + b*x*x"
   370  		p.Y.Label.Text = "y-data"
   371  		p.X.Min = -10
   372  		p.X.Max = +10
   373  		p.Y.Min = 0
   374  		p.Y.Max = 220
   375  
   376  		s := hplot.NewS2D(hplot.ZipXY(xdata, ydata))
   377  		s.Color = color.RGBA{0, 0, 255, 255}
   378  		p.Add(s)
   379  
   380  		f := plotter.NewFunction(func(x float64) float64 {
   381  			return poly(x, res.X)
   382  		})
   383  		f.Color = color.RGBA{255, 0, 0, 255}
   384  		f.Samples = 1000
   385  		p.Add(f)
   386  
   387  		p.Add(plotter.NewGrid())
   388  
   389  		err := p.Save(20*vg.Centimeter, -1, "testdata/poly-plot.png")
   390  		if err != nil {
   391  			log.Fatal(err)
   392  		}
   393  	}
   394  }
   395  ```
   396  ## Fitting with more than one independent variable (x has more than one dimension)
   397  ### Fit a flat plane
   398  
   399  ![2d-example](https://codeberg.org/go-hep/hep/raw/branch/main/fit/testdata/2d-plane-plot_golden.png)
   400  
   401  [embedmd]:# (curve_nd_example_test.go go /func ExampleCurveND_plane/ /\n}/)
   402  ```go
   403  func ExampleCurveND_plane() {
   404  	var (
   405  		m1    = 0.3
   406  		m2    = 0.1
   407  		c     = 0.2
   408  		ps    = []float64{m1, m2, c}
   409  		n0    = 10
   410  		n1    = 10
   411  		x0min = -1.
   412  		x0max = 1.
   413  		x1min = -1.
   414  		x1max = 1.
   415  	)
   416  
   417  	plane := func(x, ps []float64) float64 {
   418  		return ps[0]*x[0] + ps[1]*x[1] + ps[2]
   419  	}
   420  
   421  	xData, yData := genData2D(n0, n1, plane, ps, x0min, x0max, x1min, x1max)
   422  
   423  	res, err := fit.CurveND(
   424  		fit.FuncND{
   425  			F: func(x []float64, ps []float64) float64 {
   426  				return plane(x, ps)
   427  			},
   428  			X: xData,
   429  			Y: yData,
   430  			N: 3,
   431  		},
   432  		nil, &optimize.NelderMead{},
   433  	)
   434  	if err != nil {
   435  		log.Fatal(err)
   436  	}
   437  
   438  	if err := res.Status.Err(); err != nil {
   439  		log.Fatal(err)
   440  	}
   441  	if got, want := res.X, []float64{m1, m2, c}; !floats.EqualApprox(got, want, 0.1) {
   442  		log.Fatalf("got= %v\nwant=%v\n", got, want)
   443  	}
   444  
   445  	{
   446  		// slicing for a particular x0 value to plot y as a function of x1,
   447  		// to visualise how well the fit is working for a given x0.
   448  		x0Selection := 8
   449  		if 0 > x0Selection || x0Selection > n0 {
   450  			log.Fatalf("x0 slice, %d, is not in valid range [0 - %d]", x0Selection, n0)
   451  		}
   452  		x0SlicePos := x0min + ((x0max-x0min)/float64(n0))*float64(x0Selection)
   453  
   454  		var x1Slice []float64
   455  		var ySlice []float64
   456  
   457  		for i := range xData {
   458  			if xData[i][0] == x0SlicePos {
   459  				x1Slice = append(x1Slice, xData[i][1])
   460  				ySlice = append(ySlice, yData[i])
   461  			}
   462  		}
   463  
   464  		p := hplot.New()
   465  		p.Title.Text = fmt.Sprintf("Slice of plane at x0 = %.2f", x0SlicePos)
   466  		p.X.Label.Text = "x1"
   467  		p.Y.Label.Text = "y"
   468  		p.Y.Min = x1min
   469  		p.Y.Max = x1max
   470  		p.X.Min = x0min
   471  		p.X.Max = x0max
   472  
   473  		s := hplot.NewS2D(hplot.ZipXY(x1Slice, ySlice))
   474  		s.Color = color.RGBA{B: 255, A: 255}
   475  		p.Add(s)
   476  
   477  		shiftLine := func(x, m, c, mxOtherAxis float64) float64 {
   478  			return m*x + c + mxOtherAxis
   479  		}
   480  
   481  		f := plotter.NewFunction(func(x float64) float64 {
   482  			return shiftLine(x, res.X[1], res.X[2], res.X[0]*x0SlicePos)
   483  		})
   484  		f.Color = color.RGBA{R: 255, A: 255}
   485  		f.Samples = 1000
   486  		p.Add(f)
   487  
   488  		p.Add(plotter.NewGrid())
   489  		err := p.Save(20*vg.Centimeter, -1, "testdata/2d-plane-plot.png")
   490  		if err != nil {
   491  			log.Fatal(err)
   492  		}
   493  	}
   494  }
   495  ```