Simulation Diagnostics#
pyiwfm can parse IWFM SimulationMessages.out files to extract structured
diagnostic information — message severities, convergence tracking, mass balance
errors, and spatial entity references.
Reading Simulation Messages#
Use SimulationMessagesReader to parse
a SimulationMessages.out file:
from pyiwfm.io.simulation_messages import SimulationMessagesReader
reader = SimulationMessagesReader("SimulationMessages.out")
result = reader.read()
print(f"Total messages: {len(result.messages)}")
print(f"Warnings: {result.warning_count}")
print(f"Errors: {result.error_count}")
print(f"Total runtime: {result.total_runtime}")
Filtering by Severity#
Messages are classified into four severity levels: MESSAGE, INFO,
WARN, and FATAL:
from pyiwfm.io.simulation_messages import MessageSeverity
warnings = result.filter_by_severity(MessageSeverity.WARN)
for msg in warnings[:5]:
print(f"[{msg.procedure}] {msg.text}")
Convergence Tracking#
The parser extracts per-timestep convergence iteration counts and residuals:
for rec in result.convergence_records[:5]:
print(f"Timestep {rec.timestep_index}: {rec.iteration_count} iterations, "
f"converged={rec.convergence_achieved}")
summary = result.get_convergence_summary()
print(f"Max iterations: {summary['max_iterations']}")
print(f"Avg iterations: {summary['avg_iterations']:.1f}")
Plotting convergence over time:
import matplotlib.pyplot as plt
dates = [rec.date for rec in result.convergence_records]
iters = [rec.iteration_count for rec in result.convergence_records]
fig, ax = plt.subplots(figsize=(12, 4))
ax.plot(range(len(iters)), iters, linewidth=0.5)
ax.set_xlabel("Timestep")
ax.set_ylabel("Iteration Count")
ax.set_title("Convergence Iterations Over Time")
plt.tight_layout()
plt.show()
Mass Balance Errors#
Mass balance error records track per-component errors at each timestep:
for rec in result.mass_balance_records[:5]:
print(f"Timestep {rec.timestep_index} [{rec.component}]: "
f"error={rec.error_value:.4f}, {rec.error_percent:.2f}%")
Spatial Summary#
Map message counts to spatial entities (nodes, elements, reaches) for identifying problem areas:
spatial = result.get_spatial_summary()
# Top 5 nodes with most messages
node_counts = spatial.get("nodes", {})
top_nodes = sorted(node_counts.items(), key=lambda x: x[1], reverse=True)[:5]
for node_id, count in top_nodes:
print(f"Node {node_id}: {count} messages")
If you have a loaded mesh, convert to a GeoDataFrame for GIS analysis:
gdf = result.to_geodataframe(model.grid)
gdf.to_file("diagnostics.gpkg", driver="GPKG")
Web Viewer Diagnostics Tab#
The web viewer includes a Diagnostics tab that provides an interactive interface for exploring simulation diagnostic data:
Messages table: Filterable by severity with pagination
Convergence chart: Iteration counts plotted over simulation timesteps
Mass balance error chart: Per-component error timeseries
Spatial summary: Message counts overlaid on the model mesh
Launch the viewer and navigate to the Diagnostics tab:
pyiwfm viewer --model-dir /path/to/model
The diagnostics data is served by the /api/diagnostics/ endpoints:
GET /api/diagnostics/messages— Paginated message list with severity filteringGET /api/diagnostics/convergence— Convergence iteration timeseriesGET /api/diagnostics/mass-balance— Mass balance error timeseries by componentGET /api/diagnostics/summary— Combined diagnostics summaryGET /api/diagnostics/spatial-summary— Spatial message counts for map overlay