Database → EPANET Synchronization
Feature: Real-time database-to-EPANET synchronization Date: October 16-17, 2025 Status: ✅ Implemented and Complete Purpose: Apply database changes to EPANET model before simulations
Table of Contents
1. Overview
1.1 Purpose
Enable database changes to flow directly to EPANET before running hydraulic and quality simulations, eliminating the need for manual INP file regeneration.
1.2 Problem Solved
Before:
- Network loaded from
.inpfiles only - Database modifications not reflected in simulations
- Manual INP file export required after every change
- Simulation results didn't reflect database state
After:
- Database changes automatically applied to EPANET model
- Simulations use current database values
- No manual export required
- Database is source of truth
1.3 Solution Approach
Chosen: Option 1 - Update EPANET In-Memory Model
Apply database changes to EPANET's in-memory model after loading from INP file, before running simulation.
┌─────────────┐
│ Load from │
│ INP file │
└──────┬──────┘
│
▼
┌─────────────┐
│ Apply DB │ ← New synchronization step
│ Changes │
└──────┬──────┘
│
▼
┌─────────────┐
│ Run Sim │
└─────────────┘
Why this approach:
- ✅ Minimal code changes
- ✅ Leverages existing EPANET setter functions
- ✅ INP file remains as a baseline/template
- ✅ Database becomes source of truth for changes
- ✅ No binary format dependencies
2. Strategy
2.1 Data Categories
Global Network Data (All Records Synced):
- Junctions, Tanks, Reservoirs (all nodes)
- Pipes, Valves, Pumps (all links via LinkProperty table)
- Patterns (all patterns)
- Curves (all curves)
- Node and Link properties
Scenario-Specific Data (Needs Special Handling):
- Options (demand model, solver settings)
- Time Parameters (duration, timesteps)
- Controls (simple and rule-based)
2.2 Update Flow
2.3 Property Mapping
Nodes: | Database | EPANET Property Code | Entity | Sync Method | |----------|---------------------|--------|-------------| | Elevation | EN_ELEVATION (0) | All Nodes | SyncJunctionsAsync | | BaseDemand | EN_BASEDEMAND (1) | Junctions | SyncJunctionsAsync | | Pattern | EN_PATTERN (2) | Junctions/Reservoirs | SyncJunctionsAsync/ReservoirsAsync | | InitLevel | EN_INITLEVEL (21) | Tanks | SyncTanksAsync | | MinLevel | EN_MINLEVEL (20) | Tanks | SyncTanksAsync | | MaxLevel | EN_MAXLEVEL (22) | Tanks | SyncTanksAsync | | Diameter | EN_TANKDIAM (23) | Tanks | SyncTanksAsync | | VolCurve | EN_VOLCURVE (26) | Tanks | SyncTanksAsync |
Links: | Database | EPANET Property Code | Entity | Sync Method | |----------|---------------------|--------|-------------| | Diameter | EN_DIAMETER (0) | Pipes/Valves | SyncPipesAsync/ValvesAsync | | Length | EN_LENGTH (1) | Pipes | SyncPipesAsync | | Roughness | EN_ROUGHNESS (2) | Pipes | SyncPipesAsync | | MinorLoss | EN_MINORLOSS (3) | Pipes/Valves | SyncPipesAsync/ValvesAsync | | InitSetting | EN_INITSETTING (4) | Valves/Pumps | SyncValvesAsync/LinkPropertiesAsync | | InitStatus | EN_INITSTATUS (11) | All Links | SyncLinkPropertiesAsync | | KWall | EN_KWALL (23) | Pipes | SyncLinkPropertiesAsync | | PumpCurve | EN_PUMP_HCURVE (14) | Pumps | SyncLinkPropertiesAsync |
3. Implementation
3.1 Core Infrastructure ✅
New Service: NetworkSynchronizationService.cs
Location: epanetconsolecore/EpanetConsoleCore/Services/
Main Interface:
public class NetworkSynchronizationService
{
private readonly EpanetWrapper _epanetWrapper;
private readonly EN2PostgresContext _context;
private readonly HTLogger _logger;
public NetworkSynchronizationService(
EpanetWrapper epanetWrapper,
EN2PostgresContext context,
HTLogger logger)
{
_epanetWrapper = epanetWrapper;
_context = context;
_logger = logger;
}
// Main synchronization method
public async Task SyncAllFromDatabaseAsync()
{
await SyncNodesAsync();
await SyncLinksAsync();
await SyncPatternsAsync();
await SyncCurvesAsync();
await SyncOptionsAsync();
await SyncTimeParametersAsync();
}
}
3.2 Synchronization Methods
Node Synchronization
// Junctions: Elevation, BaseDemand, Pattern
private async Task SyncJunctionsAsync()
{
var junctions = await _context.Junctions.ToListAsync();
foreach (var junction in junctions)
{
int index = _epanetWrapper.EN_getnodeindex(junction.Id);
if (index <= 0) continue;
_epanetWrapper.EN_setnodevalue(index, EN_ELEVATION, junction.Elevation);
_epanetWrapper.EN_setnodevalue(index, EN_BASEDEMAND, junction.BaseDemand);
if (!string.IsNullOrEmpty(junction.Pattern))
{
int patIndex = _epanetWrapper.EN_getpatternindex(junction.Pattern);
if (patIndex > 0)
_epanetWrapper.EN_setnodevalue(index, EN_PATTERN, patIndex);
}
}
}
Link Synchronization via LinkProperty
// Uses LinkProperty table for flexible property updates
private async Task SyncLinkPropertiesAsync()
{
var linkProperties = await _context.LinkProperties.ToListAsync();
foreach (var prop in linkProperties)
{
int index = _epanetWrapper.EN_getlinkindex(prop.LinkId);
if (index <= 0) continue;
_epanetWrapper.EN_setlinkvalue(index, prop.LinkPropertyTypeId, prop.LinkValue);
}
}
Pattern & Curve Bulk Updates
// Efficient bulk pattern update
public int EN_setpattern(int index, double[] values, int len)
{
return EpanetImport.EN_setpattern(_project, index, values, len);
}
// Efficient bulk curve update
public int EN_setcurve(int index, double[] xValues, double[] yValues, int nPoints)
{
return EpanetImport.EN_setcurve(_project, index, xValues, yValues, nPoints);
}
3.3 Integration Point
File: epanetconsolecore/EpanetConsoleCore/Program.cs
Integrated at line 251 (after startup, before simulation):
// Load network from INP file
bool startupSuccess = epanetWrapper.Startup(doQuality, msxFilename);
if (!startupSuccess) { /* error handling */ }
// ✨ Synchronize network from database
logger.LogInformation("Synchronizing network from database...");
scenario.CurrentStep = "Synchronizing network from database";
scenario.ProgressPercentage = 7;
await enContext.SaveChangesAsync();
var syncService = new NetworkSynchronizationService(epanetWrapper, enContext, logger);
try
{
await syncService.SyncAllFromDatabaseAsync();
logger.LogInformation("Network synchronization completed");
}
catch (Exception syncEx)
{
logger.LogError($"Network synchronization failed: {syncEx.Message}");
// Continue with INP values (partial sync better than crash)
}
// Continue with simulation...
4. Service Architecture
4.1 Synchronization Categories
Implemented Methods:
Node Sync Methods:
SyncJunctionsAsync()- Elevation, BaseDemand, PatternSyncTanksAsync()- Elevation, Diameter, Levels, VolCurveSyncReservoirsAsync()- InitialHead, PatternSyncNodePropertiesAsync()- InitQual, Emitter, TankBulkCoeffSyncTankMixingAsync()- Mixing model and fraction
Link Sync Methods:
SyncPipesAsync()- Diameter, Length, Roughness, MinorLossSyncValvesAsync()- Diameter, ValveType, InitSetting, MinorLossSyncLinkPropertiesAsync()- Pump properties, KWall, InitStatus, etc.
Pattern & Curve Methods:
SyncPatternsAsync()- Updates existing patterns (bulk operation)SyncCurvesAsync()- Updates existing curves (bulk operation)
Configuration Methods:
SyncOptionsAsync()- Trials, Accuracy, QualityToleranceSyncTimeParametersAsync()- Duration, timesteps (partial)
4.2 Scenario-Specific Handling
Options:
- Base sync uses default options
- SaveInpFileMode applies scenario-specific options via
SyncOptionsFromObject() - Regular runs apply demand model via
WriteDemandModel()
Controls:
- Simple controls: Written via existing
WriteControls()method - Rule-based controls: Written via existing
WriteControls()method - Already scenario-aware
4.3 Audit Results ✅
Global Data Synchronization:
- ✅ All nodes synced (Junctions, Tanks, Reservoirs)
- ✅ All links synced (Pipes, Valves, Pumps)
- ✅ All patterns synced
- ✅ All curves synced
- ✅ All properties synced
Scenario-Specific Handling:
- ✅ Options handled correctly (SaveInpFileMode fixed)
- ✅ Controls already scenario-aware
- ⚠️ TimeParameters partially implemented (string parsing pending)
5. Testing
5.1 Test Scenarios
Test Case 1: Pipe Property Changes
-- Change pipe diameter in database
UPDATE "Pipes" SET "Diameter" = 12 WHERE "Id" = 'P-1';
-- Run simulation
-- Expected: EPANET uses diameter=12, not INP file value
Test Case 2: Tank Level Changes
-- Change tank initial level
UPDATE "Tanks" SET "InitLevel" = 50 WHERE "Id" = 'T-1';
-- Run simulation
-- Expected: Tank starts at level 50
Test Case 3: Pattern Changes
-- Update pattern multipliers
UPDATE "DataPattern" SET "Pattern" = 1.5 WHERE "Pattern_Id" = 'PAT1' AND "SeqNum" = 1;
-- Run simulation
-- Expected: Pattern uses new multipliers
5.2 Verification Points
✅ Verify synchronization happens:
- Check logs for "Synchronizing network from database..."
- Check logs for "Network synchronization completed"
- Progress bar updates to 7% during sync
✅ Verify values applied:
- Compare EPANET results with expected values
- Check that database values override INP values
- Verify scenario-specific options are used
✅ Verify error handling:
- Sync failures logged but don't crash simulation
- Partial sync better than no sync
- Clear error messages
6. Limitations
6.1 Current Limitations
Cannot Add New Elements:
- Cannot add new junctions, pipes, etc. via database
- Can only update existing elements from INP file
- INP file defines network topology
String Parsing Pending:
- Time parameters stored as strings ("24 HOURS")
- Need parsing logic to convert to seconds
- Currently skipped in sync
Pattern/Curve Creation:
- Cannot add new patterns or curves
- Can only update existing ones
- Pattern/Curve IDs must exist in INP
6.2 Design Rationale
Why INP as baseline:
- Network topology is complex (connectivity graph)
- EPANET requires valid network structure
- Easier to load valid baseline then update values
- Avoids topology validation complexity
Why not sync new elements:
- Adding nodes/links requires topology validation
- Must ensure network connectivity
- Graph algorithms complex and error-prone
- Future enhancement if needed
7. Future Enhancements
7.1 Time Parameter Parsing
Status: Planned Effort: Low (1-2 hours)
Parse time strings and apply to EPANET:
// Parse "24 HOURS" → 86400 seconds
// Parse "5 DAYS" → 432000 seconds
// Parse "30 MINUTES" → 1800 seconds
7.2 Topology Updates
Status: Future Consideration Effort: High (weeks)
Support adding/removing network elements:
- Add new junctions/pipes via database
- Validate network connectivity
- Update topology in EPANET
- Complex graph algorithms required
7.3 Differential Sync
Status: Optimization Idea Effort: Medium (days)
Only sync changed records:
- Track modified timestamp on entities
- Sync only records changed since last sync
- Performance improvement for large networks
Summary
✅ Implemented:
- NetworkSynchronizationService with comprehensive sync methods
- Automatic sync before every simulation
- Scenario-specific options handling
- Error handling and logging
- Audit of all sync methods
✅ Benefits:
- Database is source of truth
- Real-time updates reflected in simulations
- No manual INP export required
- Improved workflow efficiency
✅ Production Ready:
- Error handling prevents crashes
- Logging for troubleshooting
- Backward compatible (INP still works)
- Tested with multiple scenarios
⏸️ Future Work:
- Time parameter string parsing
- Consider topology updates if needed
- Performance optimization for large networks
Related Documentation:
- Implementation Plan: Available in archive (detailed step-by-step)
- EPANET API Reference: See EpanetWrapper documentation
- Database Schema: See architecture documentation
Status: ✅ Complete and Production Ready Last Updated: October 17, 2025