|
|
|
|
@ -0,0 +1,908 @@
|
|
|
|
|
#include "nmCalculationAutoFitPSO.h"
|
|
|
|
|
|
|
|
|
|
#include "nmDataAnalyzeManager.h"
|
|
|
|
|
#include "nmDataReservoir.h"
|
|
|
|
|
#include "nmDataWellBase.h"
|
|
|
|
|
#include "iBase/iUtils/ZxBaseUtil.h"
|
|
|
|
|
|
|
|
|
|
#include <QCoreApplication>
|
|
|
|
|
#include <QDir>
|
|
|
|
|
#include <QFileInfo>
|
|
|
|
|
#include <QMap>
|
|
|
|
|
#include <QMutexLocker>
|
|
|
|
|
#include <QTextStream>
|
|
|
|
|
#include <QUuid>
|
|
|
|
|
#include <QtCore/qmath.h>
|
|
|
|
|
#include <cmath>
|
|
|
|
|
|
|
|
|
|
#include "rapidjson/document.h"
|
|
|
|
|
#include "rapidjson/prettywriter.h"
|
|
|
|
|
#include "rapidjson/stringbuffer.h"
|
|
|
|
|
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
#ifndef NOMINMAX
|
|
|
|
|
#define NOMINMAX
|
|
|
|
|
#endif
|
|
|
|
|
#include <windows.h>
|
|
|
|
|
#include <wincrypt.h>
|
|
|
|
|
#include <float.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
static bool autoFitResultIsFinite(double value)
|
|
|
|
|
{
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
return _finite(value) != 0;
|
|
|
|
|
#else
|
|
|
|
|
return std::isfinite(value);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool autoFitResultIsNan(double value)
|
|
|
|
|
{
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
return _isnan(value) != 0;
|
|
|
|
|
#else
|
|
|
|
|
return std::isnan(value);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool autoFitCurvePassedForRun(const QString& status,
|
|
|
|
|
const AutoFitCurveMetrics& metrics)
|
|
|
|
|
{
|
|
|
|
|
return status == "SUCCESS" &&
|
|
|
|
|
metrics.valid &&
|
|
|
|
|
autoFitResultIsFinite(metrics.coverage) && metrics.coverage >= 0.95 &&
|
|
|
|
|
autoFitResultIsFinite(metrics.logDeltaPRmseDecade) &&
|
|
|
|
|
metrics.logDeltaPRmseDecade <= 0.02 &&
|
|
|
|
|
autoFitResultIsFinite(metrics.logDerivativeRmseDecade) &&
|
|
|
|
|
metrics.logDerivativeRmseDecade <= 0.02;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static QByteArray autoFitCanonicalDouble(double value)
|
|
|
|
|
{
|
|
|
|
|
if(autoFitResultIsFinite(value)) {
|
|
|
|
|
return QByteArray("FINITE:") +
|
|
|
|
|
QString::number(value, 'g', 17).toLatin1();
|
|
|
|
|
}
|
|
|
|
|
if(autoFitResultIsNan(value)) {
|
|
|
|
|
return QByteArray("NAN");
|
|
|
|
|
}
|
|
|
|
|
return value > 0.0
|
|
|
|
|
? QByteArray("POSITIVE_INFINITY")
|
|
|
|
|
: QByteArray("NEGATIVE_INFINITY");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static rapidjson::Value autoFitJsonNumber(double value)
|
|
|
|
|
{
|
|
|
|
|
rapidjson::Value jsonValue;
|
|
|
|
|
if(autoFitResultIsFinite(value)) {
|
|
|
|
|
jsonValue.SetDouble(value);
|
|
|
|
|
} else {
|
|
|
|
|
jsonValue.SetNull();
|
|
|
|
|
}
|
|
|
|
|
return jsonValue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static rapidjson::Value autoFitJsonInteger(qint64 value)
|
|
|
|
|
{
|
|
|
|
|
rapidjson::Value jsonValue;
|
|
|
|
|
if(value >= 0) {
|
|
|
|
|
jsonValue.SetInt64(value);
|
|
|
|
|
} else {
|
|
|
|
|
jsonValue.SetNull();
|
|
|
|
|
}
|
|
|
|
|
return jsonValue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static rapidjson::Value autoFitJsonString(
|
|
|
|
|
const QString& value,
|
|
|
|
|
rapidjson::Document::AllocatorType& allocator)
|
|
|
|
|
{
|
|
|
|
|
const QByteArray utf8 = value.toUtf8();
|
|
|
|
|
rapidjson::Value jsonValue;
|
|
|
|
|
jsonValue.SetString(utf8.constData(),
|
|
|
|
|
static_cast<rapidjson::SizeType>(utf8.size()), allocator);
|
|
|
|
|
return jsonValue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static QString autoFitDateTimeText(const QDateTime& value)
|
|
|
|
|
{
|
|
|
|
|
return value.isValid()
|
|
|
|
|
? value.toString("yyyy-MM-ddTHH:mm:ss.zzz")
|
|
|
|
|
: QString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static QByteArray autoFitCalculateSha256(const QByteArray& content)
|
|
|
|
|
{
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
HCRYPTPROV provider = 0;
|
|
|
|
|
HCRYPTHASH hash = 0;
|
|
|
|
|
QByteArray digest;
|
|
|
|
|
if(!CryptAcquireContext(&provider, NULL, NULL, PROV_RSA_AES,
|
|
|
|
|
CRYPT_VERIFYCONTEXT)) {
|
|
|
|
|
return digest;
|
|
|
|
|
}
|
|
|
|
|
if(!CryptCreateHash(provider, CALG_SHA_256, 0, 0, &hash)) {
|
|
|
|
|
CryptReleaseContext(provider, 0);
|
|
|
|
|
return digest;
|
|
|
|
|
}
|
|
|
|
|
const bool hashed = CryptHashData(hash,
|
|
|
|
|
reinterpret_cast<const BYTE*>(content.constData()),
|
|
|
|
|
static_cast<DWORD>(content.size()), 0) != FALSE;
|
|
|
|
|
BYTE hashBytes[32] = { 0 };
|
|
|
|
|
DWORD hashSize = sizeof(hashBytes);
|
|
|
|
|
if(hashed && CryptGetHashParam(hash, HP_HASHVAL, hashBytes,
|
|
|
|
|
&hashSize, 0)) {
|
|
|
|
|
digest = QByteArray(reinterpret_cast<const char*>(hashBytes),
|
|
|
|
|
static_cast<int>(hashSize));
|
|
|
|
|
}
|
|
|
|
|
CryptDestroyHash(hash);
|
|
|
|
|
CryptReleaseContext(provider, 0);
|
|
|
|
|
return digest;
|
|
|
|
|
#else
|
|
|
|
|
Q_UNUSED(content);
|
|
|
|
|
return QByteArray();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static QString autoFitCsvField(const QString& value)
|
|
|
|
|
{
|
|
|
|
|
QString escaped = value;
|
|
|
|
|
escaped.replace('"', "\"\"");
|
|
|
|
|
return QString("\"%1\"").arg(escaped);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static QString autoFitCsvNumber(double value)
|
|
|
|
|
{
|
|
|
|
|
return autoFitResultIsFinite(value)
|
|
|
|
|
? QString::number(value, 'g', 17)
|
|
|
|
|
: QString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static QString autoFitCsvInteger(qint64 value)
|
|
|
|
|
{
|
|
|
|
|
return value >= 0 ? QString::number(value) : QString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void autoFitAppendArtifactError(QString* errors,
|
|
|
|
|
const QString& error)
|
|
|
|
|
{
|
|
|
|
|
if(!errors || error.isEmpty() || errors->contains(error)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if(!errors->isEmpty()) {
|
|
|
|
|
errors->append(';');
|
|
|
|
|
}
|
|
|
|
|
errors->append(error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static QString autoFitPhaseName(NM_SOLVER_MODEL_TYPE modelType)
|
|
|
|
|
{
|
|
|
|
|
switch(modelType) {
|
|
|
|
|
case SMT_Oil_ConstPvt:
|
|
|
|
|
return "OIL_CONSTANT_PVT";
|
|
|
|
|
case SMT_Oil_VariablePvt:
|
|
|
|
|
return "OIL_VARIABLE_PVT";
|
|
|
|
|
case SMT_Water_ConstPvt:
|
|
|
|
|
return "WATER_CONSTANT_PVT";
|
|
|
|
|
case SMT_Water_VariablePvt:
|
|
|
|
|
return "WATER_VARIABLE_PVT";
|
|
|
|
|
case SMT_Gas_VariablePvt:
|
|
|
|
|
return "GAS_VARIABLE_PVT";
|
|
|
|
|
case SMT_Gas_PseudoPressure:
|
|
|
|
|
return "GAS_PSEUDO_PRESSURE";
|
|
|
|
|
case SMT_Oil_Gas_TwoPhase:
|
|
|
|
|
return "OIL_GAS_TWO_PHASE";
|
|
|
|
|
case SMT_Oil_Water_TwoPhase:
|
|
|
|
|
return "OIL_WATER_TWO_PHASE";
|
|
|
|
|
case SMT_Gas_Water_TwoPhase:
|
|
|
|
|
return "GAS_WATER_TWO_PHASE";
|
|
|
|
|
case SMT_Oil_Gas_Water_ThreePhase:
|
|
|
|
|
return "OIL_GAS_WATER_THREE_PHASE";
|
|
|
|
|
default:
|
|
|
|
|
return "UNKNOWN";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static QStringList autoFitParameterUnits()
|
|
|
|
|
{
|
|
|
|
|
QStringList units;
|
|
|
|
|
units << "mD"
|
|
|
|
|
<< ""
|
|
|
|
|
<< "m^3/MPa"
|
|
|
|
|
<< ""
|
|
|
|
|
<< "m"
|
|
|
|
|
<< "MPa^-1"
|
|
|
|
|
<< "MPa^-1"
|
|
|
|
|
<< ""
|
|
|
|
|
<< "mD.m"
|
|
|
|
|
<< "m";
|
|
|
|
|
return units;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static rapidjson::Value autoFitJsonDoubleArray(
|
|
|
|
|
const QVector<double>& values,
|
|
|
|
|
rapidjson::Document::AllocatorType& allocator)
|
|
|
|
|
{
|
|
|
|
|
rapidjson::Value array(rapidjson::kArrayType);
|
|
|
|
|
for(int i = 0; i < values.size(); ++i) {
|
|
|
|
|
array.PushBack(autoFitJsonNumber(values[i]).Move(), allocator);
|
|
|
|
|
}
|
|
|
|
|
return array;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
void nmCalculationAutoFitPSO::initializeRunResult()
|
|
|
|
|
{
|
|
|
|
|
closeTraceFile();
|
|
|
|
|
m_lastRunResult = AutoFitRunResult();
|
|
|
|
|
m_runTimingStarted = false;
|
|
|
|
|
m_runTimedOut = false;
|
|
|
|
|
// 配置校验也可能提前失败,因此在读取配置前先清空上一次运行的统计和
|
|
|
|
|
// 最优曲线,避免失败结果错误引用上一轮的调用次数、参数或统一误差。
|
|
|
|
|
m_currentIteration = 0;
|
|
|
|
|
m_completedIterationCount = 0;
|
|
|
|
|
m_totalEvaluations = 0;
|
|
|
|
|
m_successfulEvaluations = 0;
|
|
|
|
|
m_lastError.clear();
|
|
|
|
|
m_initialValues.clear();
|
|
|
|
|
m_userInitialSolution.clear();
|
|
|
|
|
m_userInitialFitness = 1.0e10;
|
|
|
|
|
m_hasValidUserSolution = false;
|
|
|
|
|
m_parameterSelected.clear();
|
|
|
|
|
m_parameterLower.clear();
|
|
|
|
|
m_parameterUpper.clear();
|
|
|
|
|
m_enabledParamIndices.clear();
|
|
|
|
|
m_globalBestPosition.clear();
|
|
|
|
|
m_globalBestPressureData.clear();
|
|
|
|
|
m_globalBestFitness = 1.0e10;
|
|
|
|
|
m_traceRunId.clear();
|
|
|
|
|
m_traceFilePath.clear();
|
|
|
|
|
m_traceMetaFilePath.clear();
|
|
|
|
|
m_lastRunResult.runId = QString("AF-%1-%2-%3")
|
|
|
|
|
.arg(QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss-zzz"))
|
|
|
|
|
.arg(QCoreApplication::applicationPid())
|
|
|
|
|
.arg(QUuid::createUuid().toString().remove('{').remove('}').remove('-').left(8));
|
|
|
|
|
m_lastRunResult.startedAt = QDateTime::currentDateTime();
|
|
|
|
|
m_lastRunResult.targetWell = m_targetWellName;
|
|
|
|
|
m_lastRunResult.status = "FAILED";
|
|
|
|
|
m_lastRunResult.stopReason = "RUN_INITIALIZATION";
|
|
|
|
|
m_lastRunResult.projectPath = QDir::cleanPath(ZxBaseUtil::getCurProjectDir());
|
|
|
|
|
// 即使后续配置读取失败,也尽量保留当前工程可取得的相态与求解器信息;
|
|
|
|
|
// 算法只有在配置读取成功后才能确定,失败路径明确记为不可用。
|
|
|
|
|
captureRunConfiguration(false);
|
|
|
|
|
m_lastRunResult.algorithm = "UNAVAILABLE";
|
|
|
|
|
nmDataAnalyzeManager* pDataManager = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
|
|
if(pDataManager && pDataManager->getReservoirData()) {
|
|
|
|
|
m_lastRunResult.initialPressureMpa =
|
|
|
|
|
pDataManager->getReservoirData()->getInitialPressure()
|
|
|
|
|
.getValue().toDouble();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_frozenTargetPressureData = m_targetPressureData;
|
|
|
|
|
if(m_frozenTargetPressureData.size() < 2) {
|
|
|
|
|
nmDataWellBase* pTargetWell = pDataManager
|
|
|
|
|
? pDataManager->findWellByName(m_targetWellName)
|
|
|
|
|
: nullptr;
|
|
|
|
|
if(pTargetWell) {
|
|
|
|
|
m_frozenTargetPressureData = pTargetWell->getHistoryPressure();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
m_lastRunResult.targetCurveSha256 =
|
|
|
|
|
calculateTargetCurveHash(m_frozenTargetPressureData);
|
|
|
|
|
|
|
|
|
|
const QString outputRoot = getAutoFitOutputRoot();
|
|
|
|
|
m_lastRunResult.resultDirectory =
|
|
|
|
|
QDir(outputRoot).absoluteFilePath(m_lastRunResult.runId);
|
|
|
|
|
m_lastRunResult.resultJsonPath =
|
|
|
|
|
QDir(m_lastRunResult.resultDirectory).absoluteFilePath("autofit_result.json");
|
|
|
|
|
m_lastRunResult.curveCsvPath =
|
|
|
|
|
QDir(m_lastRunResult.resultDirectory).absoluteFilePath("autofit_curve.csv");
|
|
|
|
|
m_lastRunResult.runsCsvPath =
|
|
|
|
|
QDir(outputRoot).absoluteFilePath("autofit_runs.csv");
|
|
|
|
|
|
|
|
|
|
const QString pebiRoot = ZxBaseUtil::getCurWellDirOf("Nm/Solver");
|
|
|
|
|
m_lastRunResult.fullFieldPressurePath = QDir(pebiRoot).absoluteFilePath(
|
|
|
|
|
QString("output/Pebi/%1/Pressure.txt").arg(m_targetWellName));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmCalculationAutoFitPSO::captureRunConfiguration(bool useParticleSwarm)
|
|
|
|
|
{
|
|
|
|
|
m_lastRunResult.phase = "UNKNOWN";
|
|
|
|
|
m_lastRunResult.algorithm = useParticleSwarm
|
|
|
|
|
? "PSO_WITH_SURROGATE_SCREENING"
|
|
|
|
|
: "DIAGNOSTIC_TRUST_REGION";
|
|
|
|
|
m_lastRunResult.solverType = "UNKNOWN";
|
|
|
|
|
nmDataAnalyzeManager* pDataManager = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
|
|
if(!pDataManager) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_lastRunResult.phase = autoFitPhaseName(pDataManager->getSolverModelType());
|
|
|
|
|
m_lastRunResult.solverType =
|
|
|
|
|
pDataManager->getPebiSolverType() ==
|
|
|
|
|
nmDataAnalyzeManager::PebiSolverCpuAccelerated
|
|
|
|
|
? "CPU_ACCELERATED"
|
|
|
|
|
: "ORIGINAL";
|
|
|
|
|
m_lastRunResult.ompThreads = pDataManager->getPebiOmpThreads();
|
|
|
|
|
m_lastRunResult.iluReuseSteps = pDataManager->getPebiIluReuseSteps();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmCalculationAutoFitPSO::beginRunTiming()
|
|
|
|
|
{
|
|
|
|
|
m_optimizationWallTimer.restart();
|
|
|
|
|
m_workflowWallTimer.restart();
|
|
|
|
|
m_runTimingStarted = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmCalculationAutoFitPSO::markOptimizationFinished()
|
|
|
|
|
{
|
|
|
|
|
if(m_runTimingStarted && m_lastRunResult.optimizationWallTimeMs < 0) {
|
|
|
|
|
m_lastRunResult.optimizationWallTimeMs = m_optimizationWallTimer.elapsed();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmCalculationAutoFitPSO::markWorkflowFinished()
|
|
|
|
|
{
|
|
|
|
|
if(m_runTimingStarted && m_lastRunResult.workflowWallTimeMs < 0) {
|
|
|
|
|
m_lastRunResult.workflowWallTimeMs = m_workflowWallTimer.elapsed();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::isRunTimeLimitReached()
|
|
|
|
|
{
|
|
|
|
|
if(m_runTimedOut) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
// workflow_wall_time_ms 一旦冻结,后续文件写出、日志和界面清理均不再
|
|
|
|
|
// 属于正式运行预算,不能在收口之后把既有状态反向改成 TIMEOUT。
|
|
|
|
|
if(m_lastRunResult.workflowWallTimeMs >= 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if(!m_runTimingStarted ||
|
|
|
|
|
m_workflowWallTimer.elapsed() < RUN_TIME_LIMIT_MS) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 超时是完整拟合运行的独立结束路径,不能复用用户停止标志,否则最终
|
|
|
|
|
// JSON 会把 TIMEOUT 错记成 STOPPED,求解器调用统计也会丢失超时次数。
|
|
|
|
|
m_runTimedOut = true;
|
|
|
|
|
// 超时边界就是本次运行的参数确定和工作流结束时刻。终止后台任务所需的
|
|
|
|
|
// 清理等待不属于拟合耗时,否则同一超时会因线程退出速度不同得到不同记录。
|
|
|
|
|
if(m_lastRunResult.optimizationWallTimeMs < 0) {
|
|
|
|
|
m_lastRunResult.optimizationWallTimeMs = RUN_TIME_LIMIT_MS;
|
|
|
|
|
}
|
|
|
|
|
if(m_lastRunResult.workflowWallTimeMs < 0) {
|
|
|
|
|
m_lastRunResult.workflowWallTimeMs = RUN_TIME_LIMIT_MS;
|
|
|
|
|
}
|
|
|
|
|
emit logMessageGenerated(tr("Automatic fitting run time limit reached"));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int nmCalculationAutoFitPSO::remainingRunTimeMs()
|
|
|
|
|
{
|
|
|
|
|
if(isRunTimeLimitReached()) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
if(!m_runTimingStarted) {
|
|
|
|
|
return RUN_TIME_LIMIT_MS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const qint64 remaining = static_cast<qint64>(RUN_TIME_LIMIT_MS) -
|
|
|
|
|
m_workflowWallTimer.elapsed();
|
|
|
|
|
return remaining > 0
|
|
|
|
|
? static_cast<int>(qMin(remaining,
|
|
|
|
|
static_cast<qint64>(RUN_TIME_LIMIT_MS)))
|
|
|
|
|
: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmCalculationAutoFitPSO::captureRunParameters()
|
|
|
|
|
{
|
|
|
|
|
m_lastRunResult.parameters.clear();
|
|
|
|
|
const QStringList parameterNames = traceParameterNames();
|
|
|
|
|
const QStringList parameterUnits = autoFitParameterUnits();
|
|
|
|
|
for(int selectedIndex = 0;
|
|
|
|
|
selectedIndex < m_enabledParamIndices.size();
|
|
|
|
|
++selectedIndex) {
|
|
|
|
|
const int parameterIndex = m_enabledParamIndices[selectedIndex];
|
|
|
|
|
if(parameterIndex < 0 || parameterIndex >= parameterNames.size()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AutoFitParameterResult parameter;
|
|
|
|
|
parameter.name = parameterNames[parameterIndex];
|
|
|
|
|
parameter.unit = parameterIndex < parameterUnits.size()
|
|
|
|
|
? parameterUnits[parameterIndex]
|
|
|
|
|
: QString();
|
|
|
|
|
if(selectedIndex < m_initialValues.size()) {
|
|
|
|
|
parameter.initialValue = m_initialValues[selectedIndex];
|
|
|
|
|
} else if(selectedIndex < m_userInitialSolution.size()) {
|
|
|
|
|
parameter.initialValue = m_userInitialSolution[selectedIndex];
|
|
|
|
|
}
|
|
|
|
|
if(parameterIndex < m_parameterLower.size()) {
|
|
|
|
|
parameter.lowerBound = m_parameterLower[parameterIndex];
|
|
|
|
|
}
|
|
|
|
|
if(parameterIndex < m_parameterUpper.size()) {
|
|
|
|
|
parameter.upperBound = m_parameterUpper[parameterIndex];
|
|
|
|
|
}
|
|
|
|
|
if(selectedIndex < m_globalBestPosition.size()) {
|
|
|
|
|
parameter.finalValue = m_globalBestPosition[selectedIndex];
|
|
|
|
|
}
|
|
|
|
|
m_lastRunResult.parameters.append(parameter);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmCalculationAutoFitPSO::finalizeRunResult(const QString& status,
|
|
|
|
|
const QString& stopReason,
|
|
|
|
|
bool writeArtifacts)
|
|
|
|
|
{
|
|
|
|
|
markOptimizationFinished();
|
|
|
|
|
markWorkflowFinished();
|
|
|
|
|
m_lastRunResult.finishedAt = QDateTime::currentDateTime();
|
|
|
|
|
m_lastRunResult.status = status;
|
|
|
|
|
m_lastRunResult.stopReason = stopReason;
|
|
|
|
|
m_lastRunResult.iterationCount = qMax(0, m_completedIterationCount);
|
|
|
|
|
m_lastRunResult.parameterEvaluationCount = m_totalEvaluations;
|
|
|
|
|
m_lastRunResult.initialInternalError = m_hasValidUserSolution
|
|
|
|
|
? m_userInitialFitness
|
|
|
|
|
: std::numeric_limits<double>::quiet_NaN();
|
|
|
|
|
m_lastRunResult.finalInternalError =
|
|
|
|
|
m_globalBestFitness < 1.0e9
|
|
|
|
|
? m_globalBestFitness
|
|
|
|
|
: std::numeric_limits<double>::quiet_NaN();
|
|
|
|
|
m_lastRunResult.traceCsvPath = m_traceFilePath;
|
|
|
|
|
m_lastRunResult.traceMetaJsonPath = m_traceMetaFilePath;
|
|
|
|
|
captureRunParameters();
|
|
|
|
|
|
|
|
|
|
m_lastRunResult.curveMetrics = calculateUnifiedCurveMetrics(
|
|
|
|
|
m_frozenTargetPressureData,
|
|
|
|
|
m_globalBestPressureData,
|
|
|
|
|
m_lastRunResult.initialPressureMpa,
|
|
|
|
|
80);
|
|
|
|
|
// 协议中的 curve_passed 不只是曲线数值判据,还要求原生任务真正达到
|
|
|
|
|
// SUCCESS。COMPLETED、STOPPED 和 FAILED 即使保留了一条好曲线也不能通过。
|
|
|
|
|
m_lastRunResult.curveMetrics.passed = autoFitCurvePassedForRun(
|
|
|
|
|
m_lastRunResult.status, m_lastRunResult.curveMetrics);
|
|
|
|
|
|
|
|
|
|
if(writeArtifacts) {
|
|
|
|
|
const bool artifactsWritten = writeStructuredRunArtifacts();
|
|
|
|
|
if(!artifactsWritten) {
|
|
|
|
|
emit logMessageGenerated(tr("Result artifact export failed: %1")
|
|
|
|
|
.arg(m_lastRunResult.artifactError));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString nmCalculationAutoFitPSO::getAutoFitOutputRoot() const
|
|
|
|
|
{
|
|
|
|
|
const QString solverRoot = ZxBaseUtil::getCurProjectDirOf("Nm/Solver");
|
|
|
|
|
return QDir(solverRoot).absoluteFilePath("output/AutoFit");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString nmCalculationAutoFitPSO::calculateTargetCurveHash(
|
|
|
|
|
const QVector<QVector<double> >& pressureData) const
|
|
|
|
|
{
|
|
|
|
|
QByteArray canonical;
|
|
|
|
|
canonical.append("AUTOFIT_TARGET_PRESSURE_V2\r\n");
|
|
|
|
|
canonical.append("column_count=");
|
|
|
|
|
canonical.append(QByteArray::number(pressureData.size()));
|
|
|
|
|
canonical.append("\r\n");
|
|
|
|
|
for(int column = 0; column < pressureData.size(); ++column) {
|
|
|
|
|
canonical.append("column=");
|
|
|
|
|
canonical.append(QByteArray::number(column));
|
|
|
|
|
canonical.append(",length=");
|
|
|
|
|
canonical.append(QByteArray::number(pressureData[column].size()));
|
|
|
|
|
canonical.append("\r\n");
|
|
|
|
|
for(int row = 0; row < pressureData[column].size(); ++row) {
|
|
|
|
|
canonical.append("row=");
|
|
|
|
|
canonical.append(QByteArray::number(row));
|
|
|
|
|
canonical.append(",value=");
|
|
|
|
|
canonical.append(autoFitCanonicalDouble(pressureData[column][row]));
|
|
|
|
|
canonical.append("\r\n");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return QString::fromLatin1(autoFitCalculateSha256(canonical).toHex().toUpper());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::writeStructuredRunArtifacts()
|
|
|
|
|
{
|
|
|
|
|
m_lastRunResult.artifactError.clear();
|
|
|
|
|
// 写出层再次执行原生状态门控,防止测试入口或未来批处理入口绕过
|
|
|
|
|
// finalizeRunResult() 后产生 curve_passed=1 的非 SUCCESS 记录。
|
|
|
|
|
m_lastRunResult.curveMetrics.passed = autoFitCurvePassedForRun(
|
|
|
|
|
m_lastRunResult.status, m_lastRunResult.curveMetrics);
|
|
|
|
|
if(m_lastRunResult.resultDirectory.isEmpty() ||
|
|
|
|
|
!QDir().mkpath(m_lastRunResult.resultDirectory)) {
|
|
|
|
|
m_lastRunResult.artifactError = "CREATE_RESULT_DIRECTORY_FAILED";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool allSucceeded = true;
|
|
|
|
|
if(!writeRunCurveCsv()) {
|
|
|
|
|
autoFitAppendArtifactError(&m_lastRunResult.artifactError,
|
|
|
|
|
"WRITE_CURVE_CSV_FAILED");
|
|
|
|
|
allSucceeded = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// JSON 先于汇总 CSV 写出。若 JSON 失败,artifact_error 会随随后追加的
|
|
|
|
|
// 汇总行持久化,避免 CSV 只留下一个不存在的 JSON 路径却没有失败原因。
|
|
|
|
|
const bool jsonSucceeded = writeRunResultJson();
|
|
|
|
|
if(!jsonSucceeded) {
|
|
|
|
|
autoFitAppendArtifactError(&m_lastRunResult.artifactError,
|
|
|
|
|
"WRITE_RESULT_JSON_FAILED");
|
|
|
|
|
allSucceeded = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool summarySucceeded = appendRunSummaryCsv();
|
|
|
|
|
if(!summarySucceeded) {
|
|
|
|
|
autoFitAppendArtifactError(&m_lastRunResult.artifactError,
|
|
|
|
|
"APPEND_RUNS_CSV_FAILED");
|
|
|
|
|
allSucceeded = false;
|
|
|
|
|
// 首次 JSON 已成功时再覆盖一次,使 JSON 也记录汇总追加失败。
|
|
|
|
|
if(jsonSucceeded && !writeRunResultJson()) {
|
|
|
|
|
autoFitAppendArtifactError(&m_lastRunResult.artifactError,
|
|
|
|
|
"REWRITE_RESULT_JSON_FAILED");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return allSucceeded;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::writeRunCurveCsv()
|
|
|
|
|
{
|
|
|
|
|
QFile file(m_lastRunResult.curveCsvPath);
|
|
|
|
|
if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
QTextStream stream(&file);
|
|
|
|
|
stream.setCodec("UTF-8");
|
|
|
|
|
stream << "point_no,time_hr,target_pressure_mpa,fitted_pressure_mpa,"
|
|
|
|
|
"target_delta_p_mpa,fitted_delta_p_mpa,target_derivative_mpa,"
|
|
|
|
|
"fitted_derivative_mpa,pressure_residual_mpa,"
|
|
|
|
|
"log_delta_p_residual_decade,log_derivative_residual_decade\n";
|
|
|
|
|
|
|
|
|
|
const AutoFitCurveMetrics& metrics = m_lastRunResult.curveMetrics;
|
|
|
|
|
for(int i = 0; i < metrics.timeHr.size(); ++i) {
|
|
|
|
|
const double pressureResidual =
|
|
|
|
|
metrics.fittedPressureMpa[i] - metrics.targetPressureMpa[i];
|
|
|
|
|
double logDeltaPResidual =
|
|
|
|
|
std::numeric_limits<double>::quiet_NaN();
|
|
|
|
|
if(autoFitResultIsFinite(metrics.targetDeltaPMpa[i]) &&
|
|
|
|
|
metrics.targetDeltaPMpa[i] > 0.0 &&
|
|
|
|
|
autoFitResultIsFinite(metrics.fittedDeltaPMpa[i]) &&
|
|
|
|
|
metrics.fittedDeltaPMpa[i] > 0.0) {
|
|
|
|
|
logDeltaPResidual =
|
|
|
|
|
qLn(metrics.fittedDeltaPMpa[i] /
|
|
|
|
|
metrics.targetDeltaPMpa[i]) /
|
|
|
|
|
qLn(10.0);
|
|
|
|
|
}
|
|
|
|
|
double logDerivativeResidual =
|
|
|
|
|
std::numeric_limits<double>::quiet_NaN();
|
|
|
|
|
if(i < metrics.targetDerivativeMpa.size() &&
|
|
|
|
|
i < metrics.fittedDerivativeMpa.size() &&
|
|
|
|
|
autoFitResultIsFinite(metrics.targetDerivativeMpa[i]) &&
|
|
|
|
|
metrics.targetDerivativeMpa[i] > 0.0 &&
|
|
|
|
|
autoFitResultIsFinite(metrics.fittedDerivativeMpa[i]) &&
|
|
|
|
|
metrics.fittedDerivativeMpa[i] > 0.0) {
|
|
|
|
|
logDerivativeResidual =
|
|
|
|
|
qLn(metrics.fittedDerivativeMpa[i] /
|
|
|
|
|
metrics.targetDerivativeMpa[i]) /
|
|
|
|
|
qLn(10.0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stream << (i + 1) << ','
|
|
|
|
|
<< autoFitCsvNumber(metrics.timeHr[i]) << ','
|
|
|
|
|
<< autoFitCsvNumber(metrics.targetPressureMpa[i]) << ','
|
|
|
|
|
<< autoFitCsvNumber(metrics.fittedPressureMpa[i]) << ','
|
|
|
|
|
<< autoFitCsvNumber(metrics.targetDeltaPMpa[i]) << ','
|
|
|
|
|
<< autoFitCsvNumber(metrics.fittedDeltaPMpa[i]) << ','
|
|
|
|
|
<< autoFitCsvNumber(i < metrics.targetDerivativeMpa.size()
|
|
|
|
|
? metrics.targetDerivativeMpa[i]
|
|
|
|
|
: std::numeric_limits<double>::quiet_NaN()) << ','
|
|
|
|
|
<< autoFitCsvNumber(i < metrics.fittedDerivativeMpa.size()
|
|
|
|
|
? metrics.fittedDerivativeMpa[i]
|
|
|
|
|
: std::numeric_limits<double>::quiet_NaN()) << ','
|
|
|
|
|
<< autoFitCsvNumber(pressureResidual) << ','
|
|
|
|
|
<< autoFitCsvNumber(logDeltaPResidual) << ','
|
|
|
|
|
<< autoFitCsvNumber(logDerivativeResidual) << "\n";
|
|
|
|
|
}
|
|
|
|
|
stream.flush();
|
|
|
|
|
const bool succeeded = stream.status() == QTextStream::Ok;
|
|
|
|
|
file.close();
|
|
|
|
|
return succeeded;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::writeRunResultJson()
|
|
|
|
|
{
|
|
|
|
|
rapidjson::Document root;
|
|
|
|
|
root.SetObject();
|
|
|
|
|
rapidjson::Document::AllocatorType& allocator = root.GetAllocator();
|
|
|
|
|
root.AddMember("schema_version", 1, allocator);
|
|
|
|
|
root.AddMember("run_id", autoFitJsonString(
|
|
|
|
|
m_lastRunResult.runId, allocator).Move(), allocator);
|
|
|
|
|
root.AddMember("status", autoFitJsonString(
|
|
|
|
|
m_lastRunResult.status, allocator).Move(), allocator);
|
|
|
|
|
root.AddMember("stop_reason", autoFitJsonString(
|
|
|
|
|
m_lastRunResult.stopReason, allocator).Move(), allocator);
|
|
|
|
|
root.AddMember("started_at", autoFitJsonString(
|
|
|
|
|
autoFitDateTimeText(m_lastRunResult.startedAt), allocator).Move(), allocator);
|
|
|
|
|
root.AddMember("finished_at", autoFitJsonString(
|
|
|
|
|
autoFitDateTimeText(m_lastRunResult.finishedAt), allocator).Move(), allocator);
|
|
|
|
|
|
|
|
|
|
rapidjson::Value context(rapidjson::kObjectType);
|
|
|
|
|
context.AddMember("target_well", autoFitJsonString(
|
|
|
|
|
m_lastRunResult.targetWell, allocator).Move(), allocator);
|
|
|
|
|
context.AddMember("phase", autoFitJsonString(
|
|
|
|
|
m_lastRunResult.phase, allocator).Move(), allocator);
|
|
|
|
|
context.AddMember("algorithm", autoFitJsonString(
|
|
|
|
|
m_lastRunResult.algorithm, allocator).Move(), allocator);
|
|
|
|
|
context.AddMember("solver_type", autoFitJsonString(
|
|
|
|
|
m_lastRunResult.solverType, allocator).Move(), allocator);
|
|
|
|
|
context.AddMember("openmp_threads", autoFitJsonInteger(
|
|
|
|
|
m_lastRunResult.ompThreads).Move(), allocator);
|
|
|
|
|
context.AddMember("ilu_reuse_steps", autoFitJsonInteger(
|
|
|
|
|
m_lastRunResult.iluReuseSteps).Move(), allocator);
|
|
|
|
|
context.AddMember("project_path", autoFitJsonString(
|
|
|
|
|
m_lastRunResult.projectPath, allocator).Move(), allocator);
|
|
|
|
|
context.AddMember("target_curve_sha256", autoFitJsonString(
|
|
|
|
|
m_lastRunResult.targetCurveSha256, allocator).Move(), allocator);
|
|
|
|
|
context.AddMember("initial_pressure_mpa", autoFitJsonNumber(
|
|
|
|
|
m_lastRunResult.initialPressureMpa).Move(), allocator);
|
|
|
|
|
root.AddMember("context", context, allocator);
|
|
|
|
|
|
|
|
|
|
rapidjson::Value timing(rapidjson::kObjectType);
|
|
|
|
|
timing.AddMember("optimization_wall_time_ms", autoFitJsonInteger(
|
|
|
|
|
m_lastRunResult.optimizationWallTimeMs).Move(), allocator);
|
|
|
|
|
timing.AddMember("workflow_wall_time_ms", autoFitJsonInteger(
|
|
|
|
|
m_lastRunResult.workflowWallTimeMs).Move(), allocator);
|
|
|
|
|
timing.AddMember("solver_time_sum_ms", autoFitJsonInteger(
|
|
|
|
|
m_lastRunResult.solverTimeSumMs).Move(), allocator);
|
|
|
|
|
timing.AddMember("final_solver_time_ms", autoFitJsonInteger(
|
|
|
|
|
m_lastRunResult.finalSolverTimeMs).Move(), allocator);
|
|
|
|
|
root.AddMember("timing", timing, allocator);
|
|
|
|
|
|
|
|
|
|
rapidjson::Value counts(rapidjson::kObjectType);
|
|
|
|
|
counts.AddMember("iterations", m_lastRunResult.iterationCount, allocator);
|
|
|
|
|
counts.AddMember("parameter_evaluations",
|
|
|
|
|
m_lastRunResult.parameterEvaluationCount, allocator);
|
|
|
|
|
counts.AddMember("model_solver_calls",
|
|
|
|
|
m_lastRunResult.modelSolverCallCount, allocator);
|
|
|
|
|
counts.AddMember("final_solver_calls",
|
|
|
|
|
m_lastRunResult.finalSolverCallCount, allocator);
|
|
|
|
|
counts.AddMember("solver_successes",
|
|
|
|
|
m_lastRunResult.solverSuccessCount, allocator);
|
|
|
|
|
counts.AddMember("solver_failures",
|
|
|
|
|
m_lastRunResult.solverFailureCount, allocator);
|
|
|
|
|
counts.AddMember("solver_timeouts",
|
|
|
|
|
m_lastRunResult.solverTimeoutCount, allocator);
|
|
|
|
|
counts.AddMember("optimization_pebi_count", autoFitJsonInteger(
|
|
|
|
|
m_lastRunResult.optimizationPebiCount).Move(), allocator);
|
|
|
|
|
counts.AddMember("final_pebi_count", autoFitJsonInteger(
|
|
|
|
|
m_lastRunResult.finalPebiCount).Move(), allocator);
|
|
|
|
|
counts.AddMember("pebi_count", autoFitJsonInteger(
|
|
|
|
|
m_lastRunResult.pebiCount).Move(), allocator);
|
|
|
|
|
counts.AddMember("final_solver_status", autoFitJsonString(
|
|
|
|
|
m_lastRunResult.finalSolverStatus, allocator).Move(), allocator);
|
|
|
|
|
root.AddMember("counts", counts, allocator);
|
|
|
|
|
|
|
|
|
|
rapidjson::Value objective(rapidjson::kObjectType);
|
|
|
|
|
objective.AddMember("initial_internal_error", autoFitJsonNumber(
|
|
|
|
|
m_lastRunResult.initialInternalError).Move(), allocator);
|
|
|
|
|
objective.AddMember("final_internal_error", autoFitJsonNumber(
|
|
|
|
|
m_lastRunResult.finalInternalError).Move(), allocator);
|
|
|
|
|
root.AddMember("optimizer_objective", objective, allocator);
|
|
|
|
|
|
|
|
|
|
const AutoFitCurveMetrics& metrics = m_lastRunResult.curveMetrics;
|
|
|
|
|
rapidjson::Value curveMetrics(rapidjson::kObjectType);
|
|
|
|
|
curveMetrics.AddMember("valid", metrics.valid, allocator);
|
|
|
|
|
curveMetrics.AddMember("passed", metrics.passed, allocator);
|
|
|
|
|
curveMetrics.AddMember("invalid_reason", autoFitJsonString(
|
|
|
|
|
metrics.invalidReason, allocator).Move(), allocator);
|
|
|
|
|
curveMetrics.AddMember("sample_count", metrics.sampleCount, allocator);
|
|
|
|
|
curveMetrics.AddMember("valid_derivative_count",
|
|
|
|
|
metrics.validDerivativeCount, allocator);
|
|
|
|
|
curveMetrics.AddMember("coverage", autoFitJsonNumber(
|
|
|
|
|
metrics.coverage).Move(), allocator);
|
|
|
|
|
curveMetrics.AddMember("pressure_rmse_mpa", autoFitJsonNumber(
|
|
|
|
|
metrics.pressureRmseMpa).Move(), allocator);
|
|
|
|
|
curveMetrics.AddMember("pressure_max_abs_error_mpa", autoFitJsonNumber(
|
|
|
|
|
metrics.pressureMaxAbsErrorMpa).Move(), allocator);
|
|
|
|
|
curveMetrics.AddMember("log_delta_p_rmse_decade", autoFitJsonNumber(
|
|
|
|
|
metrics.logDeltaPRmseDecade).Move(), allocator);
|
|
|
|
|
curveMetrics.AddMember("log_derivative_rmse_decade", autoFitJsonNumber(
|
|
|
|
|
metrics.logDerivativeRmseDecade).Move(), allocator);
|
|
|
|
|
curveMetrics.AddMember("unified_curve_error", autoFitJsonNumber(
|
|
|
|
|
metrics.unifiedCurveError).Move(), allocator);
|
|
|
|
|
curveMetrics.AddMember("coverage_threshold", 0.95, allocator);
|
|
|
|
|
curveMetrics.AddMember("log_rmse_threshold_decade", 0.02, allocator);
|
|
|
|
|
curveMetrics.AddMember("unified_curve_error_formula", autoFitJsonString(
|
|
|
|
|
"sqrt((log_delta_p_rmse_decade^2 + log_derivative_rmse_decade^2) / 2)",
|
|
|
|
|
allocator).Move(), allocator);
|
|
|
|
|
root.AddMember("curve_metrics", curveMetrics, allocator);
|
|
|
|
|
|
|
|
|
|
rapidjson::Value parameters(rapidjson::kArrayType);
|
|
|
|
|
for(int i = 0; i < m_lastRunResult.parameters.size(); ++i) {
|
|
|
|
|
const AutoFitParameterResult& parameter = m_lastRunResult.parameters[i];
|
|
|
|
|
rapidjson::Value item(rapidjson::kObjectType);
|
|
|
|
|
item.AddMember("name", autoFitJsonString(
|
|
|
|
|
parameter.name, allocator).Move(), allocator);
|
|
|
|
|
item.AddMember("unit", autoFitJsonString(
|
|
|
|
|
parameter.unit, allocator).Move(), allocator);
|
|
|
|
|
item.AddMember("initial_value", autoFitJsonNumber(
|
|
|
|
|
parameter.initialValue).Move(), allocator);
|
|
|
|
|
item.AddMember("lower_bound", autoFitJsonNumber(
|
|
|
|
|
parameter.lowerBound).Move(), allocator);
|
|
|
|
|
item.AddMember("upper_bound", autoFitJsonNumber(
|
|
|
|
|
parameter.upperBound).Move(), allocator);
|
|
|
|
|
item.AddMember("final_value", autoFitJsonNumber(
|
|
|
|
|
parameter.finalValue).Move(), allocator);
|
|
|
|
|
parameters.PushBack(item, allocator);
|
|
|
|
|
}
|
|
|
|
|
root.AddMember("parameters", parameters, allocator);
|
|
|
|
|
|
|
|
|
|
rapidjson::Value evidence(rapidjson::kObjectType);
|
|
|
|
|
evidence.AddMember("result_json", autoFitJsonString(
|
|
|
|
|
m_lastRunResult.resultJsonPath, allocator).Move(), allocator);
|
|
|
|
|
evidence.AddMember("curve_csv", autoFitJsonString(
|
|
|
|
|
m_lastRunResult.curveCsvPath, allocator).Move(), allocator);
|
|
|
|
|
evidence.AddMember("runs_csv", autoFitJsonString(
|
|
|
|
|
m_lastRunResult.runsCsvPath, allocator).Move(), allocator);
|
|
|
|
|
evidence.AddMember("trace_csv", autoFitJsonString(
|
|
|
|
|
m_lastRunResult.traceCsvPath, allocator).Move(), allocator);
|
|
|
|
|
evidence.AddMember("trace_meta_json", autoFitJsonString(
|
|
|
|
|
m_lastRunResult.traceMetaJsonPath, allocator).Move(), allocator);
|
|
|
|
|
evidence.AddMember("full_field_pressure", autoFitJsonString(
|
|
|
|
|
m_lastRunResult.fullFieldPressurePath, allocator).Move(), allocator);
|
|
|
|
|
evidence.AddMember("artifact_error", autoFitJsonString(
|
|
|
|
|
m_lastRunResult.artifactError, allocator).Move(), allocator);
|
|
|
|
|
root.AddMember("evidence", evidence, allocator);
|
|
|
|
|
|
|
|
|
|
rapidjson::Value frozenCurve(rapidjson::kObjectType);
|
|
|
|
|
frozenCurve.AddMember("time_hr", autoFitJsonDoubleArray(
|
|
|
|
|
m_frozenTargetPressureData.size() > 0
|
|
|
|
|
? m_frozenTargetPressureData[0]
|
|
|
|
|
: QVector<double>(), allocator).Move(), allocator);
|
|
|
|
|
frozenCurve.AddMember("pressure_mpa", autoFitJsonDoubleArray(
|
|
|
|
|
m_frozenTargetPressureData.size() > 1
|
|
|
|
|
? m_frozenTargetPressureData[1]
|
|
|
|
|
: QVector<double>(), allocator).Move(), allocator);
|
|
|
|
|
root.AddMember("frozen_target_pressure", frozenCurve, allocator);
|
|
|
|
|
|
|
|
|
|
// 保留最优参数真实评价返回的原始压力点,80 点曲线只用于统一误差复核,
|
|
|
|
|
// 不能替代求解器原始采样结果。
|
|
|
|
|
rapidjson::Value globalBestCurve(rapidjson::kObjectType);
|
|
|
|
|
globalBestCurve.AddMember("time_hr", autoFitJsonDoubleArray(
|
|
|
|
|
m_globalBestPressureData.size() > 0
|
|
|
|
|
? m_globalBestPressureData[0]
|
|
|
|
|
: QVector<double>(), allocator).Move(), allocator);
|
|
|
|
|
globalBestCurve.AddMember("pressure_mpa", autoFitJsonDoubleArray(
|
|
|
|
|
m_globalBestPressureData.size() > 1
|
|
|
|
|
? m_globalBestPressureData[1]
|
|
|
|
|
: QVector<double>(), allocator).Move(), allocator);
|
|
|
|
|
root.AddMember("global_best_pressure", globalBestCurve, allocator);
|
|
|
|
|
|
|
|
|
|
rapidjson::StringBuffer buffer;
|
|
|
|
|
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
|
|
|
|
|
root.Accept(writer);
|
|
|
|
|
|
|
|
|
|
QFile file(m_lastRunResult.resultJsonPath);
|
|
|
|
|
if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
const qint64 size = static_cast<qint64>(buffer.GetSize());
|
|
|
|
|
const qint64 written = file.write(buffer.GetString(), size);
|
|
|
|
|
file.close();
|
|
|
|
|
return written == size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::appendRunSummaryCsv()
|
|
|
|
|
{
|
|
|
|
|
static QMutex s_runsCsvMutex;
|
|
|
|
|
QMutexLocker locker(&s_runsCsvMutex);
|
|
|
|
|
QDir rootDir = QFileInfo(m_lastRunResult.runsCsvPath).absoluteDir();
|
|
|
|
|
if(!rootDir.exists() && !QDir().mkpath(rootDir.absolutePath())) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QFile file(m_lastRunResult.runsCsvPath);
|
|
|
|
|
const bool writeHeader = !file.exists() || file.size() == 0;
|
|
|
|
|
if(!file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
QTextStream stream(&file);
|
|
|
|
|
stream.setCodec("UTF-8");
|
|
|
|
|
stream.setGenerateByteOrderMark(writeHeader);
|
|
|
|
|
|
|
|
|
|
const QStringList parameterNames = traceParameterNames();
|
|
|
|
|
if(writeHeader) {
|
|
|
|
|
QStringList header;
|
|
|
|
|
header << "run_id" << "status" << "stop_reason"
|
|
|
|
|
<< "started_at" << "finished_at" << "target_well"
|
|
|
|
|
<< "phase" << "algorithm" << "solver_type"
|
|
|
|
|
<< "openmp_threads" << "ilu_reuse_steps"
|
|
|
|
|
<< "optimization_wall_time_ms" << "workflow_wall_time_ms"
|
|
|
|
|
<< "solver_time_sum_ms" << "final_solver_time_ms"
|
|
|
|
|
<< "iterations" << "parameter_evaluations"
|
|
|
|
|
<< "model_solver_calls" << "final_solver_calls"
|
|
|
|
|
<< "solver_successes" << "solver_failures" << "solver_timeouts"
|
|
|
|
|
<< "final_solver_status" << "optimization_pebi_count"
|
|
|
|
|
<< "final_pebi_count" << "pebi_count" << "initial_internal_error"
|
|
|
|
|
<< "initial_pressure_mpa" << "final_internal_error" << "curve_metrics_valid"
|
|
|
|
|
<< "curve_invalid_reason" << "coverage"
|
|
|
|
|
<< "pressure_rmse_mpa" << "pressure_max_abs_error_mpa"
|
|
|
|
|
<< "log_delta_p_rmse_decade"
|
|
|
|
|
<< "log_derivative_rmse_decade" << "unified_curve_error"
|
|
|
|
|
<< "curve_passed" << "target_curve_sha256";
|
|
|
|
|
for(int i = 0; i < parameterNames.size(); ++i) {
|
|
|
|
|
header << QString("final_%1").arg(parameterNames[i]);
|
|
|
|
|
}
|
|
|
|
|
header << "result_json_path" << "curve_csv_path"
|
|
|
|
|
<< "trace_csv_path" << "trace_meta_json_path" << "project_path"
|
|
|
|
|
<< "artifact_error";
|
|
|
|
|
stream << header.join(QString(",")) << "\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QMap<QString, double> finalParameters;
|
|
|
|
|
for(int i = 0; i < m_lastRunResult.parameters.size(); ++i) {
|
|
|
|
|
finalParameters.insert(m_lastRunResult.parameters[i].name,
|
|
|
|
|
m_lastRunResult.parameters[i].finalValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const AutoFitCurveMetrics& metrics = m_lastRunResult.curveMetrics;
|
|
|
|
|
QStringList row;
|
|
|
|
|
row << autoFitCsvField(m_lastRunResult.runId)
|
|
|
|
|
<< autoFitCsvField(m_lastRunResult.status)
|
|
|
|
|
<< autoFitCsvField(m_lastRunResult.stopReason)
|
|
|
|
|
<< autoFitCsvField(autoFitDateTimeText(m_lastRunResult.startedAt))
|
|
|
|
|
<< autoFitCsvField(autoFitDateTimeText(m_lastRunResult.finishedAt))
|
|
|
|
|
<< autoFitCsvField(m_lastRunResult.targetWell)
|
|
|
|
|
<< autoFitCsvField(m_lastRunResult.phase)
|
|
|
|
|
<< autoFitCsvField(m_lastRunResult.algorithm)
|
|
|
|
|
<< autoFitCsvField(m_lastRunResult.solverType)
|
|
|
|
|
<< autoFitCsvInteger(m_lastRunResult.ompThreads)
|
|
|
|
|
<< autoFitCsvInteger(m_lastRunResult.iluReuseSteps)
|
|
|
|
|
<< autoFitCsvInteger(m_lastRunResult.optimizationWallTimeMs)
|
|
|
|
|
<< autoFitCsvInteger(m_lastRunResult.workflowWallTimeMs)
|
|
|
|
|
<< autoFitCsvInteger(m_lastRunResult.solverTimeSumMs)
|
|
|
|
|
<< autoFitCsvInteger(m_lastRunResult.finalSolverTimeMs)
|
|
|
|
|
<< QString::number(m_lastRunResult.iterationCount)
|
|
|
|
|
<< QString::number(m_lastRunResult.parameterEvaluationCount)
|
|
|
|
|
<< QString::number(m_lastRunResult.modelSolverCallCount)
|
|
|
|
|
<< QString::number(m_lastRunResult.finalSolverCallCount)
|
|
|
|
|
<< QString::number(m_lastRunResult.solverSuccessCount)
|
|
|
|
|
<< QString::number(m_lastRunResult.solverFailureCount)
|
|
|
|
|
<< QString::number(m_lastRunResult.solverTimeoutCount)
|
|
|
|
|
<< autoFitCsvField(m_lastRunResult.finalSolverStatus)
|
|
|
|
|
<< autoFitCsvInteger(m_lastRunResult.optimizationPebiCount)
|
|
|
|
|
<< autoFitCsvInteger(m_lastRunResult.finalPebiCount)
|
|
|
|
|
<< autoFitCsvInteger(m_lastRunResult.pebiCount)
|
|
|
|
|
<< autoFitCsvNumber(m_lastRunResult.initialInternalError)
|
|
|
|
|
<< autoFitCsvNumber(m_lastRunResult.initialPressureMpa)
|
|
|
|
|
<< autoFitCsvNumber(m_lastRunResult.finalInternalError)
|
|
|
|
|
<< (metrics.valid ? "1" : "0")
|
|
|
|
|
<< autoFitCsvField(metrics.invalidReason)
|
|
|
|
|
<< autoFitCsvNumber(metrics.coverage)
|
|
|
|
|
<< autoFitCsvNumber(metrics.pressureRmseMpa)
|
|
|
|
|
<< autoFitCsvNumber(metrics.pressureMaxAbsErrorMpa)
|
|
|
|
|
<< autoFitCsvNumber(metrics.logDeltaPRmseDecade)
|
|
|
|
|
<< autoFitCsvNumber(metrics.logDerivativeRmseDecade)
|
|
|
|
|
<< autoFitCsvNumber(metrics.unifiedCurveError)
|
|
|
|
|
<< (metrics.passed ? "1" : "0")
|
|
|
|
|
<< autoFitCsvField(m_lastRunResult.targetCurveSha256);
|
|
|
|
|
for(int i = 0; i < parameterNames.size(); ++i) {
|
|
|
|
|
row << (finalParameters.contains(parameterNames[i])
|
|
|
|
|
? autoFitCsvNumber(finalParameters.value(parameterNames[i]))
|
|
|
|
|
: QString());
|
|
|
|
|
}
|
|
|
|
|
row << autoFitCsvField(m_lastRunResult.resultJsonPath)
|
|
|
|
|
<< autoFitCsvField(m_lastRunResult.curveCsvPath)
|
|
|
|
|
<< autoFitCsvField(m_lastRunResult.traceCsvPath)
|
|
|
|
|
<< autoFitCsvField(m_lastRunResult.traceMetaJsonPath)
|
|
|
|
|
<< autoFitCsvField(m_lastRunResult.projectPath)
|
|
|
|
|
<< autoFitCsvField(m_lastRunResult.artifactError);
|
|
|
|
|
stream << row.join(QString(",")) << "\n";
|
|
|
|
|
stream.flush();
|
|
|
|
|
return stream.status() == QTextStream::Ok;
|
|
|
|
|
}
|