In lecture, we learned that risk emerges from the interaction of hazard, exposure, and vulnerability. Today, we’ll implement the mathematical machinery that connects them: convolution.
Convolution is how we transform a distribution of hazards into a distribution of damages. This is the core computational technique behind flood risk scores, insurance pricing, and cost-benefit analysis.
Learning Objectives:
Understand convolution as the bridge between hazard and damage distributions
Implement convolution both analytically (simple case) and via Monte Carlo
Work with the GEV distribution for extreme events
Apply a depth-damage function to compute flood losses
Extend analysis to a community of 100 houses
Get introduced to the SimOptDecisions.jl framework
2 Before Lab
Complete these steps before coming to lab. Read through the entire notebook so you understand what we’ll be doing.
2.1 Setup
Julia uses packages to make code reusable and modular. Run this block to install all required packages – the first time may be slow while everything is installed and compiled.
The .* does element-wise multiplication; sum() adds them up
Expected Annual Damage: $2900.0
This is convolution: we’ve transformed a hazard distribution (flood probabilities) into a damage distribution and computed its expected value.
TipYour Response
A homeowner asks: “My expected damage is only $2,900/year, so floods aren’t a big deal, right?”
What would you tell them? Think about what expected value hides.
Replace this text with your answer.
3 During Lab
Work through these sections during the lab session.
3.1 Monte Carlo Convolution
With continuous distributions and nonlinear damage functions, analytical convolution becomes intractable. Monte Carlo simulation offers a flexible alternative.
3.1.1 The GEV Distribution
The Generalized Extreme Value (GEV) distribution models annual maximum flood stages. It has three parameters:
μ (location): roughly the “typical” annual maximum
normalization=:pdf scales the histogram so the total area equals 1
2
Overlay the true PDF to show our samples match the distribution
Figure 3: Monte Carlo samples from the flood stage distribution, normalized as a PDF.
3.1.4 A Depth-Damage Function
Now we need a function that converts flood depth to damage. We’ll use a logistic (S-shaped) curve: damage starts slowly, increases rapidly in the middle range, then saturates.
Type parameter ensures all inputs have consistent floating-point type
2
Flood depth = water level minus floor elevation (can’t be negative)
3
No damage below 1 ft of water inside the house (furniture above floor, etc.)
4
Total loss at 7 ft of water
5
Logistic curve centered at midpoint between threshold and saturation
6
Standard logistic function formula
Let’s visualize this damage function:
let house_elev =2.0 house_val =100_000.0 flood_stages =range(0, 12; length=200) damages = [logistic_damage(s, house_elev, house_val) for s in flood_stages] fig =Figure() ax =Axis( fig[1, 1]; xlabel="Flood Stage (ft)", ylabel="Damage (\$)", title="Damage Function (House at 2 ft elevation)" )lines!(ax, flood_stages, damages; linewidth=2)vlines!(ax, [house_elev]; color=:gray, linestyle=:dash, label="House floor")axislegend(ax; position=:lt) figend
1
List comprehension: apply the function to each flood stage
2
Vertical line showing where the house floor is
Figure 4: Logistic depth-damage function for a house at 2 ft elevation.
3.1.5 Convolution via Monte Carlo
Now we convolve the hazard samples through the damage function. This transforms our distribution of flood stages into a distribution of damages.
house_elevation =2.0# feethouse_value =100_000.0# dollarsdamage_samples = [logistic_damage(h, house_elevation, house_value) for h in hazard_samples]
1
Apply the damage function to each of the 10,000 flood stage samples
Again, we plot as a normalized histogram (probability density). Notice the spike at $0 (years with no damage) and the long right tail.
let fig =Figure() ax =Axis( fig[1, 1]; xlabel="Annual Damage (\$)", ylabel="Density", title="Distribution of Annual Damages" )hist!(ax, damage_samples; bins=50, normalization=:pdf) figend
Figure 5: Distribution of annual damages from Monte Carlo convolution, shown as a PDF.
3.1.6 Risk Statistics
ead_mc =mean(damage_samples)prob_any_damage =mean(damage_samples .>0)prob_severe =mean(damage_samples .>50_000)println("Expected Annual Damage (Monte Carlo): \$$(round(ead_mc; digits=2))")println("Probability of any damage: $(round(prob_any_damage *100; digits=1))%")println("Probability of damage > \$50k: $(round(prob_severe *100; digits=1))%")
1
Mean of samples estimates the expected value
2
Fraction of samples with positive damage
3
Fraction of samples exceeding $50,000
Expected Annual Damage (Monte Carlo): $48917.84
Probability of any damage: 94.1%
Probability of damage > $50k: 45.9%
3.2 Structuring Analysis with SimOptDecisions
Now let’s use the SimOptDecisions.jl framework to structure our analysis. This framework helps organize risk analysis by clearly separating:
Config: Fixed parameters (house value, house elevation)
Scenario: One state of the world (one flood event)
include("house_elevation.jl")
1
Load type definitions from a separate file
The house_elevation.jl file defines:
HouseConfig: holds house value and base elevation
FloodScenario: represents ONE flood event (one water level)
depth_damage(): logistic depth-damage function
compute_damage(): computes dollar damage for a house given a flood scenario
Key insight: A FloodScenario represents a single state of the world – one sampled water level. It is NOT a probability distribution. We create many scenarios by sampling from the hazard distribution.
3.2.1 Visualizing the Depth-Damage Function
let depths =range(-2, 12; length=200) damages = [depth_damage(Float64(d)) for d in depths] fig =Figure() ax =Axis( fig[1, 1]; xlabel="Flood Depth Above First Floor (ft)", ylabel="Damage Ratio", title="Depth-Damage Function" )lines!(ax, depths, damages; linewidth=2)vlines!(ax, [0]; color=:gray, linestyle=:dash, label="Floor level") figend
Figure 6: Logistic depth-damage function from SimOptDecisions.
Create a configuration object with keyword arguments
Now we create a FloodScenario for each sampled water level and compute damage:
scenarios = [FloodScenario(h) for h in hazard_samples]house_damages = [compute_damage(my_house, s) for s in scenarios]
1
Wrap each water level sample in a FloodScenario object
2
Compute damage for each scenario using our house configuration
let fig =Figure() ax =Axis( fig[1, 1]; xlabel="Annual Damage (\$)", ylabel="Density", title="Damage Distribution for House at 4 ft Elevation" )hist!(ax, house_damages; bins=50, normalization=:pdf) figend
Figure 7: Distribution of annual damages for a single house at 4 ft elevation.
ead_house =mean(house_damages)prob_damage_house =mean(house_damages .>0)prob_severe_house =mean(house_damages .>50_000)println("Expected Annual Damage: \$$(round(ead_house; digits=2))")println("Probability of any damage: $(round(prob_damage_house *100; digits=1))%")println("Probability of damage > \$50k: $(round(prob_severe_house *100; digits=1))%")
Expected Annual Damage: $63915.29
Probability of any damage: 63.5%
Probability of damage > $50k: 36.7%
3.3 Community Risk Analysis
Now let’s extend our analysis to a community of 100 houses at different elevations. This represents a “city on a wedge” where houses closer to the water are at lower elevations.
3.3.1 Setting Up the Community
n_houses =100house_elevations =rand(Uniform(3.0, 12.0), n_houses)house_value_community =250_000.0houses = [HouseConfig(house_value=house_value_community, base_elevation=e) for ein house_elevations]
1
Random elevations between 3 and 12 feet
2
All houses have the same value for simplicity
3
Create a vector of HouseConfig objects
3.3.2 Computing EAD for Each House
Key insight: All houses experience the same storm (same hazard samples). The only difference is their elevation.
expected_damages =zeros(n_houses)for i in1:n_houses damages_i = [compute_damage(houses[i], s) for s in scenarios] expected_damages[i] =mean(damages_i)end
1
Pre-allocate a vector to store results
2
Loop over each house
3
Compute damage for all 10,000 scenarios
4
Store the mean (expected) damage
let fig =Figure() ax =Axis( fig[1, 1]; xlabel="House Elevation (ft)", ylabel="Expected Annual Damage (\$)", title="EAD vs. Elevation for 100 Houses" )scatter!(ax, house_elevations, expected_damages; markersize=8) figend
Figure 8: Expected annual damage decreases nonlinearly with house elevation.
TipYour Response
Why is the relationship between elevation and expected damage nonlinear? What causes the steep drop-off as elevation increases?
Replace this text with your explanation.
3.3.3 Community-Level Damage Distribution
To understand total community risk, we compute total damage for each Monte Carlo sample:
total_damages =zeros(n_samples)for j in1:n_samples scenario_j = scenarios[j] total_damages[j] =sum(compute_damage(houses[i], scenario_j) for i in1:n_houses)end
1
One total damage value per scenario (possible year)
2
Loop over scenarios (not houses!)
3
Sum damage across all 100 houses for this scenario
let fig =Figure() ax =Axis( fig[1, 1]; xlabel="Total Community Damage (\$)", ylabel="Density", title="Distribution of Annual Community Damages" )hist!(ax, total_damages; bins=50, normalization=:pdf) figend
Figure 9: Distribution of total annual damages across the community.
total_ead =mean(total_damages)println("Total Community EAD: \$$(round(total_ead /1e6; digits=3)) million")println("Sum of individual EADs: \$$(round(sum(expected_damages) /1e6; digits=3)) million")
Total Community EAD: $2.841 million
Sum of individual EADs: $2.841 million
TipYour Response
Why are the total community EAD and the sum of individual EADs equal (or nearly equal)? Would they still be equal if we used different hazard samples for each house?
Replace this text with your answer.
3.4 The SimOptDecisions Framework
The SimOptDecisions.jl package provides a structured way to think about decisions under uncertainty. Here’s the key vocabulary:
Term
What it means
In this lab
Config
Fixed parameters that don’t change
House value, base elevation
Scenario
One state of the world
One flood water level
State
Evolving conditions over time
(Not used today)
Action
A decision
(Not used today)
Policy
A decision rule
(Not used today)
Today we focused on risk analysis: understanding the distribution of damages given uncertainty in hazards.
In future labs, we’ll use SimOptDecisions to:
Add State to track conditions that change over time (like sea level rise)
Add Actions and Policies to represent decisions (like how high to elevate)
Compare different policies across many scenarios
3.5 Reflection Questions
Answer these questions in a few sentences each:
TipQuestion 1
How does uncertainty in the hazard distribution propagate to uncertainty in damages through convolution?
Replace this text with your answer.
TipQuestion 2
Why is expected damage NOT equal to damage at the expected flood stage? (Hint: think about Jensen’s inequality and the shape of the damage function.)
Replace this text with your answer.
TipQuestion 3
What assumptions did we make that might not hold in reality? List at least three and explain why they matter.
Replace this text with your answer.
3.6 Summary
In this lab, you learned to:
Convolve hazard distributions through damage functions analytically and via Monte Carlo
Work with the GEV distribution for extreme event modeling
Apply depth-damage functions to translate hazard into losses
Analyze community-level risk with 100 houses at varying elevations
Structure risk analysis using SimOptDecisions.jl (Config + Scenario)
The key insight: convolution is the mathematical bridge between “how bad could the flood be?” and “how much will it cost?”
3.7 Submission
Push your completed notebook to GitHub
If the formatter creates a pull request, approve it and re-render
Submit the link to your repository on Canvas
Checklist
Before submitting, make sure you have completed these tasks. Each task involves modifying existing code, not writing from scratch.