Mobile Testbed Port#
1.1 Background#
Mobile, Alabama is a metropolitan city located in southwest Alabama (see figures below). The City of Mobile serves as the county seat for Mobile County; one of only two Alabama counties located on the Gulf Coast. According to the 2020 US Census, the population within the City of Mobile was 187,041 making it the fourth most populous city in Alabama, after Huntsville, Birmingham and Montgomery. The City, Urban and Metropolitan areas are 180 mi2, 222.8 mi2 and 1,644 mi2, respectively. The average elevation is +10 ft MSL. The median household income is approximately 71% of the national average. The Mobile metropolitan area consists of 430,197 residents. Baldwin County on the east side of Mobile Bay adds an additional 223,000 residents (est. 2019) to the metropolitan area, many of whom work in Mobile, bringing the total to approximately 653,000. As of 2011, approximately 1.2 million people lived within a 60-mile radius of Mobile. Major Utility Providers include Alabama Power, Mobile Area Water and Sewer System, Spire Natural Gas and various telecommunication companies - AT&T, Comcast, Verizon, etc.
Mobile’s waterfront location has long played an important role in its history. Situated along the Mobile River and adjacent to the Mobile Bay estuary, Mobile’s waterfront is accessible by deep draft ships through the Mobile Ship Channel, a Federally authorized navigation channel that connects Mobile River to the Gulf Intracoastal Waterway and the Gulf of Mexico. The deep water access supports robust waterfront commerce in the form of private and State port facilities, numerous ship manufacturers and service providers, industries supporting the offshore oil and gas industry, a cruise terminal, and many other activities.
1.2 General Information#
Mobile is a commercial and industrial hub on the Gulf Coast, supporting an intermodal network of sea, air, rail, tunnel, and surface transportation infrastructure. Notable industrial facilities include the bulk and container terminals managed by the Alabama State Port Authority, the Alabama Cruise Terminal and the 1,650-acre Mobile Aeroplex at Brookley (previously Brookley Air Force Base), which supports a major Airbus final assembly line facility for the Airbus A220 and A320 commercial aircraft. In addition to the port, shipbuilding began to make a major comeback in Mobile in 1999 with the founding of Austal USA. These industrial facilities are served by four Class I railroads (Canadian National Railway (CNR), CSX Transportation (CSX), the Kansas City Southern Railway (KCS), and the Norfolk Southern Railway (NS), three Interstates 10, 65 and 165, the confluence of three major state highways (90, 98, 45) and the George Wallace and Bankhead tunnels under the Mobile River, and. The George Wallace twin tunnels, which support daily traffic of over 70,000 vehicles. This intermodal infrastructure has a tremendous economic impact on the City and State. The Port of Mobile is the 12th largest port in the US in terms of tonnage, and the Alabama State Port Authority adds an estimated economic value of over $25 billion/year and supports, directly and indirectly, over 154,000 jobs. Mobile also serves the central Gulf Coast as a regional center for medicine.

1.3 Purpose of Mobile, AL Testbed#
Resilience assessment of coastal industrial city of moderate size susceptible to changing climate
Multiple hazards and vulnerabilities:
Tropical cyclones – winds, storm surge
Coastal flooding
Precipitation extremes
Sea Level Rise (SLR)
Resilience modeling of intermodal transportation hub:
Seaport
Rail
Roads
Airport
2.1 The Port of Mobile#
The Port of Mobile is an important node in Alabama’s transport network and the South-Central Area of the U.S. The Port contributes $25 billion in annual financial activity to the State of Alabama, creating direct and indirect jobs for around 153 thousand people (USACE, 2019), and handles 59 million tons annually in goods and commodities. The Port covers an area of around 16 km 2 (Asdd, 2020) at the north end of Mobile Bay, which has a surface area of 1070 square kilometers and an average depth of around 3 m (USACE, 2019). A channel 12.2 m in depth is maintained by the USACE; keeping the channel clear following extreme hurricanes is a necessary part of the port’s resilience management. The following description lays the basis for the FT model of port functionality, described subsequently.
The Port of Mobile has several terminals, some of which are owned by the Alabama State Port Authority (ASPA) or by private companies, while others are partially owned by either of the two sectors (USACE, 2019).
2.2 Resilience of seaports under climate scenarios#
When a tropical cyclone strikes a seaport, damage to harbor infrastructure can lead to a drop in functionality (i.e., lack of inoperability), which may disrupt port operations and lead to enormous financial losses. This loss in functionality, in turn, can spread to industries dependent on the port across distribution networks and have negative effects on the local, national and international markets. Therefore, seaport facilities must recover quickly and efficiently to pre-disaster rates after a severe environmental incident such as a hurricane.
Fault tree analysis (FTA) is a risk assessment technique based on logical analysis that has been used in different industries, beginning with nuclear power plants (Rasmussen, 1981), and later with community food security systems (Nozhati et al., 2019), hospitals (Hassan and Mahmoud, 2019), and schools (Hassan et al., 2020). The probability of the top event is determined from a “bottoms-up” analysis, in which the probability of failure for each basic event is computed from basic data or from engineering analysis and the probability of the intermediate and top events are calculated from the basic events. Failure is broadly categorized by whether an event is inaccessible, unavailable, unacceptable, or non-functional. Assuming a parallel or series model of system performance, the probability of occurrence of the higher-level event can be calculated per Eq. (1) and Eq. (2) for “AND” gate and “OR” gate, respectively.
The functionality of the Port of Mobile is measured by the damage to the exposed physical components of the seaport due to the SLR/hurricane scenarios considered. Depth-damage curves for flooding in container and bulk terminals (Tebodin, 1998) are used to identify the damage associated with each basic event in the FTA.

2.3 More Information about Mobile, AL Testbed#
Hazard models and datasets used in this testbed come from:
Adhikari P, Abdelhafez MA, Dong Y, Guo Y, Mahmoud HN and Ellingwood BR (2021) Achieving Residential Coastal Communities Resilient to Tropical Cyclones and Climate Change. Front. Built Environ. 6:576403. https://doi.org/10.3389/fbuil.2020.576403
Abdelhafez M.A., Ellingwood B., Mahmoud H. (2021) Vulnerability of seaports to hurricanes and sea level rise in a changing climate: A case study for Mobile, AL. Coast. Eng. , Article 103884, https://doi.org/10.1016/j.coastaleng.2021.103884
2.4 Storm surge coupled with the projected sea level rise scenarios#
Six hurricane scenarios were identified in this testbed. NOAA has a gauge station at Mobile State Docks (ID: 8737048), which is near the Port of Mobile, to capture daily water levels and tides. Simulation outputs comparing the storm surge water levels for the six scenarios considered at the Mobile State Docks station are shown in the below figure (b).

2.5 Models of port functionality (Fault tree analysis)#
"""
Created on Tue Feb 25 12:47:02 2020
@author: Mohamed Abdelhafez (CSU)
"""
# Import necessary libraries
import geopandas as gpd
import pandas as pd
import numpy as np
from scipy.interpolate import interp1d
import matplotlib.pyplot as plt
import pylab
import itertools
from numpy import array
import plotly as ply
import plotly.graph_objs as go
import plotly.io as pio
# Reading scenarioes files
files_and_sheets = [
('data/WL_SKN2005.xlsx', 'wl_MSD_3m_kat'), # Natural Katrina in 2005
('data/WL_SKN2100_4.5.xlsx', 'wl_MSD_3m_kat'), # Katrina + NOAA intermediate SLR
('data/WL_SKN2100_8.5.xlsx', 'wl_MSD_3m_kat'), # Katrina + NOAA extreme SLR
('data/WL_SKS2005.xlsx', 'wl_MSD_3m_kat'), # Shifted Katrina
('data/WL_SKS2100_4.5.xlsx', 'wl_MSD_3m_kat'), # Shifted Katrina + NOAA intermediate SLR
('data/WL_SKS2100_8.5.xlsx', 'wl_MSD_3m_kat') # Shifted Katrina + NOAA extreme SLR
]
# Dictionary to store all transposed water level data with 'Date'
WL_k_transpose = {}
# Loop through files and sheets
for idx, (file, sheet) in enumerate(files_and_sheets):
try:
xls = pd.ExcelFile(file)
data = xls.parse(sheet) # Parse the sheet
# Extract the 'Date' and 'Water Level' columns (assuming their positions)
dates = pd.to_datetime(data.iloc[:, 0], errors='coerce') # First column as 'Date'
water_levels = data.iloc[:, 2] # third column as 'WL_Final'
# Combine Date and Water Level into a single DataFrame
scenario_data = pd.DataFrame({
'Date': dates,
'Water Level (m)': water_levels
})
# Transpose only the water levels for functionality compatibility
transposed_water_levels = water_levels.to_frame().transpose()
# Store both the full scenario data and the transposed water levels
WL_k_transpose[idx] = {
'Scenario Data': scenario_data, # Original data with Date
'Transposed Water Levels': transposed_water_levels # Only water levels (transposed)
}
print(f"Scenario {idx} ({file}) successfully processed.")
except Exception as e:
print(f"Error processing scenario {idx} ({file}): {e}")
# Accessing data for a specific scenario
print("First scenario (full data):")
print(WL_k_transpose[0]['Scenario Data'].head())
print("\nFirst scenario (transposed water levels):")
print(WL_k_transpose[0]['Transposed Water Levels'])
Scenario 0 (data/WL_SKN2005.xlsx) successfully processed.
Scenario 1 (data/WL_SKN2100_4.5.xlsx) successfully processed.
Scenario 2 (data/WL_SKN2100_8.5.xlsx) successfully processed.
Scenario 3 (data/WL_SKS2005.xlsx) successfully processed.
Scenario 4 (data/WL_SKS2100_4.5.xlsx) successfully processed.
Scenario 5 (data/WL_SKS2100_8.5.xlsx) successfully processed.
First scenario (full data):
Date Water Level (m)
0 2005-08-24 18:00:00 0.04
1 2005-08-24 19:00:00 0.04
2 2005-08-24 20:00:00 0.04
3 2005-08-24 21:00:00 0.04
4 2005-08-24 22:00:00 0.04
First scenario (transposed water levels):
0 1 2 3 4 5 6 7 8 9 ... \
WL_Final 0.04 0.04 0.04 0.04 0.04 0.04 0.04 0.04 0.04 0.04 ...
123 124 125 126 127 128 129 130 131 132
WL_Final NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
[1 rows x 133 columns]
2.5.1 Importing the port data#

# Load shapefile for docks
fp = "data/Port_of_Mobile_nonzero.shp"
docks = gpd.read_file(fp) #41 Berth (dock) location
# Load shapefile for houses
# Note: That part can be updated using NSI data or Mobile Buildings Inventory
hP = "data/Houses_Mobile_Baldwin_NonZero.shp"
houses = gpd.read_file(hP)
#Random selection of 640 employees houses out of 4565
#selected_houses = houses.sample(640)
#Randomly selected 640 houses saved into shapefile:
#selected_houses.to_file("selected_houses.shp")
# Note: Selection of houses was previously done and saved as another shapefile
# Load the selected houses shapefile
hP_final = "data/selected_houses.shp"
houses_final = gpd.read_file(hP_final)
# Extract the minimum elevation of each house (column index 5)
heig_houses = houses_final[houses_final.columns[5]]
# Reset index for further operations
heig_houses_final = heig_houses.reset_index(drop=True)
# Fill missing values in 'commoditie' column with "No commoditie"
docks.fillna(value=np.nan, inplace=True)
docks["commoditie"].fillna("No commoditie", inplace = True)
# Ensure data types are consistent for certain columns
#note: docks are the "Berth" in previous figure
docks = docks.astype({"commoditie":'str', "purpose":'str',"owners":'str'})
# Filter docks that belong to Alabama Port Authorities
docks_final=docks[docks['owners'].str.contains('State', na=False)]
docks_final.to_file("docks_final2.shp")
# Reload docks data after edits in external software (ArcGIS)
fp_final = "data/docks_final5.shp"
docks_final3 = gpd.read_file(fp_final)
# Extract height of each dock from the reloaded data
heig_docks=docks_final3[docks.columns[39]]
/tmp/ipykernel_181021/3823011615.py:27: FutureWarning: A value is trying to be set on a copy of a DataFrame or Series through chained assignment using an inplace method.
The behavior will change in pandas 3.0. This inplace method will never work because the intermediate object on which we are setting values always behaves as a copy.
For example, when doing 'df[col].method(value, inplace=True)', try using 'df.method({col: value}, inplace=True)' or df[col] = df[col].method(value) instead, to perform the operation inplace on the original object.
docks["commoditie"].fillna("No commoditie", inplace = True)
2.5.2 Importing the water-depth functions for the port components#

# Load multiple Excel sheets into separate DataFrames
#Those DataFrames represent water depth-damage funtions for different component types in the port
file = 'data/BulkTerminals.xlsx'
xl = pd.ExcelFile(file)
bt = xl.parse('Sheet2')
file = 'data/PublicUtillities.xlsx'
x2 = pd.ExcelFile(file)
pu = x2.parse('Sheet2')
file = 'data/Vehicles_cars.xlsx'
x3 = pd.ExcelFile(file)
veh_car = x3.parse('Sheet2')
file = 'data/Vehicles_trucks.xlsx'
x4 = pd.ExcelFile(file)
veh_truck = x4.parse('Sheet2')
file = 'data/Container terminals Products.xlsx'
x6 = pd.ExcelFile(file)
ct_prod = x6.parse('Sheet2')
file = 'data/Bulk terminals Products.xlsx'
x7 = pd.ExcelFile(file)
bt_prod = x7.parse('Sheet2')
dl= bt.iloc[4:14,1:2].squeeze() #Depth level
build= 1-bt.iloc[4:14,2:3].squeeze() #Buildings damage
ins_bt= 1-bt.iloc[4:14,3:4].squeeze() #Installation_Builings damage
ins_pu= 1-pu.iloc[4:14,3:4].squeeze() #Installation_Public_Utilities damage
ins_veh_car= 1-veh_car.iloc[4:14,2:3].squeeze() #Vehicls damage
ins_veh_truck= 1-veh_truck.iloc[4:14,2:3].squeeze() #trucks damage
prod_ct= 1-ct_prod.iloc[4:14,2:3].squeeze() #Container terminal products damage
prod_bt= 1-bt_prod.iloc[4:14,2:3].squeeze() #Bulk terminal products damage
2.5.3 Running the Fault tree analysis#
The fault tree analysis will be ran for each component (R1..R21) and for the whole port to calculate the port functionality.

scenario_names = {
0: "Natural Katrina in 2005",
1: "Katrina + NOAA intermediate SLR",
2: "Katrina + NOAA extreme SLR",
3: "Shifted Katrina",
4: "Shifted Katrina + NOAA intermediate SLR",
5: "Shifted Katrina + NOAA extreme SLR"
}
# Loop through each scenario in the dictionary WL_k_transpose
for scenario_idx, scenario_data in WL_k_transpose.items():
scenario_name = scenario_names[scenario_idx]
full_data = scenario_data['Scenario Data'] # Full data with Date and Water Level
WL_k = scenario_data['Transposed Water Levels'].iloc[0, :].to_numpy() # Water levels as NumPy array
# Ensure 'Date' is filled or interpolated
data = pd.DataFrame({
'Date': full_data['Date'],
'water level (m)': full_data['Water Level (m)']
})
# data['Date'].interpolate(method='time', inplace=True) # Handle missing dates
# Update dependent variables for the current scenario
dates_k = data[['Date']] # Extract dates
n_houses = len(heig_houses_final)
n_docks = len(heig_docks)
m_wlk = len(data)
print(f"Processing {scenario_name}")
R6= np.zeros((n_docks,len(WL_k))) #Structural component functionality
R7= np.zeros((n_docks,len(WL_k))) #Backup Structural functionality
R8= np.zeros((n_docks,len(WL_k))) #Warehousing storage functionality
R9= np.zeros((n_docks,len(WL_k))) #Warehousing contents functionality
R1= np.zeros((n_docks,len(WL_k))) #Quay cycle system functionality
R2= np.zeros((n_docks,len(WL_k))) #Rubber-tired gantries (RTG) functionality
R3= np.zeros((n_docks,len(WL_k))) #Rail-mounted gantries functionality
R14= np.zeros((n_docks,len(WL_k))) #Power line/Electricity sub-station functionality
R16= np.zeros((n_docks,len(WL_k))) #Liquid fuel system functionality
R17= np.zeros((n_docks,len(WL_k))) #Communications functionality
R19= np.zeros((n_docks,len(WL_k))) #Fire-fighting plant functionality
R20= np.zeros((n_docks,len(WL_k))) #Water utilities functionality
R18= np.zeros((n_docks,len(WL_k))) #Waste water system functionality
R11= np.zeros((n_docks,len(WL_k))) #Roadway system accessibility for cars
R12= np.zeros((n_docks,len(WL_k))) #Railway system accessibility
R13= np.zeros((n_docks,len(WL_k))) #Roadway system accessibility for trucks
R4= np.zeros((n_houses,len(WL_k))) #Main Stuff availability
R5= np.zeros((n_docks,len(WL_k))) #Alternative Stuff availability
R10= np.zeros((n_docks,len(WL_k))) #Temporary storage functionality
R15= np.zeros((n_docks,len(WL_k))) #Backup Power system functionality
r14_15= np.zeros((n_docks,len(WL_k)))
r20_21= np.zeros((n_docks,len(WL_k)))
r14_21= np.zeros((n_docks,len(WL_k))) #Port Utility Systems
r11_13= np.zeros((n_docks,len(WL_k))) #Accessibility
r2_3= np.zeros((n_docks,len(WL_k))) #Inter connection
r7_6= np.zeros((n_docks,len(WL_k))) #Building
r9_8= np.zeros((n_docks,len(WL_k))) #Wharehouse
r8_10= np.zeros((n_docks,len(WL_k))) #Wharehouse or backup
R10= np.zeros((n_docks,len(WL_k))) #Wharehouse backup
r6_10= np.zeros((n_docks,len(WL_k))) #port Buildings
R21= np.zeros((n_docks,len(WL_k))) #Backup water utility functionality
r1_21= np.zeros((n_docks,len(WL_k))) #SeaPort Functionality
r1_21_fun= np.zeros((n_docks,len(WL_k))) #SeaPort Functionality
# Compute house functionality
for o in range(n_houses):
R4[o, :] = interp1d((dl + heig_houses_final[o]), build, bounds_error=False, fill_value=1)(WL_k)
#To get the moving average of the 640 houses so that every 5 houses
#serve one dock ( moving average for a window of 5 )
R4_t1 = pd.DataFrame(data=R4)
R4_t1 = R4_t1.rolling(19).mean()
R4_t2 = R4_t1.iloc[::19, :]
R4_t3 = R4_t2.reset_index(drop=True)
R4_t5 = R4_t3.iloc[1:33, :].to_numpy()
# Compute dock functionality
for n in range(n_docks):
R6[n, :] = interp1d((dl + (0.3048 * heig_docks[n])), build, bounds_error=False, fill_value=1)(WL_k)
R8[n,:] = interp1d((dl+(0.3048*heig_docks[n])),build, bounds_error=False, fill_value=1)(WL_k)
R9[n,:] = interp1d((dl+(0.3048*heig_docks[n])),ins_bt, bounds_error=False, fill_value=1)(WL_k)
R10[n,:] = interp1d((dl+(0.3048*heig_docks[n])),prod_ct, bounds_error=False, fill_value=1)(WL_k)
R2[n,:] = interp1d((dl+(0.3048*heig_docks[n])),ins_bt, bounds_error=False, fill_value=1)(WL_k)
R3[n,:] = interp1d((dl+(0.3048*heig_docks[n])),ins_bt, bounds_error=False, fill_value=1)(WL_k)
R11[n,:] = interp1d((dl+(0.3048*heig_docks[n])),ins_veh_car, bounds_error=False, fill_value=1)(WL_k)
R13[n,:] = interp1d((dl+(0.3048*heig_docks[n])),ins_veh_truck, bounds_error=False, fill_value=1)(WL_k)
R14[n,:] = interp1d((dl+(0.3048*heig_docks[n])),ins_pu, bounds_error=False, fill_value=1)(WL_k)
R16[n,:] = interp1d((dl+(0.3048*heig_docks[n])),ins_pu, bounds_error=False, fill_value=1)(WL_k)
R17[n,:] = interp1d((dl+(0.3048*heig_docks[n])),ins_pu, bounds_error=False, fill_value=1)(WL_k)
R19[n,:] = interp1d((dl+(0.3048*heig_docks[n])),ins_pu, bounds_error=False, fill_value=1)(WL_k)
R20[n,:] = interp1d((dl+(0.3048*heig_docks[n])),ins_pu, bounds_error=False, fill_value=1)(WL_k)
R18[n,:] = interp1d((dl+(0.3048*heig_docks[n])),ins_pu, bounds_error=False, fill_value=1)(WL_k)
for m in range(m_wlk):
if (0.3048*heig_docks[n]) <= WL_k[m]-0.2: #To allow 0.2 m freeboard for railsection
R12[n,m] = 0
else:
R12[n,m] = 1
#Accessibility
r11_13[n,m] = R11[n,m]*R12[n,m]*R13[n,m]
for l in range(m_wlk):
if (0.3048*heig_docks[n]) < WL_k[l]: #To allow 1 m freeboard for load/unload
R1[n,l] = 0
else:
R1[n,l] = 1
# Compute Inter Connection
r2_3[n,:] = 1- ((1-R2[n,:])*(1-R3[n,:]));
#Compute Port Utility Systems
r14_15[n,:] = 1- ((1-R14[n,:])*(1-R15[n,:]))
r20_21[n,:] = 1- ((1-R20[n,:])*(1-R21[n,:]))
r14_21[n,:] = r14_15[n,:]*r20_21[n,:]*R16[n,:]*R17[n,:]*R18[n,:]*R19[n,:]
#Compute Port Buildings
r7_6[n,:] = 1- ((1-R6[n,:])*(1-R7[n,:]));
r9_8[n,:] = R9[n,:]*R8[n,:];
r8_10[n,:] = 1- ((1-r9_8[n,:])*(1-R10[n,:]));
r6_10[n,:] = r7_6[n,:]*r8_10[n,:];
# Compute sea port functionality
r1_21[n,:] = R1[n,:]*r2_3[n,:]*r6_10[n,:]*r11_13[n,:]*r14_21[n,:]*R4_t5[n,:]
r1_21_fun[n,:] = 1- r1_21[n,:]
R1_fta = r1_21.mean(axis=0)
R1_fta_fun = r1_21_fun.mean(axis=0)
# Plot functionality for each dock and the whole port
for j in range(n_docks):
plt.plot(dates_k, r1_21[j, :], color='gray', linewidth=0.1)
plt.plot(dates_k, R1_fta, color='red', linewidth=1)
plt.xlabel('Date')
plt.ylabel('Functionality')
plt.title(f'Scenario {scenario_name}: Fault Tree Analysis')
plt.xticks(rotation=90)
plt.show()
Processing Natural Katrina in 2005

Processing Katrina + NOAA intermediate SLR

Processing Katrina + NOAA extreme SLR

Processing Shifted Katrina

Processing Shifted Katrina + NOAA intermediate SLR

Processing Shifted Katrina + NOAA extreme SLR
