eis/eqpalg/algs/UNIFIED_EXPRESSION_ENGINE.md

377 lines
15 KiB
Markdown
Raw Normal View History

# 统一表达式引擎设计:消除双 FunVars 和 is_exp_alg_ 补丁
> 针对 ExpBase::fun_vars_ 与 ExpModule::fun_vars_ 双重存在、is_exp_alg_ 补丁标志的历史遗留问题的根治方案
---
## 一、现状问题精准画像
### 1.1 重复代码对照
```
ExpBase::refresh_exp_vars_mem() ExpModule::refresh_exp_vars_mem()
───────────────────────────────── ─────────────────────────────────
更新 tagN ← 共享内存 更新 tagN ← 共享内存
更新 pN ← 旧 tagN 更新 pN ← 旧 tagN
更新 pvN_0..pvN_5 历史移位 更新 pvN_0..pvN_5 历史移位
更新 "now" ← epoch ms 更新 "now" ← epoch ms
调用 fun_vars_.refresh_fun_vars(false) (在 update() 中调用 fun_vars_.refresh_fun_vars(false))
调用 refresh_hold_var() (无 hold_var 处理)
设置 query_time_range_ (无)
```
两段代码做**几乎相同的事**,操作**同一个 mm_vars**,属于典型的 Cargo Cult 编程。
```
ExpBase::first_fill_mm_vars() ExpModule::init()
───────────────────────────────── ──────────────────
初始化所有 tagN, pN, pvN_0..pvN_5 初始化所有 tagN, pN, pvN_0..pvN_5
初始化 stime, now, etime, time 初始化 "now"
初始化 sN, mx_tagN, mi_tagN (无)
初始化 mv2_tagN, up_tagN, dw_tagN (无)
```
也是重复。
### 1.2 双 FunVars 的冲突风险
```
ExpBase::fun_vars_ ExpModule::fun_vars_
───────────────────── ─────────────────────
管理表达式: 管理表达式:
exp_act_ (带状态函数替换) pre_result (带状态函数替换)
exp_feedback_ (带状态函数替换)
exp_result_ (带状态函数替换)
pre_result (通过 fun_vars_init 共享)
内部index: 独立计数 内部index: 独立计数
创建的 mm_vars 变量: fun0,fun1,... 创建的 mm_vars 变量: fun0,fun1,...
```
**冲突场景**
- ExpModule::add_exp("pre_result", "KeepC(tag1,1)") → 创建 mm_vars["fun0"]
- ExpBase::fun_vars_init() → fun_vars_.add_exp_str("KeepC(tag1,1)") → 也创建 mm_vars["fun0"]
两个 FunVars 实例的 `index` 都从 0 开始,会向 mm_vars 写入同名的 `fun0` 变量。当 `is_exp_alg_=true` 时 ExpModule 跳过刷新,所以两个 `fun0` 不会在同一周期冲突。但如果某天代码修改导致两个 FunVars 都活跃,就会产生难以排查的 bug。
### 1.3 `is_exp_alg_` 传递链的脆弱性
```
AlgBase::is_exp_alg_ = false (默认)
│ 引用传递
ExpModule::is_exp_alg_& ← 持有 AlgBase 成员的引用
│ ExpBase 构造时
AlgBase::is_exp_alg_ = true
│ ExpModule 通过引用看到变化
ExpModule::is_exp_alg_ == true → 跳过变量刷新
```
这条引用链的问题:
- ExpModule 的生命周期依赖 AlgBase引用不能悬挂
- `is_exp_alg_` 被当作"表达式算法"的标记,实际上它是"是否需要 ExpModule 管理变量刷新"的补丁
- 新算法开发者需要理解这个隐式约定才能正确设置
---
## 二、统一方案ExpressionEngine
### 2.1 核心思路
**让 ExpModule 吃掉 ExpBase 的表达式管理职责**,成为唯一的表达式引擎。
```
BEFORE (现状): AFTER (统一后):
AlgBase AlgBase
├── exp_mpdule_ptr_ (ExpModule) └── expr_engine_ (ExpressionEngine)
│ ├── PRR 表达式 ├── 所有命名表达式
│ ├── 自己的 FunVars │ ├── "pre_result" (PRR)
│ └── 变量刷新 (当 is_exp_alg_=false) │ ├── "act" (前提表达式)
│ │ ├── "feedback" (反馈表达式)
ExpBase (is_exp_alg_=true) │ ├── "result" (结果表达式)
├── exp_act_ (独立的 Expression) │ ├── "sample_X", "sample_Y"
├── exp_feedback_ (独立的 Expression) │ ├── "dataX"
├── exp_result_ (独立的 Expression) │ └── "X1".."X9" (Roller2)
├── 自己的 FunVars ├── 唯一的 FunVars
├── 变量刷新 ├── 唯一的变量刷新
└── 自己的表达式配置加载 └── 统一的配置加载
```
### 2.2 ExpressionEngine 完整设计
```cpp
// expression_engine.h
// 统一表达式引擎 —— 替代 ExpModule + ExpBase 的表达式管理
class ExpressionEngine {
public:
// ========== 构造 ==========
ExpressionEngine(std::map<std::string, double>& mm_vars,
std::vector<std::string>& m_tags);
// ========== 表达式注册 ==========
// 注册一个命名表达式。返回 0 成功,-1 失败。
// exp_name: "act", "feedback", "result", "sample_X", "sample_Y",
// "dataX", "X1"..."X9", "pre_result" 等
// raw_exp_str: 原始表达式字符串(含 KeepC(tag1,1) 等)
// use_fun_vars: 是否启用状态函数替换(默认 true
int registerExpression(const std::string& exp_name,
const std::string& raw_exp_str,
bool use_fun_vars = true);
// ========== 每周期数据刷新 ==========
// 从共享内存刷新变量 + fun_vars
void refreshFromMemory();
// 从 IHDB 行数据刷新变量 + fun_vars
void refreshFromIhdRow(int row, const Eigen::MatrixXd& data,
const std::vector<TimePoint>& times);
// 首次填充(冷启动)
void firstFill(int data_source);
// ========== 表达式求值 ==========
double evaluate(const std::string& exp_name);
bool evaluateBool(const std::string& exp_name);
// ========== FunVars 控制 ==========
// 每周期调用一次(通常在 mon_proc 开头)
void autoResetFunVars();
// 标记下周期需要重置
void markFunVarsNeedReset();
// 立即强制重置(报警后调用)
void forceResetFunVars();
// ========== hold(n,T) 变量 ==========
void refreshHoldVars();
// ========== 查询 ==========
bool hasExpression(const std::string& exp_name) const;
std::string getExpressionStr(const std::string& exp_name) const;
std::map<std::string, double>& vars() { return mm_vars_; }
private:
std::map<std::string, double>& mm_vars_;
std::vector<std::string>& m_tags_;
// 唯一的 FunVars 实例
StatExp::FunVars fun_vars_;
bool fun_vars_need_reset_ = false;
// 表达式注册表
std::map<std::string, std::string> exp_raw_strs_; // 原始表达式字符串
std::map<std::string, std::string> exp_processed_; // FunVars 处理后的字符串
std::map<std::string, std::unique_ptr<MExp>> exp_ptrs_; // 编译后的表达式
// hold 变量管理
std::map<std::string, std::unique_ptr<HoldTime>> hold_times_;
// 变量缓存pv 键名管理)
VarsCache var_cache_;
static constexpr size_t PV_NUM = 6;
// 内部方法
void doRefreshVarsMemory();
void doRefreshVarsIhd(int row, const Eigen::MatrixXd& data,
const std::vector<TimePoint>& times);
int initHoldExpStr(const std::string& exp_str);
};
```
### 2.3 实现要点
```cpp
int ExpressionEngine::registerExpression(const std::string& exp_name,
const std::string& raw_exp_str,
bool use_fun_vars) {
if (exp_ptrs_.count(exp_name)) return 0; // 已注册
exp_raw_strs_[exp_name] = raw_exp_str;
std::string processed = raw_exp_str;
if (use_fun_vars) {
auto [ok, result] = fun_vars_.add_exp_str(raw_exp_str, &mm_vars_);
if (!ok) return -1;
processed = result;
}
exp_processed_[exp_name] = processed;
exp_ptrs_[exp_name] = std::make_unique<MExp>(processed, &mm_vars_);
return 0;
}
void ExpressionEngine::refreshFromMemory() {
// 1. 刷新 tag/pv 变量(统一的,只做一次)
for (size_t i = 0; i < m_tags_.size(); i++) {
double current = SingletonTemplate<GlobaltemSharedMemory>::GetInstance()[m_tags_[i]];
auto idx = std::to_string(i + 1);
mm_vars_[var_cache_.p_keys[i]] = mm_vars_[var_cache_.tag_keys[i]];
mm_vars_[var_cache_.tag_keys[i]] = current;
for (size_t j = PV_NUM - 1; j > 0; j--) {
mm_vars_[var_cache_.pv_keys[i][j]] = mm_vars_[var_cache_.pv_keys[i][j - 1]];
}
mm_vars_[var_cache_.pv_keys[i][0]] = current;
}
auto now = system_clock::now();
mm_vars_["now"] = duration_cast<milliseconds>(now.time_since_epoch()).count();
// 2. 刷新 fun_vars只做一次处理所有表达式的状态函数
fun_vars_.refresh_fun_vars(false, &mm_vars_);
// 3. 刷新 hold 变量
refreshHoldVars();
}
void ExpressionEngine::autoResetFunVars() {
if (fun_vars_need_reset_) {
fun_vars_.refresh_fun_vars(true, &mm_vars_);
fun_vars_need_reset_ = false;
}
}
```
### 2.4 各算法使用 ExpressionEngine 的方式
```
// ========== AlgBase ==========
class AlgBase {
protected:
std::unique_ptr<ExpressionEngine> expr_engine_; // 替代 exp_mpdule_ptr_
// 删除: is_exp_alg_
};
// AlgBase::init() 中:
expr_engine_ = std::make_unique<ExpressionEngine>(mm_vars, m_tags);
if (prr_ == 1) {
expr_engine_->registerExpression("pre_result", pre_exp_str);
}
// AlgBase::exec_mon_call() 中:
if (alarm.alarmed) {
expr_engine_->forceResetFunVars(); // 替代 exp_mpdule_ptr_->fun_reset()
}
// AlgBase::get_prr() 中:
expr_engine_->refreshFromMemory(); // 统一刷新(不再有 is_exp_alg_ 判断)
bool prr_result = expr_engine_->evaluateBool("pre_result");
// ========== ExpBase (expression algorithms) ==========
// init() 中:
expr_engine_->registerExpression("act", exp_str);
if (feedback_mode_) {
expr_engine_->registerExpression("feedback", fb_exp_str);
}
expr_engine_->registerExpression("result", result_exp_str);
expr_engine_->firstFill(data_source_);
// exec_mon() 中:
expr_engine_->refreshFromMemory(); // 统一刷新
// ... 然后 mon_proc() 使用 expr_engine_->evaluate("act") 等
// ========== TrendSlope2 (non-expression algorithm) ==========
// 只需要 PRR:
// 在 AlgBase::get_prr() 中 expr_engine_->evaluateBool("pre_result")
// 自身的数据获取不受影响
// ========== Roller2 (multi-expression) ==========
// init() 中:
expr_engine_->registerExpression("pre_exp", pre_str);
expr_engine_->registerExpression("X1", x1_str);
expr_engine_->registerExpression("X2", x2_str);
// ...
```
---
## 三、消除项清单
统一后,以下内容可以**删除**
### 从 ExpBase 删除
| 删除项 | 行数 | 说明 |
|--------|------|------|
| `ExpBase::fun_vars_` | 1 行声明 | 合并到 ExpressionEngine |
| `ExpBase::refresh_exp_vars_mem()` | ~25 行 | 合并到 ExpressionEngine::refreshFromMemory() |
| `ExpBase::refresh_exp_vars_ihd()` | ~45 行 | 合并到 ExpressionEngine::refreshFromIhdRow() |
| `ExpBase::first_fill_mm_vars()` | ~110 行 | 合并到 ExpressionEngine::firstFill() |
| `ExpBase::auto_fun_vars_reset()` | ~15 行 | 合并到 ExpressionEngine::autoResetFunVars() |
| `ExpBase::fun_vars_init()` | ~6 行 | 不再需要(注册时自动处理) |
| `ExpBase::refresh_hold_var()` | ~10 行 | 合并到 ExpressionEngine |
| `ExpBase::init_hold_exp_str()` | ~30 行 | 合并到 ExpressionEngine |
| `ExpBase::exp_messy_code_check()` | ~8 行 | 合并到 ExpressionEngine |
| `exp_act_`, `exp_feedback_`, `exp_result_` | 3 个成员 | 改为通过 expr_engine_->evaluate("act") 等 |
### 从 ExpModule 删除
| 删除项 | 说明 |
|--------|------|
| **整个 ExpModule 类** | 被 ExpressionEngine 完全替代 |
### 从 AlgBase 删除
| 删除项 | 说明 |
|--------|------|
| `is_exp_alg_` | 补丁标志不再需要 |
| `exp_mpdule_ptr_` | 替换为 `expr_engine_` |
### 总计消除
- **重复的变量刷新代码**~180 行ExpBase+ExpModule 各一套)
- **重复的 FunVars 实例**1 个
- **补丁标志 `is_exp_alg_`**1 个布尔 + 引用传递链
- **ExpModule 整个类**~140 行(被 ExpressionEngine 取代)
- **ExpBase 可精简**~260 行
---
## 四、实施路径
### 阶段一:创建 ExpressionEngine1 天)
1. 新建 `eqpalg/utility/expression_engine.h/.cpp`
2. 将 ExpBase 的变量刷新逻辑(`refresh_exp_vars_mem/ihd`、`first_fill_mm_vars`)迁入
3. 将 ExpBase 的 FunVars 管理(`auto_fun_vars_reset`、`refresh_hold_var`、`init_hold_exp_str`)迁入
4. 将 ExpModule 的表达式注册机制(`add_exp`、`update`)迁入并重命名为 `registerExpression` / `evaluate`
5. **此阶段不修改 AlgBase/ExpBase仅创建新类并编写独立单元测试**
### 阶段二:替换 ExpModule1 天)
6. 将 AlgBase 中的 `exp_mpdule_ptr_` 替换为 `expr_engine_`
7. 适配 `AlgBase::init()` 中的 PRR 注册
8. 适配 `AlgBase::get_prr()``AlgBase::exec_mon_call()` 中的 fun_reset 调用
9. 适配所有非 ExpBase 算法TrendSlope2/3、Roller、FaultCode、GlitchDetection
10. **删除 ExpModule 类**
11. **删除 `is_exp_alg_` 标志**
12. 运行非 ExpBase 算法的回归测试
### 阶段三:替换 ExpBase 的表达式管理1-2 天)
13. 将 ExpBase 中的 `exp_act_`/`exp_feedback_`/`exp_result_` 改为通过 `expr_engine_->evaluate()` 访问
14. 删除 ExpBase 的 `fun_vars_`、变量刷新方法、`auto_fun_vars_reset` 等
15. 适配 ExpBase 的所有子类ExpBound、ExpTimes、ExpSample2D、Roller2、Roller3
16. 运行全量回归测试
### 阶段四清理0.5 天)
17. 删除 ExpBase 中不再需要的成员和方法
18. 删除 `exp_base.cpp` 中 ~260 行已迁移代码
19. 更新 build_algorithm.cpp 如需要
---
## 五、收益评估
| 指标 | 优化前 | 优化后 |
|------|--------|--------|
| FunVars 实例数 | 2 | 1 |
| 变量刷新代码份数 | 2ExpBase + ExpModule | 1ExpressionEngine |
| `is_exp_alg_` 补丁 | 存在 | 消除 |
| 表达式管理入口 | 3 处ExpBase独立3成员 + ExpModule map | 1 处ExpressionEngine 统一 map |
| 新算法接入方式 | 需了解 is_exp_alg_ 约定 | 直接 registerExpression |
| 状态函数冲突风险 | 存在(同名 funN | 消除 |
| ExpModule 类 | 存在 | 删除 |