From 50eb92f3b864687db80352efe9940c5db6c328c8 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Tue, 19 Oct 2021 11:55:02 -0600 Subject: [PATCH 01/45] Added Jupyter notebook and regular Python script. --- 1d_crop_work.ipynb | 250 +++++++++++++++++++++++++++++++++++++++++++++ 1d_crop_work.py | 178 ++++++++++++++++++++++++++++++++ 2 files changed, 428 insertions(+) create mode 100644 1d_crop_work.ipynb create mode 100644 1d_crop_work.py diff --git a/1d_crop_work.ipynb b/1d_crop_work.ipynb new file mode 100644 index 0000000..f498237 --- /dev/null +++ b/1d_crop_work.ipynb @@ -0,0 +1,250 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import xarray as xr\n", + "from ctsm_py import utils\n", + "import matplotlib.pyplot as plt\n", + "import warnings\n", + "import glob" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "pftname = [\"needleleaf_evergreen_temperate_tree\",\n", + " \"needleleaf_evergreen_boreal_tree\",\n", + " \"needleleaf_deciduous_boreal_tree\",\n", + " \"broadleaf_evergreen_tropical_tree\",\n", + " \"broadleaf_evergreen_temperate_tree\",\n", + " \"broadleaf_deciduous_tropical_tree\",\n", + " \"broadleaf_deciduous_temperate_tree\",\n", + " \"broadleaf_deciduous_boreal_tree\",\n", + " \"broadleaf_evergreen_shrub\",\n", + " \"broadleaf_deciduous_temperate_shrub\",\n", + " \"broadleaf_deciduous_boreal_shrub\",\n", + " \"c3_arctic_grass\",\n", + " \"c3_non-arctic_grass\",\n", + " \"c4_grass\",\n", + " \"unmanaged_c3_crop\",\n", + " \"unmanaged_c3_irrigated\",\n", + " \"temperate_corn\",\n", + " \"irrigated_temperate_corn\",\n", + " \"spring_wheat\",\n", + " \"irrigated_spring_wheat\",\n", + " \"winter_wheat\",\n", + " \"irrigated_winter_wheat\",\n", + " \"soybean\",\n", + " \"irrigated_soybean\",\n", + " \"barley\",\n", + " \"irrigated_barley\",\n", + " \"winter_barley\",\n", + " \"irrigated_winter_barley\",\n", + " \"rye\",\n", + " \"irrigated_rye\",\n", + " \"winter_rye\",\n", + " \"irrigated_winter_rye\",\n", + " \"cassava\",\n", + " \"irrigated_cassava\",\n", + " \"citrus\",\n", + " \"irrigated_citrus\",\n", + " \"cocoa\",\n", + " \"irrigated_cocoa\",\n", + " \"coffee\",\n", + " \"irrigated_coffee\",\n", + " \"cotton\",\n", + " \"irrigated_cotton\",\n", + " \"datepalm\",\n", + " \"irrigated_datepalm\",\n", + " \"foddergrass\",\n", + " \"irrigated_foddergrass\",\n", + " \"grapes\",\n", + " \"irrigated_grapes\",\n", + " \"groundnuts\",\n", + " \"irrigated_groundnuts\",\n", + " \"millet\",\n", + " \"irrigated_millet\",\n", + " \"oilpalm\",\n", + " \"irrigated_oilpalm\",\n", + " \"potatoes\",\n", + " \"irrigated_potatoes\",\n", + " \"pulses\",\n", + " \"irrigated_pulses\",\n", + " \"rapeseed\",\n", + " \"irrigated_rapeseed\",\n", + " \"rice\",\n", + " \"irrigated_rice\",\n", + " \"sorghum\",\n", + " \"irrigated_sorghum\",\n", + " \"sugarbeet\",\n", + " \"irrigated_sugarbeet\",\n", + " \"sugarcane\",\n", + " \"irrigated_sugarcane\",\n", + " \"sunflower\",\n", + " \"irrigated_sunflower\",\n", + " \"miscanthus\",\n", + " \"irrigated_miscanthus\",\n", + " \"switchgrass\",\n", + " \"irrigated_switchgrass\",\n", + " \"tropical_corn\",\n", + " \"irrigated_tropical_corn\",\n", + " \"tropical_soybean\",\n", + " \"irrigated_tropical_soybean\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# Get list of all files in $indir matching $pattern\n", + "indir = \"/Volumes/Reacher/CESM_runs/numa_20211014/\"\n", + "pattern = \"*h1.*-01-01-00000.nc\"\n", + "filelist = glob.glob(indir + pattern)\n", + "\n", + "# Set up function to drop unwanted vars in preprocessing of open_mfdataset()\n", + "def mfdataset_preproc(ds):\n", + " vars_to_import = list(ds.dims) + \\\n", + " [\"CPHASE\", \n", + " \"GDDHARV\", \n", + " \"GDDPLANT\", \n", + " \"GPP\", \n", + " \"GRAINC_TO_FOOD\", \n", + " \"NPP\", \n", + " \"TLAI\", \n", + " \"TOTVEGC\", \n", + " \"pfts1d_itype_veg\"]\n", + " varlist = list(ds.variables)\n", + " vars_to_drop = list(np.setdiff1d(varlist, vars_to_import))\n", + " ds = ds.drop_vars(vars_to_drop)\n", + " ds = xr.decode_cf(ds, decode_times = True)\n", + " return ds\n", + "\n", + "# Import\n", + "this_ds = xr.open_mfdataset(filelist, \\\n", + " concat_dim=\"time\", \n", + " preprocess=mfdataset_preproc)\n", + "# this_ds = utils.time_set_mid(this_ds, 'time')\n", + "\n", + "# Get dates in a format that matplotlib can use\n", + "with warnings.catch_warnings():\n", + " # Ignore this warning in this with-block\n", + " warnings.filterwarnings(\"ignore\", message=\"Converting a CFTimeIndex with dates from a non-standard calendar, 'noleap', to a pandas.DatetimeIndex, which uses dates from the standard calendar. This may lead to subtle errors in operations that depend on the length of time between dates.\")\n", + " datetime_vals = this_ds.indexes[\"time\"].to_datetimeindex()\n", + "\n", + "# Get PFT list, integers (use only first timestep)\n", + "vegtype_int = this_ds.pfts1d_itype_veg\n", + "vegtype_int.values = vegtype_int.values.astype(int)\n", + "if not all((vegtype_int.values == vegtype_int.values[0,:]).all(axis=1)):\n", + " raise ValueError(\"Some veg type changes over time\")\n", + "vegtype_int = vegtype_int[0,:]\n", + "\n", + "# Get PFT list, strings\n", + "vegtype_str = list(np.array(pftname)[vegtype_int.values])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read variable and trim to crops" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAaQAAAEICAYAAAAQkoCgAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8rg+JYAAAACXBIWXMAAAsTAAALEwEAmpwYAABwk0lEQVR4nO2dd5gcxdG435oNl++kU45IAiGBBBYggkgWxjIYg0niI5lobAMmOZJ+GIzBHzYY23w2xmAjwIFgMiZnEAiEhAQIESRACAnFk+50cdP074+ZnZ3Ne9Ld7exev89zz+2Enu3emenqqq6uEqUUGo1Go9EUG6PYFdBoNBqNBrRA0mg0Go1H0AJJo9FoNJ5ACySNRqPReAItkDQajUbjCbRA0mg0Go0n0AJJo9FoNJ5ACySNpkQQkRUisk5Ealz7zhKRl+zPSkTaRaRNRFaLyI0i4nOV7bSPrROROSJSW6SmaDQZ0QJJoykt/MCFOY5/RSlVCxwMnAR8z3XsCPvY7sCewP/rtVpqNFuBFkgaTWlxPfBTERmQ6ySl1IfAq8DUDMdWA09mOqbRFBMtkDSa0mIB8BLw01wnicjOwAHAogzHxgCHZTqm0RQTf7EroNFous0vgNdE5I8Zjr0tIjFgE/A3YI7r2MMiEgVagMeBX/d6TTWabqAFkkZTYiillojIf4FLgA9SDu+ulFqepehRSqnnerd2Gs3Wo012Gk1pciWWw8KoYldEo+kptEDSaEoQWwu6F7ig2HXRaHoKLZA0mtLlaqAm71kaTYkgOkGfRqPRaLyA1pA0Go1G4wm0QNJoNBqNJ9ACSaPRaDSeQAskjUaj0XgCvTB2Kxk8eLAaN25csauh0Wg0JcXChQs3KqWGZDqmBdJWMm7cOBYsWFDsamg0Gk1JISKfZzumTXYajUaj8QRaIGk0Go3GE2iBpNFoNBpPoOeQNJoyIxKJsGrVKrq6uopdFU0/prKyktGjRxMIBAouowWSRlNmrFq1irq6OsaNG4eIFLs6mn6IUoqmpiZWrVrF+PHjCy6nTXYaTZnR1dXFoEGDtDDSFA0RYdCgQd3W0rVA0mjKEC2MNMVma55BLZA0ZUN7pJ37PrqPSCxS7KpoNJqtQM8hacqGPyz8A/d8dA9j6sYwY+SMYldHo9F0E60hacqGNe1rAOiKau+yYrPvvvv2yrmp3HHHHXz55ZfdKrNixQqmTp2a9fjixYt54okntrpOfUVzczM333xzsavRo2iBpCkbFFayST1/Unxef/31tH2xWCzjdqZzC2VrBFI+vCSQotFo1mPFEki56rStaJOdpmwwlQmAoAVSnF8+9j5Lv9zSo9fceWQ9Vx4xJec5tbW1tLW18dJLL/HLX/6SESNGsHjxYm6++eak7aVLlzrnmqbJeeedx8svv8z48eMxTZMzzzyT2bNnc/XVV/PYY4/R2dnJvvvuy1//+lceeOABFixYwMknn0xVVRXz5s1j6dKl/PjHP6atrY3Bgwdzxx13MGLECBYuXMiZZ55JdXU1+++/f9Z6h8NhfvGLX9DZ2cncuXO59NJLOfzwwzn//PN57733iEajXHXVVRx55JHccccdPPzww8RiMZYsWcJPfvITwuEw//jHP6ioqOCJJ56gsbGRmTNnMm3aNObPn8+WLVu4/fbb2WuvvWhvb8963ccff5yuri7a29t59NFHOfLII9m8eTORSIRrrrmGI488kksuuYRPPvmEadOmMWvWLK6//nquv/567rvvPkKhEEcffTS//OUvs7b1rrvu4oYbbkBE2HXXXfnHP/7B559/zplnnsmGDRsYMmQIc+bMYezYsZx++uk0NjayaNEidt99d5qamqivr2fBggWsXbuW3/72t8yePXurn6k4WkPSlA1aQ/Im8+fP59prr2Xp0qUZt+M8+OCDrFixgvfee4+//e1vzJs3zzl23nnn8dZbb7FkyRI6Ozv573//y+zZs5k+fTr/+te/WLx4MX6/n/PPP5/777/fEUCXX345AGeccQY33XRT0jUzEQwGufrqqzn++ONZvHgxxx9/PNdeey1f+9rXeOutt3jxxRf52c9+Rnt7OwBLlizh3//+N/Pnz+fyyy+nurqaRYsWMWPGDO666y7nuu3t7bz++uvcfPPNnHnmmQA5rztv3jzuvPNOXnjhBSorK3nooYd4++23efHFF/nJT36CUorrrruO7bffnsWLF3P99dfzzDPPsGzZMubPn8/ixYtZuHAhr7zySsZ2vv/++1x77bW88MILvPPOO/zxj390fudTTz2Vd999l5NPPpkLLrjAKfPxxx/z3HPP8bvf/Q6ANWvWMHfuXP773/9yySWX5HkKCkNrSJryQRW7At4jnybTF+y1115JiyNTt+PMnTuX4447DsMwGD58OAcd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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# Which variable?\n", + "thisVar = \"NPP\"\n", + "\n", + "# Make DataArray for this variable\n", + "thisvar_da = np.array(this_ds.variables[thisVar])\n", + "theseDims = this_ds.variables[thisVar].dims\n", + "thisvar_da = xr.DataArray(thisvar_da, \n", + " dims = theseDims)\n", + "\n", + "# Define coordinates of this variable's DataArray\n", + "dimsDict = dict()\n", + "for thisDim in theseDims:\n", + " if thisDim == \"time\":\n", + " dimsDict[thisDim] = this_ds.time\n", + " elif thisDim == \"pft\":\n", + " dimsDict[thisDim] = vegtype_str\n", + " else:\n", + " raise ValueError(\"Unknown dimension for coordinate assignment: \" + thisDim)\n", + "thisvar_da = thisvar_da.assign_coords(dimsDict)\n", + "\n", + "# Trim to managed crops\n", + "def is_this_mgd_crop(x):\n", + " notcrop_list = [\"tree\", \"grass\", \"shrub\", \"unmanaged\"]\n", + " return not any(n in x for n in notcrop_list)\n", + "is_crop = [ is_this_mgd_crop(x) for x in thisvar_da.pft.values ]\n", + "thisvar_da = thisvar_da[:, is_crop]\n", + "\n", + "# Plot\n", + "for p in np.arange(0,np.size(thisvar_da.pft.values)):\n", + " this_pft_char = thisvar_da.pft.values[p]\n", + " plt.plot(datetime_vals, thisvar_da.values[:,p], label = this_pft_char)\n", + "plt.title(thisVar)\n", + "plt.ylabel(this_ds.variables[thisVar].attrs['units'])\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Get sowing and harvest date for each crop" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "e8083de178eb7a8a37debdd6606e8115abc0bcba8804cd799c64479bb9dd6f05" + }, + "kernelspec": { + "display_name": "Python 3.7.9 64-bit ('base': conda)", + "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.7.9" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/1d_crop_work.py b/1d_crop_work.py new file mode 100644 index 0000000..ca85436 --- /dev/null +++ b/1d_crop_work.py @@ -0,0 +1,178 @@ + +# %% Setup + +import numpy as np +import xarray as xr +from ctsm_py import utils +import matplotlib.pyplot as plt +import warnings +import glob + +pftname = ["needleleaf_evergreen_temperate_tree", + "needleleaf_evergreen_boreal_tree", + "needleleaf_deciduous_boreal_tree", + "broadleaf_evergreen_tropical_tree", + "broadleaf_evergreen_temperate_tree", + "broadleaf_deciduous_tropical_tree", + "broadleaf_deciduous_temperate_tree", + "broadleaf_deciduous_boreal_tree", + "broadleaf_evergreen_shrub", + "broadleaf_deciduous_temperate_shrub", + "broadleaf_deciduous_boreal_shrub", + "c3_arctic_grass", + "c3_non-arctic_grass", + "c4_grass", + "unmanaged_c3_crop", + "unmanaged_c3_irrigated", + "temperate_corn", + "irrigated_temperate_corn", + "spring_wheat", + "irrigated_spring_wheat", + "winter_wheat", + "irrigated_winter_wheat", + "soybean", + "irrigated_soybean", + "barley", + "irrigated_barley", + "winter_barley", + "irrigated_winter_barley", + "rye", + "irrigated_rye", + "winter_rye", + "irrigated_winter_rye", + "cassava", + "irrigated_cassava", + "citrus", + "irrigated_citrus", + "cocoa", + "irrigated_cocoa", + "coffee", + "irrigated_coffee", + "cotton", + "irrigated_cotton", + "datepalm", + "irrigated_datepalm", + "foddergrass", + "irrigated_foddergrass", + "grapes", + "irrigated_grapes", + "groundnuts", + "irrigated_groundnuts", + "millet", + "irrigated_millet", + "oilpalm", + "irrigated_oilpalm", + "potatoes", + "irrigated_potatoes", + "pulses", + "irrigated_pulses", + "rapeseed", + "irrigated_rapeseed", + "rice", + "irrigated_rice", + "sorghum", + "irrigated_sorghum", + "sugarbeet", + "irrigated_sugarbeet", + "sugarcane", + "irrigated_sugarcane", + "sunflower", + "irrigated_sunflower", + "miscanthus", + "irrigated_miscanthus", + "switchgrass", + "irrigated_switchgrass", + "tropical_corn", + "irrigated_tropical_corn", + "tropical_soybean", + "irrigated_tropical_soybean"] + + +# %% Import dataset + +# Get list of all files in $indir matching $pattern +indir = "/Volumes/Reacher/CESM_runs/numa_20211014/" +pattern = "*h1.*-01-01-00000.nc" +filelist = glob.glob(indir + pattern) + +# Set up function to drop unwanted vars in preprocessing of open_mfdataset() +def mfdataset_preproc(ds): + vars_to_import = list(ds.dims) + \ + ["CPHASE", + "GDDHARV", + "GDDPLANT", + "GPP", + "GRAINC_TO_FOOD", + "NPP", + "TLAI", + "TOTVEGC", + "pfts1d_itype_veg"] + varlist = list(ds.variables) + vars_to_drop = list(np.setdiff1d(varlist, vars_to_import)) + ds = ds.drop_vars(vars_to_drop) + ds = xr.decode_cf(ds, decode_times = True) + return ds + +# Import +this_ds = xr.open_mfdataset(filelist, \ + concat_dim="time", + preprocess=mfdataset_preproc) +# this_ds = utils.time_set_mid(this_ds, 'time') + +# Get dates in a format that matplotlib can use +with warnings.catch_warnings(): + # Ignore this warning in this with-block + warnings.filterwarnings("ignore", message="Converting a CFTimeIndex with dates from a non-standard calendar, 'noleap', to a pandas.DatetimeIndex, which uses dates from the standard calendar. This may lead to subtle errors in operations that depend on the length of time between dates.") + datetime_vals = this_ds.indexes["time"].to_datetimeindex() + +# Get PFT list, integers (use only first timestep) +vegtype_int = this_ds.pfts1d_itype_veg +vegtype_int.values = vegtype_int.values.astype(int) +if not all((vegtype_int.values == vegtype_int.values[0,:]).all(axis=1)): + raise ValueError("Some veg type changes over time") +vegtype_int = vegtype_int[0,:] + +# Get PFT list, strings +vegtype_str = list(np.array(pftname)[vegtype_int.values]) + + +# %% Read variable + +# Which variable? +thisVar = "NPP" + +# Make DataArray for this variable +thisvar_da = np.array(this_ds.variables[thisVar]) +theseDims = this_ds.variables[thisVar].dims +thisvar_da = xr.DataArray(thisvar_da, + dims = theseDims) + +# Define coordinates of this variable's DataArray +dimsDict = dict() +for thisDim in theseDims: + if thisDim == "time": + dimsDict[thisDim] = this_ds.time + elif thisDim == "pft": + dimsDict[thisDim] = vegtype_str + else: + raise ValueError("Unknown dimension for coordinate assignment: " + thisDim) +thisvar_da = thisvar_da.assign_coords(dimsDict) + +# Trim to managed crops +def is_this_mgd_crop(x): + notcrop_list = ["tree", "grass", "shrub", "unmanaged"] + return not any(n in x for n in notcrop_list) +is_crop = [ is_this_mgd_crop(x) for x in thisvar_da.pft.values ] +thisvar_da = thisvar_da[:, is_crop] + + +# %% Plot timeseries + +for p in np.arange(0,np.size(thisvar_da.pft.values)): + this_pft_char = thisvar_da.pft.values[p] + this_pft_char = this_pft_char.replace("_", " ") + plt.plot(datetime_vals, thisvar_da.values[:,p], label = this_pft_char) +plt.title(thisVar) +plt.ylabel(this_ds.variables[thisVar].attrs['units']) +plt.legend() +plt.show() \ No newline at end of file From f008f52de49377f2c22b82f3665c0ab62e7f52ab Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Tue, 19 Oct 2021 15:54:08 -0600 Subject: [PATCH 02/45] Functionized extraction of a variable to DataArray. --- 1d_crop_work.py | 50 ++++++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/1d_crop_work.py b/1d_crop_work.py index ca85436..7e716d6 100644 --- a/1d_crop_work.py +++ b/1d_crop_work.py @@ -139,32 +139,36 @@ def mfdataset_preproc(ds): # %% Read variable # Which variable? -thisVar = "NPP" - -# Make DataArray for this variable -thisvar_da = np.array(this_ds.variables[thisVar]) -theseDims = this_ds.variables[thisVar].dims -thisvar_da = xr.DataArray(thisvar_da, - dims = theseDims) - -# Define coordinates of this variable's DataArray -dimsDict = dict() -for thisDim in theseDims: - if thisDim == "time": - dimsDict[thisDim] = this_ds.time - elif thisDim == "pft": - dimsDict[thisDim] = vegtype_str - else: - raise ValueError("Unknown dimension for coordinate assignment: " + thisDim) -thisvar_da = thisvar_da.assign_coords(dimsDict) - -# Trim to managed crops +thisVar = "CPHASE" + def is_this_mgd_crop(x): notcrop_list = ["tree", "grass", "shrub", "unmanaged"] return not any(n in x for n in notcrop_list) -is_crop = [ is_this_mgd_crop(x) for x in thisvar_da.pft.values ] -thisvar_da = thisvar_da[:, is_crop] - +def get_thisVar_da(thisVar, this_ds, vegtype_str): + # Make DataArray for this variable + thisvar_da = np.array(this_ds.variables[thisVar]) + theseDims = this_ds.variables[thisVar].dims + thisvar_da = xr.DataArray(thisvar_da, + dims = theseDims) + + # Define coordinates of this variable's DataArray + dimsDict = dict() + for thisDim in theseDims: + if thisDim == "time": + dimsDict[thisDim] = this_ds.time + elif thisDim == "pft": + dimsDict[thisDim] = vegtype_str + else: + raise ValueError("Unknown dimension for coordinate assignment: " + thisDim) + thisvar_da = thisvar_da.assign_coords(dimsDict) + + # Trim to managed crops + is_crop = [ is_this_mgd_crop(x) for x in thisvar_da.pft.values ] + thisvar_da = thisvar_da[:, is_crop] + + return thisvar_da + +get_thisVar_da(thisVar, this_ds, vegtype_str) # %% Plot timeseries From 9d511241200ade9a2e1614db843a39aaf0fcdf12 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Tue, 19 Oct 2021 15:55:50 -0600 Subject: [PATCH 03/45] Added cell: Print sowing and harvest date arrays for each crop. --- 1d_crop_work.py | 69 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/1d_crop_work.py b/1d_crop_work.py index 7e716d6..e41fdf5 100644 --- a/1d_crop_work.py +++ b/1d_crop_work.py @@ -7,6 +7,7 @@ import matplotlib.pyplot as plt import warnings import glob +import cftime pftname = ["needleleaf_evergreen_temperate_tree", "needleleaf_evergreen_boreal_tree", @@ -179,4 +180,70 @@ def get_thisVar_da(thisVar, this_ds, vegtype_str): plt.title(thisVar) plt.ylabel(this_ds.variables[thisVar].attrs['units']) plt.legend() -plt.show() \ No newline at end of file +plt.show() + + +# %% Get sowing and harvest dates + +# Get year and day number +def get_jday(cftime_datetime_object): + return cftime.datetime.timetuple(cftime_datetime_object).tm_yday +jday = np.array([get_jday(d) for d in this_ds.indexes["time"]]) +def get_year(cftime_datetime_object): + return cftime.datetime.timetuple(cftime_datetime_object).tm_year +year = np.array([get_year(d) for d in this_ds.indexes["time"]]) +year_jday = np.stack((year, jday), axis=1) + +# Find sowing and harvest dates in dataset +cphase_da = get_thisVar_da("CPHASE", this_ds, vegtype_str) +false_1xNpft = np.full((1,np.size(cphase_da.pft.values)), fill_value=False) +is_sdate = np.bitwise_and( \ + cphase_da.values[:-1,:]==4, \ + cphase_da.values[1:,:]<4) +is_sdate = np.concatenate((is_sdate, false_1xNpft)) +is_hdate = np.bitwise_and( \ + cphase_da.values[:-1,:]<4, \ + cphase_da.values[1:,:]==4) +is_hdate = np.concatenate((is_hdate, false_1xNpft)) + +# Define function for extracting an array of sowing or harvest dates (each row: year, DOY) for a given crop +def get_dates(thisCrop, vegtype_str, is_somedate, year_jday): + is_somedate_thiscrop = is_somedate[:,[d==thisCrop for d in vegtype_str]] + is_somedate_thiscrop = np.squeeze(is_somedate_thiscrop) + return year_jday[is_somedate_thiscrop,:] + +# Loop through crops and print their sowing and harvest dates +for thisCrop in cphase_da.pft.values: + + # Get dates + this_sdates = get_dates(thisCrop, cphase_da.pft.values, is_sdate, year_jday) + this_hdates = get_dates(thisCrop, cphase_da.pft.values, is_hdate, year_jday) + + # The first event in a dataset could be a harvest. If so, discard. + if this_sdates[0,1] > this_hdates[0,1]: + this_hdates = this_hdates[1:,:] + + # There should be at least as many sowings as harvests + nsow = np.shape(this_sdates)[0] + nhar = np.shape(this_hdates)[0] + if nsow < nhar: + raise ValueError("%d harvests but only %d sowings" % \ + (nhar, nsow)) + + # If there are more sowings than harvests, append NaN for last growing season + if nsow > nhar: + if nsow > nhar + 1: + raise ValueError("%d sowings but only %d harvests" % \ + (nsow, nhar)) + this_hdates = np.concatenate(( \ + this_hdates[1:,:], + np.array([[this_sdates[-1,0], np.nan]]))) + + # Ensure harvests occurred either the same year as sowing or the next year + if any(this_hdates[:,0] > this_sdates[:,0] + 1): + raise ValueError("Some harvest does not occur in either the same year as or year after corresponding sowing") + + # Print dates. Each row: sowing year, sowing DOY, harvest DOY + this_dates = np.concatenate((this_sdates, this_hdates[:,1:]), axis=1) + print(thisCrop) + print(this_dates) From b2f88d501780e784c7cb1c6431e976eab999d05d Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 28 Oct 2021 11:42:14 -0600 Subject: [PATCH 04/45] 1d: Reworked variable plotting. --- 1d_crop_work.py | 80 +++++++++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 42 deletions(-) diff --git a/1d_crop_work.py b/1d_crop_work.py index e41fdf5..04d2b9f 100644 --- a/1d_crop_work.py +++ b/1d_crop_work.py @@ -88,11 +88,38 @@ "tropical_soybean", "irrigated_tropical_soybean"] +def is_this_mgd_crop(x): + notcrop_list = ["tree", "grass", "shrub", "unmanaged"] + return not any(n in x for n in notcrop_list) +def get_thisVar_da(thisVar, this_ds, vegtype_str): + # Make DataArray for this variable + thisvar_da = np.array(this_ds.variables[thisVar]) + theseDims = this_ds.variables[thisVar].dims + thisvar_da = xr.DataArray(thisvar_da, + dims = theseDims) + + # Define coordinates of this variable's DataArray + dimsDict = dict() + for thisDim in theseDims: + if thisDim == "time": + dimsDict[thisDim] = this_ds.time + elif thisDim == "pft": + dimsDict[thisDim] = vegtype_str + else: + raise ValueError("Unknown dimension for coordinate assignment: " + thisDim) + thisvar_da = thisvar_da.assign_coords(dimsDict) + + # Trim to managed crops + is_crop = [ is_this_mgd_crop(x) for x in thisvar_da.pft.values ] + thisvar_da = thisvar_da[:, is_crop] + + return thisvar_da # %% Import dataset # Get list of all files in $indir matching $pattern -indir = "/Volumes/Reacher/CESM_runs/numa_20211014/" +# indir = "/Volumes/Reacher/CESM_runs/numa_20211014/" +indir = "/Volumes/Reacher/CESM_runs/numa_20211014_rx/" pattern = "*h1.*-01-01-00000.nc" filelist = glob.glob(indir + pattern) @@ -137,50 +164,19 @@ def mfdataset_preproc(ds): vegtype_str = list(np.array(pftname)[vegtype_int.values]) -# %% Read variable +# %% Plot timeseries -# Which variable? thisVar = "CPHASE" -def is_this_mgd_crop(x): - notcrop_list = ["tree", "grass", "shrub", "unmanaged"] - return not any(n in x for n in notcrop_list) -def get_thisVar_da(thisVar, this_ds, vegtype_str): - # Make DataArray for this variable - thisvar_da = np.array(this_ds.variables[thisVar]) - theseDims = this_ds.variables[thisVar].dims - thisvar_da = xr.DataArray(thisvar_da, - dims = theseDims) - - # Define coordinates of this variable's DataArray - dimsDict = dict() - for thisDim in theseDims: - if thisDim == "time": - dimsDict[thisDim] = this_ds.time - elif thisDim == "pft": - dimsDict[thisDim] = vegtype_str - else: - raise ValueError("Unknown dimension for coordinate assignment: " + thisDim) - thisvar_da = thisvar_da.assign_coords(dimsDict) - - # Trim to managed crops - is_crop = [ is_this_mgd_crop(x) for x in thisvar_da.pft.values ] - thisvar_da = thisvar_da[:, is_crop] - - return thisvar_da - -get_thisVar_da(thisVar, this_ds, vegtype_str) - -# %% Plot timeseries - -for p in np.arange(0,np.size(thisvar_da.pft.values)): - this_pft_char = thisvar_da.pft.values[p] - this_pft_char = this_pft_char.replace("_", " ") - plt.plot(datetime_vals, thisvar_da.values[:,p], label = this_pft_char) -plt.title(thisVar) -plt.ylabel(this_ds.variables[thisVar].attrs['units']) -plt.legend() -plt.show() +with get_thisVar_da(thisVar, this_ds, vegtype_str) as thisvar_da: + for p in np.arange(0,np.size(thisvar_da.pft.values)): + this_pft_char = thisvar_da.pft.values[p] + this_pft_char = this_pft_char.replace("_", " ") + plt.plot(datetime_vals, thisvar_da.values[:,p], label = this_pft_char) + plt.title(thisVar) + plt.ylabel(this_ds.variables[thisVar].attrs['units']) + plt.legend() + plt.show() # %% Get sowing and harvest dates From bfbfd7441300ae51ab89e694eed2e44a2f67d522 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 28 Oct 2021 12:22:46 -0600 Subject: [PATCH 05/45] Added 2d_crop_work.py. --- 2d_crop_work.py | 391 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 391 insertions(+) create mode 100644 2d_crop_work.py diff --git a/2d_crop_work.py b/2d_crop_work.py new file mode 100644 index 0000000..b35490c --- /dev/null +++ b/2d_crop_work.py @@ -0,0 +1,391 @@ + +# %% Setup + +import numpy as np +import xarray as xr +from xarray.backends.api import load_dataset +from ctsm_py import utils +import matplotlib.pyplot as plt +import warnings +import glob +import cftime +import cartopy.crs as ccrs +import cartopy.feature as cfeature + +import sys +sys.path.append("/Users/sam/Documents/git_repos/ctsm_python_gallery_myfork/ctsm_py/") +from utils import cyclic_dataarray + +pftname = ["not_vegetated", + "needleleaf_evergreen_temperate_tree", + "needleleaf_evergreen_boreal_tree", + "needleleaf_deciduous_boreal_tree", + "broadleaf_evergreen_tropical_tree", + "broadleaf_evergreen_temperate_tree", + "broadleaf_deciduous_tropical_tree", + "broadleaf_deciduous_temperate_tree", + "broadleaf_deciduous_boreal_tree", + "broadleaf_evergreen_shrub", + "broadleaf_deciduous_temperate_shrub", + "broadleaf_deciduous_boreal_shrub", + "c3_arctic_grass", + "c3_non-arctic_grass", + "c4_grass", + "unmanaged_c3_crop", + "unmanaged_c3_irrigated", + "temperate_corn", + "irrigated_temperate_corn", + "spring_wheat", + "irrigated_spring_wheat", + "winter_wheat", + "irrigated_winter_wheat", + "soybean", + "irrigated_soybean", + "barley", + "irrigated_barley", + "winter_barley", + "irrigated_winter_barley", + "rye", + "irrigated_rye", + "winter_rye", + "irrigated_winter_rye", + "cassava", + "irrigated_cassava", + "citrus", + "irrigated_citrus", + "cocoa", + "irrigated_cocoa", + "coffee", + "irrigated_coffee", + "cotton", + "irrigated_cotton", + "datepalm", + "irrigated_datepalm", + "foddergrass", + "irrigated_foddergrass", + "grapes", + "irrigated_grapes", + "groundnuts", + "irrigated_groundnuts", + "millet", + "irrigated_millet", + "oilpalm", + "irrigated_oilpalm", + "potatoes", + "irrigated_potatoes", + "pulses", + "irrigated_pulses", + "rapeseed", + "irrigated_rapeseed", + "rice", + "irrigated_rice", + "sorghum", + "irrigated_sorghum", + "sugarbeet", + "irrigated_sugarbeet", + "sugarcane", + "irrigated_sugarcane", + "sunflower", + "irrigated_sunflower", + "miscanthus", + "irrigated_miscanthus", + "switchgrass", + "irrigated_switchgrass", + "tropical_corn", + "irrigated_tropical_corn", + "tropical_soybean", + "irrigated_tropical_soybean"] + + +# %% Import dataset + +# Get list of all files in $indir matching $pattern +indir = "/Volumes/Reacher/CESM_runs/f10_f10_mg37/" +pattern = "*h1.*-01-01-00000.nc" +filelist = glob.glob(indir + pattern) + +# Set up function to drop unwanted vars in preprocessing of open_mfdataset() +def mfdataset_preproc(ds): + vars_to_import = list(ds.dims) + \ + ["CPHASE", + "GDDHARV", + "GDDPLANT", + "GPP", + "GRAINC_TO_FOOD", + "NPP", + "TLAI", + "TOTVEGC", + "pfts1d_itype_veg", + "pfts1d_ixy", + "pfts1d_jxy", + "pfts1d_lon", + "pfts1d_lat"] + varlist = list(ds.variables) + vars_to_drop = list(np.setdiff1d(varlist, vars_to_import)) + ds = ds.drop_vars(vars_to_drop) + ds = xr.decode_cf(ds, decode_times = True) + return ds + +# Import +this_ds = xr.open_mfdataset(filelist, \ + concat_dim="time", + preprocess=mfdataset_preproc) +# this_ds = utils.time_set_mid(this_ds, 'time') + +# Get dates in a format that matplotlib can use +with warnings.catch_warnings(): + # Ignore this warning in this with-block + warnings.filterwarnings("ignore", message="Converting a CFTimeIndex with dates from a non-standard calendar, 'noleap', to a pandas.DatetimeIndex, which uses dates from the standard calendar. This may lead to subtle errors in operations that depend on the length of time between dates.") + datetime_vals = this_ds.indexes["time"].to_datetimeindex() + +# Get PFT list, integers (use only first timestep) +vegtype_int = this_ds.pfts1d_itype_veg +vegtype_int.values = vegtype_int.values.astype(int) +if not all((vegtype_int.values == vegtype_int.values[0,:]).all(axis=1)): + raise ValueError("Some veg type changes over time") +vegtype_int = vegtype_int[0,:] + +# Get PFT list, strings +vegtype_str = list(np.array(pftname)[vegtype_int.values]) + +# %% Read variable + +# Which variable? +thisVar = "CPHASE" + +def is_this_mgd_crop(x): + notcrop_list = ["tree", "grass", "shrub", "unmanaged", "not_vegetated"] + return not any(n in x for n in notcrop_list) +def get_thisVar_da(thisVar, this_ds, vegtype_str): + # Make DataArray for this variable + thisvar_da = np.array(this_ds.variables[thisVar]) + theseDims = this_ds.variables[thisVar].dims + thisvar_da = xr.DataArray(thisvar_da, + dims = theseDims) + + # Define coordinates of this variable's DataArray + dimsDict = dict() + for thisDim in theseDims: + if thisDim == "pft": + dimsDict[thisDim] = vegtype_str + elif any(np.array(list(this_ds.dims.keys())) == thisDim): + dimsDict[thisDim] = this_ds[thisDim] + else: + raise ValueError("Unknown dimension for coordinate assignment: " + thisDim) + thisvar_da = thisvar_da.assign_coords(dimsDict) + + # If it has PFT dimension, trim to managed crops + if any(np.array(list(thisvar_da.dims)) == "pft"): + is_crop = [ is_this_mgd_crop(x) for x in thisvar_da.pft.values ] + thisvar_da = thisvar_da[:, is_crop] + + return thisvar_da + +thisvar_da = get_thisVar_da(thisVar, this_ds, vegtype_str) + + +# %% Grid variable (takes a while) and make map + +# ixy = get_thisVar_da("pfts1d_ixy", this_ds, vegtype_str) +# jxy = get_thisVar_da("pfts1d_jxy", this_ds, vegtype_str) +# lon = get_thisVar_da("lon", this_ds, vegtype_str) +# lat = get_thisVar_da("lat", this_ds, vegtype_str) +# ttime = get_thisVar_da("time", this_ds, vegtype_str) +ixy = this_ds.pfts1d_ixy +jxy = this_ds.pfts1d_jxy +lon = this_ds.lon +lat = this_ds.lat +ttime = this_ds.time + +nlat = len(lat.values) +nlon = len(lon.values) +npft = np.max(vegtype_int.values) + 1 +ntim = len(ttime.values) + +tmp_tpyx = np.empty([ntim, npft, nlat, nlon]) +tmp_tpyx[:, \ + vegtype_int.values, + jxy.values.astype(int) - 1, + ixy.values.astype(int) - 1] = this_ds.variables[thisVar].values + +tmp2_tpyx = xr.DataArray(tmp_tpyx, dims=("time","pft","lat","lon")) +tmp2_tpyx = tmp2_tpyx.assign_coords( \ + time=ttime, + pft=pftname, + lat=lat.values, + lon=lon.values) +tmp2_tpyx.name = thisVar +is_crop = [ is_this_mgd_crop(x) for x in tmp2_tpyx.pft.values ] +tmp2_tpyx = tmp2_tpyx[:, is_crop] +print(tmp2_tpyx) + +# Make map +tmp3 = tmp2_tpyx.isel(time=0, pft=0) +tmp4 = cyclic_dataarray(tmp3) +ax = plt.axes(projection=ccrs.PlateCarree()) +plt.pcolor(tmp4.lon.values, tmp4.lat.values, tmp4, transform=ccrs.PlateCarree()) +ax.coastlines() +plt.show() + + +# %% Plot and make map, more efficiently + +tmp = thisvar_da[dict(time=0)] + +# ixy = this_ds.pfts1d_ixy[dict(time=0)] +# jxy = this_ds.pfts1d_jxy[dict(time=0)] +ixy_da = get_thisVar_da("pfts1d_ixy", this_ds, vegtype_str) +jxy_da = get_thisVar_da("pfts1d_jxy", this_ds, vegtype_str) +ixy = ixy_da[dict(time=0)] +jxy = jxy_da[dict(time=0)] +lon = this_ds.lon +lat = this_ds.lat + +vt_da = get_thisVar_da("pfts1d_itype_veg", this_ds, vegtype_str) + +vt = vt_da[dict(time=0)].values + +nlat = len(lat.values) +nlon = len(lon.values) +npft = np.max(vegtype_int.values) + 1 + +tmp_pyx = np.empty([npft, nlat, nlon]) +tmp_pyx[vt, + jxy.values.astype(int) - 1, + ixy.values.astype(int) - 1] = tmp.values + +tmp2_pyx = xr.DataArray(tmp_pyx, dims=("pft","lat","lon")) +tmp2_pyx = tmp2_pyx.assign_coords( \ + pft=pftname, + lat=lat.values, + lon=lon.values) +tmp2_pyx.name = thisVar +is_crop = [ is_this_mgd_crop(x) for x in tmp2_pyx.pft.values ] +tmp2_pyx = tmp2_pyx[is_crop] + +# Make map +tmp3 = tmp2_pyx.isel(pft=0) +tmp4 = cyclic_dataarray(tmp3) +ax = plt.axes(projection=ccrs.PlateCarree()) +plt.pcolor(tmp4.lon.values, tmp4.lat.values, tmp4, transform=ccrs.PlateCarree()) +ax.coastlines() +plt.show() + + +# %% Plot and make map, more efficiently, as function + +def grid_one_timestep(thisvar_da, time_index): + + # Get this variable's values for this time step + thisvar_da_1time = thisvar_da[dict(time=time_index)] + + # Get gridcell indices for this time step + ixy_da = get_thisVar_da("pfts1d_ixy", this_ds, vegtype_str) + jxy_da = get_thisVar_da("pfts1d_jxy", this_ds, vegtype_str) + ixy = ixy_da[dict(time=time_index)] + jxy = jxy_da[dict(time=time_index)] + + # Get PFT indices for this time step + vt_da = get_thisVar_da("pfts1d_itype_veg", this_ds, vegtype_str) + vt = vt_da[dict(time=time_index)].values + + # Get dataset lon/lat grid + lon = this_ds.lon + lat = this_ds.lat + + # Set up empty array: PFT * lat * lon + npft = np.max(vegtype_int.values) + 1 + nlat = len(lat.values) + nlon = len(lon.values) + thisvar_pyx = np.empty([npft, nlat, nlon]) + + # Fill with this variable + thisvar_pyx[vt, + jxy.values.astype(int) - 1, + ixy.values.astype(int) - 1] = thisvar_da_1time.values + + # Assign coordinates and name + thisvar_pyx = xr.DataArray(thisvar_pyx, dims=("pft","lat","lon")) + thisvar_pyx = thisvar_pyx.assign_coords( \ + pft=pftname, + lat=lat.values, + lon=lon.values) + thisvar_pyx.name = thisVar + + # Restrict to managed crops + is_crop = [ is_this_mgd_crop(x) for x in thisvar_pyx.pft.values ] + thisvar_pyx = thisvar_pyx[is_crop] + + return thisvar_pyx + +def grid_timeslice(thisvar_da, time_str_0: str, time_str_1: str = ""): + + one_timestep = time_str_1 == "" + if (one_timestep): + time_slice = slice(time_str_0) + else: + time_slice = slice(time_str_0, time_str_1) + + # Get this variable's values for this time slice + thisvar_da_1time = thisvar_da[dict(time=time_slice)] + + # Get gridcell indices for this time slice + ixy_da = get_thisVar_da("pfts1d_ixy", this_ds, vegtype_str) + jxy_da = get_thisVar_da("pfts1d_jxy", this_ds, vegtype_str) + ixy = ixy_da[dict(time=time_slice)] + jxy = jxy_da[dict(time=time_slice)] + + # Get PFT indices for this time slice + vt_da = get_thisVar_da("pfts1d_itype_veg", this_ds, vegtype_str) + vt = vt_da[dict(time=time_slice)].values + + # Get dataset lon/lat grid + lon = this_ds.lon + lat = this_ds.lat + + # Set up empty array: PFT * lat * lon + npft = np.max(vegtype_int.values) + 1 + nlat = len(lat.values) + nlon = len(lon.values) + if (one_timestep): + raise ValueError("Finish coding this") + ntim = len(ttime.values) + tmp_tpyx = np.empty([ntim, npft, nlat, nlon]) + else: + thisvar_out = np.empty([npft, nlat, nlon]) + + # Fill with this variable + if (one_timestep): + raise ValueError("Finish coding this") + else: + thisvar_out[vt, + jxy.values.astype(int) - 1, + ixy.values.astype(int) - 1] = thisvar_da_1time.values + + # Assign coordinates and name + if (one_timestep): + raise ValueError("Finish coding this") + else: + thisvar_out = xr.DataArray(thisvar_out, dims=("pft","lat","lon")) + thisvar_out = thisvar_out.assign_coords( \ + pft=pftname, + lat=lat.values, + lon=lon.values) + thisvar_out.name = thisVar + + # Restrict to managed crops + is_crop = [ is_this_mgd_crop(x) for x in thisvar_out.pft.values ] + thisvar_out = thisvar_out[is_crop] + + return thisvar_out + +# Grid this timestep +tmp_pyx = grid_one_timestep(thisvar_da, 0) + +# Make map +tmp_yx = tmp_pyx.isel(pft=0) +tmp_yx = cyclic_dataarray(tmp_yx) +ax = plt.axes(projection=ccrs.PlateCarree()) +plt.pcolor(tmp_yx.lon.values, tmp_yx.lat.values, tmp_yx, transform=ccrs.PlateCarree()) +ax.coastlines() +plt.show() From 96413c07d9a2e97f364fcd883b27f546c81b4b59 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 28 Oct 2021 14:10:26 -0600 Subject: [PATCH 06/45] Added import_ds_from_filelist() to utils.py. Import a dataset that's spread over multiple files, only including specified variables. Concatenates by time. --- ctsm_py/utils.py | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index 12def2d..d0b9202 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -176,4 +176,49 @@ def cyclic_dataset(ds, coord='lon'): new_ds.coords[c].attrs[att] = ds.coords[c].attrs[att] return new_ds -''' \ No newline at end of file +''' + +# Import a dataset that's spread over multiple files, only including specified variables. Concatenate by time. +def import_ds_from_filelist(filelist, myVars): + + # Set up function to drop unwanted vars in preprocessing of open_mfdataset(), making sure to include any unspecified variables that will be useful in gridding. + def mfdataset_preproc(ds, vars_to_import): + + # Get list of dimensions present in variables in vars_to_import. + dimList = [] + for thisVar in vars_to_import: + # list(set(x)) returns a list of the unique items in x + dimList = list(set(dimList + list(ds.variables[thisVar].dims))) + + # Get any _1d variables that are associated with those dimensions. These will be useful in gridding. + onedVars = [] + for thisDim in dimList: + pattern = re.compile(f"{thisDim}.*1d") + matches = [x for x in list(ds.keys()) if pattern.search(x) != None] + onedVars = list(set(onedVars + matches)) + + # Add dimensions and _1d variables to vars_to_import + vars_to_import = list(set(vars_to_import \ + + dimList + onedVars)) + + # Get list of variables to drop + varlist = list(ds.variables) + vars_to_drop = list(np.setdiff1d(varlist, vars_to_import)) + + # Drop them + ds = ds.drop_vars(vars_to_drop) + + # Finish import + ds = xr.decode_cf(ds, decode_times = True) + return ds + + # xr.open_mfdataset()'s "preprocess" argument requires a function that only takes one variable (an xarray.Dataset object). Wrapping mfdataset_preproc() in this lambda function allows this. Could also just allow mfdataset_preproc() to access the myVars directly, but that's bad practice as it could lead to scoping issues. + mfdataset_preproc_closure = \ + lambda ds: mfdataset_preproc(ds, myVars) + + # Import + this_ds = xr.open_mfdataset(filelist, \ + concat_dim="time", + preprocess=mfdataset_preproc_closure) + + return this_ds From 2f25d491ed0d349b0d99ab45a845efa773b3fb05 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 28 Oct 2021 15:08:48 -0600 Subject: [PATCH 07/45] myVars now optional in import_ds_from_filelist(). If unspecified, will import all variables. --- ctsm_py/utils.py | 49 ++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index d0b9202..935eef2 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -179,34 +179,35 @@ def cyclic_dataset(ds, coord='lon'): ''' # Import a dataset that's spread over multiple files, only including specified variables. Concatenate by time. -def import_ds_from_filelist(filelist, myVars): +def import_ds_from_filelist(filelist, myVars=None): # Set up function to drop unwanted vars in preprocessing of open_mfdataset(), making sure to include any unspecified variables that will be useful in gridding. def mfdataset_preproc(ds, vars_to_import): - # Get list of dimensions present in variables in vars_to_import. - dimList = [] - for thisVar in vars_to_import: - # list(set(x)) returns a list of the unique items in x - dimList = list(set(dimList + list(ds.variables[thisVar].dims))) - - # Get any _1d variables that are associated with those dimensions. These will be useful in gridding. - onedVars = [] - for thisDim in dimList: - pattern = re.compile(f"{thisDim}.*1d") - matches = [x for x in list(ds.keys()) if pattern.search(x) != None] - onedVars = list(set(onedVars + matches)) - - # Add dimensions and _1d variables to vars_to_import - vars_to_import = list(set(vars_to_import \ - + dimList + onedVars)) - - # Get list of variables to drop - varlist = list(ds.variables) - vars_to_drop = list(np.setdiff1d(varlist, vars_to_import)) - - # Drop them - ds = ds.drop_vars(vars_to_drop) + if vars_to_import != None: + # Get list of dimensions present in variables in vars_to_import. + dimList = [] + for thisVar in vars_to_import: + # list(set(x)) returns a list of the unique items in x + dimList = list(set(dimList + list(ds.variables[thisVar].dims))) + + # Get any _1d variables that are associated with those dimensions. These will be useful in gridding. + onedVars = [] + for thisDim in dimList: + pattern = re.compile(f"{thisDim}.*1d") + matches = [x for x in list(ds.keys()) if pattern.search(x) != None] + onedVars = list(set(onedVars + matches)) + + # Add dimensions and _1d variables to vars_to_import + vars_to_import = list(set(vars_to_import \ + + dimList + onedVars)) + + # Get list of variables to drop + varlist = list(ds.variables) + vars_to_drop = list(np.setdiff1d(varlist, vars_to_import)) + + # Drop them + ds = ds.drop_vars(vars_to_drop) # Finish import ds = xr.decode_cf(ds, decode_times = True) From 40ac1e09c0b2c8bc474b4a819acc3490d204bdda Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 28 Oct 2021 15:39:10 -0600 Subject: [PATCH 08/45] Read in ALL dimensions in import_ds_from_filelist(). --- ctsm_py/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index 935eef2..81be00f 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -200,7 +200,7 @@ def mfdataset_preproc(ds, vars_to_import): # Add dimensions and _1d variables to vars_to_import vars_to_import = list(set(vars_to_import \ - + dimList + onedVars)) + + list(ds.dims) + onedVars)) # Get list of variables to drop varlist = list(ds.variables) From f6e018ae5d549337957f615b90660549488fc2f5 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 28 Oct 2021 16:47:58 -0600 Subject: [PATCH 09/45] Added to utils.py: List of PFTs used in CLM (pftlist). --- ctsm_py/utils.py | 82 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index 81be00f..5c02922 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -178,6 +178,88 @@ def cyclic_dataset(ds, coord='lon'): return new_ds ''' +# List of PFTs used in CLM +pftlist = ["not_vegetated", + "needleleaf_evergreen_temperate_tree", + "needleleaf_evergreen_boreal_tree", + "needleleaf_deciduous_boreal_tree", + "broadleaf_evergreen_tropical_tree", + "broadleaf_evergreen_temperate_tree", + "broadleaf_deciduous_tropical_tree", + "broadleaf_deciduous_temperate_tree", + "broadleaf_deciduous_boreal_tree", + "broadleaf_evergreen_shrub", + "broadleaf_deciduous_temperate_shrub", + "broadleaf_deciduous_boreal_shrub", + "c3_arctic_grass", + "c3_non-arctic_grass", + "c4_grass", + "unmanaged_c3_crop", + "unmanaged_c3_irrigated", + "temperate_corn", + "irrigated_temperate_corn", + "spring_wheat", + "irrigated_spring_wheat", + "winter_wheat", + "irrigated_winter_wheat", + "soybean", + "irrigated_soybean", + "barley", + "irrigated_barley", + "winter_barley", + "irrigated_winter_barley", + "rye", + "irrigated_rye", + "winter_rye", + "irrigated_winter_rye", + "cassava", + "irrigated_cassava", + "citrus", + "irrigated_citrus", + "cocoa", + "irrigated_cocoa", + "coffee", + "irrigated_coffee", + "cotton", + "irrigated_cotton", + "datepalm", + "irrigated_datepalm", + "foddergrass", + "irrigated_foddergrass", + "grapes", + "irrigated_grapes", + "groundnuts", + "irrigated_groundnuts", + "millet", + "irrigated_millet", + "oilpalm", + "irrigated_oilpalm", + "potatoes", + "irrigated_potatoes", + "pulses", + "irrigated_pulses", + "rapeseed", + "irrigated_rapeseed", + "rice", + "irrigated_rice", + "sorghum", + "irrigated_sorghum", + "sugarbeet", + "irrigated_sugarbeet", + "sugarcane", + "irrigated_sugarcane", + "sunflower", + "irrigated_sunflower", + "miscanthus", + "irrigated_miscanthus", + "switchgrass", + "irrigated_switchgrass", + "tropical_corn", + "irrigated_tropical_corn", + "tropical_soybean", + "irrigated_tropical_soybean"] + + # Import a dataset that's spread over multiple files, only including specified variables. Concatenate by time. def import_ds_from_filelist(filelist, myVars=None): From 19a9dda5936ba45dde9c52f556257ef386f2507b Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 28 Oct 2021 16:49:04 -0600 Subject: [PATCH 10/45] Added to utils.py: function to get PFT of each patch as integer and string. --- ctsm_py/utils.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index 5c02922..d27496d 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -260,6 +260,30 @@ def cyclic_dataset(ds, coord='lon'): "irrigated_tropical_soybean"] +# Get PFT of each patch, in both integer and string forms +def ivt_int_str(this_ds, this_pftlist): + # First, get all the integer values; should be time*pft or pft*time. We will eventually just take the first timestep. + vegtype_int = this_ds.pfts1d_itype_veg + vegtype_int.values = vegtype_int.values.astype(int) + + # Make sure no vegtype changes over time. + time_index = vegtype_int.dims.index("time") + uniques = np.unique(vegtype_int.values, \ + axis=time_index) + max_num_ivt_per_patch = uniques.shape[time_index] + if max_num_ivt_per_patch != 1: + raise ValueError("Some veg type changes over time") + + # Take the first timestep. + vegtype_int = vegtype_int.isel(time=0) + + # Convert to strings. + vegtype_str = list(np.array(this_pftlist)[vegtype_int.values]) + + # Return a dictionary with both results + return {"int": vegtype_int, "str": vegtype_str, "all_str": this_pftlist} + + # Import a dataset that's spread over multiple files, only including specified variables. Concatenate by time. def import_ds_from_filelist(filelist, myVars=None): From f3c34a06251aea2805ed90a99bafa7feb350937b Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 28 Oct 2021 16:49:36 -0600 Subject: [PATCH 11/45] import_ds_from_filelist() now also returns vegtypes dictionary. --- ctsm_py/utils.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index d27496d..e231013 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -285,7 +285,7 @@ def ivt_int_str(this_ds, this_pftlist): # Import a dataset that's spread over multiple files, only including specified variables. Concatenate by time. -def import_ds_from_filelist(filelist, myVars=None): +def import_ds_from_filelist(filelist, this_pftlist, myVars=None): # Set up function to drop unwanted vars in preprocessing of open_mfdataset(), making sure to include any unspecified variables that will be useful in gridding. def mfdataset_preproc(ds, vars_to_import): @@ -328,4 +328,9 @@ def mfdataset_preproc(ds, vars_to_import): concat_dim="time", preprocess=mfdataset_preproc_closure) - return this_ds + # Get vegetation type info + vegtypes = ivt_int_str(this_ds, this_pftlist) + + return this_ds, vegtypes + + From 20fbeef88daf95e896a44ec03f7942c5d96b77fb Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 28 Oct 2021 16:50:16 -0600 Subject: [PATCH 12/45] Added to utils.py: function get_thisVar_da(). Return a DataArray, with defined coordinates (PFT as string), for a given variable in a dataset. --- ctsm_py/utils.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index e231013..1980203 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -334,3 +334,26 @@ def mfdataset_preproc(ds, vars_to_import): return this_ds, vegtypes +# Return a DataArray, with defined coordinates (PFT as string), for a given variable in a dataset +def get_thisVar_da(thisVar, this_ds, vegtypes_str): + + # Make DataArray for this variable + thisvar_da = np.array(this_ds.variables[thisVar]) + theseDims = this_ds.variables[thisVar].dims + thisvar_da = xr.DataArray(thisvar_da, + dims = theseDims) + + # Define coordinates of this variable's DataArray + dimsDict = dict() + for thisDim in theseDims: + if thisDim == "pft": + dimsDict[thisDim] = vegtypes_str + elif any(np.array(list(this_ds.dims.keys())) == thisDim): + dimsDict[thisDim] = this_ds[thisDim] + else: + raise ValueError("Unknown dimension for coordinate assignment: " + thisDim) + thisvar_da = thisvar_da.assign_coords(dimsDict) + + return thisvar_da + + From febd079a0d25e0236d97c400e502713b633f51fc Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 28 Oct 2021 16:53:57 -0600 Subject: [PATCH 13/45] Added to utils.py: is_this_mgd_crop(). Given a PFT, returns False if it's a tree, grass, shrub, unmanaged, or not vegetated. True otherwise. --- ctsm_py/utils.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index 1980203..d4afa27 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -357,3 +357,10 @@ def get_thisVar_da(thisVar, this_ds, vegtypes_str): return thisvar_da +# Is this PFT a managed crop? +# SSR TODO: Require that input be a single string. +def is_this_mgd_crop(this_pft): + notcrop_list = ["tree", "grass", "shrub", "unmanaged", "not_vegetated"] + return not any(n in this_pft for n in notcrop_list) + + From 1c9ce066fb7c8b3179e9324715fb0366d4e7d7de Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 28 Oct 2021 16:58:05 -0600 Subject: [PATCH 14/45] Added to utils.py: is_each_mgd_crop(). Given a list of PFTs, returns a list with True for managed crops and False otherwise. --- ctsm_py/utils.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index d4afa27..0c74c9c 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -364,3 +364,8 @@ def is_this_mgd_crop(this_pft): return not any(n in this_pft for n in notcrop_list) +# Get boolean list of whether each PFT in list is a managed crop +def is_each_mgd_crop(this_pftlist): + return [is_this_mgd_crop(x) for x in this_pftlist] + + From 36d62d8788a8ce3f494d90e861a58e028f47aa2b Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 28 Oct 2021 16:58:50 -0600 Subject: [PATCH 15/45] Added to utils.py: trim_to_mgd_crop(). Given a DataArray, remove all PFTs except managed crops. --- ctsm_py/utils.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index 0c74c9c..3addbeb 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -369,3 +369,24 @@ def is_each_mgd_crop(this_pftlist): return [is_this_mgd_crop(x) for x in this_pftlist] +# Given a DataArray, remove all PFTs except managed crops. +def trim_to_mgd_crop(thisvar_da): + + # Handle input DataArray without pft dimension + if not any(np.array(list(thisvar_da.dims)) == "pft"): + print("Input DataArray has no pft dimension and therefore trim_to_mgd_crop() has no effect.") + return thisvar_da + + # Throw error if pft dimension isn't strings + if not isinstance(thisvar_da.pft.values[0], str): + raise TypeError("Input DataArray's pft dimension is not in string form, and therefore trim_to_mgd_crop() cannot work.") + + # Get boolean list of whether each PFT is a managed crop + is_crop = is_each_mgd_crop(thisvar_da.pft.values) + + # Warn if no managed crops were found, but still return the empty result + if np.all(np.bitwise_not(is_crop)): + print("No managed crops found! Returning empty DataArray.") + return thisvar_da.isel(pft = [i for i, x in enumerate(is_crop) if x]) + + From e6ad064ab3ffa0b87fe888c91ee4b490e360a67b Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 28 Oct 2021 17:01:37 -0600 Subject: [PATCH 16/45] Added to utils.py: grid_one_timestep(). Make a geographically gridded DataArray (with PFT dimension) of one timestep in a given variable within a DataSet. --- ctsm_py/utils.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index 3addbeb..f55ba5a 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -390,3 +390,49 @@ def trim_to_mgd_crop(thisvar_da): return thisvar_da.isel(pft = [i for i, x in enumerate(is_crop) if x]) +# Make a geographically gridded DataArray (with PFT dimension) of one timestep in a given variable within a DataSet. +def grid_one_timestep(this_ds, thisVar, time_index, vegtypes): + + # Get this variable's values for this time step + thisvar_da = get_thisVar_da(thisVar, this_ds, vegtypes["str"]) + thisvar_da_1time = thisvar_da[dict(time=time_index)] + + # Get gridcell indices for this time step + ixy_da = get_thisVar_da("pfts1d_ixy", this_ds, vegtypes["str"]) + jxy_da = get_thisVar_da("pfts1d_jxy", this_ds, vegtypes["str"]) + ixy = ixy_da[dict(time=time_index)] + jxy = jxy_da[dict(time=time_index)] + + # Get PFT indices for this time step + vt_da = get_thisVar_da("pfts1d_itype_veg", this_ds, vegtypes["str"]) + vt = vt_da[dict(time=time_index)].values + + # Get dataset lon/lat grid + lon = this_ds.lon + lat = this_ds.lat + + # Set up empty array: PFT * lat * lon + npft = np.max(vegtypes["int"].values) + 1 + nlat = len(lat.values) + nlon = len(lon.values) + thisvar_pyx = np.empty([npft, nlat, nlon]) + + # Fill with this variable + thisvar_pyx[vt, + jxy.values.astype(int) - 1, + ixy.values.astype(int) - 1] = thisvar_da_1time.values + + # Assign coordinates and name + thisvar_pyx = xr.DataArray(thisvar_pyx, dims=("pft","lat","lon")) + thisvar_pyx = thisvar_pyx.assign_coords( \ + pft=vegtypes["all_str"], + lat=lat.values, + lon=lon.values) + thisvar_pyx.name = thisVar + + # Restrict to managed crops + thisvar_pyx = thisvar_pyx[is_each_mgd_crop(thisvar_pyx.pft.values)] + + return thisvar_pyx + + From 303306210bc7abe1332eec3e1a217fccdd2b5fcc Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 28 Oct 2021 17:05:55 -0600 Subject: [PATCH 17/45] Updated 2d_crop_work.py to use new functions in utils.py. --- 2d_crop_work.py | 362 +++--------------------------------------------- 1 file changed, 21 insertions(+), 341 deletions(-) diff --git a/2d_crop_work.py b/2d_crop_work.py index b35490c..c594b47 100644 --- a/2d_crop_work.py +++ b/2d_crop_work.py @@ -8,129 +8,34 @@ import matplotlib.pyplot as plt import warnings import glob -import cftime import cartopy.crs as ccrs import cartopy.feature as cfeature import sys sys.path.append("/Users/sam/Documents/git_repos/ctsm_python_gallery_myfork/ctsm_py/") -from utils import cyclic_dataarray +from utils import import_ds_from_filelist, get_thisVar_da, trim_to_mgd_crop, grid_one_timestep, pftlist, cyclic_dataarray -pftname = ["not_vegetated", - "needleleaf_evergreen_temperate_tree", - "needleleaf_evergreen_boreal_tree", - "needleleaf_deciduous_boreal_tree", - "broadleaf_evergreen_tropical_tree", - "broadleaf_evergreen_temperate_tree", - "broadleaf_deciduous_tropical_tree", - "broadleaf_deciduous_temperate_tree", - "broadleaf_deciduous_boreal_tree", - "broadleaf_evergreen_shrub", - "broadleaf_deciduous_temperate_shrub", - "broadleaf_deciduous_boreal_shrub", - "c3_arctic_grass", - "c3_non-arctic_grass", - "c4_grass", - "unmanaged_c3_crop", - "unmanaged_c3_irrigated", - "temperate_corn", - "irrigated_temperate_corn", - "spring_wheat", - "irrigated_spring_wheat", - "winter_wheat", - "irrigated_winter_wheat", - "soybean", - "irrigated_soybean", - "barley", - "irrigated_barley", - "winter_barley", - "irrigated_winter_barley", - "rye", - "irrigated_rye", - "winter_rye", - "irrigated_winter_rye", - "cassava", - "irrigated_cassava", - "citrus", - "irrigated_citrus", - "cocoa", - "irrigated_cocoa", - "coffee", - "irrigated_coffee", - "cotton", - "irrigated_cotton", - "datepalm", - "irrigated_datepalm", - "foddergrass", - "irrigated_foddergrass", - "grapes", - "irrigated_grapes", - "groundnuts", - "irrigated_groundnuts", - "millet", - "irrigated_millet", - "oilpalm", - "irrigated_oilpalm", - "potatoes", - "irrigated_potatoes", - "pulses", - "irrigated_pulses", - "rapeseed", - "irrigated_rapeseed", - "rice", - "irrigated_rice", - "sorghum", - "irrigated_sorghum", - "sugarbeet", - "irrigated_sugarbeet", - "sugarcane", - "irrigated_sugarcane", - "sunflower", - "irrigated_sunflower", - "miscanthus", - "irrigated_miscanthus", - "switchgrass", - "irrigated_switchgrass", - "tropical_corn", - "irrigated_tropical_corn", - "tropical_soybean", - "irrigated_tropical_soybean"] -# %% Import dataset +# Import dataset + +# Define list of variables to import +myVars = ["CPHASE", \ + "GDDHARV", + "GDDPLANT", + "GPP", + "GRAINC_TO_FOOD", + "NPP", + "TLAI", + "TOTVEGC"] # Get list of all files in $indir matching $pattern indir = "/Volumes/Reacher/CESM_runs/f10_f10_mg37/" pattern = "*h1.*-01-01-00000.nc" filelist = glob.glob(indir + pattern) -# Set up function to drop unwanted vars in preprocessing of open_mfdataset() -def mfdataset_preproc(ds): - vars_to_import = list(ds.dims) + \ - ["CPHASE", - "GDDHARV", - "GDDPLANT", - "GPP", - "GRAINC_TO_FOOD", - "NPP", - "TLAI", - "TOTVEGC", - "pfts1d_itype_veg", - "pfts1d_ixy", - "pfts1d_jxy", - "pfts1d_lon", - "pfts1d_lat"] - varlist = list(ds.variables) - vars_to_drop = list(np.setdiff1d(varlist, vars_to_import)) - ds = ds.drop_vars(vars_to_drop) - ds = xr.decode_cf(ds, decode_times = True) - return ds - # Import -this_ds = xr.open_mfdataset(filelist, \ - concat_dim="time", - preprocess=mfdataset_preproc) -# this_ds = utils.time_set_mid(this_ds, 'time') +this_ds, vegtypes = import_ds_from_filelist(filelist, pftlist, myVars) # Get dates in a format that matplotlib can use with warnings.catch_warnings(): @@ -139,248 +44,23 @@ def mfdataset_preproc(ds): datetime_vals = this_ds.indexes["time"].to_datetimeindex() # Get PFT list, integers (use only first timestep) -vegtype_int = this_ds.pfts1d_itype_veg -vegtype_int.values = vegtype_int.values.astype(int) -if not all((vegtype_int.values == vegtype_int.values[0,:]).all(axis=1)): - raise ValueError("Some veg type changes over time") -vegtype_int = vegtype_int[0,:] -# Get PFT list, strings -vegtype_str = list(np.array(pftname)[vegtype_int.values]) -# %% Read variable + +# %% Read one variable from dataset. (Do nothing with it.) # Which variable? thisVar = "CPHASE" -def is_this_mgd_crop(x): - notcrop_list = ["tree", "grass", "shrub", "unmanaged", "not_vegetated"] - return not any(n in x for n in notcrop_list) -def get_thisVar_da(thisVar, this_ds, vegtype_str): - # Make DataArray for this variable - thisvar_da = np.array(this_ds.variables[thisVar]) - theseDims = this_ds.variables[thisVar].dims - thisvar_da = xr.DataArray(thisvar_da, - dims = theseDims) - - # Define coordinates of this variable's DataArray - dimsDict = dict() - for thisDim in theseDims: - if thisDim == "pft": - dimsDict[thisDim] = vegtype_str - elif any(np.array(list(this_ds.dims.keys())) == thisDim): - dimsDict[thisDim] = this_ds[thisDim] - else: - raise ValueError("Unknown dimension for coordinate assignment: " + thisDim) - thisvar_da = thisvar_da.assign_coords(dimsDict) - - # If it has PFT dimension, trim to managed crops - if any(np.array(list(thisvar_da.dims)) == "pft"): - is_crop = [ is_this_mgd_crop(x) for x in thisvar_da.pft.values ] - thisvar_da = thisvar_da[:, is_crop] - - return thisvar_da - -thisvar_da = get_thisVar_da(thisVar, this_ds, vegtype_str) - - -# %% Grid variable (takes a while) and make map - -# ixy = get_thisVar_da("pfts1d_ixy", this_ds, vegtype_str) -# jxy = get_thisVar_da("pfts1d_jxy", this_ds, vegtype_str) -# lon = get_thisVar_da("lon", this_ds, vegtype_str) -# lat = get_thisVar_da("lat", this_ds, vegtype_str) -# ttime = get_thisVar_da("time", this_ds, vegtype_str) -ixy = this_ds.pfts1d_ixy -jxy = this_ds.pfts1d_jxy -lon = this_ds.lon -lat = this_ds.lat -ttime = this_ds.time - -nlat = len(lat.values) -nlon = len(lon.values) -npft = np.max(vegtype_int.values) + 1 -ntim = len(ttime.values) - -tmp_tpyx = np.empty([ntim, npft, nlat, nlon]) -tmp_tpyx[:, \ - vegtype_int.values, - jxy.values.astype(int) - 1, - ixy.values.astype(int) - 1] = this_ds.variables[thisVar].values - -tmp2_tpyx = xr.DataArray(tmp_tpyx, dims=("time","pft","lat","lon")) -tmp2_tpyx = tmp2_tpyx.assign_coords( \ - time=ttime, - pft=pftname, - lat=lat.values, - lon=lon.values) -tmp2_tpyx.name = thisVar -is_crop = [ is_this_mgd_crop(x) for x in tmp2_tpyx.pft.values ] -tmp2_tpyx = tmp2_tpyx[:, is_crop] -print(tmp2_tpyx) - -# Make map -tmp3 = tmp2_tpyx.isel(time=0, pft=0) -tmp4 = cyclic_dataarray(tmp3) -ax = plt.axes(projection=ccrs.PlateCarree()) -plt.pcolor(tmp4.lon.values, tmp4.lat.values, tmp4, transform=ccrs.PlateCarree()) -ax.coastlines() -plt.show() - - -# %% Plot and make map, more efficiently - -tmp = thisvar_da[dict(time=0)] - -# ixy = this_ds.pfts1d_ixy[dict(time=0)] -# jxy = this_ds.pfts1d_jxy[dict(time=0)] -ixy_da = get_thisVar_da("pfts1d_ixy", this_ds, vegtype_str) -jxy_da = get_thisVar_da("pfts1d_jxy", this_ds, vegtype_str) -ixy = ixy_da[dict(time=0)] -jxy = jxy_da[dict(time=0)] -lon = this_ds.lon -lat = this_ds.lat - -vt_da = get_thisVar_da("pfts1d_itype_veg", this_ds, vegtype_str) - -vt = vt_da[dict(time=0)].values - -nlat = len(lat.values) -nlon = len(lon.values) -npft = np.max(vegtype_int.values) + 1 - -tmp_pyx = np.empty([npft, nlat, nlon]) -tmp_pyx[vt, - jxy.values.astype(int) - 1, - ixy.values.astype(int) - 1] = tmp.values - -tmp2_pyx = xr.DataArray(tmp_pyx, dims=("pft","lat","lon")) -tmp2_pyx = tmp2_pyx.assign_coords( \ - pft=pftname, - lat=lat.values, - lon=lon.values) -tmp2_pyx.name = thisVar -is_crop = [ is_this_mgd_crop(x) for x in tmp2_pyx.pft.values ] -tmp2_pyx = tmp2_pyx[is_crop] - -# Make map -tmp3 = tmp2_pyx.isel(pft=0) -tmp4 = cyclic_dataarray(tmp3) -ax = plt.axes(projection=ccrs.PlateCarree()) -plt.pcolor(tmp4.lon.values, tmp4.lat.values, tmp4, transform=ccrs.PlateCarree()) -ax.coastlines() -plt.show() - - -# %% Plot and make map, more efficiently, as function - -def grid_one_timestep(thisvar_da, time_index): - - # Get this variable's values for this time step - thisvar_da_1time = thisvar_da[dict(time=time_index)] - - # Get gridcell indices for this time step - ixy_da = get_thisVar_da("pfts1d_ixy", this_ds, vegtype_str) - jxy_da = get_thisVar_da("pfts1d_jxy", this_ds, vegtype_str) - ixy = ixy_da[dict(time=time_index)] - jxy = jxy_da[dict(time=time_index)] - - # Get PFT indices for this time step - vt_da = get_thisVar_da("pfts1d_itype_veg", this_ds, vegtype_str) - vt = vt_da[dict(time=time_index)].values - - # Get dataset lon/lat grid - lon = this_ds.lon - lat = this_ds.lat - - # Set up empty array: PFT * lat * lon - npft = np.max(vegtype_int.values) + 1 - nlat = len(lat.values) - nlon = len(lon.values) - thisvar_pyx = np.empty([npft, nlat, nlon]) - - # Fill with this variable - thisvar_pyx[vt, - jxy.values.astype(int) - 1, - ixy.values.astype(int) - 1] = thisvar_da_1time.values - - # Assign coordinates and name - thisvar_pyx = xr.DataArray(thisvar_pyx, dims=("pft","lat","lon")) - thisvar_pyx = thisvar_pyx.assign_coords( \ - pft=pftname, - lat=lat.values, - lon=lon.values) - thisvar_pyx.name = thisVar - - # Restrict to managed crops - is_crop = [ is_this_mgd_crop(x) for x in thisvar_pyx.pft.values ] - thisvar_pyx = thisvar_pyx[is_crop] - - return thisvar_pyx - -def grid_timeslice(thisvar_da, time_str_0: str, time_str_1: str = ""): - - one_timestep = time_str_1 == "" - if (one_timestep): - time_slice = slice(time_str_0) - else: - time_slice = slice(time_str_0, time_str_1) - - # Get this variable's values for this time slice - thisvar_da_1time = thisvar_da[dict(time=time_slice)] - - # Get gridcell indices for this time slice - ixy_da = get_thisVar_da("pfts1d_ixy", this_ds, vegtype_str) - jxy_da = get_thisVar_da("pfts1d_jxy", this_ds, vegtype_str) - ixy = ixy_da[dict(time=time_slice)] - jxy = jxy_da[dict(time=time_slice)] - - # Get PFT indices for this time slice - vt_da = get_thisVar_da("pfts1d_itype_veg", this_ds, vegtype_str) - vt = vt_da[dict(time=time_slice)].values - - # Get dataset lon/lat grid - lon = this_ds.lon - lat = this_ds.lat - - # Set up empty array: PFT * lat * lon - npft = np.max(vegtype_int.values) + 1 - nlat = len(lat.values) - nlon = len(lon.values) - if (one_timestep): - raise ValueError("Finish coding this") - ntim = len(ttime.values) - tmp_tpyx = np.empty([ntim, npft, nlat, nlon]) - else: - thisvar_out = np.empty([npft, nlat, nlon]) - - # Fill with this variable - if (one_timestep): - raise ValueError("Finish coding this") - else: - thisvar_out[vt, - jxy.values.astype(int) - 1, - ixy.values.astype(int) - 1] = thisvar_da_1time.values - - # Assign coordinates and name - if (one_timestep): - raise ValueError("Finish coding this") - else: - thisvar_out = xr.DataArray(thisvar_out, dims=("pft","lat","lon")) - thisvar_out = thisvar_out.assign_coords( \ - pft=pftname, - lat=lat.values, - lon=lon.values) - thisvar_out.name = thisVar +thisvar_da = get_thisVar_da(thisVar, this_ds, vegtypes["str"]) +thisvar_da = trim_to_mgd_crop(thisvar_da) +thisvar_da - # Restrict to managed crops - is_crop = [ is_this_mgd_crop(x) for x in thisvar_out.pft.values ] - thisvar_out = thisvar_out[is_crop] - return thisvar_out +# %% Grid and make map, more efficiently, as function -# Grid this timestep -tmp_pyx = grid_one_timestep(thisvar_da, 0) +# Grid +tmp_pyx = grid_one_timestep(this_ds, "pfts1d_itype_veg", 0, vegtypes) # Make map tmp_yx = tmp_pyx.isel(pft=0) From 3083724d9451862d5683cd44557e18dc79f577b8 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 29 Oct 2021 10:53:06 -0600 Subject: [PATCH 18/45] Removed managed-crop restriction step from grid_one_timestep(). --- ctsm_py/utils.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index f55ba5a..bd59c7a 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -406,7 +406,7 @@ def grid_one_timestep(this_ds, thisVar, time_index, vegtypes): # Get PFT indices for this time step vt_da = get_thisVar_da("pfts1d_itype_veg", this_ds, vegtypes["str"]) vt = vt_da[dict(time=time_index)].values - + # Get dataset lon/lat grid lon = this_ds.lon lat = this_ds.lat @@ -425,14 +425,10 @@ def grid_one_timestep(this_ds, thisVar, time_index, vegtypes): # Assign coordinates and name thisvar_pyx = xr.DataArray(thisvar_pyx, dims=("pft","lat","lon")) thisvar_pyx = thisvar_pyx.assign_coords( \ - pft=vegtypes["all_str"], - lat=lat.values, - lon=lon.values) + pft = vegtypes["all_str"], + lat = lat.values, + lon = lon.values) thisvar_pyx.name = thisVar - # Restrict to managed crops - thisvar_pyx = thisvar_pyx[is_each_mgd_crop(thisvar_pyx.pft.values)] - return thisvar_pyx - From d0f043e64a0c322fcc4499cddcdbddf9317c3e2b Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 29 Oct 2021 11:28:38 -0600 Subject: [PATCH 19/45] grid_one_timestep() is now grid_one_variable(). Instead of requiring one timestep specified by an integer, now allows (optionally) integer, str, or slice of either. --- 2d_crop_work.py | 20 +++++++++----- ctsm_py/utils.py | 69 ++++++++++++++++++++++++++++++++++-------------- 2 files changed, 63 insertions(+), 26 deletions(-) diff --git a/2d_crop_work.py b/2d_crop_work.py index c594b47..60eba02 100644 --- a/2d_crop_work.py +++ b/2d_crop_work.py @@ -13,7 +13,7 @@ import sys sys.path.append("/Users/sam/Documents/git_repos/ctsm_python_gallery_myfork/ctsm_py/") -from utils import import_ds_from_filelist, get_thisVar_da, trim_to_mgd_crop, grid_one_timestep, pftlist, cyclic_dataarray +import utils @@ -35,7 +35,7 @@ filelist = glob.glob(indir + pattern) # Import -this_ds, vegtypes = import_ds_from_filelist(filelist, pftlist, myVars) +this_ds, vegtypes = utils.import_ds_from_filelist(filelist, utils.pftlist, myVars) # Get dates in a format that matplotlib can use with warnings.catch_warnings(): @@ -52,19 +52,27 @@ # Which variable? thisVar = "CPHASE" -thisvar_da = get_thisVar_da(thisVar, this_ds, vegtypes["str"]) -thisvar_da = trim_to_mgd_crop(thisvar_da) +thisvar_da = utils.get_thisVar_da(thisVar, this_ds, vegtypes["str"]) +thisvar_da = utils.trim_to_mgd_crop(thisvar_da) thisvar_da # %% Grid and make map, more efficiently, as function +# import importlib +# importlib.reload(utils) + # Grid -tmp_pyx = grid_one_timestep(this_ds, "pfts1d_itype_veg", 0, vegtypes) +# tmp_pyx = utils.grid_one_variable(this_ds, "pfts1d_itype_veg", vegtypes, time=3) +tmp_pyx = utils.grid_one_variable(this_ds, "pfts1d_itype_veg", vegtypes, time="2000-01-04") # Make map tmp_yx = tmp_pyx.isel(pft=0) -tmp_yx = cyclic_dataarray(tmp_yx) +if tmp_yx.shape[0] == 1: + tmp_yx = tmp_yx.squeeze() +else: + raise ValueError("You must select one time step to plot") +tmp_yx = utils.cyclic_dataarray(tmp_yx) ax = plt.axes(projection=ccrs.PlateCarree()) plt.pcolor(tmp_yx.lon.values, tmp_yx.lat.values, tmp_yx, transform=ccrs.PlateCarree()) ax.coastlines() diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index bd59c7a..4bb36ad 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -390,45 +390,74 @@ def trim_to_mgd_crop(thisvar_da): return thisvar_da.isel(pft = [i for i, x in enumerate(is_crop) if x]) -# Make a geographically gridded DataArray (with PFT dimension) of one timestep in a given variable within a DataSet. -def grid_one_timestep(this_ds, thisVar, time_index, vegtypes): +# Make a geographically gridded DataArray (with PFT dimension) of one variable within a DataSet. Optionally subset by time index (integer) or slice. +def grid_one_variable(this_ds, thisVar, vegtypes, time=None): - # Get this variable's values for this time step thisvar_da = get_thisVar_da(thisVar, this_ds, vegtypes["str"]) - thisvar_da_1time = thisvar_da[dict(time=time_index)] - - # Get gridcell indices for this time step ixy_da = get_thisVar_da("pfts1d_ixy", this_ds, vegtypes["str"]) jxy_da = get_thisVar_da("pfts1d_jxy", this_ds, vegtypes["str"]) - ixy = ixy_da[dict(time=time_index)] - jxy = jxy_da[dict(time=time_index)] - - # Get PFT indices for this time step vt_da = get_thisVar_da("pfts1d_itype_veg", this_ds, vegtypes["str"]) - vt = vt_da[dict(time=time_index)].values + + # Get this variable's values for selected time step(s), if provided + if time != None: + def check_slice_type(this_time): + if isinstance(this_time, slice): + if this_time == slice(0): + raise ValueError("slice(0) will be empty") + elif this_time.start != None: + return type(this_time.start) + elif this_time.stop != None: + return type(this_time.stop) + elif this_time.step != None: + return type(this_time.step) + else: + raise TypeError("slice is all None?") + else: + return type(this_time) + time_type = check_slice_type(time) + if time_type == int: + # thisvar_da = thisvar_da.isel(time=time) + if isinstance(time, int): + thisvar_da = thisvar_da.isel(time=slice(time,time+1)) + else: + thisvar_da = thisvar_da.isel(time=time) + # ^ Have to slice time like that instead of with index directly because otherwise .assign_coords() will throw an error + ixy_da = ixy_da.isel(time=time) + jxy_da = jxy_da.isel(time=time) + vt_da = vt_da.isel(time=time).values + elif time_type == str: + thisvar_da = thisvar_da.sel(time=time) + ixy_da = ixy_da.sel(time=time) + jxy_da = jxy_da.sel(time=time) + vt_da = vt_da.sel(time=time).values + else: + raise TypeError(f"'time' argument must be type int, str, or slice of those (not {type(time)})") # Get dataset lon/lat grid lon = this_ds.lon lat = this_ds.lat - # Set up empty array: PFT * lat * lon + # Set up empty array: time * PFT * lat * lon + ntime = len(thisvar_da.time) npft = np.max(vegtypes["int"].values) + 1 nlat = len(lat.values) nlon = len(lon.values) - thisvar_pyx = np.empty([npft, nlat, nlon]) + thisvar_tpyx = np.empty([ntime, npft, nlat, nlon]) # Fill with this variable - thisvar_pyx[vt, - jxy.values.astype(int) - 1, - ixy.values.astype(int) - 1] = thisvar_da_1time.values + thisvar_tpyx[:, + vt_da, + jxy_da.values.astype(int) - 1, + ixy_da.values.astype(int) - 1] = thisvar_da.values # Assign coordinates and name - thisvar_pyx = xr.DataArray(thisvar_pyx, dims=("pft","lat","lon")) - thisvar_pyx = thisvar_pyx.assign_coords( \ + thisvar_tpyx = xr.DataArray(thisvar_tpyx, dims=("time","pft","lat","lon")) + thisvar_tpyx = thisvar_tpyx.assign_coords( \ + time = thisvar_da.time, pft = vegtypes["all_str"], lat = lat.values, lon = lon.values) - thisvar_pyx.name = thisVar + thisvar_tpyx.name = thisVar - return thisvar_pyx + return thisvar_tpyx From af96ea9e90e2400867ff1f7c467f3326dedc6d4e Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 29 Oct 2021 12:20:14 -0600 Subject: [PATCH 20/45] Changes to 1d_crop_work.ipnyb. --- 1d_crop_work.ipynb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/1d_crop_work.ipynb b/1d_crop_work.ipynb index f498237..73b653f 100644 --- a/1d_crop_work.ipynb +++ b/1d_crop_work.ipynb @@ -161,12 +161,12 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "metadata": {}, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAaQAAAEICAYAAAAQkoCgAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8rg+JYAAAACXBIWXMAAAsTAAALEwEAmpwYAABwk0lEQVR4nO2dd5gcxdG435oNl++kU45IAiGBBBYggkgWxjIYg0niI5lobAMmOZJ+GIzBHzYY23w2xmAjwIFgMiZnEAiEhAQIESRACAnFk+50cdP074+ZnZ3Ne9Ld7exev89zz+2Enu3emenqqq6uEqUUGo1Go9EUG6PYFdBoNBqNBrRA0mg0Go1H0AJJo9FoNJ5ACySNRqPReAItkDQajUbjCbRA0mg0Go0n0AJJo9FoNJ5ACySNpkQQkRUisk5Ealz7zhKRl+zPSkTaRaRNRFaLyI0i4nOV7bSPrROROSJSW6SmaDQZ0QJJoykt/MCFOY5/RSlVCxwMnAR8z3XsCPvY7sCewP/rtVpqNFuBFkgaTWlxPfBTERmQ6ySl1IfAq8DUDMdWA09mOqbRFBMtkDSa0mIB8BLw01wnicjOwAHAogzHxgCHZTqm0RQTf7EroNFous0vgNdE5I8Zjr0tIjFgE/A3YI7r2MMiEgVagMeBX/d6TTWabqAFkkZTYiillojIf4FLgA9SDu+ulFqepehRSqnnerd2Gs3Wo012Gk1pciWWw8KoYldEo+kptEDSaEoQWwu6F7ig2HXRaHoKLZA0mtLlaqAm71kaTYkgOkGfRqPRaLyA1pA0Go1G4wm0QNJoNBqNJ9ACSaPRaDSeQAskjUaj0XgCvTB2Kxk8eLAaN25csauh0Wg0JcXChQs3KqWGZDqmBdJWMm7cOBYsWFDsamg0Gk1JISKfZzumTXYajUaj8QRaIGk0Go3GE2iBpNFoNBpPoOeQNJoyIxKJsGrVKrq6uopdFU0/prKyktGjRxMIBAouowWSRlNmrFq1irq6OsaNG4eIFLs6mn6IUoqmpiZWrVrF+PHjCy6nTXYaTZnR1dXFoEGDtDDSFA0RYdCgQd3W0rVA0mjKEC2MNMVma55BLZA0ZUN7pJ37PrqPSCxS7KpoNJqtQM8hacqGPyz8A/d8dA9j6sYwY+SMYldHo9F0E60hacqGNe1rAOiKau+yYrPvvvv2yrmp3HHHHXz55ZfdKrNixQqmTp2a9fjixYt54okntrpOfUVzczM333xzsavRo2iBpCkbFFayST1/Unxef/31tH2xWCzjdqZzC2VrBFI+vCSQotFo1mPFEki56rStaJOdpmwwlQmAoAVSnF8+9j5Lv9zSo9fceWQ9Vx4xJec5tbW1tLW18dJLL/HLX/6SESNGsHjxYm6++eak7aVLlzrnmqbJeeedx8svv8z48eMxTZMzzzyT2bNnc/XVV/PYY4/R2dnJvvvuy1//+lceeOABFixYwMknn0xVVRXz5s1j6dKl/PjHP6atrY3Bgwdzxx13MGLECBYuXMiZZ55JdXU1+++/f9Z6h8NhfvGLX9DZ2cncuXO59NJLOfzwwzn//PN57733iEajXHXVVRx55JHccccdPPzww8RiMZYsWcJPfvITwuEw//jHP6ioqOCJJ56gsbGRmTNnMm3aNObPn8+WLVu4/fbb2WuvvWhvb8963ccff5yuri7a29t59NFHOfLII9m8eTORSIRrrrmGI488kksuuYRPPvmEadOmMWvWLK6//nquv/567rvvPkKhEEcffTS//OUvs7b1rrvu4oYbbkBE2HXXXfnHP/7B559/zplnnsmGDRsYMmQIc+bMYezYsZx++uk0NjayaNEidt99d5qamqivr2fBggWsXbuW3/72t8yePXurn6k4WkPSlA1aQ/Im8+fP59prr2Xp0qUZt+M8+OCDrFixgvfee4+//e1vzJs3zzl23nnn8dZbb7FkyRI6Ozv573//y+zZs5k+fTr/+te/WLx4MX6/n/PPP5/777/fEUCXX345AGeccQY33XRT0jUzEQwGufrqqzn++ONZvHgxxx9/PNdeey1f+9rXeOutt3jxxRf52c9+Rnt7OwBLlizh3//+N/Pnz+fyyy+nurqaRYsWMWPGDO666y7nuu3t7bz++uvcfPPNnHnmmQA5rztv3jzuvPNOXnjhBSorK3nooYd4++23efHFF/nJT36CUorrrruO7bffnsWLF3P99dfzzDPPsGzZMubPn8/ixYtZuHAhr7zySsZ2vv/++1x77bW88MILvPPOO/zxj390fudTTz2Vd999l5NPPpkLLrjAKfPxxx/z3HPP8bvf/Q6ANWvWMHfuXP773/9yySWX5HkKCkNrSJryQRW7At4jnybTF+y1115JiyNTt+PMnTuX4447DsMwGD58OAcddJBz7MUXX+S3v/0tHR0dbNq0iSlTpnDEEUcklf/oo49YsmQJs2bNAiyT4IgRI2hpaaG5uZmvfvWrAJxyyik8+eSTBdf/mWee4dFHH+WGG24ArHVeK1euBOCggw6irq6Ouro6GhoanDrtsssuvPvuu841TjzxRAAOPPBAtmzZQnNzc87rzpo1i8bGRsBaZHrZZZfxyiuvYBgGq1evZt26dRnr+cwzz7DbbrsB0NbWxrJlyzjwwAPTzn3hhReYPXs2gwcPBnC+a968eTz44IPO7/Tzn//cKXPcccfh8/mc7aOOOgrDMNh5550z1mdr0AJJUzYoLZE8SU1NTc7tOEplvn9dXV2ce+65LFiwgDFjxnDVVVdlXHCplGLKlClpWlBzc/M2ac1KKR544AEmTZqUtP/NN9+koqLC2TYMw9k2DCNpriX1+0Uk83XNGG8+dR81FYmu+V//+hcbNmxg4cKFBAIBxo0bl7X9l156KT/4wQ8KalMhv4n7nNT75m57tnvXXbTJTlM2xF+Knno5NH3L/vvvzwMPPIBpmqxbt46XXnoJwOl8Bw8eTFtbG/fff79Tpq6ujtbWVgAmTZrEhg0bHIEUiUR4//33GTBgAA0NDcydOxewOvhcuK8JcMghh/B///d/znO1aNGibrft3nvvBSwtsKGhgYaGhszXjYWtApEOp2xLSwtDhw4lEAjw4osv8vnnn2et5+23305bWxsAq1evZv369Rnrc/DBB3PffffR1NQEwKZNmwDL4/Gee+4BrN8p13xbb6A1JE3ZYGI5NcSdGzSlxbHHHsvzzz/P1KlT2XHHHdl7771paGhgwIABfO9732OXXXZh3Lhx7Lnnnk6Z008/nbPPPttxarj//vu54IILaGlpIRqNctFFFzFlyhTmzJnjODUccsghOetx0EEHcd111zFt2jQuvfRSrrjiCi666CJ23XVXlFKMGzeO//73v91q28CBA9l3330dpwYg83Uf+k9a2ZNPPpkjjjiC6dOnM23aNCZPngzAoEGD2G+//Zg6dSrf/OY3uf766/nggw+YMcNag1dbW8s///lPhg4dmnbNKVOmcPnll/PVr34Vn8/Hbrvtxh133MFNN93EmWeeyfXXX+84NfQlokeTW8f06dOVzhjrLc56+izeXPsmfzjoDxw89uBiV6dofPDBB+y0007FrsZW0dbWRm1tLU1NTey111689tprDB8+vNjV2iZmzpzJDTfcwPTp0/OfHOmCDR+AvwKG7tz7letlMj2LIrJQKZXxxyiqyU5EDhWRj0RkuYikuWmIxU328XdFZPd8ZUWkUUSeFZFl9v+B9v5BIvKiiLSJyJ9Svucl+1qL7b/0IYXG82gNqfQ5/PDDmTZtGgcccABXXHFFyQujraaf6glFM9mJiA/4MzALWAW8JSKPKqXcvqDfBCbaf3sDfwH2zlP2EuB5pdR1tqC6BLgY6AKuAKbaf6mcrJQqH5VnzTvw1wPhey/AqD2KXZveY+7voXMzzLrascVrgVS6xOeN+oKnn36aiy++OGnf+PHjeeihh3r0e/qyTak0NTVx8MHp1oLnn3+eQYMGFaFGuSnmHNJewHKl1KcAInIPcCTgFkhHAncpq6d5Q0QGiMgIYFyOskcCM+3ydwIvARcrpdqBuSKyQy+3yxt88Jj1f9lz5S2QnrsKgOsH1LFgnTWe+OG/FvJ/396Vb+4yoogV6z0e+PgBrpp3Fa8c/woDKwcWuzolyyGHHJJ3PqnvUSn/t41BgwaxePHiHrlWX1BMk90o4AvX9ip7XyHn5Co7TCm1BsD+X6j5bY5trrtCymFlZcjytKGitrj16CPuWppYhIgo/vTi8uJVppd5ePnDAHza8mlxK6LR9DDFFEiZOv3UYUG2cwop2x1OVkrtAhxg/52S6SQR+b6ILBCRBRs2bNiGr+tFOpvhximwZrG17a8sZm2KhKIzEst/WolSG7QGGe2R9iLXpJeJRWD9hxANFbsmfUc/dzIrpkBaBYxxbY8GUqMkZjsnV9l1tlkP+39mR3wXSqnV9v9W4N9Y5sRM592qlJqulJo+ZMiQfJctDq/9AbasgpX24kDpj0vNFJ3h8hVIVf4qAF5d9WqRa9LLdG6GaCe0e3Tw1ytogVQs3gImish4EQkCJwCPppzzKHCq7W23D9Bim+FylX0UOM3+fBrwSK5KiIhfRAbbnwPA4cCSbW9ekWgYDUCnCK9XVUIZWB+7jSgisfJ9sePBY+/56J4i16QX6WqBLasBiCiz/LXBDKxqXcWatjXFrkafUjSBpJSKAucBTwMfAPcppd4XkbNF5Gz7tCeAT4HlwG3AubnK2mWuA2aJyDIsL7zr4t8pIiuAG4HTRWSViOwMVABPi8i7wGJgtf1dpUeky5k7+vWggfxg+FA+DTUVuVLFwCQUjRGKlqeW5J7i/MPCP3Du47+lpdNbWXK3OR/SpsT82IpYOytaVrC5a3Paab2RD2lb+cUvfsFzzz23dYVtk12zQEuohU1dmwoqVlvbM3PFxU69UdRIDUqpJ7CEjnvfLa7PCvhhoWXt/U1AxlWRSqlxWapSHm5o/7e7M6pcGbBu7aZoJxOKWaciIKJo7Ypy4G9f5M3Lvl7s6vQqf1/ydwAue/Dr/Pnk3fOc3Xdky4fkDs4Z386XDylsu/F/2fZlmlfhHXfcwdSpUxk5cmQP1HrbicViXH311dt8nXVFUhUWL17MggULOOyww4ry/Tp0UDlhCyOAoD3SCqvy1BJyY7V93ZYymwyPReHPeyLj06MwbGoPZy7z5CWw9r2ercfwXeCb1+U8ZZvzIY1oxFSKM48/kqnHfIs/3XAzLz39EkTo1XxIYKVmOOOMMwiHw5imyQMPPEAgEODQQw9l7733ZtGiRey4447cddddVFdXM27cOM4880yeeeYZzjvvPJ566ikOP/xwZs+ezbhx4zjttNN47LHHiEQi/Oc//2Hy5Mls2LCBk046iaamJvbcc0+eeuopFi5cyOC6irT6/Pa3v6WyspILLriAH/3oR7zzzju88MILPP/888yZM4d//vOfAFx++eX897//paqqikceeYRhw4axYcMGzj77bCeK+B/+8Af2228/5s+fz0UXXURnZydVVVXMmTOH8ePHp+WCOv7447fuGdlK+uOMd7/AZ0+hRPvhIlEjWKaT4MuegU2fIqvfLnZNusVW5UO64RfMW2ilbxCBk757Evc+ey9Lliyhrb2NRx59pFfyIQHccsstXHjhhY62MHq0NS/70Ucf8f3vf593332X+vr6pGytlZWVzJ07lxNOOCHteoMHD+btt9/mnHPOcVJN/PKXv+RrX/sab7/9NkcffbQjMOImO/fM74EHHsirr1oOLAsWLKCtrY1IJMLcuXM54IADACvf0j777MM777zDgQceyG23WbMOF154IT/60Y946623eOCBBzjrrLMAmDx5Mq+88gqLFi3i6quv5rLLLsuYC6qv0RpSmRK15xn64w02KnomN4vnuMfKqdMt1+A8mkxfsFX5kIYO5qB9rXBnSsH8ufO5/U+3o8KK9RvXM2qHURx15FFJ5XsqH9KMGTO49tprWbVqFccccwwTJ04EYMyYMey3334AfOc73+Gmm27ipz/9KUDOzvuYY44BYI899nByDc2dO9eJCHHooYcycGD2Bc577LEHCxcupLW1lYqKCnbffXcWLFjAq6++yk033QRYiQUPP/xw5/xnn30WgOeeey5J8G/ZsoXW1lZaWlo47bTTWLZsGSJCJOKNOcj+2F/1C+KZWJTZD012UoZtdgkhIV3r9bIz5bbmQwp1hfjVxb/i3mfv5etf+Trn/vxc2jvTve56Kh/SSSedxN57783jjz/OIYccwt/+9jcmTJiQMadRvjZBIm+Qz+dzciRlD2qdvj+eA2nOnDnsu+++7Lrrrrz44ot88sknTuDSQCDg1Mf9PaZpMm/ePKqqqpKuef7553PQQQfx0EMPsWLFCmbOnJn9B+lDtMmuTInYD6fqx3NIZcVS1+qFMl08mZQPaUMTL81bCEAoZM0FDmwcSFtbG88+9qxTpjfyIX366adMmDCBCy64gG9/+9tO5teVK1c617777ru3KVfQ/vvvz3333QdYmV43b7Y9CLPc2v0POIAbbriBAw88kAMOOIBbbrmFadOm5RW03/jGN/jTnxKxpONhhFpaWhg1ygpuc8cddzjHU3Ms9TVaIJUpIftBNc0Y0ZhZti7QGZEy7LBXzHU+SoZ5wTW++/qyNr3Csccey+jRo5k6dSo/uPha9t5tKg31tdQ11DH7O7M5+sCjOeqoo5g6LeGyHc+HNG3aNGKxGPfffz8XX3wxX/nKV5g2bZrjwTdnzhx++MMfMmPGjDRtIZV7772XqVOnMm3aND788ENOPfVUAHbaaSfuvPNOdt11VzZt2sQ555yz1W298soreeaZZ9h999158sknGTFiBHV1dWSSSJvbw4zZaXfWrFnDjBkzGDZsGJWVlc78US5uuukmFixYwK677srOO+/MLbdYTsw///nPufTSS9lvv/2IxRJ9w0EHHcTSpUuZNm2ak1SwL9H5kLYST+ZDuqrB+XjY6BF8EQjw5+Gz+NOK45j/2SZWXPetIlauF3j2F/DaHwHYZfxYZ3esYzs6Prc6i7Jp85MXw5tWZ3LpkEH8tzbdRPTeaZY3XVnkQ1ryInsdfiqvPXw7TaMTLt07D9qZpU3WnMiOjTsSMAJ9Uq8VK1Zw+OGHs2RJz6yZD4VC+Hw+/H4/8+bN45xzzmHx2wthraWNLQsGiftN1so4WjojjG2sZkB1sEe+v6/obj4kPYdUpnTaIYOUijF/xVrAl7tAKWILozSkDD0LXQPHch5CHn744TQ3NxPu2MIVF57F8KGDyba0O2pG+0wg9TQrV67kf/7nfzBNk2AwaHnFrXs/47kenh7scbRAKlMi9lNsKpO6yb8g2jqZeZ/sR22Fn11GN+QuXPIkuux/LljM2uYYP/16qa997h8Cyckd9OWijMfdIYQ+bf6UYTXDGFw1eKu+qzv5kMaNG9dj2hHAxIkTWbTI1UYzStPSVzn4eCtITUjEuc/3PvQsUlWXwxGifNACqUwx7XFV3MvOX/chJ972BlBGZqysJF7c37xvBW7/iXq3W55WnsOeN4oAT2Qw1/UXPt/yedL2uvZ1Wy2QPJUPqXkVgxoHsPhZKz6h22RXZwxiS2Q9X3auZmDNlOLVsQ/QTg1lSKsIrb6Eya6/Ib52KoY9ApJYWxEzS3x0aY+OWw39ypYlGeL0xRGsZxpyuYuXB1pDKhdeT7h2XjYkkZq4bNchRbqyHjKCzQQb52GGEhljIzGFv1Sn0SJdsMCKWRfOoeV1hKNUB/UrXeqYgDsQVGe0s9+oDv2kmf2AZy53Pn4STEz0lq2G9JcZzsfsY8bEkXCshB0dlj3tfAzlEEiPLO5e1GuNN+lKuccRY63zWZX1DKIWSGVJhyspn2mWcEecC1d6gkJe0WgpC6RgIrVALoEUjpZwGzUatEAqS9xd1vKNLa4tEzB58aO8SXRLiuzdcOKXKOmEfRX1zsdcJrtnl67jk+ZP+qJGednmfEgF8vDdD3suHxJY3oLx2HIFUaD7ernPIWmBVIaIS2do2pxYxVG302VUjHiQM+a8VYxq9RrZXlFfzTLnc6SUNSQjMfm1JsdE2Lw1r3PUI0fRGi5e6Jc42fIhZdrOlw8pl2/kw/d0XyBpvIueAS1DNvgTt7VBkjun4IAFhNbMxjQVhlHCbtAusgmkQH0iD1BJzyG52OzLLpCqx94OQCiWyAP1m/m/4cNNH/ZoHSY3TubivS7OeU5P5kPa+ZjD+MsNf+Glp18i1BVi2p7TuPJ3V/LsY8/y/uL3+yQf0sSJE7nxxhu5/XbrNz7rrLO46KKLuOKKKxg8eDAXXnghYOUkGjZsGLvuuitbtmzh6KOP5qOPPuLAAw/k5ptvxjAMnnnmGa688kpCoRDbb789c+bMoRbF1b+/lceefYX2rhBT9tqNK393JSLC6Ueezq577Mr8ufMJtYX4+9//XlDYoFJEa0hlgjLBjApbUoSM4cT9TiZa6m7QLswC1heZpdxel5nGfTcly+vrtegF25oPSZHIh/Twqw/T1dXFy8+8zDe+/Q2mTJvCb275DfMXzu/VfEgLFy5kzpw5vPnmm7zxxhvcdtttLFq0iO9+97vceeedgDVfe88993DyySc77fzd737He++9xyeffMKDDz7Ixo0bueaaa3juued4++23mT59OjfeeCMA551+PG898U/eevF+p41xYtEY9zxzD7+78Xf88pe/3LYb4mG0hlQmfHifFe9rwEnJuYBiRoxMtzlqmgTLZDxSkFNDKTt3fJzI3RN1CV+VY/YsTj5Npi/Y1nxIkMiH1NXZRcvmFnaYtAMzD5kJWBrhl21f0vx5c6/lQ5o7dy5HH320k2bimGOO4dVXX+WCCy5g0KBBLFq0iHXr1rHbbrsxaNAgp50TJkwA4MQTT2Tu3LlUVlaydOlSJ69SOBxmxgzLY/TF1xfw27/cSVtnFxubtyS18eBvHQzAbrvvxooVK7r1+5cSWiCVGV0pFve/D858i8tJQyqkJZFYZk2xJHjleudjtAStrNuSD0mRnA9pxKgR/Pm3f3ZSUsRpj7TzecvnvZYPKZczwVlnncUdd9zB2rVrOfPMM539mfInKaWYNWsWd999d9Kxrs/e4tzL/pcFT/yTxtEj+Nnvb01qY7DCCqoaJerkOipHymOIrHEIFTgvFCtlr7MUCtF9wrFw/pNKgGgBoTbbQqXZYeXMh6SEgY0D6WjrSMqHVFNbQ0dbBwDbbb9dr+VDOvDAA3n44Yfp6Oigvb2dhx56yJnHOfroo3nqqad46623kkIRzZ8/n88++wzTNLn33nvZf//92WeffXjttddYvnw5AB0dHXz88cd02cJncOMA2tqT2+hmcyh7RIdyQGtI5UBn4iENFxgbOFLKJqwUChGtN79/Lb8b9isGVmZPFV0KRAsY6UuJLp489thjef7555m60yR2HD+avXebSn19LfUN9Rx78kkcfeDRjBo7Kikf0lEnHMXVP72aisoK/vXkv7j//vu54IILaGlpIRqNctFFFzFlyhTmzJnjODXki19377338s9//pNAIMDw4cP5xS9+QWNjI6effjp77bUXYGlFu+22G2ClDz/ooIMYMGAAPpfTyYwZM7jkkkt47733OPDAAzn66KMxDIM77riDE0880dGArrnmGnbccxzfO+lodvn6/zBm9MikNroJRUMZ95cLOh/SVuKpfEiP/4QPfvIEAG3faeLMMcNynt76wXXMu/RrjGjInajM07hyP7UYwv7bjclb5Ogdjubq/a7uzVr1Dq623jSwgdsG5I7W/ofJN3Pw3qXphdXW1kbtlmU0bWpmr8NP5dWHb2fz6JGY0XoM/5a85acM7vvgo6Zpsvvuu/Of//yHiRMnbt1F1rwLdlSVNhE+D2R3TClGG7cWnQ+pPxJNxHXLtZI/qUgJm+xizc00vVPHkF1aEQNiWbRCFatAfIkRZWe0s6+q2GsUYrJDFEqpkoxufvjhh9O8YQ3hSMTJh+RlI9XSpUs5/PDDOfroo7deGAHlnVSkcLRAKgeMxG0s1GRXyk4N6677DS0f1FE5MEL92K7sZqyURH1dsewBWUuFSOnJmG7x0ksvJeVCSiyl7fmGdycfUjZ23nlnPv300/wn5kBFIkQ7BH8llOAYokfRAqkckITdOlTgixsr4TkkM2QJlri12e15pkw/YtiT+pIcGaAc7O9thaSfcKXdKHVW+3uvi/JKPqTwqlWYnQaG38QXyB8+dU37GkbUjMhzVmmivezKAX+l8zFS4BCrpGO72VUXAQ74KQ+a1kRz5+rjMSMJpwVJ0ZBMVbpCOE5BAonyMQAl8j+VS4vSMdvtLLhOE3O/w5s6N/VqfYqJFkhlgHIJpEItcaWasE5FIrQ+9VRihwhPD7FeUCOY+0WNlmBuqHVXX87nLyTyW3UUOODY3FWinVZnc8bdUpBzf2mj/NXW/35sttMCqQzY9MJHzmdV5nNIbfZaEsAeSAprKixTnPjac7o8r27u6N3K9QKb/v0gHesrnO1c0b7drO8o0YjunZldGAp9rksaIwCDdySkCjNTloPGn4oWSGVAeF0ixYRZwIsbHPxs6eYHSp37EgPDGVKaINkFUnNn6S+OLVQgle5yjn4geLIhQLCGEPljEW7o2MAHTR8QK0GtPxdaIJUBRiBxGwuZGqoY8jzhWLQk3aBVSgoDxMCv4u1P/A6R1p3Ty5ZsJ52g0DnCYmcW3fp8SN2r98N3P8z6td3TBrubD+nLL79k9uzZ3fqOrcO6t4X8Ahs6NgDlpyVpgVQGiD/RSRWiIQH87cNfs9e/9uqtKvUebg1JADHYucWaYwk3fRXndVbpj3ZHOMqm9tLWkkrF7Xvr8yF1UyDd032B1B2i0SgjR47k/vvv77XvcLAXxhZiniz2gKO30G7fZYC4NKRs4yUzUo8RSKx0f2vj871cq97BrSHZU0iILXxUrJJcAgkU373zLR46d7/ermavUaiGFGftr39N6IOezYdUsdNkhl92Wc5ztjof0k+v4OXX3mD8mJGYSjHr5GP5xre/wS03/pGXn306Yz6kS86+xAkdtHDhwm3Oh3THHXfw+OOP09XVRXt7O7fffjuHH344S5YsIRaLcfHFF/P0008jInzve9/j/PPPz/q93SJse9t14xaXm2DSGlI54FLbC0nnXdJkmEOKOZ20QWKEnfnRXr6+rbdq1vuM3qvgOSSAqOmNIKvdyoe0cjXvPX9fUj4kgBPPOC1rPqTrbrmOB156AJ/fxw/P++E250MCmDdvHnfeeScvvPBC0v5bb72Vzz77jEWLFvHuu+9y8sknE4lEsuZh6i5KKTp81joyFcscFb2c0RpSOeByUDBnXACr7ko7JbxpPyqHPZG2v9RCzCTNIYkCxFnNL0o5cteQDJlVRRGOlrDNvXEC4c2rk3Z1fnEKVWP+kfH0iBnJq8n0Bd3Kh3T0EZnzIb32Onf85eaM+ZDirFi+okfyIQHMmjWLxsbGtP3PPfccZ599Nn57wW5jYyNLlizJ+L1bQ0zFiNmOOcqsQHztOc8vh3lRN1oglQHK5WmTqbuNtOxGZNO+BBrexle5NulYTMXwSwk9BqnegSJOLLs9ZDkbaGETMHlYA8vSfDYUoVIWSCJJc0ihDQcTbc8eP83wiAGkW/mQMmS7DXWFuPbSK6x8SKOHZcyHFC+/w+QdeOLFJxhZO9LZ3918SPnqmHotpVTGPEzdRpWfgOku3nhiNduGW0PKEBIotOFgrLFH+sNeal46KtXNVQxMu3+4PPAv/nfjOoaooVT70ke3Jb/aX4ykOaTwxlkcYWTvBGMl1rntv//+PPDIY5nzIQED8uRDGr/DeDZt3MSbb7wJbH0+pFx84xvf4JZbbnGS5G3atIlJkyZlzMO0NbjnhKSAFbId0Q66oqUfozGOFkhlgHIJoVimeYP42hxJFz4xVWLrGGIpXnZYGpKhFLsZn7FvVxdP7vUzAlKZXjbHGqWSQCTNqeHGwF+znv5liS0EPvbYYxk9aiRTv3YcP7j4WvbebSq1dj6k/zn5fzjmq0dywWkXZMyHdOzMY4nFYvz+9t9z3ZXX8ZWvfIVp06Y5Hnxz5szhhz/8ITNmzKCqauvTrpx11lmMHTuWXXfdla985Sv8+9//JhgMcv/993PxxRenfW936a6G9GXbl3zS/MlWfZcXKSFbjSYrrk46TYPAStimAL8v3ScnFI1SVUJPgbt9ljwyiAm4Z4wqAn4aAiNTi1LKGpIyLXfg1MjmGWbKHIo5X9bWZjmPzJw5k5kzZzr7U7fd5xqGwQ3XXkmt2eLkQ9pxpx0BuOrSH3De5eelDapmHTGLWUfMcrYn7zKZe5+4l3EN45LO22OPPXjnnXec7auuuipr3U8//XROP/10Z3vcuHEsWbIEAL/fz4033siNN96YVGbatGm88sorWa9ZKOXmNdddiqohicihIvKRiCwXkUsyHBcRuck+/q6I7J6vrIg0isizIrLM/j/Q3j9IRF4UkTYR+VPK9+whIu/Z17pJSmmWn2QNycyycvuywyYzckBF2v5T/v5Gr9WrV8gyh+RzjywFhgR2yFC4tMyTblpXVWZcg1RuJo7Dj/sO02adwAHHfJcrLjyLwcMGd/saJdup1wwh5TEGIFBiptdtoWhjYxHxAX8GZgGrgLdE5FGllNsX9JvARPtvb+AvwN55yl4CPK+Uus4WVJcAFwNdwBXAVPvPzV+A7wNvAE8AhwK53XC8hMvzzHR9Ht5Ry9rqNmehXcRMT0vw3pdeTn+WAbfAtRfGmpLSMZsmdYH0jkykxMyTLpQpdLv2JWiifOmJB6F1jbO9NTMxhc6L9kQ+pG0lyURnBJKEadA0aIxGqTdNPg4G+6xOxaSYxpq9gOVKqU8BROQe4EjALZCOBO5S1l17Q0QGiMgIYFyOskcCM+3ydwIvARcrpdqBuSKSNHS2r1evlJpnb98FHEUJCSTlCpTa1pnwPjp47faMr3+Ri8NDADLGvSq1Ttrd1vgckgn43H2vivH3uZ8RGz4CX+Ua1/neWJeztZgV9UnbrwYvzHiewsoYW27aUyrVXY10VKZHNS9UQ/JEPqQU7cctoCYaq8EsXb1+azwGi/nMjgK+cG2vsvcVck6ussOUUmsA7P9DC6jHqjz18DTKZcZ6+v1EB1wZC3B8axu/9M9h9MbXMo4cK4Y/3BdV7DmS5pCUrSEpDHcnZMaYOqo+ragRbEb8JaYROgiqNvlRHmNY8cz26+jkN+s30vnFKUTbt+eLzi8It4ZL1IXYrvPgHZMyIbvZPhJhYjicNbJ7SbXbXVeVEKbDo4nBU0nNH9gopWhqaqKyMoNzUQ6KqSFl+p1Tn6Rs5xRStifrYZ0o8n0s0x5jx47dyq/rBVxzSO68MUGsh/o0/7Pw7rNcM2lKWtFA3dK0fV5GZZhDWl3/JTHDNb3f1cINg9/k4C/T58yqxs5hfcc32NCxgSmD038Pz3LItShaMx66ZZ0lmM7tmoJRtYrbVt7G9/geYyuiSHNTX9ZymzDb24m1tBCojkFLJbQ2sdbW4NtMkzbDByhUNIYPxSbVRVcg3ZPQZ/iIVpeGNqxMk+i6dQAYHR1EqoJs6moiEoux3iWs1vr91JsmW7IkaDQ2eE8frqysZPTo0d0qU0yBtAoY49oeDXxZ4DnBHGXXicgIpdQa2xyXL/LiKrt8rnoAoJS6FbgVYPr06Z4ZhrmdGgyXFrSXkRzDrNTWHGUkZQ7JBGJGitnxhWsY0fol+/qOY17FBgx/YrW7+Do44qEj6Ih28N5p7/VNnbeSdvdCS6UKTtzWGmvlxs9upGPld3nrx+fSWFMa8w8f7r4HRkcHO57Qju+qlfD3C/kfv6Xxn9Xcwr8Ck+isWcurn69igGnyo/A5PDfp8bTrDKkawgv/80Lafi8S3biRZcdakcSH/OhHLP7GFH709kXcvmYd07oS5ved7P+7dt2G2ulXadfx+rNcKMUUq28BE0VkvIgEgROAR1POeRQ41fa22wdosc1wuco+Cpxmfz4NeCRXJezrtYrIPrZ33an5yniOpHkVlwt4ymnlkDslNf1Em0p31CBkaRJDu+ppX3ZF2uGOaGmsz1l5xpmJDcmVetCNW2qZrGjKHXrGS0jAitKw+lUrc2rzHuclHR/35YH8Ze16BtgDMH+W+c+wWToR3ZXLNIcZI2KvI/Rludm+DK4tQ6qG9EbVikLRBJJSKgqcBzwNfADcp5R6X0TOFpGz7dOeAD4FlgO3AefmKmuXuQ6YJSLLsLzwrot/p4isAG4ETheRVSIST5pzDvA3+3s+oYQcGiDZjOXWkGIpq1SiqjTMGDlJMdlFMs0X2C/1DsPqAOhYeYbroGcU224iqfPfmXGrUaIKK+MRxDZHta+22nB/a8IZVoAK08/+nYmoBIEsfoeRWIZBikcJfZJY1KpMk6g94PJleU79GVwcysLyYVPUJZFKqSewhI573y2uzwr4YaFl7f1NwMFZyozLsn8B6a7gpYNLQzJwzyeplNNK/8FNWvirIJIp0oSdeHCXOks7iLVPQsUqEF96/LPSQTALEabKPQgp/fsdRwEBkgdUfqJE28fjr/ksaX9HtIP2SDs1Ae9Hy/7iu2clNmKmoyH5s9zq3YxlpAaLKtl1Vxnw3kyYptu455DcQ+I0I08pDZezkaIhhXO8jHt+fltiw16TU6jhy3MUYLKrIIxyCSSrRIm2NwOpAilAjM4vziC8KT077YebejYHVF/Q0RXmuQ+tObNsGtKtwd+n7SuHgWYcLZDKgQI1JMrgwXVrSAohXHAsPrvtrsWipTenllu4zPa9kqwhiUqaXvQ8KQFSusxkr8LdjOSYbX5ioIKYkQFplyrFTvr2uZ/yjL1sI9scUiZKsa3Z0AKpDHAvFhXXw1npT769pbieIQ23hqQgVKjW5wiixPklNacmgpmnrYPYQnJ0u9KaQ0oVSB+2vpg4BJztfyzpuD9H7AqvJCfsDj5lOk5J2TSkTJTUuqs8aIFUDrgFkmQ32ZWDQEoNHttVsJYTF2SJ36SUJr+VAjXp0JznDJRWVMwdybq0O6oKo9b5fGRrurdgIEfkjWc+f6ZX6tSbGEoRf06zzSEBKJXcbZtlNFeoBVKpoxSqK2HaMFyjJaMcJFAq0WQBFC5QIEkmDanERtGqOlOOpwT7jvSxW+NMwpv3jJco6dFzQ2AYAL/Y2MR20fR7lUtDuv/j+3utXr2FlfE4t5cdAKkCSZvsNJ5h1YKUCMHuOaRkykE+Ja1DUhDK8TK2BdPXZ5jhYc7nkjLZIXmFy6SBwtG7jSG86UCrhC9EUyjfunAPkRpk397cLpL5Pp3nL63lgvkwlInETXY5b3V6xtpyQQukUifaaa89sb3IXB20kTLK2ilUOgsGs6GiyWa2XCa72vAGHghembTPV5UIgVhSGpJIfvfeWNjq0+21SJUjHuTi+f/T+3XrIWr33z9pW9nPcjkMpArBWkNom+xy3GsVq07a1hqSxjuID2WC2HcySUNKeZNvXreBCzc1913deoOo28sOwrbL+79Xr814+h7GsqyXypSOw7Mo2ByyAsOOjEQ5q7kl/RwzYnfeJdqF+1KdcOKu+rkoJ+0gYbIzcjQrunp20rZeh6TxDobPeiftJzhpDinlQW0wTfZxrXQvRZJCrSgI2Wa3mhyjxK/vlDngu9c1JN/AgYkNgV+89gsA9u3s5MLNGQSS46RRogIpLXCu9S9X5xzn4MUms94ubU3BUMrxssuVlC8YTU7BHjfZXfn6ldz27m2ZipQMWiCVONHmNro2B1FR61aKZJ9DgnQhVWqkxrIL2y9jrhf4umN3paYz/bjnBdKAAa4tYXOXpSGpbAmNY7ZJttAorB4j1YMybrLL9czGtagfPGnyvacTz/5+I/djwdoF7HLnLnzZljFWsieo2GEHqgaH8DVU23NI1m+QK4ROMFJLtHWysx33sntw2YPctOim3qxur9NtgSQihoikJ5vRFIXQp58nbbtfXkOSR4zNn1ZhrCyNyM+ZUErR+tRTSftCtlAJ2M02G9LTgtSu+ow5f4ix/5Lk38PrAil1EVHUCbyZpYMudQ0pxYPy6aXWItF8rTFDyRrwgIoBGGLw0PKHAHhzzZs9VsWeRplR/FUmGGJbN6zfINcAq0oihDZ+3dnud3NIIvJvEakXkRqsrKwficjPerdqmoKIpKzPSAodlKBzY4A18weiXmpIOj3atmMvVq5n6XrnnaTtjUvqeO4jK5dM0G63MWAs/PwzmHK0c15kjdWxHfB+8kveFmnrzepuM6kaQ0zl8cCKRZgwpJZSFUhmV2fS9rotlnk5V2sEiLVPTtrnlwARM0LQZw2+vDxXaHZ0YvitRJOGMl1u39kxMqw7KhdPu0I1pJ2VUluwUns/AYwFTumtSmkKR+bekLRtuNZmONrSD15l5cuDMpZX0VqWr/d2xxzH7Eqe/+raHKQ1bJmpnBGlCFQ3QuME5zxfrbXAsiKS/NKe+fSZeBqXebK5M0qLPf+XtEZlrCuOmxlhr/GN3Pv99NhupYDZkZIWpBtzSG62dCpLIBmWQArHvOtdqjo7MfwmKq4hiYlSviQhrPb8Xt7rrGpdlfecUqBQgRQQkQCWQHpEKRWhnNxbSplY8kssmYKrioBkGXOJyV9f/iTzsRKgFavTCcbb6rNy6iSZu+y0Bt3t2IqOK07f8g1tzoR30ir+o/6c+Gx3vJaWVHqY7anRGOJu3/nnkNwo05+kIXk5P5KjIRkGgrLmkFTKu7r/j5yPG96r45Qlz5OqN377kW/3QW17n0IF0l+BFUAN8IqIbAds6a1KabrBpMOTNt0vqOHai5FtmlTR3Oldk0Yy6cabtZWW1hCfQ8LuhFj2bOKkuDmv1Ezt7ijuCIkO2uJzGZ1oL0AsQmzLFsyPlvdVDXsU1WmZ7PyVliCOP8vdnejuigiRWISAYQ1OvKohqWgUFYkgPoUSSZjsUiIxiOvd3fh+HfsveztNHfD8fGiB5LzXIjJDREQpdZNSapRS6jA7R9FK4KC+qaImFy3vNgHw4m6HACnRvsWlIdkvJ8CwTe6nuYQCcGbwLgvUW3kZnQc53kEPsk12VQOd9BxSKu20SZoXcLkEJ+EWSFtW8/lJx9N0whnp55UAcZOd02onZUh2MmpPKkVD8qpAss3Nhg8wDDu4aiwphQiQWGTYD8jX0tOAhSJyj4icLiLDwUqcZ2dt1RSZ5let0bA4nW4mp4bk1/ZX/3RNlpdaL50Pf4X1/8g/w5DJUDnA0TQkUmLOoSkaUnyAkXTHfAH3FqHlK3q7Vr2GaWtIqGRhnPqIRlI77BSU6SdqRqnwWc+CV50a4gLJ0pAMew4pg8nOyNTe7GK6lB0ccgokpdTZSqndgauAgcAdIjJPRH4tIgeKZJuY0PQVRoWlzm+pstJ1iysC8qL6r1kfaoclzalUJyVOLSE7VoZ3MNo+nsaOAYkdcY2hog6G70osZNJ0q7VYsORkb5bEi6b9O9xUc16yhuTCPTAplQ7KdC3ajpoKspjsls1OmGOP231U+oWUn3As7GhIoZg3MwWbcYFk2HNISlmRVrZRQ+qKle7i94JaqpT6UCn1e6XUocDXgLnAcYB3Hfz7CQP3HwfA0lE7A2BIYjT4wtDT4NJVUDMoqUPrcg+qS6iXlgwmOxETw70Q1K0xiLD5fZP211+3NkunqYSWLSPa1OxsK1enFG/GRWeellUguefLSiW0jIrYz64SwlET7MFV6sLYnccOdz7/+phduPrIKWnXiqqo9+eQwlZ7xYhrSGZmDSmjQMquIXVGO7Me8zrdNk4qpTqVUk8opc5XSk3vjUppCkfFYohPEYtP3LvSPPsMw9IUIEkghdx9dol0VkDGOSQkRpKTrG2miRNtdzl5lIimANB0+5zkHUa6QBrTWO04qygTPn4w0VG7PQpLRUOKexUqBeGoSdWoewGr6/2wZs/EeZLkrpM+UBGTqJkQSN432YEyEgIpbQ4po8kuO2UrkERkFxF5Q0S+EJFbRWSg69j83q+eJi/RKGKAmSFcjOG+u1kEUsl770ssu4aEYEYT7UuebYHdhu7Wq1XbFmKtyU6sSSY4twC2O+Nwq59YOHHD3RpSySRwc93HiCuunQBrK8ZZpueM5RK/TXjTvpihIUleZ16NZKDClilRDIUyDCsCh8RI65YzzIzsPT57bqyuaPma7P6CNX+0C/AxMFdEtrePpb7fmiKgojHEEOJjQPfIeEOry1ThemkHutfBismA6hK5lSkj4bopAwEzeX1RignL7XqTnh/KuxENVHw+xXFkSNQ1bQhx4M+Izrw+aVdpakh2PRX8882VKQdT7tW+FyTKuBYQh9Z9G5SPmIo5gsirJsskpwbDl7QOKVrtCoeUwWQ3e48xAMS6hhFu2h+fS2iVsgt4PoFUq5R6SinVrJS6ATgPeEpE9qHkh9blgYrFwIC9x1uRGOLzJCe3tCaf5/pcE0o+ssPQEllI6RJIwbqItd5XYoRVZeKc3U9NOj/53fSuAErD7pwlw2retD1f+3/EKkcn7Sq1OaTIelciQV+Qm19MrKWKpt43EahKREKXtJxYPqJm1Gm3dzUk2+3bUJjOHJLl1LDmLDtM1oSDMprsBj35DJO+sAcrtgCO4/5cauQTSCIiTvAzpdSLwLHAP4DterNimsJQ0RjiEwIB66EVBf/94ktmbRycnMI8ZZQ8pFlxzuMxfKZZOuuQ3AgofCAmX6ohhE+8H65shsbxSSfleje93VHbAil+D83cdXUcAmzccsyrHbKbDX/4o/NZGQHby84iKgACtbbWkGTCUinu8XYHbbo0JI8+4ElzSLZAEiOEMiugpZktu/4VTrwHMxRmk/8EzH1+4pQddvs/Xcs3kgV2LEfSSq+TK8o5wG+AnYA34juUUu+KyMHAFb1ZMU1hqJiJGIIZT82mYFbX74lFBzDLbeKKJqvxP3ooxg5r4cmdrAnQtnAbNYGajJ5snsG9NqW6ATVkCsgSUD78E7+e0elBRbN3Rl7tqJJwhoyKaPt4/DWf8cNMyfliyfc34E4bVQrtdAsV02RgdcBxz4kiiCFw0n9g+XNQOyT5XqcIJPG1E1VRfvXGr6zDHhXIbrdvZfgwVBTxdWKGB9H5s4toW/Ie1V+dxbL9rEy64aZ9MlxF0iI7REt4iWi+dUj/Vkq9kWH/SqVU/oh/mt4nZmlIboEUVpXE8CU7NaQwwA4b1lYhdMSamXH3DG5595Y+qPA24Op4pH6ENQluB6M0jAyCVIRcg0Uva0hxIeJkTTUVYBDt2I4GM72DVSmpG665y51Z17vtdFDJa66OnJZYX9RgmpYAqh8Bu6fEdFYqzWQXHJjsb+XV9qtQYg4pZvgsL1CjC2VWELMj1LsDzprtmZwVrOfCzROfPtFbVe51Ck0/sWtvV0SzdaiomSyQAMyA/dnaFzcNuPHZ778S6Ig1A/DMimd6u7rbhHKbrXw+O1lfeuwvgOYHH2Ld48uT1v1Kiqbgac0hXrV4HLNsq/jjp6doSINdU4he1RDctDzyqPM5fl+ibZNAwdhoNIMDims7NdNsCl5tv3JpSBFfFYYyqQwqMP2Oud3scLlw+7O4f6c8//d8dA9r29f2RpV7nbwCSUS+DtzcB3XRbAUqZiX3imcRFQUoqxOLWzViaVGUE6+zUUqx7OyKjpyxGfH7IRa1M2ymv6hrLruMTa9+kdK25E7NqyNnIGGetKss8Wyi2QRSNLuZxtPtzIRpYtrtbwzVAJCqAJtd8Tmz9Dmk0MavJp/rdYFUWYMZX4dElB98dUfnSVWuHFHiy9xdB33pMy8dkY4MZ3qffOuQTgZ+Cxyd6zxN8ej8YgtmR6ejCIhpEL+t8fkglZpnxoWUyhoVcMw6gaoY+P32/JnJuMa6HGUSH6MpL7S3NaRkgWRtZwgrEyeawTap4l5YHm5nJpSyBVIMIz76d80Ztb/xJh+ddztta+1F0CnaYaw9OemkVwWyitgCKRhwnBrCZtjK42TfM3c4JQlknvJXGdYgenUxcD7yaUh/B2YrpTb0RWU03ScW8hFp8zvrVMSlvjtqfyhDLC+V9sHzxKN2IyA+v22mihHwZV9H5R4cd1SkrFEqgbZL/CYqBRJNX8Vvo2LpAilulvWqhpAV07ScCl2Lng2XQOp8910AOtYG7TmklPap0hh4OG7fgQpMxAmRFHStpXPPIbW+8X7m62QwWc9+bHZPVrXPyCeQrgb+LiJVfVEZTfdwv2ixuEAyEx1W/CV2oijbmEkDKm++rBmJzyGJQnw+SysQEyNXwmd3UFKjdEx28fTlCYFkr1HJ0taWRx5J2+e3ZZSX25kNiUQQl0Bye39GN1rjY2XGn/ncAter7XdMdhWBxDokSNaQ2hOr2KObMqegMzMIpFIln5fdr7G0pIf7pDaa7hFOaD5xrd0tkOLvsEpJ/W2oxDoVwfTsCDIN+4UVAfwBTNtU45PsqxdUklNDyjEPtzu+rkh8ttk1Fp9DyvzKhj78MG1fXCCVnIYEGKEue7ARf7ATAmnzXf8A4gJJJTTnLHi1/Y7bd7ACM56gDyyN3xFI6fO/qWQy2ZUq+dYhoZT6p4is6YvKaLqH6ko8rHEvO3eHFfdMMrvS3UX9zjvq3U45FXfHI36/41kWyDCpmyiU+FhKwVWdha62hqTiJjuz8DBPgbiGVELtjuMLdSaZ7KaOGpB2TlzOpEdqSG6vZ4ONxuf9AhXExMBnz+cGfcHuCSSzn2hIcZRSz/d2RTTdx+y07MvDTtzfCa4qym2ys/6nakgAPvtdKKlo347JDvAHHM+ygJFjDimHQPKqKQeAVA3JNK1cVyqz8PUPGZK2L7441tPtzIIvFEoSSHUVWQYdSuV1+17Vuqqnq9cjKDNmzYcGKlFIFpNdfoGUKbByqZJXQwKwE/F9CxjnLqOUurF3qqUpBNVlCSSpqODhd77kBMAw3U4N1oO6/sbfp5X1O4PKEuqs4iY7w4eIH9MWSMFcTg2uwbOREn7Hy5qDY85xCSSMCKjMbTUzrDWLm2W9arKKk9HkFosigZRcV6nYh6rWfJFxf5x1HeuImlH8RkHdXd9hKquuPktDqrDfRfcAK9bWlqWwm36mIQGPAacDg4A615+miKhOa/QkwQCmM2Hk0pDsuxv+5JO0sj5H2Sid8bPTcfn8SDCYWOme4zF2C6SqcLIrrJdbbrZaHZFRYd1PM2baXnZZXH8zeFLGA6x6WfACaXH4AIjF8PljbL/9Ttb2LselnSICoeWfMOmWX6deMe1cTy4UNWP2fGgwaQ4p6As6LShEQ8q6FKAEKXTIMFoppaM1eIz4ojkJViTcvl0aUiFx6URKY2Gs2dHB6vPtlAM+PxIIEO6yOuFN7dkXhbqjfTe2Jr/cXhZIwXHj6Ny0iZFHjmX5Hz/k5Y/XIjsrMNM7H6VURoHkROPwcDshszANRcIoo4UJw3eCqzLE7gMQRbSpqaDv8OI8kkrSkFLcvh2TXQELXMvIZFeohvSkiHyjV2ui6TZxpwajsjKzQCKLOST5Kr1Uu54lsnad81lsDQl7YeGMCUOzFUt6WVPny7ysOUTWrqF+chB/g7X407DTeSuzIsPJETKNKkrGZJdBIM3/dC2ISUNFQ4YSFps/qaFt7usFfYcnUzLEXBqScnnZGd3zsuuPJrs3gIdEpFNEtohIq4hkdorX9BkqZDkrSDDohA769uLEGmaRLOaQpIuYnh9BQ0rYFMOPEQwitgluQFVlllIplJDbd2xzM/4qcZKzGXYKRmWmtzV1/qjxjDMAl4bk4XYCmKEM8192e3M5rGAKm/5xd4brpQ9QvCiQlGkmzSG5TXZxzELmkPrLOiQXvwNmANVKqXqlVJ1Sqn5bv1xEDhWRj0RkuYhckuG4iMhN9vF3RWT3fGVFpFFEnhWRZfZ/d9r1S+3zPxKRQ1z7X7L3Lbb/cgy5vYMZN9lVVDpzSIPbI4zdYtnLDZGMgVXdWFqD91X+pFhtfktD8nV14YspqoPJWkO2DjhNQ/KwIFbRKCIK8QVAxOmgMSvYpGrpVIlOK9WL0ldvTe8apWKyc62ni7uG+rAESKojQqaIFHE2VDXY59QxoWEHACp81rPhyRxBpuloSDERxws0ycsuR9ivOKofakjLgCWqB4datufen4FvAjsDJ4rIzimnfROYaP99Hyuler6ylwDPK6UmAs/b29jHTwCmAIcCN9vXiXOyUmqa/edKX+ldlEsguW/MOe8+BEB9ZSCvQAJVElY7d3oFMXw0d1nbFzxqUhNMDgnU+vTThV3Tow1XSkEkghgmGD6MmhqqopbQUWaQvUI3s0vob875a668Kqm82AI6LpBKyWQXj0xhkNmlP1cQ2Zgr1XcoZv1eJ00+yTrmSQ0pBiiXhmR72bkWxuZ6fzvij30/1JDWAC/ZGsaP43/b+N17AcuVUp8qpcLAPcCRKeccCdylLN4ABojIiDxljwTutD/fCRzl2n+PUiqklPoMWG5fp2SJm+xWtZso18tYG7H2n/e1HfKa7ES82zEn4Q6g6Q9wzxufATDjQ0V9RbIZK7Q83aswE541ZdmdrhgKxIdRX0eNLZBQfqJYf3Hank9eJigVlkCKe1KaHg+gmzSHZCvrhsqiIeXooE3XO9Bl/151QUtbjJoeTFoXM63wIf5KTEVGDSnX+9tcG//U/wTSZ1jaRpCec/seBbgXEKyy9xVyTq6yw5RSawDs/3HzW77vm2Ob664QT6dNTRAXSK3KnyRSgjHrIa4M+Ii1WB5KFTvvlPEaosxSEEfJphrDn5TbqD6YHGrRPzR9kSikhw7yKolOV1nzZdU1VNsOHIVEapCgdY4vvu7K4+12zyGJvVbBF9eQUtaYfbxn9jGkW0OK2QKoOlBtbXtQQ0KZiCjwBwn7AwSiUVCKgC/g3LKcFg7npMzduGcHXDnI6fYtIpcCTymlftkL352p00/9BbOdU0jZ7nzfyUqp1SJSBzwAnALclXYBke9jmQ4ZO3Zsnq/rfeICSVVUoozECLAilniIV//4JwD4Bw8mQ8xvPN9b2bhNNeJPFkgVKaFTjKrqjNcolTmkTXdZj96WT0wGGwbK78dnu7hni9TgRmwTZsmY7NxzSPG8XvYcUkAKD5WkXK94/N7GTX5enENyzNC+CtorDHxKUR1KNlPmdUqCJE/Sc79yLje/Y6Wvi5rRnJHwvUg+Dekz4EIRWSQid4jI8W4ngW1kFTDGtT0a+LLAc3KVXWeb9bD/x+eDspZRSq22/7cC/yaLKU8pdatSarpSavqQDKFa+pp4WgkzUIEpiRduWGez89lZFBsPr5Jyx0XM0tAckjSkgLNmA6Ai5Z1NzZ6aDa+OICNrLKeUaIdlslu9JczI1i2WGUf5OW/xA8z84u2s5Y2KlDmkkjTZZdaQCr6m/T/useZJDSkWdTSkLbYmV9eZvA4p/xwwKNzhwhIv+EurXurZ+vYB+aJ936OUOl0ptRvwR2AC8KCIvCIivxCRbZmDeQuYKCLjRSSI5XDwaMo5jwKn2t52+wAtthkuV9lHgdPsz6cBj7j2nyAiFSIyHstRYr6I+EVkMICIBIDDgSXb0K4+I64hhQPViC+5E5686fPkk+Mjz5QUDKlpvb2K26mBFA0pGE5uw5pLLs1ykdTNEmi74WfA2pWMbWnhG28rlPLzrRXzuHjhvwGIrE2PQBDXkErH7dvt1GCb7MQaZcS95CD/mjpfBhtIvLwX55BULOK4fbcEbYHUAZFX30DZKWNyaUhOc10mO5+REE4/fmlbp/n7noKDOymlFgGLgP8VkXpgFnAWMH9rvlgpFRWR84CnsZK83K6Uel9EzraP3wI8ARyG5YDQAZyRq6x96euA+0Tku8BK4Di7zPsich+wFIgCP1RKxUSkBnjaFkY+4Dngtq1pU1/jCCR/JaYkr2aviaSsTHfy66T0y1IS3XKy1uMPJAVKDcYKm/IrlYWxwfHjARi6WxgMH0G7M52yUvHglGSNYcXxJ6SVd7zsnCkkb7YzjnKvQ4p72dkaUqU/4bCSy8MOoK4yvTsLGh7WkKJRO1BwBc1+q+4zPjTZdNdPnFMKMtm5TJX+HKlYSoF8c0jfAUQp9Y+UQ8cD7Uqp72/LlyulnsASOu59t7g+K+CHhZa19zcBB2cpcy1wbcq+dmCP7tbdC8QFUqe/GjGiRHyJlAOTNq+0PhgGmCbBceNpf32erSElOiiRKMrjJh0gyWQnRiBJuATtlzC8ahWfHXNs9mu4+mWf+DzbUYvdOdUO2wwdidA4e36sOKg2obzH2tqJrluXXr4iWUMqxTkknx2ZotLnEkjh7J1z/WHfJLD0A87afzx/m/uZM9hwm7C8RnTDBuuZ9AXZ7LcGESM2pZyUY9DkmNqTskR7t72FkK/2PyFzcr577GOaIhK3vXf5KsGI0uTyezzlw2eIbtwItplj4He+A0DNuPTQM2HTe3G+Ukk22SVrSAH7hfzk67Mwt6QHEPENGWwvLk1giOFZDSk+0DB8Cj583NnvN+FnCx5wtiOrM6dVMFKcGrwqeOO455BqdhwOgN+0tKYkk10kfT5l4PHHMPHVV4hn/xo7KNmhJW7C8tq9Njs66Hj7HSLtfvBXsClg+XCP2di9esbad8DdjbtNdqVIPoHksyf6k7D3lZb7RhmiwiHEUIQIIhLhlyf7uH7frznH1/4y4RxZMWE8O/xwAoP2TvbWFyBiFhDAscgkmex8/mQNKY/ledQNv2PwOWcDMEBZwT68rCGZtked+BTkCp2TZXVCfB2SY7LzWGecStzte9w3NjD0WGtaujpq/QZJAimDhjT8ZxclckGphJ9d/N7GTVheu9eRNa6cp74g7cEqVg9rYFhz4dcwIwMJrT8seQ5JylsgBew5liRs9+hghvM1fYgZDiM+RZfyg0RoqhdeGTvZOR5ZlxxwIjAgSNrzqiCkCgngWGTcJjt/wIn7BVbHq0yThqOOylg0OG47Ur3+DTE810nFUaEuxCd2WJkMwVRtsi2XcxbG2j9Rc6i5h2vYs8Q1pMqBEdZFrXtyyLJPOGSBmeRlF9ccR/zv/6ZfJP5bpPwmXtUYkrzn/JWYSrGlPvu9zoQoA/Ch+pHJ7u/A/SIyLr7D/nyvfUxTRFQ4jBi2QLLXIZmucULXu+8CMOqPf3T2pXrZAYRVCWhI0RS3b/eo3zT5cOcptDz8cFq52jFRAsOGJToq19yCVzUHsyuEBOyO1F+Z9bxsXmepXnY/f/nnPVq/nkaFQ4jfQARq1r4MwMSNm/nusyYBI0CsrY3o5s3EWi1jja+u1l3a9SldQ4prDF4bfCQ5K/iDmAqivq0VJolynktC2E3yuX3fgOU2/bKINInIRuBl4FGl1PV9UUFNdlQ4gvggFBNE4sE30008RmVi5JU6qBYgbHpfQ1JR1wscbkt2V88RcNMXtM9zFlzamx5OTai6ujACdk392Q0RbhOWb9Ag53PcKSJusmuNpFndPYUKhxG/1d6KyBY6XFph0Bdk9UU/YtmMfYnaLu5GbYYgMfH7m/J8e1ZDcgskXwVKKSL+7gaIidtky0dDyitOlVK32Ga7Kqz3uQOott2qFyqlFvduFTXZiAukSMzE57fXLcTSoxRIZTy0jqSZ7ESVhkByCx217DmEbyW2c6xPqRhgl0sMnRPb3pRHmKEup4PGX8mbw8az97oP0s5zd2r+wYOJ2cnqHIHkbec6BzMUctorho9Of8CZQwo9/QLtc+cCsPGvtwJg1NUy7urv4Hvzd8leaCru2oBzb1O3vUKyhlSBqbqyCyTbUzY7/WcOKc4ewKlAAzASK3zOTOA2EfG2PaCMUZEIhk+IxEz8AcvslkkguTWkTHc8bM8hedWEBWB2WvMH9WM78QXNJJNdNMPiUICBB0xg4KTMK929rSGFMByBVMGv9zo183nuTs2tGvisTslXIgJJhcIYtrlKiUGnS0Na85OfOp/jJmhfXR1V44cTrHNpximqkVfvbZxkDSmIqRTRbOpBviSb/TDa9yBgd6XUT5VSPwGmA0OAA4HTe6lumjyoSBTxG4SjJkagg9pALZA+QnI0JBEyafQx5b1V7Kmsu+YaAIbt3oJI8iLXeLw+N8Muu5Ths6dZrtOQbrIT7woks6vTWUvE5G8RzhY+x+156OqQxRZIpaIhKZeGZIovyWSXCaPaPehya0jK+RlOnvRdAKr8VfZZ3rrXSU4NviCmqZi8PHURUm7i74A7hp/bZDe6dvS2VbIIFCqQxgLuoWYE2E4p1QlZYnZqeh0zEkX8QjimMHwdDKgYkPG8nHNICpTtsVYSQc6dWGe5O5jqPfe0P1nnxdsWn3sy8K5Tg+oKJQYR+2df7qfMzPWPCySfN5uXhhkOITErM6rCyCuQfI2N6Tsl6R+zd/gO7532nuOl57V7bXa41v75K1AKajryx60bsddm53NiYWzmWHY7D0pNL+d9ChVI/wbeEJErReRK4DXgbntuaWmv1U6TExWJIX6DSMzE8IWc3C9p+OK2gMwaUklEarAxfAoOuwHJE31AKiqTpW+KsPWyhhRra8VX4QNfhTV/kA1XBOvQB645ppQ5pH2G79Mb1ewxVMjyFoW4yS67Z+HAU06x4t1lGjy5NCRv3tkEZrtr3tYXwFSKtpr8SzuVmWh3R8AW3C6TXU2ghhMnn9hj9exrChJISqlfAd8DmoEW4Gyl1NVKqXal1Mm9WD9NDlQ0hvh9hKMmGF2OeSKVwMgRiQ0j+VW15va9/vqCb/Bg6r82A8OvYOC4pGjfmXC0wrSRsStFgcdGzXFiGzbiqw2AL0hXJLsHYbZ03vEApYYJygwwuXFyxvO8ggqFHNOqSW6TXWDkyJTCKRqwfX/j99bZ9tgzniSQjACmgvaqzA4JFRMnJjZczfiyZrD9Kdlkd9nelzGhYYLn2lwIBc+GKaUWKqX+qJT6g1JqQW9WSlMYlobkIxIzwQhTE0hbwwwkOihEspjlrKG0VztoALOtDX9jvbVh+PJGKbcWh7rbmrwOyctODbHWVnxVPvAF2NCabBH3NTQw9o451ka2yW63U4MyiHoxsKgLFQpZUSmwsr52BrILpETyxcwaUsptTnjZeYyksE+2U4Nb2tQfcYTzeeztiSWfSmVqd6Ibj7fXq+3OR/m4Z/RDVNTECNgCSUJUB6p587KMcWVzXMTbggisuF+qqwt/vSVwTXx5XzejLm6+THZqUHYm0qHVQz0pkFQsZq9DAvwVNLVb8wq3ft1qj8ISSgCtL7yQ8RpiGGAY7Fp9GGB4Mjmdm3jEEbDaF/JlX3sVGDYsZY/Lj1+prM+F1+51dHNiLghfAKVg5YiEqXLw97/nfDbs+w0kpT126UWuffH1WOL59zoTWiCVMJEtUctkF1MoCVHtr2ZYfSXb3XM3E559JnOhlIe0FEx20U3Wy+uLCyTDx593PZrndqngpf3GJZ1btdtu7PThB1aAURHXUNl6UcfETuCcr5zDV4Z8xZMvrNlhue8bvhgEqmlqszSktya6hv72Ys/2117PfiGfjwoqUMogYhaSwqB4qFDImduM+aporqjNeq4/LpDyOOAkbrs3NYVMbt/3HTKY+348jcnvvZs0d+huwYAJHQw86uuo4S4zvOsMd3u9/l5nQgukEiW6cSMqBi3vtRCOxlASckx21dOmUTFmDKNv+QsTnnBn6Mj8csadGrz6AMc2WQs+fXWWu28UH5uqGrjl0ApCweSJYHGvucrQ3iCNnDvtXM/GsnMEknRBdSOhqHVvYn7bA0spxF6zk239FViedmKagDhelF7FPYeklOKz+hFZzw0MH55SODHgUKisAshzg4+IO1iw5dQQ8Sk2bj8YCQRI8j5ytcnwwfBzToKa2gzzo665NK0hafqSWEsiIV84ZmISSnNqqJs5k4oJ41NKZtCQ7A7r05ZPe6Oq24wTw6zaMuXc/ZYVKVmMCFF/skAyKlI9tJzJBGvLPQnuwffVbLcFUrQZ6oYTMxVgonz2iNqlIbkZeMopSduOQFIGMY8LJDOcmENa3zgdM4Mr6OBzz2XSoretzhrINrhKjWXnxbkUpRRtL7/sbMcwMJW1HjDupu7EnBTJHtU9477EHJIXB1z50AKpRDG7rMgFjd+cwNsrN4CYVAfSozQkkdFV1ruakYOdKTTeT322uQsrTkyUaEqsN6PWZe6RdFOGOPLJmy9s3PvKiGyCAdtZk90SJWrLoIrtt08KkGvU1LDThx8w/PLLqBwCDdNt7cLnwzBjgGB63K3fcWqY9h2WTjiDRUMm8v4uiXmT2pkzaTzzTIyqTF6kLpOsSouh60mi6zc4n7f/djMPLLQcHDZ3diUyvtomO/H7M7y32TVB7dSgKQpmm9VxtVeGEcOaZ8jmZZdEhjfVix2zG5UikCKmAcQQUWw/KrFI0qivZ9glF6cUTp5DcqtFXmy3Y7Izwk5aAowIoaDw4vlHMfqWvzhedJAcx2/80cLI43YC3BqSEMsXeqbIqFAYw1AwZBJRZWkES3dKDCxG/f5GfLUpz3a2DjllHZIX3b5jTRudz8EBPj7fZLuAS0JDcuaQXAKpcsdxiYuIJEUrSezWc0iaImBuWAHA5tYmMKz5hWp/Hg0pw6jJDtjSk1XrcVTc3m6voZq/cgsYlgmrckBiJD3q9zfiHzzYVdIdQTU+crQjNXg0/UQ8nbdhxMBfwY/ufQex03lvnLID/oEDE278gOpMyfYbb5Lfh5gxwPD0HJJSytaQgGC1baJULKpMzI9l1oycC1j/Je5ll7wOyYuKQsyd1dgXIGYCKMTodAaVErA0f/+QIYhhMO6euxl7zflWmVxpzV1ediUoj/JH+9Z4E3PzOgDuYyYSF0j5THYAKCp32YXYlhYin68EE++bdFI0pCg+xBZIDB/inFcxblxywRweVl412cWT1YlPQdz92RZIfrG3s0ZvSAhgMRJODV6eQ4rHdBOfgkAN0ZACo4vlo/JJkjxedqnbHhp8mG1WmKTBB48H3ycAiH8L4guxfcP2gLXeatAPfkDtgQcAUDVtGnzaknSdTGvx3AuEvfh850NrSCWK2WK5Qo8YPx5sk11eDcl+WMf/5z5GXHVVfKenXtZMxF1kxZ4AiuGDeP6nnXZwzguMGpWhcMo6JDO+6c12m45AwskWG9eQam2HDXeEhiEXXZgonBJgVcwYShmYHl4Y2/LQwwC0fVkBgSpipkKMrm5cwaUJuUMHeXhhrBO5/itDwBck6DcwKqzszhMGTACs53Pojy6ieo89Mlwh+3NrugYfWiBp+gzVYY2yuirqGdNozSkUNIcUJyn6tbcf3PW//S0A8sKVAESV4WhIFb5cgThdJjtJNtl5dQQZT7onhoL1dnw6Oxvw1JF2Er5owmW4eu+9Uy5gt8nvdzQkL2vAnYveBiC0JQDBGmJKIT7LDNn0/85k3H/+k7lgN9cXeelex1OxG0YMfH5uen4ZRtASSOMbUr1iXeSIzRgnZg8+vDrgyoc22ZUoMVvtbw3WIlFLlc8Wyy4J5xl1EjF4PrhqzFnVbq/JwQdiTQRX+iuZOO/1nFljAZc/cGJE7cUXNslkt+Mh8DqOya7SZ2lIvsEJM6XPvYrfvUDSMJBYzPNODS2PPArAyL2aExqSz+qwAwcfSNWIqbkvkDqHlOK84sWFsXENSYyoY5Y1Apup9FcyqHJQrqIWSmV1ajDte+1FzbAQtEAqUVRHB+JTdEiV42VXG8y+wt0ig1NDKbh928TTkUcx8FVYc2hV/ir8AwdmLuCO1ODgdQ3JdmrwKRi+C/Cek54+aHdevtoaJs59ldbnnqdiwoTUK1j//D47IrqRZMbxEu4BQeXACARriMasOSQge/R6IO86pJRb66XBh9llaYDGp0+B334efZ0MqBiQR4BKji2LmMs868XnOx/aZFeimB2dGH6TThVwXuAafyEmuxQTlvL+Sv7g9ttTd/BMK9I3loZUOfIBIN9IMHNIlXg5L76wziS/AVTYwWRtDcltnvQPHszAE45PLuy26Pj8loaEeFcghRKBY8WvoHIAmzvCVFVYv0FugeRcxSrvaEjuvd7UFFSXSwsGJg2rY9gAVWB7IWnBVQrxe+3V5zsfWiCVKGZHB4Zf8ern7bRHLPNV3jmkbGFVvG6y29KCryZhjoy6suJ2xfJNgKc6NXg8UoNjshNidgdlBK11K8EcQUcd4pqAz7Dcvj3s1GDaETj8jbX4giZUNrCxLURttaUR1gVydNB5FoZ62qnBzggcb0J7OIoyOnO3F9LnkDJofe45JC8+3/nQAqlEMTtDGH5Fe8xPc1cbQSOYWFSXi7T1Ge61Ot7EbNmCUWNpB5+O+BYdVBJt2xGAvYfvnb1gUqflduLw7ghShcLgE6R6AJ812aadoBXLb3jN8FxFSXL79rmcGjxkrnITa7XmQYceuZt1qyrqCUdNDHsOKb8JGlennHw/U++tl+616uzCqEhoux3hGCYd1AfrC7xA9qjmWkPSFAWzK4QRFEAQX6h7Hna41isobz+4ZlcXKhzGV2VpBx8PPQSAUfUDGVO7XR4hnClUkrcjNahwGMMnUDWQ4/86D7DWqExo2J6GiobchdPcvq1IDZ7VkNosDcnwRyBYxwOL1/LM0nU0dbRQ7a/Gb+Sa4s4TqcFRjD2oIXV0IMFE29pDUaJ0FGCyS7SlIuhjp+F1XHOU5fQxY9hBgGsOyaNOO/nQAqlEMbvCSMAWKkZXNwRSptBB3jXZxTZtAsBnp3fuUJan2cThQWqDhSwExvFKgkTrvRipQcVimO1tiN8SSPFcSGKEC4jCEb9I3GQXj9QgzF/3Jl9s+aJ3Kr0NxJqbAfD5QlDZwM0vLbcO+LoK046AtFh22c7y0L0OLVtGcGQiokgoahJRhQikOFZbhtVVMNAOOCz2qnGtIWn6HBWL0fl5szPKqq6MFiaQMqxj8LpTQ3S9tT7D32AJojZViQiEYl3dCybrtDcxh+S1F3b1j35M83/utxYAVydi9GFEqCzEpZ90DcmosCKjX/zqxdkKFY3IGis8UKAqApUN1FRYz7MYnfnNV6maj5N2wdr0kPxJI7phA4FG632NVgwEChRIbg04RfQatkCKmtGMx0sFLZBKkKZbbwWgy47ROGaQUbiGlBZs1OMaUnziu9J6VNtUBZV+H53RzgLWXcV7JzPxMSUtgZdGzq3PWEkVox0mVCVc2cUIF7bGDHDmkOKx7OzoFl5qZ5zImi/B58Mf6IDKBvbYzmqz+ELUBgrUkNztUorEUm9vziEp0yTa1IS/xqrnbbs/jPg6UCgGVmZZvpB2EeVad2XtOmrcKQyrHsZ+o/ZLnOaRNncHLZBKkM73lgCglPU0xijUZJdp1OQ9TcFNPD+Q+KyRX6uqoDJg0BHtyN9Jx/MGmbF0DcmDUaAdFFA1kEnDrBFzZTBCbSFxCt1ag+P2bWFkjX9XPKJr1uAfNhQJt0BlA5GYbW4yOrs1n+Le9HrooFhzM8Ri+CtN8FcxZ8FGJGAtbB9eXYjTSuY9Y+u257njnqOx0tKstZedps9Qdi4kZWcORSJU+lMT0+XBlR/IyxqSkx+oay2Ij8c+6kBE6Ix25p9XcQRSNP2Y03F59K2taqQyYDBz0hCqq8L5HRrixAWuYdgLYy2cPDseIrL6SwIjRkKXJZDC0bhA6oaG5CaH95lXBh7RjZZZwx8MQ80QRg2sorbSandVoBtasFgeldlMlHoOSdNnxDtpZY96oyqcJ6abm+SHVDweOsjJD7R+IW3D92JVG2xqDxemIYktkFQs4W2VMnL2ykurUsL7qKoBbGwLE/QJreHWAie8XS78/riGZG0bGbKwFpvImjUERoywBFLVAEcgYUSo8Od5nrPMIcVJ9bLzysAjZgskn78TagZhKth+qDWYDBp51pmlzImqHCbKbPu8jveeUk1eYi3NAJhBH3UVfsKxAgVShnU54G2nBkcgRTbRUTnM2d8Z6czv1BB3GzajabYcrwkk050jB3hjjcnq5k42tndhKrPb99dx+/YoKhYjsm6dLZC2QGUDq5vtdVdGtPABVsockiOAUuYKvUK0yVpT5g+vRNUO5dP1bVQErfu0NW3OqiGVaHBVLZBKENVhBRvdfu/Pqa8KEIqFClvFD+kLYz1usmufOxcA6WqyPZIAYoTNAib6HZOdtUAUSET7To0xU2SiGxJprQPVUZ5bYUUr2NBqCeTca3JcpEZqsPFa+KDQJ59ANEpgcAOgWLpZeGvFZr61ywiqKwqJSpESCioper13iW6wTXaR1TQ17EJrKEpjjdUNd6vNeeaIvCaIC0ULpBIkuqmNxsltdFVXsHZLV+EaUg4Lu1fpeOstACTazgcd1ryCY3MvWCAl5pBSY5x5RUNqm/taYsOAt9bFP1tCpTCB5NaQ/EkaklfaGWfDH/4IgBG2BPFNC6x50ab2EOFYaOtM0JnyIXlsYWx040YkGMAIKNYb1lqkKaMtTb/gQWW8za45s0zKkNfueSFogVRimJ2dqKiJL2jyeGwfYqZJaCte4ESkBgMTV4RgD6n5sRQz1sPLrbr933es1en51yEl5pAyrUMCb7y0SinW/+Y3AASHNzJqn800YwnfmC1MA0YBYaGsqwG2yc7lZRfzWLQGFbU0wPrdLM+yJWo8AOtbO4iqAkx2aYImc3BV5/s88lzHmjbiH9iACLy/yXo+7bWt3Wuz41WYLWKFNtlp+oBYi+Ui6guaXBU9DYihUN2fQ3JMHAamiji7vdBBx4msWgVA/cw9AVivLJNdZcDqpLvn9m3vS3Vq8MBL654/mvCr46gaHGGTspwYhtZbmlFBGpI74KbPl+Rl54V2uolt2kzNAQcgq94i4q9jrX1v7ZCFWzeHhLfuayaiGzbia7AGG/9ZYt33+BxS4WZ3u51uDSmDs1IpUlSBJCKHishHIrJcRC7JcFxE5Cb7+Lsisnu+siLSKCLPisgy+/9A17FL7fM/EpFDXPv3EJH37GM3idf0fBdxgdRRN5gIfr4x1Uro1e05JGfbwCRh0vLSXEP7m/MBaDxifwDasF3bfQWmbM/k1GDjJQ0pHo1i5O9uQFpW0kwtbVhtu/qonYBCNaQUp4aYd+eQYlu24Kuvh7XvsLFxGlE7NVtjXaHzKSlIcrq6NA3JA/cZoP311/FVWm3dgrV2MKaS811lJ8M6JO323TOIiA/4M/BNYGfgRBHZOeW0bwIT7b/vA38poOwlwPNKqYnA8/Y29vETgCnAocDN9nWwr/t913cd2tPt7SlizZZACldbI+hrjp4EFDqizBRKJ1kgeWlkGfliJQAV9gLRLuwXVqwYb4W7fZsuE6X3IjXEHRoCQ4dC8+dsCowA4NWfH8SAaqsNhWtItuDx+yzN0MZrAsncsgVfQz20baA9aGXA/dauI/jZoVbCwa1dxkBKB+2lucLIWitUUvu7Vsy+VmU9v6GYNcDK6/btoJK1YTLMApdocNVirpbbC1iulPoUQETuAY4ElrrOORK4S1m/7BsiMkBERgDjcpQ9Ephpl78TeAm42N5/j1IqBHwmIsuBvURkBVCvlJpnX+su4Cjgyd5o9KdL57N2+TtbXT6wZDm1wCdVQeqqNvLR5g+BAl9gZULTMlj/IWxaAcDwlgijNiY6q9f/+zcCHllEWTv/NWTEYBasXsiggJ/xIwx+e8QEPmt7H+iGyW7jMthidQYDV8zns2dMIhuWMmqj4s3H51BRcEfQOwTfXkoNsGjDewz5cgHvBiexy7hOwsYaVjSvAAq8v5UDoHUNrP8QCW3BFwkxemMMDEVFyzpef/S23mxGN1AM2LKF1e1rafNv5h3lwwiu47sHbU9z1AoCW/B8yvoPrI65qxkiYeqWvsz+rctoe30Ln31UQ2u0jVEbFSvmPcfrH7f1brPy4FuzgXogOnMknwTWs8XswGAd69otD5a8GlK8zRs+hGgnZmcXdR+8wv6ty9jyeiufDUxYDGo+Xc/mzjW8Hu2dez560h6MnbR7/hO7iRRLiorIbOBQpdRZ9vYpwN5KqfNc5/wXuE4pNdfefh5LuIzLVlZEmpVSA1zX2KyUGigifwLeUEr9097/dyyhs8L+jq/b+w8ALlZKHZ6r/tOnT1cLFizodrvvOfdrfOWFNd0ul8r3zvfRUpvQeG4/5Hb2HL5n7kJXJVb7dzX7+eypodtcj95m8Xjh1yf4Mh577KjHGNcwLnvhj56Cu62sqi2fV/HlvAJjhRWBkB9O/7GPmC+ztfj+I+5nUuOk3Bf5zxnw/oMArH+njqYPCo0eXRxun2Xw1PTMRpr/+9r/MXPMzOyFV74BtztWd9YvrqPpQ2+3N87lp/pYNir5PtcEanjjpDdyF/x8HsyxjDcrX26kfU03o7P0IO8cNoETbnx8q8qKyEKl1PRMx4o5FM705qVKx2znFFK20O8r+Foi8n0s0x5jx47N83WZGfutU3h/6PNbVTZOuLaCIybNYMjgQYwcUEVtoJbpwzLe38zMnkOFUoyZvpzLFz5EZ0Wrc2h6cCKNxlaEbekNRGjbfjDnVgeJVTaw3Y7TCPitDqwh2JBbGAHscDCceA9EOqmLRHlcPY3PjHHQ5CFEVIwvohs9Y8rqHFTDDwYMQIlBS8Nkxg0byMAaa8RcH6zPL4wADvk17HQEAIMO7eTVe1/n6cDLYFiP82Cjnt2D2/daG7qDMoQddhzKORVBWgbsxIDaasYPsZ67Sl9lUpDQjIzZG77zoBXlARh0SAeV732CqWD15g4itst7pxlmTttzAHyjcrfea1CBxCr8zNpxKAdUD6dyyHaMH2zNI42pG5O/cMRak0b1YEb84jI6l61Ka2+cm7c8AfRem3c4IOd4fasppkBaBbjvwmjgywLPCeYou05ERiil1tjmvfV5rrXK/pyrHgAopW4FbgVLQ8rVuGzs+60z4FtnbE3RnmPqMQhQuws8s2Ue/pp259DPvn09EwdOLF7dehJfACZ9E7AmS/803Hr5f3TZtwDIo0+WHvUjYOoxAPiAJz/akTfM1xHDmiM8cPTuzD74z0WsYA8iYg04bHxA/Qzr8wDXaZu7NjPv3hcAuPW0f/ZZ9XqFmO0NO3o6gYO+S+Aga3NAhlPn3fkUUHptLqaX3VvARBEZLyJBLIeDR1POeRQ41fa22wdoUUqtyVP2UeA0+/NpwCOu/SeISIWIjMdyXphvX69VRPaxvetOdZUpf1TyI1DQSE1TEsTMZANAqboCbwtl1Wbb+YGcWZJLm6JpSEqpqIicBzyNNcC5XSn1voicbR+/BXgCOAxYDnQAZ+Qqa1/6OuA+EfkusBI4zi7zvojch+X4EAV+qJSzWvAc4A6gCmteqVccGopO9WDo2Ji0a5exfj6wkrLy6FGPdj9quMazmAqs18MaWZdV59wfiWtIBXsglh5FdadSSj2BJXTc+25xfVbADwsta+9vAg5OLwFKqWuBazPsXwBM7U7dS5LzFybs0DZdsS7ns+6wygtTKRT+RBhd7y6v6zXKqs3RuIaU3yv0kaMeIRKL5D3Pa3jDv1fTN1QNsP5chOIPOd5MUaDZekylsDQkCz3gKHHiae0H5XdMmdAwoZcr0ztogdTP0RpS+WKaoGINYGckLSttoT+y46GW1+jEbxS7Jr2GHhL3czqjnYkN3V+VFTGl6Fx1irPdHzXgshLCIpbXqJF5XV450P+eUE0SboHUHzusckYphYrVscvAvYtdFY2mILTJrp8TMAJEzP7hhTV7j9G0dpXeRO/WYtor5eIDjXK/v5noj20uZbRA6ufc/a27mf3YbKD8NaQbjvtKsavQp5h2WDDDDjJb7vdXU/roJ7SfU1BIGk1JYppxgRTPfdX/tIX+2OZSRgskjYMeQZcXcZOdLz4JrvtmjcfRPZDGQY8my4uYLZF8/dhkV1Zedv2A/veEarKiX97yIh5JOuCzBFJ/HHD0xzaXMlogaTRlym9m78qc0/ekvtIKNaM7Z43X0QJJ41B42mhNKVBb4eegyUMTbt9aA9Z4HC2QNA460nd50p/XIWlKCy2QNA4Bo3zzrPRn4k4N/VFD6o9tLmW0QNJoyhytIWlKBS2QNPx0+k+ZOqj800H1V+ICqV+6fWshXFLo0EEaTptyGqdNOS3/iZqSpD8KojjaZFda9N8nVaPpJ/TnOSRNaaEFkkZT5sQFkdEPX/e4yU6b7kqD/veEajT9DK0h9e+2lxJaIGk0ZU6/nkPSmlFJ0X+fVI2mn9CfveziaMFUGvTfJ1Sj6Sf063VIEv/XD9tegmiBpNGUOf16DkkVuwKa7qAFkkZT5sQFkVL9sHe2ZfCEAROKWw9NQeiFsRpNmRPXkFQ/VBcCRoC/fv2vTB40udhV0RSAFkgaTZkTn0PqlxoSsO+ofYtdBU2BaJOdRlPmaC87Tamgn1CNpszp104NmpJCCySNpsyp8lcB0BXtKnJNNJrcaIGk0ZQ5jZWNAGzq2lTkmmg0udECSaMpc4K+IAARM1Lkmmg0udECSaMpc/xiOdNGzWiRa6LR5EYLJI2mzPEZllNDTMWKXBONJjdaIGk0ZU6FrwKwFolqNF5GL4zVaMqcXYfsyvd3/T7HTzq+2FXRaHKiBZJGU+YYYnD+bucXuxoaTV60yU6j0Wg0nkALJI1Go9F4gqIIJBFpFJFnRWSZ/X9glvMOFZGPRGS5iFxSSHkRudQ+/yMROcS1fw8Rec8+dpPYcVRE5HQR2SAii+2/s3qz7RqNRqPJTLE0pEuA55VSE4Hn7e0kRMQH/Bn4JrAzcKKI7JyrvH38BGAKcChws30dgL8A3wcm2n+Hur7uXqXUNPvvbz3aUo1Go9EURLEE0pHAnfbnO4GjMpyzF7BcKfWpUioM3GOXy1X+SOAepVRIKfUZsBzYS0RGAPVKqXnKisF/V5bv1Gg0Gk2RKJZAGqaUWgNg/x+a4ZxRwBeu7VX2vlzls5UZZX/OdC2AY0XkXRG5X0TGbF2TNBqNRrMt9Jrbt4g8BwzPcOjyQi+RYV++DGPZyuS61mPA3UqpkIicjaVxfS3jxUW+j2X2Y+zYsXmqotFoNJru0GsCSSn19WzHRGSdiIxQSq2xzWnrM5y2CnBrK6OBL+3P2cpnK7PK/px2LaVUk2v/bcBvcrTpVuBWgOnTp/fP9JsajUbTSxRrYeyjwGnAdfb/RzKc8xYwUUTGA6uxnBVOylP+UeDfInIjMBLLeWG+UiomIq0isg/wJnAq8H8AccFml/828EEhDVi4cOFGEfm88CYnMRjYuJVlSxHd3vJGt7e86en2bpftgFhz/H2LiAwC7gPGAiuB45RSm0RkJPA3pdRh9nmHAX8AfMDtSqlrc5W3j10OnAlEgYuUUk/a+6cDdwBVwJPA+UopJSL/iyWIosAm4Byl1Ie93P4FSqnpvfkdXkK3t7zR7S1v+rK9RRFI/R39QJc3ur3ljW5v76EjNWg0Go3GE2iBVBxuLXYF+hjd3vJGt7e86bP2apOdRqPRaDyB1pA0Go1G4wm0QNJoNBqNJ9ACqQcQkTEi8qKIfCAi74vIhfb+rYlKfq2IfCEibcVoSyH0VHtFpFpEHheRD+3rXFesNuWih+/vUyLyjn2dW1zBfz1DT7bXdfxREVnSl+0olB6+vy/Z++LZAzKFRSsqPdzeoIjcKiIf2+/xsdtUOaWU/tvGP2AEsLv9uQ74GCtC+W+BS+z9lwC/sT/vDLwDVADjgU8An31sH/t6bcVuV2+3F6gGDrLPCQKvAt8sdvt6+f7W2/8FeAA4odjt68322sePAf4NLCl22/rg/r4ETC92m/qwvb8ErrE/G8DgbapbsX+ccvzDihwxC/gIGOF6CD6yP18KXOo6/2lgRso1PCuQeqO99v4/At8rdnv66P4GsOIoHl/s9vRme4FaYK7dqXlSIPVwez0vkHq4vV8ANT1VF22y62FEZBywG1aIou5GJS85eqq9IjIAOAIrv5Vn6Yn2isjTWPEXW4H7e7/WW08PtPdXwO+Ajr6o77bSQ8/zHNtcd4WIZArs7Bm2pb32OwvwKxF5W0T+IyLDtqU+WiD1ICJSi2WGuUgptSXXqRn2lZz/fU+1V0T8wN3ATUqpT3u2lj1HT7VXKXUI1gi0giyR5b3AtrZXRKYBOyilHuqN+vU0PXR/T1ZK7QIcYP+d0rO17Dl6oL1+rEDVrymldgfmATdsS520QOohRCSAdXP/pZR60N69Tqxo5EhhUclLhh5u763AMqXUH3q10ttAT99fpVQXVjDgI/EgPdTeGcAeIrICy2y3o4i81Pu17z49dX+VUqvt/61Y82Z79X7tu08PtbcJS/ONDzj+A+y+LfXSAqkHsNXyvwMfKKVudB2KRyWH9KjkJ4hIhVjRzCcC8/uqvttKT7ZXRK4BGoCL+qDqW0VPtVdEal0vvB84DOjVQL5bQ0+1Vyn1F6XUSKXUOGB/4GOl1My+aEN36MH76xeRwfY1A8DhgOc8C3vw/iqsedCZ9nkHA0u3qXLFnlArhz+sl00B7wKL7b/DgEFYcyLL7P+NrjKXY3mrfITLswzL02UVYNr/ryp2+3qrvVgjLYWV8iN+nbOK3b5ebO8wrLQq7wLvY6VA8Re7fb35PLuOj8OjTg09eH9rgIWu+/tHXN6GXvnr4f5qO+AV+1rPA2O3pW46dJBGo9FoPIE22Wk0Go3GE2iBpNFoNBpPoAWSRqPRaDyBFkgajUaj8QRaIGk0Go3GE2iBpNFoNBpPoAWSRqPRaDzB/wcG4sxG81F13QAAAABJRU5ErkJggg==", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAaQAAAEICAYAAAAQkoCgAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8rg+JYAAAACXBIWXMAAAsTAAALEwEAmpwYAABvG0lEQVR4nO2dd5wdVfXAv2de2Zrd9AJJSAIhgRRCSEKHYAQC0lQQBJEi0gTBgiD8VCw/RMUCShGl/qQjTQVBSoDQApFOAkkgQEL6ZjdbX5m5vz+mvHl13yZb5r29388n2Wl39t6dmXvuOffcc0QphUaj0Wg0fY3R1xXQaDQajQa0QNJoNBpNQNACSaPRaDSBQAskjUaj0QQCLZA0Go1GEwi0QNJoNBpNINACSaPRaDSBQAskjaZEEJGVIrJORGp8x84QkQXOthKRVhFpEZHVIvI7EQn5yrY759aJyC0iUttHTdFocqIFkkZTWoSBCwqc300pVQvMA04Evuk7d6RzbiYwG/ifHqulRrMVaIGk0ZQWvwG+LyIDC12klFoKPA9MzXFuNfBYrnMaTV+iBZJGU1q8BiwAvl/oIhHZFdgfeD3HuTHA4bnOaTR9SbivK6DRaLrMj4EXROTqHOf+KyIm0AD8FbjFd+4hEUkCTcC/gCt6vKYaTRfQAkmjKTGUUu+IyD+BS4AlGadnKqWW5yl6jFLqyZ6tnUaz9WiTnUZTmvwE22Fh+76uiEbTXWiBpNGUII4WdA/w7b6ui0bTXWiBpNGULj8Dajq9SqMpEUQn6NNoNBpNENAakkaj0WgCgRZIGo1GowkEWiBpNBqNJhBogaTRaDSaQKAXxm4lQ4cOVePGjevramg0Gk1JsXjx4o1KqWG5zmmBtJWMGzeO1157ra+rodFoNCWFiHyc75w22Wk0Go0mEGiBpNFoNJpAoAWSRqPRaAKBnkPSaMqcRCLBqlWr6Ojo6OuqaPoRlZWVjB49mkgkUnQZLZA0mjJn1apVDBgwgHHjxiEifV0dTT9AKcWmTZtYtWoV48ePL7qcNtlpNGVOR0cHQ4YM0cJI02uICEOGDOmyVq4FkkbTD9DCSNPbbM07pwWSpmxoTbRy7/v3kjATfV0VjUazFWiBpCkb/rD4D/z85Z/z2jq9YDlo7LPPPj1ybSa33norn332WZfKrFy5kqlTp+Y8fuedd251XXqTK664oq+r0C1ogaQpG9a0rgGgI6m9yYLGiy++mHXMNM2c+7muLZatEUj5CJJAyvxbZdKbAimZTPbYvbVA0pQNCjvZpJ4vCR61tbUALFiwgIMOOogTTzyRadOmZe37r7Usi3PPPZcpU6ZwxBFHcPjhh3P//fcD8LOf/YzZs2czdepUzjzzTJRS3H///bz22mucdNJJzJgxg/b2dhYvXsyBBx7IHnvswaGHHsqaNfagZfHixey2227svffeXHvttTnrfMkll/D8888zY8YMfv/732OaJhdddBGzZ89m+vTp/PnPf/badOCBB/KVr3yFnXfemUsuuYQ77riDOXPmMG3aNFasWAHAqaeeytlnn83+++/PzjvvzD//+U+AgvfN/Nscc8wx7LHHHkyZMoUbb7zRq2d7ezszZszgpJNOAuBvf/sbc+bMYcaMGZx11lk5Bdqrr77KPvvsw2677cacOXNobm6mo6OD0047jWnTprH77rvzzDPPALagP+644zjyyCM55JBDuPXWW/nSl77E/PnzmThxIj/4wQ+2+t3wo92+NWWDpSwABC2Q8vHTf7zLe59t6dZ77rpdHT85ckrR1y9atIh33nmH8ePHs2DBgrR9Pw888AArV67k7bffZv369eyyyy6cfvrpAJx33nn8+Mc/BuDkk0/mn//8J8ceeyx/+tOfuOqqq5g1axaJRILzzz+fhx9+mGHDhnHPPfdw2WWXcfPNN3Paaafxxz/+kQMPPJCLLrooZz2vvPJKrrrqKk9w3HjjjdTX1/Pqq68Si8XYd999OeSQQwB48803WbJkCYMHD2bChAmcccYZLFq0iKuvvpo//vGP/OEPfwBsrevZZ59lxYoVHHTQQSxfvpzbb789730z/zY333wzgwcPpr29ndmzZ/PlL3+ZK6+8kj/96U+88cYbACxZsoR77rmHF154gUgkwrnnnssdd9zB17/+da9t8Xic448/nnvuuYfZs2ezZcsWqqqquPrqqwF4++23Wbp0KYcccggffPABAC+99BJvvfUWgwcP5tZbb+WNN97g9ddfp6KigkmTJnH++eczZsyYot+DXGiBpCkbtIZUGsyZMydN+GTuuyxcuJDjjjsOwzAYOXIkBx10kHfumWee4de//jVtbW00NDQwZcoUjjzyyLTy77//Pu+88w4HH3wwYGsio0aNoqmpicbGRg488EDAFmiP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", "text/plain": [ "
" ] @@ -208,6 +208,7 @@ "# Plot\n", "for p in np.arange(0,np.size(thisvar_da.pft.values)):\n", " this_pft_char = thisvar_da.pft.values[p]\n", + " this_pft_char = this_pft_char.replace(\"_\", \" \")\n", " plt.plot(datetime_vals, thisvar_da.values[:,p], label = this_pft_char)\n", "plt.title(thisVar)\n", "plt.ylabel(this_ds.variables[thisVar].attrs['units'])\n", From 6d7a6a181869405b4f181db9070a99443f02e8a5 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 29 Oct 2021 12:20:40 -0600 Subject: [PATCH 21/45] Start of comparing read-in sowing dates in 1d script. --- 1d_crop_work.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/1d_crop_work.py b/1d_crop_work.py index 04d2b9f..85c5e7b 100644 --- a/1d_crop_work.py +++ b/1d_crop_work.py @@ -179,7 +179,7 @@ def mfdataset_preproc(ds): plt.show() -# %% Get sowing and harvest dates +# %% Get simulated sowing and harvest dates # Get year and day number def get_jday(cftime_datetime_object): @@ -243,3 +243,9 @@ def get_dates(thisCrop, vegtype_str, is_somedate, year_jday): this_dates = np.concatenate((this_sdates, this_hdates[:,1:]), axis=1) print(thisCrop) print(this_dates) + + +# %% Get read-in sowing dates for this cell + +sdate_file = "/Volumes/Reacher/CESM_work/crop_dates/sdates_ggcmi_crop_calendar_phase3_v1.01.2000-2000.nc" + From d9ba88f72d0f91f3c3ba8af8fac89337786abbab Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 29 Oct 2021 12:20:53 -0600 Subject: [PATCH 22/45] Added clm_yield_conv.ipynb. --- clm_yield_conv.ipynb | 501 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 501 insertions(+) create mode 100644 clm_yield_conv.ipynb diff --git a/clm_yield_conv.ipynb b/clm_yield_conv.ipynb new file mode 100644 index 0000000..1e02aa3 --- /dev/null +++ b/clm_yield_conv.ipynb @@ -0,0 +1,501 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "809d1d31-6385-4c49-8d1d-76a55dcfe91d", + "metadata": {}, + "outputs": [], + "source": [ + "# install libraries if necessary\n", + "pip install matplotlib\n", + "pip install xarray\n", + "pip install cartopy\n", + "pip install netCDF4" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "94a62cf4-e407-41ba-b4df-05d3343a6838", + "metadata": {}, + "outputs": [], + "source": [ + "# import libraries\n", + "import numpy as np\n", + "import pandas as pd \n", + "import matplotlib.pylab as plt\n", + "import xarray as xr" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34ae96b3-8102-446d-ad8b-862403d646e8", + "metadata": {}, + "outputs": [], + "source": [ + "=============================================\n", + "How to process CLM5crop output to crop yield\n", + "=============================================\n", + "\n", + "=============================================\n", + "1. Original crop yield output:\n", + "=============================================\n", + "Under h1 files:\n", + "$CASE/lnd/hist/*h1*\n", + "\n", + "Variable:\n", + "GRAINC_TO_FOOD\n", + "\n", + "dimension:\n", + "(time-monthly,pft)\n", + "\n", + "=============================================\n", + "2. Regrid pft-level data from the 1D output and output a netCDF file with (year,cropPFT,lat,lon)\n", + "=============================================\n", + "***input variables:\n", + "\n", + "float GRAINC_TO_FOOD(time, pft) ;\n", + " GRAINC_TO_FOOD:long_name = \"grain C to food\" ;\n", + " GRAINC_TO_FOOD:units = \"gC/m^2/s\" ;\n", + " GRAINC_TO_FOOD:cell_methods = \"time: mean\" ;\n", + " GRAINC_TO_FOOD:_FillValue = 1.e+36f ;\n", + " GRAINC_TO_FOOD:missing_value = 1.e+36f ;\n", + "\n", + "int pfts1d_ixy(pft) ;\n", + " pfts1d_ixy:long_name = \"2d longitude index of corresponding pft\" ;\n", + "\n", + "int pfts1d_jxy(pft) ;\n", + " pfts1d_jxy:long_name = \"2d latitude index of corresponding pft\" ;\n", + "\n", + "double pfts1d_wtgcell(pft) ;\n", + " pfts1d_wtgcell:long_name = \"pft weight relative to corresponding gridcell\" ;\n", + "\n", + "float area(lat, lon) ;\n", + " area:long_name = \"grid cell areas\" ;\n", + " area:units = \"km^2\" ;\n", + " area:_FillValue = 1.e+36f ;\n", + " area:missing_value = 1.e+36f ;\n", + "\n", + "float landfrac(lat, lon) ;\n", + " landfrac:long_name = \"land fraction\" ;\n", + " landfrac:_FillValue = 1.e+36f ;\n", + " landfrac:missing_value = 1.e+36f ;\n", + "\n", + "\n", + "***convert GRAINC_TO_FOOD(mon,pft) to GRAINC_TO_FOOD(mon,PFT,lat,lon) (where pft exists) using ixy and jxy\n", + "\n", + "***sum up monthly data to annual, and mutiply 60*60*24*30*0.85*10/(1000*0.45). After the conversion, \"gC/m^2/s\" is changed to \"ton/ha/yr\"\n", + "\n", + "***output the netCDF file with new GRAINC_TO_FOOD, and landarea (area*landfrac)\n", + "\n", + "=============================================\n", + "3. remap cropPFT to 8 active crop types\n", + "=============================================\n", + "\n", + "***input files and variables:\n", + "\n", + "from the new generated file:\n", + "GRAINC_TO_FOOD(annual,PFT,lat,lon)\n", + "area(lat,lon)\n", + "\n", + "from land surface file (e.g. /glade/p/univ/urtg0006/Yaqiong/):\n", + "\n", + "double PCT_CFT(cft, lsmlat, lsmlon) ;\n", + " PCT_CFT:long_name = \"percent crop functional type on the crop landunit (% of landunit)\" ;\n", + " PCT_CFT:units = \"unitless\" ;\n", + "\n", + "double PCT_CROP(lsmlat, lsmlon) ;\n", + " PCT_CROP:long_name = \"total percent crop landunit\" ;\n", + " PCT_CROP:units = \"unitless\" ;\n", + "\n", + "***\n", + "\n", + "calculate cropping area for specific crops using area, PCT_CFT, and PCT_CROP\n", + "\n", + "***\n", + "\n", + "extract 8 active crops from cpt (number starts from 0)\n", + "\n", + "* cornrain 2, 60 (one is tropical, the other is temperate)\n", + "* cornirr 3, 61\n", + "* soyrain 8, 62\n", + "* soyirr 9, 63\n", + "* ricerain 46\n", + "* riceirr 47\n", + "* springwheatrain 4\n", + "* springwheatirr 5\n", + "* cottonrain 26\n", + "* cottonirr 27\n", + "* sugarcanerain 52\n", + "* sugarcaneirr 53\n", + "\n", + "***\n", + "\n", + "output crop yields and crop area" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c9610b0d-50e1-4b3d-8ee2-1972fc863997", + "metadata": {}, + "outputs": [], + "source": [ + "crops = {\n", + " 'cornrain': [2, 60],\n", + " 'cornirr': [3, 61],\n", + " 'ricerain': [46],\n", + " 'riceirr': [47],\n", + " 'soyrain': [8, 62],\n", + " 'soyirr': [9, 63],\n", + " 'springwheatrain': [4],\n", + " 'springwheatirr': [5],\n", + " 'cottonrain': [26],\n", + " 'cottonirr': [27],\n", + " 'sugarcanerain': [52],\n", + " 'sugarcaneirr': [53]\n", + " }" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e2547222-1472-40b9-91a5-dd3cb75c7458", + "metadata": {}, + "outputs": [], + "source": [ + "crop_ids = [item for sublist in [crops[crop] for crop in crops] for item in sublist]\n", + "crop_ids" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5376a000-b0e0-4d29-ae90-97c925d6c400", + "metadata": {}, + "outputs": [], + "source": [ + "### Step 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cda8ee2e-7dd4-485a-9fc6-ad5df465830b", + "metadata": {}, + "outputs": [], + "source": [ + "filedir = '/glade/p/univ/urtg0006/Brendan/clmcrop/GRAINC_TO_FOOD'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f07f1802-63d2-406b-98f0-cdef7179e79a", + "metadata": {}, + "outputs": [], + "source": [ + "grainc = xr.open_dataset(filedir + '/b.e21.BW.f09_g17.SSP245-TSMLT-GAUSS-DEFAULT.006.clm2.h1.GRAINC_TO_FOOD.203501-206912.nc')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d7f767b6-5451-46b9-818a-b7839cfb4082", + "metadata": {}, + "outputs": [], + "source": [ + "grainc = grainc.sel(time=slice('2060', '2069'))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2602cb99-676a-432d-8fa0-1968cbbd578e", + "metadata": {}, + "outputs": [], + "source": [ + "grain = grainc.GRAINC_TO_FOOD" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "439e8a43-f6b2-41b6-9678-8b2db6024dc8", + "metadata": {}, + "outputs": [], + "source": [ + "grain = grain.assign_coords(time = pd.date_range(start='2060', end='2070', freq='1M'))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2d41c802-fe6a-4514-a8f9-f0d1472f4faf", + "metadata": {}, + "outputs": [], + "source": [ + "grainc" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43929c58-a9b9-4eba-af72-3009fc995262", + "metadata": {}, + "outputs": [], + "source": [ + "#some GRAINC_TO_FOOD files will not have the variables ixy anf jxy to convert pft to lat lon, import a file that does\n", + "#grainc1 = xr.open_dataset(filedir + 'b.e21.BWSSP245cmip6.f09_g17.CMIP6-SSP2-4.5-WACCM.001.clm2.h1.GRAINC_TO_FOOD.2015-2100.nc')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0a9cc784-4a4f-4136-9c7c-76cfccb9e1b8", + "metadata": {}, + "outputs": [], + "source": [ + "pfts1d_ixy = grainc.pfts1d_ixy\n", + "pfts1d_jxy = grainc.pfts1d_jxy\n", + "pfts1d_wtgcell = grainc.pfts1d_wtgcell\n", + "pfts1d_itype_veg = grainc.pfts1d_itype_veg\n", + "area = grainc.area\n", + "landfrac = grainc.landfrac\n", + "landarea = area * landfrac" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5e18f3f7-4a51-49e6-9ba5-2e2f9461638a", + "metadata": {}, + "outputs": [], + "source": [ + "# Assign PFT coordinate to veg-type data\n", + "pfts1d_itype_veg = pfts1d_itype_veg.assign_coords(pft = pfts1d_itype_veg.pft)\n", + "\n", + "# Resample grain to yearly sums\n", + "grain = grain.resample(time='1A').sum()\n", + "\n", + "# Create empty 4D array to construct from 1D GRAINC array \n", + "dims = ['time', 'pft', 'lat', 'lon']\n", + "coords = {'time':grain.time, 'pft':np.arange(pfts1d_itype_veg.max()+1), 'lat':grainc.lat, 'lon':grainc.lon}\n", + "grain4d = xr.DataArray(dims=dims, coords=coords)\n", + "\n", + "# Run for loop over 1D array to fill in 4D array\n", + "for pft in grainc.pft.values:\n", + " if (pfts1d_wtgcell.isel(pft = pft) > 0.0):\n", + " veg = int(pfts1d_itype_veg.isel(pft = pft).item())\n", + " lat = int(pfts1d_jxy.isel(pft = pft).item() - 1)\n", + " lon = int(pfts1d_ixy.isel(pft = pft).item() - 1)\n", + " print(lat, lon, veg)\n", + " grain4d[dict(pft = veg, lat=lat, lon=lon)] = grain.sel(pft = pft)\n", + "\n", + "# Change units to ton/ha\n", + "grain4d = grain4d * ((60*60*24*30*0.85*10)/(1000*0.45))\n", + "grain4d.attrs[\"units\"] = \"ton/ha/yr\"\n", + "\n", + "# Save filled-in array\n", + "grain4d.to_netcdf(filedir + '/GRAIN4D.b.e21.BW.f09_g17.SSP245-TSMLT-GAUSS-DEFAULT.006.clm2.h1.GRAINC_TO_FOOD.203501-206912.nc')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "79d3957f-c26c-461a-a8d9-db6475206276", + "metadata": {}, + "outputs": [], + "source": [ + "grain4d" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d00767fd-6623-4a78-81bb-826e86637e6b", + "metadata": {}, + "outputs": [], + "source": [ + "#grain4d = xr.open_dataset(filedir + '/GRAIN4D.tran-CO2-clm5.3-b.e21.BW.f09_g17.SSP245-TSMLT-GAUSS-DEFAULT.006.GRAINC_TO_FOOD.2060-2069.nc')\n", + "#grain4d = grain4d['__xarray_dataarray_variable__']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b746b49f-0932-4509-be48-f4367060d411", + "metadata": {}, + "outputs": [], + "source": [ + "### Step 2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1d914116-dc79-4f9f-8794-f2fb53ffb40f", + "metadata": {}, + "outputs": [], + "source": [ + "surf_data = xr.open_dataset(filedir + 'landuse.timeseries_0.9x1.25_SSP2-4.5_78pfts_CMIP6_simyr1850-2100_c190102.nc')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "234ee8b1-f726-4e9b-ae20-1b44fd30f4b1", + "metadata": {}, + "outputs": [], + "source": [ + "surf_data = surf_data.sel(time=slice('2060', '2069'))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e0a3394b-5263-46f8-846a-4c9847a2f379", + "metadata": {}, + "outputs": [], + "source": [ + "surf_data['time'] = pd.date_range(start='2060', end='2070', freq='1A')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56a13c21-d338-4e56-8c06-f9bd0e62a6c4", + "metadata": {}, + "outputs": [], + "source": [ + "surf_data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "309a6101-a257-49f0-9af7-5dcadb5f0bce", + "metadata": {}, + "outputs": [], + "source": [ + "pct_crop = surf_data.PCT_CROP\n", + "pct_cft = surf_data.PCT_CFT\n", + "\n", + "# Create empty 4D array to construct YIELD_OUT by CROP\n", + "dims = ['cft', 'time', 'lat', 'lon']\n", + "cft_coord = pct_cft.cft-15.0\n", + "coords = {'time':grain4d.time, 'cft':cft_coord, 'lat':grain4d.lat, 'lon':grain4d.lon}\n", + "yield_OUT = xr.DataArray(dims=dims, coords=coords).rename('yield')\n", + "yield_OUT.attrs[\"units\"] = \"ton/ha/yr\"\n", + "\n", + "# Create empty 3D array to construct AREA_OUT by CROP\n", + "dims = ['cft','time', 'lat', 'lon']\n", + "coords = {'cft':cft_coord,'time':surf_data.time, 'lat':grain4d.lat, 'lon':grain4d.lon}\n", + "area_OUT = xr.DataArray(dims=dims, coords=coords).rename('area')\n", + "area_OUT.attrs[\"units\"] = \"km^2\"\n", + "\n", + "# For loop to create new file\n", + "for crop_id in cft_coord:\n", + " area_OUT.loc[dict(cft=crop_id)] = (pct_cft.sel(cft=crop_id+15)/100).values * (pct_crop/100).values * landarea.values\n", + " yield_OUT.loc[dict(cft=crop_id)] = grain4d.sel(pft=crop_id+15)\n", + "\n", + "# Merge arrays to dataset and save\n", + "yield_cft = xr.merge([yield_OUT, area_OUT])\n", + "yield_cft['yield'] = yield_cft['yield'].where(yield_cft['area']>0)\n", + "yield_cft.to_netcdf('STEP2.tran-CO2-clm5.3-b.e21.BWSSP245cmip6.f09_g17.CMIP6-SSP2-4.5-WACCM.006.GRAINC_TO_FOOD.2060-2069.nc')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bff74aca-de67-4a60-9d80-0fa088e9f053", + "metadata": {}, + "outputs": [], + "source": [ + "# (one is tropical, the other is temperate)\n", + "crops_tot = {\n", + " 'corn': [2, 3, 60, 61],\n", + " 'cornrain': [2, 60],\n", + " 'cornirr': [3, 61],\n", + " 'rice': [46, 47],\n", + " 'ricerain': [46],\n", + " 'riceirr': [47],\n", + " 'soy': [8, 9, 62, 63],\n", + " 'soyrain': [8, 62],\n", + " 'soyirr': [9, 63],\n", + " 'springwheat': [4, 5],\n", + " 'springwheatrain': [4],\n", + " 'springwheatirr': [5],\n", + " 'cotton': [26, 27],\n", + " 'cottonrain': [26],\n", + " 'cottonirr': [27],\n", + " 'sugar': [52, 53],\n", + " 'sugarcanerain': [52],\n", + " 'sugarcaneirr': [53]\n", + " }\n", + "\n", + "# Create empty 4D array to construct YIELD_OUT by CROP\n", + "dims = ['crops', 'time', 'lat', 'lon']\n", + "coords = { 'crops':np.arange(0, 18, 1.0),'time':yield_cft.time, 'lat':yield_cft.lat, 'lon':yield_cft.lon}\n", + "yield_OUT_crop = xr.DataArray(dims=dims, coords=coords).rename('yield')\n", + "yield_OUT_crop.attrs[\"units\"] = \"ton/ha/yr\"\n", + "\n", + "# Create empty 3D array to construct AREA_OUT by CROP\n", + "dims = ['crops','time', 'lat', 'lon']\n", + "coords = {'crops':np.arange(0, 18, 1.0),'time':yield_cft.time,'lat':yield_cft.lat, 'lon':yield_cft.lon}\n", + "area_OUT_crop = xr.DataArray(dims=dims, coords=coords).rename('area')\n", + "area_OUT_crop.attrs[\"units\"] = \"km^2\"\n", + "\n", + "for i, crop in enumerate(crops_tot):\n", + " if i%3 !=0: \n", + " print(crop)\n", + " IDs = crops_tot[crop]\n", + " IDs = [id for id in IDs]\n", + " subset = yield_cft.sel(cft=IDs)\n", + " yields = subset['yield']\n", + " area = subset['area']\n", + " yields = yields.where(area>0).sum(dim='cft', min_count=1)\n", + " area = area.sum(dim='cft', min_count=1)\n", + " yield_OUT_crop.loc[dict(crops=i)] = yields\n", + " area_OUT_crop.loc[dict(crops=i)] = area\n", + "\n", + "for i, crop in enumerate(crops_tot):\n", + " if i%3 ==0:\n", + " print(crop)\n", + " yields = yield_OUT_crop.sel(crops=[i+1, i+2])\n", + " area = area_OUT_crop.sel(crops=[i+1, i+2])\n", + " yields = (yields * area).sum(dim='crops', min_count=1)\n", + " area = area.sum(dim='crops', min_count=1)\n", + " yields = yields / area\n", + " yield_OUT_crop.loc[dict(crops=i)] = yields\n", + " area_OUT_crop.loc[dict(crops=i)] = area\n", + "\n", + "yield_crop = xr.merge([yield_OUT_crop, area_OUT_crop])\n", + "yield_crop.to_netcdf(filedir + '/tran-CO2-clm5.3-b.e21.BW.f09_g17.SSP245-TSMLT-GAUSS-DEFAULT.006.yield.2060-2069.nc')" + ] + } + ], + "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.9.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 4e1c23520f5b1d63c2c2f563ac2c9ebeb8cd0b3c Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 29 Oct 2021 12:27:41 -0600 Subject: [PATCH 23/45] Map now uses an actual crop. --- 2d_crop_work.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/2d_crop_work.py b/2d_crop_work.py index 60eba02..338558d 100644 --- a/2d_crop_work.py +++ b/2d_crop_work.py @@ -67,7 +67,7 @@ tmp_pyx = utils.grid_one_variable(this_ds, "pfts1d_itype_veg", vegtypes, time="2000-01-04") # Make map -tmp_yx = tmp_pyx.isel(pft=0) +tmp_yx = tmp_pyx.sel(pft="temperate_corn") if tmp_yx.shape[0] == 1: tmp_yx = tmp_yx.squeeze() else: From ee4256980a9ddbf3e838bac8d9ff00a6f807e1c3 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 29 Oct 2021 15:16:03 -0600 Subject: [PATCH 24/45] import_ds_from_filelist() now renames dimension "pft" to "patch". Along with all pft-named variables. --- 2d_crop_work.py | 26 +++++++------- ctsm_py/utils.py | 93 ++++++++++++++++++++++++++++++------------------ 2 files changed, 73 insertions(+), 46 deletions(-) diff --git a/2d_crop_work.py b/2d_crop_work.py index 338558d..93545bf 100644 --- a/2d_crop_work.py +++ b/2d_crop_work.py @@ -16,8 +16,10 @@ import utils +# %% Import dataset -# Import dataset +# import importlib +# importlib.reload(utils) # Define list of variables to import myVars = ["CPHASE", \ @@ -35,7 +37,7 @@ filelist = glob.glob(indir + pattern) # Import -this_ds, vegtypes = utils.import_ds_from_filelist(filelist, utils.pftlist, myVars) +this_ds = utils.import_ds_from_filelist(filelist, utils.pftlist, myVars=myVars) # Get dates in a format that matplotlib can use with warnings.catch_warnings(): @@ -43,31 +45,31 @@ warnings.filterwarnings("ignore", message="Converting a CFTimeIndex with dates from a non-standard calendar, 'noleap', to a pandas.DatetimeIndex, which uses dates from the standard calendar. This may lead to subtle errors in operations that depend on the length of time between dates.") datetime_vals = this_ds.indexes["time"].to_datetimeindex() -# Get PFT list, integers (use only first timestep) - - # %% Read one variable from dataset. (Do nothing with it.) +# import importlib +# importlib.reload(utils) + # Which variable? thisVar = "CPHASE" -thisvar_da = utils.get_thisVar_da(thisVar, this_ds, vegtypes["str"]) -thisvar_da = utils.trim_to_mgd_crop(thisvar_da) +thisvar_da = utils.get_thisVar_da(thisVar, this_ds) +thisvar_da = utils.trim_to_mgd_crop(thisvar_da, this_ds.patches1d_itype_veg_str) thisvar_da # %% Grid and make map, more efficiently, as function -# import importlib -# importlib.reload(utils) +import importlib +importlib.reload(utils) # Grid -# tmp_pyx = utils.grid_one_variable(this_ds, "pfts1d_itype_veg", vegtypes, time=3) -tmp_pyx = utils.grid_one_variable(this_ds, "pfts1d_itype_veg", vegtypes, time="2000-01-04") +# tmp_vyx = utils.grid_one_variable(this_ds, "CPHASE", time=181) +tmp_vyx = utils.grid_one_variable(this_ds, "CPHASE", time="2000-07-01") # Make map -tmp_yx = tmp_pyx.sel(pft="temperate_corn") +tmp_yx = tmp_vyx.sel(ivt_str="temperate_corn") if tmp_yx.shape[0] == 1: tmp_yx = tmp_yx.squeeze() else: diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index 4bb36ad..99dbf5c 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -263,7 +263,7 @@ def cyclic_dataset(ds, coord='lon'): # Get PFT of each patch, in both integer and string forms def ivt_int_str(this_ds, this_pftlist): # First, get all the integer values; should be time*pft or pft*time. We will eventually just take the first timestep. - vegtype_int = this_ds.pfts1d_itype_veg + vegtype_int = this_ds.patches1d_itype_veg vegtype_int.values = vegtype_int.values.astype(int) # Make sure no vegtype changes over time. @@ -284,6 +284,16 @@ def ivt_int_str(this_ds, this_pftlist): return {"int": vegtype_int, "str": vegtype_str, "all_str": this_pftlist} +def get_vegtype_str_da(vegtype_str): + nvt = len(vegtype_str) + thisName = "vegtype_str" + vegtype_str_da = xr.DataArray(\ + vegtype_str, + coords={"ivt": np.arange(0,nvt)}, + dims=["ivt"], + name = thisName) + return vegtype_str_da + # Import a dataset that's spread over multiple files, only including specified variables. Concatenate by time. def import_ds_from_filelist(filelist, this_pftlist, myVars=None): @@ -297,12 +307,17 @@ def mfdataset_preproc(ds, vars_to_import): # list(set(x)) returns a list of the unique items in x dimList = list(set(dimList + list(ds.variables[thisVar].dims))) - # Get any _1d variables that are associated with those dimensions. These will be useful in gridding. + # Get any _1d variables that are associated with those dimensions. These will be useful in gridding. Also, if any dimension is "pft", set up to rename it and all like-named variables to "patch" onedVars = [] + pft2patch_dict = {} for thisDim in dimList: pattern = re.compile(f"{thisDim}.*1d") matches = [x for x in list(ds.keys()) if pattern.search(x) != None] onedVars = list(set(onedVars + matches)) + if thisDim == "pft": + pft2patch_dict["pft"] = "patch" + for m in matches: + pft2patch_dict[m] = m.replace("pft","patch").replace("patchs","patches") # Add dimensions and _1d variables to vars_to_import vars_to_import = list(set(vars_to_import \ @@ -315,6 +330,10 @@ def mfdataset_preproc(ds, vars_to_import): # Drop them ds = ds.drop_vars(vars_to_drop) + # Rename "pft" dimension and variables to "patch", if needed + if len(pft2patch_dict) > 0: + ds = ds.rename(pft2patch_dict) + # Finish import ds = xr.decode_cf(ds, decode_times = True) return ds @@ -328,14 +347,23 @@ def mfdataset_preproc(ds, vars_to_import): concat_dim="time", preprocess=mfdataset_preproc_closure) - # Get vegetation type info - vegtypes = ivt_int_str(this_ds, this_pftlist) + # Add vegetation type info + ivt_int_str(this_ds, this_pftlist) # Includes check of whether vegtype changes over time anywhere + vegtype_da = get_vegtype_str_da(this_pftlist) + patches1d_itype_veg_str = vegtype_da.values[this_ds.isel(time=0).patches1d_itype_veg.values.astype(int)] + npatch = len(patches1d_itype_veg_str) + patches1d_itype_veg_str = xr.DataArray( \ + patches1d_itype_veg_str, + coords={"patch": np.arange(0,npatch)}, + dims=["patch"], + name = "patches1d_itype_veg_str") + this_ds = xr.merge([this_ds, vegtype_da, patches1d_itype_veg_str]) - return this_ds, vegtypes + return this_ds -# Return a DataArray, with defined coordinates (PFT as string), for a given variable in a dataset -def get_thisVar_da(thisVar, this_ds, vegtypes_str): +# Return a DataArray, with defined coordinates, for a given variable in a dataset +def get_thisVar_da(thisVar, this_ds): # Make DataArray for this variable thisvar_da = np.array(this_ds.variables[thisVar]) @@ -346,12 +374,7 @@ def get_thisVar_da(thisVar, this_ds, vegtypes_str): # Define coordinates of this variable's DataArray dimsDict = dict() for thisDim in theseDims: - if thisDim == "pft": - dimsDict[thisDim] = vegtypes_str - elif any(np.array(list(this_ds.dims.keys())) == thisDim): - dimsDict[thisDim] = this_ds[thisDim] - else: - raise ValueError("Unknown dimension for coordinate assignment: " + thisDim) + dimsDict[thisDim] = this_ds[thisDim] thisvar_da = thisvar_da.assign_coords(dimsDict) return thisvar_da @@ -369,34 +392,36 @@ def is_each_mgd_crop(this_pftlist): return [is_this_mgd_crop(x) for x in this_pftlist] -# Given a DataArray, remove all PFTs except managed crops. -def trim_to_mgd_crop(thisvar_da): +# Given a DataArray, remove all patches except those planted with managed crops. +def trim_to_mgd_crop(thisvar_da, patches1d_itype_veg_str): - # Handle input DataArray without pft dimension - if not any(np.array(list(thisvar_da.dims)) == "pft"): - print("Input DataArray has no pft dimension and therefore trim_to_mgd_crop() has no effect.") + # Handle input DataArray without patch dimension + if not any(np.array(list(thisvar_da.dims)) == "patch"): + print("Input DataArray has no patch dimension and therefore trim_to_mgd_crop() has no effect.") return thisvar_da - # Throw error if pft dimension isn't strings - if not isinstance(thisvar_da.pft.values[0], str): - raise TypeError("Input DataArray's pft dimension is not in string form, and therefore trim_to_mgd_crop() cannot work.") + # Throw error if patches1d_itype_veg_str isn't strings + if isinstance(patches1d_itype_veg_str, xr.DataArray): + patches1d_itype_veg_str = patches1d_itype_veg_str.values + if not isinstance(patches1d_itype_veg_str[0], str): + raise TypeError("Input patches1d_itype_veg_str is not in string form, and therefore trim_to_mgd_crop() cannot work.") - # Get boolean list of whether each PFT is a managed crop - is_crop = is_each_mgd_crop(thisvar_da.pft.values) + # Get boolean list of whether each patch is planted with a managed crop + is_crop = is_each_mgd_crop(patches1d_itype_veg_str) # Warn if no managed crops were found, but still return the empty result if np.all(np.bitwise_not(is_crop)): print("No managed crops found! Returning empty DataArray.") - return thisvar_da.isel(pft = [i for i, x in enumerate(is_crop) if x]) + return thisvar_da.isel(patch = [i for i, x in enumerate(is_crop) if x]) # Make a geographically gridded DataArray (with PFT dimension) of one variable within a DataSet. Optionally subset by time index (integer) or slice. -def grid_one_variable(this_ds, thisVar, vegtypes, time=None): +def grid_one_variable(this_ds, thisVar, time=None): - thisvar_da = get_thisVar_da(thisVar, this_ds, vegtypes["str"]) - ixy_da = get_thisVar_da("pfts1d_ixy", this_ds, vegtypes["str"]) - jxy_da = get_thisVar_da("pfts1d_jxy", this_ds, vegtypes["str"]) - vt_da = get_thisVar_da("pfts1d_itype_veg", this_ds, vegtypes["str"]) + thisvar_da = get_thisVar_da(thisVar, this_ds) + ixy_da = get_thisVar_da("patches1d_ixy", this_ds) + jxy_da = get_thisVar_da("patches1d_jxy", this_ds) + vt_da = get_thisVar_da("patches1d_itype_veg", this_ds) # Get this variable's values for selected time step(s), if provided if time != None: @@ -437,12 +462,12 @@ def check_slice_type(this_time): lon = this_ds.lon lat = this_ds.lat - # Set up empty array: time * PFT * lat * lon + # Set up empty array: time * vegtype * lat * lon ntime = len(thisvar_da.time) - npft = np.max(vegtypes["int"].values) + 1 + nvt = np.max(this_ds.patches1d_itype_veg.values) + 1 nlat = len(lat.values) nlon = len(lon.values) - thisvar_tpyx = np.empty([ntime, npft, nlat, nlon]) + thisvar_tpyx = np.empty([ntime, nvt, nlat, nlon]) # Fill with this variable thisvar_tpyx[:, @@ -451,10 +476,10 @@ def check_slice_type(this_time): ixy_da.values.astype(int) - 1] = thisvar_da.values # Assign coordinates and name - thisvar_tpyx = xr.DataArray(thisvar_tpyx, dims=("time","pft","lat","lon")) + thisvar_tpyx = xr.DataArray(thisvar_tpyx, dims=("time","ivt_str","lat","lon")) thisvar_tpyx = thisvar_tpyx.assign_coords( \ time = thisvar_da.time, - pft = vegtypes["all_str"], + ivt_str = this_ds.vegtype_str.values, lat = lat.values, lon = lon.values) thisvar_tpyx.name = thisVar From c57e0af5bd7d453380d58872c0257a90cc366cc3 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 29 Oct 2021 15:25:14 -0600 Subject: [PATCH 25/45] Commented out bit about dates in matplotlib format. --- 2d_crop_work.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/2d_crop_work.py b/2d_crop_work.py index 93545bf..7db21b3 100644 --- a/2d_crop_work.py +++ b/2d_crop_work.py @@ -39,12 +39,6 @@ # Import this_ds = utils.import_ds_from_filelist(filelist, utils.pftlist, myVars=myVars) -# Get dates in a format that matplotlib can use -with warnings.catch_warnings(): - # Ignore this warning in this with-block - warnings.filterwarnings("ignore", message="Converting a CFTimeIndex with dates from a non-standard calendar, 'noleap', to a pandas.DatetimeIndex, which uses dates from the standard calendar. This may lead to subtle errors in operations that depend on the length of time between dates.") - datetime_vals = this_ds.indexes["time"].to_datetimeindex() - # %% Read one variable from dataset. (Do nothing with it.) @@ -79,3 +73,9 @@ plt.pcolor(tmp_yx.lon.values, tmp_yx.lat.values, tmp_yx, transform=ccrs.PlateCarree()) ax.coastlines() plt.show() + +# # Get dates in a format that matplotlib can use +# with warnings.catch_warnings(): +# # Ignore this warning in this with-block +# warnings.filterwarnings("ignore", message="Converting a CFTimeIndex with dates from a non-standard calendar, 'noleap', to a pandas.DatetimeIndex, which uses dates from the standard calendar. This may lead to subtle errors in operations that depend on the length of time between dates.") +# datetime_vals = this_ds.indexes["time"].to_datetimeindex() \ No newline at end of file From d564668dad2b65186fb85b1be7b18cdb3f939805 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 29 Oct 2021 15:48:56 -0600 Subject: [PATCH 26/45] import_ds_from_filelist() now just import_ds(): Can provide just 1 file. --- 2d_crop_work.py | 1 + ctsm_py/utils.py | 120 +++++++++++++++++++++++++---------------------- 2 files changed, 65 insertions(+), 56 deletions(-) diff --git a/2d_crop_work.py b/2d_crop_work.py index 7db21b3..3b2be1e 100644 --- a/2d_crop_work.py +++ b/2d_crop_work.py @@ -38,6 +38,7 @@ # Import this_ds = utils.import_ds_from_filelist(filelist, utils.pftlist, myVars=myVars) +this_ds = utils.import_ds(filelist, utils.pftlist, myVars=myVars) # %% Read one variable from dataset. (Do nothing with it.) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index 99dbf5c..0dcb4e5 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -267,15 +267,17 @@ def ivt_int_str(this_ds, this_pftlist): vegtype_int.values = vegtype_int.values.astype(int) # Make sure no vegtype changes over time. - time_index = vegtype_int.dims.index("time") - uniques = np.unique(vegtype_int.values, \ - axis=time_index) - max_num_ivt_per_patch = uniques.shape[time_index] - if max_num_ivt_per_patch != 1: - raise ValueError("Some veg type changes over time") - - # Take the first timestep. - vegtype_int = vegtype_int.isel(time=0) + has_time = any([m == "time" for m in list(vegtype_int.dims)]) + if has_time: + time_index = vegtype_int.dims.index("time") + uniques = np.unique(vegtype_int.values, \ + axis=time_index) + max_num_ivt_per_patch = uniques.shape[time_index] + if max_num_ivt_per_patch != 1: + raise ValueError("Some veg type changes over time") + + # Take the first timestep. + vegtype_int = vegtype_int.isel(time=0) # Convert to strings. vegtype_str = list(np.array(this_pftlist)[vegtype_int.values]) @@ -294,58 +296,64 @@ def get_vegtype_str_da(vegtype_str): name = thisName) return vegtype_str_da -# Import a dataset that's spread over multiple files, only including specified variables. Concatenate by time. -def import_ds_from_filelist(filelist, this_pftlist, myVars=None): - - # Set up function to drop unwanted vars in preprocessing of open_mfdataset(), making sure to include any unspecified variables that will be useful in gridding. - def mfdataset_preproc(ds, vars_to_import): - - if vars_to_import != None: - # Get list of dimensions present in variables in vars_to_import. - dimList = [] - for thisVar in vars_to_import: - # list(set(x)) returns a list of the unique items in x - dimList = list(set(dimList + list(ds.variables[thisVar].dims))) - - # Get any _1d variables that are associated with those dimensions. These will be useful in gridding. Also, if any dimension is "pft", set up to rename it and all like-named variables to "patch" - onedVars = [] - pft2patch_dict = {} - for thisDim in dimList: - pattern = re.compile(f"{thisDim}.*1d") - matches = [x for x in list(ds.keys()) if pattern.search(x) != None] - onedVars = list(set(onedVars + matches)) - if thisDim == "pft": - pft2patch_dict["pft"] = "patch" - for m in matches: - pft2patch_dict[m] = m.replace("pft","patch").replace("patchs","patches") - - # Add dimensions and _1d variables to vars_to_import - vars_to_import = list(set(vars_to_import \ - + list(ds.dims) + onedVars)) - - # Get list of variables to drop - varlist = list(ds.variables) - vars_to_drop = list(np.setdiff1d(varlist, vars_to_import)) - - # Drop them - ds = ds.drop_vars(vars_to_drop) - - # Rename "pft" dimension and variables to "patch", if needed - if len(pft2patch_dict) > 0: - ds = ds.rename(pft2patch_dict) - - # Finish import - ds = xr.decode_cf(ds, decode_times = True) - return ds - # xr.open_mfdataset()'s "preprocess" argument requires a function that only takes one variable (an xarray.Dataset object). Wrapping mfdataset_preproc() in this lambda function allows this. Could also just allow mfdataset_preproc() to access the myVars directly, but that's bad practice as it could lead to scoping issues. +# Set up function to drop unwanted vars in preprocessing of open_mfdataset(), making sure to include any unspecified variables that will be useful in gridding. +def mfdataset_preproc(ds, vars_to_import): + + if vars_to_import != None: + # Get list of dimensions present in variables in vars_to_import. + dimList = [] + for thisVar in vars_to_import: + # list(set(x)) returns a list of the unique items in x + dimList = list(set(dimList + list(ds.variables[thisVar].dims))) + + # Get any _1d variables that are associated with those dimensions. These will be useful in gridding. Also, if any dimension is "pft", set up to rename it and all like-named variables to "patch" + onedVars = [] + pft2patch_dict = {} + for thisDim in dimList: + pattern = re.compile(f"{thisDim}.*1d") + matches = [x for x in list(ds.keys()) if pattern.search(x) != None] + onedVars = list(set(onedVars + matches)) + if thisDim == "pft": + pft2patch_dict["pft"] = "patch" + for m in matches: + pft2patch_dict[m] = m.replace("pft","patch").replace("patchs","patches") + + # Add dimensions and _1d variables to vars_to_import + vars_to_import = list(set(vars_to_import \ + + list(ds.dims) + onedVars)) + + # Get list of variables to drop + varlist = list(ds.variables) + vars_to_drop = list(np.setdiff1d(varlist, vars_to_import)) + + # Drop them + ds = ds.drop_vars(vars_to_drop) + + # Rename "pft" dimension and variables to "patch", if needed + if len(pft2patch_dict) > 0: + ds = ds.rename(pft2patch_dict) + + # Finish import + ds = xr.decode_cf(ds, decode_times = True) + return ds + + +# Import a dataset that's spread over multiple files, only including specified variables. Concatenate by time. +def import_ds(filelist, this_pftlist, myVars=None): + # "preprocess" argument requires a function that only takes one variable (an xarray.Dataset object). Wrapping mfdataset_preproc() in this lambda function allows this. Could also just allow mfdataset_preproc() to access the myVars directly, but that's bad practice as it could lead to scoping issues. mfdataset_preproc_closure = \ lambda ds: mfdataset_preproc(ds, myVars) # Import - this_ds = xr.open_mfdataset(filelist, \ - concat_dim="time", - preprocess=mfdataset_preproc_closure) + if isinstance(filelist, list): + this_ds = xr.open_mfdataset(filelist, \ + concat_dim="time", + preprocess=mfdataset_preproc_closure) + elif isinstance(filelist, str): + this_ds = xr.open_dataset(filelist) + this_ds = mfdataset_preproc(this_ds, myVars) + this_ds = this_ds.compute() # Add vegetation type info ivt_int_str(this_ds, this_pftlist) # Includes check of whether vegtype changes over time anywhere From 3ee2b8a84fcebec65ba717510068b217a21c9a74 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 29 Oct 2021 15:59:05 -0600 Subject: [PATCH 27/45] import_ds() now avoids expanding dimensions where unnecessary. --- 2d_crop_work.py | 6 ++---- ctsm_py/utils.py | 21 +-------------------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/2d_crop_work.py b/2d_crop_work.py index 3b2be1e..a9349b5 100644 --- a/2d_crop_work.py +++ b/2d_crop_work.py @@ -37,7 +37,6 @@ filelist = glob.glob(indir + pattern) # Import -this_ds = utils.import_ds_from_filelist(filelist, utils.pftlist, myVars=myVars) this_ds = utils.import_ds(filelist, utils.pftlist, myVars=myVars) @@ -51,13 +50,12 @@ thisvar_da = utils.get_thisVar_da(thisVar, this_ds) thisvar_da = utils.trim_to_mgd_crop(thisvar_da, this_ds.patches1d_itype_veg_str) -thisvar_da # %% Grid and make map, more efficiently, as function -import importlib -importlib.reload(utils) +# import importlib +# importlib.reload(utils) # Grid # tmp_vyx = utils.grid_one_variable(this_ds, "CPHASE", time=181) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index 0dcb4e5..c8ba9e3 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -266,19 +266,6 @@ def ivt_int_str(this_ds, this_pftlist): vegtype_int = this_ds.patches1d_itype_veg vegtype_int.values = vegtype_int.values.astype(int) - # Make sure no vegtype changes over time. - has_time = any([m == "time" for m in list(vegtype_int.dims)]) - if has_time: - time_index = vegtype_int.dims.index("time") - uniques = np.unique(vegtype_int.values, \ - axis=time_index) - max_num_ivt_per_patch = uniques.shape[time_index] - if max_num_ivt_per_patch != 1: - raise ValueError("Some veg type changes over time") - - # Take the first timestep. - vegtype_int = vegtype_int.isel(time=0) - # Convert to strings. vegtype_str = list(np.array(this_pftlist)[vegtype_int.values]) @@ -348,7 +335,7 @@ def import_ds(filelist, this_pftlist, myVars=None): # Import if isinstance(filelist, list): this_ds = xr.open_mfdataset(filelist, \ - concat_dim="time", + data_vars="minimal", preprocess=mfdataset_preproc_closure) elif isinstance(filelist, str): this_ds = xr.open_dataset(filelist) @@ -455,14 +442,8 @@ def check_slice_type(this_time): else: thisvar_da = thisvar_da.isel(time=time) # ^ Have to slice time like that instead of with index directly because otherwise .assign_coords() will throw an error - ixy_da = ixy_da.isel(time=time) - jxy_da = jxy_da.isel(time=time) - vt_da = vt_da.isel(time=time).values elif time_type == str: thisvar_da = thisvar_da.sel(time=time) - ixy_da = ixy_da.sel(time=time) - jxy_da = jxy_da.sel(time=time) - vt_da = vt_da.sel(time=time).values else: raise TypeError(f"'time' argument must be type int, str, or slice of those (not {type(time)})") From d360d84bb8d3983031eb80b429929c06276fea4a Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 29 Oct 2021 16:06:44 -0600 Subject: [PATCH 28/45] Cleaning up 2d_crop_work.py. --- 2d_crop_work.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/2d_crop_work.py b/2d_crop_work.py index a9349b5..b4ef01a 100644 --- a/2d_crop_work.py +++ b/2d_crop_work.py @@ -1,18 +1,26 @@ +# %% User-defined variables + +# Your path to ctsm_py directory (i.e., where utils.py lives) +sys.path.append("/Users/sam/Documents/git_repos/ctsm_python_gallery_myfork/ctsm_py/") + +# Directory where input file(s) can be found +indir = "/Volumes/Reacher/CESM_runs/f10_f10_mg37/" + +# Either the name of a file within $indir, or a pattern that will return a list of files. +pattern = "*h1.*-01-01-00000.nc" + # %% Setup import numpy as np import xarray as xr from xarray.backends.api import load_dataset -from ctsm_py import utils import matplotlib.pyplot as plt import warnings import glob import cartopy.crs as ccrs import cartopy.feature as cfeature - import sys -sys.path.append("/Users/sam/Documents/git_repos/ctsm_python_gallery_myfork/ctsm_py/") import utils @@ -32,8 +40,6 @@ "TOTVEGC"] # Get list of all files in $indir matching $pattern -indir = "/Volumes/Reacher/CESM_runs/f10_f10_mg37/" -pattern = "*h1.*-01-01-00000.nc" filelist = glob.glob(indir + pattern) # Import From b730936a37039177f0a3a560d0da3c487d13893d Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 29 Oct 2021 16:17:11 -0600 Subject: [PATCH 29/45] pftlist now produced by a function. --- 2d_crop_work.py | 3 +- ctsm_py/utils.py | 163 ++++++++++++++++++++++++----------------------- 2 files changed, 84 insertions(+), 82 deletions(-) diff --git a/2d_crop_work.py b/2d_crop_work.py index b4ef01a..38467ed 100644 --- a/2d_crop_work.py +++ b/2d_crop_work.py @@ -14,7 +14,6 @@ import numpy as np import xarray as xr -from xarray.backends.api import load_dataset import matplotlib.pyplot as plt import warnings import glob @@ -43,7 +42,7 @@ filelist = glob.glob(indir + pattern) # Import -this_ds = utils.import_ds(filelist, utils.pftlist, myVars=myVars) +this_ds = utils.import_ds(filelist, myVars=myVars) # %% Read one variable from dataset. (Do nothing with it.) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index c8ba9e3..27712e1 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -179,85 +179,87 @@ def cyclic_dataset(ds, coord='lon'): ''' # List of PFTs used in CLM -pftlist = ["not_vegetated", - "needleleaf_evergreen_temperate_tree", - "needleleaf_evergreen_boreal_tree", - "needleleaf_deciduous_boreal_tree", - "broadleaf_evergreen_tropical_tree", - "broadleaf_evergreen_temperate_tree", - "broadleaf_deciduous_tropical_tree", - "broadleaf_deciduous_temperate_tree", - "broadleaf_deciduous_boreal_tree", - "broadleaf_evergreen_shrub", - "broadleaf_deciduous_temperate_shrub", - "broadleaf_deciduous_boreal_shrub", - "c3_arctic_grass", - "c3_non-arctic_grass", - "c4_grass", - "unmanaged_c3_crop", - "unmanaged_c3_irrigated", - "temperate_corn", - "irrigated_temperate_corn", - "spring_wheat", - "irrigated_spring_wheat", - "winter_wheat", - "irrigated_winter_wheat", - "soybean", - "irrigated_soybean", - "barley", - "irrigated_barley", - "winter_barley", - "irrigated_winter_barley", - "rye", - "irrigated_rye", - "winter_rye", - "irrigated_winter_rye", - "cassava", - "irrigated_cassava", - "citrus", - "irrigated_citrus", - "cocoa", - "irrigated_cocoa", - "coffee", - "irrigated_coffee", - "cotton", - "irrigated_cotton", - "datepalm", - "irrigated_datepalm", - "foddergrass", - "irrigated_foddergrass", - "grapes", - "irrigated_grapes", - "groundnuts", - "irrigated_groundnuts", - "millet", - "irrigated_millet", - "oilpalm", - "irrigated_oilpalm", - "potatoes", - "irrigated_potatoes", - "pulses", - "irrigated_pulses", - "rapeseed", - "irrigated_rapeseed", - "rice", - "irrigated_rice", - "sorghum", - "irrigated_sorghum", - "sugarbeet", - "irrigated_sugarbeet", - "sugarcane", - "irrigated_sugarcane", - "sunflower", - "irrigated_sunflower", - "miscanthus", - "irrigated_miscanthus", - "switchgrass", - "irrigated_switchgrass", - "tropical_corn", - "irrigated_tropical_corn", - "tropical_soybean", - "irrigated_tropical_soybean"] +def define_pftlist(): + pftlist = ["not_vegetated", + "needleleaf_evergreen_temperate_tree", + "needleleaf_evergreen_boreal_tree", + "needleleaf_deciduous_boreal_tree", + "broadleaf_evergreen_tropical_tree", + "broadleaf_evergreen_temperate_tree", + "broadleaf_deciduous_tropical_tree", + "broadleaf_deciduous_temperate_tree", + "broadleaf_deciduous_boreal_tree", + "broadleaf_evergreen_shrub", + "broadleaf_deciduous_temperate_shrub", + "broadleaf_deciduous_boreal_shrub", + "c3_arctic_grass", + "c3_non-arctic_grass", + "c4_grass", + "unmanaged_c3_crop", + "unmanaged_c3_irrigated", + "temperate_corn", + "irrigated_temperate_corn", + "spring_wheat", + "irrigated_spring_wheat", + "winter_wheat", + "irrigated_winter_wheat", + "soybean", + "irrigated_soybean", + "barley", + "irrigated_barley", + "winter_barley", + "irrigated_winter_barley", + "rye", + "irrigated_rye", + "winter_rye", + "irrigated_winter_rye", + "cassava", + "irrigated_cassava", + "citrus", + "irrigated_citrus", + "cocoa", + "irrigated_cocoa", + "coffee", + "irrigated_coffee", + "cotton", + "irrigated_cotton", + "datepalm", + "irrigated_datepalm", + "foddergrass", + "irrigated_foddergrass", + "grapes", + "irrigated_grapes", + "groundnuts", + "irrigated_groundnuts", + "millet", + "irrigated_millet", + "oilpalm", + "irrigated_oilpalm", + "potatoes", + "irrigated_potatoes", + "pulses", + "irrigated_pulses", + "rapeseed", + "irrigated_rapeseed", + "rice", + "irrigated_rice", + "sorghum", + "irrigated_sorghum", + "sugarbeet", + "irrigated_sugarbeet", + "sugarcane", + "irrigated_sugarcane", + "sunflower", + "irrigated_sunflower", + "miscanthus", + "irrigated_miscanthus", + "switchgrass", + "irrigated_switchgrass", + "tropical_corn", + "irrigated_tropical_corn", + "tropical_soybean", + "irrigated_tropical_soybean"] + return pftlist # Get PFT of each patch, in both integer and string forms @@ -327,7 +329,7 @@ def mfdataset_preproc(ds, vars_to_import): # Import a dataset that's spread over multiple files, only including specified variables. Concatenate by time. -def import_ds(filelist, this_pftlist, myVars=None): +def import_ds(filelist, myVars=None): # "preprocess" argument requires a function that only takes one variable (an xarray.Dataset object). Wrapping mfdataset_preproc() in this lambda function allows this. Could also just allow mfdataset_preproc() to access the myVars directly, but that's bad practice as it could lead to scoping issues. mfdataset_preproc_closure = \ lambda ds: mfdataset_preproc(ds, myVars) @@ -343,6 +345,7 @@ def import_ds(filelist, this_pftlist, myVars=None): this_ds = this_ds.compute() # Add vegetation type info + this_pftlist = define_pftlist() ivt_int_str(this_ds, this_pftlist) # Includes check of whether vegtype changes over time anywhere vegtype_da = get_vegtype_str_da(this_pftlist) patches1d_itype_veg_str = vegtype_da.values[this_ds.isel(time=0).patches1d_itype_veg.values.astype(int)] From 298361781dec0ed92830e86ca52b79b2c9246e35 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 29 Oct 2021 16:35:30 -0600 Subject: [PATCH 30/45] Added xr_flexsel() function. Flexibly subset from an xarray DataSet or DataArray, to avoid having to choose between .sel() or .isel(). Selections can be individual values or slices. Similar to what was already in grid_one_variable(), but can also take selection of vegtypes (not yet tested). --- ctsm_py/utils.py | 86 +++++++++++++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 30 deletions(-) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index 27712e1..1bc1f37 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -178,6 +178,56 @@ def cyclic_dataset(ds, coord='lon'): return new_ds ''' +# Check the type of a selection +def check_sel_type(this_sel): + if isinstance(this_sel, slice): + if this_sel == slice(0): + raise ValueError("slice(0) will be empty") + elif this_sel.start != None: + return type(this_sel.start) + elif this_sel.stop != None: + return type(this_sel.stop) + elif this_sel.step != None: + return type(this_sel.step) + else: + raise TypeError("slice is all None?") + else: + return type(this_sel) + +# Flexibly subset from an xarray DataSet or DataArray. Selections can be individual values or slices. +def xr_flexsel(xr_object, time=None, vegtype=None): + # SSR TODO: Consolidate repetitive code. + # SSR TODO: Optimize by starting selections with dimension that will result in the largest reduction of object size. + + if time != None: + time_type = check_sel_type(time) + if time_type == int: + # Have to select like this instead of with index directly because otherwise assign_coords() will throw an error. Not sure why. + if isinstance(time, int): + xr_object = xr_object.isel(time=slice(time,time+1)) + else: + xr_object = xr_object.isel(time=time) + + elif time_type == str: + xr_object = xr_object.sel(time=time) + else: + raise TypeError(f"'time' argument must be type int, str, or slice of those (not {type(time)})") + + if vegtype != None: + vegtype_type = check_sel_type(vegtype) + if vegtype_type == int: + # Have to select like this instead of with index directly because otherwise assign_coords() will throw an error. Not sure why. + if isinstance(vegtype, int): + xr_object = xr_object.isel(vegtype=slice(vegtype,vegtype+1)) + else: + xr_object = xr_object.isel(vegtype=vegtype) + elif vegtype_type == str: + xr_object = xr_object.sel(time=vegtype) + else: + raise TypeError(f"'vegtype' argument must be type int, str, or slice of those (not {type(vegtype)})") + return xr_object + + # List of PFTs used in CLM def define_pftlist(): pftlist = ["not_vegetated", @@ -287,7 +337,7 @@ def get_vegtype_str_da(vegtype_str): # Set up function to drop unwanted vars in preprocessing of open_mfdataset(), making sure to include any unspecified variables that will be useful in gridding. -def mfdataset_preproc(ds, vars_to_import): +def mfdataset_preproc(ds, vars_to_import, vegtypes_to_import): if vars_to_import != None: # Get list of dimensions present in variables in vars_to_import. @@ -322,6 +372,8 @@ def mfdataset_preproc(ds, vars_to_import): # Rename "pft" dimension and variables to "patch", if needed if len(pft2patch_dict) > 0: ds = ds.rename(pft2patch_dict) + + # if vegtypes_to_import != None: # Finish import ds = xr.decode_cf(ds, decode_times = True) @@ -329,10 +381,10 @@ def mfdataset_preproc(ds, vars_to_import): # Import a dataset that's spread over multiple files, only including specified variables. Concatenate by time. -def import_ds(filelist, myVars=None): +def import_ds(filelist, myVars=None, myVegtypes=None): # "preprocess" argument requires a function that only takes one variable (an xarray.Dataset object). Wrapping mfdataset_preproc() in this lambda function allows this. Could also just allow mfdataset_preproc() to access the myVars directly, but that's bad practice as it could lead to scoping issues. mfdataset_preproc_closure = \ - lambda ds: mfdataset_preproc(ds, myVars) + lambda ds: mfdataset_preproc(ds, myVars, myVegtypes) # Import if isinstance(filelist, list): @@ -422,33 +474,7 @@ def grid_one_variable(this_ds, thisVar, time=None): vt_da = get_thisVar_da("patches1d_itype_veg", this_ds) # Get this variable's values for selected time step(s), if provided - if time != None: - def check_slice_type(this_time): - if isinstance(this_time, slice): - if this_time == slice(0): - raise ValueError("slice(0) will be empty") - elif this_time.start != None: - return type(this_time.start) - elif this_time.stop != None: - return type(this_time.stop) - elif this_time.step != None: - return type(this_time.step) - else: - raise TypeError("slice is all None?") - else: - return type(this_time) - time_type = check_slice_type(time) - if time_type == int: - # thisvar_da = thisvar_da.isel(time=time) - if isinstance(time, int): - thisvar_da = thisvar_da.isel(time=slice(time,time+1)) - else: - thisvar_da = thisvar_da.isel(time=time) - # ^ Have to slice time like that instead of with index directly because otherwise .assign_coords() will throw an error - elif time_type == str: - thisvar_da = thisvar_da.sel(time=time) - else: - raise TypeError(f"'time' argument must be type int, str, or slice of those (not {type(time)})") + thisvar_da = xr_flexsel(thisvar_da, time=time) # Get dataset lon/lat grid lon = this_ds.lon From e1fb811e1bc3f4c862eb56a05f8e2677c8cb49ac Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 29 Oct 2021 17:09:40 -0600 Subject: [PATCH 31/45] Generalized functions to find matching (or NOT matching) vegtypes. --- ctsm_py/utils.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index 1bc1f37..029d72a 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -194,6 +194,7 @@ def check_sel_type(this_sel): else: return type(this_sel) + # Flexibly subset from an xarray DataSet or DataArray. Selections can be individual values or slices. def xr_flexsel(xr_object, time=None, vegtype=None): # SSR TODO: Consolidate repetitive code. @@ -432,14 +433,22 @@ def get_thisVar_da(thisVar, this_ds): # Is this PFT a managed crop? # SSR TODO: Require that input be a single string. -def is_this_mgd_crop(this_pft): - notcrop_list = ["tree", "grass", "shrub", "unmanaged", "not_vegetated"] - return not any(n in this_pft for n in notcrop_list) +def is_this_vegtype(this_pft, this_list, method): + if method == "ok_contains": + return any(n in this_pft for n in this_list) + elif method == "notok_contains": + return not any(n in this_pft for n in this_list) + elif method == "ok_exact": + return any(n == this_pft for n in this_list) + elif method == "notok_exact": + return not any(n == this_pft for n in this_list) + else: + raise ValueError(f"Unknown method: '{method}'") # Get boolean list of whether each PFT in list is a managed crop -def is_each_mgd_crop(this_pftlist): - return [is_this_mgd_crop(x) for x in this_pftlist] +def is_each_vegtype(this_pftlist, this_filter, this_method): + return [is_this_vegtype(x, this_filter, this_method) for x in this_pftlist] # Given a DataArray, remove all patches except those planted with managed crops. @@ -457,7 +466,8 @@ def trim_to_mgd_crop(thisvar_da, patches1d_itype_veg_str): raise TypeError("Input patches1d_itype_veg_str is not in string form, and therefore trim_to_mgd_crop() cannot work.") # Get boolean list of whether each patch is planted with a managed crop - is_crop = is_each_mgd_crop(patches1d_itype_veg_str) + notcrop_list = ["tree", "grass", "shrub", "unmanaged", "not_vegetated"] + is_crop = is_each_vegtype(patches1d_itype_veg_str, notcrop_list, "notok_contains") # Warn if no managed crops were found, but still return the empty result if np.all(np.bitwise_not(is_crop)): From d4e21b7d3122c585c0442b2e562a7a3f19cf544a Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 29 Oct 2021 17:12:29 -0600 Subject: [PATCH 32/45] Moved is_*_vegtype() functions. --- ctsm_py/utils.py | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index 029d72a..e9a3e9e 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -194,6 +194,25 @@ def check_sel_type(this_sel): else: return type(this_sel) +# Is this PFT a managed crop? +# SSR TODO: Require that input be a single string. +def is_this_vegtype(this_pft, this_list, method): + if method == "ok_contains": + return any(n in this_pft for n in this_list) + elif method == "notok_contains": + return not any(n in this_pft for n in this_list) + elif method == "ok_exact": + return any(n == this_pft for n in this_list) + elif method == "notok_exact": + return not any(n == this_pft for n in this_list) + else: + raise ValueError(f"Unknown method: '{method}'") + + +# Get boolean list of whether each PFT in list is a managed crop +def is_each_vegtype(this_pftlist, this_filter, this_method): + return [is_this_vegtype(x, this_filter, this_method) for x in this_pftlist] + # Flexibly subset from an xarray DataSet or DataArray. Selections can be individual values or slices. def xr_flexsel(xr_object, time=None, vegtype=None): @@ -431,26 +450,6 @@ def get_thisVar_da(thisVar, this_ds): return thisvar_da -# Is this PFT a managed crop? -# SSR TODO: Require that input be a single string. -def is_this_vegtype(this_pft, this_list, method): - if method == "ok_contains": - return any(n in this_pft for n in this_list) - elif method == "notok_contains": - return not any(n in this_pft for n in this_list) - elif method == "ok_exact": - return any(n == this_pft for n in this_list) - elif method == "notok_exact": - return not any(n == this_pft for n in this_list) - else: - raise ValueError(f"Unknown method: '{method}'") - - -# Get boolean list of whether each PFT in list is a managed crop -def is_each_vegtype(this_pftlist, this_filter, this_method): - return [is_this_vegtype(x, this_filter, this_method) for x in this_pftlist] - - # Given a DataArray, remove all patches except those planted with managed crops. def trim_to_mgd_crop(thisvar_da, patches1d_itype_veg_str): From f8fc96676482c36b06c0b9a123d331a1c30ca5e6 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 29 Oct 2021 17:34:14 -0600 Subject: [PATCH 33/45] import_ds() can now handle specified exact vegtype names to import. INCOMPLETE. Need to add handling of vegtype "names" when specified as (slice of) integers. --- ctsm_py/utils.py | 47 ++++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index e9a3e9e..62ac43d 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -236,15 +236,22 @@ def xr_flexsel(xr_object, time=None, vegtype=None): if vegtype != None: vegtype_type = check_sel_type(vegtype) if vegtype_type == int: + raise TypeError("Add handling of vegtype_type == int") # Have to select like this instead of with index directly because otherwise assign_coords() will throw an error. Not sure why. if isinstance(vegtype, int): - xr_object = xr_object.isel(vegtype=slice(vegtype,vegtype+1)) + xr_object = xr_object.isel(patches1d_itype_veg=slice(vegtype,vegtype+1)) else: - xr_object = xr_object.isel(vegtype=vegtype) - elif vegtype_type == str: - xr_object = xr_object.sel(time=vegtype) + xr_object = xr_object.isel(patches1d_itype_veg=vegtype) + elif vegtype_type == str or vegtype_type == list: + # SSR TODO: Test whether it's faster to convert vegtype list to int and compare that way + if vegtype_type == str: + vegtype = [vegtype] + is_vegtype = is_each_vegtype(xr_object.patches1d_itype_veg_str, \ + vegtype, "ok_exact") + xr_object = xr_object.sel(patch=[i for i, x in enumerate(is_vegtype) if x]) else: - raise TypeError(f"'vegtype' argument must be type int, str, or slice of those (not {type(vegtype)})") + raise TypeError(f"'vegtype' argument must be type int, str, or slice of those, or list of str (not {type(vegtype)})") + return xr_object @@ -393,7 +400,22 @@ def mfdataset_preproc(ds, vars_to_import, vegtypes_to_import): if len(pft2patch_dict) > 0: ds = ds.rename(pft2patch_dict) - # if vegtypes_to_import != None: + # Add vegetation type info + this_pftlist = define_pftlist() + ivt_int_str(ds, this_pftlist) # Includes check of whether vegtype changes over time anywhere + vegtype_da = get_vegtype_str_da(this_pftlist) + patches1d_itype_veg_str = vegtype_da.values[ds.isel(time=0).patches1d_itype_veg.values.astype(int)] + npatch = len(patches1d_itype_veg_str) + patches1d_itype_veg_str = xr.DataArray( \ + patches1d_itype_veg_str, + coords={"patch": np.arange(0,npatch)}, + dims=["patch"], + name = "patches1d_itype_veg_str") + ds = xr.merge([ds, vegtype_da, patches1d_itype_veg_str]) + + # Restrict to veg. types of interest, if any + if vegtypes_to_import != None: + ds = xr_flexsel(ds, vegtype=vegtypes_to_import) # Finish import ds = xr.decode_cf(ds, decode_times = True) @@ -416,19 +438,6 @@ def import_ds(filelist, myVars=None, myVegtypes=None): this_ds = mfdataset_preproc(this_ds, myVars) this_ds = this_ds.compute() - # Add vegetation type info - this_pftlist = define_pftlist() - ivt_int_str(this_ds, this_pftlist) # Includes check of whether vegtype changes over time anywhere - vegtype_da = get_vegtype_str_da(this_pftlist) - patches1d_itype_veg_str = vegtype_da.values[this_ds.isel(time=0).patches1d_itype_veg.values.astype(int)] - npatch = len(patches1d_itype_veg_str) - patches1d_itype_veg_str = xr.DataArray( \ - patches1d_itype_veg_str, - coords={"patch": np.arange(0,npatch)}, - dims=["patch"], - name = "patches1d_itype_veg_str") - this_ds = xr.merge([this_ds, vegtype_da, patches1d_itype_veg_str]) - return this_ds From 4e4df29621d8e9be9b8560ba8078a1d8525d714c Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 29 Oct 2021 21:01:39 -0600 Subject: [PATCH 34/45] vegtype selection in xr_flexsel() now uses integers for efficiency. --- ctsm_py/utils.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index 62ac43d..3b9d8df 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -227,30 +227,21 @@ def xr_flexsel(xr_object, time=None, vegtype=None): xr_object = xr_object.isel(time=slice(time,time+1)) else: xr_object = xr_object.isel(time=time) - elif time_type == str: xr_object = xr_object.sel(time=time) else: raise TypeError(f"'time' argument must be type int, str, or slice of those (not {type(time)})") if vegtype != None: - vegtype_type = check_sel_type(vegtype) - if vegtype_type == int: - raise TypeError("Add handling of vegtype_type == int") - # Have to select like this instead of with index directly because otherwise assign_coords() will throw an error. Not sure why. - if isinstance(vegtype, int): - xr_object = xr_object.isel(patches1d_itype_veg=slice(vegtype,vegtype+1)) - else: - xr_object = xr_object.isel(patches1d_itype_veg=vegtype) - elif vegtype_type == str or vegtype_type == list: - # SSR TODO: Test whether it's faster to convert vegtype list to int and compare that way - if vegtype_type == str: - vegtype = [vegtype] - is_vegtype = is_each_vegtype(xr_object.patches1d_itype_veg_str, \ - vegtype, "ok_exact") - xr_object = xr_object.sel(patch=[i for i, x in enumerate(is_vegtype) if x]) - else: - raise TypeError(f"'vegtype' argument must be type int, str, or slice of those, or list of str (not {type(vegtype)})") + if not isinstance(vegtype, list): + vegtype = [vegtype] + if isinstance(vegtype[0], str): + ind_dict = dict((k,i) for i,k in enumerate(xr_object.vegtype_str.values)) + inter = set(ind_dict).intersection(vegtype) + indices = [ ind_dict[x] for x in inter ] + vegtype = indices + is_vegtype = is_each_vegtype(xr_object.patches1d_itype_veg.values, indices, "ok_exact") + xr_object = xr_object.sel(patch=[i for i, x in enumerate(is_vegtype) if x]) return xr_object From 797706fce7223316acbb65464ec78f87c4ddcfb0 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 29 Oct 2021 21:39:16 -0600 Subject: [PATCH 35/45] xr_flexsel() can now handle more types of vegtype input. Integer, list of integers, or list of booleans. Also improved efficiency when specifying myVegtypes in xr.open_mfdataset() in import_ds(). --- ctsm_py/utils.py | 140 ++++++++++++++++++++++++++++++----------------- 1 file changed, 89 insertions(+), 51 deletions(-) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index 3b9d8df..0ff384c 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -194,57 +194,6 @@ def check_sel_type(this_sel): else: return type(this_sel) -# Is this PFT a managed crop? -# SSR TODO: Require that input be a single string. -def is_this_vegtype(this_pft, this_list, method): - if method == "ok_contains": - return any(n in this_pft for n in this_list) - elif method == "notok_contains": - return not any(n in this_pft for n in this_list) - elif method == "ok_exact": - return any(n == this_pft for n in this_list) - elif method == "notok_exact": - return not any(n == this_pft for n in this_list) - else: - raise ValueError(f"Unknown method: '{method}'") - - -# Get boolean list of whether each PFT in list is a managed crop -def is_each_vegtype(this_pftlist, this_filter, this_method): - return [is_this_vegtype(x, this_filter, this_method) for x in this_pftlist] - - -# Flexibly subset from an xarray DataSet or DataArray. Selections can be individual values or slices. -def xr_flexsel(xr_object, time=None, vegtype=None): - # SSR TODO: Consolidate repetitive code. - # SSR TODO: Optimize by starting selections with dimension that will result in the largest reduction of object size. - - if time != None: - time_type = check_sel_type(time) - if time_type == int: - # Have to select like this instead of with index directly because otherwise assign_coords() will throw an error. Not sure why. - if isinstance(time, int): - xr_object = xr_object.isel(time=slice(time,time+1)) - else: - xr_object = xr_object.isel(time=time) - elif time_type == str: - xr_object = xr_object.sel(time=time) - else: - raise TypeError(f"'time' argument must be type int, str, or slice of those (not {type(time)})") - - if vegtype != None: - if not isinstance(vegtype, list): - vegtype = [vegtype] - if isinstance(vegtype[0], str): - ind_dict = dict((k,i) for i,k in enumerate(xr_object.vegtype_str.values)) - inter = set(ind_dict).intersection(vegtype) - indices = [ ind_dict[x] for x in inter ] - vegtype = indices - is_vegtype = is_each_vegtype(xr_object.patches1d_itype_veg.values, indices, "ok_exact") - xr_object = xr_object.sel(patch=[i for i, x in enumerate(is_vegtype) if x]) - - return xr_object - # List of PFTs used in CLM def define_pftlist(): @@ -330,6 +279,87 @@ def define_pftlist(): return pftlist + +# Is this PFT a managed crop? +# SSR TODO: Require that input be a single string. +def is_this_vegtype(this_pft, this_list, method): + if method == "ok_contains": + return any(n in this_pft for n in this_list) + elif method == "notok_contains": + return not any(n in this_pft for n in this_list) + elif method == "ok_exact": + return any(n == this_pft for n in this_list) + elif method == "notok_exact": + return not any(n == this_pft for n in this_list) + else: + raise ValueError(f"Unknown method: '{method}'") + + +# Get boolean list of whether each PFT in list is a managed crop +def is_each_vegtype(this_pftlist, this_filter, this_method): + return [is_this_vegtype(x, this_filter, this_method) for x in this_pftlist] + + +# Convert list of vegtypes to integer index equivalents +def vegtype_str2int(vegtype_str, vegtype_mainlist=None): + if isinstance(vegtype_mainlist, xr.Dataset): + vegtype_mainlist = vegtype_mainlist.vegtype_str.values + elif isinstance(vegtype_mainlist, xr.DataArray): + vegtype_mainlist = vegtype_mainlist.values + elif vegtype_mainlist == None: + vegtype_mainlist = define_pftlist() + if not isinstance(vegtype_mainlist, list) and isinstance(vegtype_mainlist[0], str): + if isinstance(vegtype_mainlist, list): + raise TypeError(f"Not sure how to handle vegtype_mainlist as list of {type(vegtype_mainlist[0])}") + else: + raise TypeError(f"Not sure how to handle vegtype_mainlist as type {type(vegtype_mainlist[0])}") + ind_dict = dict((k,i) for i,k in enumerate(vegtype_mainlist)) + inter = set(ind_dict).intersection(vegtype_str) + indices = [ ind_dict[x] for x in inter ] + return indices + +# Flexibly subset from an xarray DataSet or DataArray. Selections can be individual values or slices. +def xr_flexsel(xr_object, time=None, vegtype=None): + # SSR TODO: Consolidate repetitive code. + # SSR TODO: Optimize by starting selections with dimension that will result in the largest reduction of object size. + + if time != None: + time_type = check_sel_type(time) + if time_type == int: + # Have to select like this instead of with index directly because otherwise assign_coords() will throw an error. Not sure why. + if isinstance(time, int): + xr_object = xr_object.isel(time=slice(time,time+1)) + else: + xr_object = xr_object.isel(time=time) + elif time_type == str: + xr_object = xr_object.sel(time=time) + else: + raise TypeError(f"'time' argument must be type int, str, or slice of those (not {type(time)})") + + if vegtype != None: + + # Convert to list, if needed + if not isinstance(vegtype, list): + vegtype = [vegtype] + + # Convert to indices, if needed + if isinstance(vegtype[0], str): + vegtype = vegtype_str2int(vegtype) + + # Get list of boolean(s) + if isinstance(vegtype[0], int): + is_vegtype = is_each_vegtype(xr_object.patches1d_itype_veg.values, vegtype, "ok_exact") + elif isinstance(vegtype[0], bool): + if len(vegtype) != len(xr_object.patch): + raise ValueError(f"If providing boolean 'vegtype' argument to xr_flexsel(), it must be the same length as xr_object.patch ({len(vegtype)} vs. {len(xr_object.patch)})") + is_vegtype = vegtype + else: + raise TypeError(f"Not sure how to handle 'vegtype' of type {type(vegtype)}") + xr_object = xr_object.sel(patch=[i for i, x in enumerate(is_vegtype) if x]) + + return xr_object + + # Get PFT of each patch, in both integer and string forms def ivt_int_str(this_ds, this_pftlist): # First, get all the integer values; should be time*pft or pft*time. We will eventually just take the first timestep. @@ -415,6 +445,14 @@ def mfdataset_preproc(ds, vars_to_import, vegtypes_to_import): # Import a dataset that's spread over multiple files, only including specified variables. Concatenate by time. def import_ds(filelist, myVars=None, myVegtypes=None): + + # Convert myVegtypes here, if needed, to avoid repeating the process each time you read a file in xr.open_mfdataset(). + if myVegtypes != None: + if not isinstance(myVegtypes, list): + myVegtypes = [myVegtypes] + if isinstance(myVegtypes[0], str): + myVegtypes = vegtype_str2int(myVegtypes) + # "preprocess" argument requires a function that only takes one variable (an xarray.Dataset object). Wrapping mfdataset_preproc() in this lambda function allows this. Could also just allow mfdataset_preproc() to access the myVars directly, but that's bad practice as it could lead to scoping issues. mfdataset_preproc_closure = \ lambda ds: mfdataset_preproc(ds, myVars, myVegtypes) From 792626309f50ff575206adfd3b957b34f7d38fc7 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 29 Oct 2021 22:07:13 -0600 Subject: [PATCH 36/45] Added function define_mgdcrop_list(). Returns the subset of CLM pft names that are managed crops. --- 2d_crop_work.py | 3 ++- ctsm_py/utils.py | 11 +++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/2d_crop_work.py b/2d_crop_work.py index 38467ed..163a4a4 100644 --- a/2d_crop_work.py +++ b/2d_crop_work.py @@ -42,7 +42,8 @@ filelist = glob.glob(indir + pattern) # Import -this_ds = utils.import_ds(filelist, myVars=myVars) +# this_ds = utils.import_ds(filelist, myVars=myVars) +this_ds = utils.import_ds(filelist, myVars=myVars, myVegtypes=utils.define_mgdcrop_list()) # %% Read one variable from dataset. (Do nothing with it.) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index 0ff384c..43d379c 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -279,7 +279,6 @@ def define_pftlist(): return pftlist - # Is this PFT a managed crop? # SSR TODO: Require that input be a single string. def is_this_vegtype(this_pft, this_list, method): @@ -300,6 +299,14 @@ def is_each_vegtype(this_pftlist, this_filter, this_method): return [is_this_vegtype(x, this_filter, this_method) for x in this_pftlist] +# List of managed crops in CLM +def define_mgdcrop_list(): + notcrop_list = ["tree", "grass", "shrub", "unmanaged", "not_vegetated"] + defined_pftlist = define_pftlist() + is_crop = is_each_vegtype(defined_pftlist, notcrop_list, "notok_contains") + return [defined_pftlist[i] for i, x in enumerate(is_crop) if x] + + # Convert list of vegtypes to integer index equivalents def vegtype_str2int(vegtype_str, vegtype_mainlist=None): if isinstance(vegtype_mainlist, xr.Dataset): @@ -489,7 +496,7 @@ def get_thisVar_da(thisVar, this_ds): # Given a DataArray, remove all patches except those planted with managed crops. -def trim_to_mgd_crop(thisvar_da, patches1d_itype_veg_str): +def trim_da_to_mgd_crop(thisvar_da, patches1d_itype_veg_str): # Handle input DataArray without patch dimension if not any(np.array(list(thisvar_da.dims)) == "patch"): From fd081f447b1b4c5b2a13ca01afaf1928d5437aa6 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 29 Oct 2021 22:14:06 -0600 Subject: [PATCH 37/45] To-do/comment changes re: xr_flexsel(). --- ctsm_py/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index 43d379c..fdfda9d 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -325,10 +325,9 @@ def vegtype_str2int(vegtype_str, vegtype_mainlist=None): indices = [ ind_dict[x] for x in inter ] return indices -# Flexibly subset from an xarray DataSet or DataArray. Selections can be individual values or slices. +# Flexibly subset from an xarray Dataset or DataArray. Selections can be individual values or slices. def xr_flexsel(xr_object, time=None, vegtype=None): - # SSR TODO: Consolidate repetitive code. - # SSR TODO: Optimize by starting selections with dimension that will result in the largest reduction of object size. + # SSR TODO: Optimize by starting selections with dimension that will result in the largest reduction of object size. Is there a way to do this without repeating a bunch of code, that DOESN'T involve writing another function (and therefore making another in-memory copy of the object)? Although I guess that's not an issue for Datasets that haven't yet been loaded into memory. But then, in that case, this optimization is unnecessary! if time != None: time_type = check_sel_type(time) From 78b403fd845bd054157e05c99f7945888204c20f Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Mon, 1 Nov 2021 10:22:44 -0600 Subject: [PATCH 38/45] Moved and improved description of check_sel_type(). --- ctsm_py/utils.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index fdfda9d..9916666 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -178,22 +178,6 @@ def cyclic_dataset(ds, coord='lon'): return new_ds ''' -# Check the type of a selection -def check_sel_type(this_sel): - if isinstance(this_sel, slice): - if this_sel == slice(0): - raise ValueError("slice(0) will be empty") - elif this_sel.start != None: - return type(this_sel.start) - elif this_sel.stop != None: - return type(this_sel.stop) - elif this_sel.step != None: - return type(this_sel.step) - else: - raise TypeError("slice is all None?") - else: - return type(this_sel) - # List of PFTs used in CLM def define_pftlist(): @@ -325,6 +309,22 @@ def vegtype_str2int(vegtype_str, vegtype_mainlist=None): indices = [ ind_dict[x] for x in inter ] return indices +# Check the type of a selection. Used in xr_flexsel(). This function ended up only being used once there, but keep it separate anyway to avoid having to re-do it in the future. +def check_sel_type(this_sel): + if isinstance(this_sel, slice): + if this_sel == slice(0): + raise ValueError("slice(0) will be empty") + elif this_sel.start != None: + return type(this_sel.start) + elif this_sel.stop != None: + return type(this_sel.stop) + elif this_sel.step != None: + return type(this_sel.step) + else: + raise TypeError("slice is all None?") + else: + return type(this_sel) + # Flexibly subset from an xarray Dataset or DataArray. Selections can be individual values or slices. def xr_flexsel(xr_object, time=None, vegtype=None): # SSR TODO: Optimize by starting selections with dimension that will result in the largest reduction of object size. Is there a way to do this without repeating a bunch of code, that DOESN'T involve writing another function (and therefore making another in-memory copy of the object)? Although I guess that's not an issue for Datasets that haven't yet been loaded into memory. But then, in that case, this optimization is unnecessary! From 11c232fc2b3ade2d6f136e3b4614aa6314e6a5cf Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Mon, 1 Nov 2021 10:25:56 -0600 Subject: [PATCH 39/45] import_ds() now ensures filelist is sorted. As suggested by @andersy005 in #32 (https://github.com/NCAR/ctsm_python_gallery/issues/32#issuecomment-623804687_). --- ctsm_py/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index 9916666..d91ad6f 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -465,7 +465,7 @@ def import_ds(filelist, myVars=None, myVegtypes=None): # Import if isinstance(filelist, list): - this_ds = xr.open_mfdataset(filelist, \ + this_ds = xr.open_mfdataset(sorted(filelist), \ data_vars="minimal", preprocess=mfdataset_preproc_closure) elif isinstance(filelist, str): From d28885c8e509032a9abac7db1f8271b7f37f5c20 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Mon, 1 Nov 2021 10:48:29 -0600 Subject: [PATCH 40/45] Added function patch2pft() to restore original "patch" dim/var names. --- ctsm_py/utils.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index d91ad6f..425ce91 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -390,7 +390,8 @@ def get_vegtype_str_da(vegtype_str): return vegtype_str_da -# Set up function to drop unwanted vars in preprocessing of open_mfdataset(), making sure to include any unspecified variables that will be useful in gridding. +# Function to drop unwanted variables in preprocessing of open_mfdataset(), making sure to include any unspecified variables that will be useful in gridding. +# Also renames "pft" dimension (and all like-named variables, e.g., pft1d_itype_veg_str) to be named like "patch". This can later be reversed, for compatibility with other code, using patch2pft(). def mfdataset_preproc(ds, vars_to_import, vegtypes_to_import): if vars_to_import != None: @@ -448,6 +449,29 @@ def mfdataset_preproc(ds, vars_to_import, vegtypes_to_import): ds = xr.decode_cf(ds, decode_times = True) return ds +# Rename "patch" dimension and any associated variables back to "pft". Uses a dictionary with the names of the dimensions and variables we want to rename. This allows us to do it all at once, which may be more efficient than one-by-one. +def patch2pft(xr_object): + + # Rename "patch" dimension + patch2pft_dict = {} + for thisDim in xr_object.dims: + if thisDim == "patch": + patch2pft_dict["patch"] = "pft" + break + + # Rename variables containing "patch" + if isinstance(xr_object, xr.Dataset): + pattern = re.compile("patch.*1d") + matches = [x for x in list(xr_object.keys()) if pattern.search(x) != None] + if len(matches) > 0: + for m in matches: + patch2pft_dict[m] = m.replace("patches","patchs").replace("patch","pft") + + # Do the rename + if len(patch2pft_dict) > 0: + xr_object = xr_object.rename(patch2pft_dict) + + return xr_object # Import a dataset that's spread over multiple files, only including specified variables. Concatenate by time. def import_ds(filelist, myVars=None, myVegtypes=None): From 2e8a967f777d5c7c63bedf579c831693eb3166c5 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Mon, 1 Nov 2021 10:48:47 -0600 Subject: [PATCH 41/45] Correction to call of trim_da_to_mgd_crop(). --- 2d_crop_work.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/2d_crop_work.py b/2d_crop_work.py index 163a4a4..33fd77a 100644 --- a/2d_crop_work.py +++ b/2d_crop_work.py @@ -55,7 +55,7 @@ thisVar = "CPHASE" thisvar_da = utils.get_thisVar_da(thisVar, this_ds) -thisvar_da = utils.trim_to_mgd_crop(thisvar_da, this_ds.patches1d_itype_veg_str) +thisvar_da = utils.trim_da_to_mgd_crop(thisvar_da, this_ds.patches1d_itype_veg_str) # %% Grid and make map, more efficiently, as function From 713c758ae00276142b9debea934ea93af2232b18 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Mon, 1 Nov 2021 11:11:20 -0600 Subject: [PATCH 42/45] Commenting improvements. --- ctsm_py/utils.py | 68 +++++++++++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 24 deletions(-) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index 425ce91..7f51de3 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -263,27 +263,43 @@ def define_pftlist(): return pftlist -# Is this PFT a managed crop? +# Does this vegetation type's name match (for a given comparison method) any member of a filtering list? # SSR TODO: Require that input be a single string. -def is_this_vegtype(this_pft, this_list, method): - if method == "ok_contains": - return any(n in this_pft for n in this_list) - elif method == "notok_contains": - return not any(n in this_pft for n in this_list) - elif method == "ok_exact": - return any(n == this_pft for n in this_list) - elif method == "notok_exact": - return not any(n == this_pft for n in this_list) +''' +Methods: + ok_contains: True if any member of this_filter is found in this_vegtype. + notok_contains: True of no member of this_filter is found in this_vegtype. + ok_exact: True if this_vegtype matches any member of this_filter + exactly. + notok_exact: True if this_vegtype does not match any member of + this_filter exactly. +''' +def is_this_vegtype(this_vegtype, this_filter, this_method): + if this_method == "ok_contains": + return any(n in this_vegtype for n in this_filter) + elif this_method == "notok_contains": + return not any(n in this_vegtype for n in this_filter) + elif this_method == "ok_exact": + return any(n == this_vegtype for n in this_filter) + elif this_method == "notok_exact": + return not any(n == this_vegtype for n in this_filter) else: - raise ValueError(f"Unknown method: '{method}'") + raise ValueError(f"Unknown method: '{this_method}'") -# Get boolean list of whether each PFT in list is a managed crop -def is_each_vegtype(this_pftlist, this_filter, this_method): - return [is_this_vegtype(x, this_filter, this_method) for x in this_pftlist] +# Get boolean list of whether each vegetation type in list is a managed crop +''' + this_vegtypelist: The list of vegetation types whose members you want to + test. + this_filter: The list of strings against which you want to compare + each member of this_vegtypelist. + this_method: How you want to do the comparison. See is_this_vegtype(). +''' +def is_each_vegtype(this_vegtypelist, this_filter, this_method): + return [is_this_vegtype(x, this_filter, this_method) for x in this_vegtypelist] -# List of managed crops in CLM +# List (strings) of managed crops in CLM. def define_mgdcrop_list(): notcrop_list = ["tree", "grass", "shrub", "unmanaged", "not_vegetated"] defined_pftlist = define_pftlist() @@ -291,7 +307,7 @@ def define_mgdcrop_list(): return [defined_pftlist[i] for i, x in enumerate(is_crop) if x] -# Convert list of vegtypes to integer index equivalents +# Convert list of vegtype strings to integer index equivalents. def vegtype_str2int(vegtype_str, vegtype_mainlist=None): if isinstance(vegtype_mainlist, xr.Dataset): vegtype_mainlist = vegtype_mainlist.vegtype_str.values @@ -325,9 +341,10 @@ def check_sel_type(this_sel): else: return type(this_sel) -# Flexibly subset from an xarray Dataset or DataArray. Selections can be individual values or slices. + +# Flexibly subset time(s) and/or vegetation type(s) from an xarray Dataset or DataArray. Selections can be individual values or slice()s. def xr_flexsel(xr_object, time=None, vegtype=None): - # SSR TODO: Optimize by starting selections with dimension that will result in the largest reduction of object size. Is there a way to do this without repeating a bunch of code, that DOESN'T involve writing another function (and therefore making another in-memory copy of the object)? Although I guess that's not an issue for Datasets that haven't yet been loaded into memory. But then, in that case, this optimization is unnecessary! + # SSR TODO: Optimize by starting selections with dimension that will result in the largest reduction of object size. Is there a way to do this without repeating a bunch of code, that DOESN'T involve writing another function (and therefore making another in-memory copy of the object)? if time != None: time_type = check_sel_type(time) @@ -366,7 +383,7 @@ def xr_flexsel(xr_object, time=None, vegtype=None): return xr_object -# Get PFT of each patch, in both integer and string forms +# Get PFT of each patch, in both integer and string forms. def ivt_int_str(this_ds, this_pftlist): # First, get all the integer values; should be time*pft or pft*time. We will eventually just take the first timestep. vegtype_int = this_ds.patches1d_itype_veg @@ -379,6 +396,7 @@ def ivt_int_str(this_ds, this_pftlist): return {"int": vegtype_int, "str": vegtype_str, "all_str": this_pftlist} +# Convert a list of strings with vegetation type names into a DataArray. Used to add vegetation type info in import_ds(). def get_vegtype_str_da(vegtype_str): nvt = len(vegtype_str) thisName = "vegtype_str" @@ -390,7 +408,7 @@ def get_vegtype_str_da(vegtype_str): return vegtype_str_da -# Function to drop unwanted variables in preprocessing of open_mfdataset(), making sure to include any unspecified variables that will be useful in gridding. +# Function to drop unwanted variables in preprocessing of open_mfdataset(), making sure to NOT drop any unspecified variables that will be useful in gridding. Also adds vegetation type info in the form of a DataArray of strings. # Also renames "pft" dimension (and all like-named variables, e.g., pft1d_itype_veg_str) to be named like "patch". This can later be reversed, for compatibility with other code, using patch2pft(). def mfdataset_preproc(ds, vars_to_import, vegtypes_to_import): @@ -449,6 +467,7 @@ def mfdataset_preproc(ds, vars_to_import, vegtypes_to_import): ds = xr.decode_cf(ds, decode_times = True) return ds + # Rename "patch" dimension and any associated variables back to "pft". Uses a dictionary with the names of the dimensions and variables we want to rename. This allows us to do it all at once, which may be more efficient than one-by-one. def patch2pft(xr_object): @@ -473,7 +492,8 @@ def patch2pft(xr_object): return xr_object -# Import a dataset that's spread over multiple files, only including specified variables. Concatenate by time. + +# Import a dataset that can be spread over multiple files, only including specified variables and/or vegetation types, concatenating by time. DOES actually read the dataset into memory, but only AFTER dropping unwanted variables and/or vegetation types. def import_ds(filelist, myVars=None, myVegtypes=None): # Convert myVegtypes here, if needed, to avoid repeating the process each time you read a file in xr.open_mfdataset(). @@ -483,7 +503,7 @@ def import_ds(filelist, myVars=None, myVegtypes=None): if isinstance(myVegtypes[0], str): myVegtypes = vegtype_str2int(myVegtypes) - # "preprocess" argument requires a function that only takes one variable (an xarray.Dataset object). Wrapping mfdataset_preproc() in this lambda function allows this. Could also just allow mfdataset_preproc() to access the myVars directly, but that's bad practice as it could lead to scoping issues. + # The xarray open_mfdataset() "preprocess" argument requires a function that takes exactly one variable (an xarray.Dataset object). Wrapping mfdataset_preproc() in this lambda function allows this. Could also just allow mfdataset_preproc() to access myVars and myVegtypes directly, but that's bad practice as it could lead to scoping issues. mfdataset_preproc_closure = \ lambda ds: mfdataset_preproc(ds, myVars, myVegtypes) @@ -500,7 +520,7 @@ def import_ds(filelist, myVars=None, myVegtypes=None): return this_ds -# Return a DataArray, with defined coordinates, for a given variable in a dataset +# Return a DataArray, with defined coordinates, for a given variable in a dataset. def get_thisVar_da(thisVar, this_ds): # Make DataArray for this variable @@ -542,7 +562,7 @@ def trim_da_to_mgd_crop(thisvar_da, patches1d_itype_veg_str): return thisvar_da.isel(patch = [i for i, x in enumerate(is_crop) if x]) -# Make a geographically gridded DataArray (with PFT dimension) of one variable within a DataSet. Optionally subset by time index (integer) or slice. +# Make a geographically gridded DataArray (with dimensions time, vegetation type [as string], lat, lon) of one variable within a Dataset. Optionally subset by time index (integer) or slice(). def grid_one_variable(this_ds, thisVar, time=None): thisvar_da = get_thisVar_da(thisVar, this_ds) From 453d0d0e713651f5d66d38c1a8e25fd8d346705f Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Mon, 1 Nov 2021 11:21:33 -0600 Subject: [PATCH 43/45] is_this_vegtype() now checks data type of this_vegtype. --- ctsm_py/utils.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/ctsm_py/utils.py b/ctsm_py/utils.py index 7f51de3..eca194a 100644 --- a/ctsm_py/utils.py +++ b/ctsm_py/utils.py @@ -264,7 +264,6 @@ def define_pftlist(): # Does this vegetation type's name match (for a given comparison method) any member of a filtering list? -# SSR TODO: Require that input be a single string. ''' Methods: ok_contains: True if any member of this_filter is found in this_vegtype. @@ -274,7 +273,29 @@ def define_pftlist(): notok_exact: True if this_vegtype does not match any member of this_filter exactly. ''' -def is_this_vegtype(this_vegtype, this_filter, this_method): +def is_this_vegtype(this_vegtype, this_filter, this_method): + + # Make sure data type of this_vegtype is acceptable + data_type_ok = lambda x: isinstance(x, str) or isinstance(x, int) or isinstance(x, np.int64) + ok_input = True + if not data_type_ok(this_vegtype): + if isinstance(this_vegtype, list): + if len(this_vegtype) == 1 and data_type_ok(this_vegtype[0]): + this_vegtype = this_vegtype[0] + elif data_type_ok(this_vegtype[0]): + raise TypeError("is_this_vegtype(): this_vegtype must be a single string or integer, not a list of them. Did you mean to call is_each_vegtype() instead?") + else: + ok_input = False + else: + ok_input = False + if not ok_input: + raise TypeError(f"is_this_vegtype(): First argument (this_vegtype) must be a string or integer, not {type(this_vegtype)}") + + # Make sure data type of this_filter is acceptable + if not np.iterable(this_filter): + raise TypeError(f"is_this_vegtype(): Second argument (this_filter) must be iterable (e.g., a list), not {type(this_filter)}") + + # Perform the comparison if this_method == "ok_contains": return any(n in this_vegtype for n in this_filter) elif this_method == "notok_contains": @@ -284,7 +305,7 @@ def is_this_vegtype(this_vegtype, this_filter, this_method): elif this_method == "notok_exact": return not any(n == this_vegtype for n in this_filter) else: - raise ValueError(f"Unknown method: '{this_method}'") + raise ValueError(f"Unknown comparison method: '{this_method}'") # Get boolean list of whether each vegetation type in list is a managed crop From 79b27d713a3d12c533505a74c24d13cf70718b3b Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Mon, 1 Nov 2021 12:03:38 -0600 Subject: [PATCH 44/45] Added SamRabin_examples notebook. --- notebooks/SamRabin_examples.ipynb | 2312 +++++++++++++++++++++++++++++ 1 file changed, 2312 insertions(+) create mode 100644 notebooks/SamRabin_examples.ipynb diff --git a/notebooks/SamRabin_examples.ipynb b/notebooks/SamRabin_examples.ipynb new file mode 100644 index 0000000..18436d3 --- /dev/null +++ b/notebooks/SamRabin_examples.ipynb @@ -0,0 +1,2312 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Sam Rabin's utility example notebook for 1-d files\n", + "\n", + "Contains code to show example uses of the functions I add to utils.py which are designed to work with 1-dimensional (i.e., not lat-lon gridded) CTSM output data.\n", + "\n", + "Questions or comments? Email me: sam dot rabin at gmail dot com." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Define useful variables\n", + "\n", + "You will need to customize these to work with your system and data." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Your path to ctsm_py directory (i.e., where utils.py lives)\n", + "my_ctsm_python_gallery = \"/Users/sam/Documents/git_repos/ctsm_python_gallery_myfork/ctsm_py/\"\n", + "\n", + "# Directory where input file(s) can be found\n", + "indir = \"/Volumes/Reacher/CESM_runs/f10_f10_mg37/\"\n", + "\n", + "# Either the name of a file within $indir, or a pattern that will return a list of files.\n", + "pattern = \"*h1.*-01-01-00000.nc\"\n", + "\n", + "# List of variables to import from file(s) in $indir matching $pattern. Additional variables will be imported as necessary if they will be useful in gridding any of these. So, e.g., since CPHASE \n", + "myVars = [\"CPHASE\", \\\n", + " \"GDDHARV\", \n", + " \"GDDPLANT\", \n", + " \"GPP\", \n", + " \"GRAINC_TO_FOOD\", \n", + " \"NPP\", \n", + " \"TLAI\", \n", + " \"TOTVEGC\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Import CTSM utils module" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "sys.path.append(my_ctsm_python_gallery)\n", + "import utils" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Import a dataset.\n", + "\n", + "- Set `myVars=None` to import all variables.\n", + "- Currently, the `myVegtypes` argument will import only patches with vegetation types that are managed crops. Set `myVegTypes=None` to import all patches. You can also set `myVegTypes=some_list` to import only patches with any of some arbitrary list of vegetation types." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.Dataset>\n",
+       "Dimensions:                  (patch: 1376, levgrnd: 25, levsoi: 20, levlak: 10, levdcmp: 25, time: 762, lon: 24, lat: 19, ivt: 79)\n",
+       "Coordinates:\n",
+       "  * patch                    (patch) int64 547 548 549 550 ... 4716 4733 4734\n",
+       "  * levgrnd                  (levgrnd) float32 0.01 0.04 0.09 ... 28.87 42.0\n",
+       "  * levsoi                   (levsoi) float32 0.01 0.04 0.09 ... 5.95 6.94 8.03\n",
+       "  * levlak                   (levlak) float32 0.05 0.6 2.1 ... 25.6 34.33 44.78\n",
+       "  * levdcmp                  (levdcmp) float32 0.01 0.04 0.09 ... 28.87 42.0\n",
+       "  * time                     (time) object 2000-01-01 00:00:00 ... 2002-02-01...\n",
+       "  * lon                      (lon) float32 0.0 15.0 30.0 ... 315.0 330.0 345.0\n",
+       "  * lat                      (lat) float32 -90.0 -80.0 -70.0 ... 70.0 80.0 90.0\n",
+       "  * ivt                      (ivt) int64 0 1 2 3 4 5 6 ... 72 73 74 75 76 77 78\n",
+       "Data variables: (12/24)\n",
+       "    patches1d_lon            (patch) float64 dask.array<chunksize=(1376,), meta=np.ndarray>\n",
+       "    patches1d_lat            (patch) float64 dask.array<chunksize=(1376,), meta=np.ndarray>\n",
+       "    patches1d_ixy            (patch) float64 dask.array<chunksize=(1376,), meta=np.ndarray>\n",
+       "    patches1d_jxy            (patch) float64 dask.array<chunksize=(1376,), meta=np.ndarray>\n",
+       "    patches1d_gi             (patch) float64 dask.array<chunksize=(1376,), meta=np.ndarray>\n",
+       "    patches1d_li             (patch) float64 dask.array<chunksize=(1376,), meta=np.ndarray>\n",
+       "    ...                       ...\n",
+       "    GRAINC_TO_FOOD           (time, patch) float32 dask.array<chunksize=(365, 1376), meta=np.ndarray>\n",
+       "    NPP                      (time, patch) float32 dask.array<chunksize=(365, 1376), meta=np.ndarray>\n",
+       "    TLAI                     (time, patch) float32 dask.array<chunksize=(365, 1376), meta=np.ndarray>\n",
+       "    TOTVEGC                  (time, patch) float32 dask.array<chunksize=(365, 1376), meta=np.ndarray>\n",
+       "    vegtype_str              (ivt) <U35 'not_vegetated' ... 'irrigated_tropic...\n",
+       "    patches1d_itype_veg_str  (patch) <U35 'temperate_corn' ... 'rice'\n",
+       "Attributes: (12/99)\n",
+       "    title:                                CLM History file information\n",
+       "    comment:                              NOTE: None of the variables are wei...\n",
+       "    Conventions:                          CF-1.0\n",
+       "    history:                              created on 10/19/21 16:32:21\n",
+       "    source:                               Community Terrestrial Systems Model\n",
+       "    hostname:                             cheyenne\n",
+       "    ...                                   ...\n",
+       "    cft_irrigated_switchgrass:            60\n",
+       "    cft_tropical_corn:                    61\n",
+       "    cft_irrigated_tropical_corn:          62\n",
+       "    cft_tropical_soybean:                 63\n",
+       "    cft_irrigated_tropical_soybean:       64\n",
+       "    time_period_freq:                     day_1
" + ], + "text/plain": [ + "\n", + "Dimensions: (patch: 1376, levgrnd: 25, levsoi: 20, levlak: 10, levdcmp: 25, time: 762, lon: 24, lat: 19, ivt: 79)\n", + "Coordinates:\n", + " * patch (patch) int64 547 548 549 550 ... 4716 4733 4734\n", + " * levgrnd (levgrnd) float32 0.01 0.04 0.09 ... 28.87 42.0\n", + " * levsoi (levsoi) float32 0.01 0.04 0.09 ... 5.95 6.94 8.03\n", + " * levlak (levlak) float32 0.05 0.6 2.1 ... 25.6 34.33 44.78\n", + " * levdcmp (levdcmp) float32 0.01 0.04 0.09 ... 28.87 42.0\n", + " * time (time) object 2000-01-01 00:00:00 ... 2002-02-01...\n", + " * lon (lon) float32 0.0 15.0 30.0 ... 315.0 330.0 345.0\n", + " * lat (lat) float32 -90.0 -80.0 -70.0 ... 70.0 80.0 90.0\n", + " * ivt (ivt) int64 0 1 2 3 4 5 6 ... 72 73 74 75 76 77 78\n", + "Data variables: (12/24)\n", + " patches1d_lon (patch) float64 dask.array\n", + " patches1d_lat (patch) float64 dask.array\n", + " patches1d_ixy (patch) float64 dask.array\n", + " patches1d_jxy (patch) float64 dask.array\n", + " patches1d_gi (patch) float64 dask.array\n", + " patches1d_li (patch) float64 dask.array\n", + " ... ...\n", + " GRAINC_TO_FOOD (time, patch) float32 dask.array\n", + " NPP (time, patch) float32 dask.array\n", + " TLAI (time, patch) float32 dask.array\n", + " TOTVEGC (time, patch) float32 dask.array\n", + " vegtype_str (ivt) \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.DataArray (time: 762, patch: 1376)>\n",
+       "array([[2., 2., 3., ..., 4., 4., 4.],\n",
+       "       [2., 2., 3., ..., 4., 4., 4.],\n",
+       "       [2., 2., 3., ..., 4., 4., 4.],\n",
+       "       ...,\n",
+       "       [2., 2., 3., ..., 4., 4., 4.],\n",
+       "       [2., 2., 3., ..., 4., 4., 4.],\n",
+       "       [2., 2., 3., ..., 4., 4., 4.]], dtype=float32)\n",
+       "Coordinates:\n",
+       "  * time     (time) object 2000-01-01 00:00:00 ... 2002-02-01 00:00:00\n",
+       "  * patch    (patch) int64 547 548 549 550 573 574 ... 4625 4715 4716 4733 4734
" + ], + "text/plain": [ + "\n", + "array([[2., 2., 3., ..., 4., 4., 4.],\n", + " [2., 2., 3., ..., 4., 4., 4.],\n", + " [2., 2., 3., ..., 4., 4., 4.],\n", + " ...,\n", + " [2., 2., 3., ..., 4., 4., 4.],\n", + " [2., 2., 3., ..., 4., 4., 4.],\n", + " [2., 2., 3., ..., 4., 4., 4.]], dtype=float32)\n", + "Coordinates:\n", + " * time (time) object 2000-01-01 00:00:00 ... 2002-02-01 00:00:00\n", + " * patch (patch) int64 547 548 549 550 573 574 ... 4625 4715 4716 4733 4734" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Which variable? CPHASE = crop development phase.\n", + "thisVar = \"CPHASE\"\n", + "\n", + "thisvar_da = utils.get_thisVar_da(thisVar, this_ds)\n", + "thisvar_da" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Make a map of one timestep of some variable\n", + "\n", + "- Can also specify `thisTime` as an integer (index on `time` dimension).\n", + "- (I want to rework this to use `xarray`'s built-in plotting functions.)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/sam/Applications/anaconda3/lib/python3.7/site-packages/cartopy/mpl/geoaxes.py:1665: MatplotlibDeprecationWarning: shading='flat' when X and Y have the same dimensions as C is deprecated since 3.3. Either specify the corners of the quadrilaterals with X and Y, or pass shading='auto', 'nearest' or 'gouraud', or set rcParams['pcolor.shading']. This will become an error two minor releases later.\n", + " result = matplotlib.axes.Axes.pcolor(self, *args, **kwargs)\n", + "/Users/sam/Applications/anaconda3/lib/python3.7/site-packages/cartopy/mpl/geoaxes.py:388: MatplotlibDeprecationWarning: \n", + "The 'inframe' parameter of draw() was deprecated in Matplotlib 3.3 and will be removed two minor releases later. Use Axes.redraw_in_frame() instead. If any parameter follows 'inframe', they should be passed as keyword, not positionally.\n", + " inframe=inframe)\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# Which variable, which vegetation type, and when? CPHASE = crop development phase.\n", + "thisVar = \"CPHASE\"\n", + "thisVegtype = \"temperate_corn\"\n", + "thisTime = \"2000-07-01\"\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import cartopy.crs as ccrs\n", + "\n", + "# Extract and grid the variable\n", + "thisVar_da_gridded = utils.grid_one_variable(this_ds, thisVar, time=thisTime)\n", + "\n", + "# Make map\n", + "thisVar_da_gridded = thisVar_da_gridded.sel(ivt_str=thisVegtype)\n", + "if thisVar_da_gridded.shape[0] == 1:\n", + " thisVar_da_gridded = thisVar_da_gridded.squeeze()\n", + "else:\n", + " raise ValueError(\"You must select one time step to plot\")\n", + "thisVar_da_gridded = utils.cyclic_dataarray(thisVar_da_gridded)\n", + "ax = plt.axes(projection=ccrs.PlateCarree())\n", + "plt.pcolor(thisVar_da_gridded.lon.values, thisVar_da_gridded.lat.values, thisVar_da_gridded, transform=ccrs.PlateCarree())\n", + "ax.coastlines()\n", + "plt.show()" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "e8083de178eb7a8a37debdd6606e8115abc0bcba8804cd799c64479bb9dd6f05" + }, + "kernelspec": { + "display_name": "Python 3.7.9 64-bit ('base': conda)", + "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.7.9" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 0be1e0111d9dc7edcf2d36e128be8e2924b4ad94 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Mon, 1 Nov 2021 12:07:34 -0600 Subject: [PATCH 45/45] Moved dev scripts into ignore/ directory. --- .gitignore | 3 + 1d_crop_work.ipynb | 251 ---------------------- 1d_crop_work.py | 251 ---------------------- 2d_crop_work.py | 86 -------- clm_yield_conv.ipynb | 501 ------------------------------------------- 5 files changed, 3 insertions(+), 1089 deletions(-) delete mode 100644 1d_crop_work.ipynb delete mode 100644 1d_crop_work.py delete mode 100644 2d_crop_work.py delete mode 100644 clm_yield_conv.ipynb diff --git a/.gitignore b/.gitignore index 505cefb..507c4c8 100644 --- a/.gitignore +++ b/.gitignore @@ -106,3 +106,6 @@ venv.bak/ # mypy .mypy_cache/ + +# ignore directory +ignore/ diff --git a/1d_crop_work.ipynb b/1d_crop_work.ipynb deleted file mode 100644 index 73b653f..0000000 --- a/1d_crop_work.ipynb +++ /dev/null @@ -1,251 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import xarray as xr\n", - "from ctsm_py import utils\n", - "import matplotlib.pyplot as plt\n", - "import warnings\n", - "import glob" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "pftname = [\"needleleaf_evergreen_temperate_tree\",\n", - " \"needleleaf_evergreen_boreal_tree\",\n", - " \"needleleaf_deciduous_boreal_tree\",\n", - " \"broadleaf_evergreen_tropical_tree\",\n", - " \"broadleaf_evergreen_temperate_tree\",\n", - " \"broadleaf_deciduous_tropical_tree\",\n", - " \"broadleaf_deciduous_temperate_tree\",\n", - " \"broadleaf_deciduous_boreal_tree\",\n", - " \"broadleaf_evergreen_shrub\",\n", - " \"broadleaf_deciduous_temperate_shrub\",\n", - " \"broadleaf_deciduous_boreal_shrub\",\n", - " \"c3_arctic_grass\",\n", - " \"c3_non-arctic_grass\",\n", - " \"c4_grass\",\n", - " \"unmanaged_c3_crop\",\n", - " \"unmanaged_c3_irrigated\",\n", - " \"temperate_corn\",\n", - " \"irrigated_temperate_corn\",\n", - " \"spring_wheat\",\n", - " \"irrigated_spring_wheat\",\n", - " \"winter_wheat\",\n", - " \"irrigated_winter_wheat\",\n", - " \"soybean\",\n", - " \"irrigated_soybean\",\n", - " \"barley\",\n", - " \"irrigated_barley\",\n", - " \"winter_barley\",\n", - " \"irrigated_winter_barley\",\n", - " \"rye\",\n", - " \"irrigated_rye\",\n", - " \"winter_rye\",\n", - " \"irrigated_winter_rye\",\n", - " \"cassava\",\n", - " \"irrigated_cassava\",\n", - " \"citrus\",\n", - " \"irrigated_citrus\",\n", - " \"cocoa\",\n", - " \"irrigated_cocoa\",\n", - " \"coffee\",\n", - " \"irrigated_coffee\",\n", - " \"cotton\",\n", - " \"irrigated_cotton\",\n", - " \"datepalm\",\n", - " \"irrigated_datepalm\",\n", - " \"foddergrass\",\n", - " \"irrigated_foddergrass\",\n", - " \"grapes\",\n", - " \"irrigated_grapes\",\n", - " \"groundnuts\",\n", - " \"irrigated_groundnuts\",\n", - " \"millet\",\n", - " \"irrigated_millet\",\n", - " \"oilpalm\",\n", - " \"irrigated_oilpalm\",\n", - " \"potatoes\",\n", - " \"irrigated_potatoes\",\n", - " \"pulses\",\n", - " \"irrigated_pulses\",\n", - " \"rapeseed\",\n", - " \"irrigated_rapeseed\",\n", - " \"rice\",\n", - " \"irrigated_rice\",\n", - " \"sorghum\",\n", - " \"irrigated_sorghum\",\n", - " \"sugarbeet\",\n", - " \"irrigated_sugarbeet\",\n", - " \"sugarcane\",\n", - " \"irrigated_sugarcane\",\n", - " \"sunflower\",\n", - " \"irrigated_sunflower\",\n", - " \"miscanthus\",\n", - " \"irrigated_miscanthus\",\n", - " \"switchgrass\",\n", - " \"irrigated_switchgrass\",\n", - " \"tropical_corn\",\n", - " \"irrigated_tropical_corn\",\n", - " \"tropical_soybean\",\n", - " \"irrigated_tropical_soybean\"]" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "# Get list of all files in $indir matching $pattern\n", - "indir = \"/Volumes/Reacher/CESM_runs/numa_20211014/\"\n", - "pattern = \"*h1.*-01-01-00000.nc\"\n", - "filelist = glob.glob(indir + pattern)\n", - "\n", - "# Set up function to drop unwanted vars in preprocessing of open_mfdataset()\n", - "def mfdataset_preproc(ds):\n", - " vars_to_import = list(ds.dims) + \\\n", - " [\"CPHASE\", \n", - " \"GDDHARV\", \n", - " \"GDDPLANT\", \n", - " \"GPP\", \n", - " \"GRAINC_TO_FOOD\", \n", - " \"NPP\", \n", - " \"TLAI\", \n", - " \"TOTVEGC\", \n", - " \"pfts1d_itype_veg\"]\n", - " varlist = list(ds.variables)\n", - " vars_to_drop = list(np.setdiff1d(varlist, vars_to_import))\n", - " ds = ds.drop_vars(vars_to_drop)\n", - " ds = xr.decode_cf(ds, decode_times = True)\n", - " return ds\n", - "\n", - "# Import\n", - "this_ds = xr.open_mfdataset(filelist, \\\n", - " concat_dim=\"time\", \n", - " preprocess=mfdataset_preproc)\n", - "# this_ds = utils.time_set_mid(this_ds, 'time')\n", - "\n", - "# Get dates in a format that matplotlib can use\n", - "with warnings.catch_warnings():\n", - " # Ignore this warning in this with-block\n", - " warnings.filterwarnings(\"ignore\", message=\"Converting a CFTimeIndex with dates from a non-standard calendar, 'noleap', to a pandas.DatetimeIndex, which uses dates from the standard calendar. This may lead to subtle errors in operations that depend on the length of time between dates.\")\n", - " datetime_vals = this_ds.indexes[\"time\"].to_datetimeindex()\n", - "\n", - "# Get PFT list, integers (use only first timestep)\n", - "vegtype_int = this_ds.pfts1d_itype_veg\n", - "vegtype_int.values = vegtype_int.values.astype(int)\n", - "if not all((vegtype_int.values == vegtype_int.values[0,:]).all(axis=1)):\n", - " raise ValueError(\"Some veg type changes over time\")\n", - "vegtype_int = vegtype_int[0,:]\n", - "\n", - "# Get PFT list, strings\n", - "vegtype_str = list(np.array(pftname)[vegtype_int.values])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Read variable and trim to crops" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAaQAAAEICAYAAAAQkoCgAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8rg+JYAAAACXBIWXMAAAsTAAALEwEAmpwYAABvG0lEQVR4nO2dd5wdVfXAv2de2Zrd9AJJSAIhgRRCSEKHYAQC0lQQBJEi0gTBgiD8VCw/RMUCShGl/qQjTQVBSoDQApFOAkkgQEL6ZjdbX5m5vz+mvHl13yZb5r29388n2Wl39t6dmXvuOffcc0QphUaj0Wg0fY3R1xXQaDQajQa0QNJoNBpNQNACSaPRaDSBQAskjUaj0QQCLZA0Go1GEwi0QNJoNBpNINACSaPRaDSBQAskjaZEEJGVIrJORGp8x84QkQXOthKRVhFpEZHVIvI7EQn5yrY759aJyC0iUttHTdFocqIFkkZTWoSBCwqc300pVQvMA04Evuk7d6RzbiYwG/ifHqulRrMVaIGk0ZQWvwG+LyIDC12klFoKPA9MzXFuNfBYrnMaTV+iBZJGU1q8BiwAvl/oIhHZFdgfeD3HuTHA4bnOaTR9SbivK6DRaLrMj4EXROTqHOf+KyIm0AD8FbjFd+4hEUkCTcC/gCt6vKYaTRfQAkmjKTGUUu+IyD+BS4AlGadnKqWW5yl6jFLqyZ6tnUaz9WiTnUZTmvwE22Fh+76uiEbTXWiBpNGUII4WdA/w7b6ui0bTXWiBpNGULj8Dajq9SqMpEUQn6NNoNBpNENAakkaj0WgCgRZIGo1GowkEWiBpNBqNJhBogaTRaDSaQKAXxm4lQ4cOVePGjevramg0Gk1JsXjx4o1KqWG5zmmBtJWMGzeO1157ra+rodFoNCWFiHyc75w22Wk0Go0mEGiBpNFoNJpAoAWSRqPRaAKBnkPSaMqcRCLBqlWr6Ojo6OuqaPoRlZWVjB49mkgkUnQZLZA0mjJn1apVDBgwgHHjxiEifV0dTT9AKcWmTZtYtWoV48ePL7qcNtlpNGVOR0cHQ4YM0cJI02uICEOGDOmyVq4FkkbTD9DCSNPbbM07pwWSpmxoTbRy7/v3kjATfV0VjUazFWiBpCkb/rD4D/z85Z/z2jq9YDlo7LPPPj1ybSa33norn332WZfKrFy5kqlTp+Y8fuedd251XXqTK664oq+r0C1ogaQpG9a0rgGgI6m9yYLGiy++mHXMNM2c+7muLZatEUj5CJJAyvxbZdKbAimZTPbYvbVA0pQNCjvZpJ4vCR61tbUALFiwgIMOOogTTzyRadOmZe37r7Usi3PPPZcpU6ZwxBFHcPjhh3P//fcD8LOf/YzZs2czdepUzjzzTJRS3H///bz22mucdNJJzJgxg/b2dhYvXsyBBx7IHnvswaGHHsqaNfagZfHixey2227svffeXHvttTnrfMkll/D8888zY8YMfv/732OaJhdddBGzZ89m+vTp/PnPf/badOCBB/KVr3yFnXfemUsuuYQ77riDOXPmMG3aNFasWAHAqaeeytlnn83+++/PzjvvzD//+U+AgvfN/Nscc8wx7LHHHkyZMoUbb7zRq2d7ezszZszgpJNOAuBvf/sbc+bMYcaMGZx11lk5Bdqrr77KPvvsw2677cacOXNobm6mo6OD0047jWnTprH77rvzzDPPALagP+644zjyyCM55JBDuPXWW/nSl77E/PnzmThxIj/4wQ+2+t3wo92+NWWDpSwABC2Q8vHTf7zLe59t6dZ77rpdHT85ckrR1y9atIh33nmH8ePHs2DBgrR9Pw888AArV67k7bffZv369eyyyy6cfvrpAJx33nn8+Mc/BuDkk0/mn//8J8ceeyx/+tOfuOqqq5g1axaJRILzzz+fhx9+mGHDhnHPPfdw2WWXcfPNN3Paaafxxz/+kQMPPJCLLrooZz2vvPJKrrrqKk9w3HjjjdTX1/Pqq68Si8XYd999OeSQQwB48803WbJkCYMHD2bChAmcccYZLFq0iKuvvpo//vGP/OEPfwBsrevZZ59lxYoVHHTQQSxfvpzbb789730z/zY333wzgwcPpr29ndmzZ/PlL3+ZK6+8kj/96U+88cYbACxZsoR77rmHF154gUgkwrnnnssdd9zB17/+da9t8Xic448/nnvuuYfZs2ezZcsWqqqquPrqqwF4++23Wbp0KYcccggffPABAC+99BJvvfUWgwcP5tZbb+WNN97g9ddfp6KigkmTJnH++eczZsyYot+DXGiBpCkbtIZUGsyZMydN+GTuuyxcuJDjjjsOwzAYOXIkBx10kHfumWee4de//jVtbW00NDQwZcoUjjzyyLTy77//Pu+88w4HH3wwYGsio0aNoqmpicbGRg488EDAFmiP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", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "# Which variable?\n", - "thisVar = \"NPP\"\n", - "\n", - "# Make DataArray for this variable\n", - "thisvar_da = np.array(this_ds.variables[thisVar])\n", - "theseDims = this_ds.variables[thisVar].dims\n", - "thisvar_da = xr.DataArray(thisvar_da, \n", - " dims = theseDims)\n", - "\n", - "# Define coordinates of this variable's DataArray\n", - "dimsDict = dict()\n", - "for thisDim in theseDims:\n", - " if thisDim == \"time\":\n", - " dimsDict[thisDim] = this_ds.time\n", - " elif thisDim == \"pft\":\n", - " dimsDict[thisDim] = vegtype_str\n", - " else:\n", - " raise ValueError(\"Unknown dimension for coordinate assignment: \" + thisDim)\n", - "thisvar_da = thisvar_da.assign_coords(dimsDict)\n", - "\n", - "# Trim to managed crops\n", - "def is_this_mgd_crop(x):\n", - " notcrop_list = [\"tree\", \"grass\", \"shrub\", \"unmanaged\"]\n", - " return not any(n in x for n in notcrop_list)\n", - "is_crop = [ is_this_mgd_crop(x) for x in thisvar_da.pft.values ]\n", - "thisvar_da = thisvar_da[:, is_crop]\n", - "\n", - "# Plot\n", - "for p in np.arange(0,np.size(thisvar_da.pft.values)):\n", - " this_pft_char = thisvar_da.pft.values[p]\n", - " this_pft_char = this_pft_char.replace(\"_\", \" \")\n", - " plt.plot(datetime_vals, thisvar_da.values[:,p], label = this_pft_char)\n", - "plt.title(thisVar)\n", - "plt.ylabel(this_ds.variables[thisVar].attrs['units'])\n", - "plt.legend()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Get sowing and harvest date for each crop" - ] - } - ], - "metadata": { - "interpreter": { - "hash": "e8083de178eb7a8a37debdd6606e8115abc0bcba8804cd799c64479bb9dd6f05" - }, - "kernelspec": { - "display_name": "Python 3.7.9 64-bit ('base': conda)", - "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.7.9" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/1d_crop_work.py b/1d_crop_work.py deleted file mode 100644 index 85c5e7b..0000000 --- a/1d_crop_work.py +++ /dev/null @@ -1,251 +0,0 @@ - -# %% Setup - -import numpy as np -import xarray as xr -from ctsm_py import utils -import matplotlib.pyplot as plt -import warnings -import glob -import cftime - -pftname = ["needleleaf_evergreen_temperate_tree", - "needleleaf_evergreen_boreal_tree", - "needleleaf_deciduous_boreal_tree", - "broadleaf_evergreen_tropical_tree", - "broadleaf_evergreen_temperate_tree", - "broadleaf_deciduous_tropical_tree", - "broadleaf_deciduous_temperate_tree", - "broadleaf_deciduous_boreal_tree", - "broadleaf_evergreen_shrub", - "broadleaf_deciduous_temperate_shrub", - "broadleaf_deciduous_boreal_shrub", - "c3_arctic_grass", - "c3_non-arctic_grass", - "c4_grass", - "unmanaged_c3_crop", - "unmanaged_c3_irrigated", - "temperate_corn", - "irrigated_temperate_corn", - "spring_wheat", - "irrigated_spring_wheat", - "winter_wheat", - "irrigated_winter_wheat", - "soybean", - "irrigated_soybean", - "barley", - "irrigated_barley", - "winter_barley", - "irrigated_winter_barley", - "rye", - "irrigated_rye", - "winter_rye", - "irrigated_winter_rye", - "cassava", - "irrigated_cassava", - "citrus", - "irrigated_citrus", - "cocoa", - "irrigated_cocoa", - "coffee", - "irrigated_coffee", - "cotton", - "irrigated_cotton", - "datepalm", - "irrigated_datepalm", - "foddergrass", - "irrigated_foddergrass", - "grapes", - "irrigated_grapes", - "groundnuts", - "irrigated_groundnuts", - "millet", - "irrigated_millet", - "oilpalm", - "irrigated_oilpalm", - "potatoes", - "irrigated_potatoes", - "pulses", - "irrigated_pulses", - "rapeseed", - "irrigated_rapeseed", - "rice", - "irrigated_rice", - "sorghum", - "irrigated_sorghum", - "sugarbeet", - "irrigated_sugarbeet", - "sugarcane", - "irrigated_sugarcane", - "sunflower", - "irrigated_sunflower", - "miscanthus", - "irrigated_miscanthus", - "switchgrass", - "irrigated_switchgrass", - "tropical_corn", - "irrigated_tropical_corn", - "tropical_soybean", - "irrigated_tropical_soybean"] - -def is_this_mgd_crop(x): - notcrop_list = ["tree", "grass", "shrub", "unmanaged"] - return not any(n in x for n in notcrop_list) -def get_thisVar_da(thisVar, this_ds, vegtype_str): - # Make DataArray for this variable - thisvar_da = np.array(this_ds.variables[thisVar]) - theseDims = this_ds.variables[thisVar].dims - thisvar_da = xr.DataArray(thisvar_da, - dims = theseDims) - - # Define coordinates of this variable's DataArray - dimsDict = dict() - for thisDim in theseDims: - if thisDim == "time": - dimsDict[thisDim] = this_ds.time - elif thisDim == "pft": - dimsDict[thisDim] = vegtype_str - else: - raise ValueError("Unknown dimension for coordinate assignment: " + thisDim) - thisvar_da = thisvar_da.assign_coords(dimsDict) - - # Trim to managed crops - is_crop = [ is_this_mgd_crop(x) for x in thisvar_da.pft.values ] - thisvar_da = thisvar_da[:, is_crop] - - return thisvar_da - -# %% Import dataset - -# Get list of all files in $indir matching $pattern -# indir = "/Volumes/Reacher/CESM_runs/numa_20211014/" -indir = "/Volumes/Reacher/CESM_runs/numa_20211014_rx/" -pattern = "*h1.*-01-01-00000.nc" -filelist = glob.glob(indir + pattern) - -# Set up function to drop unwanted vars in preprocessing of open_mfdataset() -def mfdataset_preproc(ds): - vars_to_import = list(ds.dims) + \ - ["CPHASE", - "GDDHARV", - "GDDPLANT", - "GPP", - "GRAINC_TO_FOOD", - "NPP", - "TLAI", - "TOTVEGC", - "pfts1d_itype_veg"] - varlist = list(ds.variables) - vars_to_drop = list(np.setdiff1d(varlist, vars_to_import)) - ds = ds.drop_vars(vars_to_drop) - ds = xr.decode_cf(ds, decode_times = True) - return ds - -# Import -this_ds = xr.open_mfdataset(filelist, \ - concat_dim="time", - preprocess=mfdataset_preproc) -# this_ds = utils.time_set_mid(this_ds, 'time') - -# Get dates in a format that matplotlib can use -with warnings.catch_warnings(): - # Ignore this warning in this with-block - warnings.filterwarnings("ignore", message="Converting a CFTimeIndex with dates from a non-standard calendar, 'noleap', to a pandas.DatetimeIndex, which uses dates from the standard calendar. This may lead to subtle errors in operations that depend on the length of time between dates.") - datetime_vals = this_ds.indexes["time"].to_datetimeindex() - -# Get PFT list, integers (use only first timestep) -vegtype_int = this_ds.pfts1d_itype_veg -vegtype_int.values = vegtype_int.values.astype(int) -if not all((vegtype_int.values == vegtype_int.values[0,:]).all(axis=1)): - raise ValueError("Some veg type changes over time") -vegtype_int = vegtype_int[0,:] - -# Get PFT list, strings -vegtype_str = list(np.array(pftname)[vegtype_int.values]) - - -# %% Plot timeseries - -thisVar = "CPHASE" - -with get_thisVar_da(thisVar, this_ds, vegtype_str) as thisvar_da: - for p in np.arange(0,np.size(thisvar_da.pft.values)): - this_pft_char = thisvar_da.pft.values[p] - this_pft_char = this_pft_char.replace("_", " ") - plt.plot(datetime_vals, thisvar_da.values[:,p], label = this_pft_char) - plt.title(thisVar) - plt.ylabel(this_ds.variables[thisVar].attrs['units']) - plt.legend() - plt.show() - - -# %% Get simulated sowing and harvest dates - -# Get year and day number -def get_jday(cftime_datetime_object): - return cftime.datetime.timetuple(cftime_datetime_object).tm_yday -jday = np.array([get_jday(d) for d in this_ds.indexes["time"]]) -def get_year(cftime_datetime_object): - return cftime.datetime.timetuple(cftime_datetime_object).tm_year -year = np.array([get_year(d) for d in this_ds.indexes["time"]]) -year_jday = np.stack((year, jday), axis=1) - -# Find sowing and harvest dates in dataset -cphase_da = get_thisVar_da("CPHASE", this_ds, vegtype_str) -false_1xNpft = np.full((1,np.size(cphase_da.pft.values)), fill_value=False) -is_sdate = np.bitwise_and( \ - cphase_da.values[:-1,:]==4, \ - cphase_da.values[1:,:]<4) -is_sdate = np.concatenate((is_sdate, false_1xNpft)) -is_hdate = np.bitwise_and( \ - cphase_da.values[:-1,:]<4, \ - cphase_da.values[1:,:]==4) -is_hdate = np.concatenate((is_hdate, false_1xNpft)) - -# Define function for extracting an array of sowing or harvest dates (each row: year, DOY) for a given crop -def get_dates(thisCrop, vegtype_str, is_somedate, year_jday): - is_somedate_thiscrop = is_somedate[:,[d==thisCrop for d in vegtype_str]] - is_somedate_thiscrop = np.squeeze(is_somedate_thiscrop) - return year_jday[is_somedate_thiscrop,:] - -# Loop through crops and print their sowing and harvest dates -for thisCrop in cphase_da.pft.values: - - # Get dates - this_sdates = get_dates(thisCrop, cphase_da.pft.values, is_sdate, year_jday) - this_hdates = get_dates(thisCrop, cphase_da.pft.values, is_hdate, year_jday) - - # The first event in a dataset could be a harvest. If so, discard. - if this_sdates[0,1] > this_hdates[0,1]: - this_hdates = this_hdates[1:,:] - - # There should be at least as many sowings as harvests - nsow = np.shape(this_sdates)[0] - nhar = np.shape(this_hdates)[0] - if nsow < nhar: - raise ValueError("%d harvests but only %d sowings" % \ - (nhar, nsow)) - - # If there are more sowings than harvests, append NaN for last growing season - if nsow > nhar: - if nsow > nhar + 1: - raise ValueError("%d sowings but only %d harvests" % \ - (nsow, nhar)) - this_hdates = np.concatenate(( \ - this_hdates[1:,:], - np.array([[this_sdates[-1,0], np.nan]]))) - - # Ensure harvests occurred either the same year as sowing or the next year - if any(this_hdates[:,0] > this_sdates[:,0] + 1): - raise ValueError("Some harvest does not occur in either the same year as or year after corresponding sowing") - - # Print dates. Each row: sowing year, sowing DOY, harvest DOY - this_dates = np.concatenate((this_sdates, this_hdates[:,1:]), axis=1) - print(thisCrop) - print(this_dates) - - -# %% Get read-in sowing dates for this cell - -sdate_file = "/Volumes/Reacher/CESM_work/crop_dates/sdates_ggcmi_crop_calendar_phase3_v1.01.2000-2000.nc" - diff --git a/2d_crop_work.py b/2d_crop_work.py deleted file mode 100644 index 33fd77a..0000000 --- a/2d_crop_work.py +++ /dev/null @@ -1,86 +0,0 @@ -# %% User-defined variables - -# Your path to ctsm_py directory (i.e., where utils.py lives) -sys.path.append("/Users/sam/Documents/git_repos/ctsm_python_gallery_myfork/ctsm_py/") - -# Directory where input file(s) can be found -indir = "/Volumes/Reacher/CESM_runs/f10_f10_mg37/" - -# Either the name of a file within $indir, or a pattern that will return a list of files. -pattern = "*h1.*-01-01-00000.nc" - - -# %% Setup - -import numpy as np -import xarray as xr -import matplotlib.pyplot as plt -import warnings -import glob -import cartopy.crs as ccrs -import cartopy.feature as cfeature -import sys -import utils - - -# %% Import dataset - -# import importlib -# importlib.reload(utils) - -# Define list of variables to import -myVars = ["CPHASE", \ - "GDDHARV", - "GDDPLANT", - "GPP", - "GRAINC_TO_FOOD", - "NPP", - "TLAI", - "TOTVEGC"] - -# Get list of all files in $indir matching $pattern -filelist = glob.glob(indir + pattern) - -# Import -# this_ds = utils.import_ds(filelist, myVars=myVars) -this_ds = utils.import_ds(filelist, myVars=myVars, myVegtypes=utils.define_mgdcrop_list()) - - -# %% Read one variable from dataset. (Do nothing with it.) - -# import importlib -# importlib.reload(utils) - -# Which variable? -thisVar = "CPHASE" - -thisvar_da = utils.get_thisVar_da(thisVar, this_ds) -thisvar_da = utils.trim_da_to_mgd_crop(thisvar_da, this_ds.patches1d_itype_veg_str) - - -# %% Grid and make map, more efficiently, as function - -# import importlib -# importlib.reload(utils) - -# Grid -# tmp_vyx = utils.grid_one_variable(this_ds, "CPHASE", time=181) -tmp_vyx = utils.grid_one_variable(this_ds, "CPHASE", time="2000-07-01") - -# Make map -tmp_yx = tmp_vyx.sel(ivt_str="temperate_corn") -if tmp_yx.shape[0] == 1: - tmp_yx = tmp_yx.squeeze() -else: - raise ValueError("You must select one time step to plot") -tmp_yx = utils.cyclic_dataarray(tmp_yx) -ax = plt.axes(projection=ccrs.PlateCarree()) -plt.pcolor(tmp_yx.lon.values, tmp_yx.lat.values, tmp_yx, transform=ccrs.PlateCarree()) -ax.coastlines() -plt.show() - -# # Get dates in a format that matplotlib can use -# with warnings.catch_warnings(): -# # Ignore this warning in this with-block -# warnings.filterwarnings("ignore", message="Converting a CFTimeIndex with dates from a non-standard calendar, 'noleap', to a pandas.DatetimeIndex, which uses dates from the standard calendar. This may lead to subtle errors in operations that depend on the length of time between dates.") -# datetime_vals = this_ds.indexes["time"].to_datetimeindex() \ No newline at end of file diff --git a/clm_yield_conv.ipynb b/clm_yield_conv.ipynb deleted file mode 100644 index 1e02aa3..0000000 --- a/clm_yield_conv.ipynb +++ /dev/null @@ -1,501 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "809d1d31-6385-4c49-8d1d-76a55dcfe91d", - "metadata": {}, - "outputs": [], - "source": [ - "# install libraries if necessary\n", - "pip install matplotlib\n", - "pip install xarray\n", - "pip install cartopy\n", - "pip install netCDF4" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "94a62cf4-e407-41ba-b4df-05d3343a6838", - "metadata": {}, - "outputs": [], - "source": [ - "# import libraries\n", - "import numpy as np\n", - "import pandas as pd \n", - "import matplotlib.pylab as plt\n", - "import xarray as xr" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "34ae96b3-8102-446d-ad8b-862403d646e8", - "metadata": {}, - "outputs": [], - "source": [ - "=============================================\n", - "How to process CLM5crop output to crop yield\n", - "=============================================\n", - "\n", - "=============================================\n", - "1. Original crop yield output:\n", - "=============================================\n", - "Under h1 files:\n", - "$CASE/lnd/hist/*h1*\n", - "\n", - "Variable:\n", - "GRAINC_TO_FOOD\n", - "\n", - "dimension:\n", - "(time-monthly,pft)\n", - "\n", - "=============================================\n", - "2. Regrid pft-level data from the 1D output and output a netCDF file with (year,cropPFT,lat,lon)\n", - "=============================================\n", - "***input variables:\n", - "\n", - "float GRAINC_TO_FOOD(time, pft) ;\n", - " GRAINC_TO_FOOD:long_name = \"grain C to food\" ;\n", - " GRAINC_TO_FOOD:units = \"gC/m^2/s\" ;\n", - " GRAINC_TO_FOOD:cell_methods = \"time: mean\" ;\n", - " GRAINC_TO_FOOD:_FillValue = 1.e+36f ;\n", - " GRAINC_TO_FOOD:missing_value = 1.e+36f ;\n", - "\n", - "int pfts1d_ixy(pft) ;\n", - " pfts1d_ixy:long_name = \"2d longitude index of corresponding pft\" ;\n", - "\n", - "int pfts1d_jxy(pft) ;\n", - " pfts1d_jxy:long_name = \"2d latitude index of corresponding pft\" ;\n", - "\n", - "double pfts1d_wtgcell(pft) ;\n", - " pfts1d_wtgcell:long_name = \"pft weight relative to corresponding gridcell\" ;\n", - "\n", - "float area(lat, lon) ;\n", - " area:long_name = \"grid cell areas\" ;\n", - " area:units = \"km^2\" ;\n", - " area:_FillValue = 1.e+36f ;\n", - " area:missing_value = 1.e+36f ;\n", - "\n", - "float landfrac(lat, lon) ;\n", - " landfrac:long_name = \"land fraction\" ;\n", - " landfrac:_FillValue = 1.e+36f ;\n", - " landfrac:missing_value = 1.e+36f ;\n", - "\n", - "\n", - "***convert GRAINC_TO_FOOD(mon,pft) to GRAINC_TO_FOOD(mon,PFT,lat,lon) (where pft exists) using ixy and jxy\n", - "\n", - "***sum up monthly data to annual, and mutiply 60*60*24*30*0.85*10/(1000*0.45). After the conversion, \"gC/m^2/s\" is changed to \"ton/ha/yr\"\n", - "\n", - "***output the netCDF file with new GRAINC_TO_FOOD, and landarea (area*landfrac)\n", - "\n", - "=============================================\n", - "3. remap cropPFT to 8 active crop types\n", - "=============================================\n", - "\n", - "***input files and variables:\n", - "\n", - "from the new generated file:\n", - "GRAINC_TO_FOOD(annual,PFT,lat,lon)\n", - "area(lat,lon)\n", - "\n", - "from land surface file (e.g. /glade/p/univ/urtg0006/Yaqiong/):\n", - "\n", - "double PCT_CFT(cft, lsmlat, lsmlon) ;\n", - " PCT_CFT:long_name = \"percent crop functional type on the crop landunit (% of landunit)\" ;\n", - " PCT_CFT:units = \"unitless\" ;\n", - "\n", - "double PCT_CROP(lsmlat, lsmlon) ;\n", - " PCT_CROP:long_name = \"total percent crop landunit\" ;\n", - " PCT_CROP:units = \"unitless\" ;\n", - "\n", - "***\n", - "\n", - "calculate cropping area for specific crops using area, PCT_CFT, and PCT_CROP\n", - "\n", - "***\n", - "\n", - "extract 8 active crops from cpt (number starts from 0)\n", - "\n", - "* cornrain 2, 60 (one is tropical, the other is temperate)\n", - "* cornirr 3, 61\n", - "* soyrain 8, 62\n", - "* soyirr 9, 63\n", - "* ricerain 46\n", - "* riceirr 47\n", - "* springwheatrain 4\n", - "* springwheatirr 5\n", - "* cottonrain 26\n", - "* cottonirr 27\n", - "* sugarcanerain 52\n", - "* sugarcaneirr 53\n", - "\n", - "***\n", - "\n", - "output crop yields and crop area" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c9610b0d-50e1-4b3d-8ee2-1972fc863997", - "metadata": {}, - "outputs": [], - "source": [ - "crops = {\n", - " 'cornrain': [2, 60],\n", - " 'cornirr': [3, 61],\n", - " 'ricerain': [46],\n", - " 'riceirr': [47],\n", - " 'soyrain': [8, 62],\n", - " 'soyirr': [9, 63],\n", - " 'springwheatrain': [4],\n", - " 'springwheatirr': [5],\n", - " 'cottonrain': [26],\n", - " 'cottonirr': [27],\n", - " 'sugarcanerain': [52],\n", - " 'sugarcaneirr': [53]\n", - " }" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e2547222-1472-40b9-91a5-dd3cb75c7458", - "metadata": {}, - "outputs": [], - "source": [ - "crop_ids = [item for sublist in [crops[crop] for crop in crops] for item in sublist]\n", - "crop_ids" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5376a000-b0e0-4d29-ae90-97c925d6c400", - "metadata": {}, - "outputs": [], - "source": [ - "### Step 1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cda8ee2e-7dd4-485a-9fc6-ad5df465830b", - "metadata": {}, - "outputs": [], - "source": [ - "filedir = '/glade/p/univ/urtg0006/Brendan/clmcrop/GRAINC_TO_FOOD'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f07f1802-63d2-406b-98f0-cdef7179e79a", - "metadata": {}, - "outputs": [], - "source": [ - "grainc = xr.open_dataset(filedir + '/b.e21.BW.f09_g17.SSP245-TSMLT-GAUSS-DEFAULT.006.clm2.h1.GRAINC_TO_FOOD.203501-206912.nc')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d7f767b6-5451-46b9-818a-b7839cfb4082", - "metadata": {}, - "outputs": [], - "source": [ - "grainc = grainc.sel(time=slice('2060', '2069'))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2602cb99-676a-432d-8fa0-1968cbbd578e", - "metadata": {}, - "outputs": [], - "source": [ - "grain = grainc.GRAINC_TO_FOOD" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "439e8a43-f6b2-41b6-9678-8b2db6024dc8", - "metadata": {}, - "outputs": [], - "source": [ - "grain = grain.assign_coords(time = pd.date_range(start='2060', end='2070', freq='1M'))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2d41c802-fe6a-4514-a8f9-f0d1472f4faf", - "metadata": {}, - "outputs": [], - "source": [ - "grainc" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "43929c58-a9b9-4eba-af72-3009fc995262", - "metadata": {}, - "outputs": [], - "source": [ - "#some GRAINC_TO_FOOD files will not have the variables ixy anf jxy to convert pft to lat lon, import a file that does\n", - "#grainc1 = xr.open_dataset(filedir + 'b.e21.BWSSP245cmip6.f09_g17.CMIP6-SSP2-4.5-WACCM.001.clm2.h1.GRAINC_TO_FOOD.2015-2100.nc')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0a9cc784-4a4f-4136-9c7c-76cfccb9e1b8", - "metadata": {}, - "outputs": [], - "source": [ - "pfts1d_ixy = grainc.pfts1d_ixy\n", - "pfts1d_jxy = grainc.pfts1d_jxy\n", - "pfts1d_wtgcell = grainc.pfts1d_wtgcell\n", - "pfts1d_itype_veg = grainc.pfts1d_itype_veg\n", - "area = grainc.area\n", - "landfrac = grainc.landfrac\n", - "landarea = area * landfrac" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5e18f3f7-4a51-49e6-9ba5-2e2f9461638a", - "metadata": {}, - "outputs": [], - "source": [ - "# Assign PFT coordinate to veg-type data\n", - "pfts1d_itype_veg = pfts1d_itype_veg.assign_coords(pft = pfts1d_itype_veg.pft)\n", - "\n", - "# Resample grain to yearly sums\n", - "grain = grain.resample(time='1A').sum()\n", - "\n", - "# Create empty 4D array to construct from 1D GRAINC array \n", - "dims = ['time', 'pft', 'lat', 'lon']\n", - "coords = {'time':grain.time, 'pft':np.arange(pfts1d_itype_veg.max()+1), 'lat':grainc.lat, 'lon':grainc.lon}\n", - "grain4d = xr.DataArray(dims=dims, coords=coords)\n", - "\n", - "# Run for loop over 1D array to fill in 4D array\n", - "for pft in grainc.pft.values:\n", - " if (pfts1d_wtgcell.isel(pft = pft) > 0.0):\n", - " veg = int(pfts1d_itype_veg.isel(pft = pft).item())\n", - " lat = int(pfts1d_jxy.isel(pft = pft).item() - 1)\n", - " lon = int(pfts1d_ixy.isel(pft = pft).item() - 1)\n", - " print(lat, lon, veg)\n", - " grain4d[dict(pft = veg, lat=lat, lon=lon)] = grain.sel(pft = pft)\n", - "\n", - "# Change units to ton/ha\n", - "grain4d = grain4d * ((60*60*24*30*0.85*10)/(1000*0.45))\n", - "grain4d.attrs[\"units\"] = \"ton/ha/yr\"\n", - "\n", - "# Save filled-in array\n", - "grain4d.to_netcdf(filedir + '/GRAIN4D.b.e21.BW.f09_g17.SSP245-TSMLT-GAUSS-DEFAULT.006.clm2.h1.GRAINC_TO_FOOD.203501-206912.nc')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "79d3957f-c26c-461a-a8d9-db6475206276", - "metadata": {}, - "outputs": [], - "source": [ - "grain4d" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d00767fd-6623-4a78-81bb-826e86637e6b", - "metadata": {}, - "outputs": [], - "source": [ - "#grain4d = xr.open_dataset(filedir + '/GRAIN4D.tran-CO2-clm5.3-b.e21.BW.f09_g17.SSP245-TSMLT-GAUSS-DEFAULT.006.GRAINC_TO_FOOD.2060-2069.nc')\n", - "#grain4d = grain4d['__xarray_dataarray_variable__']" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b746b49f-0932-4509-be48-f4367060d411", - "metadata": {}, - "outputs": [], - "source": [ - "### Step 2" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1d914116-dc79-4f9f-8794-f2fb53ffb40f", - "metadata": {}, - "outputs": [], - "source": [ - "surf_data = xr.open_dataset(filedir + 'landuse.timeseries_0.9x1.25_SSP2-4.5_78pfts_CMIP6_simyr1850-2100_c190102.nc')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "234ee8b1-f726-4e9b-ae20-1b44fd30f4b1", - "metadata": {}, - "outputs": [], - "source": [ - "surf_data = surf_data.sel(time=slice('2060', '2069'))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e0a3394b-5263-46f8-846a-4c9847a2f379", - "metadata": {}, - "outputs": [], - "source": [ - "surf_data['time'] = pd.date_range(start='2060', end='2070', freq='1A')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "56a13c21-d338-4e56-8c06-f9bd0e62a6c4", - "metadata": {}, - "outputs": [], - "source": [ - "surf_data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "309a6101-a257-49f0-9af7-5dcadb5f0bce", - "metadata": {}, - "outputs": [], - "source": [ - "pct_crop = surf_data.PCT_CROP\n", - "pct_cft = surf_data.PCT_CFT\n", - "\n", - "# Create empty 4D array to construct YIELD_OUT by CROP\n", - "dims = ['cft', 'time', 'lat', 'lon']\n", - "cft_coord = pct_cft.cft-15.0\n", - "coords = {'time':grain4d.time, 'cft':cft_coord, 'lat':grain4d.lat, 'lon':grain4d.lon}\n", - "yield_OUT = xr.DataArray(dims=dims, coords=coords).rename('yield')\n", - "yield_OUT.attrs[\"units\"] = \"ton/ha/yr\"\n", - "\n", - "# Create empty 3D array to construct AREA_OUT by CROP\n", - "dims = ['cft','time', 'lat', 'lon']\n", - "coords = {'cft':cft_coord,'time':surf_data.time, 'lat':grain4d.lat, 'lon':grain4d.lon}\n", - "area_OUT = xr.DataArray(dims=dims, coords=coords).rename('area')\n", - "area_OUT.attrs[\"units\"] = \"km^2\"\n", - "\n", - "# For loop to create new file\n", - "for crop_id in cft_coord:\n", - " area_OUT.loc[dict(cft=crop_id)] = (pct_cft.sel(cft=crop_id+15)/100).values * (pct_crop/100).values * landarea.values\n", - " yield_OUT.loc[dict(cft=crop_id)] = grain4d.sel(pft=crop_id+15)\n", - "\n", - "# Merge arrays to dataset and save\n", - "yield_cft = xr.merge([yield_OUT, area_OUT])\n", - "yield_cft['yield'] = yield_cft['yield'].where(yield_cft['area']>0)\n", - "yield_cft.to_netcdf('STEP2.tran-CO2-clm5.3-b.e21.BWSSP245cmip6.f09_g17.CMIP6-SSP2-4.5-WACCM.006.GRAINC_TO_FOOD.2060-2069.nc')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bff74aca-de67-4a60-9d80-0fa088e9f053", - "metadata": {}, - "outputs": [], - "source": [ - "# (one is tropical, the other is temperate)\n", - "crops_tot = {\n", - " 'corn': [2, 3, 60, 61],\n", - " 'cornrain': [2, 60],\n", - " 'cornirr': [3, 61],\n", - " 'rice': [46, 47],\n", - " 'ricerain': [46],\n", - " 'riceirr': [47],\n", - " 'soy': [8, 9, 62, 63],\n", - " 'soyrain': [8, 62],\n", - " 'soyirr': [9, 63],\n", - " 'springwheat': [4, 5],\n", - " 'springwheatrain': [4],\n", - " 'springwheatirr': [5],\n", - " 'cotton': [26, 27],\n", - " 'cottonrain': [26],\n", - " 'cottonirr': [27],\n", - " 'sugar': [52, 53],\n", - " 'sugarcanerain': [52],\n", - " 'sugarcaneirr': [53]\n", - " }\n", - "\n", - "# Create empty 4D array to construct YIELD_OUT by CROP\n", - "dims = ['crops', 'time', 'lat', 'lon']\n", - "coords = { 'crops':np.arange(0, 18, 1.0),'time':yield_cft.time, 'lat':yield_cft.lat, 'lon':yield_cft.lon}\n", - "yield_OUT_crop = xr.DataArray(dims=dims, coords=coords).rename('yield')\n", - "yield_OUT_crop.attrs[\"units\"] = \"ton/ha/yr\"\n", - "\n", - "# Create empty 3D array to construct AREA_OUT by CROP\n", - "dims = ['crops','time', 'lat', 'lon']\n", - "coords = {'crops':np.arange(0, 18, 1.0),'time':yield_cft.time,'lat':yield_cft.lat, 'lon':yield_cft.lon}\n", - "area_OUT_crop = xr.DataArray(dims=dims, coords=coords).rename('area')\n", - "area_OUT_crop.attrs[\"units\"] = \"km^2\"\n", - "\n", - "for i, crop in enumerate(crops_tot):\n", - " if i%3 !=0: \n", - " print(crop)\n", - " IDs = crops_tot[crop]\n", - " IDs = [id for id in IDs]\n", - " subset = yield_cft.sel(cft=IDs)\n", - " yields = subset['yield']\n", - " area = subset['area']\n", - " yields = yields.where(area>0).sum(dim='cft', min_count=1)\n", - " area = area.sum(dim='cft', min_count=1)\n", - " yield_OUT_crop.loc[dict(crops=i)] = yields\n", - " area_OUT_crop.loc[dict(crops=i)] = area\n", - "\n", - "for i, crop in enumerate(crops_tot):\n", - " if i%3 ==0:\n", - " print(crop)\n", - " yields = yield_OUT_crop.sel(crops=[i+1, i+2])\n", - " area = area_OUT_crop.sel(crops=[i+1, i+2])\n", - " yields = (yields * area).sum(dim='crops', min_count=1)\n", - " area = area.sum(dim='crops', min_count=1)\n", - " yields = yields / area\n", - " yield_OUT_crop.loc[dict(crops=i)] = yields\n", - " area_OUT_crop.loc[dict(crops=i)] = area\n", - "\n", - "yield_crop = xr.merge([yield_OUT_crop, area_OUT_crop])\n", - "yield_crop.to_netcdf(filedir + '/tran-CO2-clm5.3-b.e21.BW.f09_g17.SSP245-TSMLT-GAUSS-DEFAULT.006.yield.2060-2069.nc')" - ] - } - ], - "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.9.4" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -}