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

     1  // Copyright ©2016 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  	"fmt"
    11  	"math"
    12  	"strings"
    13  )
    14  
    15  // H2D is a 2-dim histogram with weighted entries.
    16  type H2D struct {
    17  	Binning Binning2D
    18  	Ann     Annotation
    19  }
    20  
    21  // NewH2D creates a new 2-dim histogram.
    22  func NewH2D(nx int, xlow, xhigh float64, ny int, ylow, yhigh float64) *H2D {
    23  	return &H2D{
    24  		Binning: newBinning2D(nx, xlow, xhigh, ny, ylow, yhigh),
    25  		Ann:     make(Annotation),
    26  	}
    27  }
    28  
    29  // NewH2DFromEdges creates a new 2-dim histogram from slices
    30  // of edges in x and y.
    31  // The number of bins in x and y is thus len(edges)-1.
    32  // It panics if the length of edges is <=1 (in any dimension.)
    33  // It panics if the edges are not sorted (in any dimension.)
    34  // It panics if there are duplicate edge values (in any dimension.)
    35  func NewH2DFromEdges(xedges []float64, yedges []float64) *H2D {
    36  	return &H2D{
    37  		Binning: newBinning2DFromEdges(xedges, yedges),
    38  		Ann:     make(Annotation),
    39  	}
    40  }
    41  
    42  // Name returns the name of this histogram, if any
    43  func (h *H2D) Name() string {
    44  	v, ok := h.Ann["name"]
    45  	if !ok {
    46  		return ""
    47  	}
    48  	n, ok := v.(string)
    49  	if !ok {
    50  		return ""
    51  	}
    52  	return n
    53  }
    54  
    55  // Annotation returns the annotations attached to this histogram
    56  func (h *H2D) Annotation() Annotation {
    57  	return h.Ann
    58  }
    59  
    60  // Rank returns the number of dimensions for this histogram
    61  func (h *H2D) Rank() int {
    62  	return 2
    63  }
    64  
    65  // Entries returns the number of entries in this histogram
    66  func (h *H2D) Entries() int64 {
    67  	return h.Binning.entries()
    68  }
    69  
    70  // EffEntries returns the number of effective entries in this histogram
    71  func (h *H2D) EffEntries() float64 {
    72  	return h.Binning.effEntries()
    73  }
    74  
    75  // SumW returns the sum of weights in this histogram.
    76  // Overflows are included in the computation.
    77  func (h *H2D) SumW() float64 {
    78  	return h.Binning.Dist.SumW()
    79  }
    80  
    81  // SumW2 returns the sum of squared weights in this histogram.
    82  // Overflows are included in the computation.
    83  func (h *H2D) SumW2() float64 {
    84  	return h.Binning.Dist.SumW2()
    85  }
    86  
    87  // SumWX returns the 1st order weighted x moment
    88  // Overflows are included in the computation.
    89  func (h *H2D) SumWX() float64 {
    90  	return h.Binning.Dist.SumWX()
    91  }
    92  
    93  // SumWX2 returns the 2nd order weighted x moment
    94  // Overflows are included in the computation.
    95  func (h *H2D) SumWX2() float64 {
    96  	return h.Binning.Dist.SumWX2()
    97  }
    98  
    99  // SumWY returns the 1st order weighted y moment
   100  // Overflows are included in the computation.
   101  func (h *H2D) SumWY() float64 {
   102  	return h.Binning.Dist.SumWY()
   103  }
   104  
   105  // SumWY2 returns the 2nd order weighted y moment
   106  // Overflows are included in the computation.
   107  func (h *H2D) SumWY2() float64 {
   108  	return h.Binning.Dist.SumWY2()
   109  }
   110  
   111  // SumWXY returns the 1st order weighted x*y moment
   112  // Overflows are included in the computation.
   113  func (h *H2D) SumWXY() float64 {
   114  	return h.Binning.Dist.SumWXY()
   115  }
   116  
   117  // XMean returns the mean X.
   118  // Overflows are included in the computation.
   119  func (h *H2D) XMean() float64 {
   120  	return h.Binning.Dist.xMean()
   121  }
   122  
   123  // YMean returns the mean Y.
   124  // Overflows are included in the computation.
   125  func (h *H2D) YMean() float64 {
   126  	return h.Binning.Dist.yMean()
   127  }
   128  
   129  // XVariance returns the variance in X.
   130  // Overflows are included in the computation.
   131  func (h *H2D) XVariance() float64 {
   132  	return h.Binning.Dist.xVariance()
   133  }
   134  
   135  // YVariance returns the variance in Y.
   136  // Overflows are included in the computation.
   137  func (h *H2D) YVariance() float64 {
   138  	return h.Binning.Dist.yVariance()
   139  }
   140  
   141  // XStdDev returns the standard deviation in X.
   142  // Overflows are included in the computation.
   143  func (h *H2D) XStdDev() float64 {
   144  	return h.Binning.Dist.xStdDev()
   145  }
   146  
   147  // YStdDev returns the standard deviation in Y.
   148  // Overflows are included in the computation.
   149  func (h *H2D) YStdDev() float64 {
   150  	return h.Binning.Dist.yStdDev()
   151  }
   152  
   153  // XStdErr returns the standard error in X.
   154  // Overflows are included in the computation.
   155  func (h *H2D) XStdErr() float64 {
   156  	return h.Binning.Dist.xStdErr()
   157  }
   158  
   159  // YStdErr returns the standard error in Y.
   160  // Overflows are included in the computation.
   161  func (h *H2D) YStdErr() float64 {
   162  	return h.Binning.Dist.yStdErr()
   163  }
   164  
   165  // XRMS returns the RMS in X.
   166  // Overflows are included in the computation.
   167  func (h *H2D) XRMS() float64 {
   168  	return h.Binning.Dist.xRMS()
   169  }
   170  
   171  // YRMS returns the RMS in Y.
   172  // Overflows are included in the computation.
   173  func (h *H2D) YRMS() float64 {
   174  	return h.Binning.Dist.yRMS()
   175  }
   176  
   177  // Fill fills this histogram with (x,y) and weight w.
   178  func (h *H2D) Fill(x, y, w float64) {
   179  	h.Binning.fill(x, y, w)
   180  }
   181  
   182  // FillN fills this histogram with the provided slices (xs,ys) and weights ws.
   183  // if ws is nil, the histogram will be filled with entries of weight 1.
   184  // Otherwise, FillN panics if the slices lengths differ.
   185  func (h *H2D) FillN(xs, ys, ws []float64) {
   186  	switch ws {
   187  	case nil:
   188  		if len(xs) != len(ys) {
   189  			panic(fmt.Errorf("hbook: lengths mismatch"))
   190  		}
   191  		for i := range xs {
   192  			x := xs[i]
   193  			y := ys[i]
   194  			h.Binning.fill(x, y, 1)
   195  		}
   196  	default:
   197  		if len(xs) != len(ys) {
   198  			panic(fmt.Errorf("hbook: lengths mismatch"))
   199  		}
   200  		if len(xs) != len(ws) {
   201  			panic(fmt.Errorf("hbook: lengths mismatch"))
   202  		}
   203  		for i := range xs {
   204  			x := xs[i]
   205  			y := ys[i]
   206  			w := ws[i]
   207  			h.Binning.fill(x, y, w)
   208  		}
   209  	}
   210  }
   211  
   212  // Bin returns the bin at coordinates (x,y) for this 2-dim histogram.
   213  // Bin returns nil for under/over flow bins.
   214  func (h *H2D) Bin(x, y float64) *Bin2D {
   215  	idx := h.Binning.coordToIndex(x, y)
   216  	if idx < 0 {
   217  		return nil
   218  	}
   219  	return &h.Binning.Bins[idx]
   220  }
   221  
   222  // XMin returns the low edge of the X-axis of this histogram.
   223  func (h *H2D) XMin() float64 {
   224  	return h.Binning.xMin()
   225  }
   226  
   227  // XMax returns the high edge of the X-axis of this histogram.
   228  func (h *H2D) XMax() float64 {
   229  	return h.Binning.xMax()
   230  }
   231  
   232  // YMin returns the low edge of the Y-axis of this histogram.
   233  func (h *H2D) YMin() float64 {
   234  	return h.Binning.yMin()
   235  }
   236  
   237  // YMax returns the high edge of the Y-axis of this histogram.
   238  func (h *H2D) YMax() float64 {
   239  	return h.Binning.yMax()
   240  }
   241  
   242  // Integral computes the integral of the histogram.
   243  //
   244  // Overflows are included in the computation.
   245  func (h *H2D) Integral() float64 {
   246  	return h.SumW()
   247  }
   248  
   249  // GridXYZ returns an anonymous struct value that implements
   250  // gonum/plot/plotter.GridXYZ and is ready to plot.
   251  func (h *H2D) GridXYZ() h2dGridXYZ {
   252  	return h2dGridXYZ{h}
   253  }
   254  
   255  type h2dGridXYZ struct {
   256  	h *H2D
   257  }
   258  
   259  func (g h2dGridXYZ) Dims() (c, r int) {
   260  	return g.h.Binning.Nx, g.h.Binning.Ny
   261  }
   262  
   263  func (g h2dGridXYZ) Z(c, r int) float64 {
   264  	idx := r*g.h.Binning.Nx + c
   265  	return g.h.Binning.Bins[idx].SumW()
   266  }
   267  
   268  func (g h2dGridXYZ) X(c int) float64 {
   269  	return g.h.Binning.Bins[c].XMid()
   270  }
   271  
   272  func (g h2dGridXYZ) Y(r int) float64 {
   273  	idx := r * g.h.Binning.Nx
   274  	return g.h.Binning.Bins[idx].YMid()
   275  }
   276  
   277  // check various interfaces
   278  var _ Object = (*H2D)(nil)
   279  var _ Histogram = (*H2D)(nil)
   280  
   281  // annToYODA creates a new Annotation with fields compatible with YODA
   282  func (h *H2D) annToYODA() Annotation {
   283  	ann := make(Annotation, len(h.Ann))
   284  	ann["Type"] = "Histo2D"
   285  	ann["Path"] = "/" + h.Name()
   286  	ann["Title"] = ""
   287  	for k, v := range h.Ann {
   288  		if k == "name" {
   289  			continue
   290  		}
   291  		if k == "title" {
   292  			ann["Title"] = v
   293  			continue
   294  		}
   295  		ann[k] = v
   296  	}
   297  	return ann
   298  }
   299  
   300  // annFromYODA creates a new Annotation from YODA compatible fields
   301  func (h *H2D) annFromYODA(ann Annotation) {
   302  	if len(h.Ann) == 0 {
   303  		h.Ann = make(Annotation, len(ann))
   304  	}
   305  	for k, v := range ann {
   306  		switch k {
   307  		case "Type":
   308  			// noop
   309  		case "Path":
   310  			name := v.(string)
   311  			name = strings.TrimPrefix(name, "/")
   312  			h.Ann["name"] = name
   313  		case "Title":
   314  			h.Ann["title"] = v
   315  		default:
   316  			h.Ann[k] = v
   317  		}
   318  	}
   319  }
   320  
   321  // MarshalYODA implements the YODAMarshaler interface.
   322  func (h *H2D) MarshalYODA() ([]byte, error) {
   323  	return h.marshalYODAv2()
   324  }
   325  
   326  func (h *H2D) marshalYODAv1() ([]byte, error) {
   327  	buf := new(bytes.Buffer)
   328  	ann := h.annToYODA()
   329  	fmt.Fprintf(buf, "BEGIN YODA_HISTO2D %s\n", ann["Path"])
   330  	data, err := ann.marshalYODAv1()
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  	buf.Write(data)
   335  
   336  	fmt.Fprintf(buf, "# Mean: (%e, %e)\n", h.XMean(), h.YMean())
   337  	fmt.Fprintf(buf, "# Volume: %e\n", h.Integral())
   338  
   339  	fmt.Fprintf(buf, "# ID\t ID\t sumw\t sumw2\t sumwx\t sumwx2\t sumwy\t sumwy2\t sumwxy\t numEntries\n")
   340  	d := h.Binning.Dist
   341  	fmt.Fprintf(
   342  		buf,
   343  		"Total   \tTotal   \t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%d\n",
   344  		d.SumW(), d.SumW2(), d.SumWX(), d.SumWX2(), d.SumWY(), d.SumWY2(), d.SumWXY(), d.Entries(),
   345  	)
   346  
   347  	// outflows
   348  	fmt.Fprintf(buf, "# 2D outflow persistency not currently supported until API is stable\n")
   349  
   350  	// bins
   351  	fmt.Fprintf(buf, "# xlow\t xhigh\t ylow\t yhigh\t sumw\t sumw2\t sumwx\t sumwx2\t sumwy\t sumwy2\t sumwxy\t numEntries\n")
   352  	for ix := range h.Binning.Nx {
   353  		for iy := range h.Binning.Ny {
   354  			bin := h.Binning.Bins[iy*h.Binning.Nx+ix]
   355  			d := bin.Dist
   356  			fmt.Fprintf(
   357  				buf,
   358  				"%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%d\n",
   359  				bin.XRange.Min, bin.XRange.Max, bin.YRange.Min, bin.YRange.Max,
   360  				d.SumW(), d.SumW2(), d.SumWX(), d.SumWX2(), d.SumWY(), d.SumWY2(), d.SumWXY(), d.Entries(),
   361  			)
   362  		}
   363  	}
   364  	fmt.Fprintf(buf, "END YODA_HISTO2D\n\n")
   365  	return buf.Bytes(), err
   366  }
   367  
   368  func (h *H2D) marshalYODAv2() ([]byte, error) {
   369  	buf := new(bytes.Buffer)
   370  	ann := h.annToYODA()
   371  	fmt.Fprintf(buf, "BEGIN YODA_HISTO2D_V2 %s\n", ann["Path"])
   372  	data, err := ann.marshalYODAv2()
   373  	if err != nil {
   374  		return nil, err
   375  	}
   376  	buf.Write(data)
   377  	buf.Write([]byte("---\n"))
   378  
   379  	fmt.Fprintf(buf, "# Mean: (%e, %e)\n", h.XMean(), h.YMean())
   380  	fmt.Fprintf(buf, "# Volume: %e\n", h.Integral())
   381  
   382  	fmt.Fprintf(buf, "# ID\t ID\t sumw\t sumw2\t sumwx\t sumwx2\t sumwy\t sumwy2\t sumwxy\t numEntries\n")
   383  	d := h.Binning.Dist
   384  	fmt.Fprintf(
   385  		buf,
   386  		"Total   \tTotal   \t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\n",
   387  		d.SumW(), d.SumW2(), d.SumWX(), d.SumWX2(), d.SumWY(), d.SumWY2(), d.SumWXY(), float64(d.Entries()),
   388  	)
   389  
   390  	// outflows
   391  	fmt.Fprintf(buf, "# 2D outflow persistency not currently supported until API is stable\n")
   392  
   393  	// bins
   394  	fmt.Fprintf(buf, "# xlow\t xhigh\t ylow\t yhigh\t sumw\t sumw2\t sumwx\t sumwx2\t sumwy\t sumwy2\t sumwxy\t numEntries\n")
   395  	for ix := range h.Binning.Nx {
   396  		for iy := range h.Binning.Ny {
   397  			bin := h.Binning.Bins[iy*h.Binning.Nx+ix]
   398  			d := bin.Dist
   399  			fmt.Fprintf(
   400  				buf,
   401  				"%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\n",
   402  				bin.XRange.Min, bin.XRange.Max, bin.YRange.Min, bin.YRange.Max,
   403  				d.SumW(), d.SumW2(), d.SumWX(), d.SumWX2(), d.SumWY(), d.SumWY2(), d.SumWXY(), float64(d.Entries()),
   404  			)
   405  		}
   406  	}
   407  	fmt.Fprintf(buf, "END YODA_HISTO2D_V2\n\n")
   408  	return buf.Bytes(), err
   409  }
   410  
   411  // UnmarshalYODA implements the YODAUnmarshaler interface.
   412  func (h *H2D) UnmarshalYODA(data []byte) error {
   413  	r := newRBuffer(data)
   414  	_, vers, err := readYODAHeader(r, "BEGIN YODA_HISTO2D")
   415  	if err != nil {
   416  		return err
   417  	}
   418  	switch vers {
   419  	case 1:
   420  		return h.unmarshalYODAv1(r)
   421  	case 2:
   422  		return h.unmarshalYODAv2(r)
   423  	default:
   424  		return fmt.Errorf("hbook: invalid YODA version %v", vers)
   425  	}
   426  }
   427  
   428  func (h *H2D) unmarshalYODAv1(r *rbuffer) error {
   429  	ann := make(Annotation)
   430  
   431  	// pos of end of annotations
   432  	pos := bytes.Index(r.Bytes(), []byte("\n# Mean:"))
   433  	if pos < 0 {
   434  		return fmt.Errorf("hbook: invalid H2D-YODA data")
   435  	}
   436  	err := ann.unmarshalYODAv1(r.Bytes()[:pos+1])
   437  	if err != nil {
   438  		return fmt.Errorf("hbook: %q\nhbook: %w", string(r.Bytes()[:pos+1]), err)
   439  	}
   440  	h.annFromYODA(ann)
   441  	r.next(pos)
   442  
   443  	var ctx struct {
   444  		dist bool
   445  		bins bool
   446  	}
   447  
   448  	// sets of xlow and ylow values, to infer number of bins in X and Y.
   449  	xset := make(map[float64]int)
   450  	yset := make(map[float64]int)
   451  
   452  	var (
   453  		dist Dist2D
   454  		bins []Bin2D
   455  		xmin = math.Inf(+1)
   456  		xmax = math.Inf(-1)
   457  		ymin = math.Inf(+1)
   458  		ymax = math.Inf(-1)
   459  	)
   460  	s := bufio.NewScanner(r)
   461  scanLoop:
   462  	for s.Scan() {
   463  		buf := s.Bytes()
   464  		if len(buf) == 0 || buf[0] == '#' {
   465  			continue
   466  		}
   467  		rbuf := bytes.NewReader(buf)
   468  		switch {
   469  		case bytes.HasPrefix(buf, []byte("END YODA_HISTO2D")):
   470  			break scanLoop
   471  		case !ctx.dist && bytes.HasPrefix(buf, []byte("Total   \t")):
   472  			ctx.dist = true
   473  			d := &dist
   474  			_, err = fmt.Fscanf(
   475  				rbuf,
   476  				"Total   \tTotal   \t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%d\n",
   477  				&d.X.Dist.SumW, &d.X.Dist.SumW2,
   478  				&d.X.Stats.SumWX, &d.X.Stats.SumWX2,
   479  				&d.Y.Stats.SumWX, &d.Y.Stats.SumWX2,
   480  				&d.Stats.SumWXY, &d.X.Dist.N,
   481  			)
   482  			if err != nil {
   483  				return fmt.Errorf("hbook: %q\nhbook: %w", string(buf), err)
   484  			}
   485  			d.Y.Dist = d.X.Dist
   486  			ctx.bins = true
   487  		case ctx.bins:
   488  			var bin Bin2D
   489  			d := &bin.Dist
   490  			_, err = fmt.Fscanf(
   491  				rbuf,
   492  				"%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%d\n",
   493  				&bin.XRange.Min, &bin.XRange.Max, &bin.YRange.Min, &bin.YRange.Max,
   494  				&d.X.Dist.SumW, &d.X.Dist.SumW2,
   495  				&d.X.Stats.SumWX, &d.X.Stats.SumWX2,
   496  				&d.Y.Stats.SumWX, &d.Y.Stats.SumWX2,
   497  				&d.Stats.SumWXY, &d.X.Dist.N,
   498  			)
   499  			if err != nil {
   500  				return fmt.Errorf("hbook: %q\nhbook: %w", string(buf), err)
   501  			}
   502  			d.Y.Dist = d.X.Dist
   503  			xset[bin.XRange.Min] = 1
   504  			yset[bin.YRange.Min] = 1
   505  			xmin = math.Min(xmin, bin.XRange.Min)
   506  			xmax = math.Max(xmax, bin.XRange.Max)
   507  			ymin = math.Min(ymin, bin.YRange.Min)
   508  			ymax = math.Max(ymax, bin.YRange.Max)
   509  			bins = append(bins, bin)
   510  
   511  		default:
   512  			return fmt.Errorf("hbook: invalid H2D-YODA data: %q", string(buf))
   513  		}
   514  	}
   515  	h.Binning = newBinning2D(len(xset), xmin, xmax, len(yset), ymin, ymax)
   516  	h.Binning.Dist = dist
   517  	// YODA bins are transposed wrt ours
   518  	for ix := range h.Binning.Nx {
   519  		for iy := range h.Binning.Ny {
   520  			h.Binning.Bins[iy*h.Binning.Nx+ix] = bins[ix*h.Binning.Ny+iy]
   521  		}
   522  	}
   523  	return err
   524  }
   525  
   526  func (h *H2D) unmarshalYODAv2(r *rbuffer) error {
   527  	ann := make(Annotation)
   528  
   529  	// pos of end of annotations
   530  	pos := bytes.Index(r.Bytes(), []byte("\n# Mean:"))
   531  	if pos < 0 {
   532  		return fmt.Errorf("hbook: invalid H2D-YODA data")
   533  	}
   534  	err := ann.unmarshalYODAv2(r.Bytes()[:pos+1])
   535  	if err != nil {
   536  		return fmt.Errorf("hbook: %q\nhbook: %w", string(r.Bytes()[:pos+1]), err)
   537  	}
   538  	h.annFromYODA(ann)
   539  	r.next(pos)
   540  
   541  	var ctx struct {
   542  		dist bool
   543  		bins bool
   544  	}
   545  
   546  	// sets of xlow and ylow values, to infer number of bins in X and Y.
   547  	xset := make(map[float64]int)
   548  	yset := make(map[float64]int)
   549  
   550  	var (
   551  		dist Dist2D
   552  		bins []Bin2D
   553  		xmin = math.Inf(+1)
   554  		xmax = math.Inf(-1)
   555  		ymin = math.Inf(+1)
   556  		ymax = math.Inf(-1)
   557  	)
   558  	s := bufio.NewScanner(r)
   559  scanLoop:
   560  	for s.Scan() {
   561  		buf := s.Bytes()
   562  		if len(buf) == 0 || buf[0] == '#' {
   563  			continue
   564  		}
   565  		rbuf := bytes.NewReader(buf)
   566  		switch {
   567  		case bytes.HasPrefix(buf, []byte("END YODA_HISTO2D_V2")):
   568  			break scanLoop
   569  		case !ctx.dist && bytes.HasPrefix(buf, []byte("Total   \t")):
   570  			ctx.dist = true
   571  			d := &dist
   572  			var n float64
   573  			_, err = fmt.Fscanf(
   574  				rbuf,
   575  				"Total   \tTotal   \t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\n",
   576  				&d.X.Dist.SumW, &d.X.Dist.SumW2,
   577  				&d.X.Stats.SumWX, &d.X.Stats.SumWX2,
   578  				&d.Y.Stats.SumWX, &d.Y.Stats.SumWX2,
   579  				&d.Stats.SumWXY, &n,
   580  			)
   581  			if err != nil {
   582  				return fmt.Errorf("hbook: %q\nhbook: %w", string(buf), err)
   583  			}
   584  			d.X.Dist.N = int64(n)
   585  			d.Y.Dist = d.X.Dist
   586  			ctx.bins = true
   587  		case ctx.bins:
   588  			var bin Bin2D
   589  			d := &bin.Dist
   590  			var n float64
   591  			_, err = fmt.Fscanf(
   592  				rbuf,
   593  				"%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\n",
   594  				&bin.XRange.Min, &bin.XRange.Max, &bin.YRange.Min, &bin.YRange.Max,
   595  				&d.X.Dist.SumW, &d.X.Dist.SumW2,
   596  				&d.X.Stats.SumWX, &d.X.Stats.SumWX2,
   597  				&d.Y.Stats.SumWX, &d.Y.Stats.SumWX2,
   598  				&d.Stats.SumWXY, &n,
   599  			)
   600  			if err != nil {
   601  				return fmt.Errorf("hbook: %q\nhbook: %w", string(buf), err)
   602  			}
   603  			d.X.Dist.N = int64(n)
   604  			d.Y.Dist = d.X.Dist
   605  			xset[bin.XRange.Min] = 1
   606  			yset[bin.YRange.Min] = 1
   607  			xmin = math.Min(xmin, bin.XRange.Min)
   608  			xmax = math.Max(xmax, bin.XRange.Max)
   609  			ymin = math.Min(ymin, bin.YRange.Min)
   610  			ymax = math.Max(ymax, bin.YRange.Max)
   611  			bins = append(bins, bin)
   612  
   613  		default:
   614  			return fmt.Errorf("hbook: invalid H2D-YODA data: %q", string(buf))
   615  		}
   616  	}
   617  	h.Binning = newBinning2D(len(xset), xmin, xmax, len(yset), ymin, ymax)
   618  	h.Binning.Dist = dist
   619  	// YODA bins are transposed wrt ours
   620  	for ix := range h.Binning.Nx {
   621  		for iy := range h.Binning.Ny {
   622  			h.Binning.Bins[iy*h.Binning.Nx+ix] = bins[ix*h.Binning.Ny+iy]
   623  		}
   624  	}
   625  	return err
   626  }