You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmCalculation/nmCalculationAutoFitRunResu...

909 lines
33 KiB
C++

#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;
}