功能: 支持功率曲线手动恢复滤除点
- 增加额定拐角过渡点保护,减少过度筛除 - 补齐滤除点导出字段 - 增加功率曲线编辑模式和笔刷恢复交互
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user