go-hep.org/x/hep@v0.38.1/groot/rsql/scan.go (about)

     1  // Copyright ©2019 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 rsql provides a convenient access to ROOT files/trees as a database.
     6  package rsql // import "go-hep.org/x/hep/groot/rsql"
     7  
     8  import (
     9  	"fmt"
    10  	"io"
    11  	"math"
    12  	"reflect"
    13  
    14  	"go-hep.org/x/hep/groot/rsql/rsqldrv"
    15  	"go-hep.org/x/hep/groot/rtree"
    16  	"go-hep.org/x/hep/hbook"
    17  )
    18  
    19  // Scan executes a query against the given tree and runs the function f
    20  // within that context.
    21  func Scan(tree rtree.Tree, query string, f any) error {
    22  	if f == nil {
    23  		return fmt.Errorf("groot/rsql: nil func")
    24  	}
    25  	rv := reflect.ValueOf(f)
    26  	rt := rv.Type()
    27  	if rt.Kind() != reflect.Func {
    28  		return fmt.Errorf("groot/rsql: expected a func, got %T", f)
    29  	}
    30  	if rt.NumOut() != 1 || rt.Out(0) != reflect.TypeOf((*error)(nil)).Elem() {
    31  		return fmt.Errorf("groot/rsql: expected a func returning an error. got %T", f)
    32  	}
    33  	vargs := make([]reflect.Value, rt.NumIn())
    34  	args := make([]any, rt.NumIn())
    35  	for i := range args {
    36  		ptr := reflect.New(rt.In(i))
    37  		args[i] = ptr.Interface()
    38  		vargs[i] = ptr.Elem()
    39  	}
    40  
    41  	db := rsqldrv.OpenDB(rtree.FileOf(tree))
    42  	defer db.Close()
    43  
    44  	rows, err := db.Query(query)
    45  	if err != nil {
    46  		return err
    47  	}
    48  	defer rows.Close()
    49  
    50  	for rows.Next() {
    51  		err = rows.Scan(args...)
    52  		if err != nil {
    53  			return err
    54  		}
    55  
    56  		out := rv.Call(vargs)[0].Interface()
    57  		if out != nil {
    58  			return out.(error)
    59  		}
    60  	}
    61  
    62  	err = rows.Err()
    63  	if err == io.EOF {
    64  		err = nil
    65  	}
    66  	return err
    67  }
    68  
    69  // ScanH1D executes a query against the tree and fills the histogram with
    70  // the results of the query.
    71  // If h is nil, a (100-bins, xmin, xmax+ULP) histogram is created,
    72  // where xmin and xmax are inferred from the content of the underlying database.
    73  func ScanH1D(tree rtree.Tree, query string, h *hbook.H1D) (*hbook.H1D, error) {
    74  	if h == nil {
    75  		var (
    76  			xmin = +math.MaxFloat64
    77  			xmax = -math.MaxFloat64
    78  		)
    79  		// FIXME(sbinet) leverage the underlying db min/max functions,
    80  		// instead of crawling through the whole data set.
    81  		err := Scan(tree, query, func(x float64) error {
    82  			xmin = math.Min(xmin, x)
    83  			xmax = math.Max(xmax, x)
    84  			return nil
    85  		})
    86  		if err != nil {
    87  			return nil, err
    88  		}
    89  
    90  		h = hbook.NewH1D(100, xmin, nextULP(xmax))
    91  	}
    92  
    93  	err := Scan(tree, query, func(x float64) error {
    94  		h.Fill(x, 1)
    95  		return nil
    96  	})
    97  
    98  	return h, err
    99  }
   100  
   101  // ScanH2D executes a query against the ntuple and fills the histogram with
   102  // the results of the query.
   103  // If h is nil, a (100-bins, xmin, xmax+ULP) (100-bins, ymin, ymax+ULP) 2d-histogram
   104  // is created,
   105  // where xmin, xmax and ymin,ymax are inferred from the content of the
   106  // underlying database.
   107  func ScanH2D(tree rtree.Tree, query string, h *hbook.H2D) (*hbook.H2D, error) {
   108  	if h == nil {
   109  		var (
   110  			xmin = +math.MaxFloat64
   111  			xmax = -math.MaxFloat64
   112  			ymin = +math.MaxFloat64
   113  			ymax = -math.MaxFloat64
   114  		)
   115  		// FIXME(sbinet) leverage the underlying db min/max functions,
   116  		// instead of crawling through the whole data set.
   117  		err := Scan(tree, query, func(x, y float64) error {
   118  			xmin = math.Min(xmin, x)
   119  			xmax = math.Max(xmax, x)
   120  			ymin = math.Min(ymin, y)
   121  			ymax = math.Max(ymax, y)
   122  			return nil
   123  		})
   124  		if err != nil {
   125  			return nil, err
   126  		}
   127  
   128  		h = hbook.NewH2D(100, xmin, nextULP(xmax), 100, ymin, nextULP(ymax))
   129  	}
   130  
   131  	err := Scan(tree, query, func(x, y float64) error {
   132  		h.Fill(x, y, 1)
   133  		return nil
   134  	})
   135  
   136  	return h, err
   137  }
   138  
   139  func nextULP(v float64) float64 {
   140  	return math.Nextafter(v, v+1)
   141  }