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();