PyROOT 所需的 Python 基础¶
本页介绍《核物理实验方法》PyROOT 教程中用到的 Python 语法,适合尚不熟悉 Python 的同学。
我们以探测器的 channel、刻度后的能量、阈值和重复测量为例,学习变量、流程控制、函数和容器。
阅读说明
阅读 ROOT Tutorial I — PyROOT 前,可按需补充本页内容。后续作业会用数组处理表格数据、事例时间和波形。若已熟悉 Python 变量、循环、函数和 NumPy 数组,可直接进入 Tutorial I。
在 notebook 中运行 Python¶
选择 Python 3 kernel,按顺序运行单元格。按 Shift+Enter 执行当前单元格;其中定义的变量可在后续单元格继续使用,直到 kernel 重启。
单元格旁的执行编号表示运行顺序,不是它在页面中的位置。若乱序运行后结果不一致,可重启 kernel,再从头运行。报错会中止当前单元格,但不会清除之前创建的变量。
1. 第一个 Python 单元格¶
print(...) 输出文字或数值。文字写在引号内,Python 语句通常不需要以分号结尾。
print("Python is ready for detector analysis.")
Python is ready for detector analysis.
引号中的文字称为字符串。以 # 开头的内容是注释,运行时不会执行。
# 这条注释用于说明下面的数值。
print("One measured gamma-ray energy:", 661.7, "keV")
One measured gamma-ray energy: 661.7 keV
2. 变量与数值表达式¶
变量用名称保存一个值,Python 根据赋给它的值确定类型。例如,ADC channel 通常是整数,刻度后的能量通常用浮点数表示,筛选结果则是布尔值(True 或 False)。
adc_channel = 1326
energy_keV = 661.7
above_threshold = True
detector_name = "gamma detector"
print(type(adc_channel), type(energy_keV))
print(detector_name, energy_keV, above_threshold)
<class 'int'> <class 'float'> gamma detector 661.7 True
= 表示赋值,不是判断相等。type(value) 返回值的 Python 类型;这里常用的类型有 int、float、bool 和 str。
变量名可包含字母、数字和下划线,但不能以数字开头。像 energy_keV 这样的名称还标明了单位,有助于避免分析中的单位混淆。
线性能量刻度¶
假设探测器记录的是 ADC channel,记为 C,已有的刻度关系为
$$ E = a + bC . $$
其中 a 是零点偏移,单位为 keV;b 是增益,单位为 keV/channel。用 Python 的基本算术运算即可完成换算。
offset_keV = -1.8
gain_keV_per_channel = 0.500
channel = 1327
calibrated_energy_keV = offset_keV + gain_keV_per_channel * channel
print(f"Calibrated energy = {calibrated_energy_keV:.1f} keV")
Calibrated energy = 661.7 keV
乘、除、加、减分别写作 *、/、+、-,乘方写作 **;括号用于确定运算顺序。以 f 开头的字符串称为 f-string,可将数值嵌入文字;{calibrated_energy_keV:.1f} 表示保留一位小数。
在 Python 3 中,即使两个操作数都是整数,/ 也进行浮点除法。// 表示向下取整的除法,做探测器数值计算时不要误用。
print("ordinary division:", 5 / 2)
print("floor division: ", 5 // 2)
print("square: ", 3.0**2)
ordinary division: 2.5 floor division: 2 square: 9.0
3. 列表与索引¶
列表按顺序保存一组值。下面的列表可以看作一次短测量中选出的六个脉冲能量,已经完成刻度。
energies_keV = [118.9, 356.1, 511.0, 661.7, 1173.2, 1332.5]
print("number of measurements:", len(energies_keV))
print("first energy:", energies_keV[0])
print("last energy: ", energies_keV[-1])
print("middle part: ", energies_keV[1:4])
number of measurements: 6 first energy: 118.9 last energy: 1332.5 middle part: [356.1, 511.0, 661.7]
Python 索引从 0 开始。energies_keV[0] 是第一项,energies_keV[-1] 是最后一项。切片 start:stop 包含 start、不包含 stop,因此 [1:4] 对应索引 1、2、3。
len(...) 返回元素个数,append(value) 在列表末尾追加一个元素。
energies_keV.append(1460.8)
print(energies_keV)
[118.9, 356.1, 511.0, 661.7, 1173.2, 1332.5, 1460.8]
4. 比较与筛选¶
数据分析中经常需要判断测量值是否满足某个条件。比较运算的结果是 True 或 False。
energy_keV = 661.7
print(energy_keV > 100.0) # 大于
print(energy_keV <= 700.0) # 小于或等于
print(energy_keV == 661.7) # 等于
print(energy_keV != 511.0) # 不等于
True True True True
判断相等用 ==,赋值用单个 =。多个条件可用 and、or 和 not 组合。
下面选取 650–675 keV 的峰区,包含下边界、不包含上边界。采用这种左闭右开的区间,可避免公共边界上的值同时进入两个相邻区间。
energy_keV = 661.7
in_peak_window = (energy_keV >= 650.0) and (energy_keV < 675.0)
if in_peak_window:
print("inside the peak window")
else:
print("outside the peak window")
inside the peak window
缩进的语句属于相应的 if 或 else 代码块。Python 用缩进表示代码层次,通常每级缩进四个空格;if 和 else 所在行以冒号结束。
elif 用于增加其他互斥条件;程序执行第一个满足条件的代码块。
energy_keV = 420.0
if energy_keV < 100.0:
region = "below threshold"
elif energy_keV < 600.0:
region = "low-energy region"
else:
region = "high-energy region"
print(region)
low-energy region
5. 用 for 循环重复计算¶
for 循环对每个值执行相同操作。下面每次循环时,energy 依次取 energies_keV 中的一个值。
for energy in energies_keV:
if energy >= 600.0:
print(energy, "keV passes the threshold")
661.7 keV passes the threshold 1173.2 keV passes the threshold 1332.5 keV passes the threshold 1460.8 keV passes the threshold
循环体需要缩进。循环也可用于累加:下面两个变量分别记录通过筛选的测量次数及能量总和,都要在循环开始前初始化。
selected_count = 0
selected_sum_keV = 0.0
for energy in energies_keV:
if energy >= 600.0:
selected_count += 1
selected_sum_keV += energy
selected_mean_keV = selected_sum_keV / selected_count
print("count =", selected_count)
print("mean =", selected_mean_keV, "keV")
count = 4 mean = 1157.05 keV
x += value 是 x = x + value 的简写。实际分析中,求平均值前应检查 selected_count 是否为零;本例的数据中已有满足条件的值。
range(n) 依次给出 0 到 n-1 的整数。需要重复固定次数时常用这种写法,例如模拟多次测量,或按索引读取数据记录(entry)。
for measurement_index in range(5):
print("measurement", measurement_index)
measurement 0 measurement 1 measurement 2 measurement 3 measurement 4
6. 函数¶
函数将一段计算封装在一个名称下。下面的刻度函数接收三个参数,返回一个结果;三引号中的说明文字称为 docstring,用来说明公式和单位。
def calibrate_channel(channel, offset_keV, gain_keV_per_channel):
"""Convert one ADC channel to energy using E = offset + gain*channel."""
energy_keV = offset_keV + gain_keV_per_channel * channel
return energy_keV
energy_keV = calibrate_channel(1327, -1.8, 0.500)
print(energy_keV, "keV")
661.7 keV
def 开始函数定义;括号中的形参接收调用时传入的值;return 返回计算结果。函数内部创建的 energy_keV 等变量是局部变量,通常不会替换函数外的同名变量。
函数也可以返回布尔值,使筛选条件更容易阅读。
def inside_window(value, lower, upper):
return (value >= lower) and (value < upper)
for energy in energies_keV:
if inside_window(energy, 650.0, 675.0):
print("peak candidate:", energy, "keV")
peak candidate: 661.7 keV
7. 用 NumPy 数组处理数值数据¶
Python 列表是通用容器;NumPy 数组将元素保存为统一的数值类型,便于高效地逐元素计算。它适合保存刻度数据、波形,以及向 ROOT 传递数值数组。
import numpy as np
channels = np.array([241, 714, 1024, 1327, 2349, 2668], dtype=np.float64)
energies = offset_keV + gain_keV_per_channel * channels
print(energies)
print("data type:", energies.dtype)
[ 118.7 355.2 510.2 661.7 1172.7 1332.2] data type: float64
import numpy as np 导入 NumPy,并采用常用简称 np。np.array(values, dtype=np.float64) 创建 64 位浮点数组。这里明确指定 dtype,是因为 ROOT 的图对象构造函数通常需要内存连续的 C++ double 数组。
下面的刻度表达式作用于数组的每个元素。若把 NumPy 数组换成普通 Python 列表,相同写法不会进行逐元素数值乘法。
NumPy 提供常用统计量。mean() 计算样本均值;std(ddof=1) 用 N−1 作分母计算样本标准差,默认的 ddof=0 则用 N 作分母。应根据所估计的统计量选择,而不是直接沿用软件默认值。
repeated_peak_energies = np.array([660.9, 662.2, 661.4, 661.8, 662.0])
print("mean =", repeated_peak_energies.mean(), "keV")
print("sample standard deviation =",
repeated_peak_energies.std(ddof=1), "keV")
mean = 661.6600000000001 keV sample standard deviation = 0.5176871642218114 keV
数组的比较结果是一个布尔数组。将它放在方括号中,就能选出对应位置的值,这称为布尔索引(Boolean indexing)。
peak_mask = (energies >= 650.0) & (energies < 675.0)
peak_energies = energies[peak_mask]
print("mask:", peak_mask)
print("selected energies:", peak_energies)
mask: [False False False True False False] selected energies: [661.7]
NumPy 数组的逐元素逻辑运算使用 &、| 和 ~,每个比较表达式都放在括号内。Python 的 and 和 or 用于单个布尔值,如前面的 if 示例。
8. 模块、对象与方法¶
模块将相关功能组织在一起。执行 import ROOT 后,ROOT.TGraph 表示 ROOT 模块中的 TGraph 类;调用类的构造函数会创建一个对象。对象提供的操作称为方法,用点号调用。
下面的单元格只检查 ROOT 是否可用。ROOT Tutorial I 会结合探测器实例,介绍画布、函数、图、随机采样和直方图,并说明构造函数的参数。
import ROOT
print("ROOT version:", ROOT.gROOT.GetVersion())
ROOT version: 6.40.00
PyROOT 中会反复用到以下写法:
histogram = ROOT.TH1F(name, title, number_of_bins, lower_edge, upper_edge)
histogram.Fill(measured_value)
ROOT.TH1F 是类,histogram 是一个对象,Fill 是方法。构造函数的五个参数给出对象的内部名称、显示标题及 bin 设置。这里先熟悉 Python 调用语法,具体参数在 ROOT Tutorial I 的 TH1F 部分介绍。
9. 综合示例:探测器信号的刻度与筛选¶
现有一小组探测器 ADC 读数,以及已知的刻度零点和增益。将每个 channel 换算成能量,再选出 650–675 keV 范围内的测量值。本例处理已记录的数据,不涉及探测器响应模拟。
raw_channels = [1280, 1311, 1325, 1328, 1331, 1400]
offset_keV = -1.8
gain_keV_per_channel = 0.500
selected_energies_keV = []
for channel in raw_channels:
energy = calibrate_channel(channel, offset_keV, gain_keV_per_channel)
if inside_window(energy, 650.0, 675.0):
selected_energies_keV.append(energy)
print("selected energies:", selected_energies_keV)
print("selected count:", len(selected_energies_keV))
selected energies: [653.7, 660.7, 662.2, 663.7] selected count: 4
列表最初为空。每次循环处理一个探测器读数:先将 channel 刻度为能量,再判断是否保留。ROOT Tutorial I 中会进一步用 Fill(...) 将这些值累积到直方图的 bin 中。
输入数据、刻度方法和筛选条件分别处理。调整能量区间不会改变刻度关系;修改刻度常数也不需要重写循环。
练习¶
- 改变线性刻度中的增益,先判断哪些能量值会进入峰区,再运行验证。
- 向
raw_channels添加一个读数,检查是否只有当其刻度能量落在选定区间内时,筛选计数才增加。 - 编写返回布尔值的函数
above_threshold(value, threshold),并在循环中使用。 - 将
raw_channels转成 NumPy 数组,用一个数组表达式完成能量刻度。
小结¶
Python 变量不需要显式声明类型,用缩进划分代码块,用 if 和 for 完成判断与循环。列表按顺序保存数值,NumPy 数组支持整组数据的数值运算;函数可将刻度等计算与分析流程分开。模块、类、对象和方法则构成了调用 PyROOT 的基本语法。
接下来阅读 ROOT Tutorial I — PyROOT。