EPANET Critical Fixes - History
Purpose: Document critical bugs fixed during Hydrotrek DWD Suite development Period: October 2025 Status: All Fixes Implemented and Verified
Table of Contents
1. P/Invoke Type Mismatch Fixes
1.1 Problem Summary
Severity: HIGH - Data Corruption Date Discovered: October 20, 2025 Status: ✅ FIXED
Issue:
The EPANET wrapper C# P/Invoke declarations had systematic type mismatches where the C API uses double * but C# declarations used ref float. This caused memory corruption because:
floatis 4 bytes (32-bit IEEE 754)doubleis 8 bytes (64-bit IEEE 754)
When C code wrote 8 bytes into a location where C# allocated only 4 bytes, it corrupted adjacent memory.
1.2 Symptoms Observed
Example: Curve Parsing
Input file curve data:
[CURVES]
1 0.0000 400.0000
1 1800.0000 300.0000
1 6000.0000 0.0000
Expected: XValues = {0.0, 1800.0, 6000.0}
Actual: XValues = {3.890625, 3.79296875, 0.0} ❌
Impact:
- Wrong curve values in database
- EPANET error 230: "nonincreasing x-values for curve"
- Synchronization failures during scenario runs
- Potential crashes and silent data corruption
1.3 Functions Fixed (10 total)
| Function | Issue | Fix | File |
|---|---|---|---|
EN_getcurvevalue | ref float parameters | ref double | EpanetImport.cs |
EN_getcurve | ref float array | ref double | EpanetImport.cs |
EN_setcurve | ref float array | ref double | EpanetImport.cs |
EN_getdemandmodel | ref float pmin, preq, pexp | ref double | EpanetImport.cs |
EN_getbasedemand | ref float baseDemand | ref double | EpanetImport.cs |
EN_setpattern | ref float values | ref double | EpanetImport.cs |
EN_getcontrol | ref float setting, level | ref double | EpanetImport.cs |
EN_getrule | ref float priority | ref double | EpanetImport.cs |
EN_getthenaction | ref float setting | ref double | EpanetImport.cs |
EN_getelseaction | ref float setting | ref double | EpanetImport.cs |
1.4 Files Modified
epanetconsolecore/EpanetWrapper/EpanetImport.cs- Fixed 10 P/Invoke declarations (
ref float→ref double)
- Fixed 10 P/Invoke declarations (
epanetconsolecore/EpanetWrapper/EpanetWrapper/EpanetWrapper.cs- Removed unnecessary float conversions in 3 wrapper methods
epanetconsolecore/EpanetWrapper/EpanetWrapper/EpanetWrapperDemand.cs- Fixed
EN_getdemandmodelsignature
- Fixed
epanetconsolecore/EpanetWrapper/EpanetWrapper/EpanetWrapperControls.cs- Fixed control and rule method signatures
epanetconsolecore/NetworkManager/Parsers/OptionsSectionParser.cs- Changed caller variables from
floattodouble
- Changed caller variables from
efcorelibraries/HTModels/Models/EN2Models/DataCurve.cs- Added missing case for
EN_GENERIC_CURVEinCurveTypeDescriptionproperty
- Added missing case for
1.5 Verification
Before Fix:
- ❌ Curves had garbage values
- ❌ Error 230: "nonincreasing x-values"
- ❌ Synchronization failures
- ❌ Controls/patterns potentially corrupted
After Fix:
- ✅ Curves parse correctly
- ✅ No Error 230
- ✅ Synchronization works
- ✅ All numeric values accurate
2. Warning vs Error Classification
2.1 Problem Summary
Severity: MEDIUM - Failed Simulations Date Discovered: October 21, 2025 Status: ✅ FIXED
Issue: Error Code 6 (System hydraulically unbalanced) was treated as a fatal error instead of a warning, causing simulations to stop prematurely.
2.2 EPANET GUI Analysis
The EPANET GUI source code reveals the classification rule:
// From simulator.pas
if ErrorCode <= 100 then ErrorCode := epanet2.ENnextH(tstep);
until (tstep = 0) or (ErrorCode > 100) or (SimStatus = ssCancelled);
// Ignore any warning code
if ErrorCode <= 100 then ErrorCode := 0;
Rule:
- Error Codes ≤ 100: Warnings (continue simulation)
- Error Codes > 100: True errors (stop simulation)
2.3 Implementation
File: epanetconsolecore/EpanetWrapper/EpanetWrapper/EpanetWrapper.cs
Named Constant:
// EPANET uses error codes 1-100 for warnings, >100 for true errors
// Reference: EPANET GUI source (USEPA/EPANET/src/simulator/simulator.pas, lines 251, 255, 263, 270)
private const int EPANET_WARNING_THRESHOLD = 100;
Updated Functions:
EN_runH- Hydraulic solverEN_runQ- Quality solverEN_nextH- Next hydraulic time stepEN_nextQ- Next quality time step
Before:
int error = this.EN_runH(ref t);
if (error != 0)
{
return false; // Stopped on ANY non-zero code
}
After:
int error = this.EN_runH(ref t);
if (error > EPANET_WARNING_THRESHOLD)
{
// True errors (>100) - stop simulation
string errorDescription = GetEpanetErrorDescription(error);
this.Logger.LogError($"EPANET Error {error} on EN_runH: {errorDescription}");
return false;
}
else if (error > 0)
{
// Warnings (1-100) - log but continue
string warningDescription = GetEpanetErrorDescription(error);
this.Logger.LogWarning($"EPANET Warning {error} on EN_runH: {warningDescription}");
// Continue simulation despite warning
}
2.4 Impact
Before Fix:
- ❌ Scenarios 1, 7, 13 failed with "Object reference not set" (masked Error 6)
- ❌ Only Scenario 9 worked
- ❌ Simulations stopped on hydraulic imbalance
After Fix:
- ✅ All scenarios complete successfully
- ✅ Warnings logged but don't stop simulation
- ✅ Results generated even with warnings
- ✅ Clear distinction between warnings and errors
2.5 Common Error Codes
| Code | Type | Description | Action |
|---|---|---|---|
| 6 | ⚠️ Warning | System hydraulically unbalanced | Continue |
| 110 | ❌ Error | Cannot solve network hydraulic equations | Stop |
| 200 | ❌ Error | Insufficient memory available | Stop |
| 223 | ❌ Error | Not enough nodes in network | Stop |
| 230 | ❌ Error | Nonincreasing x-values for curve | Stop |
3. Scenario Export Options Fix
3.1 Problem Summary
Severity: MEDIUM - Incorrect Exports Date Discovered: October 21, 2025 Status: ✅ FIXED
Issue: When exporting INP files for different scenarios, all exported files were identical, even when scenarios had different options (PDA vs DDA).
3.2 Root Cause
The SaveInpFileMode() method in EpanetConsoleCore/Program.cs called SyncAllFromDatabaseAsync() which always synced default options, ignoring scenario-specific options.
Missing:
- Scenario-specific Options (Trials, Accuracy, Tolerance)
- Scenario-specific Demand Model (DDA/PDA with pressure parameters)
- Scenario-specific Controls
3.3 Solution
Created new method: SyncOptionsForScenarioAsync()
private static async Task SyncOptionsForScenarioAsync(
Scenario scenario, EN2PostgresContext enContext, HTLogger logger)
{
// Load scenario's options (or fallback to default)
var options = scenario.Options;
if (options == null)
{
options = await enContext.Options.FirstOrDefaultAsync(o => o.IsDefault)
?? await enContext.Options.FirstOrDefaultAsync();
}
logger.LogInformation($"Using options: {(scenario.Options != null ? "Scenario-specific" : "Default")} (DemandModel: {options.DemandModel})");
// Sync using scenario's options
await networkSyncService.SyncOptionsAsync(options);
// Sync scenario-specific controls if present
await networkSyncService.SyncControlsForScenarioAsync(scenario.Id);
}
3.4 Verification
Tested Scenarios:
- ✅ Scenario 7 (PDA): Exported with PDA demand model
- ✅ Scenario 9 (DDA): Exported with DDA demand model
- ✅ Scenarios now have different INP files based on their options
4. Summary
All Fixes Implemented ✅
| Fix | Files Modified | Impact | Status |
|---|---|---|---|
| P/Invoke Type Mismatches | 6 files | Data corruption prevented | ✅ Complete |
| Warning Classification | 1 file | Simulations no longer fail on warnings | ✅ Complete |
| Scenario Export Options | 1 file | Exports now scenario-specific | ✅ Complete |
Total Impact
Files Modified: 8 files Functions Fixed: 10 P/Invoke declarations + 4 runtime functions Bugs Resolved: 3 critical issues
Result:
- ✅ No more memory corruption
- ✅ Accurate numeric values (curves, controls, patterns)
- ✅ Simulations handle warnings appropriately
- ✅ Scenario exports correctly reflect options
- ✅ System stable and production-ready
Lessons Learned
- Always match C API types exactly -
double *in C =ref doublein C#, neverref float - Study reference implementations - EPANET GUI source code provided the error classification rule
- Test with real data - Memory corruption only appeared with actual curve parsing
- Scenario-specific logic - Always check if scenario has overrides before using defaults
Related Documentation:
- EPANET API Reference: See epanet2.h
- P/Invoke Guidelines: https://docs.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke
- EPANET GUI Source: USEPA/EPANET repository
Status: ✅ All Critical Fixes Implemented Last Updated: October 21, 2025 Stability: Production Ready