massive transfer of risk-reliability book

This commit is contained in:
Robert Lanzafame 2024-02-23 16:41:43 +01:00
parent 6697c0f5f8
commit 3d24a01439
152 changed files with 57886 additions and 0 deletions

View file

@ -0,0 +1,555 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "49a19d81",
"metadata": {
"tags": [
"thebe-remove-input-init"
]
},
"source": [
"```{custom_download_link} exercise.zip\n",
":text: \"Download Files\"\n",
":replace_default: \"True\"\n",
"```"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "bcb1b890",
"metadata": {},
"source": [
"# Contaminant Transport"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0e18b539",
"metadata": {
"tags": [
"thebe-remove-input-init",
"thebe-init"
]
},
"outputs": [],
"source": [
"import micropip\n",
"await micropip.install(\"seaborn\")\n",
"\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"from scipy.special import erfc\n",
"from scipy import stats\n",
"import seaborn as sns\n",
"\n",
"from exercise_setup import process_negative_values\n",
"from exercise_setup import RandomVariableDistribution\n",
"from exercise_setup import pdf_of_function_of_RV\n",
"from exercise_setup import monte_carlo"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "06fa7af4",
"metadata": {},
"source": [
"`````{admonition} Interactive Python Page\n",
":class: tip, dropdown\n",
"\n",
"The code on this page can be used interactively: click {fa}`rocket` --> {guilabel}`Live Code` in the top right corner, then wait until the message {guilabel}`Python interaction ready!` appears.\n",
"\n",
"When this page is activated:\n",
"- Several packages will be imported automatically\n",
"- Code cells will **not** be executed automatically (you do it!)\n",
"\n",
"The download button {fa}`download` provides a `*.zip` file with everything needed to run this analysis on your computer. \n",
"\n",
"````{admonition} Which packages are imported when this page is activated?\n",
":class: note, dropdown\n",
"```\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"from scipy.special import erfc\n",
"from scipy import stats\n",
"import seaborn as sns\n",
"\n",
"from exercise_setup import process_negative_values\n",
"from exercise_setup import RandomVariableDistribution\n",
"from exercise_setup import pdf_of_function_of_RV\n",
"from exercise_setup import monte_carlo\n",
"```\n",
"````\n",
"`````\n",
"Here we use component reliability methods to evaluate the probability of a contaminant causing dangerous concentrations in a groundwater well. The contaminant plume is transported on the groundwater phreatic surface due to advection and diffusion.\n",
"\n",
"The purpose of this exercise is to:\n",
"- evaluate component reliability\n",
"- illustrate functions of random variables\n",
"- see the effect of random variable distributions on reliability\n",
"- consider 'sensitivity' as a functional and/or stochastic influence on the limit-state function\n",
"\n",
"### Instructions for Interactive Programming Exercise\n",
"\n",
"This page is designed to lead you through the analysis sequentially, so we recommend executing the cells one-by-one as you read through the page. Afterwards, you are welcome to use the \"run all\" button to execute all cells on the page.\n",
"\n",
"Once the interactive feature {fa}`rocket` is activated, you can use the code cells to answer the following questions:\n",
"1. Which random variables have the biggest influence on reliability?\n",
"2. How does the assumption of probability distribution influence reliability?\n",
"3. Do the random variables act as loads or resistances?\n",
"\n",
"## Introduction of Case Study\n",
"\n",
"The case study is a contaminant transport problem, illustrated in the figure below. A factory located near a town produces hazardous waste, which can threaten the drinking water supply of the town that is extracted from groundwater wells (C). The regional groundwater system has a gradient that causes water to flow in the direction of the town from the factory, thus any spilled hazardous waste that enters the ground will eventually be transported to the wells. In order to mitigate the consequences of a spill a ditch is constructed around the factory to collect it (A). Unfortunately, even with powerful pumps, there will still be some waste that reaches the groundwater table. Our task is to quantify the probability that the waste concentration exceeds the maximum exposure level to prevent the drinking water becoming unhealthy.\n",
"\n",
"```{figure} ../notebooks/contamination/figures/sketch.png\n",
"---\n",
"width: 600px\n",
"name: sketch_contaminant\n",
"---\n",
"\n",
"```\n",
"\n",
"### Contaminant Transport\n",
"\n",
"Once the ditch becomes contaminated it is cleaned quickly, but unfortunately a 'pulse' of contamination enters the ground, after which the regional gradient carries it towards the town (advection). The pulse also disperses due to diffusion, which results in a classic advection-diffusion problem (see red shapes in diagram, at three different times, $t$). Because the ditch extends a long distance in the lateral direction and the contaminant is buoyant (it floats on the groundwater table and we neglect vertical dispersion), the problem can be analyzed in 1 dimension with the following analytic expression:\n",
"\n",
"$$\n",
"\\frac{C}{C_0} = \\frac{1}{2} \\mathrm{erf}\\left(\\frac{x-(q/n)t}{2\\sqrt{a(q/n)t}}\\right)\n",
"$$\n",
"\n",
"Where $C$ contaminant concentration (e.g., parts per million, ppm) at distance $x$ from the ditch at time $t$ after the spill, and $C_0$ is the concentration in the ditch (just prior to cleanup). $\\mathrm{erf}$ is the error function. There are three parameters in the equation which are related to soil properties and groundwater flow:\n",
"\n",
"- $q$ is the horizontal groundwater flow velocity (specific discharge) [m/day]\n",
"- $n$ is the soil porosity [$-$]\n",
"- $a$ is the longitudinal dispersivity, which describes dispersion in the left to right direction in the image [m]\n",
"\n",
"Unfortunately there is not a lot of soil information, so we have to take into account uncertainty in these three variables, using the following probability distributions with first and second moments (i.e., $\\mu$ and $\\sigma$).\n",
"\n",
"- $q\\sim\\mathrm{N}\\left(\\mu_q=1.00,\\sigma_q=0.10\\right)$\n",
"- $n\\sim\\mathrm{N}\\left(\\mu_n=0.20,\\sigma_n=0.02\\right)$\n",
"- $a\\sim\\mathrm{N}\\left(\\mu_a=10.0,\\sigma_a=1.00\\right)$\n",
"\n",
"It is important to check the sensitivity of the reliability analysis to the distribution type and moments. We will use the concentration ratio $C/C_0$ to evaluate this problem and compare it to a threshold defined by health regulations: $\\mathrm{threshold}=1e-4$.\n",
"\n",
"### Definition of Limit States\n",
"\n",
"As shown in the figure above, there are two locations of interest: the monitoring station ($x=100$ m from the ditch, point B) and the town well ($x=500$ m, point C). We are interested in evaluating two cases with a reliability analysis:\n",
"1. Whether the contamination can be detected at the monitoring station above the threshold level within 1 week of the spill, and \n",
"2. If the contamination level at the town groundwater well exceeds the threshold level within 1 month of the spill (30 days)\n",
"\n",
"Relating these descriptions to our variables of interest:\n",
"1. find the probability that $C/C_0$ > threshold \n",
"2. find the probability that $C/C_0$ < threshold\n",
"\n",
"We will refer to these values as 'failure probabilities', even though case 1 technically doesn't correspond to failure---it's just the way we refer to things in practice.\n",
"\n",
"Next we will convert these into functions that will allow us to quantify the 'failure' conditions. For convenience, it is often useful to define the limit state as a function, $Z$, such that failure occurs when $Z<0$. It follows from the descriptions above, that the limit state functions for both cases are:\n",
"\n",
"1. $Z=C/C_0 - \\mathrm{threshold}$\n",
"2. $Z=\\mathrm{threshold} - C/C_0$\n",
"\n",
"NB: *in the book another notation used for $Z$ is $g_X(X)$.*\n",
"\n",
"### Analysis Approach\n",
"\n",
"Our programming task is to create a function of random variables to represent these two limit-states, define the random variables, then evaluate the reliability with Monte Carlo simulation. In other words, we will numerically approximate the probability distribution of $Z$, then evaluate reliability as $P(Z<0)$.\n",
"\n",
"This is a lot of work, but fortunately a number of helper functions and a Class are provided in the auxiliary file `exercise_setup.py`!\n",
"\n",
"### References\n",
"\n",
"This example draws inspiration from Case 2 of the paper by {cite:t}`sitar1987`."
]
},
{
"cell_type": "markdown",
"id": "d104b4b2-4fa5-4b37-891d-39201281ed2f",
"metadata": {},
"source": [
"## Defining the Limit States\n",
"\n",
"We only define limit state Case 2 below, and will use a simple multiplying factor of $-1$ to switch to Case 1 using a multiplying factor `flip_LSF`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "64c24956",
"metadata": {},
"outputs": [],
"source": [
"def C_C0(q, n, a, x, t):\n",
" \"\"\"Contaminant concentration ratio.\"\"\"\n",
" q, n, a = process_negative_values(q, n, a)\n",
" return 0.5*erfc((x - q/n*t)/(2*np.sqrt(a*(q/n)*t)))\n",
"\n",
"def limit_state_fun(q, n, a, x, t, threshold):\n",
" \"\"\"Limit state fxn, Case 2 (multiply by flip_LSF=-1 for Case 1).\"\"\"\n",
" return threshold - C_C0(q, n, a, x, t)"
]
},
{
"cell_type": "markdown",
"id": "d7410d16-14d4-45c6-9f93-81ae08de0594",
"metadata": {},
"source": [
"## Class `RandomVariableDistribution`\n",
"\n",
"The class `RandomVariableDistribution` makes it very easy to define Normal and Lognormal distributions for the random variables. See documentation and example below.\n",
"\n",
"NB: *you can use the code in `exercisesetup.py` and this notebook to confirm the result of the 2 random variable case in the Probabilistic Design chapter, and recreate the figures.*"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8bbf80be-5124-40ba-b3de-0da3eaa12ef1",
"metadata": {},
"outputs": [],
"source": [
"print(RandomVariableDistribution.__doc__)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a9b93099-b1cb-458b-baf6-8ad5dd39c440",
"metadata": {},
"outputs": [],
"source": [
"example_random_variable = RandomVariableDistribution()\n",
"example_random_variable.define_distribution(mean=5,\n",
" dist_type='LN',\n",
" print_new_dist=True)\n",
"example_random_variable.plot_pdf_or_cdf()"
]
},
{
"cell_type": "markdown",
"id": "62617026-f251-462b-a93c-b7305cfd3fa4",
"metadata": {},
"source": [
"## Setting up the problem\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4641ae3c",
"metadata": {},
"outputs": [],
"source": [
"N = 10000\n",
"\n",
"t = 7\n",
"x = 100\n",
"threshold = 1e-4\n",
"\n",
"flip_LSF = -1\n",
"\n",
"q_rv = RandomVariableDistribution()\n",
"q_rv.define_distribution(mean=1., stddev=.1)\n",
"n_rv = RandomVariableDistribution()\n",
"n_rv.define_distribution(mean=0.2, stddev=.02)\n",
"a_rv = RandomVariableDistribution()\n",
"a_rv.define_distribution(mean=10., stddev=1.)"
]
},
{
"cell_type": "markdown",
"id": "f3e0ffb1-6035-4f76-a550-4ff19c1b5946",
"metadata": {},
"source": [
"## Monte Carlo analysis\n",
"\n",
"The Monte Carlo procedure for estimating failure probability, $p_f$, is:\n",
"- For each random variable $X_i$ ($i = 1, …, n$) one simulates N realizations, $x_{i,1}, x_{i,1}, ..., x_{i,N}$. \n",
"- For each set of simulations $j$ ($j = 1, ..., N$) one calculates $g(x_{1,j}, x_{2,j}, …, x_{n,j})$\n",
"- In case $g(.) < 0$ a counter $N_f$ is increased by one. After $N$ simulations one calculates: $\\hat{p_f}=N_f/N$\n",
"- In case $N\\rightarrow+\\infty$ one obtains the failure probability $p_f$.\n",
"\n",
"The number of simulations $N$ directly influences the accuracy of $\\hat{p_f}$, which is an estimate of the 'true' value $p_f$. As an infinite number of simulations is not possible, one must choose a value for $N$ that balances total computation time, which increases linearly with $N$, and accuracy of the estimator $\\hat{p_f}$, which can be determined as the number of significant digits desired for $p_f$, or based on relative error (coefficient of variation, $V=\\sigma/\\mu$. The failure observations, $N_f$, within the Monte Carlo procedure are the result of a sequence of statistically independent and identically distributed random trials in a Bernoulli process. Using the binomial distribution, the standard deviation of of $\\hat{p_f}$ can be written\n",
"\n",
"$$\\sigma_\\hat{p_f}=\\sqrt{\\frac{p_f(1-p_f)}{N}}$$\n",
"\n",
"As $\\hat{p_f}\\rightarrow1$ this leads to the coefficient of variation, $V_\\hat{p_f}$:\n",
"\n",
"$$V_\\hat{p_f}=\\frac{\\sigma_\\hat{p_f}}{\\hat{p_f}}=\\frac{1}{\\sqrt{N\\hat{p_f}}}$$\n",
"\n",
"The coefficient of variation is a useful measure for gauging the accuracy of a Monte Carlo simulation: values of 0.01 are ideal, but 0.1 can be sufficient in many applications, depending on the need for accuracy. For example, consider $V=0.1$ for a target failure probability of $0.01$. Assuming the samples follow a Gaussian distribution implies a probability of 0.997 that the 'true' failure probability is within the interval 0.007-0.013. It is also useful to plot the calculated values of $V$ and $\\hat{p_f}$ for every realization $j = 1, ..., N$, as shown below. The figure clearly shows convergence to a stable value of $\\hat{p_f}$ after X simulations, indicating that for future analyses that are similar can use a lower number of simulations instead ($N\\sim X$).\n",
"\n",
"In case of a target coefficient of variation the number of required simulations $N$ increases as $p_f$ decreases. As an example, for $V = 0.01$ and $p_f=1e-5$ one has to execute $10^9$ simulations. As for this case on average only one of 10^5 simulations leads to an increase of $N_f$, the frequency of obtaining a 'success' (realization in the unsafe domain) is very low. In addition, for low failure probabilities the number of function evaluations is quite high, which may be practically impossible if there is a high function evaluation time, for example with a complex finite element model. To address this problem, variance reducing techniques have been developed which allow the simulations to be performed in a more effective way. In the following the so-called importance sampling or Latin hypercube sampling."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ec7883de-544e-48d5-85f2-dfc7856e9028",
"metadata": {},
"outputs": [],
"source": [
"sample_q = q_rv.get_sample(N)\n",
"sample_n = n_rv.get_sample(N)\n",
"sample_a = a_rv.get_sample(N)\n",
"sample_C_C0 = C_C0(sample_q, sample_n, sample_a, x, t)\n",
"sample_Z = flip_LSF*limit_state_fun(sample_q,\n",
" sample_n,\n",
" sample_a,\n",
" x, t, threshold)\n",
"\n",
"monte_carlo(sample_Z);"
]
},
{
"cell_type": "markdown",
"id": "d00e1316-c4ac-474d-93ad-18abcda30cfc",
"metadata": {},
"source": [
"The method `pdf_of_function_of_RV` makes it easy to see what the distribution of $C/C_0$ and the limit state function for different distributions of q, n or a. Below a second distribution of a is considered:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c08e0e56-8a6b-45a2-ad3f-abe3ce869513",
"metadata": {},
"outputs": [],
"source": [
"C_C0_cases = []\n",
"Z_cases = []\n",
"C_C0_cases.append(sample_C_C0)\n",
"Z_cases.append(sample_Z)\n",
"\n",
"a_2 = RandomVariableDistribution()\n",
"a_2.define_distribution(mean=10, stddev=4, dist_type='N')\n",
"C_C0_cases.append(C_C0(sample_q, sample_n, a_2.get_sample(N), x, t))\n",
"Z_cases.append(flip_LSF*limit_state_fun(sample_q,\n",
" sample_n,\n",
" a_2.get_sample(N),\n",
" x, t, threshold))\n",
"\n",
"a_rv.print_status()\n",
"a_2.print_status()\n",
"\n",
"pdf_of_function_of_RV(C_C0_cases)\n",
"pdf_of_function_of_RV(Z_cases, case_C=False)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "695f1336-8d3e-4503-9196-9969d181ae10",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "8c197ae8-3a6c-45fe-b304-75f601dbb9ac",
"metadata": {},
"source": [
"This is the density of the function of random variables, $C/C_0$, two values of $\\sigma_a$, and a third with the uniform distribution (not using the `RandomVariableDistribution` class)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "53273645-453b-48b9-85c0-e69eb1ac579e",
"metadata": {},
"outputs": [],
"source": [
"values_of_RV_1 = sample_q\n",
"values_of_RV_2 = sample_a\n",
"\n",
"g = sns.JointGrid()\n",
"sns.kdeplot(x=values_of_RV_1,\n",
" y=values_of_RV_2, ax=g.ax_joint, bw_adjust=3)\n",
"sns.scatterplot(x=values_of_RV_1[sample_Z<0],\n",
" y=values_of_RV_2[sample_Z<0], ax=g.ax_joint)\n",
"g.ax_joint.set_xlabel('Random Variable 1 (chosen by you)')\n",
"g.ax_joint.set_ylabel('Random Variable 2 (chosen by you)')\n",
"print('Joint density of 2 RVs with failure realizations')"
]
},
{
"cell_type": "markdown",
"id": "75f127f7-c40b-425d-91cf-6c6ecad207a2",
"metadata": {},
"source": [
"## Case 2: exceeding the health threshold\n",
"\n",
"Now we return to the failure definition of exceeding the threshold (`flip_LSF` = 1)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3ee884a7-16fb-46bf-9766-9899e662a9bc",
"metadata": {},
"outputs": [],
"source": [
"N = 100000\n",
"\n",
"t = 30\n",
"x = 500\n",
"threshold = 1e-4\n",
"\n",
"flip_LSF = 1\n",
"\n",
"sample_q = q_rv.get_sample(N)\n",
"sample_n = n_rv.get_sample(N)\n",
"sample_a = a_rv.get_sample(N)\n",
"\n",
"sample_C_C0 = C_C0(sample_q, sample_n, sample_a, x, t)\n",
"sample_Z = flip_LSF*limit_state_fun(sample_q,\n",
" sample_n,\n",
" sample_a,\n",
" x, t, threshold)\n",
"\n",
"monte_carlo(sample_Z);"
]
},
{
"cell_type": "markdown",
"id": "d1d693c6-df03-442f-a100-a065355b2c12",
"metadata": {},
"source": [
"NB: *the joint density and failure point plot is commented out here to prevent slowing down the kernel. When N=100000 (1e5) or higher, it will take around a minute to run.*"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "19a0f38a-f4e2-4dc0-8ade-0094f2a3cc91",
"metadata": {},
"outputs": [],
"source": [
"# values_of_RV_1 = sample_q\n",
"# values_of_RV_2 = sample_a\n",
"\n",
"# g = sns.JointGrid()\n",
"# sns.kdeplot(x=values_of_RV_1,\n",
"# y=values_of_RV_2, ax=g.ax_joint, bw_adjust=3)\n",
"# sns.scatterplot(x=values_of_RV_1[sample_Z<0],\n",
"# y=values_of_RV_2[sample_Z<0], ax=g.ax_joint)\n",
"# g.ax_joint.set_xlabel('Random Variable 1 (chosen by you)')\n",
"# g.ax_joint.set_ylabel('Random Variable 2 (chosen by you)')\n",
"# print('Joint density of 2 RVs with failure realizations')"
]
},
{
"cell_type": "markdown",
"id": "3e7d0cc4",
"metadata": {},
"source": [
"### Gradient Vector\n",
"\n",
"Partial derivatives will be useful for evaluating the functional influence of the variables on the limit states. The gradient vector is defined as:\n",
"\n",
"$\\nabla_{\\mathbf{y}} G(\\mathbf{y})=\\left(\\frac{\\partial G(\\mathbf{y})}{\\partial \\mathbf{y}_1}, \\cdots, \\frac{\\partial G(\\mathbf{y})}{\\partial \\mathbf{y}_n}\\right) $\n",
"\n",
"To find the gradient vector we need to find the partial derivatives of $\\frac{C}{C_0}$\n",
"\n",
"$\\frac{C}{C_0} = \\frac{1}{2} \\mathrm{erfc}\\left(\\frac{x-(q/n)t}{2\\sqrt{a(q/n)t}}\\right)=\\frac{1}{2} \\left(1-\\mathrm{erf}\\frac{x-(q/n)t}{2\\sqrt{a(q/n)t}}\\right)$\n",
"\n",
"Using $\\frac{\\partial}{\\partial x} (\\mathrm{erf} x) = \\frac{2}{\\sqrt{\\pi}}e^{-x^2} $ and $\\Omega = \\frac{1}{\\sqrt{\\pi}}e^{-\\left(\\frac{x-(q/n)t}{2\\sqrt{a(q/n)t}}\\right)^2} $, the derivatives are:\n",
"\n",
"$\\frac{\\partial (C/C_0)}{\\partial x} = \\Omega\\left(-\\frac{1}{2\\sqrt{a(q/n)t}}\\right)$\n",
"\n",
"$\\frac{\\partial (C/C_0)}{\\partial t} =\\Omega\\left(\\frac{x}{4\\sqrt{a(q/n)t^3}}+\\frac{q}{4n\\sqrt{a(q/n)t}}\\right)$\n",
"\n",
"$\\frac{\\partial (C/C_0)}{\\partial q} = \\Omega\\left(\\frac{x}{4\\sqrt{a(1/n)tq^3}}+\\frac{t}{4n\\sqrt{a(q/n)t}}\\right)$\n",
"\n",
"$\\frac{\\partial (C/C_0)}{\\partial n} = \\Omega\\left(-\\frac{x}{4\\sqrt{aqtn}}-\\frac{qt}{4\\sqrt{qatn^3}}\\right)$\n",
"\n",
"$\\frac{\\partial (C/C_0)}{\\partial a} = \\Omega\\left(\\frac{x-q/nt}{4\\sqrt{(q/n)ta^3}}\\right)$"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a761d424",
"metadata": {},
"outputs": [],
"source": [
"def gradient(q, n, a, x, t):\n",
" Om = 1/np.sqrt(np.pi)*np.exp(-((x - q/n*t)/(2*np.sqrt(a*q/n*t)))**2)\n",
" dx = Om*(-1/(2*np.sqrt(a*q/n*t)))\n",
" dt = Om*(x/(4*np.sqrt(a*q/n*t**3)) + (q)/(4*n*np.sqrt(a*q/n*t)))\n",
" dq = Om*(x/(4*np.sqrt(a*q**3/n*t)) + (t)/(4*n*np.sqrt(a*q/n*t)))\n",
" dn = Om*(-x/(4*np.sqrt(a*q*n*t)) - (q*t)/(4*np.sqrt(a*q*n**3*t)))\n",
" da = Om*((x - q/n*t)/(4*np.sqrt(a**3 *q/n*t)))\n",
" return np.array([dq,dn,da,dx,dt])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d6492f20",
"metadata": {},
"outputs": [],
"source": [
"print(gradient(2.5, 0.2, 10, x, t))"
]
},
{
"cell_type": "markdown",
"id": "18ae1605-4c60-4c65-be4f-ce9cef2e40fd",
"metadata": {},
"source": [
"## Your turn!\n",
"\n",
"Now try and use the code to answer the questions posed at the beginning of the notebook, which are repeated here:\n",
"1. Which random variables have the biggest influence on reliability?\n",
"2. How does the assumption of probability distribution influence reliability?\n",
"3. Do the random variables act as loads or resistances?\n",
"\n",
"In particular, to evaluate sensitivity, you can consider two quantitative aspects:\n",
"1. What is the functional influence of the random variables and x and t on the limit-state function?\n",
"2. What is the stochastic influence of the random variables, \n",
"3. What is the combined influence of the two aspects above?\n",
"\n",
"<!-- \n",
"Hint: note the striking similarity of these terms to the material you have already seen in the variance propagation part of MUDE, where the variance of a function of random variables is proportional to $\\sigma_x\\cdot \\frac{\\partial Z}{\\partial x}$. The variable with the biggest influence on reliability will have the highest value of this term. Also, the sign of the partial derivative will indicate whether a variable acts as a load or resistance. -->"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "base",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.7"
},
"vscode": {
"interpreter": {
"hash": "85471ad55176ca86192ed6be6d105faaef73891404cad6bd910e89e8513744d9"
}
},
"widgets": {
"application/vnd.jupyter.widget-state+json": {
"state": {},
"version_major": 2,
"version_minor": 0
}
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,498 @@
{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"id": "bcb1b890",
"metadata": {},
"source": [
"# Contaminant Transport"
]
},
{
"cell_type": "markdown",
"id": "a05b1f14",
"metadata": {},
"source": [
"_This notebook and associated files were downloaded from the book [Risk and Reliability for Engineers](https://gitlab.tudelft.nl/interactivetextbooks-citg/risk-and-reliability/) (Lanzafame, 2024) under a CC-BY 4.0 License._\n",
"\n",
"Here we use component reliability methods to evaluate the probability of a contaminant causing dangerous concentrations in a groundwater well. The contaminant plume is transported on the groundwater phreatic surface due to advection and diffusion.\n",
"\n",
"The purpose of this exercise is to:\n",
"- evaluate component reliability\n",
"- illustrate functions of random variables\n",
"- see the effect of random variable distributions on reliability\n",
"- consider 'sensitivity' as a functional and/or stochastic influence on the limit-state function\n",
"\n",
"You can use the code to answer the following questions:\n",
"1. Which random variables have the biggest influence on reliability?\n",
"2. How does the assumption of probability distribution influence reliability?\n",
"3. Do the random variables act as loads or resistances?\n",
"\n",
"## Introduction of Case Study\n",
"\n",
"The case study is a contaminant transport problem, illustrated in the figure below. A factory located near a town produces hazardous waste, which can threaten the drinking water supply of the town that is extracted from groundwater wells (C). The regional groundwater system has a gradient that causes water to flow in the direction of the town from the factory, thus any spilled hazardous waste that enters the ground will eventually be transported to the wells. In order to mitigate the consequences of a spill a ditch is constructed around the factory to collect it (A). Unfortunately, even with powerful pumps, there will still be some waste that reaches the groundwater table. Our task is to quantify the probability that the waste concentration exceeds the maximum exposure level to prevent the drinking water becoming unhealthy.\n",
"\n",
"![sketch](./sketch.png)\n",
"\n",
"### Contaminant Transport\n",
"\n",
"Once the ditch becomes contaminated it is cleaned quickly, but unfortunately a 'pulse' of contamination enters the ground, after which the regional gradient carries it towards the town (advection). The pulse also disperses due to diffusion, which results in a classic advection-diffusion problem (see red shapes in diagram, at three different times, $t$). Because the ditch extends a long distance in the lateral direction and the contaminant is buoyant (it floats on the groundwater table and we neglect vertical dispersion), the problem can be analyzed in 1 dimension with the following analytic expression:\n",
"\n",
"$$\n",
"\\frac{C}{C_0} = \\frac{1}{2} \\mathrm{erf}\\left(\\frac{x-(q/n)t}{2\\sqrt{a(q/n)t}}\\right)\n",
"$$\n",
"\n",
"Where $C$ contaminant concentration (e.g., parts per million, ppm) at distance $x$ from the ditch at time $t$ after the spill, and $C_0$ is the concentration in the ditch (just prior to cleanup). $\\mathrm{erf}$ is the error function. There are three parameters in the equation which are related to soil properties and groundwater flow:\n",
"\n",
"- $q$ is the horizontal groundwater flow velocity (specific discharge) [m/day]\n",
"- $n$ is the soil porosity [$-$]\n",
"- $a$ is the longitudinal dispersivity, which describes dispersion in the left to right direction in the image [m]\n",
"\n",
"Unfortunately there is not a lot of soil information, so we have to take into account uncertainty in these three variables, using the following probability distributions with first and second moments (i.e., $\\mu$ and $\\sigma$).\n",
"\n",
"- $q\\sim\\mathrm{N}\\left(\\mu_q=1.00,\\sigma_q=0.10\\right)$\n",
"- $n\\sim\\mathrm{N}\\left(\\mu_n=0.20,\\sigma_n=0.02\\right)$\n",
"- $a\\sim\\mathrm{N}\\left(\\mu_a=10.0,\\sigma_a=1.00\\right)$\n",
"\n",
"It is important to check the sensitivity of the reliability analysis to the distribution type and moments. We will use the concentration ratio $C/C_0$ to evaluate this problem and compare it to a threshold defined by health regulations: $\\mathrm{threshold}=1e-4$.\n",
"\n",
"### Definition of Limit States\n",
"\n",
"As shown in the figure above, there are two locations of interest: the monitoring station ($x=100$ m from the ditch, point B) and the town well ($x=500$ m, point C). We are interested in evaluating two cases with a reliability analysis:\n",
"1. Whether the contamination can be detected at the monitoring station above the threshold level within 1 week of the spill, and \n",
"2. If the contamination level at the town groundwater well exceeds the threshold level within 1 month of the spill (30 days)\n",
"\n",
"Relating these descriptions to our variables of interest:\n",
"1. find the probability that $C/C_0$ > threshold \n",
"2. find the probability that $C/C_0$ < threshold\n",
"\n",
"We will refer to these values as 'failure probabilities', even though case 1 technically doesn't correspond to failure---it's just the way we refer to things in practice.\n",
"\n",
"Next we will convert these into functions that will allow us to quantify the 'failure' conditions. For convenience, it is often useful to define the limit state as a function, $Z$, such that failure occurs when $Z<0$. It follows from the descriptions above, that the limit state functions for both cases are:\n",
"\n",
"1. $Z=C/C_0 - \\mathrm{threshold}$\n",
"2. $Z=\\mathrm{threshold} - C/C_0$\n",
"\n",
"NB: *in the book another notation used for $Z$ is $g_X(X)$.*\n",
"\n",
"### Analysis Approach\n",
"\n",
"Our programming task is to create a function of random variables to represent these two limit-states, define the random variables, then evaluate the reliability with Monte Carlo simulation. In other words, we will numerically approximate the probability distribution of $Z$, then evaluate reliability as $P(Z<0)$.\n",
"\n",
"This is a lot of work, but fortunately a number of helper functions and a Class are provided in the auxiliary file `exercise_setup.py`!\n",
"\n",
"### References\n",
"\n",
"This example draws inspiration from Case 2 of the paper by {cite:t}`sitar1987`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0e18b539",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"from scipy.special import erfc\n",
"from scipy import stats\n",
"import seaborn as sns\n",
"\n",
"from exercise_setup import process_negative_values\n",
"from exercise_setup import RandomVariableDistribution\n",
"from exercise_setup import pdf_of_function_of_RV\n",
"from exercise_setup import monte_carlo"
]
},
{
"cell_type": "markdown",
"id": "d104b4b2-4fa5-4b37-891d-39201281ed2f",
"metadata": {},
"source": [
"## Defining the Limit States\n",
"\n",
"We only define limit state Case 2 below, and will use a simple multiplying factor of $-1$ to switch to Case 1 using a multiplying factor `flip_LSF`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "64c24956",
"metadata": {},
"outputs": [],
"source": [
"def C_C0(q, n, a, x, t):\n",
" \"\"\"Contaminant concentration ratio.\"\"\"\n",
" q, n, a = process_negative_values(q, n, a)\n",
" return 0.5*erfc((x - q/n*t)/(2*np.sqrt(a*(q/n)*t)))\n",
"\n",
"def limit_state_fun(q, n, a, x, t, threshold):\n",
" \"\"\"Limit state fxn, Case 2 (multiply by flip_LSF=-1 for Case 1).\"\"\"\n",
" return threshold - C_C0(q, n, a, x, t)"
]
},
{
"cell_type": "markdown",
"id": "d7410d16-14d4-45c6-9f93-81ae08de0594",
"metadata": {},
"source": [
"## Class `RandomVariableDistribution`\n",
"\n",
"The class `RandomVariableDistribution` makes it very easy to define Normal and Lognormal distributions for the random variables. See documentation and example below.\n",
"\n",
"NB: *you can use the code in `exercisesetup.py` and this notebook to confirm the result of the 2 random variable case in the Probabilistic Design chapter, and recreate the figures.*"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8bbf80be-5124-40ba-b3de-0da3eaa12ef1",
"metadata": {},
"outputs": [],
"source": [
"print(RandomVariableDistribution.__doc__)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a9b93099-b1cb-458b-baf6-8ad5dd39c440",
"metadata": {},
"outputs": [],
"source": [
"example_random_variable = RandomVariableDistribution()\n",
"example_random_variable.define_distribution(mean=5,\n",
" dist_type='LN',\n",
" print_new_dist=True)\n",
"example_random_variable.plot_pdf_or_cdf()"
]
},
{
"cell_type": "markdown",
"id": "62617026-f251-462b-a93c-b7305cfd3fa4",
"metadata": {},
"source": [
"## Setting up the problem\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4641ae3c",
"metadata": {},
"outputs": [],
"source": [
"N = 10000\n",
"\n",
"t = 7\n",
"x = 100\n",
"threshold = 1e-4\n",
"\n",
"flip_LSF = -1\n",
"\n",
"q_rv = RandomVariableDistribution()\n",
"q_rv.define_distribution(mean=1., stddev=.1)\n",
"n_rv = RandomVariableDistribution()\n",
"n_rv.define_distribution(mean=0.2, stddev=.02)\n",
"a_rv = RandomVariableDistribution()\n",
"a_rv.define_distribution(mean=10., stddev=1.)"
]
},
{
"cell_type": "markdown",
"id": "f3e0ffb1-6035-4f76-a550-4ff19c1b5946",
"metadata": {},
"source": [
"## Monte Carlo analysis\n",
"\n",
"The Monte Carlo procedure for estimating failure probability, $p_f$, is:\n",
"- For each random variable $X_i$ ($i = 1, …, n$) one simulates N realizations, $x_{i,1}, x_{i,1}, ..., x_{i,N}$. \n",
"- For each set of simulations $j$ ($j = 1, ..., N$) one calculates $g(x_{1,j}, x_{2,j}, …, x_{n,j})$\n",
"- In case $g(.) < 0$ a counter $N_f$ is increased by one. After $N$ simulations one calculates: $\\hat{p_f}=N_f/N$\n",
"- In case $N\\rightarrow+\\infty$ one obtains the failure probability $p_f$.\n",
"\n",
"The number of simulations $N$ directly influences the accuracy of $\\hat{p_f}$, which is an estimate of the 'true' value $p_f$. As an infinite number of simulations is not possible, one must choose a value for $N$ that balances total computation time, which increases linearly with $N$, and accuracy of the estimator $\\hat{p_f}$, which can be determined as the number of significant digits desired for $p_f$, or based on relative error (coefficient of variation, $V=\\sigma/\\mu$. The failure observations, $N_f$, within the Monte Carlo procedure are the result of a sequence of statistically independent and identically distributed random trials in a Bernoulli process. Using the binomial distribution, the standard deviation of of $\\hat{p_f}$ can be written\n",
"\n",
"$$\\sigma_\\hat{p_f}=\\sqrt{\\frac{p_f(1-p_f)}{N}}$$\n",
"\n",
"As $\\hat{p_f}\\rightarrow1$ this leads to the coefficient of variation, $V_\\hat{p_f}$:\n",
"\n",
"$$V_\\hat{p_f}=\\frac{\\sigma_\\hat{p_f}}{\\hat{p_f}}=\\frac{1}{\\sqrt{N\\hat{p_f}}}$$\n",
"\n",
"The coefficient of variation is a useful measure for gauging the accuracy of a Monte Carlo simulation: values of 0.01 are ideal, but 0.1 can be sufficient in many applications, depending on the need for accuracy. For example, consider $V=0.1$ for a target failure probability of $0.01$. Assuming the samples follow a Gaussian distribution implies a probability of 0.997 that the 'true' failure probability is within the interval 0.007-0.013. It is also useful to plot the calculated values of $V$ and $\\hat{p_f}$ for every realization $j = 1, ..., N$, as shown below. The figure clearly shows convergence to a stable value of $\\hat{p_f}$ after X simulations, indicating that for future analyses that are similar can use a lower number of simulations instead ($N\\sim X$).\n",
"\n",
"In case of a target coefficient of variation the number of required simulations $N$ increases as $p_f$ decreases. As an example, for $V = 0.01$ and $p_f=1e-5$ one has to execute $10^9$ simulations. As for this case on average only one of 10^5 simulations leads to an increase of $N_f$, the frequency of obtaining a 'success' (realization in the unsafe domain) is very low. In addition, for low failure probabilities the number of function evaluations is quite high, which may be practically impossible if there is a high function evaluation time, for example with a complex finite element model. To address this problem, variance reducing techniques have been developed which allow the simulations to be performed in a more effective way. In the following the so-called importance sampling or Latin hypercube sampling."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ec7883de-544e-48d5-85f2-dfc7856e9028",
"metadata": {},
"outputs": [],
"source": [
"sample_q = q_rv.get_sample(N)\n",
"sample_n = n_rv.get_sample(N)\n",
"sample_a = a_rv.get_sample(N)\n",
"sample_C_C0 = C_C0(sample_q, sample_n, sample_a, x, t)\n",
"sample_Z = flip_LSF*limit_state_fun(sample_q,\n",
" sample_n,\n",
" sample_a,\n",
" x, t, threshold)\n",
"\n",
"monte_carlo(sample_Z);"
]
},
{
"cell_type": "markdown",
"id": "d00e1316-c4ac-474d-93ad-18abcda30cfc",
"metadata": {},
"source": [
"The method `pdf_of_function_of_RV` makes it easy to see what the distribution of $C/C_0$ and the limit state function for different distributions of q, n or a. Below a second distribution of a is considered:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c08e0e56-8a6b-45a2-ad3f-abe3ce869513",
"metadata": {},
"outputs": [],
"source": [
"C_C0_cases = []\n",
"Z_cases = []\n",
"C_C0_cases.append(sample_C_C0)\n",
"Z_cases.append(sample_Z)\n",
"\n",
"a_2 = RandomVariableDistribution()\n",
"a_2.define_distribution(mean=10, stddev=4, dist_type='N')\n",
"C_C0_cases.append(C_C0(sample_q, sample_n, a_2.get_sample(N), x, t))\n",
"Z_cases.append(flip_LSF*limit_state_fun(sample_q,\n",
" sample_n,\n",
" a_2.get_sample(N),\n",
" x, t, threshold))\n",
"\n",
"a_rv.print_status()\n",
"a_2.print_status()\n",
"\n",
"pdf_of_function_of_RV(C_C0_cases)\n",
"pdf_of_function_of_RV(Z_cases, case_C=False)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "695f1336-8d3e-4503-9196-9969d181ae10",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "8c197ae8-3a6c-45fe-b304-75f601dbb9ac",
"metadata": {},
"source": [
"This is the density of the function of random variables, $C/C_0$, two values of $\\sigma_a$, and a third with the uniform distribution (not using the `RandomVariableDistribution` class)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "53273645-453b-48b9-85c0-e69eb1ac579e",
"metadata": {},
"outputs": [],
"source": [
"values_of_RV_1 = sample_q\n",
"values_of_RV_2 = sample_a\n",
"\n",
"g = sns.JointGrid()\n",
"sns.kdeplot(x=values_of_RV_1,\n",
" y=values_of_RV_2, ax=g.ax_joint, bw_adjust=3)\n",
"sns.scatterplot(x=values_of_RV_1[sample_Z<0],\n",
" y=values_of_RV_2[sample_Z<0], ax=g.ax_joint)\n",
"g.ax_joint.set_xlabel('Random Variable 1 (chosen by you)')\n",
"g.ax_joint.set_ylabel('Random Variable 2 (chosen by you)')\n",
"print('Joint density of 2 RVs with failure realizations')"
]
},
{
"cell_type": "markdown",
"id": "75f127f7-c40b-425d-91cf-6c6ecad207a2",
"metadata": {},
"source": [
"## Case 2: exceeding the health threshold\n",
"\n",
"Now we return to the failure definition of exceeding the threshold (`flip_LSF` = 1)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3ee884a7-16fb-46bf-9766-9899e662a9bc",
"metadata": {},
"outputs": [],
"source": [
"N = 100000\n",
"\n",
"t = 30\n",
"x = 500\n",
"threshold = 1e-4\n",
"\n",
"flip_LSF = 1\n",
"\n",
"sample_q = q_rv.get_sample(N)\n",
"sample_n = n_rv.get_sample(N)\n",
"sample_a = a_rv.get_sample(N)\n",
"\n",
"sample_C_C0 = C_C0(sample_q, sample_n, sample_a, x, t)\n",
"sample_Z = flip_LSF*limit_state_fun(sample_q,\n",
" sample_n,\n",
" sample_a,\n",
" x, t, threshold)\n",
"\n",
"monte_carlo(sample_Z);"
]
},
{
"cell_type": "markdown",
"id": "d1d693c6-df03-442f-a100-a065355b2c12",
"metadata": {},
"source": [
"NB: *the joint density and failure point plot is commented out here to prevent slowing down the kernel. When N=100000 (1e5) or higher, it will take around a minute to run.*"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "19a0f38a-f4e2-4dc0-8ade-0094f2a3cc91",
"metadata": {},
"outputs": [],
"source": [
"# values_of_RV_1 = sample_q\n",
"# values_of_RV_2 = sample_a\n",
"\n",
"# g = sns.JointGrid()\n",
"# sns.kdeplot(x=values_of_RV_1,\n",
"# y=values_of_RV_2, ax=g.ax_joint, bw_adjust=3)\n",
"# sns.scatterplot(x=values_of_RV_1[sample_Z<0],\n",
"# y=values_of_RV_2[sample_Z<0], ax=g.ax_joint)\n",
"# g.ax_joint.set_xlabel('Random Variable 1 (chosen by you)')\n",
"# g.ax_joint.set_ylabel('Random Variable 2 (chosen by you)')\n",
"# print('Joint density of 2 RVs with failure realizations')"
]
},
{
"cell_type": "markdown",
"id": "3e7d0cc4",
"metadata": {},
"source": [
"### Gradient Vector\n",
"\n",
"Partial derivatives will be useful for evaluating the functional influence of the variables on the limit states. The gradient vector is defined as:\n",
"\n",
"$\\nabla_{\\mathbf{y}} G(\\mathbf{y})=\\left(\\frac{\\partial G(\\mathbf{y})}{\\partial \\mathbf{y}_1}, \\cdots, \\frac{\\partial G(\\mathbf{y})}{\\partial \\mathbf{y}_n}\\right) $\n",
"\n",
"To find the gradient vector we need to find the partial derivatives of $\\frac{C}{C_0}$\n",
"\n",
"$\\frac{C}{C_0} = \\frac{1}{2} \\mathrm{erfc}\\left(\\frac{x-(q/n)t}{2\\sqrt{a(q/n)t}}\\right)=\\frac{1}{2} \\left(1-\\mathrm{erf}\\frac{x-(q/n)t}{2\\sqrt{a(q/n)t}}\\right)$\n",
"\n",
"Using $\\frac{\\partial}{\\partial x} (\\mathrm{erf} x) = \\frac{2}{\\sqrt{\\pi}}e^{-x^2} $ and $\\Omega = \\frac{1}{\\sqrt{\\pi}}e^{-\\left(\\frac{x-(q/n)t}{2\\sqrt{a(q/n)t}}\\right)^2} $, the derivatives are:\n",
"\n",
"$\\frac{\\partial (C/C_0)}{\\partial x} = \\Omega\\left(-\\frac{1}{2\\sqrt{a(q/n)t}}\\right)$\n",
"\n",
"$\\frac{\\partial (C/C_0)}{\\partial t} =\\Omega\\left(\\frac{x}{4\\sqrt{a(q/n)t^3}}+\\frac{q}{4n\\sqrt{a(q/n)t}}\\right)$\n",
"\n",
"$\\frac{\\partial (C/C_0)}{\\partial q} = \\Omega\\left(\\frac{x}{4\\sqrt{a(1/n)tq^3}}+\\frac{t}{4n\\sqrt{a(q/n)t}}\\right)$\n",
"\n",
"$\\frac{\\partial (C/C_0)}{\\partial n} = \\Omega\\left(-\\frac{x}{4\\sqrt{aqtn}}-\\frac{qt}{4\\sqrt{qatn^3}}\\right)$\n",
"\n",
"$\\frac{\\partial (C/C_0)}{\\partial a} = \\Omega\\left(\\frac{x-q/nt}{4\\sqrt{(q/n)ta^3}}\\right)$"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a761d424",
"metadata": {},
"outputs": [],
"source": [
"def gradient(q, n, a, x, t):\n",
" Om = 1/np.sqrt(np.pi)*np.exp(-((x - q/n*t)/(2*np.sqrt(a*q/n*t)))**2)\n",
" dx = Om*(-1/(2*np.sqrt(a*q/n*t)))\n",
" dt = Om*(x/(4*np.sqrt(a*q/n*t**3)) + (q)/(4*n*np.sqrt(a*q/n*t)))\n",
" dq = Om*(x/(4*np.sqrt(a*q**3/n*t)) + (t)/(4*n*np.sqrt(a*q/n*t)))\n",
" dn = Om*(-x/(4*np.sqrt(a*q*n*t)) - (q*t)/(4*np.sqrt(a*q*n**3*t)))\n",
" da = Om*((x - q/n*t)/(4*np.sqrt(a**3 *q/n*t)))\n",
" return np.array([dq,dn,da,dx,dt])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d6492f20",
"metadata": {},
"outputs": [],
"source": [
"print(gradient(2.5, 0.2, 10, x, t))"
]
},
{
"cell_type": "markdown",
"id": "18ae1605-4c60-4c65-be4f-ce9cef2e40fd",
"metadata": {},
"source": [
"## Your turn!\n",
"\n",
"Now try and use the code to answer the questions posed at the beginning of the notebook, which are repeated here:\n",
"1. Which random variables have the biggest influence on reliability?\n",
"2. How does the assumption of probability distribution influence reliability?\n",
"3. Do the random variables act as loads or resistances?\n",
"\n",
"In particular, to evaluate sensitivity, you can consider two quantitative aspects:\n",
"1. What is the functional influence of the random variables and x and t on the limit-state function?\n",
"2. What is the stochastic influence of the random variables, \n",
"3. What is the combined influence of the two aspects above?\n",
"\n",
"<!-- \n",
"Hint: note the striking similarity of these terms to the material you have already seen in the variance propagation part of MUDE, where the variance of a function of random variables is proportional to $\\sigma_x\\cdot \\frac{\\partial Z}{\\partial x}$. The variable with the biggest influence on reliability will have the highest value of this term. Also, the sign of the partial derivative will indicate whether a variable acts as a load or resistance. -->"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "base",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.13 (main, Aug 25 2022, 23:51:50) [MSC v.1916 64 bit (AMD64)]"
},
"vscode": {
"interpreter": {
"hash": "85471ad55176ca86192ed6be6d105faaef73891404cad6bd910e89e8513744d9"
}
},
"widgets": {
"application/vnd.jupyter.widget-state+json": {
"state": {},
"version_major": 2,
"version_minor": 0
}
}
},
"nbformat": 4,
"nbformat_minor": 5
}

Binary file not shown.

View file

@ -0,0 +1,247 @@
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import erfc
from scipy import stats
import seaborn as sns
def process_negative_values(q, n, a):
"""Prevents problems with negative values in sqrt of C_C0 function."""
N_q = sum(q<0)
N_n = sum(n<0)
N_a = sum(a<0)
set_value = 0.001
if N_q > 0:
for i in range(len(q)):
if q[i]<0:
q[i] = set_value
print(f'q has {N_q} negative values that have been set to {set_value:.1e}.')
if N_n > 0:
for i in range(len(n)):
if n[i]<0:
n[i] = set_value
print(f'n has {N_n} negative values that have been set to {set_value:.1e}.')
if N_a > 0:
for i in range(len(a)):
if a[i]<0:
a[i] = set_value
print(f'a has {N_a} negative values that have been set to {set_value:.1e}.')
return q, n, a
class RandomVariableDistribution():
"""Define a N or LN probability to use in MUDE week 2.7 exercise.
Usage:
-----
Create an instance of the class
by default, the random variable is ~N(0,1)
Specify distribution using 1st and 2nd moments for N or LN:
--> use method define_distribution to change it!
(the method prints a summary of the change)
for example, copy/paste the following:
test = RandomVariableDistribution()
test.print_status()
test.define_distribution(mean=5, stddev=1, dist_type='LN')
An instance will include the following attributes:
- distribution moment and (scipy) parameters shape, location, scale
- dist_object: distribution object (scipy.stats)
- get_sample: method for getting random samples from the distribution
- plot_pdf_or_cdf:
"""
def __init__(self):
self.mean = 0.
self.stddev = 1.
self.dist_type = 'N'
def print_status(self):
"""Returns current parameter values and distribution type."""
print(f'The mean is {self.mean:.3e}')
print(f'The standard deviation is {self.stddev:.3e}')
if self.dist_type == 'N':
distribution_name = 'Normal'
elif self.dist_type == 'LN':
distribution_name = 'Lognormal'
print('The distribution is ' + distribution_name)
def define_distribution(self,
mean=None,
stddev=None,
dist_type=None,
print_new_dist=False):
"""Compute distribution parameters and RV object from moments.
Usage:
-----
Optional arguments will override current dist:
--> default distribution is standard normal, ~N(0,1)
mean: float
stddev: float
dist_type: 'N' or 'LN'
print_new_dist=True to print distribution changes.
"""
if mean is None:
mean = self.mean
if stddev is None:
stddev = self.stddev
if dist_type is None:
dist_type = self.dist_type
if dist_type == 'N':
shape = None
location = mean
scale = stddev
dist_object = stats.norm(loc=location, scale=scale)
elif dist_type == 'LN':
"""
Finds distribution parameters of LN-distributed X,
which are equivalent to the mean and standard deviation
of the log of X: Y = ln(X), then converts to the
parameterization used by scipy.stats.lognorm.
"""
stddev_y = np.sqrt(np.log(1 + (stddev/mean)**2))
mean_y = np.log(mean) - 0.5*stddev_y**2
shape = stddev_y
location = 0
scale = np.exp(mean_y)
dist_object = stats.lognorm(s=shape,
loc=location,
scale=scale)
else:
raise ValueError("You can only use N or LN for self.dist_type.")
self.mean = mean
self.stddev = stddev
self.dist_type = dist_type
self.shape = shape
self.location = location
self.scale = scale
self.dist_object = dist_object
self.get_sample = lambda size : self.dist_object.rvs(size=size)
if print_new_dist:
self.print_status()
def plot_pdf_or_cdf(self, plot_type='pdf', plot_limits=None):
"""Plots the pdf or cdf (specify choice with plot_type).
Usage:
-----
No inputs required, but there are optional inputs:
plot_type='cdf' to plot cdf (pdf is default value)
plot_limits: list with 2 values for x-axis
"""
if plot_limits is None:
if self.dist_type == 'N':
plot_limits = [self.mean - 3*self.stddev,
self.mean + 3*self.stddev]
elif self.dist_type == 'LN':
'Avoid negative or zero value for lognormal dist.'
lower_value = self.mean - 3*self.stddev
if lower_value <= 0:
lower_value = 0.01
plot_limits = [lower_value,
self.mean + 4*self.stddev]
else:
raise ValueError("You can only use N or LN for self.dist_type.")
plot_x = np.linspace(plot_limits[0], plot_limits[1], 1000)
if plot_type == 'pdf':
plot_y = self.dist_object.pdf(plot_x)
y_label = 'PDF [-]'
elif plot_type == 'cdf':
plot_y = self.dist_object.cdf(plot_x)
y_label = 'CDF [-]'
else:
raise ValueError("You must specify pdf or cdf for plot_type.")
fontsize = 15
plt.figure()
plt.plot(plot_x, plot_y, 'k-', linewidth=3)
plt.xlabel('Random Variable, X', fontsize=fontsize)
plt.ylabel(y_label, fontsize=fontsize)
plt.show()
def pdf_of_function_of_RV(cases, case_C=True, case_name=None, plot_limits=None):
"""Plot the PDF of a sampled function of random variables (C_C0 or Z).
Usage:
-----
cases: a list of arrays
case_C=True (default) assumes C_C0 is given. False is for Z.
case_name: list of strings with the labels for each array.
e.g., ['Original', 'High sigma_a', 'Low sigma_a']
(default will return Case i for all arrays)
plot_limits: list of two values that override default limits
for x-axis.
"""
bw_adjust = 3 # adjusts smoothness of Seaborn kernel density contours
labels = []
plt.figure()
for i in range(len(cases)):
if case_name == None:
labels.append(f'Case {i+1}')
else:
labels.append(case_name[i])
sns.kdeplot(cases[i], label=labels[i], ax=plt.gca(), bw_adjust=bw_adjust)
plt.legend()
if case_C:
plt.xlabel('Concentration ratio, $C/C_0$ [$-$]')
if plot_limits == None:
plt.xlim(0,1)
else:
plt.xlim(plot_limits)
else:
plt.xlabel('Limit-state function, $Z=\mathrm{threshold}-C/C_0$ [$-$]')
if plot_limits == None:
plt.xlim(-2,1)
else:
plt.xlim(plot_limits)
plt.ylabel('PDF [$]$')
plt.grid()
plt.show()
def monte_carlo(Z):
"""Monte Carlo analysis for sampled function of random variables.
Returns a summary of the analysis and a plot of pf and c.o.v..
The only input is an array of the sampled limit state-function.
Assumes that failure is when the limit-state function Z<0.
"""
N = len(Z)
Nf = np.sum(Z<0)
if Nf == 0:
raise ValueError('No failure realizations in the sample!')
elif Nf == N:
raise ValueError('All realizations in the sample failed!')
pf = Nf/N
N_f_c = np.cumsum(Z<0)
N_i = np.arange(1,len(N_f_c)+1,1)
p_f_c = N_f_c / N_i
with np.errstate(divide='ignore'):
V = 1/(np.sqrt(N_i*p_f_c))
print(f'MONTE CARLO RESULTS\n'
f'--------------------------------------------------\n'
f' N = {N}, number of samples\n'
f' Nf = {Nf}, number of failed realizations\n'
f' pf = {pf:.3e}, estimated failure probability\n'
f' V = {V[-1]:.3f}, c.o.v. of estimated pf\n')
plt.loglog(N_i,p_f_c)
plt.xlim(10,N)
plt.ylim(10**(np.floor(np.log10(pf)) - 1),1)
plt.grid()
plt.xlabel('Realization No.')
plt.ylabel('Estimated failure probability, $\hat{p}_f$')
plt.show()
plt.plot(N_i,V)
plt.xscale('log')
plt.grid()
plt.xlabel('time step')
plt.ylabel('c.o.v.')
plt.show()
return Nf,pf

View file

@ -0,0 +1,127 @@
(rel_comp)=
# Component Reliability
This Section gives a brief introduction to some of the mathematical foundations of component reliability theory, which is focused on determining the probability of a 'bad thing' happening (see {ref}`intro`). The topic has also been covered in the {ref}`prob_design` Section (especially the {ref}`prob_design_2_rv` example), and is a key part of Step 3 (quantitative analysis) in the {ref}`risk_steps`.
## Definition
In short, a component reliability analysis evaluates the reliability of a particular engineered object, where reliability is the complement of failure probability, $p_f$. The analysis is performed by defining a function of random variables, $g_X(x)$, and mathematically specifying a region of interest, $\Omega$, often called the failure region. The function of random variables implies a multivariate probability density function, $f_X(x)$, where integrating over the failure region $\Omega$ gives $p_f$.
$$
p_f=\int_\Omega f_X(x)\:\mathrm{d}x
$$
## A Simple Case
The most simple reliability analysis is a univariate case with one random variable. Consider a water distribution *system* that relies on groundwater (i.e., one *component* within the system). Suppose we can model the groundwater elevation as a random variable with the normal distribution:
$$
h_w \: [\mathrm{m}] \sim \mathrm{N}(\mu=5,\sigma=1)
$$
If $h_w$ drops below 2 m, the pumps run dry, and our component fails. In this case our function of random variables is simply:
$$
g(X) \rightarrow g(h_w)=h_w
$$
and failure is defined as:
$$
\Omega: h_w < 2 \mathrm{m}
$$
The component reliability analysis is thus:
$$
p_f=\int_{-\infty}^{2} f_{h_w}(h_w)\:\mathrm{d}h_w
$$
Which is easily evaluated with the CDF of $h_w$:
$$
p_f=F_{h_w}(2)=\Phi\left[ \frac{2-\mu}{\sigma}\right] =\Phi[-3]=0.00135
$$
This is nearly equivalent to the {ref}`prob_design_1_rv` example using river discharge from the {ref}`prob_design` Chapter, the only difference is there a load variable was considered and therefore evaluated with the complementary CDF.
## Not Simple Cases
The example above is 'simple' for three reasons, specifically. It has:
1. only one random variable, so the distribution of $g(X)$ is also univariate
2. the function of random variables, $g(X)$ is linear
3. the distribution of the random variables (variable, in this case) is Gaussian
These three characteristics make component reliability problems very easy to solve. In fact, you should have already done this earlier probability courses with (co)variance propagation methods. For example, solving for the mean and standard deviation of a linear function of random variables that are each Gaussian is straightforward, as the function of random variables is also Gaussian. Unfortunately, as problems become more complex, so do the methods required for solving such problems.
In this book we focus on simple situations with up to two variables (the 'bivariate' case) that can be solved analytically (linear functions and Gaussian random variables). Random variables are limited to continuous parametric distributions and linear measures of dependence (correlation coefficient, multivariate Gaussian). If these requirements are relaxed, we will use simulation to calculate the failure probability numerically (i.e., Monte Carlo simulation).
With this in mind, a generic procedure for using component reliability analysis in a probabilistic design context is:
1. determine probability distributions for each random variable (*marginal* distributions)
2. specify dependence between random variables (or independence)
3. define the function of random variables, $g_X(X)$, and region of interest, $\Omega$
4. integrate multivariate PDF, $f_X(X)$, over $\Omega$ to evaluate probability of interest
5. check probability requirement, and if needed:
6. modify component (i.e., modify 1, 2 or 3)
7. repeat 4, 5 and 6 until the requirement is met
```{note}
The term 'failure' here, widely used in civil engineering, is used here as a matter of convenience. Component reliability methods can also be used to find the probability of something that has nothing to do with failure, as long as it can be desribed with a function of random variables.
```
## Two Random Variables
The example with discharge from two rivers in the Probabilistic Design Chapter can be considered a component reliability problem with two random variables. The discharge of both tributary rivers act as loads because they both are proportional to the main design variable, $q$ or $h_{dike}$. The reliability integral is thus
$$
p_f=\int_\Omega f_{Q_1,Q_2}(q_1,q_2)\:\mathrm{d}x
$$
where the failure region $\Omega$ contains all values of $Q_1$ and $Q_2$ such that the river level exceeds the dike height:
$$
\Omega: h_{dike} \leq h_w(Q_1,Q_2)
$$
Note that if the dike were constructed to a height of 8.29 m, the component reliability would be 0.01 and the failure region would be equivalent to {numref}`design_2_rv_correct`.
<!-- Component reliability Analysis:
- limit states (servicability, ultimate) and limit-state functions; failure definition
- Design frameworks
- A simple case
- Special page to illustrate partial safety factor approach. Start with univariate, then show bivariate (pointing out ‘choice’ that must be made for design point, reference it, then move along).
- other
- R-S
- Z = R + S
- non-linear
- most of these should probably go in the component reliability chapter
Overview of non-linear problems (distribution and LSF); explanation of LSF; formulations of structural reliability. the area under the curve. Solution techniquest. Partial safety factor approach / LRFD / beta and reliability index. -->
% save for later: compare Z=R-S to and/or and explicitly show how the generalized method can incorporate higher/lower together or values related to each other; lognormal case
%To simple slope problem: *this example is borrowed from Moss, which is borrowed from Baecher and Christian. Both are excellent texts on risk and reliability analysis in civil and geotechnical engineering.*
% THIS IS REMOVED FOR 2022-23 SINCE THE SEPARATE PAGES WERE NOT COMPLETED
---
_The primary author for this chapter is Robert Lanzafame._
<!--
```{admonition} Exam Information
:class: tip, dropdown
You are expected to recognize the role of random variables (e.g., loads and resistances) within the function of random variables for a component and visualize it on a bivariate plot. Component reliability problems will limited to linear functions of normally distributed random variables; although you will *not* be asked to compute reliability.
``` -->
<!-- ```{admonition} Exam Information
:class: tip, dropdown
You are expected to recognize the role of random variables (e.g., loads and resistances) within the function of random variables for a component and visualize it on a bivariate plot. You may be asked to solve simple component reliability problems, limited to linear functions of normally distributed random variables, and evaluate the role of dependence between variables. You can produce a probabilistic design by modifying the component such that the probability of interest meets a specific criteria.
``` -->
<!-- ```{admonition} Exam Information
:class: tip, dropdown
For the exam, you will be expected to be able to recognize and solve simple component reliability problems, and describe the influence that dependence between random variables may have on the calculated probability of interest. Given a specific problem of interest, you should be able to describe failure (or non-failure) analytically as a function of random variables and visually in a bivariate plot. Using these simple frameworks, you can modify the component such that the probability of interest meets a specific criteria.
``` -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB