diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts
index 1b4e761b..ff5abd58 100644
--- a/Bin/Config/Lang/cn/nmNum_cn.ts
+++ b/Bin/Config/Lang/cn/nmNum_cn.ts
@@ -8421,8 +8421,8 @@ Average pressure in contour: %2 MPa
产量预测目前仅支持单相模型。
- Rate forecast currently requires one rate-controlled well.
- 产量预测目前要求模型中仅有一口井,且该井采用产量控制。
+ The forecast well must be rate-controlled.
+ 当前预测井必须采用产量控制。
Enter a positive initial pressure and a complete rate schedule.
@@ -8440,10 +8440,6 @@ Average pressure in contour: %2 MPa
Invalid forecast pressure or time. Check the rate schedule and model.
预测压力或时间无效,请检查分段产量制度和模型。
-
- The solver did not reach the end of the forecast schedule.
- 求解器未计算到预测制度的结束时刻。
-
nmParameterField
@@ -8796,8 +8792,12 @@ Average pressure in contour: %2 MPa
请先生成当前模型,再进行预测。
- Rate forecast currently supports single-well, single-phase models only.
- 产量预测目前仅支持单井单相模型。
+ Rate forecast currently supports single-phase models only.
+ 产量预测目前仅支持单相模型。
+
+
+ The target well is unavailable.
+ 目标井不可用。
Enter a positive initial pressure.
diff --git a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h
index 6bb745a3..69cb2ae0 100644
--- a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h
+++ b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h
@@ -76,7 +76,7 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
/** @brief 返回自动拟合目标井的原始压力结果副本。 */
QVector > getAutoFitResultPressure() const;
- /** @brief 启动前设置单井单相预测制度;单位为 MPa、hr、m^3/D,仅修改任务副本。 */
+ /** @brief 设置当前井的单相预测制度,其他井保留原制度;单位为 MPa、hr、m^3/D,仅修改任务副本。 */
bool configureRateForecast(double initialPressure, const QVector& durations,
const QVector& rates);
/** @brief 线程结束后读取预测时间(hr)和井底压力(MPa),不生成试井诊断曲线。 */
diff --git a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp
index 7c2b34bd..8d8531bb 100644
--- a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp
+++ b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp
@@ -1812,15 +1812,13 @@ bool nmCalculationDllPebiSolverTask::configureRateForecast(
return fail(tr("Rate forecast currently supports single-phase models only."));
}
int target = -1;
- int wellCount = 0;
for(int i = 0; i < grid.m_vecWellInputs.size(); ++i) {
const nmPebiWellInputSnapshot& well = grid.m_vecWellInputs[i];
if(!well.m_bRealWell) continue;
- ++wellCount;
if(well.m_sWellCode == m_sAutoFitTargetWellCode && well.m_bRateControlled) target = i;
}
- if(wellCount != 1 || target < 0) {
- return fail(tr("Rate forecast currently requires one rate-controlled well."));
+ if(target < 0) {
+ return fail(tr("The forecast well must be rate-controlled."));
}
if(!isFiniteSolverNumber(initialPressure) || initialPressure <= 0.0 ||
durations.isEmpty() || durations.size() != rates.size()) {
@@ -1849,12 +1847,7 @@ bool nmCalculationDllPebiSolverTask::configureRateForecast(
solverDurations.append(durations[0] - solverDurations[0]);
solverRates.append(rates[0]);
}
- // 外层数组保持网格槽位顺序;手工裂缝不施加流量,单相流量只填入对应相。
- const int slotCount = grid.m_vecWellInputs.size();
- scene.Rate.t.assign(slotCount, std::vector());
- scene.Rate.qo.assign(slotCount, std::vector());
- scene.Rate.qg.assign(slotCount, std::vector());
- scene.Rate.qw.assign(slotCount, std::vector());
+ // 只替换当前井的制度,保留其他井及手工裂缝槽位,沿用普通多井求解计算井间干扰。
scene.Rate.t[target].assign(solverDurations.constBegin(), solverDurations.constEnd());
scene.Rate.qo[target].assign(solverRates.size(), 0.0);
scene.Rate.qg[target].assign(solverRates.size(), 0.0);
@@ -2252,28 +2245,22 @@ bool nmCalculationDllPebiSolverTask::buildPebiModeResult(
p1.pw[target].size() != p1.t.size()) {
return fail(tr("The solver returned incomplete forecast pressure data."));
}
- double endTime = 0.0;
- for(size_t i = 0; i < grid.m_oScene.Rate.t[target].size(); ++i)
- endTime += grid.m_oScene.Rate.t[target][i];
- const double tolerance = qMax(1.0, endTime) * 1.0e-8;
QVector > pressure(2);
if(p1.t[0] > 0.0) {
pressure[0].append(0.0);
pressure[1].append(grid.m_oScene.Base.Pi);
}
+ // 与普通多井求解一致,保留 DLL 返回的完整时间轴,不按当前井制度截断结果。
for(size_t i = 0; i < p1.t.size(); ++i) {
if(isCancelRequested()) return false;
if(!isFiniteSolverNumber(p1.t[i]) || p1.t[i] < 0.0 ||
- p1.t[i] > endTime + tolerance || (i > 0 && p1.t[i] <= p1.t[i - 1]) ||
+ (i > 0 && p1.t[i] <= p1.t[i - 1]) ||
!isFiniteSolverNumber(p1.pw[target][i]) || p1.pw[target][i] <= 0.0) {
return fail(tr("Invalid forecast pressure or time. Check the rate schedule and model."));
}
pressure[0].append(p1.t[i]);
pressure[1].append(p1.pw[target][i]);
}
- if(qAbs(p1.t.back() - endTime) > tolerance) {
- return fail(tr("The solver did not reach the end of the forecast schedule."));
- }
m_autoFitResultPressure = pressure;
return true;
}
diff --git a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp
index d20c8523..889936ae 100644
--- a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp
+++ b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp
@@ -342,6 +342,25 @@ void nmSubWndMain::onPebiResultSnapshotChanged()
const QString sSnapshotId = pSnapshot->getSnapshotId();
if(sSnapshotId == m_sObservedPebiResultSnapshotId) {
+ // 切换结果井时关闭上一口井的敏感性窗口及附属表格,保留批次数据供再次查看。
+ ZxMdiArea* area = pSubWndFitting->m_pMdiArea;
+ if(area) {
+ iGuiPlot* loglog = pSubWndFitting->getPlotByTag(s_Tag_ChartSensitivity);
+ bool closed = false;
+ foreach(QMdiSubWindow* window, area->subWindowList()) {
+ if(window->objectName() != "nmSensitivityHistory" &&
+ window->objectName() != "nmSensitivityHistogram" &&
+ !(loglog && window->isAncestorOf(loglog))) continue;
+ foreach(iGuiPlot* plot, window->findChildren())
+ clearResultCurveDataTables(plot);
+ foreach(QDialog* dialog, window->findChildren()) {
+ if(dialog->isWindow()) dialog->close();
+ }
+ window->close();
+ closed = true;
+ }
+ if(closed && area->isVisible()) area->relayoutSubWnds();
+ }
// 同一快照内切换结果井仍由 onWellSelected() 处理,不重复刷新全部结果页面。
return;
}
@@ -2051,10 +2070,11 @@ void nmSubWndMain::onShowForecastResult(const QString& wellName, const QVectorgetAxisOf(2)->setMinimizeAxis(false);
- // 两张图共用时间范围,零时刻左侧留白,避免起始竖线与纵轴重叠。
+ // 两张图共用包含完整 DLL 结果的时间范围,多井计算可能长于当前井输入制度。
+ const double endTime = qMax(time, times.last());
VecDouble ticks;
- for(int tick = 0; tick <= 5; ++tick) ticks.append(time * tick / 5.0);
- plot->setAxisScaleX(-time * 0.05, time, ticks);
+ for(int tick = 0; tick <= 5; ++tick) ticks.append(endTime * tick / 5.0);
+ plot->setAxisScaleX(-endTime * 0.05, endTime, ticks);
// 时间刻度及标题只在下图显示,上图仍保留横轴数据与同步关系。
plot->getAxisOf(0)->setMinimizeAxis(i == 0);
}
@@ -2135,6 +2155,8 @@ void nmSubWndMain::clearForecastResult()
if(window) {
fitting->m_pMdiArea->removeSubWindow(window);
delete window;
+ // 直接移除窗口不会经过框架的关闭流程,清场后按当前布局重排其余窗口。
+ if(fitting->m_pMdiArea->isVisible()) fitting->m_pMdiArea->relayoutSubWnds();
}
}
diff --git a/Src/nmNum/nmSubWxs/nmWxForecast.cpp b/Src/nmNum/nmSubWxs/nmWxForecast.cpp
index 455b602c..0332098d 100644
--- a/Src/nmNum/nmSubWxs/nmWxForecast.cpp
+++ b/Src/nmNum/nmSubWxs/nmWxForecast.cpp
@@ -1010,9 +1010,9 @@ void nmWxForecast::onGenerateClicked()
return;
}
const QSharedPointer solved = m_manager->getPebiResultSnapshot();
- if(solved->getWellCount() != 1 || solved->getSolverSettings().m_nSolverModelType < 1 ||
+ if(solved->getSolverSettings().m_nSolverModelType < 1 ||
solved->getSolverSettings().m_nSolverModelType > 6) {
- QMessageBox::warning(this, tr("Forecast"), tr("Rate forecast currently supports single-well, single-phase models only."));
+ QMessageBox::warning(this, tr("Forecast"), tr("Rate forecast currently supports single-phase models only."));
return;
}
QString error;
@@ -1027,7 +1027,13 @@ void nmWxForecast::onGenerateClicked()
return;
}
const double pi = pressure.convertValueByUnitType(pressure.getValue().toDouble(), pressure.getUnit(), "MPa");
- m_wellName = solved->getWellAt(0)->m_sWellName;
+ // 与主界面的结果井选择保持一致,预测制度只替换当前井,其他井共同参与求解。
+ const nmPebiResultWellSnapshot* well = solved->findWell(m_manager->getCurrentResultWellInstanceId());
+ if(!well) {
+ QMessageBox::warning(this, tr("Forecast"), tr("The target well is unavailable."));
+ return;
+ }
+ m_wellName = well->m_sWellName;
m_task = new nmCalculationDllPebiSolverTask(QString(), m_manager, m_wellName, this);
if(!m_task->configureRateForecast(pi, durations, rates)) {
error = m_task->getFailureReason();