功能: 支持功率曲线手动恢复滤除点

- 增加额定拐角过渡点保护,减少过度筛除
- 补齐滤除点导出字段
- 增加功率曲线编辑模式和笔刷恢复交互
This commit is contained in:
cloud
2026-07-17 17:21:12 +08:00
parent d5d381759e
commit 84d67dc431
3 changed files with 284 additions and 24 deletions
+60 -10
View File
@@ -101,6 +101,11 @@ struct SchemeInfo {
double rated_power = 2000.0;
};
constexpr double kRatedCornerWindBefore = 0.5;
constexpr double kRatedCornerWindAfter = 1.2;
constexpr double kRatedCornerPowerLowerRatio = 0.88;
constexpr double kRatedCornerPowerUpperRatio = 1.03;
std::string Trim(const std::string& value) {
const auto begin = value.find_first_not_of(" \t\r\n");
if (begin == std::string::npos) {
@@ -485,8 +490,20 @@ void ComputeWindSpeed600sAverage(std::vector<ValidRow>& rows) {
}
}
bool IsRatedTransitionCorner(const ValidRow& row, const EstimatedParams& params) {
if (params.rated_power <= 0.0 || params.rated_wind_speed <= 0.0) {
return false;
}
return row.wind_speed >= params.rated_wind_speed - kRatedCornerWindBefore &&
row.wind_speed <= params.rated_wind_speed + kRatedCornerWindAfter &&
row.active_power >= params.rated_power * kRatedCornerPowerLowerRatio &&
row.active_power <= params.rated_power * kRatedCornerPowerUpperRatio;
}
std::vector<ValidRow> FilterLimitPower(const std::vector<ValidRow>& rows,
const CalculationOptions& options,
const EstimatedParams* transition_params,
int& removed_count,
std::vector<RemovedPoint>& removed_points) {
removed_count = 0;
@@ -529,13 +546,15 @@ std::vector<ValidRow> FilterLimitPower(const std::vector<ValidRow>& rows,
std::vector<ValidRow> result;
result.reserve(rows.size());
for (size_t i = 0; i < rows.size(); ++i) {
if (remove_indexes.count(i) == 0) {
const bool protect_transition =
transition_params != nullptr && IsRatedTransitionCorner(rows[i], *transition_params);
if (remove_indexes.count(i) == 0 || protect_transition) {
result.push_back(rows[i]);
} else {
removed_points.push_back(RemovedPoint{rows[i], "limit_power"});
++removed_count;
}
}
removed_count = static_cast<int>(remove_indexes.size());
return result;
}
@@ -607,7 +626,8 @@ std::vector<ValidRow> FilterByWindBinIqr(const std::vector<ValidRow>& rows,
ValueGetter value_getter,
int& removed_count,
std::vector<RemovedPoint>& removed_points,
const std::string& reason) {
const std::string& reason,
const EstimatedParams* transition_params = nullptr) {
removed_count = 0;
if (rows.empty()) {
return {};
@@ -649,6 +669,9 @@ std::vector<ValidRow> FilterByWindBinIqr(const std::vector<ValidRow>& rows,
const double value = value_getter(row);
if (value >= lower && value <= upper) {
result.push_back(row);
} else if (transition_params != nullptr &&
IsRatedTransitionCorner(row, *transition_params)) {
result.push_back(row);
} else {
++removed_count;
removed_points.push_back(RemovedPoint{row, reason});
@@ -768,9 +791,10 @@ std::vector<ValidRow> FilterHighWindLowPower(const std::vector<ValidRow>& rows,
return rows;
}
const double high_wind_start = params.rated_wind_speed + 1.0;
std::vector<double> platform_powers;
for (const auto& row : rows) {
if (row.wind_speed >= params.rated_wind_speed) {
if (row.wind_speed >= high_wind_start) {
platform_powers.push_back(row.active_power);
}
}
@@ -786,7 +810,8 @@ std::vector<ValidRow> FilterHighWindLowPower(const std::vector<ValidRow>& rows,
std::vector<ValidRow> result;
result.reserve(rows.size());
for (const auto& row : rows) {
if (row.wind_speed >= params.rated_wind_speed &&
if (row.wind_speed >= high_wind_start &&
!IsRatedTransitionCorner(row, params) &&
row.active_power < lower_limit) {
++removed_count;
removed_points.push_back(RemovedPoint{row, "high_wind_low_power"});
@@ -846,14 +871,26 @@ std::vector<ValidRow> FilterCurveResidualOutliers(const std::vector<ValidRow>& r
const double median = Quantile(residuals, 0.5);
const double mad_sigma = MedianAbsoluteDeviation(residuals, median) * 1.4826;
const double rated_power = params.rated_power > 0.0 ? params.rated_power : options.rated_power;
const double lower_margin = std::max(4.0 * mad_sigma, rated_power * 0.10);
const double upper_margin = std::max(4.0 * mad_sigma, rated_power * 0.16);
double transition_multiplier = 1.0;
if (params.rated_wind_speed > 0.0) {
const double interval_center = (start + end) / 2.0;
if (interval_center >= params.rated_wind_speed - 1.0 &&
interval_center <= params.rated_wind_speed + 1.5) {
transition_multiplier = 1.8;
}
}
const double lower_margin =
std::max(4.0 * mad_sigma, rated_power * 0.10) * transition_multiplier;
const double upper_margin =
std::max(4.0 * mad_sigma, rated_power * 0.16) * transition_multiplier;
const double lower = median - lower_margin;
const double upper = median + upper_margin;
for (size_t i = 0; i < interval_rows.size(); ++i) {
if (residuals[i] >= lower && residuals[i] <= upper) {
result.push_back(interval_rows[i]);
} else if (IsRatedTransitionCorner(interval_rows[i], params)) {
result.push_back(interval_rows[i]);
} else {
++removed_count;
removed_points.push_back(RemovedPoint{interval_rows[i], "curve_residual_outlier"});
@@ -873,7 +910,7 @@ std::vector<ValidRow> FilterStrictRatedPlateau(const std::vector<ValidRow>& rows
return rows;
}
const double strict_start_wind_speed = params.rated_wind_speed + 0.5;
const double strict_start_wind_speed = params.rated_wind_speed + 1.5;
std::vector<double> plateau_powers;
for (const auto& row : rows) {
if (row.wind_speed >= strict_start_wind_speed) {
@@ -894,6 +931,7 @@ std::vector<ValidRow> FilterStrictRatedPlateau(const std::vector<ValidRow>& rows
result.reserve(rows.size());
for (const auto& row : rows) {
if (row.wind_speed >= strict_start_wind_speed &&
!IsRatedTransitionCorner(row, params) &&
row.active_power < lower_limit) {
++removed_count;
removed_points.push_back(RemovedPoint{row, "rated_plateau_low_power"});
@@ -1270,7 +1308,14 @@ void WindPowerController::FinishJob(
fan_removed_points);
scheme_two_grid_speed_low_count += removed;
fan_rows = FilterLimitPower(fan_rows, options, removed, fan_removed_points);
const auto initial_estimated_params = EstimateRatedParams(fan_rows, options);
fan_rows = FilterLimitPower(
fan_rows,
options,
&initial_estimated_params,
removed,
fan_removed_points);
limit_power_count += removed;
for (auto& row : fan_rows) {
@@ -1293,7 +1338,8 @@ void WindPowerController::FinishJob(
[](const ValidRow& row) { return row.active_power; },
removed,
fan_removed_points,
"speed_power_outlier");
"speed_power_outlier",
&initial_estimated_params);
speed_power_outlier_count += removed;
const auto fan_estimated_params = EstimateRatedParams(fan_rows, options);
@@ -1357,8 +1403,12 @@ void WindPowerController::FinishJob(
json fan_filtered = json::array();
for (const auto& removed_point : fan_removed_points) {
json point;
point["fan_id"] = removed_point.row.fan_id;
point["time"] = removed_point.row.time;
point["wind_speed"] = removed_point.row.wind_speed;
point["active_power"] = removed_point.row.active_power;
point["generator_speed"] = removed_point.row.generator_speed;
point["pitch_angle_average"] = removed_point.row.pitch_angle_average;
point["reason"] = removed_point.reason;
fan_filtered.push_back(point);
}