2. 算法环境搭建:Python科学计算环境配置、NumPy与Matplotlib基础、实时仿真框架设计

好,咱们正式开始动手了。这一章说白了就是「磨刀」——把Python科学计算的环境搭好,把NumPy和Matplotlib这两个核心工具练熟,最后再搭一个能跑实时仿真的小框架。我个人习惯是先把地基打牢,后面写算法时才不会手忙脚乱。

2.1 Python科学计算环境配置

先说说环境。我建议你用Anaconda,省心。我在项目中遇到过好几次因为Python版本或包依赖冲突导致仿真跑不起来的情况,后来统一用conda管理环境,再没出过这种低级问题。

推荐配置清单:

  • Python 3.8 或 3.9(稳定,兼容性好)
  • NumPy 1.21+
  • Matplotlib 3.4+
  • SciPy 1.7+(后面会用)
  • Jupyter Notebook 或 VS Code(看个人喜好)

创建环境的命令很简单:

conda create -n ventilator python=3.9
conda activate ventilator
pip install numpy matplotlib scipy jupyter

嗯,这里要注意:别用pip装numpy的时候不加版本号,有时候会自动装到最新版,反而跟某些旧代码不兼容。我吃过这个亏。

2.2 NumPy基础:呼吸机数据的「骨架」

NumPy是啥?说白了就是Python里的数组工具箱。呼吸机算法里,压力、流量、容积这些信号,本质上都是一串数字序列——NumPy就是用来高效处理这些序列的。

举个例子,模拟一个呼吸周期内的流量波形:

import numpy as np

# 模拟一个呼吸周期,采样率100Hz,周期4秒
fs = 100  # 采样率
T = 4.0   # 周期
t = np.linspace(0, T, int(fs * T), endpoint=False)

# 吸气相:正弦波形(简化版)
flow = np.zeros_like(t)
inspiratory_idx = t < 2.0  # 吸气2秒
flow[inspiratory_idx] = 60 * np.sin(np.pi * t[inspiratory_idx] / 2.0)

print(f"流量数组长度: {len(flow)}")
print(f"最大流量: {np.max(flow):.2f} L/min")

你想想看,如果没有NumPy,你得用Python原生列表一个个算,慢不说,代码还丑。NumPy的向量化操作,一次搞定整个数组,这才是做实时仿真的底气。

我的小技巧: 写呼吸机算法时,多用 np.where() 和布尔索引来分段处理波形。比如区分吸气相和呼气相,一行代码就搞定,比写if-else快多了。

2.3 Matplotlib基础:把波形「画」出来

光有数据不行,你得看得见波形。Matplotlib就是干这个的。我在调试呼吸机算法时,90%的时间都在看波形图——压力波形、流量波形、容积波形,一眼就能看出算法有没有问题。

画一个简单的流量-时间图:

import matplotlib.pyplot as plt

plt.figure(figsize=(10, 4))
plt.plot(t, flow, 'b-', linewidth=2, label='Flow')
plt.xlabel('Time (s)')
plt.ylabel('Flow (L/min)')
plt.title('Simulated Inspiratory Flow Waveform')
plt.grid(True, alpha=0.3)
plt.legend()
plt.show()

我曾经犯过一个错:画图时忘了加网格线,结果波形稍微有点偏移根本看不出来。后来我养成了习惯,所有仿真图都加网格,alpha设成0.3,不刺眼又清晰。

避坑指南: 如果你在Jupyter里画图,记得加一行 %matplotlib inline,否则图可能不显示。另外,实时仿真时别用 plt.show(),它会阻塞程序。后面我们会用 plt.pause() 来实现动态更新。

2.4 实时仿真框架设计

好,重头戏来了。呼吸机算法不是跑一次就完事的,它需要「实时」地采集数据、计算、输出控制信号。所以我们要搭一个仿真框架,模拟这个循环过程。

框架的核心思想很简单:一个无限循环,每次循环处理一个时间步的数据。我把它叫做「心跳循环」。

import time
import numpy as np
import matplotlib.pyplot as plt

class VentilatorSimulator:
    def __init__(self, fs=100):
        self.fs = fs
        self.dt = 1.0 / fs
        self.time = 0.0
        self.flow = 0.0
        self.pressure = 0.0
        self.volume = 0.0
        
        # 画图准备
        plt.ion()  # 交互模式
        self.fig, self.ax = plt.subplots(3, 1, figsize=(10, 6))
        self.time_data = []
        self.flow_data = []
        self.pressure_data = []
        self.volume_data = []
    
    def step(self):
        """一个时间步的仿真"""
        # 这里放你的呼吸算法
        # 比如简单的正弦波流量
        self.flow = 60 * np.sin(2 * np.pi * 0.25 * self.time)
        self.pressure = 20 + 5 * np.sin(2 * np.pi * 0.25 * self.time)
        self.volume += self.flow * self.dt / 60.0  # 积分求容积
        
        self.time += self.dt
        
        # 记录数据
        self.time_data.append(self.time)
        self.flow_data.append(self.flow)
        self.pressure_data.append(self.pressure)
        self.volume_data.append(self.volume)
    
    def run(self, duration=10.0):
        """运行仿真"""
        start_time = time.time()
        while self.time < duration:
            self.step()
            
            # 每10个时间步更新一次图
            if len(self.time_data) % 10 == 0:
                self.update_plot()
            
            # 控制仿真速度,接近实时
            elapsed = time.time() - start_time
            if elapsed < self.time:
                time.sleep(self.time - elapsed)
    
    def update_plot(self):
        """更新波形图"""
        self.ax[0].cla()
        self.ax[0].plot(self.time_data, self.flow_data, 'b-')
        self.ax[0].set_ylabel('Flow (L/min)')
        self.ax[0].grid(True, alpha=0.3)
        
        self.ax[1].cla()
        self.ax[1].plot(self.time_data, self.pressure_data, 'r-')
        self.ax[1].set_ylabel('Pressure (cmH2O)')
        self.ax[1].grid(True, alpha=0.3)
        
        self.ax[2].cla()
        self.ax[2].plot(self.time_data, self.volume_data, 'g-')
        self.ax[2].set_ylabel('Volume (L)')
        self.ax[2].set_xlabel('Time (s)')
        self.ax[2].grid(True, alpha=0.3)
        
        plt.pause(0.01)

# 启动仿真
sim = VentilatorSimulator()
sim.run(duration=10.0)

框架设计要点:

  • 模块化: step() 方法只做一件事——推进一个时间步。这样方便后面替换不同的呼吸算法。
  • 实时性:time.sleep() 控制仿真速度,让仿真时间和真实时间尽量同步。
  • 可视化:plt.ion()plt.pause() 实现动态更新,而不是每次都重新画图。

你可能会问:「为什么要用类?直接写函数不行吗?」嗯,我刚开始也是用函数写的,但后来发现要加参数、要保存状态、要切换模式,类封装起来方便太多了。尤其是后面要加PID控制器、报警逻辑,类结构能让你少写很多重复代码。

我的经验: 这个框架我用了好几年,从简单的正弦波仿真一直迭代到完整的PCV、VCV模式。每次加新功能,只需要在 step() 里改算法逻辑,框架本身几乎不用动。这就是好的架构设计——稳定、可扩展。

2.5 本章小结

这一章我们干了三件事:搭环境、练NumPy和Matplotlib、搭实时仿真框架。说白了就是让电脑能算、能画、能跑实时。下一章我们会往这个框架里塞真正的呼吸算法——从最简单的定压通气开始。

哦对了,如果你在配置环境时遇到问题,别硬扛。我当年为了装一个旧版本的NumPy折腾了一下午,最后发现是conda源的问题。换清华源,一分钟搞定。

好,动手试试吧。把上面的代码跑起来,看看波形是不是在动。如果不动,检查一下 plt.ion() 有没有加。嗯,就这些,下章见。