Saturday, December 28, 2024

SWMM5 flowrout.c Summary

 Below is a step‐by‐step explanation of how flowrout.c implements and coordinates flow routing in SWMM5. The functions here support Steady Flow, Kinematic Wave, and (indirectly) Dynamic Wave routing methods. They initialize link/node states, compute flow propagation each time step, and update depths and volumes at nodes and links.


1. Role of flowrout.c

  • In SWMM5, flow routing is the process of moving water through the network of nodes and links based on continuity and momentum principles.
  • flowrout.c is a high‐level module that:
    1. Initializes node/link depths and volumes when the simulation starts.
    2. Chooses the correct solver approach (steady, kinematic, or dynamic wave).
    3. Executes a routing step, updating flow rates, depths, and volumes at each node/link for the current time step.

Note that Dynamic Wave routing has much of its logic in dynwave.c, but flowrout.c still coordinates calls to it.


2. Key Externally Called Functions

  1. flowrout_init(routingModel)

    • Called when the routing process is opened (one time at start).
    • If Dynamic Wave:
      1. Calls validateGeneralLayout() to ensure the network is topologically consistent for dynamic wave.
      2. Calls dynwave_init() to set up dynamic wave parameters.
      3. If no hotstart file is used, initializes node and link depths with initNodeDepths() and initLinkDepths().
    • If Kinematic Wave or Steady Flow:
      • Calls validateTreeLayout() which checks for a “tree-like” network structure: each node has at most one (or two) outlets, no adverse slopes unless dummy links, etc.
    • Then calls initNodes() and initLinks(routingModel) to set initial volumes/flows.
  2. flowrout_close(routingModel)

    • Called at the end of the simulation.
    • If dynamic wave was used, calls dynwave_close() to free memory used by the dynamic wave engine.
  3. flowrout_getRoutingStep(routingModel, fixedStep)

    • Returns the routing time step to use for the next iteration.
    • If Dynamic Wave, calls dynwave_getRoutingStep() to find a variable time step that meets Courant stability criteria. Otherwise, returns fixedStep.
  4. flowrout_execute(links, routingModel, tStep)

    • Main function that routes flow through the network for a single time step (tStep).
    • Steps:
      1. Resets node overflow to 0.
      2. If routingModel == DW, calls dynwave_execute(tStep) and returns.
      3. Else (Steady or Kinematic wave), processes links in topological order:
        • If the upstream node is a storage, calls updateStorageState() to iteratively find the node’s outflow and final water level.
        • Gets inflow to the link (getLinkInflow()) and routes flow either with:
          • steadyflow_execute() or
          • kinwave_execute()
        • The result is assigned to Link[j].newFlow.
      4. After going through all links, updates each node’s and link’s final state (setNewNodeState() and setNewLinkState()).
    • Returns the number of computational sub-steps performed (e.g., for partial iterative updates in kinematic or steady flow).

3. Important Internal Functions

3.1 Initialization & Validation

  1. validateTreeLayout()

    • Ensures the network is “tree-like” for Kinematic Wave or Steady Flow.
    • Checks each node’s outlet links (dividers, outfalls, storage) and ensures no node has multiple downstream links (unless it’s a valid exception).
  2. validateGeneralLayout()

    • Used by Dynamic Wave. Checks for conditions like dummy links, ideal pumps, or outfalls that must have limited connectivity. Ensures at least one outlet node, etc.
  3. initNodeDepths()

    • Assigns initial water depth to nodes if not specified.
    • E.g., for a non-storage node with no user-supplied depth, it might average the depths of its connecting links.
  4. initLinkDepths()

    • For Dynamic Wave only—initial depth in each conduit is the average of the depths at its end nodes, unless a user-supplied q0 is given.
  5. initNodes() & initLinks()

    • initNodes(): sets initial inflow/outflow, volume, overflow, etc.
    • initLinks(): sets initial link volumes (or zero flow for Steady Flow) and prepares internal states in conduits/pumps.

3.2 Steady/Kinematic Wave Execution

  1. flowrout_execute(...) (as explained above).

  2. updateStorageState(i, j, links, dt)

    • For a storage node i, we do an iterative approach to find the node’s new depth.
    • Because outflow from a storage node depends on the water depth in it, we do multiple iterations up to MAXITER (10) or until the depth change is under STOPTOL (0.005 ft).
    • We call getStorageOutflow(...) to sum flows leaving the node’s connected links.
  3. getLinkInflow(link, dt)

    • Finds how much flow can enter a link from its upstream node.
    • For example, we might limit it if the node cannot supply more than node_getMaxOutflow(...).
  4. steadyflow_execute(...)

    • A simplified approach for Steady Flow:
      1. If it’s a conduit, tries to see if the input flow exceeds full capacity; if so, it limits flow to full capacity.
      2. Adjusts for evaporation/infiltration losses via link_getLossRate(...).
  5. kinwave_execute(...)

    • (Not shown in detail in this code snippet.) Similar approach but uses kinematic wave formulas.

3.3 Updating Final States

  • After each link is routed, we do:
    • setNewNodeState(...):
      • For non‐storage nodes, updates volume = oldVolume + netInflow × dt.
      • If volume > fullVolume and ponding not allowed, set overflow and cap volume.
      • Depth is then node_getDepth(j, volume).
    • setNewLinkState(...):
      • For a conduit, we find average area from upstream and downstream cross-sectional areas, compute link volume, and set new depth based on y1 + offset, y2 + offset, etc.

4. Putting It All Together in a Typical Time Step

  1. SWMM calls flowrout_getRoutingStep() to find tStep.
  2. flowrout_execute(links, model, tStep) is invoked:
    1. If Dynamic Wave: goes directly to dynwave_execute(tStep), which solves St. Venant PDEs iteratively.
    2. Else (Steady or Kinematic):
      • Loops through each link in a topologically sorted order.
      • If the link’s upstream node is a storage, calls updateStorageState(...) to iteratively refine the storage node’s depth.
      • Finds link inflow, routes the flow with steady or kinematic formula.
      • Updates link’s newFlow, increments node outflow/inflow.
      • After all links are processed, calls setNewNodeState(...) for each node and setNewLinkState(...) for each link to finalize volumes and depths.
  3. Return the count of computational steps used (1 step per link in steady/kinwave, or the iterative steps in dynamic wave).

5. Key Constants & Notes

  • OMEGA = 0.55: Under‐relaxation factor for storage iteration—blends old depth with newly calculated depth to stabilize.
  • MAXITER = 10: Max iteration passes for updating storage nodes.
  • STOPTOL = 0.005 ft: Convergence tolerance on storage node depth.
  • AllowPonding: If set, a node can have indefinite capacity beyond fullDepth by storing water in a “ponded area.”

6. Conclusions

  • flowrout.c is the routing orchestrator. It sets up initial states (volumes/depths), picks the correct solver path, and after each step updates node/link states.
  • For Steady and Kinematic wave, it uses simplified, topological progression: from upstream to downstream links, adjusting flows and volumes in each storage node.
  • For Dynamic Wave, it delegates to dynwave_execute(), but still handles some setup/close routines.
  • The iterative logic in updateStorageState() is a key mechanism for bridging the fact that outflow from a storage node depends on the node’s depth, and the node’s depth depends on how much outflow it has—hence the repeated approximation each time step.

No comments:

SWMM5 Delphi GUI Dabout.pas Summary

 The Dabout.pas unit is part of the EPA SWMM (Storm Water Management Model) project and contains the logic for the "About" dialo...