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/nmCalculationUtils.cpp

670 lines
21 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 "nmCalculationUtils.h"
#include <QFile>
#include <QFileInfo>
#include <QDir>
#include <QDebug>
#include <QCoreApplication>
#include <QDateTime>
#include <QMutex>
#include <QMutexLocker>
#include <QRegExp>
#include <fstream>
#include <float.h>
#include "pch.h"
#ifdef Q_OS_WIN
#include <windows.h>
#else
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#endif
namespace
{
// HX_NWTM.dll 的配置和结果查询接口使用进程级共享状态,所有入口共用此锁。
QMutex s_oHxNwtmDllMutex;
bool isKrigingCancellationRequested(const QAtomicInt* pCancelRequested)
{
return pCancelRequested != NULL &&
static_cast<int>(*pCancelRequested) != 0;
}
class nmKrigingMutexLocker
{
public:
nmKrigingMutexLocker()
: m_pMutex(NULL),
m_bLocked(false)
{
}
~nmKrigingMutexLocker()
{
if(m_bLocked && m_pMutex != NULL) {
m_pMutex->unlock();
}
}
bool lock(QMutex* pMutex, const QAtomicInt* pCancelRequested)
{
if(pMutex == NULL) {
return false;
}
if(pCancelRequested == NULL) {
pMutex->lock();
} else {
// 只改变等待方式,不改变 DLL 全局锁的保护范围和互斥语义。
while(!pMutex->tryLock(100)) {
if(isKrigingCancellationRequested(pCancelRequested)) {
return false;
}
}
}
m_pMutex = pMutex;
m_bLocked = true;
return true;
}
private:
QMutex* m_pMutex;
bool m_bLocked;
};
QString krigingText(const char* sourceText)
{
return QCoreApplication::translate("nmCalculationUtils", sourceText);
}
bool isFiniteValue(double value)
{
#ifdef _MSC_VER
return _finite(value) != 0;
#else
return std::isfinite(value);
#endif
}
// 从自动拟合运行目录名中解析进程 ID。
bool parseAutoFitRunDirectoryProcessId(const QString& directoryName,
qint64* processId)
{
// 名称为 <pid>-<yyyyMMdd_hhmmss_zzz>,极端重名时追加 -<counter>。
QRegExp runDirectoryPattern(
"^([0-9]+)-[0-9]{8}_[0-9]{6}_[0-9]{3}(-[0-9]+)?$");
if(!runDirectoryPattern.exactMatch(directoryName)) {
return false;
}
bool conversionSucceeded = false;
const qint64 parsedProcessId =
runDirectoryPattern.cap(1).toLongLong(&conversionSucceeded);
if(!conversionSucceeded || parsedProcessId <= 0) {
return false;
}
if(processId != NULL) {
*processId = parsedProcessId;
}
return true;
}
bool isAutoFitRunProcessActive(const QString& directoryName)
{
// 判断该运行目录所属的进程是否仍在运行。
qint64 processId = 0;
if(!parseAutoFitRunDirectoryProcessId(directoryName, &processId)) {
return false;
}
if(processId == QCoreApplication::applicationPid()) {
return true;
}
#ifdef Q_OS_WIN
if(static_cast<quint64>(processId) > 0xFFFFFFFFULL) {
return false;
}
HANDLE processHandle = OpenProcess(
SYNCHRONIZE, FALSE, static_cast<DWORD>(processId));
if(processHandle == NULL) {
// ERROR_INVALID_PARAMETER 表示 PID 已不存在;其他错误(例如拒绝访问)
// 无法证明进程已退出,保守地保留目录。
return GetLastError() != ERROR_INVALID_PARAMETER;
}
const DWORD waitResult = WaitForSingleObject(processHandle, 0);
CloseHandle(processHandle);
return waitResult == WAIT_TIMEOUT || waitResult == WAIT_FAILED;
#else
const int result = kill(static_cast<pid_t>(processId), 0);
return result == 0 || errno == EPERM;
#endif
}
bool isOwnedAutoFitTemporaryDirectory(const QString& directoryPath)
{
if(directoryPath.trimmed().isEmpty()) {
return false;
}
// 校验路径是否为本程序创建的自动拟合临时目录。
const QString rootPath = nmCalculationUtils::autoFitTemporaryRootPath();
const QFileInfo directoryInfo(QDir::cleanPath(
QFileInfo(directoryPath).absoluteFilePath()));
const QString parentPath = QDir::cleanPath(directoryInfo.absolutePath());
#ifdef Q_OS_WIN
const Qt::CaseSensitivity pathCaseSensitivity = Qt::CaseInsensitive;
#else
const Qt::CaseSensitivity pathCaseSensitivity = Qt::CaseSensitive;
#endif
if(parentPath.compare(rootPath, pathCaseSensitivity) != 0 ||
directoryInfo.isSymLink()) {
return false;
}
return parseAutoFitRunDirectoryProcessId(directoryInfo.fileName(), NULL);
}
bool removeDirectoryRecursively(const QString& directoryPath)
{
QDir directory(directoryPath);
if(!directory.exists()) {
return true;
}
const QFileInfoList entries = directory.entryInfoList(
QDir::NoDotAndDotDot | QDir::AllEntries | QDir::Hidden | QDir::System);
bool allRemoved = true;
for(int i = 0; i < entries.size(); ++i) {
const QFileInfo& entry = entries[i];
if(entry.isSymLink()) {
// 符号链接只删除链接本身。
if(!QFile::remove(entry.absoluteFilePath())) {
allRemoved = false;
}
continue;
}
if(entry.isDir()) {
if(!removeDirectoryRecursively(entry.absoluteFilePath())) {
allRemoved = false;
}
continue;
}
QFile file(entry.absoluteFilePath());
if(!file.permissions().testFlag(QFile::WriteUser)) {
file.setPermissions(file.permissions() | QFile::WriteUser);
}
if(!file.remove()) {
allRemoved = false;
}
}
return allRemoved && directory.rmdir(directory.absolutePath());
}
void setKrigingError(QString* errorMessage, const QString& message)
{
if(errorMessage != NULL) {
*errorMessage = message;
}
}
}
nmCalculationUtils::nmCalculationUtils() {
}
QString nmCalculationUtils::autoFitTemporaryRootPath()
{
// 返回系统临时目录下的自动拟合根目录。
return QDir::cleanPath(
QDir(QDir::tempPath()).absoluteFilePath("WTAI/AutoFit"));
}
QString nmCalculationUtils::createAutoFitTemporaryDirectory()
{
const QString rootPath = autoFitTemporaryRootPath();
if(!QDir().mkpath(rootPath)) {
qWarning() << "Cannot create auto-fit temporary root:" << rootPath;
return QString();
}
QDir rootDirectory(rootPath);
// 使用进程 ID 和时间戳创建本次拟合的独立目录。
const QString baseName = QString("%1-%2")
.arg(QString::number(QCoreApplication::applicationPid()))
.arg(QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss_zzz"));
for(int counter = 0; counter < 1000; ++counter) {
const QString directoryName = counter == 0
? baseName
: QString("%1-%2").arg(baseName).arg(counter);
if(rootDirectory.mkdir(directoryName)) {
return QDir::cleanPath(rootDirectory.absoluteFilePath(directoryName));
}
}
qWarning() << "Cannot create unique auto-fit temporary directory in:" << rootPath;
return QString();
}
bool nmCalculationUtils::removeAutoFitTemporaryDirectory(
const QString& directoryPath)
{
if(!isOwnedAutoFitTemporaryDirectory(directoryPath)) {
qWarning() << "Refusing to remove non auto-fit directory:" << directoryPath;
return false;
}
return removeDirectoryRecursively(QDir::cleanPath(directoryPath));
}
void nmCalculationUtils::cleanupStaleAutoFitTemporaryDirectories(
int maximumAgeHours)
{
// 清理超过保留时间且所属进程已经退出的残留目录。
if(maximumAgeHours <= 0) {
return;
}
const QString rootPath = autoFitTemporaryRootPath();
QDir rootDirectory(rootPath);
if(!rootDirectory.exists()) {
return;
}
const qint64 maximumAgeSeconds =
static_cast<qint64>(maximumAgeHours) * 60 * 60;
const QDateTime now = QDateTime::currentDateTime();
const QFileInfoList directories = rootDirectory.entryInfoList(
QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System);
for(int i = 0; i < directories.size(); ++i) {
const QFileInfo& directoryInfo = directories[i];
if(!isOwnedAutoFitTemporaryDirectory(directoryInfo.absoluteFilePath())) {
continue;
}
if(directoryInfo.lastModified().secsTo(now) < maximumAgeSeconds) {
continue;
}
if(isAutoFitRunProcessActive(directoryInfo.fileName())) {
continue;
}
if(!removeAutoFitTemporaryDirectory(directoryInfo.absoluteFilePath())) {
qWarning() << "Failed to remove stale auto-fit directory:"
<< directoryInfo.absoluteFilePath();
}
}
}
QMutex* nmCalculationUtils::getHxNwtmDllMutex()
{
return &s_oHxNwtmDllMutex;
}
void nmCalculationUtils::cleanupPebiGridDebugFiles()
{
const QDir workingDirectory(QDir::currentPath());
QStringList fileNames;
fileNames << "TRI_cell.csv" << "PEBI_cell.csv";
for(int i = 0; i < fileNames.size(); ++i) {
const QString filePath = workingDirectory.absoluteFilePath(fileNames[i]);
if(QFile::exists(filePath) && !QFile::remove(filePath)) {
qWarning() << "Failed to remove PEBI grid debug file:" << filePath;
}
}
}
double nmCalculationUtils::milliDarcyToDarcy(double dPermeabilityMilliDarcy)
{
// PEBI输入结构使用D数据层统一使用mD因此仅在求解器边界换算。
return dPermeabilityMilliDarcy / 1000.0;
}
bool nmCalculationUtils::copyFileToDir(QString filePath, QString destDir) {
// 如果文件不存在,则退出
if(!QFile::exists(filePath)) {
return false;
}
// 如果目标目录不存在,则创建
if(!QDir(destDir).exists()) {
if(!QDir().mkdir(destDir)) {
qWarning() << "cannot mkdir:" << destDir;
return false;
}
}
// 获取源文件的文件名
QString fileName = QFileInfo(filePath).fileName();
// 构造目标文件的完整路径
QString destinationFilePath = QDir(destDir).filePath(fileName);
// 如果目标文件已存在,则删除
if(QFile::exists(destinationFilePath)) {
QFile destinationFile(destinationFilePath);
if(!destinationFile.remove()) {
qWarning() << "cannot delete file:" << destinationFilePath;
return false;
}
}
// 拷贝文件
if(QFile::copy(filePath, destinationFilePath)) {
qDebug() << "file copy success:" << destinationFilePath;
return true;
} else {
qWarning() << "file copy failed:" << destinationFilePath;
return false;
}
return true;
}
bool nmCalculationUtils::createDirectoryIfNotExists(QString directoryPath) {
QDir dir(directoryPath);
// 检查目录是否存在
if(dir.exists()) {
// 目录已存在
return true;
}
// 尝试创建目录
if(dir.mkpath(directoryPath)) {
// 创建成功
return true;
}
// 创建失败
qDebug() << "创建目录失败,错误代码。";
return false;
}
bool nmCalculationUtils::copyFile(const std::string& sourcePath, const std::string& destinationPath) {
std::ifstream src(sourcePath, std::ios::binary); // 以二进制模式打开源文件
std::ofstream dst(destinationPath, std::ios::binary); // 以二进制模式打开目标文件
if(!src || !dst) {
std::cerr << "文件打开失败" << std::endl;
return false;
}
dst << src.rdbuf(); // 拷贝文件内容
return true;
}
bool nmCalculationUtils::removeDirectory(const QString &dirPath) {
QDir dir(dirPath);
if(!dir.exists()) {
qDebug() << "Directory does not exist.";
return true;
}
// 获取目录中的所有文件和目录
QFileInfoList fileList = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden);
foreach(QFileInfo fileInfo, fileList) {
if(fileInfo.isDir()) {
// 递归删除子目录
if(!removeDirectory(fileInfo.absoluteFilePath())) {
return false;
}
} else {
// 删除文件
if(!QFile::remove(fileInfo.absoluteFilePath())) {
qDebug() << "Failed to remove file:" << fileInfo.absoluteFilePath();
return false;
}
}
}
// 删除目录自身
if(!dir.rmdir(dirPath)) {
qDebug() << "Failed to remove directory:" << dirPath;
return false;
}
return true;
}
bool nmCalculationUtils::writeFile(const QStringList& content, const QString &filePath) {
QFile file(filePath);
//检查
if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qWarning("Cannot open file for writing: %s", qPrintable(file.errorString()));
return false;
}
QTextStream out(&file);
out << content.join("\n");
file.close();
return true;
}
QStringList nmCalculationUtils::readFile(const QString &filePath) {
QStringList content;
// 打开文件
QFile file(filePath);
if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug() << "Failed to open file:" << filePath;
return content;
}
// 使用QTextStream读取文件
QTextStream in(&file);
while(!in.atEnd()) {
// 读取一行
QString line = in.readLine();
// 将行内容放入QStringList
content.append(line);
}
// 关闭文件
file.close();
return content;
}
bool nmCalculationUtils::calculateKriging(
const QVector<QPointF>& targetPoints,
const QVector<QPointF>& measurementPoints,
const QVector<double>& measurementValues,
double nugget,
double sill,
double range,
int model,
const QString& licensePath,
QVector<double>& outputValues,
QString* errorMessage,
const QAtomicInt* pCancelRequested)
{
outputValues.clear();
setKrigingError(errorMessage, QString());
if(isKrigingCancellationRequested(pCancelRequested)) {
setKrigingError(errorMessage, krigingText("Kriging calculation was cancelled."));
return false;
}
if(targetPoints.isEmpty()) {
setKrigingError(errorMessage, krigingText("No interpolation points are available."));
return false;
}
if(measurementPoints.size() < 2) {
setKrigingError(errorMessage,
krigingText("At least two measurement points are required."));
return false;
}
if(measurementPoints.size() != measurementValues.size()) {
setKrigingError(errorMessage,
krigingText("Measurement point coordinates and values do not match."));
return false;
}
if(!isFiniteValue(nugget) || !isFiniteValue(sill) ||
!isFiniteValue(range) || nugget < 0.0 || sill <= 0.0 || range <= 0.0) {
setKrigingError(errorMessage, krigingText("Kriging parameters are invalid."));
return false;
}
if(model < 0 || model > 2) {
setKrigingError(errorMessage, krigingText("The Kriging model is invalid."));
return false;
}
for(int i = 0; i < targetPoints.size(); ++i) {
if((i % 256) == 0 &&
isKrigingCancellationRequested(pCancelRequested)) {
setKrigingError(errorMessage, krigingText("Kriging calculation was cancelled."));
return false;
}
if(!isFiniteValue(targetPoints[i].x()) || !isFiniteValue(targetPoints[i].y())) {
setKrigingError(errorMessage,
krigingText("An interpolation point contains an invalid coordinate."));
return false;
}
}
for(int i = 0; i < measurementPoints.size(); ++i) {
if(isKrigingCancellationRequested(pCancelRequested)) {
setKrigingError(errorMessage, krigingText("Kriging calculation was cancelled."));
return false;
}
if(!isFiniteValue(measurementPoints[i].x()) ||
!isFiniteValue(measurementPoints[i].y()) ||
!isFiniteValue(measurementValues[i])) {
setKrigingError(errorMessage,
krigingText("A measurement point contains invalid data."));
return false;
}
for(int j = 0; j < i; ++j) {
if(measurementPoints[i] == measurementPoints[j]) {
setKrigingError(errorMessage,
krigingText("Measurement point coordinates cannot be duplicated."));
return false;
}
}
}
if(licensePath.isEmpty() || !QFileInfo(licensePath).exists()) {
setKrigingError(errorMessage, krigingText("The solver license file was not found."));
return false;
}
// Kriging 与建网、模型求解来自同一个 DLL不能在不同线程中并发进入。
nmKrigingMutexLocker oDllLocker;
if(!oDllLocker.lock(getHxNwtmDllMutex(), pCancelRequested)) {
setKrigingError(errorMessage, krigingText("Kriging calculation was cancelled."));
return false;
}
HMODULE dll = LoadLibrary(L"HX_NWTM.dll");
if(dll == NULL) {
setKrigingError(errorMessage, krigingText("Failed to load HX_NWTM.dll."));
return false;
}
typedef void (*HX_NWTM_KRINGING_Func)(HX_KRING_OUTPUT&,
const HX_KRING_INPUT,
std::string);
HX_NWTM_KRINGING_Func krigingFunction =
(HX_NWTM_KRINGING_Func)GetProcAddress(dll, "HX_NWTM_KRINGING");
if(krigingFunction == NULL) {
FreeLibrary(dll);
setKrigingError(errorMessage,
krigingText("The Kriging interface was not found in HX_NWTM.dll."));
return false;
}
// 求解器p参数每行保存{x, y}并保持targetPoints的原始顺序.
dVec2 targetData;
targetData.reserve(targetPoints.size());
for(int i = 0; i < targetPoints.size(); ++i) {
dVec1 point(2, 0.0);
point[0] = targetPoints[i].x();
point[1] = targetPoints[i].y();
targetData.push_back(point);
}
// 求解器v参数每行保存{x, y, value},坐标和值按相同下标配对.
dVec2 measurementData;
measurementData.reserve(measurementPoints.size());
for(int i = 0; i < measurementPoints.size(); ++i) {
dVec1 point(3, 0.0);
point[0] = measurementPoints[i].x();
point[1] = measurementPoints[i].y();
point[2] = measurementValues[i];
measurementData.push_back(point);
}
bool calculationSucceeded = true;
QString calculationError;
// 局部作用域保证求解器输入、输出对象在FreeLibrary前析构.
{
HX_KRING_INPUT input(nugget, sill, range, model,
targetData, measurementData);
HX_KRING_OUTPUT output;
try {
krigingFunction(output, input, licensePath.toStdString());
} catch(const std::exception& exception) {
calculationSucceeded = false;
calculationError = krigingText("Kriging calculation failed: %1")
.arg(QString::fromLocal8Bit(exception.what()));
} catch(...) {
calculationSucceeded = false;
calculationError = krigingText("Kriging calculation failed.");
}
// DLL 无取消入口;调用期间收到停止请求时只丢弃返回值,不提交插值结果。
if(calculationSucceeded &&
isKrigingCancellationRequested(pCancelRequested)) {
calculationSucceeded = false;
calculationError = krigingText("Kriging calculation was cancelled.");
}
if(calculationSucceeded &&
output.v.size() != static_cast<size_t>(targetPoints.size())) {
calculationSucceeded = false;
calculationError = krigingText(
"The Kriging result count does not match the interpolation points.");
}
if(calculationSucceeded) {
outputValues.reserve(targetPoints.size());
for(size_t i = 0; i < output.v.size(); ++i) {
if((i % 256) == 0 &&
isKrigingCancellationRequested(pCancelRequested)) {
calculationSucceeded = false;
calculationError = krigingText("Kriging calculation was cancelled.");
break;
}
if(!isFiniteValue(output.v[i])) {
calculationSucceeded = false;
calculationError = krigingText(
"The Kriging result contains an invalid value.");
break;
}
outputValues.append(output.v[i]);
}
}
}
FreeLibrary(dll);
if(!calculationSucceeded) {
outputValues.clear();
setKrigingError(errorMessage, calculationError);
}
return calculationSucceeded;
}