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