From fc5538fa38b5bdff079702e00613e3db6d34f79c Mon Sep 17 00:00:00 2001 From: lkugler <lukas.kugler@gmail.com> Date: Tue, 14 Mar 2023 16:55:16 +0100 Subject: [PATCH] add real docs --- docs/source/index.rst | 4 +- docs/source/notebooks/test.ipynb | 51 -------- docs/source/notebooks/tutorial1.ipynb | 169 ++++++++++++++++++++++++++ docs/source/notebooks/tutorial2.ipynb | 122 +++++++++++++++++++ docs/source/notebooks/tutorial3.ipynb | 129 ++++++++++++++++++++ 5 files changed, 423 insertions(+), 52 deletions(-) delete mode 100644 docs/source/notebooks/test.ipynb create mode 100644 docs/source/notebooks/tutorial1.ipynb create mode 100644 docs/source/notebooks/tutorial2.ipynb create mode 100644 docs/source/notebooks/tutorial3.ipynb diff --git a/docs/source/index.rst b/docs/source/index.rst index bb84e17..282d53b 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -31,7 +31,9 @@ Other helpful resources :maxdepth: 2 :caption: Tutorials: - notebooks/test + notebooks/tutorial1 + notebooks/tutorial2 + notebooks/tutorial3 API diff --git a/docs/source/notebooks/test.ipynb b/docs/source/notebooks/test.ipynb deleted file mode 100644 index 130030b..0000000 --- a/docs/source/notebooks/test.ipynb +++ /dev/null @@ -1,51 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "aed6f7a5-6afe-4e11-ab8b-02d8557ad457", - "metadata": {}, - "source": [ - "# Hello World" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "f66039b3-717f-48ff-b4af-19c93cccb911", - "metadata": {}, - "outputs": [], - "source": [ - "import os" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5799568d-2ffe-4e67-88c1-87113b252a74", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "JHubPy - 3.9.6", - "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.6" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/source/notebooks/tutorial1.ipynb b/docs/source/notebooks/tutorial1.ipynb new file mode 100644 index 0000000..798d6b9 --- /dev/null +++ b/docs/source/notebooks/tutorial1.ipynb @@ -0,0 +1,169 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "fd5c3005-f237-4495-9185-2d4d474cafd5", + "metadata": {}, + "source": [ + "# Tutorial 1: The assimilation step\n", + "DART-WRF is a python package which automates many things like configuration, saving configuration and output, handling computing resources, etc.\n", + "\n", + "The data for this experiment is accessible for students on the server srvx1.\n" + ] + }, + { + "cell_type": "markdown", + "id": "93d59d4d-c514-414e-81fa-4ff390290811", + "metadata": {}, + "source": [ + "### Configuring the experiment\n", + "Firstly, you need to configure the experiment in `config/cfg.py`.\n", + "\n", + "Let's go through the most important settings:\n", + "\n", + "- expname should be a unique identifier and will be used as folder name\n", + "- model_dx is the model resolution in meters\n", + "- n_ens is the ensemble size\n", + "- update_vars are the WRF variables which shall be updated by the assimilation\n", + "- filter_kind is 1 for the EAKF (see the DART documentation for more)\n", + "- prior and post_inflation defines what inflation we want (see the DART docs)\n", + "- sec is the statistical sampling error correction from Anderson (2012)\n", + "\n", + "```python\n", + "exp = utils.Experiment()\n", + "exp.expname = \"test_newcode\"\n", + "exp.model_dx = 2000\n", + "exp.n_ens = 10\n", + "exp.update_vars = ['U', 'V', 'W', 'THM', 'PH', 'MU', 'QVAPOR', 'QCLOUD', 'QICE', 'PSFC']\n", + "exp.filter_kind = 1\n", + "exp.prior_inflation = 0\n", + "exp.post_inflation = 4\n", + "exp.sec = True\n", + "\n", + "```\n", + "In case you want to generate new observations like for an observing system simulations experiment, OSSE), set \n", + "```python\n", + "exp.use_existing_obsseq = False`.\n", + "```\n", + "\n", + "`exp.nature` defines which WRF files will be used to draw observations from, e.g.: \n", + "```python\n", + "exp.nature = '/users/students/lehre/advDA_s2023/data/sample_nature/'\n", + "```\n", + "\n", + "`exp.input_profile` is used, if you create initial conditions from a so called wrf_profile (see WRF guide).\n", + "```python\n", + "exp.input_profile = '/doesnt_exist/initial_profiles/wrf/ens/raso.fc.<iens>.wrfprof'\n", + "```\n", + "\n", + "\n", + "For horizontal localization half-width of 20 km and 3 km vertically, set\n", + "```python\n", + "exp.cov_loc_vert_km_horiz_km = (3, 20)\n", + "```\n", + "You can also set it to False for no vertical localization.\n", + "\n", + "#### Single observation\n", + "Set your desired observations like this. \n", + "```python\n", + "t = dict(plotname='Temperature', plotunits='[K]',\n", + " kind='RADIOSONDE_TEMPERATURE', \n", + " n_obs=1, # number of observations\n", + " obs_locations=[(45., 0.)], # location of observations\n", + " error_generate=0.2, # observation error used to generate observations\n", + " error_assimilate=0.2, # observation error used for assimilation\n", + " heights=[1000,], # for radiosondes, use range(1000, 17001, 2000)\n", + " cov_loc_radius_km=50) # horizontal localization half-width\n", + "\n", + "exp.observations = [t,] # select observations for assimilation\n", + "```\n", + "\n", + "#### Multiple observations\n", + "To generate a grid of observations, use\n", + "```python\n", + "vis = dict(plotname='VIS 0.6µm', plotunits='[1]',\n", + " kind='MSG_4_SEVIRI_BDRF', sat_channel=1, \n", + " n_obs=961, obs_locations='square_array_evenly_on_grid',\n", + " error_generate=0.03, error_assimilate=0.03,\n", + " cov_loc_radius_km=20)\n", + "```\n", + "\n", + "But caution, n_obs should only be one of the following:\n", + "\n", + "- 22500 for 2km observation density/resolution \n", + "- 5776 for 4km; \n", + "- 961 for 10km; \n", + "- 256 for 20km; \n", + "- 121 for 30km\n", + "\n", + "For vertically resolved data, like radar, n_obs is the number of observations at each observation height level." + ] + }, + { + "cell_type": "markdown", + "id": "16bd3521-f98f-4c4f-8019-31029fd678ae", + "metadata": {}, + "source": [ + "### Configuring the hardware\n", + "In case you use a cluster which is not supported, configure paths inside `config/clusters.py`.\n", + "\n", + "\n", + "\n", + "\n", + "### Assimilate observations\n", + "We start by importing some modules:\n", + "```python\n", + "import datetime as dt\n", + "from dartwrf.workflows import WorkFlows\n", + "```\n", + "\n", + "To assimilate observations at dt.datetime `time` we set the directory paths and times of the prior ensemble forecasts:\n", + "\n", + "```python\n", + "prior_path_exp = '/users/students/lehre/advDA_s2023/data/sample_ensemble/'\n", + "prior_init_time = dt.datetime(2008,7,30,12)\n", + "prior_valid_time = dt.datetime(2008,7,30,12,30)\n", + "assim_time = prior_valid_time\n", + "```\n", + "\n", + "Finally, we run the data assimilation by calling\n", + "```python\n", + "w = WorkFlows(exp_config='cfg.py', server_config='srvx1.py')\n", + "\n", + "w.assimilate(assim_time, prior_init_time, prior_valid_time, prior_path_exp)\n", + "```\n", + "\n", + "Congratulations! You're done!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82e809a8-5972-47f3-ad78-6290afe4ae17", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "nwp 2023.1 - 3.10", + "language": "python", + "name": "nwp2023.1" + }, + "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.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/source/notebooks/tutorial2.ipynb b/docs/source/notebooks/tutorial2.ipynb new file mode 100644 index 0000000..df58b78 --- /dev/null +++ b/docs/source/notebooks/tutorial2.ipynb @@ -0,0 +1,122 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "fd5c3005-f237-4495-9185-2d4d474cafd5", + "metadata": { + "tags": [] + }, + "source": [ + "# Tutorial 2: Forecast after DA\n", + "\n", + "\n", + "**Goal**: To run a cycled data assimilation experiment.\n", + "[`cycled_exp.py`](https://github.com/lkugler/DART-WRF/blob/master/generate_free.py) contains an example which will be explained here:\n", + "\n", + "Now there are two options:\n", + "1) To start a forecast from an existing forecast, i.e. from WRF restart files\n", + "2) To start a forecast from defined thermodynamic profiles, i.e. from a `wrf_profile`\n", + "\n", + "\n", + "### Restart a forecast\n", + "To run a forecast from initial conditions of a previous forecasts, we import these modules\n", + "```python\n", + "import datetime as dt\n", + "from dartwrf.workflows import WorkFlows\n", + "```\n", + "\n", + "Let's say you want to run a forecast starting at 9 UTC until 12 UTC.\n", + "Initial conditions shall be taken from a previous experiment in `/user/test/data/sim_archive/exp_abc` which was initialized at 6 UTC and there are WRF restart files for 9 UTC.\n", + "Then the code would be\n", + "\n", + "```python\n", + "prior_path_exp = '/user/test/data/sim_archive/exp_abc'\n", + "prior_init_time = dt.datetime(2008,7,30,6)\n", + "prior_valid_time = dt.datetime(2008,7,30,9)\n", + "\n", + "w = WorkFlows(exp_config='cfg.py', server_config='srvx1.py')\n", + "\n", + "begin = dt.datetime(2008, 7, 30, 9)\n", + "end = dt.datetime(2008, 7, 30, 12)\n", + "\n", + "w.prepare_WRFrundir(begin)\n", + "\n", + "w.prepare_IC_from_prior(prior_path_exp, prior_init_time, prior_valid_time)\n", + "\n", + "w.run_ENS(begin=begin, # start integration from here\n", + " end=end, # integrate until here\n", + " output_restart_interval=9999, # do not write WRF restart files\n", + " )\n", + "```\n", + "Note that we use predefined workflow functions like `run_ENS`.\n", + "\n", + "\n", + "### Forecast run after Data Assimilation\n", + "In order to continue after assimilation you need the posterior = prior (1) + increments (2)\n", + "\n", + "1. Set posterior = prior\n", + "```python\n", + "id = w.prepare_IC_from_prior(prior_path_exp, prior_init_time, prior_valid_time, depends_on=id)\n", + "```\n", + "\n", + "2) Update posterior with increments from assimilation\n", + "After this, the wrfrst files are updated with assimilation increments from DART output and copied to the WRF's run directories so you can continue to run the forecast ensemble.\n", + "```python\n", + "id = w.update_IC_from_DA(time, depends_on=id)\n", + "```\n", + "\n", + "3) Define how long you want to run the forecast and when you want WRF restart files. Since they take a lot of space, we want as few as possible.\n", + "\n", + "```python\n", + "timedelta_integrate = dt.timedelta(hours=5)\n", + "output_restart_interval = 9999 # any value larger than the forecast duration\n", + "```\n", + "\n", + "If you run DA in cycles of 15 minutes, it will be\n", + "```python\n", + "timedelta_integrate = dt.timedelta(hours=5)\n", + "timedelta_btw_assim = dt.timedelta(minutes=15)\n", + "output_restart_interval = timedelta_btw_assim.total_seconds()/60\n", + "```\n", + "\n", + "\n", + "3) Run WRF ensemble\n", + "```python\n", + "id = w.run_ENS(begin=time, # start integration from here\n", + " end=time + timedelta_integrate, # integrate until here\n", + " output_restart_interval=output_restart_interval,\n", + " depends_on=id)\n", + "```\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "400244f1-098b-46ea-b29d-2226c7cbc827", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "nwp 2023.1 - 3.10", + "language": "python", + "name": "nwp2023.1" + }, + "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.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/source/notebooks/tutorial3.ipynb b/docs/source/notebooks/tutorial3.ipynb new file mode 100644 index 0000000..d3018ab --- /dev/null +++ b/docs/source/notebooks/tutorial3.ipynb @@ -0,0 +1,129 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "fd5c3005-f237-4495-9185-2d4d474cafd5", + "metadata": { + "tags": [] + }, + "source": [ + "# Tutorial 3: Cycle forecast and assimilation\n", + "\n", + "\n", + "**Goal**: To run a cycled data assimilation experiment.\n", + "[`cycled_exp.py`](https://github.com/lkugler/DART-WRF/blob/master/generate_free.py) contains an example which will be explained here:\n", + "\n", + "For example, your experiment can look like this\n", + "\n", + "```python\n", + "prior_path_exp = '/jetfs/home/lkugler/data/sim_archive/exp_v1.19_P2_noDA'\n", + "\n", + "init_time = dt.datetime(2008, 7, 30, 13)\n", + "time = dt.datetime(2008, 7, 30, 14)\n", + "last_assim_time = dt.datetime(2008, 7, 30, 14)\n", + "forecast_until = dt.datetime(2008, 7, 30, 14, 15)\n", + "\n", + "w.prepare_WRFrundir(init_time)\n", + "id = w.run_ideal(depends_on=id)\n", + "\n", + "prior_init_time = init_time\n", + "prior_valid_time = time\n", + "\n", + "while time <= last_assim_time:\n", + "\n", + " # usually we take the prior from the current time\n", + " # but one could use a prior from a different time from another run\n", + " # i.e. 13z as a prior to assimilate 12z observations\n", + " prior_valid_time = time\n", + "\n", + " id = w.assimilate(time, prior_init_time, prior_valid_time, prior_path_exp, depends_on=id)\n", + "\n", + " # 1) Set posterior = prior\n", + " id = w.prepare_IC_from_prior(prior_path_exp, prior_init_time, prior_valid_time, depends_on=id)\n", + "\n", + " # 2) Update posterior += updates from assimilation\n", + " id = w.update_IC_from_DA(time, depends_on=id)\n", + "\n", + " # How long shall we integrate?\n", + " timedelta_integrate = timedelta_btw_assim\n", + " output_restart_interval = timedelta_btw_assim.total_seconds()/60\n", + " if time == last_assim_time: #this_forecast_init.minute in [0,]: # longer forecast every full hour\n", + " timedelta_integrate = forecast_until - last_assim_time # dt.timedelta(hours=4)\n", + " output_restart_interval = 9999 # no restart file after last assim\n", + "\n", + " # 3) Run WRF ensemble\n", + " id = w.run_ENS(begin=time, # start integration from here\n", + " end=time + timedelta_integrate, # integrate until here\n", + " output_restart_interval=output_restart_interval,\n", + " depends_on=id)\n", + "\n", + " # as we have WRF output, we can use own exp path as prior\n", + " prior_path_exp = cluster.archivedir \n", + "\n", + " id_sat = w.create_satimages(time, depends_on=id)\n", + "\n", + " # increment time\n", + " time += timedelta_btw_assim\n", + "\n", + " # update time variables\n", + " prior_init_time = time - timedelta_btw_assim\n", + " \n", + "w.verify_sat(id_sat)\n", + "w.verify_wrf(id)\n", + "w.verify_fast(id)\n", + "```\n", + "\n", + "#### Job scheduling status\n", + "If you work on a server with a queueing system, the script submits jobs into the SLURM queue with dependencies so that SLURM starts the jobs itself as soon as resources are available. Most jobs need only a few cores, but model integration is done across many nodes. You can look at the status with\n", + "```bash\n", + "$ squeue -u `whoami` --sort=i\n", + " JOBID PARTITION NAME USER ST TIME NODES NODELIST(REASON)\n", + " 1710274 mem_0384 prepwrfr lkugler PD 0:00 1 (Priority)\n", + " 1710275 mem_0384 IC-prior lkugler PD 0:00 1 (Dependency)\n", + " 1710276 mem_0384 Assim-42 lkugler PD 0:00 1 (Dependency)\n", + " 1710277 mem_0384 IC-prior lkugler PD 0:00 1 (Dependency)\n", + " 1710278 mem_0384 IC-updat lkugler PD 0:00 1 (Dependency)\n", + " 1710279 mem_0384 preWRF2- lkugler PD 0:00 1 (Dependency)\n", + " 1710280_[1-10] mem_0384 runWRF2- lkugler PD 0:00 1 (Dependency)\n", + " 1710281 mem_0384 pRTTOV-6 lkugler PD 0:00 1 (Dependency)\n", + " 1710282 mem_0384 Assim-3a lkugler PD 0:00 1 (Dependency)\n", + " 1710283 mem_0384 IC-prior lkugler PD 0:00 1 (Dependency)\n", + " 1710284 mem_0384 IC-updat lkugler PD 0:00 1 (Dependency)\n", + " 1710285 mem_0384 preWRF2- lkugler PD 0:00 1 (Dependency)\n", + " 1710286_[1-10] mem_0384 runWRF2- lkugler PD 0:00 1 (Dependency)\n", + " 1710287 mem_0384 pRTTOV-7 lkugler PD 0:00 1 (Dependency)\n", + "```\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "400244f1-098b-46ea-b29d-2226c7cbc827", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "nwp 2023.1 - 3.10", + "language": "python", + "name": "nwp2023.1" + }, + "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.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} -- GitLab