{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Calculate Sauter diameter from grain size distributions" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "from os.path import join\n", "import numpy as np\n", "from os import listdir" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "path = 'data/grainsize_distributions'\n", "sites = listdir(path)\n", "sites = [s for s in sites if not s.endswith('.csv')]\n", "column_names = ['d_lower','vol_frac','-2SD','+2SD','d_center',\\\n", " 'd_upper','d_center_phi','cum']\n", "\n", "datasets = pd.DataFrame()\n", "for site in sites:\n", " site_path = join(path, site)\n", " data_files = listdir(site_path)\n", " data_files.sort()\n", " for df in data_files:\n", " # z-position of the sample for which the grainsize\n", " # distribution was measured (this is an integer number,\n", " # NOT a depth in centimeters)\n", " z = int(df.split(',')[1].replace(').csv', ''))\n", " data = pd.read_csv(join(site_path, df), header=[0,1,2,3])\n", " data.columns = column_names\n", " data['z'] = z\n", " data['site'] = site\n", " \n", " # convert data to micrometers\n", " data['d_center'] = data['d_center']*1e-6\n", " data['d_upper'] = data['d_upper']*1e-6\n", " data['d_lower'] = data['d_lower']*1e-6\n", " \n", " # calculate area fraction from volume fraction\n", " data['radius'] = data['d_center'] / 2\n", " area_frac = 3 * data['vol_frac'] / data['radius']\n", " area_frac = area_frac / area_frac.sum()\n", " data['area_frac'] = area_frac\n", " \n", " datasets = datasets.append(data, ignore_index=True)\n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "From [[1], p.47](https://ediss.uni-goettingen.de/handle/11858/00-1735-0000-002E-E5DB-2): Given a non-spherical particle with some volume V and some surface area $A$, $d_S$ is defined as the diameter of the sphere with the same surface to area ratio than the original particle. This concept can be transferred to a porous medium with a distribution of particle diameters: Given a polydisperse porous medium with total particle volume $V$ total and particle surface area A total and some distribution of particle diameters $d_i$. The Sauter diameter of the medium is defined as the particle diameter $d_S$ of a monodisperse porous medium that gives the same ratio of total volume to total surface area:\n", "\\begin{align}\n", "d_S := 6\\cdot \\frac{V_{total}}{A_{total}}\\,.\n", "\\end{align}\n", "To calculate the $d_S$ from LPS data, we first need to calculate the total volume of the sample. We do this by calculating the number $n_i$ of particles in each bin $i$\n", "\\begin{align}\n", "n_i = \\varphi_i \\cdot\\frac{V_{total}}{V_i}\\,.\n", "\\end{align}\n", "This is a system of $N$ equations which is closed because $\\sum_{i=1}^N\\varphi_i = 1$ by definition. We solve for $V_{total}$ by arbitrarily assuming that $n_N = 1$ in the highest bin that recorded data. This is possible because we are looking for a ratio rather than an absolute value. We then calculate the $n_i$ for all bins and subsequently the total area given as\n", "\\begin{align*}\n", " A_{total} = \\pi\\sum_{i=1}^N n_id_i^2\\;. \n", "\\end{align*}\n", "Using the definition of the Sauter diameter above, we can then calculate $d_S$, which gives me a sensible estimate of the intrinsic length scale of the porous medium under the condition that drag is constant.\n", "\n", "The main source of error for the calculation of the Sauter diameter comes from the fact that grain sizes are binned by the LPS into bins of varying size and the distribution of grain sizes within one bin is unknown. We estimate error bounds for the Sauter diameter by calculating a sample's Sauter diameter once by assuming all grain diameters are grouped on the low end of each bin and once by assuming the opposite. " ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "def CalculateSauterDiameter(sample_data, diameter_col):\n", " try:\n", " last_chan = sample_data[sample_data['vol_frac'] != 0].index[-1]\n", " except: # if data = nan\n", " return np.nan\n", "\n", " d = sample_data[diameter_col] # channel diameter used as grain diameter\n", " d_max = sample_data[diameter_col].loc[last_chan] # last (largest) channel in which grains were recorded\n", " phi_V = sample_data['vol_frac'] # volume fractions in each channel\n", " phi_V_max = sample_data['vol_frac'].loc[last_chan] # volume fraction of the largest recorded grains\n", "\n", " # calculate Sauter diameter\n", " const = np.pi / 6.\n", " V = const * d**3 # volume of a sphere with diameter d\n", " V_tot_max = const * d_max**3 / phi_V_max # total volume in the last channel\n", " N = (phi_V * V_tot_max) / (d**3 * const)*1e-2 # number of particles in each bin\n", " V_tot = N * V # total volume\n", " A_tot = N * np.pi * d**2 # total area\n", " d_s = 6 * V_tot.sum() / A_tot.sum() # definition of d_s\n", " \n", " return (d_s)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "sauter_diameter = pd.DataFrame()\n", "# iterate over all \n", "for site in datasets['site'].unique():\n", " site_data = datasets[datasets['site'] == site]\n", " maxdepth = site_data['z'].max()\n", " \n", " for z in range(1, maxdepth + 1):\n", " sample_data = site_data[site_data['z'] == z]\n", " d_s_center = CalculateSauterDiameter(sample_data, 'd_center')\n", " d_s_lower = CalculateSauterDiameter(sample_data, 'd_lower')\n", " d_s_upper = CalculateSauterDiameter(sample_data, 'd_upper')\n", " sauter_diameter = sauter_diameter.append({'site':site,\n", " 'z':z,\n", " 'd_s_center':d_s_center,\n", " 'd_s_lower':d_s_lower,\n", " 'd_s_upper':d_s_upper}, ignore_index=True)\n", " \n", "sauter_diameter.to_csv('data/sauter_diameter.csv', index=False)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
d_s_centerd_s_lowerd_s_uppersitez
00.0000110.0000100.000011owens_lake_T8-W_P11.0
10.0000180.0000170.000019owens_lake_T8-W_P12.0
20.0000050.0000040.000005owens_lake_T8-W_P13.0
\n", "
" ], "text/plain": [ " d_s_center d_s_lower d_s_upper site z\n", "0 0.000011 0.000010 0.000011 owens_lake_T8-W_P1 1.0\n", "1 0.000018 0.000017 0.000019 owens_lake_T8-W_P1 2.0\n", "2 0.000005 0.000004 0.000005 owens_lake_T8-W_P1 3.0" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sauter_diameter.head(3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## References " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[[1](#ref_1)] Lasser, J. Geophysical Pattern Formation of Salt Playa. _Dissertation_ [URL](https://ediss.uni-goettingen.de/handle/11858/00-1735-0000-002E-E5DB-2) (2019)." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.8.1" } }, "nbformat": 4, "nbformat_minor": 4 }