Skip to main content

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
  2. Warning vs Error Classification
  3. Scenario Export Options Fix
  4. Summary

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:

  • float is 4 bytes (32-bit IEEE 754)
  • double is 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)

FunctionIssueFixFile
EN_getcurvevalueref float parametersref doubleEpanetImport.cs
EN_getcurveref float arrayref doubleEpanetImport.cs
EN_setcurveref float arrayref doubleEpanetImport.cs
EN_getdemandmodelref float pmin, preq, pexpref doubleEpanetImport.cs
EN_getbasedemandref float baseDemandref doubleEpanetImport.cs
EN_setpatternref float valuesref doubleEpanetImport.cs
EN_getcontrolref float setting, levelref doubleEpanetImport.cs
EN_getruleref float priorityref doubleEpanetImport.cs
EN_getthenactionref float settingref doubleEpanetImport.cs
EN_getelseactionref float settingref doubleEpanetImport.cs

1.4 Files Modified

  1. epanetconsolecore/EpanetWrapper/EpanetImport.cs

    • Fixed 10 P/Invoke declarations (ref floatref double)
  2. epanetconsolecore/EpanetWrapper/EpanetWrapper/EpanetWrapper.cs

    • Removed unnecessary float conversions in 3 wrapper methods
  3. epanetconsolecore/EpanetWrapper/EpanetWrapper/EpanetWrapperDemand.cs

    • Fixed EN_getdemandmodel signature
  4. epanetconsolecore/EpanetWrapper/EpanetWrapper/EpanetWrapperControls.cs

    • Fixed control and rule method signatures
  5. epanetconsolecore/NetworkManager/Parsers/OptionsSectionParser.cs

    • Changed caller variables from float to double
  6. efcorelibraries/HTModels/Models/EN2Models/DataCurve.cs

    • Added missing case for EN_GENERIC_CURVE in CurveTypeDescription property

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 solver
  • EN_runQ - Quality solver
  • EN_nextH - Next hydraulic time step
  • EN_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

CodeTypeDescriptionAction
6⚠️ WarningSystem hydraulically unbalancedContinue
110❌ ErrorCannot solve network hydraulic equationsStop
200❌ ErrorInsufficient memory availableStop
223❌ ErrorNot enough nodes in networkStop
230❌ ErrorNonincreasing x-values for curveStop

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 ✅

FixFiles ModifiedImpactStatus
P/Invoke Type Mismatches6 filesData corruption prevented✅ Complete
Warning Classification1 fileSimulations no longer fail on warnings✅ Complete
Scenario Export Options1 fileExports 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

  1. Always match C API types exactly - double * in C = ref double in C#, never ref float
  2. Study reference implementations - EPANET GUI source code provided the error classification rule
  3. Test with real data - Memory corruption only appeared with actual curve parsing
  4. Scenario-specific logic - Always check if scenario has overrides before using defaults

Related Documentation:


Status: ✅ All Critical Fixes Implemented Last Updated: October 21, 2025 Stability: Production Ready