Compare commits

...

20 Commits

Author SHA1 Message Date
Huamonarch
47dd4fad4b fix: qualify is_same_v with std:: in FlowData constructor 2026-05-13 16:02:27 +08:00
Huamonarch
f41ce0c40f fix: GuassRand use sigma as stddev directly instead of sqrt(sigma) 2026-05-13 15:53:25 +08:00
Huamonarch
5ee17627e9 fix: address code review issues
- read_csv.hpp: replace std::exit(1) with std::runtime_error throw
- read_csv.hpp: remove duplicate #pragma once
- RNG.cc: wrap JSON load in try-catch to prevent crash on missing config
- Generator.cc: fix comments to reflect actual 3-pass structure
2026-05-13 15:46:06 +08:00
Huamonarch
d9b4ee8eb3 fix: prevent ValvePairModel stuck-state by freezing prev_action during delay 2026-05-13 15:43:56 +08:00
Huamonarch
10bb9937d6 chore: remove unused BaseData.h 2026-05-13 15:33:36 +08:00
Huamonarch
7385b7cc6e build: add model/ source directory to CMake 2026-05-13 15:27:22 +08:00
Huamonarch
022993ab5a feat: load rng_models.json on startup 2026-05-13 15:26:19 +08:00
Huamonarch
4821cb9d36 refactor: replace hardcoded signal mappings with ModelRegistry 2026-05-13 15:24:32 +08:00
Huamonarch
b4bb27f1e5 feat: add ModelRegistry with JSON loading and composite model
Add CompositeModel for combining base+noise models, ModelRegistry singleton
with JSON-based model template loading, per-instance-key model isolation,
and inline CSV/valve pair/composite syntax parsing in createModel.
2026-05-13 15:20:51 +08:00
Huamonarch
4f83e41c0c feat: add ValvePairModel with jitter/flash/over-delay 2026-05-13 15:15:14 +08:00
Huamonarch
3ce03911c3 feat: add 3 boolean signal models 2026-05-13 15:13:31 +08:00
Huamonarch
2ef663af81 feat: add CsvReplayModel for CSV data replay 2026-05-13 15:12:04 +08:00
Huamonarch
afd753f803 feat: add 7 analog signal models (constant, normal, linear, sine, uniform, spike, drift) 2026-05-13 15:10:37 +08:00
Huamonarch
6afce89326 feat: add IModel base interface 2026-05-13 15:07:59 +08:00
Huamonarch
ae5f55c8ac feat: add rng_models.json with model templates 2026-05-13 15:07:06 +08:00
Huamonarch
70a976fe58 chore: add .worktrees/ to .gitignore 2026-05-13 15:04:31 +08:00
Huamonarch
7339801f2f docs: add RNG parameterized models implementation plan
11 tasks covering JSON config, 14 model classes, ModelRegistry factory,
Generator refactoring, valve_pair linking, and CMake updates.
2026-05-13 15:02:58 +08:00
Huamonarch
302aa82e38 docs: add valve_pair jitter/over-delay fault simulation params
delay_jitter_ms for natural timing variation, delay_over_prob/delay_over_ms
for simulating occasional valve timeout faults (e.g. once per day).
2026-05-13 14:47:35 +08:00
Huamonarch
a93801fea5 docs: fix rng_models.json path to absolute Linux path 2026-05-13 14:38:17 +08:00
Huamonarch
71feae9e79 docs: add RNG parameterized models design spec
Config-driven random number generation with model templates in JSON and
signal-to-model binding via DB2 tables[1] field. Supports 9 analog modes,
4 boolean modes including valve_pair timing simulation.
2026-05-13 14:32:58 +08:00
27 changed files with 1734 additions and 219 deletions

6
.gitignore vendored
View File

@ -38,3 +38,9 @@ Testing/
# IDE (keep only if team agrees; uncomment if needed) # IDE (keep only if team agrees; uncomment if needed)
# .vscode/ # .vscode/
# Git worktrees
.worktrees/
# Images/assets
图片/

View File

@ -1,28 +0,0 @@
#pragma once
#include <mlpack/core.hpp>
#include <mlpack/core/data/split_data.hpp>
namespace BaseData {
using namespace mlpack;
using namespace arma;
const size_t MaxNum = 36000;
struct BaseData {
static arma::vec vec1;
struct Init {
static arma::vec init(double x0, double xn) {
arma::vec vec2;
vec2.ones(MaxNum*2);
for (int i = 0; i < MaxNum; i++) {
vec2[i] = x0 + i * (xn - x0) / (double)MaxNum;
vec2[MaxNum+i] = xn - i * (xn - x0) / (double)MaxNum;
}
return vec2;
}
};
double operator()(size_t index) { return vec1[index % (2*MaxNum)]; }
};
// arma::vec BaseData::BaseData::vec1
arma::vec BaseData::vec1 = BaseData::Init::init(600, 300);
}; // namespace BaseData

View File

@ -13,7 +13,7 @@ find_package(mlpack CONFIG REQUIRED)
find_package(Armadillo CONFIG REQUIRED) find_package(Armadillo CONFIG REQUIRED)
aux_source_directory(./ DIR_ROOT) aux_source_directory(./ DIR_ROOT)
aux_source_directory(./model MODEL_DIR)
set(LINK_OPTION set(LINK_OPTION
${ICE} ${ICE}
@ -34,6 +34,7 @@ set(LINK_OPTION
add_executable( add_executable(
RNG RNG
${DIR_ROOT} ${DIR_ROOT}
${MODEL_DIR}
) )
target_link_libraries(RNG target_link_libraries(RNG

View File

@ -1,205 +1,78 @@
#include <TestProject/RNG/BaseData.h>
#include <TestProject/RNG/Generator.h> #include <TestProject/RNG/Generator.h>
#include <TestProject/RNG/RandT.h> #include <TestProject/RNG/model/ModelRegistry.h>
#include <TestProject/RNG/read_csv.hpp> #include <glob/BinaryTele.h>
BaseData::BaseData data1; #include <zlib/MemFix.hpp>
// ReadCSV::DoubleData ddata("D102-1#酸槽数据.csv"); ///< 2列:入口流量,出口流量 #include <zlib/zoneDef.h>
ReadCSV::DoubleData #include <string>
ddata("数据毛刺-仿真.csv"); ///< 4列:实际速度实际电流设定速度spderr #include <cmath>
ReadCSV::DoubleData gdata("毛刺数据.csv"); ///< 6列:RW_o_Drv_ActSpd #include "mix_cc/debug/pre_define.h"
///< RW_o_Drv_Current
///< RW_o_Drv_TQ_Ref
///< RW_o_Drv_SpdRegInt
///< RW_o_Drv_SpdErr RW_o_Drv_AVF
ReadCSV::DoubleData spbdata(
"转矩减小-速度偏差大-仿真2.csv"); ///< 4列:实际速度,实际电流,实际转矩,设定速度
ReadCSV::DoubleData
liquid_data("液位.csv"); ///< 4列:实际速度,实际电流,实际转矩,设定速度
// ReadCSV::IntData idata("D302-压辊数据.csv"); ///< 2列:阀动作,接近开关信号
ReadCSV::IntData
idata("C308仿-压辊数据.csv"); ///< 3列:阀动作- 开,接近开关信号-开,阀动作-关
ReadCSV::DoubleData
ddata_cn("D102-3#BR电流数据.csv"); ///< 4列:2/3/4电流,速度设定值
ReadCSV::DoubleData
ddata_cb("D102-3#BR异常电流数据.csv"); ///< 4列:2/3/4电流,速度设定值
ReadCSV::IntData car_data("D302-2#小车减速位.csv"); ///< 2列位置接近开关 using namespace std;
ReadCSV::DoubleData std1_wr("D302-1#机架正弯.csv"); ///< 2列给定反馈 using namespace chrono;
ReadCSV::DoubleData std5_wr("D302-工作辊正弯DS.csv"); ///< 2列给定反馈
ReadCSV::DoubleData loop3("D302-3#活套1-5电流.csv"); ///< 5列1-5电机电流
ReadCSV::DoubleData c308fur2col("C308速度毛刺仿真.csv"); ///< 2列 正常 毛刺
ReadCSV::IntData
std4_cr("D102-4#机架上中间辊窜辊.csv"); ///< 5列编码器-4个窜动
ReadCSV::IntData loop8_bool("D102-loop8.csv"); ///< 2列DS,OS
Generator::Generator() { Generator::Generator() : registry(ModelRegistry::instance()) {
stime = chrono::system_clock::now(); stime = chrono::system_clock::now();
logger_ = std::make_unique<LOG>("Generator"); logger_ = make_unique<LOG>("Generator");
} }
Generator::~Generator() { Generator::~Generator() {
// if (!map_tables_.empty()) {
// auto iter = map_tables_.begin();
// while (iter != map_tables_.end()) {
// delete iter->second;
// iter->second = nullptr;
// map_tables_.erase(iter++);
// }
// }
for (auto& it : m_mapfix) { for (auto& it : m_mapfix) {
if (it.second != nullptr) { if (it.second != nullptr) {
delete it.second; delete it.second;
} }
} }
} }
bool Generator::wtite_in_shm(int event_no) { bool Generator::wtite_in_shm(int event_no) {
try { try {
// if (map_tables_.find(event_no) == map_tables_.end()) {
// map_tables_[event_no] =
// new CMemTable<PLC_DATA>(StringHelper::ToString<int>(event_no),
// 100);
// }
// logger_->Debug() << "wtite_in_shm Test--1" << endl;
if (m_mapfix.find(event_no) == m_mapfix.end()) { if (m_mapfix.find(event_no) == m_mapfix.end()) {
m_mapfix.insert( m_mapfix.insert(
make_pair(event_no, new CMemFix<PLC_DATA>(std::to_string(event_no), make_pair(event_no, new CMemFix<PLC_DATA>(to_string(event_no),
TEL_CACHE_SIZE))); TEL_CACHE_SIZE)));
} }
binary_tele.ReBuild(event_no); binary_tele.ReBuild(event_no);
int size = binary_tele.size(); int size = binary_tele.size();
// binary_tele.Print();
size_t data_index = size_t data_index =
((chrono::system_clock::now() - stime).count() / (size_t)pow(10, 6)) / ((chrono::system_clock::now() - stime).count() / (size_t)pow(10, 6)) /
20; ///<时间-下标 20;
logger_->Debug() << "wtite_in_shm Test--2"
<< "---data_index:" << data_index << endl; // Pass 1: create model instances
for (int i = 0; i < size; i++) { 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);
string key = string(binary_tele[i].item);
registry.getOrCreate(specStr, defaultVal, key);
}
// Pass 2: link valve_pair models to action signals
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);
string key = string(binary_tele[i].item);
IModel* model = registry.getOrCreate(specStr, defaultVal, key);
model->linkPeers(registry);
}
// Pass 3: evaluate all 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);
string key = string(binary_tele[i].item);
IModel* model = registry.getOrCreate(specStr, defaultVal, key);
if (binary_tele[i].type[0] == 'b') { if (binary_tele[i].type[0] == 'b') {
/*bool量*/ binary_tele[i] = model->evaluateBool(data_index) ? 1.0f : 0.0f;
if (binary_tele[i].length != '1') {
binary_tele[i] = RandT::randomBool();
if (strcmp(binary_tele[i].item, "3G-DSA-B01-01B") == 0) {
/*2#张力辊压辊打开*/
binary_tele[i] = (float)idata(data_index, 0);
} else if (strcmp(binary_tele[i].item, "3G-PX-B01-01") == 0) {
/*2#张力辊压辊打开位*/
binary_tele[i] = (float)idata(data_index, 1);
} else if (strcmp(binary_tele[i].item, "3G-DSA-B01-01A") == 0) {
/*2#张力辊压辊关闭*/
binary_tele[i] = (float)idata(data_index, 2);
}
else if (strcmp(binary_tele[i].item, "ENT_DR_2R22_limi") == 0) {
/*No.2入口钢卷小车-后退极限接近开关*/
binary_tele[i] = (float)car_data(data_index, 1);
} else if (strcmp(binary_tele[i].item, "ENT_DR_2R11_act") == 0) {
/*4#机架窜辊动作1*/
binary_tele[i] = (float)std4_cr(data_index, 1);
} else if (strcmp(binary_tele[i].item, "ENT_DR_2R12_act") == 0) {
/*4#机架窜辊动作2*/
binary_tele[i] = (float)std4_cr(data_index, 2);
} else if (strcmp(binary_tele[i].item, "ENT_DR_2R11_limi") == 0) {
/*4#机架窜辊动作3*/
binary_tele[i] = (float)std4_cr(data_index, 3);
} else if (strcmp(binary_tele[i].item, "ENT_DR_2R12_limi") == 0) {
/*4#机架窜辊动作4*/
binary_tele[i] = (float)std4_cr(data_index, 4);
} else if (strcmp(binary_tele[i].item, "ENT_DR_2_run") == 0) {
/*入口活套-No.8摆动门传动侧关闭极限接近开关*/
binary_tele[i] = (float)loop8_bool(data_index, 0);
} else if (strcmp(binary_tele[i].item, "ENT_DR_2_Ft") == 0) {
/*入口活套-No.8摆动门操作侧关闭极限接近开关*/
binary_tele[i] = (float)loop8_bool(data_index, 1);
}
} else { } else {
logger_->Error() << "event_no:" << event_no << ",数据项:" binary_tele[i] = model->evaluate(data_index);
<< binary_tele[i].chinese
<< ",item:" << binary_tele[i].item
<< "类型长度不匹配,"
<< "type:" << binary_tele[i].type
<< ",lenth:" << binary_tele[i].length << std::endl;
} }
} else {
/*模拟量*/
strcpy((char *)binary_tele[i].value, binary_tele[i].defaultValue);
float dataN = binary_tele[i];
binary_tele[i] = float(dataN + RandT::RandT(-0.01, 0.01));
if (strcmp(binary_tele[i].item, "BR1_1_V_act") == 0) {
/*1#张力辊1#辊 实际速度*/
binary_tele[i] =
(float)binary_tele[i] + (float)c308fur2col(data_index, 1);
} else if (strcmp(binary_tele[i].item, "BR1_2_V_act") == 0) {
/*2#张力辊1#辊 设定速度*/
binary_tele[i] =
(float)binary_tele[i] + (float)c308fur2col(data_index, 0);
} else if (strcmp(binary_tele[i].item, "BR2_1_V_act") == 0) {
/*2#张力辊1#辊 实际电流*/
binary_tele[i] =
(float)binary_tele[i] + (float)c308fur2col(data_index, 0);
} else if (strcmp(binary_tele[i].item, "BR2_2_V_act") == 0) {
/*2#张力辊2#辊 实际速度*/
binary_tele[i] =
(float)binary_tele[i] + (float)c308fur2col(data_index, 1);
} else if (strcmp(binary_tele[i].item, "BR2_2_I_act") == 0) {
/*2#张力辊2#辊 实际电流*/
binary_tele[i] = (float)spbdata(data_index, 1);
} else if (strcmp(binary_tele[i].item, "BR2_2_TQ_act") == 0) {
/*2#张力辊2#辊 实际转矩*/
binary_tele[i] = (float)spbdata(data_index, 2);
} else if (strcmp(binary_tele[i].item, "IW_3G_LV_J02_03") == 0) {
/*入口液压站液位*/
binary_tele[i] = (float)liquid_data(data_index, 0);
} else if (strcmp(binary_tele[i].item, "TCM-4-82-BDRef-1-118") == 0) {
/*1#机架工作辊正弯辊压力给定*/
binary_tele[i] = (float)std1_wr(data_index, 0);
} else if (strcmp(binary_tele[i].item, "TCM-4-82-BD-1-101") == 0) {
/*1#机架工作辊正弯辊压力反馈*/
binary_tele[i] = (float)std1_wr(data_index, 1);
} else if (strcmp(binary_tele[i].item, "TCM-72-150-BDRef-5-134") == 0) {
/*5#机架工作辊DS正弯辊压力给定*/
binary_tele[i] = (float)std5_wr(data_index, 0);
} else if (strcmp(binary_tele[i].item, "TCM-72-150-BD-5-117") == 0) {
/*5#机架工作辊DS正弯辊压力反馈*/
binary_tele[i] = (float)std5_wr(data_index, 1);
} else if (strcmp(binary_tele[i].item, "ENT_BR_4R1_torfbk") == 0) {
/*3#活套1#电机电流*/
binary_tele[i] = (float)loop3(data_index, 0);
} else if (strcmp(binary_tele[i].item, "ENT_BR_4R2_torfbk") == 0) {
/*3#活套2#电机电流*/
binary_tele[i] = (float)loop3(data_index, 1);
} else if (strcmp(binary_tele[i].item, "ENT_BR_4R3_torfbk") == 0) {
/*3#活套3#电机电流*/
binary_tele[i] = (float)loop3(data_index, 2);
} else if (strcmp(binary_tele[i].item, "ENT_BR_4R3_curfbk") == 0) {
/*3#活套4#电机电流*/
binary_tele[i] = (float)loop3(data_index, 3);
} else if (strcmp(binary_tele[i].item, "ENT_BR_4R1_curfbk") == 0) {
/*3#活套5#电机电流*/
binary_tele[i] = (float)loop3(data_index, 4);
} else if (strcmp(binary_tele[i].item, "ENT_BR_4R2_curfbk") == 0) {
/*4#机架上中间辊窜辊编码器*/
binary_tele[i] = (float)std4_cr(data_index, 0);
} else if (strcmp(binary_tele[i].item, "ACD-3-42-FQ-2") == 0) {
/*3#酸槽出口流量 ---Test*/
binary_tele[i] = (float)RandT::randomBool();
} else {
// logger_->Debug() << "test:" << binary_tele[i].item
// << ",value:" << binary_tele[i] << endl;
} }
// logger_->Debug() << "vale:" << binary_tele[i].value << "," << dataN
// << endl;
}
}
// binary_tele.Print();
char* buff = binary_tele.GetTeleData(); char* buff = binary_tele.GetTeleData();
// this->logger_->Debug() << buff << endl;
// map_tables_[event_no]->push((PLC_DATA*)buff);
m_mapfix[event_no]->push((PLC_DATA*)buff); m_mapfix[event_no]->push((PLC_DATA*)buff);
// ((BinaryTele*)buff)->Print();
return true; return true;
} catch (const std::exception& e) { } catch (const std::exception& e) {
logger_->Error() << "wtite_in_shm Error!" << e.what() << std::endl; logger_->Error() << "wtite_in_shm Error!" << e.what() << std::endl;

View File

@ -3,14 +3,16 @@
#include <glob/BinaryTele.h> #include <glob/BinaryTele.h>
#include <log4cplus/LOG.h> #include <log4cplus/LOG.h>
#include <zlib/MemVar.h> #include <zlib/MemVar.h>
#include <zlib/MemFix.hpp>
#include <zlib/zoneDef.h> #include <zlib/zoneDef.h>
#include <TestProject/RNG/model/ModelRegistry.h>
#include <array> #include <array>
#include <chrono> #include <chrono>
#include <map>
#include <numeric> #include <numeric>
#include <string> #include <string>
#include <zlib/MemFix.hpp> using namespace std;
#include <zlib/MemTable.hpp>
using namespace chrono;
class Generator { class Generator {
public: public:
Generator(); Generator();
@ -20,7 +22,7 @@ class Generator {
private: private:
std::unique_ptr<LOG> logger_; std::unique_ptr<LOG> logger_;
BinaryTele binary_tele{CMemVar::Const()->event_eis_start, "T_LOV_FDAAITEM"}; BinaryTele binary_tele{CMemVar::Const()->event_eis_start, "T_LOV_FDAAITEM"};
// map<int, CMemTable<PLC_DATA>*> map_tables_;
map<int, CMemFix<PLC_DATA>*> m_mapfix; map<int, CMemFix<PLC_DATA>*> m_mapfix;
chrono::system_clock::time_point stime; chrono::system_clock::time_point stime;
ModelRegistry& registry;
}; };

View File

@ -4,6 +4,7 @@
#include <utility/IniProperty.h> #include <utility/IniProperty.h>
#include <memory> #include <memory>
#include <string> #include <string>
#include <TestProject/RNG/model/ModelRegistry.h>
#include "mix_cc/debug/pre_define.h" #include "mix_cc/debug/pre_define.h"
using namespace baosight; using namespace baosight;
@ -16,6 +17,12 @@ int RNG::start() {
logger_->Info() << "-------RNG::start-------" << std::endl; logger_->Info() << "-------RNG::start-------" << std::endl;
con_mag_ = std::make_shared<ConnectionMag>(); con_mag_ = std::make_shared<ConnectionMag>();
con_mag_->dbLogin(); con_mag_->dbLogin();
try {
ModelRegistry::instance().loadModels("/users/dsc/code/TestProject/RNG/json/rng_models.json");
} catch (const std::exception& e) {
logger_->Error() << "Failed to load rng_models.json: " << e.what() << endl;
return (-1);
}
try { try {
auto module_name = name(); auto module_name = name();
RNG_server = new RNGICEI(); RNG_server = new RNGICEI();

View File

@ -20,7 +20,7 @@ double GuassRand(double mean = 0, double sigma = 0) {
sqrt(-2 * log(U1)) * cos(2 * M_PI * U2); // 均值为0方差为1的正态分布 sqrt(-2 * log(U1)) * cos(2 * M_PI * U2); // 均值为0方差为1的正态分布
double Y; double Y;
if (sigma >= 0) { if (sigma >= 0) {
Y = mean + sqrt(sigma) * Z; Y = mean + sigma * Z;
} else { } else {
Y = mean + Z; Y = mean + Z;
} }

View File

@ -0,0 +1,20 @@
{
"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 } }
}
}

View File

@ -0,0 +1,17 @@
#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); }
};

View File

@ -0,0 +1,14 @@
#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;
}
};

View File

@ -0,0 +1,13 @@
#pragma once
#include <TestProject/RNG/model/IModel.h>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
struct BoolToggleModel : IModel {
int period_ticks;
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;
}
};

View File

@ -0,0 +1,15 @@
#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);
}
};

View File

@ -0,0 +1,10 @@
#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; }
};

View File

@ -0,0 +1,17 @@
#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); }
};

View File

@ -0,0 +1,11 @@
#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; }
};

View File

@ -0,0 +1,14 @@
#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() {}
};

View File

@ -0,0 +1,11 @@
#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; }
};

View File

@ -0,0 +1,126 @@
#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 <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) {
// 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));
}
// 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);
}
// Valve pair reference: pair_model:action_model
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()) {
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,
const std::string& instanceKey) {
std::string key = instanceKey.empty() ? (spec.empty() ? "default" : spec) : instanceKey;
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 (extract base name: strip composite/noise and pair suffixes)
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];
}

View File

@ -0,0 +1,38 @@
#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,
const std::string& instanceKey = "");
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;
std::map<std::string, std::unique_ptr<IModel>> instances;
std::map<std::string, std::vector<IModel*>> byModelName;
std::map<std::string, Ctor> factory;
};

View File

@ -0,0 +1,12 @@
#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); }
};

View File

@ -0,0 +1,13 @@
#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; }
};

View File

@ -0,0 +1,19 @@
#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;
}
};

View File

@ -0,0 +1,12 @@
#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); }
};

View File

@ -0,0 +1,76 @@
#pragma once
#include <TestProject/RNG/model/IModel.h>
#include <nlohmann/json.hpp>
#include <cstdlib>
#include <string>
#include <vector>
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);
switch (state) {
case IDLE_LOW:
if (!prev_action && action_now) {
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;
}
prev_action = action_now;
break;
case WAITING_RISE:
if (delay_counter > 0) { delay_counter--; }
else { state = HIGH; }
break;
case HIGH:
if (prev_action && !action_now) {
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;
}
prev_action = action_now;
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 is defined in ModelRegistry.cc to avoid circular include
// (ValvePairModel needs ModelRegistry, ModelRegistry includes ValvePairModel)

View File

@ -1,8 +1,8 @@
#pragma once #pragma once
#pragma once
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <stdexcept>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
#include <vector> #include <vector>
@ -20,8 +20,7 @@ struct ReadCSV {
string word; string word;
using std::is_same_v; using std::is_same_v;
if (!infile.is_open()) { if (!infile.is_open()) {
std::cout << "Error: opening file fail" << std::endl; throw std::runtime_error("Cannot open CSV file: " + file_dir);
std::exit(1);
} }
while (std::getline(infile, line)) { while (std::getline(infile, line)) {
std::istringstream sin; std::istringstream sin;
@ -50,9 +49,9 @@ struct FlowData {
int length; int length;
int cols; int cols;
FlowData(string file_name = "D102-1#酸槽数据.csv") { FlowData(string file_name = "D102-1#酸槽数据.csv") {
if constexpr (is_same_v<T, double>) { if constexpr (std::is_same_v<T, double>) {
data = readf(file_name); data = readf(file_name);
} else if constexpr (is_same_v<T, int>) { } else if constexpr (std::is_same_v<T, int>) {
data = readi(file_name); data = readi(file_name);
} }
length = data.size(); length = data.size();

View File

@ -0,0 +1,956 @@
# 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 环境进行(本机无编译环境)。

View File

@ -0,0 +1,261 @@
# RNG 参数化随机数生成 — 设计规格
## 概述
将 RNG 从硬编码的信号→数据源映射重构为配置驱动的参数化模型架构,使随机数/仿真数据的生成完全由外部配置控制,无需修改 C++ 代码即可定义新的信号行为。
## 核心设计决策
- **模型模板与信号绑定分离**:模型模板定义在 `rng_models.json`,每个信号通过 DB2 表 `T_LOV_FDAAITEM``tables[1]` 字段引用模型名
- **每个信号独立实例化**:即使 100 个信号用同一个模型模板,各自创建独立实例,状态不共享
- **基值参数取自 defaultValue**`TeleItem.defaultValue` 天然作为 `c`(constant)、`b`(linear)、`offset`(sine)、`base`(spike/drift)、`mean`(normal) 的默认值
- **纯 JSON 参数透传**:模型构造函数直接解析 JSON params key-value工厂不关心具体参数内容
## 模型模式
### 模拟量模式
| mode | 函数 | 从 defaultValue 取的基值 | 模板 params |
|------|------|------------------------|------------|
| `constant` | y = c | c | — |
| `linear` | y = k·t + b | b | k |
| `sine` | y = A·sin(ω·t + φ) + offset | offset | A, ω, φ |
| `normal` | y = μ + σ·N(0,1) | μ | σ |
| `uniform` | y = random(−Δ, +Δ) + center | center | Δ |
| `spike` | y = base + spike(t) | base | amplitude, probability |
| `drift` | y = base + rate·t | base | drift_rate |
| `csv` | y = csv(t, col) | — | file, column |
| `composite` | y = base(t) + noise(t) | — | base_model, noise_model |
t 为时间索引,从进程启动起每周期(~20ms递增。
### 布尔量模式
| mode | 行为 | 模板 params |
|------|------|------------|
| `bool_random` | 随机 0/1可配置为 1 的概率 | prob_true (default 0.5) |
| `bool_toggle` | 固定周期 0/1 翻转 | period_ms |
| `bool_csv` | CSV 数据回放int 列) | file, column |
| `valve_pair` | 跟随另一个信号,加延迟/闪断/抖动/超时 | on_delay_ms, off_delay_ms, delay_jitter_ms, flash_prob, delay_over_prob, delay_over_ms |
### spike 毛刺行为
- 每个周期以 `probability` 概率触发毛刺
- 触发时 y = base ± amplitude符号随机
- 毛刺持续 1 个周期后恢复
### drift 漂移行为
- y = base + drift_rate × t
- 漂移累加无上限,模拟传感器退化趋势
### valve_pair 时序模型
不模拟动作信号本身,而是通过模型名引用另一个信号(如 `bool_toggle`)的当前值,模拟传感器对动作的响应延迟。
参数说明:
| 参数 | 含义 | 默认值 |
|------|------|--------|
| `on_delay_ms` | 阀开到位延迟ms信号 0→1 后传感器多久变 1 | 必填 |
| `off_delay_ms` | 阀关到位延迟ms信号 1→0 后传感器多久变 0 | 必填 |
| `delay_jitter_ms` | 延迟抖动范围,实际延迟 = delay ± random(0, jitter) | 0 |
| `flash_prob` | 高电平期间每周期出现闪断的概率 | 0 |
| `delay_over_prob` | 每次阀动作时触发超时的概率on/off 独立判断) | 0 |
| `delay_over_ms` | 超时时的延迟值,替代正常延迟 | 0 |
正常时序(无超时,有抖动):
```
动作信号 A:
────────────┐ ┌────────────────
│ │
├── on_delay ──→ ├── off_delay ──→
传感器 B: │ ±jitter │ ±jitter
────────────┘ └────────────────
└─ 阀芯移动 ──→ └─ 阀芯复位 ──→
```
超时场景delay_over_prob 触发时):
```
动作信号 A:
────────────┐
├──────── on_delay_over (4000ms) ────────→ 正常 off_delay
传感器 B: │ ┌────── ...
────────────┘ │
└── 超时! 远超正常范围
```
- 每次 A 跳变时0→1 或 1→0`delay_over_prob` 概率独立判断是否超时
- 超时时延迟 = `delay_over_ms`,未超时时延迟 = `delay_ms` ± random(0, `delay_jitter_ms`)
- 闪断:高电平期间以 `flash_prob` 概率单周期归零后自动恢复
- `delay_over_prob = 0` = 永不超时;`delay_jitter_ms = 0` = 无抖动
- on 和 off 方向各自由各自概率独立触发超时
配对方式B 的 `tables[1]` = `valve_px_std:toggle_2s`,其中 `toggle_2s` 是 A 的模型名。运行时通过模型名查找动作信号的 IModel 实例。
一对多自然支持:同一个动作信号可被多个 valve_pair 传感器跟随,各自维护独立的延迟/闪断状态。
## JSON 配置格式
文件路径:`/users/dsc/code/TestProject/RNG/json/rng_models.json`Linux 运行环境固定路径)
```json
{
"models": {
"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 } }
}
}
```
## DB2 信号绑定
每个信号在 `T_LOV_FDAAITEM` 表中通过 `tables[1]` 字段指定模型引用。
### tables[1] 语法
| 格式 | 含义 | 示例 |
|------|------|------|
| `model_name` | JSON 中定义的模型 | `normal_tiny` |
| `csv:file_name:col` | CSV 文件回放(内联简写) | `csv:C308速度毛刺仿真.csv:1` |
| `base_model+noise_model` | 组合模型 | `linear_slow+normal_tiny` |
| `pair_model:action_model` | 布尔配对模型(仅 valve_pair | `valve_px_std:toggle_2s` |
| 空 / `default` | 默认模型 `normal_tiny` | — |
### 示例数据行
| item | defaultValue | tables[1] | 说明 |
|------|-------------|-----------|------|
| BR1_1_V_act | 100.0 | `normal_tiny` | 均值100的正态噪声 |
| BR1_2_V_act | 120.0 | `normal_tiny` | 同上模板,独立实例 |
| BR2_2_I_act | 0.0 | `csv:spbdata:1` | CSV第1列回放 |
| TCM-4-82-BD-1-101 | 80.0 | `sine_ecc1` | 幅值5周期0.314的正弦 |
| TCM-72-150-BD-5-117 | 75.0 | `sine_ecc1` | 同上模板,独立实例 |
| RW_o_Drv_SpdErr | 0.0 | `linear_slow+spike_mild` | 线性爬升叠加毛刺 |
| 3G-DSA-B01-01B | 0 | `toggle_2s` | 2秒周期翻转动作阀 |
| 3G-PX-B01-01 | 0 | `valve_px_std:toggle_2s` | 跟随动作阀的到位传感器 |
## 软件架构
### 文件结构
```
TestProject/RNG/
├── model/
│ ├── IModel.h # 统一接口
│ ├── ModelRegistry.h/cc # 工厂 + tables[1] 解析
│ ├── NormalModel.h
│ ├── LinearModel.h
│ ├── SineModel.h
│ ├── UniformModel.h
│ ├── SpikeModel.h
│ ├── DriftModel.h
│ ├── CsvReplayModel.h
│ ├── BoolRandomModel.h
│ ├── BoolToggleModel.h
│ ├── BoolCsvModel.h
│ ├── ValvePairModel.h
│ └── CompositeModel.h
├── json/ # nlohmann_json (已有)
├── Generator.h/cc # 重构:去掉硬编码,走 ModelRegistry
├── RNG_icei.h/cc # 不变
├── RNG.h/cc # 启动时加载 rng_models.json
├── RandT.h # 保留,底层数学工具
├── read_csv.hpp # 保留CsvReplayModel 内部使用
└── CMakeLists.txt # 新增 model/ 源文件
```
### 关键接口
```cpp
// IModel.h — 所有模型的基类
class IModel {
public:
virtual ~IModel() = default;
virtual float evaluate(size_t t_index); // 模拟量默认返回0
virtual bool evaluateBool(size_t t_index); // 布尔量默认返回false
virtual void linkPeers(ModelRegistry& reg); // 配对信号关联,默认空
virtual void reset(); // 重置内部状态
};
// ModelRegistry — 单例
class ModelRegistry {
public:
using Ctor = std::function<std::unique_ptr<IModel>(const json& params, float defaultVal)>;
void loadModels(const std::string& jsonPath); // 加载 rng_models.json
IModel* getOrCreate(const std::string& tables1Spec, // 解析并创建实例
float defaultValue);
// 按模型名查找所有使用该模型的信号实例valve_pair 查询动作信号用)
std::vector<IModel*> findByModelName(const std::string& modelName);
void registerMode(const std::string& mode, Ctor ctor); // 注册新模型类型
};
```
### 数据流(每周期 TimeNotify
```
TimeNotify(eventNo)
→ BinaryTele.ReBuild(eventNo)
→ for i in 0..BinaryTele.size():
item = BinaryTele[i]
spec = item.tables[1]
model = ModelRegistry::getOrCreate(spec, atof(item.defaultValue))
if item.type[0] == 'b':
item = model->evaluateBool(timeIndex)
else:
item = model->evaluate(timeIndex) (float)
→ BinaryTele.GetTeleData() → m_mapfix[eventNo]->push(buff)
```
### 启动流程
```
RNG::start()
→ con_mag_->dbLogin()
→ ModelRegistry::instance().loadModels("/users/dsc/code/TestProject/RNG/json/rng_models.json")
→ RNG_server = new RNGICEI()
→ PACE 服务注册
```
## 扩展流程
新增一个模型模式(如指数衰减 `exponential`)只需要:
1. 新建 `model/ExponentialModel.h`,实现 `IModel::evaluate(t)`
2. 在 `ModelRegistry` 构造函数中注册一行:`reg("exponential", ExponentialModel::create)`
3. 在 `rng_models.json` 中添加模板:`"exp_decay": { "mode": "exponential", "params": { "lambda": 0.01 } }`
JSON params 以通用 map 形式透传给模型构造函数,工厂不需要理解新参数。
## 存量兼容
- `RandT.h` 保留,底层随机数工具被各 Model 内部使用
- `read_csv.hpp` 保留,`CsvReplayModel` 内部使用
- `Generator.cc` 中所有硬编码的 if-else 信号映射删除
- `BinaryTele` + `CMemFix<PLC_DATA>` 共享内存写入逻辑保持不变
- ICE 接口 `RNGICEI` 不变
- DB2 中已有的 `tables[1]` 为空的行走默认模型 `normal_tiny`,保持向后兼容
## 约束
- `tables[1]` 字段长度 40 字符(`name_type` = `char[40]`),模型名 + 引用语法需控制在此范围内
- 时间索引 `t_index` 从进程启动起单调递增,每周期(~20ms加 1不持久化