Compare commits
20 Commits
11e5a6aeef
...
47dd4fad4b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47dd4fad4b | ||
|
|
f41ce0c40f | ||
|
|
5ee17627e9 | ||
|
|
d9b4ee8eb3 | ||
|
|
10bb9937d6 | ||
|
|
7385b7cc6e | ||
|
|
022993ab5a | ||
|
|
4821cb9d36 | ||
|
|
b4bb27f1e5 | ||
|
|
4f83e41c0c | ||
|
|
3ce03911c3 | ||
|
|
2ef663af81 | ||
|
|
afd753f803 | ||
|
|
6afce89326 | ||
|
|
ae5f55c8ac | ||
|
|
70a976fe58 | ||
|
|
7339801f2f | ||
|
|
302aa82e38 | ||
|
|
a93801fea5 | ||
|
|
71feae9e79 |
6
.gitignore
vendored
6
.gitignore
vendored
@ -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
|
||||||
|
图片/
|
||||||
|
|||||||
@ -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
|
|
||||||
@ -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
|
||||||
|
|||||||
@ -1,208 +1,81 @@
|
|||||||
|
|
||||||
#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()) {
|
for (auto& it : m_mapfix) {
|
||||||
// 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) {
|
|
||||||
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 {
|
|
||||||
logger_->Error() << "event_no:" << event_no << ",数据项:"
|
|
||||||
<< binary_tele[i].chinese
|
|
||||||
<< ",item:" << binary_tele[i].item
|
|
||||||
<< "类型长度不匹配,"
|
|
||||||
<< "type:" << binary_tele[i].type
|
|
||||||
<< ",lenth:" << binary_tele[i].length << std::endl;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
/*模拟量*/
|
binary_tele[i] = model->evaluate(data_index);
|
||||||
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;
|
m_mapfix[event_no]->push((PLC_DATA*)buff);
|
||||||
// map_tables_[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;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,24 +3,26 @@
|
|||||||
#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();
|
||||||
~Generator();
|
~Generator();
|
||||||
bool wtite_in_shm(int event_no);
|
bool wtite_in_shm(int event_no);
|
||||||
|
|
||||||
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;
|
||||||
|
};
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
20
TestProject/RNG/json/rng_models.json
Normal file
20
TestProject/RNG/json/rng_models.json
Normal 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 } }
|
||||||
|
}
|
||||||
|
}
|
||||||
17
TestProject/RNG/model/BoolCsvModel.h
Normal file
17
TestProject/RNG/model/BoolCsvModel.h
Normal 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); }
|
||||||
|
};
|
||||||
14
TestProject/RNG/model/BoolRandomModel.h
Normal file
14
TestProject/RNG/model/BoolRandomModel.h
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
13
TestProject/RNG/model/BoolToggleModel.h
Normal file
13
TestProject/RNG/model/BoolToggleModel.h
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
15
TestProject/RNG/model/CompositeModel.h
Normal file
15
TestProject/RNG/model/CompositeModel.h
Normal 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
10
TestProject/RNG/model/ConstantModel.h
Normal file
10
TestProject/RNG/model/ConstantModel.h
Normal 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; }
|
||||||
|
};
|
||||||
17
TestProject/RNG/model/CsvReplayModel.h
Normal file
17
TestProject/RNG/model/CsvReplayModel.h
Normal 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); }
|
||||||
|
};
|
||||||
11
TestProject/RNG/model/DriftModel.h
Normal file
11
TestProject/RNG/model/DriftModel.h
Normal 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; }
|
||||||
|
};
|
||||||
14
TestProject/RNG/model/IModel.h
Normal file
14
TestProject/RNG/model/IModel.h
Normal 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() {}
|
||||||
|
};
|
||||||
11
TestProject/RNG/model/LinearModel.h
Normal file
11
TestProject/RNG/model/LinearModel.h
Normal 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; }
|
||||||
|
};
|
||||||
126
TestProject/RNG/model/ModelRegistry.cc
Normal file
126
TestProject/RNG/model/ModelRegistry.cc
Normal 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];
|
||||||
|
}
|
||||||
38
TestProject/RNG/model/ModelRegistry.h
Normal file
38
TestProject/RNG/model/ModelRegistry.h
Normal 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;
|
||||||
|
};
|
||||||
12
TestProject/RNG/model/NormalModel.h
Normal file
12
TestProject/RNG/model/NormalModel.h
Normal 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); }
|
||||||
|
};
|
||||||
13
TestProject/RNG/model/SineModel.h
Normal file
13
TestProject/RNG/model/SineModel.h
Normal 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; }
|
||||||
|
};
|
||||||
19
TestProject/RNG/model/SpikeModel.h
Normal file
19
TestProject/RNG/model/SpikeModel.h
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
12
TestProject/RNG/model/UniformModel.h
Normal file
12
TestProject/RNG/model/UniformModel.h
Normal 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); }
|
||||||
|
};
|
||||||
76
TestProject/RNG/model/ValvePairModel.h
Normal file
76
TestProject/RNG/model/ValvePairModel.h
Normal 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)
|
||||||
@ -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();
|
||||||
|
|||||||
@ -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 环境进行(本机无编译环境)。
|
||||||
@ -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,不持久化
|
||||||
Loading…
Reference in New Issue
Block a user