登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go groot 怎样按对象名称读取直方图

来源:17golang原创

时间:2026-10-31 01:33:23 101浏览 收藏

Go groot 按名称读取ROOT直方图时,先用 groot.Open 打开文件,再调用 f.Get("对象键名")。Get 返回的是通用ROOT对象,需要检查它是否为一维直方图;如果后续想用Go的分箱接口处理,可把支持的 rhist.H1 转为 hbook.H1D。

官方地址:https://go-hep.org/

假设报告页需要显示 analysis.root 中名为 h_energy 的直方图。用户输入的是对象键名,页面要展示标题、分箱区间和权重。这个场景的关键不是先画图,而是让“对象不存在”“类型不对”“读取成功但没有填充”分别得到清楚的处理。

先分清对象键名与显示标题

ROOT文件中的对象以键保存,键名用于查找。直方图的标题则是展示文字,不能看到标题像“Energy distribution”就直接把它当成 Get 的参数。文件中的 TTree、图形和其他对象同样可以被按名读取,名称正确也不保证类型正确。

本例约定目标在文件顶层,键名为 h_energy,并固定参考 Go-HEP v0.40.0 接口。示例中的文件名与键名不是库自带素材,也没有声称你的文件一定包含该对象。运行前应从数据生产方确认它们。

字段使用位置含义
文件路径groot.Open定位ROOT容器
对象键名File.Get在当前目录查找对象
对象类名Object.Class用于说明实际读到的类型
直方图标题展示层供用户阅读,不替代键名
文件、对象键名、Get接口、通用对象与一维直方图类型的静态关系
图1:按名称查找的结构图。“查找约定”和“对象类型”分组说明键名与类型各自负责什么;关系线没有执行顺序含义,也不表示某个文件的实测内容。

用Get取得对象,再检查接受的直方图类型

在独立目录准备Go模块,安装固定的示例依赖,把下面程序保存为 main.go。已有模块跳过初始化,构建所需Go版本以该依赖版本的 go.mod 为准。

# 新建独立示例模块;已有Go项目不必重复初始化
go mod init example.org/root-hist-reader
# 固定本文参考接口,不把版本号当作最新版本声明
go get go-hep.org/x/hep@v0.40.0
# 输入文件需事先包含约定的一维直方图
go run .

下面函数把文件访问封装在读取层,调用者只获得转换后的数值对象。它先进行带 ok 的接口断言,再限制支持的具体类型,避免用单值断言让错误对象直接触发panic。

package main

import (
    "errors"
    "fmt"
    "os"
    "strings"

    "go-hep.org/x/hep/groot"
    "go-hep.org/x/hep/groot/rhist"
    "go-hep.org/x/hep/hbook"
    "go-hep.org/x/hep/hbook/rootcnv"
)

// ReadNamedH1只接受当前文件顶层的一维直方图,结果为独立的hbook数值对象。
func ReadNamedH1(path, key string) (out *hbook.H1D, err error) {
    if strings.TrimSpace(key) == "" || strings.Contains(key, "/") {
        return nil, fmt.Errorf("请提供非空的顶层对象键名")
    }
    f, err := groot.Open(path)
    if err != nil { return nil, fmt.Errorf("打开ROOT文件: %w", err) }
    defer func() {
        // 把关闭错误交给调用方;失败时不返回可被误用的结果。
        if closeErr := f.Close(); closeErr != nil {
            out = nil
            err = errors.Join(err, fmt.Errorf("关闭ROOT文件: %w", closeErr))
        }
    }()

    obj, err := f.Get(key)
    if err != nil { return nil, fmt.Errorf("读取对象%q: %w", key, err) }
    if obj == nil { return nil, fmt.Errorf("对象%q为空", key) }
    hist, ok := obj.(rhist.H1)
    if !ok {
        return nil, fmt.Errorf("对象%q是%s,不是一维直方图", key, obj.Class())
    }
    // 显式限制转换范围,不把任意ROOT对象强制转换为TH1D。
    switch hist.(type) {
    case *rhist.H1D, *rhist.H1F, *rhist.H1I:
        out = rootcnv.H1D(hist)
    default:
        return nil, fmt.Errorf("不支持对象%q的直方图类型%s", key, obj.Class())
    }
    return out, nil
}

func main() {
    // analysis.root和h_energy是本次示例的输入约定,请替换为自己的文件与键名。
    hist, err := ReadNamedH1("analysis.root", "h_energy")
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    fmt.Printf("名称=%s 标题=%v 填充次数=%d 权重和=%g\n",
        hist.Name(), hist.Annotation()["title"], hist.Entries(), hist.SumW())
    // 常规分箱不含下溢、上溢;用半开区间和权重和描述内容。
    for i, bin := range hist.Binning.Bins {
        fmt.Printf("bin=%d 区间=[%g,%g) 权重和=%g\n",
            i, bin.XMin(), bin.XMax(), bin.SumW())
    }
}

常见 TH1D、TH1F、TH1I 分别由 *rhist.H1D、*rhist.H1F、*rhist.H1I 表示。它们都可以满足一维直方图接口;本例只允许这三种,其他类型会明确报错,而不是尝试猜测转换。

不要写成 obj.(*rhist.H1D) 后就直接读取:文件中可能存放的是TH1F,这时对象确实是一维直方图,但具体Go类型并不是H1D。更不应该在错误时随便创建一个空直方图返回,否则页面会把“没读到”显示成“没有数据”。

把hbook分箱数据交给展示层

rootcnv.H1D 将受支持的ROOT一维直方图转换为 hbook.H1D,保留名称、标题、分箱及统计信息。函数在退出时关闭输入文件,界面层或报告层使用返回的数值对象,不再依赖用户继续保持文件打开。这个做法针对这里已转换的直方图,不应推广成“所有ROOT对象关文件后都能继续读取”。

展示层可以从 hist.Binning.Bins 取得常规分箱:每个bin的 XMin()、XMax() 描述区间,SumW() 表示其中的权重和。变宽分箱也应保留各自上下界,不要只保存一个统一宽度,再用索引反推所有区间。

ROOT一维直方图转换后,hbook对象包含标题注解、常规分箱、权重和及上下溢出统计
图2:数值对象组成结构图。“类型转换”和“报告数据”分组表示直方图与展示字段的关系;图中没有虚构柱形高度、事件数量或运行结果。

例如,某个示例直方图有区间 [0,1)、[1,3),权重和分别为2与5,报告可以逐项展示这两个区间及其权重。这里是人为设定的数据说明,不是读取结果;若要画“单位横轴长度上的密度”,还要另外考虑不同bin宽度和归一化定义,不能把权重和直接改名为概率。

命令行例子使用文字输出,放进网页时也应保留区间表和文本标题,别让读者只能通过柱形颜色猜含义。对象名和标题若进入HTML,要作为普通文本转义,不应当成可执行的HTML片段。

填充次数与权重和要分别展示

带权填充时,Entries() 和 SumW() 不必相同。给一个样本权重2,再给另一个样本权重0.5,概念上是两次填充,权重和为2.5;不能把2.5写成“2.5个原始事件”。负权重数据更不能按普通频数直觉解释。

常规bin之外还存在下溢、上溢分布,转换后的 hist.Binning.Underflow()、Overflow() 可访问这些信息。示例循环只打印常规分箱,不代表溢出样本已被归入边缘bin。全局统计与各bin求和是否采用相同范围,也应按文件写入时的统计设置解释。

ROOT直方图已经是聚合结果。只有分箱和汇总统计,并不能还原每个事件的原始观测值;若任务需要逐事件分析,应读取对应的事件数据对象,而不是凭直方图反向生成一组假样本。

把名称错误和目录范围交代清楚

返回情况对调用者的含义建议反馈
Open失败容器未能打开检查输入文件是否存在、可读且格式受支持
Get失败当前目录不能取得指定键核对实际键名与所属目录
不是rhist.H1对象存在,但不是接受的一维直方图反馈实际Class,不自动换另一个对象
具体类型不支持超出本函数转换范围增加经过明确设计的类型分支
成功且Entries为0对象可读,但没有记录的填充与读取失败分开显示

此处的 Get 在当前目录查找。本函数主动拒绝含斜杠的键名,避免把目录路径与对象名混为一个约定。如果直方图在子目录中,应先获取目录对象并判断其是否满足 riofs.Directory,再从该目录读取局部键名,或者采用该版本提供的目录路径辅助接口。

同名键可能有多个cycle,不指定cycle会涉及当前目录对同名记录的选择。日常读取若需要固定某次记录,可把完整键选择交给调用者,例如确认文件中确实存在的 h_energy;1;不要把cycle解释成直方图中的bin编号。

对象叫h_energy,就能确认它是一维直方图吗?

不能,键名只是约定。需要检查对象接口和具体支持类型,错误信息中保留实际类名更有帮助。

要读取TH2二维直方图,可以继续用这个函数吗?

不可以。二维直方图应走对应的H2读取及转换路径,分箱表达也需要两个坐标轴。本例会拒绝它。

读取成功后是否必须立刻生成图片?

不必。读取层可只返回数值对象,报告层再决定展示表格或图形;这样也便于区分数据访问错误与绘图配置错误。

参考:groot接口文档、直方图接口定义、ROOT与hbook转换、目录查找实现。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>