|
|
|
|
|
#include "nmPebiExampleCalculator.h"
|
|
|
|
|
|
|
|
|
|
|
|
#include "pch.h"
|
|
|
|
|
|
#include "nmDataAnalyzeManager.h"
|
|
|
|
|
|
#include "nmDataPvtParaForPebi.h"
|
|
|
|
|
|
|
|
|
|
|
|
#include <QApplication>
|
|
|
|
|
|
#include <QCoreApplication>
|
|
|
|
|
|
#include <QDateTime>
|
|
|
|
|
|
#include <QDesktopServices>
|
|
|
|
|
|
#include <QDir>
|
|
|
|
|
|
#include <QFile>
|
|
|
|
|
|
#include <QFileInfo>
|
|
|
|
|
|
#include <QInputDialog>
|
|
|
|
|
|
#include <QMessageBox>
|
|
|
|
|
|
#include <QStringList>
|
|
|
|
|
|
#include <QTextStream>
|
|
|
|
|
|
|
|
|
|
|
|
#include <exception>
|
|
|
|
|
|
|
|
|
|
|
|
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<int>(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<double>& pressure,
|
|
|
|
|
|
const QVector<double>& volumeFactor,
|
|
|
|
|
|
const QVector<double>& compressibility,
|
|
|
|
|
|
const QVector<double>& 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<double>& 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<int>(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<const wchar_t*>(dllPath.utf16()));
|
|
|
|
|
|
if(dll == NULL) {
|
|
|
|
|
|
error = QString::fromLocal8Bit("加载PEBI求解器失败,错误码:%1").arg(GetLastError());
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HX_NWTM_GRID_Func gridFunction = reinterpret_cast<HX_NWTM_GRID_Func>(
|
|
|
|
|
|
GetProcAddress(dll, "HX_NWTM_GRID"));
|
|
|
|
|
|
HX_NWTM_MODEL_Func modelFunction = reinterpret_cast<HX_NWTM_MODEL_Func>(
|
|
|
|
|
|
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));
|
|
|
|
|
|
}
|