EPANET 学习手册:面向 Java 工程师的入门指南
EPANET 学习手册:面向 Java 工程师的入门指南
目录
1. EPANET 概述
1.1 什么是 EPANET
EPANET(Environmental Protection Agency NETwork)是由美国环保署(EPA)开发的开源供水管网水力与水质分析工具包。它用于模拟压力管道网络中的水流分布、压力变化以及化学物质在管网中的传输与反应过程。对于 Java 工程师而言,可以把 EPANET 理解为一个用 C 语言编写的水力计算引擎,它暴露了一套 C API(Toolkit),允许外部程序加载管网模型、执行仿真并提取结果。
1.2 核心能力

| 能力 | 说明 |
|---|---|
| 水力分析 | 计算每个管段的流量、流速,每个节点的压力与水头 |
| 扩展时段模拟 | 模拟数小时至数天的系统动态行为 |
| 水质分析 | 追踪化学物质浓度、水龄、水源溯源 |
| 能耗计算 | 计算泵站能耗与运行成本 |
| 控制规则 | 支持基于时间或条件的管网设备自动控制 |
1.3 架构视角
从软件架构角度看,EPANET 采用分层设计:
- 输入层:
.inp文本文件定义网络拓扑与参数 - 计算引擎:C 语言核心求解水力与水质方程
- 输出层:
.out二进制文件或.rpt文本报告存储结果 - Toolkit API:C 函数库供外部程序调用
2. 核心对象模型
EPANET 将供水管网抽象为**节点(Nodes)和连接(Links)**构成的有向图。理解这些对象及其属性是建模的基础。
2.1 物理对象
2.1.1 节点(Nodes)
Junction(连接点)
连接点是管网中最常见的节点类型,表示管道交汇点或用户取水点。
| 属性类型 | 属性名 | 说明 | 单位 |
|---|---|---|---|
| 输入 | Elevation | 地面高程 | 米/英尺 |
| 输入 | Base Demand | 基础需水量 | 流量单位 |
| 输入 | Demand Pattern | 需求时间模式 | - |
| 输入 | Emitter Coefficient | 喷射器/漏损系数 | - |
| 输出 | Hydraulic Head | 总水头 | 米/英尺 |
| 输出 | Pressure | 压力 | 米/英尺 |
| 输出 | Actual Demand | 实际需水量 | 流量单位 |
从面向对象角度理解,Junction 是一个具有流量边界条件的节点。当需求为正值时表示取水,负值表示水源注入。
Reservoir(水库)
水库代表管网的外部水源或汇点,其水位不受管网内部状态影响。
| 属性类型 | 属性名 | 说明 |
|---|---|---|
| 输入 | Total Head | 总水头(固定边界条件) |
| 输入 | Head Pattern | 水头随时间变化的模式 |
| 输入 | Initial Quality | 初始水质 |
水库在数学上是一个固定水头边界条件(Fixed Grade Node),其水头值由用户指定或通过时间模式变化。
Tank(水池/水塔)
水池是具有储水能力的节点,其水位在仿真过程中随时间变化。
| 属性类型 | 属性名 | 说明 |
|---|---|---|
| 输入 | Bottom Elevation | 池底高程 |
| 输入 | Initial Level | 初始水位 |
| 输入 | Minimum Level | 最低允许水位 |
| 输入 | Maximum Level | 最高允许水位 |
| 输入 | Diameter | 直径(圆柱形)或容积曲线 |
| 输出 | Hydraulic Head | 当前水头(高程 + 水位) |
| 输出 | Water Quality | 水质 |
水池是可变水头边界条件,EPANET 会在仿真中动态追踪其水位变化。当水位达到最小值时停止出水,达到最大值时停止进水。
2.1.2 连接(Links)
Pipe(管段)
管段是连接两个节点的输水通道。
| 属性类型 | 属性名 | 说明 | 单位 |
|---|---|---|---|
| 输入 | Start Node | 起始节点 | - |
| 输入 | End Node | 终止节点 | - |
| 输入 | Length | 长度 | 米/英尺 |
| 输入 | Diameter | 直径 | 毫米/英寸 |
| 输入 | Roughness | 粗糙系数 | 视公式而定 |
| 输入 | Status | 状态(开启/关闭/单向阀) | - |
| 输入 | Minor Loss Coefficient | 局部水头损失系数 | - |
| 输出 | Flow Rate | 流量 | 流量单位 |
| 输出 | Velocity | 流速 | 长度/时间 |
| 输出 | Headloss | 水头损失 | 长度 |
水头损失公式
EPANET 支持三种水头损失计算公式:
| 公式 | 适用场景 | 粗糙系数 |
|---|---|---|
| Hazen-Williams | 美国最常用的湍流公式,仅适用于水 | C(无量纲) |
| Darcy-Weisbach | 理论上最准确,适用于所有流态和液体 | ε(绝对粗糙度,mm/ft) |
| Chezy-Manning | 常用于明渠流 | n(无量纲) |
水头损失的通用形式:
hL = A * q^B
其中 hL 为水头损失,q 为流量,A 为阻力系数,B 为流量指数。
Pump(水泵)
水泵是向流体施加能量、提升水头的连接对象。
| 属性类型 | 属性名 | 说明 |
|---|---|---|
| 输入 | Start/End Node | 起止节点(水流单向) |
| 输入 | Pump Curve | 水泵特性曲线 |
| 输入 | Power | 额定功率(替代曲线时使用) |
| 输入 | Speed Pattern | 转速时间模式 |
| 输出 | Flow Rate | 流量 |
| 输出 | Head Gain | 扬程 |
水泵特性曲线描述流量与扬程的关系,通常表示为:
h = h0 - r * (q/q0)^n
其中 h0 为关死点扬程,r 和 n 为曲线系数。
Valve(阀门)
EPANET 支持六种阀门类型:
| 阀门类型 | 英文缩写 | 功能 | 设置参数 |
|---|---|---|---|
| 减压阀 | PRV | 限制下游压力 | 目标压力 |
| 稳压阀 | PSV | 维持上游压力 | 目标压力 |
| 压力断路阀 | PBV | 产生指定压降 | 压降值 |
| 流量控制阀 | FCV | 限制流量 | 目标流量 |
| 节流控制阀 | TCV | 模拟局部关闭 | 损失系数 |
| 通用阀门 | GPV | 自定义流态关系 | 水头损失曲线 |
阀门建模的约束规则:
- PRV、PSV、FCV 不能直接连接水库或水池
- 两个 PRV 不能共享同一个下游节点或串联
- 两个 PSV 不能共享同一个上游节点或串联
2.2 非物理对象
2.2.1 Curve(曲线)
曲线定义了设备或系统的非线性特性关系:
| 曲线类型 | 用途 | X轴 | Y轴 |
|---|---|---|---|
| Pump Curve | 水泵特性 | 流量 | 扬程 |
| Efficiency Curve | 水泵效率 | 流量 | 效率(%) |
| Volume Curve | 非圆柱水池容积 | 水位 | 容积 |
| Headloss Curve | GPV 水头损失 | 流量 | 水头损失 |
2.2.2 Pattern(时间模式)
时间模式是一组乘数因子,用于表示物理量随时间的变化规律。
实际值 = 基础值 × 模式乘数
常见应用场景:
- 需水量日变化模式
- 水库水位季节变化模式
- 水泵运行调度模式
- 电价分时模式
2.2.3 Control(控制规则)
控制规则定义了设备在仿真过程中的自动操作逻辑。
Simple Control(简单控制)
基于时间或单个条件的控制:
LINK pump1 OPEN AT TIME 8:00
LINK valve1 CLOSE IF NODE tank1 BELOW 5.0
Rule-Based Control(规则控制)
支持多条件组合与优先级:
RULE 1
IF NODE tank1 LEVEL > 10.0
AND SYSTEM CLOCKTIME > 6:00
THEN LINK pump1 STATUS = OPEN
AND LINK pump2 STATUS = CLOSE
PRIORITY 5
2.3 对象关系图
Network
├── Nodes
│ ├── Junctions (需水量, 高程)
│ ├── Reservoirs (固定水头)
│ └── Tanks (可变水头, 容积)
├── Links
│ ├── Pipes (长度, 直径, 粗糙度)
│ ├── Pumps (特性曲线, 转速)
│ └── Valves (类型, 设定值)
├── Curves
│ ├── Pump Curves
│ ├── Efficiency Curves
│ └── Volume Curves
├── Patterns (时间乘数序列)
└── Controls (操作规则)
3. 建模流程
3.1 建模步骤
建立 EPANET 模型的标准流程:
步骤 1:定义网络拓扑
- 绘制或定义所有节点(Junction、Reservoir、Tank)
- 绘制或定义所有连接(Pipe、Pump、Valve)
- 确保网络连通性,每个节点至少连接一条管段
步骤 2:设置节点属性
- 为所有节点指定高程
- 为 Junction 指定基础需水量
- 为 Reservoir 指定总水头
- 为 Tank 指定尺寸和运行水位范围
步骤 3:设置连接属性
- 为所有 Pipe 指定长度、直径和粗糙系数
- 为 Pump 指定特性曲线或功率
- 为 Valve 指定类型和设定值
步骤 4:定义时间模式
- 创建需水量变化模式
- 创建水库水位变化模式(如需要)
- 创建水泵运行模式(如需要)
步骤 5:设置控制规则
- 定义水泵启停逻辑
- 定义阀门操作逻辑
步骤 6:配置分析选项
- 选择水头损失公式
- 设置流量单位
- 设置时间参数(总时长、水力步长、水质步长)
- 设置收敛准则
3.2 INP 文件结构
EPANET 使用 .inp 文本文件存储模型数据。对于 Java 工程师,理解其结构有助于程序化生成和解析模型。
[TITLE]
示例管网模型
[JUNCTIONS]
;ID Elevation Demand
J1 100.0 50.0
J2 95.0 30.0
[RESERVOIRS]
;ID Head
R1 120.0
[TANKS]
;ID Elevation InitLevel MinLevel MaxLevel Diameter
T1 110.0 10.0 5.0 15.0 20.0
[PIPES]
;ID Node1 Node2 Length Diameter Roughness
P1 R1 J1 1000 300 100
P2 J1 J2 500 200 100
[PUMPS]
;ID Node1 Node2 Parameters
PU1 T1 J1 HEAD PU1_CURVE
[CURVES]
;ID X-Value Y-Value
PU1_CURVE 0 50
PU1_CURVE 100 45
PU1_CURVE 200 35
[PATTERNS]
;ID Multipliers
DEMAND_PAT 1.0 1.2 1.5 1.3 1.0 0.8
[CONTROLS]
LINK PU1 OPEN IF NODE T1 LEVEL BELOW 6.0
LINK PU1 CLOSE IF NODE T1 LEVEL ABOVE 14.0
[OPTIONS]
UNITS LPS
HEADLOSS H-W
QUALITY NONE
[TIMES]
DURATION 24:00
HYDRAULIC TIMESTEP 1:00
QUALITY TIMESTEP 0:05
PATTERN TIMESTEP 1:00
REPORT TIMESTEP 1:00
[END]
3.3 单位系统
EPANET 的单位由流量单位决定:
| 流量单位 | 系统 | 长度 | 压力 | 管径 |
|---|---|---|---|---|
| LPS(升/秒) | 公制 | 米 | 米 | 毫米 |
| GPM(加仑/分) | 美制 | 英尺 | psi | 英寸 |
4. 水力仿真原理
4.1 数学基础
EPANET 水力仿真的核心是求解管网中的质量守恒方程和能量守恒方程。
节点质量守恒
对于任意节点 i:
Σ qij - Di = 0
其中 qij 为连接节点 i 和 j 的管段流量(流入为正),Di 为节点 i 的需水量。
管段能量守恒
对于任意管段连接节点 i 和 j:
hi - hj = hL(qij)
其中 hi 和 hj 为节点水头,hL 为水头损失函数。
4.2 全局梯度算法(GGA)
EPANET 采用 Todini 和 Pilati 提出的**全局梯度算法(Global Gradient Algorithm, GGA)**求解上述非线性方程组。
算法核心是一个两步迭代过程:
步骤 1:求解节点水头
构建并求解线性方程组:
A * h = F
其中:
A是 N×N 对称稀疏系数矩阵h是节点水头向量F是右端项向量
矩阵元素:
Aii = Σ(1/gij) // 对角元素,gij 为水头损失梯度
Aij = -1/gij // 非零非对角元素
步骤 2:更新管段流量
qij_new = qij - (hLij - hi + hj) / gij
迭代持续进行,直到满足收敛条件:
- 流量变化率之和除以总流量小于精度阈值(默认 0.001)
- 或水头误差小于阈值
- 或最大流量变化小于阈值
4.3 线性方程求解
EPANET 使用 Cholesky 分解求解对称正定线性方程组:
A = L * L^T
求解过程:
L * y = F // 前向代入
L^T * h = y // 回代
为提高效率,EPANET 在首次分解前使用 Multiple Minimum-Degree 算法对矩阵进行重排序,减少填充元(fill-in)。
4.4 压力驱动需求(PDA)
传统模型假设需求始终被满足(DDA)。EPANET 2.2 引入了压力驱动分析(PDA),认为实际需水量与节点压力相关:
qD = Di * ((p - P0) / (Pf - P0))^e 当 P0 < p < Pf
qD = Di 当 p >= Pf
qD = 0 当 p <= P0
其中:
Di为完整需求p为当前压力Pf为满足完整需求所需压力P0为需求为零的压力e为压力指数(通常取 0.5)
5. 水质仿真原理
5.1 水质分析类型
EPANET 支持三种水质分析:
| 分析类型 | 说明 | 应用场景 |
|---|---|---|
| 化学物质浓度 | 追踪化学物质的传输与反应 | 余氯衰减、污染物扩散 |
| 水龄 | 计算水在管网中的停留时间 | 水质新鲜度评估 |
| 水源溯源 | 追踪特定水源的供水比例 | 多水源系统分析 |
5.2 水质传输模型
EPANET 采用拉格朗日时间驱动法模拟水质传输:
管道内传输
将管道中的水分割为若干"水体段(segments)",每个水体段具有均匀的水质浓度。水流推动水体段在管道中移动,当水体段到达节点时发生混合。
节点混合
假设节点处完全混合,混合后浓度为:
C_mix = Σ(qin * Cin) / Σ(qin)
管道内反应
水体段在管道传输过程中发生反应,浓度变化遵循:
dC/dt = R(C)
反应类型包括:
| 反应类型 | 速率方程 | 说明 |
|---|---|---|
| 本体反应(Bulk) | R = Kb * C^n | 发生在水体内部 |
| 管壁反应(Wall) | R = Kw * C^m | 发生在管壁表面 |
其中 Kb 和 Kw 为反应系数,正值表示增长,负值表示衰减。
5.3 水质时间步长
水质仿真的时间步长通常远小于水力步长(默认 5 分钟 vs 1 小时),因为水质变化的时间尺度更短。EPANET 自动在每个水力步长内细分水质步长。
6. EPANET 与 Java 集成
6.1 集成方案概述
EPANET 核心引擎是 C 语言编写的动态链接库(Windows 为 epanet2.dll,Linux 为 libepanet2.so)。Java 程序可以通过以下方式调用:
| 方案 | 技术 | 复杂度 | 推荐度 |
|---|---|---|---|
| JNA | Java Native Access | 低 | 推荐 |
| JNI | Java Native Interface | 高 | 不推荐 |
| 命令行 | 调用 epanet 可执行文件 | 低 | 简单场景 |
| 现有封装库 | 如 Baseform-Epanet-Java-Library | 低 | 推荐 |
6.2 使用 JNA 集成
JNA(Java Native Access)允许 Java 代码直接加载和调用本地库,无需编写 C 代码。
Maven 依赖
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.14.0</version>
</dependency>
定义 EPANET 接口
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.FloatByReference;
public interface Epanet2 extends Library {
Epanet2 INSTANCE = Native.load("epanet2", Epanet2.class);
// 项目生命周期
int ENopen(String inpFile, String rptFile, String outFile);
int ENclose();
int ENsaveinpfile(String filename);
// 水力分析
int ENsolveH();
int ENopenH();
int ENinitH(int saveFlag);
int ENrunH(IntByReference currentTime);
int ENnextH(IntByReference nextTime);
int ENcloseH();
// 水质分析
int ENsolveQ();
int ENopenQ();
int ENinitQ(int saveFlag);
int ENrunQ(IntByReference currentTime);
int ENnextQ(IntByReference nextTime);
int ENcloseQ();
// 节点操作
int ENgetnodeindex(String id, IntByReference index);
int ENgetnodeid(int index, byte[] id);
int ENgetnodetype(int index, IntByReference type);
int ENgetnodevalue(int index, int param, FloatByReference value);
int ENsetnodevalue(int index, int param, float value);
// 管段操作
int ENgetlinkindex(String id, IntByReference index);
int ENgetlinkid(int index, byte[] id);
int ENgetlinktype(int index, IntByReference type);
int ENgetlinknodes(int index, IntByReference fromNode, IntByReference toNode);
int ENgetlinkvalue(int index, int param, FloatByReference value);
int ENsetlinkvalue(int index, int param, float value);
}
常量定义
public class EpanetConstants {
// 节点类型
public static final int EN_JUNCTION = 0;
public static final int EN_RESERVOIR = 1;
public static final int EN_TANK = 2;
// 连接类型
public static final int EN_CVPIPE = 0;
public static final int EN_PIPE = 1;
public static final int EN_PUMP = 2;
public static final int EN_PRV = 3;
public static final int EN_PSV = 4;
public static final int EN_PBV = 5;
public static final int EN_FCV = 6;
public static final int EN_TCV = 7;
public static final int EN_GPV = 8;
// 节点参数
public static final int EN_ELEVATION = 0;
public static final int EN_BASEDEMAND = 1;
public static final int EN_PATTERN = 2;
public static final int EN_EMITTER = 3;
public static final int EN_INITQUAL = 4;
public static final int EN_SOURCEQUAL = 5;
public static final int EN_SOURCEPAT = 6;
public static final int EN_SOURCETYPE = 7;
public static final int EN_TANKLEVEL = 8;
public static final int EN_DEMAND = 9;
public static final int EN_HEAD = 10;
public static final int EN_PRESSURE = 11;
public static final int EN_QUALITY = 12;
public static final int EN_SOURCEMASS = 13;
// 连接参数
public static final int EN_DIAMETER = 0;
public static final int EN_LENGTH = 1;
public static final int EN_ROUGHNESS = 2;
public static final int EN_MINORLOSS = 3;
public static final int EN_INITSTATUS = 4;
public static final int EN_INITSETTING = 5;
public static final int EN_KBULK = 6;
public static final int EN_KWALL = 7;
public static final int EN_FLOW = 8;
public static final int EN_VELOCITY = 9;
public static final int EN_HEADLOSS = 10;
public static final int EN_STATUS = 11;
public static final int EN_SETTING = 12;
public static final int EN_ENERGY = 13;
// 时间参数
public static final int EN_DURATION = 0;
public static final int EN_HYDSTEP = 1;
public static final int EN_QUALSTEP = 2;
public static final int EN_PATTERNSTEP = 3;
public static final int EN_PATTERNSTART = 4;
public static final int EN_REPORTSTEP = 5;
public static final int EN_REPORTSTART = 6;
public static final int EN_RULESTEP = 7;
public static final int EN_STATISTIC = 8;
public static final int EN_PERIODS = 9;
}
6.3 完整 Java 调用示例
import com.sun.jna.ptr.FloatByReference;
import com.sun.jna.ptr.IntByReference;
public class EpanetExample {
public static void main(String[] args) {
String inpFile = "network.inp";
String rptFile = "report.txt";
String outFile = "output.out";
try {
// 1. 打开项目
int error = Epanet2.INSTANCE.ENopen(inpFile, rptFile, outFile);
checkError(error, "ENopen");
// 2. 运行完整水力分析
error = Epanet2.INSTANCE.ENsolveH();
checkError(error, "ENsolveH");
// 3. 运行完整水质分析
error = Epanet2.INSTANCE.ENsolveQ();
checkError(error, "ENsolveQ");
// 4. 提取结果
printNodeResults();
printLinkResults();
// 5. 关闭项目
Epanet2.INSTANCE.ENclose();
System.out.println("仿真完成!");
} catch (Exception e) {
e.printStackTrace();
}
}
private static void printNodeResults() {
IntByReference nodeCount = new IntByReference();
Epanet2.INSTANCE.ENgetcount(EpanetConstants.EN_NODECOUNT, nodeCount);
System.out.println("\n=== 节点结果 ===");
System.out.printf("%-10s %-15s %-15s %-15s%n", "节点ID", "水头(m)", "压力(m)", "水质");
for (int i = 1; i <= nodeCount.getValue(); i++) {
byte[] id = new byte[32];
Epanet2.INSTANCE.ENgetnodeid(i, id);
String nodeId = new String(id).trim();
FloatByReference head = new FloatByReference();
FloatByReference pressure = new FloatByReference();
FloatByReference quality = new FloatByReference();
Epanet2.INSTANCE.ENgetnodevalue(i, EpanetConstants.EN_HEAD, head);
Epanet2.INSTANCE.ENgetnodevalue(i, EpanetConstants.EN_PRESSURE, pressure);
Epanet2.INSTANCE.ENgetnodevalue(i, EpanetConstants.EN_QUALITY, quality);
System.out.printf("%-10s %-15.2f %-15.2f %-15.4f%n",
nodeId, head.getValue(), pressure.getValue(), quality.getValue());
}
}
private static void printLinkResults() {
IntByReference linkCount = new IntByReference();
Epanet2.INSTANCE.ENgetcount(EpanetConstants.EN_LINKCOUNT, linkCount);
System.out.println("\n=== 管段结果 ===");
System.out.printf("%-10s %-15s %-15s %-15s%n", "管段ID", "流量(L/s)", "流速(m/s)", "水头损失(m)");
for (int i = 1; i <= linkCount.getValue(); i++) {
byte[] id = new byte[32];
Epanet2.INSTANCE.ENgetlinkid(i, id);
String linkId = new String(id).trim();
FloatByReference flow = new FloatByReference();
FloatByReference velocity = new FloatByReference();
FloatByReference headloss = new FloatByReference();
Epanet2.INSTANCE.ENgetlinkvalue(i, EpanetConstants.EN_FLOW, flow);
Epanet2.INSTANCE.ENgetlinkvalue(i, EpanetConstants.EN_VELOCITY, velocity);
Epanet2.INSTANCE.ENgetlinkvalue(i, EpanetConstants.EN_HEADLOSS, headloss);
System.out.printf("%-10s %-15.2f %-15.2f %-15.4f%n",
linkId, flow.getValue(), velocity.getValue(), headloss.getValue());
}
}
private static void checkError(int errorCode, String functionName) {
if (errorCode != 0) {
throw new RuntimeException(
String.format("EPANET 函数 %s 返回错误码: %d", functionName, errorCode));
}
}
}
6.4 分步仿真控制
对于需要实时监控或干预的场景,可以使用分步仿真:
// 打开水力分析系统
Epanet2.INSTANCE.ENopenH();
// 初始化(参数1表示保存结果到水力文件)
Epanet2.INSTANCE.ENinitH(1);
IntByReference currentTime = new IntByReference();
IntByReference nextTime = new IntByReference();
// 逐步运行水力仿真
do {
int error = Epanet2.INSTANCE.ENrunH(currentTime);
checkError(error, "ENrunH");
// 在此可以读取当前时刻的结果
System.out.println("当前仿真时间: " + currentTime.getValue() + " 秒");
// 推进到下一个时间步
error = Epanet2.INSTANCE.ENnextH(nextTime);
checkError(error, "ENnextH");
} while (nextTime.getValue() > 0);
Epanet2.INSTANCE.ENcloseH();
6.5 动态修改模型
Toolkit API 支持在仿真前或分步仿真中修改模型参数:
// 修改节点需水量
int nodeIndex = ...; // 获取节点索引
Epanet2.INSTANCE.ENsetnodevalue(
nodeIndex,
EpanetConstants.EN_BASEDEMAND,
100.0f // 新的需水量
);
// 修改管段状态(开启/关闭)
int linkIndex = ...;
Epanet2.INSTANCE.ENsetlinkvalue(
linkIndex,
EpanetConstants.EN_STATUS,
0 // 0=关闭, 1=开启
);
// 修改水泵转速设置
Epanet2.INSTANCE.ENsetlinkvalue(
linkIndex,
EpanetConstants.EN_SETTING,
1.2f // 转速比(1.0=额定转速)
);
7. 完整建模示例
7.1 问题描述
构建一个简单的供水系统模型:
- 1 个水源水库(R1),水头 120m
- 1 个水泵(PU1)将水提升至管网
- 2 个连接点(J1、J2)代表用户
- 1 个水塔(T1)用于调节水量
7.2 INP 文件
[TITLE]
简单供水系统示例
[JUNCTIONS]
;ID Elevation BaseDemand
J1 100.0 50.0
J2 95.0 30.0
[RESERVOIRS]
;ID Head
R1 120.0
[TANKS]
;ID Elevation InitLevel MinLevel MaxLevel Diameter VolumeCurve
T1 110.0 10.0 5.0 15.0 20.0
[PIPES]
;ID Node1 Node2 Length Diameter Roughness MinorLoss Status
P1 J1 J2 500 200 100 0 Open
P2 T1 J1 300 250 100 0 Open
[PUMPS]
;ID Node1 Node2 Properties
PU1 R1 T1 HEAD PU1_CURVE
[CURVES]
;ID Flow Head
PU1_CURVE 0 50
PU1_CURVE 100 45
PU1_CURVE 200 35
PU1_CURVE 300 20
[PATTERNS]
;ID Multipliers
DEMAND_PAT 0.5 0.6 0.8 1.0 1.2 1.5 1.3 1.0
0.8 0.6 0.5 0.4 0.4 0.5 0.6 0.8
1.0 1.2 1.0 0.8 0.6 0.5 0.4 0.4
[CONTROLS]
LINK PU1 OPEN IF NODE T1 LEVEL BELOW 6.0
LINK PU1 CLOSE IF NODE T1 LEVEL ABOVE 14.0
[OPTIONS]
UNITS LPS
HEADLOSS H-W
SPECIFIC GRAVITY 1.0
VISCOSITY 1.0
TRIALS 40
ACCURACY 0.001
UNBALANCED CONTINUE 10
DEMAND MULTIPLIER 1.0
[TIMES]
DURATION 24:00
HYDRAULIC TIMESTEP 1:00
QUALITY TIMESTEP 0:05
PATTERN TIMESTEP 1:00
PATTERN START 0
REPORT TIMESTEP 1:00
REPORT START 0
[REPORT]
STATUS YES
SUMMARY YES
NODES ALL
LINKS ALL
[END]
7.3 Java 分析代码
public class SimpleNetworkAnalysis {
public static void main(String[] args) {
Epanet2 epanet = Epanet2.INSTANCE;
// 打开模型
int err = epanet.ENopen("simple.inp", "simple.rpt", "simple.out");
if (err != 0) {
System.err.println("打开模型失败,错误码: " + err);
return;
}
try {
// 运行完整分析
err = epanet.ENsolveH();
if (err != 0) throw new RuntimeException("水力分析失败: " + err);
err = epanet.ENsolveQ();
if (err != 0) throw new RuntimeException("水质分析失败: " + err);
// 输出结果报告
epanet.ENreport();
// 提取关键指标
analyzeResults(epanet);
} finally {
epanet.ENclose();
}
}
private static void analyzeResults(Epanet2 epanet) {
System.out.println("=== 系统分析结果 ===\n");
// 获取节点数量
IntByReference count = new IntByReference();
epanet.ENgetcount(0, count); // EN_NODECOUNT = 0
int nodeCount = count.getValue();
// 检查压力最低节点
float minPressure = Float.MAX_VALUE;
String minPressureNode = "";
for (int i = 1; i <= nodeCount; i++) {
byte[] id = new byte[32];
epanet.ENgetnodeid(i, id);
FloatByReference pressure = new FloatByReference();
epanet.ENgetnodevalue(i, EpanetConstants.EN_PRESSURE, pressure);
if (pressure.getValue() < minPressure) {
minPressure = pressure.getValue();
minPressureNode = new String(id).trim();
}
}
System.out.println("最低压力节点: " + minPressureNode);
System.out.println("最低压力值: " + String.format("%.2f", minPressure) + " m");
// 获取水泵能耗
IntByReference linkCount = new IntByReference();
epanet.ENgetcount(2, linkCount); // EN_LINKCOUNT = 2
for (int i = 1; i <= linkCount.getValue(); i++) {
IntByReference linkType = new IntByReference();
epanet.ENgetlinktype(i, linkType);
if (linkType.getValue() == EpanetConstants.EN_PUMP) {
byte[] id = new byte[32];
epanet.ENgetlinkid(i, id);
FloatByReference energy = new FloatByReference();
epanet.ENgetlinkvalue(i, EpanetConstants.EN_ENERGY, energy);
System.out.println("水泵 " + new String(id).trim() +
" 能耗: " + String.format("%.2f", energy.getValue()) + " kW");
}
}
}
}
8. 常见问题与调试
8.1 模型构建问题
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 模型不收敛 | 管网不连通 | 检查所有节点是否连接到水源 |
| 模型不收敛 | 管径过小 | 检查管径设置是否合理 |
| 模型不收敛 | 阀门设置冲突 | 检查 PRV/PSV 的串联和并联规则 |
| 负压力警告 | 水泵扬程不足 | 增加水泵扬程或减少下游需求 |
| 水池溢出 | 进水流量过大 | 调整控制规则或增加出水管径 |
8.2 Java 集成问题
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| UnsatisfiedLinkError | DLL/SO 文件未找到 | 确保库文件在 java.library.path 中 |
| 错误码 200+ | INP 文件格式错误 | 检查 INP 文件语法 |
| 内存访问错误 | JNA 类型映射错误 | 检查指针和引用类型的使用 |
| 中文路径失败 | 编码问题 | 使用 ASCII 路径或设置文件编码 |
8.3 调试技巧
- 启用详细报告:在
[REPORT]节设置STATUS FULL查看每次迭代的状态变化 - 分步仿真:使用
ENopenH/ENrunH/ENnextH序列逐步检查 - 简化模型:从最简单的管网开始,逐步增加复杂度
- 验证质量守恒:检查总流入量是否等于总流出量
9. 进阶学习路径
9.1 深入方向
| 方向 | 内容 | 应用场景 |
|---|---|---|
| 优化算法 | 结合遗传算法优化管网设计 | 管网改扩建规划 |
| 实时仿真 | EPANET-RTX 实时扩展 | SCADA 系统集成 |
| 多物种反应 | EPANET-MSX 扩展 | 复杂水质化学反应 |
| 不确定性分析 | 蒙特卡洛模拟 | 风险评估 |
| 管网简化 | 等效管段方法 | 大规模管网加速 |
9.2 推荐资源
- EPANET 2.2 用户手册:https://usepa.github.io/EPANET2.2/
- EPANET 开源项目:https://github.com/USEPA/EPANET2.2
- Toolkit API 文档:随 EPANET 安装包提供
- EPANET-MATLAB Toolkit:学习 API 调用模式(尽管是 MATLAB 版本)
9.3 Java 生态相关项目
| 项目 | 说明 | 链接 |
|---|---|---|
| Baseform-Epanet-Java-Library | 成熟的 Java 封装库 | 搜索 GitHub |
| JGrassTools | 包含 EPANET JNA 封装 | 开源 GIS 工具集 |
附录:常用 Toolkit API 速查
项目生命周期
| 函数 | 功能 |
|---|---|
ENopen |
打开项目 |
ENclose |
关闭项目 |
ENsaveinpfile |
保存为 INP 文件 |
仿真控制
| 函数 | 功能 |
|---|---|
ENsolveH |
完整水力分析 |
ENopenH / ENinitH / ENrunH / ENnextH / ENcloseH |
分步水力分析 |
ENsolveQ |
完整水质分析 |
ENopenQ / ENinitQ / ENrunQ / ENnextQ / ENcloseQ |
分步水质分析 |
数据访问
| 函数 | 功能 |
|---|---|
ENgetcount |
获取对象数量 |
ENgetnodeindex / ENgetnodeid |
节点索引/ID 转换 |
ENgetnodevalue / ENsetnodevalue |
读取/设置节点参数 |
ENgetlinkvalue / ENsetlinkvalue |
读取/设置连接参数 |
ENgetpatternvalue |
读取模式乘数 |
ENgetcontrol / ENsetcontrol |
读取/设置控制规则 |
本手册基于 EPANET 2.2 版本编写。EPANET 是公共领域软件,可自由复制和分发。
更多推荐
所有评论(0)