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++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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