【人工智能驱动的电催化研究-05】条件、循环与数据检查

上一节,我们把电催化信息组织成了"列表中的字典"。现在,程序已经能够找到一条记录的电流、面积、电位与来源。

但如果某条记录的面积为零、电流没有填写,或者参考电极尚未确认,程序应该怎样处理?

本节学习如何把科研中的检查要求写成明确规则,并让程序逐条执行。

一、本节学习目标

完成本节后,你应能够:

  1. ++理解条件判断产生的`True`与`False`。++
  2. ++使用`if`、`elif`和`else`选择执行步骤。++
  3. ++使用`for`循环逐条处理记录。++
  4. ++区分缺字段、未知值、空白文字与数值零。++
  5. ++在计算前检查数据,并保存问题原因。++
  6. ++保留原始记录,区分"可以计算"和"科学可信"。++
  7. ++根据实际证据总结前五节的学习情况。++

本节使用Python标准库,不定义函数,不读取CSV,也不训练机器学习模型。函数与文件读写分别安排在第六、七节。

二、为什么不能直接对所有记录计算?

电流密度的基本关系是:

\ j_{\\mathrm{geo}}=\\frac{I}{A_{\\mathrm{geo}}} \\

其中,\(I\)是电流,\(A_{\mathrm{geo}}\)是几何面积,\(j_{\mathrm{geo}}\)是按几何面积计算的平均电流密度。

如果电流为−12.5 mA、面积为0.50 cm²,则:

\ j_{\\mathrm{geo}}=-25\\ \\mathrm{mA/cm\^2} \\

但遇到下面的记录,就不能直接相除:

复制代码
record = {
    "current_mA": None,
    "area_cm2": 0.0,
}

电流未知,面积又是零。即使只修复其中一个问题,另一个问题仍然存在。

因此,一套基础处理流程应当是:

复制代码
读取记录
→ 检查字段与数值
→ 记录问题
→ 判断是否满足本次计算要求
→ 计算或暂缓
→ 保存处理结果

这里的"暂缓"表示保留记录并等待核实,不等于删除数据。

三、条件判断:把检查要求变成真假问题

假设:

复制代码
area_cm2 = 0.50

表达式:

复制代码
area_cm2 > 0

提出的问题是:"面积是否大于零?"

结果为:

复制代码
True

如果面积为0,则结果为:

复制代码
False

True与`False`是布尔值,类型名为`bool`,分别表示真与假。它们不需要引号。

复制代码
True

与:

复制代码
"True"

不同:前者是布尔值,后者是字符串。

常用比较符号如下:

符号 含义 示例
> 大于 area_cm2 > 0
< 小于 current_mA < 0
>= 大于等于 temperature_K >= 273.15
<= 小于等于 area_cm2 <= 0
== 比较相等 reference == "RHE"
!= 比较不相等 reference != "RHE"

需要区分:

复制代码
area_cm2 = 0.50

是赋值,而:

复制代码
area_cm2 == 0.50

是比较。

检查规则还必须符合物理意义。负电流可能符合阴极电流的符号约定,不能一律当作错误;面积作为本课归一化分母,则应为正。

四、用if和else决定执行什么

先看一个完整小例子:

复制代码
area_cm2 = 0.50

if area_cm2 > 0:
    print("Area can be used")
else:
    print("Check area before division")

执行过程是:

  1. 判断`area_cm2 > 0`。
  2. 如果成立,执行第一段输出。
  3. 否则,执行`else`下面的输出。

冒号后面的缩进表示代码块。这里统一采用四个空格。

缩进决定执行结构,不只是排版。

例如:

复制代码
if area_cm2 > 0:
    print("Positive area")

print("Check completed")

最后一行没有缩进,因此无论面积是否为正,都会执行。

如果希望区分多个情况,可以使用`elif`,意思是"否则,再检查一个条件":

复制代码
area_cm2 = None

if area_cm2 is None:
    print("Missing area")
elif type(area_cm2) not in (int, float):
    print("Area must be numeric")
elif area_cm2 <= 0:
    print("Area must be positive")
else:
    print("Ready for calculation")

这段代码先检查缺失,再检查类型,最后检查数值范围。

这样安排,是为了避免直接对`None`或文字进行大小比较。条件分支与循环的基本行为见Python官方控制流教程。

五、缺失、零和空文字不能混为一谈

下面四种情况不同:

复制代码
record = {}

这是没有对应字段。

复制代码
record = {"current_mA": None}

这是字段存在,但明确标记为未知。

复制代码
record = {"current_mA": ""}

这是空字符串。

复制代码
record = {"current_mA": 0.0}

这是一个明确的数值零。

零电流可能是真实工作点;零电位也可能具有明确物理含义。它们不能因为数值为零就被当作缺失。

因此,不宜这样检查电流是否缺失:

复制代码
if not current_mA:
    print("Missing current")

因为`0.0`在这种判断中也会被当作假值。

本节使用明确判断:

复制代码
if current_mA is None:
    print("Missing current")

面积为零则不同:它不能作为本课的归一化分母。检查规则取决于字段的物理意义。

同样,未知温度不能直接填入"室温",未知参考电极也不能默认写成RHE。

六、for循环:对每条记录重复执行步骤

上一节需要分别写三条计算语句:

复制代码
sample_a["j_mA_cm2"] = sample_a["current_mA"] / sample_a["area_cm2"]
sample_b["j_mA_cm2"] = sample_b["current_mA"] / sample_b["area_cm2"]
sample_c["j_mA_cm2"] = sample_c["current_mA"] / sample_c["area_cm2"]

当记录增加时,可以用循环重复执行同一套步骤。

先看最简单的例子:

复制代码
record_ids = ["A_run01", "B_run01", "C_run01"]

for record_id in record_ids:
    print(record_id)

输出:

复制代码
A_run01
B_run01
C_run01

每一轮,程序从列表中取一个元素,用`record_id`这个名字引用它,再执行缩进块。

对于字典记录,可以写:

复制代码
for record in records:
    print(record["record_id"])

这里的`record`依次代表一条字典。

循环只是重复我们写定的步骤,不会自动学习检查规则,也不等于机器学习。

七、组合条件与检查字段

有些检查需要多个条件配合。

  • and:两个条件都成立。
  • or:至少一个条件成立。
  • not:对真假结果取反。
  • in:判断是否包含某项。
  • not in:判断是否不包含某项。

例如:

复制代码
if "current_mA" not in record:
    print("Current field is absent")

对字典使用`in`时,检查的是键是否存在。

检查文字是否为空,可以使用:

复制代码
value.strip() == ""

strip()返回去除首尾空白后的文字。例如,只含空格的字符串经过处理后就是空字符串。

但应先确认`value`是字符串,再调用文字工具:

复制代码
if type(value) is str and value.strip() != "":
    print("Non-empty text")

and从左往右判断;如果前面不成立,就不会继续执行右边。这可以避免对`None`调用字符串操作。

八、数值类型正确,还要检查是否有限

科研数据处理中可能遇到:

  • NaN:一种"非数值"的特殊浮点表示。
  • inf:无穷大。

它们不适合作为本课的普通测量数值直接计算。

Python标准库提供:

复制代码
import math

print(math.isfinite(12.5))

输出`True`。如果数值是NaN或无穷大,则返回`False`。官方说明

调用前仍要先确认类型。

本课使用:

复制代码
type(value) not in (int, float)

其中,`(int, float)`把允许的两种类型组成一个组合,称为元组。本节只需理解它在这里列出允许类型的作用。

这项检查也会挡住`True`和`False`,避免把布尔标记当成1或0的测量值。

九、明确本次数据检查规则

本次练习检查四个数值字段:

复制代码
numeric_fields = [
    "current_mA",
    "area_cm2",
    "potential_mV",
    "temperature_K",
]

它们必须:

  1. 字段存在。
  2. 不为`None`。
  3. 是本课接受的数值类型。
  4. 是有限数值。
  5. 面积为正,温度符合本练习的正值要求。

文字字段包括编号、反应、参考、电解液、面积基准、iR状态和来源,要求是非空字符串。

此外:

  • 本批记录编号不得重复。
  • 本次只处理HER工作点。
  • 电位参考为RHE。
  • 面积基准为几何面积。

最后三项是本次任务的准入范围,不是通用物理定律。采用其他参考电极或面积基准的数据,可能完全有效,但需要相应处理,不能直接混入当前计算。

这些检查也不能发现全部问题。例如,字段名写`current_mA`,实际却填入以A表示的数值,程序未必能自动识别。非空电解液说明也不证明不同记录条件相同。

十、用七条教学记录检验程序

本节沿用第四节的三条教学工作点:

记录 电流/mA 几何面积/cm² 预期电流密度/(mA/cm²)
A −12.5 0.50 −25
B −20.0 0.50 −40
C −10.0 0.25 −40

共同条件为HER、−180 mV vs RHE、0.10 mol/L HClO₄、298.15 K、几何面积归一化、未应用iR补偿。

这些数值全部为教学设定,不是实验测量。

然后加入四条故障测试记录:

输入 设置的问题 预期处理
D 面积为0 暂缓计算
E 电流为`None` 暂缓计算
F 参考电极为`None` 暂缓计算
重复A 再次使用A的记录编号 保留并标记重复

本课用`PASS`表示满足当前规则,用`HOLD`表示保留待核实。

HOLD不等于实验无效,更不等于删除。

十一、完整、可运行的Python代码

下面代码使用第四节学过的平面字典复制来构造教学输入,再进行批量检查。

复制代码
import math

# All input records are teaching assumptions, not measured data.
sample_a = {
    "record_id": "A_run01",
    "sample_id": "demo_A",
    "reaction": "HER",
    "potential_mV": -180.0,
    "reference": "RHE",
    "electrolyte": "0.10 mol/L HClO4",
    "temperature_K": 298.15,
    "current_mA": -12.5,
    "area_cm2": 0.50,
    "area_basis": "geometric",
    "ir_compensation": "not applied",
    "source": "teaching, not measured",
}

sample_b = sample_a.copy()
sample_b["record_id"] = "B_run01"
sample_b["sample_id"] = "demo_B"
sample_b["current_mA"] = -20.0

sample_c = sample_a.copy()
sample_c["record_id"] = "C_run01"
sample_c["sample_id"] = "demo_C"
sample_c["current_mA"] = -10.0
sample_c["area_cm2"] = 0.25

bad_area = sample_a.copy()
bad_area["record_id"] = "D_run01"
bad_area["area_cm2"] = 0.0

missing_current = sample_a.copy()
missing_current["record_id"] = "E_run01"
missing_current["current_mA"] = None

missing_reference = sample_a.copy()
missing_reference["record_id"] = "F_run01"
missing_reference["reference"] = None

duplicate_id = sample_a.copy()

records = [
    sample_a, sample_b, sample_c, bad_area,
    missing_current, missing_reference, duplicate_id,
]

numeric_fields = [
    "current_mA", "area_cm2", "potential_mV", "temperature_K",
]

text_fields = [
    "record_id", "sample_id", "reaction", "reference",
    "electrolyte", "area_basis", "ir_compensation", "source",
]

seen_ids = []
accepted = []
audit = []

for record in records:
    reasons = []
    j_value = None

    for key in numeric_fields:
        if key not in record:
            reasons.append(key + ": absent")
        elif record[key] is None:
            reasons.append(key + ": missing")
        elif type(record[key]) not in (int, float):
            reasons.append(key + ": not numeric")
        elif not math.isfinite(record[key]):
            reasons.append(key + ": not finite")
        elif key == "area_cm2" and record[key] <= 0:
            reasons.append(key + ": must be positive")
        elif key == "temperature_K" and record[key] <= 0:
            reasons.append(key + ": must be positive for this experiment")

    for key in text_fields:
        value = record.get(key)
        if type(value) is not str:
            reasons.append(key + ": missing or not text")
        elif value.strip() == "":
            reasons.append(key + ": empty text")

    record_id = record.get("record_id")

    if type(record_id) is str and record_id.strip() != "":
        if record_id in seen_ids:
            reasons.append("record_id: duplicate")
        else:
            seen_ids.append(record_id)

    # Eligibility rules for this exercise
    if record.get("reaction") != "HER":
        reasons.append("reaction: outside this exercise")

    if record.get("reference") != "RHE":
        reasons.append("reference: unconfirmed or outside this exercise")

    if record.get("area_basis") != "geometric":
        reasons.append("area_basis: outside this exercise")

    if len(reasons) == 0:
        j_value = record["current_mA"] / record["area_cm2"]

        if not math.isfinite(j_value):
            reasons.append("computed j: not finite")
            j_value = None

    if len(reasons) == 0:
        checked_record = record.copy()
        checked_record["j_mA_cm2"] = j_value
        accepted.append(checked_record)
        status = "PASS"
    else:
        status = "HOLD"

    audit.append({
        "record_id": record_id,
        "status": status,
        "reasons": reasons,
        "j_mA_cm2": j_value,
    })

    print(record_id, status, "j =", j_value, "reasons =", reasons)

print("Total records:", len(records))
print("Passed for this exercise:", len(accepted))
print("Held for review:", len(records) - len(accepted))
print("Raw inputs were preserved; PASS is not scientific validation.")

十二、逐段理解代码

代码较长,但可以分成六个清楚的部分。

第一部分构造输入。前三条是可计算教学记录,后四条用于检验错误能否被发现。

第二部分定义字段规则。把要检查的字段名称放入列表,避免逐条重复写检查语句。

第三部分是外层循环:

复制代码
for record in records:

每一轮处理一条记录。循环内部重新建立:

复制代码
reasons = []

这样上一条记录的问题不会传到下一条。

第四部分使用内层循环逐个检查字段:

复制代码
for key in numeric_fields:

数值检查采用"是否存在→是否未知→类型→有限性→范围"的顺序,避免在前提不满足时执行不合适的操作。

第五部分在全部检查通过后才计算。结果写入复制得到的新字典,原输入不增加电流密度字段。

第六部分保存审计信息:

复制代码
audit.append(...)

audit保存所有记录的状态和原因;`accepted`只保存本次可计算的副本。

seen_ids放在外层循环之外,是因为它需要记住前面见过的编号。如果每轮都清空,就无法发现重复。

本例按输入顺序接纳首次出现的编号,对后续重复编号暂缓。这只是教学处理约定,不证明首条记录一定正确;真实冲突仍须回查来源。

十三、运行结果与数学核对

在项目根目录的PowerShell中运行:

复制代码
& 'C:\Users\Admin\AppData\Roaming\uv\python\cpython-3.11-windows-x86_64-none\python.exe' '.\01_课程\Day05\code\Day05_checks.py'

本机代码已实际运行,主要输出为:

复制代码
A_run01 PASS j = -25.0 reasons = []
B_run01 PASS j = -40.0 reasons = []
C_run01 PASS j = -40.0 reasons = []
D_run01 HOLD j = None reasons = ['area_cm2: must be positive']
E_run01 HOLD j = None reasons = ['current_mA: missing']
F_run01 HOLD j = None reasons = ['reference: missing or not text', 'reference: unconfirmed or outside this exercise']
A_run01 HOLD j = None reasons = ['record_id: duplicate']
Total records: 7
Passed for this exercise: 3
Held for review: 4

三个通过记录分别满足:

\ j_A=-12.5/0.50=-25\\ \\mathrm{mA/cm\^2} \\\ j_B=-20.0/0.50=-40\\ \\mathrm{mA/cm\^2} \\\ j_C=-10.0/0.25=-40\\ \\mathrm{mA/cm\^2} \\

反向核对时,\(jA\)应恢复原电流。

计数也应满足:

\ N_{\\mathrm{输入}} = N_{\\mathrm{通过}} + N_{\\mathrm{暂缓}} = 3+4=7 \\

F记录有两个问题原因,但仍然只是一条暂缓记录。不能把原因条数当成暂缓记录数。

同样,3/7只是本教学批次的准入比例,不是测量准确率,也不是模型准确率。

额外边界测试已确认:零电流可以通过;`None`、数字文字、布尔值、NaN和无穷值会被拦截。原始记录保持不变。

十四、真实电催化数据为什么还需要物理审查?

此前课程使用了Nørskov等2005年论文中的Pt记录,其指标是交换电流密度的对数:

\ \\log_{10}\\left\[\\frac{j_0}{1\\ \\mathrm{A/cm\^2}}\\right=-3.1 \]

文献为 Trends in the Exchange Current for Hydrogen Evolution ,发表于 Journal of The Electrochemical Society 。论文DOI

这条真实文献记录不能直接放入本节的工作点\(I/A\)计算流程,因为指标定义不同,而且没有为当前流程核实完整的工作点字段。

正确做法是保留来源、说明缺少什么、决定是否适合当前任务。不能为了让程序通过,就补入教学设定的电位、面积和电解液。

同样,一条记录通过本节格式检查,也不能证明:

  • 原始测量真实且无误。
  • 单位名称与实际数据一致。
  • 不同实验条件已经可比。
  • 几何电流密度代表本征活性。
  • 数据已经适合训练模型。

本节检查是科研质量控制的一个起点。

十五、练习与验收

基础练习:

  1. 解释`=`与`==`,以及`True`与`"True"`的区别。
  2. 对`None`、0、负面积和正常面积,预测程序经过哪些分支。
  3. 用`for`打印三条记录的编号。
  4. 解释为什么`reasons`每轮清空,而`seen_ids`不清空。

进阶练习:

  1. 在副本中把电流改为0,确认记录可以通过且\(j=0\)。
  2. 分别用数字文字、NaN和`True`替换电流,观察原因。
  3. 同时缺少电流和参考电极,确认多个问题均被记录。
  4. 测试缺键、空白来源和不在本次范围内的参考电极。
  5. 核对原始输入没有新增电流密度字段。

科研思考:

  1. 发现未知值后,能否直接删除所有未知样本?
  2. 重复记录编号能否自动取平均?
  3. 通过本节检查是否意味着可以直接用于机器学习?
  4. 根据电流大小删除"低活性异常值",可能怎样改变研究结论?

本节按概念25分、数学20分、代码25分、科学解释20分、复现记录10分评分。

达到80分,且能做到先检查后计算、正确区分未知与零、保留原始信息及暂缓原因、不把`PASS`当作科研验证,才算通过本节验收。

十六、第一次五天学习总结

前五节已经建立以下学习路线:

单元 课程内容 需要由学习者独立证明的能力
Day 1 AI基本概念 解释概念关系、训练与验证
Day 2 文件与运行环境 保存脚本并确认实际解释器
Day 3 变量与单位 正确换算、保留参考与符号
Day 4 列表与字典 组织带条件、可溯源的记录
Day 5 条件与循环检查 批量检查、保存问题并核查结果

下一节严格按目录学习 "函数、参数、返回值与错误定位"。

相关推荐
IT_陈寒20 分钟前
JavaScript项目实战经验分享:从内存泄漏到性能优化的血泪史
前端·人工智能·后端
小oo呆2 小时前
【自然语言处理与大模型】如何编写优质的Skill?
人工智能
非优秀程序员10 小时前
第12章 烧钱无底洞!!!不烧就死,烧了也不知道啥时候死|《互联网产品恩仇录》
人工智能
Godspeed Zhao10 小时前
从零开始学AI25——无监督学习之层次聚类
学习·机器学习·聚类
Zelman94511 小时前
第06章-交互与观察动作空间
人工智能
冬奇Lab11 小时前
开源项目第258期:Harness² —— Google Research 的「递归式 Agent Harness 优化」,模型权重不动,只改 Agent 的外壳
人工智能·开源
字节跳动视频云技术团队12 小时前
每两家头部手机厂商就有一家选它:火山引擎如何成为 AI 手机助手的“隐形底座”?
人工智能
泯泷13 小时前
像项目经理一样指挥 AI 团队:管理者模式
人工智能·llm·agent