Compare commits

...

10 Commits

Author SHA1 Message Date
lvjunjie c3297259b2 fix(nmNum): LM 模式下禁用 PSO 加速选项
- 选择 LM 时置灰 PSO 加速标签和下拉框

- 切回 PSO 时恢复可用状态并保留原选择
10 hours ago
lvjunjie 6dd776ebc7 fix(nmNum): 将拟合停止按钮文案缩短为正在停止 15 hours ago
lvjunjie 19c7c7cf02 fix(nmNum): 修复 LM 等待求解时停止按钮无法响应
- 等待 DLL 求解期间处理鼠标和键盘事件,使停止按钮能够响应
- 移除停止函数中的阻塞等待,登记停止请求后立即返回
- 用户停止改用协作取消接口,等待 DLL 返回后丢弃本次结果,避免强制终止线程破坏内部锁
16 hours ago
lvjunjie ec384f7483 fix(nmNum): 移除储层厚度及压缩系数的自动拟合功能
- 删除厚度、综合压缩系数 Ct、岩石压缩系数 Cf 的拟合控件、选择标志、上下界及配置读写代码
- 同步精简 LM、PSO 参数向量,调整剩余参数索引、初值读取和拟合结果回写
- 更新拟合记录字段及元数据版本,保持参数名称与索引一致
- 保留厚度、Ct、Cf 作为求解器和代理模型的固定输入,拟合过程不再调整
16 hours ago
lvjunjie 586a40df5c fix(numerical): 捕获网格访问违例
- 为网格 DLL 调用增加访问违例捕获,异常时返回生成失败
- 失败时清理临时网格文件并释放 DLL,保留已有成果
- 启用 LM 拟合诊断轨迹
3 days ago
lvjunjie b7148fcfc9 过滤自动拟合目标井列表
- 默认显示当前分析主井
- 仅显示“包含其他井”中已勾选的井
- 过滤缺少有效流量或压力数据的包含井
6 days ago
lvjunjie 073db447a3 拟合相关翻译 6 days ago
lvjunjie 27256f9c59 fix(numerical): 修复停止拟合后结果未刷新
- LM 和 PSO 停止优化后保留当前最优参数并执行最终完整求解
- 最终求解成功后提交正式结果快照并刷新主界面
- 增加最终结果生成状态,避免重复停止及未提交快照时误报成功
6 days ago
lvjunjie b9181b9dce fix(numerical): 修复自动拟合结果未刷新
- LM 和 PSO 通过正式结果快照刷新主界面
- PSO 最优参数落地后补充最终完整求解与快照提交
- 区分快照发布、结果井切换与手工生成刷新流程
6 days ago
lvjunjie 77bd6cd4e9 fix(numerical): 修复拟合后井参数无法修改
- LM 和 PSO 通过参数副本更新现有井属性
- 避免整井赋值重建射孔并导致参数绑定失效
- 保存拟合初值及拟合结束后刷新 PEBI 参数面板
7 days ago

Binary file not shown.

@ -67,7 +67,11 @@ Reason: %1</source>
</message>
<message>
<source>Gracefully stopping PSO optimization...</source>
<translation>PSO...</translation>
<translation> PSO ...</translation>
</message>
<message>
<source>The final full-field calculation is already running</source>
<translation></translation>
</message>
<message>
<source>Force stopping current evaluation...</source>
@ -495,7 +499,7 @@ Reason: %1</source>
</message>
<message>
<source>Stopped by user. Best error: %1, Iterations: %2</source>
<translation>: %1: %2</translation>
<translation>%1%2</translation>
</message>
<message>
<source>Better solution found: %1</source>
@ -800,6 +804,10 @@ Reason: %1</source>
<source>CRITICAL ERROR: Unknown exception in automatic fitting</source>
<translation></translation>
</message>
<message>
<source>The final full-field calculation is already running</source>
<translation></translation>
</message>
<message>
<source>Applying optimized parameters to model...</source>
<translation>...</translation>
@ -897,8 +905,8 @@ Reason: %1</source>
<translation>=== LM - ===</translation>
</message>
<message>
<source>Best error: %1, Iterations: %2</source>
<translation>%1%2</translation>
<source>Stopped by user. Best error: %1, Iterations: %2</source>
<translation>%1%2</translation>
</message>
<message>
<source>=== LM AUTOMATIC FITTING STOPPED BY USER ===</source>
@ -4157,6 +4165,26 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We
<source>Stop</source>
<translation></translation>
</message>
<message>
<source>Stopping...</source>
<translation></translation>
</message>
<message>
<source>Finalizing...</source>
<translation>...</translation>
</message>
<message>
<source>Generating the final result with the current best parameters...</source>
<translation>使...</translation>
</message>
<message>
<source>Finalizing</source>
<translation></translation>
</message>
<message>
<source>The final result is being generated. Please wait for the calculation to finish.</source>
<translation></translation>
</message>
<message>
<source>PSO auto fitting started</source>
<translation></translation>

@ -11,8 +11,6 @@
#include "nmCalculation_global.h"
class nmDataWellBase;
// 双对数曲线误差分解。total 是 LM 候选接受和排序的唯一依据,
// 其余诊断量用于有限差分灵敏度分析和信赖域选参。
struct AutoFitObjectiveBreakdownLM {
@ -92,6 +90,8 @@ signals:
QVector<QVector<double> > bestData,
int iteration,
double fitness);
/** @brief 优化迭代结束,开始用当前最优参数生成正式结果。 */
void finalizingStarted();
void logMessageGenerated(const QString& message);
private:
@ -115,7 +115,6 @@ private:
void applyParametersToDataManager(const QVector<double>& parameters);
void updateReservoirParameters(const QVector<double>& parameters);
void updateWellParameters(const QVector<double>& parameters);
void updateWellToDataManager(nmDataWellBase* pWell);
QVector<QVector<double> > runSolver();
QVector<QVector<double> > runSolverDll();
@ -152,6 +151,7 @@ private:
private:
bool m_isRunning;
bool m_shouldStop;
bool m_isFinalizing;
int m_currentIteration;
QString m_lastError;
@ -165,8 +165,8 @@ private:
QVector<QVector<double> > m_userInitialLogLogData;
AutoFitObjectiveBreakdownLM m_userInitialObjectiveBreakdown;
// 参数索引0 k1 skin2 wellboreC3 phi4 h5 Ct
// 6 Cf7 Swi8 Dfc9 fractureHalfLength。
// 参数索引0 k1 skin2 wellboreC3 phi4 Swi
// 5 Dfc6 fractureHalfLength。
QVector<bool> m_parameterSelected;
QVector<double> m_parameterLower;
QVector<double> m_parameterUpper;

@ -14,14 +14,13 @@
// 前向声明
class nmCalculationDllPebiSolverTask;
class nmDataWellBase;
class QTimer;
class QProcess;
// PSO粒子结构
// 这里的 position / velocity / bestPosition 只保存“用户勾选参与拟合的参数”,
// 不是完整的 11 个储层/井筒参数。完整参数向量会在写 trace 或调用代理模型时
// 拟合参数为 k/skin/wellboreC/phi/Swi。包含固定储层输入的物理参数向量在写 trace 或调用代理模型时
// 通过 buildTraceParameterVector() 重新组装。
//
// surrogate* 和 screeningDecision 是 PSO 加速筛选的辅助字段。真实 pbest / gbest
@ -120,6 +119,8 @@ signals:
QVector<QVector<double> > bestData,
int iteration,
double fitness);
/** @brief 优化迭代结束,开始用当前最优参数生成正式结果。 */
void finalizingStarted();
signals:
@ -160,12 +161,13 @@ private:
void applyParametersToDataManager(const QVector<double>& parameters);
void updateReservoirParameters(const QVector<double>& parameters);
void updateWellParameters(const QVector<double>& parameters);
void updateWellToDataManager(nmDataWellBase* pWell);
// ===== 求解器相关 =====
QVector<QVector<double> > runSolver();
QVector<QVector<double>> runSolverDll();
QVector<QVector<double>> runSolverExe();
/** @brief 使用最终最优参数执行完整求解并提交正式结果快照。 */
bool runFinalFullSolver();
// ===== 数据处理 =====
QVector<QPointF> interpolateData(const QVector<QPointF>& source,
@ -274,6 +276,7 @@ private:
// ===== 运行状态 =====
bool m_isRunning; // 当前是否有一次自动拟合正在运行。
bool m_shouldStop; // 用户停止标志;主循环和求解器等待循环会定期检查它。
bool m_isFinalizing; // 正在用最优参数生成正式结果,此阶段不再接受停止请求。
bool m_isPaused; // 预留暂停标志;主循环中有暂停等待逻辑。
int m_currentIteration; // 当前 PSO 迭代序号,从 0 开始。
QString m_lastError; // 最近一次失败原因,供 UI 展示或日志排查。
@ -292,13 +295,12 @@ private:
//
// 参数索引约定:
// 0 k 渗透率1 skin 表皮系数2 wellboreC 井筒储集;
// 3 phi 孔隙度4 h 储层厚度5 Ct 综合压缩系数;
// 6 Cf 岩石压缩系数7 Swi 初始含水饱和度。
// 3 phi 孔隙度4 Swi 初始含水饱和度。
// m_enabledParamIndices 保存被用户勾选的参数索引,粒子的 position 维度与它一致。
QVector<bool> m_parameterSelected; // 完整 8 个参数是否被用户勾选参与拟合
QVector<double> m_parameterLower; // 完整 8 个参数的搜索下界。
QVector<double> m_parameterUpper; // 完整 8 个参数的搜索上界。
QVector<int> m_enabledParamIndices; // 被勾选参数在完整 8 维体系中的索引。
QVector<bool> m_parameterSelected; // 0 k1 skin2 wellboreC3 phi4 Swi
QVector<double> m_parameterLower; // 5 个拟合参数的搜索下界。
QVector<double> m_parameterUpper; // 5 个拟合参数的搜索上界。
QVector<int> m_enabledParamIndices; // 被勾选参数在 5 维拟合参数中的索引。
QVector<QVector<double> > m_targetLogLogData; // 目标井 history log-log 曲线time/pressure/derivative。
QString m_targetWellName; // 目标井名称;读写井参数和读取模拟曲线都依赖它。

@ -47,27 +47,6 @@ public:
nmDataAttribute& getPorosityMin();
void setPorosityMin(const nmDataAttribute& porosityMin);
// Getter and Setter for thicknessMax
nmDataAttribute& getThicknessMax();
void setThicknessMax(const nmDataAttribute& thicknessMax);
// Getter and Setter for thicknessMin
nmDataAttribute& getThicknessMin();
void setThicknessMin(const nmDataAttribute& thicknessMin);
// Getter and Setter for ctMax
nmDataAttribute& getCtMax();
void setCtMax(const nmDataAttribute& ctMax);
// Getter and Setter for ctMin
nmDataAttribute& getCtMin();
void setCtMin(const nmDataAttribute& ctMin);
// Getter and Setter for cfMax
nmDataAttribute& getCfMax();
void setCfMax(const nmDataAttribute& cfMax);
// Getter and Setter for cfMin
nmDataAttribute& getCfMin();
void setCfMin(const nmDataAttribute& cfMin);
// Getter and Setter for swiMax
nmDataAttribute& getSwiMax();
void setSwiMax(const nmDataAttribute& swiMax);
@ -116,15 +95,6 @@ public:
bool getPorositySelected() const;
void setPorositySelected(bool selected);
bool getThicknessSelected() const;
void setThicknessSelected(bool selected);
bool getCtSelected() const;
void setCtSelected(bool selected);
bool getCfSelected() const;
void setCfSelected(bool selected);
bool getSwiSelected() const;
void setSwiSelected(bool selected);
@ -140,9 +110,6 @@ private:
nmDataAttribute m_skinMax;
nmDataAttribute m_wellboreStorageMax;
nmDataAttribute m_porosityMax;
nmDataAttribute m_thicknessMax;
nmDataAttribute m_ctMax;
nmDataAttribute m_cfMax;
nmDataAttribute m_swiMax;
nmDataAttribute m_fractureConductivityMax;
nmDataAttribute m_fractureHalfLengthMax;
@ -152,9 +119,6 @@ private:
nmDataAttribute m_skinMin;
nmDataAttribute m_wellboreStorageMin;
nmDataAttribute m_porosityMin;
nmDataAttribute m_thicknessMin;
nmDataAttribute m_ctMin;
nmDataAttribute m_cfMin;
nmDataAttribute m_swiMin;
nmDataAttribute m_fractureConductivityMin;
nmDataAttribute m_fractureHalfLengthMin;
@ -170,9 +134,6 @@ private:
bool m_skinSelected; // 是否选择表皮系数进行拟合
bool m_wellboreStorageSelected; // 是否选择井筒储集系数进行拟合
bool m_porositySelected; // 是否选择孔隙度进行拟合
bool m_thicknessSelected; // 是否选择储层厚度进行拟合
bool m_ctSelected; // 是否选择综合压缩系数进行拟合
bool m_cfSelected; // 是否选择岩石压缩系数进行拟合
bool m_swiSelected; // 是否选择初始含水饱和度进行拟合
bool m_fractureConductivitySelected; // 是否选择裂缝导流能力进行拟合
bool m_fractureHalfLengthSelected; // 是否选择裂缝半长进行拟合

@ -121,6 +121,10 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit {
// 根据分析数据绘制对应的地图信息
void updateMapByDataManager(nmDataAnalyzeManager* pDataManager);
/** @brief 绑定当前数值面板所属的数据管理器和成果窗口。 */
void bindPebiResultRefreshContext(nmDataAnalyzeManager* pDataManager,
iSubWndFitting* pSubWndFitting);
signals:
void sigFreshRnStates(QStringList list);
void sigShowProgressAbout(bool);
@ -159,6 +163,8 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit {
/** @brief 当前分析的建网提交结束后,恢复待启动或待捕获的求解流程。 */
void onPebiGridGenerationFinished(quint64 nGridInputRevision,
bool bSucceeded);
/** @brief 正式结果快照发布后刷新当前分析的曲线和结果页面。 */
void onPebiResultSnapshotChanged();
/** @brief 按快照井 UUID 切换结果曲线,不改变实时井或参数编辑井。 */
void onWellSelected(const QString& sWellInstanceId);
@ -313,6 +319,12 @@ private:
int m_nPendingSolverTimeMs; ///< 待显示的求解耗时。
bool m_bSolverStartPending; ///< 进度框已显示,输入正分批捕获或任务等待启动。
bool m_bSolverCancelRequested; ///< 已向当前任务发送停止请求,忽略后续阶段信号。
/** @brief 数值面板固定绑定的数据管理器,自动拟合刷新不依赖当前活动页签。 */
QPointer<nmDataAnalyzeManager> m_pResultRefreshDataManager;
/** @brief 数值面板固定绑定的成果窗口。 */
QPointer<iSubWndFitting> m_pResultRefreshFitting;
QString m_sObservedPebiResultSnapshotId; ///< 最近已经交给界面处理的快照 UUID。
bool m_bRefreshingPebiResultSnapshot; ///< 防止结果井校正信号造成刷新重入。
#ifdef QT_DEBUG
// 示例演示增加Dock并且追加至程序主窗口

@ -167,6 +167,7 @@ public:
// 统一装配新建、加载分析共用的数值设计与参数属性Dock控件
static void setupNumericalDockWidgets(nmDataAnalyzeManager* pDataManager,
nmSubWndMain* pSubWndMain,
iSubWndFitting* pSubWndFitting,
iDockBaseWx* pWxDockNm1,
iDockBaseWx* pWxDockNm2);

@ -40,6 +40,7 @@ public:
void onAccept();
void onReject();
void onWellSelected(int index);
void onAlgorithmChanged(int index);
void onParameterTableItemChanged(QTableWidgetItem* item);
// 自动拟合相关槽函数
@ -81,6 +82,7 @@ private:
QLineEdit* m_errorLimitEdit;
QComboBox* m_targetWellCombo;
QComboBox* m_algorithmCombo;
QLabel* m_surrogateLabel;
QComboBox* m_surrogateCombo;
// 参数复选框
@ -88,9 +90,6 @@ private:
QCheckBox* m_sCheckBox; // 表皮系数
QCheckBox* m_cCheckBox; // 井筒储集系数
QCheckBox* m_phiCheckBox; // 孔隙度
QCheckBox* m_hCheckBox; // 储层厚度
QCheckBox* m_ctCheckBox; // 综合压缩系数
QCheckBox* m_cfCheckBox; // 岩石压缩系数
QCheckBox* m_swiCheckBox; // 初始含水饱和度
QCheckBox* m_dfcCheckBox; // 裂缝导流能力
QCheckBox* m_fractureHalfLengthCheckBox; // 裂缝半长

@ -93,6 +93,7 @@ public:
public slots:
void onFittingProgress(int iteration, double fitness);
void onFittingFinished(bool success, const QString& message);
void onFinalizingStarted();
void onStopButtonClicked();
void onLogMessageReceived(const QString& message);
void onBestCurveUpdated(QVector<QVector<double> > targetData,
@ -163,6 +164,7 @@ private:
QString m_wellName;
QStringList m_selectedParameters;
bool m_isFinished;
bool m_isFinalizing;
double m_bestFitnessEver;
QDateTime m_startTime;

@ -3,7 +3,6 @@
#include "nmCalculationUtils.h"
#include "nmDataAnalyzeManager.h"
#include "nmDataWellBase.h"
#include "nmDataVerticalWell.h"
#include "nmDataVerticalFracturedWell.h"
#include "nmDataHorizontalFracturedWell.h"
#include "nmDataReservoir.h"
@ -27,11 +26,7 @@
#define DEBUG_OUT(msg) OutputDebugStringA(QString("[AutoFitLM] %1\n").arg(msg).toLocal8Bit().data())
#endif
#ifdef QT_NO_DEBUG
static const bool kAutoFitDiagnosticTraceEnabled = false;
#else
static const bool kAutoFitDiagnosticTraceEnabled = true;
#endif
static inline bool isFiniteNumber(double value)
{
@ -134,11 +129,11 @@ static QString jsonStringArray(const QStringList& values)
}
// 信赖域搜索统一在 [0, 1] 内部坐标工作。正值参数使用对数坐标,使内部相同步长
// 表示近似相同的相对变化,避免 k、C、Ct、Cf 等跨数量级参数被线性尺度支配;
// 表示近似相同的相对变化,避免 k、C、Dfc 等跨数量级参数被线性尺度支配;
// skin 可为负数、Swi 的物理意义是线性比例,因此二者保持有界线性坐标。
static bool useTrustRegionLogScale(int parameterIndex, double lower, double upper)
{
return parameterIndex != 1 && parameterIndex != 7 &&
return parameterIndex != 1 && parameterIndex != 4 &&
lower > 0.0 && upper > lower;
}
@ -289,7 +284,7 @@ static int trustRegionDominantComponent(
}
// 求解选中参数对应的阻尼正规方程。上下和左右诊断量保留方向;形状没有
// 天然正负,因此使用 shapeLoss 对参数的局部导数。参数最多维,使用带
// 天然正负,因此使用 shapeLoss 对参数的局部导数。参数最多维,使用带
// 部分主元的高斯消元即可处理该小矩阵,并在主元退化时明确返回失败。
static bool solveTrustRegionLinearSystem(
QVector<QVector<double> > matrix,
@ -458,9 +453,6 @@ static QStringList traceParameterNames()
<< "skin"
<< "wellboreC"
<< "phi"
<< "h"
<< "Ct"
<< "Cf"
<< "Swi"
<< "Dfc"
<< "fractureHalfLength";
@ -471,6 +463,7 @@ nmCalculationAutoFitLM::nmCalculationAutoFitLM(QObject* parent)
: QObject(parent)
, m_isRunning(false)
, m_shouldStop(false)
, m_isFinalizing(false)
, m_currentIteration(0)
, m_globalBestFitness(1e10)
, m_maxIterations(100)
@ -569,22 +562,19 @@ void nmCalculationAutoFitLM::setTargetLogLogData(const QVector<QVector<double> >
void nmCalculationAutoFitLM::stopFitting()
{
if(m_isFinalizing) {
emit logMessageGenerated(
tr("The final full-field calculation is already running"));
return;
}
// 用户点击停止时只设置请求标志,让 LM 主循环和求解器等待逻辑自然退出。
if(m_isRunning) {
emit logMessageGenerated(tr("=== User Stop Request Received ==="));
emit logMessageGenerated(tr("Gracefully stopping LM automatic fitting..."));
m_shouldStop = true;
// 给当前评价一个短暂的自然退出时间。若仍在运行,
// runSolverDll() 会在下一个等待周期检查 m_shouldStop 并结束任务。
int waitCount = 0;
while(m_evaluationInProgress > 0 && waitCount < 30) {
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 50);
msleep(50);
waitCount++;
}
// 此槽由求解等待循环派发,必须立即返回,外层循环才能转发取消请求。
if(m_evaluationInProgress > 0) {
emit logMessageGenerated(tr("Waiting for current solver evaluation to stop..."));
}
@ -660,6 +650,7 @@ void nmCalculationAutoFitLM::resetOptimizer()
m_userInitialFitness = 1e10;
m_hasValidUserSolution = false;
m_traceMetaFilePath.clear();
m_isFinalizing = false;
DEBUG_OUT("LM optimizer reset");
}
@ -680,7 +671,7 @@ void nmCalculationAutoFitLM::initializeTraceFile()
return;
}
// 诊断轨迹仅在 Debug 构建中启用,并与源码、安装目录完全分离。
// 诊断轨迹在所有构建中启用,并与源码、安装目录完全分离。
if(m_traceRunId.isEmpty()) {
m_traceRunId = QString("%1-%2")
.arg(QString::number(QCoreApplication::applicationPid()))
@ -740,9 +731,6 @@ void nmCalculationAutoFitLM::writeTraceHeader()
<< "skin"
<< "wellboreC"
<< "phi"
<< "h"
<< "Ct"
<< "Cf"
<< "Swi"
<< "Dfc"
<< "fractureHalfLength"
@ -801,7 +789,7 @@ void nmCalculationAutoFitLM::writeTraceMetaFile()
QTextStream out(&metaFile);
out << "{\n";
out << " \"schema_version\": 1,\n";
out << " \"schema_version\": 2,\n";
out << " \"trace_type\": \"finite_difference_lm_trust_region\",\n";
out << " \"run_id\": " << jsonEscape(m_traceRunId) << ",\n";
out << " \"created_at\": "
@ -858,7 +846,7 @@ void nmCalculationAutoFitLM::writeTraceRow(
<< QString::number(iteration)
<< QString::number(parameterIndex)
<< csvEscape(phase);
for(int i = 0; i < 10; ++i) {
for(int i = 0; i < 7; ++i) {
cols << traceParamAt(fullParams, i);
}
cols << traceNumber(solverObjective)
@ -916,10 +904,10 @@ void nmCalculationAutoFitLM::emitRunSummary(bool success, StopReasonLM finalReas
QVector<double> nmCalculationAutoFitLM::buildTraceParameterVector(const QVector<double>& selectedParameters) const
{
// 将 LM 内部使用的“启用参数向量”还原成完整 10 维参数向量。
// 将 LM 内部使用的“启用参数向量”还原成完整 7 维参数向量。
// 未启用的参数从当前 DataManager 读取,启用的参数用 selectedParameters 覆盖。
// trace CSV 和 meta 使用该完整向量记录一次候选评价。
QVector<double> fullParams(10, 0.0);
QVector<double> fullParams(7, 0.0);
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
@ -927,10 +915,7 @@ QVector<double> nmCalculationAutoFitLM::buildTraceParameterVector(const QVector<
nmDataReservoir reservoirData = dataManager->getReservoirDataCopy();
fullParams[0] = reservoirData.getPermeability().getValue().toDouble();
fullParams[3] = reservoirData.getPorosity().getValue().toDouble();
fullParams[4] = reservoirData.getThickness().getValue().toDouble();
fullParams[5] = reservoirData.getCt().getValue().toDouble();
fullParams[6] = reservoirData.getCf().getValue().toDouble();
fullParams[7] = reservoirData.getSwi().getValue().toDouble();
fullParams[4] = reservoirData.getSwi().getValue().toDouble();
nmDataWellBase* pTargetWell = dataManager->findWellByName(m_targetWellName);
@ -946,15 +931,15 @@ QVector<double> nmCalculationAutoFitLM::buildTraceParameterVector(const QVector<
nmDataVerticalFracturedWell* fracturedWell =
dynamic_cast<nmDataVerticalFracturedWell*>(pTargetWell);
if(fracturedWell) {
fullParams[8] = fracturedWell->getDfc().getValue().toDouble();
fullParams[9] = fracturedWell->getFractureHalfLength().getValue().toDouble();
fullParams[5] = fracturedWell->getDfc().getValue().toDouble();
fullParams[6] = fracturedWell->getFractureHalfLength().getValue().toDouble();
}
} else if(pTargetWell->getWellType() == NM_WELL_MODEL::Horizontal_Fractured_Well) {
nmDataHorizontalFracturedWell* fracturedWell =
dynamic_cast<nmDataHorizontalFracturedWell*>(pTargetWell);
if(fracturedWell) {
fullParams[8] = fracturedWell->getDfc().getValue().toDouble();
fullParams[9] = fracturedWell->getFractureHalfLength().getValue().toDouble();
fullParams[5] = fracturedWell->getDfc().getValue().toDouble();
fullParams[6] = fracturedWell->getFractureHalfLength().getValue().toDouble();
}
}
}
@ -1005,28 +990,25 @@ void nmCalculationAutoFitLM::loadParameterBounds()
// 读取用户勾选的拟合参数及上下界。
//
// 这里构建三个核心数组:
// - m_parameterSelected[10]:完整参数体系中每个参数是否参与拟合;
// - m_parameterLower/Upper[10]:完整参数体系的搜索上下界;
// - m_parameterSelected[7]:完整参数体系中每个参数是否参与拟合;
// - m_parameterLower/Upper[7]:完整参数体系的搜索上下界;
// - m_enabledParamIndices把粒子内部紧凑向量映射回完整参数索引。
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
nmDataAutomaticFitting fittingData = dataManager->getAutomaticFittingDataCopy();
// 获取参数选择状态
m_parameterSelected.resize(10);
m_parameterSelected.resize(7);
m_parameterSelected[0] = fittingData.getPermeabilitySelected();
m_parameterSelected[1] = fittingData.getSkinSelected();
m_parameterSelected[2] = fittingData.getWellboreStorageSelected();
m_parameterSelected[3] = fittingData.getPorositySelected();
m_parameterSelected[4] = fittingData.getThicknessSelected();
m_parameterSelected[5] = fittingData.getCtSelected();
m_parameterSelected[6] = fittingData.getCfSelected();
m_parameterSelected[7] = fittingData.getSwiSelected();
m_parameterSelected[8] = fittingData.getFractureConductivitySelected();
m_parameterSelected[9] = fittingData.getFractureHalfLengthSelected();
m_parameterSelected[4] = fittingData.getSwiSelected();
m_parameterSelected[5] = fittingData.getFractureConductivitySelected();
m_parameterSelected[6] = fittingData.getFractureHalfLengthSelected();
// 获取参数边界
m_parameterLower.resize(10);
m_parameterUpper.resize(10);
m_parameterLower.resize(7);
m_parameterUpper.resize(7);
m_parameterLower[0] = fittingData.getPermeabilityMin().getValue().toDouble();
m_parameterUpper[0] = fittingData.getPermeabilityMax().getValue().toDouble();
@ -1040,23 +1022,14 @@ void nmCalculationAutoFitLM::loadParameterBounds()
m_parameterLower[3] = fittingData.getPorosityMin().getValue().toDouble();
m_parameterUpper[3] = fittingData.getPorosityMax().getValue().toDouble();
m_parameterLower[4] = fittingData.getThicknessMin().getValue().toDouble();
m_parameterUpper[4] = fittingData.getThicknessMax().getValue().toDouble();
m_parameterLower[5] = fittingData.getCtMin().getValue().toDouble();
m_parameterUpper[5] = fittingData.getCtMax().getValue().toDouble();
m_parameterLower[6] = fittingData.getCfMin().getValue().toDouble();
m_parameterUpper[6] = fittingData.getCfMax().getValue().toDouble();
m_parameterLower[7] = fittingData.getSwiMin().getValue().toDouble();
m_parameterUpper[7] = fittingData.getSwiMax().getValue().toDouble();
m_parameterLower[4] = fittingData.getSwiMin().getValue().toDouble();
m_parameterUpper[4] = fittingData.getSwiMax().getValue().toDouble();
m_parameterLower[8] = fittingData.getFractureConductivityMin().getValue().toDouble();
m_parameterUpper[8] = fittingData.getFractureConductivityMax().getValue().toDouble();
m_parameterLower[5] = fittingData.getFractureConductivityMin().getValue().toDouble();
m_parameterUpper[5] = fittingData.getFractureConductivityMax().getValue().toDouble();
m_parameterLower[9] = fittingData.getFractureHalfLengthMin().getValue().toDouble();
m_parameterUpper[9] = fittingData.getFractureHalfLengthMax().getValue().toDouble();
m_parameterLower[6] = fittingData.getFractureHalfLengthMin().getValue().toDouble();
m_parameterUpper[6] = fittingData.getFractureHalfLengthMax().getValue().toDouble();
// 更新启用参数索引
m_enabledParamIndices.clear();
@ -1133,6 +1106,7 @@ bool nmCalculationAutoFitLM::startAutoFitting()
}
m_isRunning = true;
m_shouldStop = false;
m_isFinalizing = false;
m_currentIteration = 0;
m_consecutiveFailures = 0;
m_initialValues = savedInitialValues;
@ -1214,7 +1188,8 @@ bool nmCalculationAutoFitLM::startAutoFitting()
&m_globalBestObjectiveBreakdown);
}
if(m_globalBestFitness < m_targetError) {
if(finalReason != LM_USER_STOPPED &&
m_globalBestFitness < m_targetError) {
finalReason = LM_TARGET_ACHIEVED;
}
} catch(const std::exception& e) {
@ -1222,6 +1197,7 @@ bool nmCalculationAutoFitLM::startAutoFitting()
emit logMessageGenerated(tr("CRITICAL ERROR: %1").arg(e.what()));
closeTraceFile();
cleanupTemporaryDirectory();
m_isFinalizing = false;
m_isRunning = false;
emit fittingFinished(false, m_lastError);
return false;
@ -1230,23 +1206,28 @@ bool nmCalculationAutoFitLM::startAutoFitting()
emit logMessageGenerated(tr("CRITICAL ERROR: Unknown exception in automatic fitting"));
closeTraceFile();
cleanupTemporaryDirectory();
m_isFinalizing = false;
m_isRunning = false;
emit fittingFinished(false, m_lastError);
return false;
}
bool finalFullSolverSucceeded = true;
// 只有最终完整求解实际执行并提交快照后才能置为成功。
bool finalFullSolverSucceeded = false;
bool finalFullSolverExecuted = false;
if(!m_globalBestPosition.isEmpty()) {
try {
// Stop 只结束优化迭代;从这里开始必须用当前最优参数生成并发布正式快照。
m_isFinalizing = true;
emit finalizingStarted();
emit logMessageGenerated(tr("Applying optimized parameters to model..."));
applyParametersToDataManager(m_globalBestPosition);
// 裂缝参数会改变网格输入;标记失效后,最终求解任务会基于新快照重建网格。
const bool fractureGridParameterSelected =
(m_parameterSelected.size() > 8 && m_parameterSelected[8]) ||
(m_parameterSelected.size() > 9 && m_parameterSelected[9]);
(m_parameterSelected.size() > 5 && m_parameterSelected[5]) ||
(m_parameterSelected.size() > 6 && m_parameterSelected[6]);
if(fractureGridParameterSelected) {
nmDataAnalyzeManager* dataManager =
nmDataAnalyzeManager::getCurrentInstance();
@ -1256,29 +1237,19 @@ bool nmCalculationAutoFitLM::startAutoFitting()
dataManager->invalidatePebiGrid();
}
if(m_shouldStop) {
emit logMessageGenerated(
tr("Running final full-field calculation with optimized parameters..."));
finalFullSolverExecuted = true;
finalFullSolverSucceeded = runFinalFullSolver();
if(finalFullSolverSucceeded) {
emit logMessageGenerated(
tr("Final full-field calculation skipped after user stop"));
tr("Final full-field calculation completed successfully"));
} else {
m_lastError =
tr("Optimized parameters were found, but the final full-field calculation failed");
emit logMessageGenerated(
tr("Running final full-field calculation with optimized parameters..."));
finalFullSolverExecuted = true;
finalFullSolverSucceeded = runFinalFullSolver();
if(finalFullSolverSucceeded) {
emit logMessageGenerated(
tr("Final full-field calculation completed successfully"));
} else if(m_shouldStop) {
finalFullSolverExecuted = false;
finalFullSolverSucceeded = true;
emit logMessageGenerated(
tr("Final full-field calculation stopped by user"));
} else {
m_lastError =
tr("Optimized parameters were found, but the final full-field calculation failed");
emit logMessageGenerated(
tr("ERROR: Final full-field calculation failed"));
}
tr("ERROR: Final full-field calculation failed"));
}
saveOptimizationResult();
@ -1318,6 +1289,7 @@ bool nmCalculationAutoFitLM::startAutoFitting()
}
}
m_isFinalizing = false;
m_isRunning = false;
bool success = false;
QString message;
@ -1350,7 +1322,7 @@ bool nmCalculationAutoFitLM::startAutoFitting()
emit logMessageGenerated(tr("=== LM AUTOMATIC FITTING - MAX ITERATIONS ==="));
} else if(finalReason == LM_USER_STOPPED) {
success = true;
message = QString(tr("Best error: %1, Iterations: %2"))
message = QString(tr("Stopped by user. Best error: %1, Iterations: %2"))
.arg(m_globalBestFitness, 0, 'e', 4)
.arg(m_currentIteration + 1);
emit logMessageGenerated(tr("=== LM AUTOMATIC FITTING STOPPED BY USER ==="));
@ -1425,23 +1397,11 @@ void nmCalculationAutoFitLM::extractUserInitialValues()
initialValue = reservoirData.getPorosity().getValue().toDouble();
break;
case 4: // 储层厚度
initialValue = reservoirData.getThickness().getValue().toDouble();
break;
case 5: // 综合压缩系数
initialValue = reservoirData.getCt().getValue().toDouble();
break;
case 6: // 岩石压缩系数
initialValue = reservoirData.getCf().getValue().toDouble();
break;
case 7: // 初始含水饱和度
case 4: // 初始含水饱和度
initialValue = reservoirData.getSwi().getValue().toDouble();
break;
case 8: // 裂缝导流能力
case 5: // 裂缝导流能力
if(pTargetWell && pTargetWell->getWellType() == NM_WELL_MODEL::Vertical_Fractured_Well) {
nmDataVerticalFracturedWell* fracturedWell =
dynamic_cast<nmDataVerticalFracturedWell*>(pTargetWell);
@ -1457,7 +1417,7 @@ void nmCalculationAutoFitLM::extractUserInitialValues()
}
break;
case 9: // 裂缝半长
case 6: // 裂缝半长
if(pTargetWell && pTargetWell->getWellType() == NM_WELL_MODEL::Vertical_Fractured_Well) {
nmDataVerticalFracturedWell* fracturedWell =
dynamic_cast<nmDataVerticalFracturedWell*>(pTargetWell);
@ -2551,8 +2511,8 @@ double nmCalculationAutoFitLM::evaluateFitness(const QVector<double>& parameters
// Dfc 和裂缝半长属于网格输入。标记网格失效,使下一次任务基于当前参数快照重建。
const bool fractureGridParameterSelected =
(m_parameterSelected.size() > 8 && m_parameterSelected[8]) ||
(m_parameterSelected.size() > 9 && m_parameterSelected[9]);
(m_parameterSelected.size() > 5 && m_parameterSelected[5]) ||
(m_parameterSelected.size() > 6 && m_parameterSelected[6]);
if(fractureGridParameterSelected) {
dataManager->invalidatePebiGrid();
}
@ -2719,7 +2679,7 @@ void nmCalculationAutoFitLM::updateReservoirParameters(const QVector<double>& pa
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
nmDataReservoir reservoirData = dataManager->getReservoirDataCopy();
// paramIndex 是粒子 position 中的索引i 是完整 10 个参数体系中的索引。
// paramIndex 是粒子 position 中的索引i 是完整 7 个参数体系中的索引。
// 只有 m_parameterSelected[i] 为 true 时,才从 parameters 中消费一个值。
int paramIndex = 0;
@ -2736,19 +2696,7 @@ void nmCalculationAutoFitLM::updateReservoirParameters(const QVector<double>& pa
reservoirData.getPorosity().setValue(value);
break;
case 4: // 储层厚度
reservoirData.getThickness().setValue(value);
break;
case 5: // 综合压缩系数
reservoirData.getCt().setValue(value);
break;
case 6: // 岩石压缩系数
reservoirData.getCf().setValue(value);
break;
case 7: // 初始含水饱和度
case 4: // 初始含水饱和度
reservoirData.getSwi().setValue(value);
break;
}
@ -2776,7 +2724,34 @@ void nmCalculationAutoFitLM::updateWellParameters(const QVector<double>& paramet
if(!pWell) return;
//nmDataWellBase* pWell = wells[0]; // 使用第一口井
// 先在参数副本中组装本次候选值,全部解析完成后再写回现有井对象。
// 这样不会触发整井赋值,也不会删除并重建井内已有的射孔对象。
nmDataPerforation* pPerforation = pWell->getPerforation(0);
nmDataAttribute skinAttr;
bool updateSkin = false;
if(pPerforation) {
skinAttr = pPerforation->getSkin();
}
nmDataAttribute wellboreAttr = pWell->getWellboreStorage();
bool updateWellboreStorage = false;
nmDataVerticalFracturedWell* pVerticalFracturedWell =
dynamic_cast<nmDataVerticalFracturedWell*>(pWell);
nmDataHorizontalFracturedWell* pHorizontalFracturedWell =
dynamic_cast<nmDataHorizontalFracturedWell*>(pWell);
nmDataAttribute dfcAttr;
nmDataAttribute fractureHalfLengthAttr;
if(pVerticalFracturedWell) {
dfcAttr = pVerticalFracturedWell->getDfc();
fractureHalfLengthAttr = pVerticalFracturedWell->getFractureHalfLength();
} else if(pHorizontalFracturedWell) {
dfcAttr = pHorizontalFracturedWell->getDfc();
fractureHalfLengthAttr = pHorizontalFracturedWell->getFractureHalfLength();
}
bool updateDfc = false;
bool updateFractureHalfLength = false;
int paramIndex = 0;
@ -2786,58 +2761,31 @@ void nmCalculationAutoFitLM::updateWellParameters(const QVector<double>& paramet
switch(i) {
case 1: { // 表皮系数
nmDataPerforation* perf = pWell->getPerforation(0);
if(perf) {
nmDataAttribute skinAttr = perf->getSkin();
if(pPerforation) {
skinAttr.setValue(value);
perf->setSkin(skinAttr);
updateSkin = true;
}
}
break;
case 2: { // 井筒储集系数
nmDataAttribute wellboreAttr = pWell->getWellboreStorage();
wellboreAttr.setValue(value);
pWell->setWellboreStorage(wellboreAttr);
updateWellboreStorage = true;
}
break;
case 8: { // 裂缝导流能力
if(pWell->getWellType() == NM_WELL_MODEL::Vertical_Fractured_Well) {
nmDataVerticalFracturedWell* fracturedWell =
dynamic_cast<nmDataVerticalFracturedWell*>(pWell);
if(fracturedWell) {
nmDataAttribute dfc = fracturedWell->getDfc();
dfc.setValue(value);
fracturedWell->setDfc(dfc);
}
} else if(pWell->getWellType() == NM_WELL_MODEL::Horizontal_Fractured_Well) {
nmDataHorizontalFracturedWell* fracturedWell =
dynamic_cast<nmDataHorizontalFracturedWell*>(pWell);
if(fracturedWell) {
nmDataAttribute dfc = fracturedWell->getDfc();
dfc.setValue(value);
fracturedWell->setDfc(dfc);
}
case 5: { // 裂缝导流能力
if(pVerticalFracturedWell || pHorizontalFracturedWell) {
dfcAttr.setValue(value);
updateDfc = true;
}
}
break;
case 9: { // 裂缝半长
// 直接修改井对象中的属性,复用已有信号重算裂缝端点。
if(pWell->getWellType() == NM_WELL_MODEL::Vertical_Fractured_Well) {
nmDataVerticalFracturedWell* fracturedWell =
dynamic_cast<nmDataVerticalFracturedWell*>(pWell);
if(fracturedWell) {
fracturedWell->getFractureHalfLength().setValue(value);
}
} else if(pWell->getWellType() == NM_WELL_MODEL::Horizontal_Fractured_Well) {
nmDataHorizontalFracturedWell* fracturedWell =
dynamic_cast<nmDataHorizontalFracturedWell*>(pWell);
if(fracturedWell) {
fracturedWell->getFractureHalfLength().setValue(value);
}
case 6: { // 裂缝半长
if(pVerticalFracturedWell || pHorizontalFracturedWell) {
fractureHalfLengthAttr.setValue(value);
updateFractureHalfLength = true;
}
}
break;
@ -2847,58 +2795,28 @@ void nmCalculationAutoFitLM::updateWellParameters(const QVector<double>& paramet
}
}
// 根据井类型更新到数据管理器
updateWellToDataManager(pWell);
}
void nmCalculationAutoFitLM::updateWellToDataManager(nmDataWellBase* pWell)
{
if(!pWell) return;
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
NM_WELL_MODEL wellType = pWell->getWellType();
// DataManager 内部按井型维护不同容器。修改基类指针后,需要根据实际井型
// 调用对应 update 接口,才能让后续求解器组装读到最新 skin / wellboreC。
switch(wellType) {
case NM_WELL_MODEL::Vertical_Well: {
nmDataVerticalWell* pVerticalWell = dynamic_cast<nmDataVerticalWell*>(pWell);
if(pVerticalWell) {
QVector<nmDataVerticalWell> wells;
wells.append(*pVerticalWell);
dataManager->updateVerticalWells(wells);
}
break;
}
case NM_WELL_MODEL::Vertical_Fractured_Well: {
nmDataVerticalFracturedWell* pVFracturedWell = dynamic_cast<nmDataVerticalFracturedWell*>(pWell);
if(pVFracturedWell) {
QVector<nmDataVerticalFracturedWell> wells;
wells.append(*pVFracturedWell);
dataManager->updateVerticalFracturedWells(wells);
}
break;
}
case NM_WELL_MODEL::Horizontal_Fractured_Well: {
nmDataHorizontalFracturedWell* pHFracturedWell = dynamic_cast<nmDataHorizontalFracturedWell*>(pWell);
if(pHFracturedWell) {
QVector<nmDataHorizontalFracturedWell> wells;
wells.append(*pHFracturedWell);
dataManager->updateHorizontalFracturedWells(wells);
}
break;
}
default:
break;
if(updateSkin) {
pPerforation->getSkin().setValue(skinAttr.getValue());
}
if(updateWellboreStorage) {
pWell->getWellboreStorage().setValue(wellboreAttr.getValue());
}
if(pVerticalFracturedWell) {
if(updateDfc) {
pVerticalFracturedWell->getDfc().setValue(dfcAttr.getValue());
}
if(updateFractureHalfLength) {
pVerticalFracturedWell->getFractureHalfLength().setValue(
fractureHalfLengthAttr.getValue());
}
} else if(pHorizontalFracturedWell) {
if(updateDfc) {
pHorizontalFracturedWell->getDfc().setValue(dfcAttr.getValue());
}
if(updateFractureHalfLength) {
pHorizontalFracturedWell->getFractureHalfLength().setValue(
fractureHalfLengthAttr.getValue());
}
}
}
@ -3992,7 +3910,7 @@ QVector<QVector<double>> nmCalculationAutoFitLM::runSolverDll()
// 异步执行 DLL 任务。循环等待期间持续 processEvents保证界面不会完全卡死。
dllTask->start();
// 等待完成。最大等待 1 小时,适配大模型慢算;用户停止时会 terminate
// 等待完成。最大等待 1 小时;用户停止使用任务已有的协作取消接口
const int maxWait = 3600000; // 1h超时
const int checkInterval = 50;
QTime waitTimer;
@ -4006,12 +3924,12 @@ QVector<QVector<double>> nmCalculationAutoFitLM::runSolverDll()
break;
}
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, checkInterval);
// 等待期间也派发鼠标、键盘事件,让停止按钮能及时登记请求。
QApplication::processEvents(QEventLoop::AllEvents, checkInterval);
if(m_shouldStop) {
DEBUG_OUT("DLL solver task terminated by user");
dllTask->terminate();
break;
// DLL 没有中断接口,返回后丢弃结果,避免强制终止破坏其内部锁。
dllTask->requestCancel();
}
}
@ -4030,6 +3948,13 @@ QVector<QVector<double>> nmCalculationAutoFitLM::runSolverDll()
// 线程结束后检查真实执行结果,防止失败时复用上一粒子的旧曲线。
dllTask->wait();
if(m_shouldStop) {
DEBUG_OUT("DLL solver evaluation cancelled by user");
delete dllTask;
dllTask = nullptr;
--m_evaluationInProgress;
return result;
}
if(!dllTask->wasSuccessful()) {
DEBUG_OUT("DLL solver task reported failure");
delete dllTask;
@ -4169,13 +4094,6 @@ bool nmCalculationAutoFitLM::runFinalFullSolver()
break;
}
if(m_shouldStop) {
DEBUG_OUT("Final full-field solver terminated by user");
dllTask.terminate();
dllTask.wait(2000);
--m_evaluationInProgress;
return false;
}
}
if(dllTask.isRunning()) {

@ -3,7 +3,6 @@
#include "nmCalculationUtils.h"
#include "nmDataAnalyzeManager.h"
#include "nmDataWellBase.h"
#include "nmDataVerticalWell.h"
#include "nmDataVerticalFracturedWell.h"
#include "nmDataHorizontalFracturedWell.h"
#include "nmDataReservoir.h"
@ -427,16 +426,13 @@ static QString findExecutableInPath(const QString& executableName)
static QStringList traceParameterNames()
{
// trace 和 trace meta 使用的完整参数名顺序
// 这个顺序必须与 buildTraceParameterVector() 和 m_parameterSelected 的 0-7 索引一致
// 拟合参数顺序与 m_parameterSelected 的 0-4 索引一致
// 代理输入仍使用 buildTraceParameterVector() 的物理参数顺序
QStringList names;
names << "k"
<< "skin"
<< "wellboreC"
<< "phi"
<< "h"
<< "Ct"
<< "Cf"
<< "Swi";
return names;
}
@ -448,6 +444,7 @@ nmCalculationAutoFitPSO::nmCalculationAutoFitPSO(QObject* parent)
: QObject(parent)
, m_isRunning(false)
, m_shouldStop(false)
, m_isFinalizing(false)
, m_isPaused(false)
, m_currentIteration(0)
, m_globalBestFitness(1e10)
@ -613,6 +610,12 @@ void nmCalculationAutoFitPSO::setTargetLogLogData(const QVector<QVector<double>
void nmCalculationAutoFitPSO::stopFitting()
{
if(m_isFinalizing) {
emit logMessageGenerated(
tr("The final full-field calculation is already running"));
return;
}
// 用户点击停止时走这里。停止策略是“请求式停止”:
// 先置 m_shouldStop让主循环/求解器等待逻辑自然退出;短时间内还在评价时再重置计数。
// 这样可以减少 DLL 任务被硬中断导致的数据状态残留。
@ -719,6 +722,7 @@ void nmCalculationAutoFitPSO::resetOptimizer()
m_particleStagnationHistory.clear();
m_traceMetaFilePath.clear();
m_surrogateContextFilePath.clear();
m_isFinalizing = false;
resetRunSummary();
DEBUG_OUT("Optimizer reset");
@ -972,7 +976,9 @@ bool nmCalculationAutoFitPSO::writeTraceMetaFile(const QString& filePath)
enabledNames << ((paramIndex >= 0 && paramIndex < parameterNames.size()) ? parameterNames[paramIndex] : QString::number(paramIndex));
}
QVector<double> initialFullParams = buildTraceParameterVector(m_initialValues);
QVector<double> initialPhysicalParams = buildTraceParameterVector(m_initialValues);
QVector<double> initialFullParams = initialPhysicalParams.mid(0, 4);
initialFullParams.append(initialPhysicalParams[7]); // Swi 在物理输入中的位置不变。
QVector<double> targetTime = m_targetLogLogData.size() > 0 ? m_targetLogLogData[0] : QVector<double>();
QVector<double> targetPressure = m_targetLogLogData.size() > 1 ? m_targetLogLogData[1] : QVector<double>();
QVector<double> targetDerivative = m_targetLogLogData.size() > 2 ? m_targetLogLogData[2] : QVector<double>();
@ -980,7 +986,7 @@ bool nmCalculationAutoFitPSO::writeTraceMetaFile(const QString& filePath)
QTextStream out(&metaFile);
out << "{\n";
out << " \"schema_version\": 1,\n";
out << " \"schema_version\": 2,\n";
out << " \"trace_type\": \"pso_baseline_replay_meta\",\n";
out << " \"run_id\": " << jsonEscape(m_traceRunId) << ",\n";
out << " \"created_at\": " << jsonEscape(QDateTime::currentDateTime().toString(Qt::ISODate)) << ",\n";
@ -1039,8 +1045,8 @@ bool nmCalculationAutoFitPSO::writeTraceMetaFile(const QString& filePath)
QVector<double> nmCalculationAutoFitPSO::buildTraceParameterVector(const QVector<double>& selectedParameters) const
{
// 将粒子内部使用的“启用参数向量”还原成完整 8 维参数向量
// 未启用的参数从当前 DataManager 读取,启用的参数用 selectedParameters 覆盖
// 代理输入保留 k/skin/wellboreC/phi/h/Ct/Cf/Swi 的 8 维物理参数顺序
// h/Ct/Cf 始终读取储层固定值,仅用候选值覆盖剩余拟合参数
// trace CSV、候选 CSV、代理训练域检查都需要这个完整向量。
QVector<double> fullParams(8, 0.0);
@ -1066,8 +1072,9 @@ QVector<double> nmCalculationAutoFitPSO::buildTraceParameterVector(const QVector
for(int i = 0; i < selectedParameters.size() && i < m_enabledParamIndices.size(); ++i) {
int paramIndex = m_enabledParamIndices[i];
if(paramIndex >= 0 && paramIndex < fullParams.size()) {
fullParams[paramIndex] = selectedParameters[i];
if(paramIndex >= 0 && paramIndex < m_parameterSelected.size()) {
const int physicalIndex = paramIndex == 4 ? 7 : paramIndex;
fullParams[physicalIndex] = selectedParameters[i];
}
}
@ -1986,14 +1993,9 @@ bool nmCalculationAutoFitPSO::isSurrogateRunContextSupported(QString* reason) co
return false;
}
// 参数门控:只有训练数据中 Cf 实际变化的 T2/T4 允许 Cf 参与拟合。
// 用户勾选其它参数时,代理无法可靠反映这些参数变化,直接禁用代理筛选。
// 代理支持拟合 k/skin/wellboreC/phiSwi 不在训练输入集中。
QVector<int> allowedParamIndices;
allowedParamIndices << 0 << 1 << 2 << 3 << 4;
if(parameterDomain.variableCf) {
allowedParamIndices << 6;
}
allowedParamIndices << 0 << 1 << 2 << 3;
for(int i = 0; i < m_enabledParamIndices.size(); ++i) {
if(!allowedParamIndices.contains(m_enabledParamIndices[i])) {
@ -2707,26 +2709,23 @@ void nmCalculationAutoFitPSO::loadParameterBounds()
// 读取用户勾选的拟合参数及上下界。
//
// 这里构建三个核心数组:
// - m_parameterSelected[8]:完整参数体系中每个参数是否参与拟合;
// - m_parameterLower/Upper[8]:完整参数体系的搜索上下界;
// - m_parameterSelected[5]:完整参数体系中每个参数是否参与拟合;
// - m_parameterLower/Upper[5]:完整参数体系的搜索上下界;
// - m_enabledParamIndices把粒子内部紧凑向量映射回完整参数索引。
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
nmDataAutomaticFitting fittingData = dataManager->getAutomaticFittingDataCopy();
// 获取参数选择状态
m_parameterSelected.resize(8);
m_parameterSelected.resize(5);
m_parameterSelected[0] = fittingData.getPermeabilitySelected();
m_parameterSelected[1] = fittingData.getSkinSelected();
m_parameterSelected[2] = fittingData.getWellboreStorageSelected();
m_parameterSelected[3] = fittingData.getPorositySelected();
m_parameterSelected[4] = fittingData.getThicknessSelected();
m_parameterSelected[5] = fittingData.getCtSelected();
m_parameterSelected[6] = fittingData.getCfSelected();
m_parameterSelected[7] = fittingData.getSwiSelected();
m_parameterSelected[4] = fittingData.getSwiSelected();
// 获取参数边界
m_parameterLower.resize(8);
m_parameterUpper.resize(8);
m_parameterLower.resize(5);
m_parameterUpper.resize(5);
m_parameterLower[0] = fittingData.getPermeabilityMin().getValue().toDouble();
m_parameterUpper[0] = fittingData.getPermeabilityMax().getValue().toDouble();
@ -2740,17 +2739,8 @@ void nmCalculationAutoFitPSO::loadParameterBounds()
m_parameterLower[3] = fittingData.getPorosityMin().getValue().toDouble();
m_parameterUpper[3] = fittingData.getPorosityMax().getValue().toDouble();
m_parameterLower[4] = fittingData.getThicknessMin().getValue().toDouble();
m_parameterUpper[4] = fittingData.getThicknessMax().getValue().toDouble();
m_parameterLower[5] = fittingData.getCtMin().getValue().toDouble();
m_parameterUpper[5] = fittingData.getCtMax().getValue().toDouble();
m_parameterLower[6] = fittingData.getCfMin().getValue().toDouble();
m_parameterUpper[6] = fittingData.getCfMax().getValue().toDouble();
m_parameterLower[7] = fittingData.getSwiMin().getValue().toDouble();
m_parameterUpper[7] = fittingData.getSwiMax().getValue().toDouble();
m_parameterLower[4] = fittingData.getSwiMin().getValue().toDouble();
m_parameterUpper[4] = fittingData.getSwiMax().getValue().toDouble();
// 更新启用参数索引
m_enabledParamIndices.clear();
@ -2835,6 +2825,7 @@ bool nmCalculationAutoFitPSO::startAutoFitting()
}
m_isRunning = true;
m_shouldStop = false;
m_isFinalizing = false;
m_isPaused = false;
m_currentIteration = 0;
m_consecutiveFailures = 0;
@ -3200,6 +3191,7 @@ bool nmCalculationAutoFitPSO::startAutoFitting()
emit logMessageGenerated(tr("CRITICAL ERROR: %1").arg(e.what()));
closeTraceFile();
cleanupTemporaryDirectory();
m_isFinalizing = false;
m_isRunning = false;
emit fittingFinished(false, m_lastError);
return false;
@ -3208,18 +3200,35 @@ bool nmCalculationAutoFitPSO::startAutoFitting()
emit logMessageGenerated(tr("CRITICAL ERROR: Unknown exception in PSO main loop"));
closeTraceFile();
cleanupTemporaryDirectory();
m_isFinalizing = false;
m_isRunning = false;
emit fittingFinished(false, m_lastError);
return false;
}
m_isRunning = false;
bool finalFullSolverSucceeded = false;
// 应用最终参数
if(!m_globalBestPosition.isEmpty()) {
try {
// Stop 只结束优化迭代;从这里开始必须用当前最优参数生成并发布正式快照。
m_isFinalizing = true;
emit finalizingStarted();
emit logMessageGenerated(tr("Applying optimized parameters to model..."));
applyParametersToDataManager(m_globalBestPosition);
// 候选粒子只返回目标井曲线;最优参数确定后必须再生成并提交完整结果快照。
emit logMessageGenerated(
tr("Running final full-field calculation with optimized parameters..."));
finalFullSolverSucceeded = runFinalFullSolver();
if(finalFullSolverSucceeded) {
emit logMessageGenerated(
tr("Final full-field calculation completed successfully"));
} else {
m_lastError = tr("Optimized parameters were found, but the final full-field calculation failed");
emit logMessageGenerated(
tr("ERROR: Final full-field calculation failed"));
}
saveOptimizationResult();
// 输出最终优化结果
@ -3238,7 +3247,13 @@ bool nmCalculationAutoFitPSO::startAutoFitting()
emit logMessageGenerated(finalParams);
emit logMessageGenerated(tr("Parameters applied successfully to data manager"));
if(finalFullSolverSucceeded) {
emit logMessageGenerated(
tr("Parameters and full-field results applied successfully to data manager"));
} else {
emit logMessageGenerated(
tr("Optimized parameters applied to data manager"));
}
} catch(const std::exception& e) {
emit logMessageGenerated(tr("ERROR: Failed to apply final parameters: %1").arg(e.what()));
m_lastError = QString("Failed to apply final parameters: %1").arg(e.what());
@ -3248,6 +3263,9 @@ bool nmCalculationAutoFitPSO::startAutoFitting()
}
}
m_isFinalizing = false;
m_isRunning = false;
// 判断系统确定最终结果
bool success;
QString message;
@ -3275,7 +3293,7 @@ bool nmCalculationAutoFitPSO::startAutoFitting()
emit logMessageGenerated(tr("=== PSO OPTIMIZATION - MAX ITERATIONS ==="));
} else if(finalReason == PSO_USER_STOPPED) {
success = true;
message = QString(tr("Best error: %1, Iterations: %2"))
message = QString(tr("Stopped by user. Best error: %1, Iterations: %2"))
.arg(m_globalBestFitness, 0, 'e', 4).arg(m_currentIteration + 1);
emit logMessageGenerated(tr("=== PSO OPTIMIZATION STOPPED BY USER ==="));
} else if(finalReason == PSO_CONSECUTIVE_FAILURES) {
@ -3290,6 +3308,14 @@ bool nmCalculationAutoFitPSO::startAutoFitting()
emit logMessageGenerated(tr("=== PSO OPTIMIZATION - UNKNOWN END ==="));
}
// 只有正式快照已经提交,界面收到的成功信号才代表本次拟合结果完整可用。
if(success && !finalFullSolverSucceeded) {
success = false;
message = m_lastError.isEmpty()
? tr("The final full-field calculation failed")
: m_lastError;
}
emitRunSummary(success, finalReason);
// 先发送最终进度更新确保进度条达到100%
@ -3357,19 +3383,7 @@ void nmCalculationAutoFitPSO::extractUserInitialValues()
initialValue = reservoirData.getPorosity().getValue().toDouble();
break;
case 4: // 储层厚度
initialValue = reservoirData.getThickness().getValue().toDouble();
break;
case 5: // 综合压缩系数
initialValue = reservoirData.getCt().getValue().toDouble();
break;
case 6: // 岩石压缩系数
initialValue = reservoirData.getCf().getValue().toDouble();
break;
case 7: // 初始含水饱和度
case 4: // 初始含水饱和度
initialValue = reservoirData.getSwi().getValue().toDouble();
break;
}
@ -4153,7 +4167,7 @@ void nmCalculationAutoFitPSO::updateReservoirParameters(const QVector<double>& p
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
nmDataReservoir reservoirData = dataManager->getReservoirDataCopy();
// paramIndex 是粒子 position 中的索引i 是完整 8 个参数体系中的索引。
// paramIndex 是粒子 position 中的索引i 是完整 5 个拟合参数体系中的索引。
// 只有 m_parameterSelected[i] 为 true 时,才从 parameters 中消费一个值。
int paramIndex = 0;
@ -4170,19 +4184,7 @@ void nmCalculationAutoFitPSO::updateReservoirParameters(const QVector<double>& p
reservoirData.getPorosity().setValue(value);
break;
case 4: // 储层厚度
reservoirData.getThickness().setValue(value);
break;
case 5: // 综合压缩系数
reservoirData.getCt().setValue(value);
break;
case 6: // 岩石压缩系数
reservoirData.getCf().setValue(value);
break;
case 7: // 初始含水饱和度
case 4: // 初始含水饱和度
reservoirData.getSwi().setValue(value);
break;
}
@ -4209,7 +4211,17 @@ void nmCalculationAutoFitPSO::updateWellParameters(const QVector<double>& parame
if(!pWell) return;
//nmDataWellBase* pWell = wells[0]; // 使用第一口井
// 先修改独立的参数副本,解析完成后再写回原井对象中的现有属性。
// 不再整井赋值,避免拟合过程中删除并重建射孔对象。
nmDataPerforation* pPerforation = pWell->getPerforation(0);
nmDataAttribute skinAttr;
bool updateSkin = false;
if(pPerforation) {
skinAttr = pPerforation->getSkin();
}
nmDataAttribute wellboreAttr = pWell->getWellboreStorage();
bool updateWellboreStorage = false;
int paramIndex = 0;
@ -4219,20 +4231,16 @@ void nmCalculationAutoFitPSO::updateWellParameters(const QVector<double>& parame
switch(i) {
case 1: { // 表皮系数
nmDataPerforation* perf = pWell->getPerforation(0);
if(perf) {
nmDataAttribute skinAttr = perf->getSkin();
if(pPerforation) {
skinAttr.setValue(value);
perf->setSkin(skinAttr);
updateSkin = true;
}
}
break;
case 2: { // 井筒储集系数
nmDataAttribute wellboreAttr = pWell->getWellboreStorage();
wellboreAttr.setValue(value);
pWell->setWellboreStorage(wellboreAttr);
updateWellboreStorage = true;
}
break;
}
@ -4241,58 +4249,11 @@ void nmCalculationAutoFitPSO::updateWellParameters(const QVector<double>& parame
}
}
// 根据井类型更新到数据管理器
updateWellToDataManager(pWell);
}
void nmCalculationAutoFitPSO::updateWellToDataManager(nmDataWellBase* pWell)
{
if(!pWell) return;
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
NM_WELL_MODEL wellType = pWell->getWellType();
// DataManager 内部按井型维护不同容器。修改基类指针后,需要根据实际井型
// 调用对应 update 接口,才能让后续求解器组装读到最新 skin / wellboreC。
switch(wellType) {
case NM_WELL_MODEL::Vertical_Well: {
nmDataVerticalWell* pVerticalWell = dynamic_cast<nmDataVerticalWell*>(pWell);
if(pVerticalWell) {
QVector<nmDataVerticalWell> wells;
wells.append(*pVerticalWell);
dataManager->updateVerticalWells(wells);
}
break;
}
case NM_WELL_MODEL::Vertical_Fractured_Well: {
nmDataVerticalFracturedWell* pVFracturedWell = dynamic_cast<nmDataVerticalFracturedWell*>(pWell);
if(pVFracturedWell) {
QVector<nmDataVerticalFracturedWell> wells;
wells.append(*pVFracturedWell);
dataManager->updateVerticalFracturedWells(wells);
}
break;
}
case NM_WELL_MODEL::Horizontal_Fractured_Well: {
nmDataHorizontalFracturedWell* pHFracturedWell = dynamic_cast<nmDataHorizontalFracturedWell*>(pWell);
if(pHFracturedWell) {
QVector<nmDataHorizontalFracturedWell> wells;
wells.append(*pHFracturedWell);
dataManager->updateHorizontalFracturedWells(wells);
}
break;
}
default:
break;
if(updateSkin) {
pPerforation->getSkin().setValue(skinAttr.getValue());
}
if(updateWellboreStorage) {
pWell->getWellboreStorage().setValue(wellboreAttr.getValue());
}
}
@ -4655,14 +4616,6 @@ bool nmCalculationAutoFitPSO::validateParameters(const QVector<double>& paramete
return false;
}
break;
case 5: // 综合压缩系数:必须大于零
if(value <= 1e-8) {
DEBUG_OUT(QString("Rejecting near-zero total compressibility: %1").arg(value));
return false;
}
break;
}
}
@ -5241,6 +5194,51 @@ QVector<QVector<double>> nmCalculationAutoFitPSO::runSolverDll()
return result;
}
bool nmCalculationAutoFitPSO::runFinalFullSolver()
{
// 不设置目标井名,任务按普通完整模式构建全部井、网格和压力场结果。
nmDataAnalyzeManager* pDataManager =
nmDataAnalyzeManager::getCurrentInstance();
if(pDataManager == nullptr || m_evaluationInProgress > 0) {
DEBUG_OUT("Cannot start final full-field solver");
return false;
}
++m_evaluationInProgress;
nmCalculationDllPebiSolverTask dllTask(m_tempDirectory, pDataManager);
dllTask.start();
const int maxWait = 3600000;
const int checkInterval = 50;
QTime waitTimer;
waitTimer.start();
while(waitTimer.elapsed() < maxWait) {
if(dllTask.wait(checkInterval) || !dllTask.isRunning()) {
break;
}
// 最终求解期间继续处理绘制事件,避免进度窗口看起来失去响应。
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents,
checkInterval);
}
if(dllTask.isRunning()) {
DEBUG_OUT("Final full-field solver timeout, terminating task");
dllTask.terminate();
dllTask.wait(2000);
--m_evaluationInProgress;
return false;
}
dllTask.wait();
// 后台只生成候选快照,必须回到 DataManager 所属线程一次性提交。
const bool succeeded =
dllTask.wasSuccessful() && dllTask.commitResult(pDataManager);
--m_evaluationInProgress;
return succeeded;
}
//QVector<QVector<double>> nmCalculationAutoFitPSO::runSolverExe()
//{
// DEBUG_OUT("SOLVER EXE START");

@ -36,6 +36,36 @@ namespace {
const int CONST_PVT_POINT_COUNT = 200;
typedef void (*HX_NWTM_GRID_Func)(
HX_NWTM_GRID_OUTPUT1&,
HX_NWTM_GRID_OUTPUT2&,
const HX_NWTM_GRID_INPUT&,
std::string);
__declspec(noinline) void invokePebiGridDll(
HX_NWTM_GRID_Func pfnGenerateGrid,
const nmPebiGridInputSnapshot& oSnapshot,
nmPebiGridResult& oResult)
{
// 单独调用网格 DLL避免字符串参数的构造、析构与异常捕获放在同一函数中导致编译错误。
pfnGenerateGrid(oResult.m_oGridOutput1, oResult.m_oGridOutput2,
oSnapshot.m_oGridInput, oSnapshot.m_sLicensePath.toStdString());
}
bool invokePebiGridDllGuarded(HX_NWTM_GRID_Func pfnGenerateGrid,
const nmPebiGridInputSnapshot& oSnapshot,
nmPebiGridResult& oResult)
{
// 仅拦截已复现的访问违例;普通 C++ 异常继续交给外层 catch堆损坏不在此恢复。
__try {
invokePebiGridDll(pfnGenerateGrid, oSnapshot, oResult);
} __except(GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION
? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
return false;
}
return true;
}
// 该锁只保护 PEBI 网格单例缓存HX_NWTM.dll 的所有入口由独立进程级锁保护。
// 保留递归锁以兼容可能在持锁网格入口中调用缓存查询的旧代码路径。
QMutex s_oPebiGridMutex(QMutex::Recursive);
@ -1660,11 +1690,6 @@ bool nmCalculationPebiGrid::calculateSnapshot(
return false;
}
typedef void (*HX_NWTM_GRID_Func)(
HX_NWTM_GRID_OUTPUT1&,
HX_NWTM_GRID_OUTPUT2&,
const HX_NWTM_GRID_INPUT&,
std::string);
typedef int (*GetIntValueFunc)();
HX_NWTM_GRID_Func pfnGenerateGrid =
@ -1687,10 +1712,13 @@ bool nmCalculationPebiGrid::calculateSnapshot(
}
// 第二步DLL 输出先落在局部结果,失败时不清空单例缓存和已有成果。
pfnGenerateGrid(oResult.m_oGridOutput1,
oResult.m_oGridOutput2,
oSnapshot.m_oGridInput,
oSnapshot.m_sLicensePath.toStdString());
if(!invokePebiGridDllGuarded(pfnGenerateGrid, oSnapshot, oResult)) {
// 本轮结果不再用于显示或求解,沿用现有失败通知保留旧成果。
nmCalculationUtils::cleanupPebiGridDebugFiles();
FreeLibrary(hGridModule);
hGridModule = nullptr;
return false;
}
nmCalculationUtils::cleanupPebiGridDebugFiles();
oResult.m_nPebiCount = pfnGetPebiCount();

@ -7,9 +7,6 @@ nmDataAutomaticFitting::nmDataAutomaticFitting()
m_skinSelected = true; // 默认选中
m_wellboreStorageSelected = true; // 默认选中
m_porositySelected = true; // 默认选中
m_thicknessSelected = true; // 默认选中
m_ctSelected = true; // 默认选中
m_cfSelected = false; // 默认不选中
m_swiSelected = false; // 默认不选中
m_fractureConductivitySelected = false; // 仅压裂井可用,默认不选中
m_fractureHalfLengthSelected = false; // 仅压裂井可用,默认不选中
@ -19,9 +16,6 @@ nmDataAutomaticFitting::nmDataAutomaticFitting()
m_skinMax = nmDataAttribute("Skin Max", QVariant(), "");
m_wellboreStorageMax = nmDataAttribute("Wellbore Storage Max", QVariant(), "m^3/MPa");
m_porosityMax = nmDataAttribute("Porosity Max", QVariant(), "");
m_thicknessMax = nmDataAttribute("Thickness Max", QVariant(), "m");
m_ctMax = nmDataAttribute("Ct Max", QVariant(), "");
m_cfMax = nmDataAttribute("Cf Max", QVariant(), "");
m_swiMax = nmDataAttribute("Swi Max", QVariant(), "");
m_fractureConductivityMax = nmDataAttribute("Fracture Conductivity Max", QVariant(), "md.m");
m_fractureHalfLengthMax = nmDataAttribute("Fracture Half Length Max", QVariant(), "m");
@ -30,9 +24,6 @@ nmDataAutomaticFitting::nmDataAutomaticFitting()
m_skinMin = nmDataAttribute("Skin Min", QVariant(), "");
m_wellboreStorageMin = nmDataAttribute("Wellbore Storage Min", QVariant(), "m^3/MPa");
m_porosityMin = nmDataAttribute("Porosity Min", QVariant(), "");
m_thicknessMin = nmDataAttribute("Thickness Min", QVariant(), "m");
m_ctMin = nmDataAttribute("Ct Min", QVariant(), "");
m_cfMin = nmDataAttribute("Cf Min", QVariant(), "");
m_swiMin = nmDataAttribute("Swi Min", QVariant(), "");
m_fractureConductivityMin = nmDataAttribute("Fracture Conductivity Min", QVariant(), "mD.m");
m_fractureHalfLengthMin = nmDataAttribute("Fracture Half Length Min", QVariant(), "m");
@ -61,9 +52,6 @@ nmDataAutomaticFitting& nmDataAutomaticFitting::operator=(const nmDataAutomaticF
m_skinSelected = other.m_skinSelected;
m_wellboreStorageSelected = other.m_wellboreStorageSelected;
m_porositySelected = other.m_porositySelected;
m_thicknessSelected = other.m_thicknessSelected;
m_ctSelected = other.m_ctSelected;
m_cfSelected = other.m_cfSelected;
m_swiSelected = other.m_swiSelected;
m_fractureConductivitySelected = other.m_fractureConductivitySelected;
m_fractureHalfLengthSelected = other.m_fractureHalfLengthSelected;
@ -73,9 +61,6 @@ nmDataAutomaticFitting& nmDataAutomaticFitting::operator=(const nmDataAutomaticF
m_skinMax = other.m_skinMax;
m_wellboreStorageMax = other.m_wellboreStorageMax;
m_porosityMax = other.m_porosityMax;
m_thicknessMax = other.m_thicknessMax;
m_ctMax = other.m_ctMax;
m_cfMax = other.m_cfMax;
m_swiMax = other.m_swiMax;
m_fractureConductivityMax = other.m_fractureConductivityMax;
m_fractureHalfLengthMax = other.m_fractureHalfLengthMax;
@ -85,9 +70,6 @@ nmDataAutomaticFitting& nmDataAutomaticFitting::operator=(const nmDataAutomaticF
m_skinMin = other.m_skinMin;
m_wellboreStorageMin = other.m_wellboreStorageMin;
m_porosityMin = other.m_porosityMin;
m_thicknessMin = other.m_thicknessMin;
m_ctMin = other.m_ctMin;
m_cfMin = other.m_cfMin;
m_swiMin = other.m_swiMin;
m_fractureConductivityMin = other.m_fractureConductivityMin;
m_fractureHalfLengthMin = other.m_fractureHalfLengthMin;
@ -110,9 +92,6 @@ rapidjson::Value nmDataAutomaticFitting::ToJsonValue(rapidjson::Document::Alloca
fittingObject.AddMember("SkinSelected", m_skinSelected, allocator);
fittingObject.AddMember("WellboreStorageSelected", m_wellboreStorageSelected, allocator);
fittingObject.AddMember("PorositySelected", m_porositySelected, allocator);
fittingObject.AddMember("ThicknessSelected", m_thicknessSelected, allocator);
fittingObject.AddMember("CtSelected", m_ctSelected, allocator);
fittingObject.AddMember("CfSelected", m_cfSelected, allocator);
fittingObject.AddMember("SwiSelected", m_swiSelected, allocator);
fittingObject.AddMember("FractureConductivitySelected", m_fractureConductivitySelected, allocator);
fittingObject.AddMember("FractureHalfLengthSelected", m_fractureHalfLengthSelected, allocator);
@ -122,9 +101,6 @@ rapidjson::Value nmDataAutomaticFitting::ToJsonValue(rapidjson::Document::Alloca
fittingObject.AddMember("SkinMax", m_skinMax.ToJsonValue(allocator), allocator);
fittingObject.AddMember("WellboreStorageMax", m_wellboreStorageMax.ToJsonValue(allocator), allocator);
fittingObject.AddMember("PorosityMax", m_porosityMax.ToJsonValue(allocator), allocator);
fittingObject.AddMember("ThicknessMax", m_thicknessMax.ToJsonValue(allocator), allocator);
fittingObject.AddMember("CtMax", m_ctMax.ToJsonValue(allocator), allocator);
fittingObject.AddMember("CfMax", m_cfMax.ToJsonValue(allocator), allocator);
fittingObject.AddMember("SwiMax", m_swiMax.ToJsonValue(allocator), allocator);
fittingObject.AddMember("FractureConductivityMax", m_fractureConductivityMax.ToJsonValue(allocator), allocator);
fittingObject.AddMember("FractureHalfLengthMax", m_fractureHalfLengthMax.ToJsonValue(allocator), allocator);
@ -134,9 +110,6 @@ rapidjson::Value nmDataAutomaticFitting::ToJsonValue(rapidjson::Document::Alloca
fittingObject.AddMember("SkinMin", m_skinMin.ToJsonValue(allocator), allocator);
fittingObject.AddMember("WellboreStorageMin", m_wellboreStorageMin.ToJsonValue(allocator), allocator);
fittingObject.AddMember("PorosityMin", m_porosityMin.ToJsonValue(allocator), allocator);
fittingObject.AddMember("ThicknessMin", m_thicknessMin.ToJsonValue(allocator), allocator);
fittingObject.AddMember("CtMin", m_ctMin.ToJsonValue(allocator), allocator);
fittingObject.AddMember("CfMin", m_cfMin.ToJsonValue(allocator), allocator);
fittingObject.AddMember("SwiMin", m_swiMin.ToJsonValue(allocator), allocator);
fittingObject.AddMember("FractureConductivityMin", m_fractureConductivityMin.ToJsonValue(allocator), allocator);
fittingObject.AddMember("FractureHalfLengthMin", m_fractureHalfLengthMin.ToJsonValue(allocator), allocator);
@ -165,15 +138,6 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue)
if (jsonValue.HasMember("PorositySelected") && jsonValue["PorositySelected"].IsBool()) {
m_porositySelected = jsonValue["PorositySelected"].GetBool();
}
if (jsonValue.HasMember("ThicknessSelected") && jsonValue["ThicknessSelected"].IsBool()) {
m_thicknessSelected = jsonValue["ThicknessSelected"].GetBool();
}
if (jsonValue.HasMember("CtSelected") && jsonValue["CtSelected"].IsBool()) {
m_ctSelected = jsonValue["CtSelected"].GetBool();
}
if (jsonValue.HasMember("CfSelected") && jsonValue["CfSelected"].IsBool()) {
m_cfSelected = jsonValue["CfSelected"].GetBool();
}
if (jsonValue.HasMember("SwiSelected") && jsonValue["SwiSelected"].IsBool()) {
m_swiSelected = jsonValue["SwiSelected"].GetBool();
}
@ -199,15 +163,6 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue)
if (jsonValue.HasMember("PorosityMax") && jsonValue["PorosityMax"].IsObject()) {
m_porosityMax.FromJsonValue(jsonValue["PorosityMax"]);
}
if (jsonValue.HasMember("ThicknessMax") && jsonValue["ThicknessMax"].IsObject()) {
m_thicknessMax.FromJsonValue(jsonValue["ThicknessMax"]);
}
if (jsonValue.HasMember("CtMax") && jsonValue["CtMax"].IsObject()) {
m_ctMax.FromJsonValue(jsonValue["CtMax"]);
}
if (jsonValue.HasMember("CfMax") && jsonValue["CfMax"].IsObject()) {
m_cfMax.FromJsonValue(jsonValue["CfMax"]);
}
if (jsonValue.HasMember("SwiMax") && jsonValue["SwiMax"].IsObject()) {
m_swiMax.FromJsonValue(jsonValue["SwiMax"]);
}
@ -232,15 +187,6 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue)
if (jsonValue.HasMember("PorosityMin") && jsonValue["PorosityMin"].IsObject()) {
m_porosityMin.FromJsonValue(jsonValue["PorosityMin"]);
}
if (jsonValue.HasMember("ThicknessMin") && jsonValue["ThicknessMin"].IsObject()) {
m_thicknessMin.FromJsonValue(jsonValue["ThicknessMin"]);
}
if (jsonValue.HasMember("CtMin") && jsonValue["CtMin"].IsObject()) {
m_ctMin.FromJsonValue(jsonValue["CtMin"]);
}
if (jsonValue.HasMember("CfMin") && jsonValue["CfMin"].IsObject()) {
m_cfMin.FromJsonValue(jsonValue["CfMin"]);
}
if (jsonValue.HasMember("SwiMin") && jsonValue["SwiMin"].IsObject()) {
m_swiMin.FromJsonValue(jsonValue["SwiMin"]);
}
@ -280,15 +226,6 @@ void nmDataAutomaticFitting::setWellboreStorageSelected(bool selected) { m_wellb
bool nmDataAutomaticFitting::getPorositySelected() const { return m_porositySelected; }
void nmDataAutomaticFitting::setPorositySelected(bool selected) { m_porositySelected = selected; }
bool nmDataAutomaticFitting::getThicknessSelected() const { return m_thicknessSelected; }
void nmDataAutomaticFitting::setThicknessSelected(bool selected) { m_thicknessSelected = selected; }
bool nmDataAutomaticFitting::getCtSelected() const { return m_ctSelected; }
void nmDataAutomaticFitting::setCtSelected(bool selected) { m_ctSelected = selected; }
bool nmDataAutomaticFitting::getCfSelected() const { return m_cfSelected; }
void nmDataAutomaticFitting::setCfSelected(bool selected) { m_cfSelected = selected; }
bool nmDataAutomaticFitting::getSwiSelected() const { return m_swiSelected; }
void nmDataAutomaticFitting::setSwiSelected(bool selected) { m_swiSelected = selected; }
@ -312,15 +249,6 @@ void nmDataAutomaticFitting::setWellboreStorageMax(const nmDataAttribute& wellbo
nmDataAttribute& nmDataAutomaticFitting::getPorosityMax() { return m_porosityMax; }
void nmDataAutomaticFitting::setPorosityMax(const nmDataAttribute& porosityMax) { m_porosityMax = porosityMax; }
nmDataAttribute& nmDataAutomaticFitting::getThicknessMax() { return m_thicknessMax; }
void nmDataAutomaticFitting::setThicknessMax(const nmDataAttribute& thicknessMax) { m_thicknessMax = thicknessMax; }
nmDataAttribute& nmDataAutomaticFitting::getCtMax() { return m_ctMax; }
void nmDataAutomaticFitting::setCtMax(const nmDataAttribute& ctMax) { m_ctMax = ctMax; }
nmDataAttribute& nmDataAutomaticFitting::getCfMax() { return m_cfMax; }
void nmDataAutomaticFitting::setCfMax(const nmDataAttribute& cfMax) { m_cfMax = cfMax; }
nmDataAttribute& nmDataAutomaticFitting::getSwiMax() { return m_swiMax; }
void nmDataAutomaticFitting::setSwiMax(const nmDataAttribute& swiMax) { m_swiMax = swiMax; }
@ -343,15 +271,6 @@ void nmDataAutomaticFitting::setWellboreStorageMin(const nmDataAttribute& wellbo
nmDataAttribute& nmDataAutomaticFitting::getPorosityMin() { return m_porosityMin; }
void nmDataAutomaticFitting::setPorosityMin(const nmDataAttribute& porosityMin) { m_porosityMin = porosityMin; }
nmDataAttribute& nmDataAutomaticFitting::getThicknessMin() { return m_thicknessMin; }
void nmDataAutomaticFitting::setThicknessMin(const nmDataAttribute& thicknessMin) { m_thicknessMin = thicknessMin; }
nmDataAttribute& nmDataAutomaticFitting::getCtMin() { return m_ctMin; }
void nmDataAutomaticFitting::setCtMin(const nmDataAttribute& ctMin) { m_ctMin = ctMin; }
nmDataAttribute& nmDataAutomaticFitting::getCfMin() { return m_cfMin; }
void nmDataAutomaticFitting::setCfMin(const nmDataAttribute& cfMin) { m_cfMin = cfMin; }
nmDataAttribute& nmDataAutomaticFitting::getSwiMin() { return m_swiMin; }
void nmDataAutomaticFitting::setSwiMin(const nmDataAttribute& swiMin) { m_swiMin = swiMin; }

@ -145,6 +145,10 @@ nmSubWndMain::nmSubWndMain(QWidget *parent, QString sExt) :
m_nPendingSolverTimeMs = -1;
m_bSolverStartPending = false;
m_bSolverCancelRequested = false;
m_pResultRefreshDataManager = nullptr;
m_pResultRefreshFitting = nullptr;
m_sObservedPebiResultSnapshotId.clear();
m_bRefreshingPebiResultSnapshot = false;
m_pPlotToolBar = nullptr;
}
@ -221,6 +225,66 @@ nmSubWndMain::~nmSubWndMain()
}*/
}
void nmSubWndMain::bindPebiResultRefreshContext(
nmDataAnalyzeManager* pDataManager,
iSubWndFitting* pSubWndFitting)
{
// 自动拟合完成时始终刷新创建该数值面板的分析窗口,避免页签切换后取到其他分析。
m_pResultRefreshDataManager = pDataManager;
m_pResultRefreshFitting = pSubWndFitting;
QSharedPointer<const nmPebiResultSnapshot> pSnapshot =
pDataManager != nullptr
? pDataManager->getPebiResultSnapshot()
: QSharedPointer<const nmPebiResultSnapshot>();
m_sObservedPebiResultSnapshotId = pSnapshot.isNull()
? QString() : pSnapshot->getSnapshotId();
}
void nmSubWndMain::onPebiResultSnapshotChanged()
{
nmDataAnalyzeManager* pDataManager = m_pResultRefreshDataManager.data();
iSubWndFitting* pSubWndFitting = m_pResultRefreshFitting.data();
if(pDataManager == nullptr || pSubWndFitting == nullptr ||
sender() != pDataManager || m_bRefreshingPebiResultSnapshot) {
return;
}
QSharedPointer<const nmPebiResultSnapshot> pSnapshot =
pDataManager->getPebiResultSnapshot();
if(pSnapshot.isNull()) {
m_sObservedPebiResultSnapshotId.clear();
return;
}
const QString sSnapshotId = pSnapshot->getSnapshotId();
if(sSnapshotId == m_sObservedPebiResultSnapshotId) {
// 同一快照内切换结果井仍由 onWellSelected() 处理,不重复刷新全部结果页面。
return;
}
// 手工 Generate 有自己的完成和提示流程;这里只接管自动拟合提交的新快照。
if(m_pSolverTask != nullptr || m_bSolverStartPending ||
m_bSolverResultCompletionPending) {
m_sObservedPebiResultSnapshotId = sSnapshotId;
return;
}
m_bRefreshingPebiResultSnapshot = true;
QPointer<nmDataAnalyzeManager> pSavedSolverDataManager =
m_pSolverDataManager;
QPointer<iSubWndFitting> pSavedSolverFitting = m_pSolverFitting;
m_pSolverDataManager = pDataManager;
m_pSolverFitting = pSubWndFitting;
const bool bRefreshed = mergeAnaResultToFitting();
schedulePendingResultWidgets();
m_pSolverDataManager = pSavedSolverDataManager;
m_pSolverFitting = pSavedSolverFitting;
m_bRefreshingPebiResultSnapshot = false;
if(bRefreshed) {
m_sObservedPebiResultSnapshotId = sSnapshotId;
}
}
void nmSubWndMain::configWnd(QString sPrevInfo)
{
iSubWndBaseFit::configWnd(sPrevInfo);

@ -409,11 +409,13 @@ void nmSubWndUtils::connectWellListSignals(nmDataAnalyzeManager* pDataManager,
void nmSubWndUtils::setupNumericalDockWidgets(nmDataAnalyzeManager* pDataManager,
nmSubWndMain* pSubWndMain,
iSubWndFitting* pSubWndFitting,
iDockBaseWx* pWxDockNm1,
iDockBaseWx* pWxDockNm2)
{
Q_ASSERT(pDataManager != nullptr);
Q_ASSERT(pSubWndMain != nullptr);
Q_ASSERT(pSubWndFitting != nullptr);
Q_ASSERT(pWxDockNm1 != nullptr);
Q_ASSERT(pWxDockNm2 != nullptr);
@ -447,6 +449,12 @@ void nmSubWndUtils::setupNumericalDockWidgets(nmDataAnalyzeManager* pDataManager
connect(pAnalWx, SIGNAL(sigResultWellChanged(QString)),
pSubWndMain, SLOT(onWellSelected(QString)));
// 手工求解和自动拟合都发布同一种正式快照,统一复用主窗口结果刷新逻辑。
pSubWndMain->bindPebiResultRefreshContext(pDataManager, pSubWndFitting);
connect(pDataManager, SIGNAL(sigPebiResultSnapshotChanged()),
pSubWndMain, SLOT(onPebiResultSnapshotChanged()),
Qt::UniqueConnection);
connectWellListSignals(pDataManager, pParaWx);
pWxDockNm1->setWidget(pAnalWx);
@ -677,7 +685,7 @@ bool nmSubWndUtils::fillNmDockWxs(iSubWnd* pSubWnd)
ZxMainWindow* pMainWnd = pSubWndF->getMainWindow();
// 设置当前窗口到Map
pSubWndMain->setMainWindow(pMainWnd);
setupNumericalDockWidgets(pDataManager, pSubWndMain,
setupNumericalDockWidgets(pDataManager, pSubWndMain, pSubWndF,
pWxDockNm1, pWxDockNm2);
return true; //your own codes
@ -1067,7 +1075,7 @@ bool nmSubWndUtils::loadRsts(iSubWnd* pSubWnd, \
nmSubWndMain* pSubWndMain = new nmSubWndMain(NULL, "");
ZxMainWindow* pMainWnd1 = const_cast<ZxMainWindow*>(pMainWnd);
pSubWndMain->setMainWindow(pMainWnd1);
setupNumericalDockWidgets(pDataManager, pSubWndMain,
setupNumericalDockWidgets(pDataManager, pSubWndMain, pSubWndF,
pWxDockNm1, pWxDockNm2);
// v3 场图只以独立快照是否存在为准,不再依赖旧时间步 Map。

@ -3,6 +3,7 @@
#include "nmCalculationAutoFitLM.h"
#include "nmWxAutomaticfittingStart.h"
#include "nmWxParameterProperty.h"
#include "nmAttrRegistry.h"
#include "nmDataAnalyzeManager.h"
#include "iSubWndFitting.h"
#include "iSysParaHelper.h"
@ -164,7 +165,7 @@ void nmWxAutomaticFitting::setParameterRowVisible(QTableWidget* table, int row,
}
}
// 隐藏参数行后重新整理序号,让界面看起来像删除了不需要的参数
// 按当前模型和井型可用的参数行重新整理显示序号
void nmWxAutomaticFitting::renumberVisibleParameterRows(QTableWidget* table)
{
if(!table) {
@ -188,7 +189,7 @@ void nmWxAutomaticFitting::renumberVisibleParameterRows(QTableWidget* table)
}
}
// 根据当前模型类型控制 Ct/Cf/Swi并根据目标井类型控制裂缝导流能力显示。
// 拟合表仅包含可优化参数,按模型和目标井类型控制显示。
void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOLVER_MODEL_TYPE eType)
{
if(!table) {
@ -199,35 +200,7 @@ void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOL
setParameterRowVisible(table, row, true);
}
bool showCt = false;
bool showCf = false;
bool showSwi = false;
switch(eType) {
case SMT_Oil_ConstPvt:
case SMT_Water_ConstPvt:
// 常量PVT使用综合压缩系数Ct。
showCt = true;
break;
case SMT_Oil_VariablePvt:
case SMT_Water_VariablePvt:
case SMT_Gas_VariablePvt:
// 变化PVT使用岩石压缩系数Cf。
showCf = true;
break;
case SMT_Oil_Water_TwoPhase:
// 油水两相使用Cf并且只有它需要初始含水饱和度Swi。
showCf = true;
showSwi = true;
break;
default:
showCf = true;
break;
}
setParameterRowVisible(table, 5, showCt); // Ct
setParameterRowVisible(table, 6, showCf); // Cf
setParameterRowVisible(table, 7, showSwi); // Swi
setParameterRowVisible(table, 4, eType == SMT_Oil_Water_TwoPhase); // Swi
// Dfc 只属于垂直压裂井和多段压裂水平井。普通井隐藏并取消勾选,
// 防止切换目标井后不可见的裂缝参数仍进入拟合参数向量。
@ -242,8 +215,8 @@ void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOL
wellType == NM_WELL_MODEL::Horizontal_Fractured_Well;
}
}
setParameterRowVisible(table, 8, showFractureConductivity); // Dfc
setParameterRowVisible(table, 9, showFractureConductivity); // 裂缝半长
setParameterRowVisible(table, 5, showFractureConductivity); // Dfc
setParameterRowVisible(table, 6, showFractureConductivity); // 裂缝半长
renumberVisibleParameterRows(table);
}
@ -254,10 +227,10 @@ bool nmWxAutomaticFitting::getPhysicalParameterRange(int parameterIndex,
{
static const char* parameterNames[] = {
"Result_K", "Result_W_Skin", "Result_W_C", "Result_phi",
"Result_h", "Result_Cti", "Result_Cf", "Result_Swi", "Result_W_Dfc",
"Result_Swi", "Result_W_Dfc",
"W_FractureHalfLength"
};
if(parameterIndex < 0 || parameterIndex >= 10) {
if(parameterIndex < 0 || parameterIndex >= 7) {
return false;
}
@ -265,7 +238,7 @@ bool nmWxAutomaticFitting::getPhysicalParameterRange(int parameterIndex,
return false;
}
if(parameterIndex == 7) {
if(parameterIndex == 4) {
double soi = reservoirData.getSoi().getValue().toDouble();
double sgi = reservoirData.getSgi().getValue().toDouble();
if(nmAutoFitUiIsFinite(soi) && nmAutoFitUiIsFinite(sgi)) {
@ -323,26 +296,14 @@ void nmWxAutomaticFitting::setParameterRange(int parameterIndex,
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;
case 8:
case 5:
automaticFittingData.getFractureConductivityMin().setValue(minValue);
automaticFittingData.getFractureConductivityMax().setValue(maxValue);
break;
case 9:
case 6:
automaticFittingData.getFractureHalfLengthMin().setValue(minValue);
automaticFittingData.getFractureHalfLengthMax().setValue(maxValue);
break;
@ -358,7 +319,7 @@ void nmWxAutomaticFitting::setParameterRange(int parameterIndex,
void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex,
double centerValue)
{
if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= 10
if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= 7
|| !nmAutoFitUiIsFinite(centerValue)) {
return;
}
@ -394,17 +355,17 @@ void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex,
newMin = qMax(physicalMin, reference - skinHalfRange);
newMax = qMin(physicalMax, reference + skinHalfRange);
} else if(reference > 0.0
&& !(parameterIndex == 7 && centerValue <= 0.0)
&& !(parameterIndex == 8 && centerValue <= 0.0)) {
&& !(parameterIndex == 4 && centerValue <= 0.0)
&& !(parameterIndex == 5 && centerValue <= 0.0)) {
const double lowerFactor = 0.1;
const double upperFactor = 10.0;
newMin = qMax(physicalMin, reference * lowerFactor);
newMax = qMin(physicalMax, reference * upperFactor);
} else if(parameterIndex == 7) {
} else if(parameterIndex == 4) {
// 没有可靠 Swi 初值时,不把搜索范围压缩到零附近。
newMin = physicalMin;
newMax = physicalMax;
} else if(parameterIndex == 8) {
} else if(parameterIndex == 5) {
// Dfc=0 表示无限导流,不存在以零为中心的连续倍率范围。
newMin = physicalMin;
newMax = physicalMax;
@ -428,7 +389,7 @@ void nmWxAutomaticFitting::initializeSuggestedParameterRanges()
return;
}
for(int parameterIndex = 0; parameterIndex < 10; ++parameterIndex) {
for(int parameterIndex = 0; parameterIndex < 7; ++parameterIndex) {
QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3);
if(initialItem) {
bool initialOk = false;
@ -455,7 +416,7 @@ void nmWxAutomaticFitting::normalizeSavedParameterRanges()
return;
}
for(int parameterIndex = 0; parameterIndex < 10; ++parameterIndex) {
for(int parameterIndex = 0; parameterIndex < 7; ++parameterIndex) {
QTableWidgetItem* minItem = m_parameterTable->item(parameterIndex, 2);
QTableWidgetItem* maxItem = m_parameterTable->item(parameterIndex, 4);
QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3);
@ -477,7 +438,7 @@ void nmWxAutomaticFitting::normalizeSavedParameterRanges()
const bool savedRangeValid = savedMinOk && savedMaxOk
&& nmAutoFitUiIsFinite(savedMin) && nmAutoFitUiIsFinite(savedMax)
&& savedMax >= savedMin
&& !(parameterIndex == 8 && savedMax <= 1.0e-10);
&& !(parameterIndex == 5 && savedMax <= 1.0e-10);
if(savedRangeValid && physicalMax >= physicalMin) {
const double clippedMin = qMax(savedMin, physicalMin);
@ -506,19 +467,19 @@ bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int par
errorMessage = tr("The parameter table is unavailable.");
return false;
}
if(parameterIndex < -1 || parameterIndex >= 10) {
if(parameterIndex < -1 || parameterIndex >= 7) {
errorMessage = tr("The parameter row is invalid.");
return false;
}
static const char* parameterNames[] = {
"Permeability", "Skin", "Wellbore storage", "Porosity",
"Thickness", "Ct", "Cf", "Swi", "Fracture conductivity",
"Swi", "Fracture conductivity",
"Fracture half length"
};
const int firstParameterIndex = parameterIndex < 0 ? 0 : parameterIndex;
const int lastParameterIndex = parameterIndex < 0 ? 10 : parameterIndex + 1;
const int lastParameterIndex = parameterIndex < 0 ? 7 : parameterIndex + 1;
for(int currentParameterIndex = firstParameterIndex;
currentParameterIndex < lastParameterIndex; ++currentParameterIndex) {
// 隐藏参数不参与当前模型拟合,不用它们的历史值阻塞当前设置。
@ -580,7 +541,7 @@ bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int par
}
// 底层用 Dfc=0 表示无限导流,这是离散模型选项,不属于有限导流拟合域。
if(currentParameterIndex == 8 && m_dfcCheckBox->isChecked()
if(currentParameterIndex == 5 && m_dfcCheckBox->isChecked()
&& (minValue <= 1.0e-10 || initialValue <= 1.0e-10)) {
errorMessage = tr("The minimum value of %1 must be greater than zero for automatic fitting.")
.arg(tr(parameterNames[currentParameterIndex]));
@ -631,20 +592,6 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent)
// 自动范围始终开启用户在表格中修改上下限后itemChanged 会临时切换为手工范围。
m_autoParameterRanges = true;
NM_SOLVER_MODEL_TYPE solverModelType = pManager->getSolverModelType();
// 未保存过配置时只保留参数选择的相态默认值,不再覆盖数据对象中的初值或范围。
if(!hasSavedFittingData) {
if(solverModelType == SMT_Oil_ConstPvt ||
solverModelType == SMT_Water_ConstPvt) {
// T1/T3 的综合压缩系数默认不参与拟合。
automaticFittingData.setCtSelected(false);
} else if(solverModelType == SMT_Oil_VariablePvt ||
solverModelType == SMT_Water_VariablePvt) {
// T2/T4 的岩石压缩系数默认参与拟合。
automaticFittingData.setCfSelected(true);
}
}
setupUI();
setWindowTitle(tr("Automatic fitting"));
setModal(true);
@ -715,7 +662,7 @@ void nmWxAutomaticFitting::setupUI()
void nmWxAutomaticFitting::setupParameterTable()
{
// 创建表格
m_parameterTable = new QTableWidget(10, 6, this);
m_parameterTable = new QTableWidget(7, 6, this);
// 设置表头
QStringList headers;
@ -784,66 +731,36 @@ void nmWxAutomaticFitting::setupParameterTable()
m_parameterTable->setItem(3, 4, new QTableWidgetItem(QString::number(automaticFittingData.getPorosityMax().getValue().toDouble())));
m_parameterTable->setItem(3, 5, new QTableWidgetItem(""));
// 储层厚度 (Thickness)
m_parameterTable->setItem(4, 0, new QTableWidgetItem("5"));
m_hCheckBox = new QCheckBox(tr("Thickness"));
m_hCheckBox->setChecked(automaticFittingData.getThicknessSelected());
m_parameterTable->setCellWidget(4, 1, m_hCheckBox);
m_parameterTable->setItem(4, 2, new QTableWidgetItem(QString::number(automaticFittingData.getThicknessMin().getValue().toDouble())));
m_parameterTable->setItem(4, 3, new QTableWidgetItem(QString::number(reservoirData.getThickness().getValue().toDouble())));
m_parameterTable->setItem(4, 4, new QTableWidgetItem(QString::number(automaticFittingData.getThicknessMax().getValue().toDouble())));
m_parameterTable->setItem(4, 5, new QTableWidgetItem(tr("m")));
// 综合压缩系数 (Ct)
m_parameterTable->setItem(5, 0, new QTableWidgetItem("6"));
m_ctCheckBox = new QCheckBox(tr("Ct"));
m_ctCheckBox->setChecked(automaticFittingData.getCtSelected());
m_parameterTable->setCellWidget(5, 1, m_ctCheckBox);
m_parameterTable->setItem(5, 2, new QTableWidgetItem(QString::number(automaticFittingData.getCtMin().getValue().toDouble())));
m_parameterTable->setItem(5, 3, new QTableWidgetItem(QString::number(reservoirData.getCt().getValue().toDouble())));
m_parameterTable->setItem(5, 4, new QTableWidgetItem(QString::number(automaticFittingData.getCtMax().getValue().toDouble())));
m_parameterTable->setItem(5, 5, new QTableWidgetItem(""));
// 岩石压缩系数 (Cf)
m_parameterTable->setItem(6, 0, new QTableWidgetItem("7"));
m_cfCheckBox = new QCheckBox(tr("Cf"));
m_cfCheckBox->setChecked(automaticFittingData.getCfSelected());
m_parameterTable->setCellWidget(6, 1, m_cfCheckBox);
m_parameterTable->setItem(6, 2, new QTableWidgetItem(QString::number(automaticFittingData.getCfMin().getValue().toDouble())));
m_parameterTable->setItem(6, 3, new QTableWidgetItem(QString::number(reservoirData.getCf().getValue().toDouble())));
m_parameterTable->setItem(6, 4, new QTableWidgetItem(QString::number(automaticFittingData.getCfMax().getValue().toDouble())));
m_parameterTable->setItem(6, 5, new QTableWidgetItem(""));
// 初始含水饱和度 (Swi)
m_parameterTable->setItem(7, 0, new QTableWidgetItem("8"));
m_parameterTable->setItem(4, 0, new QTableWidgetItem("5"));
m_swiCheckBox = new QCheckBox(tr("Swi"));
m_swiCheckBox->setChecked(automaticFittingData.getSwiSelected());
m_parameterTable->setCellWidget(7, 1, m_swiCheckBox);
m_parameterTable->setItem(7, 2, new QTableWidgetItem(QString::number(automaticFittingData.getSwiMin().getValue().toDouble())));
m_parameterTable->setItem(7, 3, new QTableWidgetItem(QString::number(reservoirData.getSwi().getValue().toDouble())));
m_parameterTable->setItem(7, 4, new QTableWidgetItem(QString::number(automaticFittingData.getSwiMax().getValue().toDouble())));
m_parameterTable->setItem(7, 5, new QTableWidgetItem(""));
m_parameterTable->setCellWidget(4, 1, m_swiCheckBox);
m_parameterTable->setItem(4, 2, new QTableWidgetItem(QString::number(automaticFittingData.getSwiMin().getValue().toDouble())));
m_parameterTable->setItem(4, 3, new QTableWidgetItem(QString::number(reservoirData.getSwi().getValue().toDouble())));
m_parameterTable->setItem(4, 4, new QTableWidgetItem(QString::number(automaticFittingData.getSwiMax().getValue().toDouble())));
m_parameterTable->setItem(4, 5, new QTableWidgetItem(""));
// 裂缝导流能力 (Dfc)。该行只对压裂井显示,初值在 onWellSelected() 中
// 从当前目标井读取,其他裂缝几何参数保持固定,不进入自动拟合。
m_parameterTable->setItem(8, 0, new QTableWidgetItem("9"));
m_parameterTable->setItem(5, 0, new QTableWidgetItem("6"));
m_dfcCheckBox = new QCheckBox(tr("Fracture conductivity"));
m_dfcCheckBox->setChecked(automaticFittingData.getFractureConductivitySelected());
m_parameterTable->setCellWidget(8, 1, m_dfcCheckBox);
m_parameterTable->setItem(8, 2, new QTableWidgetItem(QString::number(automaticFittingData.getFractureConductivityMin().getValue().toDouble())));
m_parameterTable->setItem(8, 3, new QTableWidgetItem());
m_parameterTable->setItem(8, 4, new QTableWidgetItem(QString::number(automaticFittingData.getFractureConductivityMax().getValue().toDouble())));
m_parameterTable->setItem(8, 5, new QTableWidgetItem(tr("md.m")));
m_parameterTable->setCellWidget(5, 1, m_dfcCheckBox);
m_parameterTable->setItem(5, 2, new QTableWidgetItem(QString::number(automaticFittingData.getFractureConductivityMin().getValue().toDouble())));
m_parameterTable->setItem(5, 3, new QTableWidgetItem());
m_parameterTable->setItem(5, 4, new QTableWidgetItem(QString::number(automaticFittingData.getFractureConductivityMax().getValue().toDouble())));
m_parameterTable->setItem(5, 5, new QTableWidgetItem(tr("md.m")));
// 裂缝半长。该行与 Dfc 一样只对压裂井显示,初值从当前目标井读取。
m_parameterTable->setItem(9, 0, new QTableWidgetItem("10"));
m_parameterTable->setItem(6, 0, new QTableWidgetItem("7"));
m_fractureHalfLengthCheckBox = new QCheckBox(tr("Fracture half length"));
m_fractureHalfLengthCheckBox->setChecked(automaticFittingData.getFractureHalfLengthSelected());
m_parameterTable->setCellWidget(9, 1, m_fractureHalfLengthCheckBox);
m_parameterTable->setItem(9, 2, new QTableWidgetItem(QString::number(automaticFittingData.getFractureHalfLengthMin().getValue().toDouble())));
m_parameterTable->setItem(9, 3, new QTableWidgetItem());
m_parameterTable->setItem(9, 4, new QTableWidgetItem(QString::number(automaticFittingData.getFractureHalfLengthMax().getValue().toDouble())));
m_parameterTable->setItem(9, 5, new QTableWidgetItem(tr("m")));
m_parameterTable->setCellWidget(6, 1, m_fractureHalfLengthCheckBox);
m_parameterTable->setItem(6, 2, new QTableWidgetItem(QString::number(automaticFittingData.getFractureHalfLengthMin().getValue().toDouble())));
m_parameterTable->setItem(6, 3, new QTableWidgetItem());
m_parameterTable->setItem(6, 4, new QTableWidgetItem(QString::number(automaticFittingData.getFractureHalfLengthMax().getValue().toDouble())));
m_parameterTable->setItem(6, 5, new QTableWidgetItem(tr("m")));
// 设置表格行为
for(int i = 0; i < m_parameterTable->rowCount(); ++i) {
@ -900,14 +817,15 @@ void nmWxAutomaticFitting::setupControlPanel()
m_algorithmCombo->setMaximumWidth(160);
m_algorithmCombo->setMinimumWidth(160);
QLabel* surrogateLabel = new QLabel(tr("PSO acceleration:"));
m_surrogateLabel = new QLabel(tr("PSO acceleration:"));
m_surrogateCombo = new QComboBox();
//m_surrogateCombo->setEnabled(false);// 暂时不可编辑
m_surrogateCombo->addItem(tr("Off"));
m_surrogateCombo->addItem(tr("On"));
m_surrogateCombo->setCurrentIndex(automaticFittingData.getSurrogateScreeningEnabled() ? 1 : 0);
m_surrogateCombo->setMaximumWidth(160);
m_surrogateCombo->setMinimumWidth(160);
connect(m_algorithmCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onAlgorithmChanged(int)));
onAlgorithmChanged(m_algorithmCombo->currentIndex());
// 迭代次数
QLabel* iterationLabel = new QLabel(tr("Number of iterations:"));
@ -925,24 +843,29 @@ void nmWxAutomaticFitting::setupControlPanel()
QLabel* wellLabel = new QLabel(tr("Target Well:"));
m_targetWellCombo = new QComboBox();
// 添加垂直井
for(int i = 0; i < m_verticalWells.size(); ++i) {
m_targetWellCombo->addItem(m_verticalWells[i].getWellName());
}
// 添加水平井
for(int i = 0; i < m_horizontalWells.size(); ++i) {
m_targetWellCombo->addItem(m_horizontalWells[i].getWellName());
}
// 添加垂直压裂井
for(int i = 0; i < m_verticalFracturedWells.size(); ++i) {
m_targetWellCombo->addItem(m_verticalFracturedWells[i].getWellName());
}
// 目标井先加入当前分析主井,再加入已勾选且具备流量、压力数据的包含井。
nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance();
if(pManager != nullptr) {
const QString sPrimaryWellCode = pManager->getPrimaryWellCode();
nmDataWellBase* pPrimaryWell = pManager->findWellByCode(sPrimaryWellCode);
if(pPrimaryWell != nullptr) {
m_targetWellCombo->addItem(pPrimaryWell->getWellName());
}
// 添加水平压裂井
for(int i = 0; i < m_horizontalFracturedWells.size(); ++i) {
m_targetWellCombo->addItem(m_horizontalFracturedWells[i].getWellName());
if(pManager->getIncludeOtherWells()) {
const QVector<nmCalculationWellRef> vecIncludedWells =
pManager->getIncludedCalculationWells();
for(int nIndex = 0; nIndex < vecIncludedWells.size(); ++nIndex) {
const QString& sWellCode = vecIncludedWells[nIndex].m_sWellCode;
nmDataWellBase* pWellData = pManager->findWellByCode(sWellCode);
if(pWellData == nullptr || sWellCode == sPrimaryWellCode ||
pWellData->getFlowSegmentCount() <= 0 ||
pWellData->getPressurePoints().size() < 2) {
continue;
}
m_targetWellCombo->addItem(pWellData->getWellName());
}
}
}
connect(m_targetWellCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onWellSelected(int)));
@ -954,7 +877,7 @@ void nmWxAutomaticFitting::setupControlPanel()
m_controlLayout->addWidget(m_algorithmCombo);
m_controlLayout->addSpacing(15);
m_controlLayout->addWidget(surrogateLabel);
m_controlLayout->addWidget(m_surrogateLabel);
m_controlLayout->addWidget(m_surrogateCombo);
m_controlLayout->addSpacing(15);
@ -973,6 +896,14 @@ void nmWxAutomaticFitting::setupControlPanel()
m_controlLayout->addStretch(1);
}
void nmWxAutomaticFitting::onAlgorithmChanged(int index)
{
// LM 不使用 PSO 加速;只切换可用状态,保留切回 PSO 时的原选择。
const bool usePSO = (index == 0);
m_surrogateLabel->setEnabled(usePSO);
m_surrogateCombo->setEnabled(usePSO);
}
void nmWxAutomaticFitting::setupButtons()
{
QHBoxLayout* buttonLayout = new QHBoxLayout();
@ -1041,12 +972,9 @@ void nmWxAutomaticFitting::onReverseSelection()
if(!m_parameterTable->isRowHidden(1)) m_sCheckBox->setChecked(!m_sCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(2)) m_cCheckBox->setChecked(!m_cCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(3)) m_phiCheckBox->setChecked(!m_phiCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(4)) m_hCheckBox->setChecked(!m_hCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(5)) m_ctCheckBox->setChecked(!m_ctCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(6)) m_cfCheckBox->setChecked(!m_cfCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(7)) m_swiCheckBox->setChecked(!m_swiCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(8)) m_dfcCheckBox->setChecked(!m_dfcCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(9)) m_fractureHalfLengthCheckBox->setChecked(!m_fractureHalfLengthCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(4)) m_swiCheckBox->setChecked(!m_swiCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(5)) m_dfcCheckBox->setChecked(!m_dfcCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(6)) m_fractureHalfLengthCheckBox->setChecked(!m_fractureHalfLengthCheckBox->isChecked());
}
void nmWxAutomaticFitting::onParameterTableItemChanged(QTableWidgetItem* item)
@ -1163,8 +1091,6 @@ void nmWxAutomaticFitting::onAccept()
// 检查是否有参数被选中
bool hasSelectedParams = m_kCheckBox->isChecked() || m_sCheckBox->isChecked() ||
m_cCheckBox->isChecked() || m_phiCheckBox->isChecked() ||
m_hCheckBox->isChecked() ||
m_ctCheckBox->isChecked() || m_cfCheckBox->isChecked() ||
m_swiCheckBox->isChecked() || m_dfcCheckBox->isChecked() ||
m_fractureHalfLengthCheckBox->isChecked();
@ -1180,9 +1106,6 @@ void nmWxAutomaticFitting::onAccept()
if(m_sCheckBox->isChecked()) selectedParameterNames << tr("Skin");
if(m_cCheckBox->isChecked()) selectedParameterNames << tr("Wellbore storage");
if(m_phiCheckBox->isChecked()) selectedParameterNames << tr("Porosity");
if(m_hCheckBox->isChecked()) selectedParameterNames << tr("Thickness");
if(m_ctCheckBox->isChecked()) selectedParameterNames << tr("Ct");
if(m_cfCheckBox->isChecked()) selectedParameterNames << tr("Cf");
if(m_swiCheckBox->isChecked()) selectedParameterNames << tr("Swi");
if(m_dfcCheckBox->isChecked()) selectedParameterNames << tr("Fracture conductivity");
if(m_fractureHalfLengthCheckBox->isChecked()) selectedParameterNames << tr("Fracture half length");
@ -1280,19 +1203,19 @@ void nmWxAutomaticFitting::onWellSelected(int index)
// 设置井筒储集系数Wellbore storage
m_parameterTable->item(2, 3)->setText(QString::number(wellboreStorageValue));
if(fracturedWell && m_parameterTable->item(8, 3)) {
m_parameterTable->item(8, 3)->setText(QString::number(fractureConductivityValue));
if(fracturedWell && m_parameterTable->item(5, 3)) {
m_parameterTable->item(5, 3)->setText(QString::number(fractureConductivityValue));
}
if(fracturedWell && m_parameterTable->item(9, 3)) {
m_parameterTable->item(9, 3)->setText(QString::number(fractureHalfLengthValue));
if(fracturedWell && m_parameterTable->item(6, 3)) {
m_parameterTable->item(6, 3)->setText(QString::number(fractureHalfLengthValue));
}
if(m_autoParameterRanges) {
updateRangeForParameter(1, skinValue);
updateRangeForParameter(2, wellboreStorageValue);
if(fracturedWell) {
updateRangeForParameter(8, fractureConductivityValue);
updateRangeForParameter(9, fractureHalfLengthValue);
updateRangeForParameter(5, fractureConductivityValue);
updateRangeForParameter(6, fractureHalfLengthValue);
}
}
}
@ -1300,14 +1223,11 @@ void nmWxAutomaticFitting::onWellSelected(int index)
void nmWxAutomaticFitting::setAutomaticFittingValue()
{
// 保存参数选择状态
// 保存剩余拟合参数选择状态LM 和 PSO 共用此配置。
automaticFittingData.setPermeabilitySelected(m_kCheckBox->isChecked());
automaticFittingData.setSkinSelected(m_sCheckBox->isChecked());
automaticFittingData.setWellboreStorageSelected(m_cCheckBox->isChecked());
automaticFittingData.setPorositySelected(m_phiCheckBox->isChecked());
automaticFittingData.setThicknessSelected(m_hCheckBox->isChecked());
automaticFittingData.setCtSelected(m_ctCheckBox->isChecked());
automaticFittingData.setCfSelected(m_cfCheckBox->isChecked());
automaticFittingData.setSwiSelected(m_swiCheckBox->isChecked());
automaticFittingData.setFractureConductivitySelected(m_dfcCheckBox->isChecked());
automaticFittingData.setFractureHalfLengthSelected(m_fractureHalfLengthCheckBox->isChecked());
@ -1329,41 +1249,26 @@ void nmWxAutomaticFitting::setAutomaticFittingValue()
automaticFittingData.getPorosityMin().setValue(m_parameterTable->item(3, 2)->text().toDouble());
automaticFittingData.getPorosityMax().setValue(m_parameterTable->item(3, 4)->text().toDouble());
// 保存储层厚度的最小值和最大值
automaticFittingData.getThicknessMin().setValue(m_parameterTable->item(4, 2)->text().toDouble());
automaticFittingData.getThicknessMax().setValue(m_parameterTable->item(4, 4)->text().toDouble());
// 保存综合压缩系数的最小值和最大值
automaticFittingData.getCtMin().setValue(m_parameterTable->item(5, 2)->text().toDouble());
automaticFittingData.getCtMax().setValue(m_parameterTable->item(5, 4)->text().toDouble());
// 保存岩石压缩系数的最小值和最大值
automaticFittingData.getCfMin().setValue(m_parameterTable->item(6, 2)->text().toDouble());
automaticFittingData.getCfMax().setValue(m_parameterTable->item(6, 4)->text().toDouble());
// 保存初始含水饱和度的最小值和最大值
automaticFittingData.getSwiMin().setValue(m_parameterTable->item(7, 2)->text().toDouble());
automaticFittingData.getSwiMax().setValue(m_parameterTable->item(7, 4)->text().toDouble());
automaticFittingData.getSwiMin().setValue(m_parameterTable->item(4, 2)->text().toDouble());
automaticFittingData.getSwiMax().setValue(m_parameterTable->item(4, 4)->text().toDouble());
// 保存裂缝导流能力的最小值和最大值
automaticFittingData.getFractureConductivityMin().setValue(m_parameterTable->item(8, 2)->text().toDouble());
automaticFittingData.getFractureConductivityMax().setValue(m_parameterTable->item(8, 4)->text().toDouble());
automaticFittingData.getFractureConductivityMin().setValue(m_parameterTable->item(5, 2)->text().toDouble());
automaticFittingData.getFractureConductivityMax().setValue(m_parameterTable->item(5, 4)->text().toDouble());
// 保存裂缝半长的最小值和最大值
automaticFittingData.getFractureHalfLengthMin().setValue(m_parameterTable->item(9, 2)->text().toDouble());
automaticFittingData.getFractureHalfLengthMax().setValue(m_parameterTable->item(9, 4)->text().toDouble());
automaticFittingData.getFractureHalfLengthMin().setValue(m_parameterTable->item(6, 2)->text().toDouble());
automaticFittingData.getFractureHalfLengthMax().setValue(m_parameterTable->item(6, 4)->text().toDouble());
// 保存迭代参数
automaticFittingData.getIterationCount().setValue(m_iterationEdit->text().toInt());
automaticFittingData.getErrorTolerance().setValue(m_errorLimitEdit->text().toDouble());
// 保存储层数据的初值
// 只写回仍可编辑的储层初值,厚度和压缩系数保留原始精度和值。
reservoirData.getPermeability().setValue(m_parameterTable->item(0, 3)->text().toDouble()); // 渗透率
reservoirData.getPorosity().setValue(m_parameterTable->item(3, 3)->text().toDouble()); // 孔隙度
reservoirData.getThickness().setValue(m_parameterTable->item(4, 3)->text().toDouble()); // 储层厚度
reservoirData.getCt().setValue(m_parameterTable->item(5, 3)->text().toDouble()); // 综合压缩系数
reservoirData.getCf().setValue(m_parameterTable->item(6, 3)->text().toDouble()); // 岩石压缩系数
reservoirData.getSwi().setValue(m_parameterTable->item(7, 3)->text().toDouble()); // 初始含水饱和度
reservoirData.getSwi().setValue(m_parameterTable->item(4, 3)->text().toDouble()); // 初始含水饱和度
// 更新储层数据(全局)
nmDataAnalyzeManager::getCurrentInstance()->updateReservoirData(reservoirData);
@ -1376,28 +1281,27 @@ void nmWxAutomaticFitting::setAutomaticFittingValue()
if(!selectedWellName.isEmpty()) {
double newSkinValue = m_parameterTable->item(1, 3)->text().toDouble();
double newWellboreStorageValue = m_parameterTable->item(2, 3)->text().toDouble();
double newFractureConductivityValue = m_parameterTable->item(8, 3)->text().toDouble();
double newFractureHalfLengthValue = m_parameterTable->item(9, 3)->text().toDouble();
double newFractureConductivityValue = m_parameterTable->item(5, 3)->text().toDouble();
double newFractureHalfLengthValue = m_parameterTable->item(6, 3)->text().toDouble();
// 直接从数据管理器获取目标井
nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance();
nmDataWellBase* pTargetWell = manager->findWellByName(selectedWellName);
if(pTargetWell) {
// 更新Skin
// 先修改井参数副本,最后写回原对象中的现有属性,保持射孔对象地址不变。
nmDataPerforation* perf = pTargetWell->getPerforation(0);
if(perf) {
nmDataAttribute skinAttr = perf->getSkin();
skinAttr.setValue(newSkinValue);
perf->setSkin(skinAttr);
perf->getSkin().setValue(skinAttr.getValue());
}
// 更新井筒储集系数
nmDataAttribute wellboreAttr = pTargetWell->getWellboreStorage();
wellboreAttr.setValue(newWellboreStorageValue);
pTargetWell->setWellboreStorage(wellboreAttr);
pTargetWell->getWellboreStorage().setValue(wellboreAttr.getValue());
// 根据井类型单独更新这一口井
NM_WELL_MODEL wellType = pTargetWell->getWellType();
// Dfc 属于压裂井对象;只修改这一项,裂缝位置、长度和段数保持原值。
@ -1406,40 +1310,26 @@ void nmWxAutomaticFitting::setAutomaticFittingValue()
if(fracturedWell) {
nmDataAttribute dfc = fracturedWell->getDfc();
dfc.setValue(newFractureConductivityValue);
fracturedWell->setDfc(dfc);
fracturedWell->getFractureHalfLength().setValue(newFractureHalfLengthValue);
fracturedWell->getDfc().setValue(dfc.getValue());
nmDataAttribute halfLength = fracturedWell->getFractureHalfLength();
halfLength.setValue(newFractureHalfLengthValue);
fracturedWell->getFractureHalfLength().setValue(halfLength.getValue());
}
} else if(wellType == NM_WELL_MODEL::Horizontal_Fractured_Well) {
nmDataHorizontalFracturedWell* fracturedWell = dynamic_cast<nmDataHorizontalFracturedWell*>(pTargetWell);
if(fracturedWell) {
nmDataAttribute dfc = fracturedWell->getDfc();
dfc.setValue(newFractureConductivityValue);
fracturedWell->setDfc(dfc);
fracturedWell->getFractureHalfLength().setValue(newFractureHalfLengthValue);
fracturedWell->getDfc().setValue(dfc.getValue());
nmDataAttribute halfLength = fracturedWell->getFractureHalfLength();
halfLength.setValue(newFractureHalfLengthValue);
fracturedWell->getFractureHalfLength().setValue(halfLength.getValue());
}
}
if(wellType == NM_WELL_MODEL::Vertical_Well) {
nmDataVerticalWell* pVerticalWell = dynamic_cast<nmDataVerticalWell*>(pTargetWell);
if(pVerticalWell != nullptr) {
QVector<nmDataVerticalWell> wells;
wells.append(*pVerticalWell);
manager->updateVerticalWells(wells);
}
} else if(wellType == NM_WELL_MODEL::Vertical_Fractured_Well) {
nmDataVerticalFracturedWell* pVerticalFracturedWell = dynamic_cast<nmDataVerticalFracturedWell*>(pTargetWell);
if(pVerticalFracturedWell != nullptr) {
QVector<nmDataVerticalFracturedWell> wells;
wells.append(*pVerticalFracturedWell);
manager->updateVerticalFracturedWells(wells);
}
} else if(wellType == NM_WELL_MODEL::Horizontal_Fractured_Well) {
nmDataHorizontalFracturedWell* pHorizontalFracturedWell = dynamic_cast<nmDataHorizontalFracturedWell*>(pTargetWell);
if(pHorizontalFracturedWell != nullptr) {
QVector<nmDataHorizontalFracturedWell> wells;
wells.append(*pHorizontalFracturedWell);
manager->updateHorizontalFracturedWells(wells);
}
// 全部井参数落地后统一刷新一次右侧参数面板。
if(manager->getAttrRegistry()) {
manager->getAttrRegistry()->refreshAll();
}
}
}
@ -1555,6 +1445,12 @@ void nmWxAutomaticFitting::onFittingFinished(bool success, const QString& messag
disconnect(m_autoFitterLM, SIGNAL(fittingFinished(bool, QString)),
this, SLOT(onFittingFinished(bool, QString)));
}
// 拟合候选值通过参数副本写回,结束时统一把最终值同步到右侧参数面板。
nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance();
if(manager && manager->getAttrRegistry()) {
manager->getAttrRegistry()->refreshAll();
}
if(success) {
// 只有成功拟合的结果才用于生成下一轮范围,失败结果不污染当前配置。
updateBestParametersToTable();
@ -1692,12 +1588,9 @@ void nmWxAutomaticFitting::updateBestParametersToTable()
if(m_sCheckBox->isChecked()) enabledParams.append(1); // 表皮系数
if(m_cCheckBox->isChecked()) enabledParams.append(2); // 井筒储集系数
if(m_phiCheckBox->isChecked()) enabledParams.append(3); // 孔隙度
if(m_hCheckBox->isChecked()) enabledParams.append(4); // 储层厚度
if(m_ctCheckBox->isChecked()) enabledParams.append(5); // 综合压缩系数
if(m_cfCheckBox->isChecked()) enabledParams.append(6); // 岩石压缩系数
if(m_swiCheckBox->isChecked()) enabledParams.append(7); // 初始含水饱和度
if(m_dfcCheckBox->isChecked()) enabledParams.append(8); // 裂缝导流能力
if(m_fractureHalfLengthCheckBox->isChecked()) enabledParams.append(9); // 裂缝半长
if(m_swiCheckBox->isChecked()) enabledParams.append(4); // 初始含水饱和度
if(m_dfcCheckBox->isChecked()) enabledParams.append(5); // 裂缝导流能力
if(m_fractureHalfLengthCheckBox->isChecked()) enabledParams.append(6); // 裂缝半长
// 更新参数值和范围
for (int i = 0; i < bestSolution.size() && i < enabledParams.size(); ++i) {

@ -389,6 +389,7 @@ nmWxAutomaticfittingStart::nmWxAutomaticfittingStart(QWidget *parent)
, m_targetError(0.001)
, m_wellName("")
, m_isFinished(false)
, m_isFinalizing(false)
, m_bestFitnessEver(1e10)
, m_startTime()
{
@ -592,6 +593,8 @@ void nmWxAutomaticfittingStart::setAutoFitter(nmCalculationAutoFitPSO* autoFitte
this, SLOT(onFittingProgress(int, double)));//更新进度条、当前迭代数、当前误差
connect(m_autoFitterPSO, SIGNAL(fittingFinished(bool, QString)),
this, SLOT(onFittingFinished(bool, QString)));//显示结束状态
connect(m_autoFitterPSO, SIGNAL(finalizingStarted()),
this, SLOT(onFinalizingStarted()));
connect(m_autoFitterPSO, SIGNAL(logMessageGenerated(QString)),
this, SLOT(onLogMessageReceived(QString)));//把 PSO 内部日志显示到窗口
connect(m_autoFitterPSO, SIGNAL(bestCurveUpdated(QVector<QVector<double> >,QVector<QVector<double> >,int,double)),
@ -618,6 +621,8 @@ void nmWxAutomaticfittingStart::setAutoFitter(nmCalculationAutoFitLM* autoFitter
this, SLOT(onFittingProgress(int, double)));
connect(m_autoFitterLM, SIGNAL(fittingFinished(bool, QString)),
this, SLOT(onFittingFinished(bool, QString)));
connect(m_autoFitterLM, SIGNAL(finalizingStarted()),
this, SLOT(onFinalizingStarted()));
connect(m_autoFitterLM, SIGNAL(logMessageGenerated(QString)),
this, SLOT(onLogMessageReceived(QString)));
connect(m_autoFitterLM, SIGNAL(bestCurveUpdated(QVector<QVector<double> >,QVector<QVector<double> >,int,double)),
@ -714,6 +719,7 @@ void nmWxAutomaticfittingStart::onFittingProgress(int iteration, double fitness)
void nmWxAutomaticfittingStart::onFittingFinished(bool success, const QString& message)
{
m_isFinished = true;
m_isFinalizing = false;
const QString algorithmName = m_algorithmName;
@ -801,6 +807,15 @@ void nmWxAutomaticfittingStart::onFittingFinished(bool success, const QString& m
}
void nmWxAutomaticfittingStart::onFinalizingStarted()
{
// 最终完整求解负责生成正式快照,此阶段不能再次停止或关闭进度窗口。
m_isFinalizing = true;
stopButton->setEnabled(false);
stopButton->setText(tr("Finalizing..."));
addLogMessage(tr("Generating the final result with the current best parameters..."));
}
void nmWxAutomaticfittingStart::onStopButtonClicked()
{
const bool isRunning = (m_autoFitterPSO && m_autoFitterPSO->isRunning()) ||
@ -814,6 +829,9 @@ void nmWxAutomaticfittingStart::onStopButtonClicked()
QMessageBox::No);
if (ret == QMessageBox::Yes) {
// 先锁定按钮,等优化循环退出后由 finalizingStarted() 切换到最终求解状态。
stopButton->setEnabled(false);
stopButton->setText(tr("Stopping..."));
if (m_autoFitterPSO) {
m_autoFitterPSO->stopFitting();
} else if (m_autoFitterLM) {
@ -885,6 +903,14 @@ void nmWxAutomaticfittingStart::closeEvent(QCloseEvent *event)
const bool isRunning = (m_autoFitterPSO && m_autoFitterPSO->isRunning()) ||
(m_autoFitterLM && m_autoFitterLM->isRunning());
const QString algorithmName = m_algorithmName;
if(m_isFinalizing && !m_isFinished) {
QMessageBox::information(
this,
tr("Finalizing"),
tr("The final result is being generated. Please wait for the calculation to finish."));
event->ignore();
return;
}
if (isRunning && !m_isFinished) {
int ret = QMessageBox::question(this, tr("Confirm Close"),

Loading…
Cancel
Save