feat(nmNum): 复用预测窗口并支持预测数据保存读取

- 复用预测输入窗口,保存输入参数及单位。
- 保存预测结果,读取项目时恢复曲线。
- 简化预测窗口标题,修改模型参数时保留已有预测。
feature/Sensitivity-20261012
lvjunjie 22 hours ago
parent 6797e813ac
commit 8e0f1a9851

@ -418,6 +418,7 @@ public:
nmDataForecast* getForecastData() const;
nmDataForecast getForecastDataCopy() const;
void updateForecastData(const nmDataForecast& newData);
void publishForecastResult(const nmDataForecast& completedData);
// 敏感性分析相关
nmDataSensitive* createSensitiveData();
@ -782,6 +783,8 @@ public:
void syncGeometryAttrs();
signals:
void sigForecastResultChanged();
void sigForecastDataChanged();
void dataChanged();
void sigSensitiveResultsChanged();
/// @brief 井数据准备完成信号
@ -802,6 +805,7 @@ public:
void sigPebiResultStateChanged();
private slots:
void clearInvalidForecastResult();
/** @brief 根据参数面板实际编辑的属性类型,使对应 PEBI 输入版本失效。 */
void onRegisteredAttributeEdited(QString sName, QVariant oValue);

@ -2,6 +2,7 @@
#define NMDATAFORECAST_H
#include <QObject>
#include <QSharedPointer>
#include "nmData_global.h"
#include "nmDataBase.h"
@ -149,6 +150,9 @@ struct NM_DATA_EXPORT MultiplePressures {
struct NM_DATA_EXPORT MultipleRates {
nmDataAttribute initialPressure;
QString pressureUnit;
QString durationUnit;
QString rateUnit;
MultipleRates();
@ -192,10 +196,23 @@ public:
void setMultipleRatesData(const MultipleRates& mr);
MultipleRates& getMultipleRatesData();
// 已生成结果保留独立制度,后续编辑输入不改变原曲线。
struct Result {
QString snapshotId;
QString wellInstanceId;
QVector<double> times; ///< hr
QVector<double> pressures; ///< MPa
QVector<double> durations; ///< hr
QVector<double> rates; ///< m^3/D
};
QSharedPointer<Result> getResult() const { return m_result; }
void setResult(const QSharedPointer<Result>& result) { m_result = result; }
virtual rapidjson::Value ToJsonValue(rapidjson::Document::AllocatorType& allocator) const override;
virtual void FromJsonValue(const rapidjson::Value& jsonValue) override;
private:
QSharedPointer<Result> m_result;
ForecastOption currentOption;
ConstantPressure constantPressureData;
DecliningPressure decliningPressureData;

@ -173,10 +173,8 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit {
/** @brief 在框架已有的敏感性子窗口叠加显示本分析的双对数结果。 */
void onShowSensitiveResults();
// 预测结果复用所属分析内的 MDI 子窗口,模型变化时移除旧结果。
void onShowForecastResult(const QString& wellName, const QVector<double>& times,
const QVector<double>& pressures, const QVector<double>& durations,
const QVector<double>& rates);
void clearForecastResult();
void onShowForecastResult();
void onForecastDataChanged();
// 只读结果曲线共用框架数据表,不开放拖动编辑。
void onReadonlyCurveDoubleClicked(ZxPlot* plot, ZxObjBase* object,
const QPointF& point, bool& processed);

@ -33,10 +33,6 @@ public:
signals:
void sigForecastTaskReady(nmWxForecast* dialog, nmCalculationDllPebiSolverTask* task);
void sigForecastCancelRequested();
// 计算完成后由所属分析主界面承载结果,输入窗口不持有结果窗口。
void sigForecastReady(const QString& wellName, const QVector<double>& times,
const QVector<double>& pressures, const QVector<double>& durations,
const QVector<double>& rates);
public slots:
void reject();
@ -63,6 +59,7 @@ protected:
void onCancelClicked();
void onForecastFinished();
void onModelChanged();
void saveDataToManager();
// Table edit
void onPressureCellChanged(int row, int column);
@ -81,7 +78,6 @@ private:
void updateParametersForOption();
void updateUIFromData();
void updateDataFromUI();
void saveDataToManager();
bool readRateSchedule(QVector<double>& durations, QVector<double>& rates, QString& error) const;
QWidget* buildExtEmptyPage(const QString& text = QString());
@ -98,7 +94,7 @@ private:
nmCalculationDllPebiSolverTask* m_task;
bool m_calculationRunning;
QString m_sourceSnapshotId;
QString m_wellName;
QString m_sourceWellInstanceId;
QVector<double> m_durations; ///< 本次计算的分段时长(hr)。
QVector<double> m_rates; ///< 本次计算的分段产量(m^3/D)。

@ -5,6 +5,7 @@
#include "iDlgBase.h"
#include "nmParameterField.h"
#include <QWidget>
#include <QPointer>
// 前向声明
class QGroupBox;
@ -73,10 +74,6 @@ public:
signals:
// 预测任务交给所属主窗口,复用普通求解进度框。
void sigForecastTaskReady(nmWxForecast* dialog, nmCalculationDllPebiSolverTask* task);
// 将预测结果转交给本面板所属的分析主界面。
void sigForecastReady(const QString& wellName, const QVector<double>& times,
const QVector<double>& pressures, const QVector<double>& durations,
const QVector<double>& rates);
void sigGenerateClicked(); // 点击生成按钮时发出的信号
// 敏感性批量任务交给所属主窗口,复用普通求解进度框。
void sigSensitivityTaskReady(nmWxSensitive* dialog, nmCalculationDllPebiSolverTask* task);
@ -230,6 +227,7 @@ private:
QPushButton* m_pGenerateButton;
QPushButton* m_pAutomaticfittingBtn;
QPushButton* m_pForecastBtn;
QPointer<nmWxForecast> m_pForecastDialog; ///< 每个分析面板复用自己的预测输入窗口。
QPushButton* m_pSensitiveBtn;
QHBoxLayout* m_pGenerateLayout;

@ -1358,6 +1358,8 @@ ZX_DEFINE_DYNAMIC(DataAnalyzeManager, nmDataAnalyzeManager)
//m_pSkinVsRateData = nullptr;
//m_pFlowSegmentData = nullptr;
m_pForecastData = nullptr;
// 基础结果或结果井切换时清理预测结果,修改模型参数时保留上次预测。
connect(this, SIGNAL(sigPebiResultSnapshotChanged()), this, SLOT(clearInvalidForecastResult()));
m_pSensitiveData = nullptr;
// 属性注册表
@ -2884,7 +2886,33 @@ void nmDataAnalyzeManager::updateForecastData(const nmDataForecast& newData)
m_pForecastData = new nmDataForecast;
}
*m_pForecastData = newData; // 调用赋值运算符
// 参数更新不覆盖上一次生成的结果。
const QSharedPointer<nmDataForecast::Result> result = m_pForecastData->getResult();
*m_pForecastData = newData;
m_pForecastData->setResult(result);
emit sigForecastDataChanged();
}
void nmDataAnalyzeManager::publishForecastResult(const nmDataForecast& completedData)
{
// 与敏感性分析一致,通过数据中心发布结果并通知所属主窗口。
if(!m_pForecastData) m_pForecastData = new nmDataForecast;
m_pForecastData->setResult(completedData.getResult());
emit sigForecastDataChanged();
emit sigForecastResultChanged();
}
void nmDataAnalyzeManager::clearInvalidForecastResult()
{
// 项目读取也会发出快照通知;只有来源不匹配时才丢弃已保存结果。
if(!m_pForecastData || m_pForecastData->getResult().isNull()) return;
const QSharedPointer<nmDataForecast::Result> result = m_pForecastData->getResult();
if(!m_pPebiResultSnapshot.isNull() &&
result->snapshotId == m_pPebiResultSnapshot->getSnapshotId() &&
result->wellInstanceId == getCurrentResultWellInstanceId()) return;
m_pForecastData->setResult(QSharedPointer<nmDataForecast::Result>());
emit sigForecastDataChanged();
emit sigForecastResultChanged();
}
nmDataSensitive* nmDataAnalyzeManager::createSensitiveData()
@ -5921,6 +5949,14 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath)
m_pAutomaticFittingData->FromJsonValue(doc["AutomaticFitting"]);
}
// 预测参数和结果随项目保存,旧项目没有此字段时使用默认值。
delete m_pForecastData;
m_pForecastData = NULL;
if(doc.HasMember("Forecast") && doc["Forecast"].IsObject()) {
m_pForecastData = new nmDataForecast;
m_pForecastData->FromJsonValue(doc["Forecast"]);
}
// 解析 "Sensitive" 对象
if(doc.HasMember("Sensitive") && doc["Sensitive"].IsObject()) {
m_pSensitiveData = new nmDataSensitive;
@ -6375,6 +6411,12 @@ bool nmDataAnalyzeManager::WriteProjectData(
doc.AddMember("MixResult", mixResultsJson, allocator);
}
// 复用预测数据已有的 JSON 序列化入口。
if(m_pForecastData) {
rapidjson::Value forecastJson = m_pForecastData->ToJsonValue(allocator);
doc.AddMember("Forecast", forecastJson, allocator);
}
// 序列化 "Sensitive"
if(m_pSensitiveData) {
rapidjson::Value sensitiveJson(rapidjson::kObjectType);
@ -7213,6 +7255,7 @@ void nmDataAnalyzeManager::swapLoadedProjectState(
qSwap(m_reservoirData, oLoadedManager.m_reservoirData);
qSwap(m_pGeoRefData, oLoadedManager.m_pGeoRefData);
qSwap(m_pSensitiveData, oLoadedManager.m_pSensitiveData);
qSwap(m_pForecastData, oLoadedManager.m_pForecastData);
qSwap(m_pebiPvtPara, oLoadedManager.m_pebiPvtPara);
qSwap(m_pMixedResults, oLoadedManager.m_pMixedResults);
qSwap(m_pTimeStep, oLoadedManager.m_pTimeStep);

@ -1,4 +1,5 @@
#include "nmDataForecast.h"
#include <QtCore/qnumeric.h>
PressureRow::PressureRow() {
const QStringList timeUnits = (QStringList()
@ -334,6 +335,7 @@ nmDataForecast::nmDataForecast(const nmDataForecast& other) {
nmDataForecast& nmDataForecast::operator=(const nmDataForecast& other) {
if (this != &other) {
m_result = other.m_result;
currentOption = other.currentOption;
constantPressureData = other.constantPressureData;
decliningPressureData = other.decliningPressureData;
@ -408,6 +410,9 @@ rapidjson::Value nmDataForecast::ToJsonValue(rapidjson::Document::AllocatorType&
rapidjson::Value mr(rapidjson::kObjectType);
mr.AddMember("InitialPressure", multipleRatesData.initialPressure.ToJsonValue(allocator), allocator);
mr.AddMember("PressureUnit", rapidjson::Value(multipleRatesData.pressureUnit.toUtf8().constData(), allocator).Move(), allocator);
mr.AddMember("DurationUnit", rapidjson::Value(multipleRatesData.durationUnit.toUtf8().constData(), allocator).Move(), allocator);
mr.AddMember("RateUnit", rapidjson::Value(multipleRatesData.rateUnit.toUtf8().constData(), allocator).Move(), allocator);
// FlowTable:
rapidjson::Value arr(rapidjson::kArrayType);
@ -424,6 +429,21 @@ rapidjson::Value nmDataForecast::ToJsonValue(rapidjson::Document::AllocatorType&
root.AddMember("MultipleRatesData", mr, allocator);
}
// 只保存原始曲线数值,读取后复用主界面的绘图流程。
if(!m_result.isNull()) {
rapidjson::Value result(rapidjson::kObjectType);
result.AddMember("SnapshotId", rapidjson::Value(m_result->snapshotId.toUtf8().constData(), allocator).Move(), allocator);
result.AddMember("WellInstanceId", rapidjson::Value(m_result->wellInstanceId.toUtf8().constData(), allocator).Move(), allocator);
const char* names[] = { "Times", "Pressures", "Durations", "Rates" };
const QVector<double>* values[] = { &m_result->times, &m_result->pressures, &m_result->durations, &m_result->rates };
for(int column = 0; column < 4; ++column) {
rapidjson::Value array(rapidjson::kArrayType);
for(int i = 0; i < values[column]->size(); ++i)
array.PushBack(values[column]->at(i), allocator);
result.AddMember(rapidjson::Value(names[column], allocator).Move(), array, allocator);
}
root.AddMember("Result", result, allocator);
}
return root;
}
@ -432,7 +452,9 @@ void nmDataForecast::FromJsonValue(const rapidjson::Value& jsonValue)
if (!jsonValue.IsObject())
return;
if (jsonValue.HasMember("ForecastOption"))
// 重复读取或旧项目没有结果时,不沿用上一份预测。
m_result.clear();
if (jsonValue.HasMember("ForecastOption") && jsonValue["ForecastOption"].IsInt())
currentOption = static_cast<ForecastOption>(jsonValue["ForecastOption"].GetInt());
if (currentOption == ConstantPressureOption && jsonValue.HasMember("ConstantPressureData"))
@ -500,6 +522,12 @@ void nmDataForecast::FromJsonValue(const rapidjson::Value& jsonValue)
if (mr.IsObject())
{
if (mr.HasMember("InitialPressure")) multipleRatesData.initialPressure.FromJsonValue(mr["InitialPressure"]);
if (mr.HasMember("PressureUnit") && mr["PressureUnit"].IsString())
multipleRatesData.pressureUnit = QString::fromUtf8(mr["PressureUnit"].GetString());
if (mr.HasMember("DurationUnit") && mr["DurationUnit"].IsString())
multipleRatesData.durationUnit = QString::fromUtf8(mr["DurationUnit"].GetString());
if (mr.HasMember("RateUnit") && mr["RateUnit"].IsString())
multipleRatesData.rateUnit = QString::fromUtf8(mr["RateUnit"].GetString());
multipleRatesData.flowTable.clear();
if (mr.HasMember("FlowTable") && mr["FlowTable"].IsArray())
@ -519,6 +547,32 @@ void nmDataForecast::FromJsonValue(const rapidjson::Value& jsonValue)
}
}
}
// 残缺或无效结果不恢复,输入参数仍可正常使用。
if(jsonValue.HasMember("Result") && jsonValue["Result"].IsObject()) {
const rapidjson::Value& json = jsonValue["Result"];
if(!json.HasMember("SnapshotId") || !json["SnapshotId"].IsString() ||
!json.HasMember("WellInstanceId") || !json["WellInstanceId"].IsString()) return;
QSharedPointer<Result> result(new Result);
result->snapshotId = QString::fromUtf8(json["SnapshotId"].GetString());
result->wellInstanceId = QString::fromUtf8(json["WellInstanceId"].GetString());
if(result->snapshotId.isEmpty() || result->wellInstanceId.isEmpty()) return;
const char* names[] = { "Times", "Pressures", "Durations", "Rates" };
QVector<double>* values[] = { &result->times, &result->pressures, &result->durations, &result->rates };
for(int column = 0; column < 4; ++column) {
if(!json.HasMember(names[column]) || !json[names[column]].IsArray()) return;
const rapidjson::Value& array = json[names[column]];
if(array.Empty()) return;
for(rapidjson::SizeType i = 0; i < array.Size(); ++i) {
if(!array[i].IsNumber() || !qIsFinite(array[i].GetDouble())) return;
const double value = array[i].GetDouble();
if((column == 0 && (value < 0.0 || (i > 0 && value < values[column]->last()))) ||
(column == 2 && value <= 0.0) || (column == 3 && value < 0.0)) return;
values[column]->append(value);
}
}
if(result->times.size() != result->pressures.size() || result->durations.size() != result->rates.size()) return;
m_result = result;
}
}

@ -51,6 +51,7 @@
#include "nmDataAnalyzeManager.h"
#include "nmPebiResultSnapshot.h"
#include "nmDataForecast.h"
#include "nmDataDemo.h"
#include "nmDataLogFile.h"
#include "nmDataMeasure.h"
@ -1953,18 +1954,41 @@ void nmSubWndMain::onAuxiliarySolverTaskDestroyed()
m_pProgressDlg->hide();
}
void nmSubWndMain::onShowForecastResult(const QString& wellName, const QVector<double>& times,
const QVector<double>& pressures, const QVector<double>& durations,
const QVector<double>& rates)
void nmSubWndMain::onForecastDataChanged()
{
// 输入编辑和生成结果都需要参与框架的项目未保存提示。
if(sender() == m_pResultRefreshDataManager.data() && m_pResultRefreshFitting)
m_pResultRefreshFitting->setModified(true);
}
void nmSubWndMain::onShowForecastResult()
{
// 使用面板绑定的分析页面,切换活动分析不会改变预测结果的归属。
iSubWndFitting* fitting = m_pResultRefreshFitting.data();
nmDataAnalyzeManager* manager = m_pResultRefreshDataManager.data();
if(!fitting || !fitting->m_pMdiArea || !manager ||
nmDataAnalyzeManager::findManagerByFitting(fitting) != manager ||
!manager->isPebiResultSnapshotCurrent()) return;
nmDataAnalyzeManager::findManagerByFitting(fitting) != manager) return;
ZxMdiArea* area = fitting->m_pMdiArea;
QMdiSubWindow* window = area->findChild<QMdiSubWindow*>("nmForecastResult");
const QSharedPointer<nmDataForecast::Result> result = manager->getForecastData()
? manager->getForecastData()->getResult() : QSharedPointer<nmDataForecast::Result>();
const QSharedPointer<const nmPebiResultSnapshot> snapshot = manager->getPebiResultSnapshot();
if(result.isNull() || snapshot.isNull() ||
result->snapshotId != snapshot->getSnapshotId() ||
result->wellInstanceId != manager->getCurrentResultWellInstanceId()) {
// 基础结果或结果井切换后清除旧窗口;模型参数编辑不影响上次预测的显示。
if(window) {
foreach(iGuiPlot* plot, window->findChildren<iGuiPlot*>()) clearResultCurveDataTables(plot);
area->removeSubWindow(window);
delete window;
if(area->isVisible()) area->relayoutSubWnds();
}
return;
}
const QVector<double>& times = result->times;
const QVector<double>& pressures = result->pressures;
const QVector<double>& durations = result->durations;
const QVector<double>& rates = result->rates;
const bool created = window == nullptr;
if(created) {
// 结果页面由 MDI 承载,关闭预测输入对话框不销毁结果。
@ -2009,7 +2033,8 @@ void nmSubWndMain::onShowForecastResult(const QString& wellName, const QVector<d
window->setObjectName("nmForecastResult");
window->setAttribute(Qt::WA_DeleteOnClose, false);
}
window->setWindowTitle(tr("Forecast - %1").arg(wellName));
// 与预测输入窗口共用标题译文,不附加井名。
window->setWindowTitle(nmWxForecast::tr("Forecast"));
// 压力直接取求解器结果;阶梯产量及累计量按本次输入制度精确构造。
QVector<double> rateTimes, rateValues, cumulativeTimes, cumulativeValues;
@ -2146,20 +2171,6 @@ void nmSubWndMain::syncForecastTimeAxis(double minimum, double maximum, bool rec
target->m_pPlot->update();
}
void nmSubWndMain::clearForecastResult()
{
// 由数据管理器通知主界面清场,不依赖预测输入对话框是否仍然打开。
iSubWndFitting* fitting = m_pResultRefreshFitting.data();
if(sender() != m_pResultRefreshDataManager.data() || !fitting || !fitting->m_pMdiArea) return;
QMdiSubWindow* window = fitting->m_pMdiArea->findChild<QMdiSubWindow*>("nmForecastResult");
if(window) {
fitting->m_pMdiArea->removeSubWindow(window);
delete window;
// 直接移除窗口不会经过框架的关闭流程,清场后按当前布局重排其余窗口。
if(fitting->m_pMdiArea->isVisible()) fitting->m_pMdiArea->relayoutSubWnds();
}
}
void nmSubWndMain::onShowSensitiveResults()
{
// 使用固定所属分析的窗口与数据,不能把结果画到当前活动的其他分析中。

@ -483,8 +483,6 @@ void nmSubWndUtils::setupNumericalDockWidgets(nmDataAnalyzeManager* pDataManager
pSubWndMain, SLOT(onGenerateButtonClicked()));
connect(pAnalWx, SIGNAL(sigForecastTaskReady(nmWxForecast*,nmCalculationDllPebiSolverTask*)),
pSubWndMain, SLOT(onForecastTaskReady(nmWxForecast*,nmCalculationDllPebiSolverTask*)));
connect(pAnalWx, SIGNAL(sigForecastReady(QString,QVector<double>,QVector<double>,QVector<double>,QVector<double>)),
pSubWndMain, SLOT(onShowForecastResult(QString,QVector<double>,QVector<double>,QVector<double>,QVector<double>)));
connect(pAnalWx, SIGNAL(sigSensitivityTaskReady(nmWxSensitive*,nmCalculationDllPebiSolverTask*)),
pSubWndMain, SLOT(onSensitivityTaskReady(nmWxSensitive*,nmCalculationDllPebiSolverTask*)));
connect(pAnalWx, SIGNAL(sigResultWellChanged(QString)),
@ -492,11 +490,13 @@ void nmSubWndUtils::setupNumericalDockWidgets(nmDataAnalyzeManager* pDataManager
// 手工求解和自动拟合都发布同一种正式快照,统一复用主窗口结果刷新逻辑。
pSubWndMain->bindPebiResultRefreshContext(pDataManager, pSubWndFitting);
// 输入窗口关闭后,主界面仍负责清理因模型变化而失效的预测。
// 预测发布和失效统一由数据管理器通知,加载时也复用同一绘图入口。
connect(pDataManager, SIGNAL(sigForecastDataChanged()),
pSubWndMain, SLOT(onForecastDataChanged()), Qt::UniqueConnection);
connect(pDataManager, SIGNAL(sigForecastResultChanged()),
pSubWndMain, SLOT(onShowForecastResult()), Qt::UniqueConnection);
connect(pDataManager, SIGNAL(sigPebiResultSnapshotChanged()),
pSubWndMain, SLOT(clearForecastResult()), Qt::UniqueConnection);
connect(pDataManager, SIGNAL(sigPebiResultStateChanged()),
pSubWndMain, SLOT(clearForecastResult()), Qt::UniqueConnection);
pSubWndMain, SLOT(onShowForecastResult()), Qt::UniqueConnection);
connect(pDataManager, SIGNAL(sigSensitiveResultsChanged()),
pSubWndMain, SLOT(onShowSensitiveResults()),
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::UniqueConnection));
@ -511,6 +511,7 @@ void nmSubWndUtils::setupNumericalDockWidgets(nmDataAnalyzeManager* pDataManager
// 成果加载和首次创建面板共用入口,有已保存的敏感性结果时恢复同一子窗口。
if(pDataManager->getSensitiveData() && !pDataManager->getSensitiveData()->getResults().isNull())
pSubWndMain->onShowSensitiveResults();
pSubWndMain->onShowForecastResult();
}
bool nmSubWndUtils::fillNmDockWxs(iSubWnd* pSubWnd)

@ -6,6 +6,7 @@
#include <QLabel>
#include <QMessageBox>
#include <QCloseEvent>
#include <QLineEdit>
#include <QtCore/qnumeric.h>
#include "nmCalculationDllPebiSolverTask.h"
#include "nmGUIComponentLineEdit.h"
@ -35,7 +36,8 @@ nmWxForecast::nmWxForecast(QWidget *parent, nmDataAnalyzeManager* manager)
{
setWindowTitle(tr("Forecast"));
setFixedSize(520, 560);
setAttribute(Qt::WA_DeleteOnClose);
// 关闭时只隐藏,所属设计面板复用同一个输入窗口。
setAttribute(Qt::WA_DeleteOnClose, false);
PressureRow tempDefaults;
m_currentDurationUnit = tempDefaults.duration.getUnit();
@ -53,6 +55,19 @@ nmWxForecast::nmWxForecast(QWidget *parent, nmDataAnalyzeManager* manager)
// 根据加载的数据更新界面
updateParametersForOption();
// 初始化完成后再同步编辑,避免加载项目时被标记为修改。
connect(m_rateTableWidget, SIGNAL(cellChanged(int,int)), this, SLOT(saveDataToManager()));
connect(m_rDurationUnitCombo, SIGNAL(currentIndexChanged(QString)), this, SLOT(saveDataToManager()));
connect(m_rRateUnitCombo, SIGNAL(currentIndexChanged(QString)), this, SLOT(saveDataToManager()));
const QList<QLineEdit*> edits = m_initialPressureComponent->findChildren<QLineEdit*>();
foreach(QLineEdit* edit, edits)
connect(edit, SIGNAL(textEdited(QString)), this, SLOT(saveDataToManager()));
const QList<QComboBox*> units = m_initialPressureComponent->findChildren<QComboBox*>();
foreach(QComboBox* unit, units) {
const int index = unit->findData(m_forecastData.getMultipleRatesData().pressureUnit);
if(index >= 0) unit->setCurrentIndex(index);
connect(unit, SIGNAL(currentIndexChanged(QString)), this, SLOT(saveDataToManager()));
}
if(m_manager) {
connect(m_manager, SIGNAL(sigPebiResultSnapshotChanged()), this, SLOT(onModelChanged()));
connect(m_manager, SIGNAL(sigPebiResultStateChanged()), this, SLOT(onModelChanged()));
@ -72,8 +87,12 @@ nmWxForecast::~nmWxForecast()
void nmWxForecast::loadDataFromManager()
{
// 当前先开放分段产量,初始压力默认取已生成模型,输入只保留在内存中。
// 优先恢复已保存的输入,首次打开时初始压力取已生成模型。
if(m_manager) m_forecastData = m_manager->getForecastDataCopy();
m_forecastData.setResult(QSharedPointer<nmDataForecast::Result>());
const MultipleRates& data = m_forecastData.getMultipleRatesData();
if(m_rDurationUnitCombo->findText(data.durationUnit) >= 0) m_currentDurationUnit = data.durationUnit;
if(m_rRateUnitCombo->findText(data.rateUnit) >= 0) m_currentRateUnit = data.rateUnit;
nmDataAttribute& pressure = m_forecastData.getMultipleRatesData().getInitialPressure();
if(m_manager && m_forecastData.getForecastOption() != nmDataForecast::MultipleRatesOption) {
const QSharedPointer<const nmPebiResultSnapshot> solved = m_manager->getPebiResultSnapshot();
@ -95,6 +114,10 @@ void nmWxForecast::saveDataToManager()
// 先从界面更新数据
updateDataFromUI();
const QList<QComboBox*> units = m_initialPressureComponent->findChildren<QComboBox*>();
if(!units.isEmpty())
m_forecastData.getMultipleRatesData().pressureUnit = units.first()->itemData(units.first()->currentIndex()).toString();
// 保存到数据中心
nmDataAnalyzeManager* pManager = m_manager.data();
@ -842,7 +865,7 @@ void nmWxForecast::updateUIFromData()
FlowRow tempDefaults; // 用于获取单位列表和默认值
int dataSize = tableData.size();
int displayRowCount = (dataSize == 0) ? 1 : dataSize;
int displayRowCount = (dataSize == 0 && (!m_manager || !m_manager->getForecastData())) ? 1 : dataSize;
// 设置行数: 1(单位) + N(数据) + 1(空白)
m_rateTableWidget->setRowCount(1 + displayRowCount + 1);
@ -866,11 +889,14 @@ void nmWxForecast::updateUIFromData()
QString baseRateUnit = rowData.q0.getUnit();
double displayRateValue = tempDefaults.q0.convertValueByUnitType(baseRateValue, baseRateUnit, m_currentRateUnit);
QTableWidgetItem* durationItem = new QTableWidgetItem(QString::number(displayDurValue, 'g', 15));
// 同单位直接恢复原输入文本,半空行不能补成默认数值。
QTableWidgetItem* durationItem = new QTableWidgetItem(baseDurUnit == m_currentDurationUnit && rowData.duration.getValue().type() == QVariant::String
? rowData.duration.getValue().toString() : QString::number(displayDurValue, 'g', 15));
durationItem->setTextAlignment(Qt::AlignCenter);
m_rateTableWidget->setItem(tableRow, 0, durationItem);
QTableWidgetItem* rateItem = new QTableWidgetItem(QString::number(displayRateValue, 'g', 15));
QTableWidgetItem* rateItem = new QTableWidgetItem(baseRateUnit == m_currentRateUnit && rowData.q0.getValue().type() == QVariant::String
? rowData.q0.getValue().toString() : QString::number(displayRateValue, 'g', 15));
rateItem->setTextAlignment(Qt::AlignCenter);
m_rateTableWidget->setItem(tableRow, 1, rateItem);
}
@ -959,10 +985,9 @@ void nmWxForecast::updateDataFromUI()
QVector<FlowRow>& tableData = m_forecastData.getMultipleRatesData().getFlowTable();
tableData.clear();
FlowRow tempDefaults;
// 获取数据模型的基础单位
QString baseDurUnit = tempDefaults.duration.getUnit();
QString baseRateUnit = tempDefaults.q0.getUnit();
// 保留输入单位和未填完的单元格,只有生成时才校验并换算给求解器。
m_forecastData.getMultipleRatesData().durationUnit = m_currentDurationUnit;
m_forecastData.getMultipleRatesData().rateUnit = m_currentRateUnit;
// 循环到 rowCount() - 1 来跳过最后那个空白行
for(int i = 1; i < m_rateTableWidget->rowCount() - 1; ++i) {
@ -977,19 +1002,11 @@ void nmWxForecast::updateDataFromUI()
continue;
}
FlowRow newRow; // newRow 将使用基础单位
if(!durationEmpty) {
double displayVal = durationItem->text().toDouble();
// 转换值:从 "当前显示的单位" 转换回 "数据的基础单位"
double storageVal = tempDefaults.duration.convertValueByUnitType(displayVal, m_currentDurationUnit, baseDurUnit);
newRow.duration.setValue(storageVal);
}
if(!rateEmpty) {
double displayVal = rateItem->text().toDouble();
double storageVal = tempDefaults.q0.convertValueByUnitType(displayVal, m_currentRateUnit, baseRateUnit);
newRow.q0.setValue(storageVal);
}
FlowRow newRow;
newRow.duration.setUnit(m_currentDurationUnit);
newRow.q0.setUnit(m_currentRateUnit);
newRow.duration.setValue(durationItem ? durationItem->text().trimmed() : QString());
newRow.q0.setValue(rateItem ? rateItem->text().trimmed() : QString());
tableData.append(newRow);
}
}
@ -1033,8 +1050,7 @@ void nmWxForecast::onGenerateClicked()
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);
m_task = new nmCalculationDllPebiSolverTask(QString(), m_manager, well->m_sWellName, this);
if(!m_task->configureRateForecast(pi, durations, rates)) {
error = m_task->getFailureReason();
delete m_task;
@ -1046,6 +1062,7 @@ void nmWxForecast::onGenerateClicked()
m_durations = durations;
m_rates = rates;
m_sourceSnapshotId = solved->getSnapshotId();
m_sourceWellInstanceId = well->m_sWellInstanceId;
// 与敏感性分析一样,主窗口接管成功后才启动,忙碌时释放未启动任务。
emit sigForecastTaskReady(this, m_task);
if(!m_calculationRunning) {
@ -1132,7 +1149,8 @@ void nmWxForecast::onForecastFinished()
const bool cancelled = m_task->wasCancelled();
const bool current = m_manager && m_manager->isPebiResultSnapshotCurrent() &&
!m_task->hasInputChanged(m_manager) &&
m_manager->getPebiResultSnapshot()->getSnapshotId() == m_sourceSnapshotId;
m_manager->getPebiResultSnapshot()->getSnapshotId() == m_sourceSnapshotId &&
m_manager->getCurrentResultWellInstanceId() == m_sourceWellInstanceId;
const bool success = m_task->wasSuccessful();
const QString error = m_task->getFailureReason();
const QVector<QVector<double> > pressure = m_task->getRateForecastPressure();
@ -1141,12 +1159,27 @@ void nmWxForecast::onForecastFinished()
m_calculationRunning = false;
m_parametersGroup->setEnabled(true);
m_generateButton->setEnabled(true);
// 取消或失败后恢复同一份输入,方便直接调整后重算。
if(cancelled || !current || !success) {
show();
raise();
activateWindow();
}
if(cancelled) return;
if(!current || !success) {
QMessageBox::warning(this, tr("Forecast"), current ? error : tr("The model changed. Generate the forecast again."));
return;
}
emit sigForecastReady(m_wellName, pressure[0], pressure[1], m_durations, m_rates);
QSharedPointer<nmDataForecast::Result> result(new nmDataForecast::Result);
result->snapshotId = m_sourceSnapshotId;
result->wellInstanceId = m_sourceWellInstanceId;
result->times = pressure[0];
result->pressures = pressure[1];
result->durations = m_durations;
result->rates = m_rates;
m_forecastData.setResult(result);
m_manager->publishForecastResult(m_forecastData);
m_forecastData.setResult(QSharedPointer<nmDataForecast::Result>());
}
void nmWxForecast::onModelChanged()

@ -1448,12 +1448,15 @@ void nmWxNumericalDesign::onForecastClicked()
// 预测固定绑定当前分析窗口,并要求先生成基础模型。
updateSensitiveButtonEnabled();
if(!m_pForecastBtn->isEnabled()) return;
nmWxForecast* dlg = new nmWxForecast(this, m_pDataManager);
connect(dlg, SIGNAL(sigForecastTaskReady(nmWxForecast*,nmCalculationDllPebiSolverTask*)),
// 关闭和生成后均保留输入窗口,只在首次使用时创建并连接任务信号。
if(m_pForecastDialog.isNull()) {
m_pForecastDialog = new nmWxForecast(this, m_pDataManager);
connect(m_pForecastDialog, SIGNAL(sigForecastTaskReady(nmWxForecast*,nmCalculationDllPebiSolverTask*)),
this, SIGNAL(sigForecastTaskReady(nmWxForecast*,nmCalculationDllPebiSolverTask*)));
connect(dlg, SIGNAL(sigForecastReady(QString,QVector<double>,QVector<double>,QVector<double>,QVector<double>)),
this, SIGNAL(sigForecastReady(QString,QVector<double>,QVector<double>,QVector<double>,QVector<double>)));
dlg->show();
}
m_pForecastDialog->show();
m_pForecastDialog->raise();
m_pForecastDialog->activateWindow();
}
void nmWxNumericalDesign::updateSensitiveButtonEnabled()

Loading…
Cancel
Save