|
|
|
|
@ -4,6 +4,9 @@
|
|
|
|
|
#include "nmWxParameterProperty.h"
|
|
|
|
|
#include "nmDataAnalyzeManager.h"
|
|
|
|
|
#include "iSubWndFitting.h"
|
|
|
|
|
#include "iSysParaHelper.h"
|
|
|
|
|
#include "iParameter.h"
|
|
|
|
|
#include "mModuleDefines.h"
|
|
|
|
|
#include "mGui/mGuiAnal/iAnalRun.h"
|
|
|
|
|
#include "iGuiPlot.h"
|
|
|
|
|
#include "ZxObjCurve.h"
|
|
|
|
|
@ -32,6 +35,30 @@ bool nmAutoFitUiIsFinite(double value)
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 从系统参数表读取物理边界,读取失败时保留调用方提供的兜底边界。
|
|
|
|
|
bool nmAutoFitReadPhysicalRange(const char* parameterName,
|
|
|
|
|
double fallbackMin, double fallbackMax, double& minValue, double& maxValue)
|
|
|
|
|
{
|
|
|
|
|
minValue = fallbackMin;
|
|
|
|
|
maxValue = fallbackMax;
|
|
|
|
|
|
|
|
|
|
iSysParaHelper* paraHelper = _paraHelper;
|
|
|
|
|
if(!paraHelper) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
iParameter* parameter = paraHelper->getPara(QString::fromLatin1(parameterName), s_Nm_Serie);
|
|
|
|
|
if(!parameter || !nmAutoFitUiIsFinite(parameter->m_dMin)
|
|
|
|
|
|| !nmAutoFitUiIsFinite(parameter->m_dMax)
|
|
|
|
|
|| parameter->m_dMin >= parameter->m_dMax) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
minValue = parameter->m_dMin;
|
|
|
|
|
maxValue = parameter->m_dMax;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 主界面气井双对数中的源曲线和导数曲线已经采用拟压力量纲.
|
|
|
|
|
// 自动拟合直接读取这两条显示曲线, 避免井对象中的原始压力历史数据与结果曲线量纲不一致.
|
|
|
|
|
// 两条曲线可能因无效点过滤而长度不同, 因此按时间坐标匹配共同有效点.
|
|
|
|
|
@ -199,14 +226,338 @@ void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOL
|
|
|
|
|
renumberVisibleParameterRows(table);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取参数的系统物理边界,并为 Swi 叠加当前储层饱和度约束。
|
|
|
|
|
bool nmWxAutomaticFitting::getPhysicalParameterRange(int parameterIndex,
|
|
|
|
|
double& minValue, double& maxValue)
|
|
|
|
|
{
|
|
|
|
|
static const char* parameterNames[] = {
|
|
|
|
|
"Result_K", "Result_W_Skin", "Result_W_C", "Result_phi",
|
|
|
|
|
"Result_h", "Result_Cti", "Result_Cf", "Result_Swi"
|
|
|
|
|
};
|
|
|
|
|
// KAPPA 的边界使用 md、ft、bbl/psi;自动拟合界面使用 Darcy、m、m^3/MPa,
|
|
|
|
|
// 这里统一换算到界面和 PSO 实际使用的单位:K 除以 1000,h 由 ft 换成 m,
|
|
|
|
|
// 井筒储集系数的 4.33667154546306e34 bbl/psi 对应约 1e36 m^3/MPa。
|
|
|
|
|
// Ct/Cf/Swi 沿用模型参数表边界。
|
|
|
|
|
static const double physicalMin[] = {
|
|
|
|
|
1.01325027383089e-18, -5.0, 0.0, 1.0e-4, 1.0e-5, 1.0e-30, 1.0e-30, 0.0
|
|
|
|
|
};
|
|
|
|
|
static const double physicalMax[] = {
|
|
|
|
|
1.01325027383089e42, 5000.0, 1.0e36, 0.9999, 1.0e9, 10.0, 10.0, 1.0
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if(parameterIndex < 0 || parameterIndex >= 8) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
minValue = physicalMin[parameterIndex];
|
|
|
|
|
maxValue = physicalMax[parameterIndex];
|
|
|
|
|
bool rangeRead = true;
|
|
|
|
|
if(parameterIndex >= 5) {
|
|
|
|
|
rangeRead = nmAutoFitReadPhysicalRange(parameterNames[parameterIndex],
|
|
|
|
|
physicalMin[parameterIndex], physicalMax[parameterIndex], minValue, maxValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(parameterIndex == 7) {
|
|
|
|
|
double soi = reservoirData.getSoi().getValue().toDouble();
|
|
|
|
|
double sgi = reservoirData.getSgi().getValue().toDouble();
|
|
|
|
|
if(nmAutoFitUiIsFinite(soi) && nmAutoFitUiIsFinite(sgi)) {
|
|
|
|
|
if(soi < 0.0 || sgi < 0.0 || soi + sgi > 1.0) {
|
|
|
|
|
// Soi+Sgi 超过 1 时没有可行的 Swi,固定到物理下限,避免继续搜索非法区间。
|
|
|
|
|
minValue = 0.0;
|
|
|
|
|
maxValue = 0.0;
|
|
|
|
|
} else {
|
|
|
|
|
maxValue = qMin(maxValue, 1.0 - soi - sgi);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return rangeRead;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 将一组上下界同步到表格和自动拟合数据,保证 PSO 读取到同一份配置。
|
|
|
|
|
void nmWxAutomaticFitting::setParameterRange(int parameterIndex,
|
|
|
|
|
double minValue, double maxValue)
|
|
|
|
|
{
|
|
|
|
|
if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= m_parameterTable->rowCount()
|
|
|
|
|
|| !nmAutoFitUiIsFinite(minValue) || !nmAutoFitUiIsFinite(maxValue)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(maxValue < minValue) {
|
|
|
|
|
qSwap(minValue, maxValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool wasUpdatingRanges = m_updatingParameterRanges;
|
|
|
|
|
m_updatingParameterRanges = true;
|
|
|
|
|
|
|
|
|
|
if(m_parameterTable->item(parameterIndex, 2)) {
|
|
|
|
|
m_parameterTable->item(parameterIndex, 2)->setText(QString::number(minValue, 'g', 10));
|
|
|
|
|
}
|
|
|
|
|
if(m_parameterTable->item(parameterIndex, 4)) {
|
|
|
|
|
m_parameterTable->item(parameterIndex, 4)->setText(QString::number(maxValue, 'g', 10));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch(parameterIndex) {
|
|
|
|
|
case 0:
|
|
|
|
|
automaticFittingData.getPermeabilityMin().setValue(minValue);
|
|
|
|
|
automaticFittingData.getPermeabilityMax().setValue(maxValue);
|
|
|
|
|
break;
|
|
|
|
|
case 1:
|
|
|
|
|
automaticFittingData.getSkinMin().setValue(minValue);
|
|
|
|
|
automaticFittingData.getSkinMax().setValue(maxValue);
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
automaticFittingData.getWellboreStorageMin().setValue(minValue);
|
|
|
|
|
automaticFittingData.getWellboreStorageMax().setValue(maxValue);
|
|
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
automaticFittingData.getPorosityMin().setValue(minValue);
|
|
|
|
|
automaticFittingData.getPorosityMax().setValue(maxValue);
|
|
|
|
|
break;
|
|
|
|
|
case 4:
|
|
|
|
|
automaticFittingData.getThicknessMin().setValue(minValue);
|
|
|
|
|
automaticFittingData.getThicknessMax().setValue(maxValue);
|
|
|
|
|
break;
|
|
|
|
|
case 5:
|
|
|
|
|
automaticFittingData.getCtMin().setValue(minValue);
|
|
|
|
|
automaticFittingData.getCtMax().setValue(maxValue);
|
|
|
|
|
break;
|
|
|
|
|
case 6:
|
|
|
|
|
automaticFittingData.getCfMin().setValue(minValue);
|
|
|
|
|
automaticFittingData.getCfMax().setValue(maxValue);
|
|
|
|
|
break;
|
|
|
|
|
case 7:
|
|
|
|
|
automaticFittingData.getSwiMin().setValue(minValue);
|
|
|
|
|
automaticFittingData.getSwiMax().setValue(maxValue);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_updatingParameterRanges = wasUpdatingRanges;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 根据初值生成首次或拟合后的建议范围,并始终限制在物理边界内。
|
|
|
|
|
void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex,
|
|
|
|
|
double centerValue, bool afterFit)
|
|
|
|
|
{
|
|
|
|
|
if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= 8
|
|
|
|
|
|| !nmAutoFitUiIsFinite(centerValue)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double physicalMin = 0.0;
|
|
|
|
|
double physicalMax = 0.0;
|
|
|
|
|
getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax);
|
|
|
|
|
|
|
|
|
|
double reference = centerValue;
|
|
|
|
|
const bool positiveParameter = parameterIndex != 1;
|
|
|
|
|
|
|
|
|
|
// 正值参数初值为 0 时不再借用旧的界面范围,直接从物理边界选取搜索尺度。
|
|
|
|
|
if(positiveParameter && reference <= 0.0) {
|
|
|
|
|
if(physicalMin > 0.0) {
|
|
|
|
|
reference = physicalMin;
|
|
|
|
|
} else {
|
|
|
|
|
reference = qMax(physicalMax * 0.01, 1.0e-12);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(physicalMax < physicalMin) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
reference = qBound(physicalMin, reference, physicalMax);
|
|
|
|
|
const double boundedCenterValue = qBound(physicalMin, centerValue, physicalMax);
|
|
|
|
|
|
|
|
|
|
double newMin = physicalMin;
|
|
|
|
|
double newMax = physicalMax;
|
|
|
|
|
if(parameterIndex == 1) {
|
|
|
|
|
const double skinHalfRange = afterFit ? 1.0 : 10.0;
|
|
|
|
|
newMin = qMax(physicalMin, reference - skinHalfRange);
|
|
|
|
|
newMax = qMin(physicalMax, reference + skinHalfRange);
|
|
|
|
|
} else if(reference > 0.0 && !(parameterIndex == 7 && centerValue <= 0.0)) {
|
|
|
|
|
const double lowerFactor = afterFit ? 0.5 : 0.1;
|
|
|
|
|
const double upperFactor = afterFit ? 2.0 : 10.0;
|
|
|
|
|
newMin = qMax(physicalMin, reference * lowerFactor);
|
|
|
|
|
newMax = qMin(physicalMax, reference * upperFactor);
|
|
|
|
|
} else if(parameterIndex == 7) {
|
|
|
|
|
// 没有可靠 Swi 初值时,不把搜索范围压缩到零附近。
|
|
|
|
|
newMin = physicalMin;
|
|
|
|
|
newMax = physicalMax;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 任何自动范围都必须包含本次使用的中心值,并且不能越过物理边界。
|
|
|
|
|
newMin = qMin(newMin, boundedCenterValue);
|
|
|
|
|
newMax = qMax(newMax, boundedCenterValue);
|
|
|
|
|
newMin = qMax(newMin, physicalMin);
|
|
|
|
|
newMax = qMin(newMax, physicalMax);
|
|
|
|
|
|
|
|
|
|
if(newMax >= newMin) {
|
|
|
|
|
setParameterRange(parameterIndex, newMin, newMax);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 首次进入自动范围模式时,按当前表格中的初值为所有参数建立建议范围。
|
|
|
|
|
void nmWxAutomaticFitting::initializeSuggestedParameterRanges()
|
|
|
|
|
{
|
|
|
|
|
if(!m_parameterTable) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(int parameterIndex = 0; parameterIndex < 8; ++parameterIndex) {
|
|
|
|
|
QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3);
|
|
|
|
|
if(initialItem) {
|
|
|
|
|
bool initialOk = false;
|
|
|
|
|
const double initialValue = initialItem->text().toDouble(&initialOk);
|
|
|
|
|
if(initialOk && nmAutoFitUiIsFinite(initialValue)) {
|
|
|
|
|
updateRangeForParameter(parameterIndex, initialValue, false);
|
|
|
|
|
} else {
|
|
|
|
|
// 数据对象没有提供该初值时使用完整物理区间,不回退到旧的默认范围。
|
|
|
|
|
double physicalMin = 0.0;
|
|
|
|
|
double physicalMax = 0.0;
|
|
|
|
|
getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax);
|
|
|
|
|
if(physicalMax >= physicalMin) {
|
|
|
|
|
setParameterRange(parameterIndex, physicalMin, physicalMax);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 校正已保存的范围:保留物理边界内的用户区间,无交集时按当前初值生成兜底区间。
|
|
|
|
|
void nmWxAutomaticFitting::normalizeSavedParameterRanges()
|
|
|
|
|
{
|
|
|
|
|
if(!m_parameterTable) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(int parameterIndex = 0; parameterIndex < 8; ++parameterIndex) {
|
|
|
|
|
QTableWidgetItem* minItem = m_parameterTable->item(parameterIndex, 2);
|
|
|
|
|
QTableWidgetItem* maxItem = m_parameterTable->item(parameterIndex, 4);
|
|
|
|
|
QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3);
|
|
|
|
|
if(!minItem || !maxItem || !initialItem) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double physicalMin = 0.0;
|
|
|
|
|
double physicalMax = 0.0;
|
|
|
|
|
getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax);
|
|
|
|
|
bool savedMinOk = false;
|
|
|
|
|
bool savedMaxOk = false;
|
|
|
|
|
const double savedMin = minItem->text().toDouble(&savedMinOk);
|
|
|
|
|
const double savedMax = maxItem->text().toDouble(&savedMaxOk);
|
|
|
|
|
const bool savedRangeValid = savedMinOk && savedMaxOk
|
|
|
|
|
&& nmAutoFitUiIsFinite(savedMin) && nmAutoFitUiIsFinite(savedMax)
|
|
|
|
|
&& savedMax >= savedMin;
|
|
|
|
|
|
|
|
|
|
if(savedRangeValid && physicalMax >= physicalMin) {
|
|
|
|
|
const double clippedMin = qMax(savedMin, physicalMin);
|
|
|
|
|
const double clippedMax = qMin(savedMax, physicalMax);
|
|
|
|
|
if(clippedMax >= clippedMin) {
|
|
|
|
|
setParameterRange(parameterIndex, clippedMin, clippedMax);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 已保存范围无效或与物理边界无交集时,按数据对象初值重新生成。
|
|
|
|
|
bool initialOk = false;
|
|
|
|
|
const double initialValue = initialItem->text().toDouble(&initialOk);
|
|
|
|
|
if(initialOk && nmAutoFitUiIsFinite(initialValue)) {
|
|
|
|
|
updateRangeForParameter(parameterIndex, initialValue, false);
|
|
|
|
|
} else if(physicalMax >= physicalMin) {
|
|
|
|
|
setParameterRange(parameterIndex, physicalMin, physicalMax);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 校验当前表格中的参数范围;parameterIndex 为 -1 时检查所有可见参数行。
|
|
|
|
|
bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int parameterIndex)
|
|
|
|
|
{
|
|
|
|
|
if(!m_parameterTable) {
|
|
|
|
|
errorMessage = tr("The parameter table is unavailable.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if(parameterIndex < -1 || parameterIndex >= 8) {
|
|
|
|
|
errorMessage = tr("The parameter row is invalid.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const char* parameterNames[] = {
|
|
|
|
|
"Permeability", "Skin", "Wellbore storage", "Porosity",
|
|
|
|
|
"Thickness", "Ct", "Cf", "Swi"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const int firstParameterIndex = parameterIndex < 0 ? 0 : parameterIndex;
|
|
|
|
|
const int lastParameterIndex = parameterIndex < 0 ? 8 : parameterIndex + 1;
|
|
|
|
|
for(int currentParameterIndex = firstParameterIndex;
|
|
|
|
|
currentParameterIndex < lastParameterIndex; ++currentParameterIndex) {
|
|
|
|
|
// 隐藏参数不参与当前模型拟合,不用它们的历史值阻塞当前设置。
|
|
|
|
|
if(m_parameterTable->isRowHidden(currentParameterIndex)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QTableWidgetItem* minItem = m_parameterTable->item(currentParameterIndex, 2);
|
|
|
|
|
QTableWidgetItem* initialItem = m_parameterTable->item(currentParameterIndex, 3);
|
|
|
|
|
QTableWidgetItem* maxItem = m_parameterTable->item(currentParameterIndex, 4);
|
|
|
|
|
if(!minItem || !initialItem || !maxItem) {
|
|
|
|
|
errorMessage = tr("The range values for %1 are incomplete.")
|
|
|
|
|
.arg(tr(parameterNames[currentParameterIndex]));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool minOk = false;
|
|
|
|
|
bool initialOk = false;
|
|
|
|
|
bool maxOk = false;
|
|
|
|
|
const double minValue = minItem->text().toDouble(&minOk);
|
|
|
|
|
const double initialValue = initialItem->text().toDouble(&initialOk);
|
|
|
|
|
const double maxValue = maxItem->text().toDouble(&maxOk);
|
|
|
|
|
if(!minOk || !initialOk || !maxOk
|
|
|
|
|
|| !nmAutoFitUiIsFinite(minValue)
|
|
|
|
|
|| !nmAutoFitUiIsFinite(initialValue)
|
|
|
|
|
|| !nmAutoFitUiIsFinite(maxValue)) {
|
|
|
|
|
errorMessage = tr("The minimum value, initial value, and maximum value of %1 must be finite numbers.")
|
|
|
|
|
.arg(tr(parameterNames[currentParameterIndex]));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double physicalMin = 0.0;
|
|
|
|
|
double physicalMax = 0.0;
|
|
|
|
|
getPhysicalParameterRange(currentParameterIndex, physicalMin, physicalMax);
|
|
|
|
|
if(!nmAutoFitUiIsFinite(physicalMin) || !nmAutoFitUiIsFinite(physicalMax)
|
|
|
|
|
|| physicalMax < physicalMin) {
|
|
|
|
|
errorMessage = tr("The physical range of %1 is invalid.")
|
|
|
|
|
.arg(tr(parameterNames[currentParameterIndex]));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(minValue < physicalMin || minValue > physicalMax
|
|
|
|
|
|| initialValue < physicalMin || initialValue > physicalMax
|
|
|
|
|
|| maxValue < physicalMin || maxValue > physicalMax) {
|
|
|
|
|
errorMessage = tr("The values of %1 exceed the physical range [%2, %3].")
|
|
|
|
|
.arg(tr(parameterNames[currentParameterIndex]))
|
|
|
|
|
.arg(QString::number(physicalMin, 'g', 10))
|
|
|
|
|
.arg(QString::number(physicalMax, 'g', 10));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(minValue > maxValue || minValue > initialValue || initialValue > maxValue) {
|
|
|
|
|
errorMessage = tr("The values of %1 must satisfy: minimum <= initial value <= maximum.")
|
|
|
|
|
.arg(tr(parameterNames[currentParameterIndex]));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent)
|
|
|
|
|
: iDlgBase(parent)
|
|
|
|
|
, m_autoFitterPSO(nullptr)
|
|
|
|
|
, m_autoFitterGA(nullptr)
|
|
|
|
|
, m_progressDialog(nullptr)
|
|
|
|
|
, m_progressTimer(nullptr)
|
|
|
|
|
, m_progressMonitor(nullptr)
|
|
|
|
|
, m_selectedAlgorithm(ALGORITHM_PSO) // 默认选择PSO算法
|
|
|
|
|
, m_autoParameterRanges(true)
|
|
|
|
|
// 构造期间先禁止即时校验,避免初始值写入和范围生成之间出现短暂的不一致。
|
|
|
|
|
, m_updatingParameterRanges(true)
|
|
|
|
|
{
|
|
|
|
|
DEBUG_UI(QString("AutoFitting Constructor: this=0x%1").arg((quintptr)this, 0, 16));
|
|
|
|
|
|
|
|
|
|
@ -233,32 +584,13 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent)
|
|
|
|
|
nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
|
|
reservoirData = pManager->getReservoirDataCopy();
|
|
|
|
|
automaticFittingData = pManager->getAutomaticFittingDataCopy();
|
|
|
|
|
const bool hasSavedFittingData = pManager && pManager->getAutomaticFittingData() != nullptr;
|
|
|
|
|
// 自动范围始终开启;用户在表格中修改上下限后,itemChanged 会临时切换为手工范围。
|
|
|
|
|
m_autoParameterRanges = true;
|
|
|
|
|
|
|
|
|
|
NM_SOLVER_MODEL_TYPE solverModelType = pManager->getSolverModelType();
|
|
|
|
|
// 只有尚未点击确定保存过配置时才使用相态默认值;重新打开时保留已保存的数据。
|
|
|
|
|
if(pManager->getAutomaticFittingData() == nullptr) {
|
|
|
|
|
bool isOilOrWaterSinglePhase = solverModelType == SMT_Oil_ConstPvt ||
|
|
|
|
|
solverModelType == SMT_Oil_VariablePvt ||
|
|
|
|
|
solverModelType == SMT_Water_ConstPvt ||
|
|
|
|
|
solverModelType == SMT_Water_VariablePvt;
|
|
|
|
|
if(isOilOrWaterSinglePhase) {
|
|
|
|
|
reservoirData.getPermeability().setValue(2.5e-2);
|
|
|
|
|
automaticFittingData.getPermeabilityMin().setValue(1.0e-3);
|
|
|
|
|
automaticFittingData.getPermeabilityMax().setValue(10.0);
|
|
|
|
|
automaticFittingData.getSkinMin().setValue(-10.0);
|
|
|
|
|
automaticFittingData.getSkinMax().setValue(10.0);
|
|
|
|
|
automaticFittingData.getWellboreStorageMin().setValue(1.0e-4);
|
|
|
|
|
automaticFittingData.getWellboreStorageMax().setValue(2.0);
|
|
|
|
|
automaticFittingData.getPorosityMin().setValue(1.0e-2);
|
|
|
|
|
automaticFittingData.getPorosityMax().setValue(5.0e-1);
|
|
|
|
|
automaticFittingData.getThicknessMin().setValue(2.0);
|
|
|
|
|
automaticFittingData.getThicknessMax().setValue(50.0);
|
|
|
|
|
automaticFittingData.getCtMin().setValue(1.0e-3);
|
|
|
|
|
automaticFittingData.getCtMax().setValue(1.0e-1);
|
|
|
|
|
automaticFittingData.getCfMin().setValue(1.0e-5);
|
|
|
|
|
automaticFittingData.getCfMax().setValue(1.0e-2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 未保存过配置时只保留参数选择的相态默认值,不再覆盖数据对象中的初值或范围。
|
|
|
|
|
if(!hasSavedFittingData) {
|
|
|
|
|
if(solverModelType == SMT_Oil_ConstPvt ||
|
|
|
|
|
solverModelType == SMT_Water_ConstPvt) {
|
|
|
|
|
// T1/T3 的综合压缩系数默认不参与拟合。
|
|
|
|
|
@ -268,43 +600,6 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent)
|
|
|
|
|
// T2/T4 的岩石压缩系数默认参与拟合。
|
|
|
|
|
automaticFittingData.setCfSelected(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 气单相变化 PVT 使用 T5 数据集对应的初始值和拟合范围。
|
|
|
|
|
if(solverModelType == SMT_Gas_VariablePvt) {
|
|
|
|
|
reservoirData.getPermeability().setValue(5.5e-6);
|
|
|
|
|
reservoirData.getPorosity().setValue(3.5e-2);
|
|
|
|
|
reservoirData.getThickness().setValue(8.0);
|
|
|
|
|
reservoirData.getCf().setValue(3.0e-4);
|
|
|
|
|
|
|
|
|
|
automaticFittingData.getPermeabilityMin().setValue(4.5e-6);
|
|
|
|
|
automaticFittingData.getPermeabilityMax().setValue(6.2e-6);
|
|
|
|
|
automaticFittingData.getSkinMin().setValue(0.0);
|
|
|
|
|
automaticFittingData.getSkinMax().setValue(4.5);
|
|
|
|
|
automaticFittingData.getWellboreStorageMin().setValue(5.0e-5);
|
|
|
|
|
automaticFittingData.getWellboreStorageMax().setValue(1.6e-4);
|
|
|
|
|
automaticFittingData.getPorosityMin().setValue(8.0e-3);
|
|
|
|
|
automaticFittingData.getPorosityMax().setValue(4.0e-2);
|
|
|
|
|
automaticFittingData.getThicknessMin().setValue(7.0);
|
|
|
|
|
automaticFittingData.getThicknessMax().setValue(11.0);
|
|
|
|
|
automaticFittingData.getCfMin().setValue(1.0e-4);
|
|
|
|
|
automaticFittingData.getCfMax().setValue(2.0e-3);
|
|
|
|
|
} else if(solverModelType == SMT_Oil_ConstPvt ||
|
|
|
|
|
solverModelType == SMT_Water_ConstPvt) {
|
|
|
|
|
// T1/T3 使用固定初值,不继承当前储层参数。
|
|
|
|
|
reservoirData.getPorosity().setValue(2.45e-2);
|
|
|
|
|
reservoirData.getThickness().setValue(9.144);
|
|
|
|
|
reservoirData.getCt().setValue(1.0e-2);
|
|
|
|
|
} else if(solverModelType == SMT_Oil_VariablePvt) {
|
|
|
|
|
// 油单相变化 PVT 使用固定初值。
|
|
|
|
|
reservoirData.getPorosity().setValue(6.0e-2);
|
|
|
|
|
reservoirData.getThickness().setValue(10.5);
|
|
|
|
|
reservoirData.getCf().setValue(1.0e-3);
|
|
|
|
|
} else if(solverModelType == SMT_Water_VariablePvt) {
|
|
|
|
|
// 水单相变化 PVT 使用固定初值。
|
|
|
|
|
reservoirData.getPorosity().setValue(6.0e-2);
|
|
|
|
|
reservoirData.getThickness().setValue(10.5);
|
|
|
|
|
reservoirData.getCf().setValue(1.5e-3);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupUI();
|
|
|
|
|
@ -315,6 +610,12 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent)
|
|
|
|
|
if(m_targetWellCombo->count() > 0) {
|
|
|
|
|
onWellSelected(0); // 默认选中第一口井
|
|
|
|
|
}
|
|
|
|
|
if(!hasSavedFittingData) {
|
|
|
|
|
initializeSuggestedParameterRanges();
|
|
|
|
|
} else {
|
|
|
|
|
normalizeSavedParameterRanges();
|
|
|
|
|
}
|
|
|
|
|
m_updatingParameterRanges = false;
|
|
|
|
|
|
|
|
|
|
DEBUG_UI("AutoFitting Constructor completed");
|
|
|
|
|
}
|
|
|
|
|
@ -333,9 +634,6 @@ nmWxAutomaticFitting::~nmWxAutomaticFitting()
|
|
|
|
|
if (m_autoFitterPSO) {
|
|
|
|
|
disconnect(m_autoFitterPSO, nullptr, this, nullptr);
|
|
|
|
|
}
|
|
|
|
|
if (m_autoFitterGA) {
|
|
|
|
|
disconnect(m_autoFitterGA, nullptr, this, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DEBUG_UI("AutoFitting destructor - completed");
|
|
|
|
|
}
|
|
|
|
|
@ -514,6 +812,8 @@ void nmWxAutomaticFitting::setupParameterTable()
|
|
|
|
|
// 连接选择改变信号
|
|
|
|
|
connect(m_parameterTable, SIGNAL(currentCellChanged(int, int, int, int)),
|
|
|
|
|
this, SLOT(onCellSelectionChanged(int, int, int, int)));
|
|
|
|
|
connect(m_parameterTable, SIGNAL(itemChanged(QTableWidgetItem*)),
|
|
|
|
|
this, SLOT(onParameterTableItemChanged(QTableWidgetItem*)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmWxAutomaticFitting::setupControlPanel()
|
|
|
|
|
@ -528,9 +828,7 @@ void nmWxAutomaticFitting::setupControlPanel()
|
|
|
|
|
QLabel* algorithmLabel = new QLabel(tr("Algorithm:"));
|
|
|
|
|
m_algorithmCombo = new QComboBox();
|
|
|
|
|
m_algorithmCombo->addItem(tr("PSO (Particle Swarm)"));
|
|
|
|
|
m_algorithmCombo->addItem(tr("GA (Genetic Algorithm)"));
|
|
|
|
|
m_algorithmCombo->setCurrentIndex(0); // 默认选择PSO
|
|
|
|
|
connect(m_algorithmCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onAlgorithmChanged(int)));
|
|
|
|
|
m_algorithmCombo->setCurrentIndex(0);
|
|
|
|
|
m_algorithmCombo->setMaximumWidth(160);
|
|
|
|
|
m_algorithmCombo->setMinimumWidth(160);
|
|
|
|
|
|
|
|
|
|
@ -681,35 +979,37 @@ void nmWxAutomaticFitting::onReverseSelection()
|
|
|
|
|
if(!m_parameterTable->isRowHidden(7)) m_swiCheckBox->setChecked(!m_swiCheckBox->isChecked());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmWxAutomaticFitting::onAlgorithmChanged(int index)
|
|
|
|
|
void nmWxAutomaticFitting::onParameterTableItemChanged(QTableWidgetItem* item)
|
|
|
|
|
{
|
|
|
|
|
m_selectedAlgorithm = static_cast<OptimizationAlgorithm>(index);
|
|
|
|
|
|
|
|
|
|
// 根据算法类型调整界面提示或参数
|
|
|
|
|
QString algorithmInfo;
|
|
|
|
|
switch(m_selectedAlgorithm) {
|
|
|
|
|
case ALGORITHM_PSO:
|
|
|
|
|
algorithmInfo = tr("PSO algorithm selected.");
|
|
|
|
|
if(m_surrogateCombo) {
|
|
|
|
|
m_surrogateCombo->setEnabled(true);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case ALGORITHM_GA:
|
|
|
|
|
algorithmInfo = tr("GA algorithm selected.");
|
|
|
|
|
if(m_surrogateCombo) {
|
|
|
|
|
m_surrogateCombo->setEnabled(false);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
if(!item || m_updatingParameterRanges) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 在状态栏或工具提示中显示算法信息
|
|
|
|
|
m_algorithmCombo->setToolTip(algorithmInfo);
|
|
|
|
|
// 用户改动范围后,切井和拟合结果不再自动覆盖这组手工范围。
|
|
|
|
|
if(item->column() == 2 || item->column() == 4) {
|
|
|
|
|
m_autoParameterRanges = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(item->column() >= 2 && item->column() <= 4
|
|
|
|
|
&& !m_parameterTable->isRowHidden(item->row())) {
|
|
|
|
|
QString validationError;
|
|
|
|
|
if(!validateParameterTable(validationError, item->row())) {
|
|
|
|
|
// 表格编辑提交后立即提示,用户不需要先点击“确定”才发现错误。
|
|
|
|
|
QMessageBox::warning(this, tr("Invalid parameter range"), validationError);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmWxAutomaticFitting::onAccept()
|
|
|
|
|
{
|
|
|
|
|
// 首先保存参数设置
|
|
|
|
|
setAutomaticFittingValue();
|
|
|
|
|
QString validationError;
|
|
|
|
|
if(!validateParameterTable(validationError)) {
|
|
|
|
|
QMessageBox::warning(this, tr("Invalid parameter range"), validationError);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 校验通过后再保存参数设置,非法输入不会被静默裁剪。
|
|
|
|
|
setAutomaticFittingValue();
|
|
|
|
|
// 更新完成后,通知参数界面刷新
|
|
|
|
|
nmWxParameterProperty::notifyUpdateTable();
|
|
|
|
|
|
|
|
|
|
@ -878,28 +1178,6 @@ void nmWxAutomaticFitting::onWellSelected(int index)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 尚未保存拟合配置时使用相态默认值;已保存时保留井上的 Skin 和井筒储集系数。
|
|
|
|
|
nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
|
|
bool useDefaultFittingValues = pManager && pManager->getAutomaticFittingData() == nullptr;
|
|
|
|
|
if(useDefaultFittingValues && (pManager->getSolverModelType() == SMT_Oil_ConstPvt ||
|
|
|
|
|
pManager->getSolverModelType() == SMT_Water_ConstPvt)) {
|
|
|
|
|
skinValue = 0.0;
|
|
|
|
|
wellboreStorageValue = 1.0e-2;
|
|
|
|
|
found = true;
|
|
|
|
|
} else if(useDefaultFittingValues && pManager->getSolverModelType() == SMT_Gas_VariablePvt) {
|
|
|
|
|
skinValue = 1.0;
|
|
|
|
|
wellboreStorageValue = 6.0e-5;
|
|
|
|
|
found = true;
|
|
|
|
|
} else if(useDefaultFittingValues && pManager->getSolverModelType() == SMT_Oil_VariablePvt) {
|
|
|
|
|
skinValue = -0.2;
|
|
|
|
|
wellboreStorageValue = 1.5e-2;
|
|
|
|
|
found = true;
|
|
|
|
|
} else if(useDefaultFittingValues && pManager->getSolverModelType() == SMT_Water_VariablePvt) {
|
|
|
|
|
skinValue = -0.2;
|
|
|
|
|
wellboreStorageValue = 1.0e-2;
|
|
|
|
|
found = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (found) {
|
|
|
|
|
// 更新表格数据
|
|
|
|
|
// 确保表格项存在
|
|
|
|
|
@ -915,6 +1193,11 @@ void nmWxAutomaticFitting::onWellSelected(int index)
|
|
|
|
|
|
|
|
|
|
// 设置井筒储集系数(Wellbore storage)
|
|
|
|
|
m_parameterTable->item(2, 3)->setText(QString::number(wellboreStorageValue));
|
|
|
|
|
|
|
|
|
|
if(m_autoParameterRanges) {
|
|
|
|
|
updateRangeForParameter(1, skinValue, false);
|
|
|
|
|
updateRangeForParameter(2, wellboreStorageValue, false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -1041,11 +1324,10 @@ void nmWxAutomaticFitting::startAutoFitting(const QVector<QVector<double>>& targ
|
|
|
|
|
// 先清理之前的实例
|
|
|
|
|
cleanupFitting();
|
|
|
|
|
|
|
|
|
|
if (m_selectedAlgorithm == ALGORITHM_PSO) {
|
|
|
|
|
DEBUG_UI("Creating PSO auto fitter");
|
|
|
|
|
m_autoFitterPSO = new nmCalculationAutoFitPSO(this);
|
|
|
|
|
m_autoFitterPSO->setTargetLogLogData(targetData);
|
|
|
|
|
m_autoFitterPSO->setPSOTargetWellName(targetWellName);
|
|
|
|
|
DEBUG_UI("Creating PSO auto fitter");
|
|
|
|
|
m_autoFitterPSO = new nmCalculationAutoFitPSO(this);
|
|
|
|
|
m_autoFitterPSO->setTargetLogLogData(targetData);
|
|
|
|
|
m_autoFitterPSO->setPSOTargetWellName(targetWellName);
|
|
|
|
|
|
|
|
|
|
//// 特定井名时使用快速路径
|
|
|
|
|
//if (targetWellName == "VerticalWell1") {
|
|
|
|
|
@ -1080,46 +1362,23 @@ void nmWxAutomaticFitting::startAutoFitting(const QVector<QVector<double>>& targ
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
m_progressMonitor = new nmWxAutomaticfittingStart(this);
|
|
|
|
|
m_progressMonitor->setAutoFitter(m_autoFitterPSO);
|
|
|
|
|
m_progressMonitor->setPseudoPressureMode(
|
|
|
|
|
nmDataAnalyzeManager::getCurrentInstance()->getSolverModelType() == SMT_Gas_VariablePvt);
|
|
|
|
|
m_progressMonitor->setTargetLogLogData(targetData);
|
|
|
|
|
|
|
|
|
|
connect(m_autoFitterPSO, SIGNAL(fittingFinished(bool, QString)),
|
|
|
|
|
this, SLOT(onFittingFinished(bool, QString)));
|
|
|
|
|
|
|
|
|
|
int maxIterations = m_iterationEdit->text().toInt();
|
|
|
|
|
double targetError = m_errorLimitEdit->text().toDouble();
|
|
|
|
|
QString wellName = m_targetWellCombo->currentText();
|
|
|
|
|
m_progressMonitor->setFittingParameters(maxIterations, targetError, wellName);
|
|
|
|
|
m_progressMonitor->setSelectedParameters(selectedParams);
|
|
|
|
|
|
|
|
|
|
m_progressMonitor->show();
|
|
|
|
|
QTimer::singleShot(100, this, SLOT(runAutoFitting()));
|
|
|
|
|
m_progressMonitor = new nmWxAutomaticfittingStart(this);
|
|
|
|
|
m_progressMonitor->setAutoFitter(m_autoFitterPSO);
|
|
|
|
|
m_progressMonitor->setPseudoPressureMode(
|
|
|
|
|
nmDataAnalyzeManager::getCurrentInstance()->getSolverModelType() == SMT_Gas_VariablePvt);
|
|
|
|
|
m_progressMonitor->setTargetLogLogData(targetData);
|
|
|
|
|
|
|
|
|
|
} else if (m_selectedAlgorithm == ALGORITHM_GA) {
|
|
|
|
|
DEBUG_UI("Creating GA auto fitter");
|
|
|
|
|
m_autoFitterGA = new nmCalculationAutoFitGA(this);
|
|
|
|
|
m_autoFitterGA->setTargetLogLogData(targetData);
|
|
|
|
|
m_autoFitterGA->setGATargetWellName(targetWellName);
|
|
|
|
|
connect(m_autoFitterPSO, SIGNAL(fittingFinished(bool, QString)),
|
|
|
|
|
this, SLOT(onFittingFinished(bool, QString)));
|
|
|
|
|
|
|
|
|
|
m_progressMonitor = new nmWxAutomaticfittingStart(this);
|
|
|
|
|
m_progressMonitor->setAutoFitterGA(m_autoFitterGA);
|
|
|
|
|
m_progressMonitor->setTargetLogLogData(targetData);
|
|
|
|
|
int maxIterations = m_iterationEdit->text().toInt();
|
|
|
|
|
double targetError = m_errorLimitEdit->text().toDouble();
|
|
|
|
|
QString wellName = m_targetWellCombo->currentText();
|
|
|
|
|
m_progressMonitor->setFittingParameters(maxIterations, targetError, wellName);
|
|
|
|
|
m_progressMonitor->setSelectedParameters(selectedParams);
|
|
|
|
|
|
|
|
|
|
connect(m_autoFitterGA, SIGNAL(fittingFinished(bool, QString)),
|
|
|
|
|
this, SLOT(onFittingFinished(bool, QString)));
|
|
|
|
|
|
|
|
|
|
int maxIterations = m_iterationEdit->text().toInt();
|
|
|
|
|
double targetError = m_errorLimitEdit->text().toDouble();
|
|
|
|
|
QString wellName = m_targetWellCombo->currentText();
|
|
|
|
|
m_progressMonitor->setFittingParameters(maxIterations, targetError, wellName);
|
|
|
|
|
m_progressMonitor->setSelectedParameters(selectedParams);
|
|
|
|
|
|
|
|
|
|
m_progressMonitor->show();
|
|
|
|
|
QTimer::singleShot(100, this, SLOT(runAutoFitting()));
|
|
|
|
|
}
|
|
|
|
|
m_progressMonitor->show();
|
|
|
|
|
QTimer::singleShot(100, this, SLOT(runAutoFitting()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmWxAutomaticFitting::runAutoFitting()
|
|
|
|
|
@ -1128,10 +1387,8 @@ void nmWxAutomaticFitting::runAutoFitting()
|
|
|
|
|
m_progressMonitor->markFittingStarted();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(m_selectedAlgorithm == ALGORITHM_PSO && m_autoFitterPSO) {
|
|
|
|
|
if(m_autoFitterPSO) {
|
|
|
|
|
m_autoFitterPSO->startAutoFitting();
|
|
|
|
|
} else if(m_selectedAlgorithm == ALGORITHM_GA && m_autoFitterGA) {
|
|
|
|
|
m_autoFitterGA->startAutoFitting();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -1147,18 +1404,13 @@ void nmWxAutomaticFitting::onFittingFinished(bool success, const QString& messag
|
|
|
|
|
disconnect(m_autoFitterPSO, SIGNAL(fittingFinished(bool, QString)),
|
|
|
|
|
this, SLOT(onFittingFinished(bool, QString)));
|
|
|
|
|
}
|
|
|
|
|
if (m_autoFitterGA) {
|
|
|
|
|
disconnect(m_autoFitterGA, SIGNAL(fittingFinished(bool, QString)),
|
|
|
|
|
this, SLOT(onFittingFinished(bool, QString)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新最佳参数到表格
|
|
|
|
|
updateBestParametersToTable();
|
|
|
|
|
|
|
|
|
|
if(success) {
|
|
|
|
|
// 只有成功拟合的结果才用于生成下一轮范围,失败结果不污染当前配置。
|
|
|
|
|
updateBestParametersToTable();
|
|
|
|
|
|
|
|
|
|
QString resultInfo;
|
|
|
|
|
|
|
|
|
|
if(m_selectedAlgorithm == ALGORITHM_PSO && m_autoFitterPSO) {
|
|
|
|
|
if(m_autoFitterPSO) {
|
|
|
|
|
double bestFitness = m_autoFitterPSO->getBestFitness();
|
|
|
|
|
|
|
|
|
|
// 检查是否是用户停止的情况
|
|
|
|
|
@ -1173,23 +1425,6 @@ void nmWxAutomaticFitting::onFittingFinished(bool success, const QString& messag
|
|
|
|
|
resultInfo += tr("Optimized parameters have been applied to the model.");
|
|
|
|
|
QMessageBox::information(this, tr("Optimization Completed"), resultInfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else if(m_selectedAlgorithm == ALGORITHM_GA && m_autoFitterGA) {
|
|
|
|
|
// GA的类似处理
|
|
|
|
|
|
|
|
|
|
double bestFitness = m_autoFitterGA->getBestFitness();
|
|
|
|
|
|
|
|
|
|
if(message.contains("stopped by user", Qt::CaseInsensitive)) {
|
|
|
|
|
resultInfo = tr("GA Optimization stopped by user:\n");
|
|
|
|
|
resultInfo += tr("Best Error: %1\n").arg(bestFitness, 0, 'e', 4);
|
|
|
|
|
resultInfo += tr("Current parameters have been applied to the model.");
|
|
|
|
|
QMessageBox::information(this, tr("Optimization Stopped"), resultInfo);
|
|
|
|
|
} else {
|
|
|
|
|
resultInfo = tr("GA Optimization completed:\n");
|
|
|
|
|
resultInfo += tr("Best Error: %1\n").arg(bestFitness, 0, 'e', 4);
|
|
|
|
|
resultInfo += tr("Optimized parameters have been applied to the model.");
|
|
|
|
|
QMessageBox::information(this, tr("Optimization Completed"), resultInfo);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 只有真正失败的情况才显示警告
|
|
|
|
|
@ -1199,11 +1434,9 @@ void nmWxAutomaticFitting::onFittingFinished(bool success, const QString& messag
|
|
|
|
|
|
|
|
|
|
void nmWxAutomaticFitting::onStopFitting()
|
|
|
|
|
{
|
|
|
|
|
if(m_selectedAlgorithm == ALGORITHM_PSO && m_autoFitterPSO && m_autoFitterPSO->isRunning()) {
|
|
|
|
|
if(m_autoFitterPSO && m_autoFitterPSO->isRunning()) {
|
|
|
|
|
m_autoFitterPSO->stopFitting();
|
|
|
|
|
} else if(m_selectedAlgorithm == ALGORITHM_GA && m_autoFitterGA && m_autoFitterGA->isRunning()) {
|
|
|
|
|
m_autoFitterGA->stopFitting();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmWxAutomaticFitting::cleanupFitting()
|
|
|
|
|
@ -1246,31 +1479,6 @@ void nmWxAutomaticFitting::cleanupFitting()
|
|
|
|
|
DEBUG_UI("PSO fitter cleaned up");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_autoFitterGA) {
|
|
|
|
|
DEBUG_UI("Stopping and disconnecting GA fitter");
|
|
|
|
|
|
|
|
|
|
disconnect(m_autoFitterGA, nullptr, nullptr, nullptr);
|
|
|
|
|
|
|
|
|
|
if (m_autoFitterGA->isRunning()) {
|
|
|
|
|
m_autoFitterGA->stopFitting();
|
|
|
|
|
|
|
|
|
|
int waitCount = 0;
|
|
|
|
|
while (m_autoFitterGA->isRunning() && waitCount < 50) {
|
|
|
|
|
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 100);
|
|
|
|
|
waitCount++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果仍在运行则强制停止
|
|
|
|
|
if (m_autoFitterGA->isRunning()) {
|
|
|
|
|
DEBUG_UI("Force stopping GA - timeout reached");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delete m_autoFitterGA;
|
|
|
|
|
m_autoFitterGA = nullptr;
|
|
|
|
|
DEBUG_UI("GA fitter cleaned up");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 清理进度监控
|
|
|
|
|
if (m_progressMonitor) {
|
|
|
|
|
// 先断开进度监控的信号连接
|
|
|
|
|
@ -1296,10 +1504,8 @@ void nmWxAutomaticFitting::updateBestParametersToTable()
|
|
|
|
|
QVector<double> bestSolution;
|
|
|
|
|
|
|
|
|
|
// 获取最佳解决方案
|
|
|
|
|
if (m_selectedAlgorithm == ALGORITHM_PSO && m_autoFitterPSO) {
|
|
|
|
|
if (m_autoFitterPSO) {
|
|
|
|
|
bestSolution = m_autoFitterPSO->getBestSolution();
|
|
|
|
|
} else if (m_selectedAlgorithm == ALGORITHM_GA && m_autoFitterGA) {
|
|
|
|
|
bestSolution = m_autoFitterGA->getBestSolution();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (bestSolution.isEmpty()) return;
|
|
|
|
|
@ -1315,78 +1521,19 @@ void nmWxAutomaticFitting::updateBestParametersToTable()
|
|
|
|
|
if(m_cfCheckBox->isChecked()) enabledParams.append(6); // 岩石压缩系数
|
|
|
|
|
if(m_swiCheckBox->isChecked()) enabledParams.append(7); // 初始含水饱和度
|
|
|
|
|
|
|
|
|
|
// 范围收缩比例
|
|
|
|
|
double shrinkFactor = 0.3;
|
|
|
|
|
|
|
|
|
|
// 更新参数值和范围
|
|
|
|
|
for (int i = 0; i < bestSolution.size() && i < enabledParams.size(); ++i) {
|
|
|
|
|
int paramIndex = enabledParams[i];
|
|
|
|
|
double bestValue = bestSolution[i];
|
|
|
|
|
if(!nmAutoFitUiIsFinite(bestValue)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新初始值
|
|
|
|
|
m_parameterTable->item(paramIndex, 3)->setText(QString::number(bestValue, 'g', 4));
|
|
|
|
|
|
|
|
|
|
// 获取当前的最小值和最大值
|
|
|
|
|
double currentMin = m_parameterTable->item(paramIndex, 2)->text().toDouble();
|
|
|
|
|
double currentMax = m_parameterTable->item(paramIndex, 4)->text().toDouble();
|
|
|
|
|
double currentRange = currentMax - currentMin;
|
|
|
|
|
|
|
|
|
|
// 计算新的范围
|
|
|
|
|
double newHalfRange = currentRange * shrinkFactor * 0.5;
|
|
|
|
|
double newMin = bestValue - newHalfRange;
|
|
|
|
|
double newMax = bestValue + newHalfRange;
|
|
|
|
|
|
|
|
|
|
// 确保某些参数不为负数
|
|
|
|
|
if (paramIndex == 0 || paramIndex == 2 || paramIndex == 3 || paramIndex == 4) {
|
|
|
|
|
newMin = qMax(newMin, 0.0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 确保最小范围,避免范围过小
|
|
|
|
|
double minAllowedRange = currentRange * 0.05;
|
|
|
|
|
if ((newMax - newMin) < minAllowedRange) {
|
|
|
|
|
double center = (newMax + newMin) * 0.5;
|
|
|
|
|
newMin = center - minAllowedRange * 0.5;
|
|
|
|
|
newMax = center + minAllowedRange * 0.5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新表格中的最小值和最大值
|
|
|
|
|
m_parameterTable->item(paramIndex, 2)->setText(QString::number(newMin, 'g', 4));
|
|
|
|
|
m_parameterTable->item(paramIndex, 4)->setText(QString::number(newMax, 'g', 4));
|
|
|
|
|
|
|
|
|
|
// 保存收缩后的范围
|
|
|
|
|
switch(paramIndex) {
|
|
|
|
|
case 0: // 渗透率
|
|
|
|
|
automaticFittingData.getPermeabilityMin().setValue(newMin);
|
|
|
|
|
automaticFittingData.getPermeabilityMax().setValue(newMax);
|
|
|
|
|
break;
|
|
|
|
|
case 1: // 表皮系数
|
|
|
|
|
automaticFittingData.getSkinMin().setValue(newMin);
|
|
|
|
|
automaticFittingData.getSkinMax().setValue(newMax);
|
|
|
|
|
break;
|
|
|
|
|
case 2: // 井筒储集系数
|
|
|
|
|
automaticFittingData.getWellboreStorageMin().setValue(newMin);
|
|
|
|
|
automaticFittingData.getWellboreStorageMax().setValue(newMax);
|
|
|
|
|
break;
|
|
|
|
|
case 3: // 孔隙度
|
|
|
|
|
automaticFittingData.getPorosityMin().setValue(newMin);
|
|
|
|
|
automaticFittingData.getPorosityMax().setValue(newMax);
|
|
|
|
|
break;
|
|
|
|
|
case 4: // 储层厚度
|
|
|
|
|
automaticFittingData.getThicknessMin().setValue(newMin);
|
|
|
|
|
automaticFittingData.getThicknessMax().setValue(newMax);
|
|
|
|
|
break;
|
|
|
|
|
case 5: // 综合压缩系数
|
|
|
|
|
automaticFittingData.getCtMin().setValue(newMin);
|
|
|
|
|
automaticFittingData.getCtMax().setValue(newMax);
|
|
|
|
|
break;
|
|
|
|
|
case 6: // 岩石压缩系数
|
|
|
|
|
automaticFittingData.getCfMin().setValue(newMin);
|
|
|
|
|
automaticFittingData.getCfMax().setValue(newMax);
|
|
|
|
|
break;
|
|
|
|
|
case 7: // 初始含水饱和度
|
|
|
|
|
automaticFittingData.getSwiMin().setValue(newMin);
|
|
|
|
|
automaticFittingData.getSwiMax().setValue(newMax);
|
|
|
|
|
break;
|
|
|
|
|
// 只有自动范围模式才根据拟合结果收窄下一轮搜索区间;手工范围由用户保留。
|
|
|
|
|
if(m_autoParameterRanges) {
|
|
|
|
|
updateRangeForParameter(paramIndex, bestValue, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|