|
|
|
|
@ -62,18 +62,17 @@ struct nmPebiSolverInputSnapshot
|
|
|
|
|
struct SensitivityBaseline
|
|
|
|
|
{
|
|
|
|
|
SensitivityBaseline()
|
|
|
|
|
: m_nTarget(-1), m_dSceneSkin(0.0), m_dSceneStorage(0.0),
|
|
|
|
|
m_dWellSkin(0.0), m_dWellStorage(0.0), m_dPressure(0.0),
|
|
|
|
|
: m_nTarget(-1), m_dPressure(0.0),
|
|
|
|
|
m_dScenePermeability(0.0), m_dSceneThickness(0.0),
|
|
|
|
|
m_dScenePorosity(0.0), m_dReferencePermeability(0.0),
|
|
|
|
|
m_dReferenceThickness(0.0), m_dReferencePorosity(0.0)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
int m_nTarget; ///< 非负值表示已初始化的独立敏感性任务。
|
|
|
|
|
double m_dSceneSkin;
|
|
|
|
|
double m_dSceneStorage;
|
|
|
|
|
double m_dWellSkin;
|
|
|
|
|
double m_dWellStorage;
|
|
|
|
|
QVector<double> m_dSceneSkin;
|
|
|
|
|
QVector<double> m_dSceneStorage;
|
|
|
|
|
QVector<double> m_dWellSkin;
|
|
|
|
|
QVector<double> m_dWellStorage;
|
|
|
|
|
double m_dPressure;
|
|
|
|
|
double m_dScenePermeability;
|
|
|
|
|
double m_dSceneThickness;
|
|
|
|
|
@ -817,10 +816,12 @@ nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivit
|
|
|
|
|
nmPebiSolverInputSnapshot::SensitivityBaseline& baseline =
|
|
|
|
|
task->m_pInputSnapshot->m_oSensitivityBaseline;
|
|
|
|
|
baseline.m_nTarget = target;
|
|
|
|
|
baseline.m_dSceneSkin = grid.m_oScene.CS.S[target];
|
|
|
|
|
baseline.m_dSceneStorage = grid.m_oScene.CS.C[target];
|
|
|
|
|
baseline.m_dWellSkin = grid.m_vecWellInputs[target].m_dSkin;
|
|
|
|
|
baseline.m_dWellStorage = grid.m_vecWellInputs[target].m_dWellboreStorage;
|
|
|
|
|
for(int i = 0; i < grid.m_vecWellInputs.size(); ++i) {
|
|
|
|
|
baseline.m_dSceneSkin.append(grid.m_oScene.CS.S[i]);
|
|
|
|
|
baseline.m_dSceneStorage.append(grid.m_oScene.CS.C[i]);
|
|
|
|
|
baseline.m_dWellSkin.append(grid.m_vecWellInputs[i].m_dSkin);
|
|
|
|
|
baseline.m_dWellStorage.append(grid.m_vecWellInputs[i].m_dWellboreStorage);
|
|
|
|
|
}
|
|
|
|
|
baseline.m_dPressure = grid.m_oScene.Base.Pi;
|
|
|
|
|
baseline.m_dScenePermeability = grid.m_oScene.Base.k_ref;
|
|
|
|
|
baseline.m_dSceneThickness = grid.m_oScene.Base.h_ref;
|
|
|
|
|
@ -878,15 +879,21 @@ bool nmCalculationDllPebiSolverTask::updateSensitivityRun(
|
|
|
|
|
const double value = values[i];
|
|
|
|
|
valid = isFiniteSolverNumber(value);
|
|
|
|
|
if(!valid) break;
|
|
|
|
|
// 井筒参数按目标井槽位写入,储层参数同步到场景和静态属性计算输入。
|
|
|
|
|
if(name == "Skin") {
|
|
|
|
|
valid = value >= 0.0;
|
|
|
|
|
parameters.m_dSceneSkin = value;
|
|
|
|
|
parameters.m_dWellSkin = value;
|
|
|
|
|
} else if(name == "C") {
|
|
|
|
|
valid = value >= 0.0;
|
|
|
|
|
parameters.m_dSceneStorage = value;
|
|
|
|
|
parameters.m_dWellStorage = value;
|
|
|
|
|
// 用井编码定位槽位,旧版无编码的参数仍对应分析主井。
|
|
|
|
|
if(name == "Skin" || name == "C") {
|
|
|
|
|
int wellIndex = variable.m_wellCode.isEmpty() ? parameters.m_nTarget : -1;
|
|
|
|
|
for(int w = 0; w < grid.m_vecWellInputs.size(); ++w)
|
|
|
|
|
if(grid.m_vecWellInputs[w].m_bRealWell &&
|
|
|
|
|
grid.m_vecWellInputs[w].m_sWellCode == variable.m_wellCode) { wellIndex = w; break; }
|
|
|
|
|
valid = value >= 0.0 && wellIndex >= 0;
|
|
|
|
|
if(!valid) break;
|
|
|
|
|
if(name == "Skin") {
|
|
|
|
|
parameters.m_dSceneSkin[wellIndex] = value;
|
|
|
|
|
parameters.m_dWellSkin[wellIndex] = value;
|
|
|
|
|
} else {
|
|
|
|
|
parameters.m_dSceneStorage[wellIndex] = value;
|
|
|
|
|
parameters.m_dWellStorage[wellIndex] = value;
|
|
|
|
|
}
|
|
|
|
|
} else if(name == "Pi") {
|
|
|
|
|
valid = value > 0.0;
|
|
|
|
|
parameters.m_dPressure = value;
|
|
|
|
|
@ -915,11 +922,13 @@ bool nmCalculationDllPebiSolverTask::updateSensitivityRun(
|
|
|
|
|
if(errorMessage) *errorMessage = m_sFailureReason;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
const int target = parameters.m_nTarget;
|
|
|
|
|
grid.m_oScene.CS.S[target] = parameters.m_dSceneSkin;
|
|
|
|
|
grid.m_oScene.CS.C[target] = parameters.m_dSceneStorage;
|
|
|
|
|
grid.m_vecWellInputs[target].m_dSkin = parameters.m_dWellSkin;
|
|
|
|
|
grid.m_vecWellInputs[target].m_dWellboreStorage = parameters.m_dWellStorage;
|
|
|
|
|
// 每组恢复所有井的基准,确定性单参数切换时不会继承上一口井的采样值。
|
|
|
|
|
for(int i = 0; i < grid.m_vecWellInputs.size(); ++i) {
|
|
|
|
|
grid.m_oScene.CS.S[i] = parameters.m_dSceneSkin[i];
|
|
|
|
|
grid.m_oScene.CS.C[i] = parameters.m_dSceneStorage[i];
|
|
|
|
|
grid.m_vecWellInputs[i].m_dSkin = parameters.m_dWellSkin[i];
|
|
|
|
|
grid.m_vecWellInputs[i].m_dWellboreStorage = parameters.m_dWellStorage[i];
|
|
|
|
|
}
|
|
|
|
|
grid.m_oScene.Base.Pi = parameters.m_dPressure;
|
|
|
|
|
grid.m_oScene.Base.k_ref = parameters.m_dScenePermeability;
|
|
|
|
|
grid.m_oScene.Base.h_ref = parameters.m_dSceneThickness;
|
|
|
|
|
@ -990,6 +999,25 @@ nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivit
|
|
|
|
|
// 拟合参数基准值读取当前模型,不使用设置窗口中可编辑的拟合初值。
|
|
|
|
|
if(improveSettings) {
|
|
|
|
|
const nmDataAutomaticFitting& fit = improveSettings->parameters;
|
|
|
|
|
const QVector<nmFitParameter>& modelParameters = fit.getModelParameters();
|
|
|
|
|
for(int i = 0; i < modelParameters.size(); ++i) {
|
|
|
|
|
const nmFitParameter& p = modelParameters[i];
|
|
|
|
|
if(!p.selected) continue;
|
|
|
|
|
double value = p.type == 0 ? m_pInputSnapshot->m_oStaticPropertyInput.m_dReferencePermeabilityMilliDarcy
|
|
|
|
|
: m_pInputSnapshot->m_oStaticPropertyInput.m_dReferencePorosity;
|
|
|
|
|
nmDataWellBase* parameterWell = m_pDataManager->findWellByCode(p.wellCode);
|
|
|
|
|
if(p.type != 0 && p.type != 3) {
|
|
|
|
|
if(!parameterWell) { if(errorMessage) *errorMessage = tr("The target well is unavailable."); return nullptr; }
|
|
|
|
|
if(p.type == 1) value = parameterWell->getPerforation(0)->getSkin().getValue().toDouble();
|
|
|
|
|
else if(p.type == 2) value = parameterWell->getWellboreStorage().getValue().toDouble();
|
|
|
|
|
else if(nmDataVerticalFracturedWell* fractured = dynamic_cast<nmDataVerticalFracturedWell*>(parameterWell))
|
|
|
|
|
value = (p.type == 4 ? fractured->getDfc() : fractured->getFractureHalfLength()).getValue().toDouble();
|
|
|
|
|
else if(nmDataHorizontalFracturedWell* fractured = dynamic_cast<nmDataHorizontalFracturedWell*>(parameterWell))
|
|
|
|
|
value = (p.type == 4 ? fractured->getDfc() : fractured->getFractureHalfLength()).getValue().toDouble();
|
|
|
|
|
}
|
|
|
|
|
task->m_pSensitivityResult->m_fitParameters.append(nmDataAttribute(p.name(), value, p.unit()));
|
|
|
|
|
task->m_pSensitivityResult->m_fitParameterCodes.append(p.wellCode.isEmpty() ? p.code() : p.wellCode + "/" + p.code());
|
|
|
|
|
}
|
|
|
|
|
const bool selected[] = { fit.getPermeabilitySelected(), fit.getSkinSelected(),
|
|
|
|
|
fit.getWellboreStorageSelected(), fit.getPorositySelected(),
|
|
|
|
|
fit.getFractureConductivitySelected(), fit.getFractureHalfLengthSelected() };
|
|
|
|
|
@ -1019,7 +1047,7 @@ nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivit
|
|
|
|
|
well->getPerforationCount() > 0 ? well->getPerforation(0)->getSkin().getValue().toDouble() : 0.0,
|
|
|
|
|
well->getWellboreStorage().getValue().toDouble(),
|
|
|
|
|
m_pInputSnapshot->m_oStaticPropertyInput.m_dReferencePorosity, conductivity, halfLength };
|
|
|
|
|
for(int j = 0; j < 6; ++j) if(selected[j]) {
|
|
|
|
|
for(int j = 0; j < 6; ++j) if(modelParameters.isEmpty() && selected[j]) {
|
|
|
|
|
task->m_pSensitivityResult->m_fitParameters.append(nmDataAttribute(
|
|
|
|
|
QCoreApplication::translate("nmWxAutomaticFitting", names[j]), values[j], units[j]));
|
|
|
|
|
task->m_pSensitivityResult->m_fitParameterCodes.append(codes[j]);
|
|
|
|
|
@ -1083,81 +1111,99 @@ bool nmCalculationDllPebiSolverTask::improveSensitivityRun(QVector<double>& fitt
|
|
|
|
|
grid.m_vecWellInputs[i].m_sWellName == settings.targetWell) { target = i; break; }
|
|
|
|
|
}
|
|
|
|
|
if(target < 0) return fail(tr("The target well is unavailable."));
|
|
|
|
|
const bool selected[] = { fit.getPermeabilitySelected(), fit.getSkinSelected(),
|
|
|
|
|
fit.getWellboreStorageSelected(), fit.getPorositySelected(),
|
|
|
|
|
fit.getFractureConductivitySelected(), fit.getFractureHalfLengthSelected() };
|
|
|
|
|
for(int i = 0; i < properties.m_vecDataSets.size(); ++i) {
|
|
|
|
|
const nmPropertyInterpolationDataSet& field = properties.m_vecDataSets[i];
|
|
|
|
|
if(field.useForCalculation && ((selected[0] && field.property == "k") ||
|
|
|
|
|
(selected[3] && field.property == "phi")))
|
|
|
|
|
return fail(tr("The sampled parameters are invalid or use a non-uniform field."));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 井型数组与求解槽位顺序一致,保留原端点以免反复缩放造成几何累积偏差。
|
|
|
|
|
std::vector<std::vector<double>*> fractures;
|
|
|
|
|
std::vector<std::vector<double> > originalFractures;
|
|
|
|
|
if(selected[4] || selected[5]) {
|
|
|
|
|
size_t vertical = 0, horizontal = 0;
|
|
|
|
|
for(int i = 0; i <= target; ++i) {
|
|
|
|
|
const int type = grid.m_vecSolverWellOrder[i].m_eWellType;
|
|
|
|
|
if(type == Vertical_Fractured_Well) {
|
|
|
|
|
if(i == target) fractures.push_back(&grid.m_oGridInput.FractureVerticalWell[vertical]);
|
|
|
|
|
++vertical;
|
|
|
|
|
} else if(type == Horizontal_Fractured_Well) {
|
|
|
|
|
if(i == target) {
|
|
|
|
|
std::vector<std::vector<double> >& well =
|
|
|
|
|
grid.m_oGridInput.MultistageFracturedHorizontalWell[horizontal];
|
|
|
|
|
for(size_t j = 0; j < well.size(); ++j) fractures.push_back(&well[j]);
|
|
|
|
|
}
|
|
|
|
|
++horizontal;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(fractures.empty()) return fail(tr("Fracture parameters are unavailable for the target well."));
|
|
|
|
|
for(size_t i = 0; i < fractures.size(); ++i) originalFractures.push_back(*fractures[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QVector<nmFitParameter> fitted = fit.getModelParameters();
|
|
|
|
|
if(fitted.isEmpty()) {
|
|
|
|
|
// 旧项目仍按原来的目标井和六参数顺序运行。
|
|
|
|
|
const bool selected[] = { fit.getPermeabilitySelected(), fit.getSkinSelected(),
|
|
|
|
|
fit.getWellboreStorageSelected(), fit.getPorositySelected(),
|
|
|
|
|
fit.getFractureConductivitySelected(), fit.getFractureHalfLengthSelected() };
|
|
|
|
|
for(int type = 0; type < 6; ++type) if(selected[type]) {
|
|
|
|
|
nmFitParameter p;
|
|
|
|
|
p.type = type;
|
|
|
|
|
p.selected = true;
|
|
|
|
|
if(type != 0 && type != 3) p.wellCode = grid.m_vecWellInputs[target].m_sWellCode;
|
|
|
|
|
fitted.append(p);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for(int i = fitted.size() - 1; i >= 0; --i)
|
|
|
|
|
if(!fitted[i].selected) fitted.remove(i);
|
|
|
|
|
// 每项记录自己的求解槽位,联合拟合时不再共用一个目标井槽位。
|
|
|
|
|
QVector<int> wellIndices;
|
|
|
|
|
for(int i = 0; i < fitted.size(); ++i) {
|
|
|
|
|
const nmFitParameter& p = fitted[i];
|
|
|
|
|
int slot = -1;
|
|
|
|
|
for(int w = 0; w < grid.m_vecWellInputs.size(); ++w)
|
|
|
|
|
if(grid.m_vecWellInputs[w].m_bRealWell && grid.m_vecWellInputs[w].m_sWellCode == p.wellCode) { slot = w; break; }
|
|
|
|
|
if(p.type != 0 && p.type != 3 && slot < 0) return fail(tr("The target well is unavailable."));
|
|
|
|
|
wellIndices.append(slot);
|
|
|
|
|
for(int field = 0; field < properties.m_vecDataSets.size(); ++field) {
|
|
|
|
|
const nmPropertyInterpolationDataSet& data = properties.m_vecDataSets[field];
|
|
|
|
|
if(data.useForCalculation && ((p.type == 0 && data.property == "k") || (p.type == 3 && data.property == "phi")))
|
|
|
|
|
return fail(tr("The sampled parameters are invalid or use a non-uniform field."));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 保存原裂缝端点,所有候选都从原几何缩放,防止重复评价产生累计偏差。
|
|
|
|
|
const std::vector<std::vector<double> > originalVertical = grid.m_oGridInput.FractureVerticalWell;
|
|
|
|
|
const std::vector<std::vector<std::vector<double> > > originalHorizontal = grid.m_oGridInput.MultistageFracturedHorizontalWell;
|
|
|
|
|
const QString resultWellCode = m_sAutoFitTargetWellCode;
|
|
|
|
|
m_sAutoFitTargetWellCode = grid.m_vecWellInputs[target].m_sWellCode;
|
|
|
|
|
auto evaluate = [&](const QVector<double>& parameters) -> QVector<QVector<double> > {
|
|
|
|
|
auto evaluate = [&](const QVector<double>& values) -> QVector<QVector<double> > {
|
|
|
|
|
if(isCancelRequested()) return QVector<QVector<double> >();
|
|
|
|
|
QVector<double> values = settings.initialValues;
|
|
|
|
|
int index = 0;
|
|
|
|
|
for(int i = 0; i < 6; ++i) if(selected[i]) values[i] = parameters[index++];
|
|
|
|
|
if(selected[0]) {
|
|
|
|
|
grid.m_oScene.Base.k_ref = nmCalculationUtils::milliDarcyToDarcy(values[0]);
|
|
|
|
|
properties.m_dReferencePermeabilityMilliDarcy = values[0];
|
|
|
|
|
}
|
|
|
|
|
if(selected[1]) {
|
|
|
|
|
grid.m_oScene.CS.S[target] = values[1];
|
|
|
|
|
grid.m_vecWellInputs[target].m_dSkin = values[1];
|
|
|
|
|
}
|
|
|
|
|
if(selected[2]) {
|
|
|
|
|
grid.m_oScene.CS.C[target] = values[2];
|
|
|
|
|
grid.m_vecWellInputs[target].m_dWellboreStorage = values[2];
|
|
|
|
|
}
|
|
|
|
|
if(selected[3]) {
|
|
|
|
|
grid.m_oScene.Base.phi_ref = values[3];
|
|
|
|
|
properties.m_dReferencePorosity = values[3];
|
|
|
|
|
}
|
|
|
|
|
for(size_t i = 0; i < fractures.size(); ++i) {
|
|
|
|
|
std::vector<double>& fracture = *fractures[i];
|
|
|
|
|
fracture = originalFractures[i];
|
|
|
|
|
if(selected[4]) fracture[5] = values[4];
|
|
|
|
|
if(selected[5]) {
|
|
|
|
|
const double dx = (fracture[2] - fracture[0]) * 0.5;
|
|
|
|
|
const double dy = (fracture[3] - fracture[1]) * 0.5;
|
|
|
|
|
const double centerX = (fracture[0] + fracture[2]) * 0.5;
|
|
|
|
|
const double centerY = (fracture[1] + fracture[3]) * 0.5;
|
|
|
|
|
const double scale = values[5] / std::sqrt(dx * dx + dy * dy);
|
|
|
|
|
fracture[0] = centerX - dx * scale;
|
|
|
|
|
fracture[1] = centerY - dy * scale;
|
|
|
|
|
fracture[2] = centerX + dx * scale;
|
|
|
|
|
fracture[3] = centerY + dy * scale;
|
|
|
|
|
grid.m_oGridInput.FractureVerticalWell = originalVertical;
|
|
|
|
|
grid.m_oGridInput.MultistageFracturedHorizontalWell = originalHorizontal;
|
|
|
|
|
for(int i = 0; i < fitted.size(); ++i) {
|
|
|
|
|
const int type = fitted[i].type;
|
|
|
|
|
const int slot = wellIndices[i];
|
|
|
|
|
const double value = values[i];
|
|
|
|
|
if(type == 0) {
|
|
|
|
|
grid.m_oScene.Base.k_ref = nmCalculationUtils::milliDarcyToDarcy(value);
|
|
|
|
|
properties.m_dReferencePermeabilityMilliDarcy = value;
|
|
|
|
|
} else if(type == 3) {
|
|
|
|
|
grid.m_oScene.Base.phi_ref = value;
|
|
|
|
|
properties.m_dReferencePorosity = value;
|
|
|
|
|
} else if(type == 1) {
|
|
|
|
|
grid.m_oScene.CS.S[slot] = value;
|
|
|
|
|
grid.m_vecWellInputs[slot].m_dSkin = value;
|
|
|
|
|
} else if(type == 2) {
|
|
|
|
|
grid.m_oScene.CS.C[slot] = value;
|
|
|
|
|
grid.m_vecWellInputs[slot].m_dWellboreStorage = value;
|
|
|
|
|
} else {
|
|
|
|
|
std::vector<std::vector<double>*> fractures;
|
|
|
|
|
size_t vertical = 0, horizontal = 0;
|
|
|
|
|
for(int w = 0; w <= slot; ++w) {
|
|
|
|
|
const int wellType = grid.m_vecSolverWellOrder[w].m_eWellType;
|
|
|
|
|
if(wellType == Vertical_Fractured_Well) {
|
|
|
|
|
if(w == slot) fractures.push_back(&grid.m_oGridInput.FractureVerticalWell[vertical]);
|
|
|
|
|
++vertical;
|
|
|
|
|
} else if(wellType == Horizontal_Fractured_Well) {
|
|
|
|
|
if(w == slot) {
|
|
|
|
|
std::vector<std::vector<double> >& stages = grid.m_oGridInput.MultistageFracturedHorizontalWell[horizontal];
|
|
|
|
|
for(size_t j = 0; j < stages.size(); ++j) fractures.push_back(&stages[j]);
|
|
|
|
|
}
|
|
|
|
|
++horizontal;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(fractures.empty()) return QVector<QVector<double> >();
|
|
|
|
|
for(size_t j = 0; j < fractures.size(); ++j) {
|
|
|
|
|
std::vector<double>& fracture = *fractures[j];
|
|
|
|
|
if(type == 4) fracture[5] = value;
|
|
|
|
|
else {
|
|
|
|
|
const double dx = (fracture[2] - fracture[0]) * 0.5;
|
|
|
|
|
const double dy = (fracture[3] - fracture[1]) * 0.5;
|
|
|
|
|
const double centerX = (fracture[0] + fracture[2]) * 0.5;
|
|
|
|
|
const double centerY = (fracture[1] + fracture[3]) * 0.5;
|
|
|
|
|
const double scale = value / std::sqrt(dx * dx + dy * dy);
|
|
|
|
|
fracture[0] = centerX - dx * scale;
|
|
|
|
|
fracture[1] = centerY - dy * scale;
|
|
|
|
|
fracture[2] = centerX + dx * scale;
|
|
|
|
|
fracture[3] = centerY + dy * scale;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(type == 4) grid.m_vecWellInputs[slot].m_dDfc = value;
|
|
|
|
|
m_pInputSnapshot->m_bRequiresGridCalculation = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(selected[4]) grid.m_vecWellInputs[target].m_dDfc = values[4];
|
|
|
|
|
m_pInputSnapshot->m_bRequiresGridCalculation =
|
|
|
|
|
m_pInputSnapshot->m_bRequiresGridCalculation || !fractures.empty();
|
|
|
|
|
m_sFailureReason.clear();
|
|
|
|
|
calculateSingleRun();
|
|
|
|
|
return m_lastRunSucceeded ? m_autoFitResultLogLog : QVector<QVector<double> >();
|
|
|
|
|
|