|
|
|
|
@ -4,9 +4,17 @@
|
|
|
|
|
#include <QSpacerItem>
|
|
|
|
|
#include <QSizePolicy>
|
|
|
|
|
#include <QLabel>
|
|
|
|
|
#include <QMessageBox>
|
|
|
|
|
#include <QProgressDialog>
|
|
|
|
|
#include <QtCore/qnumeric.h>
|
|
|
|
|
#include "nmCalculationDllPebiSolverTask.h"
|
|
|
|
|
#include "nmPebiResultSnapshot.h"
|
|
|
|
|
|
|
|
|
|
nmWxForecast::nmWxForecast(QWidget *parent)
|
|
|
|
|
nmWxForecast::nmWxForecast(QWidget *parent, nmDataAnalyzeManager* manager)
|
|
|
|
|
: iDlgBase(parent)
|
|
|
|
|
, m_manager(manager ? manager : nmDataAnalyzeManager::getCurrentInstance())
|
|
|
|
|
, m_task(NULL)
|
|
|
|
|
, m_progress(NULL)
|
|
|
|
|
, m_initialPressureComponent(nullptr)
|
|
|
|
|
, m_abandonmentRateComponent(nullptr)
|
|
|
|
|
, m_maximumRateComponent(nullptr)
|
|
|
|
|
@ -26,6 +34,7 @@ nmWxForecast::nmWxForecast(QWidget *parent)
|
|
|
|
|
{
|
|
|
|
|
setWindowTitle("Forecast");
|
|
|
|
|
setFixedSize(520, 560);
|
|
|
|
|
setAttribute(Qt::WA_DeleteOnClose);
|
|
|
|
|
|
|
|
|
|
PressureRow tempDefaults;
|
|
|
|
|
m_currentDurationUnit = tempDefaults.duration.getUnit();
|
|
|
|
|
@ -43,44 +52,41 @@ nmWxForecast::nmWxForecast(QWidget *parent)
|
|
|
|
|
|
|
|
|
|
// 根据加载的数据更新界面
|
|
|
|
|
updateParametersForOption();
|
|
|
|
|
if(m_manager) {
|
|
|
|
|
connect(m_manager, SIGNAL(sigPebiResultSnapshotChanged()), this, SLOT(onModelChanged()));
|
|
|
|
|
connect(m_manager, SIGNAL(sigPebiResultStateChanged()), this, SLOT(onModelChanged()));
|
|
|
|
|
connect(m_manager, SIGNAL(destroyed()), this, SLOT(close()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nmWxForecast::~nmWxForecast()
|
|
|
|
|
{
|
|
|
|
|
// DLL 内部不能强行中断,析构前等待任务退出,避免悬空回调。
|
|
|
|
|
if(m_task) {
|
|
|
|
|
m_task->requestCancel();
|
|
|
|
|
m_task->wait();
|
|
|
|
|
delete m_task;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmWxForecast::loadDataFromManager()
|
|
|
|
|
{
|
|
|
|
|
// 从数据中心获取预测数据的副本
|
|
|
|
|
nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
|
|
|
|
|
|
|
if(pManager) {
|
|
|
|
|
m_forecastData = pManager->getForecastDataCopy();
|
|
|
|
|
|
|
|
|
|
// 根据数据设置单选按钮
|
|
|
|
|
nmDataForecast::ForecastOption option = m_forecastData.getForecastOption();
|
|
|
|
|
|
|
|
|
|
switch(option) {
|
|
|
|
|
case nmDataForecast::ConstantPressureOption:
|
|
|
|
|
m_constantPressureRadio->setChecked(true);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case nmDataForecast::DecliningPressureOption:
|
|
|
|
|
m_decliningPressureRadio->setChecked(true);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case nmDataForecast::MultiplePressuresOption:
|
|
|
|
|
m_multiplePressuresRadio->setChecked(true);
|
|
|
|
|
break;
|
|
|
|
|
case nmDataForecast::MultipleRatesOption:
|
|
|
|
|
// 当前先开放分段产量,初始压力默认取已生成模型,输入只保留在内存中。
|
|
|
|
|
if(m_manager) m_forecastData = m_manager->getForecastDataCopy();
|
|
|
|
|
nmDataAttribute& pressure = m_forecastData.getMultipleRatesData().getInitialPressure();
|
|
|
|
|
if(m_manager && m_forecastData.getForecastOption() != nmDataForecast::MultipleRatesOption) {
|
|
|
|
|
const QSharedPointer<const nmPebiResultSnapshot> solved = m_manager->getPebiResultSnapshot();
|
|
|
|
|
if(!solved.isNull()) {
|
|
|
|
|
pressure.setValue(pressure.convertValueByUnitType(
|
|
|
|
|
solved->getReservoirParameters().m_dInitialPressure, "MPa", pressure.getUnit()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 本页使用绝对压力,避免通用单位控件把表压当作绝压换算。
|
|
|
|
|
pressure.setListUnitSelections(QStringList() << "MPa" << "psia" << "kPa" << "bara");
|
|
|
|
|
m_forecastData.setForecastOption(nmDataForecast::MultipleRatesOption);
|
|
|
|
|
m_multipleRatesRadio->blockSignals(true);
|
|
|
|
|
m_multipleRatesRadio->setChecked(true);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 如果数据中心不存在,使用默认数据
|
|
|
|
|
m_constantPressureRadio->setChecked(true);
|
|
|
|
|
m_forecastData.setForecastOption(nmDataForecast::ConstantPressureOption);
|
|
|
|
|
}
|
|
|
|
|
m_multipleRatesRadio->blockSignals(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmWxForecast::saveDataToManager()
|
|
|
|
|
@ -89,7 +95,7 @@ void nmWxForecast::saveDataToManager()
|
|
|
|
|
updateDataFromUI();
|
|
|
|
|
|
|
|
|
|
// 保存到数据中心
|
|
|
|
|
nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
|
|
nmDataAnalyzeManager* pManager = m_manager.data();
|
|
|
|
|
|
|
|
|
|
if(pManager) {
|
|
|
|
|
pManager->updateForecastData(m_forecastData);
|
|
|
|
|
@ -108,6 +114,10 @@ void nmWxForecast::setupUI()
|
|
|
|
|
m_decliningPressureRadio = new QRadioButton("Declining pressure (ramp)", this);
|
|
|
|
|
m_multiplePressuresRadio = new QRadioButton("Multiple pressures", this);
|
|
|
|
|
m_multipleRatesRadio = new QRadioButton("Multiple rates", this);
|
|
|
|
|
// 求解器目前仅接通产量控制;压力控制保留入口但不允许生成。
|
|
|
|
|
m_constantPressureRadio->setEnabled(false);
|
|
|
|
|
m_decliningPressureRadio->setEnabled(false);
|
|
|
|
|
m_multiplePressuresRadio->setEnabled(false);
|
|
|
|
|
|
|
|
|
|
m_forecastButtonGroup = new QButtonGroup(this);
|
|
|
|
|
m_forecastButtonGroup->addButton(m_constantPressureRadio, 0);
|
|
|
|
|
@ -390,7 +400,7 @@ QWidget* nmWxForecast::buildExtMultiRatePage()
|
|
|
|
|
// 创建表格
|
|
|
|
|
m_rateTableWidget = new QTableWidget(0, 2);
|
|
|
|
|
// 根据截图设置表头
|
|
|
|
|
m_rateTableWidget->setHorizontalHeaderLabels(QStringList() << "Duration" << "Qo");
|
|
|
|
|
m_rateTableWidget->setHorizontalHeaderLabels(QStringList() << "Duration" << "Rate");
|
|
|
|
|
m_rateTableWidget->verticalHeader()->setVisible(false);
|
|
|
|
|
m_rateTableWidget->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
|
|
|
|
|
|
|
|
|
|
@ -799,7 +809,7 @@ void nmWxForecast::updateUIFromData()
|
|
|
|
|
QString basePresUnit = rowData.pressure.getUnit();
|
|
|
|
|
double displayPresValue = tempDefaults.pressure.convertValueByUnitType(basePresValue, basePresUnit, m_currentPressureUnit);
|
|
|
|
|
|
|
|
|
|
QTableWidgetItem* durationItem = new QTableWidgetItem(QString::number(displayDurValue));
|
|
|
|
|
QTableWidgetItem* durationItem = new QTableWidgetItem(QString::number(displayDurValue, 'g', 15));
|
|
|
|
|
durationItem->setTextAlignment(Qt::AlignCenter);
|
|
|
|
|
m_pressureTableWidget->setItem(tableRow, 0, durationItem);
|
|
|
|
|
|
|
|
|
|
@ -855,11 +865,11 @@ void nmWxForecast::updateUIFromData()
|
|
|
|
|
QString baseRateUnit = rowData.q0.getUnit();
|
|
|
|
|
double displayRateValue = tempDefaults.q0.convertValueByUnitType(baseRateValue, baseRateUnit, m_currentRateUnit);
|
|
|
|
|
|
|
|
|
|
QTableWidgetItem* durationItem = new QTableWidgetItem(QString::number(displayDurValue));
|
|
|
|
|
QTableWidgetItem* durationItem = new QTableWidgetItem(QString::number(displayDurValue, 'g', 15));
|
|
|
|
|
durationItem->setTextAlignment(Qt::AlignCenter);
|
|
|
|
|
m_rateTableWidget->setItem(tableRow, 0, durationItem);
|
|
|
|
|
|
|
|
|
|
QTableWidgetItem* rateItem = new QTableWidgetItem(QString::number(displayRateValue));
|
|
|
|
|
QTableWidgetItem* rateItem = new QTableWidgetItem(QString::number(displayRateValue, 'g', 15));
|
|
|
|
|
rateItem->setTextAlignment(Qt::AlignCenter);
|
|
|
|
|
m_rateTableWidget->setItem(tableRow, 1, rateItem);
|
|
|
|
|
}
|
|
|
|
|
@ -992,22 +1002,138 @@ void nmWxForecast::onLoadDataClicked()
|
|
|
|
|
|
|
|
|
|
void nmWxForecast::onGenerateClicked()
|
|
|
|
|
{
|
|
|
|
|
// 确保最新的界面数据写回到模型
|
|
|
|
|
updateDataFromUI();
|
|
|
|
|
|
|
|
|
|
// 保存数据到数据中心
|
|
|
|
|
// 先校验原始输入,避免空单元格被旧的数据同步逻辑补成默认值。
|
|
|
|
|
if(m_task) return;
|
|
|
|
|
if(!m_manager || !m_manager->isPebiResultSnapshotCurrent()) {
|
|
|
|
|
QMessageBox::warning(this, tr("Forecast"), tr("Generate the current model before forecasting."));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const QSharedPointer<const nmPebiResultSnapshot> solved = m_manager->getPebiResultSnapshot();
|
|
|
|
|
if(solved->getWellCount() != 1 || 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."));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
QString error;
|
|
|
|
|
QVector<double> durations, rates;
|
|
|
|
|
if(!readRateSchedule(durations, rates, error)) {
|
|
|
|
|
QMessageBox::warning(this, tr("Forecast"), error);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
nmDataAttribute& pressure = m_forecastData.getMultipleRatesData().getInitialPressure();
|
|
|
|
|
if(!m_initialPressureComponent->updateValueToOrigin()) {
|
|
|
|
|
QMessageBox::warning(this, tr("Forecast"), tr("Enter a positive initial pressure."));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const double pi = pressure.convertValueByUnitType(pressure.getValue().toDouble(), pressure.getUnit(), "MPa");
|
|
|
|
|
m_wellName = solved->getWellAt(0)->m_sWellName;
|
|
|
|
|
m_task = new nmCalculationDllPebiSolverTask(QString(), m_manager, m_wellName, this);
|
|
|
|
|
if(!m_task->configureRateForecast(pi, durations, rates)) {
|
|
|
|
|
error = m_task->getFailureReason();
|
|
|
|
|
delete m_task;
|
|
|
|
|
m_task = NULL;
|
|
|
|
|
QMessageBox::warning(this, tr("Forecast"), error);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
saveDataToManager();
|
|
|
|
|
|
|
|
|
|
// TODO: 使用数据中心的数据进行预测计算
|
|
|
|
|
|
|
|
|
|
accept();
|
|
|
|
|
m_durations = durations;
|
|
|
|
|
m_rates = rates;
|
|
|
|
|
m_sourceSnapshotId = solved->getSnapshotId();
|
|
|
|
|
m_parametersGroup->setEnabled(false);
|
|
|
|
|
m_generateButton->setEnabled(false);
|
|
|
|
|
m_progress = new QProgressDialog(tr("Calculating forecast..."), tr("Cancel"), 0, 0, this);
|
|
|
|
|
m_progress->setWindowTitle(tr("Forecast"));
|
|
|
|
|
m_progress->setWindowModality(Qt::ApplicationModal);
|
|
|
|
|
m_progress->setMinimumDuration(0);
|
|
|
|
|
m_progress->setAutoClose(false);
|
|
|
|
|
connect(m_progress, SIGNAL(canceled()), this, SLOT(onCancelClicked()));
|
|
|
|
|
connect(m_task, SIGNAL(finished()), this, SLOT(onForecastFinished()));
|
|
|
|
|
m_progress->show();
|
|
|
|
|
m_task->start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmWxForecast::onCancelClicked()
|
|
|
|
|
{
|
|
|
|
|
// 关闭按钮和进度框共用协作式停止,不在 DLL 内部强制终止线程。
|
|
|
|
|
reject();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmWxForecast::reject()
|
|
|
|
|
{
|
|
|
|
|
// 正在求解时保留窗口,等线程结束后恢复输入,避免关闭操作阻塞界面。
|
|
|
|
|
if(m_task) {
|
|
|
|
|
m_task->requestCancel();
|
|
|
|
|
m_progress->setLabelText(tr("Stopping after the current solver call finishes..."));
|
|
|
|
|
m_progress->setCancelButton(NULL);
|
|
|
|
|
m_progress->show();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
iDlgBase::reject();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool nmWxForecast::readRateSchedule(QVector<double>& durations, QVector<double>& rates, QString& error) const
|
|
|
|
|
{
|
|
|
|
|
// 求解器统一使用小时及标准体积产量,完整空行跳过,半空行必须补全。
|
|
|
|
|
durations.clear();
|
|
|
|
|
rates.clear();
|
|
|
|
|
FlowRow units;
|
|
|
|
|
for(int row = 1; row < m_rateTableWidget->rowCount(); ++row) {
|
|
|
|
|
const QTableWidgetItem* durationItem = m_rateTableWidget->item(row, 0);
|
|
|
|
|
const QTableWidgetItem* rateItem = m_rateTableWidget->item(row, 1);
|
|
|
|
|
const QString durationText = durationItem ? durationItem->text().trimmed() : QString();
|
|
|
|
|
const QString rateText = rateItem ? rateItem->text().trimmed() : QString();
|
|
|
|
|
if(durationText.isEmpty() && rateText.isEmpty()) continue;
|
|
|
|
|
bool durationOk = false, rateOk = false;
|
|
|
|
|
double duration = durationText.toDouble(&durationOk);
|
|
|
|
|
double rate = rateText.toDouble(&rateOk);
|
|
|
|
|
duration = units.duration.convertValueByUnitType(duration, m_currentDurationUnit, "hr");
|
|
|
|
|
rate = units.q0.convertValueByUnitType(rate, m_currentRateUnit, "m^3/D");
|
|
|
|
|
if(!durationOk || !rateOk || !qIsFinite(duration) || duration <= 0.0 ||
|
|
|
|
|
!qIsFinite(rate) || rate < 0.0) {
|
|
|
|
|
error = tr("Row %1: enter a positive duration and a non-negative production rate (0 for shut-in).").arg(row);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
durations.append(duration);
|
|
|
|
|
rates.append(rate);
|
|
|
|
|
}
|
|
|
|
|
if(durations.isEmpty()) {
|
|
|
|
|
error = tr("Enter at least one duration and production rate.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmWxForecast::onForecastFinished()
|
|
|
|
|
{
|
|
|
|
|
// finished 后才读取及销毁任务;输入变化或模型重新生成时丢弃本次预测。
|
|
|
|
|
m_task->wait();
|
|
|
|
|
const bool cancelled = m_task->wasCancelled() || m_progress->wasCanceled();
|
|
|
|
|
const bool current = m_manager && m_manager->isPebiResultSnapshotCurrent() &&
|
|
|
|
|
!m_task->hasInputChanged(m_manager) &&
|
|
|
|
|
m_manager->getPebiResultSnapshot()->getSnapshotId() == m_sourceSnapshotId;
|
|
|
|
|
const bool success = m_task->wasSuccessful();
|
|
|
|
|
const QString error = m_task->getFailureReason();
|
|
|
|
|
const QVector<QVector<double> > pressure = m_task->getRateForecastPressure();
|
|
|
|
|
delete m_task;
|
|
|
|
|
m_task = NULL;
|
|
|
|
|
delete m_progress;
|
|
|
|
|
m_progress = NULL;
|
|
|
|
|
m_parametersGroup->setEnabled(true);
|
|
|
|
|
m_generateButton->setEnabled(true);
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmWxForecast::onModelChanged()
|
|
|
|
|
{
|
|
|
|
|
// 模型或输入改变时停止本次计算;已显示的结果由所属主界面清理。
|
|
|
|
|
if(m_task) m_task->requestCancel();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmWxForecast::onPressureCellChanged(int row, int column)
|
|
|
|
|
{
|
|
|
|
|
Q_UNUSED(column);
|
|
|
|
|
@ -1131,10 +1257,12 @@ void nmWxForecast::onSharedDurationUnitChanged(const QString& unit)
|
|
|
|
|
QTableWidgetItem* dataItem = m_rateTableWidget->item(i, 0); // 第0列 (Duration)
|
|
|
|
|
|
|
|
|
|
if(dataItem && !dataItem->text().isEmpty()) {
|
|
|
|
|
double oldDisplayValue = dataItem->text().toDouble();
|
|
|
|
|
bool ok = false;
|
|
|
|
|
double oldDisplayValue = dataItem->text().toDouble(&ok);
|
|
|
|
|
if(!ok || !qIsFinite(oldDisplayValue)) continue;
|
|
|
|
|
// 转换值
|
|
|
|
|
double newDisplayValue = tempDefaults.duration.convertValueByUnitType(oldDisplayValue, oldUnit, unit);
|
|
|
|
|
dataItem->setText(QString::number(newDisplayValue));
|
|
|
|
|
dataItem->setText(QString::number(newDisplayValue, 'g', 15));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
m_rateTableWidget->blockSignals(false);
|
|
|
|
|
@ -1170,10 +1298,12 @@ void nmWxForecast::onRateUnitChanged(const QString& unit)
|
|
|
|
|
QTableWidgetItem* dataItem = m_rateTableWidget->item(i, 1); // 第1列
|
|
|
|
|
|
|
|
|
|
if(dataItem && !dataItem->text().isEmpty()) {
|
|
|
|
|
double oldDisplayValue = dataItem->text().toDouble();
|
|
|
|
|
bool ok = false;
|
|
|
|
|
double oldDisplayValue = dataItem->text().toDouble(&ok);
|
|
|
|
|
if(!ok || !qIsFinite(oldDisplayValue)) continue;
|
|
|
|
|
// 使用 q0 (FlowRate) 进行单位转换
|
|
|
|
|
double newDisplayValue = tempDefaults.q0.convertValueByUnitType(oldDisplayValue, oldUnit, unit);
|
|
|
|
|
dataItem->setText(QString::number(newDisplayValue));
|
|
|
|
|
dataItem->setText(QString::number(newDisplayValue, 'g', 15));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|