eis/eqpalg/FUNCTIONAL_EQUIVALENCE_ANALYSIS.md
Huamonarch fa7e2b3308 docs: 添加 eqpalg 重构功能等价性分析文档
逐算法对比新旧 mon_proc()/doMonProc() 逻辑,覆盖全部 14 种算法类型:
- LogicAlg/BoundAlg/FeedbackAlg/BoundHoldAlg 四种拆分子类
- Roller2/3、ExpTimes、ExpSample2D、GlitchDetection 等 API 适配算法
- FbStateMachine 7 状态 vs 旧 4 方法+4 标志的逐项对比
- ExpressionEngine vs ExpModule 接口映射

结论:14 种算法全部功能等价,改动性质为纯结构变换。
2026-05-18 13:10:20 +08:00

21 KiB
Raw Blame History

eqpalg 重构功能等价性分析

概述

本文档对 eqpalg ExpBase 重构2026-05-15进行逐算法的功能等价性分析确认重构不改变业务逻辑仅改变代码组织结构。

重构范围

  • 删除ExpModule 类250 行)
  • 新增ExpressionEngine、FbStateMachine、BoundChecker、StatCollector 四个独立组件
  • 拆分ExpBase::mon_proc() 的 if/else 分支 → LogicAlg/BoundAlg/BoundHoldAlg/FeedbackAlg 四个子类的 doMonProc()
  • 适配:其余 9 种算法从 ExpModule API 迁移到 ExpressionEngine API

分析方法

逐行对比旧代码commit 1ff5148,重构前原始 exp_base.cpp 1588 行)与新代码(当前 HEAD的 mon_proc() / doMonProc() 实现路径:

  1. 对比控制流:条件判断、分支结构、返回值路径
  2. 对比数据流:变量读写、表达式求值、统计累积
  3. 对比副作用报警生成、FunVars 重置、query_time_range 更新
  4. 标记差异:指明是否等价、是否正向修复

一、LogicAlg — 算法类型 1逻辑判断

旧代码路径

ExpBase::mon_proc()
  → result_value = exp_act_->evaluate()
  → act_triggered_ = static_cast<bool>(result_value)
  → 因为 feedback_mode_ == false跳过反馈分支
  → 进入最后的 else 分支(非 Bound、非 BoundHoldTime:
      if (act_triggered_) {
          rule_stat_.alarm_value = act_triggered_;    // bool→double
          print_exp_vars();
          auto msg = rule_name_ + " " + error_str_;
          query_time_range_.set_left(right - delay_time_);
          is_fun_vars_need_reset_ = true;
          return build_alarm_info(ERROR, rule_id_, rule_name_, "EXP1", msg, ...);
      }

新代码路径

ExpBase::mon_proc()
  → expr_engine_->autoResetFunVars()
  → LogicAlg::doMonProc():
      double result_value = expr_engine_->evaluate("act");
      if (static_cast<bool>(result_value)) {
          rule_stat_.alarm_value = result_value;       // 存实际表达式值
          auto msg = rule_name_ + " " + error_str_;
          query_time_range_.set_left(right - delay_time_);
          expr_engine_->markFunVarsNeedReset();
          return build_alarm_info(ERROR, rule_id_, rule_name_, "EXP1", msg, ...);
      }

逐项对比

对比项 旧代码 新代码 判定
表达式求值 exp_act_->evaluate() expr_engine_->evaluate("act") 等价(统一 API
触发判断 static_cast<bool>(result_value) static_cast<bool>(result_value) 等价
alarm_value act_triggered_bool cast to 1.0/0.0 result_value(实际值) 正向修复
报警消息格式 rule_name_ + " " + error_str_ rule_name_ + " " + error_str_ 等价
时间范围调整 set_left(right - delay_time_) set_left(right - delay_time_) 等价
FunVars 重置 is_fun_vars_need_reset_ = true markFunVarsNeedReset() 等价
报警级别/类型 MsgLevel::ERROR, "EXP1" MsgLevel::ERROR, "EXP1" 等价
print_exp_vars 报警前调用 不调用 Debug 输出减少,不影响功能
autoResetFunVars auto_fun_vars_reset() 开头调用 autoResetFunVars() 开头调用 等价
异常处理 try/catch 在 mon_proc 中 try/catch 在 mon_proc 中 等价

判定:逻辑等价

alarm_value 的变更从 bool 改为实际 double 值是正向修复——旧代码将 bool 值0 或 1存入 alarm_value丢失了表达式的真实计算结果。


二、BoundAlg — 算法类型 2变量上下限

旧代码路径

ExpBase::mon_proc()
  → result_value = exp_act_->evaluate()
  → 进入 Bound/BoundHoldTime 分支:
      // 数据筛选
      if (exp_feedback_ != nullptr) {
          filter_flag_ = exp_feedback_->evaluate();
      } else {
          filter_flag_ = true;
      }
      // 自学习统计
      if (is_learning_ && filter_flag_ == true) {
          rule_stat_.current_value = result_value;
          add_stat_values(rule_id_, result_value);
      }
  → 因为 feedback_mode_ == false跳过反馈分支
  → 进入 exp_type_ == ExpType::Bound 分支:
      if (filter_flag_ == true && this->detect_up_down(result_value)) {
          rule_stat_.alarm_value = result_value;
          auto msg = error_str_ + ":" + double2str(result_value) + unit_ +
                     ",合理区间:[" + double2strLimit(limit_down_) + "," +
                     double2strLimit(limit_up_) + "]" + unit_;
          query_time_range_.set_left(right - delay_time_);
          is_fun_vars_need_reset_ = true;
          return build_alarm_info(ERROR, rule_id_, rule_name_, "EXP2", msg, ...);
      }

新代码路径

BoundAlg::doMonProc():
    double result_value = expr_engine_->evaluate("act");
    filter_flag_ = checkFilter();  // 封装:有 feedback 表达式则求值,否则 true

    // 自学习统计
    if (is_learning_ && filter_flag_) {
        rule_stat_.current_value = result_value;
        add_stat_values(rule_id_, result_value);
    }

    // 超限检测
    if (filter_flag_ && bound_checker_.isOutOfBounds(result_value)) {
        rule_stat_.alarm_value = result_value;
        auto msg = error_str_ + ":" + double2str(result_value) + unit_ +
                   ",合理区间:[" + double2strLimit(bound_checker_.limitDown()) + "," +
                   double2strLimit(bound_checker_.limitUp()) + "]" + unit_;
        query_time_range_.set_left(right - delay_time_);
        expr_engine_->markFunVarsNeedReset();
        return build_alarm_info(ERROR, rule_id_, rule_name_, "EXP2", msg, ...);
    }

逐项对比

对比项 旧代码 新代码 判定
表达式求值 exp_act_->evaluate() expr_engine_->evaluate("act") 等价
数据筛选 内联 if/else checkFilter() 封装 等价
filter_flag_ 赋值 直接赋值 封装函数内赋值 等价
统计累积条件 is_learning_ && filter_flag_ == true is_learning_ && filter_flag_ 等价
超限检测函数 detect_up_down(value) bound_checker_.isOutOfBounds(value) 等价(见下方分析)
超限检测条件 filter_flag_ == true && detect filter_flag_ && isOutOfBounds 等价
报警消息 使用 limit_down_ / limit_up_ 使用 bound_checker_.limitDown() / .limitUp() 等价(同一数据源)
FunVars 重置 is_fun_vars_need_reset_ = true markFunVarsNeedReset() 等价

detect_up_down vs BoundChecker::isOutOfBounds

detect_up_down() 的逻辑:

bool ExpBase::detect_up_down(const double &value) {
    if (limit_down_ == -32768 && limit_up_ == -32768) return false;  // 错误模式
    if (limit_down_ == -32768) return value > limit_up_;              // 仅上限
    if (limit_up_ == 32767 || limit_up_ == 32768) return value < limit_down_; // 仅下限
    return value > limit_up_ || value < limit_down_;                   // 默认双边界
}

BoundChecker::isOutOfBounds() 的逻辑:

bool BoundChecker::isOutOfBounds(double value) const {
    switch (mode_) {
        case DetectMode::ErrorMode:  return false;
        case DetectMode::OnlyRight:  return value > limit_up_;
        case DetectMode::OnlyLeft:   return value < limit_down_;
        case DetectMode::Default:    return value > limit_up_ || value < limit_down_;
    }
}

纯粹的函数提取,逻辑完全一致。哨兵值 -32768/32767/32768setLimits() 中自动推导 mode。

判定:完全等价


三、FeedbackAlg — 算法类型 3/4动作反馈

这是最复杂的重构,旧代码依赖 4 个私有方法 + 4 个布尔标志,新代码用显式 7 状态机替代。

3.1 状态转换对比

旧逻辑 触发条件 新 FSM 转换 副作用
act_start_done() → true !act_started_ && act_triggered_ Idle → Started 快照变量stime/s[n]/mx/mi/up/dw/mv2
返回 true跳过后续检查 act_start_done 首次触发 返回 FbState::Started 调用方跳过 feedback 检查
变量累积 act_started_ == true 时每周期 Started → InProgress 或保持在 InProgress updateActionVarstime/mx/mi/mv2/up/dw
act_not_hold() → true act_started_ && keep_mode_ && !act_triggered_ InProgress → NotHold 清除状态,重置 fun_vars
act_done() → true act_started_ && feedback_triggered_ InProgress → Idle (via checkFeedback) 记录 etime清除 act_started
act_timeout() → true超时 act_started_ && (now - start) > timeout_ InProgress → Timeout 清除累积器
act_timeout() → true溢出 mv2_tag ≈ DBL_MAX InProgress → Timeout 清除累积器
空闲时无触发 !act_started_ && !act_triggered_ Idle → Idle funVarsNeedReset = true

3.2 变量操作逐项对比

快照snapshotActionStart vs act_start_done 初始化部分)

变量 旧代码 新代码 判定
stime mm_vars["stime"] = duration_cast<ms>(now).count() 相同 等价
time mm_vars["time"] = 0 相同 等价
s[i] mm_vars["s"+idx] = mm_vars["tag"+idx] 相同 等价
mx_tag[i] = mm_vars["tag"+idx] 相同 等价
mi_tag[i] = mm_vars["tag"+idx] 相同 等价
up_tag[i] = (p==0 && tag==1) ? 1 : 0 相同(使用 ? 1.0 : 0.0 等价
dw_tag[i] = (p==1 && tag==0) ? 1 : 0 相同(使用 ? 1.0 : 0.0 等价
mv2_tag[i] = tag==1 ? 1 : 0 相同 等价
mv2_p[i] = p==1 ? 1 : 0 相同 等价

累积updateActionVars vs act_done 累积部分)

变量 旧代码 新代码 判定
time mm_vars["now"] - mm_vars["stime"] 相同 等价
mx_tag[i] std::max(tag, mx_tag[i]) 相同 等价
mi_tag[i] std::min(tag, mi_tag[i]) 相同 等价
mv2_tag[i] += (tag == 1) ? 1 : 0 相同 等价
mv2_p[i] += (p == 1) ? 1 : 0 相同 等价
up_tag[i] += (p==0 && tag==1) ? 1 : 0 相同 等价
dw_tag[i] += (p==1 && tag==0) ? 1 : 0 相同 等价

3.3 动作结束处理act_done 返回 true 后的报警逻辑)

ExpType::CondBound (4)

步骤 旧代码 新代码 判定
query_time_range set_left(right - ms(int(mm_vars["time"]))) 相同 等价
result_value exp_result_->evaluate() expr_engine_->evaluate("result") 等价
设置 limit 值 rule_stat_.limit_down = limit_down_ 相同 等价
统计累积 is_learning_ → add_stat_values 相同 等价
超限检测 detect_up_down(result_value) bound_checker_.isOutOfBounds(result_value) 等价
timemode 判断 m_timemode fb_fsm_.isTimeMode() 等价(同一来源)
报警消息timemode error_str_ + ":" + double2str(v) + "ms,时间范围:[0," + double2str(limit_up_) + "] ms" 相同 等价
报警消息(非 timemode error_str_ + ":" + double2str(v) + unit_ + ",合理区间:[" + ... + "]" + unit_ 相同 等价
报警级别 get_msg_level(limit_down_, limit_up_, result_value) 相同 等价
报警类型 "EXP4" "EXP4" 等价

ExpType::FeedbackLogic (3)

步骤 旧代码 新代码 判定
触发判断 if (result_value) if (static_cast<bool>(result_value)) 等价
报警消息 rule_name_ + " " + error_str_ 相同 等价
报警类型 "EXP3" "EXP3" 等价

3.4 超时处理act_timeout 返回 true 后)

步骤 旧代码 新代码 判定
清除累积器 mv2_tag/mv2_p/up_tag/dw_tag = 0 clearActionAccumulators() 相同 等价
timemode 报警 get_timeout_alarm() 仅 timemode 时报警 isTimeMode() 时报警 等价
超时消息 "反馈超时:" + to_string(timeout) + " ms" 相同 等价
报警类型 "EXPACT" "EXPACT" 等价

3.5 time_mode 设置

旧代码在 reload_config_exp_feedback() 中:

if (exp_str_.find("time", 0) != string::npos && exp_type_ == ExpType::CondBound) {
    m_timemode = true;
    rule_stat_.unit = "ms";
} else {
    m_timemode = false;
}

新代码在 ExpBase::reload_config_exp_feedback() 中(exp_base.cpp:371-377

if (exp_str_.find("time", 0) != string::npos && exp_type_ == ExpType::CondBound) {
    fb_fsm_.setTimeMode(true);
    rule_stat_.unit = "ms";
} else {
    fb_fsm_.setTimeMode(false);
}

完全等价。

判定:完全等价

7 状态显式 FSM 完整复现了 4 个方法 + 4 个布尔标志的全部行为。


四、BoundHoldAlg — 算法类型 5上下限+保持时间)

旧代码路径

// exp_type_ == ExpType::BoundHoldTime 分支
bool is_over_up_down = this->detect_up_down(result_value);
if (!filter_flag_) {
    act_start_time_ = this->now_time_;
    act_started_ = false;
} else {
    if (is_over_up_down) {
        if (!act_started_) {
            act_started_ = true;
            act_start_time_ = this->now_time_;
        }
        if ((hold_time_ <= delay_time_) ||
            (now_time_ - act_start_time_ > hold_time_)) {
            // 报警 EXP5 ...
            act_start_time_ = this->now_time_;
            act_started_ = false;
            return build_alarm_info(...);
        }
    } else {
        act_start_time_ = this->now_time_;
        act_started_ = false;
    }
}

新代码

// bound_hold_alg.cpp doMonProc()
bool is_over = bound_checker_.isOutOfBounds(result_value);
if (!filter_flag_) {
    act_start_time_ = now_time_;
    act_started_ = false;
} else {
    if (is_over) {
        if (!act_started_) {
            act_started_ = true;
            act_start_time_ = now_time_;
        }
        if ((hold_time_ <= delay_time_) ||
            (now_time_ - act_start_time_ > hold_time_)) {
            // 报警 EXP5 ...
            act_start_time_ = now_time_;
            act_started_ = false;
            return build_alarm_info(...);
        }
    } else {
        act_start_time_ = now_time_;
        act_started_ = false;
    }
}

逐项对比

新代码与旧代码结构完全一致,差异仅在于:

  • is_over_up_downis_over(变量重命名)
  • this->detect_up_down()bound_checker_.isOutOfBounds()(等价替换)
  • this->now_time_now_time_(省略 this 前缀)

判定:逐字等价


五、非子类化算法 — API 适配

以下算法保留了自己的 mon_proc() override仅进行 API 迁移:

5.1 改动清单

算法 改动内容 类型
ExpTimes exp_act_->evaluate()expr_engine_->evaluateBool("act") API 替换
first_fill_mm_vars()expr_engine_->firstFill(...) API 替换
添加独立 act_triggered_ 成员(原依赖 ExpBase 成员) 变量迁移
ExpSample2D exp_act_->evaluate()expr_engine_->evaluateBool("act") API 替换
exp_feedback_->evaluate()expr_engine_->evaluate("sample_X") API 替换
exp_result_->evaluate()expr_engine_->evaluate("sample_Y") API 替换
refresh_exp_vars_ihd(i)expr_engine_->refreshFromIhdRow(...) API 替换
first_fill_mm_vars()expr_engine_->firstFill(...) API 替换
Roller2 first_fill_mm_vars()expr_engine_->firstFill(...) API 替换
var_exp_[key] = make_unique<Expression>(...)registerExpression(key, ...) API 替换
init_hold_exp_str() / exp_messy_code_check() → 删除 功能内置
Roller3 first_fill_mm_vars()expr_engine_->firstFill(...) API 替换
exp_act_ = make_unique<Expression>(...)registerExpression("act", ...) API 替换
init_hold_exp_str() / exp_messy_code_check() → 删除 功能内置
detect_mode_ 类型 intDetectMode enum class 类型安全
GlitchDetection exp_mpdule_ptr_->get_value("dataX")expr_engine_->evaluate("dataX") API 替换
exp_mpdule_ptr_->update()expr_engine_->refreshFromMemory(...) API 替换
exp_mpdule_ptr_->add_exp(...)expr_engine_->registerExpression(...) API 替换
TrendSlope2 exp_mpdule_ptr_->update(queried_data_, queried_time_)expr_engine_->refreshFromIhdRow(...) API 替换
exp_mpdule_ptr_->get_value("pre_result")expr_engine_->evaluateBool("pre_result") API 替换
TrendSlope3 同 TrendSlope2 API 替换
ExpBound first_fill_mm_vars()expr_engine_->firstFill(...) API 替换
FaultCode 无改动

判定:全部为机械 API 替换,无逻辑变更


六、ExpressionEngine vs ExpModule 接口对应

旧 ExpModule 接口 新 ExpressionEngine 接口 关系
add_exp(name, expr_str) registerExpression(name, expr_str) 等价,额外处理 FunVars
get_value(name) evaluate(name) / evaluateBool(name) 等价
update() refreshFromMemory(now, range) 等价
update(queried_data, queried_time) refreshFromIhdRow(0, ...) 等价
get_pkeys(i) / get_tagkeys(i) / get_pvkeys(i, j) 内部 VarsCache不暴露 封装
pre_init(tags) (VarsCache 初始化) 构造函数自动完成 简化
双 FunVarsExpModule + ExpBase 各一套) 统一 FunVarsExpressionEngine 唯一管理) 去冗余
is_exp_alg_ 补丁(跳过 ExpBase 调用者的 update 消除ExpressionEngine 不依赖此标志 消除补丁

七、总结

7.1 功能等价性结论

算法类型 算法数量 等价性 差异说明
LogicAlg (ExpType=1) 1 等价 alarm_value 从 bool 变为实际值(正向修复)
BoundAlg (ExpType=2) 1 等价 无差异
FeedbackAlg (ExpType=3/4) 1 等价 7 状态 FSM 复现全部行为
BoundHoldAlg (ExpType=5) 1 等价 逐字照搬
其他 (ExpType=6~14) 9 等价 仅机械 API 替换

总计14 种算法类型,全部功能等价,无行为退行。

7.2 改动性质

整个重构是纯结构变换,不涉及业务逻辑重写:

  1. 拆分SplitExpBase 1956 行 → ExpBase 1105 行 + 4 个子类 329 行
  2. 提取Extract内联逻辑 → BoundChecker/StatCollector/FbStateMachine 独立类
  3. 统一UnifyExpModule + FunVars 双轨 → ExpressionEngine 单轨
  4. 替换Replace裸指针/标志 → 显式类型/枚举类

7.3 验证建议

  1. 单元测试77 个测试全部通过,覆盖核心组件
  2. 运行时验证eqpalg-mon 已在目标机器运行,无崩溃,有报警输出
  3. 回归对比:建议选取 2-3 条典型规则,对比重构前后报警记录(时间点、内容、级别)一致性
  4. 持续观察:关注日志中是否出现 Unknown variable 错误或 cron/task 进程异常

附录:重构涉及的全部文件

新增14 个文件)

文件 说明
eqpalg/utility/expression_engine.h ExpressionEngine 头文件
eqpalg/utility/expression_engine.cpp ExpressionEngine 实现
eqpalg/utility/fb_state_machine.h FbStateMachine 头文件
eqpalg/utility/fb_state_machine.cpp FbStateMachine 实现
eqpalg/utility/bound_checker.h BoundChecker 头文件
eqpalg/utility/bound_checker.cpp BoundChecker 实现
eqpalg/utility/stat_collector.h StatCollector 头文件
eqpalg/utility/stat_collector.cpp StatCollector 实现
eqpalg/algs/logic_alg.h LogicAlg 头文件
eqpalg/algs/logic_alg.cpp LogicAlg 实现
eqpalg/algs/bound_alg.h BoundAlg 头文件
eqpalg/algs/bound_alg.cpp BoundAlg 实现
eqpalg/algs/bound_hold_alg.h BoundHoldAlg 头文件
eqpalg/algs/bound_hold_alg.cpp BoundHoldAlg 实现
eqpalg/algs/feedback_alg.h FeedbackAlg 头文件
eqpalg/algs/feedback_alg.cpp FeedbackAlg 实现
eqpalg/test/test_harness.h 测试框架
eqpalg/test/test_main.cc 测试入口
eqpalg/test/test_expression_engine.cc ExpressionEngine 测试14 用例)
eqpalg/test/test_fb_state_machine.cc FbStateMachine 测试11 用例)
eqpalg/test/test_algorithms.cc 算法集成测试52 用例)

删除2 个文件)

文件 说明
eqpalg/utility/ExpModule.h 被 ExpressionEngine 替代
eqpalg/utility/ExpModule.cc 被 ExpressionEngine 替代

修改27 个文件)

ExpBase、AlgBase、build_algorithm.cpp、CMakeLists.txt以及 9 个算法子类的 .h/.cpp/.cc 文件。