11 tasks covering JSON config, 14 model classes, ModelRegistry factory, Generator refactoring, valve_pair linking, and CMake updates.
957 lines
28 KiB
Markdown
957 lines
28 KiB
Markdown
# RNG 参数化随机数生成 — 实现计划
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** 将 RNG 从硬编码信号映射重构为 JSON 配置驱动的参数化模型架构,支持 9 种模拟量模式 + 4 种布尔量模式。
|
||
|
||
**Architecture:** 每个模型实现 `IModel` 接口,`ModelRegistry` 负责加载 `rng_models.json` 并根据 `tables[1]` 创建实例。`Generator::wtite_in_shm` 中去掉所有硬编码 if-else,改为遍历 signals 调用 `model->evaluate()`。
|
||
|
||
**Tech Stack:** C++20, nlohmann_json (vcpkg), 现有 RandT.h / read_csv.hpp
|
||
|
||
**Build note:** 该项目在 Linux (CentOS 7, GCC 10/devtoolset-10) 编译,当前 Windows 环境仅用于代码编辑。
|
||
|
||
---
|
||
|
||
### Task 1: 创建 JSON 配置文件
|
||
|
||
**Files:**
|
||
- Create: `TestProject/RNG/json/rng_models.json`
|
||
|
||
- [ ] **Step 1: 创建 rng_models.json**
|
||
|
||
```json
|
||
{
|
||
"models": {
|
||
"constant_zero": { "mode": "constant", "params": {} },
|
||
"normal_tiny": { "mode": "normal", "params": { "sigma": 0.01 } },
|
||
"normal_med": { "mode": "normal", "params": { "sigma": 0.5 } },
|
||
"linear_slow": { "mode": "linear", "params": { "k": 0.001 } },
|
||
"linear_fast": { "mode": "linear", "params": { "k": 0.05 } },
|
||
"sine_ecc1": { "mode": "sine", "params": { "A": 5.0, "omega": 0.314, "phi": 0 } },
|
||
"sine_ecc2": { "mode": "sine", "params": { "A": 3.0, "omega": 0.628, "phi": 1.57 } },
|
||
"spike_sharp": { "mode": "spike", "params": { "amplitude": 50, "probability": 0.05 } },
|
||
"spike_mild": { "mode": "spike", "params": { "amplitude": 10, "probability": 0.15 } },
|
||
"drift_slow": { "mode": "drift", "params": { "drift_rate": 0.0001 } },
|
||
"drift_fast": { "mode": "drift", "params": { "drift_rate": 0.005 } },
|
||
"toggle_2s": { "mode": "bool_toggle", "params": { "period_ms": 2000 } },
|
||
"toggle_5s": { "mode": "bool_toggle", "params": { "period_ms": 5000 } },
|
||
"toggle_10s": { "mode": "bool_toggle", "params": { "period_ms": 10000 } },
|
||
"valve_px_std": { "mode": "valve_pair", "params": { "on_delay_ms": 200, "off_delay_ms": 150, "delay_jitter_ms": 20, "flash_prob": 0.02, "delay_over_prob": 0.0001, "delay_over_ms": 4000 } },
|
||
"valve_px_fast": { "mode": "valve_pair", "params": { "on_delay_ms": 80, "off_delay_ms": 60, "delay_jitter_ms": 10, "flash_prob": 0.01, "delay_over_prob": 0, "delay_over_ms": 0 } }
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Commit**
|
||
|
||
```bash
|
||
git add TestProject/RNG/json/rng_models.json
|
||
git commit -m "feat: add rng_models.json with model templates"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2: 创建 IModel 基类接口
|
||
|
||
**Files:**
|
||
- Create: `TestProject/RNG/model/IModel.h`
|
||
|
||
- [ ] **Step 1: 编写 IModel.h**
|
||
|
||
```cpp
|
||
#pragma once
|
||
#include <cstddef>
|
||
|
||
class ModelRegistry;
|
||
|
||
class IModel {
|
||
public:
|
||
virtual ~IModel() = default;
|
||
|
||
virtual float evaluate(size_t t_index) { return 0.0f; }
|
||
virtual bool evaluateBool(size_t t_index) { return false; }
|
||
virtual void linkPeers(ModelRegistry& reg) {}
|
||
virtual void reset() {}
|
||
};
|
||
```
|
||
|
||
- [ ] **Step 2: Commit**
|
||
|
||
```bash
|
||
git add TestProject/RNG/model/IModel.h
|
||
git commit -m "feat: add IModel base interface"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3: 实现模拟量模型(Constant, Normal, Linear, Sine, Uniform, Spike, Drift)
|
||
|
||
**Files:**
|
||
- Create: `TestProject/RNG/model/ConstantModel.h`
|
||
- Create: `TestProject/RNG/model/NormalModel.h`
|
||
- Create: `TestProject/RNG/model/LinearModel.h`
|
||
- Create: `TestProject/RNG/model/SineModel.h`
|
||
- Create: `TestProject/RNG/model/UniformModel.h`
|
||
- Create: `TestProject/RNG/model/SpikeModel.h`
|
||
- Create: `TestProject/RNG/model/DriftModel.h`
|
||
|
||
- [ ] **Step 1: 编写 ConstantModel.h 和 NormalModel.h**
|
||
|
||
```cpp
|
||
// ConstantModel.h
|
||
#pragma once
|
||
#include <TestProject/RNG/model/IModel.h>
|
||
#include <nlohmann/json.hpp>
|
||
using json = nlohmann::json;
|
||
|
||
struct ConstantModel : IModel {
|
||
float c;
|
||
ConstantModel(const json& params, float defaultVal) : c(defaultVal) {}
|
||
float evaluate(size_t) override { return c; }
|
||
};
|
||
```
|
||
|
||
```cpp
|
||
// NormalModel.h
|
||
#pragma once
|
||
#include <TestProject/RNG/model/IModel.h>
|
||
#include <TestProject/RNG/RandT.h>
|
||
#include <nlohmann/json.hpp>
|
||
using json = nlohmann::json;
|
||
|
||
struct NormalModel : IModel {
|
||
float mean, sigma;
|
||
NormalModel(const json& params, float defaultVal)
|
||
: mean(defaultVal), sigma(params.value("sigma", 0.01f)) {}
|
||
float evaluate(size_t) override { return RandT::GuassRand(mean, sigma); }
|
||
};
|
||
```
|
||
|
||
- [ ] **Step 2: 编写 LinearModel.h**
|
||
|
||
```cpp
|
||
#pragma once
|
||
#include <TestProject/RNG/model/IModel.h>
|
||
#include <nlohmann/json.hpp>
|
||
using json = nlohmann::json;
|
||
|
||
struct LinearModel : IModel {
|
||
float k, b;
|
||
LinearModel(const json& params, float defaultVal)
|
||
: k(params.value("k", 0.0f)), b(defaultVal) {}
|
||
float evaluate(size_t t) override { return k * t + b; }
|
||
};
|
||
```
|
||
|
||
- [ ] **Step 3: 编写 SineModel.h**
|
||
|
||
```cpp
|
||
#pragma once
|
||
#include <TestProject/RNG/model/IModel.h>
|
||
#include <nlohmann/json.hpp>
|
||
#include <cmath>
|
||
using json = nlohmann::json;
|
||
|
||
struct SineModel : IModel {
|
||
float A, omega, phi, offset;
|
||
SineModel(const json& params, float defaultVal)
|
||
: A(params.value("A", 1.0f)), omega(params.value("omega", 1.0f)),
|
||
phi(params.value("phi", 0.0f)), offset(defaultVal) {}
|
||
float evaluate(size_t t) override { return A * sinf(omega * t + phi) + offset; }
|
||
};
|
||
```
|
||
|
||
- [ ] **Step 4: 编写 UniformModel.h**
|
||
|
||
```cpp
|
||
#pragma once
|
||
#include <TestProject/RNG/model/IModel.h>
|
||
#include <TestProject/RNG/RandT.h>
|
||
#include <nlohmann/json.hpp>
|
||
using json = nlohmann::json;
|
||
|
||
struct UniformModel : IModel {
|
||
float center, delta;
|
||
UniformModel(const json& params, float defaultVal)
|
||
: center(defaultVal), delta(params.value("delta", 0.01f)) {}
|
||
float evaluate(size_t) override { return RandT::RandT(center - delta, center + delta); }
|
||
};
|
||
```
|
||
|
||
- [ ] **Step 5: 编写 SpikeModel.h**
|
||
|
||
```cpp
|
||
#pragma once
|
||
#include <TestProject/RNG/model/IModel.h>
|
||
#include <nlohmann/json.hpp>
|
||
#include <cstdlib>
|
||
using json = nlohmann::json;
|
||
|
||
struct SpikeModel : IModel {
|
||
float base, amplitude, probability;
|
||
SpikeModel(const json& params, float defaultVal)
|
||
: base(defaultVal)
|
||
, amplitude(params.value("amplitude", 1.0f))
|
||
, probability(params.value("probability", 0.01f)) {}
|
||
float evaluate(size_t) override {
|
||
if ((double)rand() / RAND_MAX < probability) {
|
||
return base + ((rand() % 2) ? amplitude : -amplitude);
|
||
}
|
||
return base;
|
||
}
|
||
};
|
||
```
|
||
|
||
- [ ] **Step 6: 编写 DriftModel.h**
|
||
|
||
```cpp
|
||
#pragma once
|
||
#include <TestProject/RNG/model/IModel.h>
|
||
#include <nlohmann/json.hpp>
|
||
using json = nlohmann::json;
|
||
|
||
struct DriftModel : IModel {
|
||
float base, drift_rate;
|
||
DriftModel(const json& params, float defaultVal)
|
||
: base(defaultVal), drift_rate(params.value("drift_rate", 0.0f)) {}
|
||
float evaluate(size_t t) override { return base + drift_rate * t; }
|
||
};
|
||
```
|
||
|
||
- [ ] **Step 7: Commit**
|
||
|
||
```bash
|
||
git add TestProject/RNG/model/NormalModel.h TestProject/RNG/model/LinearModel.h \
|
||
TestProject/RNG/model/SineModel.h TestProject/RNG/model/UniformModel.h \
|
||
TestProject/RNG/model/SpikeModel.h TestProject/RNG/model/DriftModel.h
|
||
git commit -m "feat: add 7 analog signal models (constant, normal, linear, sine, uniform, spike, drift)"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 4: 实现 CsvReplayModel
|
||
|
||
**Files:**
|
||
- Create: `TestProject/RNG/model/CsvReplayModel.h`
|
||
|
||
- [ ] **Step 1: 编写 CsvReplayModel.h**
|
||
|
||
```cpp
|
||
#pragma once
|
||
#include <TestProject/RNG/model/IModel.h>
|
||
#include <TestProject/RNG/read_csv.hpp>
|
||
#include <nlohmann/json.hpp>
|
||
#include <string>
|
||
using json = nlohmann::json;
|
||
|
||
struct CsvReplayModel : IModel {
|
||
ReadCSV::DoubleData data;
|
||
int column;
|
||
|
||
CsvReplayModel(const json& params, float)
|
||
: data(params["file"].get<std::string>())
|
||
, column(params["column"].get<int>()) {}
|
||
|
||
float evaluate(size_t t) override { return data(t, column); }
|
||
};
|
||
```
|
||
|
||
- [ ] **Step 2: Commit**
|
||
|
||
```bash
|
||
git add TestProject/RNG/model/CsvReplayModel.h
|
||
git commit -m "feat: add CsvReplayModel for CSV data replay"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 5: 实现布尔量模型(BoolRandom, BoolToggle, BoolCsv)
|
||
|
||
**Files:**
|
||
- Create: `TestProject/RNG/model/BoolRandomModel.h`
|
||
- Create: `TestProject/RNG/model/BoolToggleModel.h`
|
||
- Create: `TestProject/RNG/model/BoolCsvModel.h`
|
||
|
||
- [ ] **Step 1: 编写 BoolRandomModel.h**
|
||
|
||
```cpp
|
||
#pragma once
|
||
#include <TestProject/RNG/model/IModel.h>
|
||
#include <nlohmann/json.hpp>
|
||
#include <cstdlib>
|
||
using json = nlohmann::json;
|
||
|
||
struct BoolRandomModel : IModel {
|
||
float prob_true;
|
||
BoolRandomModel(const json& params, float defaultVal)
|
||
: prob_true(params.value("prob_true", 0.5f)) {}
|
||
bool evaluateBool(size_t) override {
|
||
return (double)rand() / RAND_MAX < prob_true;
|
||
}
|
||
};
|
||
```
|
||
|
||
- [ ] **Step 2: 编写 BoolToggleModel.h**
|
||
|
||
```cpp
|
||
#pragma once
|
||
#include <TestProject/RNG/model/IModel.h>
|
||
#include <nlohmann/json.hpp>
|
||
using json = nlohmann::json;
|
||
|
||
struct BoolToggleModel : IModel {
|
||
int period_ticks; // ~20ms per tick
|
||
BoolToggleModel(const json& params, float)
|
||
: period_ticks(params.value("period_ms", 2000) / 20) {}
|
||
bool evaluateBool(size_t t) override {
|
||
return (t / period_ticks) % 2 == 0;
|
||
}
|
||
};
|
||
```
|
||
|
||
- [ ] **Step 3: 编写 BoolCsvModel.h**
|
||
|
||
```cpp
|
||
#pragma once
|
||
#include <TestProject/RNG/model/IModel.h>
|
||
#include <TestProject/RNG/read_csv.hpp>
|
||
#include <nlohmann/json.hpp>
|
||
#include <string>
|
||
using json = nlohmann::json;
|
||
|
||
struct BoolCsvModel : IModel {
|
||
ReadCSV::IntData data;
|
||
int column;
|
||
|
||
BoolCsvModel(const json& params, float)
|
||
: data(params["file"].get<std::string>())
|
||
, column(params["column"].get<int>()) {}
|
||
|
||
bool evaluateBool(size_t t) override { return (bool)data(t, column); }
|
||
};
|
||
```
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
git add TestProject/RNG/model/BoolRandomModel.h TestProject/RNG/model/BoolToggleModel.h \
|
||
TestProject/RNG/model/BoolCsvModel.h
|
||
git commit -m "feat: add 3 boolean signal models"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 6: 实现 ValvePairModel
|
||
|
||
**Files:**
|
||
- Create: `TestProject/RNG/model/ValvePairModel.h`
|
||
|
||
- [ ] **Step 1: 编写 ValvePairModel.h**
|
||
|
||
```cpp
|
||
#pragma once
|
||
#include <TestProject/RNG/model/IModel.h>
|
||
#include <nlohmann/json.hpp>
|
||
#include <cstdlib>
|
||
#include <string>
|
||
using json = nlohmann::json;
|
||
|
||
struct ValvePairModel : IModel {
|
||
IModel* action = nullptr;
|
||
std::string actionModelName;
|
||
|
||
int on_delay_ticks;
|
||
int off_delay_ticks;
|
||
int delay_jitter_ticks;
|
||
float flash_prob;
|
||
float delay_over_prob;
|
||
int delay_over_ticks;
|
||
|
||
enum State { IDLE_LOW, WAITING_RISE, HIGH, WAITING_FALL };
|
||
State state = IDLE_LOW;
|
||
int delay_counter = 0;
|
||
bool prev_action = false;
|
||
|
||
ValvePairModel(const json& params, float)
|
||
: on_delay_ticks(params.value("on_delay_ms", 200) / 20)
|
||
, off_delay_ticks(params.value("off_delay_ms", 150) / 20)
|
||
, delay_jitter_ticks(params.value("delay_jitter_ms", 0) / 20)
|
||
, flash_prob(params.value("flash_prob", 0.0f))
|
||
, delay_over_prob(params.value("delay_over_prob", 0.0f))
|
||
, delay_over_ticks(params.value("delay_over_ms", 0) / 20) {}
|
||
|
||
void linkPeers(ModelRegistry& reg) override;
|
||
|
||
bool evaluateBool(size_t t) override {
|
||
if (!action) return false;
|
||
bool action_now = action->evaluateBool(t);
|
||
|
||
// edge detection
|
||
bool rising_edge = !prev_action && action_now;
|
||
bool falling_edge = prev_action && !action_now;
|
||
prev_action = action_now;
|
||
|
||
switch (state) {
|
||
case IDLE_LOW:
|
||
if (rising_edge) {
|
||
bool over = ((double)rand() / RAND_MAX) < delay_over_prob;
|
||
int base = over ? delay_over_ticks : on_delay_ticks;
|
||
delay_counter = base + (delay_jitter_ticks ? rand() % (delay_jitter_ticks + 1) : 0);
|
||
state = WAITING_RISE;
|
||
}
|
||
break;
|
||
case WAITING_RISE:
|
||
if (delay_counter > 0) { delay_counter--; }
|
||
else { state = HIGH; }
|
||
break;
|
||
case HIGH:
|
||
if (falling_edge) {
|
||
bool over = ((double)rand() / RAND_MAX) < delay_over_prob;
|
||
int base = over ? delay_over_ticks : off_delay_ticks;
|
||
delay_counter = base + (delay_jitter_ticks ? rand() % (delay_jitter_ticks + 1) : 0);
|
||
state = WAITING_FALL;
|
||
} else if (((double)rand() / RAND_MAX) < flash_prob) {
|
||
return false; // flash: momentary drop, auto-recover next cycle
|
||
}
|
||
break;
|
||
case WAITING_FALL:
|
||
if (delay_counter > 0) { delay_counter--; }
|
||
else { state = IDLE_LOW; }
|
||
break;
|
||
}
|
||
return state == HIGH || state == WAITING_FALL;
|
||
}
|
||
|
||
void reset() override { state = IDLE_LOW; prev_action = false; }
|
||
};
|
||
```
|
||
|
||
`linkPeers` 实现在 Task 7 ModelRegistry 之后,详见 Step 2。
|
||
|
||
- [ ] **Step 2: 在 ValvePairModel 中实现 linkPeers(需要 ModelRegistry 声明)**
|
||
|
||
```cpp
|
||
// ValvePairModel::linkPeers — 在 model/ValvePairModel.h 末尾,需要先 include ModelRegistry.h
|
||
// (或者将 linkPeers 实现放在 ModelRegistry.cc 中)
|
||
|
||
// 由于循环依赖(ValvePairModel::linkPeers 需要 ModelRegistry),使用前向声明 + 延迟实现:
|
||
// model/ValvePairModel.h 中声明 linkPeers,实现在 ModelRegistry.cc 尾部。
|
||
|
||
#include <vector>
|
||
// ... in ModelRegistry.cc or a separate file:
|
||
|
||
void ValvePairModel::linkPeers(ModelRegistry& reg) {
|
||
auto models = reg.findByModelName(actionModelName);
|
||
if (!models.empty()) action = models[0];
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add TestProject/RNG/model/ValvePairModel.h
|
||
git commit -m "feat: add ValvePairModel with jitter/flash/over-delay"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 7: 实现 CompositeModel + ModelRegistry
|
||
|
||
**Files:**
|
||
- Create: `TestProject/RNG/model/CompositeModel.h`
|
||
- Create: `TestProject/RNG/model/ModelRegistry.h`
|
||
- Create: `TestProject/RNG/model/ModelRegistry.cc`
|
||
|
||
- [ ] **Step 1: 编写 CompositeModel.h**
|
||
|
||
```cpp
|
||
#pragma once
|
||
#include <TestProject/RNG/model/IModel.h>
|
||
#include <memory>
|
||
|
||
struct CompositeModel : IModel {
|
||
std::unique_ptr<IModel> base;
|
||
std::unique_ptr<IModel> noise;
|
||
|
||
CompositeModel(std::unique_ptr<IModel> b, std::unique_ptr<IModel> n)
|
||
: base(std::move(b)), noise(std::move(n)) {}
|
||
|
||
float evaluate(size_t t) override {
|
||
return base->evaluate(t) + noise->evaluate(t);
|
||
}
|
||
};
|
||
```
|
||
|
||
- [ ] **Step 2: 编写 ModelRegistry.h**
|
||
|
||
```cpp
|
||
#pragma once
|
||
#include <TestProject/RNG/model/IModel.h>
|
||
#include <nlohmann/json.hpp>
|
||
#include <functional>
|
||
#include <map>
|
||
#include <memory>
|
||
#include <string>
|
||
#include <vector>
|
||
using json = nlohmann::json;
|
||
|
||
class ModelRegistry {
|
||
public:
|
||
using Ctor = std::function<std::unique_ptr<IModel>(const json& params, float defaultVal)>;
|
||
|
||
static ModelRegistry& instance();
|
||
|
||
void loadModels(const std::string& jsonPath);
|
||
|
||
IModel* getOrCreate(const std::string& tables1Spec, float defaultVal);
|
||
|
||
std::vector<IModel*> findByModelName(const std::string& modelName);
|
||
|
||
void registerMode(const std::string& mode, Ctor ctor);
|
||
|
||
private:
|
||
ModelRegistry();
|
||
std::unique_ptr<IModel> createModel(const std::string& modelName, float defaultVal);
|
||
|
||
struct ModelDef {
|
||
std::string mode;
|
||
json params;
|
||
};
|
||
std::map<std::string, ModelDef> modelTemplates; // JSON loaded templates
|
||
std::map<std::string, std::unique_ptr<IModel>> instances; // created instances, keyed by spec
|
||
std::map<std::string, std::vector<IModel*>> byModelName; // model name → instances
|
||
std::map<std::string, Ctor> factory; // mode → constructor
|
||
};
|
||
```
|
||
|
||
- [ ] **Step 3: 编写 ModelRegistry.cc**
|
||
|
||
```cpp
|
||
#include <TestProject/RNG/model/ModelRegistry.h>
|
||
#include <TestProject/RNG/model/ConstantModel.h>
|
||
#include <TestProject/RNG/model/NormalModel.h>
|
||
#include <TestProject/RNG/model/LinearModel.h>
|
||
#include <TestProject/RNG/model/SineModel.h>
|
||
#include <TestProject/RNG/model/UniformModel.h>
|
||
#include <TestProject/RNG/model/SpikeModel.h>
|
||
#include <TestProject/RNG/model/DriftModel.h>
|
||
#include <TestProject/RNG/model/CsvReplayModel.h>
|
||
#include <TestProject/RNG/model/BoolRandomModel.h>
|
||
#include <TestProject/RNG/model/BoolToggleModel.h>
|
||
#include <TestProject/RNG/model/BoolCsvModel.h>
|
||
#include <TestProject/RNG/model/ValvePairModel.h>
|
||
#include <TestProject/RNG/model/CompositeModel.h>
|
||
#include <fstream>
|
||
#include <sstream>
|
||
#include <stdexcept>
|
||
|
||
ModelRegistry& ModelRegistry::instance() {
|
||
static ModelRegistry reg;
|
||
return reg;
|
||
}
|
||
|
||
ModelRegistry::ModelRegistry() {
|
||
registerMode("constant", [](const json& p, float d) { return std::make_unique<ConstantModel>(p, d); });
|
||
registerMode("normal", [](const json& p, float d) { return std::make_unique<NormalModel>(p, d); });
|
||
registerMode("linear", [](const json& p, float d) { return std::make_unique<LinearModel>(p, d); });
|
||
registerMode("sine", [](const json& p, float d) { return std::make_unique<SineModel>(p, d); });
|
||
registerMode("uniform", [](const json& p, float d) { return std::make_unique<UniformModel>(p, d); });
|
||
registerMode("spike", [](const json& p, float d) { return std::make_unique<SpikeModel>(p, d); });
|
||
registerMode("drift", [](const json& p, float d) { return std::make_unique<DriftModel>(p, d); });
|
||
registerMode("csv", [](const json& p, float d) { return std::make_unique<CsvReplayModel>(p, d); });
|
||
registerMode("bool_random",[](const json& p, float d) { return std::make_unique<BoolRandomModel>(p, d); });
|
||
registerMode("bool_toggle",[](const json& p, float d) { return std::make_unique<BoolToggleModel>(p, d); });
|
||
registerMode("bool_csv", [](const json& p, float d) { return std::make_unique<BoolCsvModel>(p, d); });
|
||
registerMode("valve_pair", [](const json& p, float d) { return std::make_unique<ValvePairModel>(p, d); });
|
||
}
|
||
|
||
void ModelRegistry::registerMode(const std::string& mode, Ctor ctor) {
|
||
factory[mode] = std::move(ctor);
|
||
}
|
||
|
||
void ModelRegistry::loadModels(const std::string& jsonPath) {
|
||
std::ifstream f(jsonPath);
|
||
if (!f.is_open()) throw std::runtime_error("Cannot open " + jsonPath);
|
||
json j;
|
||
f >> j;
|
||
for (auto& [name, def] : j["models"].items()) {
|
||
modelTemplates[name] = { def["mode"].get<std::string>(), def.value("params", json::object()) };
|
||
}
|
||
}
|
||
|
||
std::unique_ptr<IModel> ModelRegistry::createModel(const std::string& modelName, float defaultVal) {
|
||
// Check if it's a composite: base+noise
|
||
auto plusPos = modelName.find('+');
|
||
if (plusPos != std::string::npos) {
|
||
auto base = createModel(modelName.substr(0, plusPos), defaultVal);
|
||
auto noise = createModel(modelName.substr(plusPos + 1), defaultVal);
|
||
return std::make_unique<CompositeModel>(std::move(base), std::move(noise));
|
||
}
|
||
|
||
// Check if it's inline CSV: csv:file:col
|
||
if (modelName.rfind("csv:", 0) == 0) {
|
||
auto first = modelName.find(':', 4);
|
||
auto second = modelName.find(':', first + 1);
|
||
json p;
|
||
p["file"] = modelName.substr(4, first - 4);
|
||
p["column"] = std::stoi(modelName.substr(first + 1, second - first - 1));
|
||
return factory["csv"](p, 0.0f);
|
||
}
|
||
|
||
// Check if it's a valve_pair:action_model pair reference
|
||
auto colonPos = modelName.find(':');
|
||
if (colonPos != std::string::npos) {
|
||
std::string pairModel = modelName.substr(0, colonPos);
|
||
std::string actionModel = modelName.substr(colonPos + 1);
|
||
auto it = modelTemplates.find(pairModel);
|
||
if (it != modelTemplates.end() && it->second.mode == "valve_pair") {
|
||
auto model = factory["valve_pair"](it->second.params, defaultVal);
|
||
static_cast<ValvePairModel*>(model.get())->actionModelName = actionModel;
|
||
return model;
|
||
}
|
||
}
|
||
|
||
// Simple model name lookup
|
||
auto it = modelTemplates.find(modelName);
|
||
if (it == modelTemplates.end()) {
|
||
// Fallback to normal_tiny if model not found
|
||
it = modelTemplates.find("normal_tiny");
|
||
}
|
||
auto fit = factory.find(it->second.mode);
|
||
if (fit == factory.end()) {
|
||
throw std::runtime_error("Unknown mode: " + it->second.mode);
|
||
}
|
||
return fit->second(it->second.params, defaultVal);
|
||
}
|
||
|
||
IModel* ModelRegistry::getOrCreate(const std::string& spec, float defaultVal) {
|
||
std::string key = spec.empty() ? "default" : spec;
|
||
auto it = instances.find(key);
|
||
if (it != instances.end()) return it->second.get();
|
||
|
||
auto model = createModel(spec.empty() ? "normal_tiny" : spec, defaultVal);
|
||
|
||
// Track by model name (parse base model name for composite/pair)
|
||
std::string modelName = spec;
|
||
auto plusPos = modelName.find('+');
|
||
if (plusPos != std::string::npos) modelName = modelName.substr(0, plusPos);
|
||
auto colonPos = modelName.find(':');
|
||
if (colonPos != std::string::npos) modelName = modelName.substr(0, colonPos);
|
||
|
||
byModelName[modelName].push_back(model.get());
|
||
instances[key] = std::move(model);
|
||
return instances[key].get();
|
||
}
|
||
|
||
std::vector<IModel*> ModelRegistry::findByModelName(const std::string& modelName) {
|
||
auto it = byModelName.find(modelName);
|
||
if (it != byModelName.end()) return it->second;
|
||
return {};
|
||
}
|
||
|
||
// ValvePairModel::linkPeers — defined here to avoid circular include
|
||
void ValvePairModel::linkPeers(ModelRegistry& reg) {
|
||
auto models = reg.findByModelName(actionModelName);
|
||
if (!models.empty()) action = models[0];
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
git add TestProject/RNG/model/CompositeModel.h TestProject/RNG/model/ModelRegistry.h \
|
||
TestProject/RNG/model/ModelRegistry.cc
|
||
git commit -m "feat: add ModelRegistry with JSON loading and composite model"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 8: 重构 Generator 去除硬编码
|
||
|
||
**Files:**
|
||
- Modify: `TestProject/RNG/Generator.h`
|
||
- Modify: `TestProject/RNG/Generator.cc`
|
||
|
||
- [ ] **Step 1: 更新 Generator.h — 添加 ModelRegistry 成员**
|
||
|
||
将现有 `Generator.h` 的内容替换为:
|
||
|
||
```cpp
|
||
#pragma once
|
||
|
||
#include <glob/BinaryTele.h>
|
||
#include <log4cplus/LOG.h>
|
||
#include <zlib/MemVar.h>
|
||
#include <zlib/MemFix.hpp>
|
||
#include <zlib/zoneDef.h>
|
||
#include <TestProject/RNG/model/ModelRegistry.h>
|
||
#include <array>
|
||
#include <chrono>
|
||
#include <map>
|
||
#include <numeric>
|
||
#include <string>
|
||
using namespace std;
|
||
|
||
class Generator {
|
||
public:
|
||
Generator();
|
||
~Generator();
|
||
bool wtite_in_shm(int event_no);
|
||
|
||
private:
|
||
std::unique_ptr<LOG> logger_;
|
||
BinaryTele binary_tele{CMemVar::Const()->event_eis_start, "T_LOV_FDAAITEM"};
|
||
map<int, CMemFix<PLC_DATA>*> m_mapfix;
|
||
chrono::system_clock::time_point stime;
|
||
ModelRegistry& registry;
|
||
};
|
||
```
|
||
|
||
- [ ] **Step 2: 重写 Generator.cc — 去掉全局 CSV 变量和硬编码 if-else**
|
||
|
||
将现有 `Generator.cc` 的内容替换为:
|
||
|
||
```cpp
|
||
#include <TestProject/RNG/Generator.h>
|
||
#include <TestProject/RNG/model/ModelRegistry.h>
|
||
#include <glob/BinaryTele.h>
|
||
#include <zlib/MemFix.hpp>
|
||
#include <zlib/zoneDef.h>
|
||
#include <string>
|
||
#include <cmath>
|
||
#include "mix_cc/debug/pre_define.h"
|
||
|
||
using namespace std;
|
||
using namespace chrono;
|
||
|
||
Generator::Generator() {
|
||
stime = chrono::system_clock::now();
|
||
logger_ = make_unique<LOG>("Generator");
|
||
registry = ModelRegistry::instance();
|
||
}
|
||
|
||
Generator::~Generator() {
|
||
for (auto& it : m_mapfix) {
|
||
if (it.second != nullptr) {
|
||
delete it.second;
|
||
}
|
||
}
|
||
}
|
||
|
||
bool Generator::wtite_in_shm(int event_no) {
|
||
try {
|
||
if (m_mapfix.find(event_no) == m_mapfix.end()) {
|
||
m_mapfix.insert(
|
||
make_pair(event_no, new CMemFix<PLC_DATA>(to_string(event_no),
|
||
TEL_CACHE_SIZE)));
|
||
}
|
||
|
||
binary_tele.ReBuild(event_no);
|
||
int size = binary_tele.size();
|
||
|
||
size_t data_index =
|
||
((chrono::system_clock::now() - stime).count() / (size_t)pow(10, 6)) /
|
||
20;
|
||
|
||
for (int i = 0; i < size; i++) {
|
||
const char* spec = binary_tele[i].tables[1];
|
||
string specStr = (spec != nullptr && strlen(spec) > 0) ? string(spec) : "";
|
||
float defaultVal = atof(binary_tele[i].defaultValue);
|
||
IModel* model = registry.getOrCreate(specStr, defaultVal);
|
||
|
||
if (binary_tele[i].type[0] == 'b') {
|
||
binary_tele[i] = model->evaluateBool(data_index) ? 1.0f : 0.0f;
|
||
} else {
|
||
binary_tele[i] = model->evaluate(data_index);
|
||
}
|
||
}
|
||
|
||
char* buff = binary_tele.GetTeleData();
|
||
m_mapfix[event_no]->push((PLC_DATA*)buff);
|
||
return true;
|
||
} catch (const std::exception& e) {
|
||
logger_->Error() << "wtite_in_shm Error!" << e.what() << std::endl;
|
||
return false;
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add TestProject/RNG/Generator.h TestProject/RNG/Generator.cc
|
||
git commit -m "refactor: replace hardcoded signal mappings with ModelRegistry"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 9: 更新 RNG 启动流程加载配置 + 链接 valve_pair
|
||
|
||
**Files:**
|
||
- Modify: `TestProject/RNG/RNG.h`
|
||
- Modify: `TestProject/RNG/RNG.cc`
|
||
|
||
- [ ] **Step 1: 更新 RNG.cc — 在 start() 中加载 rng_models.json**
|
||
|
||
修改 `RNG::start()`(文件 `TestProject/RNG/RNG.cc`),在 `con_mag_->dbLogin()` 之后添加一行:
|
||
|
||
原始 code:
|
||
```cpp
|
||
int RNG::start() {
|
||
logger_->Info() << "-------RNG::start-------" << std::endl;
|
||
con_mag_ = std::make_shared<ConnectionMag>();
|
||
con_mag_->dbLogin();
|
||
try {
|
||
auto module_name = name();
|
||
RNG_server = new RNGICEI();
|
||
this->add(string("baosight/") + name(), RNG_server);
|
||
```
|
||
|
||
替换为:
|
||
```cpp
|
||
int RNG::start() {
|
||
logger_->Info() << "-------RNG::start-------" << std::endl;
|
||
con_mag_ = std::make_shared<ConnectionMag>();
|
||
con_mag_->dbLogin();
|
||
ModelRegistry::instance().loadModels("/users/dsc/code/TestProject/RNG/json/rng_models.json");
|
||
try {
|
||
auto module_name = name();
|
||
RNG_server = new RNGICEI();
|
||
this->add(string("baosight/") + name(), RNG_server);
|
||
```
|
||
|
||
同时在 RNG.cc 顶部添加 include:
|
||
```cpp
|
||
#include <TestProject/RNG/model/ModelRegistry.h>
|
||
```
|
||
|
||
- [ ] **Step 2: 提交**
|
||
|
||
```bash
|
||
git add TestProject/RNG/RNG.cc
|
||
git commit -m "feat: load rng_models.json on startup"
|
||
```
|
||
|
||
- [ ] **Step 3: 在 Generator::wtite_in_shm 中添加 valve_pair linkPeers 调用**
|
||
|
||
Generator 首次为 event_no 创建所有 model 实例后需要 link。修改 `Generator.cc`:
|
||
|
||
在 `for (int i = 0; i < size; i++)` 循环**之前**添加一次 link 循环:
|
||
|
||
```cpp
|
||
// First pass: create all models, then link valve_pair models
|
||
for (int i = 0; i < size; i++) {
|
||
const char* spec = binary_tele[i].tables[1];
|
||
string specStr = (spec != nullptr && strlen(spec) > 0) ? string(spec) : "";
|
||
float defaultVal = atof(binary_tele[i].defaultValue);
|
||
IModel* model = registry.getOrCreate(specStr, defaultVal);
|
||
model->linkPeers(registry);
|
||
}
|
||
|
||
// Second pass: evaluate
|
||
for (int i = 0; i < size; i++) {
|
||
const char* spec = binary_tele[i].tables[1];
|
||
string specStr = (spec != nullptr && strlen(spec) > 0) ? string(spec) : "";
|
||
float defaultVal = atof(binary_tele[i].defaultValue);
|
||
IModel* model = registry.getOrCreate(specStr, defaultVal);
|
||
|
||
if (binary_tele[i].type[0] == 'b') {
|
||
binary_tele[i] = model->evaluateBool(data_index) ? 1.0f : 0.0f;
|
||
} else {
|
||
binary_tele[i] = model->evaluate(data_index);
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 4: 提交**
|
||
|
||
```bash
|
||
git add TestProject/RNG/Generator.cc
|
||
git commit -m "feat: link valve_pair action models before evaluation"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 10: 更新 CMakeLists.txt
|
||
|
||
**Files:**
|
||
- Modify: `TestProject/RNG/CMakeLists.txt`
|
||
|
||
- [ ] **Step 1: 添加 model/ 目录的源文件**
|
||
|
||
在 `aux_source_directory(./ DIR_ROOT)` 之后添加:
|
||
|
||
```cmake
|
||
aux_source_directory(./model MODEL_DIR)
|
||
```
|
||
|
||
在 `add_executable(RNG ${DIR_ROOT})` 中添加 `${MODEL_DIR}`:
|
||
|
||
```cmake
|
||
add_executable(
|
||
RNG
|
||
${DIR_ROOT}
|
||
${MODEL_DIR}
|
||
)
|
||
```
|
||
|
||
- [ ] **Step 2: 验证 CMake 变更**
|
||
|
||
```bash
|
||
cd TestProject/RNG/build && cmake .. -DCMAKE_BUILD_TYPE=Release
|
||
# 预期: Configuring done, 无错误
|
||
```
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add TestProject/RNG/CMakeLists.txt
|
||
git commit -m "build: add model/ source directory to CMake"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 11: 清理旧的未使用文件
|
||
|
||
**Files:**
|
||
- Remove references: `TestProject/RNG/BaseData.h` (不再被任何文件引用)
|
||
|
||
- [ ] **Step 1: 验证 BaseData.h 无引用**
|
||
|
||
```bash
|
||
grep -r "BaseData" TestProject/RNG/ --include="*.cc" --include="*.h"
|
||
# 预期: 无匹配(所有引用在 Generator.cc 重构中已移除)
|
||
```
|
||
|
||
- [ ] **Step 2: 删除 BaseData.h**
|
||
|
||
```bash
|
||
git rm TestProject/RNG/BaseData.h
|
||
```
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git commit -m "chore: remove unused BaseData.h"
|
||
```
|
||
|
||
---
|
||
|
||
## 实现顺序总结
|
||
|
||
```
|
||
Task 1 (json config) ──┐
|
||
Task 2 (IModel interface) ──┤ 基础设施,可并行
|
||
Task 3 (6 analog models) ──┤
|
||
Task 4 (CsvReplayModel) ──┤
|
||
Task 5 (3 boolean models) ──┤
|
||
Task 6 (ValvePairModel) ──┤
|
||
Task 7 (ModelRegistry) ──┘ ← 依赖 Task 2-6
|
||
Task 8 (refactor Generator) ──── ← 依赖 Task 7
|
||
Task 9 (RNG startup + link) ──── ← 依赖 Task 8
|
||
Task 10 (CMakeLists) ──── ← 依赖 Task 7 (需要 model/ModelRegistry.cc 存在)
|
||
Task 11 (cleanup) ──── ← 最后
|
||
```
|
||
|
||
每个 Task 提交一次,编译验证在 Linux 环境进行(本机无编译环境)。
|