go-hep.org/x/hep@v0.38.1/hbook/h1d.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  	"bufio"
     9  	"bytes"
    10  	"encoding/binary"
    11  	"encoding/gob"
    12  	"fmt"
    13  	"io"
    14  	"math"
    15  	"strings"
    16  
    17  	"go-hep.org/x/hep/rio"
    18  )
    19  
    20  // H1D is a 1-dim histogram with weighted entries.
    21  type H1D struct {
    22  	Binning Binning1D
    23  	Ann     Annotation
    24  }
    25  
    26  // NewH1D returns a 1-dim histogram with n bins between xmin and xmax.
    27  func NewH1D(n int, xmin, xmax float64) *H1D {
    28  	return &H1D{
    29  		Binning: newBinning1D(n, xmin, xmax),
    30  		Ann:     make(Annotation),
    31  	}
    32  }
    33  
    34  // NewH1DFromEdges returns a 1-dim histogram given a slice of edges.
    35  // The number of bins is thus len(edges)-1.
    36  // It panics if the length of edges is <= 1.
    37  // It panics if the edges are not sorted.
    38  // It panics if there are duplicate edge values.
    39  func NewH1DFromEdges(edges []float64) *H1D {
    40  	return &H1D{
    41  		Binning: newBinning1DFromEdges(edges),
    42  		Ann:     make(Annotation),
    43  	}
    44  }
    45  
    46  // NewH1DFromBins returns a 1-dim histogram given a slice of 1-dim bins.
    47  // It panics if the length of bins is < 1.
    48  // It panics if the bins overlap.
    49  func NewH1DFromBins(bins ...Range) *H1D {
    50  	return &H1D{
    51  		Binning: newBinning1DFromBins(bins),
    52  		Ann:     make(Annotation),
    53  	}
    54  }
    55  
    56  // Clone returns a deep copy of this 1-dim histogram.
    57  func (h *H1D) Clone() *H1D {
    58  	return &H1D{
    59  		Binning: h.Binning.clone(),
    60  		Ann:     h.Ann.clone(),
    61  	}
    62  }
    63  
    64  // Name returns the name of this histogram, if any
    65  func (h *H1D) Name() string {
    66  	v, ok := h.Ann["name"]
    67  	if !ok {
    68  		return ""
    69  	}
    70  	n, ok := v.(string)
    71  	if !ok {
    72  		return ""
    73  	}
    74  	return n
    75  }
    76  
    77  // Annotation returns the annotations attached to this histogram
    78  func (h *H1D) Annotation() Annotation {
    79  	return h.Ann
    80  }
    81  
    82  // Rank returns the number of dimensions for this histogram
    83  func (h *H1D) Rank() int {
    84  	return 1
    85  }
    86  
    87  // Entries returns the number of entries in this histogram
    88  func (h *H1D) Entries() int64 {
    89  	return h.Binning.entries()
    90  }
    91  
    92  // EffEntries returns the number of effective entries in this histogram
    93  func (h *H1D) EffEntries() float64 {
    94  	return h.Binning.effEntries()
    95  }
    96  
    97  // SumW returns the sum of weights in this histogram.
    98  // Overflows are included in the computation.
    99  func (h *H1D) SumW() float64 {
   100  	return h.Binning.Dist.SumW()
   101  }
   102  
   103  // SumW2 returns the sum of squared weights in this histogram.
   104  // Overflows are included in the computation.
   105  func (h *H1D) SumW2() float64 {
   106  	return h.Binning.Dist.SumW2()
   107  }
   108  
   109  // SumWX returns the 1st order weighted x moment
   110  func (h *H1D) SumWX() float64 {
   111  	return h.Binning.Dist.SumWX()
   112  }
   113  
   114  // SumWX2 returns the 2nd order weighted x moment
   115  func (h *H1D) SumWX2() float64 {
   116  	return h.Binning.Dist.SumWX2()
   117  }
   118  
   119  // XMean returns the mean X.
   120  // Overflows are included in the computation.
   121  func (h *H1D) XMean() float64 {
   122  	return h.Binning.Dist.mean()
   123  }
   124  
   125  // XVariance returns the variance in X.
   126  // Overflows are included in the computation.
   127  func (h *H1D) XVariance() float64 {
   128  	return h.Binning.Dist.variance()
   129  }
   130  
   131  // XStdDev returns the standard deviation in X.
   132  // Overflows are included in the computation.
   133  func (h *H1D) XStdDev() float64 {
   134  	return h.Binning.Dist.stdDev()
   135  }
   136  
   137  // XStdErr returns the standard error in X.
   138  // Overflows are included in the computation.
   139  func (h *H1D) XStdErr() float64 {
   140  	return h.Binning.Dist.stdErr()
   141  }
   142  
   143  // XRMS returns the XRMS in X.
   144  // Overflows are included in the computation.
   145  func (h *H1D) XRMS() float64 {
   146  	return h.Binning.Dist.rms()
   147  }
   148  
   149  // Fill fills this histogram with x and weight w.
   150  func (h *H1D) Fill(x, w float64) {
   151  	h.Binning.fill(x, w)
   152  }
   153  
   154  // FillN fills this histogram with the provided slices of xs and weight ws.
   155  // if ws is nil, the histogram will be filled with entries of weight 1.
   156  // Otherwise, FillN panics if the slices lengths differ.
   157  func (h *H1D) FillN(xs, ws []float64) {
   158  	switch ws {
   159  	case nil:
   160  		for _, x := range xs {
   161  			h.Binning.fill(x, 1)
   162  		}
   163  	default:
   164  		if len(xs) != len(ws) {
   165  			panic(fmt.Errorf("hbook: lengths mismatch"))
   166  		}
   167  		for i, x := range xs {
   168  			h.Binning.fill(x, ws[i])
   169  		}
   170  	}
   171  }
   172  
   173  // Bin returns the bin at coordinates (x) for this 1-dim histogram.
   174  // Bin returns nil for under/over flow bins.
   175  func (h *H1D) Bin(x float64) *Bin1D {
   176  	idx := h.Binning.coordToIndex(x)
   177  	if idx < 0 {
   178  		return nil
   179  	}
   180  	return &h.Binning.Bins[idx]
   181  }
   182  
   183  // XMin returns the low edge of the X-axis of this histogram.
   184  func (h *H1D) XMin() float64 {
   185  	return h.Binning.xMin()
   186  }
   187  
   188  // XMax returns the high edge of the X-axis of this histogram.
   189  func (h *H1D) XMax() float64 {
   190  	return h.Binning.xMax()
   191  }
   192  
   193  // Scale scales the content of each bin by the given factor.
   194  func (h *H1D) Scale(factor float64) {
   195  	h.Binning.scaleW(factor)
   196  }
   197  
   198  // Integral computes the integral of the histogram.
   199  //
   200  // The number of parameters can be 0 or 2.
   201  // If 0, overflows are included.
   202  // If 2, the first parameter must be the lower bound of the range in which
   203  // the integral is computed and the second one the upper range.
   204  //
   205  // If the lower bound is math.Inf(-1) then the underflow bin is included.
   206  // If the upper bound is math.Inf(+1) then the overflow bin is included.
   207  //
   208  // Examples:
   209  //
   210  //	// integral of all in-range bins + overflows
   211  //	v := h.Integral()
   212  //
   213  //	// integral of all in-range bins, underflow and overflow bins included.
   214  //	v := h.Integral(math.Inf(-1), math.Inf(+1))
   215  //
   216  //	// integrall of all in-range bins, overflow bin included
   217  //	v := h.Integral(h.Binning.XRange.Min, math.Inf(+1))
   218  //
   219  //	// integrall of all bins for which the lower edge is in [0.5, 5.5)
   220  //	v := h.Integral(0.5, 5.5)
   221  func (h *H1D) Integral(args ...float64) float64 {
   222  	min, max := 0., 0.
   223  	switch len(args) {
   224  	case 0:
   225  		return h.SumW()
   226  	case 2:
   227  		min = args[0]
   228  		max = args[1]
   229  		if min > max {
   230  			panic("hbook: min > max")
   231  		}
   232  	default:
   233  		panic("hbook: invalid number of arguments. expected 0 or 2.")
   234  	}
   235  
   236  	integral := 0.0
   237  	for _, bin := range h.Binning.Bins {
   238  		v := bin.Range.Min
   239  		if min <= v && v < max {
   240  			integral += bin.SumW()
   241  		}
   242  	}
   243  	if math.IsInf(min, -1) {
   244  		integral += h.Binning.Outflows[0].SumW()
   245  	}
   246  	if math.IsInf(max, +1) {
   247  		integral += h.Binning.Outflows[1].SumW()
   248  	}
   249  	return integral
   250  }
   251  
   252  // Value returns the content of the idx-th bin.
   253  //
   254  // Value implements gonum/plot/plotter.Valuer
   255  func (h *H1D) Value(i int) float64 {
   256  	return h.Binning.Bins[i].SumW()
   257  }
   258  
   259  // Error returns the error, defined as sqrt(sumW2), of the idx-th bin.
   260  func (h *H1D) Error(i int) float64 {
   261  	return math.Sqrt(h.Binning.Bins[i].SumW2())
   262  }
   263  
   264  // Len returns the number of bins for this histogram
   265  //
   266  // Len implements gonum/plot/plotter.Valuer
   267  func (h *H1D) Len() int {
   268  	return len(h.Binning.Bins)
   269  }
   270  
   271  // XY returns the x,y values for the i-th bin
   272  //
   273  // XY implements gonum/plot/plotter.XYer
   274  func (h *H1D) XY(i int) (float64, float64) {
   275  	bin := h.Binning.Bins[i]
   276  	x := bin.Range.Min
   277  	y := bin.SumW()
   278  	return x, y
   279  }
   280  
   281  // DataRange implements the gonum/plot.DataRanger interface
   282  func (h *H1D) DataRange() (xmin, xmax, ymin, ymax float64) {
   283  	xmin = h.XMin()
   284  	xmax = h.XMax()
   285  	ymin = +math.MaxFloat64
   286  	ymax = -math.MaxFloat64
   287  	for _, b := range h.Binning.Bins {
   288  		v := b.SumW()
   289  		ymax = math.Max(ymax, v)
   290  		ymin = math.Min(ymin, v)
   291  	}
   292  	return xmin, xmax, ymin, ymax
   293  }
   294  
   295  // RioMarshal implements rio.RioMarshaler
   296  func (h *H1D) RioMarshal(w io.Writer) error {
   297  	data, err := h.MarshalBinary()
   298  	if err != nil {
   299  		return err
   300  	}
   301  	var buf [8]byte
   302  	binary.LittleEndian.PutUint64(buf[:], uint64(len(data)))
   303  	_, err = w.Write(buf[:])
   304  	if err != nil {
   305  		return err
   306  	}
   307  	_, err = w.Write(data)
   308  	return err
   309  }
   310  
   311  // RioUnmarshal implements rio.RioUnmarshaler
   312  func (h *H1D) RioUnmarshal(r io.Reader) error {
   313  	buf := make([]byte, 8)
   314  	_, err := io.ReadFull(r, buf)
   315  	if err != nil {
   316  		return err
   317  	}
   318  	n := int64(binary.LittleEndian.Uint64(buf))
   319  	buf = make([]byte, int(n))
   320  	_, err = io.ReadFull(r, buf)
   321  	if err != nil {
   322  		return err
   323  	}
   324  	return h.UnmarshalBinary(buf)
   325  }
   326  
   327  // RioVersion implements rio.RioStreamer
   328  func (h *H1D) RioVersion() rio.Version {
   329  	return 0
   330  }
   331  
   332  // annToYODA creates a new Annotation with fields compatible with YODA
   333  func (h *H1D) annToYODA() Annotation {
   334  	ann := make(Annotation, len(h.Ann))
   335  	ann["Type"] = "Histo1D"
   336  	ann["Path"] = "/" + h.Name()
   337  	ann["Title"] = ""
   338  	for k, v := range h.Ann {
   339  		if k == "name" {
   340  			continue
   341  		}
   342  		if k == "title" {
   343  			ann["Title"] = v
   344  			continue
   345  		}
   346  		ann[k] = v
   347  	}
   348  	return ann
   349  }
   350  
   351  // annFromYODA creates a new Annotation from YODA compatible fields
   352  func (h *H1D) annFromYODA(ann Annotation) {
   353  	if len(h.Ann) == 0 {
   354  		h.Ann = make(Annotation, len(ann))
   355  	}
   356  	for k, v := range ann {
   357  		switch k {
   358  		case "Type":
   359  			// noop
   360  		case "Path":
   361  			name := v.(string)
   362  			name = strings.TrimPrefix(name, "/")
   363  			h.Ann["name"] = name
   364  		case "Title":
   365  			h.Ann["title"] = v
   366  		default:
   367  			h.Ann[k] = v
   368  		}
   369  	}
   370  }
   371  
   372  // MarshalYODA implements the YODAMarshaler interface.
   373  func (h *H1D) MarshalYODA() ([]byte, error) {
   374  	return h.marshalYODAv2()
   375  }
   376  
   377  func (h *H1D) marshalYODAv1() ([]byte, error) {
   378  	buf := new(bytes.Buffer)
   379  	ann := h.annToYODA()
   380  	fmt.Fprintf(buf, "BEGIN YODA_HISTO1D %s\n", ann["Path"])
   381  	data, err := ann.marshalYODAv1()
   382  	if err != nil {
   383  		return nil, err
   384  	}
   385  	buf.Write(data)
   386  
   387  	fmt.Fprintf(buf, "# Mean: %e\n", h.XMean())
   388  	fmt.Fprintf(buf, "# Area: %e\n", h.Integral())
   389  
   390  	fmt.Fprintf(buf, "# ID\t ID\t sumw\t sumw2\t sumwx\t sumwx2\t numEntries\n")
   391  	d := h.Binning.Dist
   392  	fmt.Fprintf(
   393  		buf,
   394  		"Total   \tTotal   \t%e\t%e\t%e\t%e\t%d\n",
   395  		d.SumW(), d.SumW2(), d.SumWX(), d.SumWX2(), d.Entries(),
   396  	)
   397  	d = h.Binning.Outflows[0]
   398  	fmt.Fprintf(
   399  		buf,
   400  		"Underflow\tUnderflow\t%e\t%e\t%e\t%e\t%d\n",
   401  		d.SumW(), d.SumW2(), d.SumWX(), d.SumWX2(), d.Entries(),
   402  	)
   403  
   404  	d = h.Binning.Outflows[1]
   405  	fmt.Fprintf(
   406  		buf,
   407  		"Overflow\tOverflow\t%e\t%e\t%e\t%e\t%d\n",
   408  		d.SumW(), d.SumW2(), d.SumWX(), d.SumWX2(), d.Entries(),
   409  	)
   410  
   411  	// bins
   412  	fmt.Fprintf(buf, "# xlow\t xhigh\t sumw\t sumw2\t sumwx\t sumwx2\t numEntries\n")
   413  	for _, bin := range h.Binning.Bins {
   414  		d := bin.Dist
   415  		fmt.Fprintf(
   416  			buf,
   417  			"%e\t%e\t%e\t%e\t%e\t%e\t%d\n",
   418  			bin.Range.Min, bin.Range.Max,
   419  			d.SumW(), d.SumW2(), d.SumWX(), d.SumWX2(), d.Entries(),
   420  		)
   421  	}
   422  	fmt.Fprintf(buf, "END YODA_HISTO1D\n\n")
   423  	return buf.Bytes(), err
   424  }
   425  
   426  func (h *H1D) marshalYODAv2() ([]byte, error) {
   427  	buf := new(bytes.Buffer)
   428  	ann := h.annToYODA()
   429  	fmt.Fprintf(buf, "BEGIN YODA_HISTO1D_V2 %s\n", ann["Path"])
   430  	data, err := ann.marshalYODAv2()
   431  	if err != nil {
   432  		return nil, err
   433  	}
   434  	buf.Write(data)
   435  	buf.Write([]byte("---\n"))
   436  
   437  	fmt.Fprintf(buf, "# Mean: %e\n", h.XMean())
   438  	fmt.Fprintf(buf, "# Area: %e\n", h.Integral())
   439  
   440  	fmt.Fprintf(buf, "# ID\t ID\t sumw\t sumw2\t sumwx\t sumwx2\t numEntries\n")
   441  	d := h.Binning.Dist
   442  	fmt.Fprintf(
   443  		buf,
   444  		"Total   \tTotal   \t%e\t%e\t%e\t%e\t%e\n",
   445  		d.SumW(), d.SumW2(), d.SumWX(), d.SumWX2(), float64(d.Entries()),
   446  	)
   447  	d = h.Binning.Outflows[0]
   448  	fmt.Fprintf(
   449  		buf,
   450  		"Underflow\tUnderflow\t%e\t%e\t%e\t%e\t%e\n",
   451  		d.SumW(), d.SumW2(), d.SumWX(), d.SumWX2(), float64(d.Entries()),
   452  	)
   453  
   454  	d = h.Binning.Outflows[1]
   455  	fmt.Fprintf(
   456  		buf,
   457  		"Overflow\tOverflow\t%e\t%e\t%e\t%e\t%e\n",
   458  		d.SumW(), d.SumW2(), d.SumWX(), d.SumWX2(), float64(d.Entries()),
   459  	)
   460  
   461  	// bins
   462  	fmt.Fprintf(buf, "# xlow\t xhigh\t sumw\t sumw2\t sumwx\t sumwx2\t numEntries\n")
   463  	for _, bin := range h.Binning.Bins {
   464  		d := bin.Dist
   465  		fmt.Fprintf(
   466  			buf,
   467  			"%e\t%e\t%e\t%e\t%e\t%e\t%e\n",
   468  			bin.Range.Min, bin.Range.Max,
   469  			d.SumW(), d.SumW2(), d.SumWX(), d.SumWX2(), float64(d.Entries()),
   470  		)
   471  	}
   472  	fmt.Fprintf(buf, "END YODA_HISTO1D_V2\n\n")
   473  	return buf.Bytes(), err
   474  }
   475  
   476  // UnmarshalYODA implements the YODAUnmarshaler interface.
   477  func (h *H1D) UnmarshalYODA(data []byte) error {
   478  	r := newRBuffer(data)
   479  	_, vers, err := readYODAHeader(r, "BEGIN YODA_HISTO1D")
   480  	if err != nil {
   481  		return err
   482  	}
   483  	switch vers {
   484  	case 1:
   485  		return h.unmarshalYODAv1(r)
   486  	case 2:
   487  		return h.unmarshalYODAv2(r)
   488  	default:
   489  		return fmt.Errorf("hbook: invalid YODA version %v", vers)
   490  	}
   491  }
   492  
   493  func (h *H1D) unmarshalYODAv1(r *rbuffer) error {
   494  	ann := make(Annotation)
   495  
   496  	// pos of end of annotations
   497  	pos := bytes.Index(r.Bytes(), []byte("\n# Mean:"))
   498  	if pos < 0 {
   499  		return fmt.Errorf("hbook: invalid H1D-YODA data")
   500  	}
   501  	err := ann.unmarshalYODAv1(r.Bytes()[:pos+1])
   502  	if err != nil {
   503  		return fmt.Errorf("hbook: %q\nhbook: %w", string(r.Bytes()[:pos+1]), err)
   504  	}
   505  	h.annFromYODA(ann)
   506  	r.next(pos)
   507  
   508  	var ctx struct {
   509  		total bool
   510  		under bool
   511  		over  bool
   512  		bins  bool
   513  	}
   514  
   515  	// sets of xlow values, to infer number of bins in X.
   516  	xset := make(map[float64]int)
   517  
   518  	var (
   519  		dist   Dist1D
   520  		oflows [2]Dist1D
   521  		bins   []Bin1D
   522  		xmin   = math.Inf(+1)
   523  		xmax   = math.Inf(-1)
   524  	)
   525  	s := bufio.NewScanner(r)
   526  scanLoop:
   527  	for s.Scan() {
   528  		buf := s.Bytes()
   529  		if len(buf) == 0 || buf[0] == '#' {
   530  			continue
   531  		}
   532  		rbuf := bytes.NewReader(buf)
   533  		switch {
   534  		case bytes.HasPrefix(buf, []byte("END YODA_HISTO1D")):
   535  			break scanLoop
   536  		case !ctx.total && bytes.HasPrefix(buf, []byte("Total   \t")):
   537  			ctx.total = true
   538  			d := &dist
   539  			_, err = fmt.Fscanf(
   540  				rbuf,
   541  				"Total   \tTotal   \t%e\t%e\t%e\t%e\t%d\n",
   542  				&d.Dist.SumW, &d.Dist.SumW2,
   543  				&d.Stats.SumWX, &d.Stats.SumWX2,
   544  				&d.Dist.N,
   545  			)
   546  			if err != nil {
   547  				return fmt.Errorf("hbook: %q\nhbook: %w", string(buf), err)
   548  			}
   549  		case !ctx.under && bytes.HasPrefix(buf, []byte("Underflow\t")):
   550  			ctx.under = true
   551  			d := &oflows[0]
   552  			_, err = fmt.Fscanf(
   553  				rbuf,
   554  				"Underflow\tUnderflow\t%e\t%e\t%e\t%e\t%d\n",
   555  				&d.Dist.SumW, &d.Dist.SumW2,
   556  				&d.Stats.SumWX, &d.Stats.SumWX2,
   557  				&d.Dist.N,
   558  			)
   559  			if err != nil {
   560  				return fmt.Errorf("hbook: %q\nhbook: %w", string(buf), err)
   561  			}
   562  		case !ctx.over && bytes.HasPrefix(buf, []byte("Overflow\t")):
   563  			ctx.over = true
   564  			d := &oflows[1]
   565  			_, err = fmt.Fscanf(
   566  				rbuf,
   567  				"Overflow\tOverflow\t%e\t%e\t%e\t%e\t%d\n",
   568  				&d.Dist.SumW, &d.Dist.SumW2,
   569  				&d.Stats.SumWX, &d.Stats.SumWX2,
   570  				&d.Dist.N,
   571  			)
   572  			if err != nil {
   573  				return fmt.Errorf("hbook: %q\nhbook: %w", string(buf), err)
   574  			}
   575  			ctx.bins = true
   576  		case ctx.bins:
   577  			var bin Bin1D
   578  			d := &bin.Dist
   579  			_, err = fmt.Fscanf(
   580  				rbuf,
   581  				"%e\t%e\t%e\t%e\t%e\t%e\t%d\n",
   582  				&bin.Range.Min, &bin.Range.Max,
   583  				&d.Dist.SumW, &d.Dist.SumW2,
   584  				&d.Stats.SumWX, &d.Stats.SumWX2,
   585  				&d.Dist.N,
   586  			)
   587  			if err != nil {
   588  				return fmt.Errorf("hbook: %q\nhbook: %w", string(buf), err)
   589  			}
   590  			xset[bin.Range.Min] = 1
   591  			xmin = math.Min(xmin, bin.Range.Min)
   592  			xmax = math.Max(xmax, bin.Range.Max)
   593  			bins = append(bins, bin)
   594  
   595  		default:
   596  			return fmt.Errorf("hbook: invalid H1D-YODA data: %q", string(buf))
   597  		}
   598  	}
   599  	h.Binning = Binning1D{
   600  		Bins:     bins,
   601  		Dist:     dist,
   602  		Outflows: oflows,
   603  		XRange:   Range{xmin, xmax},
   604  	}
   605  	return err
   606  }
   607  
   608  func (h *H1D) unmarshalYODAv2(r *rbuffer) error {
   609  	ann := make(Annotation)
   610  
   611  	// pos of end of annotations
   612  	pos := bytes.Index(r.Bytes(), []byte("\n# Mean:"))
   613  	if pos < 0 {
   614  		return fmt.Errorf("hbook: invalid H1D-YODA data")
   615  	}
   616  	err := ann.unmarshalYODAv2(r.Bytes()[:pos+1])
   617  	if err != nil {
   618  		return fmt.Errorf("hbook: %q\nhbook: %w", string(r.Bytes()[:pos+1]), err)
   619  	}
   620  	h.annFromYODA(ann)
   621  	r.next(pos)
   622  
   623  	var ctx struct {
   624  		total bool
   625  		under bool
   626  		over  bool
   627  		bins  bool
   628  	}
   629  
   630  	// sets of xlow values, to infer number of bins in X.
   631  	xset := make(map[float64]int)
   632  
   633  	var (
   634  		dist   Dist1D
   635  		oflows [2]Dist1D
   636  		bins   []Bin1D
   637  		xmin   = math.Inf(+1)
   638  		xmax   = math.Inf(-1)
   639  	)
   640  	s := bufio.NewScanner(r)
   641  scanLoop:
   642  	for s.Scan() {
   643  		buf := s.Bytes()
   644  		if len(buf) == 0 || buf[0] == '#' {
   645  			continue
   646  		}
   647  		rbuf := bytes.NewReader(buf)
   648  		switch {
   649  		case bytes.HasPrefix(buf, []byte("END YODA_HISTO1D")):
   650  			break scanLoop
   651  		case !ctx.total && bytes.HasPrefix(buf, []byte("Total   \t")):
   652  			ctx.total = true
   653  			d := &dist
   654  			var n float64
   655  			_, err = fmt.Fscanf(
   656  				rbuf,
   657  				"Total   \tTotal   \t%e\t%e\t%e\t%e\t%e\n",
   658  				&d.Dist.SumW, &d.Dist.SumW2,
   659  				&d.Stats.SumWX, &d.Stats.SumWX2,
   660  				&n,
   661  			)
   662  			if err != nil {
   663  				return fmt.Errorf("hbook: %q\nhbook: %w", string(buf), err)
   664  			}
   665  			d.Dist.N = int64(n)
   666  		case !ctx.under && bytes.HasPrefix(buf, []byte("Underflow\t")):
   667  			ctx.under = true
   668  			d := &oflows[0]
   669  			var n float64
   670  			_, err = fmt.Fscanf(
   671  				rbuf,
   672  				"Underflow\tUnderflow\t%e\t%e\t%e\t%e\t%e\n",
   673  				&d.Dist.SumW, &d.Dist.SumW2,
   674  				&d.Stats.SumWX, &d.Stats.SumWX2,
   675  				&n,
   676  			)
   677  			if err != nil {
   678  				return fmt.Errorf("hbook: %q\nhbook: %w", string(buf), err)
   679  			}
   680  			d.Dist.N = int64(n)
   681  		case !ctx.over && bytes.HasPrefix(buf, []byte("Overflow\t")):
   682  			ctx.over = true
   683  			d := &oflows[1]
   684  			var n float64
   685  			_, err = fmt.Fscanf(
   686  				rbuf,
   687  				"Overflow\tOverflow\t%e\t%e\t%e\t%e\t%e\n",
   688  				&d.Dist.SumW, &d.Dist.SumW2,
   689  				&d.Stats.SumWX, &d.Stats.SumWX2,
   690  				&n,
   691  			)
   692  			if err != nil {
   693  				return fmt.Errorf("hbook: %q\nhbook: %w", string(buf), err)
   694  			}
   695  			d.Dist.N = int64(n)
   696  			ctx.bins = true
   697  		case ctx.bins:
   698  			var bin Bin1D
   699  			d := &bin.Dist
   700  			var n float64
   701  			_, err = fmt.Fscanf(
   702  				rbuf,
   703  				"%e\t%e\t%e\t%e\t%e\t%e\t%e\n",
   704  				&bin.Range.Min, &bin.Range.Max,
   705  				&d.Dist.SumW, &d.Dist.SumW2,
   706  				&d.Stats.SumWX, &d.Stats.SumWX2,
   707  				&n,
   708  			)
   709  			if err != nil {
   710  				return fmt.Errorf("hbook: %q\nhbook: %w", string(buf), err)
   711  			}
   712  			d.Dist.N = int64(n)
   713  			xset[bin.Range.Min] = 1
   714  			xmin = math.Min(xmin, bin.Range.Min)
   715  			xmax = math.Max(xmax, bin.Range.Max)
   716  			bins = append(bins, bin)
   717  
   718  		default:
   719  			return fmt.Errorf("hbook: invalid H1D-YODA data: %q", string(buf))
   720  		}
   721  	}
   722  	h.Binning = Binning1D{
   723  		Bins:     bins,
   724  		Dist:     dist,
   725  		Outflows: oflows,
   726  		XRange:   Range{xmin, xmax},
   727  	}
   728  	return err
   729  }
   730  
   731  // Counts return a slice of Count, ignoring outerflow.
   732  // The low and high error is equal to 0.5 * sqrt(sum(w^2)).
   733  func (h *H1D) Counts() []Count {
   734  	cs := make([]Count, h.Len())
   735  	for i, bin := range h.Binning.Bins {
   736  		cs[i].XRange = bin.Range
   737  		cs[i].Val = h.Value(i)
   738  		cs[i].Err.Low = h.Error(i) * 0.5
   739  		cs[i].Err.High = h.Error(i) * 0.5
   740  	}
   741  	return cs
   742  }
   743  
   744  // check various interfaces
   745  var _ Object = (*H1D)(nil)
   746  var _ Histogram = (*H1D)(nil)
   747  
   748  // serialization interfaces
   749  var _ rio.Marshaler = (*H1D)(nil)
   750  var _ rio.Unmarshaler = (*H1D)(nil)
   751  var _ rio.Streamer = (*H1D)(nil)
   752  
   753  func init() {
   754  	gob.Register((*H1D)(nil))
   755  }