Probability of failure via Monte Carlo simulation - Infinite slope mechanism#

© 2024 Exneyder A. Montoya-Araque, Daniel F. Ruiz y Universidad EAFIT.

This notebook can be interactively run in Google - Colab.

Required modules and global setup for plots#

import os
import sys
import subprocess

if 'google.colab' in str(get_ipython()):
    print('Running on CoLab. Installing the required modules...')
    # subprocess.run('pip install ipympl', shell=True);
    subprocess.run('pip install pynewmarkdisp', shell=True);
    from google.colab import output, files
    output.enable_custom_widget_manager()
else:
    import tkinter as tk
    from tkinter.filedialog import askopenfilename

import numpy as np
import pandas as pd
import scipy as sp
import matplotlib as mpl
import matplotlib.pyplot as plt
from pynewmarkdisp.newmark import direct_newmark, plot_newmark_integration
from pynewmarkdisp.infslope import factor_of_safety, get_ky

# %matplotlib widget
%matplotlib inline
mpl.rcParams.update({
    "font.family": "serif",
    "font.serif": ["Computer Modern Roman", "cmr", "cmr10", "DejaVu Serif"],  # or 
    "mathtext.fontset": "cm",  # Use Computer Modern fonts for math
    "axes.formatter.use_mathtext": True,  # Use mathtext for axis labels
    "axes.unicode_minus": False,   # Use standard minus sign instead of a unicode character
})

# Create a folder where the records will be saved
workdir = os.getcwd()
records_dir = os.path.join(workdir, "records")
os.makedirs(f"{records_dir}", exist_ok=True)

Functions#

# Function to compute the parameters of the underlying normal distribution
def underlying_parameters(mu_X, sigma_X):
    mu_Y = np.log((mu_X**2) / np.sqrt(sigma_X**2 + mu_X**2))
    sigma_Y = np.sqrt(np.log((sigma_X**2 / mu_X**2) + 1))
    return mu_Y, sigma_Y

def random_sample_uniform(min, max, n, seed=None):
    rng = np.random.default_rng(seed)
    return rng.uniform(min, max, n)

def random_rample_normal(mu, sigma, n, seed=None):
    rng = np.random.default_rng(seed)
    return rng.normal(mu, sigma, n)

def random_sample_lognormal(mu, sigma, n, seed=None):
    mu_und, sigma_und = underlying_parameters(mu, sigma)
    rng = np.random.default_rng(seed)
    return rng.lognormal(mu_und, sigma_und, n)

def compute_confidence_intervals(data, confidence_level=0.95):
    # Prepare arrays for storing the results
    data_mean, data_upper, data_lower, n = [], [], [], []
    
    # Loop through growing subsets of the data
    for n_s in range(5, len(data) + 1):
        subset = data[:n_s]
        mean = np.mean(subset)
        std_dev = np.std(subset, ddof=1)
        # Calculate standard error
        standard_error = std_dev / np.sqrt(n_s)
        # Get t-value for the desired confidence interval
        t_value = sp.stats.t.ppf((1 + confidence_level) / 2., df=n_s - 1)
        # Calculate margin of error
        margin_of_error = t_value * standard_error
        # Store mean and confidence interval bounds
        data_mean.append(mean)
        data_upper.append(mean + margin_of_error)
        data_lower.append(mean - margin_of_error)
        n.append(n_s)
    # Convert lists to arrays for easier plotting and further use
    return np.array(data_mean), np.array(data_upper), np.array(data_lower), np.array(n)

def compute_pf_variation_vector(data, confidence_level=0.95):
    # Prepare arrays for storing the results
    pf_vector, n = [], []
    # Loop through growing subsets of the data
    for n_s in range(5, len(data) + 1):
        subset = data[:n_s]
        failed = np.zeros_like(subset, dtype=int)
        failed[subset < 1] = 1
        pf = np.sum(failed) / n_s
        pf_vector.append(pf)
        n.append(n_s)
    # Convert lists to arrays for easier plotting and further use
    return np.array(pf_vector),  np.array(n)

def compute_std_confidence_intervals(data, confidence_level=0.95):
    # Prepare arrays for storing the results
    std_mean, std_upper, std_lower, n = [], [], [], []
    
    alpha = 1 - confidence_level  # Significance level
    
    # Loop through growing subsets of the data
    for n_s in range(5, len(data) + 1):  # Start at 2 because CI is undefined for n_s = 1
        subset = data[:n_s]
        sample_std = np.std(subset, ddof=1)
        sample_var = sample_std**2
        # Get chi-square critical values
        chi2_lower = sp.stats.chi2.ppf(alpha / 2, df=n_s - 1)
        chi2_upper = sp.stats.chi2.ppf(1 - alpha / 2, df=n_s - 1)
        # Calculate the confidence interval for the standard deviation
        lower_bound = np.sqrt((n_s - 1) * sample_var / chi2_upper)
        upper_bound = np.sqrt((n_s - 1) * sample_var / chi2_lower)
        # Store standard deviation and confidence interval bounds
        std_mean.append(sample_std)
        std_upper.append(upper_bound)
        std_lower.append(lower_bound)
        n.append(n_s)
    # Convert lists to arrays for easier plotting and further use
    return np.array(std_mean), np.array(std_upper), np.array(std_lower), np.array(n)

Running a case#

Inputs#

# Mean values for a deterministic analysis
# ----------------------------------------

## Geometry
β = 35  # [°] - Slope angle - Plane inclination
d = 3.0  # [m] - Depth of the slip surface - Block height
d_w = 3.0  # [m] - Depth of the water table

## Material parameters
φ = 27  # [°] - Friction angle
c = 15  # [kPa] - Cohesion
γ = 19  # [kN/m³] - Unit weight

## Triggering factor
PGA = 0.15  # [g] - Peak ground acceleration



# Uncertainties
# -------------

## Geometry
β_min, β_max = 33, 37  # [°] - Minimum and maximum slope angle (uniform distribution)
d_min, d_max = 2.9, 3.1  # [m] - Minimum and maximum depth of the slip surface (uniform distribution)
d_w_min, d_w_max = d_min, d_max  # [m] - Minimum and maximum depth of the water table (uniform distribution)

## Material parameters
φ_cov = 0.1  # [-] - Coefficient of variation for the friction angle (normal distribution)
c_cov = 0.35  # [-] - Coefficient of variation for the cohesion (lognormal distribution)
γ_cov = 0.01  # [-] - Coefficient of variation for the unit weight (normal distribution)

## Triggering factor
PGA_cov = 0.1  # [-] - Coefficient of variation for the peak ground acceleration (normal distribution)

Static factor of safety, \(\mathrm{FS}_\mathrm{static}\)#

fs = factor_of_safety(d, d_w, β, φ, c, γ, ks=0)
print(f"Static factor of safety: {fs:.2f}")
Static factor of safety: 1.29

Pseudo-static factor of safety (\(k_s = 0.5 \mathrm{PGA}\))#

ks = 0.5 * PGA
fs_det = factor_of_safety(d, d_w, β, φ, c, γ, ks)
print(f"Seismic coefficient, ks: {ks:.3f}")
print(f"Pseudostatic factor of safety: {fs_det:.2f}")

decrease_fs = (fs - fs_det) / fs * 100
print(f"Decrease in factor of safety: {decrease_fs:.2f} %")
Seismic coefficient, ks: 0.075
Pseudostatic factor of safety: 1.13
Decrease in factor of safety: 12.34 %

Generating random samples#

X1 = random_sample_uniform(0.0, 0.6, 1000, seed=123)
X2 = random_rample_normal(0.3, 0.1, 1000, seed=456)
X3 = random_sample_lognormal(0.3, 0.1, 1000, seed=789)


fig, ax = plt.subplots(figsize=(6, 4), layout='constrained')
common_pars = {'bins': 15, 'linewidth': 1.0, 'density': False}
ax.hist(X1, color=mpl.colors.to_rgba('C0', alpha=.25), edgecolor='C0', **common_pars, label='Uniform')
ax.hist(X2, color=mpl.colors.to_rgba('C1', alpha=.25), edgecolor='C1', **common_pars, label='Normal')
ax.hist(X3, color=mpl.colors.to_rgba('C2', alpha=.25), edgecolor='C2', **common_pars, label='Lognormal')
ax.set_xlabel("Random variables")
ax.set_ylabel("Frequency")
fig.legend(loc='outside center right')
plt.show()
../../_images/f12f1fe178f0a5f277fb2324215b247beddcf5e8ae2e9294547ec9bc849be7f8.png

Samples for the probabilistic analysis#

n_sample = 1000

# Slope angle
β_sample = random_sample_uniform(β_min, β_max, n_sample, seed=111)

# Depth of the slip surface
d_sample = random_sample_uniform(d_min, d_max, n_sample, seed=222)

# Depth of the water table
d_w_sample = random_sample_uniform(d_w_min, d_w_max, n_sample, seed=333)

# Friction angle
tan_φ = np.tan(np.deg2rad(φ))
φ_sd = tan_φ * φ_cov  # Standard deviation of the friction coefficient (tan(φ))
tan_φ_sample = random_rample_normal(tan_φ, φ_sd, n_sample, seed=444)
φ_sample = np.rad2deg(np.arctan(tan_φ_sample))

# Cohesion
c_sd = c * c_cov  # Standard deviation of the cohesion
c_sample = random_sample_lognormal(c, c_sd, n_sample, seed=555)

# Unit weight
γ_sd = γ * γ_cov  # Standard deviation of the unit weight
γ_sample = random_rample_normal(γ, γ_sd, n_sample, seed=666)

# Peak ground acceleration
PGA_sd = PGA * PGA_cov  # Standard deviation of the PGA
PGA_sample = random_rample_normal(PGA, PGA_sd, n_sample, seed=777)
# Plot the histogram of the samples
fig, axs = plt.subplots(nrows=4, ncols=2, layout='constrained', figsize=(6, 8), sharex=False, sharey='row')
fill_col = mpl.colors.to_rgba('C0', alpha=0.5)
common_pars = {'color': fill_col, 'bins': 15, 'edgecolor': 'k', 'linewidth': 1.0, 'density': False}

# Friction angle
axs[1, 0].hist(φ_sample, **common_pars)
# axs[1, 0].set(xlabel="$\\tan \\phi'$", ylabel='Frequency', title='Friction angle')
axs[1, 0].set(xlabel="$\\phi'$", ylabel='Frequency', title='Friction angle')
# Cohesion
axs[0, 0].hist(c_sample, **common_pars)
axs[0, 0].set(xlabel="$c'$  [kPa]", ylabel='Frequency', title='Cohesion')
# Unit weigth
axs[2, 0].hist(γ_sample, **common_pars)
axs[2, 0].set(xlabel="$\\gamma$  [kN/m$^3$]", ylabel='Frequency', title='Unit weight')
# Slope inclination
common_pars['color'] = mpl.colors.to_rgba('C2', alpha=0.5)
axs[0, 1].hist(β_sample, **common_pars)
axs[0, 1].set(xlabel="$\\beta$  [$^\circ$]", title='Slope inclination')
# Depth of the slip surface
axs[1, 1].hist(d_sample, **common_pars)
axs[1, 1].set(xlabel="$d$  [m]", title='Depth of the slip surface')
# Water table depth
axs[2, 1].hist(d_w_sample, **common_pars)
axs[2, 1].set(xlabel="$d_\\mathrm{w}$  [m]", title='Water table depth')
# Peak ground acceleration
common_pars['color'] = mpl.colors.to_rgba('C3', alpha=0.5)
axs[3, 0].hist(PGA_sample, **common_pars)
axs[3, 0].set(xlabel="PGA  [g]", title='Peak ground acceleration')

# Delete the last subplot in the last row
fig.delaxes(axs[3, 1])

for ax in axs.flat:
    ax.grid(True, ls='--', lw=0.5)

fig.canvas.header_visible = False
fig.canvas.toolbar_position = 'bottom'
plt.show()
../../_images/66a4ddfb723d3ec60b5e9e73b5124e1aee0818146daebb30f8c45810ccd2afa8.png

Calculating FS for all the realizations#

ks_sample = 0.5 * PGA_sample
fs_sample = factor_of_safety(d_sample, d_w_sample, β_sample, φ_sample, c_sample, γ_sample, ks_sample)

Plotting results#

fs_mean = np.mean(fs_sample)
failed = np.zeros_like(fs_sample, dtype=int)
failed[fs_sample < 1] = 1
pf = np.sum(failed) / n_sample

fig, axs = plt.subplots(figsize=(7, 3.5), ncols=2, layout='constrained')
# Histogram of the factor of safety
axs[0].hist(fs_sample, color=mpl.colors.to_rgba('C0', alpha=0.25), edgecolor='C0', bins=15, linewidth=1.0, density=False)
axs[0].axvline(fs_mean, color='C0', linestyle='--', lw=1.75, label=f'$\\mathrm{{FS}}_\\mathrm{{mean}} = {fs_mean:.2f}$')
axs[0].axvline(fs_det, color='C1', linestyle='--', lw=1.75, label=f'$\\mathrm{{FS}}_\\mathrm{{det}} = {fs_det:.2f}$')
axs[0].axvline(1, color='C3', linestyle='--', lw=1.75, label=f'$\\mathrm{{FS}}_\\mathrm{{limit}} = 1.00$\n$\\mathrm{{PF}} = {100*pf:.1f}\%$')
axs[0].set(xlabel='Factor of safety', ylabel='Frequency')

# Scatter plot of cohesion vs friction angle
edge_cols = np.array(['C0', 'C3'])
axs[1].scatter(φ_sample, c_sample, edgecolors=edge_cols[failed], linewidths=1.0, s=15,
               facecolors=mpl.colors.to_rgba('w', alpha=0.5))
axs[1].set(xlabel="$\\phi'$  [$^\circ$]", ylabel="$c'$  [kPa]")
for label, format in zip(['Stable', 'Unstable'], ['C0', 'C3']):
    axs[1].scatter([], [], marker='o', edgecolors=format, facecolors='w', label=label, linewidths=1.25)


for ax in axs.flat:
    ax.grid(True, ls='--', lw=0.5)
    ax.legend(loc='best')

fig.canvas.header_visible = False
fig.canvas.toolbar_position = 'bottom'
plt.show()
../../_images/57b3475b750cd06dd10739a1141b14873ce3080ac29898ed40427c993999da73.png
fig, axs = plt.subplots(figsize=(5, 7), nrows=3, layout='constrained', sharex=True)
# Compute the confidence intervals for the mean
fs_mean, fs_upper, fs_lower, n = compute_confidence_intervals(fs_sample)
axs[0].axhline(fs_det, color='C1', ls='--', lw=1.75, label='$\\mathrm{FS}_\\mathrm{det}$', zorder=3)
axs[0].plot(n, fs_mean, 'C0', ls='-', lw=1.75, label='$\\mathrm{FS}_\\mathrm{mean}$')
axs[0].fill_between(n, fs_upper, fs_lower, color='C0', alpha=0.25, label='95% CI')
axs[0].set(ylabel='Factor of safety')

# Compute the confidence intervals for the standard deviation
fs_std_mean, fs_std_upper, fs_std_lower, n = compute_std_confidence_intervals(fs_sample)
axs[1].plot(n, fs_std_mean, 'C0', ls='-', lw=1.75, label='$\\sigma_{\\mathrm{FS}}$')
axs[1].fill_between(n, fs_std_upper, fs_std_lower, color='C0', alpha=0.25, label='95% CI')
axs[1].set(ylabel='Standard deviation')

# Compute the confidence intervals for the probability of failure
pf_vector, n = compute_pf_variation_vector(fs_sample)
axs[2].plot(n, pf_vector, 'C0', ls='-', lw=1.75, label='$\\mathrm{PF}$')
axs[2].set(xlabel='Number of samples', ylabel='Probability of failure')

for ax in axs.flat:
    ax.grid(True, ls='--', lw=0.5)
    ax.legend(loc='best')

fig.canvas.header_visible = False
fig.canvas.toolbar_position = 'bottom'
plt.show()
../../_images/59a5de77bee0ab82a4ffc97afb95e795441b602e3497c5441f7133ac0a5f5c2e.png