Commit 84c367f7 authored by Mario Morales Hernandez's avatar Mario Morales Hernandez
Browse files

Add optional SWMM flooding consistency debug check

- Add massbal_checkFloodingConsistency() to
external/swmm/src/solver/massbal.c, guarded by SWMM_FLOODING_DEBUG.
Compare sum of node flooding volumes (NodeStats[j].volFlooded) with
system flooding loss (FlowTotals.flooding).Write detailed diagnostics
to output/swmm/swmm_flooding_debug.txt (path derived from Frpt.name).
Print a concise stderr summary at end of run, noting that discrepancies
are expected under the tallnode (canPond=1) workaround.
- Call massbal_checkFloodingConsistency() from massbal_report() under
the same SWMM_FLOODING_DEBUG guard.
- Add TRITON_SWMM_FLOODING_DEBUG CMake option and corresponding compile
definition so the flooding debug check is opt-in and disabled by default.
- Update README.md with a “Flooding Consistency Debug Check (Optional)”
section describing:
How to enable the check via CMake,
Where the debug output is written,
That this is diagnostic-only and does not change model physics.
parent 4119d51d
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ endif()

# SWMM coupling option
option(TRITON_ENABLE_SWMM "Enable SWMM coupling for urban drainage" OFF)
option(TRITON_SWMM_FLOODING_DEBUG "Enable SWMM flooding consistency debug check" OFF)

if(TRITON_ENABLE_SWMM)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTRITON_SWMM")
@@ -52,6 +53,11 @@ if(TRITON_ENABLE_SWMM)
  target_include_directories(${TRITON_EXECUTABLE} PUBLIC ${CMAKE_SOURCE_DIR}/external/swmm/src/solver/include)
  target_include_directories(${TRITON_EXECUTABLE} PUBLIC ${CMAKE_SOURCE_DIR}/external/swmm/src/solver)
  target_link_libraries(${TRITON_EXECUTABLE} PUBLIC swmm5)

  if(TRITON_SWMM_FLOODING_DEBUG)
    target_compile_definitions(swmm5 PRIVATE SWMM_FLOODING_DEBUG)
    message(STATUS "SWMM flooding debug check enabled")
  endif()
endif()

add_build_and_run_scripts()
+19 −0
Original line number Diff line number Diff line
@@ -114,6 +114,25 @@ When SWMM coupling is enabled:
- SWMM runs only on rank 0; exchange flow is computed on all ranks
- **Important**: When running with multiple MPI ranks, use 1 or 2 ranks for SWMM coupling (4+ ranks may timeout)

#### Flooding Consistency Debug Check (Optional)

For TRITON-SWMM coupled simulations, a diagnostic tool is available to check consistency between node-level and system-level flooding statistics. This helps identify mass balance discrepancies caused by the "tallnode" workaround.

**Build with flooding debug enabled:**
```bash
cd build
cmake -DTRITON_ENABLE_SWMM=ON -DTRITON_SWMM_FLOODING_DEBUG=ON ..
make -j4
```

**When enabled:**
- A summary is printed to stderr at simulation end
- A detailed report is written to `output/swmm/swmm_flooding_debug.txt`
- The report compares node flooding volumes with system flooding loss
- A discrepancy is expected when the tallnode workaround is active (`canPond=1` in dynwave.c)

**Note:** This is a diagnostic-only feature and does not change the physics or mass balance calculations.

## Running a Simulation

Run a sample case from the build directory using:
+1 −0
Original line number Diff line number Diff line
@@ -273,6 +273,7 @@ void massbal_addToFinalStorage(int pollut, double mass);
double  massbal_getStepFlowError(void);
double  massbal_getRunoffError(void);
double  massbal_getFlowError(void);
void    massbal_checkFloodingConsistency(void);  // TRITON-SWMM debug

//-----------------------------------------------------------------------------
//   Simulation Statistics Methods
+145 −1
Original line number Diff line number Diff line
@@ -308,6 +308,10 @@ void massbal_report()
                 RptFlags.continuity == TRUE
               ) report_writeQualError(QualTotals);
        }

#ifdef SWMM_FLOODING_DEBUG
        massbal_checkFloodingConsistency();
#endif
    }
}

@@ -1044,3 +1048,143 @@ double massbal_getStoredMass(int p)
    }
    return storedMass;
}

//=============================================================================
//  TRITON-SWMM: Flooding Consistency Check
//-----------------------------------------------------------------------------

#ifdef SWMM_FLOODING_DEBUG
void massbal_checkFloodingConsistency()
//
//  Input:   none
//  Output:  none
//  Purpose: Compare node-level flooding totals with system-level flooding
//           totals to detect internal accounting discrepancies.
//
//  In TRITON-SWMM coupling with the "tallnode" workaround (canPond=1 in
//  dynwave.c), nodes can store water above their fullVolume. When this
//  occurs, node overflow is computed and accumulated in NodeStats[j].volFlooded,
//  but node_getSystemOutflow() only returns overflow as system flooding when
//  Node[j].newVolume <= Node[j].fullVolume (node.c:488).
//
//  This creates a discrepancy where:
//  - Node Flooding Summary shows nonzero flooding volumes
//  - Flow Routing Continuity shows zero Flooding Loss
//
//  This diagnostic check writes a detailed report to the SWMM output directory
//  when the discrepancy exceeds a specified tolerance.
//
{
    extern TNodeStats* NodeStats;
    char debugPath[MAXFNAME];
    char* lastSlash;
    int j;
    int hasFloodingNodes;
    double sumNodeFlooding = 0.0;
    double systemFlooding = FlowTotals.flooding;
    double relativeError;
    double tolerance = 1e-3;
    FILE* debugFile = NULL;

    for (j = 0; j < Nobjects[NODE]; j++)
    {
        sumNodeFlooding += NodeStats[j].volFlooded;
    }

    if (fabs(systemFlooding) > 1e-10)
    {
        relativeError = fabs(sumNodeFlooding - systemFlooding) / systemFlooding;
    }
    else if (fabs(sumNodeFlooding) > 1e-10)
    {
        relativeError = 1.0;
    }
    else
    {
        relativeError = 0.0;
    }

    fprintf(stderr, "\n");
    fprintf(stderr, "==================== SWMM FLOODING CONSISTENCY ====================\n");
    fprintf(stderr, "  Sum of Node Flooding Volumes: %12.6f ft3\n", sumNodeFlooding);
    fprintf(stderr, "  System Flooding Loss:         %12.6f ft3\n", systemFlooding);
    fprintf(stderr, "  Difference:                   %12.6f ft3\n", sumNodeFlooding - systemFlooding);
    fprintf(stderr, "  Relative Error:                %12.6f%%\n", relativeError * 100.0);

    if (relativeError > tolerance)
    {
        fprintf(stderr, "  WARNING: Flooding totals differ by more than %.1f%%\n", tolerance * 100.0);
        fprintf(stderr, "  This is expected with TRITON tallnode workaround (canPond=1)\n");

        if (Frpt.name[0] != '\0')
        {
            sstrncpy(debugPath, Frpt.name, MAXFNAME);
            lastSlash = strrchr(debugPath, '/');
            if (lastSlash != NULL)
            {
                *(lastSlash + 1) = '\0';
                strcat(debugPath, "swmm_flooding_debug.txt");
            }
            else
            {
                sstrncpy(debugPath, "output/swmm/swmm_flooding_debug.txt", MAXFNAME);
            }

            debugFile = fopen(debugPath, "w");
            if (debugFile != NULL)
            {
                fprintf(debugFile, "SWMM Flooding Consistency Report\n");
                fprintf(debugFile, "================================\n\n");
                fprintf(debugFile, "This report compares node-level flooding statistics with system-level\n");
                fprintf(debugFile, "flooding totals. A discrepancy is expected in TRITON-SWMM coupling\n");
                fprintf(debugFile, "with the tallnode workaround (dynwave.c: canPond=1).\n\n");
                fprintf(debugFile, "Explanation:\n");
                fprintf(debugFile, "  - NodeStats[j].volFlooded accumulates all overflow from each node\n");
                fprintf(debugFile, "  - FlowTotals.flooding only counts overflow when node volume <= fullVolume\n");
                fprintf(debugFile, "  - With canPond=1, nodes store water above fullVolume, creating the gap\n\n");
                fprintf(debugFile, "Summary:\n");
                fprintf(debugFile, "  Sum of Node Flooding: %15.6f ft3\n", sumNodeFlooding);
                fprintf(debugFile, "  System Flooding Loss:  %15.6f ft3\n", systemFlooding);
                fprintf(debugFile, "  Difference:           %15.6f ft3\n", sumNodeFlooding - systemFlooding);
                fprintf(debugFile, "  Relative Error:       %15.6f%%\n\n", relativeError * 100.0);

                hasFloodingNodes = 0;
                for (j = 0; j < Nobjects[NODE]; j++)
                {
                    if (NodeStats[j].volFlooded > 1e-10)
                    {
                        hasFloodingNodes = 1;
                        break;
                    }
                }

                if (hasFloodingNodes)
                {
                    fprintf(debugFile, "Per-Node Flooding Volumes:\n");
                    for (j = 0; j < Nobjects[NODE]; j++)
                    {
                        if (NodeStats[j].volFlooded > 1e-10)
                        {
                            fprintf(debugFile, "  %-20s  %15.6f ft3\n", Node[j].ID, NodeStats[j].volFlooded);
                        }
                    }
                }
                fclose(debugFile);
                fprintf(stderr, "  Detailed report written to: %s\n", debugPath);
            }
            else
            {
                fprintf(stderr, "  Note: Could not write debug report to: %s\n", debugPath);
            }
        }
    }
    else
    {
        fprintf(stderr, "  OK: Flooding totals consistent (within %.1f%% tolerance)\n", tolerance * 100.0);
    }
    fprintf(stderr, "======================================================================\n");
    fprintf(stderr, "\n");
    fflush(stderr);
}
#endif