fix(numerical): 保留参数或网格变化后的求解结果

- 分离结果存在状态与当前输入匹配状态
- 独立持久化最后一次结果的井顺序和结果文件
- 兼容旧成果并按结果井顺序恢复结果界面
feature/grid-solver-workflow-20260827
lh 2 weeks ago
parent 5427b97308
commit cdc18b59b9

@ -487,6 +487,8 @@ public:
void insertSolverWell(int nIndex, const nmSolverWellRef& oWellRef);
/** @brief 返回网格确定的 DLL 井槽位顺序,与包含井选择相互独立。 */
QVector<nmSolverWellRef> getSolverWellOrder() const;
/** @brief 返回最后一次成功求解使用的 DLL 井槽位顺序。 */
QVector<nmSolverWellRef> getResultSolverWellOrder() const;
/** @brief 整体替换 DLL 井槽位顺序,并由分析方案统一重编号。 */
void setSolverWellOrder(const QVector<nmSolverWellRef>& vecSolverWellOrder);
/**
@ -515,6 +517,8 @@ public:
void discardPebiResults();
/** @brief 判断当前 PEBI 网格是否与最新输入一致。 */
bool isPebiGridValid() const;
/** @brief 判断是否存在一套可保存和查看的 PEBI 结果。 */
bool hasPebiResults() const;
/** @brief 设置结果查看井;不改变主分析井和参数编辑井。 */
void setCurrentResultWellCode(const QString& sWellCode);

@ -127,6 +127,13 @@ public:
/** @brief 整体替换求解器顺序,并按容器顺序重新编号。 */
void setSolverWellOrder(const QVector<nmSolverWellRef>& vecOrder);
/** @brief 返回最后一次成功求解所使用的 DLL 井槽位顺序。 */
QVector<nmSolverWellRef> getResultSolverWellOrder() const;
/** @brief 从成果文件恢复结果存在状态、当前性和独立井顺序。 */
void restoreResultState(const QVector<nmSolverWellRef>& vecOrder,
bool bAvailable,
bool bCurrent);
/** @brief 选井或几何输入改变后,使当前网格和结果失效。 */
void invalidateGrid();
/** @brief 产量、PVT 等只影响求解的输入改变后,仅使结果失效。 */
@ -145,6 +152,10 @@ public:
/** @brief 当前网格版本与几何输入一致且井顺序非空时返回 true。 */
bool isGridValid() const;
/** @brief 内存中存在一套可保存和查看的完整求解结果时返回 true。 */
bool hasResults() const;
/** @brief 已有结果仍对应当前输入时返回 true。 */
bool areResultsCurrent() const;
/** @brief 当前结果已提交且所依赖网格仍有效时返回 true。 */
bool areResultsValid() const;
/** @brief 返回最新几何/选井输入版本,供后台任务启动时捕获。 */
@ -160,6 +171,8 @@ public:
private:
/** @brief 规范化有产量包含井,移除空编码、主井和重复项。 */
void normalizeIncludedWells();
/** @brief 保证当前结果井属于最后结果顺序,否则回退到第一口真实井。 */
void normalizeCurrentResultWellCode();
private:
QString m_sPrimaryWellCode; ///< 主分析井的项目唯一 WellCode。
@ -170,11 +183,13 @@ private:
QString m_sCurrentResultWellCode; ///< 当前结果查看井,仅属于展示状态。
QVector<nmSolverWellRef> m_vecSolverWellOrder; ///< 网格确定的 DLL 数组下标映射。
QVector<nmSolverWellRef> m_vecResultSolverWellOrder; ///< 最后结果使用的独立槽位映射。
quint64 m_nGridInputRevision; ///< 几何、选井等网格输入的最新版本。
quint64 m_nBuiltGridRevision; ///< 当前缓存网格对应版本0 表示无有效网格。
quint64 m_nResultInputRevision; ///< 产量、角色、PVT 等求解输入版本。
bool m_bResultsValid; ///< 当前结果是否已成功映射且未过期。
bool m_bResultsAvailable; ///< 是否存在一套可保存和查看的结果载荷。
bool m_bResultsValid; ///< 已有结果是否仍对应当前输入和网格。
};
#endif // NMDATANUMERICALANALYSISCASE_H

@ -3541,6 +3541,21 @@ bool nmDataAnalyzeManager::isWellAvailableForResult(
return false;
}
// 已有结果使用求解完成时冻结的井顺序。当前参数、选井或网格变化
// 不能把最后一套结果中的井从结果界面移除。
if(m_oNumericalAnalysisCase.hasResults()) {
const QVector<nmSolverWellRef> vecResultOrder =
m_oNumericalAnalysisCase.getResultSolverWellOrder();
for(int nIndex = 0; nIndex < vecResultOrder.size(); ++nIndex) {
const nmSolverWellRef& oWellRef = vecResultOrder[nIndex];
if(oWellRef.m_eEntryKind == NM_SolverEntry_Well &&
oWellRef.m_sWellCode == sWellCode) {
return true;
}
}
return false;
}
// 第一步:主分析井是结果界面的固定入口,即使当前处于关井段也必须保留。
if(sWellCode == getPrimaryWellCode()) {
return true;
@ -3586,6 +3601,12 @@ QVector<nmSolverWellRef> nmDataAnalyzeManager::getSolverWellOrder() const
return m_oNumericalAnalysisCase.getSolverWellOrder();
}
QVector<nmSolverWellRef>
nmDataAnalyzeManager::getResultSolverWellOrder() const
{
return m_oNumericalAnalysisCase.getResultSolverWellOrder();
}
void nmDataAnalyzeManager::setSolverWellOrder(
const QVector<nmSolverWellRef>& vecSolverWellOrder)
{
@ -3682,6 +3703,11 @@ bool nmDataAnalyzeManager::isPebiGridValid() const
return m_oNumericalAnalysisCase.isGridValid();
}
bool nmDataAnalyzeManager::hasPebiResults() const
{
return m_oNumericalAnalysisCase.hasResults();
}
void nmDataAnalyzeManager::setCurrentResultWellCode(
const QString& sWellCode)
{
@ -4078,8 +4104,11 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath)
NM_CASE_WELL_MODE eSavedPrimaryWellMode = NM_CaseWell_Observation;
bool bSavedIncludeOtherWells = false;
double dSavedPebiGridControl = 150.0;
bool bSavedResultsAvailable = false;
bool bSavedResultsCurrent = false;
QVector<nmCalculationWellRef> vecSavedIncludedWells;
QVector<nmSolverWellRef> vecSavedSolverOrder;
QVector<nmSolverWellRef> vecSavedResultSolverOrder;
// 第一步:版本 2 必须完整保存分析方案,缺字段时不再按旧项目规则猜测。
if(!doc.HasMember("NumericalAnalysisCase") ||
@ -4206,8 +4235,8 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath)
vecSavedIncludedWells.append(nmCalculationWellRef(sWellCode, eMode));
}
if(!setEffectiveWellCodes.contains(sSavedCurrentResultWellCode)) {
qWarning() << "Current result WellCode is not effective:"
if(!mapSavedWellTypes.contains(sSavedCurrentResultWellCode)) {
qWarning() << "Current result WellCode is absent from Wells:"
<< sSavedCurrentResultWellCode;
return false;
}
@ -4275,6 +4304,113 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath)
}
}
// 第五步:新成果独立保存最后一次结果的井顺序和当前性。旧版本 2 文件
// 没有这些可选字段时,先以当前 SolverWellOrder 作为候选,最终由结果
// 二进制文件是否完整决定是否确实存在结果。
const bool bHasAnyResultStateField =
oCaseJson.HasMember("ResultsAvailable") ||
oCaseJson.HasMember("ResultsCurrent") ||
oCaseJson.HasMember("ResultSolverWellOrder");
const bool bHasCompleteResultState =
oCaseJson.HasMember("ResultsAvailable") &&
oCaseJson["ResultsAvailable"].IsBool() &&
oCaseJson.HasMember("ResultsCurrent") &&
oCaseJson["ResultsCurrent"].IsBool() &&
oCaseJson.HasMember("ResultSolverWellOrder") &&
oCaseJson["ResultSolverWellOrder"].IsArray();
if(bHasAnyResultStateField && !bHasCompleteResultState) {
qWarning() << "Numerical result state contains invalid fields:"
<< filePath;
return false;
}
if(bHasCompleteResultState) {
bSavedResultsAvailable =
oCaseJson["ResultsAvailable"].GetBool();
bSavedResultsCurrent = oCaseJson["ResultsCurrent"].GetBool();
if(bSavedResultsCurrent && !bSavedResultsAvailable) {
qWarning() << "Current result state has no available result:"
<< filePath;
return false;
}
QSet<QString> setResultWellCodes;
const rapidjson::Value& vecResultOrderJson =
oCaseJson["ResultSolverWellOrder"];
for(rapidjson::SizeType nIndex = 0;
nIndex < vecResultOrderJson.Size();
++nIndex) {
const rapidjson::Value& oOrderJson =
vecResultOrderJson[nIndex];
if(!oOrderJson.IsObject() ||
!oOrderJson.HasMember("WellCode") ||
!oOrderJson["WellCode"].IsString() ||
!oOrderJson.HasMember("WellType") ||
!oOrderJson["WellType"].IsInt() ||
!oOrderJson.HasMember("EntryKind") ||
!oOrderJson["EntryKind"].IsInt()) {
qWarning() << "ResultSolverWellOrder contains an invalid entry:"
<< filePath;
return false;
}
const QString sWellCode = QString::fromUtf8(
oOrderJson["WellCode"].GetString());
const NM_WELL_MODEL eWellType = static_cast<NM_WELL_MODEL>(
oOrderJson["WellType"].GetInt());
const NM_SOLVER_ENTRY_KIND eEntryKind =
static_cast<NM_SOLVER_ENTRY_KIND>(
oOrderJson["EntryKind"].GetInt());
if(eEntryKind == NM_SolverEntry_Well) {
const bool bSupportedResultWellType =
eWellType == NM_WELL_MODEL::Vertical_Well ||
eWellType == NM_WELL_MODEL::Vertical_Fractured_Well ||
eWellType == NM_WELL_MODEL::Horizontal_Fractured_Well;
if(!mapSavedWellTypes.contains(sWellCode) ||
setResultWellCodes.contains(sWellCode) ||
!bSupportedResultWellType) {
qWarning() << "ResultSolverWellOrder contains an invalid real well:"
<< sWellCode;
return false;
}
setResultWellCodes.insert(sWellCode);
} else if(eEntryKind == NM_SolverEntry_ManualFracture) {
if(!sWellCode.isEmpty() ||
eWellType != NM_WELL_MODEL::Unknow_Well) {
qWarning() << "Result manual fracture entry is malformed:"
<< filePath;
return false;
}
} else {
qWarning() << "ResultSolverWellOrder contains an unknown entry kind:"
<< filePath;
return false;
}
vecSavedResultSolverOrder.append(nmSolverWellRef(
static_cast<int>(nIndex), eWellType,
sWellCode, eEntryKind));
}
if(bSavedResultsAvailable &&
(vecSavedResultSolverOrder.isEmpty() ||
!setResultWellCodes.contains(sSavedCurrentResultWellCode))) {
qWarning() << "Available result has no complete result well order:"
<< filePath;
return false;
}
} else {
vecSavedResultSolverOrder = vecSavedSolverOrder;
}
if(!bSavedResultsAvailable &&
!setEffectiveWellCodes.contains(sSavedCurrentResultWellCode)) {
qWarning() << "Current result WellCode is not effective:"
<< sSavedCurrentResultWellCode;
return false;
}
// 清空现有数据,确保从头加载新数据
foreach(nmDataFracture* pFractureData, m_vFractureData) {
delete pFractureData; // 释放堆上分配的 nmDataFracture 对象
@ -4662,6 +4798,10 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath)
m_oNumericalAnalysisCase.setIncludedWells(vecSavedIncludedWells);
m_oNumericalAnalysisCase.setIncludeOtherWells(bSavedIncludeOtherWells);
m_oNumericalAnalysisCase.setSolverWellOrder(vecSavedSolverOrder);
m_oNumericalAnalysisCase.restoreResultState(
vecSavedResultSolverOrder,
bSavedResultsAvailable,
bSavedResultsCurrent);
m_oNumericalAnalysisCase.setCurrentResultWellCode(
sSavedCurrentResultWellCode.isEmpty()
? sSavedPrimaryWellCode : sSavedCurrentResultWellCode);
@ -4710,9 +4850,10 @@ bool nmDataAnalyzeManager::WriteProjectData(const QString & filePath)
const QString sPrimaryWellCode = getPrimaryWellCode();
const QString sCurrentResultWellCode = getCurrentResultWellCode();
if(!setWellCodes.contains(sPrimaryWellCode) ||
!setWellCodes.contains(sCurrentResultWellCode) ||
!qIsFinite(getPebiGridControl()) ||
getPebiGridControl() <= 0.0) {
qWarning() << "Cannot save numerical project: invalid primary well or GridControl.";
qWarning() << "Cannot save numerical project: invalid primary/result well or GridControl.";
return false;
}
@ -4730,12 +4871,6 @@ bool nmDataAnalyzeManager::WriteProjectData(const QString & filePath)
}
setEffectiveWellCodes.insert(oWellRef.m_sWellCode);
}
if(!setEffectiveWellCodes.contains(sCurrentResultWellCode)) {
qWarning() << "Cannot save numerical project: result WellCode is not effective:"
<< sCurrentResultWellCode;
return false;
}
QSet<QString> setOrderedWellCodes;
QVector<nmSolverWellRef> vecValidatedSolverOrder = getSolverWellOrder();
// 求解器顺序只属于有效网格。网格已经失效时保存空顺序,
@ -4772,6 +4907,56 @@ bool nmDataAnalyzeManager::WriteProjectData(const QString & filePath)
return false;
}
// 最后结果使用独立井顺序,不要求继续属于当前有效计算井,也不要求井型
// 仍与当前模型一致;但对应 WellCode 必须仍存在,才能保存单井曲线数据。
const bool bHasResults = m_oNumericalAnalysisCase.hasResults();
QVector<nmSolverWellRef> vecValidatedResultOrder;
if(bHasResults) {
vecValidatedResultOrder =
m_oNumericalAnalysisCase.getResultSolverWellOrder();
}
QSet<QString> setResultWellCodes;
for(int nIndex = 0;
nIndex < vecValidatedResultOrder.size();
++nIndex) {
const nmSolverWellRef& oWellRef = vecValidatedResultOrder[nIndex];
if(oWellRef.m_eEntryKind == NM_SolverEntry_Well) {
const bool bSupportedResultWellType =
oWellRef.m_eWellType == NM_WELL_MODEL::Vertical_Well ||
oWellRef.m_eWellType == NM_WELL_MODEL::Vertical_Fractured_Well ||
oWellRef.m_eWellType == NM_WELL_MODEL::Horizontal_Fractured_Well;
if(!setWellCodes.contains(oWellRef.m_sWellCode) ||
setResultWellCodes.contains(oWellRef.m_sWellCode) ||
!bSupportedResultWellType) {
qWarning() << "Cannot save numerical project: invalid result WellCode:"
<< oWellRef.m_sWellCode;
return false;
}
setResultWellCodes.insert(oWellRef.m_sWellCode);
} else if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) {
if(!oWellRef.m_sWellCode.isEmpty() ||
oWellRef.m_eWellType != NM_WELL_MODEL::Unknow_Well) {
qWarning() << "Cannot save numerical project: malformed result fracture entry.";
return false;
}
} else {
qWarning() << "Cannot save numerical project: unknown result entry kind.";
return false;
}
}
if(bHasResults &&
(vecValidatedResultOrder.isEmpty() ||
!setResultWellCodes.contains(sCurrentResultWellCode))) {
qWarning() << "Cannot save numerical project: result order is incomplete.";
return false;
}
if(!bHasResults &&
!setEffectiveWellCodes.contains(sCurrentResultWellCode)) {
qWarning() << "Cannot save numerical project: result WellCode is not effective:"
<< sCurrentResultWellCode;
return false;
}
// 第三步:基础身份校验通过后再构造 JSON避免生成无法重新加载的半成品文件。
rapidjson::Document doc;
doc.SetObject(); // 根节点是对象
@ -5030,6 +5215,28 @@ bool nmDataAnalyzeManager::WriteProjectData(const QString & filePath)
vecSolverOrderJson.PushBack(oOrderJson, allocator);
}
oCaseJson.AddMember("SolverWellOrder", vecSolverOrderJson, allocator);
oCaseJson.AddMember("ResultsAvailable", bHasResults, allocator);
oCaseJson.AddMember("ResultsCurrent",
m_oNumericalAnalysisCase.areResultsCurrent(), allocator);
rapidjson::Value vecResultOrderJson(rapidjson::kArrayType);
for(int nIndex = 0;
nIndex < vecValidatedResultOrder.size();
++nIndex) {
const nmSolverWellRef& oWellRef = vecValidatedResultOrder[nIndex];
rapidjson::Value oOrderJson(rapidjson::kObjectType);
QByteArray baWellCode = oWellRef.m_sWellCode.toUtf8();
oOrderJson.AddMember("WellCode",
rapidjson::Value(baWellCode.constData(), allocator).Move(),
allocator);
oOrderJson.AddMember("WellType",
static_cast<int>(oWellRef.m_eWellType), allocator);
oOrderJson.AddMember("EntryKind",
static_cast<int>(oWellRef.m_eEntryKind), allocator);
vecResultOrderJson.PushBack(oOrderJson, allocator);
}
oCaseJson.AddMember("ResultSolverWellOrder",
vecResultOrderJson, allocator);
doc.AddMember("NumericalAnalysisCase", oCaseJson, allocator);
// 序列化 时间步数据
@ -5111,37 +5318,30 @@ bool nmDataAnalyzeManager::saveNmResult(QString sRstCode, iSubWndFitting* pSubWn
sTimeStepDataDirPath + "/PressureTimeSteps.bin";
QString sWellRstDataPath = sDir + "/WellRst";
// 第二步:无有效网格时不保存内存中残留的 VTK 对象和结果,并清理同目录旧文件。
if(!isPebiGridValid()) {
QFile::remove(sGridPath + "/CurrentGrid.vtu");
QFile::remove(sResultGridFilePath);
QFile::remove(sResultWellLocationPath);
QFile::remove(sTimeStepDataFilePath);
if(QDir(sWellRstDataPath).exists() &&
!clearDirectoryContents(sWellRstDataPath)) {
return false;
}
return true;
}
// 方案标记为有效网格时,必须同时持有可写出的 VTK 网格。
if(!m_pVtkUnstructuredGrid) {
qWarning() << "Cannot save a valid PEBI grid: VTK grid is missing.";
// 第二步:当前网格和最后结果是两套独立载荷。当前网格失效时只删除
// CurrentGrid.vtu不能连带删除已经成功生成的结果。
const bool bGridValid = isPebiGridValid();
const bool bHasResults = m_oNumericalAnalysisCase.hasResults();
if((bGridValid || bHasResults) &&
!this->ensureDirectoryExists(sGridPath)) {
return false;
}
if(!this->ensureDirectoryExists(sGridPath)) {
return false;
}
// 第三步:保存与 SolverWellOrder 同版本的实时网格。
if(!this->writeUnstructuredGridToFile(
m_pVtkUnstructuredGrid, sGridPath + "/CurrentGrid.vtu")) {
return false;
if(bGridValid) {
if(!m_pVtkUnstructuredGrid) {
qWarning() << "Cannot save a valid PEBI grid: VTK grid is missing.";
return false;
}
if(!this->writeUnstructuredGridToFile(
m_pVtkUnstructuredGrid, sGridPath + "/CurrentGrid.vtu")) {
return false;
}
} else {
QFile::remove(sGridPath + "/CurrentGrid.vtu");
}
// 第四步:网格有效但结果无效时只保存网格,并删除上一次求解的残留文件。
if(!m_oNumericalAnalysisCase.areResultsValid()) {
// 第三步:确实没有任何已生成结果时,才清理同目录中的旧结果文件。
if(!bHasResults) {
QFile::remove(sResultGridFilePath);
QFile::remove(sResultWellLocationPath);
QFile::remove(sTimeStepDataFilePath);
@ -5152,13 +5352,14 @@ bool nmDataAnalyzeManager::saveNmResult(QString sRstCode, iSubWndFitting* pSubWn
return true;
}
// 结果标记有效时,基础网格和时间步压力必须同时完整存在。
// 结果存在时,基础网格和时间步压力必须同时完整存在;是否仍对应当前
// 输入不影响保存和查看。
if(!m_pResultBaseGrid || m_mapTimeStepDataP.isEmpty()) {
qWarning() << "Cannot save valid PEBI results: result grid or time steps are missing.";
qWarning() << "Cannot save available PEBI results: result grid or time steps are missing.";
return false;
}
// 第步:保存场图基础网格、井位置和全部时间步压力。
// 第步:保存场图基础网格、井位置和全部时间步压力。
if(!this->writeUnstructuredGridToFile(
m_pResultBaseGrid, sResultGridFilePath)) {
return false;
@ -5177,14 +5378,15 @@ bool nmDataAnalyzeManager::saveNmResult(QString sRstCode, iSubWndFitting* pSubWn
return false;
}
// 第六步:先清空目标目录,再按 WellCode 保存每口真实计算井的结果。
// 第五步:先清空目标目录,再按最后结果的 WellCode 顺序保存单井结果。
if(!this->ensureDirectoryExists(sWellRstDataPath)) {
return false;
}
if(!clearDirectoryContents(sWellRstDataPath)) {
return false;
}
QVector<nmSolverWellRef> vecWells = this->getSolverWellOrder();
QVector<nmSolverWellRef> vecWells =
this->getResultSolverWellOrder();
// 遍历每口井,保存里面计算出来的数据
for(int wellIdx = 0; wellIdx < vecWells.size(); ++wellIdx) {
@ -5196,10 +5398,9 @@ bool nmDataAnalyzeManager::saveNmResult(QString sRstCode, iSubWndFitting* pSubWn
nmDataWellBase* pWellData =
this->findWellByCode(oWellRef.m_sWellCode);
if(pWellData) {
if(!this->saveWellCalRstData(sWellRstDataPath, pWellData)) {
return false;
}
if(pWellData == nullptr ||
!this->saveWellCalRstData(sWellRstDataPath, pWellData)) {
return false;
}
}
@ -5214,6 +5415,15 @@ bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir)
return false;
}
// JSON 中的当前性在读取当前网格前先保存下来markGridBuilt() 会按运行时
// 语义把已有结果标为非当前,完整载荷读完后再恢复保存时的状态。
const bool bSavedResultsCurrent =
m_oNumericalAnalysisCase.areResultsCurrent();
const bool bDeclaredResultsAvailable =
m_oNumericalAnalysisCase.hasResults();
const QVector<nmSolverWellRef> vecResultSolverOrder =
getResultSolverWellOrder();
// 第一步:先检查分析方案、网格文件和结果文件是否构成完整的一组。
QString sCurrentGridPath = sLoadAnalDir + "/Grid/CurrentGrid.vtu";
QString sResultGridPath = sLoadAnalDir + "/Grid/ResultGrid.vtu";
@ -5233,6 +5443,7 @@ bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir)
const bool bHasAnyWellResultFile = !QDir(sWellRstDataPath).entryList(
listResultFilters, QDir::Files).isEmpty();
const bool bHasSolverWellOrder = !getSolverWellOrder().isEmpty();
const bool bHasResultSolverOrder = !vecResultSolverOrder.isEmpty();
const bool bHasAnyResultFile = bHasResultGridFile ||
bHasResultWellLocationFile || bHasTimeStepDataFile ||
bHasAnyWellResultFile;
@ -5245,10 +5456,15 @@ bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir)
return false;
}
if(bHasAnyResultFile &&
(!bHasCurrentGridFile || !bHasCompleteResultFiles)) {
(!bHasCompleteResultFiles || !bHasResultSolverOrder)) {
qWarning() << "PEBI result files are incomplete:" << sLoadAnalDir;
return false;
}
if(bDeclaredResultsAvailable && !bHasCompleteResultFiles) {
qWarning() << "Declared PEBI results are missing from disk:"
<< sLoadAnalDir;
return false;
}
// 第二步:读取当前网格;只有文件和求解器顺序同时存在时才标记有效。
if(bHasCurrentGridFile &&
@ -5264,7 +5480,8 @@ bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir)
m_oNumericalAnalysisCase.markGridBuilt();
}
// 第三步:读取完整求解结果;网格项目允许尚未执行过求解。
// 第三步:读取完整求解结果。结果使用自己的网格和井顺序,不要求
// CurrentGrid.vtu 仍然有效或仍然存在。
if(bHasResultGridFile &&
!this->readUnstructuredGridFromFile(m_pResultBaseGrid,
sResultGridPath)) {
@ -5302,12 +5519,13 @@ bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir)
}
// 第五步:有完整场结果时,必须同时具备每口真实求解井的结果。
// SolverWellOrder 是 DLL 返回槽位与井对象之间的唯一映射;手工裂缝只占槽位,
// 不对应 Map 中的井对象,也没有独立的结果文件。
// ResultSolverWellOrder 是最后结果槽位与井对象之间的映射;手工裂缝只占
// 槽位,不对应 Map 中的井对象,也没有独立的结果文件。
if(bHasCompleteResultFiles) {
QVector<nmSolverWellRef> vecSolverOrder = getSolverWellOrder();
for(int nIndex = 0; nIndex < vecSolverOrder.size(); ++nIndex) {
const nmSolverWellRef& oWellRef = vecSolverOrder[nIndex];
for(int nIndex = 0;
nIndex < vecResultSolverOrder.size();
++nIndex) {
const nmSolverWellRef& oWellRef = vecResultSolverOrder[nIndex];
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) {
continue;
}
@ -5374,18 +5592,23 @@ bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir)
}
}
// 结果井属于显示状态,但也必须在流量恢复后满足当前结果井规则。
// 二进制载荷完整后再登记结果存在。旧版本 2 项目没有独立结果状态时
// 也会在这里按实际文件恢复,当前输入状态则保守标为不一致。
m_oNumericalAnalysisCase.restoreResultState(
bHasCompleteResultFiles
? vecResultSolverOrder : QVector<nmSolverWellRef>(),
bHasCompleteResultFiles,
bHasCompleteResultFiles && bHasCurrentGridFile &&
bSavedResultsCurrent);
// 结果井属于显示状态;有历史结果时按结果井顺序校验,无结果时按当前方案。
if(!isWellAvailableForResult(getCurrentResultWellCode())) {
qWarning() << "Loaded result WellCode is not available:"
<< getCurrentResultWellCode();
return false;
}
if(bHasCurrentGridFile && bHasCompleteResultFiles) {
m_oNumericalAnalysisCase.markResultsAvailable();
}
if (m_pVtkUnstructuredGrid != nullptr)
if (m_pVtkUnstructuredGrid != nullptr || m_pResultBaseGrid != nullptr)
m_bIsLoadData = true;
return true;
@ -6017,9 +6240,10 @@ QMap<QString, QPointF> nmDataAnalyzeManager::getAllWellLocations() const
bool nmDataAnalyzeManager::saveWellLocations(const QString& filePath)
{
// 第一步:结果位置必须逐口覆盖求解器中的真实井,手工裂缝不对应 Map 井。
// 第一步:结果位置必须逐口覆盖最后结果中的真实井,手工裂缝不对应 Map 井。
QSet<QString> setExpectedWellCodes;
QVector<nmSolverWellRef> vecSolverOrder = getSolverWellOrder();
QVector<nmSolverWellRef> vecSolverOrder =
getResultSolverWellOrder();
for(int nIndex = 0; nIndex < vecSolverOrder.size(); ++nIndex) {
const nmSolverWellRef& oWellRef = vecSolverOrder[nIndex];
if(oWellRef.m_eEntryKind == NM_SolverEntry_Well) {
@ -6089,9 +6313,10 @@ bool nmDataAnalyzeManager::loadWellLocations(const QString& filePath)
return false;
}
// 第二步:加载结果必须与 SolverWellOrder 中的真实井 WellCode 完整对应。
// 第二步:加载结果必须与最后结果井顺序中的真实井 WellCode 完整对应。
QSet<QString> setExpectedWellCodes;
QVector<nmSolverWellRef> vecSolverOrder = getSolverWellOrder();
QVector<nmSolverWellRef> vecSolverOrder =
getResultSolverWellOrder();
for(int nIndex = 0; nIndex < vecSolverOrder.size(); ++nIndex) {
const nmSolverWellRef& oWellRef = vecSolverOrder[nIndex];
if(oWellRef.m_eEntryKind == NM_SolverEntry_Well) {

@ -66,8 +66,9 @@ void nmDataNumericalAnalysisCase::setPrimaryWellCode(const QString& sWellCode)
const QString sOldPrimaryWellCode = m_sPrimaryWellCode;
m_sPrimaryWellCode = sWellCode;
normalizeIncludedWells();
if(m_sCurrentResultWellCode.isEmpty() ||
m_sCurrentResultWellCode == sOldPrimaryWellCode) {
if(!m_bResultsAvailable &&
(m_sCurrentResultWellCode.isEmpty() ||
m_sCurrentResultWellCode == sOldPrimaryWellCode)) {
m_sCurrentResultWellCode = m_sPrimaryWellCode;
}
invalidateGrid();
@ -251,6 +252,7 @@ void nmDataNumericalAnalysisCase::appendSolverWell(
nmSolverWellRef oIndexedRef = oWellRef;
oIndexedRef.m_nSolverIndex = m_vecSolverWellOrder.size();
m_vecSolverWellOrder.append(oIndexedRef);
m_bResultsValid = false;
}
void nmDataNumericalAnalysisCase::insertSolverWell(
@ -267,6 +269,7 @@ void nmDataNumericalAnalysisCase::insertSolverWell(
++nOrderIndex) {
m_vecSolverWellOrder[nOrderIndex].m_nSolverIndex = nOrderIndex;
}
m_bResultsValid = false;
}
bool nmDataNumericalAnalysisCase::removeSolverWell(int nIndex)
@ -295,6 +298,32 @@ void nmDataNumericalAnalysisCase::setSolverWellOrder(
m_bResultsValid = false;
}
QVector<nmSolverWellRef>
nmDataNumericalAnalysisCase::getResultSolverWellOrder() const
{
return m_vecResultSolverWellOrder;
}
void nmDataNumericalAnalysisCase::restoreResultState(
const QVector<nmSolverWellRef>& vecOrder,
bool bAvailable,
bool bCurrent)
{
m_vecResultSolverWellOrder = vecOrder;
for(int nIndex = 0;
nIndex < m_vecResultSolverWellOrder.size();
++nIndex) {
m_vecResultSolverWellOrder[nIndex].m_nSolverIndex = nIndex;
}
m_bResultsAvailable = bAvailable &&
!m_vecResultSolverWellOrder.isEmpty();
m_bResultsValid = m_bResultsAvailable && bCurrent;
if(m_bResultsAvailable) {
normalizeCurrentResultWellCode();
}
}
// ==================== 网格与结果版本管理 ====================
void nmDataNumericalAnalysisCase::invalidateGrid()
@ -341,7 +370,14 @@ void nmDataNumericalAnalysisCase::discardResults()
void nmDataNumericalAnalysisCase::markResultsAvailable()
{
m_bResultsValid = isGridValid();
if(!isGridValid()) {
return;
}
m_vecResultSolverWellOrder = m_vecSolverWellOrder;
m_bResultsAvailable = true;
m_bResultsValid = true;
normalizeCurrentResultWellCode();
}
bool nmDataNumericalAnalysisCase::markResultsAvailableIfCurrent(
@ -354,8 +390,16 @@ bool nmDataNumericalAnalysisCase::markResultsAvailableIfCurrent(
nResultInputRevision == m_nResultInputRevision;
// 第二步:只有输入未变化且网格仍有效时,才允许旧线程提交结果。
m_bResultsValid = bInputCurrent && isGridValid();
return m_bResultsValid;
// 校验失败不能破坏此前已经存在的结果。
if(!bInputCurrent || !isGridValid()) {
return false;
}
m_vecResultSolverWellOrder = m_vecSolverWellOrder;
m_bResultsAvailable = true;
m_bResultsValid = true;
normalizeCurrentResultWellCode();
return true;
}
bool nmDataNumericalAnalysisCase::isGridValid() const
@ -367,7 +411,18 @@ bool nmDataNumericalAnalysisCase::isGridValid() const
bool nmDataNumericalAnalysisCase::areResultsValid() const
{
return m_bResultsValid && isGridValid();
return areResultsCurrent() && isGridValid();
}
bool nmDataNumericalAnalysisCase::hasResults() const
{
return m_bResultsAvailable &&
!m_vecResultSolverWellOrder.isEmpty();
}
bool nmDataNumericalAnalysisCase::areResultsCurrent() const
{
return hasResults() && m_bResultsValid;
}
quint64 nmDataNumericalAnalysisCase::getGridInputRevision() const
@ -395,9 +450,11 @@ void nmDataNumericalAnalysisCase::clear()
m_vecIncludedWells.clear();
m_sCurrentResultWellCode.clear();
m_vecSolverWellOrder.clear();
m_vecResultSolverWellOrder.clear();
m_nGridInputRevision = 1;
m_nBuiltGridRevision = 0;
m_nResultInputRevision = 1;
m_bResultsAvailable = false;
m_bResultsValid = false;
}
@ -422,3 +479,25 @@ void nmDataNumericalAnalysisCase::normalizeIncludedWells()
m_vecIncludedWells = vecNormalized;
}
void nmDataNumericalAnalysisCase::normalizeCurrentResultWellCode()
{
QString sFirstResultWellCode;
for(int nIndex = 0;
nIndex < m_vecResultSolverWellOrder.size();
++nIndex) {
const nmSolverWellRef& oWellRef =
m_vecResultSolverWellOrder[nIndex];
if(oWellRef.m_eEntryKind != NM_SolverEntry_Well) {
continue;
}
if(sFirstResultWellCode.isEmpty()) {
sFirstResultWellCode = oWellRef.m_sWellCode;
}
if(oWellRef.m_sWellCode == m_sCurrentResultWellCode) {
return;
}
}
m_sCurrentResultWellCode = sFirstResultWellCode;
}

@ -1983,10 +1983,13 @@ bool nmSubWndMain::mergeAnaResultToFitting()
m_nPendingResultPebiCount = -1;
m_nPendingResultSolveTimeMs = -1;
QVector<nmSolverWellRef> vecWellsOrder = pInstance->getSolverWellOrder();
QVector<nmSolverWellRef> vecWellsOrder =
pInstance->hasPebiResults()
? pInstance->getResultSolverWellOrder()
: pInstance->getSolverWellOrder();
QString sCurrentResultWellCode = pInstance->getCurrentResultWellCode();
// 当前结果井必须同时位于本次求解顺序并具备结果展示资格。
// 当前结果井必须同时位于最后一次结果顺序并具备结果展示资格。
// 无流量观察井虽然有内部压力结果,但不能成为曲线窗口的当前结果井。
const bool bResultWellAvailable =
pInstance->isWellAvailableForResult(sCurrentResultWellCode);

@ -372,9 +372,12 @@ void nmWxNumericalDesign::fillResultWellCombo(const QString& sSelectedWellCode)
}
QVector<QString> vecResultWellCodes;
QVector<nmSolverWellRef> vecSolverOrder = pManager->getSolverWellOrder();
QVector<nmSolverWellRef> vecSolverOrder =
pManager->hasPebiResults()
? pManager->getResultSolverWellOrder()
: pManager->getSolverWellOrder();
// 第一步:已有网格时按 DLL 的固定下标顺序收集可展示结果井。
// 第一步:有结果时优先按最后结果顺序,否则按当前网格顺序收集结果井。
// 无流量观察井仍在求解器顺序中,但不能出现在本下拉框。
for(int nOrderIndex = 0; nOrderIndex < vecSolverOrder.size(); ++nOrderIndex) {
const nmSolverWellRef& oWellRef = vecSolverOrder[nOrderIndex];

Loading…
Cancel
Save