#include "nmCalculationAutoFitPSO.h" #include "nmDataAnalyzeManager.h" #include "nmDataReservoir.h" #include "nmDataWellBase.h" #include "iBase/iUtils/ZxBaseUtil.h" #include #include #include #include #include #include #include #include #include #include "rapidjson/document.h" #include "rapidjson/prettywriter.h" #include "rapidjson/stringbuffer.h" #ifdef Q_OS_WIN #ifndef NOMINMAX #define NOMINMAX #endif #include #include #include #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(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(content.constData()), static_cast(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(hashBytes), static_cast(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& 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(RUN_TIME_LIMIT_MS) - m_workflowWallTimer.elapsed(); return remaining > 0 ? static_cast(qMin(remaining, static_cast(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::quiet_NaN(); m_lastRunResult.finalInternalError = m_globalBestFitness < 1.0e9 ? m_globalBestFitness : std::numeric_limits::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 >& 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::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::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::quiet_NaN()) << ',' << autoFitCsvNumber(i < metrics.fittedDerivativeMpa.size() ? metrics.fittedDerivativeMpa[i] : std::numeric_limits::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(), allocator).Move(), allocator); frozenCurve.AddMember("pressure_mpa", autoFitJsonDoubleArray( m_frozenTargetPressureData.size() > 1 ? m_frozenTargetPressureData[1] : QVector(), 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(), allocator).Move(), allocator); globalBestCurve.AddMember("pressure_mpa", autoFitJsonDoubleArray( m_globalBestPressureData.size() > 1 ? m_globalBestPressureData[1] : QVector(), allocator).Move(), allocator); root.AddMember("global_best_pressure", globalBestCurve, allocator); rapidjson::StringBuffer buffer; rapidjson::PrettyWriter writer(buffer); root.Accept(writer); QFile file(m_lastRunResult.resultJsonPath); if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return false; } const qint64 size = static_cast(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 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; }