|
|
|
@ -27,8 +27,6 @@
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
static const bool kAutoFitDiagnosticTraceEnabled = true;
|
|
|
|
static const bool kAutoFitDiagnosticTraceEnabled = true;
|
|
|
|
static const int kAutoFitTimeWindowCount = 4;
|
|
|
|
|
|
|
|
static const double kAutoFitTimeWindowOverlapRatio = 0.20;
|
|
|
|
|
|
|
|
static const int kAutoFitIntervalsPerDecade = 20;
|
|
|
|
static const int kAutoFitIntervalsPerDecade = 20;
|
|
|
|
|
|
|
|
|
|
|
|
static int autoFitShapeLag(int pointCount)
|
|
|
|
static int autoFitShapeLag(int pointCount)
|
|
|
|
@ -37,57 +35,6 @@ static int autoFitShapeLag(int pointCount)
|
|
|
|
return qMax(1, qRound((pointCount - 1) * 0.10));
|
|
|
|
return qMax(1, qRound((pointCount - 1) * 0.10));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static double autoFitTimeWindowWeight(double coordinate, int windowIndex)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// 重叠区以基础边界为中心,总宽度为窗口宽度的 20%;相邻权重互补。
|
|
|
|
|
|
|
|
const double width = 1.0 / kAutoFitTimeWindowCount;
|
|
|
|
|
|
|
|
const double overlap = width * kAutoFitTimeWindowOverlapRatio;
|
|
|
|
|
|
|
|
const double halfOverlap = overlap * 0.5;
|
|
|
|
|
|
|
|
const double pi = 3.14159265358979323846;
|
|
|
|
|
|
|
|
double weight = 1.0;
|
|
|
|
|
|
|
|
if(windowIndex > 0) {
|
|
|
|
|
|
|
|
double u = qBound(0.0,
|
|
|
|
|
|
|
|
(coordinate - windowIndex * width + halfOverlap) / overlap,
|
|
|
|
|
|
|
|
1.0);
|
|
|
|
|
|
|
|
weight *= 0.5 * (1.0 - qCos(pi * u));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if(windowIndex + 1 < kAutoFitTimeWindowCount) {
|
|
|
|
|
|
|
|
double u = qBound(0.0,
|
|
|
|
|
|
|
|
(coordinate - (windowIndex + 1) * width + halfOverlap) / overlap,
|
|
|
|
|
|
|
|
1.0);
|
|
|
|
|
|
|
|
weight *= 0.5 * (1.0 + qCos(pi * u));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return weight;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static QVector<AutoFitTimeWindowLM> calculateAutoFitTimeWindows(
|
|
|
|
|
|
|
|
const QVector<double>& residualVector, double timeMin, double timeMax)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// 残差前后两半分别为压力和导数,已包含各占一半及时间采样权重的归一化。
|
|
|
|
|
|
|
|
const int pointCount = residualVector.size() / 2;
|
|
|
|
|
|
|
|
const double logMin = qLn(timeMin);
|
|
|
|
|
|
|
|
const double logSpan = qLn(timeMax) - logMin;
|
|
|
|
|
|
|
|
QVector<AutoFitTimeWindowLM> windows(kAutoFitTimeWindowCount);
|
|
|
|
|
|
|
|
for(int k = 0; k < windows.size(); ++k) {
|
|
|
|
|
|
|
|
AutoFitTimeWindowLM& window = windows[k];
|
|
|
|
|
|
|
|
window.timeMin = k == 0 ? timeMin
|
|
|
|
|
|
|
|
: qExp(logMin + logSpan * k / kAutoFitTimeWindowCount);
|
|
|
|
|
|
|
|
window.timeMax = k + 1 == windows.size() ? timeMax
|
|
|
|
|
|
|
|
: qExp(logMin + logSpan * (k + 1) / kAutoFitTimeWindowCount);
|
|
|
|
|
|
|
|
for(int i = 0; i < pointCount; ++i) {
|
|
|
|
|
|
|
|
const double weight = autoFitTimeWindowWeight(
|
|
|
|
|
|
|
|
static_cast<double>(i) / (pointCount - 1), k);
|
|
|
|
|
|
|
|
window.weightSum += weight;
|
|
|
|
|
|
|
|
window.energy += weight *
|
|
|
|
|
|
|
|
(residualVector[i] * residualVector[i] +
|
|
|
|
|
|
|
|
residualVector[pointCount + i] * residualVector[pointCount + i]);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
window.rmsError = window.weightSum > 0.0
|
|
|
|
|
|
|
|
? qSqrt(window.energy * pointCount / window.weightSum) : 0.0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return windows;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static inline bool isFiniteNumber(double value)
|
|
|
|
static inline bool isFiniteNumber(double value)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
@ -279,26 +226,14 @@ static bool trustRegionResidualsValid(
|
|
|
|
for(int i = 0; i < breakdown.shapeResiduals.size(); ++i) {
|
|
|
|
for(int i = 0; i < breakdown.shapeResiduals.size(); ++i) {
|
|
|
|
if(!isFiniteNumber(breakdown.shapeResiduals[i])) return false;
|
|
|
|
if(!isFiniteNumber(breakdown.shapeResiduals[i])) return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(breakdown.earlyValueResiduals.size() != 2 * pointCount ||
|
|
|
|
|
|
|
|
breakdown.earlyParallelResiduals.size() != 2 * slopeCount ||
|
|
|
|
|
|
|
|
!isFiniteNumber(breakdown.earlyValueLoss) || !isFiniteNumber(breakdown.earlyParallelLoss) ||
|
|
|
|
|
|
|
|
!isFiniteNumber(breakdown.earlyParallelBias)) return false;
|
|
|
|
|
|
|
|
for(int i = 0; i < breakdown.earlyValueResiduals.size(); ++i) {
|
|
|
|
|
|
|
|
if(!isFiniteNumber(breakdown.earlyValueResiduals[i])) return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
for(int i = 0; i < breakdown.earlyParallelResiduals.size(); ++i) {
|
|
|
|
|
|
|
|
if(!isFiniteNumber(breakdown.earlyParallelResiduals[i])) return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 数值、整体形状和前期形状残差共用一次求解,联合缓存供各子阶段复用。
|
|
|
|
// 数值和整体形状残差共用一次求解,联合缓存供两个 LM 目标复用。
|
|
|
|
static QVector<double> trustRegionFullResidual(const AutoFitObjectiveBreakdownLM& objective)
|
|
|
|
static QVector<double> trustRegionFullResidual(const AutoFitObjectiveBreakdownLM& objective)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
QVector<double> residual = objective.residualVector;
|
|
|
|
QVector<double> residual = objective.residualVector;
|
|
|
|
residual += objective.shapeResiduals;
|
|
|
|
residual += objective.shapeResiduals;
|
|
|
|
residual += objective.earlyValueResiduals;
|
|
|
|
|
|
|
|
residual += objective.earlyParallelResiduals;
|
|
|
|
|
|
|
|
return residual;
|
|
|
|
return residual;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -312,108 +247,6 @@ static double trustRegionSquaredNorm(const QVector<double>& values)
|
|
|
|
return sum;
|
|
|
|
return sum;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 数值残差已含 sqrt(0.5 * 窗口权重),相减并换底后得到 log10 间距的加权 RMS。
|
|
|
|
|
|
|
|
// 不减去平均间距,保留“两条曲线整体离得太远”的信息;共同纵移会自然抵消。
|
|
|
|
|
|
|
|
static double trustRegionEarlyGapLoss(const AutoFitObjectiveBreakdownLM& objective)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
const int count = objective.earlyValueResiduals.size() / 2;
|
|
|
|
|
|
|
|
double energy = 0.0;
|
|
|
|
|
|
|
|
for(int i = 0; i < count; ++i) {
|
|
|
|
|
|
|
|
const double difference = objective.earlyValueResiduals[i] - objective.earlyValueResiduals[count + i];
|
|
|
|
|
|
|
|
energy += 2.0 * difference * difference;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return qSqrt(energy) / qLn(10.0);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 前期带符号的平均间距差:正值表示模拟比目标分得更开,负值表示更靠近。
|
|
|
|
|
|
|
|
// 复用现有加权数值残差,恢复第一窗口的归一化权重,不用 RMS 推断偏差符号。
|
|
|
|
|
|
|
|
static double trustRegionEarlyGapBias(const AutoFitObjectiveBreakdownLM& objective)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
const int count = objective.earlyValueResiduals.size() / 2;
|
|
|
|
|
|
|
|
QVector<double> weights(count, 0.0);
|
|
|
|
|
|
|
|
double weightSum = 0.0;
|
|
|
|
|
|
|
|
for(int i = 0; i < count; ++i) {
|
|
|
|
|
|
|
|
weights[i] = autoFitTimeWindowWeight(static_cast<double>(i) / (count - 1), 0) *
|
|
|
|
|
|
|
|
((i == 0 || i == count - 1) ? 0.5 : 1.0);
|
|
|
|
|
|
|
|
weightSum += weights[i];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
double bias = 0.0;
|
|
|
|
|
|
|
|
for(int i = 0; i < count; ++i) {
|
|
|
|
|
|
|
|
bias += qSqrt(2.0 * weights[i] / weightSum) *
|
|
|
|
|
|
|
|
(objective.earlyValueResiduals[i] - objective.earlyValueResiduals[count + i]);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return bias / qLn(10.0);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 第一窗口的形状能量与局部 Fisher 使用相同的中心时间和重叠权重,
|
|
|
|
|
|
|
|
// 压力、导数同时参与;不重新插值或调用求解器。
|
|
|
|
|
|
|
|
static double trustRegionEarlyShapeEnergy(const AutoFitObjectiveBreakdownLM& objective)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
const QVector<double>& residual = objective.shapeResiduals;
|
|
|
|
|
|
|
|
const int pointCount = objective.residualVector.size() / 2;
|
|
|
|
|
|
|
|
const int lag = autoFitShapeLag(pointCount);
|
|
|
|
|
|
|
|
const int slopeCount = pointCount - lag;
|
|
|
|
|
|
|
|
double energy = 0.0;
|
|
|
|
|
|
|
|
for(int row = 0; row < residual.size(); ++row) {
|
|
|
|
|
|
|
|
const double coordinate = (row % slopeCount + lag * 0.5) / (pointCount - 1);
|
|
|
|
|
|
|
|
energy += autoFitTimeWindowWeight(coordinate, 0) * residual[row] * residual[row];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return energy;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 前期复用整体形状的双曲线斜率残差,只按第一窗口重新加权并归一化。
|
|
|
|
|
|
|
|
// 单独平移任一曲线不改变此指标;前期数值误差仅作诊断,不参与井储、表皮验收。
|
|
|
|
|
|
|
|
static void populateEarlyWellboreMetrics(AutoFitObjectiveBreakdownLM* objective,
|
|
|
|
|
|
|
|
const QVector<double> targetLogs[2], const QVector<double> resultLogs[2], double logTimeSpan)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
const int count = targetLogs[0].size();
|
|
|
|
|
|
|
|
QVector<double> weights(count, 0.0);
|
|
|
|
|
|
|
|
double weightSum = 0.0;
|
|
|
|
|
|
|
|
for(int i = 0; i < count; ++i) {
|
|
|
|
|
|
|
|
weights[i] = autoFitTimeWindowWeight(static_cast<double>(i) / (count - 1), 0) *
|
|
|
|
|
|
|
|
((i == 0 || i == count - 1) ? 0.5 : 1.0);
|
|
|
|
|
|
|
|
weightSum += weights[i];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
objective->earlyValueResiduals.fill(0.0, 2 * count);
|
|
|
|
|
|
|
|
for(int i = 0; i < count; ++i) {
|
|
|
|
|
|
|
|
const double weight = weights[i] / weightSum;
|
|
|
|
|
|
|
|
for(int component = 0; component < 2; ++component) {
|
|
|
|
|
|
|
|
const int row = component * count + i;
|
|
|
|
|
|
|
|
const double scale = qSqrt(0.5 * weight);
|
|
|
|
|
|
|
|
objective->earlyValueResiduals[row] = scale * (resultLogs[component][i] - targetLogs[component][i]);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
objective->earlyValueLoss = qSqrt(trustRegionSquaredNorm(objective->earlyValueResiduals));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 复用整体形状的斜率跨度,窗口权重取区间中心,随采样数量同步变化。
|
|
|
|
|
|
|
|
const int lag = autoFitShapeLag(count);
|
|
|
|
|
|
|
|
const int slopeCount = count - lag;
|
|
|
|
|
|
|
|
const double logTimeStep = logTimeSpan * lag / (count - 1);
|
|
|
|
|
|
|
|
QVector<double> parallelWeights(slopeCount, 0.0);
|
|
|
|
|
|
|
|
double parallelWeightSum = 0.0;
|
|
|
|
|
|
|
|
for(int i = 0; i < slopeCount; ++i) {
|
|
|
|
|
|
|
|
parallelWeights[i] = autoFitTimeWindowWeight((i + lag * 0.5) / (count - 1), 0);
|
|
|
|
|
|
|
|
parallelWeightSum += parallelWeights[i];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
objective->earlyParallelResiduals.fill(0.0, 2 * slopeCount);
|
|
|
|
|
|
|
|
objective->earlyParallelBias = 0.0;
|
|
|
|
|
|
|
|
for(int i = 0; i < slopeCount; ++i) {
|
|
|
|
|
|
|
|
const double resultSlopeDifference = ((resultLogs[0][i + lag] - resultLogs[0][i]) -
|
|
|
|
|
|
|
|
(resultLogs[1][i + lag] - resultLogs[1][i])) / logTimeStep;
|
|
|
|
|
|
|
|
const double targetSlopeDifference = ((targetLogs[0][i + lag] - targetLogs[0][i]) -
|
|
|
|
|
|
|
|
(targetLogs[1][i + lag] - targetLogs[1][i])) / logTimeStep;
|
|
|
|
|
|
|
|
const double relativeSlopeError = resultSlopeDifference - targetSlopeDifference;
|
|
|
|
|
|
|
|
const double weight = parallelWeights[i] / parallelWeightSum;
|
|
|
|
|
|
|
|
objective->earlyParallelBias += weight * relativeSlopeError;
|
|
|
|
|
|
|
|
// 整体残差已含 sqrt(0.5 / slopeCount),换成第一窗口权重后仍保持双曲线等权。
|
|
|
|
|
|
|
|
// 压力、导数分别匹配目标;相对斜率偏差只保留为诊断,不参与前期验收。
|
|
|
|
|
|
|
|
const double scale = qSqrt(slopeCount * weight);
|
|
|
|
|
|
|
|
objective->earlyParallelResiduals[i] = scale * objective->shapeResiduals[i];
|
|
|
|
|
|
|
|
objective->earlyParallelResiduals[slopeCount + i] = scale * objective->shapeResiduals[slopeCount + i];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
objective->earlyParallelLoss = qSqrt(trustRegionSquaredNorm(objective->earlyParallelResiduals));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 计算同维向量内积;维度不一致表示局部模型无效,返回零让调用方放弃修正。
|
|
|
|
// 计算同维向量内积;维度不一致表示局部模型无效,返回零让调用方放弃修正。
|
|
|
|
static double trustRegionDotProduct(const QVector<double>& left,
|
|
|
|
static double trustRegionDotProduct(const QVector<double>& left,
|
|
|
|
const QVector<double>& right)
|
|
|
|
const QVector<double>& right)
|
|
|
|
@ -498,65 +331,45 @@ static bool solveTrustRegionLinearSystem(
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Fisher 仅作为当前归一化坐标下的局部信息矩阵,不用于统计置信区间。
|
|
|
|
// LM 正规方程所需的 J^T*J 与 J^T*r,使用当前归一化参数坐标。
|
|
|
|
struct TrustRegionFisher
|
|
|
|
struct TrustRegionNormalEquations
|
|
|
|
{
|
|
|
|
{
|
|
|
|
QVector<QVector<double> > matrix;
|
|
|
|
QVector<QVector<double> > matrix;
|
|
|
|
QVector<double> gradient;
|
|
|
|
QVector<double> gradient;
|
|
|
|
|
|
|
|
|
|
|
|
explicit TrustRegionFisher(int dimensions = 0)
|
|
|
|
explicit TrustRegionNormalEquations(int dimensions = 0)
|
|
|
|
: matrix(dimensions, QVector<double>(dimensions, 0.0))
|
|
|
|
: matrix(dimensions, QVector<double>(dimensions, 0.0))
|
|
|
|
, gradient(dimensions, 0.0)
|
|
|
|
, gradient(dimensions, 0.0)
|
|
|
|
{}
|
|
|
|
{}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static QVector<TrustRegionFisher> buildTrustRegionFisher(
|
|
|
|
static TrustRegionNormalEquations buildTrustRegionNormalEquations(
|
|
|
|
const QVector<QVector<double> >& jacobian,
|
|
|
|
const QVector<QVector<double> >& jacobian,
|
|
|
|
const QVector<double>& residual,
|
|
|
|
const QVector<double>& residual,
|
|
|
|
const QVector<bool>& columnValid,
|
|
|
|
const QVector<bool>& columnValid)
|
|
|
|
const QVector<double>& coordinates = QVector<double>())
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
// 残差和 J 已包含压力/导数及时间采样权重,只再乘一次窗口权重。
|
|
|
|
// 残差和 J 已包含压力、导数及采样权重,直接累加整体矩阵,避免重复计权。
|
|
|
|
// 前半段是压力,后半段是导数;同一时间点的两行使用相同权重。
|
|
|
|
|
|
|
|
const int dimensions = columnValid.size();
|
|
|
|
const int dimensions = columnValid.size();
|
|
|
|
const int pointCount = residual.size() / 2;
|
|
|
|
TrustRegionNormalEquations equations(dimensions);
|
|
|
|
QVector<TrustRegionFisher> information(
|
|
|
|
for(int row = 0; row < residual.size(); ++row) {
|
|
|
|
kAutoFitTimeWindowCount + 1, TrustRegionFisher(dimensions));
|
|
|
|
|
|
|
|
for(int k = 0; k < kAutoFitTimeWindowCount; ++k) {
|
|
|
|
|
|
|
|
TrustRegionFisher& local = information[k];
|
|
|
|
|
|
|
|
for(int row = 0; row < residual.size(); ++row) {
|
|
|
|
|
|
|
|
const double weight = autoFitTimeWindowWeight(
|
|
|
|
|
|
|
|
coordinates.isEmpty() ? static_cast<double>(row % pointCount) / (pointCount - 1)
|
|
|
|
|
|
|
|
: coordinates[row % pointCount], k);
|
|
|
|
|
|
|
|
for(int p = 0; p < dimensions; ++p) {
|
|
|
|
|
|
|
|
if(!columnValid[p]) {
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
local.gradient[p] += weight * jacobian[row][p] * residual[row];
|
|
|
|
|
|
|
|
for(int q = 0; q <= p; ++q) {
|
|
|
|
|
|
|
|
if(columnValid[q]) {
|
|
|
|
|
|
|
|
local.matrix[p][q] +=
|
|
|
|
|
|
|
|
weight * jacobian[row][p] * jacobian[row][q];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 互补窗口之和恢复全局 J^T*J 和 J^T*r,不能对各窗口再单独平均。
|
|
|
|
|
|
|
|
TrustRegionFisher& global = information[kAutoFitTimeWindowCount];
|
|
|
|
|
|
|
|
for(int p = 0; p < dimensions; ++p) {
|
|
|
|
for(int p = 0; p < dimensions; ++p) {
|
|
|
|
global.gradient[p] += local.gradient[p];
|
|
|
|
if(!columnValid[p]) continue;
|
|
|
|
|
|
|
|
equations.gradient[p] += jacobian[row][p] * residual[row];
|
|
|
|
for(int q = 0; q <= p; ++q) {
|
|
|
|
for(int q = 0; q <= p; ++q) {
|
|
|
|
local.matrix[q][p] = local.matrix[p][q];
|
|
|
|
if(columnValid[q])
|
|
|
|
global.matrix[p][q] += local.matrix[p][q];
|
|
|
|
equations.matrix[p][q] += jacobian[row][p] * jacobian[row][q];
|
|
|
|
global.matrix[q][p] = global.matrix[p][q];
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return information;
|
|
|
|
for(int p = 0; p < dimensions; ++p) {
|
|
|
|
|
|
|
|
for(int q = 0; q < p; ++q)
|
|
|
|
|
|
|
|
equations.matrix[q][p] = equations.matrix[p][q];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return equations;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static bool buildTrustRegionFisherStep(
|
|
|
|
static bool buildTrustRegionStep(
|
|
|
|
const TrustRegionFisher& global,
|
|
|
|
const TrustRegionNormalEquations& global,
|
|
|
|
const QVector<int>& selected,
|
|
|
|
const QVector<int>& selected,
|
|
|
|
const QVector<double>& coordinates,
|
|
|
|
const QVector<double>& coordinates,
|
|
|
|
double damping, double trustRadius, double minimumStep,
|
|
|
|
double damping, double trustRadius, double minimumStep,
|
|
|
|
@ -997,15 +810,8 @@ void nmCalculationAutoFitLM::writeTraceHeader()
|
|
|
|
<< "late_trend_reliable"
|
|
|
|
<< "late_trend_reliable"
|
|
|
|
<< "registration_ambiguous";
|
|
|
|
<< "registration_ambiguous";
|
|
|
|
|
|
|
|
|
|
|
|
for(int k = 0; k < kAutoFitTimeWindowCount; ++k) {
|
|
|
|
|
|
|
|
const QString prefix = QString("window_%1_").arg(k + 1);
|
|
|
|
|
|
|
|
cols << prefix + "time_min" << prefix + "time_max"
|
|
|
|
|
|
|
|
<< prefix + "weight_sum" << prefix + "rms_error" << prefix + "energy";
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cols << "sampling_mode" << "sampling_points"
|
|
|
|
cols << "sampling_mode" << "sampling_points"
|
|
|
|
<< "pressure_vertical_bias" << "derivative_vertical_bias" << "first_window_shape_loss"
|
|
|
|
<< "pressure_vertical_bias" << "derivative_vertical_bias";
|
|
|
|
<< "early_value_loss" << "early_parallel_loss" << "early_parallel_bias" << "early_gap_loss" << "early_gap_bias";
|
|
|
|
|
|
|
|
QTextStream out(&m_traceFile);
|
|
|
|
QTextStream out(&m_traceFile);
|
|
|
|
out << cols.join(",") << "\n";
|
|
|
|
out << cols.join(",") << "\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -1042,8 +848,8 @@ void nmCalculationAutoFitLM::writeTraceMetaFile()
|
|
|
|
|
|
|
|
|
|
|
|
QTextStream out(&metaFile);
|
|
|
|
QTextStream out(&metaFile);
|
|
|
|
out << "{\n";
|
|
|
|
out << "{\n";
|
|
|
|
out << " \"schema_version\": 54,\n";
|
|
|
|
out << " \"schema_version\": 55,\n";
|
|
|
|
out << " \"strategy\": \"permeability_height_then_joint_lm_shape_then_total\",\n";
|
|
|
|
out << " \"strategy\": \"permeability_height_then_joint_lm_total_for_vertical_well_else_shape_then_total\",\n";
|
|
|
|
out << " \"height_acceptance\": \"reliable_vertical_loss_decrease; no_shape_or_total_loss_constraint\",\n";
|
|
|
|
out << " \"height_acceptance\": \"reliable_vertical_loss_decrease; no_shape_or_total_loss_constraint\",\n";
|
|
|
|
out << " \"shape_metric\": \"pressure_and_derivative_log_slopes_shared_sampling_grid_lag_round_10_percent_min_1\",\n";
|
|
|
|
out << " \"shape_metric\": \"pressure_and_derivative_log_slopes_shared_sampling_grid_lag_round_10_percent_min_1\",\n";
|
|
|
|
out << " \"shape_parameter_selection\": \"all_valid_free_columns_joint_LM; no_single_parameter_sweep_or_wellbore_recheck\",\n";
|
|
|
|
out << " \"shape_parameter_selection\": \"all_valid_free_columns_joint_LM; no_single_parameter_sweep_or_wellbore_recheck\",\n";
|
|
|
|
@ -1076,10 +882,8 @@ void nmCalculationAutoFitLM::writeTraceMetaFile()
|
|
|
|
out << " \"max_iterations\": " << m_maxIterations << ",\n";
|
|
|
|
out << " \"max_iterations\": " << m_maxIterations << ",\n";
|
|
|
|
out << " \"target_error\": " << jsonNumber(m_targetError) << "\n";
|
|
|
|
out << " \"target_error\": " << jsonNumber(m_targetError) << "\n";
|
|
|
|
out << " },\n";
|
|
|
|
out << " },\n";
|
|
|
|
// 区间尚未冻结时写 null;首次有效评价后重写元数据,保存实际窗口基准。
|
|
|
|
// 区间尚未冻结时写 null;首次有效评价后记录固定的误差比较范围。
|
|
|
|
out << " \"time_windows\": {\n";
|
|
|
|
out << " \"comparison_time_range\": {\n";
|
|
|
|
out << " \"count\": " << kAutoFitTimeWindowCount << ",\n";
|
|
|
|
|
|
|
|
out << " \"overlap_ratio\": " << jsonNumber(kAutoFitTimeWindowOverlapRatio) << ",\n";
|
|
|
|
|
|
|
|
out << " \"comparison_time_min\": "
|
|
|
|
out << " \"comparison_time_min\": "
|
|
|
|
<< (m_comparisonTimeMin > 0.0 ? jsonNumber(m_comparisonTimeMin) : QString("null")) << ",\n";
|
|
|
|
<< (m_comparisonTimeMin > 0.0 ? jsonNumber(m_comparisonTimeMin) : QString("null")) << ",\n";
|
|
|
|
out << " \"comparison_time_max\": "
|
|
|
|
out << " \"comparison_time_max\": "
|
|
|
|
@ -1155,32 +959,10 @@ void nmCalculationAutoFitLM::writeTraceRow(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 无效候选也补齐窗口列,保证轨迹每行结构一致。
|
|
|
|
|
|
|
|
for(int k = 0; k < kAutoFitTimeWindowCount; ++k) {
|
|
|
|
|
|
|
|
if(objectiveBreakdown && objectiveBreakdown->valid &&
|
|
|
|
|
|
|
|
k < objectiveBreakdown->timeWindows.size()) {
|
|
|
|
|
|
|
|
const AutoFitTimeWindowLM& window = objectiveBreakdown->timeWindows[k];
|
|
|
|
|
|
|
|
cols << traceNumber(window.timeMin) << traceNumber(window.timeMax)
|
|
|
|
|
|
|
|
<< traceNumber(window.weightSum) << traceNumber(window.rmsError)
|
|
|
|
|
|
|
|
<< traceNumber(window.energy);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
for(int i = 0; i < 5; ++i) {
|
|
|
|
|
|
|
|
cols << QString();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cols << "log_uniform_per_decade"
|
|
|
|
cols << "log_uniform_per_decade"
|
|
|
|
<< (objectiveBreakdown ? QString::number(objectiveBreakdown->residualVector.size() / 2) : QString())
|
|
|
|
<< (objectiveBreakdown ? QString::number(objectiveBreakdown->residualVector.size() / 2) : QString())
|
|
|
|
<< (objectiveBreakdown ? traceNumber(objectiveBreakdown->pressureVerticalBias) : QString())
|
|
|
|
<< (objectiveBreakdown ? traceNumber(objectiveBreakdown->pressureVerticalBias) : QString())
|
|
|
|
<< (objectiveBreakdown ? traceNumber(objectiveBreakdown->derivativeVerticalBias) : QString())
|
|
|
|
<< (objectiveBreakdown ? traceNumber(objectiveBreakdown->derivativeVerticalBias) : QString());
|
|
|
|
<< (objectiveBreakdown && objectiveBreakdown->valid
|
|
|
|
|
|
|
|
? traceNumber(qSqrt(trustRegionEarlyShapeEnergy(*objectiveBreakdown))) : QString())
|
|
|
|
|
|
|
|
<< (objectiveBreakdown && objectiveBreakdown->valid ? traceNumber(objectiveBreakdown->earlyValueLoss) : QString())
|
|
|
|
|
|
|
|
<< (objectiveBreakdown && objectiveBreakdown->valid ? traceNumber(objectiveBreakdown->earlyParallelLoss) : QString())
|
|
|
|
|
|
|
|
<< (objectiveBreakdown && objectiveBreakdown->valid ? traceNumber(objectiveBreakdown->earlyParallelBias) : QString())
|
|
|
|
|
|
|
|
<< (objectiveBreakdown && objectiveBreakdown->valid ? traceNumber(trustRegionEarlyGapLoss(*objectiveBreakdown)) : QString())
|
|
|
|
|
|
|
|
<< (objectiveBreakdown && objectiveBreakdown->valid ? traceNumber(trustRegionEarlyGapBias(*objectiveBreakdown)) : QString());
|
|
|
|
|
|
|
|
QTextStream out(&m_traceFile);
|
|
|
|
QTextStream out(&m_traceFile);
|
|
|
|
out << cols.join(",") << "\n";
|
|
|
|
out << cols.join(",") << "\n";
|
|
|
|
m_traceFile.flush();
|
|
|
|
m_traceFile.flush();
|
|
|
|
@ -1824,6 +1606,10 @@ bool nmCalculationAutoFitLM::evaluateTrustRegionPoint(
|
|
|
|
|
|
|
|
|
|
|
|
StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
|
|
|
|
StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
|
|
|
|
// 本次拟合开始时按目标井的实际井型确定流程,普通直井跳过形状调整。
|
|
|
|
|
|
|
|
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
|
|
|
|
|
nmDataWellBase* targetWell = dataManager ? dataManager->findWellByName(m_targetWellName) : nullptr;
|
|
|
|
|
|
|
|
const bool skipShapeStage = targetWell && targetWell->getWellType() == NM_WELL_MODEL::Vertical_Well;
|
|
|
|
const int dimensions = getEnabledParameterCount();
|
|
|
|
const int dimensions = getEnabledParameterCount();
|
|
|
|
if(dimensions <= 0 || m_enabledParamIndices.size() != dimensions) {
|
|
|
|
if(dimensions <= 0 || m_enabledParamIndices.size() != dimensions) {
|
|
|
|
m_lastError = tr("No valid parameters are available for trust-region fitting");
|
|
|
|
m_lastError = tr("No valid parameters are available for trust-region fitting");
|
|
|
|
@ -1863,11 +1649,10 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
|
|
|
|
const char* rebuildReason = QT_TR_NOOP("initial sensitivity model");
|
|
|
|
const char* rebuildReason = QT_TR_NOOP("initial sensitivity model");
|
|
|
|
bool modelRebuiltAtMinimumRadius = false;
|
|
|
|
bool modelRebuiltAtMinimumRadius = false;
|
|
|
|
bool stagnationConfirmationRequested = false;
|
|
|
|
bool stagnationConfirmationRequested = false;
|
|
|
|
bool globalFallbackAttempted = false;
|
|
|
|
|
|
|
|
StopReasonLM stopReason = LM_MAX_ITERATIONS;
|
|
|
|
StopReasonLM stopReason = LM_MAX_ITERATIONS;
|
|
|
|
|
|
|
|
|
|
|
|
// jacobian 的行对应固定采样的残差,列对应用户勾选的参数。
|
|
|
|
// jacobian 的行对应固定采样的残差,列对应用户勾选的参数。
|
|
|
|
// Fisher 按当前目标取数值或形状残差行,两段均使用所有勾选参数。
|
|
|
|
// LM 按当前目标取数值或形状残差行,两段均使用所有勾选参数。
|
|
|
|
QVector<QVector<double> > jacobian;
|
|
|
|
QVector<QVector<double> > jacobian;
|
|
|
|
QVector<bool> jacobianColumnValid(dimensions, false);
|
|
|
|
QVector<bool> jacobianColumnValid(dimensions, false);
|
|
|
|
|
|
|
|
|
|
|
|
@ -2058,8 +1843,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
|
|
|
|
if(m_shouldStop) return LM_USER_STOPPED;
|
|
|
|
if(m_shouldStop) return LM_USER_STOPPED;
|
|
|
|
if(completedIterations >= m_maxIterations) return LM_MAX_ITERATIONS;
|
|
|
|
if(completedIterations >= m_maxIterations) return LM_MAX_ITERATIONS;
|
|
|
|
|
|
|
|
|
|
|
|
// 原逐参数第二阶段已移除;同一个联合 LM 先匹配整体形状,再匹配总误差。
|
|
|
|
// 直井直接匹配总误差,其他井型仍先匹配整体形状,再匹配总误差。
|
|
|
|
bool shapeStage = true;
|
|
|
|
bool shapeStage = !skipShapeStage;
|
|
|
|
auto stageError = [&](const TrustRegionEvaluation& point) -> double {
|
|
|
|
auto stageError = [&](const TrustRegionEvaluation& point) -> double {
|
|
|
|
return shapeStage ? point.breakdown.shapeLoss : point.fitness;
|
|
|
|
return shapeStage ? point.breakdown.shapeLoss : point.fitness;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
@ -2086,7 +1871,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 有效改善后更新累计基准,并清除停滞计数。
|
|
|
|
// 有效改善后更新累计基准,并清除停滞计数。
|
|
|
|
globalFallbackAttempted = false;
|
|
|
|
|
|
|
|
effectiveImprovementBaseline = fitness;
|
|
|
|
effectiveImprovementBaseline = fitness;
|
|
|
|
consecutiveIneffectiveSteps = 0;
|
|
|
|
consecutiveIneffectiveSteps = 0;
|
|
|
|
stagnationConfirmationRequested = false;
|
|
|
|
stagnationConfirmationRequested = false;
|
|
|
|
@ -2096,10 +1880,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
|
|
|
|
// 两个目标均沿用原 LM 的有效改善门槛及重建后停滞确认。
|
|
|
|
// 两个目标均沿用原 LM 的有效改善门槛及重建后停滞确认。
|
|
|
|
auto recordIneffectiveStep = [&]() -> bool {
|
|
|
|
auto recordIneffectiveStep = [&]() -> bool {
|
|
|
|
++consecutiveIneffectiveSteps;
|
|
|
|
++consecutiveIneffectiveSteps;
|
|
|
|
if(!globalFallbackAttempted) {
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
globalFallbackAttempted = false;
|
|
|
|
|
|
|
|
if(consecutiveIneffectiveSteps < maximumIneffectiveSteps) {
|
|
|
|
if(consecutiveIneffectiveSteps < maximumIneffectiveSteps) {
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -2308,7 +2088,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
for(int phase = 0; phase < 2 && !m_shouldStop && completedIterations < m_maxIterations; ++phase) {
|
|
|
|
for(int phase = skipShapeStage ? 1 : 0;
|
|
|
|
|
|
|
|
phase < 2 && !m_shouldStop && completedIterations < m_maxIterations; ++phase) {
|
|
|
|
shapeStage = phase == 0;
|
|
|
|
shapeStage = phase == 0;
|
|
|
|
phaseIterations = 0;
|
|
|
|
phaseIterations = 0;
|
|
|
|
maximumEvaluations = m_totalEvaluations + phaseEvaluationBudget;
|
|
|
|
maximumEvaluations = m_totalEvaluations + phaseEvaluationBudget;
|
|
|
|
@ -2323,7 +2104,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
|
|
|
|
consecutivePoorPredictions = 0;
|
|
|
|
consecutivePoorPredictions = 0;
|
|
|
|
modelRebuiltAtMinimumRadius = false;
|
|
|
|
modelRebuiltAtMinimumRadius = false;
|
|
|
|
stagnationConfirmationRequested = false;
|
|
|
|
stagnationConfirmationRequested = false;
|
|
|
|
globalFallbackAttempted = false;
|
|
|
|
|
|
|
|
jacobian.clear();
|
|
|
|
jacobian.clear();
|
|
|
|
rebuildRequested = true;
|
|
|
|
rebuildRequested = true;
|
|
|
|
rebuildReason = shapeStage ? QT_TR_NOOP("full sensitivity at shape LM entry")
|
|
|
|
rebuildReason = shapeStage ? QT_TR_NOOP("full sensitivity at shape LM entry")
|
|
|
|
@ -2336,7 +2116,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
|
|
|
|
DEBUG_OUT(tr("LM phase budget: %1 remaining shared iterations, %2 evaluations.")
|
|
|
|
DEBUG_OUT(tr("LM phase budget: %1 remaining shared iterations, %2 evaluations.")
|
|
|
|
.arg(m_maxIterations - completedIterations).arg(phaseEvaluationBudget));
|
|
|
|
.arg(m_maxIterations - completedIterations).arg(phaseEvaluationBudget));
|
|
|
|
writeTraceRow(completedIterations, -1, "stage_switch", current.parameters, current.fitness,
|
|
|
|
writeTraceRow(completedIterations, -1, "stage_switch", current.parameters, current.fitness,
|
|
|
|
true, 0, shapeStage ? "height_to_shape_lm" : "shape_to_total", ¤t.breakdown);
|
|
|
|
true, 0, shapeStage ? "height_to_shape_lm" : (skipShapeStage ? "height_to_total" : "shape_to_total"),
|
|
|
|
|
|
|
|
¤t.breakdown);
|
|
|
|
|
|
|
|
|
|
|
|
while(!m_shouldStop && completedIterations < m_maxIterations && m_totalEvaluations < maximumEvaluations) {
|
|
|
|
while(!m_shouldStop && completedIterations < m_maxIterations && m_totalEvaluations < maximumEvaluations) {
|
|
|
|
m_currentIteration = completedIterations;
|
|
|
|
m_currentIteration = completedIterations;
|
|
|
|
@ -2374,18 +2155,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
|
|
|
|
? current.breakdown.shapeResiduals : current.breakdown.residualVector;
|
|
|
|
? current.breakdown.shapeResiduals : current.breakdown.residualVector;
|
|
|
|
const int rowOffset = shapeStage ? current.breakdown.residualVector.size() : 0;
|
|
|
|
const int rowOffset = shapeStage ? current.breakdown.residualVector.size() : 0;
|
|
|
|
const QVector<QVector<double> > objectiveJacobian = jacobian.mid(rowOffset, objectiveResidual.size());
|
|
|
|
const QVector<QVector<double> > objectiveJacobian = jacobian.mid(rowOffset, objectiveResidual.size());
|
|
|
|
QVector<double> objectiveCoordinates;
|
|
|
|
const TrustRegionNormalEquations global = buildTrustRegionNormalEquations(
|
|
|
|
if(shapeStage) {
|
|
|
|
objectiveJacobian, objectiveResidual, jacobianColumnValid);
|
|
|
|
// Fisher 窗口与形状残差使用同一组斜率区间中心。
|
|
|
|
|
|
|
|
const int pointCount = current.breakdown.residualVector.size() / 2;
|
|
|
|
|
|
|
|
const int lag = autoFitShapeLag(pointCount);
|
|
|
|
|
|
|
|
for(int i = 0; i < pointCount - lag; ++i)
|
|
|
|
|
|
|
|
objectiveCoordinates.append((i + lag * 0.5) / (pointCount - 1));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const QVector<TrustRegionFisher> information = buildTrustRegionFisher(
|
|
|
|
|
|
|
|
objectiveJacobian, objectiveResidual, jacobianColumnValid, objectiveCoordinates);
|
|
|
|
|
|
|
|
// 两段都使用当前目标的全局信息,不按时间窗口另行选参。
|
|
|
|
|
|
|
|
const TrustRegionFisher& global = information[kAutoFitTimeWindowCount];
|
|
|
|
|
|
|
|
QVector<int> selectedColumns;
|
|
|
|
QVector<int> selectedColumns;
|
|
|
|
QVector<double> coordinateStep;
|
|
|
|
QVector<double> coordinateStep;
|
|
|
|
double predictedReduction = 0.0;
|
|
|
|
double predictedReduction = 0.0;
|
|
|
|
@ -2395,8 +2166,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
|
|
|
|
if(jacobianColumnValid[column] && global.matrix[column][column] > 0.0)
|
|
|
|
if(jacobianColumnValid[column] && global.matrix[column][column] > 0.0)
|
|
|
|
selectedColumns.append(column);
|
|
|
|
selectedColumns.append(column);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
globalFallbackAttempted = true;
|
|
|
|
if(selectedColumns.isEmpty() || !buildTrustRegionStep(
|
|
|
|
if(selectedColumns.isEmpty() || !buildTrustRegionFisherStep(
|
|
|
|
|
|
|
|
global, selectedColumns, current.coordinates, damping,
|
|
|
|
global, selectedColumns, current.coordinates, damping,
|
|
|
|
trustRadius, minimumCoordinateStep, &coordinateStep, &predictedReduction)) {
|
|
|
|
trustRadius, minimumCoordinateStep, &coordinateStep, &predictedReduction)) {
|
|
|
|
selectedColumns.clear();
|
|
|
|
selectedColumns.clear();
|
|
|
|
@ -2426,7 +2196,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
|
|
|
|
for(int column = 0; column < dimensions; ++column) {
|
|
|
|
for(int column = 0; column < dimensions; ++column) {
|
|
|
|
candidateCoordinates[column] += coordinateStep[column];
|
|
|
|
candidateCoordinates[column] += coordinateStep[column];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// 窗口和参数索引仅写入已有跟踪文件,不向拟合日志窗口增加分段信息。
|
|
|
|
// 参数索引保留在跟踪文件中,便于检查每次联合调整的参数。
|
|
|
|
QStringList selectedParameterIndices;
|
|
|
|
QStringList selectedParameterIndices;
|
|
|
|
for(int i = 0; i < selectedColumns.size(); ++i) {
|
|
|
|
for(int i = 0; i < selectedColumns.size(); ++i) {
|
|
|
|
selectedParameterIndices << QString::number(m_enabledParamIndices[selectedColumns[i]]);
|
|
|
|
selectedParameterIndices << QString::number(m_enabledParamIndices[selectedColumns[i]]);
|
|
|
|
@ -3300,8 +3070,8 @@ double nmCalculationAutoFitLM::calculateLogLogCurveError(
|
|
|
|
const QVector<QVector<double> >& target,
|
|
|
|
const QVector<QVector<double> >& target,
|
|
|
|
const QVector<QVector<double> >& result)
|
|
|
|
const QVector<QVector<double> >& result)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
// 首次有效评价后固定公共时间范围。窗口与上下、左右、形状只负责诊断,
|
|
|
|
// 首次有效评价后固定公共时间范围,保证各候选在相同时间点比较。
|
|
|
|
// 主目标仍由完整压力和导数残差计算,避免窗口重叠造成重复计权。
|
|
|
|
// 总误差由完整压力和导数残差计算,形状阶段单独使用斜率残差。
|
|
|
|
// 整个计算过程均位于 log(time)-log(value) 坐标。
|
|
|
|
// 整个计算过程均位于 log(time)-log(value) 坐标。
|
|
|
|
const double invalidLoss = 1.0e10;
|
|
|
|
const double invalidLoss = 1.0e10;
|
|
|
|
const double valueFloor = 1.0e-12;
|
|
|
|
const double valueFloor = 1.0e-12;
|
|
|
|
@ -3427,7 +3197,7 @@ double nmCalculationAutoFitLM::calculateLogLogCurveError(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 仅首次有效评价使用交集建立基准;失败试算不能冻结区间,后续候选
|
|
|
|
// 仅首次有效评价使用交集建立基准;失败试算不能冻结区间,后续候选
|
|
|
|
// 必须覆盖完整基准,不允许靠丢失首尾点缩小误差或改变窗口位置。
|
|
|
|
// 必须覆盖完整基准,不允许靠丢失首尾点缩小误差。
|
|
|
|
const bool comparisonRangeFixed = m_comparisonTimeMin > 0.0;
|
|
|
|
const bool comparisonRangeFixed = m_comparisonTimeMin > 0.0;
|
|
|
|
double overlapMinX = comparisonRangeFixed
|
|
|
|
double overlapMinX = comparisonRangeFixed
|
|
|
|
? m_comparisonTimeMin : qMax(targetMinX, resultMinX);
|
|
|
|
? m_comparisonTimeMin : qMax(targetMinX, resultMinX);
|
|
|
|
@ -3453,7 +3223,6 @@ double nmCalculationAutoFitLM::calculateLogLogCurveError(
|
|
|
|
const int lag = autoFitShapeLag(count);
|
|
|
|
const int lag = autoFitShapeLag(count);
|
|
|
|
const double span = qLn(overlapMaxX) - qLn(overlapMinX);
|
|
|
|
const double span = qLn(overlapMaxX) - qLn(overlapMinX);
|
|
|
|
QVector<double> residuals[2];
|
|
|
|
QVector<double> residuals[2];
|
|
|
|
QVector<double> targetLogs[2], resultLogs[2];
|
|
|
|
|
|
|
|
double biases[2] = {0.0, 0.0};
|
|
|
|
double biases[2] = {0.0, 0.0};
|
|
|
|
for(int component = 0; component < 2; ++component) {
|
|
|
|
for(int component = 0; component < 2; ++component) {
|
|
|
|
for(int i = 0; i < count; ++i) {
|
|
|
|
for(int i = 0; i < count; ++i) {
|
|
|
|
@ -3466,9 +3235,7 @@ double nmCalculationAutoFitLM::calculateLogLogCurveError(
|
|
|
|
time, &resultValue)) return false;
|
|
|
|
time, &resultValue)) return false;
|
|
|
|
const double residual = resultValue - targetValue;
|
|
|
|
const double residual = resultValue - targetValue;
|
|
|
|
residuals[component].append(residual);
|
|
|
|
residuals[component].append(residual);
|
|
|
|
targetLogs[component].append(targetValue);
|
|
|
|
// 使用对数时间梯形权重,避免密集段主导高度。
|
|
|
|
resultLogs[component].append(resultValue);
|
|
|
|
|
|
|
|
// 对数时间梯形权重等价于互补窗口加权求和,避免密集段主导高度。
|
|
|
|
|
|
|
|
biases[component] += residual * ((i == 0 || i == count - 1) ? 0.5 : 1.0)
|
|
|
|
biases[component] += residual * ((i == 0 || i == count - 1) ? 0.5 : 1.0)
|
|
|
|
/ (count - 1);
|
|
|
|
/ (count - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -3489,7 +3256,6 @@ double nmCalculationAutoFitLM::calculateLogLogCurveError(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
objective->shapeLoss = qSqrt(trustRegionSquaredNorm(objective->shapeResiduals));
|
|
|
|
objective->shapeLoss = qSqrt(trustRegionSquaredNorm(objective->shapeResiduals));
|
|
|
|
populateEarlyWellboreMetrics(objective, targetLogs, resultLogs, span);
|
|
|
|
|
|
|
|
return isFiniteNumber(objective->shapeLoss);
|
|
|
|
return isFiniteNumber(objective->shapeLoss);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
@ -4119,8 +3885,6 @@ double nmCalculationAutoFitLM::calculateLogLogCurveError(
|
|
|
|
isFiniteNumber(breakdown.total) &&
|
|
|
|
isFiniteNumber(breakdown.total) &&
|
|
|
|
breakdown.total >= 0.0;
|
|
|
|
breakdown.total >= 0.0;
|
|
|
|
if(breakdown.valid && breakdown.total < 1.0e9) {
|
|
|
|
if(breakdown.valid && breakdown.total < 1.0e9) {
|
|
|
|
breakdown.timeWindows = calculateAutoFitTimeWindows(
|
|
|
|
|
|
|
|
breakdown.residualVector, overlapMinX, overlapMaxX);
|
|
|
|
|
|
|
|
if(!comparisonRangeFixed) {
|
|
|
|
if(!comparisonRangeFixed) {
|
|
|
|
// 整个目标及诊断均有效后才提交基准,中点回退可重新建立区间。
|
|
|
|
// 整个目标及诊断均有效后才提交基准,中点回退可重新建立区间。
|
|
|
|
m_comparisonTimeMin = overlapMinX;
|
|
|
|
m_comparisonTimeMin = overlapMinX;
|
|
|
|
|