eis/eqpalg/algs/UNIFIED_EXPRESSION_ENGINE.md

377 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 统一表达式引擎设计:消除双 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 类 | 存在 | 删除 |