├── .flake8 ├── .gitignore ├── README.md ├── environment.yml ├── exercises ├── 01-molecule_spectra │ ├── absorption.ipynb │ └── absorption.py ├── 02-line_shape │ ├── lineshape.ipynb │ └── lineshape.py ├── 03-rtcalc │ ├── rtcalc.ipynb │ └── rtcalc.py ├── 04-jacobian │ ├── jacobian.ipynb │ └── jacobian.py ├── 05-inversion │ ├── H2Orad.jpg │ ├── input │ │ ├── S_xa.xml │ │ ├── measurement.xml │ │ ├── x_apriori.xml │ │ └── x_true.xml │ ├── oem.ipynb │ ├── oem.py │ ├── oem_solution.ipynb │ └── simulate_measurement.py ├── 06-non_lin_inversion │ ├── Halo.jpg │ ├── Temperature_retrieval.ipynb │ ├── atmosphere │ │ ├── atmospheres_true.xml │ │ ├── aux1d_true.xml │ │ └── aux2d_true.xml │ ├── nonlin_oem.py │ ├── observation │ │ ├── SensorCharacteristics_118GHz.xml │ │ ├── SensorCharacteristics_22GHz.xml │ │ ├── SensorCharacteristics_50GHz.xml │ │ ├── dropsonde.xml │ │ ├── lat.xml │ │ ├── y_obs_118GHz.xml │ │ ├── y_obs_22GHz.xml │ │ └── y_obs_50GHz.xml │ ├── prepare_airborne_data.py │ ├── simulate_airborne_measurement.py │ └── temperature_retrieval.py ├── 07-olr │ ├── input │ │ ├── midlatitude-summer.xml │ │ ├── midlatitude-winter.xml │ │ ├── subarctic-summer.xml │ │ ├── subarctic-winter.xml │ │ └── tropical.xml │ ├── olr.ipynb │ ├── olr.py │ └── olr_module.py ├── 08_heating_rates │ ├── heating_rates.ipynb │ ├── heating_rates_module.py │ └── input │ │ ├── midlatitude-summer.xml │ │ ├── midlatitude-winter.xml │ │ ├── subarctic-summer.xml │ │ ├── subarctic-winter.xml │ │ └── tropical.xml └── 09-scattering │ ├── input │ ├── chevallierl91_all_extract.xml │ └── pndfield_input.xml │ ├── scattering.ipynb │ └── scattering_module.py ├── script ├── AdvRaRe_script.bib ├── AdvRaRe_script.tex ├── AdvRaRe_script_print.tex ├── AdvRaRe_script_screen.tex ├── Makefile ├── figures │ ├── Buehler_et_al_OLR_spectra.png │ ├── Energy_levels.png │ ├── Fig_moments_inertia.png │ ├── Reduced_mass.png │ ├── Transitions_vib_rot.png │ ├── Vibration_parabol.png │ ├── Vibration_parabol_2.png │ ├── buehler_portrait_sketch.png │ ├── interaction_types.png │ ├── perturbedLayer_EmissionTransmission.png │ ├── plotTypes_Jacobian.png │ ├── rotating_mass.png │ ├── schematic_energy_states.png │ └── transition_types.png └── j_abbr.bib └── test ├── Makefile └── README.md /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | extend-ignore = E203 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/plots 2 | **/results 3 | .DS_Store 4 | .ipynb_checkpoints 5 | .vscode/* 6 | __pycache__ 7 | script/AdvRaRe_script_print.aux 8 | script/AdvRaRe_script_print.fdb_latexmk 9 | script/AdvRaRe_script_print.fls 10 | script/AdvRaRe_script_print.log 11 | script/AdvRaRe_script_print.out 12 | script/AdvRaRe_script_print.pdf 13 | script/AdvRaRe_script_print.synctex.gz 14 | script/AdvRaRe_script_print.toc 15 | script/AdvRaRe_script_screen.aux 16 | script/AdvRaRe_script_screen.fdb_latexmk 17 | script/AdvRaRe_script_screen.fls 18 | script/AdvRaRe_script_screen.log 19 | script/AdvRaRe_script_screen.out 20 | script/AdvRaRe_script_screen.pdf 21 | script/AdvRaRe_script_screen.toc 22 | test/*.pdf 23 | *.png 24 | *.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advanced Radiation and Remote Sensing 2 | 3 | A practical introduction to remote sensing using the 4 | _Atmospheric Radiative Transfer Simulator_ ([ARTS][arts]). 5 | 6 | The exercises make use of the ARTS Python API. 7 | 8 | Required ARTS version is release 2.6.14. 9 | 10 | ARTS requires the Miniforge3 Python environment. 11 | Installers can be downloaded from the [Miniforge Github project][conda] page. 12 | If you are not familiar with the installation procedure of Miniforge, [this page provides good instructions][robotology]. 13 | 14 | After you have installed Miniforge, ARTS can be installed with the following command: 15 | ``` 16 | mamba install -c rttools pyarts 17 | ``` 18 | 19 | If you use Spyder, Visual Studio Code or any other IDE to run the Python scripts, make sure to select the correct interpreter path (`~/miniforge3/bin/python`) in your IDE. 20 | 21 | Note that ARTS is only available for Linux and macOS. If you are on Windows or have trouble with the setup, students attending the course at Universität Hamburg can use the computers in the pool rooms or the [VDI system of CEN][vdi-cen] as a fallback solution: 22 | 23 | After logging in to a Linux virtual machine, running this command will provide a Miniforge environment with pyarts preinstalled: 24 | 25 | ``` 26 | source /data/share/lehre/unix/rtcourse/activate.sh 27 | ``` 28 | 29 | [arts]: http://radiativetransfer.org/ 30 | [vdi-cen]: https://www.cen.uni-hamburg.de/facilities/cen-it/vdi.html 31 | [conda]: https://github.com/conda-forge/miniforge#miniforge 32 | [robotology]: https://github.com/robotology/robotology-superbuild/blob/master/doc/install-miniforge.md 33 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: arts-lectures 2 | channels: 3 | - rttools 4 | - conda-forge 5 | - nodefaults 6 | dependencies: 7 | - ipympl 8 | - jupyterlab 9 | - pyarts=2.6.8 10 | 11 | -------------------------------------------------------------------------------- /exercises/01-molecule_spectra/absorption.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Advanced radiation and remote sensing\n", 8 | "\n", 9 | "Manfred Brath, Oliver Lemke\n", 10 | "\n", 11 | "## Exercise 1: Molecule absorption spectra" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "%matplotlib widget\n", 21 | "\n", 22 | "import os\n", 23 | "import matplotlib.pyplot as plt\n", 24 | "import numpy as np\n", 25 | "from absorption import tag2tex, calculate_absxsec, pyarts\n", 26 | "\n", 27 | "# make plot folder, if it is not existing\n", 28 | "os.makedirs(\"plots\", exist_ok=True)\n", 29 | "\n", 30 | "#speed of light \n", 31 | "c = pyarts.arts.constants.c # m/s" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": {}, 37 | "source": [ 38 | "\n", 39 | "### 1. Absorption spectra in the microwave spectral range (rotational spectra). \n", 40 | "\n", 41 | "Calculate the molecule absorption spectra of \n", 42 | "\n", 43 | "* $\\mathrm{HCl}$\n", 44 | "* $\\mathrm{H_2O}$\n", 45 | "* $\\mathrm{O_3}$ \n", 46 | "\n", 47 | "for a temperature of 200 K and 300 K.\n", 48 | "\n", 49 | "* How does the rotational spectra change?\n", 50 | "* Can you explain the changes?\n" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "# Define parameters\n", 60 | "species = \"HCl\"\n", 61 | "temperature = 300 # K \n", 62 | "pressure = 101325 # Pa\n", 63 | "\n", 64 | "# Call ARTS to calculate absorption cross sections\n", 65 | "freq, abs_xsec = calculate_absxsec(species, pressure, temperature)" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": null, 71 | "metadata": {}, 72 | "outputs": [], 73 | "source": [ 74 | "# Plot the results.\n", 75 | "\n", 76 | "fig, ax = plt.subplots()\n", 77 | "ax.plot(freq / 1e9, abs_xsec)\n", 78 | "ax.set_xlim(freq.min() / 1e9, freq.max() / 1e9)\n", 79 | "ax.set_ylim(bottom=0)\n", 80 | "ax.set_xlabel(\"Frequency [GHz]\")\n", 81 | "ax.set_ylabel(r\"Abs. cross section [$\\sf m^2$]\")\n", 82 | "ax.set_title(f\"{tag2tex(species)} p:{pressure/100} hPa T:{temperature:0.0f} K\")\n", 83 | "\n", 84 | "fig.savefig( # Save figure.\n", 85 | " f\"plots/plot_xsec_{species}_{pressure:.0f}Pa_{temperature:.0f}K.pdf\"\n", 86 | ")" 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "metadata": {}, 92 | "source": [ 93 | "\n", 94 | "### 2. Absorption spectra in the infrared spectral range (vibrational spectra). \n", 95 | "\n", 96 | "Calculate the molecule absorption spectra of \n", 97 | "\n", 98 | "* $\\mathrm{CO_2}$\n", 99 | "* $\\mathrm{H_2O}$\n", 100 | "* $\\mathrm{O_3}$\n", 101 | "* $\\mathrm{O_2}$\n", 102 | "* $\\mathrm{N_2}$\n", 103 | "\n", 104 | "for a temperature of 300 K.\n", 105 | "\n", 106 | "Adjust the frequency limits using the keywordargument *fmin* and *fmax* of `calculate_absxsec`. For plotting in the infrared range, it is common to use wavenumber in $\\left[\\text{cm}^{-1}\\right]$ instead of frequency. Copy the python cells from above and adjust them.\n", 107 | "\n", 108 | "* Can you explain the differences between $\\mathrm{CO_2}$, $\\mathrm{H_2O}$ and $\\mathrm{O_3}$ on one side and $\\mathrm{O_2}$ and $\\mathrm{N_2}$ on the other side?" 109 | ] 110 | }, 111 | { 112 | "cell_type": "markdown", 113 | "metadata": {}, 114 | "source": [] 115 | } 116 | ], 117 | "metadata": { 118 | "kernelspec": { 119 | "display_name": "Python 3 (ipykernel)", 120 | "language": "python", 121 | "name": "python3" 122 | }, 123 | "language_info": { 124 | "codemirror_mode": { 125 | "name": "ipython", 126 | "version": 3 127 | }, 128 | "file_extension": ".py", 129 | "mimetype": "text/x-python", 130 | "name": "python", 131 | "nbconvert_exporter": "python", 132 | "pygments_lexer": "ipython3", 133 | "version": "3.12.5" 134 | }, 135 | "varInspector": { 136 | "cols": { 137 | "lenName": 16, 138 | "lenType": 16, 139 | "lenVar": 40 140 | }, 141 | "kernels_config": { 142 | "python": { 143 | "delete_cmd_postfix": "", 144 | "delete_cmd_prefix": "del ", 145 | "library": "var_list.py", 146 | "varRefreshCmd": "print(var_dic_list())" 147 | }, 148 | "r": { 149 | "delete_cmd_postfix": ") ", 150 | "delete_cmd_prefix": "rm(", 151 | "library": "var_list.r", 152 | "varRefreshCmd": "cat(var_dic_list()) " 153 | } 154 | }, 155 | "types_to_exclude": [ 156 | "module", 157 | "function", 158 | "builtin_function_or_method", 159 | "instance", 160 | "_Feature" 161 | ], 162 | "window_display": false 163 | }, 164 | "vscode": { 165 | "interpreter": { 166 | "hash": "6bd45fef6a38d15b43f43de43ba5066924911f80576952f97fb08adaede44831" 167 | } 168 | } 169 | }, 170 | "nbformat": 4, 171 | "nbformat_minor": 4 172 | } 173 | -------------------------------------------------------------------------------- /exercises/01-molecule_spectra/absorption.py: -------------------------------------------------------------------------------- 1 | # %% Import modules and define functions 2 | """Calculate and plot absorption cross sections.""" 3 | import re 4 | 5 | import numpy as np 6 | import pyarts 7 | 8 | 9 | def tag2tex(tag): 10 | """Replace all numbers in a species tag with LaTeX subscripts.""" 11 | return re.sub("([a-zA-Z]+)([0-9]+)", r"\1$_{\2}$", tag) 12 | 13 | 14 | def calculate_absxsec( 15 | species="N2O", 16 | pressure=800e2, 17 | temperature=300.0, 18 | fmin=10e9, 19 | fmax=2000e9, 20 | fnum=10_000, 21 | lineshape="VP", 22 | normalization="VVH", 23 | verbosity=0, 24 | vmr=0.01, 25 | lines_off=0 26 | ): 27 | """Calculate absorption cross sections. 28 | 29 | Parameters: 30 | species (str): Absorption species name. 31 | pressure (float): Atmospheric pressure [Pa]. 32 | temperature (float): Atmospheric temperature [K]. 33 | fmin (float): Minimum frequency [Hz]. 34 | fmax (float): Maximum frequency [Hz]. 35 | fnum (int): Number of frequency grid points. 36 | lineshape (str): Line shape model. 37 | Available options: 38 | DP - Doppler profile, 39 | LP - Lorentz profile, 40 | VP - Voigt profile, 41 | SDVP - Speed-dependent Voigt profile, 42 | HTP - Hartman-Tran profile. 43 | normalization (str): Line shape normalization factor. 44 | Available options: 45 | VVH - Van Vleck and Huber, 46 | VVW - Van Vleck and Weisskopf, 47 | RQ - Rosenkranz quadratic, 48 | None - No extra normalization. 49 | verbosity (int): Set ARTS verbosity (``0`` prevents all output). 50 | vmr (float): Volume mixing ratio. This is mainly important for the 51 | water vapor continua. 52 | lines_off (int): Switch off lines, if no contnua is selected, 53 | absorption will be zero. 54 | 55 | Returns: 56 | ndarray, ndarray: Frequency grid [Hz], Abs. cross sections [m^2] 57 | """ 58 | 59 | pyarts.cat.download.retrieve(verbose=bool(verbosity)) 60 | 61 | # Create ARTS workspace and load default settings 62 | ws = pyarts.workspace.Workspace(verbosity=verbosity) 63 | ws.water_p_eq_agendaSet() 64 | ws.PlanetSet(option="Earth") 65 | 66 | ws.verbositySetScreen(ws.verbosity, verbosity) 67 | 68 | # We do not want to calculate the Jacobian Matrix 69 | ws.jacobianOff() 70 | 71 | # Define absorption species 72 | ws.abs_speciesSet(species=[species]) 73 | ws.abs_lines_per_speciesReadSpeciesSplitCatalog(basename="lines/") 74 | ws.abs_lines_per_speciesLineShapeType(option=lineshape) 75 | ws.abs_lines_per_speciesCutoff(option="ByLine", value=750e9) 76 | ws.abs_lines_per_speciesNormalization(option=normalization) 77 | ws.abs_lines_per_speciesTurnOffLineMixing() 78 | if lines_off: 79 | ws.abs_lines_per_speciesSetEmpty() 80 | 81 | # Create a frequency grid 82 | ws.VectorNLinSpace(ws.f_grid, fnum, fmin, fmax) 83 | 84 | # Throw away lines outside f_grid 85 | ws.abs_lines_per_speciesCompact() 86 | 87 | # Atmospheric settings 88 | ws.AtmosphereSet1D() 89 | ws.stokes_dim = 1 90 | 91 | # Setting the pressure, temperature and vmr 92 | ws.rtp_pressure = float(pressure) # [Pa] 93 | ws.rtp_temperature = float(temperature) # [K] 94 | ws.rtp_vmr = np.array([vmr]) # [VMR] 95 | ws.Touch(ws.rtp_nlte) 96 | 97 | # isotop 98 | ws.isotopologue_ratiosInitFromBuiltin() 99 | 100 | # Calculate absorption cross sections 101 | ws.lbl_checkedCalc() 102 | ws.propmat_clearsky_agenda_checked = 1 103 | ws.propmat_clearskyInit() 104 | ws.propmat_clearskyAddLines() 105 | ws.propmat_clearskyAddPredefined() 106 | 107 | # Convert abs coeff to cross sections on return 108 | number_density = pressure * vmr / (pyarts.arts.constants.k * temperature) 109 | 110 | return ( 111 | ws.f_grid.value.value.copy(), 112 | ws.propmat_clearsky.value.data.value[0, 0, :, 0].copy() / number_density, 113 | ) 114 | 115 | 116 | # %% Run module as script 117 | if __name__ == "__main__": 118 | import matplotlib.pyplot as plt 119 | 120 | pyarts.cat.download.retrieve(verbose=True, version='2.6.8') 121 | 122 | # Define parameters 123 | species = "N2O" 124 | pressure = 1000e2 125 | temperature = 300.0 126 | fmin = 1e12 127 | fmax = 1e14 128 | fnum = 10_000 129 | 130 | # Calculate absorption cross sections 131 | f_grid, absxsec = calculate_absxsec( 132 | species, pressure, temperature, fmin, fmax, fnum 133 | ) 134 | 135 | # Plot absorption cross sections 136 | fig, ax = plt.subplots() 137 | ax.semilogy(f_grid, absxsec) 138 | ax.set_xlabel("Frequency [Hz]") 139 | ax.set_ylabel(r"Abs. cross section [$\sf m^2$]") 140 | plt.show() 141 | -------------------------------------------------------------------------------- /exercises/02-line_shape/lineshape.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Advanced radiation and remote sensing\n", 8 | "\n", 9 | "\n", 10 | "Manfred Brath, Oliver Lemke\n", 11 | "\n", 12 | "## Exercise 2: line shape" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 1, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "%matplotlib widget\n", 22 | "\n", 23 | "import os\n", 24 | "import matplotlib.pyplot as plt\n", 25 | "import numpy as np\n", 26 | "from lineshape import tag2tex, calculate_absxsec, linewidth\n", 27 | "\n", 28 | "# make plot folder, if it is not existing\n", 29 | "os.makedirs(\"plots\", exist_ok=True)\n", 30 | "\n", 31 | "cache = None " 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": {}, 37 | "source": [ 38 | "The `calculate_absxsec` function calculates absorption cross sections and uses several keyword arguments as inputs, among other lineshape and normalization, see also the function definition in `lineshape.py` or the contextual help. \n", 39 | "\n", 40 | "### 1) Absorption cross section and coefficient\n", 41 | "Choose the $183\\,\\text{GHz}$-line of water vapor and perform calculations over a restricted frequency range for a number of different pressures between $10^5\\,\\text{Pa}$ and $10^1\\,\\text{Pa}$. Use the keyword arguments *fmin* and *fmax* to adjust the frequency range. Keep the temperature constant.\n", 42 | "\n", 43 | "* How does the shape of the spectral lines change?\n", 44 | "\n", 45 | "By now we investigated absorption in terms of the absorption cross-section\n", 46 | "$\\sigma$. Another widely used unit is the absorption coeffiction\n", 47 | "$\\alpha$. It takes the number concentration $n$ of the absorber\n", 48 | "into account:\n", 49 | "$$ \\alpha=n\\cdot\\sigma $$\n", 50 | "\n", 51 | "* Use the ideal gas law to calculate the number concentration.\n", 52 | "* How does the absorption coefficient in the line centre change, if pressure is changed?" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 2, 58 | "metadata": {}, 59 | "outputs": [], 60 | "source": [ 61 | "# Define parameters\n", 62 | "species = \"H2O\"\n", 63 | "temperature = 300\n", 64 | "pressure = 101325\n", 65 | "\n", 66 | "# Call ARTS to calculate absorption cross sections\n", 67 | "freq, abs_xsec, cache = calculate_absxsec(species, pressure, temperature, ws=cache)" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": null, 73 | "metadata": { 74 | "pycharm": { 75 | "name": "#%%\n" 76 | } 77 | }, 78 | "outputs": [], 79 | "source": [ 80 | "# example plotting code\n", 81 | "\n", 82 | "fig, ax = plt.subplots()\n", 83 | "ax.plot(freq / 1e9, abs_xsec)\n", 84 | "ax.set_xlim(freq.min() / 1e9, freq.max() / 1e9)\n", 85 | "ax.set_ylim(bottom=0)\n", 86 | "ax.set_xlabel(\"Frequency [GHz]\")\n", 87 | "ax.set_ylabel(r\"Abs. cross section [$\\sf m^2$]\")\n", 88 | "ax.set_title(f\"{tag2tex(species)} p:{pressure/100} hPa T:{temperature:0.0f} K\")\n", 89 | "\n", 90 | "#uncomment to save the figure\n", 91 | "# fig.savefig( # Save figure.\n", 92 | "# f\"plots/plot_xsec_{species}_{pressure:.0f}Pa_{temperature:.0f}K.pdf\"\n", 93 | "# )" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": null, 99 | "metadata": {}, 100 | "outputs": [], 101 | "source": [ 102 | "# Define parameters\n", 103 | "species = \"H2O\"\n", 104 | "temperature = 300\n", 105 | "pressure = 10132.5\n", 106 | "\n", 107 | "# Call ARTS to calculate absorption cross sections\n", 108 | "freq, abs_xsec, cache = calculate_absxsec(species, pressure, temperature, ws=cache)" 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": null, 114 | "metadata": {}, 115 | "outputs": [], 116 | "source": [ 117 | "# example plotting code\n", 118 | "\n", 119 | "fig, ax = plt.subplots()\n", 120 | "ax.plot(freq / 1e9, abs_xsec*pressure)\n", 121 | "ax.set_xlim(freq.min() / 1e9, freq.max() / 1e9)\n", 122 | "ax.set_ylim(bottom=0)\n", 123 | "ax.set_xlabel(\"Frequency [GHz]\")\n", 124 | "ax.set_ylabel(r\"Abs. cross section [$\\sf m^2$]\")\n", 125 | "ax.set_title(f\"{tag2tex(species)} p:{pressure/100} hPa T:{temperature:0.0f} K\")\n", 126 | "\n", 127 | "#uncomment to save the figure\n", 128 | "# fig.savefig( # Save figure.\n", 129 | "# f\"plots/plot_xsec_{species}_{pressure:.0f}Pa_{temperature:.0f}K.pdf\"\n", 130 | "# )" 131 | ] 132 | }, 133 | { 134 | "cell_type": "markdown", 135 | "metadata": {}, 136 | "source": [ 137 | "### 2) Linewidth\n", 138 | "The full-width at half maximum (FWHM) is a measure of the line width. Use the function `linewidth()`, which is imported from lineshape module, to calculate the FWHM for a given absorption spectrum.\n", 139 | "\n", 140 | "* Make a plot of this as a function of altitude in pressure coordinates for a microwave line and an infrared absorption line.\n", 141 | "* Hint: Use a low pressure when selecting a line." 142 | ] 143 | }, 144 | { 145 | "cell_type": "markdown", 146 | "metadata": {}, 147 | "source": [] 148 | } 149 | ], 150 | "metadata": { 151 | "kernelspec": { 152 | "display_name": "Python 3", 153 | "language": "python", 154 | "name": "python3" 155 | }, 156 | "language_info": { 157 | "codemirror_mode": { 158 | "name": "ipython", 159 | "version": 3 160 | }, 161 | "file_extension": ".py", 162 | "mimetype": "text/x-python", 163 | "name": "python", 164 | "nbconvert_exporter": "python", 165 | "pygments_lexer": "ipython3", 166 | "version": "3.12.7" 167 | }, 168 | "varInspector": { 169 | "cols": { 170 | "lenName": 16, 171 | "lenType": 16, 172 | "lenVar": 40 173 | }, 174 | "kernels_config": { 175 | "python": { 176 | "delete_cmd_postfix": "", 177 | "delete_cmd_prefix": "del ", 178 | "library": "var_list.py", 179 | "varRefreshCmd": "print(var_dic_list())" 180 | }, 181 | "r": { 182 | "delete_cmd_postfix": ") ", 183 | "delete_cmd_prefix": "rm(", 184 | "library": "var_list.r", 185 | "varRefreshCmd": "cat(var_dic_list()) " 186 | } 187 | }, 188 | "types_to_exclude": [ 189 | "module", 190 | "function", 191 | "builtin_function_or_method", 192 | "instance", 193 | "_Feature" 194 | ], 195 | "window_display": false 196 | } 197 | }, 198 | "nbformat": 4, 199 | "nbformat_minor": 4 200 | } 201 | -------------------------------------------------------------------------------- /exercises/02-line_shape/lineshape.py: -------------------------------------------------------------------------------- 1 | # %% Import modules and define functions 2 | """Calculate and plot absorption cross sections.""" 3 | import re 4 | 5 | import numpy as np 6 | import pyarts 7 | import scipy as sp 8 | 9 | 10 | def tag2tex(tag): 11 | """Replace all numbers in a species tag with LaTeX subscripts.""" 12 | return re.sub("([a-zA-Z]+)([0-9]+)", r"\1$_{\2}$", tag) 13 | 14 | 15 | def linewidth(f, a): 16 | """Calculate the full-width at half maximum (FWHM) of an absorption line. 17 | 18 | Parameters: 19 | f (ndarray): Frequency grid. 20 | a (ndarray): Line properties 21 | (e.g. absorption coefficients or cross-sections). 22 | 23 | Returns: 24 | float: Linewidth. 25 | 26 | Examples: 27 | >>> f = np.linspace(0, np.pi, 100) 28 | >>> a = np.sin(f)**2 29 | >>> linewidth(f, a) 30 | 1.571048056449009 31 | """ 32 | 33 | idx = np.argmax(a) 34 | 35 | if idx < 3 or idx > len(a) - 3: 36 | raise RuntimeError( 37 | "Maximum is located too near at the edge.\n" 38 | + "Could not found any peak. \n" 39 | + "Please adjust the frequency range." 40 | ) 41 | 42 | s = sp.interpolate.UnivariateSpline(f, a - np.max(a) / 2, s=0) 43 | 44 | zeros = s.roots() 45 | sidx = np.argsort((zeros - f[idx]) ** 2) 46 | 47 | if zeros.size == 2: 48 | logic = zeros[sidx] > f[idx] 49 | 50 | if np.sum(logic) == 1: 51 | fwhm = abs(np.diff(zeros[sidx])[0]) 52 | 53 | else: 54 | print( 55 | "I only found one half maxima.\n" 56 | + "You should adjust the frequency range to have more reliable results.\n" 57 | ) 58 | 59 | fwhm = abs(zeros[sidx[0]] - f[idx]) * 2 60 | 61 | elif zeros.size == 1: 62 | fwhm = abs(zeros[0] - f[idx]) * 2 63 | 64 | print( 65 | "I only found one half maxima.\n" 66 | + "You should adjust the frequency range to have more reliable results.\n" 67 | ) 68 | 69 | elif zeros.size > 2: 70 | sidx = sidx[0:2] 71 | 72 | logic = zeros[sidx] > f[idx] 73 | 74 | print( 75 | "It seems, that there are more than one peak" 76 | + " within the frequency range.\n" 77 | + "I stick to the maximum peak.\n" 78 | + "But I would suggest to adjust the frequevncy range. \n" 79 | ) 80 | 81 | if np.sum(logic) == 1: 82 | fwhm = abs(np.diff(zeros[sidx])[0]) 83 | 84 | else: 85 | print( 86 | "I only found one half maxima.\n" 87 | + "You should adjust the frequency range to have more reliable results.\n" 88 | ) 89 | 90 | fwhm = abs(zeros[sidx[0]] - f[idx]) * 2 91 | 92 | elif zeros.size == 0: 93 | raise RuntimeError( 94 | "Could not found any peak. :( \n" 95 | + "Probably, frequency range is too small.\n" 96 | ) 97 | 98 | return fwhm 99 | 100 | 101 | def calculate_absxsec( 102 | species="N2O", 103 | pressure=800e2, 104 | temperature=300.0, 105 | fmin=10e9, 106 | fmax=2000e9, 107 | fnum=10_000, 108 | lineshape="VP", 109 | normalization="VVH", 110 | verbosity=0, 111 | ws=None, 112 | vmr=0.05, 113 | lines_off=0 114 | ): 115 | """Calculate absorption cross sections. 116 | 117 | Parameters: 118 | species (str): Absorption species name. 119 | pressure (float): Atmospheric pressure [Pa]. 120 | temperature (float): Atmospheric temperature [K]. 121 | fmin (float): Minimum frequency [Hz]. 122 | fmax (float): Maximum frequency [Hz]. 123 | fnum (int): Number of frequency grid points. 124 | lineshape (str): Line shape model. 125 | Available options: 126 | DP - Doppler profile, 127 | LP - Lorentz profile, 128 | VP - Voigt profile, 129 | SDVP - Speed-dependent Voigt profile, 130 | HTP - Hartman-Tran profile. 131 | normalization (str): Line shape normalization factor. 132 | Available options: 133 | VVH - Van Vleck and Huber, 134 | VVW - Van Vleck and Weisskopf, 135 | RQ - Rosenkranz quadratic, 136 | None - No extra normalization. 137 | verbosity (int): Set ARTS verbosity (``0`` prevents all output). 138 | vmr (float): Volume mixing ratio. This is mainly important for the 139 | water vapor continua. 140 | lines_off (int): Switch off lines, if no contnua is selected, 141 | absorption will be zero. 142 | 143 | Returns: 144 | ndarray, ndarray: Frequency grid [Hz], Abs. cross sections [m^2] 145 | """ 146 | # Create ARTS workspace and load default settings 147 | reload = False 148 | pyarts.cat.download.retrieve(verbose=bool(verbosity)) 149 | 150 | if ws is not None: 151 | # check if species fits to cached species 152 | temp = str(ws.abs_species.value[0][0]) 153 | species_cache = temp.split("-")[0] 154 | 155 | if species != species_cache: 156 | print( 157 | f"Cached species:{species_cache} \n" 158 | f"Desired species:{species} \n" 159 | "As the chached and the desired species are different,\n" 160 | "I have to read in the catalog..." 161 | ) 162 | reload = True 163 | 164 | if ws is None or reload: 165 | ws = pyarts.workspace.Workspace(verbosity=0) 166 | ws.water_p_eq_agendaSet() 167 | ws.PlanetSet(option="Earth") 168 | 169 | ws.verbositySetScreen(ws.verbosity, verbosity) 170 | 171 | # We do not want to calculate the Jacobian Matrix 172 | ws.jacobianOff() 173 | 174 | # Define absorption species 175 | ws.abs_speciesSet(species=[species]) 176 | ws.abs_lines_per_speciesReadSpeciesSplitCatalog(basename="lines/") 177 | 178 | ws.abs_lines_per_speciesLineShapeType(option=lineshape) 179 | ws.abs_lines_per_speciesCutoff(option="ByLine", value=750e9) 180 | ws.abs_lines_per_speciesNormalization(option=normalization) 181 | ws.abs_lines_per_speciesTurnOffLineMixing() 182 | if lines_off: 183 | ws.abs_lines_per_speciesSetEmpty() 184 | 185 | # Create a frequency grid 186 | ws.VectorNLinSpace(ws.f_grid, fnum, fmin, fmax) 187 | 188 | # Throw away lines outside f_grid 189 | ws.abs_lines_per_speciesCompact() 190 | 191 | # Atmospheric settings 192 | ws.AtmosphereSet1D() 193 | ws.stokes_dim = 1 194 | 195 | # Setting the pressure, temperature and vmr 196 | ws.rtp_pressure = float(pressure) # [Pa] 197 | ws.rtp_temperature = float(temperature) # [K] 198 | ws.rtp_vmr = np.array([vmr]) # [VMR] 199 | ws.Touch(ws.rtp_nlte) 200 | 201 | # isotop 202 | ws.isotopologue_ratiosInitFromBuiltin() 203 | 204 | # Calculate absorption cross sections 205 | ws.lbl_checkedCalc() 206 | ws.propmat_clearsky_agenda_checked = 1 207 | ws.propmat_clearskyInit() 208 | ws.propmat_clearskyAddLines() 209 | ws.propmat_clearskyAddPredefined() 210 | 211 | # Convert abs coeff to cross sections on return 212 | number_density = pressure * vmr / (pyarts.arts.constants.k * temperature) 213 | 214 | return ( 215 | ws.f_grid.value.value.copy(), 216 | ws.propmat_clearsky.value.data.value[0, 0, :, 0].copy() / number_density, 217 | ws, 218 | ) 219 | 220 | 221 | # %% Run module as script 222 | if __name__ == "__main__": 223 | import matplotlib.pyplot as plt 224 | 225 | species = "H2O" 226 | temperature = 300 # [K] 227 | pressure = 101325 # [Pa] 228 | cache = None # cache for ARTS workspace 229 | 230 | # Call ARTS to calculate absorption cross sections 231 | freq, abs_xsec, cache = calculate_absxsec(species, pressure, temperature, fnum=1000, ws=cache) 232 | 233 | fig, ax = plt.subplots() 234 | ax.plot(freq, abs_xsec) 235 | ax.set_ylim(bottom=0) 236 | ax.set_xlabel("Frequency [GHz]") 237 | ax.set_ylabel(r"Abs. cross section [$\sf m^2$]") 238 | plt.show() 239 | 240 | # recall ARTS to calculate absorption cross sections at another pressure 241 | species = "H2O" 242 | temperature = 300 # [K] 243 | pressure = 10132.5 # [Pa] 244 | 245 | freq, abs_xsec, cache = calculate_absxsec(species, pressure, temperature, fnum=1000, ws=cache) 246 | 247 | fig, ax = plt.subplots() 248 | ax.plot(freq, abs_xsec) 249 | ax.set_ylim(bottom=0) 250 | ax.set_xlabel("Frequency [GHz]") 251 | ax.set_ylabel(r"Abs. cross section [$\sf m^2$]") 252 | plt.show() 253 | 254 | # recall ARTS to calculate absorption cross sections for another species 255 | species = "O3" 256 | temperature = 300 # [K] 257 | pressure = 10132.5 # [Pa] 258 | 259 | freq, abs_xsec, cache = calculate_absxsec(species, pressure, temperature, fnum=1000, ws=cache) 260 | 261 | fig, ax = plt.subplots() 262 | ax.plot(freq, abs_xsec) 263 | ax.set_ylim(bottom=0) 264 | ax.set_xlabel("Frequency [GHz]") 265 | ax.set_ylabel(r"Abs. cross section [$\sf m^2$]") 266 | plt.show() 267 | 268 | 269 | -------------------------------------------------------------------------------- /exercises/03-rtcalc/rtcalc.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Advanced radiation and remote sensing\n", 8 | "\n", 9 | "Manfred Brath, Oliver Lemke\n", 10 | "\n", 11 | "## Exercise 3: Atmospheric Brightness Temperature Spectra" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "%matplotlib widget\n", 21 | "\n", 22 | "import os\n", 23 | "import matplotlib.pyplot as plt\n", 24 | "import numpy as np\n", 25 | "from rtcalc import run_arts, tags2tex\n", 26 | "\n", 27 | "# make plot folder, if it is not existing\n", 28 | "os.makedirs(\"plots\", exist_ok=True)" 29 | ] 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "metadata": {}, 34 | "source": [ 35 | "### 1)\n", 36 | "The function `run_arts` simulates a brightness temperature and a atmospheric \n", 37 | "zenith opacity spectrum in the microwave spectral range for a midlatitude-summer \n", 38 | "atmosphere over a smooth and wet land surface for a desired height and looking \n", 39 | "direction.\n", 40 | "\n", 41 | "\n", 42 | "Run the function `run_arts` with the given values for height and direction. \n", 43 | "Ignore the brightness temperature for now and consider the zenith opacity spectrum \n", 44 | "to answer the following questions:\n", 45 | "\n", 46 | "* The spectrum includes four spectral lines. To which species do these lines \n", 47 | "belong? Play around with different absorption species.\n", 48 | "* We speak of window regions where the zenith opacity is below 1. Where are they?" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": null, 54 | "metadata": {}, 55 | "outputs": [], 56 | "source": [ 57 | "# Parameters\n", 58 | "species = [\"N2\", \"O2\", \"H2O\"]\n", 59 | "height = 0.0\n", 60 | "zenith_angle = 0.0\n", 61 | "\n", 62 | "freq, bt, od = run_arts(species, zenith_angle, height)" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": null, 68 | "metadata": {}, 69 | "outputs": [], 70 | "source": [ 71 | "HIGHLIGHT_FREQS = (22.3, 60.0, 118.8, 183.0)\n", 72 | "\n", 73 | "# Plot the zenith opacity with logarithmic scale on y axis\n", 74 | "fig, ax = plt.subplots()\n", 75 | "ax.semilogy(freq / 1e9, od)\n", 76 | "ax.axhline(1, linewidth=0.8, color=\"#b0b0b0\", zorder=0)\n", 77 | "ax.grid(True, axis=\"x\")\n", 78 | "ax.set_xticks(HIGHLIGHT_FREQS)\n", 79 | "ax.set_xlim(freq.min() / 1e9, freq.max() / 1e9)\n", 80 | "ax.set_xlabel(\"Frequency [GHz]\")\n", 81 | "ax.set_ylabel(\"Zenith opacity\")\n", 82 | "ax.set_title(f\"{', '.join(tags2tex(species))}\")\n", 83 | "fig.savefig(f\"plots/opacity_{'+'.join(species)}.pdf\")" 84 | ] 85 | }, 86 | { 87 | "cell_type": "markdown", 88 | "metadata": {}, 89 | "source": [ 90 | "### 2)\n", 91 | "Brightness temperature is a unit for intensity. It is the temperature of a \n", 92 | "blackbody that emits the same amount of intensity. Mathematically, the \n", 93 | "transformation between intensity in SI units and intensity in brightness \n", 94 | "temperature is done with the Planck formula. ARTS is capable to perform \n", 95 | "simulation in units of brightness temperature. Uncomment the code part for \n", 96 | "the second task. Investigate the brightness temperature spectra for \n", 97 | "different hypothetical sensors:\n", 98 | "\n", 99 | "* A ground-based sensor looking in the zenith direction.\n", 100 | "* A sensor on an airplane ($z=10\\,\\text{km}$) looking in the zenith direction.\n", 101 | "\n", 102 | "Consider both opacity and brightness temperatures to answer the following \n", 103 | "questions:\n", 104 | "\n", 105 | "* In plot (a), why do the lines near $60\\,\\text{GHz}$ and near $180\\,\\text{GHz}$ \n", 106 | "appear flat on top? \n", 107 | "* In plot (b), why is the line at $180\\,\\text{GHz}$ smaller than before? \n", 108 | "* Describe the difference between plots (a) and (b). What happens to the \n", 109 | "lines, what happens to the background? Can you explain what you\n", 110 | "see? " 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": null, 116 | "metadata": {}, 117 | "outputs": [], 118 | "source": [ 119 | "# Parameters\n", 120 | "species = [\"N2\", \"O2\", \"H2O\"]\n", 121 | "height = 0.0\n", 122 | "zenith_angle = 0.0\n", 123 | "\n", 124 | "freq, bt, od = run_arts(species, zenith_angle, height)" 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": null, 130 | "metadata": {}, 131 | "outputs": [], 132 | "source": [ 133 | "HIGHLIGHT_FREQS = (22.3, 60.0, 118.8, 183.0)\n", 134 | "\n", 135 | "# Plot the zenith opacity with logarithmic scale on y axis\n", 136 | "fig, ax = plt.subplots()\n", 137 | "ax.semilogy(freq / 1e9, od)\n", 138 | "ax.axhline(1, linewidth=0.8, color=\"#b0b0b0\", zorder=0)\n", 139 | "ax.grid(True, axis=\"x\")\n", 140 | "ax.set_xticks(HIGHLIGHT_FREQS)\n", 141 | "ax.set_xlim(freq.min() / 1e9, freq.max() / 1e9)\n", 142 | "ax.set_xlabel(\"Frequency [GHz]\")\n", 143 | "ax.set_ylabel(\"Zenith opacity\")\n", 144 | "ax.set_title(f\"{', '.join(tags2tex(species))}\")\n", 145 | "fig.savefig(f\"plots/opacity_{'+'.join(species)}.pdf\")\n", 146 | "\n", 147 | "# Plot the brightness temperature\n", 148 | "fig, ax = plt.subplots()\n", 149 | "ax.plot(freq / 1e9, bt)\n", 150 | "ax.grid(True)\n", 151 | "ax.set_xticks(HIGHLIGHT_FREQS)\n", 152 | "ax.set_xlim(freq.min() / 1e9, freq.max() / 1e9)\n", 153 | "ax.set_xlabel(\"Frequency [GHz]\")\n", 154 | "ax.set_ylabel(\"Brightness temperature [K]\")\n", 155 | "ax.set_title(f\"{', '.join(tags2tex(species))}, {height / 1e3} km, {zenith_angle}°\")\n", 156 | "fig.savefig(\n", 157 | " f\"plots/brightness_temperature_{'+'.join(species)}_{height / 1e3:.0f}km_{zenith_angle:.0f}deg.pdf\"\n", 158 | ")" 159 | ] 160 | }, 161 | { 162 | "cell_type": "markdown", 163 | "metadata": {}, 164 | "source": [ 165 | "### 3)\n", 166 | "Make the same calculation as in task 2 for a satellite sensor ($z=800\\,\\text{km}$) looking \n", 167 | "nadir (straight down).\n", 168 | "\n", 169 | "Answer following questions:\n", 170 | "\n", 171 | "* Explain the brightness temperature simulated in the window regions.\n", 172 | "* Why does the line at $22\\,\\text{GHz}$ look different from the others?\n", 173 | "* Investigate the the $\\text{O}_{2}$ line at $120\\,\\text{GHz}$. Perform an ARTS simulation\n", 174 | "focused around that frequency. Why does the shape close to the center of the $\\text{O}_{2}$ \n", 175 | "line at $120\\,\\text{GHz}$ looks so differently compared to the $183\\,\\text{GHz}$. " 176 | ] 177 | }, 178 | { 179 | "cell_type": "markdown", 180 | "metadata": {}, 181 | "source": [] 182 | } 183 | ], 184 | "metadata": { 185 | "kernelspec": { 186 | "display_name": "Python 3 (ipykernel)", 187 | "language": "python", 188 | "name": "python3" 189 | }, 190 | "language_info": { 191 | "codemirror_mode": { 192 | "name": "ipython", 193 | "version": 3 194 | }, 195 | "file_extension": ".py", 196 | "mimetype": "text/x-python", 197 | "name": "python", 198 | "nbconvert_exporter": "python", 199 | "pygments_lexer": "ipython3", 200 | "version": "3.12.5" 201 | }, 202 | "vscode": { 203 | "interpreter": { 204 | "hash": "6bd45fef6a38d15b43f43de43ba5066924911f80576952f97fb08adaede44831" 205 | } 206 | } 207 | }, 208 | "nbformat": 4, 209 | "nbformat_minor": 4 210 | } 211 | -------------------------------------------------------------------------------- /exercises/03-rtcalc/rtcalc.py: -------------------------------------------------------------------------------- 1 | # %% Import modules and define functions 2 | """Calculate and plot zenith opacity and brightness temperatures. """ 3 | import re 4 | import numpy as np 5 | import pyarts.workspace 6 | 7 | 8 | def tags2tex(tags): 9 | """Replace all numbers in every species tag with LaTeX subscripts.""" 10 | return [re.sub("([a-zA-Z]+)([0-9]+)", r"\1$_{\2}$", tag) for tag in tags] 11 | 12 | 13 | def run_arts( 14 | species, 15 | zenith_angle=0.0, 16 | height=0.0, 17 | fmin=10e9, 18 | fmax=250e9, 19 | fnum=1_000, 20 | verbosity=0 21 | ): 22 | """Perform a radiative transfer simulation. 23 | 24 | Parameters: 25 | species (list[str]): List of species tags. 26 | zenith_angle (float): Viewing angle [deg]. 27 | height (float): Sensor height [m]. 28 | fmin (float): Minimum frequency [Hz]. 29 | fmax (float): Maximum frequency [Hz]. 30 | fnum (int): Number of frequency grid points. 31 | 32 | Returns: 33 | ndarray, ndarray, ndarray: 34 | Frequency grid [Hz], Brightness temperature [K], Optical depth [1] 35 | """ 36 | ws = pyarts.workspace.Workspace(verbosity=verbosity) 37 | 38 | pyarts.cat.download.retrieve(verbose=bool(verbosity)) 39 | 40 | 41 | ws.water_p_eq_agendaSet() 42 | ws.PlanetSet(option="Earth") 43 | ws.iy_main_agendaSet(option="Emission") 44 | ws.ppath_agendaSet(option="FollowSensorLosPath") 45 | ws.ppath_step_agendaSet(option="GeometricPath") 46 | ws.iy_space_agendaSet(option="CosmicBackground") 47 | ws.iy_surface_agendaSet(option="UseSurfaceRtprop") 48 | 49 | ws.verbositySetScreen(ws.verbosity, verbosity) 50 | 51 | # Number of Stokes components to be computed 52 | ws.IndexSet(ws.stokes_dim, 1) 53 | 54 | # No jacobian calculation 55 | ws.jacobianOff() 56 | 57 | # Clearsky = No scattering 58 | ws.cloudboxOff() 59 | 60 | # A pressure grid rougly matching 0 to 80 km, in steps of 2 km. 61 | ws.VectorNLogSpace(ws.p_grid, 100, 1013e2, 10.0) 62 | 63 | ws.abs_speciesSet(species=species) 64 | 65 | # Read a line file and a matching small frequency grid 66 | ws.abs_lines_per_speciesReadSpeciesSplitCatalog(basename="lines/") 67 | 68 | ws.abs_lines_per_speciesCutoff(option="ByLine", value=750e9) 69 | ws.abs_lines_per_speciesTurnOffLineMixing() 70 | 71 | # Create a frequency grid 72 | ws.VectorNLinSpace(ws.f_grid, int(fnum), float(fmin), float(fmax)) 73 | 74 | # Throw away lines outside f_grid 75 | ws.abs_lines_per_speciesCompact() 76 | 77 | # Atmospheric scenario 78 | ws.AtmRawRead(basename="planets/Earth/Fascod/midlatitude-summer/midlatitude-summer") 79 | 80 | # Non reflecting surface 81 | ws.VectorSetConstant(ws.surface_scalar_reflectivity, 1, 0.1) 82 | ws.surface_rtprop_agendaSet(option="Specular_NoPol_ReflFix_SurfTFromt_surface") 83 | 84 | # No sensor properties 85 | ws.sensorOff() 86 | 87 | # We select here to use Planck brightness temperatures 88 | ws.StringSet(ws.iy_unit, "PlanckBT") 89 | 90 | # Extract optical depth as auxiliary variables 91 | ws.ArrayOfStringSet(ws.iy_aux_vars, ["Optical depth"]) 92 | 93 | # Atmosphere and surface 94 | ws.AtmosphereSet1D() 95 | ws.AtmFieldsCalc() 96 | ws.Extract(ws.z_surface, ws.z_field, 0) 97 | ws.Extract(ws.t_surface, ws.t_field, 0) 98 | 99 | # Definition of sensor position and line of sight (LOS) 100 | ws.MatrixSet(ws.sensor_pos, np.array([[height]])) 101 | ws.MatrixSet(ws.sensor_los, np.array([[zenith_angle]])) 102 | 103 | # Perform RT calculations 104 | ws.propmat_clearsky_agendaAuto() 105 | ws.lbl_checkedCalc() 106 | ws.propmat_clearsky_agenda_checkedCalc() 107 | ws.atmfields_checkedCalc() 108 | ws.atmgeom_checkedCalc() 109 | ws.cloudbox_checkedCalc() 110 | ws.sensor_checkedCalc() 111 | ws.yCalc() 112 | 113 | return ( 114 | ws.f_grid.value[:].copy(), 115 | ws.y.value[:].copy(), 116 | ws.y_aux.value[0][:].copy(), 117 | ) 118 | 119 | 120 | # %% Run module as script 121 | if __name__ == "__main__": 122 | import matplotlib.pyplot as plt 123 | 124 | # pyarts.cat.download.retrieve(verbose=True) 125 | 126 | species = ["N2", "O2", "H2O"] 127 | height = 0.0 # m 128 | zenith_angle = 0.0 # deg 129 | 130 | # Run the radiative transfer simulation to get the frequency grid, 131 | # brightness temperature and optical depth 132 | freq, bt, od = run_arts(species, zenith_angle, height) 133 | 134 | # Plot the zenith opacity with logarithmic scale on y axis 135 | fig, ax = plt.subplots() 136 | ax.semilogy(freq, od) 137 | ax.set_xlabel("Frequency [Hz]") 138 | ax.set_ylabel("Zenith opacity") 139 | 140 | # Plot the brightness temperature 141 | fig, ax = plt.subplots() 142 | ax.plot(freq, bt) 143 | ax.set_xlabel("Frequency [GHz]") 144 | ax.set_ylabel("Brightness temperature [K]") 145 | 146 | plt.show() 147 | -------------------------------------------------------------------------------- /exercises/04-jacobian/jacobian.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Advanced radiation and remote sensing\n", 8 | "\n", 9 | "\n", 10 | "Manfred Brath, Oliver Lemke\n", 11 | "\n", 12 | "## Exercise 4: Jacobian and opacity rule" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "%matplotlib widget\n", 22 | "\n", 23 | "import os\n", 24 | "import matplotlib.pyplot as plt\n", 25 | "import numpy as np\n", 26 | "from pyarts import xml\n", 27 | "from jacobian import (calc_jacobians, plot_brightness_temperature,\n", 28 | " plot_jacobian, plot_opacity, plot_opacity_profile,\n", 29 | " argclosest)\n", 30 | "\n", 31 | "os.makedirs(\"plots\", exist_ok=True)" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": {}, 37 | "source": [ 38 | "### 1)\n", 39 | "Run the next cells to calculate the brightness temperature spectrum in \n", 40 | "nadir direction and the zenith opacity around the $183\\,\\text{GHz}$ line \n", 41 | "of water vapor for a midlatitude summer atmosphere. Answer following \n", 42 | "question:\n", 43 | "\n", 44 | "* Are there window regions?\n", 45 | "\n", 46 | "The atmospheric temperature profile for the calculation was:\n", 47 | "\n", 48 | "|**Pressure** $\\left[\\text{hPa}\\right]$|**Temperature** $\\left[\\text{K}\\right]$ | **Altitude** $\\left[\\text{km}\\right]$\n", 49 | "|--- |--- |---\n", 50 | "|1013.0 | 294.2 | 0\n", 51 | "|902.0 | 289.7 | 1\n", 52 | "|802.0 | 285.2 | 2\n", 53 | "|710.0 | 279.2 | 3\n", 54 | "|628.0 | 273.2 | 4\n", 55 | "|554.0 | 267.2 | 5\n", 56 | "|487.0 | 261.2 | 6\n", 57 | "|426.0 | 254.7 | 7\n", 58 | "|372.0 | 248.2 | 8\n", 59 | "|324.0 | 241.7 | 9\n", 60 | "|281.0 | 235.3 | 10\n", 61 | "|243.0 | 228.8 | 11\n", 62 | "|209.0 | 222.3 | 12\n", 63 | "|179.0 | 215.8 | 13\n", 64 | "|153.0 | 215.7 | 14\n", 65 | "|130.0 | 215.7 | 15\n", 66 | "|111.0 | 215.7 | 16\n", 67 | "|95.0 | 215.7 | 17\n", 68 | "|81.2 | 216.8 | 18\n", 69 | "|69.5 | 217.9 | 19\n", 70 | "|59.5 | 219.2 | 20\n", 71 | "\n", 72 | "Consider the table and answer following questions:\n", 73 | "\n", 74 | "* From which altitude does the radiation at the peak of the line ($\\approx183\\,\\text{GHz}$) \n", 75 | "originate? \n", 76 | "\n", 77 | "* From which altitude does the radiation at the wing ($150\\,\\text{GHz}$) originate? \n" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": null, 83 | "metadata": {}, 84 | "outputs": [], 85 | "source": [ 86 | "# Calculate Jacobians (ARTS)\n", 87 | "jacobian_quantity = \"H2O\"\n", 88 | "calc_jacobians(jacobian_quantity=jacobian_quantity)\n", 89 | "\n", 90 | "# read in everything\n", 91 | "freq = np.array(xml.load(\"results/f_grid.xml\"))\n", 92 | "tau = np.array(xml.load(\"results/optical_thickness.xml\"))\n", 93 | "bt = np.array(xml.load(\"results/y.xml\"))\n", 94 | "jac = np.array(xml.load(\"results/jacobian.xml\"))\n", 95 | "alt = np.array(xml.load(\"results/z_field.xml\")).ravel()\n", 96 | "jac /= np.gradient(alt / 1000) # normalize by layer thickness in km" 97 | ] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "execution_count": null, 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "# select frequency\n", 106 | "highlight_frequency = None # Hz\n", 107 | "\n", 108 | "if highlight_frequency is None:\n", 109 | " fig, (ax0, ax1) = plt.subplots(ncols=2)\n", 110 | " plot_brightness_temperature(freq, bt, ax=ax0)\n", 111 | " plot_opacity(freq, tau, ax=ax1)\n", 112 | " freq_ind = None\n", 113 | "else:\n", 114 | " fig, ((ax0, ax1), (ax2, ax3)) = plt.subplots(2, 2)\n", 115 | " plot_brightness_temperature(freq, bt, where=highlight_frequency, ax=ax0)\n", 116 | " plot_opacity(freq, tau, where=highlight_frequency, ax=ax1)\n", 117 | " freq_ind = argclosest(freq, highlight_frequency)\n", 118 | " plot_jacobian(\n", 119 | " alt, jac[freq_ind, :], jacobian_quantity=jacobian_quantity, ax=ax2\n", 120 | " )\n", 121 | " plot_opacity_profile(alt, tau[:, freq_ind], ax=ax3)\n", 122 | "\n", 123 | "fig.tight_layout()\n", 124 | "fig.savefig(f\"plots/jacobians-{freq_ind}.pdf\")" 125 | ] 126 | }, 127 | { 128 | "cell_type": "markdown", 129 | "metadata": {}, 130 | "source": [ 131 | "### 2)\n", 132 | "Change the variable `highlight_frequency` from `None` to any desired \n", 133 | "frequency in $[\\text{Hz}]$ within the range of the brightness temperature spectrum \n", 134 | "of task 1 and rerun previous cell. This will calculate the water vapor Jacobian and \n", 135 | "the opacity $\\tau$ between the top of the atmosphere $z_{TOA}$ and altitude $z$ for the \n", 136 | "selected frequency. Additionally, a circle marks the selected frequency in the plot \n", 137 | "of the brightness temperature spectrum and in the plot of the zenith opacity. \n", 138 | "\n", 139 | "Write down the altitude of the Jacobian peak and the altitude where the \n", 140 | "opacity reaches 1 for some different frequencies and answer following \n", 141 | "questions:\n", 142 | "\n", 143 | "* Why are the altitude where the opacity reaches 1 and the altitude of \n", 144 | "the Jacobian peak not exactly the same?\n", 145 | "* Why are the Jacobians sometimes positive and sometimes negative?" 146 | ] 147 | }, 148 | { 149 | "cell_type": "markdown", 150 | "metadata": {}, 151 | "source": [] 152 | } 153 | ], 154 | "metadata": { 155 | "kernelspec": { 156 | "display_name": "Python 3 (ipykernel)", 157 | "language": "python", 158 | "name": "python3" 159 | }, 160 | "language_info": { 161 | "codemirror_mode": { 162 | "name": "ipython", 163 | "version": 3 164 | }, 165 | "file_extension": ".py", 166 | "mimetype": "text/x-python", 167 | "name": "python", 168 | "nbconvert_exporter": "python", 169 | "pygments_lexer": "ipython3", 170 | "version": "3.12.5" 171 | }, 172 | "vscode": { 173 | "interpreter": { 174 | "hash": "6bd45fef6a38d15b43f43de43ba5066924911f80576952f97fb08adaede44831" 175 | } 176 | } 177 | }, 178 | "nbformat": 4, 179 | "nbformat_minor": 4 180 | } 181 | -------------------------------------------------------------------------------- /exercises/04-jacobian/jacobian.py: -------------------------------------------------------------------------------- 1 | # %% Import modules and define functions 2 | """Calculate and plot clear-sky Jacobians.""" 3 | import re 4 | from os import makedirs 5 | 6 | import matplotlib.pyplot as plt 7 | import numpy as np 8 | import pyarts 9 | from matplotlib.transforms import blended_transform_factory 10 | 11 | 12 | def argclosest(array, value): 13 | """Returns the index in ``array`` which is closest to ``value``.""" 14 | return np.abs(array - value).argmin() 15 | 16 | 17 | def tag2tex(tag): 18 | """Replace all numbers in a species tag with LaTeX subscripts.""" 19 | return re.sub("([a-zA-Z]+)([0-9]+)", r"\1$_{\2}$", tag) 20 | 21 | 22 | def plot_brightness_temperature(frequency, y, where=None, ax=None): 23 | if ax is None: 24 | ax = plt.gca() 25 | 26 | ax.plot(frequency / 1e9, y) 27 | ax.set_xlim(frequency.min() / 1e9, frequency.max() / 1e9) 28 | ax.set_xlabel("Frequency [GHz]") 29 | ax.set_ylabel(r"$T\mathrm{_B}$ [K]") 30 | 31 | if where is not None: 32 | freq_ind = argclosest(frequency, where) 33 | (l,) = ax.plot( 34 | frequency[freq_ind] / 1e9, y[freq_ind], marker="o", color="tab:red" 35 | ) 36 | ax.text( 37 | 0.05, 38 | 0.9, 39 | f"{frequency[freq_ind]/1e9:.2f} GHz", 40 | size="small", 41 | color=l.get_color(), 42 | transform=ax.transAxes, 43 | ) 44 | 45 | 46 | def plot_opacity(frequency, opacity, where=None, ax=None): 47 | if ax is None: 48 | ax = plt.gca() 49 | 50 | ax.semilogy(frequency / 1e9, opacity[-1, :]) 51 | ax.set_xlim(frequency.min() / 1e9, frequency.max() / 1e9) 52 | ax.axhline(1, color="darkgrey", linewidth=0.8, zorder=-1) 53 | ax.set_xlabel("Frequency [GHz]") 54 | ax.set_ylabel("Zenith Opacity") 55 | 56 | if where is not None: 57 | freq_ind = argclosest(frequency, where) 58 | ax.plot( 59 | frequency[freq_ind] / 1e9, 60 | opacity[-1, freq_ind], 61 | marker="o", 62 | color="tab:red", 63 | ) 64 | 65 | 66 | def plot_jacobian(height, jacobian, jacobian_quantity, ax=None): 67 | if ax is None: 68 | ax = plt.gca() 69 | 70 | ax.plot(jacobian, height / 1000.0) 71 | ax.set_ylim(0.4, 20) 72 | unit = "K/K/km" if jacobian_quantity == "T" else "K/1/km" 73 | ax.set_xlabel(f"{tag2tex(jacobian_quantity)} Jacobian [{unit}]") 74 | ax.set_ylabel("$z$ [km]") 75 | jac_peak = height[np.abs(jacobian).argmax()] / 1000.0 76 | trans = blended_transform_factory(ax.transAxes, ax.transData) 77 | lh = ax.axhline(jac_peak, color="black", zorder=3) 78 | ax.text( 79 | 1, 80 | jac_peak, 81 | f"{jac_peak:.2f} km", 82 | size="small", 83 | ha="right", 84 | va="bottom", 85 | color=lh.get_color(), 86 | bbox={"color": "white", "alpha": 0.5}, 87 | zorder=2, 88 | transform=trans, 89 | ) 90 | 91 | 92 | def plot_opacity_profile(height, opacity, ax=None): 93 | if ax is None: 94 | ax = plt.gca() 95 | 96 | ax.semilogx(opacity, height[::-1] / 1000.0) 97 | ax.set_xlim(1e-8, 1e2) 98 | ax.set_xticks(10.0 ** np.arange(-8, 4, 2)) 99 | ax.set_xlabel(r"Opacity $\tau(z, z_\mathrm{TOA})$") 100 | ax.set_ylim(0.4, 20) 101 | ax.set_ylabel("$z$ [km]") 102 | 103 | try: 104 | tau1 = height[::-1][np.where(opacity >= 1)[0][0]] 105 | except IndexError: 106 | pass 107 | else: 108 | tau1 /= 1000 109 | trans = blended_transform_factory(ax.transAxes, ax.transData) 110 | lh = ax.axhline(tau1, color="black", zorder=3) 111 | ax.text( 112 | 0.05, 113 | tau1, 114 | f"{tau1:.2f} km", 115 | va="bottom", 116 | size="small", 117 | color=lh.get_color(), 118 | bbox={"color": "white", "alpha": 0.5}, 119 | zorder=2, 120 | transform=trans, 121 | ) 122 | ax.axvline(1, color="darkgrey", linewidth=0.8, zorder=-1) 123 | 124 | 125 | def calc_jacobians( 126 | jacobian_quantity="H2O", fmin=150e9, fmax=200e9, fnum=200, verbosity=0, 127 | ): 128 | """Calculate jacobians for a given species and frequency range and 129 | save the result as arts-xml-files. 130 | 131 | Parameters: 132 | jacobian_quantity (str): Species tag for which to calculate the 133 | jacobian. 134 | fmin (float): Minimum frequency [Hz]. 135 | fmax (float): Maximum frequency [Hz]. 136 | fnum (int): Number of frequency grid points. 137 | verbosity (int): ARTS verbosity level. 138 | 139 | """ 140 | 141 | pyarts.cat.download.retrieve(verbose=bool(verbosity)) 142 | makedirs("results", exist_ok=True) 143 | 144 | ws = pyarts.workspace.Workspace(verbosity=verbosity) 145 | ws.water_p_eq_agendaSet() 146 | ws.PlanetSet(option="Earth") 147 | ws.verbositySetScreen(ws.verbosity, verbosity) 148 | 149 | # Modified emission agenda to store internally calculated optical thickness. 150 | @pyarts.workspace.arts_agenda 151 | def iy_main_agenda__EmissionOpacity(ws): 152 | ws.ppathCalc() 153 | ws.iyEmissionStandard() 154 | ws.ppvar_optical_depthFromPpvar_trans_cumulat() 155 | ws.Touch(ws.geo_pos) 156 | ws.WriteXML("ascii", ws.ppvar_optical_depth, "results/optical_thickness.xml") 157 | ws.WriteXML("ascii", ws.ppvar_p, "results/ppvar_p.xml") 158 | 159 | ws.iy_main_agenda = iy_main_agenda__EmissionOpacity 160 | 161 | # cosmic background radiation 162 | ws.iy_space_agendaSet(option="CosmicBackground") 163 | 164 | # standard surface agenda (i.e., make use of surface_rtprop_agenda) 165 | ws.iy_surface_agendaSet(option="UseSurfaceRtprop") 166 | 167 | # sensor-only path 168 | ws.ppath_agendaSet(option="FollowSensorLosPath") 169 | 170 | # no refraction 171 | ws.ppath_step_agendaSet(option="GeometricPath") 172 | 173 | # Number of Stokes components to be computed 174 | ws.IndexSet(ws.stokes_dim, 1) 175 | 176 | ######################################################################### 177 | 178 | # A pressure grid rougly matching 0 to 80 km, in steps of 2 km. 179 | ws.VectorNLogSpace(ws.p_grid, 200, 1013e2, 10.0) 180 | 181 | # Definition of species: 182 | # you can take out and add again one of the species to see what effect it has 183 | # on radiative transfer in the ws.atmosphere. 184 | abs_species = {"N2", "O2", "H2O"} 185 | if jacobian_quantity != "T": 186 | abs_species.add(jacobian_quantity) 187 | 188 | ws.abs_speciesSet(species=list(abs_species)) 189 | 190 | # Create a frequency grid 191 | ws.VectorNLinSpace(ws.f_grid, int(fnum), float(fmin), float(fmax)) 192 | 193 | # Read a line file and a matching small frequency grid 194 | ws.abs_lines_per_speciesReadSpeciesSplitCatalog(basename="lines/") 195 | 196 | ws.abs_lines_per_speciesCutoff(option="ByLine", value=750e9) 197 | ws.abs_lines_per_speciesTurnOffLineMixing() 198 | 199 | # Create a frequency grid 200 | ws.VectorNLinSpace(ws.f_grid, int(fnum), float(fmin), float(fmax)) 201 | 202 | # Throw away lines outside f_grid 203 | ws.abs_lines_per_speciesCompact() 204 | 205 | # Atmospheric scenario 206 | ws.AtmRawRead(basename="planets/Earth/Fascod/midlatitude-summer/midlatitude-summer") 207 | 208 | # Non reflecting surface 209 | ws.VectorSetConstant(ws.surface_scalar_reflectivity, 1, 0.4) 210 | ws.surface_rtprop_agendaSet(option="Specular_NoPol_ReflFix_SurfTFromt_surface") 211 | 212 | # We select here to use Planck brightness temperatures 213 | ws.StringSet(ws.iy_unit, "PlanckBT") 214 | 215 | ######################################################################### 216 | 217 | # Atmosphere and surface 218 | ws.AtmosphereSet1D() 219 | ws.AtmFieldsCalc(interp_order=3) 220 | ws.Extract(ws.z_surface, ws.z_field, 0) 221 | ws.Extract(ws.t_surface, ws.t_field, 0) 222 | 223 | # Definition of sensor position and line of sight (LOS) 224 | ws.VectorSet(ws.rte_pos, np.array([800e3])) 225 | ws.MatrixSet(ws.sensor_pos, np.array([[800e3]])) 226 | ws.MatrixSet(ws.sensor_los, np.array([[180]])) 227 | ws.VectorSet(ws.rte_los, np.array([180])) 228 | ws.sensorOff() 229 | 230 | # Jacobian calculation 231 | ws.jacobianInit() 232 | if jacobian_quantity == "T": 233 | ws.jacobianAddTemperature(g1=ws.p_grid, g2=ws.lat_grid, g3=ws.lon_grid) 234 | else: 235 | ws.jacobianAddAbsSpecies( 236 | g1=ws.p_grid, 237 | g2=ws.lat_grid, 238 | g3=ws.lon_grid, 239 | species=jacobian_quantity, 240 | unit="rel", 241 | ) 242 | ws.jacobianClose() 243 | 244 | # Clearsky = No scattering 245 | ws.cloudboxOff() 246 | 247 | # Perform RT calculations 248 | ws.propmat_clearsky_agendaAuto() 249 | ws.lbl_checkedCalc() 250 | ws.propmat_clearsky_agenda_checkedCalc() 251 | ws.atmfields_checkedCalc() 252 | ws.atmgeom_checkedCalc() 253 | ws.cloudbox_checkedCalc() 254 | ws.sensor_checkedCalc() 255 | 256 | ws.yCalc() 257 | 258 | # Write output 259 | ws.WriteXML("ascii", ws.f_grid, "results/f_grid.xml") 260 | ws.WriteXML("ascii", ws.jacobian, "results/jacobian.xml") 261 | ws.WriteXML("ascii", ws.z_field, "results/z_field.xml") 262 | ws.WriteXML("ascii", ws.y, "results/y.xml") 263 | 264 | 265 | # %% Calculate and plot Jacobians 266 | if __name__ == "__main__": 267 | # Calculate Jacobians (ARTS) 268 | jacobian_quantity = "H2O" 269 | calc_jacobians(jacobian_quantity=jacobian_quantity) 270 | 271 | # read in data 272 | freq = np.array(pyarts.xml.load("results/f_grid.xml")) 273 | jac = np.array(pyarts.xml.load("results/jacobian.xml")) 274 | alt = np.array(pyarts.xml.load("results/z_field.xml")).ravel() 275 | jac /= np.gradient(alt / 1000) # normalize by layer thickness in km 276 | 277 | # plot jacobian 278 | highlight_frequency = 180e9 # Hz 279 | fig, ax = plt.subplots() 280 | freq_ind = argclosest(freq, highlight_frequency) 281 | plot_jacobian(alt, jac[freq_ind, :], jacobian_quantity=jacobian_quantity, ax=ax) 282 | 283 | plt.show() 284 | -------------------------------------------------------------------------------- /exercises/05-inversion/H2Orad.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmtools/arts-lectures/3dc28d627560c60ede546a59f24c518d093dff08/exercises/05-inversion/H2Orad.jpg -------------------------------------------------------------------------------- /exercises/05-inversion/input/measurement.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 50000000000 6 | 50100000000 7 | 50200000000 8 | 50300000000 9 | 50400000000 10 | 50500000000 11 | 50600000000 12 | 50700000000 13 | 50800000000 14 | 50900000000 15 | 51000000000 16 | 51100000000 17 | 51200000000 18 | 51300000000 19 | 51400000000 20 | 51500000000 21 | 51600000000 22 | 51700000000 23 | 51800000000 24 | 51900000000 25 | 52000000000 26 | 52100000000 27 | 52200000000 28 | 52300000000 29 | 52400000000 30 | 52500000000 31 | 52600000000 32 | 52700000000 33 | 52800000000 34 | 52900000000 35 | 53000000000 36 | 53100000000 37 | 53200000000 38 | 53300000000 39 | 53400000000 40 | 53500000000 41 | 53600000000 42 | 53700000000 43 | 53800000000 44 | 53900000000 45 | 54000000000 46 | 54100000000 47 | 54200000000 48 | 54300000000 49 | 54400000000 50 | 54500000000 51 | 54600000000 52 | 54700000000 53 | 54800000000 54 | 54900000000 55 | 55000000000 56 | 55100000000 57 | 55200000000 58 | 55300000000 59 | 55400000000 60 | 55500000000 61 | 55600000000 62 | 55700000000 63 | 55800000000 64 | 55900000000 65 | 56000000000 66 | 56100000000 67 | 56200000000 68 | 56300000000 69 | 56400000000 70 | 56500000000 71 | 56600000000 72 | 56700000000 73 | 56800000000 74 | 56900000000 75 | 57000000000 76 | 57100000000 77 | 57200000000 78 | 57300000000 79 | 57400000000 80 | 57500000000 81 | 57600000000 82 | 57700000000 83 | 57800000000 84 | 57900000000 85 | 58000000000 86 | 58100000000 87 | 58200000000 88 | 58300000000 89 | 58400000000 90 | 58500000000 91 | 58600000000 92 | 58700000000 93 | 58800000000 94 | 58900000000 95 | 59000000000 96 | 59100000000 97 | 59200000000 98 | 59300000000 99 | 59400000000 100 | 59500000000 101 | 59600000000 102 | 59700000000 103 | 59800000000 104 | 59900000000 105 | 60000000000 106 | 107 | 108 | 8.38988182159685 109 | 9.20986299420447 110 | 8.83299755684208 111 | 9.15297450686034 112 | 9.39077345288096 113 | 9.45244134240556 114 | 9.42684406789398 115 | 10.1193202769042 116 | 10.2683747350239 117 | 9.97362593555777 118 | 11.5944140796949 119 | 11.1952720449821 120 | 10.9894158696077 121 | 11.7196323414726 122 | 11.8463587195616 123 | 14.3248547778704 124 | 12.9662767779138 125 | 12.6758611958006 126 | 13.6070222025976 127 | 14.6531389242255 128 | 17.1362117230122 129 | 15.9581040664949 130 | 16.2269108495007 131 | 16.2956064199011 132 | 18.1965381382876 133 | 22.46106789282 134 | 22.5812767488472 135 | 21.5579476481772 136 | 22.8653201964649 137 | 25.7560216103673 138 | 32.097634785864 139 | 39.7852325366991 140 | 32.396370812101 141 | 32.6245509006426 142 | 36.6457038321752 143 | 47.5476669807945 144 | 100.279869428124 145 | 54.6169621963311 146 | 51.5324425456793 147 | 56.6937974838673 148 | 70.6516437125308 149 | 109.794699725964 150 | 97.400659492921 151 | 83.9248120942002 152 | 86.3928526519532 153 | 101.072511150644 154 | 135.417823054258 155 | 167.886124563677 156 | 132.209151928673 157 | 126.659445194322 158 | 137.398614192878 159 | 163.615179374814 160 | 209.906159947615 161 | 189.791417726047 162 | 174.190381139581 163 | 175.541507155752 164 | 190.715390807859 165 | 212.559892767552 166 | 226.810523096463 167 | 216.165764880204 168 | 214.323205408745 169 | 219.692042322321 170 | 226.237191599281 171 | 228.515007900191 172 | 228.178844814579 173 | 225.920186684296 174 | 223.324224636851 175 | 222.734932090952 176 | 226.161927574621 177 | 228.316537893037 178 | 228.785635202119 179 | 227.715491533668 180 | 226.348396072693 181 | 226.287563863247 182 | 227.37256267871 183 | 228.96019693159 184 | 229.718288386687 185 | 229.185625349683 186 | 228.58201193542 187 | 228.45574845385 188 | 228.579604222609 189 | 229.060408565345 190 | 229.110024519198 191 | 229.858662213182 192 | 229.530701629829 193 | 229.637600650818 194 | 229.262343860025 195 | 229.62576947705 196 | 229.336267812089 197 | 229.528742367022 198 | 229.48839580211 199 | 229.737223826999 200 | 229.867374801096 201 | 229.884088344261 202 | 229.687032592504 203 | 229.623851500732 204 | 229.55799568187 205 | 229.436975508594 206 | 229.296476995049 207 | 229.558714255526 208 | 229.478949715291 209 | 210 | 211 | 212 | -------------------------------------------------------------------------------- /exercises/05-inversion/input/x_apriori.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | "z" 6 | "T" 7 | "abs_species-H2O" 8 | 9 | 10 | 2.7384196e+04 11 | 2.0535250e+04 12 | 1.5399265e+04 13 | 1.1547820e+04 14 | 8.6596432e+03 15 | 6.4938163e+03 16 | 4.8696753e+03 17 | 3.6517413e+03 18 | 2.7384196e+03 19 | 2.0535250e+03 20 | 1.5399265e+03 21 | 1.1547820e+03 22 | 8.6596432e+02 23 | 6.4938163e+02 24 | 4.8696753e+02 25 | 3.6517413e+02 26 | 2.7384196e+02 27 | 2.0535250e+02 28 | 1.5399265e+02 29 | 1.1547820e+02 30 | 8.6596432e+01 31 | 6.4938163e+01 32 | 4.8696753e+01 33 | 3.6517413e+01 34 | 2.7384196e+01 35 | 2.0535250e+01 36 | 1.5399265e+01 37 | 1.1547820e+01 38 | 8.6596432e+00 39 | 6.4938163e+00 40 | 4.8696753e+00 41 | 3.6517413e+00 42 | 2.7384196e+00 43 | 2.0535250e+00 44 | 1.5399265e+00 45 | 1.1547820e+00 46 | 8.6596432e-01 47 | 6.4938163e-01 48 | 4.8696753e-01 49 | 3.6517413e-01 50 | 2.7384196e-01 51 | 2.0535250e-01 52 | 1.5399265e-01 53 | 1.1547820e-01 54 | 8.6596432e-02 55 | 6.4938163e-02 56 | 4.8696753e-02 57 | 3.6517413e-02 58 | 59 | 60 | 61 | 62 | 63 | 64 | 9.5371233e+03 65 | 1.1382265e+04 66 | 1.3226469e+04 67 | 1.5074930e+04 68 | 1.6925013e+04 69 | 1.8773849e+04 70 | 2.0620446e+04 71 | 2.2464313e+04 72 | 2.4306930e+04 73 | 2.6151285e+04 74 | 2.8003650e+04 75 | 2.9871923e+04 76 | 3.1764466e+04 77 | 3.3691008e+04 78 | 3.5657752e+04 79 | 3.7668282e+04 80 | 3.9724272e+04 81 | 4.1825906e+04 82 | 4.3969391e+04 83 | 4.6144987e+04 84 | 4.8339003e+04 85 | 5.0533932e+04 86 | 5.2712183e+04 87 | 5.4860289e+04 88 | 5.6972066e+04 89 | 5.9048289e+04 90 | 6.1093413e+04 91 | 6.3114087e+04 92 | 6.5116671e+04 93 | 6.7104834e+04 94 | 6.9081294e+04 95 | 7.1047847e+04 96 | 7.3004650e+04 97 | 7.4950177e+04 98 | 7.6881981e+04 99 | 7.8796439e+04 100 | 8.0689283e+04 101 | 8.2555200e+04 102 | 8.4385926e+04 103 | 8.6172850e+04 104 | 8.7912972e+04 105 | 8.9606672e+04 106 | 9.1257429e+04 107 | 9.2873933e+04 108 | 9.4471140e+04 109 | 9.6062199e+04 110 | 9.7659081e+04 111 | 9.9272672e+04 112 | 2.2580964e+02 113 | 2.1665000e+02 114 | 2.1665000e+02 115 | 2.1665000e+02 116 | 2.1665000e+02 117 | 2.1665000e+02 118 | 2.1741329e+02 119 | 2.1928865e+02 120 | 2.2116401e+02 121 | 2.2303937e+02 122 | 2.2491473e+02 123 | 2.2679009e+02 124 | 2.2869840e+02 125 | 2.3457378e+02 126 | 2.4044916e+02 127 | 2.4632454e+02 128 | 2.5219993e+02 129 | 2.5807531e+02 130 | 2.6395069e+02 131 | 2.6982607e+02 132 | 2.7065000e+02 133 | 2.7004918e+02 134 | 2.6435061e+02 135 | 2.5865204e+02 136 | 2.5295348e+02 137 | 2.4725491e+02 138 | 2.4155634e+02 139 | 2.3585778e+02 140 | 2.3015921e+02 141 | 2.2446064e+02 142 | 2.1876208e+02 143 | 2.1370695e+02 144 | 2.1031956e+02 145 | 2.0693217e+02 146 | 2.0354478e+02 147 | 2.0015739e+02 148 | 1.9677000e+02 149 | 1.9338261e+02 150 | 1.8999522e+02 151 | 1.8660783e+02 152 | 1.8322045e+02 153 | 1.7983306e+02 154 | 1.7644567e+02 155 | 1.7305828e+02 156 | 1.6967089e+02 157 | 1.6628350e+02 158 | 1.6289611e+02 159 | 1.5950872e+02 160 | 4.0980665e-05 161 | 1.4322150e-05 162 | 9.5894901e-06 163 | 6.3009805e-06 164 | 5.5101813e-06 165 | 5.5914470e-06 166 | 5.7563719e-06 167 | 5.9523387e-06 168 | 6.4025975e-06 169 | 6.9307512e-06 170 | 7.4159843e-06 171 | 7.8954985e-06 172 | 8.1504573e-06 173 | 8.1960547e-06 174 | 8.0711026e-06 175 | 7.8957773e-06 176 | 7.6763297e-06 177 | 7.4802112e-06 178 | 7.4187052e-06 179 | 7.4086676e-06 180 | 7.3904626e-06 181 | 7.3450192e-06 182 | 7.2716644e-06 183 | 7.1431916e-06 184 | 6.9678756e-06 185 | 6.7631159e-06 186 | 6.2770529e-06 187 | 5.7700694e-06 188 | 5.2963156e-06 189 | 4.7210808e-06 190 | 4.1069694e-06 191 | 3.6673236e-06 192 | 3.2157895e-06 193 | 2.7313444e-06 194 | 2.2445186e-06 195 | 1.7555167e-06 196 | 1.3383695e-06 197 | 9.4866917e-07 198 | 7.3774328e-07 199 | 5.5896598e-07 200 | 4.5608904e-07 201 | 3.7997375e-07 202 | 3.2590461e-07 203 | 2.8505600e-07 204 | 2.4875208e-07 205 | 2.3080539e-07 206 | 2.1406542e-07 207 | 1.9970193e-07 208 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /exercises/05-inversion/input/x_true.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | "z" 6 | "T" 7 | "abs_species-H2O" 8 | 9 | 10 | 27384.196 11 | 20535.25 12 | 15399.265 13 | 11547.82 14 | 8659.6432 15 | 6493.8163 16 | 4869.6753 17 | 3651.7413 18 | 2738.4196 19 | 2053.525 20 | 1539.9265 21 | 1154.782 22 | 865.96432 23 | 649.38163 24 | 486.96753 25 | 365.17413 26 | 273.84196 27 | 205.3525 28 | 153.99265 29 | 115.4782 30 | 86.596432 31 | 64.938163 32 | 48.696753 33 | 36.517413 34 | 27.384196 35 | 20.53525 36 | 15.399265 37 | 11.54782 38 | 8.6596432 39 | 6.4938163 40 | 4.8696753 41 | 3.6517413 42 | 2.7384196 43 | 2.053525 44 | 1.5399265 45 | 1.154782 46 | 0.86596432 47 | 0.64938163 48 | 0.48696753 49 | 0.36517413 50 | 0.27384196 51 | 0.2053525 52 | 0.15399265 53 | 0.1154782 54 | 0.086596432 55 | 0.064938163 56 | 0.048696753 57 | 0.036517413 58 | 59 | 60 | 61 | 62 | 63 | 64 | 9537.1233 65 | 11382.265 66 | 13226.469 67 | 15074.93 68 | 16925.013 69 | 18773.849 70 | 20620.446 71 | 22464.313 72 | 24306.93 73 | 26151.285 74 | 28003.65 75 | 29871.923 76 | 31764.466 77 | 33691.008 78 | 35657.752 79 | 37668.282 80 | 39724.272 81 | 41825.906 82 | 43969.391 83 | 46144.987 84 | 48339.003 85 | 50533.932 86 | 52712.183 87 | 54860.289 88 | 56972.066 89 | 59048.289 90 | 61093.413 91 | 63114.087 92 | 65116.671 93 | 67104.834 94 | 69081.294 95 | 71047.847 96 | 73004.65 97 | 74950.177 98 | 76881.981 99 | 78796.439 100 | 80689.283 101 | 82555.2 102 | 84385.926 103 | 86172.85 104 | 87912.972 105 | 89606.672 106 | 91257.429 107 | 92873.933 108 | 94471.14 109 | 96062.199 110 | 97659.081 111 | 99272.672 112 | 235.80964 113 | 226.65 114 | 226.65 115 | 226.65 116 | 226.65 117 | 226.65 118 | 227.41329 119 | 229.28865 120 | 231.16401 121 | 233.03937 122 | 234.91473 123 | 236.79009 124 | 238.6984 125 | 244.57378 126 | 250.44916 127 | 256.32454 128 | 262.19993 129 | 268.07531 130 | 273.95069 131 | 279.82607 132 | 280.65 133 | 280.04918 134 | 274.35061 135 | 268.65204 136 | 262.95348 137 | 257.25491 138 | 251.55634 139 | 245.85778 140 | 240.15921 141 | 234.46064 142 | 228.76208 143 | 223.70695 144 | 220.31956 145 | 216.93217 146 | 213.54478 147 | 210.15739 148 | 206.77 149 | 203.38261 150 | 199.99522 151 | 196.60783 152 | 193.22045 153 | 189.83306 154 | 186.44567 155 | 183.05828 156 | 179.67089 157 | 176.2835 158 | 172.89611 159 | 169.50872 160 | 4.0980665e-05 161 | 1.432215e-05 162 | 9.5894901e-06 163 | 6.3009805e-06 164 | 5.5101813e-06 165 | 5.591447e-06 166 | 5.7563719e-06 167 | 5.9523387e-06 168 | 6.4025975e-06 169 | 6.9307512e-06 170 | 7.4159843e-06 171 | 7.8954985e-06 172 | 8.1504573e-06 173 | 8.1960547e-06 174 | 8.0711026e-06 175 | 7.8957773e-06 176 | 7.6763297e-06 177 | 7.4802112e-06 178 | 7.4187052e-06 179 | 7.4086676e-06 180 | 7.3904626e-06 181 | 7.3450192e-06 182 | 7.2716644e-06 183 | 7.1431916e-06 184 | 6.9678756e-06 185 | 6.7631159e-06 186 | 6.2770529e-06 187 | 5.7700694e-06 188 | 5.2963156e-06 189 | 4.7210808e-06 190 | 4.1069694e-06 191 | 3.6673236e-06 192 | 3.2157895e-06 193 | 2.7313444e-06 194 | 2.2445186e-06 195 | 1.7555167e-06 196 | 1.3383695e-06 197 | 9.4866917e-07 198 | 7.3774328e-07 199 | 5.5896598e-07 200 | 4.5608904e-07 201 | 3.7997375e-07 202 | 3.2590461e-07 203 | 2.85056e-07 204 | 2.4875208e-07 205 | 2.3080539e-07 206 | 2.1406542e-07 207 | 1.9970193e-07 208 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /exercises/05-inversion/oem.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Advanced radiation and remote sensing\n", 8 | "\n", 9 | "\n", 10 | "Manfred Brath, Oliver Lemke\n", 11 | "\n", 12 | "## Exercise 5: Inversion theory: Optimal Estimation Method (OEM)" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "%matplotlib widget\n", 22 | "\n", 23 | "import os\n", 24 | "import matplotlib.pyplot as plt\n", 25 | "import numpy as np\n", 26 | "from scipy.linalg import inv\n", 27 | "\n", 28 | "from pyarts import xml\n", 29 | "from oem import forward_model\n", 30 | "\n", 31 | "os.makedirs(\"plots\", exist_ok=True)" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": {}, 37 | "source": [ 38 | "In this exercise you will work with \"realistic\" data measured by a water \n", 39 | "vapor radiometer. The data is not real but has been simulated for a well- \n", 40 | "known atmospheric state using ARTS. Simulated measurements allow to \n", 41 | "compare retrieval results to the true atmospheric state. The radiometer \n", 42 | "(image below) measures thermal radiation in a frequency range around the \n", 43 | "$22\\,\\text{GHz}$ water vapor absorption line. \n", 44 | "As the pressure broadening of absorption lines varies with height the \n", 45 | "measurement contains information about the vertical water vapor profile. \n", 46 | "This information can be retrieved using the \"Optimal Estimation Method\" (OEM). \n", 47 | "The radiometer is placed in $10\\,\\text{km}$ height, which resembles an upward \n", 48 | "looking airborne measurement. The scarce concentration of water vapor in the \n", 49 | "stratosphere allows to perform a linear retrieval approach. Retrievals that \n", 50 | "cover the whole atmosphere, including the highly absorbent lower troposphere, \n", 51 | "need more advanced retrieval approaches like an iterative OEM. \n", 52 | "\n", 53 | "![radiometer](H2Orad.jpg)\n", 54 | "\n", 55 | "* Run the next cell." 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": null, 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "# Load the (simulated) measurement.\n", 65 | "measurement = xml.load(\"input/measurement.xml\")\n", 66 | "f_grid = measurement.grids[0]\n", 67 | "y_measurement = measurement.data\n", 68 | "\n", 69 | "# Load the a priori atmospheric state.\n", 70 | "atm_fields = xml.load(\"input/x_apriori.xml\")\n", 71 | "z = atm_fields.get(\"z\", keep_dims=False)\n", 72 | "x_apriori = atm_fields.get(\"abs_species-H2O\", keep_dims=False)\n", 73 | "\n", 74 | "# Load the covariance matrices.\n", 75 | "S_xa = xml.load(\"input/S_xa.xml\")\n", 76 | "S_y = 2.5e-3 * np.eye(f_grid.size) # in [K^2]" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "metadata": {}, 82 | "source": [ 83 | "* Plot the observed brightness\n", 84 | "temperature spectrum `y_measurement` as function of frequency\n", 85 | "`f_grid`." 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": null, 91 | "metadata": {}, 92 | "outputs": [], 93 | "source": [ 94 | "# Plot the y measurement.\n" 95 | ] 96 | }, 97 | { 98 | "cell_type": "markdown", 99 | "metadata": {}, 100 | "source": [ 101 | "* Run the next cell to simulate the brightness temperature spectrum \n", 102 | "`y` and the water vapor Jacobian `K` for \n", 103 | "the *a priori* state." 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": null, 109 | "metadata": {}, 110 | "outputs": [], 111 | "source": [ 112 | "# Run the forward model (ARTS).\n", 113 | "y, K = forward_model(f_grid, atm_fields)" 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "metadata": {}, 119 | "source": [ 120 | "* Plot the simulated brightness temperature spectrum alongside with\n", 121 | "the observed brightness temperature spectrum." 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": null, 127 | "metadata": {}, 128 | "outputs": [], 129 | "source": [ 130 | "# Plot the y measurement alongside the simulated y for the a priori.\n" 131 | ] 132 | }, 133 | { 134 | "cell_type": "markdown", 135 | "metadata": {}, 136 | "source": [ 137 | "* Plot the Jacobians `K` in a suitable way. Explain the plot." 138 | ] 139 | }, 140 | { 141 | "cell_type": "code", 142 | "execution_count": null, 143 | "metadata": {}, 144 | "outputs": [], 145 | "source": [ 146 | "# Plot the Jacobians.\n" 147 | ] 148 | }, 149 | { 150 | "cell_type": "markdown", 151 | "metadata": {}, 152 | "source": [ 153 | "* Plot the measurement covariance matrix `S_y` and the *apriori* covariance matrix `S_xa` in a suitable way. \n", 154 | "What do the covariance matrices mean?" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": null, 160 | "metadata": {}, 161 | "outputs": [], 162 | "source": [ 163 | "# Plot the covariance matrices.\n" 164 | ] 165 | }, 166 | { 167 | "cell_type": "markdown", 168 | "metadata": {}, 169 | "source": [ 170 | "* Implement the function `retrieve()` according to the OEM solution: \n", 171 | "$$\\hat{\\mathbf{x}}=\\mathbf{x}_{a}+\\left(\\mathbf{K}^{T}\\mathbf{S}_{y}^{-1}\\mathbf{K}+\\mathbf{S}_{xa}^{-1}\\right)^{-1}\\mathbf{K}^{T}\\mathbf{S}_{y}^{-1}\\left(\\mathbf{y}_{measure}-\\mathbf{y}_{a}\\right)$$\n", 172 | "\n", 173 | " with $\\mathbf{x}_{a}$ the a priori profile, $\\mathbf{K}$ the Jacobian,\n", 174 | "$\\mathbf{S}_{y}$ the measurement covariance matrix, $\\mathbf{S}_{xa}$\n", 175 | "the *a priori* covariance matrix, $\\mathbf{y}_{measure}$ the\n", 176 | "observed brightness temperature spectrum and $\\mathbf{y}_{a}$ the\n", 177 | "simulated brightness temperature spectrum of profile $\\mathbf{x}_{a}$. \n", 178 | "In Python, a matrix `M` can be transposed using `M.T`\n", 179 | "and inversed using `inv(M)` We are using the inverse function \n", 180 | "`scipy.linalg.inv()` provided by the SciPy package. \n", 181 | "Two matrices `M1` and `M2` can be multiplied using\n", 182 | "`M1 @ M2.`\n", 183 | "\n", 184 | "* Use the function `retrieve()` to retrieve the water vapor profile." 185 | ] 186 | }, 187 | { 188 | "cell_type": "code", 189 | "execution_count": null, 190 | "metadata": {}, 191 | "outputs": [], 192 | "source": [ 193 | "# Implement the retrieve function.\n", 194 | "def retrieve(y, K, xa, ya, Sa, Sy):\n", 195 | " \"\"\"Perform an OEM retrieval.\n", 196 | "\n", 197 | " Parameters:\n", 198 | " y (np.ndarray): Measuremed brightness temperature [K].\n", 199 | " K (np.ndarray): Jacobians [K/1].\n", 200 | " xa (np.ndarray): A priori state [VMR].\n", 201 | " ya (np.ndarray): Forward simulation of a priori state ``F(xa)`` [K].\n", 202 | " Sa (np.ndarray): A priori error covariance matrix.\n", 203 | " Sy (np.ndarray): Measurement covariance matrix\n", 204 | "\n", 205 | " Returns:\n", 206 | " np.ndarray: Retrieved atmospheric state.\n", 207 | "\n", 208 | " \"\"\"\n", 209 | " print(\"Function needs to be implemented by you\")" 210 | ] 211 | }, 212 | { 213 | "cell_type": "code", 214 | "execution_count": null, 215 | "metadata": {}, 216 | "outputs": [], 217 | "source": [ 218 | "# retrieve\n", 219 | "x_oem = retrieve(y_measurement, K, x_apriori, y, S_xa, S_y)\n" 220 | ] 221 | }, 222 | { 223 | "cell_type": "markdown", 224 | "metadata": {}, 225 | "source": [ 226 | "* Plot the retrieved water vapor `x_oem` and the *a priori* \n", 227 | "water vapor profile as function of height `z`.\n", 228 | "\n", 229 | "* Load the true water vapor retrieval (`input/x_true.xml`) and\n", 230 | "add it to the previous plot. Dicuss the results." 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": null, 236 | "metadata": {}, 237 | "outputs": [], 238 | "source": [ 239 | "# Plot the OEM result next to the true atmospheric state and the a priori." 240 | ] 241 | }, 242 | { 243 | "cell_type": "markdown", 244 | "metadata": {}, 245 | "source": [ 246 | "* Implement the function `gain_matrix()` to calculate\n", 247 | "the same-named matrix: \n", 248 | "$$\\mathbf{G}=\\left(\\mathbf{K}^{T}\\mathbf{S}_{y}^{-1}\\mathbf{K}+\\mathbf{S}_{xa}^{-1}\\right)^{-1}\\mathbf{K}^{T}\\mathbf{S}_{y}^{-1}$$" 249 | ] 250 | }, 251 | { 252 | "cell_type": "code", 253 | "execution_count": null, 254 | "metadata": {}, 255 | "outputs": [], 256 | "source": [ 257 | "# Implement the averaging_kernel_matrix function.\n", 258 | "def gain_matrix(K, Sa, Sy):\n", 259 | " \"\"\"Calculate the gain matrix.\n", 260 | "\n", 261 | " Parameters:\n", 262 | " K (np.ndarray): Simulated Jacobians.\n", 263 | " Sa (np.ndarray): A priori error covariance matrix.\n", 264 | " Sy (np.ndarray): Measurement covariance matrix.\n", 265 | "\n", 266 | " Returns:\n", 267 | " np.ndarray: gain matrix.\n", 268 | " \"\"\"\n", 269 | " print(\"Function needs to be implemented by you\")" 270 | ] 271 | }, 272 | { 273 | "cell_type": "markdown", 274 | "metadata": {}, 275 | "source": [ 276 | "* Plot the gain matrix `G` in a suitable way. \n", 277 | "* Explain where which part of the measurement vector contributes to the retrieval.\n" 278 | ] 279 | }, 280 | { 281 | "cell_type": "code", 282 | "execution_count": null, 283 | "metadata": {}, 284 | "outputs": [], 285 | "source": [ 286 | "# Calculate gain matrix \n", 287 | "G = gain_matrix(K, S_xa, S_y)" 288 | ] 289 | }, 290 | { 291 | "cell_type": "code", 292 | "execution_count": null, 293 | "metadata": {}, 294 | "outputs": [], 295 | "source": [ 296 | "# Plot gain matrix" 297 | ] 298 | }, 299 | { 300 | "cell_type": "markdown", 301 | "metadata": {}, 302 | "source": [ 303 | "* Implement the function `averaging_kernel_matrix()` to calculate\n", 304 | "the same-named matrix: \n", 305 | "$$\\mathbf{A}=\\left(\\mathbf{K}^{T}\\mathbf{S}_{y}^{-1}\\mathbf{K}+\\mathbf{S}_{xa}^{-1}\\right)^{-1}\\mathbf{K}^{T}\\mathbf{S}_{y}^{-1}\\mathbf{K}$$" 306 | ] 307 | }, 308 | { 309 | "cell_type": "code", 310 | "execution_count": null, 311 | "metadata": {}, 312 | "outputs": [], 313 | "source": [ 314 | "# Implement the averaging_kernel_matrix function.\n", 315 | "def averaging_kernel_matrix(K, Sa, Sy):\n", 316 | " \"\"\"Calculate the averaging kernel matrix.\n", 317 | "\n", 318 | " Parameters:\n", 319 | " K (np.ndarray): Simulated Jacobians.\n", 320 | " Sa (np.ndarray): A priori error covariance matrix.\n", 321 | " Sy (np.ndarray): Measurement covariance matrix.\n", 322 | "\n", 323 | " Returns:\n", 324 | " np.ndarray: Averaging kernel matrix.\n", 325 | " \"\"\"\n", 326 | " print(\"Function needs to be implemented by you\")" 327 | ] 328 | }, 329 | { 330 | "cell_type": "markdown", 331 | "metadata": {}, 332 | "source": [ 333 | "* Plot the kernels (columns) of $\\mathbf{A}$ as function of height\n", 334 | "`z` and interpret the results. \n", 335 | "The measurement response is defined as the sum over all averaging\n", 336 | "kernels in a given height (row). The measurement response indicates\n", 337 | "in which heights the measurement actually adds information to the\n", 338 | "retrieval result.\n", 339 | "* Calculate the measurement response and plot it together with the averaging\n", 340 | "kernels.\n", 341 | "* In which heights does the measurement provide useful information?\n", 342 | "* Is it possible to estimate the vertical resolution?" 343 | ] 344 | }, 345 | { 346 | "cell_type": "code", 347 | "execution_count": null, 348 | "metadata": {}, 349 | "outputs": [], 350 | "source": [ 351 | "# Calculate averaging kernel \n", 352 | "A = averaging_kernel_matrix(K, S_xa, S_y)" 353 | ] 354 | }, 355 | { 356 | "cell_type": "code", 357 | "execution_count": null, 358 | "metadata": {}, 359 | "outputs": [], 360 | "source": [ 361 | "# Plot averaging kernels" 362 | ] 363 | } 364 | ], 365 | "metadata": { 366 | "kernelspec": { 367 | "display_name": "Python 3", 368 | "language": "python", 369 | "name": "python3" 370 | }, 371 | "language_info": { 372 | "codemirror_mode": { 373 | "name": "ipython", 374 | "version": 3 375 | }, 376 | "file_extension": ".py", 377 | "mimetype": "text/x-python", 378 | "name": "python", 379 | "nbconvert_exporter": "python", 380 | "pygments_lexer": "ipython3", 381 | "version": "3.12.7" 382 | } 383 | }, 384 | "nbformat": 4, 385 | "nbformat_minor": 4 386 | } 387 | -------------------------------------------------------------------------------- /exercises/05-inversion/oem.py: -------------------------------------------------------------------------------- 1 | """Perform an OEM retrieval and plot the results.""" 2 | import numpy as np 3 | import pyarts.workspace 4 | from pyarts import xml 5 | 6 | 7 | def forward_model(f_grid, atm_fields_compact, retrieval_quantity='H2O',verbosity=0): 8 | """Perform a radiative transfer simulation. 9 | 10 | Parameters: 11 | f_grid (ndarray): Frequency grid [Hz]. 12 | atm_fields_compact (GriddedField4): Atmosphere field. 13 | verbosity (int): Reporting levels between 0 (only error messages) 14 | and 3 (everything). 15 | 16 | Returns: 17 | ndarray, ndarray: Frequency grid [Hz], Jacobian [K/1] 18 | """ 19 | 20 | pyarts.cat.download.retrieve(verbose=bool(verbosity)) 21 | 22 | ws = pyarts.workspace.Workspace(verbosity=verbosity) 23 | ws.water_p_eq_agendaSet() 24 | ws.PlanetSet(option="Earth") 25 | ws.verbositySetScreen(ws.verbosity, verbosity) 26 | 27 | # standard emission agenda 28 | ws.iy_main_agendaSet(option="Emission") 29 | 30 | # cosmic background radiation 31 | ws.iy_space_agendaSet(option="CosmicBackground") 32 | 33 | # standard surface agenda (i.e., make use of surface_rtprop_agenda) 34 | ws.iy_surface_agendaSet(option="UseSurfaceRtprop") 35 | 36 | # sensor-only path 37 | ws.ppath_agendaSet(option="FollowSensorLosPath") 38 | 39 | # no refraction 40 | ws.ppath_step_agendaSet(option="GeometricPath") 41 | 42 | # Non reflecting surface 43 | ws.surface_rtprop_agendaSet(option="Specular_NoPol_ReflFix_SurfTFromt_surface") 44 | 45 | # Number of Stokes components to be computed 46 | ws.IndexSet(ws.stokes_dim, 1) 47 | 48 | ######################################################################### 49 | 50 | # Definition of absorption species 51 | ws.abs_speciesSet( 52 | species=[ 53 | "H2O, H2O-SelfContCKDMT400, H2O-ForeignContCKDMT400", 54 | "O2-TRE05", 55 | "N2, N2-CIAfunCKDMT252, N2-CIArotCKDMT252", 56 | ] 57 | ) 58 | 59 | ws.abs_lines_per_speciesReadSpeciesSplitCatalog(basename="lines/") 60 | 61 | # Load CKDMT400 model data 62 | ws.ReadXML(ws.predefined_model_data, "model/mt_ckd_4.0/H2O.xml") 63 | 64 | # ws.abs_lines_per_speciesLineShapeType(option=lineshape) 65 | ws.abs_lines_per_speciesCutoff(option="ByLine", value=750e9) 66 | # ws.abs_lines_per_speciesNormalization(option=normalization) 67 | 68 | ws.VectorSetConstant(ws.surface_scalar_reflectivity, 1, 0.4) 69 | 70 | # Set the frequency grid 71 | ws.f_grid = f_grid 72 | 73 | # Throw away lines outside f_grid 74 | ws.abs_lines_per_speciesCompact() 75 | 76 | # No sensor properties 77 | ws.sensorOff() 78 | 79 | # We select here to use Planck brightness temperatures 80 | ws.StringSet(ws.iy_unit, "PlanckBT") 81 | 82 | ######################################################################### 83 | 84 | # Atmosphere and surface 85 | ws.AtmosphereSet1D() 86 | ws.atm_fields_compact = atm_fields_compact 87 | ws.atm_fields_compactAddConstant( 88 | ws.atm_fields_compact, "abs_species-N2", 0.78, 0, ["abs_species-H2O"] 89 | ) 90 | ws.atm_fields_compactAddConstant( 91 | ws.atm_fields_compact, "abs_species-O2", 0.21, 0, ["abs_species-H2O"] 92 | ) 93 | ws.AtmFieldsAndParticleBulkPropFieldFromCompact() 94 | 95 | ws.Extract(ws.z_surface, ws.z_field, 0) 96 | ws.Extract(ws.t_surface, ws.t_field, 0) 97 | 98 | # Definition of sensor position and line of sight (LOS) 99 | ws.MatrixSet(ws.sensor_pos, np.array([[10e3]])) 100 | ws.MatrixSet(ws.sensor_los, np.array([[0]])) 101 | ws.sensorOff() 102 | 103 | # Jacobian calculation 104 | ws.jacobianInit() 105 | if retrieval_quantity=='H2O': 106 | ws.jacobianAddAbsSpecies( 107 | g1=ws.p_grid, 108 | g2=ws.lat_grid, 109 | g3=ws.lon_grid, 110 | species="H2O, H2O-SelfContCKDMT400, H2O-ForeignContCKDMT400", 111 | unit="vmr", 112 | ) 113 | elif retrieval_quantity=='Temperature': 114 | ws.jacobianAddTemperature( 115 | g1=ws.p_grid, 116 | g2=ws.lat_grid, 117 | g3=ws.lon_grid 118 | ) 119 | else: 120 | raise ValueError('only H2O or Temperature are allowed as retrieval quantity') 121 | ws.jacobianClose() 122 | 123 | # Clearsky = No scattering 124 | ws.cloudboxOff() 125 | 126 | # on-the-fly absorption 127 | ws.propmat_clearsky_agendaAuto() 128 | 129 | # Perform RT calculations 130 | ws.lbl_checkedCalc() 131 | ws.atmfields_checkedCalc() 132 | ws.atmgeom_checkedCalc() 133 | ws.cloudbox_checkedCalc() 134 | ws.sensor_checkedCalc() 135 | 136 | ws.yCalc() 137 | 138 | return ws.y.value[:].copy(), ws.jacobian.value[:].copy() 139 | 140 | # %% 141 | if __name__ == "__main__": 142 | 143 | import matplotlib.pyplot as plt 144 | 145 | 146 | # Load the (simulated) measurement. 147 | measurement = xml.load("input/measurement.xml") 148 | f_grid = measurement.grids[0] 149 | y_measurement = measurement.data 150 | 151 | # Load the a priori atmospheric state. 152 | atm_fields = xml.load("input/x_apriori.xml") 153 | z = atm_fields.get("z", keep_dims=False) 154 | x_apriori = atm_fields.get("abs_species-H2O", keep_dims=False) 155 | 156 | # Load the covariance matrices. 157 | S_xa = xml.load("input/S_xa.xml") 158 | S_y = 2.5e-3 * np.eye(f_grid.size) # in [K^2] 159 | 160 | 161 | y, K = forward_model(f_grid, atm_fields) 162 | 163 | 164 | # Plot absorption cross sections 165 | fig, ax = plt.subplots() 166 | ax.plot(f_grid, y) 167 | ax.set_xlabel("Frequency [Hz]") 168 | ax.set_ylabel(r"Brightness Temperature [$\sf K$]") 169 | plt.show() 170 | fig.savefig('forward_spectra.pdf') -------------------------------------------------------------------------------- /exercises/05-inversion/simulate_measurement.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Fri Dec 13 14:11:05 2024 5 | 6 | @author: Manfred Brath 7 | """ 8 | 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | from copy import deepcopy 12 | 13 | import pyarts as pa 14 | import oem 15 | 16 | 17 | 18 | # %% paths/constants 19 | 20 | # Load the a priori atmospheric state. 21 | atm_fields = pa.xml.load("input/x_apriori.xml") 22 | z = atm_fields.get("z", keep_dims=False) 23 | x_apriori = atm_fields.get("abs_species-H2O", keep_dims=False) 24 | 25 | nedt=0.05 #K 26 | #%% prepare measurement simulation 27 | 28 | #define channels 29 | f_min=22.15e9 30 | f_max=22.30e9 31 | f_num=2000 32 | f_grid=np.linspace(f_min,f_max,f_num) 33 | 34 | 35 | #define "true" atmosphere 36 | atm_true=deepcopy(atm_fields) 37 | 38 | #add pertuberation to the water vapor profile 39 | vmr_h2o=atm_true.get("abs_species-H2O", keep_dims=False) 40 | vmr_h2o_perp=vmr_h2o+3e-5*np.exp(-z/50000)*0.5*np.sin(2*np.pi*0.0001*z)+0.5e-5 41 | vmr_h2o_perp[vmr_h2o_perp<0]=vmr_h2o[-1] 42 | 43 | atm_true.set("abs_species-H2O", np.array(vmr_h2o_perp[np.newaxis,:,np.newaxis,np.newaxis])) 44 | 45 | plt.style.use('seaborn-v0_8') 46 | 47 | fig, ax =plt.subplots(1,2) 48 | 49 | ax[0].plot(atm_fields.get("abs_species-H2O", keep_dims=False), z/1000, label='a priori') 50 | ax[0].plot(atm_true.get("abs_species-H2O", keep_dims=False), z/1000, label='perturbed') 51 | ax[0].set_ylabel('altitude / km') 52 | ax[0].set_xlabel(r'$vmr_{\text{H}_2 \text{O} }$') 53 | ax[0].legend() 54 | 55 | ax[1].plot(atm_true.get("T", keep_dims=False), z/1000) 56 | ax[1].set_xlabel('temperature / K') 57 | 58 | fig.tight_layout() 59 | 60 | 61 | # %% simulate measurement 62 | 63 | y_apr, K_apr = oem.forward_model(f_grid, atm_fields) 64 | y, K = oem.forward_model(f_grid, atm_true) 65 | 66 | # %%add noise 67 | #we use a seed to be deterministic 68 | rng = np.random.default_rng(12345) 69 | y_obs = y+rng.normal(scale=nedt,size=f_num) 70 | 71 | fig, ax =plt.subplots(1,1) 72 | ax.plot(f_grid,y_apr,label='a priori') 73 | ax.plot(f_grid,y,label='simulation') 74 | ax.plot(f_grid,y_obs,label='measurement') 75 | 76 | #save measurement 77 | measurement=pa.arts.GriddedField1() 78 | measurement.grids=[f_grid] 79 | measurement.gridnames[0]='Frequency' 80 | measurement.data=y_obs 81 | measurement.savexml("input/measurement.xml") 82 | 83 | #save "true profile" 84 | atm_true.savexml("input/x_true.xml") 85 | 86 | -------------------------------------------------------------------------------- /exercises/06-non_lin_inversion/Halo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmtools/arts-lectures/3dc28d627560c60ede546a59f24c518d093dff08/exercises/06-non_lin_inversion/Halo.jpg -------------------------------------------------------------------------------- /exercises/06-non_lin_inversion/Temperature_retrieval.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\n", 8 | "## Exercise 8: Temperature retrieval from airborne observations" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "metadata": {}, 15 | "outputs": [], 16 | "source": [ 17 | "%matplotlib widget\n", 18 | "\n", 19 | "import os\n", 20 | "import matplotlib.pyplot as plt\n", 21 | "import numpy as np\n", 22 | "from pyarts import xml\n", 23 | "from nonlin_oem import Forward_model, set_correlation_length, create_apriori_covariance_matrix, temperature_retrieval\n", 24 | "\n", 25 | "os.makedirs(\"plots\", exist_ok=True)" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "metadata": {}, 31 | "source": [ 32 | "In this exercise we want to retrieve temperature profiles from (simulated) \n", 33 | "airborne microwave radiometer observation using the optimal estimation method.\n", 34 | "The radiometer is the HAMP radiometer on board the HALO aircraft.\n", 35 | "The radiometer measures the brightness temperature at several set of channels.\n", 36 | "In this exercise we will use the sets of channels around 22GHz, 50GHz and 118GHz.\n", 37 | "The radiometer is mounted in the belly pod of the HALO aircraft and measures the \n", 38 | "brightness temperature at nadir.\n", 39 | "We will use a simplified version of the HAMP radiometer but with the correct\n", 40 | "channels and NeDT.\n", 41 | "\n", 42 | "![HALO aircraft](Halo.jpg)\n", 43 | "*source https://halo-research.de/ressources/image-galery/*\n", 44 | "\n", 45 | "The NeDT (Noise equivalent delta temperature) are the following:\n", 46 | "\n", 47 | "* 22GHz channels: 0.1K\n", 48 | "* 50GHz channels: 0.2K\n", 49 | "* 118GHz channels: 0.6K\n", 50 | "\n", 51 | "The measurement data consists of a short ($\\approx$ 100km) flight segment of the HALO aircraft at clear sky conditions over the tropical pacific.\n", 52 | "The flight segment is at 15km altitude. The measurement data consists of brightness temperature observations for the three sets of channels.\n", 53 | "Each data set consists of a file that contains the measurement data (*y_obs_xxxGHz.xml*), a file with the frequencies (*f_grid_xxxGHz.xml*) \n", 54 | "and a file with the latitudeof the measurements (*lat.xml*).\n", 55 | "Furthermore there also exists dropsonde measurement data (*dropsonde.xml*) from one dropsonde that was released during the flight segment. The dropsonde data contains the temperature, altitude and H2O vmr profiles as function of pressure. \n", 56 | "The measurement data is stored in the directory *observation*.\n", 57 | "The surface temperature during that flight segment was 300K. The surface reflectivity is 0.4 for all frequencies for that flight segment." 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "metadata": {}, 63 | "source": [ 64 | "### Part I - Preparations" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "metadata": {}, 70 | "source": [ 71 | "#### 1) \n", 72 | "Read in the measurement data and sensor characteristics. Plot the brightness temperature observations for the three sets of channels as function of latitude. Furthermore plot the dropsonde temperature profile.\n", 73 | "Depending on the channel set, the actual channel consists of a single center frequency or of at least two sidebands channels around the center frequency." 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": null, 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "# Load the observation data\n", 83 | "sensor_characteristics_22GHz = xml.load(\"observation/SensorCharacteristics_22GHz.xml\") # sensor characteristics\n", 84 | "\n", 85 | "# The sensor characteristics is a matrix with the following columns:\n", 86 | "# 0: Frequency in GHz\n", 87 | "# 1: sideband offset1 to center frequency in GHz\n", 88 | "# 2: sideband offset2 to center frequency in GHz\n", 89 | "# 3: bandwidth in GHz\n", 90 | "# 4: relative mandatory frequency grid spacing for the passbands\n", 91 | "#\n", 92 | "# The rows of the matrix are the different channels\n", 93 | "\n", 94 | "\n", 95 | "y_obs_22GHz = xml.load(\"observation/y_obs_22GHz.xml.xml\")[:] # [:] converts the data to a numpy array\n", 96 | "#...\n", 97 | "\n", 98 | "\n", 99 | "#...and the dropsonde data\n", 100 | "dropsonde = xml.load(\"observation/dropsonde.xml\")\n", 101 | "\n", 102 | "# dropsonde.grids[0] gives you the name of the variables in the dropsonde file\n", 103 | "# Use dropsonde.get(\"VARIABLENAME\", keep_dims=False) to get the data of the variable VARIABLENAME\n", 104 | "# dropsonde.grids[1][:] gives the pressure grid\n", 105 | "\n", 106 | "# Plot the observation data" 107 | ] 108 | }, 109 | { 110 | "cell_type": "markdown", 111 | "metadata": {}, 112 | "source": [ 113 | "#### 2)\n", 114 | "Decide and explain which set of channels you want to use for the temperature retrieval.\n", 115 | "If you want you can use the dropsonde data and the function forward model to simulate the brightness temperatures and jacobians for the dropsonde temperature profile, but **you don't have to**." 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": null, 121 | "metadata": {}, 122 | "outputs": [], 123 | "source": [ 124 | "# y_obs, jacobians = Forward_model([], dropsonde_data,..., sensor_description=sensor_characteristics)\n" 125 | ] 126 | }, 127 | { 128 | "cell_type": "markdown", 129 | "metadata": {}, 130 | "source": [] 131 | }, 132 | { 133 | "cell_type": "markdown", 134 | "metadata": {}, 135 | "source": [ 136 | "#### 3)\n", 137 | "Prepare the covariance matrices for the temperature retrieval.\n", 138 | "Use the function *create_apriori_covariance_matrix* to create the a priori covariance matrix. The function assumes that an exponentially decaying correlation function is used. \n", 139 | "\n", 140 | "You can use the function *set_correlation_length(z, len_sfc, len_toa=None)* to set the correlation length for the a priori covariance matrix. You can use a constant or a linearly increasing correlation length with height.\n", 141 | "Make an educated guess for the correlation length. Remember that the flight segment is over the tropical pacific.\n", 142 | "\n", 143 | " *Set the a priori covaraince matrix for the temperature retrieval.\n", 144 | "* Set the measurement error covariance matrix using the NeDT values. Assume a diagonal matrix.\n", 145 | "* Plot the covariance matrices in suitable way." 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": null, 151 | "metadata": {}, 152 | "outputs": [], 153 | "source": [ 154 | "# correlation_length = set_correlation_length(z, len_sfc, len_toa)\n", 155 | "# S_a = create_apriori_covariance_matrix(x, z, delta_x, correlation_length)\n", 156 | "\n", 157 | "# S_y = ..." 158 | ] 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "metadata": {}, 163 | "source": [ 164 | "### Part II - Retrieval\n" 165 | ] 166 | }, 167 | { 168 | "cell_type": "markdown", 169 | "metadata": {}, 170 | "source": [ 171 | "#### 4)\n", 172 | "Use the function *temperature_retrieval* to retrieve the temperature profile from the first brightness temperature observation of the flight segment. The function has the following signature:\n", 173 | "```python \n", 174 | " T_ret, DeltaT, y_fit = temperature_retrieval(y_obs, f_grid, sensor_pos, sensor_los, background_atmosphere, surface_temperature, surface_reflectivity, S_y, S_a, senssor_description=[], Diagnostics=False) \n", 175 | "``` \n", 176 | "*sensor_pos*, *sensor_los*, *background_atmosphere*, *surface_temperature*, *surface_reflectivity* describe the background state of the atmosphere and the sensor position and line of sight. \n", 177 | "*background_atmosphere* has a double function. It includes the background atmospheric state (e. g. water vapor profile) for the non retrieved atmospheric variables and the a priori temperature profile. \n", 178 | "If *sensor_description* is set to *[]* then the function uses f_grid. If *sensor_description* is set then the sensor description is used.\n", 179 | "\n", 180 | "The function returns the retrieved temperature profile, the total error of the retrieved temperature profile and the fitted brightness temperature measurement. \n", 181 | "\n", 182 | "Use the prepared covariance matrices for the retrieval and the dropsonde data as background state and a priori temperature profile.\n", 183 | "\n", 184 | "Check the results:\n", 185 | "* Plot the a priori temperature profile, retrieved temperature profile in one plot and the difference between the retrieved and a priori temperature profile in a second plot. \n", 186 | "* Plot the difference between the fitted and measured brightness temperature.\n", 187 | "* If you want you can also plot the averaging kernels and the gain matrix. To do that, set the keyword *Diagnostics=True* in the function *temperature_retrieval* and add *A* and *G* to the output of the function.\n" 188 | ] 189 | }, 190 | { 191 | "cell_type": "code", 192 | "execution_count": null, 193 | "metadata": {}, 194 | "outputs": [], 195 | "source": [ 196 | "# T_ret, DeltaT, y_fit = temperature_retrieval(\n", 197 | " # y_obs,\n", 198 | " # [],\n", 199 | " # sensor_pos,\n", 200 | " # sensor_los,\n", 201 | " # dropsonde,\n", 202 | " # surface_temperature,\n", 203 | " # surface_reflectivity,\n", 204 | " # S_y,\n", 205 | " # S_a,\n", 206 | " # sensor_description=sensor_characteristics)" 207 | ] 208 | }, 209 | { 210 | "cell_type": "markdown", 211 | "metadata": {}, 212 | "source": [ 213 | "#### 5)\n", 214 | "Repeat the retrieval for the rest of the flight segment. \n", 215 | "\n", 216 | "* Plot the retrieved temperature profiles and the difference to the a priori as function of altitude and latitude. \n", 217 | "* Plot the total error of the retrieved temperature profiles as function of altitude and latitude. \n", 218 | "* Plot the difference between the fitted and measured brightness temperature (residuals) as function of latitude.\n", 219 | "\n", 220 | "\n", 221 | "\n" 222 | ] 223 | } 224 | ], 225 | "metadata": { 226 | "language_info": { 227 | "name": "python" 228 | } 229 | }, 230 | "nbformat": 4, 231 | "nbformat_minor": 2 232 | } 233 | -------------------------------------------------------------------------------- /exercises/06-non_lin_inversion/observation/SensorCharacteristics_118GHz.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 118750000000 1300000000 0 400000000 400000000 5 | 118750000000 2300000000 0 400000000 400000000 6 | 118750000000 4200000000 0 400000000 400000000 7 | 118750000000 8500000000 0 400000000 400000000 8 | 9 | 10 | -------------------------------------------------------------------------------- /exercises/06-non_lin_inversion/observation/SensorCharacteristics_22GHz.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22240000000 0 0 230000000 230000000 5 | 23040000000 0 0 230000000 230000000 6 | 23840000000 0 0 230000000 230000000 7 | 25440000000 0 0 230000000 230000000 8 | 26240000000 0 0 230000000 230000000 9 | 27840000000 0 0 230000000 230000000 10 | 31400000000 0 0 230000000 230000000 11 | 12 | 13 | -------------------------------------------------------------------------------- /exercises/06-non_lin_inversion/observation/SensorCharacteristics_50GHz.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 50300000000 0 0 230000000 230000000 5 | 51760000000 0 0 230000000 230000000 6 | 52800000000 0 0 230000000 230000000 7 | 53750000000 0 0 230000000 230000000 8 | 54940000000 0 0 230000000 230000000 9 | 56660000000 0 0 230000000 230000000 10 | 58000000000 0 0 230000000 230000000 11 | 12 | 13 | -------------------------------------------------------------------------------- /exercises/06-non_lin_inversion/observation/dropsonde.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | "T" 6 | "z" 7 | "abs_species-H2O" 8 | 9 | 10 | 100991.714773996 11 | 100118.556362501 12 | 98859.1326574627 13 | 97447.1792793992 14 | 95912.5130513097 15 | 94245.3827277987 16 | 92435.1522220354 17 | 90477.5419908952 18 | 88369.5269449006 19 | 86096.4534870001 20 | 83655.776593574 21 | 81043.8567465965 22 | 78259.9020132852 23 | 75297.0137584802 24 | 72159.0170324597 25 | 68855.839496041 26 | 65486.7349102027 27 | 62170.9128415888 28 | 58936.5773128153 29 | 55792.3991428444 30 | 52739.7526332444 31 | 49778.119471599 32 | 46910.549232156 33 | 44140.2804668532 34 | 41465.5097778122 35 | 38884.2710114648 36 | 36405.5032496594 37 | 34026.8144066115 38 | 31742.8596914261 39 | 29556.1285333279 40 | 27470.3254191222 41 | 25488.7224155002 42 | 23604.2415477494 43 | 21810.9156267841 44 | 20111.654774372 45 | 18509.696890414 46 | 16999.9202398639 47 | 15627.1118335754 48 | 14433.9247288444 49 | 13401.9535181492 50 | 51 | 52 | 53 | 54 | 55 | 56 | 300.314115594592 57 | 300.915330372873 58 | 298.782312710637 59 | 297.87411501143 60 | 296.774035926772 61 | 295.468103462684 62 | 293.899684949495 63 | 293.722830361289 64 | 292.509383509685 65 | 290.004325813321 66 | 291.232271683131 67 | 290.331672981929 68 | 289.127795984874 69 | 288.461313749925 70 | 286.567846897965 71 | 285.742079170901 72 | 284.508628125183 73 | 281.00804993402 74 | 278.814059988081 75 | 276.349252248762 76 | 273.500463859992 77 | 269.67472613962 78 | 266.822655297673 79 | 262.036218763523 80 | 258.954221987858 81 | 255.384988206163 82 | 250.795313692145 83 | 247.982466459245 84 | 245.528285350701 85 | 242.50712954472 86 | 238.279417795557 87 | 234.888612083524 88 | 230.867300106856 89 | 227.615188981353 90 | 223.969615336887 91 | 220.952525418782 92 | 215.569689644477 93 | 210.398553130983 94 | 206.288461464893 95 | 203.153408978163 96 | 25.547341549291 97 | 102.453953700783 98 | 214.224428733893 99 | 340.739264091559 100 | 479.741135858856 101 | 632.656472940361 102 | 801.060679747995 103 | 986.068051768615 104 | 1188.90479919974 105 | 1412.00128119815 106 | 1657.00720511918 107 | 1926.26118536755 108 | 2222.34076413855 109 | 2547.9738575083 110 | 2905.17349756291 111 | 3296.67714184786 112 | 3713.83095152188 113 | 4142.75693748272 114 | 4579.36015279314 115 | 5022.55042152067 116 | 5472.7646246716 117 | 5930.27152110412 118 | 6394.30634571177 119 | 6864.08837890625 120 | 7339.79788072974 121 | 7821.60314185218 122 | 8308.05660605642 123 | 8800.01743985066 124 | 9299.02158592022 125 | 9804.49139890418 126 | 10315.510506689 127 | 10830.9006066786 128 | 11352.2669170008 129 | 11881.4726648921 130 | 12416.8489180033 131 | 12955.7686540722 132 | 13496.094402482 133 | 14018.3408721654 134 | 14501.4999330234 135 | 14944.3979319345 136 | 0.0264307447277379 137 | 0.0248531839334627 138 | 0.0290274537961586 139 | 0.0246460524271902 140 | 0.0237457494839289 141 | 0.0195948415713455 142 | 0.0214977838780675 143 | 0.023192978784024 144 | 0.0176328373717528 145 | 0.0159647697511969 146 | 0.0136071479898486 147 | 0.00701667673311904 148 | 0.00511076782711294 149 | 0.00371916509870556 150 | 0.00210145532531236 151 | 0.000549513831319697 152 | 0.000387273481173217 153 | 0.000520133120816913 154 | 0.000927094164051217 155 | 0.000808087526052102 156 | 0.000762713032760954 157 | 0.000631177500790877 158 | 0.000448501481590075 159 | 0.000320447281074285 160 | 0.000150942233913906 161 | 9.96347020025363e-05 162 | 8.51211601336836e-05 163 | 7.11754914919099e-05 164 | 7.29824938614383e-05 165 | 4.17257583985655e-05 166 | 3.03399538338396e-05 167 | 2.24581888729241e-05 168 | 2.9441107824495e-05 169 | 3.68162345410877e-05 170 | 1.51035150045871e-05 171 | 1.07382384452592e-05 172 | 1.17604593018267e-05 173 | 6.75492089457961e-06 174 | 5.5514524196455e-06 175 | 4.90342904540925e-06 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /exercises/06-non_lin_inversion/observation/lat.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16.0021457672119 5 | 16.0043563842773 6 | 16.006555557251 7 | 16.0087547302246 8 | 16.0109691619873 9 | 16.0131778717041 10 | 16.0153770446777 11 | 16.017578125 12 | 16.0198040008545 13 | 16.0220031738281 14 | 16.0242042541504 15 | 16.0264148712158 16 | 16.0286273956299 17 | 16.0308265686035 18 | 16.0330390930176 19 | 16.0352382659912 20 | 16.0374374389648 21 | 16.0396633148193 22 | 16.0418605804443 23 | 16.0440635681152 24 | 16.0462760925293 25 | 16.0484886169434 26 | 16.0506858825684 27 | 16.0528984069824 28 | 16.0550975799561 29 | 16.0573120117188 30 | 16.0595264434814 31 | 16.0617237091064 32 | 16.0639209747314 33 | 16.0661487579346 34 | 16.0683479309082 35 | 16.0705471038818 36 | 16.0727596282959 37 | 16.07497215271 38 | 16.077169418335 39 | 16.0793838500977 40 | 16.0815830230713 41 | 16.0837821960449 42 | 16.0860080718994 43 | 16.0882053375244 44 | 16.0904064178467 45 | 16.0926342010498 46 | 16.0948333740234 47 | 16.0970306396484 48 | 16.0992298126221 49 | 16.1014404296875 50 | 16.1036548614502 51 | 16.1058540344238 52 | 16.1080646514893 53 | 16.1102638244629 54 | 16.112476348877 55 | 16.114688873291 56 | 16.1168880462646 57 | 16.1190872192383 58 | 16.1213130950928 59 | 16.123514175415 60 | 16.12571144104 61 | 16.1279258728027 62 | 16.1301383972168 63 | 16.1323356628418 64 | 16.1345500946045 65 | 16.1367473602295 66 | 16.1389465332031 67 | 16.1411743164062 68 | 16.1438026428223 69 | 16.1460037231445 70 | 16.1482162475586 71 | 16.150426864624 72 | 16.1526260375977 73 | 16.1548385620117 74 | 16.157039642334 75 | 16.159252166748 76 | 16.1614646911621 77 | 16.1636638641357 78 | 16.1658611297607 79 | 16.1680889129639 80 | 16.1702861785889 81 | 16.1724834442139 82 | 16.1746978759766 83 | 16.176908493042 84 | 16.1791076660156 85 | 16.1813220977783 86 | 16.1835193634033 87 | 16.185718536377 88 | 16.1875038146973 89 | 16.1897144317627 90 | 16.1919136047363 91 | 16.19411277771 92 | 16.1963386535645 93 | 16.1985378265381 94 | 16.2007369995117 95 | 16.2033805847168 96 | 16.2055912017822 97 | 16.2077903747559 98 | 16.2100028991699 99 | 16.2122001647949 100 | 16.2144165039062 101 | 16.2166290283203 102 | 16.2188262939453 103 | 16.2210273742676 104 | 16.2232532501221 105 | 16.2254524230957 106 | 16.2276515960693 107 | 16.2298641204834 108 | 16.2320766448975 109 | 16.2342758178711 110 | 16.2364864349365 111 | 16.2386875152588 112 | 16.2408866882324 113 | 16.2431106567383 114 | 16.2453136444092 115 | 16.2475109100342 116 | 16.2497234344482 117 | 16.2519378662109 118 | 16.2541351318359 119 | 16.2563438415527 120 | 16.2585430145264 121 | 16.2607574462891 122 | 16.2629680633545 123 | 16.2651691436768 124 | 16.2673683166504 125 | 16.2695922851562 126 | 16.2717952728271 127 | 16.2739925384521 128 | 16.2761898040771 129 | 16.2784156799316 130 | 16.2806148529053 131 | 16.2828159332275 132 | 16.2850284576416 133 | 16.2872257232666 134 | 16.2894382476807 135 | 16.2916507720947 136 | 16.2938499450684 137 | 16.2960510253906 138 | 16.2982769012451 139 | 16.3004741668701 140 | 16.3026752471924 141 | 16.3048858642578 142 | 16.3071002960205 143 | 16.3092994689941 144 | 16.3115100860596 145 | 16.3137092590332 146 | 16.3159217834473 147 | 16.3181343078613 148 | 16.3203372955322 149 | 16.3225345611572 150 | 16.3247585296631 151 | 16.3269577026367 152 | 16.3291568756104 153 | 16.331371307373 154 | 16.3335704803467 155 | 16.3357810974121 156 | 16.3379917144775 157 | 16.3401927947998 158 | 16.3423919677734 159 | 16.3446140289307 160 | 16.3468170166016 161 | 16.3490142822266 162 | 16.3512287139893 163 | 16.3534412384033 164 | 16.3556385040283 165 | 16.3578510284424 166 | 16.3600482940674 167 | 16.3622627258301 168 | 16.3644618988037 169 | 16.3666763305664 170 | 16.3688735961914 171 | 16.371072769165 172 | 16.3732986450195 173 | 16.3754978179932 174 | 16.3776969909668 175 | 16.3799209594727 176 | 16.3821201324463 177 | 16.3843193054199 178 | 16.386531829834 179 | 16.3887348175049 180 | 16.3909435272217 181 | 16.3931560516357 182 | 16.3953552246094 183 | 16.397554397583 184 | 16.3997821807861 185 | 16.4019794464111 186 | 16.4041786193848 187 | 16.4063892364502 188 | 16.4086036682129 189 | 16.4108047485352 190 | 16.4130153656006 191 | 16.4152145385742 192 | 16.4174270629883 193 | 16.419641494751 194 | 16.421838760376 195 | 16.4240398406982 196 | 16.4262619018555 197 | 16.4284591674805 198 | 16.4306602478027 199 | 16.4328727722168 200 | 16.4350700378418 201 | 16.4372825622559 202 | 16.4394950866699 203 | 16.4416961669922 204 | 16.4438953399658 205 | 16.4461059570312 206 | 16.4483184814453 207 | 16.4505157470703 208 | 16.4527168273926 209 | 16.4549446105957 210 | 16.4571418762207 211 | 16.4593410491943 212 | 16.4615535736084 213 | 16.4637660980225 214 | 16.4659652709961 215 | 16.4681758880615 216 | 16.4703750610352 217 | 16.4725761413574 218 | 16.4748001098633 219 | 16.4770011901855 220 | 16.4792003631592 221 | 16.4814109802246 222 | 16.4836254119873 223 | 16.4858226776123 224 | 16.4880352020264 225 | 16.490234375 226 | 16.4924449920654 227 | 16.4946575164795 228 | 16.4968585968018 229 | 16.4990577697754 230 | 16.5012836456299 231 | 16.5034809112549 232 | 16.5056800842285 233 | 16.5078926086426 234 | 16.510103225708 235 | 16.5123023986816 236 | 16.5145149230957 237 | 16.5167140960693 238 | 16.518913269043 239 | 16.5211410522461 240 | 16.5233383178711 241 | 16.5255374908447 242 | 16.5277633666992 243 | 16.5299625396729 244 | 16.5321598052979 245 | 16.5343608856201 246 | 16.5365734100342 247 | 16.5387840270996 248 | 16.5409832000732 249 | 16.5431976318359 250 | 16.5453948974609 251 | 16.5476093292236 252 | 16.5498180389404 253 | 16.5520191192627 254 | 16.554220199585 255 | 16.5564441680908 256 | 16.5586433410645 257 | 16.5608425140381 258 | 16.5630550384521 259 | 16.5652656555176 260 | 16.5674648284912 261 | 16.5696792602539 262 | 16.5718765258789 263 | 16.5740756988525 264 | 16.5763034820557 265 | 16.5785007476807 266 | 16.5806999206543 267 | 16.5829124450684 268 | 16.5851249694824 269 | 16.5873241424561 270 | 16.5895347595215 271 | 16.5917358398438 272 | 16.5939483642578 273 | 16.5961589813232 274 | 16.5983581542969 275 | 16.6005554199219 276 | 16.6027793884277 277 | 16.60498046875 278 | 16.6071796417236 279 | 16.6093940734863 280 | 16.6116046905518 281 | 16.6138038635254 282 | 16.6160163879395 283 | 16.6182136535645 284 | 16.6204128265381 285 | 16.6226234436035 286 | 16.6248340606689 287 | 16.6270370483398 288 | 16.6292343139648 289 | 16.6314640045166 290 | 16.6336612701416 291 | 16.635856628418 292 | 16.6380710601807 293 | 16.6402835845947 294 | 16.642484664917 295 | 16.6446952819824 296 | 16.6468944549561 297 | 16.6491069793701 298 | 16.6513195037842 299 | 16.6535186767578 300 | 16.6557178497314 301 | 16.6579418182373 302 | 16.6601428985596 303 | 16.6623420715332 304 | 16.6645545959473 305 | 16.6667671203613 306 | 16.6689643859863 307 | 16.6711769104004 308 | 16.6733741760254 309 | 16.6755752563477 310 | 16.6777992248535 311 | 16.6799964904785 312 | 16.6821975708008 313 | 16.6844081878662 314 | 16.6866226196289 315 | 16.6888198852539 316 | 16.6910305023193 317 | 16.6932315826416 318 | 16.6954441070557 319 | 16.6976566314697 320 | 16.6998538970947 321 | 16.7020530700684 322 | 16.7042808532715 323 | 16.7064781188965 324 | 16.7086772918701 325 | 16.7108764648438 326 | 16.7131023406982 327 | 16.7153015136719 328 | 16.7174987792969 329 | 16.7197113037109 330 | 16.7219104766846 331 | 16.72412109375 332 | 16.7263374328613 333 | 16.7285346984863 334 | 16.73073387146 335 | 16.7329597473145 336 | 16.7351570129395 337 | 16.7373580932617 338 | 16.7395668029785 339 | 16.7417793273926 340 | 16.7439804077148 341 | 16.7461929321289 342 | 16.7483921051025 343 | 16.7506065368652 344 | 16.752815246582 345 | 16.755012512207 346 | 16.7572154998779 347 | 16.7594394683838 348 | 16.7616405487061 349 | 16.7638378143311 350 | 16.7660465240479 351 | 16.7682476043701 352 | 16.7704582214355 353 | 16.7726726531982 354 | 16.7748699188232 355 | 16.7770690917969 356 | 16.779296875 357 | 16.7814922332764 358 | 16.78369140625 359 | 16.7859039306641 360 | 16.7881164550781 361 | 16.7903175354004 362 | 16.7925281524658 363 | 16.7947273254395 364 | 16.7969379425049 365 | 16.7991371154785 366 | 16.8013496398926 367 | 16.8035507202148 368 | 16.8057479858398 369 | 16.8079738616943 370 | 16.810173034668 371 | 16.8123722076416 372 | 16.8145980834961 373 | 16.8167953491211 374 | 16.8189964294434 375 | 16.8212070465088 376 | 16.8234062194824 377 | 16.8256187438965 378 | 16.8278293609619 379 | 16.8300304412842 380 | 16.8322296142578 381 | 16.8344554901123 382 | 16.8366546630859 383 | 16.8388519287109 384 | 16.8410625457764 385 | 16.8432769775391 386 | 16.8454742431641 387 | 16.8476867675781 388 | 16.8498840332031 389 | 16.8520965576172 390 | 16.8543071746826 391 | 16.8565082550049 392 | 16.8587055206299 393 | 16.8609313964844 394 | 16.8631324768066 395 | 16.8653297424316 396 | 16.8675441741943 397 | 16.8697395324707 398 | 16.8719539642334 399 | 16.8741645812988 400 | 16.8763656616211 401 | 16.8785629272461 402 | 16.8812198638916 403 | 16.8834190368652 404 | 16.8856163024902 405 | 16.8878135681152 406 | 16.8900394439697 407 | 16.8922424316406 408 | 16.894437789917 409 | 16.8966484069824 410 | 16.8988628387451 411 | 16.9010620117188 412 | 16.9032745361328 413 | 16.9054718017578 414 | 16.9076728820801 415 | 16.9098968505859 416 | 16.9120979309082 417 | 16.9142951965332 418 | 16.9165191650391 419 | 16.9187183380127 420 | 16.920919418335 421 | 16.922700881958 422 | 16.924898147583 423 | 16.9271125793457 424 | 16.9293231964111 425 | 16.9315223693848 426 | 16.9337215423584 427 | 16.9363765716553 428 | 16.9385757446289 429 | 16.9407730102539 430 | 16.9429874420166 431 | 16.945198059082 432 | 16.9473991394043 433 | 16.9496097564697 434 | 16.9518089294434 435 | 16.954008102417 436 | 16.9562320709229 437 | 16.9584312438965 438 | 16.9606304168701 439 | 16.9628582000732 440 | 16.9650554656982 441 | 16.9672546386719 442 | 16.9694652557373 443 | 16.9716625213623 444 | 16.973876953125 445 | 16.97607421875 446 | 16.9782867431641 447 | 16.9804840087891 448 | 16.9826984405518 449 | 16.9849090576172 450 | 16.9871101379395 451 | 16.9893054962158 452 | 16.9915313720703 453 | 16.9937324523926 454 | 16.9959297180176 455 | 16.998140335083 456 | 457 | 458 | -------------------------------------------------------------------------------- /exercises/06-non_lin_inversion/prepare_airborne_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Thu Jan 2 19:53:32 2025 5 | 6 | THIS SCRIPT IS NOT INTENDED TO BE USED FOR THE EXERCISE! 7 | ITS ONLY PURPOSE IS TO CREATE THE DATA FOR THE EXERCISE BY THE DEVELOPER! 8 | 9 | Prepare data for exercise - Non-linear inversion 10 | Read in data from GEM tropical pacific scene and extract 11 | a slice of the data for given latitude and altitude range for 12 | a simulation of an airborne radiometer observation. 13 | 14 | To keep it simple the slice will be simply the center in x of the data. 15 | 16 | It also creates a dropsonde data set by selecting one of the profiles. 17 | 18 | The data will be saved in ARTS XML format. 19 | 20 | The data is also plotted for a quick check. 21 | 22 | The data is saved in the folder 'observation' in the current directory. 23 | 24 | The data is saved in the following files: 25 | - atmospheres_true.xml 26 | - aux1d_true.xml 27 | - aux2d_true.xml 28 | - dropsonde.xml 29 | 30 | The plots are saved in the folder 'check_plots' in the current directory. 31 | 32 | The plots are saved in the following files: 33 | - profiles.pdf 34 | - columns.pdf 35 | - dropsonde.pdf 36 | 37 | @author: Manfred Brath 38 | """ 39 | 40 | import os 41 | from copy import deepcopy 42 | import numpy as np 43 | import xarray as xa 44 | from scipy.interpolate import interp1d 45 | import matplotlib.pyplot as plt 46 | 47 | # import seaborn as sns 48 | 49 | import pyarts as pa 50 | 51 | from typhon.physics import e_eq_mixed_mk 52 | from typhon.constants import gas_constant_water_vapor 53 | 54 | # %% paths / constants 55 | 56 | data_folder = ( 57 | "/scratch/u237/user_data/mbrath/EarthCARE_Scenes/39320_test_data/" 58 | ) 59 | 60 | # lat_range = [15.5, 16.5] # ° 61 | lat_range = [16, 17] 62 | alt_max = 15e3 # m 63 | 64 | # Amount of Oxygen 65 | O2vmr = 0.2095 66 | 67 | # Amount of Nitrogen 68 | N2vmr = 0.7808 69 | 70 | #T emperature offset of dropsonde 71 | T_offset=1. #K 72 | 73 | # %% load data 74 | 75 | 76 | file_list = os.listdir(data_folder) 77 | 78 | # get rid of non_nc files 79 | files = [file for file in file_list if ".nc" in file] 80 | 81 | # estimate common prefix 82 | idx = [ 83 | i for i in range(min(len(files[0]), len(files[1]))) if files[0][i] == files[1][i] 84 | ] 85 | 86 | 87 | # get variable names from list 88 | variable_names = [file[idx[-1] + 1 : -3] for file in files] 89 | 90 | 91 | # %% variable translation 92 | translator = { 93 | "water_content_cloud": "scat_species-LWC-mass_density", 94 | "water_content_ice": "scat_species-IWC-mass_density", 95 | "water_content_rain": "scat_species-RWC-mass_density", 96 | "water_content_snow": "scat_species-SWC-mass_density", 97 | "water_content_graupel": "scat_species-GWC-mass_density", 98 | "water_content_hail": "scat_species-HWC-mass_density", 99 | "number_concentration_cloud": "scat_species-LWC-number_density", 100 | "number_concentration_ice": "scat_species-IWC-number_density", 101 | "number_concentration_rain": "scat_species-RWC-number_density", 102 | "number_concentration_snow": "scat_species-SWC-number_density", 103 | "number_concentration_graupel": "scat_species-GWC-number_density", 104 | "number_concentration_hail": "scat_species-HWC-number_density", 105 | "relative_humidity": "abs_species-H2O", 106 | "temperature": "T", 107 | "height_thermodynamic": "z", 108 | "pressure_thermodynamic": "p_grid", 109 | } 110 | 111 | 112 | # Atmospheric field names 113 | atm_fieldnames = [ 114 | "T", 115 | "z", 116 | "scat_species-LWC-mass_density", 117 | "scat_species-IWC-mass_density", 118 | "scat_species-RWC-mass_density", 119 | "scat_species-SWC-mass_density", 120 | "scat_species-GWC-mass_density", 121 | "scat_species-HWC-mass_density", 122 | "scat_species-LWC-number_density", 123 | "scat_species-IWC-number_density", 124 | "scat_species-RWC-number_density", 125 | "scat_species-SWC-number_density", 126 | "scat_species-GWC-number_density", 127 | "scat_species-HWC-number_density", 128 | "abs_species-H2O", 129 | "abs_species-O2", 130 | "abs_species-N2", 131 | ] 132 | 133 | # %% 134 | 135 | # first read latitude 136 | lat_idx = [i for i in range(len(files)) if "latitude" in files[i]][0] 137 | lat = xa.load_dataarray(data_folder + files[lat_idx]).to_numpy() 138 | central_idx = np.size(lat, axis=0) // 2 139 | 140 | latitude = lat[central_idx, :] 141 | lat_range_idx = np.where( 142 | np.logical_and(latitude > lat_range[0], latitude < lat_range[1]) 143 | )[0] 144 | 145 | 146 | # %% now read data 147 | 148 | rawdata = {} 149 | cnt = 0 150 | N_nvars = len(variable_names) 151 | 152 | for var in variable_names: 153 | 154 | cnt += 1 155 | print(f"\n{cnt} of {N_nvars}") 156 | print(f"reading {var}") 157 | 158 | if var in translator: 159 | varname = translator[var] 160 | print(f"rename {var} -> {varname}") 161 | else: 162 | varname = var 163 | idx = [i for i in range(len(files)) if var in files[i]][0] 164 | 165 | temp = xa.open_dataarray(data_folder + files[idx]) 166 | if temp.ndim == 3: 167 | rawdata[varname] = np.float64( 168 | temp[:, central_idx, lat_range_idx].to_numpy()[::-1, :] 169 | ) 170 | # we have flipped the vertical directions as in ARTS 2.6 the pressure 171 | # must be ordered from high to low 172 | 173 | elif temp.ndim == 2: 174 | rawdata[varname] = np.float64(temp[central_idx, lat_range_idx].to_numpy()) 175 | else: 176 | raise ValueError("There should be only 2d and 3d data...mmmh") 177 | 178 | 179 | # %% get atms on the same altitude grid 180 | 181 | N_profiles = len(rawdata["latitude"]) 182 | 183 | z_default = np.mean(rawdata["z"], axis=1) 184 | z_default = z_default[z_default < alt_max] 185 | 186 | z_org = rawdata["z"] * 1.0 187 | 188 | for key in rawdata: 189 | 190 | if rawdata[key].ndim == 2: 191 | 192 | if np.size(rawdata[key], axis=0) >= len(z_default): 193 | 194 | print(f"reinterpolating {key}") 195 | 196 | data = np.zeros((len(z_default), N_profiles)) 197 | for i in range(N_profiles): 198 | F_int = interp1d( 199 | z_org[:, i], rawdata[key][:, i], fill_value="extrapolate" 200 | ) 201 | data[:, i] = F_int(z_default) 202 | 203 | rawdata[key] = data 204 | 205 | 206 | # %% now prepare data for arts 207 | # This means we have to convert relative humidity to vmr and 208 | # create batch_atm_compactand aux data 209 | 210 | 211 | # get all 2d variables that is not atm_fieldnames 212 | aux2d_fieldnames = [ 213 | var 214 | for var in rawdata.keys() 215 | if var not in atm_fieldnames 216 | and np.size(rawdata[var], axis=0) == np.size(rawdata["p_grid"], axis=0) 217 | ] 218 | 219 | # get all 1d variables that is not atm_fieldnames 220 | aux1d_fieldnames = [ 221 | var 222 | for var in rawdata.keys() 223 | if var not in atm_fieldnames and rawdata[var].ndim == 1 224 | ] 225 | 226 | batch_atms = pa.arts.ArrayOfGriddedField4() 227 | batch_aux2d = pa.arts.ArrayOfGriddedField2() 228 | batch_aux1d = pa.arts.ArrayOfGriddedField1() 229 | 230 | # allocate atm object 231 | atm = pa.arts.GriddedField4() 232 | atm.set_grid(0, atm_fieldnames) 233 | atm.set_grid(1, rawdata["p_grid"][:, 0]) 234 | atm.data = np.zeros((len(atm.grids[0]), len(atm.grids[1]), 1, 1)) 235 | 236 | # allocate aux2d object 237 | aux2d = pa.arts.GriddedField2() 238 | aux2d.set_grid(0, aux2d_fieldnames) 239 | aux2d.set_grid(1, rawdata["p_grid"][:, 0]) 240 | aux2d.data = np.zeros((len(aux2d.grids[0]), len(aux2d.grids[1]))) 241 | 242 | # allocate aux1d object 243 | aux1d = pa.arts.GriddedField1() 244 | aux1d.set_grid(0, aux1d_fieldnames) 245 | aux1d.data = np.zeros(len(aux1d.grids[0])) 246 | 247 | 248 | for i in range(N_profiles): 249 | 250 | if i % 50 == 0: 251 | print(f"processing profile {i} of {N_profiles}") 252 | 253 | atm.set_grid(1, rawdata["p_grid"][:, i]) 254 | atm.data = np.zeros((len(atm.grids[0]), len(atm.grids[1]), 1, 1)) 255 | 256 | for j, var in enumerate(atm_fieldnames): 257 | if var in rawdata: 258 | atm.data[j, :, 0, 0] = rawdata[var][:, i] 259 | 260 | # Convert mass densities from g m^{-3} to kg m^{-3} 261 | if "mass_density" in var: 262 | atm.data[j, :, 0, 0] /= 1000 263 | 264 | # convert relative humidity to vmr 265 | if var == "abs_species-H2O": 266 | temp = ( 267 | atm.data[j, :, 0, 0] 268 | * e_eq_mixed_mk(atm.data[0, :, 0, 0]) 269 | / atm.grids[1][:] 270 | ) 271 | atm.data[j, :, 0, 0] = temp 272 | 273 | if var == "abs_species-O2": 274 | atm.data[j, :, 0, 0] = O2vmr 275 | 276 | if var == "abs_species-N2": 277 | atm.data[j, :, 0, 0] = N2vmr 278 | 279 | 280 | batch_atms.append(deepcopy(atm)) 281 | 282 | # now the aux data 283 | for j, var in enumerate(aux2d_fieldnames): 284 | aux2d.data[j, :] = rawdata[var][:, i] 285 | 286 | batch_aux2d.append(deepcopy(aux2d)) 287 | 288 | for j, var in enumerate(aux1d_fieldnames): 289 | aux1d.data[j] = rawdata[var][i] 290 | 291 | batch_aux1d.append(deepcopy(aux1d)) 292 | 293 | 294 | # %% before we save the data, let's do some checks 295 | 296 | # calculate water columns and plot them 297 | 298 | columns = {} 299 | col_names = ["LWP", "IWP", "RWP", "SWP", "GWP", "HWP", "IWV"] 300 | content_names = ["LWC", "IWC", "RWC", "SWC", "GWC", "HWC", "H2O"] 301 | for name in col_names: 302 | columns[name] = np.zeros(N_profiles) 303 | 304 | 305 | for i in range(N_profiles): 306 | 307 | for j, name in enumerate(col_names): 308 | 309 | names = [str(name) for name in batch_atms[i].grids[0]] 310 | idx = [k for k in range(len(names)) if content_names[j] in names[k]] 311 | 312 | if len(idx) > 1: 313 | idx = [idx_k for idx_k in idx if "mass" in names[idx_k]][0] 314 | else: 315 | idx = idx[0] 316 | 317 | if name == "IWV": 318 | # WV=rawdata.vmr_h2o./T.*p/R_h2o; 319 | density = ( 320 | batch_atms[i].data[idx, :, 0, 0] 321 | / batch_atms[i].data[0, :, 0, 0] 322 | * batch_atms[i].grids[1] 323 | / gas_constant_water_vapor 324 | ) 325 | 326 | columns[name][i] = np.trapezoid(density, x=batch_atms[i].data[1, :, 0, 0]) 327 | else: 328 | 329 | # breakpoint() 330 | 331 | columns[name][i] = np.trapezoid( 332 | batch_atms[i].data[idx, :, 0, 0], 333 | x=batch_atms[i].data[1, :, 0, 0], 334 | ) 335 | 336 | 337 | # %% plot vertical coluns for check for checks 338 | 339 | lat = np.array([a.data[0] for a in batch_aux1d]) 340 | 341 | 342 | # plt.style.use('ggplot') 343 | fig, ax = plt.subplots(4, 2, figsize=(16, 10)) 344 | 345 | 346 | for i, name in enumerate(col_names): 347 | ax_k = ax[i // 2, i % 2] 348 | ax_k.plot(lat, columns[name], label=name) 349 | ax_k.set_title(name) 350 | ax_k.set_xlabel("latitude / °") 351 | ax_k.set_ylabel("column / kg m$^{-1}$ ") 352 | 353 | 354 | # %% Create 'dropsonde data' 355 | # simply select one of these profiles 356 | # to keep it simple we simply take the middle 357 | 358 | idx_selected = N_profiles // 2 359 | 360 | dropsonde = pa.arts.GriddedField4() 361 | dropsonde.set_grid(0, ["T", "z", "abs_species-H2O"]) 362 | dropsonde.set_grid(1, batch_atms[idx_selected].grids[1][:]) 363 | dropsonde.data = np.zeros((3, len(dropsonde.grids[1]), 1, 1)) 364 | dropsonde.data[0, :, 0, 0] = batch_atms[idx_selected].data[0, :, 0, 0] 365 | dropsonde.data[1, :, 0, 0] = batch_atms[idx_selected].data[1, :, 0, 0] 366 | dropsonde.data[2, :, 0, 0] = batch_atms[idx_selected].data[14, :, 0, 0] 367 | 368 | # add some noise 369 | rng = np.random.default_rng(12345) 370 | T_noise_free = dropsonde.data[0, :, 0, 0] * 1.0 371 | dropsonde.data[0, :, 0, 0] += rng.normal(0, 0.5, len(dropsonde.grids[1]))+T_offset 372 | 373 | vmr_noise_free = dropsonde.data[2, :, 0, 0] * 1.0 374 | temp = np.log10(dropsonde.data[2, :, 0, 0]) 375 | temp += rng.normal(0, 0.05, len(dropsonde.grids[1])) 376 | dropsonde.data[2, :, 0, 0] = 10**temp 377 | 378 | 379 | # plot dropsonde data 380 | fig2, ax2 = plt.subplots(1, 2, figsize=(10, 5)) 381 | 382 | ax2[0].plot(dropsonde.data[0, :, 0, 0], dropsonde.grids[1] / 1e3, label="obs") # T 383 | ax2[0].plot(T_noise_free, dropsonde.grids[1] / 1e3, label="true") 384 | ax2[0].set_title("Temperature") 385 | ax2[0].set_xlabel("T / K") 386 | ax2[0].set_ylabel("p / hPa") 387 | ax2[0].set_yscale("log") 388 | ax2[0].invert_yaxis() 389 | ax2[0].legend() 390 | 391 | ax2[1].loglog(dropsonde.data[2, :, 0, 0], dropsonde.grids[1] / 1e3, label="obs") # H2O 392 | ax2[1].loglog(vmr_noise_free, dropsonde.grids[1] / 1e3, label="true") 393 | ax2[1].set_title("H2O") 394 | ax2[1].set_xlabel("H2O / vmr") 395 | ax2[1].set_ylabel("p / hPa") 396 | ax2[1].invert_yaxis() 397 | ax2[1].legend() 398 | 399 | 400 | 401 | # %% now plot profles for check 402 | 403 | plot_vars = ["T", "abs_species-H2O"] + [ 404 | name for name in atm_fieldnames if "mass_density" in name 405 | ] 406 | # plot_vars=['T'] 407 | 408 | fig1, ax1 = plt.subplots(4, 2, figsize=(16, 10), sharey=True, sharex=True) 409 | 410 | p_grid = np.mean([a.grids[1] for a in batch_atms], axis=0) 411 | 412 | 413 | for i, name in enumerate(plot_vars): 414 | 415 | print(f"{name} - {i}") 416 | 417 | ax_k = ax1[i // 2, i % 2] 418 | 419 | names = [str(name) for name in batch_atms[0].grids[0]] 420 | idx = [k for k in range(len(names)) if name in names[k]][0] 421 | 422 | print(f"idx: {idx}") 423 | 424 | data = np.array([a.data[idx, :, 0, 0] for a in batch_atms]) 425 | 426 | if name == "T": 427 | data -= np.mean(data, axis=0) 428 | # data -= dropsonde.data[0,:,0,0] 429 | cmap = "YlOrRd" 430 | pcm = ax_k.pcolormesh( 431 | lat, 432 | p_grid / 1e3, 433 | data.T, 434 | cmap=cmap, 435 | clim=[np.min(data), np.max(data)], 436 | rasterized=True, 437 | ) 438 | 439 | else: 440 | data[data < 1e-10] = 1e-10 441 | data = np.log10(data) 442 | cmap = "Blues" 443 | pcm = ax_k.pcolormesh( 444 | lat, p_grid / 1e3, data.T, cmap=cmap, clim=[-6, 1], rasterized=True 445 | ) 446 | 447 | ax_k.set_title(name) 448 | ax_k.set_xlabel("latitude / °") 449 | ax_k.set_ylabel("p / hPa") 450 | ax_k.set_yscale("log") 451 | 452 | cbar = fig1.colorbar(pcm, ax=ax_k) 453 | 454 | if name == "T": 455 | cbar.set_label("T - T$_{mean}$ / K") 456 | elif "mass_density" in name: 457 | cbar.set_label(" 10$^x$ kg m$^{-3}$") 458 | elif name == "abs_species-H2O": 459 | cbar.set_label("10$^x$ vmr") 460 | 461 | 462 | ax_k.invert_yaxis() 463 | 464 | 465 | 466 | # %% save data 467 | 468 | batch_atms.savexml("atmosphere/atmospheres_true.xml") 469 | batch_aux1d.savexml("atmosphere/aux1d_true.xml") 470 | batch_aux2d.savexml("atmosphere/aux2d_true.xml") 471 | 472 | dropsonde.savexml("observation/dropsonde.xml") 473 | 474 | # %% save figures 475 | 476 | fig1.savefig("check_plots/profiles.pdf") 477 | fig.savefig("check_plots/columns.pdf") 478 | fig2.savefig("check_plots/dropsonde.pdf") 479 | -------------------------------------------------------------------------------- /exercises/06-non_lin_inversion/simulate_airborne_measurement.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Simulate airborne radiometric measurements from a HALO-like airplane. 5 | 6 | This script simulates radiometric measurements of atmospheric radiation in three frequency bands: 7 | - H2O (22 GHz) 8 | - O2_low (50 GHz) 9 | - O2_high (118 GHz) 10 | 11 | The simulation assumes: 12 | - Aircraft altitude of 15 km 13 | - Nadir-looking geometry (180 degree line of sight) 14 | - Known constant surface reflectivity of 0.4 15 | - Known constant surface temperature of 300K 16 | 17 | The script: 18 | 1. Loads atmospheric data and auxiliary information 19 | 2. Simulates clean measurements for each frequency band 20 | 3. Adds random measurement noise based on specified NeDT 21 | 4. Saves results as XML files 22 | 5. Generates plots of simulated measurements vs latitude 23 | 24 | Files: 25 | Input: 26 | - atmosphere/atmospheres_true.xml: Atmospheric profiles 27 | - atmosphere/aux1d_true.xml: Auxiliary data including latitudes 28 | 29 | Output: 30 | - observation/y_obs_*.xml: Simulated noisy measurements 31 | - observation/f_grid_*.xml: Frequency grids 32 | - observation/lat.xml: Latitude points 33 | - check_plots/airborne_measurement.pdf: Visualization of results 34 | 35 | Dependencies: 36 | - numpy: For numerical operations 37 | - matplotlib: For plotting 38 | - pyarts: For XML handling 39 | - nonlin_oem: Custom module for radiative transfer calculations 40 | 41 | Created on Sat Jan 4 00:42:52 2025 42 | @author: Manfred Brath 43 | """ 44 | 45 | import numpy as np 46 | import matplotlib.pyplot as plt 47 | from pyarts import xml 48 | 49 | import nonlin_oem as nlo 50 | 51 | 52 | # ============================================================================= 53 | # %% paths and constants 54 | # ============================================================================= 55 | 56 | 57 | # atmospheric data 58 | atms = xml.load("atmosphere/atmospheres_true.xml") 59 | auxs = xml.load("atmosphere/aux1d_true.xml") 60 | 61 | # surface reflectivity 62 | # It is assumed that the surface reflectivity is known and constant 63 | surface_reflectivity = 0.4 64 | 65 | # surface temperature 66 | # It is assumed that the surface temperature is known and constant 67 | surface_temperature = 300.0 # K 68 | 69 | # define sensor positions and line of sight 70 | # we assume a HALO like airplane with a sensor at 15 km altitude and a line of sight of 180 degrees 71 | sensor_altitude = 15000. 72 | sensor_los = 180. 73 | 74 | # set the random number generator 75 | rng = np.random.default_rng(12345) 76 | 77 | 78 | # latitude 79 | lat = np.array([auxs[i].data[0] for i in range(len(auxs))]) 80 | 81 | # ============================================================================= 82 | # %% simulate the observation 83 | 84 | 85 | bands = ["K", "V", "F"] 86 | suffixes = ["22GHz", "50GHz", "118GHz"] 87 | 88 | 89 | results = {} 90 | for band, suffix in zip(bands, suffixes): 91 | 92 | print(f"\n{band}\n") 93 | 94 | # set the "channels" (freqquency grid) for the simulation 95 | sensor_description_fine, NeDT, Accuracy, FWHM_Antenna=nlo.Hamp_channels([band], rel_mandatory_grid_spacing=1./1.) 96 | 97 | # # setup the basics 98 | # ws = nlo.basic_setup(f_grid) 99 | 100 | y = np.zeros((len(atms), np.size(sensor_description_fine,0))) 101 | 102 | for i in range(len(atms)): 103 | if i % 10 == 0: 104 | print(f"Simulating measurement for atm {i}") 105 | 106 | atm = atms[i] 107 | y[i, :], _ = nlo.Forward_model( 108 | [], 109 | atm, 110 | surface_reflectivity, 111 | surface_temperature, 112 | sensor_altitude, 113 | sensor_los, 114 | sensor_description=sensor_description_fine 115 | ) 116 | 117 | 118 | y_obs = y + rng.normal(0, NeDT, y.shape) 119 | 120 | 121 | results[("y_" + band)] = y 122 | results[("y_obs_" + band)] = y_obs 123 | results[("f_grid_" + band)] = sensor_description_fine[:,0]+sensor_description_fine[:,1]+sensor_description_fine[:,2] 124 | results[('NeDT_'+band)]=NeDT 125 | results[(band + "_suffix")] = suffix 126 | results[('sensor_description_' + band)] = sensor_description_fine 127 | 128 | Y = nlo.pa.arts.Matrix(y) 129 | Y_obs = nlo.pa.arts.Matrix(y_obs) 130 | SensorCharacteristics = nlo.pa.arts.Matrix(sensor_description_fine) 131 | 132 | Y_obs.savexml(f"observation/y_obs_{suffix}.xml") 133 | SensorCharacteristics.savexml( 134 | f"observation/SensorCharacteristics_{suffix}.xml" 135 | ) 136 | 137 | print("ddd") 138 | 139 | 140 | Lat = nlo.pa.arts.Vector(lat) 141 | Lat.savexml("observation/lat.xml") 142 | 143 | 144 | # %% plot results 145 | 146 | cmap = np.array( 147 | [ 148 | [0, 0.44701, 0.74101, 1], 149 | [0.85001, 0.32501, 0.09801, 1], 150 | [0.92901, 0.69401, 0.12501, 1], 151 | [0.49401, 0.18401, 0.55601, 1], 152 | [0.46601, 0.67401, 0.18801, 1], 153 | [0.30101, 0.74501, 0.93301, 1], 154 | [0.63501, 0.07801, 0.18401, 1], 155 | ] 156 | ) 157 | 158 | 159 | fig, ax = plt.subplots(3, 1, figsize=(16, 10)) 160 | 161 | for i, band in enumerate(bands): 162 | 163 | ax[i].set_prop_cycle(color=cmap) 164 | sen_desc=results[f"sensor_description_{band}"] 165 | 166 | for j in range(np.size(sen_desc,0)): 167 | if sen_desc[j,1]+sen_desc[j,2]>0: 168 | label=f"{sen_desc[j,0]/1e9:0.2f}"+" $\pm$ "+f"{(sen_desc[j,1]+sen_desc[j,2])/1e9:0.2f} GHz" 169 | else: 170 | label=f"{sen_desc[j,0]/1e9:0.2f}" 171 | 172 | ax[i].plot(lat, results[(f"y_{band}")][:, j], label=label) 173 | 174 | ax[i].set_prop_cycle(color=cmap) 175 | for j in range(np.size(sen_desc,0)): 176 | if sen_desc[j,1]+sen_desc[j,2]>0: 177 | label=f"{sen_desc[j,0]/1e9:0.2f}"+" $\pm$ "+f"{(sen_desc[j,1]+sen_desc[j,2])/1e9:0.2f} GHz" 178 | else: 179 | label=f"{sen_desc[j,0]/1e9:0.2f}" 180 | ax[i].plot( 181 | lat, results[(f"y_obs_{band}")][:, j], "--", label=f"{label} (obs)" 182 | ) 183 | 184 | ax[i].set_title(band) 185 | ax[i].legend() 186 | ax[i].set_xlabel("Latitude") 187 | ax[i].set_ylabel("Brightness temperature [K]") 188 | ax[i].grid() 189 | 190 | 191 | fig.tight_layout() 192 | fig.savefig("check_plots/airborne_measurement.pdf") 193 | -------------------------------------------------------------------------------- /exercises/06-non_lin_inversion/temperature_retrieval.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Temperature Retrieval Analysis Script 5 | 6 | This script performs temperature profile retrievals from radiometric observations 7 | in the 50 GHz range using the Optimal Estimation Method (OEM). 8 | 9 | The script: 10 | 1. Loads atmospheric data (true profiles, a priori data from dropsondes) 11 | 2. Loads observation data (frequencies, measurements, location) 12 | 3. Performs single profile retrieval with diagnostics 13 | 4. Performs retrieval for a complete observation segment 14 | 5. Creates visualization plots of the results 15 | 16 | Key Features: 17 | - Single profile retrieval with averaging kernels and gain matrix analysis 18 | - Multiple profile retrieval along a latitude segment 19 | - Comparison between retrieved, true and a priori profiles 20 | - Uncertainty estimation and visualization 21 | - Observation fitting analysis and residuals 22 | 23 | Required Data Files: 24 | - atmosphere/atmospheres_true.xml: True atmospheric profiles 25 | - observation/dropsonde.xml: A priori and background data 26 | - observation/SensorCharacteristics_50GHz.xml: Sensor characteristics 27 | - observation/y_obs_50GHz.xml: Observation vector 28 | - observation/lat.xml: Latitude information 29 | 30 | Dependencies: 31 | numpy 32 | matplotlib 33 | pyarts 34 | nonlin_oem 35 | 36 | @author: Manfred Brath 37 | Created: Tue Jan 7 12:13:59 2025 38 | 39 | """ 40 | 41 | import numpy as np 42 | import matplotlib.pyplot as plt 43 | 44 | import pyarts as pa 45 | import nonlin_oem as nlo 46 | 47 | 48 | # ============================================================================= 49 | # %% read data 50 | # ============================================================================= 51 | 52 | # surface temperature 53 | surface_temperature = 300 # K 54 | 55 | # surface reflectivity 56 | surface_reflectivity = 0.4 57 | 58 | # sensor position and line of sight 59 | sensor_pos = 15e3 60 | sensor_los = 180.0 61 | 62 | # load true data 63 | atms = pa.xml.load("atmosphere/atmospheres_true.xml") 64 | 65 | # load dropsonde data this will serve as a priori and background data 66 | dropsonde = pa.xml.load("observation/dropsonde.xml") 67 | 68 | # load frequency data for 50 GHz channels 69 | sensor_characteristics=pa.xml.load("observation/SensorCharacteristics_50GHz.xml")[:] 70 | 71 | # load measurement vector 72 | y_obs = pa.xml.load("observation/y_obs_50GHz.xml") 73 | 74 | lat = pa.xml.load("observation/lat.xml") 75 | 76 | NeDT = 0.2 # K 77 | 78 | # %% do the retrieval for one observation 79 | 80 | # select obervation idx 81 | idx = 328 82 | y = y_obs[idx, :] 83 | 84 | # create and add a priori covariance matrix 85 | delta_x = 6 86 | correlation_length = nlo.set_correlation_length( 87 | dropsonde.get("z", keep_dims=False), len_sfc=500, len_toa=2e3 88 | ) 89 | S_a = nlo.create_apriori_covariance_matrix( 90 | dropsonde.get("T", keep_dims=False), 91 | dropsonde.get("z", keep_dims=False), 92 | delta_x, 93 | correlation_length, 94 | ) 95 | 96 | # Define Se and its invers 97 | S_y = pa.arts.Sparse(np.diag(np.ones(np.size(sensor_characteristics,0)) * NeDT)) 98 | 99 | #Temperature retrieval for selected observation 100 | T_ret, DeltaT, y_fit, A, G = nlo.temperature_retrieval( 101 | y, 102 | [], 103 | sensor_pos, 104 | sensor_los, 105 | dropsonde, 106 | surface_temperature, 107 | surface_reflectivity, 108 | S_y, 109 | S_a, 110 | Diagnostics=True, 111 | Verbosity=True, 112 | sensor_description=sensor_characteristics 113 | ) 114 | 115 | 116 | # %% plot results for the single observation 117 | 118 | T_apr = dropsonde.get("T", keep_dims=False) 119 | T_true = atms[idx].get("T", keep_dims=False) 120 | z_true = atms[idx].get("z", keep_dims=False) 121 | z_ret = dropsonde.get("z", keep_dims=False) 122 | DeltaT_apriori = np.sqrt(np.diag(S_a)) 123 | 124 | # Frequencies of the channels 125 | f_grid=sensor_characteristics[:,0]+sensor_characteristics[:,1]+sensor_characteristics[:,2] 126 | 127 | fig, ax = plt.subplots(1, 3, figsize=(14.14, 10)) 128 | 129 | #plot difference to true temperature 130 | ax[0].plot(T_ret - T_true, z_ret, "s-", color="r", label="ret") 131 | ax[0].fill_betweenx( 132 | z_ret, T_ret - T_true - DeltaT, T_ret - T_true + DeltaT, alpha=0.3, color="r" 133 | ) 134 | ax[0].plot(T_apr - T_true, z_ret, "o-", color="g", label="apriori") 135 | ax[0].fill_betweenx( 136 | z_ret, 137 | T_apr - T_true - DeltaT_apriori, 138 | T_apr - T_true + DeltaT_apriori, 139 | alpha=0.3, 140 | color="g", 141 | ) 142 | ax[0].set_title("Difference to T$_{true}$") 143 | ax[0].set_xlabel("Temperature [K]") 144 | ax[0].set_ylabel("Altitude [m]") 145 | ax[0].legend() 146 | 147 | # Plot true temperature profile 148 | ax[1].plot(T_true, z_true, "x-", label="truth") 149 | ax[1].set_xlabel("Temperature [K]") 150 | ax[1].set_ylabel("Altitude [m]") 151 | ax[1].set_title("T$_{true}$") 152 | 153 | ax[2].plot(f_grid, y, "x-", label="obs") 154 | ax[2].plot(f_grid, y_fit, "s-", label="fit") 155 | ax[2].set_xlabel("Frequency [GHz]") 156 | ax[2].set_ylabel("Brightness temperature [K]") 157 | ax[2].legend() 158 | fig.tight_layout() 159 | 160 | 161 | #new figure 162 | fig2, ax2 = plt.subplots(1, 2, figsize=(14.14, 10)) 163 | 164 | #plot averaging kernels 165 | colormap = plt.cm.YlOrRd 166 | colors = [colormap(i) for i in np.linspace(0, 1, np.size(A, 1))] 167 | for i in range(np.size(A, 1)): 168 | ax2[0].plot( 169 | A[:, i], 170 | z_ret, 171 | color=colors[i], 172 | ) 173 | A_sum = np.sum(A, axis=1) 174 | ax2[0].plot(A_sum / 5, z_ret, color="k", label="$\Sigma A_{i}$/5") 175 | ax2[0].legend() 176 | ax2[0].set_xlabel("Altitude [m]") 177 | ax2[0].set_ylabel("Altitude [m]") 178 | ax2[0].set_title("Averaging Kernels") 179 | 180 | 181 | #plot gain matrix 182 | for i in range(np.size(G, 1)): 183 | ax2[1].plot(G[:, i], z_ret, label=f"{f_grid[i]/1e9:.2f}GHz") 184 | ax2[1].set_title("Gain Matrix") 185 | ax2[1].legend() 186 | ax2[1].set_xlabel("Contributions / KK$^{-1}$") 187 | ax2[1].set_ylabel("Altitude [m]") 188 | fig.tight_layout() 189 | 190 | 191 | # %% now do the retrieval for the whole segment 192 | 193 | #allocate 194 | DeltaT_all = np.zeros((np.size(y_obs, 0), dropsonde.get("z", keep_dims=False).size)) 195 | T_all = np.zeros((np.size(y_obs, 0), dropsonde.get("z", keep_dims=False).size)) 196 | y_fit_all = np.zeros((np.size(y_obs, 0), len(f_grid))) 197 | A_summed_all = np.zeros((np.size(y_obs, 0), dropsonde.get("z", keep_dims=False).size)) 198 | G_summed_all = np.zeros((np.size(y_obs, 0), dropsonde.get("z", keep_dims=False).size)) 199 | 200 | 201 | #loop over the observations 202 | for i in range(np.size(y_obs, 0)): 203 | 204 | print(f"...retrieving profile {i} of {np.size(y_obs, 0)}") 205 | 206 | y = y_obs[i, :] 207 | T_ret, DeltaT, y_fit, A, G = nlo.temperature_retrieval( 208 | y, 209 | [], 210 | sensor_pos, 211 | sensor_los, 212 | dropsonde, 213 | surface_temperature, 214 | surface_reflectivity, 215 | S_y, 216 | S_a, 217 | Diagnostics=True, 218 | sensor_description=sensor_characteristics 219 | ) 220 | 221 | 222 | DeltaT_all[i, :] = DeltaT 223 | T_all[i, :] = T_ret 224 | y_fit_all[i, :] = y_fit 225 | A_summed_all[i, :] = np.sum(A, axis=1) 226 | G_summed_all[i, :] = np.sum(G, axis=1) 227 | 228 | 229 | 230 | # %% plot the whole segment 231 | 232 | fig, ax = plt.subplots(2, 2, figsize=(14.14, 10)) 233 | 234 | #plot difference to a priori 235 | cmap = "YlOrRd" 236 | data = T_all - T_apr # T_all.mean(0) 237 | data_max = np.max(np.abs(data)) 238 | pcm = ax[0, 0].pcolormesh( 239 | lat, z_ret / 1e3, data.T, cmap="RdBu_r", clim=[-data_max, data_max], rasterized=True 240 | ) 241 | fig.colorbar(pcm, ax=ax[0, 0], label="$\Delta T$ [K]") 242 | ax[0, 0].set_xlabel("Latitude [deg]") 243 | ax[0, 0].set_ylabel("Altitude [km]") 244 | ax[0, 0].set_title("Difference to a priori") 245 | 246 | data = DeltaT_all 247 | pcm = ax[0, 1].pcolormesh( 248 | lat, 249 | z_ret / 1e3, 250 | data.T, 251 | cmap=cmap, 252 | clim=[np.min(data), np.max(data)], 253 | rasterized=True, 254 | ) 255 | 256 | #plot retrieved terperature uncertainty 257 | fig.colorbar(pcm, ax=ax[0, 1], label="$\Delta T$ [K]") 258 | ax[0, 1].set_xlabel("Latitude [deg]") 259 | ax[0, 1].set_ylabel("Altitude [km]") 260 | ax[0, 1].set_title("Retrieved Temperature uncertainty") 261 | 262 | #plot observed and fitted brightness temperatures 263 | for i in range(np.size(y_obs, 1)): 264 | ax[1, 0].plot(lat, y_obs[:, i], label=f"{f_grid[i]/1e9:.2f}$\,$GHz, obs") 265 | ax[1, 0].set_prop_cycle(None) 266 | for i in range(np.size(y_fit_all, 1)): 267 | ax[1, 0].plot( 268 | lat, y_fit_all[:, i], "--" 269 | ) 270 | ax[1, 0].set_prop_cycle(None) 271 | ax[1, 0].set_xlabel("Latitude [deg]") 272 | ax[1, 0].set_ylabel("Brightness temperature [K]") 273 | ax[1, 0].legend() 274 | 275 | #plot residuals 276 | ax[1, 1].plot(lat, y_obs - y_fit_all, "-", label="residual") 277 | ax[1, 1].set_xlabel("Latitude [deg]") 278 | ax[1, 1].set_ylabel("Brightness temperature [K]") 279 | ax[1, 1].set_title("Residuals") 280 | 281 | fig.tight_layout() 282 | -------------------------------------------------------------------------------- /exercises/07-olr/olr.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%matplotlib widget" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "## Exercise No. 6 -- Outgoing Longwave Radiation (OLR)\n", 17 | "\n", 18 | "#### 1)\n", 19 | "\n", 20 | "Run ARTS on the Jupyter notebook olr.ipynb . This will calculate the spectrum\n", 21 | "of outgoing longwave radiation for a midlatitude-summer atmosphere.\n", 22 | "Our calculation trades accuracy for computational efficiency. For\n", 23 | "example, we use only water vapor and carbon dioxide as absorbers.\n", 24 | "We use only 300 frequency grid points and approximately 54,000 spectral\n", 25 | "lines, whereas for accurate calculations one needs at least 10,000\n", 26 | "frequency grid points and 500,000 spectral lines, taking into account\n", 27 | "absorbing species like ozone and methane. The script plots the spectral\n", 28 | "irradiance, in SI units, as a function of wavenumber. Planck curves\n", 29 | "for different temperatures are shown for comparison. We integrate\n", 30 | "the whole spectrum to quantify the power per square that is emitted\n", 31 | "by the atmosphere (value in title).\n", 32 | "\n", 33 | "* How would the OLR spectrum look in units of brightness temperature?\n", 34 | "* How would the Planck curves look in units of brightness temperature?\n", 35 | "* Find the $\\text{CO}_{2}$ absorption band and the regions of $\\text{H}_{2}\\text{O}$ \n", 36 | "absorption. From which height in the atmosphere does the radiation in the $\\text{CO}_{2}$ band originate?\n", 37 | "* Are there window regions?\n", 38 | "* What will determine the OLR in the window regions?\n", 39 | "* Use the plot to explain the atmospheric greenhouse effect." 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": null, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "import os\n", 49 | "import matplotlib.pyplot as plt\n", 50 | "import numpy as np\n", 51 | "from pyarts import xml\n", 52 | "from pyarts.arts import convert\n", 53 | "from olr_module import calc_olr_from_atmfield, Change_T_with_RH_const, cmap2rgba, planck\n", 54 | "\n", 55 | "os.makedirs(\"plots\", exist_ok=True)" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": null, 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "# Read input atmosphere\n", 65 | "atmfield = xml.load(\"input/midlatitude-summer.xml\")\n", 66 | "\n", 67 | "# Scale the CO2 concentration\n", 68 | "atmfield.scale(\"abs_species-CO2\", 1)\n", 69 | "\n", 70 | "# Add a constant value to the temperature\n", 71 | "atmfield.set(\"T\", atmfield.get(\"T\") + 0)\n", 72 | "\n", 73 | "# Add a constant value to the temperature but \n", 74 | "# without changing relative humidity \n", 75 | "atmfield = Change_T_with_RH_const(atmfield,DeltaT=0)\n", 76 | "\n", 77 | "# Calculate the outgoing-longwave radiation\n", 78 | "f, olr = calc_olr_from_atmfield(atmfield, verbosity=0)" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": null, 84 | "metadata": {}, 85 | "outputs": [], 86 | "source": [ 87 | "# Plotting.\n", 88 | "import matplotlib\n", 89 | "font = {'size' : 12}\n", 90 | "matplotlib.rc('font', **font)\n", 91 | "\n", 92 | "wn = convert.freq2kaycm(f)\n", 93 | "\n", 94 | "temps = [225, 250, 275, atmfield.get(\"T\", keep_dims=False)[0]]\n", 95 | "temp_colors = cmap2rgba(\"plasma\", len(temps))\n", 96 | "\n", 97 | "fig, ax = plt.subplots()\n", 98 | "for t, color in sorted(zip(temps, temp_colors)):\n", 99 | " ax.plot(\n", 100 | " wn, np.pi * planck(f, t), label=f\"{t:3.1f} K\", color=color\n", 101 | " )\n", 102 | "ax.plot(wn, olr, color=\"C0\", label=\"Irradiance\")\n", 103 | "ax.legend()\n", 104 | "ax.set_title(rf\"OLR={np.trapezoid(olr, f):3.2f} $\\sf Wm^{{-2}}$\")\n", 105 | "ax.set_xlim(wn.min(), wn.max())\n", 106 | "ax.set_xlabel(r\"Wavenumber [$\\sf cm^{-1}$]\")\n", 107 | "ax.set_ylabel(r\"Irradiance [$\\sf Wm^{-2}Hz^{-1}$]\")\n", 108 | "ax.set_ylim(bottom=0)\n", 109 | "fig.savefig(\"plots/olr.pdf\")" 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "metadata": {}, 115 | "source": [ 116 | "#### 2) \n", 117 | "\n", 118 | "Investigate how the OLR changes for different atmospheric conditions\n", 119 | "by modifying the input data. \n", 120 | "Use `atmfield.scale(...)`,\n", 121 | "`atmfield.set(...)` and `Change_T_with_RH_const(...)` to change the atmospheric data:\n", 122 | "\n", 123 | "* Add $1\\,\\,\\text{K}$ to the temperature.\n", 124 | "* Add $1\\,\\,\\text{K}$ to the temperature but hold relative humidity constant.\n", 125 | "* Increase the $\\text{CO}_{2}$ conentration by a factor of $2$.\n", 126 | "* Increase the $\\text{H}_{2}\\text{O}$ conentration by a factor of $1.2$.\n", 127 | " \n", 128 | "1) Change it, and calculate and plot the spectrum for each change. \n", 129 | "2) Compare the spectra of the changed atmosphere with the unchanged OLR spectrum. Where\n", 130 | " do the changes occur? Explain the differnt effects of the changed atmospheres.\n", 131 | "3) Compare the OLR numbers, which is the more potent greenhouse gas,\n", 132 | "$\\text{CO}_{2}$ or $\\text{H}_{2}\\text{O}$?" 133 | ] 134 | } 135 | ], 136 | "metadata": { 137 | "kernelspec": { 138 | "display_name": "Python 3 (ipykernel)", 139 | "language": "python", 140 | "name": "python3" 141 | }, 142 | "language_info": { 143 | "codemirror_mode": { 144 | "name": "ipython", 145 | "version": 3 146 | }, 147 | "file_extension": ".py", 148 | "mimetype": "text/x-python", 149 | "name": "python", 150 | "nbconvert_exporter": "python", 151 | "pygments_lexer": "ipython3", 152 | "version": "3.12.5" 153 | }, 154 | "vscode": { 155 | "interpreter": { 156 | "hash": "6bd45fef6a38d15b43f43de43ba5066924911f80576952f97fb08adaede44831" 157 | } 158 | } 159 | }, 160 | "nbformat": 4, 161 | "nbformat_minor": 4 162 | } 163 | -------------------------------------------------------------------------------- /exercises/07-olr/olr.py: -------------------------------------------------------------------------------- 1 | # %% 2 | """Simulate and plot Earth's outgoing longwave radiation (OLR).""" 3 | import pyarts.workspace 4 | import numpy as np 5 | 6 | 7 | def calc_olr_from_profiles( 8 | pressure_profile, 9 | temperature_profile, 10 | h2o_profile, 11 | N2=0.78, 12 | O2=0.21, 13 | CO2=400e-6, 14 | CH4=1.8e-6, 15 | O3=0.0, 16 | surface_altitude=0.0, 17 | nstreams=10, 18 | fnum=300, 19 | fmin=1e6, 20 | fmax=75e12, 21 | verbosity=0, 22 | version='2.6.8' 23 | ): 24 | """Calculate the outgoing-longwave radiation for a given atmosphere profiles. 25 | 26 | Parameters: 27 | 28 | pressure_profile (ndarray): Pressure profile [Pa]. 29 | temperature_profile (ndarray): Temperature profile [K]. 30 | h2o_profile (ndarray): Water vapor profile [VMR]. 31 | N2 (float): Nitrogen volume mixing ratio. Defaults to 0.78. 32 | O2 (float): Oxygen volume mixing ratio. Defaults to 0.21. 33 | CO2 (float): Carbon dioxide volume mixing ratio. Defaults to 400 ppm. 34 | CH4 (float): Methane volume mixing ratio. Defaults to 1.8 ppm. 35 | O3 (float): Ozone volume mixing ratio. Defaults to 0. 36 | surface_altitude (float): Surface altitude [m]. Defaults to 0. 37 | nstreams (int): Even number of streams to integrate the radiative fluxes. 38 | fnum (int): Number of points in frequency grid. 39 | fmin (float): Lower frequency limit [Hz]. 40 | fmax (float): Upper frequency limit [Hz]. 41 | verbosity (int): Reporting levels between 0 (only error messages) 42 | and 3 (everything). 43 | 44 | Returns: 45 | ndarray, ndarray: Frequency grid [Hz], OLR [Wm^-2] 46 | 47 | """ 48 | 49 | pyarts.cat.download.retrieve(verbose=True, version=version) 50 | 51 | if fmin < 1e6: 52 | raise RuntimeError('fmin must be >= 1e6 Hz') 53 | 54 | ws = pyarts.workspace.Workspace(verbosity=0) 55 | ws.water_p_eq_agendaSet() 56 | ws.gas_scattering_agendaSet() 57 | ws.PlanetSet(option="Earth") 58 | 59 | ws.verbositySetScreen(ws.verbosity, verbosity) 60 | 61 | # Number of Stokes components to be computed 62 | ws.IndexSet(ws.stokes_dim, 1) 63 | 64 | # No jacobian calculation 65 | ws.jacobianOff() 66 | 67 | ws.abs_speciesSet( 68 | species=[ 69 | "H2O, H2O-SelfContCKDMT400, H2O-ForeignContCKDMT400", 70 | "CO2, CO2-CKDMT252", 71 | "CH4", 72 | "O2,O2-CIAfunCKDMT100", 73 | "N2, N2-CIAfunCKDMT252, N2-CIArotCKDMT252", 74 | "O3", 75 | ] 76 | ) 77 | 78 | # Read line catalog 79 | ws.abs_lines_per_speciesReadSpeciesSplitCatalog(basename="lines/") 80 | 81 | # Load CKDMT400 model data 82 | ws.ReadXML(ws.predefined_model_data, "model/mt_ckd_4.0/H2O.xml") 83 | 84 | # Read cross section data 85 | ws.ReadXsecData(basename="lines/") 86 | 87 | ws.abs_lines_per_speciesCutoff(option="ByLine", value=750e9) 88 | ws.abs_lines_per_speciesTurnOffLineMixing() 89 | 90 | # Create a frequency grid 91 | ws.VectorNLinSpace(ws.f_grid, int(fnum), float(fmin), float(fmax)) 92 | 93 | # Throw away lines outside f_grid 94 | ws.abs_lines_per_speciesCompact() 95 | 96 | # Calculate absorption 97 | ws.propmat_clearsky_agendaAuto() 98 | 99 | # Weakly reflecting surface 100 | ws.VectorSetConstant(ws.surface_scalar_reflectivity, 1, 0.0) 101 | 102 | # Atmosphere and surface 103 | ws.Touch(ws.lat_grid) 104 | ws.Touch(ws.lon_grid) 105 | ws.lat_true = np.array([0.0]) 106 | ws.lon_true = np.array([0.0]) 107 | 108 | ws.AtmosphereSet1D() 109 | ws.p_grid = pressure_profile 110 | ws.t_field = temperature_profile[:, np.newaxis, np.newaxis] 111 | 112 | vmr_field = np.zeros((6, len(pressure_profile), 1, 1)) 113 | vmr_field[0, :, 0, 0] = h2o_profile 114 | vmr_field[1, :, 0, 0] = CO2 115 | vmr_field[2, :, 0, 0] = CH4 116 | vmr_field[3, :, 0, 0] = O2 117 | vmr_field[4, :, 0, 0] = N2 118 | vmr_field[5, :, 0, 0] = O3 119 | ws.vmr_field = vmr_field 120 | 121 | ws.z_surface = np.array([[surface_altitude]]) 122 | ws.p_hse = 100000 123 | ws.z_hse_accuracy = 100.0 124 | ws.z_field = 16e3 * (5 - np.log10(pressure_profile[:, np.newaxis, np.newaxis])) 125 | ws.atmfields_checkedCalc() 126 | ws.z_fieldFromHSE() 127 | 128 | # Set surface temperature equal to the lowest atmosphere level 129 | ws.surface_skin_t = ws.t_field.value[0, 0, 0] 130 | 131 | # Output radiance not converted 132 | ws.StringSet(ws.iy_unit, "1") 133 | 134 | # set cloudbox to full atmosphere 135 | ws.cloudboxSetFullAtm() 136 | 137 | # set particle scattering to zero, because we want only clear sky 138 | ws.scat_data_checked = 1 139 | ws.Touch(ws.scat_data) 140 | ws.pnd_fieldZero() 141 | 142 | # No sensor properties 143 | ws.sensorOff() 144 | 145 | # No jacobian calculations 146 | ws.jacobianOff() 147 | 148 | # Check model atmosphere 149 | ws.scat_data_checkedCalc() 150 | ws.atmfields_checkedCalc() 151 | ws.atmgeom_checkedCalc() 152 | ws.cloudbox_checkedCalc() 153 | ws.lbl_checkedCalc() 154 | 155 | # Perform RT calculations 156 | ws.spectral_irradiance_fieldDisort(nstreams=nstreams, emission=1) 157 | 158 | olr = ws.spectral_irradiance_field.value[:, -1, 0, 0, 1][:] 159 | 160 | return ws.f_grid.value[:], olr 161 | 162 | 163 | # %% Run module as script 164 | 165 | if __name__ == "__main__": 166 | import matplotlib.pyplot as plt 167 | 168 | # generate example atmosphere 169 | # This atmosphere is not intended to be fully realistic, but to be simply 170 | # an example for the calculation of the OLR. 171 | 172 | # set pressure grid 173 | pressure_profile = np.linspace(1000e2, 1e2, 80) 174 | 175 | # create water vapor profile 176 | # Water vapor is simply define by a 1st order 177 | # polynomial in log-log space 178 | # log h2o = a + b * log pressure 179 | b = 4 180 | a = -6 - b * 4 181 | logH2O = a + b * np.log10(pressure_profile) 182 | H2O_profile = 10**logH2O 183 | 184 | # create temperature profile 185 | # Temperature is simply define by a 1st order 186 | # polynomial of log pressure 187 | # T = a + b * log pressure 188 | # For pressure < 100 hPa, the temperature is set to 200 K 189 | b = 100 190 | a = 200 - b * 4 191 | temperature_profile = a + b * np.log10(pressure_profile) 192 | temperature_profile[ 193 | pressure_profile < 100e2 194 | ] = 200 # set temperature to 200 K below 100 hPa 195 | 196 | # plot atmosphere profiles 197 | fig, ax = plt.subplots(1, 2) 198 | ax[0].plot(temperature_profile, pressure_profile / 100, label="Temperature") 199 | ax[0].set_xlabel("Temperature [K]") 200 | ax[0].set_ylabel("Pressure [hPa]") 201 | ax[0].invert_yaxis() 202 | 203 | ax[1].plot(H2O_profile, pressure_profile / 100, label="Water vapor") 204 | ax[1].set_xlabel("Water vapor [VMR]") 205 | ax[1].set_ylabel("Pressure [hPa]") 206 | ax[1].invert_yaxis() 207 | 208 | # %% calulate OLR from atmosphere profiles 209 | f_grid, olr = calc_olr_from_profiles( 210 | pressure_profile, temperature_profile, H2O_profile 211 | ) 212 | 213 | # %% plot OLR 214 | fig, ax = plt.subplots() 215 | ax.plot(f_grid / 1e12, olr, label="Irradiance") 216 | ax.set_title(rf"OLR={np.trapz(olr, f_grid):3.2f} $\sf Wm^{{-2}}$") 217 | ax.set_xlim(f_grid.min() / 1e12, f_grid.max() / 1e12) 218 | ax.set_xlabel(r"Frequency [$\sf THz$]") 219 | ax.set_ylabel(r"Irradiance [$\sf Wm^{-2}Hz^{-1}$]") 220 | ax.set_ylim(bottom=0) 221 | 222 | plt.show() 223 | -------------------------------------------------------------------------------- /exercises/07-olr/olr_module.py: -------------------------------------------------------------------------------- 1 | # %% 2 | """Simulate and plot Earth's outgoing longwave radiation (OLR).""" 3 | import pyarts.workspace 4 | from pyarts.arts import constants 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | from matplotlib import colors 8 | 9 | 10 | # %% Helper functions taken from the typhon package 11 | def cmap2rgba(cmap=None, N=None, interpolate=True): 12 | """Convert a colormap into a list of RGBA values. 13 | 14 | Parameters: 15 | cmap (str): Name of a registered colormap. 16 | N (int): Number of RGBA-values to return. 17 | If ``None`` use the number of colors defined in the colormap. 18 | interpolate (bool): Toggle the interpolation of values in the 19 | colormap. If ``False``, only values from the colormap are 20 | used. This may lead to the re-use of a color, if the colormap 21 | provides less colors than requested. If ``True``, a lookup table 22 | is used to interpolate colors (default is ``True``). 23 | 24 | Returns: 25 | ndarray: RGBA-values. 26 | 27 | Examples: 28 | >>> cmap2rgba('viridis', 5) 29 | array([[ 0.267004, 0.004874, 0.329415, 1. ], 30 | [ 0.229739, 0.322361, 0.545706, 1. ], 31 | [ 0.127568, 0.566949, 0.550556, 1. ], 32 | [ 0.369214, 0.788888, 0.382914, 1. ], 33 | [ 0.993248, 0.906157, 0.143936, 1. ]]) 34 | """ 35 | cmap = plt.get_cmap(cmap) 36 | 37 | if N is None: 38 | N = cmap.N 39 | 40 | nlut = N if interpolate else None 41 | 42 | if interpolate and isinstance(cmap, colors.ListedColormap): 43 | # `ListedColormap` does not support lookup table interpolation. 44 | cmap = colors.LinearSegmentedColormap.from_list("", cmap.colors) 45 | return cmap(np.linspace(0, 1, N)) 46 | 47 | return plt.get_cmap(cmap.name, lut=nlut)(np.linspace(0, 1, N)) 48 | 49 | 50 | def e_eq_water_mk(T): 51 | r"""Calculate the equilibrium vapor pressure of water over liquid water. 52 | 53 | .. math:: 54 | \ln(e_\mathrm{liq}) &= 55 | 54.842763 - \frac{6763.22}{T} - 4.21 \cdot \ln(T) \\ 56 | &+ 0.000367 \cdot T 57 | + \tanh \left(0.0415 \cdot (T - 218.8)\right) \\ 58 | &\cdot \left(53.878 - \frac{1331.22}{T} 59 | - 9.44523 \cdot \ln(T) 60 | + 0.014025 \cdot T \right) 61 | 62 | Parameters: 63 | T (float or ndarray): Temperature [K]. 64 | 65 | Returns: 66 | float or ndarray: Equilibrium vapor pressure [Pa]. 67 | 68 | See also: 69 | :func:`~typhon.physics.e_eq_ice_mk` 70 | Calculate the equilibrium vapor pressure of water over ice. 71 | :func:`~typhon.physics.e_eq_mixed_mk` 72 | Calculate the vapor pressure of water over the mixed phase. 73 | 74 | References: 75 | Murphy, D. M. and Koop, T. (2005): Review of the vapour pressures of 76 | ice and supercooled water for atmospheric applications, 77 | Quarterly Journal of the Royal Meteorological Society 131(608): 78 | 1539–1565. doi:10.1256/qj.04.94 79 | 80 | """ 81 | if np.any(T <= 0): 82 | raise ValueError("Temperatures must be larger than 0 Kelvin.") 83 | 84 | # Give the natural log of saturation vapor pressure over water in Pa 85 | 86 | e = ( 87 | 54.842763 88 | - 6763.22 / T 89 | - 4.21 * np.log(T) 90 | + 0.000367 * T 91 | + np.tanh(0.0415 * (T - 218.8)) 92 | * (53.878 - 1331.22 / T - 9.44523 * np.log(T) + 0.014025 * T) 93 | ) 94 | 95 | return np.exp(e) 96 | 97 | 98 | def relative_humidity2vmr(RH, p, T, e_eq=None): 99 | r"""Convert relative humidity into water vapor VMR. 100 | 101 | .. math:: 102 | x = \frac{\mathrm{RH} \cdot e_s(T)}{p} 103 | 104 | Note: 105 | By default, the relative humidity is calculated with respect to 106 | saturation over liquid water in accordance to the WMO standard for 107 | radiosonde observations. 108 | You can use :func:`~typhon.physics.e_eq_mixed_mk` to calculate 109 | relative humidity with respect to saturation over the mixed-phase 110 | following the IFS model documentation. 111 | 112 | Parameters: 113 | RH (float or ndarray): Relative humidity. 114 | p (float or ndarray): Pressue [Pa]. 115 | T (float or ndarray): Temperature [K]. 116 | e_eq (callable): Function to calculate the equilibrium vapor 117 | pressure of water in Pa. The function must implement the 118 | signature ``e_eq = f(T)`` where ``T`` is temperature in Kelvin. 119 | If ``None`` the function :func:`~typhon.physics.e_eq_water_mk` is 120 | used. 121 | 122 | Returns: 123 | float or ndarray: Volume mixing ratio [unitless]. 124 | 125 | See also: 126 | :func:`~typhon.physics.vmr2relative_humidity` 127 | Complement function (returns RH for given VMR). 128 | :func:`~typhon.physics.e_eq_water_mk` 129 | Used to calculate the equilibrium water vapor pressure. 130 | 131 | Examples: 132 | >>> relative_humidity2vmr(0.75, 101300, 300) 133 | 0.026185323887350429 134 | """ 135 | if e_eq is None: 136 | e_eq = e_eq_water_mk 137 | 138 | return RH * e_eq(T) / p 139 | 140 | 141 | def vmr2relative_humidity(vmr, p, T, e_eq=None): 142 | r"""Convert water vapor VMR into relative humidity. 143 | 144 | .. math:: 145 | \mathrm{RH} = \frac{x \cdot p}{e_s(T)} 146 | 147 | Note: 148 | By default, the relative humidity is calculated with respect to 149 | saturation over liquid water in accordance to the WMO standard for 150 | radiosonde observations. 151 | You can use :func:`~typhon.physics.e_eq_mixed_mk` to calculate 152 | relative humidity with respect to saturation over the mixed-phase 153 | following the IFS model documentation. 154 | 155 | Parameters: 156 | vmr (float or ndarray): Volume mixing ratio, 157 | p (float or ndarray): Pressure [Pa]. 158 | T (float or ndarray): Temperature [K]. 159 | e_eq (callable): Function to calculate the equilibrium vapor 160 | pressure of water in Pa. The function must implement the 161 | signature ``e_eq = f(T)`` where ``T`` is temperature in Kelvin. 162 | If ``None`` the function :func:`~typhon.physics.e_eq_water_mk` is 163 | used. 164 | 165 | Returns: 166 | float or ndarray: Relative humidity [unitless]. 167 | 168 | See also: 169 | :func:`~typhon.physics.relative_humidity2vmr` 170 | Complement function (returns VMR for given RH). 171 | :func:`~typhon.physics.e_eq_water_mk` 172 | Used to calculate the equilibrium water vapor pressure. 173 | 174 | Examples: 175 | >>> vmr2relative_humidity(0.025, 1013e2, 300) 176 | 0.71604995533615401 177 | """ 178 | if e_eq is None: 179 | e_eq = e_eq_water_mk 180 | 181 | return vmr * p / e_eq(T) 182 | 183 | 184 | def planck(f, T): 185 | """Calculate black body radiation for given frequency and temperature. 186 | 187 | Parameters: 188 | f (float or ndarray): Frquencies [Hz]. 189 | T (float or ndarray): Temperature [K]. 190 | 191 | Returns: 192 | float or ndarray: Radiances. 193 | 194 | """ 195 | c = constants.c 196 | h = constants.h 197 | k = constants.k 198 | 199 | return 2 * h * f**3 / (c**2 * (np.exp(np.divide(h * f, (k * T))) - 1)) 200 | 201 | 202 | # %% main functions 203 | def Change_T_with_RH_const(atmfield, DeltaT=0.0): 204 | """Change the temperature everywhere in the atmosphere by a value of DeltaT 205 | but without changing the relative humidity. This results in a changed 206 | volume mixing ratio of water vapor. 207 | 208 | Parameters: 209 | atmfield (GriddedField4): Atmosphere field. 210 | DeltaT (float): Temperature change [K]. 211 | 212 | Returns: 213 | GriddedField4: Atmosphere field 214 | """ 215 | 216 | # water vapor 217 | vmr = atmfield.get("abs_species-H2O") 218 | 219 | # Temperature 220 | T = atmfield.get("T") 221 | 222 | # Reshape pressure p, so that p has the same dimensions 223 | p = atmfield.grids[1][:].reshape(T.shape) 224 | 225 | # Calculate relative humidity 226 | rh = vmr2relative_humidity(vmr, p, T) 227 | 228 | # Calculate water vapor volume mixing ratio for changed temperature 229 | vmr = relative_humidity2vmr(rh, p, T + DeltaT) 230 | 231 | # update atmosphere field 232 | atmfield.set("T", T + DeltaT) 233 | atmfield.set("abs_species-H2O", vmr) 234 | 235 | return atmfield 236 | 237 | 238 | def calc_olr_from_atmfield( 239 | atmfield, 240 | nstreams=10, 241 | fnum=300, 242 | fmin=1.0, 243 | fmax=75e12, 244 | species="default", 245 | verbosity=0, 246 | ): 247 | """Calculate the outgoing-longwave radiation for a given atmosphere. 248 | 249 | Parameters: 250 | atmfield (GriddedField4): Atmosphere field. 251 | nstreams (int): Even number of streams to integrate the radiative fluxes. 252 | fnum (int): Number of points in frequency grid. 253 | fmin (float): Lower frequency limit [Hz]. 254 | fmax (float): Upper frequency limit [Hz]. 255 | species (List of strings): List fo absorption species. Defaults to "default" 256 | verbosity (int): Reporting levels between 0 (only error messages) 257 | and 3 (everything). 258 | 259 | Returns: 260 | ndarray, ndarray: Frequency grid [Hz], OLR [Wm^-2] 261 | """ 262 | 263 | pyarts.cat.download.retrieve(verbose=bool(verbosity)) 264 | 265 | ws = pyarts.workspace.Workspace(verbosity=verbosity) 266 | ws.water_p_eq_agendaSet() 267 | ws.gas_scattering_agendaSet() 268 | ws.PlanetSet(option="Earth") 269 | 270 | ws.verbositySetScreen(ws.verbosity, verbosity) 271 | 272 | # Number of Stokes components to be computed 273 | ws.IndexSet(ws.stokes_dim, 1) 274 | 275 | # No jacobian calculation 276 | ws.jacobianOff() 277 | 278 | # Clearsky = No scattering 279 | ws.cloudboxOff() 280 | 281 | # Definition of species 282 | if species == "default": 283 | ws.abs_speciesSet( 284 | species=[ 285 | "H2O, H2O-SelfContCKDMT400, H2O-ForeignContCKDMT400", 286 | "CO2, CO2-CKDMT252", 287 | ] 288 | ) 289 | else: 290 | ws.abs_speciesSet(species=species) 291 | 292 | # Read line catalog 293 | ws.abs_lines_per_speciesReadSpeciesSplitCatalog(basename="lines/") 294 | 295 | # Load CKDMT400 model data 296 | ws.ReadXML(ws.predefined_model_data, "model/mt_ckd_4.0/H2O.xml") 297 | 298 | # Read cross section data 299 | ws.ReadXsecData(basename="lines/") 300 | 301 | ws.abs_lines_per_speciesCutoff(option="ByLine", value=750e9) 302 | 303 | # Create a frequency grid 304 | ws.VectorNLinSpace(ws.f_grid, int(fnum), float(fmin), float(fmax)) 305 | 306 | # Throw away lines outside f_grid 307 | ws.abs_lines_per_speciesCompact() 308 | 309 | # Calculate absorption 310 | ws.propmat_clearsky_agendaAuto() 311 | 312 | # Weakly reflecting surface 313 | ws.VectorSetConstant(ws.surface_scalar_reflectivity, 1, 0.0) 314 | 315 | # Atmosphere and surface 316 | ws.atm_fields_compact = atmfield 317 | ws.AtmosphereSet1D() 318 | ws.AtmFieldsAndParticleBulkPropFieldFromCompact() 319 | 320 | # Set surface height and temperature equal to the lowest atmosphere level 321 | ws.Extract(ws.z_surface, ws.z_field, 0) 322 | ws.surface_skin_t = ws.t_field.value[0, 0, 0] 323 | 324 | # Output radiance not converted 325 | ws.StringSet(ws.iy_unit, "1") 326 | 327 | # set cloudbox to full atmosphere 328 | ws.cloudboxSetFullAtm() 329 | 330 | # set particle scattering to zero, because we want only clear sky 331 | ws.scat_data_checked = 1 332 | ws.Touch(ws.scat_data) 333 | ws.pnd_fieldZero() 334 | 335 | # No sensor properties 336 | ws.sensorOff() 337 | 338 | # No jacobian calculations 339 | ws.jacobianOff() 340 | 341 | # Check model atmosphere 342 | ws.scat_data_checkedCalc() 343 | ws.atmfields_checkedCalc() 344 | ws.atmgeom_checkedCalc() 345 | ws.cloudbox_checkedCalc() 346 | ws.lbl_checkedCalc() 347 | 348 | # Perform RT calculations 349 | ws.spectral_irradiance_fieldDisort(nstreams=nstreams, emission=1) 350 | 351 | olr = ws.spectral_irradiance_field.value[:, -1, 0, 0, 1][:] 352 | 353 | return ws.f_grid.value[:], olr 354 | -------------------------------------------------------------------------------- /exercises/08_heating_rates/heating_rates.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Advanced radiation and remote sensing\n", 8 | "\n", 9 | "\n", 10 | "Manfred Brath, Oliver Lemke\n", 11 | "\n", 12 | "## Exercise 8: Heating rate" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 1, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "%matplotlib widget\n", 22 | "\n", 23 | "\n", 24 | "\n", 25 | "import os\n", 26 | "import matplotlib.pyplot as plt\n", 27 | "import numpy as np\n", 28 | "from pyarts import xml\n", 29 | "from heating_rates_module import (calc_spectral_irradiance, calc_irradiance,\n", 30 | " integrate_spectral_irradiance)\n", 31 | "\n", 32 | "os.makedirs(\"plots\", exist_ok=True)" 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "The heating rate denotes the change of atmospheric temperature with time due\n", 40 | "to gain or loss of energy. Here, we consider only the gain or loss due to\n", 41 | "radiation.\n", 42 | "The heating rate including only radiation is \n", 43 | "\n", 44 | "$$\\frac{\\partial T\\left(z\\right)}{\\partial t}=-\\frac{1}{\\rho\\left( z \\right) c_p }\\frac{\\partial}{\\partial z}F_{net}\\left( z \\right)$$\n", 45 | "\n", 46 | "with $\\rho\\left( z \\right)$ the density of dry air (To keep it simple, we assume dry air. \n", 47 | "In reality the air is not dry. Nonetheless, the differences are small.), $c_p = 1.0035\\, \\text{J}\\, \\text{kg}^{-1} \\text{K}^{-1}$ the specific heat \n", 48 | "capacity of dry air and $F_{net}$ the net radiation flux. \n", 49 | "The net radiation flux is \n", 50 | "\n", 51 | "$$F_{net}=F_{up}-F_{down}$$\n", 52 | "\n", 53 | "with $F_{up}$ and $F_{down}$ the up- and downward radiation flux (irradiance), respectively. \n", 54 | "The density of dry air is \n", 55 | "\n", 56 | "$$\\rho =\\frac{p}{R_s\\,T}$$\n", 57 | "\n", 58 | "with pressure $p$, temperature $T$ and the specific gas constant \n", 59 | "$R_s = 287.058\\, \\text{J}\\,\\text{kg}^{-1} \\text{K}^{-1}$." 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "Before we start, we need to load an atmosphere." 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 2, 72 | "metadata": {}, 73 | "outputs": [], 74 | "source": [ 75 | "# Read input atmosphere\n", 76 | "atmfield = xml.load(\"input/midlatitude-summer.xml\")" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "metadata": {}, 82 | "source": [ 83 | "### 1)\n", 84 | "Run the next cell. This will calculate the upward and downward longwave radiation fluxes. \n", 85 | "Here, we will consider only the longwave flux. Calculate the net flux and plot upward, \n", 86 | "downward and net flux together in one figure against altitude. Explain the plot. " 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": 3, 92 | "metadata": {}, 93 | "outputs": [], 94 | "source": [ 95 | "# Calculate the radiation irradiance (flux)\n", 96 | "\n", 97 | "z, p, T, flux_downward, flux_upward = calc_irradiance(atmfield)" 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": 4, 103 | "metadata": {}, 104 | "outputs": [], 105 | "source": [ 106 | "# Calculate net flux and plot up-, down- and net flux\n" 107 | ] 108 | }, 109 | { 110 | "cell_type": "markdown", 111 | "metadata": {}, 112 | "source": [ 113 | "### 2)\n", 114 | "Implement the function `calc_heatingrates(...)`. Use the function to calculate \n", 115 | "the heating rate. Plot the heating rate against altitude and explain the plot. How would \n", 116 | "a heating rate in thermal equilibrium assuming only longwave radiation look like? \n", 117 | "Why is the heating rate so much higher in the stratosphere than in the troposphere?" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": 5, 123 | "metadata": {}, 124 | "outputs": [], 125 | "source": [ 126 | "# Implement heating rate function\n", 127 | "\n", 128 | "def calc_heatingrates(z, p, T, Fnet):\n", 129 | " \"\"\"Calculate the heating rate.\n", 130 | "\n", 131 | " Parameters:\n", 132 | " z (ndarray): Altitude [m].\n", 133 | " p (ndarray): Pressure [Pa].\n", 134 | " T (ndarray): Temperature [K].\n", 135 | " Fnet (ndarray): Net flux [W m^-2].\n", 136 | "\n", 137 | " Returns:\n", 138 | " ndarray, ndarray:\n", 139 | " Heating rate [K/d], Altitude [m].\n", 140 | " \"\"\"\n", 141 | "\n", 142 | "\n", 143 | "\n", 144 | "\n", 145 | " return NotImplementedError" 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": 6, 151 | "metadata": {}, 152 | "outputs": [], 153 | "source": [ 154 | "# Calculate heating rate and plot it\n", 155 | "\n", 156 | "# HR,zp_lay = calc_heatingrates(z, p, T, net_flux)\n" 157 | ] 158 | }, 159 | { 160 | "cell_type": "markdown", 161 | "metadata": {}, 162 | "source": [ 163 | "### 3)\n", 164 | "Calculate the spectral upward, downward and net flux using the function `calc_spectral_irradiance`." 165 | ] 166 | }, 167 | { 168 | "cell_type": "code", 169 | "execution_count": 7, 170 | "metadata": {}, 171 | "outputs": [], 172 | "source": [ 173 | "# Calculate the spectral irradiance (spectral flux)\n", 174 | "\n", 175 | "f, z, p, T, spectral_flux_downward, spectral_flux_upward = calc_spectral_irradiance( atmfield, verbosity=1)\n" 176 | ] 177 | }, 178 | { 179 | "cell_type": "markdown", 180 | "metadata": {}, 181 | "source": [ 182 | "### 4)\n", 183 | "Use the function `integrate_spectral_irradiance(...)` to integrate \n", 184 | "the spectral irradiance over three continuing bands:\n", 185 | "\n", 186 | "* the far infrared\n", 187 | "* the $\\text{CO}_2$-band\n", 188 | "* the window-region and above.\n", 189 | "\n", 190 | "Calculate the heating rate for each band and plot them together with the total \n", 191 | "heating rate from Task 2. Compare the band heating rates with the total \n", 192 | "heating rate and explain differences." 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": 8, 198 | "metadata": {}, 199 | "outputs": [], 200 | "source": [ 201 | "# calculate the heating rate for each band and plot them together with the other heating rate\n" 202 | ] 203 | } 204 | ], 205 | "metadata": { 206 | "kernelspec": { 207 | "display_name": "Python 3 (ipykernel)", 208 | "language": "python", 209 | "name": "python3" 210 | }, 211 | "language_info": { 212 | "codemirror_mode": { 213 | "name": "ipython", 214 | "version": 3 215 | }, 216 | "file_extension": ".py", 217 | "mimetype": "text/x-python", 218 | "name": "python", 219 | "nbconvert_exporter": "python", 220 | "pygments_lexer": "ipython3", 221 | "version": "3.12.5" 222 | }, 223 | "vscode": { 224 | "interpreter": { 225 | "hash": "6bd45fef6a38d15b43f43de43ba5066924911f80576952f97fb08adaede44831" 226 | } 227 | } 228 | }, 229 | "nbformat": 4, 230 | "nbformat_minor": 4 231 | } 232 | -------------------------------------------------------------------------------- /exercises/08_heating_rates/heating_rates_module.py: -------------------------------------------------------------------------------- 1 | """Simulate and plot Earth's outgoing longwave radiation (OLR).""" 2 | import numpy as np 3 | import pyarts.workspace 4 | 5 | 6 | def calc_spectral_irradiance( 7 | atmfield, nstreams=4, fnum=300, fmin=1.0, fmax=97e12, verbosity=0 8 | ): 9 | """Calculate the spectral downward and upward irradiance for a given atmosphere. 10 | Irradiandce is defined as positive quantity independent of direction. 11 | 12 | Parameters: 13 | atmfield (GriddedField4): Atmosphere field. 14 | nstreams (int): Even number of streams to integrate the radiative fluxes. 15 | fnum (int): Number of points in frequency grid. 16 | fmin (float): Lower frequency limit [Hz]. 17 | fmax (float): Upper frequency limit [Hz]. 18 | verbosity (int): Reporting levels between 0 (only error messages) 19 | and 3 (everything). 20 | 21 | Returns: 22 | ndarray, ndarray, ndarray, ndarray, ndarray, ndarray : 23 | Frequency grid [Hz], altitude [m], pressure [Pa], temperature [K], 24 | spectral downward irradiance [Wm^-2 Hz^-1], 25 | spectral upward irradiance [Wm^-2 Hz^-1]. 26 | """ 27 | 28 | pyarts.cat.download.retrieve(verbose=bool(verbosity)) 29 | 30 | ws = pyarts.workspace.Workspace(verbosity=verbosity) 31 | ws.water_p_eq_agendaSet() 32 | ws.gas_scattering_agendaSet() 33 | ws.PlanetSet(option="Earth") 34 | 35 | ws.verbositySetScreen(ws.verbosity, verbosity) 36 | 37 | # Number of Stokes components to be computed 38 | ws.IndexSet(ws.stokes_dim, 1) 39 | 40 | # No jacobian calculations 41 | ws.jacobianOff() 42 | 43 | # Definition of species 44 | ws.abs_speciesSet( 45 | species=[ 46 | "H2O, H2O-SelfContCKDMT400, H2O-ForeignContCKDMT400", 47 | "CO2, CO2-CKDMT252", 48 | ] 49 | ) 50 | 51 | # Read line catalog 52 | ws.abs_lines_per_speciesReadSpeciesSplitCatalog(basename="lines/") 53 | 54 | # Load CKDMT400 model data 55 | ws.ReadXML(ws.predefined_model_data, "model/mt_ckd_4.0/H2O.xml") 56 | 57 | # Read cross section data 58 | ws.ReadXsecData(basename="lines/") 59 | 60 | ws.abs_lines_per_speciesCutoff(option="ByLine", value=750e9) 61 | ws.abs_lines_per_speciesTurnOffLineMixing() 62 | 63 | # Create a frequency grid 64 | ws.VectorNLinSpace(ws.f_grid, int(fnum), float(fmin), float(fmax)) 65 | 66 | # Throw away lines outside f_grid 67 | ws.abs_lines_per_speciesCompact() 68 | 69 | # Calculate absorption 70 | ws.propmat_clearsky_agendaAuto() 71 | 72 | # Weakly reflecting surface 73 | ws.VectorSetConstant(ws.surface_scalar_reflectivity, 1, 0.0) 74 | ws.surface_rtprop_agendaSet(option="Specular_NoPol_ReflFix_SurfTFromt_surface") 75 | 76 | # Atmosphere and surface 77 | ws.atm_fields_compact = atmfield 78 | ws.AtmosphereSet1D() 79 | ws.AtmFieldsAndParticleBulkPropFieldFromCompact() 80 | 81 | # Set surface height and temperature equal to the lowest atmosphere level 82 | ws.Extract(ws.z_surface, ws.z_field, 0) 83 | ws.surface_skin_t = ws.t_field.value[0, 0, 0] 84 | 85 | # Output radiance not converted 86 | ws.StringSet(ws.iy_unit, "1") 87 | 88 | # set cloudbox to full atmosphere 89 | ws.cloudboxSetFullAtm() 90 | 91 | # set particle scattering to zero, because we want only clear sky 92 | ws.scat_data_checked = 1 93 | ws.Touch(ws.scat_data) 94 | ws.pnd_fieldZero() 95 | 96 | # Check model atmosphere 97 | ws.scat_data_checkedCalc() 98 | ws.atmfields_checkedCalc() 99 | ws.atmgeom_checkedCalc() 100 | ws.cloudbox_checkedCalc() 101 | ws.lbl_checkedCalc() 102 | 103 | # Perform RT calculations 104 | ws.spectral_irradiance_fieldDisort(nstreams=nstreams, emission=1) 105 | 106 | spectral_flux_downward = -ws.spectral_irradiance_field.value[:, :, 0, 0, 0].copy() 107 | spectral_flux_upward = ws.spectral_irradiance_field.value[:, :, 0, 0, 1].copy() 108 | 109 | # spectral_flux_downward[np.isnan(spectral_flux_downward)] = 0. 110 | # spectral_flux_upward[np.isnan(spectral_flux_upward)] = 0. 111 | 112 | # set outputs 113 | f = ws.f_grid.value[:].copy() 114 | z = ws.z_field.value[:].copy().squeeze() 115 | p = atmfield.grids[1][:].squeeze().copy() 116 | T = atmfield.get("T")[:].squeeze().copy() 117 | 118 | return f, z, p, T, spectral_flux_downward, spectral_flux_upward 119 | 120 | 121 | def calc_irradiance(atmfield, nstreams=2, fnum=300, fmin=1.0, fmax=97e12, verbosity=0): 122 | """Calculate the downward and upward irradiance for a given atmosphere. 123 | Irradiandce is defined as positive quantity independent of direction. 124 | 125 | Parameters: 126 | atmfield (GriddedField4): Atmosphere field. 127 | nstreams (int): Even number of streams to integrate the radiative fluxes. 128 | fnum (int): Number of points in frequency grid. 129 | fmin (float): Lower frequency limit [Hz]. 130 | fmax (float): Upper frequency limit [Hz]. 131 | verbosity (int): Reporting levels between 0 (only error messages) 132 | and 3 (everything). 133 | 134 | Returns: 135 | ndarray, ndarray, ndarray, ndarray, ndarray : 136 | Altitude [m], pressure [Pa], temperature [K], 137 | downward irradiance [Wm^-2], upward irradiance [Wm^-2]. 138 | """ 139 | 140 | f, z, p, T, spectral_flux_downward, spectral_flux_upward = calc_spectral_irradiance( 141 | atmfield, 142 | nstreams=nstreams, 143 | fnum=fnum, 144 | fmin=fmin, 145 | fmax=fmax, 146 | verbosity=verbosity, 147 | ) 148 | 149 | # calculate flux 150 | flux_downward = np.trapz(spectral_flux_downward, f, axis=0) 151 | flux_upward = np.trapz(spectral_flux_upward, f, axis=0) 152 | 153 | return z, p, T, flux_downward, flux_upward 154 | 155 | 156 | def integrate_spectral_irradiance(f, spectral_flux, fmin=-np.inf, fmax=np.inf): 157 | """Calculate the integral of the spectral iradiance from fmin to fmax. 158 | 159 | Parameters: 160 | f (ndarray): Frequency grid [Hz]. 161 | spectral_flux (ndarray): Spectral irradiance [Wm^-2 Hz^-1]. 162 | fmin (float): Lower frequency limit [Hz]. 163 | fmax (float): Upper frequency limit [Hz]. 164 | 165 | Returns: 166 | ndarray irradiance [Wm^-2]. 167 | """ 168 | 169 | logic = np.logical_and(fmin <= f, f < fmax) 170 | 171 | flux = np.trapezoid(spectral_flux[logic, :], f[logic], axis=0) 172 | 173 | return flux 174 | -------------------------------------------------------------------------------- /exercises/09-scattering/input/pndfield_input.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 1.1000000e+05 6 | 2.4684211e+04 7 | 2.4368421e+04 8 | 2.4052632e+04 9 | 2.3736842e+04 10 | 2.3421053e+04 11 | 2.3105263e+04 12 | 2.2789474e+04 13 | 2.2473684e+04 14 | 2.2157895e+04 15 | 2.1842105e+04 16 | 2.1526316e+04 17 | 2.1210526e+04 18 | 2.0894737e+04 19 | 2.0578947e+04 20 | 2.0263158e+04 21 | 1.9947368e+04 22 | 1.9631579e+04 23 | 1.9315789e+04 24 | 1.0000000e-05 25 | 26 | 27 | 0.0000000e+00 28 | 29 | 30 | 0.0000000e+00 31 | 32 | 33 | 0.0000000e+00 34 | 0.0000000e+00 35 | 0.0000000e+00 36 | 3.5000000e+04 37 | 3.5000000e+04 38 | 3.5000000e+04 39 | 3.5000000e+04 40 | 3.5000000e+04 41 | 3.5000000e+04 42 | 3.5000000e+04 43 | 3.5000000e+04 44 | 3.5000000e+04 45 | 3.5000000e+04 46 | 3.5000000e+04 47 | 3.5000000e+04 48 | 3.5000000e+04 49 | 3.5000000e+04 50 | 0.0000000e+00 51 | 0.0000000e+00 52 | 0.0000000e+00 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /exercises/09-scattering/scattering.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Advanced radiation and remote sensing\n", 8 | "\n", 9 | "\n", 10 | "Manfred Brath, Oliver Lemke\n", 11 | "\n", 12 | "## Exercise 8: Scattering" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 1, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "%matplotlib widget\n", 22 | "\n", 23 | "\n", 24 | "import os\n", 25 | "import numpy as np\n", 26 | "import matplotlib.pyplot as plt\n", 27 | "from scattering_module import argclosest, scattering\n", 28 | "from matplotlib.ticker import StrMethodFormatter\n", 29 | "\n", 30 | "os.makedirs(\"plots\", exist_ok=True)" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "### 1)\n", 38 | "Run the next cell. This will simulate the radiation field at a frequency of $229\\,\\text{GHz}$ \n", 39 | "for an atmosphere with an ice cloud as well for clear-sky. Since this \n", 40 | "is a one-dimensional simulation (vertical dimension only), the calculated \n", 41 | "radiation fields have two dimensions: altitude (pressure) and zenith angle. " 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "Plot the two radiation fields in the atmosphere at a zenith angle of $180{^\\circ}$. \n", 49 | "Here, the zenith angle describes the viewing direction. This means that you are \n", 50 | "looking at the upward directed radiation. The unit is brightness temperature. \n", 51 | "\n", 52 | "* Describe the difference between cloudy and clear-sky radiation. \n", 53 | "* Guess where the ice cloud is located in the atmosphere based on the\n", 54 | "two radiation fields? \n", 55 | "* Explain the difference between cloudy and clear-sky radiation." 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": null, 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "# Run ARTS simulation\n", 65 | "p, zenith_angles, ifield, ifield_clearsky = scattering()\n", 66 | "\n", 67 | "# Control parameters\n", 68 | "zenith_angle = 180.0 # viewing angle [degree, 180° = upward radiation]\n", 69 | "pressure_level = None # pressure level [Pa]\n", 70 | "\n", 71 | "ia, zenith_angle = argclosest(zenith_angles, zenith_angle, retvalue=True)\n", 72 | "\n", 73 | "# Plot Tb vs height for a specific viewing angle\n", 74 | "f0, a0 = plt.subplots()\n", 75 | "a0.plot(ifield_clearsky[:, ia], p / 100, label=\"Clear-sky\")\n", 76 | "a0.plot(ifield[:, ia], p / 100, label=\"Scattering\")\n", 77 | "a0.grid()\n", 78 | "a0.set_ylim(p.max() / 100, p.min() / 100)\n", 79 | "a0.set_ylabel(\"Pressure [hPa]\")\n", 80 | "a0.set_xlabel(r\"$T_\\mathrm{B}$ [K]\")\n", 81 | "a0.legend()\n", 82 | "a0.set_title(rf\"$T_\\mathrm{{B}}$ at $\\Theta$ = {zenith_angle:.0f}°\")\n", 83 | "\n", 84 | "# Plot Tb vs Viewing angle for a specific pressure level:\n", 85 | "if pressure_level is not None:\n", 86 | " ip, pressure_level = argclosest(p, pressure_level, retvalue=True)\n", 87 | "\n", 88 | " f1, a1 = plt.subplots(subplot_kw=dict(projection=\"polar\"))\n", 89 | " a1.plot(np.deg2rad(zenith_angles), ifield_clearsky[ip, :], label=\"Clear-sky\")\n", 90 | " a1.plot(np.deg2rad(zenith_angles), ifield[ip, :], label=\"Scattering\")\n", 91 | " a1.legend(loc=\"upper right\")\n", 92 | " a1.set_theta_offset(np.deg2rad(+90))\n", 93 | " a1.set_theta_direction(-1)\n", 94 | " a1.set_thetagrids(np.arange(0, 181, 45), ha=\"left\")\n", 95 | " a1.text(0.01, 0.75, r\"$T_\\mathrm{B}$\", transform=a1.transAxes)\n", 96 | " a1.yaxis.set_major_formatter(StrMethodFormatter(\"{x:g} K\"))\n", 97 | " a1.set_thetamin(0)\n", 98 | " a1.set_thetamax(180)\n", 99 | " a1.set_xlabel(r\"Viewing angle $\\Theta$\")\n", 100 | " a1.set_title(rf\"$T_\\mathrm{{B}}$ at p = {pressure_level/100:.0f} hPa\")\n", 101 | "\n", 102 | "plt.show()" 103 | ] 104 | }, 105 | { 106 | "cell_type": "markdown", 107 | "metadata": {}, 108 | "source": [ 109 | "### 2) \n", 110 | "Change the zenith angle (`zenith_angle`) from $180{^\\circ}$\n", 111 | "to $0{^\\circ}$ and rerun the previous cell.\n", 112 | "\n", 113 | "* Describe and explain the difference.\n", 114 | "* Why is the brightness temperature at the top of the atmosphere so\n", 115 | "low?\n", 116 | "\n", 117 | "### 3)\n", 118 | "Now you will look at the radiation fields as a function of zenith\n", 119 | "angle (viewing direction) at a fixed pressure level. In the Jupyter\n", 120 | "notebook, change the variable `pressure_level` from `None`\n", 121 | "to a pressure level in $\\left[\\text{Pa}\\right]$, which is within\n", 122 | "the ice cloud and rerun previous cell within the notebook.\n", 123 | "\n", 124 | "* Explain the shape of the radiation field without the cloud.\n", 125 | "* How does the radiation field with the cloud differ? \n", 126 | "\n", 127 | "### 4)\n", 128 | "Make the same calculation as in task 3 but with a less or a more dense\n", 129 | "ice cloud. To do that, you have to call the function `scattering()`\n", 130 | "within your script with the argument `ice_water_path` set\n", 131 | "to your desired value in $\\left[\\text{kg}\\,\\text{m}^{-2}\\right]$.\n", 132 | "The ice water path is the vertically integrated mass content of ice.\n", 133 | "In task 3, the function `scattering()` used a default value\n", 134 | "of $2\\,\\text{kg}\\,\\text{m}^{-2}$. " 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "metadata": {}, 140 | "source": [] 141 | } 142 | ], 143 | "metadata": { 144 | "kernelspec": { 145 | "display_name": "Python 3 (ipykernel)", 146 | "language": "python", 147 | "name": "python3" 148 | }, 149 | "language_info": { 150 | "codemirror_mode": { 151 | "name": "ipython", 152 | "version": 3 153 | }, 154 | "file_extension": ".py", 155 | "mimetype": "text/x-python", 156 | "name": "python", 157 | "nbconvert_exporter": "python", 158 | "pygments_lexer": "ipython3", 159 | "version": "3.12.5" 160 | }, 161 | "vscode": { 162 | "interpreter": { 163 | "hash": "6bd45fef6a38d15b43f43de43ba5066924911f80576952f97fb08adaede44831" 164 | } 165 | } 166 | }, 167 | "nbformat": 4, 168 | "nbformat_minor": 4 169 | } 170 | -------------------------------------------------------------------------------- /exercises/09-scattering/scattering_module.py: -------------------------------------------------------------------------------- 1 | """Perform a DOIT scattering radiation with ARTS. 2 | 3 | Based on a script by Jakob Doerr. 4 | """ 5 | import numpy as np 6 | import pyarts 7 | 8 | 9 | "function taken from typhon.physics" 10 | 11 | 12 | def radiance2planckTb(f, r): 13 | """Convert spectral radiance to Planck brightness temperture. 14 | 15 | Parameters: 16 | f (float or ndarray): Frequency [Hz]. 17 | r (float or ndarray): Spectral radiance [W/(m2*Hz*sr)]. 18 | 19 | Returns: 20 | float or ndarray: Planck brightness temperature [K]. 21 | """ 22 | c = pyarts.arts.constants.c 23 | k = pyarts.arts.constants.k 24 | h = pyarts.arts.constants.h 25 | 26 | return h / k * f / np.log(np.divide((2 * h / c**2) * f**3, r) + 1) 27 | 28 | 29 | def argclosest(array, value, retvalue=False): 30 | """Returns the index of the closest value in array.""" 31 | idx = np.abs(array - value).argmin() 32 | 33 | return (idx, array[idx]) if retvalue else idx 34 | 35 | 36 | def setup_doit_agendas(ws): 37 | # Calculation of the phase matrix 38 | @pyarts.workspace.arts_agenda(ws=ws, set_agenda=True) 39 | def pha_mat_spt_agenda(ws): 40 | # Optimized option: 41 | ws.pha_mat_sptFromDataDOITOpt() 42 | # Alternative option: 43 | # ws.pha_mat_sptFromMonoData 44 | 45 | @pyarts.workspace.arts_agenda(ws=ws, set_agenda=True) 46 | def doit_scat_field_agenda(ws): 47 | ws.doit_scat_fieldCalcLimb() 48 | # Alternative: use the same za grids in RT part and scattering integral part 49 | # ws.doit_scat_fieldCalc() 50 | 51 | @pyarts.workspace.arts_agenda(ws=ws, set_agenda=True) 52 | def doit_rte_agenda(ws): 53 | # Sequential update for 1D 54 | ws.cloudbox_fieldUpdateSeq1D() 55 | 56 | @pyarts.workspace.arts_agenda(ws=ws, set_agenda=True) 57 | def spt_calc_agenda(ws): 58 | ws.opt_prop_sptFromMonoData() 59 | 60 | @pyarts.workspace.arts_agenda(ws=ws, set_agenda=True) 61 | def doit_conv_test_agenda(ws): 62 | ws.doit_conv_flagAbsBT( 63 | epsilon=np.array([0.01]), max_iterations=100, nonconv_return_nan=1 64 | ) 65 | ws.Print(ws.doit_iteration_counter, 0) 66 | 67 | ws.doit_conv_test_agenda = doit_conv_test_agenda 68 | 69 | @pyarts.workspace.arts_agenda(ws=ws, set_agenda=True) 70 | def doit_mono_agenda(ws): 71 | # Prepare scattering data for DOIT calculation (Optimized method): 72 | ws.Ignore(ws.f_grid) 73 | ws.DoitScatteringDataPrepare() 74 | # Perform iterations: 1. scattering integral. 2. RT calculations with 75 | # fixed scattering integral field, 3. convergence test 76 | ws.cloudbox_field_monoIterate(accelerated=1) 77 | 78 | ws.iy_cloudbox_agendaSet(option="LinInterpField") 79 | 80 | 81 | def scattering( 82 | ice_water_path=2.0, num_viewing_angles=19, phase="ice", radius=1.5e2, verbosity=0 83 | ): 84 | """Perform a radiative transfer simulation with a simple cloud. 85 | 86 | Parameters: 87 | ice_water_path (float): Integrated ice water in cloud box [kg/m^2]. 88 | num_viewing_angles (int): Number of zenith viewing angles. 89 | phase (str): Hydrometeor phase "ice" or "liquid". 90 | radius (float): Particle radius. 91 | verbosity (int): Reporting levels between 0 (only error messages) 92 | and 3 (everything). 93 | 94 | Returns: 95 | ndarray, ndarray, ndarray, ndarray: 96 | Pressure [hPa], 97 | Viewing angles [degree], 98 | Radiation field [K], 99 | Radiation field clear-sky [K]. 100 | 101 | """ 102 | 103 | pyarts.cat.download.retrieve(verbose=bool(verbosity)) 104 | 105 | ws = pyarts.workspace.Workspace(verbosity=verbosity) 106 | ws.water_p_eq_agendaSet() 107 | ws.PlanetSet(option="Earth") 108 | 109 | ws.verbositySetScreen(ws.verbosity, verbosity) 110 | 111 | # Agenda settings 112 | 113 | # (standard) emission calculation 114 | ws.iy_main_agendaSet(option="Emission") 115 | 116 | # cosmic background radiation 117 | ws.iy_space_agendaSet(option="CosmicBackground") 118 | 119 | # standard surface agenda (i.e., make use of surface_rtprop_agenda) 120 | ws.iy_surface_agendaSet(option="UseSurfaceRtprop") 121 | 122 | # sensor-only path 123 | ws.ppath_agendaSet(option="FollowSensorLosPath") 124 | 125 | # no refraction 126 | ws.ppath_step_agendaSet(option="GeometricPath") 127 | 128 | ws.VectorSet(ws.f_grid, np.array([229.0e9])) # Define f_grid 129 | 130 | ws.IndexSet(ws.stokes_dim, 1) # Set stokes dim 131 | 132 | ws.AtmosphereSet1D() 133 | 134 | ws.jacobianOff() # No jacobian calculations 135 | 136 | ws.sensorOff() # No sensor 137 | 138 | # Set the maximum propagation step to 250m. 139 | ws.NumericSet(ws.ppath_lmax, 250.0) 140 | 141 | # Set absorption species 142 | ws.abs_speciesSet( 143 | species=["H2O-PWR98", "O3", "O2-PWR98", "N2-SelfContStandardType"] 144 | ) 145 | 146 | # Read atmospheric data 147 | ws.ReadXML(ws.batch_atm_fields_compact, "input/chevallierl91_all_extract.xml") 148 | ws.Extract(ws.atm_fields_compact, ws.batch_atm_fields_compact, 1) 149 | 150 | # Add constant profiles for O2 and N2 151 | ws.atm_fields_compactAddConstant( 152 | name="abs_species-O2", value=0.2095, condensibles=["abs_species-H2O"] 153 | ) 154 | ws.atm_fields_compactAddConstant( 155 | name="abs_species-N2", value=0.7808, condensibles=["abs_species-H2O"] 156 | ) 157 | 158 | ws.AtmFieldsAndParticleBulkPropFieldFromCompact() 159 | ws.atmfields_checkedCalc() 160 | 161 | # Read Catalog (needed for O3): 162 | ws.abs_lines_per_speciesReadSpeciesSplitCatalog(basename="lines/") 163 | 164 | # ws.abs_lines_per_speciesLineShapeType(option=lineshape) 165 | ws.abs_lines_per_speciesCutoff(option="ByLine", value=750e9) 166 | # ws.abs_lines_per_speciesNormalization(option=normalization) 167 | 168 | # absorption from LUT 169 | ws.propmat_clearsky_agendaAuto() 170 | 171 | ws.abs_lookupSetup() 172 | ws.lbl_checkedCalc() 173 | 174 | ws.abs_lookupCalc() 175 | 176 | ws.propmat_clearsky_agenda_checkedCalc() 177 | 178 | # Set surface reflectivity (= 1 - emissivity) 179 | ws.surface_rtprop_agendaSet(option="Specular_NoPol_ReflFix_SurfTFromt_surface") 180 | ws.VectorSetConstant(ws.surface_scalar_reflectivity, 1, 0.0) 181 | 182 | # Extract particle mass from scattering meta data. 183 | scat_xml = f"scattering/H2O_{phase}/MieSphere_R{radius:.5e}um" 184 | scat_meta = pyarts.arts.ScatteringMetaData() 185 | scat_meta.readxml(scat_xml + ".meta") 186 | particle_mass = float(scat_meta.mass) 187 | 188 | # Load scattering data and PND field. 189 | ws.ScatSpeciesInit() 190 | ws.ScatElementsPndAndScatAdd( 191 | scat_data_files=[scat_xml], pnd_field_files=["./input/pndfield_input.xml"] 192 | ) 193 | ws.scat_dataCalc() 194 | ws.scat_data_checkedCalc() 195 | 196 | # Set the extent of the cloud box. 197 | ws.cloudboxSetManually( 198 | p1=101300.0, p2=1000.0, lat1=0.0, lat2=0.0, lon1=0.0, lon2=0.0 199 | ) 200 | 201 | # Trim pressure grid to match cloudbox. 202 | bottom, top = ws.cloudbox_limits.value 203 | p = ws.p_grid.value[bottom : top + 1].copy() 204 | z = ws.z_field.value[bottom : top + 1, 0, 0].copy() 205 | 206 | ws.pnd_fieldCalcFrompnd_field_raw() 207 | 208 | # Calculate the initial ice water path (IWP). 209 | iwp0 = np.trapz(particle_mass * ws.pnd_field.value[0, :, 0, 0], z) 210 | 211 | # Scale the PND field to get the desired IWP. 212 | ws.Tensor4Multiply(ws.pnd_field, ws.pnd_field, ice_water_path / iwp0) 213 | 214 | # Get case-specific surface properties from corresponding atmospheric fields 215 | ws.Extract(ws.z_surface, ws.z_field, 0) 216 | ws.Extract(ws.t_surface, ws.t_field, 0) 217 | 218 | # Consistency checks 219 | ws.atmgeom_checkedCalc() 220 | ws.cloudbox_checkedCalc() 221 | 222 | setup_doit_agendas(ws) 223 | 224 | ws.doit_za_interpSet(interp_method="linear") 225 | 226 | ws.DOAngularGridsSet( 227 | N_za_grid=num_viewing_angles, N_aa_grid=37, za_grid_opt_file="" 228 | ) 229 | 230 | # Use lookup table for absorption calculation 231 | ws.propmat_clearsky_agendaAuto(use_abs_lookup=1) 232 | 233 | # Scattering calculation 234 | ws.DoitInit() 235 | ws.DoitGetIncoming(rigorous=0) 236 | ws.cloudbox_fieldSetClearsky() 237 | ws.DoitCalc() 238 | 239 | ifield = np.squeeze(ws.cloudbox_field.value[:].squeeze()) 240 | ifield = radiance2planckTb(ws.f_grid.value, ifield) 241 | 242 | # Clear-sky 243 | ws.Tensor4Multiply(ws.pnd_field, ws.pnd_field, 0.0) 244 | ws.DoitCalc() 245 | 246 | ifield_clear = np.squeeze(ws.cloudbox_field.value[:].squeeze()) 247 | ifield_clear = radiance2planckTb(ws.f_grid.value, ifield_clear) 248 | 249 | return p, ws.za_grid.value[:].copy(), ifield, ifield_clear 250 | -------------------------------------------------------------------------------- /script/AdvRaRe_script.bib: -------------------------------------------------------------------------------- 1 | @Article{hodnebrog13:_global_RG, 2 | author = {{\O} Hodnebrog and others}, 3 | title = {Global warming potentials and radiative efficiencies 4 | of halocarbons and related compounds: A 5 | comprehensive review}, 6 | journal = RG, 7 | year = 2013, 8 | volume = 51, 9 | pages = {300--378}, 10 | doi = {10.1002/rog.20013} 11 | } 12 | 13 | -------------------------------------------------------------------------------- /script/AdvRaRe_script_print.tex: -------------------------------------------------------------------------------- 1 | \documentclass[a4paper,fleqn]{article} 2 | %% Seitenaufbau 3 | \usepackage[top=3cm, bottom=2.5cm, left=3.5cm, right=3.5cm]{geometry} 4 | 5 | %% Schriftbild 6 | \usepackage{lmodern} % Latin Modern Zeichensatz 7 | \usepackage[utf8]{inputenc} % Unterstuetzung von Umlauten im Quelltext 8 | \usepackage[T1]{fontenc} % Korrekte Umlaute im Output 9 | %\renewcommand{\familydefault}{\sfdefault} % Serifenlose Schrift 10 | %\usepackage{euler} % Serifenlose Schrift in Formelumgebungen 11 | \usepackage{setspace}\onehalfspacing % 1.5-facher Zeilenabstand 12 | \renewcommand{\arraystretch}{1.5} % 1.5-facher Zeilenabstand (Tabellen) 13 | \setlength{\parindent}{0pt} % Keine Einrueckung am Beginng von Absaetzen 14 | \setlength{\parskip}{1ex} 15 | \usepackage{fancyhdr} 16 | \pagestyle{fancy} 17 | \renewcommand{\headrulewidth}{0.5pt} 18 | \renewcommand{\sectionmark}[1]{\markright{\thesection\ #1}} 19 | \lhead{\rightmark} 20 | \chead{} 21 | \cfoot{\thepage} % Richtige Schriftart fuer Seitenzahlen 22 | \sloppy % Weniger strikte Silbentrennung 23 | 24 | %% Verlinkung von Inhaltsverzeichnis, Bildern und Formeln 25 | \usepackage[pagebackref]{hyperref} % Verlinkung von URLs und Referenzen 26 | %\usepackage{color} % Definition von Linkfarben 27 | %\definecolor{DarkRed}{rgb}{0.5,0,0} 28 | %\hypersetup{ 29 | % colorlinks, 30 | % citecolor=DarkRed, 31 | % linkcolor=DarkRed, 32 | % urlcolor=blue} 33 | 34 | \input{AdvRaRe_script} 35 | -------------------------------------------------------------------------------- /script/AdvRaRe_script_screen.tex: -------------------------------------------------------------------------------- 1 | \documentclass[a5paper,landscape,fleqn,12pt]{article} 2 | %% Seitenaufbau 3 | \usepackage[top=2cm, bottom=1.5cm, left=2.5cm, right=2.5cm]{geometry} 4 | 5 | %% Schriftbild 6 | \usepackage{lmodern} % Latin Modern Zeichensatz 7 | \usepackage[utf8]{inputenc} % Unterstuetzung von Umlauten im Quelltext 8 | \usepackage[T1]{fontenc} % Korrekte Umlaute im Output 9 | \renewcommand{\familydefault}{\sfdefault} % Serifenlose Schrift 10 | \usepackage{euler} % Serifenlose Schrift in Formelumgebungen 11 | \usepackage{setspace}\onehalfspacing % 1.5-facher Zeilenabstand 12 | \renewcommand{\arraystretch}{1.5} % 1.5-facher Zeilenabstand (Tabellen) 13 | \setlength{\parindent}{0pt} % Keine Einrueckung am Beginng von Absaetzen 14 | \setlength{\parskip}{1ex} 15 | \usepackage{fancyhdr} 16 | \pagestyle{fancy} 17 | \renewcommand{\headrulewidth}{0.5pt} 18 | \renewcommand{\sectionmark}[1]{\markright{\thesection\ #1}} 19 | \lhead{\rightmark} 20 | \chead{} 21 | \cfoot{\thepage} % Richtige Schriftart fuer Seitenzahlen 22 | \sloppy % Weniger strikte Silbentrennung 23 | 24 | %% Verlinkung von Inhaltsverzeichnis, Bildern und Formeln 25 | \usepackage[pagebackref]{hyperref} % Verlinkung von URLs und Referenzen 26 | \usepackage{color} % Definition von Linkfarben 27 | \definecolor{DarkRed}{rgb}{0.5,0,0} 28 | \hypersetup{ 29 | colorlinks, 30 | citecolor=DarkRed, 31 | linkcolor=DarkRed, 32 | urlcolor=blue} 33 | 34 | \input{AdvRaRe_script} 35 | -------------------------------------------------------------------------------- /script/Makefile: -------------------------------------------------------------------------------- 1 | # Variables 2 | TC = latexmk 3 | TFLAGS = -e "$$pdflatex=q/pdflatex -interaction=nonstopmode/" -pdf -bibtex 4 | 5 | TARGETS = AdvRaRe_script_print.pdf AdvRaRe_script_screen.pdf 6 | 7 | .PHONY: all $(TARGETS) 8 | 9 | all: $(TARGETS) 10 | 11 | $(TARGETS): %.pdf : %.tex 12 | $(TC) $(TFLAGS) $< 13 | 14 | clean: 15 | $(TC) -silent -c 16 | 17 | cleanall: 18 | $(TC) -silent -C 19 | 20 | # vim:ft=make 21 | # 22 | -------------------------------------------------------------------------------- /script/figures/Buehler_et_al_OLR_spectra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmtools/arts-lectures/3dc28d627560c60ede546a59f24c518d093dff08/script/figures/Buehler_et_al_OLR_spectra.png -------------------------------------------------------------------------------- /script/figures/Energy_levels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmtools/arts-lectures/3dc28d627560c60ede546a59f24c518d093dff08/script/figures/Energy_levels.png -------------------------------------------------------------------------------- /script/figures/Fig_moments_inertia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmtools/arts-lectures/3dc28d627560c60ede546a59f24c518d093dff08/script/figures/Fig_moments_inertia.png -------------------------------------------------------------------------------- /script/figures/Reduced_mass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmtools/arts-lectures/3dc28d627560c60ede546a59f24c518d093dff08/script/figures/Reduced_mass.png -------------------------------------------------------------------------------- /script/figures/Transitions_vib_rot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmtools/arts-lectures/3dc28d627560c60ede546a59f24c518d093dff08/script/figures/Transitions_vib_rot.png -------------------------------------------------------------------------------- /script/figures/Vibration_parabol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmtools/arts-lectures/3dc28d627560c60ede546a59f24c518d093dff08/script/figures/Vibration_parabol.png -------------------------------------------------------------------------------- /script/figures/Vibration_parabol_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmtools/arts-lectures/3dc28d627560c60ede546a59f24c518d093dff08/script/figures/Vibration_parabol_2.png -------------------------------------------------------------------------------- /script/figures/buehler_portrait_sketch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmtools/arts-lectures/3dc28d627560c60ede546a59f24c518d093dff08/script/figures/buehler_portrait_sketch.png -------------------------------------------------------------------------------- /script/figures/interaction_types.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmtools/arts-lectures/3dc28d627560c60ede546a59f24c518d093dff08/script/figures/interaction_types.png -------------------------------------------------------------------------------- /script/figures/perturbedLayer_EmissionTransmission.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmtools/arts-lectures/3dc28d627560c60ede546a59f24c518d093dff08/script/figures/perturbedLayer_EmissionTransmission.png -------------------------------------------------------------------------------- /script/figures/plotTypes_Jacobian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmtools/arts-lectures/3dc28d627560c60ede546a59f24c518d093dff08/script/figures/plotTypes_Jacobian.png -------------------------------------------------------------------------------- /script/figures/rotating_mass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmtools/arts-lectures/3dc28d627560c60ede546a59f24c518d093dff08/script/figures/rotating_mass.png -------------------------------------------------------------------------------- /script/figures/schematic_energy_states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmtools/arts-lectures/3dc28d627560c60ede546a59f24c518d093dff08/script/figures/schematic_energy_states.png -------------------------------------------------------------------------------- /script/figures/transition_types.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmtools/arts-lectures/3dc28d627560c60ede546a59f24c518d093dff08/script/figures/transition_types.png -------------------------------------------------------------------------------- /script/j_abbr.bib: -------------------------------------------------------------------------------- 1 | % 2 | % In our bibtex files journal names are given as acronyms, for 3 | % example, GRL for Geophysical Research Letters. 4 | % 5 | % This file defines the conversion between these acronyms and 6 | % abbreviated journal names. 7 | % 8 | % This file should be the default choice but the conversion to 9 | % full journal names is found in the file journals.full. 10 | % 11 | % To use abbreviated journal names, put: 12 | % 13 | % \input{journals.abbr} 14 | % 15 | % at the start of the bibtex file. To use full journal names, use 16 | % 17 | % \input{journals.full} 18 | % 19 | % Please add new journals to these files when needed (in both 20 | % files!). The same acronym shall of course be used in journal.abbr 21 | % and journal.full. 22 | % 23 | 24 | @string{A = {Atmosf.}} 25 | @string{AA = {A\&A}} 26 | @string{AASS = {Astronomy \& Astrophysics Suppl. S.}} 27 | @string{ACA = {Acta Astron.}} 28 | @string{ACP = {Atmos. Chem. Phys.}} 29 | @string{ACPD = {Atmos. Chem. Phys. Discuss.}} 30 | @string{ACS = {Atm. Clim. Sci.}} 31 | @string{ADA = {Adv. Astron.}} 32 | @string{ADGEO = {Adv. Geosci.}} 33 | @string{AE = {Atmos. Environ.}} 34 | @string{AG = {Ann. Geophys.}} 35 | @string{ANJ = {Astronom. J.}} 36 | @string{AI = {Acta Inform.}} 37 | @string{AJ = {Astrophys. J.}} 38 | @string{AJL = {Astrophys. J. Lett.}} 39 | @string{AJSS = {Astrophys. J. Suppl. S.}} 40 | @string{AL = {Astrophys. J.}} 41 | @string{AJP = {Am. J. Phys.}} 42 | @string{AM = {Ann. Met.}} 43 | @string{AMM = {Aus. Met. Mag.}} 44 | @string{AMT = {Atmos. Meas. Tech.}} 45 | @string{AMTD = {Atmos. Meas. Tech. Discuss.}} 46 | @string{AO = {Appl. Opt.}} 47 | @string{AOP = {Atmos. Oce. Phy.(engl. transl.)}} 48 | @string{APL = {Appl. Phys. Lett.}} 49 | @string{AR = {Atmos. Res.}} 50 | @string{AREE = {Annu. Rev. Energy Environ.}} 51 | @string{ARPC = {Annu. Rev. Phys. Chem.}} 52 | @string{AS = {Am. Sci.}} 53 | @string{ASL = {Atm. Sci. Lett.}} 54 | @string{ASP = {Appl. Sign. Proc.}} 55 | @string{ASR = {Adv. Space. Res.}} 56 | @string{AST = {Aerosol Sci. Technol.}} 57 | @string{ATOC = {Atmos. -- Oce.}} 58 | 59 | @string{BAMS = {Bull. Amer. Met. Soc.}} 60 | @string{BAS = {Bull. Atom. Sci.}} 61 | @string{BLM = {Boun.-Lay. Met.}} 62 | @string{BPA = {Beitr. Phys. Atm.}} 63 | 64 | @string{CC = {Climate Change}} 65 | @string{CCCR = {Curr. Clim. Change Rep.}} 66 | @string{CD = {Climate Dynamics}} 67 | @string{COP = {Clim. Past}} 68 | @string{CG = {Comput. Geosci.}} 69 | @string{CJC = {Can. J. Chem.}} 70 | @string{CJP = {Can. J. Phys.}} 71 | @string{CP = {Contemp. Phys.}} 72 | @string{CPH = {Chem. Phys.}} 73 | @string{CPL = {Chem. Phys. Lett.}} 74 | @string{CR = {Climate Research}} 75 | @string{CRP = {Comptes Rendus Phys.}} 76 | @string{CS = {Curr. Sci.}} 77 | @string{CSB = {Chin. Sci. Bul.}} 78 | @string{CSPH = {Chemosph.}} 79 | @string{CSR = {Chem. Soc. Rev.}} 80 | 81 | @string{EECT = {Energy Emiss. Control Technol.}} 82 | @string{EF = {Earth's Fut.}} 83 | @string{EFM = {Env. Fluid Mech.}} 84 | @string{EL = {Elec. Lett.}} 85 | @string{EM = {Experiment. Math.}} 86 | @string{EMP = {Earth, Moon and Planets}} 87 | @string{EOS = {Eos}} 88 | @string{ENN = {Env. News Net.}} 89 | @string{EPA = {Earth and Planet. Astrophys.}} 90 | @string{EPJC = {Eur. Phys. J. Conferences}} 91 | @string{EPJST = {Eur. Phys. J. Spec. Top.}} 92 | @string{EPS = {Earth Planets Space}} 93 | @string{ERL = {Environ. Res. Lett.}} 94 | @string{ESD = {Earth Syst. Dynamics}} 95 | @string{ESS = {Earth and Space Sci.}} 96 | @string{ESSD = {Earth Syst. Sci. Data}} 97 | @string{EST = {Environ. Sci. Technol.}} 98 | @string{ETA = {Eos Trans. AGU}} 99 | 100 | @string{FES = {Front. Earth Sci.}} 101 | @string{FIP = {Front. in Phys.}} 102 | 103 | @string{GDJ = {Geosci. Data J.}} 104 | @string{GJI = {Geophys. J. Int.}} 105 | @string{GI = {Geosci. Instr., Methods and Data Syst.}} 106 | @string{GP = {Geophysica}} 107 | @string{GMD = {Geosci. Model Dev.}} 108 | @string{GMDD = {Geosci. Model Dev. Discuss.}} 109 | @string{GRL = {Geophys. Res. Lett.}} 110 | @string{HESSD = {Hydrol. Earth Syst. Sci. Discuss.}} 111 | @string{HESS = {Hydrol. Earth Syst. Sci.}} 112 | @string{IAOP = {Izv. Atmos. Oce. Phys.}} 113 | @string{ICARUS = {Icarus}} 114 | @string{IJAEOG = {Int. J. of Applied Earth Observation and Geoinformation}} 115 | @string{IJC = {Int. J. Climatol.}} 116 | @string{IJES = {Int. J. of Earth Sci.}} 117 | @string{IJRS = {Int. J. Remote Sensing}} 118 | @string{IJRSP = {Indian J. of Radio \& Space Physics}} 119 | @string{IJIMW = {Int. J. Inf. Millim. Waves}} 120 | @string{IJSC = {Int. J. Sat. Comm.}} 121 | @string{IP = {Infrared Phys.}} 122 | @string{IPT = {Infrared Phys. \& Tech.}} 123 | @string{I3EGRS = {IEEE Geosci. Remote Sens.}} 124 | @string{I3EGRSL = {IEEE Geosci. Remote Sens. Let.}} 125 | @string{I3EJOE = {IEEE J. Oce. Eng.}} 126 | @string{I3EJSTARS = {IEEE J. Sel. Top. Appl. Rem. Sens.}} 127 | @string{I3ETAP = {IEEE Trans. Antennas Propag.}} 128 | @string{I3ETC = {IEEE T. Comm.}} 129 | @string{I3ETGE = {IEEE T. Geosci. Ele.}} 130 | @string{I3ETGRS = {IEEE T. Geosci. Remote}} 131 | @string{I3ETMTT = {IEEE T. Microw. Theory}} 132 | @string{I3ETNN = {IEEE T. Ne. Netw.}} 133 | @string{I3ETIP = {IEEE T. Image Proc.}} 134 | 135 | @string{JAMES = {J. Adv. Model. Earth Syst.}} 136 | @string{JAC = {J. Atmos. Chem.}} 137 | @string{JAES = {J. Aerosol Sci.}} 138 | @string{JAM = {J. Appl. Meteorol.}} 139 | @string{JAMC = {J. Appl. Meteorol. Clim.}} 140 | @string{JAO = {J. Appl. Opt.}} 141 | @string{JAOT = {J. Atmos. Oceanic Technol.}} 142 | @string{JAP = {J. Appl. Phys.}} 143 | @string{JARS = {J. Appl. Rem. Sens.}} 144 | @string{JAS = {J. Atmos. Sci.}} 145 | @string{JASTP = {J. Atm. Sol.-Terr. Phys.}} 146 | @string{JATP = {J. Atm. Terr. Phys.}} 147 | @string{JAWMA = {J. Air \& Waste Manage. Assoc.}} 148 | @string{JC = {J. Climate}} 149 | @string{JH = {J. Hydrology}} 150 | @string{JCAM = {J. Clim. Appl. Mete.}} 151 | @string{JCG = {J. Cryst. Growth}} 152 | @string{JCP = {J. Chem. Phys.}} 153 | @string{JES = {J. Earth Simul.}} 154 | @string{JCSFT = {J. Chem. Soc. Far. Trans.}} 155 | @string{JFM = {J. Fluid Mech.}} 156 | @string{JG = {J. Geodesy}} 157 | @string{JGR = {J. Geophys. Res.}} 158 | @string{JGRa = {J. Geophys. Res.: Atm.}} 159 | @string{JGRo = {J. Geophys. Res.: Oceans}} 160 | @string{JGRP = {J. Geophys. Res.: Planets}} 161 | @string{JIMTW = {J. Infrared Milli. Terahz Waves}} 162 | @string{JLTP = {J. Low Temp. Phys.}} 163 | @string{JM = {J. Meteorol.}} 164 | @string{JMS = {J. Molec. Struct.}} 165 | @string{JMSL = {J. Molec. Liqu.}} 166 | @string{JMSp = {J. Molec. Spectro.}} 167 | @string{JMSJ = {J. Meteorol. Soc. Jpn.}} 168 | @string{JOA = {J. Opt. A: Pure Appl. Opt.}} 169 | @string{JOH = {J. Hydrometeorol.}} 170 | @string{JOSA = {J. Optical Soc. o. Am.}} 171 | @string{JOSAA = {J. Optical Soc. o. Am. A}} 172 | @string{JOSAB = {J. Optical Soc. o. Am. B}} 173 | @string{JOTA = {J. Opt. Theory and Appl.}} 174 | @string{JPC = {J. Phys. Chem.}} 175 | @string{JPCA = {J. Phys. Chem. A}} 176 | @string{JPCM = {J. Phys.: Condens. Matter}} 177 | @string{JPAGP = {J. of Phys. A: Gen. Phys.}} 178 | @string{JPAMG = {J. of Phys. A: Math. Gen.}} 179 | @string{JPESI = {J. of Phys. E: Sci. Instrum.}} 180 | @string{JCOP = {J. of Comp. Phys.}} 181 | @string{JCTE = {J. of Comm. Tech. and Elec.}} 182 | @string{JPIVF = {J. Phys. IV France}} 183 | @string{JPPA = {J. Photochem. Photobiol. A: Chem}} 184 | @string{JPO = {J. Phys. Ocea.}} 185 | @string{JQSRT = {J. Quant. Spectrosc. Radiat. Transfer}} 186 | 187 | @string{MA = {Met. Appl.}} 188 | @string{MAP = {Met. Atm. Phys.}} 189 | @string{MBT = {Met. Bul., Tai.}} 190 | @string{MC = {Mon. Corr.}} 191 | @string{MM = {Meteor. Monog.}} 192 | @string{MMS = {Multiscale Model. Simul.}} 193 | @string{MNRAS = {Mon. Not. R. Astron. Soc.}} 194 | @string{MP = {Mol. Phys.}} 195 | @string{MR = {Met. Rund.}} 196 | @string{MRRSE = {Micro. Radiat. Rem. Sen. Env.}} 197 | @string{MSSP = {Mech. Sys. and Sig. Proc.}} 198 | @string{MST = {Meas. Sci. Technol.}} 199 | @string{MWR = {Mon. Weather Rev.}} 200 | @string{MZ = {Met. Zeit.}} 201 | 202 | @string{N = {Nature}} 203 | @string{NCC = {Nature Clim. Change}} 204 | @string{NCPGH = {Nat. Clin. Pract. Gastr.}} 205 | @string{NG = {Nature Geosci.}} 206 | @string{NP = {Nature Phys.}} 207 | @string{NHESS = {Nat. Hazards and Earth Syst. Sci}} 208 | @string{NW = {Naturwissenschaften}} 209 | @string{NWD = {Nat. Weat. Dig.}} 210 | @string{NPG = {Nonlin. Processes. Geophys.}} 211 | 212 | @string{OE = {Opt. Express}} 213 | @string{OLE = {Opt. Las. Eng.g}} 214 | @string{OS = {Opt. Spectro.}} 215 | 216 | @string{P = {Physics}} 217 | @string{PAC = {Pure Appl. Chem.}} 218 | @string{PASP = {Publ. of the Astron. Soc. of Pacific}} 219 | @string{PD = {Physica D}} 220 | @string{PCE = {Phys. Chem. Earth}} 221 | @string{PERS = {Photogr. Eng. and Rem. Sens.}} 222 | @string{PLA = {Phys. Let. A}} 223 | @string{PMG = {Papers in Meteorol. and Geophys.}} 224 | @string{PNAS = {Proc. Nat. Aca. Sci.}} 225 | @string{PPG = {Prog. Phys. Geog.}} 226 | @string{PPSB = {Proc. Phys. Soc. B}} 227 | @string{PR = {Phys. Rev.}} 228 | @string{PRA = {Phys. Rev. A}} 229 | @string{PRD = {Phys. Rev. D}} 230 | @string{PRE = {Phys. Rev. E}} 231 | @string{PRL = {Phys. Rev. L}} 232 | @string{PRSA = {Proc. R. Soc. A}} 233 | @string{PRSLA = {Proc. R. Soc. Lond. A}} 234 | @string{PSPIE = {Proc. of SPIE}} 235 | @string{PSS = {Planet. Space Sci.}} 236 | @string{PT = {Phys. Today}} 237 | @string{PTRSLA = {Phil. Trans. R. Soc. Lond. A}} 238 | @string{PTRSA = {Phil. Trans. R. Soc. A}} 239 | @string{PW = {Phys. World}} 240 | @string{PU = {Phys. Usp.}} 241 | 242 | 243 | @string{QJRMS = {Q. J. R. Meteorol. Soc.}} 244 | 245 | @string{RESE = {Rem. Sens.}} 246 | @string{RG = {Rev. Geophys.}} 247 | @string{RMP = {Rev. Mod. Phys.}} 248 | @string{RPP = {Rep. Prog. Phys.}} 249 | @string{RQE = {Radiophys. Quant. Elec.}} 250 | @string{RS = {Radio Sci.}} 251 | @string{RSE = {Rem. Sen. Env.}} 252 | @string{RSI = {Rev. Sci. Inst.}} 253 | @string{RSR = {Rem. Sen. Rev.}} 254 | @string{RSPR = {Rem. Sen. Po. Reg.}} 255 | 256 | @string{S = {Science}} 257 | @string{SA = {Scient. Amer.}} 258 | @string{SD = {Sci. Data}} 259 | @string{SG = {Sur. Geophy.}} 260 | @string{SOLA = {Sci. Onl. Let. Atm.}} 261 | @string{SP = {Sov. Phys. Uspekhi}} 262 | @string{SOP = {Sol. Phys.}} 263 | @string{SSR = {Space Sci. Rev.}} 264 | @string{SW = {Spek. Wissen.}} 265 | @string{SAPAMBS = {Spectro. Acta Part A: Molec. and Biomolec. Spectro.}} 266 | 267 | @string{T = {Tellus}} 268 | @string{TA = {Tellus A: Dyn. Meteo. and Ocean.}} 269 | @string{TAC = {Theor. Appl. Climatol.}} 270 | @string{TAO = {Terr. Atmos. and Ocean. Sci. J.}} 271 | @string{TKDD = {ACM Trans. on Knowl. Disc. from Data}} 272 | @string{TOMS = {ACM Trans. on Math. Softw.}} 273 | 274 | @string{US = {Urb. Sci.}} 275 | 276 | @string{W = {Weather}} 277 | @string{WF = {Weat. and Fore.}} 278 | @string{WIRCC = {Wiley Interd. Rev.: Climate Change}} 279 | @string{WRR = {Wat. Res. Res.}} 280 | @string{WRM = {Waves Rand. Med.}} 281 | 282 | @string{Z = {Zeit}} 283 | 284 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | TARGETS = 01-absorption.pdf \ 2 | 02-lineshape.pdf \ 3 | 04-rtcalc.pdf \ 4 | 05-jacobian.pdf \ 5 | 06-oem.pdf \ 6 | 07-olr.pdf \ 7 | 08-scattering.pdf \ 8 | 09-heating_rates.pdf 9 | 10 | SOURCES = ../exercises/01-molecule_spectra/absorption.ipynb \ 11 | ../exercises/02-line_shape/lineshape.ipynb \ 12 | ../exercises/04-rtcalc/rtcalc.ipynb \ 13 | ../exercises/05-jacobian/jacobian.ipynb \ 14 | ../exercises/06-inversion/oem.ipynb \ 15 | ../exercises/07-olr/olr.ipynb \ 16 | ../exercises/08-scattering/scattering.ipynb \ 17 | ../exercises/09_heating_rates/heating_rates.ipynb 18 | 19 | CONVERT = jupyter nbconvert --log-level=ERROR --to pdf --output-dir=. --output=$@ --execute 20 | 21 | 22 | all: convert 23 | 24 | define make-exercise-target 25 | convert:: $1 26 | $1: $2 27 | endef 28 | 29 | ILIST = $(shell for x in {1..$(words $(TARGETS))}; do echo $$x; done) 30 | $(foreach i,$(ILIST), \ 31 | $(eval $(call make-exercise-target, \ 32 | $(word $(i),$(TARGETS)), \ 33 | $(word $(i),$(SOURCES))))) 34 | 35 | $(TARGETS): 36 | TEXINPUTS=$(PWD)/$(dir $<): $(CONVERT) $< 37 | 38 | clean: 39 | rm -f $(TARGETS) 40 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | This directory is meant for testing. 2 | 3 | `make` runs all exercises and converts them into PDFs. 4 | 5 | Note that you need to set the environment variable `ARTS_DATA_PATH` 6 | correctly to the locations of the catalogs. 7 | --------------------------------------------------------------------------------