EPANET 学习手册:面向 Java 工程师的入门指南

目录

  1. EPANET 概述
  2. 核心对象模型
  3. 建模流程
  4. 水力仿真原理
  5. 水质仿真原理
  6. EPANET 与 Java 集成
  7. 完整建模示例
  8. 常见问题与调试
  9. 进阶学习路径

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 为关死点扬程,rn 为曲线系数。

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)

其中 hihj 为节点水头,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

迭代持续进行,直到满足收敛条件:

  1. 流量变化率之和除以总流量小于精度阈值(默认 0.001)
  2. 或水头误差小于阈值
  3. 或最大流量变化小于阈值

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 发生在管壁表面

其中 KbKw 为反应系数,正值表示增长,负值表示衰减。

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 调试技巧

  1. 启用详细报告:在 [REPORT] 节设置 STATUS FULL 查看每次迭代的状态变化
  2. 分步仿真:使用 ENopenH / ENrunH / ENnextH 序列逐步检查
  3. 简化模型:从最简单的管网开始,逐步增加复杂度
  4. 验证质量守恒:检查总流入量是否等于总流出量

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 是公共领域软件,可自由复制和分发。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐