diff --git a/Include/nmNum/nmSubWnd/nmPebiExampleCalculator.h b/Include/nmNum/nmSubWnd/nmPebiExampleCalculator.h new file mode 100644 index 0000000..b8907e5 --- /dev/null +++ b/Include/nmNum/nmSubWnd/nmPebiExampleCalculator.h @@ -0,0 +1,16 @@ +#pragma once + +class QWidget; + +// 使用PEBI自带算例生成网格、计算压力并导出结果。 +class nmPebiExampleCalculator +{ +public: + explicit nmPebiExampleCalculator(QWidget* pParent = 0); + + // 显示模型选择对话框并执行所选算例。 + void run(); + +private: + QWidget* m_pParent; +}; diff --git a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp index 255171d..61943ef 100644 --- a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp @@ -625,6 +625,14 @@ bool nmCalculationDllPebiSolverTask::execPebiMode() } } + // 油水两相算例按定产油量处理:导入流量传给qo,qg和qw使用等长零数组。 + if(modelType == static_cast(SMT_Oil_Water_TwoPhase)) { + for(size_t i = 0; i < p0.Rate.t.size(); ++i) { + p0.Rate.qg[i].assign(p0.Rate.t[i].size(), 0.0); + p0.Rate.qw[i].assign(p0.Rate.t[i].size(), 0.0); + } + } + // PVT数据:根据求解器模型类型填充PVT输入数据 nmDataPvtParaForPebi* pebiPvtPara = pDataInstance->getPebiPvtPara(); fillPvtInputByModel(p0, static_cast(p0.T), pebiPvtPara, pReservoirData); diff --git a/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp b/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp index c61ad8b..4f85c7e 100644 --- a/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp @@ -413,7 +413,8 @@ bool nmCalculationPebiGrid::meshGenPebiWells(HX_NWTM_GRID_INPUT& inputObj) crack[1] = vFracPoints[0].y(); crack[2] = vFracPoints[1].x(); crack[3] = vFracPoints[1].y(); - crack[4] = 1; // 裂缝宽度 + // 裂缝宽度直接取井对象中保存的面板参数。 + crack[4] = pVerticalFracturedWell->getWidth().getValue().toDouble(); // FC直接取井对象中保存的裂缝导流能力,0表示无限导流。 crack[5] = pVerticalFracturedWell->getDfc().getValue().toDouble(); inputObj.FractureVerticalWell.push_back(crack); @@ -438,7 +439,8 @@ bool nmCalculationPebiGrid::meshGenPebiWells(HX_NWTM_GRID_INPUT& inputObj) crack[1] = vStartPoint.y(); crack[2] = vEndPoint.x(); crack[3] = vEndPoint.y(); - crack[4] = 2; // 裂缝宽度 + // 裂缝宽度直接取井对象中保存的面板参数。 + crack[4] = pHorizontalFracturedWell->getWidth().getValue().toDouble(); // FC直接取井对象中保存的裂缝导流能力,0表示无限导流。 crack[5] = pHorizontalFracturedWell->getDfc().getValue().toDouble(); @@ -1005,6 +1007,14 @@ bool nmCalculationPebiGrid::generateOutputPara() } } + // 油水两相算例按定产油量处理:导入流量传给qo,qg和qw使用等长零数组。 + if(solverModelType == SMT_Oil_Water_TwoPhase) { + for(size_t wellIdx = 0; wellIdx < scene.Rate.t.size(); ++wellIdx) { + scene.Rate.qg[wellIdx].assign(scene.Rate.t[wellIdx].size(), 0.0); + scene.Rate.qw[wellIdx].assign(scene.Rate.t[wellIdx].size(), 0.0); + } + } + // 3.3 CS井筒参数 scene.CS.C.resize(order.size()); scene.CS.S.resize(order.size()); diff --git a/Src/nmNum/nmSubWnd/nmPebiExampleCalculator.cpp b/Src/nmNum/nmSubWnd/nmPebiExampleCalculator.cpp new file mode 100644 index 0000000..a3ed3c4 --- /dev/null +++ b/Src/nmNum/nmSubWnd/nmPebiExampleCalculator.cpp @@ -0,0 +1,700 @@ +#include "nmPebiExampleCalculator.h" + +#include "pch.h" +#include "nmDataAnalyzeManager.h" +#include "nmDataPvtParaForPebi.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ +typedef void (*HX_NWTM_GRID_Func)(HX_NWTM_GRID_OUTPUT1&, + HX_NWTM_GRID_OUTPUT2&, + const HX_NWTM_GRID_INPUT&, + std::string); +typedef void (*HX_NWTM_MODEL_Func)(HX_NWTM_MODEL_OUTPUT&, + const HX_NWTM_MODEL_INPUT&, + std::string); + +QStringList exampleNames() +{ + QStringList names; + names << QString::fromLocal8Bit("1. 油单相常数PVT(一口多段压裂水平井)") + << QString::fromLocal8Bit("2. 油单相常数PVT(五口混合井)") + << QString::fromLocal8Bit("3. 油单相常数PVT(五十口直井)") + << QString::fromLocal8Bit("4. 油单相变化PVT(一口多段压裂水平井)") + << QString::fromLocal8Bit("5. 水单相常数PVT(一口多段压裂水平井)") + << QString::fromLocal8Bit("6. 水单相变化PVT(一口多段压裂水平井)") + << QString::fromLocal8Bit("7. 气单相变化PVT(一口多段压裂水平井)") + << QString::fromLocal8Bit("8. 气单相拟压力(一口多段压裂水平井)") + << QString::fromLocal8Bit("9. 油水两相(一口多段压裂水平井)"); + return names; +} + +dVec1 makePoint(double x, double y, double value) +{ + dVec1 point(3); + point[0] = x; + point[1] = y; + point[2] = value; + return point; +} + +dVec1 makeLine(double x0, double y0, double x1, double y1) +{ + dVec1 line(4); + line[0] = x0; + line[1] = y0; + line[2] = x1; + line[3] = y1; + return line; +} + +dVec1 makeFracture(double x0, double y0, double x1, double y1, + double halfWidth, double conductivity) +{ + dVec1 fracture(6); + fracture[0] = x0; + fracture[1] = y0; + fracture[2] = x1; + fracture[3] = y1; + fracture[4] = halfWidth; + fracture[5] = conductivity; + return fracture; +} + +void initializeGridInput(HX_NWTM_GRID_INPUT& input, double halfSize) +{ + // 构造函数带有默认井数据,算例计算前必须全部清空后重新组装。 + input.Boundary.clear(); + input.VerticalWell.clear(); + input.HorizontalWell.clear(); + input.FractureVerticalWell.clear(); + input.MultistageFracturedHorizontalWell.clear(); + input.InclinedWell.clear(); + input.Fault.clear(); + + input.Boundary.push_back(makeLine(-halfSize, -halfSize, -halfSize, halfSize)); + input.Boundary.push_back(makeLine(-halfSize, halfSize, halfSize, halfSize)); + input.Boundary.push_back(makeLine(halfSize, halfSize, halfSize, -halfSize)); + input.Boundary.push_back(makeLine(halfSize, -halfSize, -halfSize, -halfSize)); + input.GridControl = 150.0; + input.D = 2; +} + +void setupSingleWellGrid(HX_NWTM_GRID_INPUT& input) +{ + initializeGridInput(input, 1500.0); + // 使用main.cpp中单口多段压裂水平井的五条裂缝参数。 + dVec2 fractures; + fractures.push_back(makeFracture(-400.0, -200.0, -400.0, 200.0, 0.05, 100.0)); + fractures.push_back(makeFracture(-200.0, -200.0, -200.0, 200.0, 0.05, 100.0)); + fractures.push_back(makeFracture(0.0, -200.0, 0.0, 200.0, 0.05, 100.0)); + fractures.push_back(makeFracture(200.0, -200.0, 200.0, 200.0, 0.05, 100.0)); + fractures.push_back(makeFracture(400.0, -200.0, 400.0, 200.0, 0.05, 100.0)); + input.MultistageFracturedHorizontalWell.push_back(fractures); +} + +void setupFiveWellGrid(HX_NWTM_GRID_INPUT& input) +{ + initializeGridInput(input, 1500.0); + input.VerticalWell.push_back(makePoint(0.0, 0.0, 0.1)); + input.VerticalWell.push_back(makePoint(1000.0, 1000.0, 0.1)); + input.VerticalWell.push_back(makePoint(-1000.0, -1000.0, 0.1)); + input.FractureVerticalWell.push_back( + makeFracture(-200.0, -200.0, 200.0, -200.0, 0.05, 0.0)); + + dVec2 fractures; + fractures.push_back(makeFracture(-600.0, 600.0, -400.0, 600.0, 0.1, 0.0)); + fractures.push_back(makeFracture(-600.0, 400.0, -400.0, 400.0, 0.1, 0.0)); + fractures.push_back(makeFracture(-600.0, 200.0, -400.0, 200.0, 0.1, 0.0)); + input.MultistageFracturedHorizontalWell.push_back(fractures); + input.Fault.push_back(makeLine(-500.0, 1000.0, 500.0, 500.0)); +} + +void setupFiftyWellGrid(HX_NWTM_GRID_INPUT& input) +{ + initializeGridInput(input, 1000.0); + + static const double wells[50][2] = { + {766.32207856101854, -408.4253100821785}, + {-359.11086885711029, -242.17185988435722}, + {-396.28113134064483, 618.18974488936828}, + {-844.38929572547772, 859.25724767620932}, + {-631.69279373636357, 620.26791301684102}, + {-858.84439780240791, 333.48071142559911}, + {-844.38929572547772, 609.87707237947734}, + {-639.95285206603785, 867.56992018610026}, + {764.25706397860017, -190.2176566975379}, + {-410.73623341757479, 867.56992018610026}, + {-121.63419187897284, 98.647713021176514}, + {-152.6094106152517, 364.65323333769061}, + {-152.6094106152517, 620.26791301684102}, + {-361.17588343952877, 102.80404927612199}, + {-844.38929572547772, -231.78101924699342}, + {-156.73943978008879, 875.88259269599121}, + {138.55764550576896, 890.42976958830059}, + {446.24481828613784, -439.59783199426988}, + {-846.45431030789632, 61.240686726666581}, + {-142.28433770315883, -223.46834673710214}, + {-375.63098551645885, -472.84852203383423}, + {-602.78258958250342, 354.2623927003267}, + {431.78971620920788, 624.42424927178672}, + {-834.06422281338484, -474.92669016130685}, + {-836.12923739580344, -747.16671486023938}, + {813.81741395664631, -693.13434354594744}, + {458.63490578064966, -718.07236107562062}, + {409.07455580260353, 873.80442456851847}, + {124.10254342883854, -454.14500888657915}, + {138.55764550576896, 96.569544893703778}, + {101.38738302223419, 611.95524050694985}, + {-373.56597093404037, 360.49689708274514}, + {-604.8476041649219, 79.944199873921661}, + {-611.0426479121777, -219.31201048215667}, + {448.30983286855667, -202.68666546237455}, + {735.34685982474002, 880.03892895093713}, + {107.58242676948976, -724.30686545803894}, + {150.94773300028032, -196.45216107995623}, + {762.19204939618135, 113.19488991348589}, + {-125.76422104381015, -470.77035390636138}, + {452.43986203339387, 366.73140146516357}, + {-350.85081052743578, -751.32305111518485}, + {122.03752884641995, 354.2623927003267}, + {-608.9776333297591, -447.91050450416094}, + {407.0095412201847, 113.19488991348589}, + {731.21683065990283, 636.89325803662314}, + {-121.63419187897284, -724.30686545803894}, + {289.3037100223255, -925.88917382289742}, + {737.41187440715862, 366.73140146516357}, + {-611.0426479121777, -730.54136984045726} + }; + + for(int i = 0; i < 50; ++i) { + input.VerticalWell.push_back(makePoint(wells[i][0], wells[i][1], 0.108)); + } +} + +void setupGridForExample(int exampleIndex, HX_NWTM_GRID_INPUT& input) +{ + if(exampleIndex == 2) { + setupFiveWellGrid(input); + } else if(exampleIndex == 3) { + setupFiftyWellGrid(input); + } else { + setupSingleWellGrid(input); + } +} + +void resetWellData(HX_NWTM_MODEL_INPUT& input, int wellCount) +{ + // 所有相态统一按实际井数重建外层数组,避免沿用接口构造函数中的五井默认值。 + input.Rate.t.assign(wellCount, dVec1()); + input.Rate.qo.assign(wellCount, dVec1()); + input.Rate.qg.assign(wellCount, dVec1()); + input.Rate.qw.assign(wellCount, dVec1()); + input.Pressure.t.clear(); + input.Pressure.p.clear(); + input.CS.C.assign(wellCount, 0.0); + input.CS.S.assign(wellCount, 0.0); +} + +void resizeRateStep(HX_NWTM_MODEL_INPUT& input, int wellIndex, int stepCount) +{ + input.Rate.t[wellIndex].assign(stepCount, 0.0); + input.Rate.qo[wellIndex].assign(stepCount, 0.0); + input.Rate.qg[wellIndex].assign(stepCount, 0.0); + input.Rate.qw[wellIndex].assign(stepCount, 0.0); +} + +void setPressureAxis(HX_NWTM_MODEL_INPUT& input) +{ + input.PVT.p.assign(200, 0.0); + for(int i = 0; i < 200; ++i) { + input.PVT.p[i] = i + 1.0; + } +} + +void setBase(HX_NWTM_MODEL_INPUT& input, int cellCount, + double permeability, double porosity, double thickness) +{ + input.Base.k.assign(cellCount, permeability); + input.Base.phi.assign(cellCount, porosity); + input.Base.h.assign(cellCount, thickness); + input.Base.d = 1.05; + input.Base.dt_Min = 0.0025; + input.Base.dt_Max = 12.5; +} + +void setupExample1(HX_NWTM_MODEL_INPUT& input, int cellCount) +{ + input.T = 1; + resetWellData(input, 1); + resizeRateStep(input, 0, 2); + input.Rate.t[0][0] = 2000.0; + input.Rate.t[0][1] = 500.0; + input.Rate.qo[0][0] = 20.0; + input.PVT.Bo.assign(200, 1.2); + input.PVT.miuo.assign(200, 0.5); + setPressureAxis(input); + input.Base.Pi = 40.0; + input.Base.Cti = 1e-3; + setBase(input, cellCount, 0.001, 0.1, 10.0); +} + +void setupExample2(HX_NWTM_MODEL_INPUT& input, int cellCount) +{ + input.T = 1; + resetWellData(input, 5); + + resizeRateStep(input, 0, 2); + input.Rate.t[0][0] = 2000.0; + input.Rate.t[0][1] = 500.0; + input.Rate.qo[0][0] = 10.0; + + resizeRateStep(input, 1, 0); + + resizeRateStep(input, 2, 3); + input.Rate.t[2][0] = 1000.0; + input.Rate.t[2][1] = 1000.0; + input.Rate.t[2][2] = 500.0; + input.Rate.qo[2][0] = 30.0; + input.Rate.qo[2][1] = 40.0; + input.Rate.qo[2][2] = 20.0; + + resizeRateStep(input, 3, 2); + input.Rate.t[3][0] = 1500.0; + input.Rate.t[3][1] = 1000.0; + input.Rate.qo[3][0] = 30.0; + input.Rate.qo[3][1] = 20.0; + + resizeRateStep(input, 4, 2); + input.Rate.t[4][0] = 1000.0; + input.Rate.t[4][1] = 1500.0; + input.Rate.qo[4][0] = -50.0; + input.Rate.qo[4][1] = -60.0; + + input.CS.C.assign(5, 0.1); + input.CS.S.assign(5, 0.1); + setPressureAxis(input); + input.PVT.Bo.assign(200, 1.2); + input.PVT.miuo.assign(200, 0.5); + input.Base.Pi = 40.0; + input.Base.Cti = 1e-3; + setBase(input, cellCount, 0.001, 0.1, 10.0); +} + +void setupExample3(HX_NWTM_MODEL_INPUT& input, int cellCount) +{ + input.T = 1; + resetWellData(input, 50); + for(int wellIndex = 0; wellIndex < 50; ++wellIndex) { + resizeRateStep(input, wellIndex, 5); + input.Rate.t[wellIndex][0] = 12.0; + input.Rate.t[wellIndex][1] = 12.0; + input.Rate.t[wellIndex][2] = 12.0; + input.Rate.t[wellIndex][3] = 48.0; + input.Rate.t[wellIndex][4] = 72.0; + input.Rate.qo[wellIndex][0] = 158.98699999999999; + input.Rate.qo[wellIndex][1] = 190.785; + input.Rate.qo[wellIndex][2] = 222.58199999999999; + input.Rate.qo[wellIndex][3] = 238.48099999999999; + } + input.CS.C.assign(50, 0.01); + input.CS.S.assign(50, 0.0); + setPressureAxis(input); + input.PVT.Bo.assign(200, 1.07); + input.PVT.miuo.assign(200, 0.79); + input.Base.Pi = 40.0; + input.Base.Cti = 0.43e-3; + setBase(input, cellCount, 0.025, 0.1, 10.0); +} + +void setupSinglePhaseExample(HX_NWTM_MODEL_INPUT& input, int exampleIndex, + int cellCount) +{ + resetWellData(input, 1); + resizeRateStep(input, 0, 2); + input.Rate.t[0][0] = 2000.0; + input.Rate.t[0][1] = 500.0; + input.CS.C[0] = 0.1; + input.CS.S[0] = 0.1; + + if(exampleIndex == 4) { + input.T = 2; + input.Rate.qo[0][0] = 10.0; + } else if(exampleIndex == 5 || exampleIndex == 6) { + input.T = exampleIndex == 5 ? 3 : 4; + input.Rate.qw[0][0] = -10.0; + if(exampleIndex == 5) { + setPressureAxis(input); + input.PVT.Bw.assign(200, 1.05); + input.PVT.miuw.assign(200, 0.8); + } + } else { + input.T = exampleIndex == 7 ? 5 : 6; + input.Rate.qg[0][0] = 50000.0; + } + + input.Base.Pi = 40.0; + input.Base.Cf = 1e-3; + if(exampleIndex == 5) { + input.Base.Cti = 1e-3; + } + setBase(input, cellCount, 0.001, 0.1, 10.0); +} + +void setupExample9(HX_NWTM_MODEL_INPUT& input, int cellCount) +{ + input.T = 8; + resetWellData(input, 1); + resizeRateStep(input, 0, 2); + input.Rate.t[0][0] = 2000.0; + input.Rate.t[0][1] = 500.0; + input.Rate.qo[0][0] = 20.0; + input.CS.C[0] = 0.1; + input.CS.S[0] = 0.1; + + input.Base.Pi = 40.0; + input.Base.Cf = 1e-4; + input.Base.Swi = 0.2; + setBase(input, cellCount, 0.001, 0.1, 10.0); +} + +void setupModelForExample(int exampleIndex, const HX_NWTM_GRID_OUTPUT2& grid, + HX_NWTM_MODEL_INPUT& input) +{ + const int cellCount = static_cast(grid.Trinodexy.size()); + if(exampleIndex == 1) { + setupExample1(input, cellCount); + } else if(exampleIndex == 2) { + setupExample2(input, cellCount); + } else if(exampleIndex == 3) { + setupExample3(input, cellCount); + } else if(exampleIndex >= 4 && exampleIndex <= 8) { + setupSinglePhaseExample(input, exampleIndex, cellCount); + } else { + setupExample9(input, cellCount); + } +} + +bool hasPhasePvt(const QVector& pressure, + const QVector& volumeFactor, + const QVector& compressibility, + const QVector& viscosity) +{ + return !pressure.isEmpty() + && pressure.size() == volumeFactor.size() + && pressure.size() == compressibility.size() + && pressure.size() == viscosity.size(); +} + +bool loadFrameworkPvt(int exampleIndex, + HX_NWTM_MODEL_INPUT& input, + QString& error) +{ + // 常数PVT算例继续使用main.cpp中的固定数据。 + if(exampleIndex == 1 || exampleIndex == 2 + || exampleIndex == 3 || exampleIndex == 5) { + return true; + } + + nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); + nmDataPvtParaForPebi* pvt = dataManager == NULL + ? NULL : dataManager->getPebiPvtPara(); + if(pvt == NULL) { + error = QString::fromLocal8Bit("当前分析中没有可用的PVT数据。"); + return false; + } + + const QVector& pressure = pvt->getPressure(); + if(exampleIndex == 4) { + if(!hasPhasePvt(pressure, pvt->getBo(), pvt->getCo(), pvt->getMiuo())) { + error = QString::fromLocal8Bit("当前分析中的油相变化PVT数据不完整。"); + return false; + } + input.PVT.p = pressure.toStdVector(); + input.PVT.Bo = pvt->getBo().toStdVector(); + input.PVT.Co = pvt->getCo().toStdVector(); + input.PVT.miuo = pvt->getMiuo().toStdVector(); + } else if(exampleIndex == 6) { + if(!hasPhasePvt(pressure, pvt->getBw(), pvt->getCw(), pvt->getMiuw())) { + error = QString::fromLocal8Bit("当前分析中的水相变化PVT数据不完整。"); + return false; + } + input.PVT.p = pressure.toStdVector(); + input.PVT.Bw = pvt->getBw().toStdVector(); + input.PVT.Cw = pvt->getCw().toStdVector(); + input.PVT.miuw = pvt->getMiuw().toStdVector(); + } else if(exampleIndex == 7 || exampleIndex == 8) { + if(!hasPhasePvt(pressure, pvt->getBg(), pvt->getCg(), pvt->getMiug())) { + error = QString::fromLocal8Bit("当前分析中的气相变化PVT数据不完整。"); + return false; + } + input.PVT.p = pressure.toStdVector(); + input.PVT.Bg = pvt->getBg().toStdVector(); + input.PVT.Cg = pvt->getCg().toStdVector(); + input.PVT.miug = pvt->getMiug().toStdVector(); + } else if(exampleIndex == 9) { + const bool oilWaterPvtValid = !pressure.isEmpty() + && pressure.size() == pvt->getBo().size() + && pressure.size() == pvt->getMiuo().size() + && pressure.size() == pvt->getBw().size() + && pressure.size() == pvt->getMiuw().size(); + const bool relativePermeabilityValid = !pvt->getSo().isEmpty() + && pvt->getSo().size() == pvt->getKro().size() + && pvt->getSo().size() == pvt->getKrw().size(); + if(!oilWaterPvtValid || !relativePermeabilityValid) { + error = QString::fromLocal8Bit("当前分析中的油水两相PVT或相渗数据不完整。"); + return false; + } + input.PVT.p = pressure.toStdVector(); + input.PVT.Bo = pvt->getBo().toStdVector(); + input.PVT.miuo = pvt->getMiuo().toStdVector(); + input.PVT.Bw = pvt->getBw().toStdVector(); + input.PVT.miuw = pvt->getMiuw().toStdVector(); + input.PVT.So = pvt->getSo().toStdVector(); + input.PVT.Kro = pvt->getKro().toStdVector(); + input.PVT.Krw = pvt->getKrw().toStdVector(); + } + return true; +} + +QString numberText(double value) +{ + return QString::number(value, 'g', 16); +} + +bool writePressureFile(const QString& filePath, + const HX_NWTM_MODEL_OUTPUT& output, + size_t wellIndex, + QString& error) +{ + QFile file(filePath); + if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + error = QString::fromLocal8Bit("无法创建压力文件:%1").arg(filePath); + return false; + } + + QTextStream stream(&file); + stream.setCodec("UTF-8"); + + for(size_t timeIndex = 0; timeIndex < output.t.size(); ++timeIndex) { + stream << numberText(output.t[timeIndex]) << " "; + if(timeIndex < output.pw[wellIndex].size()) { + stream << numberText(output.pw[wellIndex][timeIndex]); + } + stream << "\n"; + } + return true; +} + +bool writeFlowFile(const QString& filePath, + const HX_NWTM_MODEL_INPUT& input, + int exampleIndex, + size_t wellIndex, + QString& error) +{ + QFile file(filePath); + if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + error = QString::fromLocal8Bit("无法创建流量文件:%1").arg(filePath); + return false; + } + + QTextStream stream(&file); + stream.setCodec("UTF-8"); + stream << "0 0\n"; + + // 按算例相态选择实际使用的油、气或水流量数组。 + const dVec1* rates = &input.Rate.qo[wellIndex]; + if(exampleIndex == 5 || exampleIndex == 6) { + rates = &input.Rate.qw[wellIndex]; + } else if(exampleIndex == 7 || exampleIndex == 8) { + rates = &input.Rate.qg[wellIndex]; + } + for(size_t stepIndex = 0; stepIndex < input.Rate.t[wellIndex].size(); ++stepIndex) { + stream << numberText(input.Rate.t[wellIndex][stepIndex]) << " "; + if(stepIndex < rates->size()) { + stream << numberText((*rates)[stepIndex]); + } + stream << "\n"; + } + return true; +} + +bool saveResults(int exampleIndex, + const HX_NWTM_MODEL_INPUT& input, + const HX_NWTM_MODEL_OUTPUT& output, + int& savedWellCount, + QString& error) +{ + QString desktopPath = QDesktopServices::storageLocation(QDesktopServices::DesktopLocation); + if(desktopPath.isEmpty()) { + desktopPath = QDir::home().filePath("Desktop"); + } + if(!QDir(desktopPath).exists()) { + error = QString::fromLocal8Bit("桌面目录不存在:%1").arg(desktopPath); + return false; + } + if(output.pw.size() != input.Rate.t.size()) { + error = QString::fromLocal8Bit("求解器返回的压力井数与输入井数不一致。"); + return false; + } + + const QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss"); + QStringList createdFiles; + savedWellCount = static_cast(input.Rate.t.size()); + for(int wellIndex = 0; wellIndex < savedWellCount; ++wellIndex) { + const QString pressurePath = QDir(desktopPath).filePath( + QString("PEBI_Model_%1_Well_%2_Pressure_%3.txt") + .arg(exampleIndex).arg(wellIndex + 1).arg(timestamp)); + const QString flowPath = QDir(desktopPath).filePath( + QString("PEBI_Model_%1_Well_%2_Flow_%3.txt") + .arg(exampleIndex).arg(wellIndex + 1).arg(timestamp)); + + if(!writePressureFile(pressurePath, output, wellIndex, error)) { + break; + } + createdFiles.append(pressurePath); + if(!writeFlowFile(flowPath, input, exampleIndex, wellIndex, error)) { + break; + } + createdFiles.append(flowPath); + } + if(!error.isEmpty()) { + for(int fileIndex = 0; fileIndex < createdFiles.size(); ++fileIndex) { + QFile::remove(createdFiles[fileIndex]); + } + savedWellCount = 0; + return false; + } + return true; +} + +bool calculateExample(int exampleIndex, + int& savedWellCount, + QString& error) +{ + const QString dllPath = QDir(QCoreApplication::applicationDirPath()).filePath("HX_NWTM.dll"); + if(!QFileInfo(dllPath).exists()) { + error = QString::fromLocal8Bit("未找到PEBI求解器:%1").arg(dllPath); + return false; + } + + HMODULE dll = LoadLibraryW(reinterpret_cast(dllPath.utf16())); + if(dll == NULL) { + error = QString::fromLocal8Bit("加载PEBI求解器失败,错误码:%1").arg(GetLastError()); + return false; + } + + HX_NWTM_GRID_Func gridFunction = reinterpret_cast( + GetProcAddress(dll, "HX_NWTM_GRID")); + HX_NWTM_MODEL_Func modelFunction = reinterpret_cast( + GetProcAddress(dll, "HX_NWTM_MODEL")); + if(gridFunction == NULL || modelFunction == NULL) { + error = QString::fromLocal8Bit("PEBI求解器缺少网格或模型计算接口。"); + FreeLibrary(dll); + return false; + } + + const QString licensePath = QDir::cleanPath( + QDir(QCoreApplication::applicationDirPath()).filePath("../Res/license/HXNWTM_license.dat")); + if(!QFileInfo(licensePath).exists()) { + error = QString::fromLocal8Bit("未找到PEBI许可证:%1").arg(licensePath); + FreeLibrary(dll); + return false; + } + + try { + // 必须先由所选算例的井和边界生成网格,再用网格输出构造模型输入。 + HX_NWTM_GRID_INPUT gridInput; + HX_NWTM_GRID_OUTPUT1 gridOutput1; + HX_NWTM_GRID_OUTPUT2 gridOutput2; + setupGridForExample(exampleIndex, gridInput); + gridFunction(gridOutput1, gridOutput2, gridInput, licensePath.toStdString()); + if(gridOutput2.Trinodexy.empty()) { + error = QString::fromLocal8Bit("PEBI网格生成失败,未返回有效网格单元。"); + FreeLibrary(dll); + return false; + } + + HX_NWTM_MODEL_INPUT modelInput(gridOutput2); + HX_NWTM_MODEL_OUTPUT modelOutput; + setupModelForExample(exampleIndex, gridOutput2, modelInput); + if(!loadFrameworkPvt(exampleIndex, modelInput, error)) { + FreeLibrary(dll); + return false; + } + modelFunction(modelOutput, modelInput, licensePath.toStdString()); + if(modelOutput.t.empty() || modelOutput.pw.empty()) { + error = QString::fromLocal8Bit("PEBI模型计算失败,未返回压力数据。"); + FreeLibrary(dll); + return false; + } + + const bool saved = saveResults(exampleIndex, modelInput, modelOutput, + savedWellCount, error); + FreeLibrary(dll); + return saved; + } catch(const std::exception& exception) { + error = QString::fromLocal8Bit("PEBI算例计算异常:%1") + .arg(QString::fromLocal8Bit(exception.what())); + } catch(...) { + error = QString::fromLocal8Bit("PEBI算例计算发生未知异常。"); + } + + FreeLibrary(dll); + return false; +} +} + +nmPebiExampleCalculator::nmPebiExampleCalculator(QWidget* pParent) + : m_pParent(pParent) +{ +} + +void nmPebiExampleCalculator::run() +{ + bool accepted = false; + const QString selected = QInputDialog::getItem( + m_pParent, + QString::fromLocal8Bit("PEBI算例计算"), + QString::fromLocal8Bit("请选择计算模型:"), + exampleNames(), 0, false, &accepted); + if(!accepted || selected.isEmpty()) { + return; + } + + const int exampleIndex = exampleNames().indexOf(selected) + 1; + if(exampleIndex < 1 || exampleIndex > 9) { + return; + } + + QApplication::setOverrideCursor(Qt::WaitCursor); + int savedWellCount = 0; + QString error; + const bool success = calculateExample(exampleIndex, savedWellCount, error); + QApplication::restoreOverrideCursor(); + + if(!success) { + QMessageBox::warning(m_pParent, QString::fromLocal8Bit("PEBI算例计算"), error); + return; + } + + QMessageBox::information( + m_pParent, + QString::fromLocal8Bit("PEBI算例计算"), + QString::fromLocal8Bit("计算完成,已按井保存到桌面,共%1口井、%2个文件。") + .arg(savedWellCount).arg(savedWellCount * 2)); +} diff --git a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp index 46af449..f543cb1 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp @@ -44,6 +44,7 @@ #include "nmWxParameterProperty.h" #include "nmCalculationDllPebiSolverTask.h" #include "nmCalculationUtils.h" +#include "nmPebiExampleCalculator.h" #include "nmObjPointWell.h" @@ -493,6 +494,14 @@ bool nmSubWndMain::runCmdBy(QString sName, QString sID) this->displaySetting(); return true; + case 5125: + { + // 临时复用打印按钮运行PEBI固定算例,不读取当前工区模型参数。 + nmPebiExampleCalculator calculator(this); + calculator.run(); + return true; + } + /* case 5117: this->initData();