Tip

  1. Need help? Please let us know in the SUEWS Community.

  2. Please report issues with the manual on GitHub Issues (or use Report Issue for This Page for page-specific feedback).

  3. Please cite SUEWS with proper information from our Zenodo page.

3.1. SUEWS Quick Start Tutorial#

Essential SUEWS workflow using the modern SuPy (Python) interface.

This tutorial demonstrates the complete workflow for running urban climate simulations with SUEWS:

  1. Load sample data

  2. Run simulation

  3. Explore results (statistics, plotting, resampling)

What is SUEWS?

SUEWS (Surface Urban Energy and Water Balance Scheme) is an urban climate model that simulates energy and water fluxes in urban environments. SuPy is the modern Python interface that provides powerful data analysis capabilities and seamless integration with the scientific Python ecosystem.

import matplotlib.pyplot as plt
import pandas as pd

from supy import SUEWSSimulation

3.1.1. Load Sample Data#

SuPy includes built-in sample data to get you started immediately. Load sample data for the simulation using the modern OOP API.

sim = SUEWSSimulation.from_sample_data()

print("Sample data loaded successfully!")
print(f"Grid ID: {sim.state_init.index[0]}")
print(f"Forcing period: {sim.forcing.time_range}")
print(f"Time steps: {len(sim.forcing)}")
Sample data loaded successfully!
Grid ID: 1
Forcing period: (Timestamp('2012-01-01 00:05:00'), Timestamp('2013-01-01 00:00:00'))
Time steps: 105408

3.1.2. Understanding Input Data#

SUEWS requires two main input datasets:

  • state_init: Initial conditions and site configuration

  • forcing: Meteorological forcing data (time series)

Access these through the simulation object’s properties.

# Surface characteristics: building and tree heights
print("Building and tree heights:")
print(sim.state_init.loc[:, ["bldgh", "evetreeh", "dectreeh"]])

# Surface fractions by land cover type
print("\nSurface fractions:")
print(sim.state_init.filter(like="sfr_surf"))
Building and tree heights:
var     bldgh evetreeh dectreeh
ind_dim     0        0        0
grid
1        22.0     13.1     13.1

Surface fractions:
var     sfr_surf
ind_dim     (0,)  (1,) (2,)  (3,)  (4,) (5,)  (6,)
grid
1           0.43  0.38  0.0  0.02  0.03  0.0  0.14

3.1.3. Visualise Forcing Data#

The forcing data drives the simulation. Access forcing variables directly as attributes of sim.forcing.

# Access forcing variables through the OOP interface
print("\nForcing data summary:")
print(f"  Air temperature range: {sim.forcing.Tair.min():.1f} to {sim.forcing.Tair.max():.1f} C")
print(f"  Wind speed range: {sim.forcing.U.min():.1f} to {sim.forcing.U.max():.1f} m/s")
print(f"  Total rainfall: {sim.forcing.rain.sum():.1f} mm")

# Slice forcing data by time (returns new SUEWSForcing object).
# Drop the first row with `.iloc[1:]` because accumulated variables
# (e.g. rainfall) for the partial period at the slice boundary are
# incomplete, making that row invalid as forcing input.
forcing_sliced = sim.forcing["2012-01":"2012-03"].iloc[1:]

# Update simulation with the time-sliced forcing
sim.update_forcing(forcing_sliced)

# Plot key meteorological variables
list_var_forcing = ["kdown", "Tair", "RH", "pres", "U", "rain"]
dict_var_label = {
    "kdown": r"Incoming Solar Radiation ($\mathrm{W\ m^{-2}}$)",
    "Tair": r"Air Temperature ($^\circ$C)",
    "RH": "Relative Humidity (%)",
    "pres": "Air Pressure (hPa)",
    "rain": "Rainfall (mm)",
    "U": r"Wind Speed ($\mathrm{m\ s^{-1}}$)",
}
Forcing data summary:
  Air temperature range: -5.7 to 28.8 C
  Wind speed range: 0.2 to 11.2 m/s
  Total rainfall: 821.0 mm

Tip

When resampling forcing data, call resample() first, then select columns. SUEWSForcing.resample() applies the correct aggregation method for each variable type (rain=sum, radiation=mean, instantaneous=last). Selecting columns first bypasses this logic.

# Resample to hourly for cleaner plots
df_plot_forcing = forcing_sliced.resample("1h")[list_var_forcing]

fig, axes = plt.subplots(6, 1, figsize=(10, 12), sharex=True)
for ax, var in zip(axes, list_var_forcing):
    df_plot_forcing[var].plot(ax=ax, legend=False)
    ax.set_ylabel(dict_var_label[var])
fig.tight_layout()
tutorial 01 quick start

3.1.4. Modify Input Parameters#

Modify surface parameters using the update_config method. This is the recommended approach for parameter changes.

# View original surface fractions
print("Original surface fractions:")
print(sim.state_init.loc[:, "sfr_surf"])

# Modify surface fractions using update_config
sim.update_config({"initial_states": {"sfr_surf": [0.1, 0.1, 0.2, 0.3, 0.25, 0.05, 0]}})

print("\nModified surface fractions:")
print(sim.state_init.loc[:, "sfr_surf"])
Original surface fractions:
ind_dim  (0,)  (1,)  (2,)  (3,)  (4,)  (5,)  (6,)
grid
1        0.43  0.38   0.0  0.02  0.03   0.0  0.14

Modified surface fractions:
ind_dim  (0,)  (1,)  (2,)  (3,)  (4,)  (5,)  (6,)
grid
1        0.43  0.38   0.0  0.02  0.03   0.0  0.14

3.1.5. Run Simulation#

With forcing data and initial conditions ready, run the SUEWS simulation. The run() method returns a SUEWSOutput object for convenient access.

output = sim.run()

print(f"Simulation complete: {len(output.times)} timesteps")
print(f"Output groups: {output.groups}")
print(f"Grids: {output.grids}")
Simulation complete: 26206 timesteps
Output groups: ['SUEWS', 'snow', 'BEERS', 'ESTM', 'EHC', 'DailyState', 'RSL', 'debug', 'SPARTACUS', 'STEBBS', 'NHood']
Grids: [1]

3.1.6. Explore Results: Statistics#

Access output variables directly as attributes of the output object. Use pandas’ built-in methods for quick statistical summaries.

# Access energy balance variables via SUEWS output group
df_suews = output.SUEWS

print("Energy balance statistics:")
print(f"  Net radiation (QN): mean = {df_suews['QN'].mean():.1f} W/m2")
print(f"  Sensible heat (QH): mean = {df_suews['QH'].mean():.1f} W/m2")
print(f"  Latent heat (QE): mean = {df_suews['QE'].mean():.1f} W/m2")
print(f"  Storage heat (QS): mean = {df_suews['QS'].mean():.1f} W/m2")

# Detailed statistics
df_suews.loc[:, ["QN", "QS", "QH", "QE", "QF"]].describe()
Energy balance statistics:
  Net radiation (QN): mean = 10.8 W/m2
  Sensible heat (QH): mean = 70.2 W/m2
  Latent heat (QE): mean = 16.1 W/m2
  Storage heat (QS): mean = 16.2 W/m2
var QN QS QH QE QF
count 26206.000000 26206.000000 26206.000000 26206.000000 26206.000000
mean 10.812637 16.161517 70.192227 16.051334 91.592441
std 108.168546 73.468075 54.617085 13.599827 35.105111
min -84.461392 -83.898495 -42.317077 2.126317 32.926395
25% -48.146256 -21.914420 25.558264 7.764825 58.287789
50% -36.461484 -13.508588 56.312100 12.411331 95.228474
75% 20.067901 23.287271 105.394943 19.379513 122.676977
max 510.523848 369.587104 287.046489 139.304467 161.325481


3.1.7. Explore Results: Weekly Energy Balance#

Plot the surface energy balance for one week to see diurnal patterns.

dict_var_disp = {
    "QN": r"$Q^*$",
    "QS": r"$\Delta Q_S$",
    "QE": "$Q_E$",
    "QH": "$Q_H$",
    "QF": "$Q_F$",
}

# Get grid ID for indexing
grid = output.grids[0]

# Select first week of available data for plotting
start_date = output.times[0]
end_date = start_date + pd.Timedelta(days=7)

fig, ax = plt.subplots(figsize=(10, 4))
(
    df_suews.loc[grid]
    .loc[start_date:end_date, ["QN", "QS", "QE", "QH", "QF"]]
    .rename(columns=dict_var_disp)
    .plot(ax=ax)
)
ax.set_xlabel("Date")
ax.set_ylabel(r"Flux ($\mathrm{W\ m^{-2}}$)")
ax.set_title("Surface Energy Balance (One Week)")
ax.legend()
plt.tight_layout()
Surface Energy Balance (One Week)

3.1.8. Temporal Resampling: Daily Patterns#

SUEWS runs at 5-minute intervals. Resampling to daily values reveals seasonal patterns.

rsmp_1d = df_suews.loc[grid].resample("1d")
df_1d_mean = rsmp_1d.mean()
df_1d_sum = rsmp_1d.sum()

# Plot daily mean energy balance
fig, ax = plt.subplots(figsize=(10, 4))
(
    df_1d_mean.loc[:, ["QN", "QS", "QE", "QH", "QF"]]
    .rename(columns=dict_var_disp)
    .plot(ax=ax)
)
ax.set_xlabel("Date")
ax.set_ylabel(r"Mean Flux ($\mathrm{W\ m^{-2}}$)")
ax.set_title("Daily Mean Surface Energy Balance")
ax.legend()
plt.tight_layout()
Daily Mean Surface Energy Balance
/home/runner/work/SUEWS/SUEWS/docs/source/tutorials/tutorial_01_quick_start.py:202: Pandas4Warning: 'd' is deprecated and will be removed in a future version, please use 'D' instead.
  rsmp_1d = df_suews.loc[grid].resample("1d")

3.1.9. Radiation and Water Balance#

Examine radiation components and water balance using daily aggregates.

dict_var_disp_full = {
    "QN": r"$Q^*$",
    "Kdown": r"$K_{\downarrow}$",
    "Kup": r"$K_{\uparrow}$",
    "Ldown": r"$L_{\downarrow}$",
    "Lup": r"$L_{\uparrow}$",
    "Rain": "$P$",
    "Irr": "$I$",
    "Evap": "$E$",
    "RO": "$R$",
    "TotCh": r"$\Delta S$",
}

fig, axes = plt.subplots(2, 1, figsize=(10, 6), sharex=True)

# Radiation balance
(
    df_1d_mean.loc[:, ["QN", "Kdown", "Kup", "Ldown", "Lup"]]
    .rename(columns=dict_var_disp_full)
    .plot(ax=axes[0])
)
axes[0].set_ylabel(r"Mean Flux ($\mathrm{W\ m^{-2}}$)")
axes[0].set_title("Radiation Balance")
axes[0].legend()

# Water balance
(
    df_1d_sum.loc[:, ["Rain", "Irr", "Evap", "RO", "TotCh"]]
    .rename(columns=dict_var_disp_full)
    .plot(ax=axes[1])
)
axes[1].set_xlabel("Date")
axes[1].set_ylabel("Water Amount (mm)")
axes[1].set_title("Surface Water Balance")
axes[1].legend()

plt.tight_layout()
Radiation Balance, Surface Water Balance

3.1.10. Monthly Patterns#

Aggregate to monthly values for seasonal overview using bar charts.

df_plot = df_suews.loc[grid].copy()
df_plot.index = df_plot.index.set_names("Month")
rsmp_1M = df_plot.shift(-1).dropna(how="all").resample("1ME")
df_1M_mean = rsmp_1M.mean()
df_1M_sum = rsmp_1M.sum()
# Convert index to period for better month display
df_1M_mean.index = df_1M_mean.index.to_period("M")
df_1M_sum.index = df_1M_sum.index.to_period("M")

# Month names for labels
name_mon = [x.strftime("%b") for x in rsmp_1M.groups]

fig, axes = plt.subplots(2, 1, sharex=True, figsize=(10, 6))

# Monthly energy balance
(
    df_1M_mean.loc[:, ["QN", "QS", "QE", "QH", "QF"]]
    .rename(columns=dict_var_disp)
    .plot(ax=axes[0], kind="bar")
)
axes[0].set_ylabel(r"Mean Flux ($\mathrm{W\ m^{-2}}$)")
axes[0].set_title("Monthly Surface Energy Balance")
axes[0].legend()

# Monthly water balance
(
    df_1M_sum.loc[:, ["Rain", "Irr", "Evap", "RO", "TotCh"]]
    .rename(columns=dict_var_disp_full)
    .plot(ax=axes[1], kind="bar")
)
axes[1].set_xlabel("Month")
axes[1].set_ylabel("Total Water Amount (mm)")
axes[1].set_title("Monthly Surface Water Balance")
axes[1].xaxis.set_ticklabels(name_mon, rotation=0)
axes[1].legend()

plt.tight_layout()
Monthly Surface Energy Balance, Monthly Surface Water Balance

3.1.11. Summary#

This tutorial demonstrated the essential SUEWS workflow:

  1. Load sample data using from_sample_data()

  2. Run simulation with run() which returns SUEWSOutput

  3. Explore results using the OOP interface for intuitive variable access

Key concepts covered:

  • Energy balance components: Q*, QH, QE, QS, QF

  • Radiation balance: Kdown, Kup, Ldown, Lup

  • Water balance: Rain, Evap, Runoff, Storage change

  • Temporal resampling: 5-min to hourly, daily, monthly

Next steps:

Total running time of the script: (0 minutes 24.063 seconds)

Gallery generated by Sphinx-Gallery