AI generated image

SMOFlow

Subcatchment runoff and flow calculations for Ruby

Developed by Sebastian Madrid Ontiveros

$gem install smo_flow click to copy

Built for hydraulic modellers

What is SMOFlow?

SMOFlow was created by Sebastian Madrid Ontiveros in response to the lack of hydraulic modelling libraries available for Ruby. It provides a developer-friendly way to estimate flow from roads, roofs, permeable areas, foul flow, and trade flow using the Rational Method and timestep-based calculations — helping with transparent calculations, quality checks, and understanding how each component contributes to total subcatchment output flow.

Developer & Author: Sebastian Madrid Ontiveros

Multiple surface types
Roads
Hard impermeable surfaces with high runoff coefficients.
Roofs
Pitched and flat roof areas directing rainfall to drainage.
Permeable areas
Grass and landscaped areas absorbing a portion of rainfall.
Timestep calculations

Works with storm profiles and rainfall depths across any timestep from 1 second to 86,400 seconds.

0 Δt → Q

Formulas used in SMOFlow

The Rational Method was first published by Mulvaney (1850) and later by Lloyd-Davies (1906) for UK practice. SMOFlow implements it with standard SI unit conversions.

Original formula — dimensionless
Q = C × i × A

Q = flow rate
C = runoff coefficient (0 to 1)
i = rainfall intensity
A = catchment area
Pure dimensionless form. In practice, engineers use mixed units which requires a conversion factor.
UK practice — flow in m³/s
Q = C × i × A / 360

Q = flow in m³/s
C = runoff coefficient (0 to 1)
i = rainfall intensity in mm/hr
A = catchment area in hectares
The form used in SMOFlow. The 360 is a unit conversion constant — see derivation below.
Timestep-based formula — flow from depth
Q = 10 × C × A × d / Δt

Q = flow in m³/s C = runoff coefficient A = area in hectares d = rainfall depth in mm Δt = timestep in seconds
The 10 comes from converting hectares × mm into m³: 1 hectare = 10,000 m² and 1 mm = 0.001 m, so 10,000 × 0.001 = 10. This means 1 hectare receiving 1 mm of rain produces exactly 10 m³ of water.
Where does the 360 come from?
1
We want Q in m³/s, but intensity is in mm/hr and area is in hectares
2
Convert area: 1 hectare = 10,000 m²
3
Convert intensity: 1 mm/hr = 1/3,600,000 m/s
4
Combined factor: 10,000 / 3,600,000 = 1/360
5
So: Q = C × i × A / 360 gives Q in m³/s

Simple, readable code

All methods are available on a SmoFlow::RationalMethod instance. Create one with a coefficient and area, then call any method.

irb session
SmoFlow::RationalMethod.new(coefficient:, area:)
Creates a new calculator instance
The starting point for all calculations. Takes a runoff coefficient and a catchment area and returns a calculator object you can call methods on.
coefficient:Runoff coefficient 0.0–1.0. Roads 0.85–0.95, roofs 0.9–0.95, grass 0.2–0.4.
area:Catchment area in hectares.
example.rb
require "smo_flow"

calc = SmoFlow::RationalMethod.new(
  coefficient: 0.9,
  area: 2.5  # hectares
)
flow_from_intensity(intensity)
Returns flow in m³/s
Calculates peak flow using Q = C × i × A / 360. Use when you have a rainfall intensity in mm/hr — for example from an IDF curve or a design storm.
intensity:Rainfall intensity in mm/hr. Must be greater than zero.
example.rb
calc.flow_from_intensity(50.0)
# => 0.3125 m³/s
# Q = 0.9 × 50 × 2.5 / 360
flow_ls_from_intensity(intensity)
Returns flow in L/s
Same as flow_from_intensity but returns litres per second instead of m³/s. Equivalent to multiplying the m³/s result by 1000. Common in UK drainage practice.
intensity:Rainfall intensity in mm/hr. Must be greater than zero.
example.rb
calc.flow_ls_from_intensity(50.0)
# => 312.5 L/s
# equivalent to 0.3125 × 1000
flow_from_depth(depth:, timestep:)
Returns flow in m³/s
Calculates flow using Q = 10 × C × A × d / Δt. Use when you have a rainfall profile with a depth at each timestep rather than a constant intensity.
depth:Rainfall depth during the timestep in mm. Must be greater than zero.
timestep:Duration in seconds. Pass 300 for 5-min, 3600 for 1-hour.
example.rb
calc.flow_from_depth(
  depth: 5.0,
  timestep: 3600.0
)
# => 0.03125 m³/s
# Q = 10 × 0.9 × 2.5 × 5.0 / 3600
depth_to_intensity(depth:, timestep:)
Returns intensity in mm/hr
Converts a rainfall depth over a timestep into an equivalent intensity in mm/hr. Useful for comparing against IDF curves or design storm intensities.
depth:Rainfall depth in mm during the timestep.
timestep:Timestep duration in seconds.
example.rb
calc.depth_to_intensity(
  depth: 5.0,
  timestep: 3600.0
)
# => 5.0 mm/hr
# 5mm over 1 hour = 5mm/hr
volume(depth:)
Returns volume in m³
Calculates total runoff volume using V = 10 × C × A × d. Useful for storage calculations and cumulative volume tracking across multiple timesteps.
depth:Total rainfall depth in mm. Must be greater than zero.
example.rb
calc.volume(depth: 5.0)
# => 112.5 m³
# V = 10 × 0.9 × 2.5 × 5.0

Where SMOFlow could go

These are ideas for future development — not a fixed plan. Contributions and suggestions are always welcome on GitHub.

Currently available: Rational Method — flow from intensity, depth, volume, and unit conversion. Published on RubyGems as v0.1.1.
Runoff
Surface-Based Runoff
Separate surface classes for roads, roofs, and permeable areas — allowing a surface-by-surface flow breakdown per subcatchment.
Road runoffRoof runoffPermeable areasSurface breakdownTotal subcatchment flow
DWF
Dry Weather Flow
Population-based DWF calculations per subcatchment following EA guidance. Supports both the DWF formula and the Q80 nonparametric method for existing and new discharges.
EA DWF guidanceDWF formulaQ80 methodFoul flowTrade flowInfiltration
Method 1 — DWF Formula
DWF = PG + IDWF + E
P = population  G = per capita flow
IDWF = dry weather infiltration
E = trade effluent
For new discharges and future design horizon calculations.
Method 2 — Nonparametric Q80
n = integer(0.2 × N)
Q80 = 20th percentile of daily flow
N = number of valid measurements

73rd value in a ranked set of 365.
For existing discharges with measured flow data.
EA Regulatory
Spill Count Analysis — EA 12/24 Method
Automated spill counting for bathing and shellfish waters using the EA 12/24 counting method. Spill significance and counting method agreed on a site-specific basis at the scoping stage.
EA 12/24 methodBathing watersShellfish watersAnnual spill countCSO analysis
Significant spill threshold: > 50 m³
1
Start counting when the first discharge occurs.
2
Any discharge in the first 12-hour block counts as one spill.
3
Each subsequent 24-hour block with discharge counts as one additional spill.
4
A 24-hour block with no discharge ends the sequence. The next discharge restarts from step 1.
UK Regulatory
Formula A — Storm Overflow Compliance
The minimum retained continuation flow at storm overflows and at the inlet to wastewater treatment works, calculated using the UK regulatory Formula A. Must be maintained during the full duration of a spill.
CSO pass forward flowSeparately drained areasCombined catchmentsWWTW inlet flow
Formula A — standard combined catchment
Formula A (l/d) = DWF + 1360P + 2E
DWF = PG + I + E    P = population    G = per capita flow (l/head/d)    I = infiltration (l/d)    E = trade effluent (l/d)
Where separately drained areas are present: Formula A (l/d) = DWF + 2PsG + 1360Pc + 2Et. The Formula A value must be maintained in the downstream sewer during the full duration of a spill.

Support the project

SMOFlow is free and open source. If you find it useful and would like to support its continued development, please consider buying me a coffee.

Buy Me a Coffee QR code Buy Me a Coffee