simpleLABmodel / app.py
minsikkim's picture
Upload 5 files
81e43fe verified
import streamlit as st
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from scipy.ndimage import label as nd_label
def run_lab_model(
width=30,
total_time=500,
channel_width=10,
avulsion_period=100,
lateral_migration_rate=5,
switch_step=None,
new_channel_width=None,
new_avulsion_period=None,
new_lateral_migration_rate=None
):
"""
Simple LAB Model: Uniform aggradation, lateral migration, and periodic avulsion.
Args:
width (int): Width of the basin cross-section (number of cells).
total_time (int): Total simulation time (timesteps). Represents depth (Y-axis).
channel_width (int): Width of the channel (sand) in cells.
avulsion_period (int): Number of timesteps between avulsion (channel jump).
lateral_migration_rate (float): Number of cells the channel moves per timestep (0~1).
Returns:
np.ndarray: 2D array of size (total_time, width).
0=none, 1=mud (floodplain), 2=sand (channel).
"""
# Initialize variables
stratigraphy = np.zeros((total_time, width), dtype=int)
surface_elevation = np.zeros(width)
channel_x = float(width) / 2 # Declare as float
np.random.seed() # System random
migration_dir = np.random.choice([-1, 1])
# Parameter change point (e.g., halfway through the simulation)
if switch_step is None:
switch_step = total_time // 2
# Example of parameters to change (received as arguments from sliders)
if new_avulsion_period is None:
new_avulsion_period = avulsion_period
if new_channel_width is None:
new_channel_width = channel_width
if new_lateral_migration_rate is None:
new_lateral_migration_rate = lateral_migration_rate
for t in range(total_time):
# --- Change parameters mid-simulation ---
if t == switch_step:
avulsion_period = new_avulsion_period
channel_width = new_channel_width
lateral_migration_rate = new_lateral_migration_rate
# --- 1. Uniform aggradation: Increase elevation of all cells by +1 ---
surface_elevation += 1
# --- 2. Channel lateral migration ---
channel_x += migration_dir * lateral_migration_rate
# Reverse direction and move once in the opposite direction if boundary is reached
if channel_x <= channel_width / 2:
channel_x = channel_width / 2
migration_dir *= -1
channel_x += migration_dir * lateral_migration_rate
elif channel_x >= width - channel_width / 2 - 1:
channel_x = width - channel_width / 2 - 1
migration_dir *= -1
channel_x += migration_dir * lateral_migration_rate
# --- 3. Record stratigraphy ---
new_layer_sed_type = np.full(width, 1) # 1 = Mud
start_x = max(0, int(np.floor(channel_x - channel_width / 2)))
end_x = min(width, int(np.ceil(channel_x + channel_width / 2)))
new_layer_sed_type[start_x:end_x] = 2 # 2 = Sand
stratigraphy[t, :] = new_layer_sed_type
# --- 4. Avulsion: Random channel jump every avulsion period ---
if (t + 1) % avulsion_period == 0:
channel_x = float(np.random.uniform(channel_width / 2, width - channel_width / 2))
migration_dir = np.random.choice([-1, 1])
return stratigraphy, switch_step
def analyze_stratigraphy(stratigraphy):
"""
Analyze the generated stratigraphy and output simple statistics.
"""
if stratigraphy.size == 0:
print("No data to analyze.")
return
# N/G Ratio (Net-to-Gross): Proportion of sand (reservoir) in total sediment
is_sand = (stratigraphy == 2)
net_to_gross = np.sum(is_sand) / stratigraphy.size
# Amalgamation Ratio (Vertical connectivity):
sand_cells_below_first_row = is_sand[1:, :]
if sand_cells_below_first_row.any():
sand_above_sand = is_sand[1:, :] & is_sand[:-1, :]
amalgamation_ratio = np.sum(sand_above_sand) / np.sum(sand_cells_below_first_row)
else:
amalgamation_ratio = 0
labeled_array, num_features = nd_label(is_sand)
print(f"--- Analysis Results ---")
print(f"Net-to-Gross (N/G) Ratio: {net_to_gross:.2%}")
print(f"Amalgamation Ratio (Vertical connectivity): {amalgamation_ratio:.2%}")
print(f"Total Sand Bodies (Number of connected sand regions): {num_features}")
print(f"------------------")
def plot_stratigraphy(stratigraphy, title, switch_step=None):
colors = ['#8B4513', '#FFD700']
cmap = ListedColormap(colors)
fig = plt.figure(figsize=(10, 6))
plt.imshow(stratigraphy, aspect='auto', cmap=cmap, interpolation='none')
plt.title(title)
plt.xlabel("Basin Width (cells)")
plt.ylabel("Time / Depth (timesteps)")
plt.gca().invert_yaxis()
if switch_step is not None:
plt.axhline(switch_step, color='red', linewidth=2, linestyle='--', label='Scenario Switch')
handles = [plt.Rectangle((0,0),1,1, color=colors[0]),
plt.Rectangle((0,0),1,1, color=colors[1])]
labels = ["Floodplain (Mud)", "Channel (Sand)"]
plt.legend(handles, labels, loc='upper right')
plt.show()
st.title("LAB Model Alluvial Stratigraphy Simulator")
# Sidebar: Parameter input
width = st.sidebar.slider("Basin Width (cells)", 10, 30, 20)
total_time = st.sidebar.slider("Total Time Steps", 100, 200, 100)
channel_width = st.sidebar.slider("Channel Width (cells)", 1, width//2, 5)
avulsion_period = st.sidebar.slider("Avulsion Period (timesteps)", 1, 10, 7)
lateral_migration_rate = st.sidebar.slider("Lateral Migration Rate (cells/timestep)", 0.1, 2.0, 1.0)
# Scenario switch parameters
switch_step = st.sidebar.slider("Scenario Switch Step", 1, total_time-1, total_time//2)
new_channel_width = st.sidebar.slider("New Channel Width", 1, width//2, 2)
new_avulsion_period = st.sidebar.slider("New Avulsion Period", 1, 10, 3)
new_lateral_migration_rate = st.sidebar.slider("New Lateral Migration Rate", 0.1, 2.0, 2.0)
if st.button("Run Simulation"):
# Run simulation
strat, switch_step_val = run_lab_model(
width=width,
total_time=total_time,
channel_width=channel_width,
avulsion_period=avulsion_period,
lateral_migration_rate=lateral_migration_rate,
switch_step=switch_step,
new_channel_width=new_channel_width,
new_avulsion_period=new_avulsion_period,
new_lateral_migration_rate=new_lateral_migration_rate
)
# Apply values after parameter change point
# switch_step, new_channel_width, etc., are already handled within run_lab_model
# Output analysis results (before/after parameter change)
st.subheader("Simulation Results")
def analyze_segment(segment, label):
is_sand = (segment == 2)
net_to_gross = np.sum(is_sand) / segment.size
sand_cells_below_first_row = is_sand[1:, :]
if sand_cells_below_first_row.any():
sand_above_sand = is_sand[1:, :] & is_sand[:-1, :]
amalgamation_ratio = np.sum(sand_above_sand) / np.sum(sand_cells_below_first_row)
else:
amalgamation_ratio = 0
labeled_array, num_features = nd_label(is_sand)
return {
"Label": label,
"Net-to-Gross (N/G) Ratio": f"{net_to_gross:.2%}",
"Amalgamation Ratio": f"{amalgamation_ratio:.2%}",
"Total Sand Bodies": num_features
}
# Analyze results
overall_results = analyze_segment(strat, "Overall Results")
before_results = analyze_segment(strat[:switch_step_val, :], "Before Parameter Switch")
after_results = analyze_segment(strat[switch_step_val:, :], "After Parameter Switch")
# Display plot
fig, ax = plt.subplots(figsize=(10, 6))
cmap = ListedColormap(['#8B4513', '#FFD700'])
ax.imshow(strat, aspect='auto', cmap=cmap, interpolation='none')
ax.axhline(switch_step_val, color='red', linewidth=2, linestyle='--', label='Scenario Switch')
ax.set_title("LAB Simulation")
ax.set_xlabel("Basin Width (cells)")
ax.set_ylabel("Time / Depth (timesteps)")
ax.invert_yaxis()
handles = [plt.Rectangle((0,0),1,1, color='#8B4513'), plt.Rectangle((0,0),1,1, color='#FFD700')]
labels = ["Floodplain (Mud)", "Channel (Sand)"]
ax.legend(handles, labels, loc='upper right')
st.pyplot(fig)
# Display results in a table
st.table([overall_results, before_results, after_results])