Contents — find the section you need

CEA 养分水箱——补充与稀释的核算

同时恢复体积和浓度

一个水箱装有 90 L 溶液,被追踪组分的浓度为 80 mg/L。加入 10 L 不含该组分的水,可将体积恢复到 100 L,但浓度会降至 72 mg/L。因此,液位偏低和组分不足需要分开核算。

本指南在根区基础的基础上,扩展到离散操作。所有数值均为合成值;100 mg/L 只是一个算术目标,不是作物推荐值。我们追踪一个已知浓度的组分,不涉及计算肥料配方、由 EC 推算浓度,或计算酸/碱投加量。

定义边界

假设一个完全混合的水箱,作物回路的供水和回水均已停止。在每次操作期间,不发生吸收、蒸发、沉淀或反应,液体体积可加和。一个正在运行的水箱在其平衡式中需要供水、回水体积和组分流量。如果边界包围了整个循环系统,内部循环就不算外部补充。

对于体积 V [L]、浓度 C [mg/L] 和溶解组分质量 M [mg],有 M=VC。加入体积为 V_a、浓度为 C_a 的液体后,得到

V_1=V_0+V_a,\qquad M_1=V_0C_0+V_aC_a,\qquad C_1=\frac{M_1}{V_1}

只有在加入的水完全不含被追踪组分时,才使用 C_a=0。OSU 的养分管理指南描述了水源分析,以及对盐浓度、pH、碱度和养分比例的关注。

比较三种添加方式

从 90 L、80 mg/L 开始:共 7,200 mg。假设该组分的原液浓度为 10,000 mg/L。

操作 最终体积 最终组分质量 最终浓度
加入 10 L 不含该组分的水 100 L 7,200 mg 72.00 mg/L
仅加入约 0.181818 L 原液 约 90.181818 L 约 9,018.18 mg 100.00 mg/L
加入 0.28 L 原液和 9.72 L 水 100 L 10,000 mg 100.00 mg/L

原液同样会增加体积。对于目标浓度 C_t、原液浓度 C_s 和原液体积 x,

\frac{V_0C_0+xC_s}{V_0+x}=C_t \quad\Rightarrow\quad x=\frac{V_0(C_t-C_0)}{C_s-C_t}

要提高浓度,须满足 C_s>C_t>C_0。以目标浓度本身作为原液浓度,无法通过有限的添加量将较低的初始浓度精确调到目标值。得到负数解则说明所提议的添加方式无法达成目标。

如果最终体积 V_f 也已给定,且补充水中不含该组分,则

x=\frac{V_fC_t-V_0C_0}{C_s},\qquad V_{water}=V_f-V_0-x

两项添加量都必须非负,且最终体积须能放入容器。此处得到 0.28 L 原液和 9.72 L 水。仅指定浓度与同时指定浓度和体积,会得到不同的答案。

排水和稀释的效果不同

现在从 100 L、120 mg/L 开始:共 12,000 mg。排出 10 L 充分混合的溶液会移除 1,200 mg。剩余为 90 L、10,800 mg,浓度仍为 120 mg/L。再加入 10 L 不含该组分的水,得到 100 L、108 mg/L。

如果不排水,直接加入 20 L 水,则得到 120 L、100 mg/L。水箱中仍含有 12,000 mg,因此需要更多备用容量。

情形 方程 边界条件
先排出 D L,再用水补充 C_1=C_0(1-D/V_0) 完全混合;补水不含该组分
不排水,直接加入 W L 水 C_1=V_0C_0/(V_0+W) 体积增加
恒定体积下同时进出流 C(t)=C_{in}+(C_0-C_{in})e^{-Qt/V} 完全混合,恒定流量,无吸收或反应

对于同时置换 10 L 的情形,Qt/V=0.1 且 C_{in}=0 时得到约 108.58 mg/L。这与先排水再补充得到的 108 mg/L 不同。操作顺序应记录在日志中。

用 Python 复现核算过程

保存为 tank_accounting.py,运行 python3 tank_accounting.py。它只使用标准库。

from math import isfinite


def mix(v, c, added_v, added_c):
    values = (v, c, added_v, added_c)
    if not all(isfinite(x) for x in values):
        raise ValueError("finite values required")
    if v <= 0 or min(c, added_v, added_c) < 0:
        raise ValueError("invalid volume or concentration")
    return (v * c + added_v * added_c) / (v + added_v)


v, c, target, stock = 90.0, 80.0, 100.0, 10000.0
x = v * (target - c) / (stock - target)
print(f"water top-up: {mix(v, c, 10, 0):.2f} mg/L")
print(f"stock addition: {x:.6f} L, {mix(v, c, x, stock):.2f} mg/L")
y = (100 * target - v * c) / stock
print(f"fixed final volume: stock {y:.2f} L, water {10-y:.2f} L")
print(f"drain then refill: {mix(90, 120, 10, 0):.2f} mg/L")
print(f"dilute only: {mix(100, 120, 20, 0):.2f} mg/L")

输出结果依次为 72.00 mg/L;0.181818 L 与 100.00 mg/L;0.28 L 与 9.72 L;108.00 mg/L;以及 100.00 mg/L。在尝试其他原液浓度之前,请先检查上述可达性条件,并确保分母不为零。

从核算到控制

OSU 的灌溉用水检测指南说明 EC 和 TDS 无法识别单个盐类的浓度。用 EC 替代 C 无法计算某个具体组分的补充量。混合肥料会同时改变多个组分,因此达到一个目标值并不能恢复整体成分配比。

传感器也未必能立即达到完全混合状态下的预测值。OSU 的养分管理指南要求搅拌并等待读数稳定。基于这一区别,本文提出以下控制设计的起点:

  1. 记录起始时间、体积、采样位置、组分浓度的推算依据、EC、液温和 pH。
  2. 记录原液成分、实际添加和排水体积,以及循环状态。仅凭水泵指令时长无法确定实际流量。
  3. 在重新评估之前,等待一段根据设备确定的混合间隔,并等待测量值稳定。避免在注入后立即根据单次局部读数堆叠额外的指令。
  4. 保留预测误差。发现异常水平、流量不符或陈旧的测量值时,应停止进一步添加以展开调查。在实际设备上确立容差和投加量上限。

这并不是一套完整的投配系统规范。这些算例只是在给定假设下验证了体积、组分质量和浓度之间的算术一致性。下一步是使用实测的水泵流量和混合响应,将下达的操作与观测结果连接起来。

CEA 投配泵——体积校准与混合等待时间

在把一次操作称为“20 mL”之前的两项检查

水箱核算指南根据水量和组分平衡计算出了应添加的量。计算出的 20 mL 尚未确定水泵运行时长。注入完成也不意味着此时水箱读数具有代表性。

本指南将指令量到体积的校准与判断注入后何时可重新评估测量值分开处理。以下每一组数据都是原创的合成示例,不是某台设备或某种作物的实测性能数据。代码并不操作设备,也不计算肥料配方。

保持校准条件不变

佐治亚大学的施肥注入器指南说明了定期校准与维护、运行流量范围和压力条件。水比例式注入器的配比不同于本文所讨论的按时间指令控制的水泵的 mL/s 流量。

对于一次收集实验,请遵循设备手册,并保持记录的一致性:

记录项 目的
水泵、速度设置、管路、阀门和维护记录 识别已校准的配置
液体种类、温度以及(如使用)密度 将清水试验与原液试验区分开
吸入液位、出水高度/压力和管路布置 保留收集实验与实际运行之间的差异
排气、气泡和之前的静置时间 识别启动差异
指令开始/停止时间、收集结束时间及收集体积 明确是否包含停止后的滴流

为收集而断开出水管路可能会改变背压。一个简易的收集装置本身并不能确立在实际系统中的输送量。应将初步的清水检验与使用目标液体的确认试验分开保存。

称重时,用液体密度 \rho [g/mL] 换算净收集质量 \Delta m [g]:V=\Delta m/\rho [mL]。不要假设每种液体的密度都是 1 g/mL。请记录天平分辨率、校准状态和计时精度。

长期平均值可能无法代表一次短时添加

假设每个时长各做三次试验,且都从相同的起始状态开始。

指令时长 [s] 三次合成体积 [mL] 平均值 [mL]
10 15, 16, 17 16
20 35, 36, 37 36
30 55, 56, 57 56
60 115, 116, 117 116

均值遵循 V(t)=at+b=2t-4 [mL],其中 a 的单位为 mL/s,b 的单位为 mL。要达到 20 mL 目标,(20-b)/a=12 s。在本例中,仅使用斜率会得到 10 s,实际只有 16 mL。

若改用 60 s 时的平均流量 116/60 mL/s,则要达到 20 mL 约需 10.34 s。该合成方程预测该时长下约产出 16.69 mL。使用哪种“流量”会带来不同结果。

−4 mL 的截距是在采样范围内拟合出的回归系数,不能证明存在真实的两秒纯延迟。不要将结果外推到 10–60 s 范围之外,尤其是向零外推。对于实测数据,应检查残差,并在关系曲线弯曲时考虑单独建立短时长模型。

每行合成数据的样本标准差均为 1 mL。均值上的回归误差为零,并不能消除单次投加的个体差异。三次重复也不能确立日间变异性或天平偏差。应使用未参与拟合的额外时长和试验来检验实测预测值。

将注入完成与混合确认分开

OSU 的养分管理指南要求在评估前搅拌并等待读数稳定。要确立等待时间,需要一份与体积校准数据分开的注入后时间序列。

请同时在远离注入点和靠近注入点的位置记录同步测量值。记录体积、循环流量、注入和传感器位置、温度以及传感器滤波方式。混合、传输和传感器响应共同构成观测到的动态过程;单一拟合出的响应时间不能自动等同于纯粹的混合时间。

作为示例,将从初始值 y_0 到已知最终值 y_\infty 的变化归一化:

z(t)=\frac{y(t)-y_0}{y_\infty-y_0}=1-e^{-t/\tau}

时间零点为注入完成时刻。这是一个合成的局部测量响应,不是一个质量守恒的水箱模型。假设 A、B 两个位置的时间常数分别为 40 s 和 80 s。它们的连续时间响应分别在约 120 s 和 240 s 后进入最终变化量的 5% 带内。

Diagram 1 · Use the button to switch views
合成的指令量-体积数据,以及两条归一化响应曲线。虚线标出了示意性的 95% 响应阈值。

上图:每个时长收集的三次体积以及拟合到其均值的直线。下图:合成响应 A 和 B。时间单位为秒,体积单位为 mL,归一化响应无量纲。图中未显示任何实测数据。

现在设定一条教学规则:以每 10 s 采样一次,两个位置必须在确认前的 30 s 内都保持在 5% 以内。B 首次在 240 s 达标。240、250、260 和 270 s 的四个样本在 270 s 提供确认。若只检查 A,则在 150 s 就会达标,从而忽略了 B。

5% 和 30 s 都不是通用的验收标准。应根据测量噪声/精度、可接受的浓度偏差和采样间隔来选定标准。应独立估算最终值,例如从足够长的记录中估算,并同时检查各位置最终值之间的差异。分别对每个位置做归一化,可能会掩盖持续存在的空间浓度差异。当初始值到最终值的变化相对于噪声很小时,不要使用这种归一化方法。

复现这些计算

保存为 pump_mixing.py,运行 python3 pump_mixing.py。只需要标准库。

from math import exp
from statistics import mean

# Synthetic data only; time [s], collected volume [mL].
runs = {10: [15, 16, 17], 20: [35, 36, 37],
        30: [55, 56, 57], 60: [115, 116, 117]}
ts = list(runs)
vs = [mean(runs[t]) for t in ts]
tm, vm = mean(ts), mean(vs)
a = sum((t-tm)*(v-vm) for t, v in zip(ts, vs)) / sum((t-tm)**2 for t in ts)
b = vm - a*tm
command = (20-b)/a
if not min(ts) <= command <= max(ts):
    raise ValueError("command outside calibration range")
print(f"fit: V = {a:.2f} t {b:+.2f}; 20 mL command = {command:.2f} s")
print(f"held-out 40 s: predicted {a*40+b:.2f} mL; reference 76.00 mL")

# Two synthetic normalized responses, sampled every 10 s.
# Four consecutive samples span 30 s. No missing samples here.
times = list(range(0, 401, 10))
errors = [max(exp(-t/40), exp(-t/80)) for t in times]
ready = next((times[i] for i in range(3, len(times))
              if all(e <= 0.05 for e in errors[i-3:i+1])), None)
print(f"two-location confirmation = {ready} s")

输出结果为斜率 2.00、截距 −4.00、20 mL 目标对应指令时长 12.00 s、40 s 时预测为 76.00 mL,以及在 270 s 时确认。40 s 处的留出验证参考值同样来自同一合成方程,因此它并不能证明该模型可推广到实际设备。将 80 s 的时间常数改为 120 s 后,确认时刻变为 390 s。如果记录中不存在符合条件的窗口,代码会返回 None。

将各项检查与再次添加的许可连接起来

一套控制实现所需要的不只是一个计时器。本文建议将预检查、单次添加、混合/测量等待和重新评估分为独立阶段。超出范围的指令、缺失或陈旧的读数、循环停止、水平异常,或在观测期内未能稳定下来,都应在调查清楚之前阻止进一步添加。不要用前一个数值填补缺失样本,并将其当作连续稳定的证据。

等待之后 EC 读数仍偏低,并不能立即确定是某个特定组分的不足;应保留根区基础中说明的 EC 与成分之间的区别。下一步是在依据实测日志设计具备单次、累计和重试次数限制的投配控制之前,先评估投送预测值和等待标准。

CEA 投配控制——限值、测量等待和故障状态

读数偏低并不能作为反复添加的理由

水泵校准与混合等待把投送体积与观测值稳定所需的时间分开处理。接下来的问题是当前是否允许再次添加。混合过程中的一次低读数、一个陈旧样本或循环停止,都不能确立这一许可。

本指南提出一套教学性的设计方案,包含独立的运行状态,以及对体积、尝试次数和观测值的检查。所有数值和输入均为合成值,不是运行限值或肥料建议。这段 Python 示例只复现一个授权闸门,并不操作水泵。

先计算候选值,再决定是否可以执行

一个由水箱核算推算出的候选值,仍需对照校准范围、设备状态以及能够评估上一次操作的观测值进行检查。

UGA 的注入器指南说明了定期校准与维护。OSU 的养分管理指南要求在评估前搅拌并等待读数稳定。下文的状态和限值是本文提出的设计方案,并非这两份资料给出的控制器规范。

划分六个运行状态

状态 活动 转换条件
CHECK 验证观测值、候选值、剩余额度和设备状态 在进入 DOSE 前记录一次预留额度;若目标已达成则进入 DONE
DOSE 为唯一的操作编号下达一次指令 确认停止后进入 WAIT;超时或流量不符则进入 FAULT
WAIT 禁止新的添加,并收集停止后的观测值 达标观测值进入 REVIEW;未在截止时间前稳定则进入 FAULT
REVIEW 重新评估观测值和历史记录 满意则进入 DONE;有理由再候选则进入 CHECK;出现不一致则进入 FAULT
DONE 保持输出停止并关闭记录 后续操作需要一次新的运行决策
FAULT 请求停止并禁止进一步添加 在核查原因、已投送量和设备状态后,须经明确的恢复流程

下达停止指令,不同于确认流量已经停止。软件状态名称无法防止阀门或水泵故障。一套实现还需要输出反馈、设备侧的停止机制以及明确定义的重启行为。

不要让 FAULT 在短暂等待后自动返回 CHECK。在未查明原因的情况下重复操作,可能会累积非预期的添加。

在下达指令前先预扣额度

采用示例性限值:每次添加 16–20 mL,每次运行会话累计 50 mL,最多三次授权。16 mL 的下限对应上一篇文章中在 10 s 时的合成校准结果。这些数值不是设备设置。

对于已预扣的体积 B [mL] 和候选值 d [mL],须满足

16\le d\le20,\qquad B+d\le50,\qquad n<3

在授权时,须在进入指令阶段之前记录 B\leftarrow B+d 和 n\leftarrow n+1。这份台账代表的是已消耗的授权额度,而不是实测的投送量。如果通信在指令发出后立即中断,不要假设投送量为零并退回其额度。应将实测的投送量与不确定的投送量分开记录。

合成操作 候选值 决策后累计额度 结果
第一次 16 mL 16 mL 允许
第二次,在停止、混合和复查之后 16 mL 32 mL 允许
第三次,复查之后 18 mL 50 mL 允许
第四次 16 mL 50 mL 拒绝:达到尝试次数上限

在预扣 32 mL 后再请求 20 mL 会被拒绝,因为即便还有尝试次数余量,也会达到 52 mL。应重新考虑观测值和候选值的依据,而不是自动把投配量截断到剩余额度。

这里的一次会话是指一个有记录起止时间的纠正序列。一次循环迭代、页面刷新或重新连接不算作新会话。一套实际运行的系统需要明确的额度更新条件,例如一个规定的时间窗口或换箱操作,以及能在重启后保留的记录。本例不涉及持久化存储。

将新鲜度与稳定性分开检查

仅凭接收时间无法确立数据的新鲜度。须考虑源测量时间、时钟同步以及相对于操作的先后顺序。此处 age_s 是已换算好的测量年龄,只接受介于零到五秒之间的值。来自未来时间戳的负值同样会被拒绝。

after_stop 表示该观测值发生在确认上一次停止之后。对于第一次添加,应使用在当前预检查开始之后获得的观测值。stable(稳定)是基于一系列观测值做出的上游判断,正如上一篇指南所讨论的那样。单一不变的数值不能确立稳定性:须评估数据缺失、传感器卡死以及各位置之间的不一致情况。

五秒是一个教学性假设。一个与采样间隔和通信延迟不匹配的限值,可能会拒绝有用的观测值。仅仅增大该限值并不能解决数据陈旧的问题。

在 Python 中尝试这个授权闸门

保存为 dosing_gate.py,运行 python3 dosing_gate.py。只需要标准库。

from math import isfinite


def gate(state, dose_ml, charged_ml, attempts, age_s,
         after_stop, stable, circulation, level_ok):
    """Offline authorization check; no hardware commands or persistence."""
    if state != "CHECK":
        return "STATE"
    if not all(isfinite(x) for x in (dose_ml, charged_ml, age_s)):
        return "INVALID"
    if charged_ml < 0 or type(attempts) is not int or attempts < 0:
        return "INVALID"
    if not all(type(x) is bool for x in
               (after_stop, stable, circulation, level_ok)):
        return "INVALID"
    if not circulation or not level_ok:
        return "INTERLOCK"
    if not 0 <= age_s <= 5 or not after_stop:
        return "STALE"
    if not stable:
        return "UNSTABLE"
    if not 16 <= dose_ml <= 20:
        return "DOSE_RANGE"
    if attempts >= 3:
        return "ATTEMPTS"
    if charged_ml + dose_ml > 50:
        return "BUDGET"
    return "ALLOW"


if __name__ == "__main__":
    charged, attempts, state = 0.0, 0, "CHECK"
    for requested in (16, 16, 18, 16):
        result = gate(state, requested, charged, attempts, 1,
                      True, True, True, True)
        if result == "ALLOW":
            # Charge BEFORE a simulated command; never refund uncertainty.
            charged += requested
            attempts += 1
            state = "WAIT"
        print(f"{requested} mL: {result}; charged={charged:.0f} mL")
        # Synthetic successful stop/mixing/reassessment, not a timer.
        state = "CHECK"

前三次决策均为 ALLOW;第四次为 ATTEMPTS。累计额度依次为 16、32、50 和 50 mL。将演示状态在每次迭代结束时重置为 CHECK,代表的是合成的成功停止、混合和重新评估。一套实现不应仅因为一个固定计时器到期就执行这一转换。

这个函数是一个使用数值参数的离线教学闸门。它不解析通信输入,不计算投配量,不确立稳定性,不监测实际流量,也不执行整个状态机。它会拒绝 NaN、无穷大、负的预扣体积和无效的尝试次数,但并不是一套完整的外部输入 API。

检验拒绝边界

输入或情形 预期结果
WAIT 期间到来另一个候选值 STATE;不额外预扣额度或下达指令
年龄为六秒或负值 STALE
观测值早于停止确认,或不稳定 STALE 或 UNSTABLE
循环停止或液位异常 INTERLOCK
候选值为 15 或 21 mL DOSE_RANGE
已预扣 32 mL 后请求 20 mL BUDGET
候选值或已预扣体积为 NaN INVALID

一套实现还必须测试重发一个操作编号不能被执行两次。须将额度变更和操作编号一并提交,使竞争的请求不能同时消耗同一份剩余额度。中断之后,应在恢复运行之前,将已持久化的指令与设备的实际状态进行核对。

本文确立的内容

这个示例根据给定的输入和边界检验授权决策,不从 EC 推断具体组分的不足,也不从 pH 差异推算酸的用量,同样不验证生长、产量、设备安全或浓度追踪。

核算、投送校准、混合评估和授权这几个环节现在已经相互连接。使用实测设备日志进行验证仍是未完成的工作。接下来是水路穿过 NFT、DWC 和滴灌等生产系统时的路径,其中水箱与根系之间的关系会发生变化。

What to read next

回顾 EC、pH 与组分质量之间的区别。CEA 传感与图像估算——传感器布置与投影叶面积Continue the seriesCEA 水路径与多层水力学——NFT、DWC、滴灌与水头平衡Explore another aspect of this fieldCEA 鱼菜共生——鱼类、微生物和植物的水质边界