项目的实现类projectClass可以通过构造函数实现模型的深度拷贝(克隆),从而可以在内存中实现模型的复制、修改参数、并行计算。这个特点对于将SWMM模型纳入各种优化算法的计算过程十分有意义。
1 案例项目内容
本专题对应的开发案例为\software\tutorial\exp_swmmcpp_clone文件夹中的内容,其中SWMMCPP_clone子文件夹为VS2022 C++项目内容, swmm_network子文件夹为管网模型数据,即network.inp文件。建议按照本文内容自己新建项目,编写代码以便通过练习掌握"武理排水管网模拟分析系统"相关使用方法。按照专题1内容在\software\tutorial\exp_swmmcpp_clone文件夹中新建项目,并添加头文件及库文件。对于新建项目的:配置属性->常规->输出目录设置为:
"$(SolutionDir)$(Platform)\$(Configuration)\",
这样可执行文件会生成于\software\tutorial\x64\Debug文件夹下面,将\software\bin文件夹中内容全部拷贝至Debug文件夹,可以直接编译运行案例项目,方便统一进行调试运行。
2 路径处理相关函数
在main.cpp文件的最上面中添加路径处理相关函数,由于本案例会采用接口的实现类projectClass,所以需要添加projectClass.h
c
#include <windows.h>
#include <filesystem>
#include <shlwapi.h>
#include <thread>
// TODO: 在此处引用程序需要的其他头文件
#include "swmm_cs.h"
#include "projectClass.h"
#pragma comment(lib, "Shlwapi.lib")
std::string GetExecutablePath()
{
std::vector<char> buffer(MAX_PATH);
DWORD len = GetModuleFileNameA(NULL, buffer.data(), MAX_PATH);
if (len == 0 || len == MAX_PATH) {
// 缓冲区不够,动态扩容
while (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
buffer.resize(buffer.size() * 2);
len = GetModuleFileNameA(NULL, buffer.data(), buffer.size());
}
}
return std::string(buffer.data(), len);
}
// 获取 exe 所在目录
std::string GetExecutableDirectory()
{
std::string exePath = GetExecutablePath();
char dir[MAX_PATH];
lstrcpyA(dir, exePath.c_str());
PathRemoveFileSpecA(dir); // 去掉文件名,留下目录
return std::string(dir);
}
3 projectClass实现模型克隆
本案例利用SwmmProjectClone函数实现projectClass的深度拷贝,并利用多线程对8个swmm模型进行同时计算,可以实现多个线程同时计算多个模型,可以使用projectClass相应的参数修改功能,实现不同模型参数设置情况下的多个模型的同时计算,本案例没有修改模型参数,仅仅实现了8个相同的模型同时计算。
c
int SWMM_clone(std::string inpFilePath, std::string rptFilePath, std::string outFilePath)
{
int i = 0;
int ErrorCode = 0;
int Nthread = 8;
//线程初始化
std::vector<std::thread> trds;
trds.reserve(Nthread);
std::vector<projectClass*> pswmmMP = std::vector<projectClass*>(Nthread, nullptr);
//std::thread** trds = new std::thread * [Nthread - 1];//这是新建线程,另外一个线程是主线程不需要新建
//SWMMCPP::projectClass** pswmmMP = new SWMMCPP::projectClass * [Nthread];
//初始化第一个元素,也可以使用 pswmmMP[0] = new projectClass();进行初始化
if (GetSwmmSimulation((void**)&pswmmMP[0]) != 0)
{
printf("create projectClass instance failed!");
return 1;
}
//对第一个pswmmMP数组元素进行赋值
ErrorCode = pswmmMP[0]->swmm_open((char*)inpFilePath.data(),
(char*)rptFilePath.data(), (char*)outFilePath.data());
if (ErrorCode != 0)
{
ReleaseSwmmSimulation((void*&)pswmmMP[0]);
printf("swmm_open failed");
return 1;
}
//利用第一个元素初始化后面的元素
for (i = 1; i < Nthread; ++i)
{
//对report,out文件进行重命名
int strLen, insertPos;
std::string rptFileName = pswmmMP[0]->Frpt.m_name;
std::string outFileName = pswmmMP[0]->Fout.m_name;
strLen = rptFileName.length();
insertPos = strLen - 4;
rptFileName.insert(insertPos, std::to_string(i));
strLen = outFileName.length();
insertPos = strLen - 4;
outFileName.insert(insertPos, std::to_string(i));
//通过深度拷贝为pswmmMP中的其他模型进行初始化
if (SwmmProjectClone(pswmmMP[0], (void**)&pswmmMP[i], rptFileName.data(), outFileName.data()) == 0)
printf("the %dth swmm project have been cloned successfully\n", i);
}
//对模型的参数进行更新后,可以对pswmmMP中的模型进行多线程,并行模拟,这里没有修改任何参数,只演示如何并行计算
//新建线程计算
for (i = 0; i < Nthread; ++i)
{
//新建线程,并启动计算,由于新建线程中调用的是projectClass类中的函数(第一个参数),
//所以这里还需要传递一个类对象,因为类中的函数存在一个默认参数,就是对象本身this,
//所以这里要将对象传递进去(第二个参数)
//swmm_run2函数不会向硬盘写入结果
trds.emplace_back(&SWMMCPP::projectClass::swmm_run2, pswmmMP[i]);
printf("the %dth swmm project have started computation successfully\n", i);
}
printf("%d swmm projects are computing,wait please...\n", Nthread);
// 等待所有线程结束
for (auto& t : trds)
{
if (t.joinable())
{
t.join();
}
}
//如果需要将计算结果写入硬盘需要调用end函数
for (i = 0; i < Nthread; ++i)
{
pswmmMP[i]->swmm_end();
pswmmMP[i]->swmm_close();
ReleaseSwmmSimulation((void*&)pswmmMP[i]);
printf("%d swmm project have released successfully\n", i);
}
return 0;
}
4 编译运行
编译后运行结果如下
