go-hep.org/x/hep@v0.38.1/hbook/h1d_test.go (about)

     1  // Copyright ©2015 The go-hep Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package hbook
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/gob"
    10  	"fmt"
    11  	"math"
    12  	"os"
    13  	"reflect"
    14  	"runtime"
    15  	"testing"
    16  
    17  	"github.com/google/go-cmp/cmp"
    18  	"gonum.org/v1/gonum/floats/scalar"
    19  	"gonum.org/v1/plot/plotter"
    20  )
    21  
    22  func panics(fn func()) (panicked bool, message string) {
    23  	defer func() {
    24  		r := recover()
    25  		panicked = r != nil
    26  		message = fmt.Sprint(r)
    27  	}()
    28  	fn()
    29  	return
    30  }
    31  
    32  func TestH1D(t *testing.T) {
    33  	h1 := NewH1D(100, 0., 100.)
    34  	if h1 == nil {
    35  		t.Errorf("nil pointer to H1D")
    36  	}
    37  
    38  	h1.Annotation()["name"] = "h1"
    39  
    40  	n := h1.Name()
    41  	if n != "h1" {
    42  		t.Errorf("expected H1D.Name() == %q (got %q)\n",
    43  			n, "h1")
    44  	}
    45  	xmin := h1.XMin()
    46  	if xmin != 0. {
    47  		t.Errorf("expected H1D.Min() == %v (got %v)\n",
    48  			0., xmin,
    49  		)
    50  	}
    51  	xmax := h1.XMax()
    52  	if xmax != 100. {
    53  		t.Errorf("expected H1D.Max() == %v (got %v)\n",
    54  			100., xmax,
    55  		)
    56  	}
    57  	/*
    58  		for idx := 0; idx < nbins; idx++ {
    59  			size := h1.Binning().BinWidth(idx)
    60  			if size != 1. {
    61  				t.Errorf("expected H1D.Binning.BinWidth(%v) == %v (got %v)\n",
    62  					idx, 1., size,
    63  				)
    64  			}
    65  		}
    66  	*/
    67  	var _ plotter.XYer = h1
    68  	var _ plotter.Valuer = h1
    69  }
    70  
    71  func TestH1DEdges(t *testing.T) {
    72  	h := NewH1DFromEdges([]float64{
    73  		-4.0, -3.6, -3.2, -2.8, -2.4, -2.0, -1.6, -1.2, -0.8, -0.4,
    74  		+0.0, +0.4, +0.8, +1.2, +1.6, +2.0, +2.4, +2.8, +3.2, +3.6,
    75  		+4.0,
    76  	})
    77  	if got, want := h.XMin(), -4.0; got != want {
    78  		t.Errorf("got xmin=%v. want=%v", got, want)
    79  	}
    80  	if got, want := h.XMax(), +4.0; got != want {
    81  		t.Errorf("got xmax=%v. want=%v", got, want)
    82  	}
    83  
    84  	bins := Bin1Ds(h.Binning.Bins)
    85  	for _, test := range []struct {
    86  		v    float64
    87  		want int
    88  	}{
    89  		{v: -4.1, want: UnderflowBin1D},
    90  		{v: -4.0, want: 0},
    91  		{v: -3.6, want: 1},
    92  		{v: -3.2, want: 2},
    93  		{v: -2.8, want: 3},
    94  		{v: -2.4, want: 4},
    95  		{v: -2.0, want: 5},
    96  		{v: -1.6, want: 6},
    97  		{v: -1.2, want: 7},
    98  		{v: -0.8, want: 8},
    99  		{v: -0.4, want: 9},
   100  		{v: +0.0, want: 10},
   101  		{v: +0.4, want: 11},
   102  		{v: +0.8, want: 12},
   103  		{v: +1.2, want: 13},
   104  		{v: +1.6, want: 14},
   105  		{v: +2.0, want: 15},
   106  		{v: +2.4, want: 16},
   107  		{v: +2.8, want: 17},
   108  		{v: +3.2, want: 18},
   109  		{v: +3.6, want: 19},
   110  		{v: +4.0, want: OverflowBin1D},
   111  	} {
   112  		idx := bins.IndexOf(test.v)
   113  		if idx != test.want {
   114  			t.Errorf("invalid index for %v. got=%d. want=%d\n", test.v, idx, test.want)
   115  		}
   116  	}
   117  }
   118  
   119  func TestH1DBins(t *testing.T) {
   120  	h := NewH1DFromBins([]Range{
   121  		{Min: -4.0, Max: -3.6}, {Min: -3.6, Max: -3.2}, {Min: -3.2, Max: -2.8}, {Min: -2.8, Max: -2.4}, {Min: -2.4, Max: -2.0},
   122  		{Min: -2.0, Max: -1.6}, {Min: -1.6, Max: -1.2}, {Min: -1.2, Max: -0.8}, {Min: -0.8, Max: -0.4}, {Min: -0.4, Max: +0.0},
   123  		{Min: +0.0, Max: +0.4}, {Min: +0.4, Max: +0.8}, {Min: +0.8, Max: +1.2}, {Min: +1.2, Max: +1.6}, {Min: +1.6, Max: +2.0},
   124  		{Min: +2.0, Max: +2.4}, {Min: +2.4, Max: +2.8}, {Min: +2.8, Max: +3.2}, {Min: +3.2, Max: +3.6}, {Min: +3.6, Max: +4.0},
   125  	}...)
   126  	if got, want := h.XMin(), -4.0; got != want {
   127  		t.Errorf("got xmin=%v. want=%v", got, want)
   128  	}
   129  	if got, want := h.XMax(), +4.0; got != want {
   130  		t.Errorf("got xmax=%v. want=%v", got, want)
   131  	}
   132  	bins := Bin1Ds(h.Binning.Bins)
   133  	for _, test := range []struct {
   134  		v    float64
   135  		want int
   136  	}{
   137  		{v: -4.1, want: UnderflowBin1D},
   138  		{v: -4.0, want: 0},
   139  		{v: -3.6, want: 1},
   140  		{v: -3.2, want: 2},
   141  		{v: -2.8, want: 3},
   142  		{v: -2.4, want: 4},
   143  		{v: -2.0, want: 5},
   144  		{v: -1.6, want: 6},
   145  		{v: -1.2, want: 7},
   146  		{v: -0.8, want: 8},
   147  		{v: -0.4, want: 9},
   148  		{v: +0.0, want: 10},
   149  		{v: +0.4, want: 11},
   150  		{v: +0.8, want: 12},
   151  		{v: +1.2, want: 13},
   152  		{v: +1.6, want: 14},
   153  		{v: +2.0, want: 15},
   154  		{v: +2.4, want: 16},
   155  		{v: +2.8, want: 17},
   156  		{v: +3.2, want: 18},
   157  		{v: +3.6, want: 19},
   158  		{v: +4.0, want: OverflowBin1D},
   159  	} {
   160  		idx := bins.IndexOf(test.v)
   161  		if idx != test.want {
   162  			t.Errorf("invalid index for %v. got=%d. want=%d\n", test.v, idx, test.want)
   163  		}
   164  	}
   165  
   166  }
   167  
   168  func TestH1DBinsWithGapsv1(t *testing.T) {
   169  	h1 := NewH1DFromBins([]Range{
   170  		{Min: -10, Max: -5}, {Min: -5, Max: 0}, {Min: 0, Max: 4} /*GAP*/, {Min: 5, Max: 10},
   171  	}...)
   172  	if got, want := h1.XMin(), -10.0; got != want {
   173  		t.Errorf("got xmin=%v. want=%v", got, want)
   174  	}
   175  	if got, want := h1.XMax(), 10.0; got != want {
   176  		t.Errorf("got xmax=%v. want=%v", got, want)
   177  	}
   178  
   179  	bins := Bin1Ds(h1.Binning.Bins)
   180  	for _, test := range []struct {
   181  		v    float64
   182  		want int
   183  	}{
   184  		{v: -20, want: UnderflowBin1D},
   185  		{v: -10, want: 0},
   186  		{v: -9, want: 0},
   187  		{v: -5, want: 1},
   188  		{v: 0, want: 2},
   189  		{v: 4.5, want: len(bins)},
   190  		{v: 5, want: 3},
   191  		{v: 6, want: 3},
   192  		{v: 10, want: OverflowBin1D},
   193  	} {
   194  		idx := bins.IndexOf(test.v)
   195  		if idx != test.want {
   196  			t.Errorf("invalid index for %v. got=%d. want=%d\n", test.v, idx, test.want)
   197  		}
   198  	}
   199  
   200  	h := NewH1DFromBins([]Range{
   201  		{Min: 0, Max: 1}, {Min: 1, Max: 2}, {Min: 3, Max: 4},
   202  	}...)
   203  	h.Fill(0, 1)
   204  	h.Fill(1, 1)
   205  	h.Fill(2, 1) // gap
   206  	h.Fill(3, 1)
   207  	h.Annotation()["title"] = "my-title"
   208  
   209  	raw, err := h.marshalYODAv1()
   210  	if err != nil {
   211  		t.Fatal(err)
   212  	}
   213  
   214  	want, err := os.ReadFile("testdata/h1d_gaps_v1_golden.yoda")
   215  	if err != nil {
   216  		t.Fatal(err)
   217  	}
   218  
   219  	if !reflect.DeepEqual(raw, want) {
   220  		t.Fatalf("h1d file differ:\n%s\n",
   221  			cmp.Diff(
   222  				string(want),
   223  				string(raw),
   224  			),
   225  		)
   226  	}
   227  
   228  	var href H1D
   229  	err = href.UnmarshalYODA(want)
   230  	if err != nil {
   231  		t.Fatalf("error: %+v", err)
   232  	}
   233  
   234  	raw, err = href.marshalYODAv1()
   235  	if err != nil {
   236  		t.Fatal(err)
   237  	}
   238  
   239  	if !reflect.DeepEqual(raw, want) {
   240  		t.Fatalf("h1d file differ:\n%s\n",
   241  			cmp.Diff(
   242  				string(want),
   243  				string(raw),
   244  			),
   245  		)
   246  	}
   247  }
   248  
   249  func TestH1DBinsWithGapsv2(t *testing.T) {
   250  	h1 := NewH1DFromBins([]Range{
   251  		{Min: -10, Max: -5}, {Min: -5, Max: 0}, {Min: 0, Max: 4} /*GAP*/, {Min: 5, Max: 10},
   252  	}...)
   253  	if got, want := h1.XMin(), -10.0; got != want {
   254  		t.Errorf("got xmin=%v. want=%v", got, want)
   255  	}
   256  	if got, want := h1.XMax(), 10.0; got != want {
   257  		t.Errorf("got xmax=%v. want=%v", got, want)
   258  	}
   259  
   260  	bins := Bin1Ds(h1.Binning.Bins)
   261  	for _, test := range []struct {
   262  		v    float64
   263  		want int
   264  	}{
   265  		{v: -20, want: UnderflowBin1D},
   266  		{v: -10, want: 0},
   267  		{v: -9, want: 0},
   268  		{v: -5, want: 1},
   269  		{v: 0, want: 2},
   270  		{v: 4.5, want: len(bins)},
   271  		{v: 5, want: 3},
   272  		{v: 6, want: 3},
   273  		{v: 10, want: OverflowBin1D},
   274  	} {
   275  		idx := bins.IndexOf(test.v)
   276  		if idx != test.want {
   277  			t.Errorf("invalid index for %v. got=%d. want=%d\n", test.v, idx, test.want)
   278  		}
   279  	}
   280  
   281  	h := NewH1DFromBins([]Range{
   282  		{Min: 0, Max: 1}, {Min: 1, Max: 2}, {Min: 3, Max: 4},
   283  	}...)
   284  	h.Fill(0, 1)
   285  	h.Fill(1, 1)
   286  	h.Fill(2, 1) // gap
   287  	h.Fill(3, 1)
   288  	h.Annotation()["title"] = "my-title"
   289  
   290  	raw, err := h.MarshalYODA()
   291  	if err != nil {
   292  		t.Fatal(err)
   293  	}
   294  
   295  	want, err := os.ReadFile("testdata/h1d_gaps_v2_golden.yoda")
   296  	if err != nil {
   297  		t.Fatal(err)
   298  	}
   299  
   300  	if !reflect.DeepEqual(raw, want) {
   301  		t.Fatalf("h1d file differ:\n%s\n",
   302  			cmp.Diff(
   303  				string(want),
   304  				string(raw),
   305  			),
   306  		)
   307  	}
   308  
   309  	var href H1D
   310  	err = href.UnmarshalYODA(want)
   311  	if err != nil {
   312  		t.Fatalf("error: %+v", err)
   313  	}
   314  
   315  	raw, err = href.MarshalYODA()
   316  	if err != nil {
   317  		t.Fatal(err)
   318  	}
   319  
   320  	if !reflect.DeepEqual(raw, want) {
   321  		t.Fatalf("h1d file differ:\n%s\n",
   322  			cmp.Diff(
   323  				string(want),
   324  				string(raw),
   325  			),
   326  		)
   327  	}
   328  }
   329  
   330  func TestH1DEdgesWithPanics(t *testing.T) {
   331  	for _, test := range [][]float64{
   332  		{0},
   333  		{0, 1, 0.5, 2},
   334  		{0, 1, 1},
   335  		{0, 1, 0, 1},
   336  		{0, 1, 2, 2},
   337  		{0, 1, 2, 2, 2},
   338  	} {
   339  		panicked, _ := panics(func() {
   340  			_ = NewH1DFromEdges(test)
   341  		})
   342  		if !panicked {
   343  			t.Fatalf("edges %v should have panicked", test)
   344  		}
   345  	}
   346  }
   347  
   348  func TestH1DBinsWithPanics(t *testing.T) {
   349  	for _, test := range [][]Range{
   350  		{{Min: 0, Max: 1}, {Min: 0.5, Max: 1.5}},
   351  		{{Min: 0, Max: 1}, {Min: -1, Max: 2}},
   352  		{{Min: 0, Max: 1.5}, {Min: -1, Max: 1}},
   353  		{{Min: 0, Max: 1}, {Min: 0.5, Max: 0.6}},
   354  	} {
   355  		panicked, _ := panics(func() {
   356  			_ = NewH1DFromBins(test...)
   357  		})
   358  		if !panicked {
   359  			t.Fatalf("bins %v should have panicked", test)
   360  		}
   361  	}
   362  }
   363  
   364  func TestH1DIntegral(t *testing.T) {
   365  	h1 := NewH1D(6, 0, 6)
   366  	h1.Fill(-0.5, 1.3)
   367  	h1.Fill(0, 1)
   368  	h1.Fill(0.5, 1.6)
   369  	h1.Fill(1.2, 0.7)
   370  	h1.Fill(2.1, 0.3)
   371  	h1.Fill(4.2, 1.2)
   372  	h1.Fill(5.9, 0.5)
   373  	h1.Fill(6, 2.1)
   374  
   375  	integral := h1.Integral()
   376  	if integral != 8.7 {
   377  		t.Errorf("expected H1D.Integral() == 8.7 (got %v)\n", integral)
   378  	}
   379  	if got, want := h1.Integral(h1.XMin(), h1.XMax()), 5.3; got != want {
   380  		t.Errorf("H1D.Integral(xmin,xmax) = %v. want=%v\n", got, want)
   381  	}
   382  
   383  	integralall := h1.Integral(math.Inf(-1), math.Inf(+1))
   384  	if integralall != 8.7 {
   385  		t.Errorf("expected H1D.Integral(math.Inf(-1), math.Inf(+1)) == 8.7 (got %v)\n", integralall)
   386  	}
   387  	integralu := h1.Integral(math.Inf(-1), h1.XMax())
   388  	if integralu != 6.6 {
   389  		t.Errorf("expected H1D.Integral(math.Inf(-1), h1.Binning().UpperEdge()) == 6.6 (got %v)\n", integralu)
   390  	}
   391  	integralo := h1.Integral(h1.XMin(), math.Inf(+1))
   392  	if integralo != 7.4 {
   393  		t.Errorf("expected H1D.Integral(h1.Binning().LowerEdge(), math.Inf(+1)) == 7.4 (got %v)\n", integralo)
   394  	}
   395  	integralrange := h1.Integral(0.5, 5.5)
   396  	if integralrange != 2.7 {
   397  		t.Errorf("expected H1D.Integral(0.5, 5.5) == 2.7 (got %v)\n", integralrange)
   398  	}
   399  
   400  	mean1, rms1 := h1.XMean(), h1.XRMS()
   401  
   402  	h1.Scale(1 / integral)
   403  	integral = h1.Integral()
   404  	if integral != 1 {
   405  		t.Errorf("expected H1D.Integral() == 1 (got %v)\n", integral)
   406  	}
   407  
   408  	mean2, rms2 := h1.XMean(), h1.XRMS()
   409  
   410  	if math.Abs(mean1-mean2)/mean1 > 1e-12 {
   411  		t.Errorf("mean has changed while rescaling (mean1, mean2) = (%v, %v)", mean1, mean2)
   412  	}
   413  	if math.Abs(rms1-rms2)/rms1 > 1e-12 {
   414  		t.Errorf("rms has changed while rescaling (rms1, rms2) = (%v, %v)", rms1, rms2)
   415  	}
   416  
   417  	h2 := NewH1D(2, 0, 1)
   418  	h2.Fill(0.0, 1)
   419  	h2.Fill(0.5, 1)
   420  	for _, ibin := range []int{0, 1} {
   421  		if got, want := h2.Value(ibin), 1.0; got != want {
   422  			t.Errorf("got H1D.Value(%d) = %v. want %v\n", ibin, got, want)
   423  		}
   424  		if got, want := h2.Error(ibin), 1.0; got != want {
   425  			t.Errorf("got H1D.Error(%d) = %v. want %v\n", ibin, got, want)
   426  		}
   427  	}
   428  
   429  	if got, want := h2.Integral(), 2.0; got != want {
   430  		t.Errorf("got H1D.Integral() == %v. want %v\n", got, want)
   431  	}
   432  }
   433  
   434  func TestH1DNegativeWeights(t *testing.T) {
   435  	h1 := NewH1D(5, 0, 100)
   436  	h1.Fill(10, -200)
   437  	h1.Fill(20, 1)
   438  	h1.Fill(30, 0.2)
   439  	h1.Fill(10, +200)
   440  
   441  	h2 := NewH1D(5, 0, 100)
   442  	h2.Fill(20, 1)
   443  	h2.Fill(30, 0.2)
   444  
   445  	const tol = 1e-12
   446  	if x1, x2 := h1.XMean(), h2.XMean(); !scalar.EqualWithinAbs(x1, x2, tol) {
   447  		t.Errorf("mean differ:\nh1=%v\nh2=%v\n", x1, x2)
   448  	}
   449  	if x1, x2 := h1.XRMS(), h2.XRMS(); !scalar.EqualWithinAbs(x1, x2, tol) {
   450  		t.Errorf("rms differ:\nh1=%v\nh2=%v\n", x1, x2)
   451  	}
   452  	/* FIXME
   453  	if x1, x2 := h1.StdDevX(), h2.StdDevX(); !scalar.EqualWithinAbs(x1, x2, tol) {
   454  		t.Errorf("std-dev differ:\nh1=%v\nh2=%v\n", x1, x2)
   455  	}
   456  	*/
   457  }
   458  
   459  func TestH1DSerialization(t *testing.T) {
   460  	const nentries = 50
   461  	href := NewH1D(100, 0., 100.)
   462  	for range nentries {
   463  		href.Fill(rnd()*100., 1.)
   464  	}
   465  	href.Annotation()["title"] = "histo title"
   466  	href.Annotation()["name"] = "histo name"
   467  
   468  	// test gob.GobDecode/gob.GobEncode interface
   469  	func() {
   470  		buf := new(bytes.Buffer)
   471  		enc := gob.NewEncoder(buf)
   472  		err := enc.Encode(href)
   473  		if err != nil {
   474  			t.Fatalf("could not serialize histogram: %v", err)
   475  		}
   476  
   477  		var hnew H1D
   478  		dec := gob.NewDecoder(buf)
   479  		err = dec.Decode(&hnew)
   480  		if err != nil {
   481  			t.Fatalf("could not deserialize histogram: %v", err)
   482  		}
   483  
   484  		if !reflect.DeepEqual(href, &hnew) {
   485  			t.Fatalf("ref=%v\nnew=%v\n", href, &hnew)
   486  		}
   487  	}()
   488  
   489  	// test rio.Marshaler/Unmarshaler
   490  	func() {
   491  		buf := new(bytes.Buffer)
   492  		err := href.RioMarshal(buf)
   493  		if err != nil {
   494  			t.Fatalf("could not serialize histogram: %v", err)
   495  		}
   496  
   497  		var hnew H1D
   498  		err = hnew.RioUnmarshal(buf)
   499  		if err != nil {
   500  			t.Fatalf("could not deserialize histogram: %v", err)
   501  		}
   502  
   503  		if !reflect.DeepEqual(href, &hnew) {
   504  			t.Fatalf("ref=%v\nnew=%v\n", href, &hnew)
   505  		}
   506  	}()
   507  
   508  }
   509  
   510  func TestH1DWriteYODA(t *testing.T) {
   511  	h := NewH1D(10, -4, 4)
   512  	h.Fill(1, 1)
   513  	h.Fill(2, 1)
   514  	h.Fill(-3, 1)
   515  	h.Fill(-4, 1)
   516  	h.Fill(0, 1)
   517  	h.Fill(0, 1)
   518  	h.Fill(10, 1)
   519  	h.Fill(-10, 1)
   520  
   521  	chk, err := h.MarshalYODA()
   522  	if err != nil {
   523  		t.Fatal(err)
   524  	}
   525  
   526  	ref, err := os.ReadFile("testdata/h1d_v2_golden.yoda")
   527  	if err != nil {
   528  		t.Fatal(err)
   529  	}
   530  
   531  	if !reflect.DeepEqual(chk, ref) {
   532  		fatalf := t.Fatalf
   533  		if runtime.GOOS == "darwin" {
   534  			// ignore errors for darwin and mac-silicon
   535  			fatalf = t.Logf
   536  		}
   537  		fatalf("h1d file differ:\n%s\n",
   538  			cmp.Diff(
   539  				string(ref),
   540  				string(chk),
   541  			),
   542  		)
   543  	}
   544  }
   545  
   546  func TestH1DReadYODAv1(t *testing.T) {
   547  	ref, err := os.ReadFile("testdata/h1d_v1_golden.yoda")
   548  	if err != nil {
   549  		t.Fatal(err)
   550  	}
   551  
   552  	var h H1D
   553  	err = h.UnmarshalYODA(ref)
   554  	if err != nil {
   555  		t.Fatal(err)
   556  	}
   557  
   558  	chk, err := h.marshalYODAv1()
   559  	if err != nil {
   560  		t.Fatal(err)
   561  	}
   562  
   563  	if !reflect.DeepEqual(chk, ref) {
   564  		t.Fatalf("h1d file differ:\n%s\n",
   565  			cmp.Diff(
   566  				string(ref),
   567  				string(chk),
   568  			),
   569  		)
   570  	}
   571  }
   572  
   573  func TestH1DReadYODAv2(t *testing.T) {
   574  	ref, err := os.ReadFile("testdata/h1d_v2_golden.yoda")
   575  	if err != nil {
   576  		t.Fatal(err)
   577  	}
   578  
   579  	var h H1D
   580  	err = h.UnmarshalYODA(ref)
   581  	if err != nil {
   582  		t.Fatal(err)
   583  	}
   584  
   585  	chk, err := h.MarshalYODA()
   586  	if err != nil {
   587  		t.Fatal(err)
   588  	}
   589  
   590  	if !reflect.DeepEqual(chk, ref) {
   591  		t.Fatalf("h1d file differ:\n%s\n",
   592  			cmp.Diff(
   593  				string(ref),
   594  				string(chk),
   595  			),
   596  		)
   597  	}
   598  }
   599  
   600  func TestH1DBin(t *testing.T) {
   601  	h := NewH1DFromEdges([]float64{
   602  		-4.0, -3.6, -3.2, -2.8, -2.4, -2.0, -1.6, -1.2, -0.8, -0.4,
   603  		+0.0, +0.4, +0.8, +1.2, +1.6, +2.0, +2.4, +2.8, +3.2, +3.6,
   604  		+4.0,
   605  	})
   606  	if got, want := h.XMin(), -4.0; got != want {
   607  		t.Errorf("got xmin=%v. want=%v", got, want)
   608  	}
   609  	if got, want := h.XMax(), +4.0; got != want {
   610  		t.Errorf("got xmax=%v. want=%v", got, want)
   611  	}
   612  
   613  	h.Fill(-4.0, 1)
   614  	h.Fill(-3.6, 1)
   615  	h.Fill(-3.6, 1)
   616  	h.Fill(-3.1, 1)
   617  	h.Fill(-3.1, 1)
   618  	h.Fill(-3.1, 1)
   619  
   620  	for _, tc := range []struct {
   621  		x   float64
   622  		bin int
   623  	}{
   624  		{-4.0, 1},
   625  		{-3.9, 1},
   626  		{-3.6, 2},
   627  		{-3.1, 3},
   628  		{-10, -1},
   629  		{+4, -1},
   630  	} {
   631  		t.Run(fmt.Sprintf("x=%v", tc.x), func(t *testing.T) {
   632  			bin := h.Bin(tc.x)
   633  			if tc.bin < 0 && bin == nil {
   634  				// ok
   635  				return
   636  			}
   637  			if bin == nil {
   638  				t.Fatalf("unexpected nil bin")
   639  			}
   640  
   641  			if bin.EffEntries() != float64(tc.bin) {
   642  				t.Fatalf("x=%v got=%v %v, want=%d", tc.x, bin.EffEntries(), bin.Entries(), tc.bin)
   643  			}
   644  		})
   645  	}
   646  }
   647  
   648  func TestH1DFillN(t *testing.T) {
   649  	h1 := NewH1D(10, 0, 10)
   650  	h2 := NewH1D(10, 0, 10)
   651  
   652  	xs := []float64{1, 2, 3, 4}
   653  	ws := []float64{1, 2, 1, 1}
   654  
   655  	for i := range xs {
   656  		h1.Fill(xs[i], ws[i])
   657  	}
   658  	h2.FillN(xs, ws)
   659  
   660  	if s1, s2 := h1.SumW(), h2.SumW(); s1 != s2 {
   661  		t.Fatalf("invalid sumw: h1=%v, h2=%v", s1, s2)
   662  	}
   663  
   664  	for i := range xs {
   665  		h1.Fill(xs[i], 1)
   666  	}
   667  	h2.FillN(xs, nil)
   668  
   669  	if s1, s2 := h1.SumW(), h2.SumW(); s1 != s2 {
   670  		t.Fatalf("invalid sumw: h1=%v, h2=%v", s1, s2)
   671  	}
   672  
   673  	func() {
   674  		defer func() {
   675  			err := recover()
   676  			if err == nil {
   677  				t.Fatalf("expected a panic!")
   678  			}
   679  			const want = "hbook: lengths mismatch"
   680  			if got, want := err.(error).Error(), want; got != want {
   681  				t.Fatalf("invalid panic message:\ngot= %q\nwant=%q", got, want)
   682  			}
   683  		}()
   684  
   685  		h2.FillN(xs, []float64{1})
   686  	}()
   687  }
   688  
   689  func TestH1DClone(t *testing.T) {
   690  	h1 := NewH1D(10, 0, 10)
   691  	h1.FillN(
   692  		[]float64{-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11},
   693  		[]float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 1},
   694  	)
   695  	h1.Ann["hello"] = "world"
   696  
   697  	msg1, err := h1.MarshalYODA()
   698  	if err != nil {
   699  		t.Fatalf("could not marshal h1: %+v", err)
   700  	}
   701  
   702  	h2 := h1.Clone()
   703  	msg2, err := h2.MarshalYODA()
   704  	if err != nil {
   705  		t.Fatalf("could not marshal h2: %+v", err)
   706  	}
   707  
   708  	if !bytes.Equal(msg1, msg2) {
   709  		t.Fatalf("h1d file differ:\n%s\n",
   710  			cmp.Diff(
   711  				string(msg1),
   712  				string(msg2),
   713  			),
   714  		)
   715  	}
   716  
   717  	h1.Ann["world"] = "bye"
   718  	h1.FillN(
   719  		[]float64{-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11},
   720  		[]float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 1},
   721  	)
   722  
   723  	msg3, err := h1.MarshalYODA()
   724  	if err != nil {
   725  		t.Fatalf("could not marshal h1: %+v", err)
   726  	}
   727  
   728  	msg4, err := h2.MarshalYODA()
   729  	if err != nil {
   730  		t.Fatalf("could not marshal h2: %+v", err)
   731  	}
   732  
   733  	if bytes.Equal(msg1, msg3) {
   734  		t.Fatalf("msg1/msg3 should differ")
   735  	}
   736  
   737  	if !bytes.Equal(msg4, msg2) {
   738  		t.Fatalf("h1d file differ:\n%s\n",
   739  			cmp.Diff(
   740  				string(msg4),
   741  				string(msg2),
   742  			),
   743  		)
   744  	}
   745  }