377 lines
15 KiB
Markdown
377 lines
15 KiB
Markdown
|
|
# 统一表达式引擎设计:消除双 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 行
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、实施路径
|
|||
|
|
|
|||
|
|
### 阶段一:创建 ExpressionEngine(1 天)
|
|||
|
|
|
|||
|
|
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,仅创建新类并编写独立单元测试**
|
|||
|
|
|
|||
|
|
### 阶段二:替换 ExpModule(1 天)
|
|||
|
|
|
|||
|
|
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 |
|
|||
|
|
| 变量刷新代码份数 | 2(ExpBase + ExpModule) | 1(ExpressionEngine) |
|
|||
|
|
| `is_exp_alg_` 补丁 | 存在 | 消除 |
|
|||
|
|
| 表达式管理入口 | 3 处(ExpBase独立3成员 + ExpModule map) | 1 处(ExpressionEngine 统一 map) |
|
|||
|
|
| 新算法接入方式 | 需了解 is_exp_alg_ 约定 | 直接 registerExpression |
|
|||
|
|
| 状态函数冲突风险 | 存在(同名 funN) | 消除 |
|
|||
|
|
| ExpModule 类 | 存在 | 删除 |
|