diff --git a/.gitignore b/.gitignore index fa68021..92019e9 100644 --- a/.gitignore +++ b/.gitignore @@ -366,3 +366,4 @@ tempCodeRunnerFile* dev/ _util docs/experimental +docs/examples/_* diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..14f7625 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include earthkit *.json diff --git a/docs/examples/hybrid_levels.ipynb b/docs/examples/hybrid_levels.ipynb new file mode 100644 index 0000000..62b31ce --- /dev/null +++ b/docs/examples/hybrid_levels.ipynb @@ -0,0 +1,802 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c610a676-99e0-470e-adca-5a3b148dec41", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Computing values on hybrid levels" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "159cef22-f023-4242-8fad-ee3d01c4e308", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import earthkit.meteo.vertical.array as vertical" + ] + }, + { + "cell_type": "markdown", + "id": "d10adaee-12f9-47bc-a57b-23e43f50572a", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "This notebook gives an overview about computing various quantities on hybrid levels." + ] + }, + { + "cell_type": "markdown", + "id": "efb76279-54a2-4b80-b3c7-05b0e605d477", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### Hybrid levels" + ] + }, + { + "cell_type": "markdown", + "id": "4939f1ab-e2bc-444d-825a-6b9e250fe838", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Hybrid levels divide the atmosphere into layers. These layers are defined by the pressures at the interfaces between them, which are the **half levels**. The half levels are defined by a set A and B coefficients in such a way that at the top of the atmosphere the first half level pressure is a constant, while at the bottom the last one is the surface pressure. The **full level** pressure associated with each model level is defined as the middle of the layer. As a consequence, the bottom-most (full) model level is always above the surface. Please note that by convention the model level numbering starts at 1 at the top of the atmosphere and increases towards the surface.\n", + "\n", + "For more details about the hybrid levels see [IFS Documentation CY47R3 - Part IV Physical processes, Chapter 2, Section 2.2.1.](https://www.ecmwf.int/en/elibrary/20198-ifs-documentation-cy47r3-part-iv-physical-processes)" + ] + }, + { + "cell_type": "markdown", + "id": "cf3476e5-47c4-404a-932c-f57471dff839", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### Getting the data" + ] + }, + { + "cell_type": "markdown", + "id": "c0f05922-b3ae-47fe-b4a1-8ea082684789", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "First, we get some sample data containing 137 IFS (full) model levels for two points. This data is defined on full model levels in ascending model level number order. So the first level is model level 1, while the last one is model level 137." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a15baeeb-fc9e-4a4f-9443-33f94f88d03b", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "((2,), (2,), (137, 2), (137, 2))" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from earthkit.meteo.utils.sample import get_sample\n", + "\n", + "DATA = get_sample(\"vertical_hybrid_data\")\n", + "sp = DATA.p_surf # surface pressure [Pa]\n", + "zs = DATA.z_surf # surface geopotential [m2/s2]\n", + "t = DATA.t # temperature [K]\n", + "q = DATA.q # specific humidity [kg/kg]\n", + "\n", + "sp.shape, zs.shape, t.shape, q.shape" + ] + }, + { + "cell_type": "raw", + "id": "6b17b8da-2a27-4e02-bb2a-ba9086161e56", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Next, get the hybrid level definitions using :py:meth:`~earthkit.meteo.vertical.array.hybrid_level_parameters`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d8cf8e7d-72e6-45b4-a6d8-4939983afd6e", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "((138,), (138,))" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "A, B = vertical.hybrid_level_parameters(137, model=\"ifs\")\n", + "A.shape, B.shape" + ] + }, + { + "cell_type": "markdown", + "id": "3f33959f-5f50-461d-b8c3-8a1889ae8783", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "The A and B arrays contain coefficients for each half hybrid level in ascending model level number order." + ] + }, + { + "cell_type": "markdown", + "id": "afa8e524-22ad-4103-a50f-7b35455c4062", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### Computing pressure" + ] + }, + { + "cell_type": "raw", + "id": "9057b508-a0d6-4671-81f5-604349a92cd6", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "We can compute the pressure on all the full levels from the surface pressure with :py:meth:`~meteo.vertical.array.pressure_on_hybrid_levels`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "090dec6a-fe83-40ea-a173-c030943cfbbc", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(137, 2)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p_full = vertical.pressure_on_hybrid_levels(A, B, sp, output=\"full\")\n", + "p_full.shape" + ] + }, + { + "cell_type": "markdown", + "id": "86280191-26cd-4c70-baf0-ca3f897b8104", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "The output is ordered by ascending model level number, so goes from the top of the atmosphere towards the surface." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "9caa68e5-ce8c-4815-b99a-fde1f990f030", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 94572.33402093, 102005.12626943],\n", + " [ 94829.60971572, 102283.52257037],\n", + " [ 95065.55729093, 102538.16444369]])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# the lowest 3 levels above the surface\n", + "p_full[-3:]" + ] + }, + { + "cell_type": "markdown", + "id": "d30fa7cc-8044-4188-90fd-0d750033f114", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "By using the ``output`` kwarg other pressure related quantities can be computed." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "14adab12-967c-4617-9182-22b767395647", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "((137, 2), (138, 2), (137, 2), (137, 2))" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p_full, p_half, alpha,delta = vertical.pressure_on_hybrid_levels(\n", + " A, B, sp, \n", + " output=(\"full\", \"half\",\"alpha\", \"delta\"))\n", + "\n", + "p_full.shape, p_half.shape, alpha.shape, delta.shape" + ] + }, + { + "cell_type": "markdown", + "id": "451a6131-3bc8-4f10-85ff-aa99f0e6a6c8", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "It is possible to use only part of the levels via the ``levels`` option. The level indices start at 1 and go up to 137 for the current data. The example below only computes the pressure on the lowest 3 levels." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "cc97867c-bfba-4ddf-8539-72b418ceac78", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 94572.33402093, 102005.12626943],\n", + " [ 94829.60971572, 102283.52257037],\n", + " [ 95065.55729093, 102538.16444369]])" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p_full = vertical.pressure_on_hybrid_levels(\n", + " A, B, sp, levels=[135, 136, 137],\n", + " output=(\"full\"))\n", + "p_full" + ] + }, + { + "cell_type": "markdown", + "id": "8604d688-fdd9-4243-bafb-10126b8210e8", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "The ``level`` kwarg respects the specified ordering. In the example below the output goes upwards from the lowest level." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "c6141bba-088d-4708-b112-6c517de62ae3", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 95065.55729093, 102538.16444369],\n", + " [ 94829.60971572, 102283.52257037],\n", + " [ 94572.33402093, 102005.12626943]])" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p_full = vertical.pressure_on_hybrid_levels(\n", + " A, B, sp, levels=[137, 136, 135],\n", + " output=(\"full\"))\n", + "p_full" + ] + }, + { + "cell_type": "markdown", + "id": "16967e93-9e07-428a-bd6b-047773ceee25", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### Computing geopotential thickness" + ] + }, + { + "cell_type": "raw", + "id": "f5d27db3-96fd-4066-b058-7deb7d0c666c", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + ":py:meth:`~meteo.vertical.array.relative_geopotential_thickness_on_hybrid_levels` compute the geopotential thickness between the surface and the full hybrid levels. It requires temperature and specific humidity profiles." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "0cc4ca8e-f06a-4f2e-a93d-2309084f4e87", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "z_thickness= vertical.relative_geopotential_thickness_on_hybrid_levels(t, q, \n", + " A, B, sp)" + ] + }, + { + "cell_type": "markdown", + "id": "653ab9dc-e9a3-4cdc-ac06-1ad7ab7e0e02", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Note that ``t`` and ``q`` must contain the same model levels in ascending order with respect to the model level number. The model level range must be contiguous and must include the bottom-most level, but not all the levels must be present. E.g. for our data we can compute the relative geopotential thickness for the lowest 3 levels as shown below. Please note that even in this case we use the full A and B arrays." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "8f89208e-551a-4446-9de4-9860db64b3d7", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[493.46642874, 540.17339902],\n", + " [283.62077829, 310.1849329 ],\n", + " [ 91.62752892, 100.19125537]])" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "z_thickness= vertical.relative_geopotential_thickness_on_hybrid_levels(t[-3:], q[-3:], A, B, sp)\n", + "z_thickness" + ] + }, + { + "cell_type": "markdown", + "id": "8d8ae87d-6ff2-42c6-8a7b-18995a396206", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### Computing geopotential" + ] + }, + { + "cell_type": "raw", + "id": "b4c76cbb-4111-407e-96c6-14b11d6707f2", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "We can compute the geopotential on full hybrid levels with :py:meth:`~meteo.vertical.array.geopotential_on_hybrid_levels`. " + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "7a038844-bd0c-497b-97da-1a4ef7e8d02c", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "((137, 2),\n", + " array([[5774.59167288, -289.70135684],\n", + " [5564.74602243, -519.68982296],\n", + " [5372.75277306, -729.68350049]]))" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "z = vertical.geopotential_on_hybrid_levels(t, q, zs, A, B, sp)\n", + "z.shape, z[-3:]" + ] + }, + { + "cell_type": "markdown", + "id": "2b287082-44f2-45d7-90bc-aabf7da51ebd", + "metadata": { + "editable": true, + "raw_mimetype": "", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Just like for the relative geopotential thickness a subset of levels can be used. E.g. to compute the geopotential for the lowest 3 levels we can write:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "ce46c001-9a5d-48d4-9585-d2ada97a378b", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "((3, 2),\n", + " array([[5774.59167288, -289.70135684],\n", + " [5564.74602243, -519.68982296],\n", + " [5372.75277306, -729.68350049]]))" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "z = vertical.geopotential_on_hybrid_levels(t[-3:], q[-3:], zs, A, B, sp)\n", + "z.shape, z" + ] + }, + { + "cell_type": "markdown", + "id": "05643bbc-9550-42ed-b054-ff30bc8835e7", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### Computing height" + ] + }, + { + "cell_type": "raw", + "id": "c56c1a13-ce50-421b-9196-5a88a980125f", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "The height on full hybrid levels can be computed with :py:meth:`~meteo.vertical.array.height_on_hybrid_levels`. We can specify the height type via ``h_type`` and the reference level via ``h_reference`. The following options are available: \n", + "\n", + "- h_type: \"geometric\" or \"geopotential\"\n", + "- h_reference: \"ground\" or \"sea\"" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "9f7a63c3-77c7-4f20-bb0f-d08330c09cc5", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "((137, 2),\n", + " array([[50.32847687, 55.08137028],\n", + " [28.92629165, 31.62937732],\n", + " [ 9.34500108, 10.21640974]]))" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "h = vertical.height_on_hybrid_levels(t, q, zs, A, B, sp, h_type=\"geometric\", h_reference=\"ground\")\n", + "h.shape, h[-3:]" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "990a994d-6f62-4629-8206-827efd3cb683", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "((137, 2),\n", + " array([[588.89890268, -29.54118008],\n", + " [567.49671746, -52.99317304],\n", + " [547.91542689, -74.40614062]]))" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "h = vertical.height_on_hybrid_levels(t, q, zs, A, B, sp, h_type=\"geometric\", h_reference=\"sea\")\n", + "h.shape, h[-3:]" + ] + }, + { + "cell_type": "markdown", + "id": "7b4f7eb2-0a5d-4c20-a4c3-752fa3deccaf", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Just like for the previous methods a subset of levels can be used. E.g. to compute the geometric height above ground for the lowest 3 levels we can write:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "98c956af-2ee3-4d59-a42a-b1168036fcaa", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "((3, 2),\n", + " array([[50.31996922, 55.0828335 ],\n", + " [28.92140188, 31.63021754],\n", + " [ 9.34342138, 10.21668113]]))" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "h = vertical.height_on_hybrid_levels(t[-3:], q[-3:], 0, A, B, sp, h_type=\"geometric\", h_reference=\"ground\")\n", + "h.shape, h" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8e4a2f3e-42eb-4714-a2c9-d5f11350fb74", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "dev", + "language": "python", + "name": "dev" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/examples/index.rst b/docs/examples/index.rst index 9e60465..35129d3 100644 --- a/docs/examples/index.rst +++ b/docs/examples/index.rst @@ -6,7 +6,25 @@ Examples Here is a list of example notebooks to illustrate how to use earthkit-data. + +Statistics +++++++++++++++ + .. toctree:: :maxdepth: 1 return_period.ipynb + + + +Vertical +++++++++++++++ + +.. toctree:: + :maxdepth: 1 + + hybrid_levels.ipynb + interpolate_hybrid_to_pl + interpolate_hybrid_to_hl + interpolate_pl_to_hl + interpolate_pl_to_pl diff --git a/docs/examples/interpolate_hybrid_to_hl.ipynb b/docs/examples/interpolate_hybrid_to_hl.ipynb new file mode 100644 index 0000000..f402ae5 --- /dev/null +++ b/docs/examples/interpolate_hybrid_to_hl.ipynb @@ -0,0 +1,1047 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "150548d1-f025-47bf-9604-31a339c6bc91", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Interpolating from hybrid to height levels" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "653672d0-0d06-40bd-8945-7ec046bfc93a", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import earthkit.meteo.vertical.array as vertical" + ] + }, + { + "cell_type": "markdown", + "id": "10e033ec-653b-41f5-aac0-02200a27b8f6", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### Hybrid levels" + ] + }, + { + "cell_type": "raw", + "id": "f4d807e1-f3f1-4f0f-bcda-1f7718735898", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Hybrid levels are terrain following at the bottom and isobaric at the top of the atmosphere with a transition in between. This is the vertical coordinate system in the IFS model. \n", + "\n", + "For details about the hybrid levels see `IFS Documentation CY47R3 - Part IV Physical processes, Chapter 2, Section 2.2.1. `_. Also see the :ref:`/examples/hybrid_levels.ipynb` notebook for the related computations in earthkit-meteo." + ] + }, + { + "cell_type": "markdown", + "id": "cf162a36-b766-4467-8796-452aa7f70927", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### Getting the data" + ] + }, + { + "cell_type": "markdown", + "id": "5244b1be-9462-4cee-8fba-bcc105cc3fbb", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "First, we get some sample data containing 137 IFS model levels (hybrid full-levels) for two points. This data is in ascending order with regards to the model level number. So the first level is model level 1 (i.e. the top), while the last one is model level 137 (i.e. the bottom)." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "966eb14e-fde6-423a-b13d-a9a5274b832c", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "((2,), (2,), (137, 2), (137, 2))" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from earthkit.meteo.utils.sample import get_sample\n", + "\n", + "DATA = get_sample(\"vertical_hybrid_data\")\n", + "sp = DATA.p_surf # surface pressure [Pa]\n", + "zs = DATA.z_surf # surface geopotential [m2/s2]\n", + "t = DATA.t # temperature [K]\n", + "q = DATA.q # specific humidity [kg/kg]\n", + "\n", + "sp.shape, zs.shape, t.shape, q.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "72e1953c-a1da-438e-858d-d14b6b6c09c1", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[197.06175232, 198.54808044],\n", + " [230.29292297, 219.00386047],\n", + " [234.56352234, 229.85160828],\n", + " [264.58778381, 292.04481506]])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "t[::40]" + ] + }, + { + "cell_type": "raw", + "id": "15bf8a34-a571-421d-82ea-f3a46fc4d753", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Next, get the hybrid level definitions using :py:meth:`~earthkit.meteo.vertical.array.hybrid_level_parameters`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "02c083b4-d38f-4e6b-99d3-39fc6bdca336", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "((138,), (138,))" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "A, B = vertical.hybrid_level_parameters(137, model=\"ifs\")\n", + "A.shape, B.shape" + ] + }, + { + "cell_type": "markdown", + "id": "0420e042-eb20-4181-8750-b8c68db4c922", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### Using interpolate_hybrid_to_height_levels" + ] + }, + { + "cell_type": "raw", + "id": "3efd7623-b84a-48ff-a53c-642a796bc3ae", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "We can use :py:meth:`~earthkit.meteo.vertical.array.interpolate_hybrid_to_height_levels` for hybrid to height level interpolation. The example below interpolates temperature to geometric heights above the ground." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a728b121-4642-46d6-a360-3727ede0b6aa", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1000.0 [262.36938948 291.44520344]\n", + "5000.0 [236.77461002 265.49528592]\n" + ] + } + ], + "source": [ + "target_h = [1000., 5000.] # # geometric height, above the ground\n", + "\n", + "t_res = vertical.interpolate_hybrid_to_height_levels(\n", + " t, # data to interpolate\n", + " target_h, \n", + " t, q, zs, A, B, sp, \n", + " h_type=\"geometric\", \n", + " h_reference=\"ground\",\n", + " interpolation=\"linear\")\n", + "\n", + "for hv, rv in zip(target_h, t_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "b025c0ce-12a8-40d1-8311-9fff11873b33", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "##### Heights above sea level" + ] + }, + { + "cell_type": "markdown", + "id": "e78b8f4e-8873-4659-b154-be28e23d84a3", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "The reference height can also be the sea level." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "a5bf08f5-342b-4955-9dc8-9f7d4f7cc766", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1000.0 [265.83447529 291.04194846]\n", + "5000.0 [239.80992741 264.96290891]\n" + ] + } + ], + "source": [ + "target_h = [1000., 5000.] # # geometric height, above the sea\n", + "\n", + "t_res = vertical.interpolate_hybrid_to_height_levels(\n", + " t, # data to interpolate\n", + " target_h, \n", + " t, q, zs, A, B, sp, \n", + " h_type=\"geometric\", \n", + " h_reference=\"sea\",\n", + " interpolation=\"linear\")\n", + "\n", + "for hv, rv in zip(target_h, t_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "f999af82-ca46-49bf-9ef3-b9fde15628b9", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "##### Geopotential heights" + ] + }, + { + "cell_type": "markdown", + "id": "3594a0ed-361a-4fc7-93bd-57eeea43a653", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "The height type can be geopotential height." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "46e94b8d-e002-44d4-a5c3-aa544cc4a867", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1000.0 [262.36579436 291.44471712]\n", + "5000.0 [236.7517288 265.47139844]\n" + ] + } + ], + "source": [ + "target_h = [1000., 5000.] # # geopotential height, above the ground\n", + "\n", + "t_res = vertical.interpolate_hybrid_to_height_levels(\n", + " t, # data to interpolate\n", + " target_h, \n", + " t, q, zs, A, B, sp, \n", + " h_type=\"geopotential\", \n", + " h_reference=\"ground\",\n", + " interpolation=\"linear\")\n", + "\n", + "for hv, rv in zip(target_h, t_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "4e89d7b9-823a-406d-a739-f8835757c9ad", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "##### Using a subset of levels" + ] + }, + { + "cell_type": "markdown", + "id": "e651ed62-ea64-4797-b0f9-94de2a13794e", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "It is possible to use only a **subset of the model levels** in the input data. This can significantly speed up the computations and reduce memory usage. \n", + "\n", + "In this case the model level range in the input must be contiguous and include the bottom-most level. The following example only uses the lowest 50 model levels above the surface. " + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "8e215841-9355-43fd-8108-9fa0462579c4", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1000.0 [262.36938948 291.44520344]\n", + "5000.0 [236.77461002 265.49528592]\n" + ] + } + ], + "source": [ + "# using the 50 lowest model levels as input\n", + "# Please note we still need to use the full A and B arrays\n", + "t_res = vertical.interpolate_hybrid_to_height_levels(\n", + " t[-50:], # data to interpolate\n", + " target_h, \n", + " t[-50:], q[-50:], zs, A, B, sp,\n", + " h_type=\"geometric\", \n", + " h_reference=\"ground\",\n", + " interpolation=\"linear\")\n", + "\n", + "for hv, rv in zip(target_h, t_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "dae03c01-1a3a-4d31-b2d9-ed820f06946a", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "##### Using aux levels" + ] + }, + { + "cell_type": "markdown", + "id": "a831c226-fbd2-471d-8e0b-c5edff42bf02", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Since, the lowest (full) model level is always above the surface the interpolation fails if the target height is between the surface and the lowest model level. We can compute the height of the lowest model level in our data:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "91cc251d-88ee-472f-9dd5-2315c1e23204", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 9.34500108, 10.21640974]])" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "vertical.height_on_hybrid_levels(t[-1:], q[-1:], zs, A, B, sp, \n", + " h_type=\"geometric\", \n", + " h_reference=\"ground\")" + ] + }, + { + "cell_type": "markdown", + "id": "756e06c4-294c-40e4-ab67-8e8bbaa347f7", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "As a consequence, interpolation to e.g. 5 m above the ground does not work:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "69b6fb60-f40e-4471-8ca7-edc680b47194", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5.0 [nan nan]\n" + ] + } + ], + "source": [ + "target_h = [5.] # # geometric height, above the ground\n", + "\n", + "t_res = vertical.interpolate_hybrid_to_height_levels(\n", + " t, # data to interpolate\n", + " target_h, \n", + " t, q, zs, A, B, sp, \n", + " h_type=\"geometric\", \n", + " h_reference=\"ground\",\n", + " interpolation=\"linear\")\n", + "\n", + "for hv, rv in zip(target_h, t_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "6330b804-b753-47c6-b4c0-9fe061e9da15", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "To overcome this problem we can define \"aux\" levels with a prescribed input data. As a demonstration, we set the temperature values on the surface in all the input points with the ``aux_bottom_*`` kwargs." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "ccf15534-d40b-42f8-b2b9-84071c134a67", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5.0 [274.04815857 296.50007348]\n" + ] + } + ], + "source": [ + "target_h = [5.] # # geometric height, above the ground\n", + "\n", + "t_res = vertical.interpolate_hybrid_to_height_levels(\n", + " t, # data to interpolate\n", + " target_h, \n", + " t, q, zs, A, B, sp, \n", + " h_type=\"geometric\", \n", + " h_reference=\"ground\",\n", + " aux_bottom_h=0.,\n", + " aux_bottom_data=[280., 300.],\n", + " interpolation=\"linear\")\n", + "\n", + "for hv, rv in zip(target_h, t_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "926c8528-6429-4302-98f3-e643012b8938", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### Using interpolate_monotonic" + ] + }, + { + "cell_type": "raw", + "id": "88a37616-4861-498a-b94e-746b428be2fa", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "We can also use :py:meth:`~earthkit.meteo.vertical.array.interpolate_monotonic` to carry out the interpolation. This is a generic method and we need an extra step to compute the height on (full) model levels using :py:meth:`~meteo.vertical.array.height_on_hybrid_levels`. This height data then can be used to interpolate multiple input data arrays." + ] + }, + { + "cell_type": "markdown", + "id": "d407f188-c3dd-4f0f-8965-0f7f08ae29cc", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "First, compute the geometric height above the ground on all the model levels in the input data. " + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "cdd71bf5-4755-437a-b0d4-333e84afb337", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "((137, 2),\n", + " array([[81782.73207519, 80656.03851153],\n", + " [23758.58686088, 24154.77147989],\n", + " [ 9064.8524558 , 10238.94032239],\n", + " [ 654.70913487, 736.54960026]]))" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "h = vertical.height_on_hybrid_levels(t, q, zs, A, B, sp, \n", + " h_type=\"geometric\", \n", + " h_reference=\"ground\")\n", + "h.shape, h[::40]" + ] + }, + { + "cell_type": "markdown", + "id": "e4d2d855-b0d7-463f-9437-0ca0882f24d4", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Next, interpolate the temperature to the target height levels." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "7d3107c7-36b5-4684-b081-812e90f26213", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1000.0 [262.36938948 291.44520344]\n", + "5000.0 [236.77461002 265.49528592]\n" + ] + } + ], + "source": [ + "target_h=[1000., 5000.] # geometric height (m), above the ground\n", + "\n", + "t_res = vertical.interpolate_monotonic(t, h, target_h, interpolation=\"linear\")\n", + "\n", + "for hv, rv in zip(target_h, t_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "472a9462-c037-4a3f-8fda-f8997ec43cbb", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Repeat the same computation for specific humidity." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "da1dfe47-31e2-4ff8-97db-b381a3cc86e9", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1000.0 [0.00112569 0.00748045]\n", + "5000.0 [7.55903873e-05 1.37677882e-03]\n" + ] + } + ], + "source": [ + "q_res = vertical.interpolate_monotonic(q, h, target_h, interpolation=\"linear\")\n", + "\n", + "for hv, rv in zip(target_h, q_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "27ec0863-136a-44f8-8df0-bac928786117", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "##### Heights above sea level" + ] + }, + { + "cell_type": "raw", + "id": "fe2b3bac-730c-49d1-b8a9-ce059f2a1668", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "The reference height can also be the sea level. This time we need to pass the proper surface geopotential to :py:meth:`~meteo.vertical.array.height_on_hybrid_levels`." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "3ec0ce27-1b4d-4ae5-98e8-3d0dde568769", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1000.0 [265.83447529 291.04194846]\n", + "5000.0 [239.80992741 264.96290891]\n" + ] + } + ], + "source": [ + "h_sea = vertical.height_on_hybrid_levels(t, q, zs, A, B, sp, \n", + " h_type=\"geometric\", \n", + " h_reference=\"sea\")\n", + "\n", + "target_h_sea=[1000., 5000.] # geometric height, above the sea\n", + "\n", + "t_res = vertical.interpolate_monotonic(t, h_sea, target_h_sea, interpolation=\"linear\")\n", + "\n", + "for hv, rv in zip(target_h_sea, t_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "b991af5a-251b-4cb1-83d1-15621e315c6b", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "##### Using a subset of levels" + ] + }, + { + "cell_type": "markdown", + "id": "b06f9596-af2d-4419-8e74-66295c1d7563", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "We can also use a **subset of levels**. This can significantly speed up the computations and reduce memory usage.\n", + "\n", + "In this case the model level range in the input data must be contiguous and include the bottom-most level. The example below only uses the lowest 50 model levels above the surface." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "a874cfee-fd79-4842-8893-0dc038a3a4db", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1000.0 [262.36938948 291.44520344]\n", + "5000.0 [236.77461002 265.49528592]\n" + ] + } + ], + "source": [ + "# compute height on the 50 lowest model levels\n", + "# Please note we still need to use the full A and B arrays.\n", + "h_sub = vertical.height_on_hybrid_levels(t[-50:], q[-50:], zs, A, B, sp, \n", + " h_type=\"geometric\", \n", + " h_reference=\"ground\")\n", + "\n", + "# interpolate to height levels\n", + "t_res = vertical.interpolate_monotonic(t[-50:], h_sub, target_h, interpolation=\"linear\")\n", + "\n", + "\n", + "for hv, rv in zip(target_h, t_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "b97579dd-35e8-4d9a-a4d9-e1649ae648da", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "##### Using aux levels" + ] + }, + { + "cell_type": "markdown", + "id": "806558b7-a7a4-4db0-a71d-1f0176379284", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "As described above, the lowest (full) model level is always above the surface so the interpolation fails if the target height is between the surface and the lowest model level." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "6b536af5-56e2-41c5-858f-28db8205b775", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5.0 [nan nan]\n" + ] + } + ], + "source": [ + "target_h=[5.] # geometric height, above the ground\n", + "\n", + "t_res = vertical.interpolate_monotonic(t, h, target_h, interpolation=\"linear\")\n", + "\n", + "for hv, rv in zip(target_h, t_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "b154ee25-5802-4215-95cc-6e5376373f69", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "To overcome this problem we can define \"aux\" levels with a prescribed input data. As a demonstration, we set the temperature values on the surface in all the input points with the ``aux_min_level_*`` kwargs." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "1f5e41a6-9835-4afd-b971-95e37af67e1a", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5.0 [274.04815857 296.50007348]\n" + ] + } + ], + "source": [ + "target_h=[5.] # geometric height, above the ground\n", + "\n", + "t_res = vertical.interpolate_monotonic(t, h, target_h, \n", + " aux_min_level_coord=0,\n", + " aux_min_level_data=[280., 300.],\n", + " interpolation=\"linear\")\n", + "\n", + "for hv, rv in zip(target_h, t_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd2fc482-88a5-4edb-8b9c-77e8496202d8", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "dev", + "language": "python", + "name": "dev" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/examples/interpolate_hybrid_to_pl.ipynb b/docs/examples/interpolate_hybrid_to_pl.ipynb new file mode 100644 index 0000000..1cbf5bc --- /dev/null +++ b/docs/examples/interpolate_hybrid_to_pl.ipynb @@ -0,0 +1,840 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "150548d1-f025-47bf-9604-31a339c6bc91", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Interpolating from hybrid to pressure levels" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "653672d0-0d06-40bd-8945-7ec046bfc93a", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import earthkit.meteo.vertical.array as vertical" + ] + }, + { + "cell_type": "markdown", + "id": "8dea461f-8796-48e2-8711-a7e42e65c165", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### Hybrid levels" + ] + }, + { + "cell_type": "raw", + "id": "68c2e2c1-5e21-4b52-9dd1-131bee484ba1", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Hybrid levels are terrain following at the bottom and isobaric at the top of the atmosphere with a transition in between. This is the vertical coordinate system in the IFS model. \n", + "\n", + "For details about the hybrid levels see `IFS Documentation CY47R3 - Part IV Physical processes, Chapter 2, Section 2.2.1. `_. Also see the :ref:`/examples/hybrid_levels.ipynb` notebook for the related computations in earthkit-meteo." + ] + }, + { + "cell_type": "markdown", + "id": "cf162a36-b766-4467-8796-452aa7f70927", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### Getting the data" + ] + }, + { + "cell_type": "markdown", + "id": "5244b1be-9462-4cee-8fba-bcc105cc3fbb", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "First, we get some sample data containing 137 IFS model levels (hybrid full-levels) for two points. This data is in ascending order with regards to the model level number. So the first level is model level 1 (i.e. the top), while the last one is model level 137 (i.e. the bottom)." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "966eb14e-fde6-423a-b13d-a9a5274b832c", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "((2,), (137, 2), (137, 2))" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from earthkit.meteo.utils.sample import get_sample\n", + "\n", + "DATA = get_sample(\"vertical_hybrid_data\")\n", + "sp = DATA.p_surf # surface pressure [Pa]\n", + "t = DATA.t # temperature [K]\n", + "q = DATA.q # specific humidity [kg/kg]\n", + "\n", + "sp.shape, t.shape, q.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "72e1953c-a1da-438e-858d-d14b6b6c09c1", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[197.06175232, 198.54808044],\n", + " [230.29292297, 219.00386047],\n", + " [234.56352234, 229.85160828],\n", + " [264.58778381, 292.04481506]])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "t[::40]" + ] + }, + { + "cell_type": "raw", + "id": "15bf8a34-a571-421d-82ea-f3a46fc4d753", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Next, get the hybrid level definitions using :py:meth:`~earthkit.meteo.vertical.array.hybrid_level_parameters`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "02c083b4-d38f-4e6b-99d3-39fc6bdca336", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "((138,), (138,))" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "A, B = vertical.hybrid_level_parameters(137, model=\"ifs\")\n", + "A.shape, B.shape" + ] + }, + { + "cell_type": "markdown", + "id": "0420e042-eb20-4181-8750-b8c68db4c922", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### Using interpolate_hybrid_to_pressure_levels" + ] + }, + { + "cell_type": "raw", + "id": "3efd7623-b84a-48ff-a53c-642a796bc3ae", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "We can use :py:meth:`~earthkit.meteo.vertical.array.interpolate_hybrid_to_pressure_levels` for hybrid to pressure level interpolation. The example below interpolates temperature to pressure levels." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a728b121-4642-46d6-a360-3727ede0b6aa", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "85000.0 [263.50982741 287.70299692]\n", + "50000.0 [238.00383748 259.50822691]\n" + ] + } + ], + "source": [ + "target_p = [85000., 50000.] # Pa\n", + "\n", + "t_res = vertical.interpolate_hybrid_to_pressure_levels(\n", + " t, # data to interpolate\n", + " target_p,\n", + " A, B, sp, \n", + " interpolation=\"linear\")\n", + "\n", + "for pv, rv in zip(target_p, t_res):\n", + " print(pv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "4e89d7b9-823a-406d-a739-f8835757c9ad", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "##### Using a subset of levels" + ] + }, + { + "cell_type": "markdown", + "id": "e651ed62-ea64-4797-b0f9-94de2a13794e", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "It is possible to use only a **subset of the model levels** in the input data. This can significantly speed up the computations and reduce memory usage. \n", + "\n", + "In this case the model level range in the input must be contiguous and include the bottom-most level. The following example only uses the lowest 50 model levels above the surface. " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "8e215841-9355-43fd-8108-9fa0462579c4", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "85000.0 [263.50982741 287.70299692]\n", + "50000.0 [238.00383748 259.50822691]\n" + ] + } + ], + "source": [ + "# using the 50 lowest model levels as input\n", + "# Please note we still need to use the full A and B arrays\n", + "t_res = vertical.interpolate_hybrid_to_pressure_levels(\n", + " t[-50:], # data to interpolate\n", + " target_p, \n", + " A, B, sp,\n", + " interpolation=\"linear\")\n", + "\n", + "for pv, rv in zip(target_p, t_res):\n", + " print(pv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "33982015-65e2-4711-9779-a524bb272faf", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "##### Using aux levels\n" + ] + }, + { + "cell_type": "markdown", + "id": "c90340fb-8ff2-4067-bdbc-6811f3803d47", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Since, the lowest (full) model level is always above the surface the interpolation fails if the target pressure is between the surface and the lowest model level. We can check the pressure on the surface and the lowest model level in our data:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "a2c95690-0d3a-4d78-b9de-a90658d2e7f4", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(array([ 95178.337944 , 102659.81019512]),\n", + " array([[ 95065.55729093, 102538.16444369]]))" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sp, vertical.pressure_on_hybrid_levels(A, B, sp, levels=[137], output=\"full\")" + ] + }, + { + "cell_type": "markdown", + "id": "735fb9c2-b858-4bf1-b21c-1f4fd3d76a0e", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "As a consequence, interpolation interpolation to 95100 Pa results in nan for the first point:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "2095a87d-5b31-4823-a9e2-699a358ef6cb", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "95100.0 [ nan 292.08454145]\n" + ] + } + ], + "source": [ + "target_p = [95100.] # Pa\n", + "\n", + "t_res = vertical.interpolate_hybrid_to_pressure_levels(\n", + " t, # data to interpolate\n", + " target_p,\n", + " A, B, sp, \n", + " interpolation=\"linear\")\n", + "\n", + "for pv, rv in zip(target_p, t_res):\n", + " print(pv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "9a6d217f-f157-4e8a-ba53-b345f2d40170", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "To overcome this problem we can define \"aux\" levels with a prescribed input data. As a demonstration, we set the temperature values on the surface in all the input points with the ``aux_bottom_*`` kwargs." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "97903bdd-309b-404c-b39d-18666f479e0f", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "95100.0 [269.21926951 292.08454145]\n" + ] + } + ], + "source": [ + "target_p = [95100.] # Pa\n", + "\n", + "t_res = vertical.interpolate_hybrid_to_pressure_levels(\n", + " t, # data to interpolate\n", + " target_p,\n", + " A, B, sp, \n", + " aux_bottom_p=[ 95178.337944 , 102659.81019512],\n", + " aux_bottom_data=[270.0, 293.0],\n", + " interpolation=\"linear\")\n", + "\n", + "for pv, rv in zip(target_p, t_res):\n", + " print(pv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "926c8528-6429-4302-98f3-e643012b8938", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### Using interpolate_monotonic" + ] + }, + { + "cell_type": "raw", + "id": "88a37616-4861-498a-b94e-746b428be2fa", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "We can also use :py:meth:`~earthkit.meteo.vertical.array.interpolate_monotonic` to carry out the interpolation. This is a generic method and we need an extra step to compute the pressure on (full) model levels using :py:meth:`~meteo.vertical.array.pressure_on_hybrid_levels`. This pressure data then can be used to interpolate multiple input data arrays." + ] + }, + { + "cell_type": "markdown", + "id": "6994115a-41b8-4e68-8019-3f30b4226f8d", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "First, compute the pressure all the model levels in the input data." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "cdd71bf5-4755-437a-b0d4-333e84afb337", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(137, 2)" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p = vertical.pressure_on_hybrid_levels(A, B, sp, output=\"full\")\n", + "p.shape" + ] + }, + { + "cell_type": "markdown", + "id": "b0b25af4-a8a4-4f31-9079-94c012be364c", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Next, interpolate the temperature to the target pressure levels." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "7d3107c7-36b5-4684-b081-812e90f26213", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "85000.0 [263.50982741 287.70299692]\n", + "50000.0 [238.00383748 259.50822691]\n" + ] + } + ], + "source": [ + "target_p = [85000., 50000.] # Pa\n", + "\n", + "t_res = vertical.interpolate_monotonic(t, p, target_p, interpolation=\"linear\")\n", + "\n", + "for pv, rv in zip(target_p, t_res):\n", + " print(pv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "b0a9dd40-c1a5-4420-a10f-968750952392", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Repeat the same computation for specific humidity." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "da1dfe47-31e2-4ff8-97db-b381a3cc86e9", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "85000.0 [0.00119029 0.00621813]\n", + "50000.0 [0.00010948 0.00046553]\n" + ] + } + ], + "source": [ + "q_res = vertical.interpolate_monotonic(q, p, target_p, interpolation=\"linear\")\n", + "\n", + "for pv, rv in zip(target_p, q_res):\n", + " print(pv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "b991af5a-251b-4cb1-83d1-15621e315c6b", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "##### Using a subset of levels" + ] + }, + { + "cell_type": "markdown", + "id": "b06f9596-af2d-4419-8e74-66295c1d7563", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "We can also use a **subset of levels**. This can significantly speed up the computations and reduce memory usage.\n", + "\n", + "In this case the model level range in the input data must be contiguous and include the bottom-most level. The example below only uses the lowest 50 model levels above the surface." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "de53dbe3-3f83-4480-8981-07bb8a02194f", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "85000.0 [263.50982741 287.70299692]\n", + "50000.0 [238.00383748 259.50822691]\n" + ] + } + ], + "source": [ + "# compute pressure on the 50 lowest model levels\n", + "# Please note we still need to use the full A and B arrays.\n", + "levels = list(range(138-50, 138)) # 97-137\n", + "p_sub = vertical.pressure_on_hybrid_levels(A, B, sp, levels=levels, output=\"full\")\n", + " \n", + "\n", + "# interpolate to pressure levels\n", + "t_res = vertical.interpolate_monotonic(t[-50:], p_sub, target_p, interpolation=\"linear\")\n", + "\n", + "for pv, rv in zip(target_p, t_res):\n", + " print(pv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "2f065399-7da4-4be8-b406-4ef5ac2ef2e0", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "##### Using aux levels" + ] + }, + { + "cell_type": "markdown", + "id": "ed9e2849-0504-4f54-a76b-9fe481e8afee", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "As described above, the lowest (full) model level is always above the surface so the interpolation does not work if the target pressure is between the surface and the lowest model level." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "73fe7588-8e62-43c7-b8f7-4a5d5618e941", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "95100.0 [ nan 292.08454145]\n" + ] + } + ], + "source": [ + "# this pressure is larger than the pressure on the lowest model level in point 1 \n", + "target_p=[95100.] # Pa\n", + "\n", + "t_res = vertical.interpolate_monotonic(t, p, target_p, interpolation=\"linear\")\n", + "\n", + "for pv, rv in zip(target_p, t_res):\n", + " print(pv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "ac6f5026-ff41-4a57-a5e9-f1fa75b5f90f", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "To overcome this problem we can define \"aux\" levels with a prescribed input data. As a demonstration, we set the temperature values on the surface in all the input points with the ``aux_max_level_*`` kwargs." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "40950cbf-56d8-4614-835b-9297f1e3a288", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "95100.0 [269.21926951 292.08454145]\n" + ] + } + ], + "source": [ + "target_p=[95100.] # Pa\n", + "\n", + "t_res = vertical.interpolate_monotonic(t, p, target_p, \n", + " aux_max_level_coord=[ 95178.337944 , 102659.81019512],\n", + " aux_max_level_data=[270.0, 293.0],\n", + " interpolation=\"linear\")\n", + "\n", + "for pv, rv in zip(target_p, t_res):\n", + " print(pv, rv)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a9138b7c-a695-43fd-84f6-ac9fc7d79cab", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "dev", + "language": "python", + "name": "dev" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/examples/interpolate_pl_to_hl.ipynb b/docs/examples/interpolate_pl_to_hl.ipynb new file mode 100644 index 0000000..562dc5d --- /dev/null +++ b/docs/examples/interpolate_pl_to_hl.ipynb @@ -0,0 +1,783 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c8b87a76-6a8f-45e1-80c3-51ebe6f195b6", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Interpolating from pressure to height levels" + ] + }, + { + "cell_type": "markdown", + "id": "9b306d4b-86b7-49ab-9610-c0f0a01e762c", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "This notebook demonstrates how to interpolate pressure level data to height levels." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "4cec867e-8a70-4b8f-9599-f3a02772b2e1", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import earthkit.meteo.vertical.array as vertical" + ] + }, + { + "cell_type": "markdown", + "id": "cd8394b0-d479-46b9-84a8-ed4447a041d8", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### Getting the data" + ] + }, + { + "cell_type": "markdown", + "id": "8570fbf3-aea9-4bd3-b303-4fcfd96e955d", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "We get some sample pressure level data for two points." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "4bee3ea7-6629-48e2-a2c9-76c0dbaae660", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(array([100000., 85000., 70000., 50000., 40000., 30000., 20000.,\n", + " 15000., 10000.]),\n", + " (9, 2))" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from earthkit.meteo.utils.sample import get_sample\n", + "\n", + "DATA = get_sample(\"vertical_pl_data\")\n", + "p = DATA.p # pressure [Pa]\n", + "t = DATA.t # temperature [K]\n", + "z = DATA.z # geopotential [m2/s2]\n", + "sp = DATA.p_surf # surface pressure [Pa]\n", + "zs = DATA.z_surf # surface geopotential [m2/s2]\n", + "\n", + "p, t.shape" + ] + }, + { + "cell_type": "markdown", + "id": "9f7bf6ec-b7cc-43f5-ab43-d0811302d3e4", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### Using interpolate_pressure_to_height_levels" + ] + }, + { + "cell_type": "raw", + "id": "dd44d821-3742-4aa6-a9fc-b11ab87cdb9e", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "We can use :py:meth:`~earthkit.meteo.vertical.array.interpolate_pressure_to_height_levels` to carry out the interpolation. The example below interpolates temperature to geometric heights above the ground." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "8f3de079-8007-4335-acdc-007d9590f08d", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1000.0 [294.20429573 299.22387254]\n", + "5000.0 [271.02124509 272.90306903]\n" + ] + } + ], + "source": [ + "target_h=[1000., 5000.] # geometric height (m), above the ground\n", + "\n", + "t_res = vertical.interpolate_pressure_to_height_levels(\n", + " t, # data to interpolate\n", + " target_h, \n", + " z, zs,\n", + " h_type=\"geometric\", \n", + " h_reference=\"ground\", \n", + " interpolation=\"linear\")\n", + "\n", + "for hv, rv in zip(target_h, t_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "5755dfff-a215-4ace-a924-1c399e65a8a1", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "##### Heights above sea level" + ] + }, + { + "cell_type": "markdown", + "id": "33c81c05-43ae-4f21-a7f5-d9eb567c4af1", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "The reference height can also be the sea level." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "284e0abf-e39a-46bb-b74f-c960942127d2", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1000.0 [298.45168008 298.99485246]\n", + "5000.0 [274.03261154 272.7133842 ]\n" + ] + } + ], + "source": [ + "target_h=[1000., 5000.] # geometric height (m), above the sea\n", + "\n", + "# zs is not used in interpolate_pressure_to_height_levels()\n", + "# when h_reference=\"sea\", so we can pass None for it\n", + "t_res = vertical.interpolate_pressure_to_height_levels(\n", + " t, # data to interpolate\n", + " target_h, \n", + " z, None, \n", + " h_type=\"geometric\", \n", + " h_reference=\"sea\", \n", + " interpolation=\"linear\")\n", + "\n", + "for hv, rv in zip(target_h, t_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "05cd2163-1a35-4dde-a110-cf4f7a2e8e0d", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "##### Geopotential heights" + ] + }, + { + "cell_type": "markdown", + "id": "57ffbea0-0982-498b-9022-6fcdf657a8be", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "The height type can be geopotential height." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "f2ede836-90bc-43f7-b1b8-698924a2fba1", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1000.0 [294.20228758 299.22210157]\n", + "5000.0 [270.99379632 272.87589766]\n" + ] + } + ], + "source": [ + "target_h=[1000., 5000.] # geopotential height (m), above the ground\n", + "\n", + "t_res = vertical.interpolate_pressure_to_height_levels(\n", + " t, # data to interpolate\n", + " target_h, \n", + " z, zs,\n", + " h_type=\"geopotential\", \n", + " h_reference=\"ground\", \n", + " interpolation=\"linear\")\n", + "\n", + "for hv, rv in zip(target_h, t_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "70b77161-8901-49a7-91dd-400c1f7ccd75", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "##### Using aux levels" + ] + }, + { + "cell_type": "markdown", + "id": "2efb8b20-ff6e-463a-aeef-fb3ef11abed4", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "In the input data the lowest pressure level is 1000 hPa. If we look at the surface pressure values we can see that the first point on 1000 hPa is lying below the surface (100000 > 94984), while the second point is lying above it (100000 < 101686)." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "490ae123-f73d-4289-b917-e987437c24f9", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 94984.34375, 101686.34375])" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sp" + ] + }, + { + "cell_type": "markdown", + "id": "68431f7b-b07d-4c8c-ac87-24e6c5859083", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "This means that if we interpolate to a height just above the surface the interpolation will result in missing value in the second point." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "90c86481-6eef-40db-b0e4-fab77ab9ad96", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.0 [302.09183704 nan]\n" + ] + } + ], + "source": [ + "target_h=[2.] # geometric height (m), above the ground\n", + "\n", + "t_res = vertical.interpolate_pressure_to_height_levels(\n", + " t, # data to interpolate\n", + " target_h, \n", + " z, zs,\n", + " h_type=\"geometric\", \n", + " h_reference=\"ground\", \n", + " interpolation=\"linear\")\n", + "\n", + "for hv, rv in zip(target_h, t_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "fc941560-4d16-4d77-8fc7-284a0f86ae79", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "To overcome this problem we can define \"aux\" levels with a prescribed input data. As a demonstration, we set the temperature values on the surface in all the input points with the aux_bottom_* kwargs." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "1bc54fa1-85dd-436e-8fbe-4acba840a1ac", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.0 [302.09183704 305.9996225 ]\n" + ] + } + ], + "source": [ + "target_h=[2.] # geometric height (m), above the ground\n", + "\n", + "t_res = vertical.interpolate_pressure_to_height_levels(\n", + " t, # data to interpolate\n", + " target_h, \n", + " z, zs,\n", + " h_type=\"geometric\", \n", + " h_reference=\"ground\", \n", + " aux_bottom_h=0.,\n", + " aux_bottom_data=[304., 306.],\n", + " interpolation=\"linear\")\n", + "\n", + "for hv, rv in zip(target_h, t_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "d05b7f2c-6289-40e4-8513-af0f2b236e9f", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### Using interpolate_monotonic" + ] + }, + { + "cell_type": "raw", + "id": "c0a1be1e-4aac-48f0-93e3-d21f0d6356a6", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "We can also use :py:meth:`~earthkit.meteo.vertical.array.interpolate_monotonic` to carry out the interpolation. This is a generic method and we need an extra step to compute the e.g. the geometric height on the pressure levels :py:meth:`~meteo.vertical.array.geometric_height_from_geopotential`. This height data then can be used to interpolate multiple input data arrays." + ] + }, + { + "cell_type": "markdown", + "id": "f170a822-02f7-44be-92b7-4c58fe11c827", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "First, compute the geometric height above the ground on all pressure levels in the input data. " + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "6e3b8bee-0fd9-430b-a0a0-35f4fe4b5f0b", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# geometric height is a non-linear function of geopotential. So we need to \n", + "# do the subtraction as follows:\n", + "h = vertical.geometric_height_from_geopotential(z) - vertical.geometric_height_from_geopotential(zs)" + ] + }, + { + "cell_type": "markdown", + "id": "8f91d0f1-c629-44dd-9ca6-2bf91ac748a0", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Next, interpolate the temperature to the target height levels." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "a82dc862-a510-4a1d-838d-d76dad840140", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1000.0 [294.20429573 299.22387254]\n", + "5000.0 [271.02124509 272.90306903]\n" + ] + } + ], + "source": [ + "target_h=[1000., 5000.] # geometric height (m), above the ground\n", + "\n", + "t_res = vertical.interpolate_monotonic(\n", + " t, # data to interpolate\n", + " h, \n", + " target_h, \n", + " interpolation=\"linear\")\n", + "\n", + "for hv, rv in zip(target_h, t_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "61292a39-3ffe-49e8-b18a-23a4ba5c3b2f", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "##### Heights above sea level" + ] + }, + { + "cell_type": "markdown", + "id": "74828219-4a6f-4ab9-bcba-564f665655d0", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "The reference height can also be the sea level." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "3c842a15-e7a0-4b0d-b447-2e325563ba3a", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1000.0 [298.45168008 298.99485246]\n", + "5000.0 [274.03261154 272.7133842 ]\n" + ] + } + ], + "source": [ + "# compute geometric height above sea level\n", + "h_sea = vertical.geometric_height_from_geopotential(z)\n", + "\n", + "target_h=[1000., 5000.] # geometric height (m), above the sea\n", + "\n", + "t_res = vertical.interpolate_monotonic(\n", + " t, # data to interpolate\n", + " h_sea, \n", + " target_h, \n", + " interpolation=\"linear\")\n", + "\n", + "for hv, rv in zip(target_h, t_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "62ac8dd1-6948-4607-9912-8be42502669e", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "##### Using aux levels" + ] + }, + { + "cell_type": "markdown", + "id": "96b933fd-ff8f-4195-9a8e-d79e8a2f442a", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "For the second input data point, as described above, the lowest pressure level is lying above the surface so interpolation to a height just above the surface will result in a missing value." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "b2055b3a-3276-41c3-9347-e0614e9409c8", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.0 [302.09183704 nan]\n" + ] + } + ], + "source": [ + "target_h=[2.] # geometric height (m), above the ground\n", + "\n", + "t_res = vertical.interpolate_monotonic(\n", + " t, # data to interpolate\n", + " h,\n", + " target_h, \n", + " interpolation=\"linear\")\n", + "\n", + "for hv, rv in zip(target_h, t_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "markdown", + "id": "3d6fa732-aeb0-4a89-a454-82c544b3b461", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "To overcome this problem we can define \"aux\" levels with a prescribed input data. As a demonstration, we set the temperature values on the surface in all the input points with the ``aux_min_level_*`` kwargs." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "c9fe7c0b-f032-4db7-b432-cc826138d0bd", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.0 [302.09183704 305.9996225 ]\n" + ] + } + ], + "source": [ + "target_h=[2.] # geometric height (m), above the ground\n", + "\n", + "t_res = vertical.interpolate_monotonic(\n", + " t, # data to interpolate\n", + " h,\n", + " target_h, \n", + " aux_min_level_coord=0.,\n", + " aux_min_level_data=[304., 306.],\n", + " interpolation=\"linear\")\n", + "\n", + "for hv, rv in zip(target_h, t_res):\n", + " print(hv, rv)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56167ef6-38f8-4d5e-8d4e-b037fb089139", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "dev", + "language": "python", + "name": "dev" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/examples/interpolate_pl_to_pl.ipynb b/docs/examples/interpolate_pl_to_pl.ipynb new file mode 100644 index 0000000..cd537c4 --- /dev/null +++ b/docs/examples/interpolate_pl_to_pl.ipynb @@ -0,0 +1,209 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "535a0572-8a06-40c5-8f1a-74490c38ba92", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Interpolating from pressure to pressure levels" + ] + }, + { + "cell_type": "markdown", + "id": "ded0b3b6-0111-4d09-8ade-5d224e5d0138", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "This notebook demonstrates how to interpolate pressure level data to pressure levels." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6b3b8151-772d-4519-85cc-2547c939aa71", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import earthkit.meteo.vertical.array as vertical" + ] + }, + { + "cell_type": "markdown", + "id": "438f184e-26df-4a7f-b26c-63f12b91e157", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### Getting the data" + ] + }, + { + "cell_type": "markdown", + "id": "2d69dec4-9ed5-4510-b1f5-dce8066e54c4", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "We get some sample pressure level data for two points. The data is sorted in descending order according to pressure." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "5e17f876-645d-4180-be5d-a46b4d4fe248", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(array([100000., 85000., 70000., 50000., 40000., 30000., 20000.,\n", + " 15000., 10000.]),\n", + " (9, 2))" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from earthkit.meteo.utils.sample import get_sample\n", + "\n", + "DATA = get_sample(\"vertical_pl_data\")\n", + "p = DATA.p # pressure [Pa]\n", + "t = DATA.t # temperature [K]\n", + "\n", + "p, t.shape" + ] + }, + { + "cell_type": "markdown", + "id": "228b7ba6-ad0c-4dd9-afe6-e30ff9debecb", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "#### Using interpolate_monotonic" + ] + }, + { + "cell_type": "raw", + "id": "fc83f5f3-ea5d-4ba9-b68d-22c5e4737f6e", + "metadata": { + "editable": true, + "raw_mimetype": "", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "We can use :py:meth:`~earthkit.meteo.vertical.array.interpolate_monotonic` to carry out the interpolation." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "2f9bc725-c7c6-4b3b-92bc-8ce9860bccbe", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "87000.0 [295.7878306 296.2361379]\n", + "60000.0 [276.56495667 275.68653869]\n" + ] + } + ], + "source": [ + "target_p=[87000., 60000.] # Pa\n", + "\n", + "t_res = vertical.interpolate_monotonic(\n", + " t, # data to interpolate\n", + " p, \n", + " target_p, \n", + " interpolation=\"linear\")\n", + "\n", + "for hp, rv in zip(target_p, t_res):\n", + " print(hp, rv)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "370b24de-dcbf-41a4-a23c-f85d056a4c9f", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "dev", + "language": "python", + "name": "dev" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/references.rst b/docs/references.rst index f63e93b..027126e 100644 --- a/docs/references.rst +++ b/docs/references.rst @@ -18,7 +18,7 @@ Bolton, D., (1980). The computation of equivalent potential temperature. Mon. We .. [DaviesJones1983] -Davies-Jones, R. P., (1983). An accurate theoretical approximation for adiabatic condensation temperature. Mon. Wea. Rev., 111 , 1119–1121. +Davies-Jones, R. P., (1983). An accurate theoretical approximation for adiabatic condensation temperature. Mon. Wea. Rev., 111 , 1119-1121. .. [DaviesJones2008] diff --git a/docs/release_notes/deprecation.rst b/docs/release_notes/deprecation.rst new file mode 100644 index 0000000..1aead7e --- /dev/null +++ b/docs/release_notes/deprecation.rst @@ -0,0 +1,73 @@ +Deprecations +============= + + +.. _deprecated-0.7.0: + +Version 0.7.0 +----------------- + +.. _deprecated-hybrid-pressure-at-model-levels: + +:func:`pressure_at_model_levels` is deprecated +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +It is replaced by the more generic :func:`pressure_on_hybrid_levels`. The old method is still available for backward compatibility but will be removed in a future release. + + +.. list-table:: + :header-rows: 0 + + * - Deprecated code + * - + + .. literalinclude:: include/deprec_hybrid_pressure.py + + * - New code + * - + + .. literalinclude:: include/migrated_hybrid_pressure.py + + +.. _deprecated-hybrid-relative-geopotential-thickness: + +:func:`relative_geopotential_thickness` is deprecated +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +It is replaced by the more generic :func:`relative_geopotential_thickness_on_hybrid_levels`. The old method is still available for backward compatibility but will be removed in a future release. + + +.. list-table:: + :header-rows: 0 + + * - Deprecated code + * - + + .. literalinclude:: include/deprec_hybrid_geopotential_thickness.py + + * - New code + * - + + .. literalinclude:: include/migrated_hybrid_geopotential_thickness.py + + +.. _deprecated-hybrid-pressure-at-height-levels: + +:func:`pressure_at_height_levels` is deprecated +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +It is replaced by the combined usage of :func:`pressure_on_hybrid_levels` and :func:`interpolate_hybrid_to_height_levels`. The old method is still available for backward compatibility but will be removed in a future release. + + +.. list-table:: + :header-rows: 0 + + * - Deprecated code + * - + + .. literalinclude:: include/deprec_hybrid_pressure_at_height_levels.py + + * - New code + * - + + .. literalinclude:: include/migrated_hybrid_pressure_at_height_levels.py diff --git a/docs/release_notes/include/deprec_hybrid_geopotential_thickness.py b/docs/release_notes/include/deprec_hybrid_geopotential_thickness.py new file mode 100644 index 0000000..f5c6391 --- /dev/null +++ b/docs/release_notes/include/deprec_hybrid_geopotential_thickness.py @@ -0,0 +1,21 @@ +import numpy as np + +import earthkit.meteo.vertical as vertical +from earthkit.meteo.utils.sample import get_sample + +# get hybrid (IFS model) level definition +A, B = vertical.hybrid_level_parameters(137, model="ifs") + +# define surface pressures +sp = np.array([100000.0, 90000.0]) + +# compute alpha and delta +_, _, delta, alpha = vertical.pressure_at_model_levels(A, B, sp, alpha_top="ifs") + +# get temperature and specific humidity profiles on hybrid levels (example data) +DATA = get_sample("vertical_hybrid_data") +t = DATA.t +q = DATA.q + +# compute the relative geopotential thickness +z_thickness = vertical.relative_geopotential_thickness(alpha, delta, t, q) diff --git a/docs/release_notes/include/deprec_hybrid_pressure.py b/docs/release_notes/include/deprec_hybrid_pressure.py new file mode 100644 index 0000000..e8c832f --- /dev/null +++ b/docs/release_notes/include/deprec_hybrid_pressure.py @@ -0,0 +1,11 @@ +import numpy as np + +import earthkit.meteo.vertical as vertical + +# get hybrid (IFS model) level definition +A, B = vertical.hybrid_level_parameters(137, model="ifs") + +# define surface pressures +sp = np.array([100000.0, 90000.0]) + +p_full, p_half, delta, alpha = vertical.pressure_at_model_levels(A, B, sp, alpha_top="ifs") diff --git a/docs/release_notes/include/deprec_hybrid_pressure_at_height_levels.py b/docs/release_notes/include/deprec_hybrid_pressure_at_height_levels.py new file mode 100644 index 0000000..dd328d8 --- /dev/null +++ b/docs/release_notes/include/deprec_hybrid_pressure_at_height_levels.py @@ -0,0 +1,20 @@ +import numpy as np + +import earthkit.meteo.vertical as vertical +from earthkit.meteo.utils.sample import get_sample + +# get hybrid (IFS model) level definition +A, B = vertical.hybrid_level_parameters(137, model="ifs") + +# define surface pressures +sp = np.array([100000.0, 90000.0]) + +# get temperature and specific humidity profiles on hybrid levels (example data) +DATA = get_sample("vertical_hybrid_data") +t = DATA.t +q = DATA.q + +# compute the pressure on geopotential height levels above the +# ground using linear interpolation +h_target = 10.0 +p = vertical.pressure_at_height_levels(h_target, t, q, sp, A, B, alpha_top="ifs") diff --git a/docs/release_notes/include/migrated_hybrid_geopotential_thickness.py b/docs/release_notes/include/migrated_hybrid_geopotential_thickness.py new file mode 100644 index 0000000..8566219 --- /dev/null +++ b/docs/release_notes/include/migrated_hybrid_geopotential_thickness.py @@ -0,0 +1,19 @@ +import numpy as np + +import earthkit.meteo.vertical as vertical +from earthkit.meteo.utils.sample import get_sample + +# get hybrid (IFS model) level definition +A, B = vertical.hybrid_level_parameters(137, model="ifs") + +# define surface pressures +sp = np.array([100000.0, 90000.0]) + +# get temperature and specific humidity profiles on hybrid levels (example data) +DATA = get_sample("vertical_hybrid_data") +t = DATA.t +q = DATA.q + + +# compute the relative geopotential thickness +z_thickness = vertical.relative_geopotential_thickness_on_hybrid_levels(t, q, A, B, sp, alpha_top="ifs") diff --git a/docs/release_notes/include/migrated_hybrid_pressure.py b/docs/release_notes/include/migrated_hybrid_pressure.py new file mode 100644 index 0000000..4d910d8 --- /dev/null +++ b/docs/release_notes/include/migrated_hybrid_pressure.py @@ -0,0 +1,13 @@ +import numpy as np + +import earthkit.meteo.vertical as vertical + +# get hybrid (IFS model) level definition +A, B = vertical.hybrid_level_parameters(137, model="ifs") + +# define surface pressures +sp = np.array([100000.0, 90000.0]) + +p_full, p_half, delta, alpha = vertical.pressure_on_hybrid_levels( + A, B, sp, alpha_top="ifs", output=["full", "half", "delta", "alpha"] +) diff --git a/docs/release_notes/include/migrated_hybrid_pressure_at_height_levels.py b/docs/release_notes/include/migrated_hybrid_pressure_at_height_levels.py new file mode 100644 index 0000000..856bcdc --- /dev/null +++ b/docs/release_notes/include/migrated_hybrid_pressure_at_height_levels.py @@ -0,0 +1,58 @@ +import numpy as np + +import earthkit.meteo.vertical as vertical +from earthkit.meteo.utils.sample import get_sample + +# get hybrid (IFS model) level definition +A, B = vertical.hybrid_level_parameters(137, model="ifs") + +# define surface pressures +sp = np.array([100000.0, 90000.0]) + +# get temperature and specific humidity profiles on hybrid levels (example data) +DATA = get_sample("vertical_hybrid_data") +t = DATA.t +q = DATA.q + +# Option 1 + +# compute the pressure on full hybrid levels +p_full = vertical.pressure_on_hybrid_levels(A, B, sp, alpha_top="ifs", output="full") + +# interpolate the pressure to geopotential height levels above the ground +# using linear interpolation +target_h = [10.0] +p_h = vertical.interpolate_hybrid_to_height_levels( + p_full, + target_h, + t, + q, + 0, + A, + B, + sp, + h_type="geopotential", + h_reference="ground", + interpolation="linear", + alpha_top="ifs", +) + +# Option 2 + +# compute the pressure on full hybrid levels +p_full, alpha, delta = vertical.pressure_on_hybrid_levels( + A, B, sp, alpha_top="ifs", output=("full", "alpha", "delta") +) + +z = vertical.relative_geopotential_thickness_on_hybrid_levels_from_alpha_delta(t, q, alpha, delta) +h = vertical.geopotential_height_from_geopotential(z) + +# interpolate the pressure to geopotential height levels above the ground +# using linear interpolation +target_h = [10.0] +p_h = vertical.interpolate_monotonic( + p_full, + h, + target_h, + interpolation="linear", +) diff --git a/docs/release_notes/index.rst b/docs/release_notes/index.rst index 0b70445..63db8b3 100644 --- a/docs/release_notes/index.rst +++ b/docs/release_notes/index.rst @@ -4,6 +4,7 @@ Release notes .. toctree:: :maxdepth: 1 + version_0.7_updates version_0.6_updates version_0.5_updates version_0.4_updates diff --git a/docs/release_notes/version_0.7_updates.rst b/docs/release_notes/version_0.7_updates.rst new file mode 100644 index 0000000..c7d7af4 --- /dev/null +++ b/docs/release_notes/version_0.7_updates.rst @@ -0,0 +1,24 @@ +Version 0.7 Updates +///////////////////////// + + +Version 0.7.0 +=============== + +Deprecations ++++++++++++++++++++ + +- :ref:`deprecated-hybrid-pressure-at-model-levels` +- :ref:`deprecated-hybrid-relative-geopotential-thickness` +- :ref:`deprecated-hybrid-pressure-at-height-levels` + +Vertical coordinate functions +------------------------------- + +TODO: add docs + + +Vertical interpolations +------------------------ + +TODO: add docs diff --git a/docs/skip_api_rules.py b/docs/skip_api_rules.py index ebe5d63..8af3924 100644 --- a/docs/skip_api_rules.py +++ b/docs/skip_api_rules.py @@ -18,7 +18,7 @@ def _skip_api_items(app, what, name, obj, skip, options): # and name not in ["earthkit.meteo.solar", "earthkit.meteo.solar.array"] # ): # skip = True - if name in ["earthkit.meteo.version", "earthkit.meteo.utils"]: + if name in ["earthkit.meteo.version", "earthkit.meteo.utils", "earthkit.meteo.vertical.array.monotonic"]: skip = True elif what == "module" and ".array." in name: skip = True diff --git a/pyproject.toml b/pyproject.toml index 4e590a9..0b76100 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,16 +27,37 @@ classifiers = [ ] dynamic = [ "version" ] dependencies = [ + "deprecation", "earthkit-utils>=0.0.1", "numpy", ] +optional-dependencies.all = [ + "earthkit-meteo[samples]", +] +optional-dependencies.dev = [ + "earthkit-meteo[all,docs,test]", +] +optional-dependencies.docs = [ + "nbsphinx", + "requests", + "sphinx>=7.3.7", + "sphinx-autoapi", + "sphinx-copybutton", + "sphinx-issues", + "sphinx-rtd-theme", + "sphinx-tabs", +] optional-dependencies.gpu = [ "cupy", "torch", ] +optional-dependencies.samples = [ + "requests", +] optional-dependencies.test = [ "pytest", "pytest-cov", + "requests", ] urls.Documentation = "https://earthkit-meteo.readthedocs.io/" urls.Homepage = "https://github.com/ecmwf/earthkit-meteo/" diff --git a/src/earthkit/meteo/conf/ifs_levels_conf.json b/src/earthkit/meteo/conf/ifs_levels_conf.json new file mode 100644 index 0000000..0ccf274 --- /dev/null +++ b/src/earthkit/meteo/conf/ifs_levels_conf.json @@ -0,0 +1,474 @@ +{ + "137": { + "A": [ + 0.0, + 2.000365, + 3.102241, + 4.666084, + 6.827977, + 9.746966, + 13.605424, + 18.608931, + 24.985718, + 32.98571, + 42.879242, + 54.955463, + 69.520576, + 86.895882, + 107.415741, + 131.425507, + 159.279404, + 191.338562, + 227.968948, + 269.539581, + 316.420746, + 368.982361, + 427.592499, + 492.616028, + 564.413452, + 643.339905, + 729.744141, + 823.967834, + 926.34491, + 1037.201172, + 1156.853638, + 1285.610352, + 1423.770142, + 1571.622925, + 1729.448975, + 1897.519287, + 2076.095947, + 2265.431641, + 2465.770508, + 2677.348145, + 2900.391357, + 3135.119385, + 3381.743652, + 3640.468262, + 3911.490479, + 4194.930664, + 4490.817383, + 4799.149414, + 5119.89502, + 5452.990723, + 5798.344727, + 6156.074219, + 6526.946777, + 6911.870605, + 7311.869141, + 7727.412109, + 8159.354004, + 8608.525391, + 9076.400391, + 9562.682617, + 10065.978516, + 10584.631836, + 11116.662109, + 11660.067383, + 12211.547852, + 12766.873047, + 13324.668945, + 13881.331055, + 14432.139648, + 14975.615234, + 15508.256836, + 16026.115234, + 16527.322266, + 17008.789063, + 17467.613281, + 17901.621094, + 18308.433594, + 18685.71875, + 19031.289063, + 19343.511719, + 19620.042969, + 19859.390625, + 20059.931641, + 20219.664063, + 20337.863281, + 20412.308594, + 20442.078125, + 20425.71875, + 20361.816406, + 20249.511719, + 20087.085938, + 19874.025391, + 19608.572266, + 19290.226563, + 18917.460938, + 18489.707031, + 18006.925781, + 17471.839844, + 16888.6875, + 16262.046875, + 15596.695313, + 14898.453125, + 14173.324219, + 13427.769531, + 12668.257813, + 11901.339844, + 11133.304688, + 10370.175781, + 9617.515625, + 8880.453125, + 8163.375, + 7470.34375, + 6804.421875, + 6168.53125, + 5564.382813, + 4993.796875, + 4457.375, + 3955.960938, + 3489.234375, + 3057.265625, + 2659.140625, + 2294.242188, + 1961.5, + 1659.476563, + 1387.546875, + 1143.25, + 926.507813, + 734.992188, + 568.0625, + 424.414063, + 302.476563, + 202.484375, + 122.101563, + 62.78125, + 22.835938, + 3.757813, + 0.0, + 0.0 + ], + "B": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 3.82e-08, + 6.7607e-06, + 2.4348e-05, + 5.8922e-05, + 0.0001119143, + 0.0001985774, + 0.0003403797, + 0.0005615553, + 0.0008896979, + 0.0013528055, + 0.001991838, + 0.0028571242, + 0.0039709536, + 0.0053778146, + 0.0071333768, + 0.00926146, + 0.0118060224, + 0.0148156285, + 0.0183184519, + 0.022354845, + 0.0269635208, + 0.032176096, + 0.0380263999, + 0.0445479602, + 0.0517730154, + 0.0597284138, + 0.068448253, + 0.0779583082, + 0.0882857367, + 0.0994616672, + 0.1115046516, + 0.124448128, + 0.1383128911, + 0.1531250328, + 0.168910414, + 0.1856894493, + 0.2034912109, + 0.222332865, + 0.2422440052, + 0.2632418871, + 0.2853540182, + 0.3085984588, + 0.3329390883, + 0.3582541943, + 0.3843633235, + 0.4111247659, + 0.4383912086, + 0.4660032988, + 0.4938003123, + 0.5216192007, + 0.5493011475, + 0.5766921639, + 0.6036480665, + 0.6300358176, + 0.6557359695, + 0.6806430221, + 0.7046689987, + 0.7277387381, + 0.7497965693, + 0.7707975507, + 0.7907167673, + 0.8095360398, + 0.8272560835, + 0.8438811302, + 0.8594318032, + 0.8739292622, + 0.8874075413, + 0.899900496, + 0.9114481807, + 0.9220956564, + 0.9318807721, + 0.9408595562, + 0.9490644336, + 0.9565495253, + 0.9633517265, + 0.9695134163, + 0.9750784039, + 0.9800716043, + 0.984541893, + 0.9884995222, + 0.9919840097, + 0.9950025082, + 0.9976301193, + 1.0 + ] + }, + "91": { + "A": [ + 0.0, + 2.00004, + 3.980832, + 7.387186, + 12.908319, + 21.413612, + 33.952858, + 51.746601, + 76.167656, + 108.715561, + 150.986023, + 204.637451, + 271.356506, + 352.824493, + 450.685791, + 566.519226, + 701.813354, + 857.945801, + 1036.166504, + 1237.585449, + 1463.16394, + 1713.709595, + 1989.87439, + 2292.155518, + 2620.898438, + 2976.302246, + 3358.425781, + 3767.196045, + 4202.416504, + 4663.776367, + 5150.859863, + 5663.15625, + 6199.839355, + 6759.727051, + 7341.469727, + 7942.92627, + 8564.624023, + 9208.305664, + 9873.560547, + 10558.881836, + 11262.484375, + 11982.662109, + 12713.897461, + 13453.225586, + 14192.009766, + 14922.685547, + 15638.053711, + 16329.560547, + 16990.623047, + 17613.28125, + 18191.029297, + 18716.96875, + 19184.544922, + 19587.513672, + 19919.796875, + 20175.394531, + 20348.916016, + 20434.158203, + 20426.21875, + 20319.011719, + 20107.03125, + 19785.357422, + 19348.775391, + 18798.822266, + 18141.296875, + 17385.595703, + 16544.585938, + 15633.566406, + 14665.645508, + 13653.219727, + 12608.383789, + 11543.166992, + 10471.310547, + 9405.222656, + 8356.25293, + 7335.164551, + 6353.920898, + 5422.802734, + 4550.21582, + 3743.464355, + 3010.146973, + 2356.202637, + 1784.854614, + 1297.656128, + 895.193542, + 576.314148, + 336.772369, + 162.043427, + 54.208336, + 6.575628, + 0.00316, + 0.0 + ], + "B": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 2.724e-07, + 1.39116e-05, + 5.46672e-05, + 0.0001313641, + 0.0002788848, + 0.0005483841, + 0.0010001344, + 0.0017010754, + 0.0027647193, + 0.0042670486, + 0.0063221669, + 0.0090349903, + 0.0125082629, + 0.016859578, + 0.0221886449, + 0.0286103487, + 0.0362269096, + 0.0451461338, + 0.0554742292, + 0.0673161894, + 0.0807773024, + 0.0959640592, + 0.1129790097, + 0.1319348216, + 0.1529335231, + 0.1760910898, + 0.20152013, + 0.2293148786, + 0.2595544755, + 0.2919934094, + 0.3263293803, + 0.3622025847, + 0.3992047608, + 0.4369063377, + 0.4750164151, + 0.513279736, + 0.551458478, + 0.589317143, + 0.6265588999, + 0.662933588, + 0.6982235909, + 0.7322238088, + 0.7646794915, + 0.7953847647, + 0.824185431, + 0.8509504199, + 0.8755183816, + 0.8977672458, + 0.917650938, + 0.9351570606, + 0.9502738118, + 0.9630070925, + 0.9734660387, + 0.9822381139, + 0.9891529679, + 0.9942041636, + 0.9976301193, + 1.0 + ] + } +} diff --git a/src/earthkit/meteo/utils/download.py b/src/earthkit/meteo/utils/download.py new file mode 100644 index 0000000..dece3c6 --- /dev/null +++ b/src/earthkit/meteo/utils/download.py @@ -0,0 +1,19 @@ +# (C) Copyright 2021 ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. +# + + +def simple_download(url, target): + import requests + + r = requests.get(url, allow_redirects=True) + r.raise_for_status() + open(target, "wb").write(r.content) + + # import urllib.request + # urllib.request.urlretrieve(url, target) diff --git a/src/earthkit/meteo/utils/paths.py b/src/earthkit/meteo/utils/paths.py new file mode 100644 index 0000000..2f9ecf7 --- /dev/null +++ b/src/earthkit/meteo/utils/paths.py @@ -0,0 +1,20 @@ +# (C) Copyright 2021 ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. +# + +import logging +import os + +LOG = logging.getLogger(__name__) + + +_ROOT_DIR = os.path.dirname(os.path.dirname(__file__)) + + +def earthkit_conf_file(*args): + return os.path.join(_ROOT_DIR, "conf", *args) diff --git a/src/earthkit/meteo/utils/sample.py b/src/earthkit/meteo/utils/sample.py new file mode 100644 index 0000000..a1a115f --- /dev/null +++ b/src/earthkit/meteo/utils/sample.py @@ -0,0 +1,132 @@ +# (C) Copyright 2021 ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. +# + +import os + +_CONF = {"vertical_hybrid_data": "json"} + + +class SampleInputData: + """Helper class to load sample input data.""" + + def __init__(self, d, xp=None, device=None): + self._d = d + if xp is None: + from earthkit.utils.array import array_namespace + + xp = array_namespace("numpy") + + for k, v in d.items(): + if xp is not None: + v = xp.asarray(v, device=device) + setattr(self, k, v) + + @classmethod + def from_json(cls, file_name): + import json + + with open(file_name, "r") as f: + data = json.load(f) + return cls(data) + + def to_dict(self): + return self._d + + +class TmpFile: + """The TmpFile objects are designed to be used for temporary files. + It ensures that the file is unlinked when the object is + out-of-scope (with __del__). + + Parameters + ---------- + path : str + Actual path of the file. + """ + + def __init__(self, path: str): + self.path = path + + def __del__(self): + self.cleanup() + + def __enter__(self): + return self.path + + def __exit__(self, *args, **kwargs): + self.cleanup() + + def cleanup(self): + if self.path is not None and os is not None: + os.unlink(self.path) + self.path = None + + +def temp_file(extension=".tmp") -> TmpFile: + """Create a temporary file with the given extension . + + Parameters + ---------- + extension : str, optional + By default ".tmp" + + Returns + ------- + TmpFile + """ + import tempfile + + fd, path = tempfile.mkstemp(suffix=extension) + os.close(fd) + return TmpFile(path) + + +# TODO: add thread safety if needed +class Samples: + _CACHE = {} + + def __contains__(self, name): + return name in self._CACHE + + def get(self, name): + if name in self._CACHE: + return self._CACHE[name] + + import os + + from earthkit.meteo.utils.download import simple_download + from earthkit.meteo.utils.testing import earthkit_remote_path + + if name in _CONF: + file_name = f"{name}.{_CONF[name]}" + elif not name.endswith(".json"): + file_name = f"{name}.json" + else: + file_name = name + + url = earthkit_remote_path(os.path.join("samples", file_name)) + + d = None + with temp_file() as target: + simple_download(url=url, target=target) + + d = SampleInputData.from_json(target) + self._CACHE[name] = d + + return d + + +SAMPLES = Samples() + + +def get_sample(name): + try: + return SAMPLES.get(name) + except Exception as e: + raise Exception(f"Sample data '{name}' not found or could not be loaded.") from e diff --git a/src/earthkit/meteo/utils/testing.py b/src/earthkit/meteo/utils/testing.py index 0a5d925..12eeef5 100644 --- a/src/earthkit/meteo/utils/testing.py +++ b/src/earthkit/meteo/utils/testing.py @@ -10,16 +10,129 @@ # A collection of functions to support pytest testing import os +from importlib import import_module -from earthkit.utils.testing import get_array_backend +from earthkit.meteo.utils.download import simple_download -ARRAY_BACKENDS = get_array_backend(["numpy", "torch", "cupy"], raise_on_missing=False) -NUMPY_BACKEND = get_array_backend("numpy") +_REMOTE_ROOT_URL = "https://sites.ecmwf.int/repository/earthkit-meteo/" +_REMOTE_TEST_DATA_URL = "https://sites.ecmwf.int/repository/earthkit-meteo/test-data/" +_REMOTE_EXAMPLES_URL = "https://sites.ecmwf.int/repository/earthkit-meteo/examples/" -ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) -if not os.path.exists(os.path.join(ROOT_DIR, "tests")): - ROOT_DIR = "./" +_ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))) +if not os.path.exists(os.path.join(_ROOT_DIR, "tests")): + _ROOT_DIR = "./" -def test_data_path(filename: str) -> str: - return os.path.join(ROOT_DIR, filename) +def earthkit_path(*args) -> str: + return os.path.join(_ROOT_DIR, *args) + + +def earthkit_test_data_path(name): + return earthkit_path("tests", "data", name) + + +def earthkit_remote_path(*args): + return os.path.join(_REMOTE_ROOT_URL, *args) + + +def earthkit_remote_test_data_path(*args): + return os.path.join(_REMOTE_TEST_DATA_URL, *args) + + +def earthkit_remote_examples_path(*args): + return os.path.join(_REMOTE_EXAMPLES_URL, *args) + + +def get_test_data(filename, subfolder): + if not isinstance(filename, list): + filename = [filename] + + res = [] + for fn in filename: + d_path = earthkit_test_data_path(subfolder) + os.makedirs(d_path, exist_ok=True) + f_path = os.path.join(d_path, fn) + if not os.path.exists(f_path): + simple_download(url=f"{_REMOTE_ROOT_URL}/{subfolder}/{fn}", target=f_path) + res.append(f_path) + + if len(res) == 1: + return res[0] + else: + return res + + +def read_data_file(path): + import numpy as np + + d = np.genfromtxt( + path, + delimiter=",", + names=True, + ) + return d + + +def read_test_data_file(path): + return read_data_file(earthkit_test_data_path(path)) + + +def save_test_data_reference(file_name, data): + """Helper function to save test reference data into csv""" + import numpy as np + + np.savetxt( + earthkit_test_data_path(file_name), + np.column_stack(tuple(data.values())), + delimiter=",", + header=",".join(list(data.keys())), + ) + + +def modules_installed(*modules) -> bool: + for module in modules: + try: + import_module(module) + except (ImportError, RuntimeError, SyntaxError): + return False + return True + + +class Tolerance: + def __init__(self, conf): + """Helper class to manage tolerances for different data bit depths.""" + self.conf = conf + + def bits(self, dtype=None): + bits = (64, 32, 16) + if dtype is not None: + import numpy as np + from earthkit.utils.array.convert import convert_dtype + from earthkit.utils.array.namespace import _NUMPY_NAMESPACE + + dtype = convert_dtype(dtype, _NUMPY_NAMESPACE) + if dtype in [np.float64]: + bits = (64,) + elif dtype in [np.float32]: + bits = (32, 64) + elif dtype in [np.float16]: + bits = (16, 32, 64) + return bits + + def get(self, key=None, dtype=None): + bits = self.bits(dtype) + if key is not None: + conf = self.conf[key] + else: + conf = self.conf + + for b in bits: + if b in conf: + atol = conf[b][0] + rtol = conf[b][1] + return atol, rtol + + raise ValueError(f"No tolerances found for dtype={dtype}") + + +NO_XARRAY = not modules_installed("xarray") diff --git a/src/earthkit/meteo/vertical/array/__init__.py b/src/earthkit/meteo/vertical/array/__init__.py index 9884ca5..f093932 100644 --- a/src/earthkit/meteo/vertical/array/__init__.py +++ b/src/earthkit/meteo/vertical/array/__init__.py @@ -11,4 +11,5 @@ Vertical computation functions operating on numpy arrays. """ +from .hybrid import * # noqa from .vertical import * # noqa diff --git a/src/earthkit/meteo/vertical/array/hybrid.py b/src/earthkit/meteo/vertical/array/hybrid.py new file mode 100644 index 0000000..c2fc165 --- /dev/null +++ b/src/earthkit/meteo/vertical/array/hybrid.py @@ -0,0 +1,102 @@ +# (C) Copyright 2021 ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. +# + +from typing import Any +from typing import Tuple + +import numpy as np +from numpy.typing import NDArray + + +def read_conf(): + d = dict() + + from earthkit.meteo.utils.paths import earthkit_conf_file + + with open( + earthkit_conf_file("ifs_levels_conf.json"), + "r", + encoding="utf-8", + ) as f: + import json + + data = json.load(f) + d["ifs"] = dict() + for n_levels, coeffs in data.items(): + d["ifs"][n_levels] = {"A": np.array(coeffs["A"]), "B": np.array(coeffs["B"])} + + return d + + +_CONF = read_conf() + + +def hybrid_level_parameters(n_levels: int, model: str = "ifs") -> Tuple[NDArray[Any], NDArray[Any]]: + r"""Get the A and B parameters of hybrid levels for a given configuration. + + Parameters + ----------- + n_levels: int + Number of (full) hybrid levels. Currently, only ``n_levels`` 91 and 137 are supported. + model : str + Model name. Default is "ifs". Currently, only ``model="ifs"`` are supported. + + Returns + ------- + NDArray, NDArray + A tuple containing the A and B parameters on the hybrid half-levels See details below. Both are + 1D numpy arrays of length ``n_levels + 1``. + + + Notes + ----- + - The A and B parameters are not unique; in theory there can be multiple definitions for a + given number of levels and model. :func:`hybrid_level_parameters` is merely a convenience + method returning the most typical set of coefficients used. + + - The hybrid model levels divide the atmosphere into :math:`NLEV` layers. These layers are defined + by the pressures at the interfaces between them for :math:`0 \leq k \leq NLEV`, which are + the half-levels :math:`p_{k+1/2}` (indices increase from the top of the atmosphere towards + the surface). The half levels are defined by the A and B parameters in such a way that at + the top of the atmosphere the first half level pressure :math:`p_{+1/2}` is a constant, while + at the surface :math:`p_{NLEV+1/2}` is the surface pressure. + + The full-level pressure :math:`p_{k}` associated with each model + level is defined as the middle of the layer for :math:`1 \leq k \leq NLEV`. + + The level definitions can be written as: + + .. math:: + + p_{k+1/2} = A_{k+1/2} + p_{s}\; B_{k+1/2} + + p_{k} = \frac{1}{2}\; (p_{k-1/2} + p_{k+1/2}) + + where + + - :math:`p_{s}` is the surface pressure + - :math:`p_{k+1/2}` is the pressure at the half-levels + - :math:`p_{k}` is the pressure at the full-levels + - :math:`A_{k+1/2}` and :math:`B_{k+1/2}` are the A- and B-coefficients defining + the model levels. + + For more details see [IFS-CY47R3-Dynamics]_ Chapter 2, Section 2.2.1. + """ + model = model.lower() + n_levels = str(n_levels) + if model in _CONF: + if n_levels in _CONF[model]: + c = _CONF[model][n_levels] + return c["A"], c["B"] + else: + raise ValueError( + f"Hybrid level parameters not available for {n_levels} levels in model '{model}'." + ) + else: + raise ValueError(f"Model '{model}' not recognized for hybrid level parameters.") diff --git a/src/earthkit/meteo/vertical/array/monotonic.py b/src/earthkit/meteo/vertical/array/monotonic.py new file mode 100644 index 0000000..463549c --- /dev/null +++ b/src/earthkit/meteo/vertical/array/monotonic.py @@ -0,0 +1,370 @@ +# (C) Copyright 2021 ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. + +from typing import Union + +from earthkit.utils.array import array_namespace +from numpy.typing import ArrayLike + + +class AuxBottomLayer: + def __init__(self, aux_data, aux_coord, data, coord, xp): + aux_data = xp.atleast_1d(aux_data) + aux_coord = xp.atleast_1d(aux_coord) + + if aux_coord.shape != coord.shape: + aux_coord = xp.broadcast_to(aux_coord, coord.shape) + if aux_coord.shape != data.shape: + aux_coord = xp.broadcast_to(aux_coord, data.shape) + if aux_data.shape != data.shape: + aux_data = xp.broadcast_to(aux_data, data.shape) + + mask = aux_coord > coord + self.coord = xp.where(mask, aux_coord, coord) + self.data = xp.where(mask, aux_data, data) + + +class AuxTopLayer: + def __init__(self, aux_data, aux_coord, data, coord, xp): + aux_data = xp.atleast_1d(aux_data) + aux_coord = xp.atleast_1d(aux_coord) + + if aux_coord.shape != coord.shape: + aux_coord = xp.broadcast_to(aux_coord, coord.shape) + if aux_coord.shape != data.shape: + aux_coord = xp.broadcast_to(aux_coord, data.shape) + if aux_data.shape != data.shape: + aux_data = xp.broadcast_to(aux_data, data.shape) + + mask = aux_coord < coord + self.coord = xp.where(mask, aux_coord, coord) + self.data = xp.where(mask, aux_data, data) + + +class MonotonicInterpolator: + def __call__( + self, + data: ArrayLike, + coord: Union[ArrayLike, list, tuple, float, int], + target_coord: Union[ArrayLike, list, tuple, float, int], + interpolation: str = "linear", + aux_min_level_data=None, + aux_min_level_coord=None, + aux_max_level_data=None, + aux_max_level_coord=None, + vertical_axis=0, + ): + + if interpolation not in ["linear", "log", "nearest"]: + raise ValueError( + f"Unknown interpolation method '{interpolation}'. Supported methods are 'linear', 'log' and 'nearest'." + ) + + xp = array_namespace(data, coord) + target_coord = xp.atleast_1d(target_coord) + coord = xp.atleast_1d(coord) + data = xp.asarray(data) + + # move the vertical axis to the first position for easier processing + if vertical_axis != 0: + if data.ndim > 1: + data = xp.moveaxis(data, vertical_axis, 0) + if coord.ndim > 1: + coord = xp.moveaxis(coord, vertical_axis, 0) + if target_coord.ndim > 1: + target_coord = xp.moveaxis(target_coord, vertical_axis, 0) + + # Ensure levels are in descending order with respect to the values in the first + # dimension of coord + first = [0] * coord.ndim + last = [0] * coord.ndim + first = tuple([0] + first[1:]) + last = tuple([-1] + last[1:]) + + if coord[first] < coord[last]: + coord = xp.flip(coord, axis=0) + data = xp.flip(data, axis=0) + + nlev = data.shape[0] + if nlev < 2: + raise ValueError("At least two levels are required for interpolation.") + + if data.shape[0] != coord.shape[0]: + raise ValueError( + f"The first dimension of data and that of coord must match! {data.shape=} {coord.shape=} {data.shape[0]} != {coord.shape[0]}" + ) + + self.data_is_scalar = data[0].ndim == 0 + self.coord_is_scalar = coord[0].ndim == 0 + self.target_is_scalar = target_coord[0].ndim == 0 + + same_shape = data.shape == coord.shape + if same_shape: + if self.data_is_scalar and not self.target_is_scalar: + raise ValueError("If values and p have the same shape, they cannot both be scalars.") + if ( + not self.data_is_scalar + and not self.target_is_scalar + and data.shape[1:] != target_coord.shape[1:] + ): + raise ValueError( + "When values and target_p have different shapes, target_p must be a scalar or a 1D array." + ) + + if not same_shape and coord.ndim != 1: + raise ValueError( + f"When values and p have different shapes, p must be a scalar or a 1D array. {data.shape=} {coord.shape=} {coord.ndim}" + ) + + # initialize the output array + res = xp.empty((len(target_coord),) + data.shape[1:], dtype=data.dtype) + + if same_shape: + if self.data_is_scalar: + data = xp.broadcast_to(data, (1, nlev)).T + coord = xp.broadcast_to(coord, (1, nlev)).T + else: + assert not self.data_is_scalar + assert not self.coord_is_scalar + else: + assert self.coord_is_scalar + # print(f"scalar_info.target: {scalar_info.target}") + # if scalar_info.target: + # return _to_level_1(data, coord, nlev, target_coord, interpolation, scalar_info, xp, res, aux_bottom, aux_top) + # else: + # coord = xp.broadcast_to(coord, (nlev,) + data.shape[1:]).T + + if not self.target_is_scalar: + coord = xp.broadcast_to(coord, (nlev,) + data.shape[1:]).T + + # assert data.shape == coord.shape, f"{data.shape=} != {coord.shape=}" + + # reevaluate shape after possible broadcasting + same_shape = data.shape == coord.shape + + aux_bottom = None + aux_top = None + if aux_max_level_coord is not None and aux_max_level_data is not None: + aux_bottom = AuxBottomLayer(aux_max_level_data, aux_max_level_coord, data[0], coord[0], xp) + + if aux_min_level_coord is not None and aux_min_level_data is not None: + aux_top = AuxTopLayer(aux_min_level_data, aux_min_level_coord, data[-1], coord[-1], xp) + + self.xp = xp + self.data = data + self.coord = coord + self.target_coord = target_coord + self.interpolation = interpolation + self.aux_bottom = aux_bottom + self.aux_top = aux_top + self.nlev = nlev + + if same_shape or not self.target_is_scalar: + self.compute(res) + else: + return self.simple_compute(res) + + # move back the vertical axis to the original position + if vertical_axis != 0 and res.ndim > 1: + res = xp.moveaxis(res, 0, vertical_axis) + + return res + + # values and p have the same shape + def compute(self, res): + xp = self.xp + + # The coordinate levels must be ordered in descending order with respect to the + # first dimension. So index 0 has the highest coordinate values, index -1 the lowest, + # as if it were pressure levels in the atmosphere. The algorithm below agnostic to the + # actual meaning of the coordinate in the real atmosphere. The terms "top" and "bottom" + # are used with respect to this coordinate ordering in mind and not related to actual + # vertical position in the atmosphere. Of course, if the coordinate is pressure these + # two definitions coincide. + for target_idx, tc in enumerate(self.target_coord): + + # find the level below the target + idx_bottom = (self.coord > tc).sum(0) + idx_bottom = xp.atleast_1d(idx_bottom) + + # print(f"tc: {tc} i_top: {i_top}") + # initialise the output array + r = xp.empty(idx_bottom.shape) + + # mask when the target is below the lowest level + mask_bottom = idx_bottom == 0 + + # mask when the target is above the highest level + mask_top = idx_bottom == self.nlev + + # mask when the target is in the coordinate range + mask_mid = ~(mask_bottom | mask_top) + + if xp.any(mask_bottom): + self._compute_bottom(mask_bottom, r, tc) + + if xp.any(mask_top): + self._compute_top(mask_top, r, tc) + + if xp.any(mask_mid): + self._compute_mid(idx_bottom, mask_mid, r, tc) + + if self.data_is_scalar: + r = r[0] + + res[target_idx] = r + + return res + + def _compute_bottom(self, mask, r, tc): + xp = self.xp + if xp.any(mask): + if self.aux_bottom is None: + if self.interpolation == "nearest": + r[mask] = self.data[0][mask] + else: + r[mask] = xp.nan + m = mask & (xp.isclose(self.coord[0], tc)) + r[m] = self.data[0][m] + else: + r[mask] = xp.nan + aux_mask = mask & (self.aux_bottom.coord > self.coord[0]) & (self.aux_bottom.coord >= tc) + if xp.any(aux_mask): + d_top = self.data[0][aux_mask] + d_bottom = self.aux_bottom.data[aux_mask] + c_top = self.coord[0][aux_mask] + c_bottom = self.aux_bottom.coord[aux_mask] + + if not self.target_is_scalar: + tc = tc[aux_mask] + + factor = self._factor(c_top, c_bottom, tc, self.interpolation, xp) + r[aux_mask] = (1.0 - factor) * d_bottom + factor * d_top + + def _compute_top(self, mask, r, tc): + xp = self.xp + if xp.any(mask): + if self.aux_top is None: + if self.interpolation == "nearest": + r[mask] = self.data[-1][mask] + else: + r[mask] = xp.nan + m = mask & (xp.isclose(self.coord[-1], tc)) + r[m] = self.data[-1][m] + else: + # print("Using aux top layer") + r[mask] = xp.nan + # print("aux top coord:", self.aux_top.coord, "self.coord[-1]:", self.coord[-1]) + aux_mask = mask & (self.aux_top.coord < self.coord[-1]) & (self.aux_top.coord <= tc) + if xp.any(aux_mask): + d_top = self.aux_top.data[aux_mask] + d_bottom = self.data[-1][aux_mask] + c_top = self.aux_top.coord[aux_mask] + c_bottom = self.coord[-1][aux_mask] + # print(f"tc: {tc} c_top: {c_top} c_bottom: {c_bottom} d_top: {d_top} d_bottom: {d_bottom}") + + if not self.target_is_scalar: + tc = tc[aux_mask] + + factor = self._factor(c_top, c_bottom, tc, self.interpolation, xp) + r[aux_mask] = (1.0 - factor) * d_bottom + factor * d_top + + def _compute_mid(self, idx_bottom, mask, r, tc): + xp = self.xp + i_lev = idx_bottom + indices = xp.indices(i_lev.shape) + masked_indices = tuple(dim[mask] for dim in indices) + top = (idx_bottom[mask],) + masked_indices + bottom = (idx_bottom[mask] - 1,) + masked_indices + c_top = self.coord[top] + c_bottom = self.coord[bottom] + d_top = self.data[top] + d_bottom = self.data[bottom] + + # print(f"tc: {tc} c_top: {c_top} c_bottom: {c_bottom} f_top: {f_top} f_bottom: {f_bottom}") + + if not self.target_is_scalar: + tc = tc[mask] + + factor = self._factor(c_top, c_bottom, tc, self.interpolation, xp) + r[mask] = (1.0 - factor) * d_bottom + factor * d_top + + # data and coord have a different shape, data is 1D and target is 1D + # TODO: review if it is needed or can be covered by compute + def simple_compute(self, res): + xp = self.xp + + # The coordinate levels must be ordered in descending order with respect to the + # first dimension. So index 0 has the highest coordinate values, index -1 the lowest, + # as if it were pressure levels in the atmosphere. The algorithm below agnostic to the + # actual meaning of the coordinate in the real atmosphere. The terms "top" and "bottom" + # are used with respect to this coordinate ordering in mind and not related to actual + # vertical position in the atmosphere. Of course, if the coordinate is pressure these + # two definitions coincide. + + # initialize the output array + # res = xp.empty((len(target_coord),) + data.shape[1:], dtype=data.dtype) + + # print("src_coord", src_coord, compare) + + # p on a level is a number + for target_idx, tc in enumerate(self.target_coord): + # initialise the output array + r = xp.empty(self.data.shape[1:]) + r = xp.atleast_1d(r) + + # find the level below the target + idx_bottom = (self.coord > tc).sum(0) + + # print(f"tc: {tc} i_top: {i_top}", src_coord[0]) + if idx_bottom == 0: + if self.interpolation == "nearest": + r = self.data[0] + else: + if xp.isclose(self.coord[0], tc): + r = self.data[0] + # print(f"tc: {tc} r: {r}") + else: + r.fill(xp.nan) + + elif idx_bottom == self.nlev: + if self.interpolation == "nearest": + r = self.data[-1] + else: + if xp.isclose(self.coord[-1], tc): + r = self.data[-1] + else: + r.fill(xp.nan) + else: + top = idx_bottom + bottom = idx_bottom - 1 + + c_top = self.coord[top] + c_bottom = self.coord[bottom] + + d_top = self.data[top] + d_bottom = self.data[bottom] + + factor = self._factor(c_top, c_bottom, tc, self.interpolation, xp) + r = (1.0 - factor) * d_bottom + factor * d_top + + res[target_idx] = r + + return res + + @staticmethod + def _factor(c_top, c_bottom, tc, interpolation, xp): + if interpolation == "linear": + factor = (tc - c_bottom) / (c_top - c_bottom) + elif interpolation == "log": + factor = (xp.log(tc) - xp.log(c_bottom)) / (xp.log(c_top) - xp.log(c_bottom)) + elif interpolation == "nearest": + dist_top = xp.abs(c_top - tc) + dist_bottom = xp.abs(c_bottom - tc) + factor = xp.where(dist_top < dist_bottom, 1.0, 0.0) + return factor diff --git a/src/earthkit/meteo/vertical/array/vertical.py b/src/earthkit/meteo/vertical/array/vertical.py index 9233bb6..b270df5 100644 --- a/src/earthkit/meteo/vertical/array/vertical.py +++ b/src/earthkit/meteo/vertical/array/vertical.py @@ -6,10 +6,12 @@ # granted to it by virtue of its status as an intergovernmental organisation # nor does it submit to any jurisdiction. + from typing import Any from typing import Tuple from typing import Union +import deprecation import numpy as np from earthkit.utils.array import array_namespace from numpy.typing import ArrayLike @@ -18,19 +20,23 @@ from earthkit.meteo import constants +@deprecation.deprecated(deprecated_in="0.7", details="Use pressure_on_hybrid_levels instead.") def pressure_at_model_levels( A: NDArray[Any], B: NDArray[Any], sp: Union[float, NDArray[Any]], alpha_top: str = "ifs" ) -> Tuple[NDArray[Any], NDArray[Any], NDArray[Any], NDArray[Any]]: r"""Compute pressure at model full- and half-levels. + *Deprecated in version 0.7.0* + See :ref:`deprecated-hybrid-pressure-at-model-levels` for details. + Parameters ---------- A : ndarray A-coefficients defining the model levels. See [IFS-CY47R3-Dynamics]_ - (page 6) for details. + Chapter 2, Section 2.2.1. for details. B : ndarray B-coefficients defining the model levels. See [IFS-CY47R3-Dynamics]_ - (page 6) for details. + Chapter 2, Section 2.2.1. for details. sp : number or ndarray Surface pressure (Pa) alpha_top : str, optional @@ -48,7 +54,7 @@ def pressure_at_model_levels( ndarray Delta at full-levels ndarray - Alpha at full levels + Alpha at full-levels Notes @@ -59,7 +65,7 @@ def pressure_at_model_levels( must be present. E.g. if the vertical coordinate system has 137 model levels using only a subset of levels between e.g. 137-96 is allowed. - For details on the returned parameters see [IFS-CY47R3-Dynamics]_ (page 7-8). + For details on the returned parameters see [IFS-CY47R3-Dynamics]_ Chapter 2, Section 2.2.1. The pressure on the model-levels is calculated as: @@ -91,6 +97,9 @@ def pressure_at_model_levels( alpha_top = np.log(2) if alpha_top == "ifs" else 1.0 + A = np.asarray(A) + B = np.asarray(B) + # make the calculation agnostic to the number of dimensions ndim = sp.ndim new_shape_half = (A.shape[0],) + (1,) * ndim @@ -105,10 +114,10 @@ def pressure_at_model_levels( delta = np.zeros(new_shape_full) delta[1:, ...] = np.log(p_half_level[2:, ...] / p_half_level[1:-1, ...]) - # pressure at highest half level<= 0.1 + # pressure at highest half-level <= 0.1 if np.any(p_half_level[0, ...] <= PRESSURE_TOA): delta[0, ...] = np.log(p_half_level[1, ...] / PRESSURE_TOA) - # pressure at highest half level > 0.1 + # pressure at highest half-level > 0.1 else: delta[0, ...] = np.log(p_half_level[1, ...] / p_half_level[0, ...]) @@ -119,18 +128,18 @@ def pressure_at_model_levels( 1.0 - p_half_level[1:-1, ...] / (p_half_level[2:, ...] - p_half_level[1:-1, ...]) * delta[1:, ...] ) - # pressure at highest half level <= 0.1 + # pressure at highest half-level <= 0.1 if np.any(p_half_level[0, ...] <= PRESSURE_TOA): alpha[0, ...] = alpha_top - # pressure at highest half level > 0.1 + # pressure at highest half-level > 0.1 else: alpha[0, ...] = ( 1.0 - p_half_level[0, ...] / (p_half_level[1, ...] - p_half_level[0, ...]) * delta[0, ...] ) - # calculate pressure on model full levels + # calculate pressure on model full-levels # TODO: is there a faster way to calculate the averages? - # TODO: introduce option to calculate full levels in more complicated way + # TODO: introduce option to calculate full-levels in more complicated way p_full_level = np.apply_along_axis( lambda m: np.convolve(m, np.ones(2) / 2, mode="valid"), axis=0, arr=p_half_level ) @@ -138,10 +147,16 @@ def pressure_at_model_levels( return p_full_level, p_half_level, delta, alpha +@deprecation.deprecated( + deprecated_in="0.7", details="Use relative_geopotential_thickness_on_hybrid_levels instead." +) def relative_geopotential_thickness( alpha: ArrayLike, delta: ArrayLike, t: ArrayLike, q: ArrayLike ) -> ArrayLike: - """Calculate the geopotential thickness with respect to the surface on model full-levels. + """Calculate the geopotential thickness with respect to the surface on hybrid (IFS model) full-levels. + + *Deprecated in version 0.7.0* + See :ref:`deprecated-hybrid-relative-geopotential-thickness` for details. Parameters ---------- @@ -150,28 +165,27 @@ def relative_geopotential_thickness( delta : array-like Delta term of pressure calculations t : array-like - Temperature on model full-levels (K). First dimension must - correspond to the model full-levels. - q : array-like - Specific humidity on model full-levels (kg/kg). First dimension must - correspond to the model full-levels. + Temperature on hybrid (IFS model) full-levels (K). First dimension must + correspond to the full-levels. + q : Specific humidity on hybrid (IFS model) full-levels (kg/kg). First dimension must + correspond to the full-levels. Returns ------- array-like - geopotential thickness of model full-levels with respect to the surface + Geopotential thickness (m2/s2) of hybrid (IFS model) full-levels with respect to the surface Notes ----- - ``t`` and ``q`` must contain the same model levels in ascending order with respect to - the model level number. The model level range must be contiguous and must include the + ``t`` and ``q`` must contain the same levels in ascending order with respect to + the level number. The model level range must be contiguous and must include the bottom-most level, but not all the levels must be present. E.g. if the vertical coordinate system has 137 model levels using only a subset of levels between e.g. 137-96 is allowed. ``alpha`` and ``delta`` must be defined on the same levels as ``t`` and ``q``. These values can be calculated using :func:`pressure_at_model_levels`. - The computations are described in [IFS-CY47R3-Dynamics]_ (page 7-8). + The computations are described in [IFS-CY47R3-Dynamics]_ Chapter 2, Section 2.2.1. See also -------- @@ -185,11 +199,11 @@ def relative_geopotential_thickness( R = specific_gas_constant(q) d = R * t - # compute geopotential thickness on half levels from 1 to NLEV-1 + # compute geopotential thickness on half-levels from 1 to NLEV-1 dphi_half = xp.cumulative_sum(xp.flip(d[1:, ...] * delta[1:, ...], axis=0), axis=0) dphi_half = xp.flip(dphi_half, axis=0) - # compute geopotential thickness on full levels + # compute geopotential thickness on full-levels dphi = xp.zeros_like(d) dphi[:-1, ...] = dphi_half + d[:-1, ...] * alpha[:-1, ...] dphi[-1, ...] = d[-1, ...] * alpha[-1, ...] @@ -197,6 +211,7 @@ def relative_geopotential_thickness( return dphi +@deprecation.deprecated(deprecated_in="0.7", details="Use interpolate_hybrid_to_height_levels instead.") def pressure_at_height_levels( height: float, t: NDArray[Any], @@ -208,6 +223,9 @@ def pressure_at_height_levels( ) -> Union[float, NDArray[Any]]: """Calculate the pressure at a height above the surface from model full-levels. + *Deprecated in version 0.7.0* + See :ref:`deprecated-hybrid-pressure-at-height-levels` for details. + Parameters ---------- height : number @@ -254,6 +272,9 @@ def pressure_at_height_levels( """ + A = np.asarray(A) + B = np.asarray(B) + # geopotential thickness of the height level tdphi = height * constants.g @@ -262,10 +283,10 @@ def pressure_at_height_levels( # pressure(-related) variables p_full, p_half, delta, alpha = pressure_at_model_levels(A, B, sp, alpha_top=alpha_top) - # relative geopotential thickness of full levels + # relative geopotential thickness of full-levels dphi = relative_geopotential_thickness(alpha, delta, t, q) - # find the model full level right above the height level + # find the model full-level right above the height level i_phi = (tdphi < dphi).sum(0) i_phi = i_phi - 1 @@ -306,7 +327,7 @@ def pressure_at_height_levels( return p_height -def geopotential_height_from_geopotential(z: ArrayLike) -> ArrayLike: +def geopotential_height_from_geopotential(z): r"""Compute geopotential height from geopotential. Parameters @@ -333,7 +354,7 @@ def geopotential_height_from_geopotential(z: ArrayLike) -> ArrayLike: return h -def geopotential_from_geopotential_height(h: ArrayLike) -> ArrayLike: +def geopotential_from_geopotential_height(h): r"""Compute geopotential height from geopotential. Parameters @@ -360,7 +381,7 @@ def geopotential_from_geopotential_height(h: ArrayLike) -> ArrayLike: return z -def geopotential_height_from_geometric_height(h: ArrayLike, R_earth=constants.R_earth) -> ArrayLike: +def geopotential_height_from_geometric_height(h, R_earth=constants.R_earth): r"""Compute the geopotential height from geometric height. Parameters @@ -388,7 +409,7 @@ def geopotential_height_from_geometric_height(h: ArrayLike, R_earth=constants.R_ return zh -def geopotential_from_geometric_height(h: ArrayLike, R_earth=constants.R_earth): +def geopotential_from_geometric_height(h, R_earth=constants.R_earth): r"""Compute the geopotential from geometric height. Parameters @@ -420,7 +441,7 @@ def geopotential_from_geometric_height(h: ArrayLike, R_earth=constants.R_earth): return z -def geometric_height_from_geopotential_height(gh: ArrayLike, R_earth=constants.R_earth) -> ArrayLike: +def geometric_height_from_geopotential_height(gh, R_earth=constants.R_earth): r"""Compute the geometric height from geopotential height. Parameters @@ -448,7 +469,7 @@ def geometric_height_from_geopotential_height(gh: ArrayLike, R_earth=constants.R return h -def geometric_height_from_geopotential(z: ArrayLike, R_earth=constants.R_earth) -> ArrayLike: +def geometric_height_from_geopotential(z, R_earth=constants.R_earth): r"""Compute the geometric height from geopotential. Parameters @@ -479,3 +500,1220 @@ def geometric_height_from_geopotential(z: ArrayLike, R_earth=constants.R_earth) z = z / constants.g h = R_earth * z / (R_earth - z) return h + + +def pressure_on_hybrid_levels( + A: ArrayLike, + B: ArrayLike, + sp: ArrayLike, + levels=None, + alpha_top="ifs", + output="full", + vertical_axis=0, +) -> ArrayLike: + r"""Compute pressure and related parameters on hybrid (IFS model) levels. + + *New in version 0.7.0*: This function replaces the deprecated :func:`pressure_at_model_levels`. + + Parameters + ---------- + A : array-like + A-coefficients defining the hybrid levels. Must contain all the half-levels + in ascending order with respect to the model level number (from the top of the + atmosphere toward the surface). If the total number of (full) model levels + is :math:`NLEV`, ``A`` must contain :math:`NLEV+1` values, one for each + half-level. See [IFS-CY47R3-Dynamics]_ Chapter 2, Section 2.2.1. for + details. + B : array-like + B-coefficients defining the hybrid levels. Must contain all the half-levels + in ascending order with respect to the model level number. Must have the same + size and ordering as ``A``. + See [IFS-CY47R3-Dynamics]_ Chapter 2, Section 2.2.1. for details. + sp : array-like + Surface pressure (Pa) + levels : None, array-like, list, tuple, optional + Specify the hybrid full-levels to return in the given order. Following the + IFS convention model level numbering starts at 1 at the top of the atmosphere + and increasing toward the surface. If None (default), all the levels are + returned in the order defined by the A and B coefficients (i.e. ascending order + with respect to the model level number). + alpha_top : str, optional + Option to initialise the alpha parameters (for details see below) on the top of the + model atmosphere (first half-level in the vertical coordinate system). The possible + values are: + + - "ifs": alpha is set to log(2). See [IFS-CY47R3-Dynamics]_ Chapter 2, Section 2.2.1. for details. + - "arpege": alpha is set to 1.0 + + output : str or list/tuple of str, optional + Specify which outputs to return. Possible values are "full", "half", "delta" and "alpha". + Can be a single string or a list/tuple of strings. Default is "full". The outputs are: + + - "full": pressure (Pa) on full-levels + - "half": pressure (Pa) on half-levels. When ``levels`` is None, returns all the + half-levels. When ``levels`` is not None, only returns the half-levels below + the requested full-levels. + - "delta": logarithm of pressure difference between two adjacent half-levels. Uses + the same indexing as the full-levels. + - "alpha": alpha parameter defined for layers (i.e. for full-levels). Uses the same + indexing as the full-levels. Used for the calculation of the relative geopotential + thickness on full-levels. See + :func:`relative_geopotential_thickness_on_hybrid_levels` for details. + + vertical_axis : int, optional + Axis corresponding to the vertical coordinate (hybrid levels) in the output arrays. + Default is 0 (first axis). + + Returns + ------- + array-like or tuple of array-like + See the ``output`` parameter for details. The axis corresponding to the vertical + coordinate (hybrid levels) in the output arrays is defined by the ``vertical_axis`` + parameter. + + Notes + ----- + The hybrid model levels divide the atmosphere into :math:`NLEV` layers. These layers are defined + by the pressures at the interfaces between them for :math:`0 \leq k \leq NLEV`, which are + the half-levels :math:`p_{k+1/2}` (indices increase from the top of the atmosphere towards + the surface). The half-levels are defined by the ``A`` and ``B`` coefficients in such a way + that at the top of the atmosphere the first half-level pressure :math:`p_{+1/2}` is a constant, + while at the surface :math:`p_{NLEV+1/2}` is the surface pressure. + + The full-level pressure :math:`p_{k}` associated with each model + level is defined as the middle of the layer for :math:`1 \leq k \leq NLEV`. + + The level definitions can be written as: + + .. math:: + + p_{k+1/2} = A_{k+1/2} + p_{s}\; B_{k+1/2} + + p_{k} = \frac{1}{2}\; (p_{k-1/2} + p_{k+1/2}) + + where + + - :math:`p_{s}` is the surface pressure + - :math:`p_{k+1/2}` is the pressure at the half-levels + - :math:`p_{k}` is the pressure at the full-levels + - :math:`A_{k+1/2}` and :math:`B_{k+1/2}` are the A- and B-coefficients defining + the model levels. + + For more details see [IFS-CY47R3-Dynamics]_ Chapter 2, Section 2.2.1. + + Examples + -------- + - :ref:`/examples/hybrid_levels.ipynb` + + + See also + -------- + relative_geopotential_thickness_on_hybrid_levels + + """ + if isinstance(output, str): + output = (output,) + + if not output: + raise ValueError("At least one output type must be specified.") + + for out in output: + if out not in ["full", "half", "alpha", "delta"]: + raise ValueError( + f"Unknown output type '{out}'. Allowed values are 'full', 'half', 'alpha' or 'delta'." + ) + + if alpha_top not in ["ifs", "arpege"]: + raise ValueError(f"Unknown method '{alpha_top}' for pressure calculation. Use 'ifs' or 'arpege'.") + + xp = array_namespace(sp, A, B) + A = xp.asarray(A) + B = xp.asarray(B) + device = xp.device(sp) + + if levels is not None: + # select a contiguous subset of levels + nlev = A.shape[0] - 1 # number of model full-levels + levels = xp.asarray(levels) + levels_max = int(levels.max()) + levels_min = int(levels.min()) + if levels_max > nlev: + raise ValueError(f"Requested level {levels_max} exceeds the maximum number of levels {nlev}.") + if levels_min < 1: + raise ValueError(f"Level numbering starts at 1. Found level={levels_min} < 1.") + + half_idx = xp.asarray(list(range(levels_min - 1, levels_max + 1))) + A = A[half_idx] + B = B[half_idx] + + # compute indices to select the requested full-levels later + # out_half_idx = xp.where(levels[:, None] == half_idx[None, :])[1] + + out_half_idx = xp.nonzero(xp.asarray(levels[:, None] == half_idx[None, :]))[1] + + out_full_idx = out_half_idx - 1 + + # make the calculation agnostic to the number of dimensions + ndim = sp.ndim + new_shape_half = (A.shape[0],) + (1,) * ndim + A_reshaped = xp.reshape(A, new_shape_half) + B_reshaped = xp.reshape(B, new_shape_half) + + # calculate pressure on model half-levels + p_half_level = A_reshaped + B_reshaped * sp[xp.newaxis, ...] + + if "delta" in output or "alpha" in output: + # constants + PRESSURE_TOA = 0.1 # safety when highest pressure level = 0.0 + + alpha_top = np.log(2) if alpha_top == "ifs" else 1.0 + + new_shape_full = (A.shape[0] - 1,) + sp.shape + + # calculate delta + delta = xp.zeros(new_shape_full, device=device) + delta[1:, ...] = xp.log(p_half_level[2:, ...] / p_half_level[1:-1, ...]) + + # pressure at highest half-level<= 0.1 + if xp.any(p_half_level[0, ...] <= PRESSURE_TOA): + delta[0, ...] = xp.log(p_half_level[1, ...] / PRESSURE_TOA) + # pressure at highest half-level > 0.1 + else: + delta[0, ...] = xp.log(p_half_level[1, ...] / p_half_level[0, ...]) + + # calculate alpha + alpha = xp.zeros(new_shape_full, device=device) + + alpha[1:, ...] = ( + 1.0 - p_half_level[1:-1, ...] / (p_half_level[2:, ...] - p_half_level[1:-1, ...]) * delta[1:, ...] + ) + + # pressure at highest half-level <= 0.1 + if xp.any(p_half_level[0, ...] <= PRESSURE_TOA): + alpha[0, ...] = alpha_top + # pressure at highest half-level > 0.1 + else: + alpha[0, ...] = ( + 1.0 - p_half_level[0, ...] / (p_half_level[1, ...] - p_half_level[0, ...]) * delta[0, ...] + ) + + if "full" in output: + # calculate pressure on model full-levels + # TODO: is there a faster way to calculate the averages? + # TODO: introduce option to calculate full-levels in more complicated way + # p_full_level = xp.apply_along_axis( + # lambda m: xp.convolve(m, xp.ones(2) / 2, mode="valid"), axis=0, arr=p_half_level + # ) + + p_full_level = p_half_level[:-1, ...] + 0.5 * xp.diff(p_half_level, axis=0) + + # generate output + res = [] + + for out in output: + if out == "full": + if levels is not None: + p_full_level = p_full_level[out_full_idx, ...] + res.append(p_full_level) + elif out == "half": + if levels is not None: + p_half_level = p_half_level[out_half_idx, ...] + res.append(p_half_level) + elif out == "alpha": + if levels is not None: + alpha = alpha[out_full_idx, ...] + res.append(alpha) + elif out == "delta": + if levels is not None: + delta = delta[out_full_idx, ...] + res.append(delta) + + if vertical_axis != 0 and res[0].ndim > 1: + # move the vertical axis to the required position + res = [xp.moveaxis(r, 0, vertical_axis) for r in res] + + if len(res) == 1: + return res[0] + + return tuple(res) + + +def _compute_relative_geopotential_thickness_on_hybrid_levels( + t: ArrayLike, + q: ArrayLike, + alpha: ArrayLike, + delta: ArrayLike, + xp: Any, +) -> ArrayLike: + """Compute the geopotential thickness between the surface and hybrid (IFS model) full-levels. + + *New in version 0.7.0*: This function replaces the deprecated :func:`relative_geopotential_thickness`. + + Parameters + ---------- + t : array-like + Temperature on hybrid full-levels (K). The axis corresponding to the vertical + coordinate (hybrid levels) is defined by the ``vertical_axis`` parameter. + The levels must be in ascending order with respect the model level number. Not + all the levels must be present, but a contiguous level range including the bottom-most + level must be used. E.g. if the vertical coordinate system has 137 model levels using + only a subset of levels between e.g. 137-96 is allowed. + q : array-like + Specific humidity on hybrid full-levels (kg/kg). Must have the + same shape, level range and order as ``t``. + alpha : array-like + Alpha term of pressure calculations computed using + :func:`pressure_on_hybrid_levels`. Must have the same shape, level range + and order as ``t``. + delta : array-like + Delta term of pressure calculations computed using :func:`pressure_on_hybrid_levels`. + Must have the same shape, level range and order as ``t``. + vertical_axis : int, optional + Axis corresponding to the vertical coordinate (hybrid levels) in the input arrays + and also in the output array. Default is 0 (first axis). + + Returns + ------- + array-like + Geopotential thickness (m2/s2) between the surface and hybrid full-levels. + The axis corresponding to the vertical coordinate (hybrid levels) is defined + by the ``vertical_axis`` parameter. + + Notes + ----- + ``alpha`` and ``delta``can be calculated using :func:`pressure_on_hybrid_levels`. + + The computations are described in [IFS-CY47R3-Dynamics]_ Chapter 2, Section 2.2.1. + + Examples + -------- + - :ref:`/examples/hybrid_levels.ipynb` + + + See also + -------- + pressure_on_hybrid_levels + + """ + from earthkit.meteo.thermo import specific_gas_constant + + R = specific_gas_constant(q) + d = R * t + + # compute geopotential thickness on half levels from 1 to NLEV-1 + dphi_half = xp.cumulative_sum(xp.flip(d[1:, ...] * delta[1:, ...], axis=0), axis=0) + dphi_half = xp.flip(dphi_half, axis=0) + + # compute geopotential thickness on full levels + dphi = xp.zeros_like(d) + dphi[:-1, ...] = dphi_half + d[:-1, ...] * alpha[:-1, ...] + dphi[-1, ...] = d[-1, ...] * alpha[-1, ...] + + return dphi + + +def relative_geopotential_thickness_on_hybrid_levels_from_alpha_delta( + t: ArrayLike, + q: ArrayLike, + alpha: ArrayLike, + delta: ArrayLike, + vertical_axis=0, +) -> ArrayLike: + """Compute the geopotential thickness between the surface and hybrid full-levels (IFS model levels). + + *New in version 0.7.0*: This function replaces the deprecated :func:`relative_geopotential_thickness`. + + Parameters + ---------- + t : array-like + Temperature on hybrid full-levels (K). The axis corresponding to the vertical + coordinate (hybrid levels) is defined by the ``vertical_axis`` parameter. + The levels must be in ascending order with respect the model level number. Not + all the levels must be present, but a contiguous level range including the bottom-most + level must be used. E.g. if the vertical coordinate system has 137 model levels using + only a subset of levels between e.g. 137-96 is allowed. + q : array-like + Specific humidity on hybrid full-levels (kg/kg). Must have the + same shape, level range and order as ``t``. + alpha : array-like + Alpha term of pressure calculations computed using + :func:`pressure_on_hybrid_levels`. Must have the same shape, level range + and order as ``t``. + delta : array-like + Delta term of pressure calculations computed using :func:`pressure_on_hybrid_levels`. + Must have the same shape, level range and order as ``t``. + vertical_axis : int, optional + Axis corresponding to the vertical coordinate (hybrid levels) in the input arrays + and also in the output array. Default is 0 (first axis). + + Returns + ------- + array-like + Geopotential thickness (m2/s2) between the surface and hybrid full-levels. + The axis corresponding to the vertical coordinate (hybrid levels) is defined + by the ``vertical_axis`` parameter. + + Notes + ----- + ``alpha`` and ``delta`` can be calculated using :func:`pressure_on_hybrid_levels`. + The computations are described in [IFS-CY47R3-Dynamics]_ Chapter 2, Section 2.2.1. + + Examples + -------- + - :ref:`/examples/hybrid_levels.ipynb` + + + See also + -------- + pressure_on_hybrid_levels + + """ + + xp = array_namespace(alpha, delta, q, t) + alpha = xp.asarray(alpha) + delta = xp.asarray(delta) + t = xp.asarray(t) + q = xp.asarray(q) + + if vertical_axis != 0: + # move the vertical axis to the first position + alpha = xp.moveaxis(alpha, vertical_axis, 0) + delta = xp.moveaxis(delta, vertical_axis, 0) + t = xp.moveaxis(t, vertical_axis, 0) + q = xp.moveaxis(q, vertical_axis, 0) + + dphi = _compute_relative_geopotential_thickness_on_hybrid_levels(t, q, alpha, delta, xp) + + if vertical_axis != 0: + # move the vertical axis back to its original position + dphi = xp.moveaxis(dphi, 0, vertical_axis) + + return dphi + + +def relative_geopotential_thickness_on_hybrid_levels( + t: ArrayLike, + q: ArrayLike, + A: ArrayLike, + B: ArrayLike, + sp: ArrayLike, + alpha_top="ifs", + vertical_axis=0, +) -> ArrayLike: + """Compute the geopotential thickness between the surface and hybrid full-levels (IFS model levels). + + *New in version 0.7.0* + + Parameters + ---------- + t : array-like + Temperature on hybrid full-levels (K). The axis corresponding to the vertical + coordinate (hybrid levels) is defined by the ``vertical_axis`` parameter. + The levels must be in ascending order with respect the model level number. Not + all the levels must be present, but a contiguous level range including the bottom-most + level must be used. E.g. if the vertical coordinate system has 137 model levels using + only a subset of levels between e.g. 137-96 is allowed. + q : array-like + Specific humidity on hybrid full-levels (kg/kg). Must have the + same shape, level range and order as ``t``. + A : array-like + A-coefficients defining the hybrid levels. Must contain all the half-levels + in ascending order with respect to the model level number. + B : array-like + B-coefficients defining the hybrid levels. Must contain all the half-levels + in ascending order with respect to the model level number. Must have the same + size as ``A``. + sp : array-like + Surface pressure (Pa) + alpha_top : str, optional + Option to initialise the alpha parameters (for details see below) on the top of the + model atmosphere (first half-level in the vertical coordinate system). See + :func:`pressure_on_hybrid_levels` for details. + vertical_axis : int, optional + Axis corresponding to the vertical coordinate (model levels) in the input ``t`` + and ``q`` arrays and also in the output array. Default is 0 (first axis). + + Returns + ------- + array-like + Geopotential thickness (m2/s2) between the surface and hybrid full-levels. The + axis corresponding to the vertical coordinate (hybrid levels) is defined by the + ``vertical_axis`` parameter. + + Notes + ----- + The computations are done in two steps: + + - first the ``alpha`` and ``delta`` parameters are calculated using + :func:`pressure_on_hybrid_levels` + - then the geopotential thickness is calculated with hydrostatic integration using + :func:`relative_geopotential_thickness_on_hybrid_levels_from_alpha_delta` See + [IFS-CY47R3-Dynamics]_ Chapter 2, Section 2.2.1. for details. + + Examples + -------- + - :ref:`/examples/hybrid_levels.ipynb` + + See also + -------- + pressure_on_hybrid_levels + relative_geopotential_thickness_on_hybrid_levels_from_alpha_delta + + """ + xp = array_namespace(t, q, A, B, sp) + A = xp.asarray(A) + B = xp.asarray(B) + sp = xp.asarray(sp) + t = xp.asarray(t) + q = xp.asarray(q) + + levels = _hybrid_subset(t, A, B, vertical_axis) + + alpha, delta = pressure_on_hybrid_levels( + A, B, sp, alpha_top=alpha_top, levels=levels, output=("alpha", "delta") + ) + + # return relative_geopotential_thickness_on_hybrid_levels_from_alpha_delta( + # t, q, alpha, delta, vertical_axis=vertical_axis + # ) + + # move the vertical axis to the first position + if vertical_axis != 0: + # move the vertical axis to the first position + alpha = xp.moveaxis(alpha, vertical_axis, 0) + delta = xp.moveaxis(delta, vertical_axis, 0) + t = xp.moveaxis(t, vertical_axis, 0) + q = xp.moveaxis(q, vertical_axis, 0) + + dphi = _compute_relative_geopotential_thickness_on_hybrid_levels(t, q, alpha, delta, xp) + + # move the vertical axis back to its original position + if vertical_axis != 0: + dphi = xp.moveaxis(dphi, 0, vertical_axis) + + return dphi + + +def geopotential_on_hybrid_levels( + t: ArrayLike, + q: ArrayLike, + zs: ArrayLike, + A: ArrayLike, + B: ArrayLike, + sp: ArrayLike, + alpha_top="ifs", + vertical_axis=0, +): + """Compute the geopotential on hybrid (IFS model) full-levels. + + *New in version 0.7.0* + + Parameters + ---------- + t : array-like + Temperature on hybrid full-levels (K). The axis corresponding to the vertical + coordinate (hybrid levels) is defined by the ``vertical_axis`` parameter. + The levels must be in ascending order with respect the model level number. Not + all the levels must be present, but a contiguous level range including the bottom-most + level must be used. E.g. if the vertical coordinate system has 137 model levels using + only a subset of levels between e.g. 137-96 is allowed. + q : Specific humidity on hybrid full-levels (kg/kg). Must have the + same shape, level range and order as ``t``. + zs : array-like + Surface geopotential (m2/s2). Only used when ``reference_level`` is "sea". + A : array-like + A-coefficients defining the hybrid levels. Must contain all the half-levels + in ascending order with respect to the model level number. + B : array-like + B-coefficients defining the hybrid levels. Must contain all the half-levels + in ascending order with respect to the model level number. Must have the same + size as ``A``. + sp : array-like + Surface pressure (Pa) + alpha_top : str, optional + Option to initialise the alpha parameters (for details see below) on the top of the + model atmosphere (first half-level in the vertical coordinate system). See + :func:`pressure_on_hybrid_levels` for details. + vertical_axis : int, optional + Axis corresponding to the vertical coordinate (model levels) in the input ``t`` + and ``q`` arrays and also in the output array. Default is 0 (first axis). + + + Returns + ------- + array-like + Geopotential (m2/s2) on hybrid full-levels. The axis corresponding to the vertical + coordinate (hybrid levels) is defined by the ``vertical_axis`` parameter. + + Notes + ----- + The computations are described in [IFS-CY47R3-Dynamics]_ Chapter 2, Section 2.2.1. + + + Examples + -------- + - :ref:`/examples/hybrid_levels.ipynb` + + + See also + -------- + pressure_on_hybrid_levels + relative_geopotential_thickness_on_hybrid_levels + + """ + z = relative_geopotential_thickness_on_hybrid_levels( + t, q, A, B, sp, vertical_axis=vertical_axis, alpha_top=alpha_top + ) + xp = array_namespace(z, zs) + zs = xp.asarray(zs) + return z + zs + + +def height_on_hybrid_levels( + t: ArrayLike, + q: ArrayLike, + zs: ArrayLike, + A: ArrayLike, + B: ArrayLike, + sp: ArrayLike, + alpha_top="ifs", + h_type: str = "geometric", + h_reference: str = "ground", + vertical_axis=0, +): + """Compute the height on hybrid (IFS model) full-levels. + + *New in version 0.7.0* + + Parameters + ---------- + t : array-like + Temperature on hybrid full-levels (K). The axis corresponding to the vertical + coordinate (hybrid levels) is defined by the ``vertical_axis`` parameter. + The levels must be in ascending order with respect the model level number. Not + all the levels must be present, but a contiguous level range including the bottom-most + level must be used. E.g. if the vertical coordinate system has 137 model levels using + only a subset of levels between e.g. 137-96 is allowed. + q : array-like + Specific humidity on hybrid full-levels (kg/kg). Must have the + same shape, level range and order as ``t``. + zs : array-like + Surface geopotential (m2/s2). Not used when ``h_type`` is "geopotential" and + ``h_reference`` is "ground". + A : array-like + A-coefficients defining the hybrid levels. Must contain all the half-levels + in ascending order with respect to the model level number. + B : array-like + B-coefficients defining the hybrid levels. Must contain all the half-levels + in ascending order with respect to the model level number. Must have the same + size as ``A``. + sp : array-like + Surface pressure (Pa) + alpha_top : str, optional + Option to initialise the alpha parameters (for details see below) on the top of the + model atmosphere (first half-level in the vertical coordinate system). See + :func:`pressure_on_hybrid_levels` for details. + h_type : str, optional + Type of height to compute. Default is "geometric". Possible values are: + + - "geometric": geometric height (m) with respect to ``h_reference`` + - "geopotential": geopotential height (m) with respect to ``h_reference`` + + See :func:`geometric_height_from_geopotential` and + :func:`geopotential_height_from_geopotential` for details. + + h_reference : str, optional + Reference level for the height calculation. Default is "ground". Possible values are: + + - "ground": height with respect to the ground/surface level + - "sea": height with respect to the sea level + + vertical_axis : int, optional + Axis corresponding to the vertical coordinate (hybrid full-levels) in the input + arrays and also in the output array. Default is 0 (first axis). + + Returns + ------- + array-like + Height (m) of hybrid full-levels with + respect to ``h_reference``. The type of height is defined by ``h_type`` + ("geometric" or "geopotential"). The axis corresponding to the vertical + coordinate (hybrid levels) is defined by the ``vertical_axis`` parameter. + + Notes + ----- + The height is calculated from the geopotential on hybrid levels, which is computed + from the ``t``, ``q``, ``zs`` and the hybrid + level definition (``A``, ``B`` and ``sp``). The + computations are described in [IFS-CY47R3-Dynamics]_ Chapter 2, Section 2.2.1. + + + Examples + -------- + - :ref:`/examples/hybrid_levels.ipynb` + + See also + -------- + hybrid_level_parameters + pressure_on_hybrid_levels + geopotential_on_hybrid_levels + relative_geopotential_thickness_on_hybrid_levels + """ + + if h_reference not in ["sea", "ground"]: + raise ValueError(f"Unknown '{h_reference=}'. Use 'sea' or 'ground'.") + + z_thickness = relative_geopotential_thickness_on_hybrid_levels( + t, q, A, B, sp, alpha_top=alpha_top, vertical_axis=vertical_axis + ) + + xp = array_namespace(z_thickness) + + if h_reference == "sea": + zs = xp.asarray(zs) + z = z_thickness + zs + if h_type == "geometric": + h = geometric_height_from_geopotential(z) + else: + h = geopotential_height_from_geopotential(z) + else: + if h_type == "geometric": + zs = xp.asarray(zs) + h_surf = geometric_height_from_geopotential(zs) + z = z_thickness + zs + h = geometric_height_from_geopotential(z) - h_surf + else: + h = geopotential_height_from_geopotential(z_thickness) + + return h + + +def _hybrid_subset(data, A, B, vertical_axis=0): + """Helper function to determine the subset of hybrid levels corresponding to the data levels.""" + nlev_t = data.shape[vertical_axis] + nlev = A.shape[0] - 1 # number of model full-levels + levels = None + if nlev_t != nlev: + # select a contiguous subset of levels + levels = list(range(nlev - nlev_t + 1, nlev + 1)) + assert nlev_t == len(levels), ( + "Inconsistent number of levels between data and A/B coefficients." + f" data have {nlev_t} levels, A/B have {nlev} levels." + ) + return levels + + +def interpolate_hybrid_to_pressure_levels( + data: ArrayLike, + target_p: ArrayLike, + A: ArrayLike, + B: ArrayLike, + sp: ArrayLike, + alpha_top="ifs", + interpolation: str = "linear", + aux_bottom_data=None, + aux_bottom_p=None, + aux_top_data=None, + aux_top_p=None, + vertical_axis=0, +): + """Interpolate data from hybrid full-levels (IFS model levels) to pressure levels. + + *New in version 0.7.0* + + Parameters + ---------- + data : array-like + Data to be interpolated. The axis corresponding to the vertical + coordinate (hybrid levels) is defined by the ``vertical_axis`` parameter. + Must have at least two levels. Levels must be ordered in ascending order + with respect to the model level number. By convention, model level numbering + starts at 1 at the top of the atmosphere and increases towards the surface. + Not all the levels must be present, but a contiguous level range including the + bottom-most level must be used. E.g. if the vertical coordinate system has 137 model + levels using only a subset of levels between e.g. 137-96 is allowed. + target_p : array-like + Target pressure levels (Pa) to which ``data`` will be interpolated. It can be + either a scalar or a 1D array of pressure levels. Alternatively, it can be a + multidimensional array with a vertical axis defined by ``vertical_axis``. In this + case the other axes/dimensions must match those of ``data``. + A : array-like + A-coefficients defining the hybrid levels. Must contain all the half-levels + in ascending order with respect to the model level number. + See :func:`hybrid_level_parameters` for details. + B : array-like + B-coefficients defining the hybrid levels. Must contain all the half-levels + in ascending order with respect to the model level number. Must have the same + size as ``A``. See :func:`hybrid_level_parameters` for details. + sp : array-like + Surface pressure (Pa). The shape must be compatible with the non-vertical + dimensions of ``data``. + alpha_top : str, optional + Option to initialise the alpha parameters on the top of the + model atmosphere (first half-level in the vertical coordinate system). See + :func:`pressure_on_hybrid_levels` for details. + interpolation : str, optional + Interpolation mode. Default is "linear". Possible values are: + + - "linear": linear interpolation in pressure between the two nearest levels + - "log": linear interpolation in logarithm of pressure between the two nearest levels + - "nearest": nearest level interpolation + + aux_bottom_data : array-like, optional + Auxiliary data for interpolation to targets below the bottom hybrid full-level + and above the level specified by ``aux_bottom_p``. Can be a scalar or must have the + same shape as a single level of ``data``. + aux_bottom_p : array-like, optional + Pressures (Pa) of ``aux_bottom_data``. Can be a scalar or must have the same + shape as a single level of ``data``. + aux_top_data : array-like, optional + Auxiliary data for interpolation to targets above the top hybrid full-level + and below the level specified by ``aux_top_p``. Can be a scalar or must have + the same shape as a single level of ``data``. + aux_top_p : array-like, optional + Pressures (Pa) of ``aux_top_data``. Can be a scalar or must have the same + shape as a single level of ``data``. + vertical_axis : int, optional + Axis corresponding to the vertical coordinate (hybrid full-levels) in the input + arrays and also in the output array. Default is 0 (first axis). + + + Returns + ------- + array-like + Data interpolated to the target levels. The shape depends on the shape of ``target_p``. + The axis corresponding to the vertical coordinate (hybrid levels) is defined by + the ``vertical_axis`` parameter. When interpolation is not possible for a given target + pressure level (e.g., when the target pressure is outside the available pressure range), + the corresponding output values are set to nan. + + Raises + ------ + ValueError + If ``data`` has less than two levels. + ValueError + If the first dimension of ``data`` and that of ``target_p`` do not match. + + + Examples + -------- + - :ref:`/examples/interpolate_hybrid_to_pl.ipynb` + + See also + -------- + interpolate_monotonic + + """ + xp = array_namespace(data, A, B, sp) + data = xp.asarray(data) + A = xp.asarray(A) + B = xp.asarray(B) + sp = xp.asarray(sp) + + levels = _hybrid_subset(data, A, B, vertical_axis) + + p = pressure_on_hybrid_levels(A, B, sp, alpha_top=alpha_top, levels=levels, output="full") + return interpolate_monotonic( + data=data, + coord=p, + target_coord=target_p, + interpolation=interpolation, + aux_min_level_coord=aux_top_p, + aux_min_level_data=aux_top_data, + aux_max_level_coord=aux_bottom_p, + aux_max_level_data=aux_bottom_data, + vertical_axis=vertical_axis, + ) + + +def interpolate_hybrid_to_height_levels( + data: ArrayLike, + target_h: ArrayLike, + t: ArrayLike, + q: ArrayLike, + zs: ArrayLike, + A: ArrayLike, + B: ArrayLike, + sp: ArrayLike, + alpha_top="ifs", + h_type: str = "geometric", + h_reference: str = "ground", + interpolation: str = "linear", + aux_bottom_data=None, + aux_bottom_h=None, + aux_top_data=None, + aux_top_h=None, + vertical_axis=0, +): + """Interpolate data from hybrid full-levels (IFS model levels) to height levels. + + *New in version 0.7.0* + + Parameters + ---------- + data : array-like + Data to be interpolated. The axis corresponding to the vertical + coordinate (hybrid levels) is defined by the ``vertical_axis`` parameter. + Must have at least two levels. Levels must be ordered in ascending order + with respect to the model level number. By convention, model level numbering + starts at 1 at the top of the atmosphere and increases towards the surface. Not + all the levels must be present, but a contiguous level range + including the bottom-most level must be used. E.g. if the vertical coordinate + system has 137 model levels using only a subset of levels between + e.g. 137-96 is allowed. + target_h : array-like + Target height levels (m) to which ``data`` will be interpolated. It can be + either a scalar or a 1D array of height levels. Alternatively, it can be a + multidimensional array with a vertical axis defined by `vertical_axis`. In this case + the other axes/dimensions must match those of ``data``. The type of the height and + the reference level are defined by ``h_type`` and ``h_reference``. + t : array-like + Temperature on hybrid full-levels (K). Must have the + same shape, level range and order as ``data``. + q : array-like + Specific humidity on hybrid full-levels (kg/kg). Must have the + same shape, level range and order as ``t``. + zs : array-like + Surface geopotential (m2/s2). The shape + must be compatible with the non-vertical dimensions of ``t`` and ``q``. + Not used when ``h_type`` is "geopotential" and ``h_reference`` is "ground". + A : array-like + A-coefficients defining the hybrid levels. Must contain all the half-levels + in ascending order with respect to the model level number. + See :func:`hybrid_level_parameters` for details. + B : array-like + B-coefficients defining the hybrid levels. Must contain all the half-levels + in ascending order with respect to the model level number. Must have the same + size as ``A``. See :func:`hybrid_level_parameters` for details. + sp : array-like + Surface pressure (Pa). The shape must be compatible with the non-vertical + dimensions of ``data``. + alpha_top : str, optional + Option to initialise the alpha parameters (for details see below) on the top of the + model atmosphere (first half-level in the vertical coordinate system). See + :func:`pressure_on_hybrid_levels` for details. + h_type : str, optional + Type of height to compute. Default is "geometric". Possible values are: + + - "geometric": geometric height (m) with respect to ``h_reference`` + - "geopotential": geopotential height (m) with respect to ``h_reference`` + + See :func:`geometric_height_from_geopotential` and + :func:`geopotential_height_from_geopotential` for details. + h_reference : str, optional + Reference level for the height calculation. Default is "ground". Possible values are: + + - "ground": height with respect to the ground/surface level + - "sea": height with respect to the sea level + + interpolation : str, optional + Interpolation mode. Default is "linear". Possible values are: + + - "linear": linear interpolation in height between the two nearest levels + - "log": linear interpolation in logarithm of height between the two nearest levels + - "nearest": nearest level interpolation + + aux_bottom_data : array-like, optional + Auxiliary data for interpolation to heights between the bottom hybrid full-level + and ``aux_bottom_h``. Can be a scalar or must have + the same shape as a single level of ``data``. + aux_bottom_h : array-like, optional + Heights (m) of ``aux_bottom_data``. Can be a scalar or must have the same + shape as a single level of ``data``. The type of the height and + the reference level are defined by ``h_type`` and ``h_reference``. + aux_top_data : array-like, optional + Auxiliary data for interpolation to heights above the top hybrid full-level + and below ``aux_top_h``. Can be a scalar or must have + the same shape as a single level of ``data``. + aux_top_h : array-like, optional + Heights (m) of ``aux_top_data``. Can be a scalar or must have the same + shape as a single level of ``data``. The type of the height and + the reference level are defined by ``h_type`` and ``h_reference``. + vertical_axis : int, optional + Axis corresponding to the vertical coordinate (hybrid full-levels) in the input + arrays and also in the output array. Default is 0 (first axis). + + + Returns + ------- + array-like + Data interpolated to the target height levels. The shape depends on the shape + of ``target_h``. The axis corresponding to the vertical coordinate (hybrid levels) + is defined by the ``vertical_axis`` parameter. When interpolation is not possible + for a given target height level (e.g., when the target height is outside the + available height range), the corresponding output values are set to nan. + + Raises + ------ + ValueError + If ``data`` has less than two levels. + ValueError + If the first dimension of ``data`` and that of ``target_h`` do not match. + + Examples + -------- + - :ref:`/examples/interpolate_hybrid_to_hl.ipynb` + + + See also + -------- + interpolate_monotonic + + """ + h = height_on_hybrid_levels( + t, + q, + zs, + A, + B, + sp, + alpha_top=alpha_top, + h_type=h_type, + h_reference=h_reference, + vertical_axis=vertical_axis, + ) + + return interpolate_monotonic( + data=data, + coord=h, + target_coord=target_h, + interpolation=interpolation, + aux_min_level_data=aux_bottom_data, + aux_min_level_coord=aux_bottom_h, + aux_max_level_coord=aux_top_h, + aux_max_level_data=aux_top_data, + vertical_axis=vertical_axis, + ) + + +def interpolate_pressure_to_height_levels( + data: ArrayLike, + target_h: ArrayLike, + z: ArrayLike, + zs: ArrayLike, + h_type: str = "geometric", + h_reference: str = "ground", + interpolation: str = "linear", + aux_bottom_data=None, + aux_bottom_h=None, + aux_top_data=None, + aux_top_h=None, + vertical_axis: int = 0, +): + """Interpolate data from pressure levels to height levels. + + *New in version 0.7.0* + + Parameters + ---------- + data : array-like + Data to be interpolated. The axis corresponding to the vertical + coordinate (pressure levels) is defined by the ``vertical_axis`` parameter. + Must have at least two levels. Levels must be ordered in ascending or + descending order with respect to pressure (i.e. monotonic). + target_h : array-like + Target height levels (m) to which ``data`` will be interpolated. It can be + either a scalar or a 1D array of height levels. Alternatively, it can be a + multidimensional array with a vertical axis defined by `vertical_axis`. In this case + the other axes/dimensions must match those of ``data``. The type of the height and + the reference level are defined by ``h_type`` and ``h_reference``. + z : array-like + Geopotential (m2/s2) on the same pressure levels as ``data``. + zs : array-like + Surface geopotential (m2/s2). The shape must be compatible with the non-vertical + dimensions of ``data`` and ``z``. Only used when and ``h_reference`` is "ground". + h_type : str, optional + Type of height to compute. Possible values are: + + - "geometric": geometric height (m) with respect to ``h_reference`` + - "geopotential": geopotential height (m) with respect to ``h_reference`` + Default is "geometric". See :func:`geometric_height_from_geopotential` and + :func:`geopotential_height_from_geopotential` for details. + + h_reference : str, optional + Reference level for the height calculation. Default is "ground". Possible values are: + + - "ground": height with respect to the ground/surface level + - "sea": height with respect to the sea level + + interpolation : str, optional + Interpolation mode. Default is "linear". Possible values are: + + - "linear": linear interpolation in height between the two nearest levels + - "log": linear interpolation in logarithm of height between the two nearest levels + - "nearest": nearest level interpolation + + aux_bottom_data : array-like, optional + Auxiliary data for interpolation to heights between the bottom pressure + level and ``aux_bottom_h``. Can be a scalar or must have + the same shape as a single level of ``data``. + aux_bottom_h : array-like, optional + Heights (m) of ``aux_bottom_data``. Can be a scalar or must have the same + shape as a single level of ``data``. The type of the height and + the reference level are defined by ``h_type`` and ``h_reference``. + aux_top_data : array-like, optional + Auxiliary data for interpolation to heights between the top pressure + level and ``aux_top_h``. Can be a scalar or must have + the same shape as a single level of ``data``. + aux_top_h : array-like, optional + Heights (m) of ``aux_top_data``. Can be a scalar or must have the same + shape as a single level of ``data``. The type of the height and + the reference level are defined by ``h_type`` and ``h_reference``. + vertical_axis : int, optional + Axis corresponding to the vertical coordinate (hybrid full-levels) in the input + arrays and also in the output array. Default is 0 (first axis). + + + Returns + ------- + array-like + Data interpolated to the target height levels. The shape depends on the shape + of ``target_h``. The axis corresponding to the vertical coordinate (height levels) + is defined by the ``vertical_axis`` parameter. When interpolation is not possible + for a given target height level (e.g., when the target height is outside the + available height range), the corresponding output values are set to nan. + + Raises + ------ + ValueError + If ``data`` has less than two levels. + ValueError + If the first dimension of ``data`` and that of ``target_h`` do not match. + + Examples + -------- + - :ref:`/examples/interpolate_pl_to_hl.ipynb` + + + See also + -------- + interpolate_monotonic + + """ + if h_type == "geometric": + h = geometric_height_from_geopotential(z) + if h_reference == "ground": + zs_h = geometric_height_from_geopotential(zs) + h = h - zs_h + else: + if h_reference == "ground": + z = z - zs + h = geopotential_height_from_geopotential(z) + + return interpolate_monotonic( + data=data, + coord=h, + target_coord=target_h, + interpolation=interpolation, + aux_min_level_data=aux_bottom_data, + aux_min_level_coord=aux_bottom_h, + aux_max_level_coord=aux_top_h, + aux_max_level_data=aux_top_data, + vertical_axis=vertical_axis, + ) + + +def interpolate_monotonic( + data: ArrayLike, + coord: Union[ArrayLike, list, tuple, float, int], + target_coord: Union[ArrayLike, list, tuple, float, int], + interpolation: str = "linear", + aux_min_level_data=None, + aux_min_level_coord=None, + aux_max_level_data=None, + aux_max_level_coord=None, + vertical_axis: int = 0, +) -> ArrayLike: + """Interpolate data between the same type of monotonic coordinate levels. + + *New in version 0.7.0* + + Parameters + ---------- + data : array-like + Data to be interpolated. The axis corresponding to the vertical + coordinate is defined by the ``vertical_axis`` parameter. + Must have at least two levels. + coord : array-like + Vertical coordinates related to ``data``. Either must have the same + shape as ``data`` or be a 1D array with length equal to the size of + the number of levels in ``data``. Must be monotonic (either sorted + ascending or descending) along the vertical axis. + target_coord : array-like + Target coordinate levels to which ``data`` will be interpolated. It can be + either a scalar or a 1D array of coordinate levels. Alternatively, it can be a + multidimensional array with a vertical axis defined by `vertical_axis`. In this case + the other axes/dimensions must match those of ``data``. Must be the same type + of coordinate as ``coord``. + interpolation : str, optional + Interpolation mode. Default is "linear". Possible values are: + + - "linear": linear interpolation in coordinate between the two nearest levels + - "log": linear interpolation in logarithm of coordinate between the two nearest levels + - "nearest": nearest level interpolation + + aux_min_level_data : array-like, optional + Auxiliary data for interpolation to target levels below the minimum level + of ``coord`` and above `aux_min_level_coord`. Can be a scalar or must have + the same shape as a single level of ``data``. + aux_min_level_coord : array-like, optional + Coordinates of ``aux_min_level_data``. Can be a scalar or must have the same + shape as a single level of ``data`` or ``coord``. Must be the same type + of coordinate as ``coord``. + aux_max_level_data : array-like, optional + Auxiliary data for interpolation to target levels above the maximum level + of ``coord`` and below `aux_max_level_coord`. Can be a scalar or must have + the same shape as a single level of ``data``. + aux_max_level_coord : array-like, optional + Coordinates of ``aux_max_level_data``. Can be a scalar or must have the + same shape as a single level of ``data`` or ``coord``. Must be the same type + of coordinate as ``coord``. + vertical_axis : int, optional + Axis corresponding to the vertical coordinate in the input arrays and also in the + output array. Default is 0 (first axis). + + + Returns + ------- + array-like + Data interpolated to the target levels. The shape depends on the shape of ``target_coord``. + The axis corresponding to the vertical coordinate is defined by + the ``vertical_axis`` parameter. When interpolation is not possible for a given target + level (e.g., when the target level is outside the available level range), + the corresponding output values are set to nan. + + Raises + ------ + ValueError + If ``data`` has less than two levels. + ValueError + If the shape of ``data`` and that of ``coord`` are not compatible. + + Notes + ----- + - The ordering of the input coordinate levels is not checked. + - The units of ``coord`` and ``target_coord`` are assumed to be the same; no checks + or conversions are performed. + + Examples + -------- + - :ref:`/examples/interpolate_hybrid_to_pl.ipynb` + - :ref:`/examples/interpolate_hybrid_to_hl.ipynb` + - :ref:`/examples/interpolate_pl_to_hl.ipynb` + - :ref:`/examples/interpolate_pl_to_pl.ipynb` + + """ + from .monotonic import MonotonicInterpolator + + comp = MonotonicInterpolator() + return comp( + data, + coord, + target_coord, + interpolation, + aux_min_level_data, + aux_min_level_coord, + aux_max_level_data, + aux_max_level_coord, + vertical_axis=vertical_axis, + ) diff --git a/src/earthkit/meteo/vertical/vertical.py b/src/earthkit/meteo/vertical/vertical.py index 2631870..be85cd7 100644 --- a/src/earthkit/meteo/vertical/vertical.py +++ b/src/earthkit/meteo/vertical/vertical.py @@ -44,3 +44,43 @@ def geometric_height_from_geopotential_height(*args, **kwargs): def geometric_height_from_geopotential(*args, **kwargs): return array.geometric_height_from_geopotential(*args, **kwargs) + + +def hybrid_level_parameters(*args, **kwargs): + return array.hybrid_level_parameters(*args, **kwargs) + + +def pressure_on_hybrid_levels(*args, **kwargs): + return array.pressure_on_hybrid_levels(*args, **kwargs) + + +def relative_geopotential_thickness_on_hybrid_levels(*args, **kwargs): + return array.relative_geopotential_thickness_on_hybrid_levels(*args, **kwargs) + + +def relative_geopotential_thickness_on_hybrid_levels_from_alpha_delta(*args, **kwargs): + return array.relative_geopotential_thickness_on_hybrid_levels_from_alpha_delta(*args, **kwargs) + + +def geopotential_on_hybrid_levels(*args, **kwargs): + return array.geopotential_on_hybrid_levels(*args, **kwargs) + + +def height_on_hybrid_levels(*args, **kwargs): + return array.height_on_hybrid_levels(*args, **kwargs) + + +def interpolate_hybrid_to_pressure_levels(*args, **kwargs): + return array.interpolate_hybrid_to_pressure_levels(*args, **kwargs) + + +def interpolate_hybrid_to_height_levels(*args, **kwargs): + return array.interpolate_hybrid_to_height_levels(*args, **kwargs) + + +def interpolate_pressure_to_height_levels(*args, **kwargs): + return array.interpolate_pressure_to_height_levels(*args, **kwargs) + + +def interpolate_monotonic(*args, **kwargs): + return array.interpolate_monotonic(*args, **kwargs) diff --git a/tests/documentation/test_examples.py b/tests/documentation/test_examples.py new file mode 100644 index 0000000..c72ab39 --- /dev/null +++ b/tests/documentation/test_examples.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 + +# (C) Copyright 2020 ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. +# + +import os + +import pytest + +from earthkit.meteo.utils.testing import earthkit_path + +SKIP = [ + "conf.py", + "xml2rst.py", + "xref.py", + "skip_api_rules.py", +] + +EXAMPLES = earthkit_path("docs") + + +def example_list(): + examples = [] + for root, _, files in os.walk(EXAMPLES): + for file in files: + path = os.path.join(root, file) + if path.endswith(".py") and file not in SKIP: + n = len(EXAMPLES) + 1 + examples.append(path[n:]) + + return sorted(examples) + + +# # @pytest.mark.skipif(not IN_GITHUB, reason="Not on GITHUB") +# @pytest.mark.parametrize("path", example_list()) +# def test_example(path): +# full = os.path.join(EXAMPLES, path) +# with open(full) as f: +# exec(f.read(), dict(__file__=full), {}) + + +@pytest.mark.parametrize("path", example_list()) +def test_example(tmpdir, path): + print("test_example path=", path) + full = os.path.join(EXAMPLES, path) + if not full.startswith("/"): + full = os.path.join(os.getcwd(), full) + print(" ->", full) + with tmpdir.as_cwd(): + with open(full) as f: + exec(f.read(), dict(__file__=full), {}) + + +if __name__ == "__main__": + from earthkit.meteo.utils.testing import main + + main(__file__) diff --git a/tests/downstream-ci-requirements.txt b/tests/downstream-ci-requirements.txt index 24ce15a..945b470 100644 --- a/tests/downstream-ci-requirements.txt +++ b/tests/downstream-ci-requirements.txt @@ -1 +1,2 @@ numpy +requests diff --git a/tests/vertical/_hybrid_core_data.py b/tests/vertical/_hybrid_core_data.py new file mode 100644 index 0000000..c4ed1b5 --- /dev/null +++ b/tests/vertical/_hybrid_core_data.py @@ -0,0 +1,1277 @@ +A = [ + 0.00000000e00, + 2.00036502e00, + 3.10224104e00, + 4.66608381e00, + 6.82797718e00, + 9.74696636e00, + 1.36054239e01, + 1.86089306e01, + 2.49857178e01, + 3.29857101e01, + 4.28792419e01, + 5.49554634e01, + 6.95205765e01, + 8.68958817e01, + 1.07415741e02, + 1.31425507e02, + 1.59279404e02, + 1.91338562e02, + 2.27968948e02, + 2.69539581e02, + 3.16420746e02, + 3.68982361e02, + 4.27592499e02, + 4.92616028e02, + 5.64413452e02, + 6.43339905e02, + 7.29744141e02, + 8.23967834e02, + 9.26344910e02, + 1.03720117e03, + 1.15685364e03, + 1.28561035e03, + 1.42377014e03, + 1.57162292e03, + 1.72944897e03, + 1.89751929e03, + 2.07609595e03, + 2.26543164e03, + 2.46577051e03, + 2.67734814e03, + 2.90039136e03, + 3.13511938e03, + 3.38174365e03, + 3.64046826e03, + 3.91149048e03, + 4.19493066e03, + 4.49081738e03, + 4.79914941e03, + 5.11989502e03, + 5.45299072e03, + 5.79834473e03, + 6.15607422e03, + 6.52694678e03, + 6.91187061e03, + 7.31186914e03, + 7.72741211e03, + 8.15935400e03, + 8.60852539e03, + 9.07640039e03, + 9.56268262e03, + 1.00659785e04, + 1.05846318e04, + 1.11166621e04, + 1.16600674e04, + 1.22115479e04, + 1.27668730e04, + 1.33246689e04, + 1.38813311e04, + 1.44321396e04, + 1.49756152e04, + 1.55082568e04, + 1.60261152e04, + 1.65273223e04, + 1.70087891e04, + 1.74676133e04, + 1.79016211e04, + 1.83084336e04, + 1.86857188e04, + 1.90312891e04, + 1.93435117e04, + 1.96200430e04, + 1.98593906e04, + 2.00599316e04, + 2.02196641e04, + 2.03378633e04, + 2.04123086e04, + 2.04420781e04, + 2.04257188e04, + 2.03618164e04, + 2.02495117e04, + 2.00870859e04, + 1.98740254e04, + 1.96085723e04, + 1.92902266e04, + 1.89174609e04, + 1.84897070e04, + 1.80069258e04, + 1.74718398e04, + 1.68886875e04, + 1.62620469e04, + 1.55966953e04, + 1.48984531e04, + 1.41733242e04, + 1.34277695e04, + 1.26682578e04, + 1.19013398e04, + 1.11333047e04, + 1.03701758e04, + 9.61751562e03, + 8.88045312e03, + 8.16337500e03, + 7.47034375e03, + 6.80442188e03, + 6.16853125e03, + 5.56438281e03, + 4.99379688e03, + 4.45737500e03, + 3.95596094e03, + 3.48923438e03, + 3.05726562e03, + 2.65914062e03, + 2.29424219e03, + 1.96150000e03, + 1.65947656e03, + 1.38754688e03, + 1.14325000e03, + 9.26507812e02, + 7.34992188e02, + 5.68062500e02, + 4.24414062e02, + 3.02476562e02, + 2.02484375e02, + 1.22101562e02, + 6.27812500e01, + 2.28359375e01, + 3.75781298e00, + 0.00000000e00, + 0.00000000e00, +] + + +B = [ + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 3.81999996e-08, + 6.76070022e-06, + 2.43480008e-05, + 5.89219999e-05, + 1.11914298e-04, + 1.98577400e-04, + 3.40379687e-04, + 5.61555324e-04, + 8.89697927e-04, + 1.35280553e-03, + 1.99183798e-03, + 2.85712420e-03, + 3.97095364e-03, + 5.37781464e-03, + 7.13337678e-03, + 9.26146004e-03, + 1.18060224e-02, + 1.48156285e-02, + 1.83184519e-02, + 2.23548450e-02, + 2.69635208e-02, + 3.21760960e-02, + 3.80263999e-02, + 4.45479602e-02, + 5.17730154e-02, + 5.97284138e-02, + 6.84482530e-02, + 7.79583082e-02, + 8.82857367e-02, + 9.94616672e-02, + 1.11504652e-01, + 1.24448128e-01, + 1.38312891e-01, + 1.53125033e-01, + 1.68910414e-01, + 1.85689449e-01, + 2.03491211e-01, + 2.22332865e-01, + 2.42244005e-01, + 2.63241887e-01, + 2.85354018e-01, + 3.08598459e-01, + 3.32939088e-01, + 3.58254194e-01, + 3.84363323e-01, + 4.11124766e-01, + 4.38391209e-01, + 4.66003299e-01, + 4.93800312e-01, + 5.21619201e-01, + 5.49301147e-01, + 5.76692164e-01, + 6.03648067e-01, + 6.30035818e-01, + 6.55735970e-01, + 6.80643022e-01, + 7.04668999e-01, + 7.27738738e-01, + 7.49796569e-01, + 7.70797551e-01, + 7.90716767e-01, + 8.09536040e-01, + 8.27256083e-01, + 8.43881130e-01, + 8.59431803e-01, + 8.73929262e-01, + 8.87407541e-01, + 8.99900496e-01, + 9.11448181e-01, + 9.22095656e-01, + 9.31880772e-01, + 9.40859556e-01, + 9.49064434e-01, + 9.56549525e-01, + 9.63351727e-01, + 9.69513416e-01, + 9.75078404e-01, + 9.80071604e-01, + 9.84541893e-01, + 9.88499522e-01, + 9.91984010e-01, + 9.95002508e-01, + 9.97630119e-01, + 1.00000000e00, +] + +# Test case: +# p_surf, t, q values taken form Metview test file t_lnsp_ml137.grb +# Point0: value index=644 lat=50.0 lon=-20 +# Point1: value index=8881 lat=30.0 lon=85 +# z was generated with earthkit-meteo. Values matched equivalent +# Metview computations with mlv_geopotential_on_ml() using 0 surface +# geopotential height. + +z_surf = [44.1252, 52257.1252] + +p_surf = [101183.94696484, 53169.889084751754] + +p_full = [ + [1.0001825100, 1.0001825100], + [2.5513030300, 2.5513030300], + [3.8841624250, 3.8841624250], + [5.7470304950, 5.7470304950], + [8.2874717700, 8.2874717700], + [11.6761951300, 11.6761951300], + [16.1071772500, 16.1071772500], + [21.7973242000, 21.7973242000], + [28.9857139500, 28.9857139500], + [37.9324760000, 37.9324760000], + [48.9173526500, 48.9173526500], + [62.2380199500, 62.2380199500], + [78.2082291000, 78.2082291000], + [97.1558113500, 97.1558113500], + [119.4206240000, 119.4206240000], + [145.3524555000, 145.3524555000], + [175.3089830000, 175.3089830000], + [209.6537550000, 209.6537550000], + [248.7542645000, 248.7542645000], + [292.9801635000, 292.9801635000], + [342.7015535000, 342.7015535000], + [398.2874300000, 398.2874300000], + [460.1042635000, 460.1042635000], + [528.5147400000, 528.5147400000], + [603.8766785000, 603.8766785000], + [686.5420230000, 686.5420230000], + [776.8559875000, 776.8559875000], + [875.1563720000, 875.1563720000], + [981.7730400000, 981.7730400000], + [1097.0274050000, 1097.0274050000], + [1221.2319950000, 1221.2319950000], + [1354.6902450000, 1354.6902450000], + [1497.6965300000, 1497.6965300000], + [1650.5359450000, 1650.5359450000], + [1813.4841300000, 1813.4841300000], + [1986.8076200000, 1986.8076200000], + [2170.7637950000, 2170.7637950000], + [2365.6010750000, 2365.6010750000], + [2571.5593250000, 2571.5593250000], + [2788.8697500000, 2788.8697500000], + [3017.7553700000, 3017.7553700000], + [3258.4315150000, 3258.4315150000], + [3511.1059550000, 3511.1059550000], + [3775.9793700000, 3775.9793700000], + [4053.2105700000, 4053.2105700000], + [4342.8740200000, 4342.8740200000], + [4644.9833950000, 4644.9833950000], + [4959.5222150000, 4959.5222150000], + [5286.4428700000, 5286.4428700000], + [5625.6677250000, 5625.6677250000], + [5977.2094750000, 5977.2094750000], + [6341.5105000000, 6341.5105000000], + [6719.4086950000, 6719.4086950000], + [7111.8718076134, 7111.8708905449], + [7519.9845947796, 7519.8213733853], + [7944.9569055771, 7944.2100780914], + [8388.1524886673, 8386.1534233507], + [8851.1058354532, 8847.0045635055], + [9335.2498927517, 9327.7959095722], + [9841.5974626537, 9828.6587042686], + [10370.9358221594, 10349.2830422483], + [10924.0687159909, 10889.2284371948], + [11501.8174254308, 11447.9815800404], + [12105.0197657661, 12024.7248122274], + [12734.5290160278, 12618.1198406434], + [13391.2168830172, 13227.2950207074], + [14075.9726369150, 13851.5364862633], + [14789.7012145541, 14489.3446800597], + [15533.3245496460, 15139.7332276410], + [16307.7815129458, 15802.0138523149], + [17114.0278563910, 16474.9211128030], + [17953.0372669612, 17157.5864398966], + [18825.7980582074, 17849.3530424421], + [19733.3146547499, 18549.3272197136], + [20676.6071249061, 19256.8406328854], + [21656.7101609725, 19971.3568102379], + [22674.6760365070, 20692.3109838807], + [23731.5721933560, 21419.1917445935], + [24828.4777493383, 22151.6597116777], + [25966.4878778106, 22889.3469285046], + [27146.7136618827, 23631.9271101420], + [28370.2754111910, 24379.2498141667], + [29638.3095295020, 25131.0521706565], + [30951.9661266502, 25887.2915962412], + [32312.4027388633, 26647.8775209194], + [33720.7918554617, 27412.6804680132], + [35178.3181727795, 28181.7594970852], + [36686.1763950233, 28955.0620991742], + [38245.5709157658, 29732.6817425891], + [39857.7164305907, 30514.6450630652], + [41523.8360111680, 31301.0650952685], + [43245.1595375857, 32092.0491746206], + [45022.9282938014, 32887.7138557734], + [46858.3932283242, 33688.2354605995], + [48752.8119162017, 34493.7776118518], + [50704.9669658008, 35303.5565088469], + [52708.2149941708, 36114.7178700328], + [54750.7493776446, 36922.7091556419], + [56820.6795042690, 37723.3739301856], + [58908.0607800922, 38513.7061832374], + [61002.6771663823, 39290.8520396093], + [63094.2474860430, 40052.2144200071], + [65172.6239252278, 40795.4182903513], + [67227.9874978000, 41518.3717108819], + [69251.0225314943, 42219.2685280212], + [71233.0639169855, 42896.6018322701], + [73166.2426456089, 43549.1579155478], + [75043.5779129397, 44176.0173881315], + [76859.0348957272, 44776.5457599162], + [78607.5830913097, 45350.3573121716], + [80285.1936213172, 45897.3396252151], + [81888.8098860833, 46417.5770109973], + [83416.3339615638, 46911.3869166629], + [84866.5479986751, 47379.2285761559], + [86239.0374219122, 47821.7219728763], + [87534.1296230499, 48239.6137573527], + [88752.7975355813, 48633.7581003056], + [89896.5603985990, 49005.0775221166], + [90967.4070359393, 49354.5577849386], + [91967.7079509864, 49683.2443482006], + [92900.1322178202, 49992.1764487877], + [93767.5753292005, 50282.4752199509], + [94573.0894603586, 50555.1495026601], + [95319.8380024077, 50811.3722412521], + [96011.0274536345, 51052.0962021508], + [96649.8632918694, 51278.4033694557], + [97239.5208814574, 51491.3914191923], + [97783.1205740490, 51691.9956552580], + [98283.7003407499, 51881.3509155464], + [98744.1831035708, 52060.3110042578], + [99167.3778273893, 52230.0350032058], + [99555.9669119284, 52391.4338334481], + [99912.5003533964, 52545.6380035858], + [100239.3789270635, 52693.8534590587], + [100538.8661038220, 52837.2232627185], + [100813.0959318789, 52975.9067886138], + [101064.0500081315, 53106.8859297947], +] + +p_half = [ + [0.0000000000, 0.0000000000], + [2.0003650200, 2.0003650200], + [3.1022410400, 3.1022410400], + [4.6660838100, 4.6660838100], + [6.8279771800, 6.8279771800], + [9.7469663600, 9.7469663600], + [13.6054239000, 13.6054239000], + [18.6089306000, 18.6089306000], + [24.9857178000, 24.9857178000], + [32.9857101000, 32.9857101000], + [42.8792419000, 42.8792419000], + [54.9554634000, 54.9554634000], + [69.5205765000, 69.5205765000], + [86.8958817000, 86.8958817000], + [107.4157410000, 107.4157410000], + [131.4255070000, 131.4255070000], + [159.2794040000, 159.2794040000], + [191.3385620000, 191.3385620000], + [227.9689480000, 227.9689480000], + [269.5395810000, 269.5395810000], + [316.4207460000, 316.4207460000], + [368.9823610000, 368.9823610000], + [427.5924990000, 427.5924990000], + [492.6160280000, 492.6160280000], + [564.4134520000, 564.4134520000], + [643.3399050000, 643.3399050000], + [729.7441410000, 729.7441410000], + [823.9678340000, 823.9678340000], + [926.3449100000, 926.3449100000], + [1037.2011700000, 1037.2011700000], + [1156.8536400000, 1156.8536400000], + [1285.6103500000, 1285.6103500000], + [1423.7701400000, 1423.7701400000], + [1571.6229200000, 1571.6229200000], + [1729.4489700000, 1729.4489700000], + [1897.5192900000, 1897.5192900000], + [2076.0959500000, 2076.0959500000], + [2265.4316400000, 2265.4316400000], + [2465.7705100000, 2465.7705100000], + [2677.3481400000, 2677.3481400000], + [2900.3913600000, 2900.3913600000], + [3135.1193800000, 3135.1193800000], + [3381.7436500000, 3381.7436500000], + [3640.4682600000, 3640.4682600000], + [3911.4904800000, 3911.4904800000], + [4194.9306600000, 4194.9306600000], + [4490.8173800000, 4490.8173800000], + [4799.1494100000, 4799.1494100000], + [5119.8950200000, 5119.8950200000], + [5452.9907200000, 5452.9907200000], + [5798.3447300000, 5798.3447300000], + [6156.0742200000, 6156.0742200000], + [6526.9467800000, 6526.9467800000], + [6911.8706100000, 6911.8706100000], + [7311.8730052267, 7311.8711710897], + [7728.0961843325, 7727.7715756808], + [8161.8176268216, 8160.6485805020], + [8614.4873505129, 8611.6582661993], + [9087.7243203934, 9082.3508608117], + [9582.7754651100, 9573.2409583327], + [10100.4194601973, 10084.0764502045], + [10641.4521841214, 10614.4896342920], + [11206.6852478603, 11163.9672400975], + [11796.9496030013, 11731.9959199833], + [12413.0899285309, 12317.4537044714], + [13055.9681035248, 12918.7859768154], + [13726.4656625096, 13535.8040645995], + [14425.4796113205, 14167.2689079272], + [15153.9228177877, 14811.4204521923], + [15912.7262815043, 15468.0460030897], + [16702.8367443873, 16135.9817015401], + [17525.2189683948, 16813.8605240659], + [18380.8555655276, 17501.3123557274], + [19270.7405508872, 18197.3937291568], + [20195.8887586126, 18901.2607102704], + [21157.3254911996, 19612.4205555003], + [22156.0948307454, 20330.2930649754], + [23193.2572422686, 21054.3289027859], + [24269.8871444434, 21784.0545864011], + [25387.0683542332, 22519.2648369542], + [26545.9074013880, 23259.4290200550], + [27747.5199223775, 24004.4252002289], + [28993.0309000046, 24754.0744281046], + [30283.5881589994, 25508.0299132085], + [31620.3440943009, 26266.5532792738], + [33004.4613834256, 27029.2017625650], + [34437.1223274977, 27796.1591734614], + [35919.5140180614, 28567.3598207090], + [37452.8387719852, 29342.7643776395], + [39038.3030595464, 30122.5991075387], + [40677.1298016351, 30906.6910185918], + [42370.5422207009, 31695.4391719451], + [44119.7768544704, 32488.6591772960], + [45926.0797331324, 33286.7685342508], + [47790.7067235160, 34089.7023869483], + [49714.9171088873, 34897.8528367553], + [51695.0168227142, 35709.2601809384], + [53721.4131656275, 36520.1755591271], + [55780.0855896617, 37325.2427521566], + [57861.2734188763, 38121.5051082145], + [59954.8481413081, 38905.9072582602], + [62050.5061914565, 39675.7968209584], + [64137.9887806294, 40428.6320190558], + [66207.2590698262, 41162.2045616468], + [68248.7159257738, 41874.5388601169], + [70253.3291372148, 42563.9981959255], + [72212.7986967562, 43229.2054686148], + [74119.6865944616, 43869.1103624808], + [75967.4692314179, 44482.9244137821], + [77750.6005600364, 45070.1671060502], + [79464.5656225829, 45630.5475182930], + [81105.8216200516, 46164.1317321372], + [82671.7981521150, 46671.0222898574], + [84160.8697710126, 47151.7515434683], + [85572.2262263377, 47606.7056088435], + [86905.8486174866, 48036.7383369092], + [88162.4106286133, 48442.4891777962], + [89343.1844425492, 48825.0270228150], + [90449.9363546488, 49185.1280214182], + [91484.8777172298, 49523.9875484590], + [92450.5381847431, 49842.5011479423], + [93349.7262508972, 50141.8517496331], + [94185.4244075039, 50423.0986902687], + [94960.7545132133, 50687.2003150514], + [95678.9214916021, 50935.5441674528], + [96343.1334156669, 51168.6482368488], + [96956.5931680719, 51388.1585020627], + [97522.4485948429, 51594.6243363220], + [98043.7925532550, 51789.3669741941], + [98523.6081282448, 51973.3348568988], + [98964.7580788968, 52147.2871516168], + [99369.9975758817, 52312.7828547947], + [99741.9362479752, 52470.0848121015], + [100083.0644588177, 52621.1911950701], + [100395.6933953093, 52766.5157230473], + [100682.0388123348, 52907.9308023898], + [100944.1530514230, 53043.8827748377], + [101183.9469648400, 53169.8890847518], +] + + +delta = [ + [2.9959147669, 2.9959147669], + [0.4387950925, 0.4387950925], + [0.4081953688, 0.4081953688], + [0.3807083275, 0.3807083275], + [0.3559276312, 0.3559276312], + [0.3335124354, 0.3335124354], + [0.3131730759, 0.3131730759], + [0.2946627683, 0.2946627683], + [0.2777700668, 0.2777700668], + [0.2623133966, 0.2623133966], + [0.2481352638, 0.2481352638], + [0.2350996727, 0.2350996727], + [0.2230878664, 0.2230878664], + [0.2119960957, 0.2119960957], + [0.2017334688, 0.2017334688], + [0.1922197135, 0.1922197135], + [0.1833845169, 0.1833845169], + [0.1751649919, 0.1751649919], + [0.1675058211, 0.1675058211], + [0.1603575548, 0.1603575548], + [0.1536760381, 0.1536760381], + [0.1474217962, 0.1474217962], + [0.1415593858, 0.1415593858], + [0.1360570311, 0.1360570311], + [0.1308861543, 0.1308861543], + [0.1260207727, 0.1260207727], + [0.1214375118, 0.1214375118], + [0.1171151456, 0.1171151456], + [0.1130345434, 0.1130345434], + [0.1091780379, 0.1091780379], + [0.1055296455, 0.1055296455], + [0.1020747953, 0.1020747953], + [0.0988004110, 0.0988004110], + [0.0956940509, 0.0956940509], + [0.0927445529, 0.0927445529], + [0.0899417867, 0.0899417867], + [0.0872761274, 0.0872761274], + [0.0847390285, 0.0847390285], + [0.0823224659, 0.0823224659], + [0.0800188750, 0.0800188750], + [0.0778215735, 0.0778215735], + [0.0757241962, 0.0757241962], + [0.0737208670, 0.0737208670], + [0.0718061821, 0.0718061821], + [0.0699583123, 0.0699583123], + [0.0681579191, 0.0681579191], + [0.0664039662, 0.0664039662], + [0.0646952390, 0.0646952390], + [0.0630302792, 0.0630302792], + [0.0614082713, 0.0614082713], + [0.0598667870, 0.0598667870], + [0.0584999934, 0.0584999934], + [0.0573010459, 0.0573010459], + [0.0562591543, 0.0562589035], + [0.0553630770, 0.0553213233], + [0.0546043495, 0.0545031103], + [0.0539784690, 0.0537932489], + [0.0534791655, 0.0532161669], + [0.0530427375, 0.0526387418], + [0.0526096892, 0.0519857844], + [0.0521800044, 0.0512624237], + [0.0517535393, 0.0504713669], + [0.0513304925, 0.0496284221], + [0.0509105654, 0.0486974536], + [0.0504938000, 0.0476652724], + [0.0500804143, 0.0466557983], + [0.0496702910, 0.0455959699], + [0.0492633698, 0.0444642379], + [0.0488597538, 0.0433778124], + [0.0484593858, 0.0422753188], + [0.0480623575, 0.0411519108], + [0.0476687371, 0.0400722924], + [0.0472782474, 0.0390025123], + [0.0468911450, 0.0379502420], + [0.0465071476, 0.0369344433], + [0.0461264159, 0.0359489756], + [0.0457489800, 0.0349941446], + [0.0453747694, 0.0340720736], + [0.0450035547, 0.0331928967], + [0.0446356645, 0.0323394412], + [0.0442708751, 0.0315275976], + [0.0439090233, 0.0307519021], + [0.0435504335, 0.0300032023], + [0.0431947930, 0.0293030903], + [0.0428420319, 0.0286214369], + [0.0424923742, 0.0279800236], + [0.0421455938, 0.0273669492], + [0.0418017950, 0.0267811859], + [0.0414607851, 0.0262297039], + [0.0411227193, 0.0256970073], + [0.0407873487, 0.0252000975], + [0.0404547748, 0.0247182842], + [0.0401250049, 0.0242688967], + [0.0397980590, 0.0238353792], + [0.0394738305, 0.0234299481], + [0.0390563594, 0.0229847400], + [0.0384502870, 0.0224548189], + [0.0376052392, 0.0218049842], + [0.0366313913, 0.0211087143], + [0.0355434400, 0.0203675352], + [0.0343569215, 0.0195952533], + [0.0330881676, 0.0187968974], + [0.0317532735, 0.0179822230], + [0.0303685091, 0.0171575075], + [0.0289490773, 0.0163308054], + [0.0275096005, 0.0155075361], + [0.0260638756, 0.0146941167], + [0.0246240392, 0.0138949585], + [0.0232010631, 0.0131151499], + [0.0218049316, 0.0123568496], + [0.0204435349, 0.0116257332], + [0.0191237882, 0.0109203372], + [0.0178515537, 0.0102476923], + [0.0166306869, 0.0096024687], + [0.0154645623, 0.0089924747], + [0.0143553560, 0.0084112040], + [0.0133042706, 0.0078657260], + [0.0123115488, 0.0073482716], + [0.0113771796, 0.0068658473], + [0.0105000914, 0.0064109076], + [0.0096791583, 0.0059879669], + [0.0089125018, 0.0055933538], + [0.0081982565, 0.0052240420], + [0.0075343222, 0.0048875741], + [0.0069181071, 0.0045660118], + [0.0063472600, 0.0042807612], + [0.0058192083, 0.0040097209], + [0.0053316481, 0.0037673699], + [0.0048819542, 0.0035459384], + [0.0044676119, 0.0033413641], + [0.0040864251, 0.0031685953], + [0.0037359800, 0.0030024388], + [0.0034142729, 0.0028757190], + [0.0031188261, 0.0027579046], + [0.0028481086, 0.0026764306], + [0.0026000033, 0.0025662996], + [0.0023726936, 0.0023726936], +] + +alpha = [ + [0.6931471806, 0.6931471806], + [0.2034037059, 0.2034037059], + [0.1902508041, 0.1902508041], + [0.1783050043, 0.1783050043], + [0.1674289989, 0.1674289989], + [0.1575041439, 0.1575041439], + [0.1484267521, 0.1484267521], + [0.1401063208, 0.1401063208], + [0.1324636022, 0.1324636022], + [0.1254292369, 0.1254292369], + [0.1189419637, 0.1189419637], + [0.1129480858, 0.1129480858], + [0.1074000195, 0.1074000195], + [0.1022556548, 0.1022556548], + [0.0974776664, 0.0974776664], + [0.0930327163, 0.0930327163], + [0.0888913379, 0.0888913379], + [0.0850269047, 0.0850269047], + [0.0814158199, 0.0814158199], + [0.0780368164, 0.0780368164], + [0.0748707662, 0.0748707662], + [0.0719004549, 0.0719004549], + [0.0691103287, 0.0691103287], + [0.0664863650, 0.0664863650], + [0.0640158858, 0.0640158858], + [0.0616873003, 0.0616873003], + [0.0594901354, 0.0594901354], + [0.0574148375, 0.0574148375], + [0.0554527644, 0.0554527644], + [0.0535958959, 0.0535958959], + [0.0518369528, 0.0518369528], + [0.0501692764, 0.0501692764], + [0.0485868777, 0.0485868777], + [0.0470840293, 0.0470840293], + [0.0456555832, 0.0456555832], + [0.0442968571, 0.0442968571], + [0.0430033841, 0.0430033841], + [0.0417711939, 0.0417711939], + [0.0405965477, 0.0405965477], + [0.0394759094, 0.0394759094], + [0.0384061546, 0.0384061546], + [0.0373842976, 0.0373842976], + [0.0364075773, 0.0364075773], + [0.0354734506, 0.0354734506], + [0.0345713423, 0.0345713423], + [0.0336918644, 0.0336918644], + [0.0328345529, 0.0328345529], + [0.0319988543, 0.0319988543], + [0.0311840935, 0.0311840935], + [0.0303899074, 0.0303899074], + [0.0296347420, 0.0296347420], + [0.0289648255, 0.0289648255], + [0.0283769204, 0.0283769204], + [0.0278658334, 0.0278657103], + [0.0274261290, 0.0274056373], + [0.0270537175, 0.0270040183], + [0.0267464400, 0.0266554933], + [0.0265012590, 0.0263720979], + [0.0262869187, 0.0260884785], + [0.0260742069, 0.0257676922], + [0.0258631164, 0.0254122351], + [0.0256535772, 0.0250234126], + [0.0254456876, 0.0246089711], + [0.0252393015, 0.0241511144], + [0.0250344404, 0.0236433118], + [0.0248312119, 0.0231465088], + [0.0246295591, 0.0226247416], + [0.0244294531, 0.0220673687], + [0.0242309452, 0.0215321083], + [0.0240340079, 0.0209887303], + [0.0238386870, 0.0204348361], + [0.0236450167, 0.0199023341], + [0.0234528612, 0.0193744930], + [0.0232623476, 0.0188551055], + [0.0230733374, 0.0183535448], + [0.0228859104, 0.0178667960], + [0.0227000820, 0.0173950252], + [0.0225158181, 0.0169392965], + [0.0223330064, 0.0165046360], + [0.0221518092, 0.0160825688], + [0.0219721170, 0.0156809677], + [0.0217938499, 0.0152971457], + [0.0216171684, 0.0149265863], + [0.0214419188, 0.0145799902], + [0.0212680673, 0.0142424538], + [0.0210957248, 0.0139247725], + [0.0209247804, 0.0136210629], + [0.0207552859, 0.0133308244], + [0.0205871469, 0.0130575195], + [0.0204204404, 0.0127934763], + [0.0202550442, 0.0125471289], + [0.0200910087, 0.0123082265], + [0.0199283381, 0.0120853672], + [0.0197670425, 0.0118703463], + [0.0196070700, 0.0116692276], + [0.0194010663, 0.0114483455], + [0.0191019445, 0.0111853916], + [0.0186847762, 0.0108628710], + [0.0182038766, 0.0105172259], + [0.0176664442, 0.0101491981], + [0.0170800962, 0.0097656290], + [0.0164528499, 0.0093690052], + [0.0157926156, 0.0089641650], + [0.0151074018, 0.0085542222], + [0.0144047022, 0.0081431782], + [0.0136917362, 0.0077337278], + [0.0129753280, 0.0073290653], + [0.0122614915, 0.0069313901], + [0.0115556745, 0.0065432411], + [0.0108628449, 0.0061657005], + [0.0101869395, 0.0058016035], + [0.0095314177, 0.0054502308], + [0.0088992205, 0.0051150949], + [0.0082922952, 0.0047935504], + [0.0077123519, 0.0044894987], + [0.0071605050, 0.0041997063], + [0.0066373850, 0.0039277072], + [0.0061431433, 0.0036696360], + [0.0056778031, 0.0034289953], + [0.0052408581, 0.0032020288], + [0.0048317720, 0.0029909955], + [0.0044496315, 0.0027940698], + [0.0040935273, 0.0026097468], + [0.0037624306, 0.0024417964], + [0.0034550652, 0.0022812685], + [0.0031702727, 0.0021388535], + [0.0029067822, 0.0020035207], + [0.0026634552, 0.0018825022], + [0.0024389910, 0.0017719214], + [0.0022321426, 0.0016697517], + [0.0020418210, 0.0015834610], + [0.0018668269, 0.0015004682], + [0.0017061650, 0.0014371703], + [0.0015586025, 0.0013783185], + [0.0014233783, 0.0013376184], + [0.0012994383, 0.0012826010], + [0.0011858777, 0.0011858777], +] + +t = [ + [199.78929138, 203.77757263], + [209.54397583, 209.23440552], + [215.42625427, 213.40086365], + [220.50753784, 218.10128784], + [225.61582947, 223.14024353], + [232.11523438, 228.22460938], + [241.98384094, 235.28364563], + [249.85993958, 240.85603333], + [255.94047546, 247.1426239], + [261.79225159, 254.94459534], + [266.44659424, 261.02862549], + [269.80895996, 265.64880371], + [271.38972473, 266.99128723], + [270.98959351, 265.86264038], + [268.9801178, 265.0660553], + [266.35513306, 264.21060181], + [264.7883606, 264.44656372], + [262.9664917, 264.33953857], + [260.62815857, 262.35667419], + [259.09480286, 259.76863098], + [258.02911377, 256.80450439], + [256.43600464, 252.47897339], + [254.02259827, 246.3028717], + [251.17669678, 241.76361084], + [248.12364197, 239.37657166], + [245.0716095, 237.01106262], + [241.78787231, 234.81814575], + [237.96272278, 233.11897278], + [234.79064941, 232.26428223], + [232.34790039, 232.11645508], + [230.33331299, 232.07745361], + [228.74267578, 231.71142578], + [227.43002319, 230.73666382], + [226.01573181, 228.97569275], + [224.33898926, 226.95129395], + [222.99913025, 225.23057556], + [221.87628174, 223.66241455], + [220.82601929, 221.89633179], + [220.05307007, 220.30990601], + [219.6811676, 219.03077698], + [219.55952454, 217.83784485], + [219.32658386, 216.42521667], + [218.83781433, 215.17863464], + [218.3313446, 214.52177429], + [217.94612122, 213.95002747], + [217.71180725, 212.91590881], + [217.72558594, 211.1796875], + [217.85084534, 208.8752594], + [218.0191803, 206.3941803], + [218.24353027, 203.66442871], + [218.48812866, 200.94711304], + [218.70314026, 199.05274963], + [218.97155762, 198.46179199], + [219.29553223, 198.47424316], + [219.57293701, 198.26141357], + [219.67663574, 197.93640137], + [219.62417603, 197.60269165], + [219.66845703, 197.22314453], + [219.88063049, 197.10621643], + [220.22846985, 197.54878235], + [220.71125793, 198.45149231], + [221.35551453, 199.59281921], + [222.0042572, 200.85679626], + [222.4879303, 202.2769928], + [222.89889526, 203.72409058], + [223.328125, 205.27246094], + [223.87538147, 207.09120178], + [224.53361511, 209.14152527], + [225.2956543, 211.24975586], + [225.93247986, 213.34751892], + [225.98681641, 215.43310547], + [225.56298828, 217.49853516], + [225.15385437, 219.52690125], + [225.02775574, 221.53947449], + [225.11260986, 223.52374268], + [225.00570679, 225.42367554], + [224.40518188, 227.25869751], + [223.31588745, 229.04049683], + [222.05892944, 230.67514038], + [221.43502808, 232.13815308], + [221.9316864, 233.48344421], + [223.36567688, 234.74751282], + [225.34831238, 236.03581238], + [227.61721802, 237.32913208], + [230.06221008, 238.57685852], + [232.56451416, 239.76177979], + [235.03742981, 240.90950012], + [237.43273926, 242.08410645], + [239.79444885, 243.30030823], + [242.05895996, 244.57165527], + [244.19204712, 245.84341431], + [246.19921875, 247.109375], + [248.14360046, 248.33891296], + [250.22305298, 249.51211548], + [252.3525238, 250.68650818], + [254.34068298, 251.85142517], + [256.15499878, 252.99093628], + [257.79119873, 254.08514404], + [259.3658905, 255.14811707], + [260.95251465, 256.1673584], + [262.5565033, 257.07798767], + [263.99475098, 257.93225098], + [265.31828308, 258.78312683], + [266.55743408, 259.59844971], + [267.62954712, 260.38931274], + [268.55137634, 261.14122009], + [269.22834778, 261.8865509], + [269.66952515, 262.68515015], + [270.20774841, 263.51634216], + [270.96899414, 264.27954102], + [271.88400269, 264.98361206], + [272.49493408, 265.59063721], + [272.83621216, 266.21316528], + [273.56558228, 266.8644104], + [274.46348572, 267.49278259], + [275.20922852, 268.09594727], + [275.87101746, 268.60734558], + [276.49809265, 269.04301453], + [277.09143066, 269.47424316], + [277.65512085, 269.9012146], + [278.24208069, 270.27333069], + [278.84449768, 270.61988831], + [279.46356201, 270.97723389], + [280.06265259, 271.30288696], + [280.60742188, 271.609375], + [281.11647034, 271.88600159], + [281.60151672, 272.14253235], + [282.06123352, 272.3874054], + [282.47163391, 272.62397766], + [282.83157349, 272.85305786], + [283.16653442, 273.08059692], + [283.48504639, 273.29168701], + [283.77868652, 273.4954834], + [284.04649353, 273.69688416], + [284.30439758, 273.90986633], + [284.53866577, 274.1265564], + [284.78421021, 274.35256958], +] + +q = [ + [3.01709883e-06, 3.00801844e-06], + [3.84387056e-06, 3.47529965e-06], + [4.34961817e-06, 3.78843811e-06], + [4.58298211e-06, 4.10096447e-06], + [4.58293300e-06, 4.45406124e-06], + [4.58866350e-06, 4.64279663e-06], + [4.60318688e-06, 4.64550385e-06], + [4.60787214e-06, 4.64105051e-06], + [4.60761453e-06, 4.61535615e-06], + [4.59866988e-06, 4.55425743e-06], + [4.57711599e-06, 4.45467617e-06], + [4.53649000e-06, 4.32138359e-06], + [4.47918819e-06, 4.16181092e-06], + [4.41289330e-06, 3.99970622e-06], + [4.37053859e-06, 3.87450746e-06], + [4.33979017e-06, 3.79537391e-06], + [4.28324665e-06, 3.73295143e-06], + [4.21928053e-06, 3.67246321e-06], + [4.16876492e-06, 3.61666525e-06], + [4.11070073e-06, 3.55400266e-06], + [4.03302465e-06, 3.51302651e-06], + [3.95065649e-06, 3.48921526e-06], + [3.86138026e-06, 3.46655770e-06], + [3.79883841e-06, 3.42744443e-06], + [3.80090069e-06, 3.39888948e-06], + [3.89090224e-06, 3.37730694e-06], + [4.00592694e-06, 3.33389039e-06], + [4.05859964e-06, 3.28507804e-06], + [4.05837773e-06, 3.26067084e-06], + [4.02113096e-06, 3.24958842e-06], + [3.94500034e-06, 3.23908694e-06], + [3.84701934e-06, 3.23749782e-06], + [3.74084152e-06, 3.24569805e-06], + [3.60681111e-06, 3.23803647e-06], + [3.47068021e-06, 3.19538708e-06], + [3.36873427e-06, 3.14815634e-06], + [3.28857686e-06, 3.10237056e-06], + [3.23770337e-06, 3.05647382e-06], + [3.19796345e-06, 3.00596548e-06], + [3.15370562e-06, 2.95978680e-06], + [3.09788288e-06, 2.91511083e-06], + [3.02859212e-06, 2.86412637e-06], + [2.95260304e-06, 2.81302107e-06], + [2.90156686e-06, 2.79586175e-06], + [2.88648130e-06, 2.79483334e-06], + [2.88630281e-06, 2.78819380e-06], + [2.87791408e-06, 2.75902494e-06], + [2.82208839e-06, 2.70445071e-06], + [2.72617797e-06, 2.64119478e-06], + [2.64021696e-06, 2.59429112e-06], + [2.60074262e-06, 2.57514580e-06], + [2.59540116e-06, 2.58525847e-06], + [2.59828244e-06, 2.60062529e-06], + [2.60247612e-06, 2.59450167e-06], + [2.61033165e-06, 2.53349754e-06], + [2.62331196e-06, 2.52601785e-06], + [2.63964239e-06, 2.51699885e-06], + [2.65290794e-06, 2.32904051e-06], + [2.66321103e-06, 2.11617544e-06], + [2.66851043e-06, 1.93777146e-06], + [2.65594792e-06, 1.82194856e-06], + [2.64366190e-06, 1.82584427e-06], + [2.64526034e-06, 1.92744346e-06], + [2.68849180e-06, 1.98336420e-06], + [2.77688434e-06, 1.98828695e-06], + [2.75469779e-06, 1.98589100e-06], + [2.73670707e-06, 1.98838939e-06], + [2.74721538e-06, 1.99377541e-06], + [2.79482015e-06, 2.05302172e-06], + [3.53010273e-06, 2.33614719e-06], + [5.57021826e-06, 2.94668257e-06], + [7.29458009e-06, 3.76114224e-06], + [8.37367526e-06, 4.38575199e-06], + [9.58809187e-06, 4.54404881e-06], + [1.11091222e-05, 4.53771008e-06], + [1.32116986e-05, 4.52804687e-06], + [1.50918495e-05, 4.54927795e-06], + [1.69239520e-05, 4.75715387e-06], + [2.11658953e-05, 5.53457721e-06], + [2.74663107e-05, 7.26033613e-06], + [3.30927323e-05, 1.05025720e-05], + [3.94161415e-05, 1.44566965e-05], + [4.37294509e-05, 1.72649886e-05], + [4.11307184e-05, 1.77955000e-05], + [3.94712188e-05, 1.52419307e-05], + [6.06414760e-05, 1.52227367e-05], + [7.96968197e-05, 3.11190342e-05], + [8.42861052e-05, 6.19343634e-05], + [9.09653904e-05, 8.87600185e-05], + [9.04298161e-05, 1.10158954e-04], + [7.83304636e-05, 3.09775299e-04], + [1.50449098e-04, 5.85563005e-04], + [7.37198455e-04, 8.20525748e-04], + [1.02437514e-03, 9.55472175e-04], + [1.19246052e-03, 9.92904173e-04], + [1.23465780e-03, 1.00827935e-03], + [9.50902766e-04, 1.02147467e-03], + [7.25478037e-04, 1.07917200e-03], + [6.10613264e-04, 1.18174497e-03], + [5.39789663e-04, 1.30034493e-03], + [3.69320379e-04, 1.43409775e-03], + [2.71807180e-04, 1.59932183e-03], + [2.82536016e-04, 1.78314255e-03], + [3.23305593e-04, 1.93739937e-03], + [4.17901482e-04, 2.05369135e-03], + [5.53862984e-04, 2.15555900e-03], + [5.54333196e-04, 2.28262948e-03], + [7.06444250e-04, 2.44499253e-03], + [1.01638840e-03, 2.62237595e-03], + [1.34254502e-03, 2.80452775e-03], + [1.56832741e-03, 2.99550103e-03], + [2.90717693e-03, 3.19256397e-03], + [3.79513750e-03, 3.36884508e-03], + [3.98430898e-03, 3.51367071e-03], + [4.16882193e-03, 3.63142645e-03], + [4.42667022e-03, 3.72381225e-03], + [4.60835848e-03, 3.79201326e-03], + [4.59124287e-03, 3.84260853e-03], + [4.51994942e-03, 3.88575600e-03], + [5.04645684e-03, 3.93113472e-03], + [5.33124994e-03, 3.97464823e-03], + [5.37591171e-03, 4.01740265e-03], + [5.39185889e-03, 4.06625159e-03], + [5.40263100e-03, 4.12518426e-03], + [5.41078714e-03, 4.20057443e-03], + [5.41918472e-03, 4.28669646e-03], + [5.42784484e-03, 4.38118728e-03], + [5.43691765e-03, 4.47752128e-03], + [5.44691293e-03, 4.57763879e-03], + [5.45726771e-03, 4.67668528e-03], + [5.46930194e-03, 4.75738406e-03], + [5.48250892e-03, 4.80826118e-03], + [5.49916971e-03, 4.82301462e-03], + [5.52026850e-03, 4.83362299e-03], + [5.54970912e-03, 4.84303645e-03], + [5.59888977e-03, 4.85454697e-03], + [5.71770210e-03, 4.87179298e-03], +] + +# z values were computed using the q, t, alpha and delta values above +z = [ + [785674.6611895609, 726683.7057229937], + [731762.3807604300, 671998.7786184924], + [706049.5173437172, 646430.7130901638], + [681472.4104885444, 622104.0354932949], + [657977.6933713157, 598866.4400919868], + [635406.4597338708, 576610.7630885816], + [613467.8438051059, 555164.9209378884], + [592071.9785813534, 534454.0103059614], + [571347.1395675521, 514458.2728309712], + [551328.1114241120, 495042.8558695722], + [532020.5607790757, 476182.7937724928], + [513462.3082610627, 457955.4072050027], + [495701.6180532369, 440475.6738106607], + [478797.8382728829, 423869.0128825393], + [462793.3686389009, 408132.1808688999], + [447682.9308790776, 393192.3315873753], + [433387.1953530876, 378963.1440037091], + [419826.2625380504, 365375.4097175160], + [406966.9009747809, 352439.8224746781], + [394753.0166119907, 340169.6214083833], + [383111.8592285347, 328541.0761743964], + [372006.8622080473, 317548.1874822519], + [361431.1090489363, 307214.6583482181], + [351375.3512342963, 297500.0417482505], + [341818.5605687736, 288290.8146836078], + [332733.0532245997, 279514.9085306150], + [324093.7037712368, 271142.2382222151], + [315886.5202075971, 263137.0922456337], + [308083.5711848162, 255455.7387423653], + [300638.8936687657, 248054.9730184858], + [293514.0231847941, 240906.7840713685], + [286678.3162666094, 234000.8920884140], + [280105.8053098026, 227337.9656272868], + [273779.9299701474, 220924.6697231349], + [267692.6370422462, 214762.0597978434], + [261830.5480127831, 208836.4967571302], + [256175.1067465747, 203129.9815661837], + [250712.3937889622, 197632.0185755453], + [245428.7388293059, 192332.4539171133], + [240307.6065818675, 187215.8986162398], + [235333.9592909218, 182269.1022113096], + [230499.4641994027, 177485.5209777899], + [225801.7440389481, 172858.1314602221], + [221237.5071391375, 168371.8701974018], + [216800.2972684604, 164014.0465864938], + [212483.3834423253, 159784.2402812955], + [208279.6632492336, 155689.9835123091], + [204182.7810738901, 151739.0432714997], + [200188.6093803296, 147933.5910257799], + [196293.6944196968, 144272.5541663492], + [192493.6656577820, 140751.9378731317], + [188780.7511849597, 137354.8473218240], + [185144.1987870764, 134051.9728414823], + [181573.0938655044, 130817.6511674260], + [178058.0277776842, 127641.2648638605], + [174591.9871343204, 124519.0403951609], + [171169.1157925930, 121445.3233489347], + [167781.7025602771, 118413.5467843436], + [164421.7847760531, 115418.2441291455], + [161085.0396378697, 112455.3384346859], + [157769.2891625655, 109521.4745111393], + [154472.2161809770, 106615.7597354628], + [151192.5258020088, 103739.4698629904], + [147931.3508498967, 100895.2678455577], + [144690.3544515687, 98088.0884556055], + [141469.8245022166, 95320.1056676448], + [138268.6696434019, 92590.6009269132], + [135085.1719690827, 89900.9917232744], + [131917.7507755333, 87251.3979226576], + [128766.5144629890, 84641.9949399046], + [125636.3226694767, 82075.3793432334], + [122534.3172564222, 79552.3271020128], + [119463.3901616271, 77072.8121576587], + [116421.2189572915, 74637.5205430319], + [113404.2254825187, 72246.1854639151], + [110412.0809128108, 69898.4312675361], + [107449.0962241230, 67594.1531310737], + [104521.4081457516, 65332.8998165250], + [101632.8831037436, 63114.1074808150], + [98780.0540901720, 60937.8852127141], + [95951.3193269473, 58804.0718192423], + [93133.4563281380, 56711.6169592664], + [90317.1508525112, 54659.2385375795], + [87497.3707836095, 52644.7958738826], + [84671.5134925804, 50666.7196589923], + [81838.4120321607, 48723.9298787723], + [78998.1788291420, 46814.9150281977], + [76151.7720421641, 44938.1913897929], + [73300.1508147420, 43091.7454418124], + [70444.3649643210, 41273.7621766494], + [67586.0293329483, 39482.4046599372], + [64726.8119969440, 37716.1428068200], + [61867.4787108963, 35973.7726191901], + [59007.6041524271, 34254.3086445510], + [56146.6784496655, 32556.7673486818], + [53289.1797431469, 30881.9689798065], + [50448.0862800628, 29234.8543939541], + [47641.9154043454, 27623.4128766666], + [44886.1030126574, 26054.2746929232], + [42190.6715583809, 24531.4084895318], + [39564.4265886064, 23058.4678065517], + [37015.9511024623, 21638.4302071540], + [34553.1037712811, 20273.4132505338], + [32182.1198370205, 18964.9039821012], + [29908.2411404452, 17713.9239779948], + [27735.7278348440, 16520.9715980614], + [25667.9887749863, 15386.0167050665], + [23707.4466465494, 14308.3974172862], + [21854.0204328792, 13287.0263949896], + [20105.3254446556, 12320.7887509956], + [18458.4186701964, 11408.4113920729], + [16910.9383809665, 10548.4505284582], + [15461.2852540738, 9739.0853958929], + [14106.2917705773, 8978.2156142456], + [12841.2000338954, 8263.6863207650], + [11662.0914574339, 7593.3446958257], + [10565.4402469423, 6965.0598252426], + [9547.3826507450, 6376.7411234291], + [8603.8481278586, 5826.1819839018], + [7730.5196648527, 5311.0943176855], + [6923.0981267266, 4829.3352054769], + [6177.5264502428, 4378.7288836488], + [5489.8204373312, 3957.2844933687], + [4856.1147530975, 3562.8244155125], + [4272.7731260571, 3193.5884222296], + [3736.3304136737, 2847.6621257045], + [3243.4489538470, 2523.1510936523], + [2790.9467697307, 2218.4354530348], + [2375.8321115287, 1931.6197047494], + [1995.3102319903, 1661.2631616522], + [1646.7330776064, 1405.4893500521], + [1327.5974710304, 1162.8268353262], + [1035.5643795672, 931.4987834457], + [768.4684992896, 709.6276951403], + [524.2886365124, 495.4401702448], + [301.1405693256, 288.6425768588], + [97.2824446213, 93.6709789802], +] diff --git a/tests/vertical/_hybrid_height_data.py b/tests/vertical/_hybrid_height_data.py new file mode 100644 index 0000000..5b2a19e --- /dev/null +++ b/tests/vertical/_hybrid_height_data.py @@ -0,0 +1,1135 @@ +A = [ + 0.00000000e00, + 2.00036502e00, + 3.10224104e00, + 4.66608381e00, + 6.82797718e00, + 9.74696636e00, + 1.36054239e01, + 1.86089306e01, + 2.49857178e01, + 3.29857101e01, + 4.28792419e01, + 5.49554634e01, + 6.95205765e01, + 8.68958817e01, + 1.07415741e02, + 1.31425507e02, + 1.59279404e02, + 1.91338562e02, + 2.27968948e02, + 2.69539581e02, + 3.16420746e02, + 3.68982361e02, + 4.27592499e02, + 4.92616028e02, + 5.64413452e02, + 6.43339905e02, + 7.29744141e02, + 8.23967834e02, + 9.26344910e02, + 1.03720117e03, + 1.15685364e03, + 1.28561035e03, + 1.42377014e03, + 1.57162292e03, + 1.72944897e03, + 1.89751929e03, + 2.07609595e03, + 2.26543164e03, + 2.46577051e03, + 2.67734814e03, + 2.90039136e03, + 3.13511938e03, + 3.38174365e03, + 3.64046826e03, + 3.91149048e03, + 4.19493066e03, + 4.49081738e03, + 4.79914941e03, + 5.11989502e03, + 5.45299072e03, + 5.79834473e03, + 6.15607422e03, + 6.52694678e03, + 6.91187061e03, + 7.31186914e03, + 7.72741211e03, + 8.15935400e03, + 8.60852539e03, + 9.07640039e03, + 9.56268262e03, + 1.00659785e04, + 1.05846318e04, + 1.11166621e04, + 1.16600674e04, + 1.22115479e04, + 1.27668730e04, + 1.33246689e04, + 1.38813311e04, + 1.44321396e04, + 1.49756152e04, + 1.55082568e04, + 1.60261152e04, + 1.65273223e04, + 1.70087891e04, + 1.74676133e04, + 1.79016211e04, + 1.83084336e04, + 1.86857188e04, + 1.90312891e04, + 1.93435117e04, + 1.96200430e04, + 1.98593906e04, + 2.00599316e04, + 2.02196641e04, + 2.03378633e04, + 2.04123086e04, + 2.04420781e04, + 2.04257188e04, + 2.03618164e04, + 2.02495117e04, + 2.00870859e04, + 1.98740254e04, + 1.96085723e04, + 1.92902266e04, + 1.89174609e04, + 1.84897070e04, + 1.80069258e04, + 1.74718398e04, + 1.68886875e04, + 1.62620469e04, + 1.55966953e04, + 1.48984531e04, + 1.41733242e04, + 1.34277695e04, + 1.26682578e04, + 1.19013398e04, + 1.11333047e04, + 1.03701758e04, + 9.61751562e03, + 8.88045312e03, + 8.16337500e03, + 7.47034375e03, + 6.80442188e03, + 6.16853125e03, + 5.56438281e03, + 4.99379688e03, + 4.45737500e03, + 3.95596094e03, + 3.48923438e03, + 3.05726562e03, + 2.65914062e03, + 2.29424219e03, + 1.96150000e03, + 1.65947656e03, + 1.38754688e03, + 1.14325000e03, + 9.26507812e02, + 7.34992188e02, + 5.68062500e02, + 4.24414062e02, + 3.02476562e02, + 2.02484375e02, + 1.22101562e02, + 6.27812500e01, + 2.28359375e01, + 3.75781298e00, + 0.00000000e00, + 0.00000000e00, +] + + +B = [ + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 0.00000000e00, + 3.81999996e-08, + 6.76070022e-06, + 2.43480008e-05, + 5.89219999e-05, + 1.11914298e-04, + 1.98577400e-04, + 3.40379687e-04, + 5.61555324e-04, + 8.89697927e-04, + 1.35280553e-03, + 1.99183798e-03, + 2.85712420e-03, + 3.97095364e-03, + 5.37781464e-03, + 7.13337678e-03, + 9.26146004e-03, + 1.18060224e-02, + 1.48156285e-02, + 1.83184519e-02, + 2.23548450e-02, + 2.69635208e-02, + 3.21760960e-02, + 3.80263999e-02, + 4.45479602e-02, + 5.17730154e-02, + 5.97284138e-02, + 6.84482530e-02, + 7.79583082e-02, + 8.82857367e-02, + 9.94616672e-02, + 1.11504652e-01, + 1.24448128e-01, + 1.38312891e-01, + 1.53125033e-01, + 1.68910414e-01, + 1.85689449e-01, + 2.03491211e-01, + 2.22332865e-01, + 2.42244005e-01, + 2.63241887e-01, + 2.85354018e-01, + 3.08598459e-01, + 3.32939088e-01, + 3.58254194e-01, + 3.84363323e-01, + 4.11124766e-01, + 4.38391209e-01, + 4.66003299e-01, + 4.93800312e-01, + 5.21619201e-01, + 5.49301147e-01, + 5.76692164e-01, + 6.03648067e-01, + 6.30035818e-01, + 6.55735970e-01, + 6.80643022e-01, + 7.04668999e-01, + 7.27738738e-01, + 7.49796569e-01, + 7.70797551e-01, + 7.90716767e-01, + 8.09536040e-01, + 8.27256083e-01, + 8.43881130e-01, + 8.59431803e-01, + 8.73929262e-01, + 8.87407541e-01, + 8.99900496e-01, + 9.11448181e-01, + 9.22095656e-01, + 9.31880772e-01, + 9.40859556e-01, + 9.49064434e-01, + 9.56549525e-01, + 9.63351727e-01, + 9.69513416e-01, + 9.75078404e-01, + 9.80071604e-01, + 9.84541893e-01, + 9.88499522e-01, + 9.91984010e-01, + 9.95002508e-01, + 9.97630119e-01, + 1.00000000e00, +] + + +# Test case: +# p_surf, t, q values taken form Metview test file tq_ml137.grb +# Point0: value index=200 lat=80 lon=-80 +# Point1: value index=797 lat=35 lon=25 +# h was generated with earthkit-meteo. Values matched equivalent +# Metview computations with mlv_geopotential_on_ml() when it was possible. +t = [ + [197.061752319336, 198.548080444336], + [204.980499267578, 209.801788330078], + [210.628402709961, 216.375473022461], + [214.606170654297, 221.778045654297], + [222.227157592773, 227.379501342773], + [234.219726562500, 233.137695312500], + [251.743606567383, 240.568801879883], + [256.093338012695, 245.757400512695], + [262.551803588867, 249.381881713867], + [271.972915649414, 253.886978149414], + [277.443664550781, 257.221984863281], + [280.396850585938, 260.774780273438], + [280.616287231445, 263.940505981445], + [279.139984130859, 266.393890380859], + [278.734024047852, 266.954727172852], + [278.696929931641, 266.269195556641], + [276.696563720703, 265.671173095703], + [271.736022949219, 264.253601074219], + [267.372299194336, 261.901596069336], + [265.143630981445, 257.960037231445], + [262.150207519531, 253.243957519531], + [258.098114013672, 250.353973388672], + [253.653457641602, 247.917129516602], + [250.388610839844, 244.931579589844], + [247.839462280273, 241.987899780273], + [245.416336059570, 239.334304809570], + [242.929473876953, 237.037872314453], + [241.032058715820, 235.026199340820], + [239.821899414062, 233.101196289062], + [238.880126953125, 231.370361328125], + [238.017883300781, 229.531555175781], + [237.095214843750, 227.615722656250], + [236.335296630859, 225.876312255859], + [235.877059936523, 224.714950561523], + [235.370239257812, 223.813598632812], + [234.628036499023, 222.733505249023], + [233.653625488281, 221.544250488281], + [232.668792724609, 220.621917724609], + [231.848968505859, 220.074554443359], + [231.071792602539, 219.595230102539], + [230.292922973633, 219.003860473633], + [229.618576049805, 218.267990112305], + [229.064376831055, 217.445236206055], + [228.632125854492, 216.670211791992], + [228.302566528320, 215.822097778320], + [228.069229125977, 214.959854125977], + [227.967773437500, 214.237304687500], + [227.929946899414, 213.606704711914], + [227.842422485352, 212.831680297852], + [227.628295898438, 211.760131835938], + [227.305511474609, 210.484222412109], + [227.038101196289, 209.548843383789], + [226.953002929688, 209.235229492188], + [226.938110351562, 209.048461914062], + [226.855163574219, 208.515319824219], + [226.786010742188, 207.528198242188], + [226.847808837891, 206.931793212891], + [226.991699218750, 207.607910156250], + [227.060317993164, 209.412857055664], + [227.075149536133, 211.192337036133], + [227.104812622070, 212.428054809570], + [227.224655151367, 213.318405151367], + [227.504257202148, 214.035507202148], + [227.810195922852, 214.600234985352], + [228.096160888672, 214.974090576172], + [228.383789062500, 215.285156250000], + [228.675186157227, 215.675186157227], + [229.015060424805, 216.012619018555], + [229.456787109375, 216.051513671875], + [229.932479858398, 215.927597045898], + [230.307128906250, 215.950683593750], + [230.641113281250, 216.311035156250], + [231.091354370117, 216.949752807617], + [231.691818237305, 217.776779174805], + [232.371398925781, 218.863586425781], + [233.056488037109, 220.263519287109], + [233.722564697266, 221.892486572266], + [234.310028076172, 223.528778076172], + [234.711273193359, 225.320648193359], + [234.781707763672, 227.535614013672], + [234.563522338867, 229.851608276367], + [234.170364379883, 232.139114379883], + [233.684249877930, 234.406906127930], + [233.201202392578, 236.636749267578], + [232.813186645508, 238.868850708008], + [232.541076660156, 241.108459472656], + [232.344070434570, 243.291336059570], + [232.227661132812, 245.395629882812], + [232.225112915039, 247.383316040039], + [232.380249023438, 249.274780273438], + [232.718414306641, 251.074859619141], + [233.213867187500, 252.858398437500], + [233.878952026367, 254.692428588867], + [234.743560791016, 256.613677978516], + [235.811508178711, 258.644515991211], + [237.067245483398, 260.766464233398], + [238.488983154297, 262.872772216797], + [240.049987792969, 264.857604980469], + [241.645187377930, 266.694015502930], + [243.229858398438, 268.522827148438], + [244.771347045898, 270.365097045898], + [246.254516601562, 272.186157226562], + [247.632736206055, 273.947189331055], + [248.869934082031, 275.565246582031], + [250.018218994141, 277.031890869141], + [251.111923217773, 278.488876342773], + [252.173660278320, 279.921707153320], + [253.163665771484, 281.286712646484], + [254.119857788086, 282.623764038086], + [255.090087890625, 283.935791015625], + [256.114471435547, 285.194549560547], + [257.217590332031, 286.354309082031], + [258.340118408203, 287.410430908203], + [259.397613525391, 288.348785400391], + [260.389266967773, 289.256454467773], + [261.261962890625, 290.092041015625], + [262.396408081055, 290.806564331055], + [263.412155151367, 291.369186401367], + [263.851196289062, 291.749633789062], + [264.159027099609, 291.948089599609], + [264.587783813477, 292.044815063477], + [264.998794555664, 292.084732055664], + [265.422546386719, 292.092468261719], + [265.843902587891, 292.054840087891], + [266.236328125000, 291.953125000000], + [266.585220336914, 291.784439086914], + [266.896438598633, 291.564407348633], + [267.199905395508, 291.414749145508], + [267.500930786133, 291.350540161133], + [267.759307861328, 291.339385986328], + [267.988800048828, 291.412628173828], + [268.201843261719, 291.617858886719], + [268.411499023438, 291.852905273438], + [268.616806030273, 292.105087280273], + [268.759475708008, 292.372756958008], + [268.831634521484, 292.632415771484], + [268.876007080078, 292.848663330078], +] + +q = [ + [0.000003035609, 0.000003089742], + [0.000004254409, 0.000003607198], + [0.000004487337, 0.000003906076], + [0.000004487405, 0.000004193689], + [0.000004519545, 0.000004426762], + [0.000004549432, 0.000004626673], + [0.000004539158, 0.000004651208], + [0.000004477894, 0.000004644950], + [0.000004475017, 0.000004611864], + [0.000004457342, 0.000004546749], + [0.000004474467, 0.000004455549], + [0.000004439458, 0.000004374993], + [0.000004392721, 0.000004326568], + [0.000004342506, 0.000004276382], + [0.000004258998, 0.000004200878], + [0.000004172487, 0.000004131261], + [0.000004091612, 0.000004069901], + [0.000004013895, 0.000003986959], + [0.000003943414, 0.000003849729], + [0.000003886398, 0.000003683660], + [0.000003841842, 0.000003598213], + [0.000003803275, 0.000003593727], + [0.000003772293, 0.000003636379], + [0.000003745957, 0.000003668861], + [0.000003733671, 0.000003676133], + [0.000003729172, 0.000003659352], + [0.000003716489, 0.000003619661], + [0.000003674778, 0.000003580249], + [0.000003633113, 0.000003552582], + [0.000003574329, 0.000003519876], + [0.000003480911, 0.000003463157], + [0.000003429583, 0.000003381474], + [0.000003411095, 0.000003289732], + [0.000003399243, 0.000003224096], + [0.000003379381, 0.000003208396], + [0.000003360993, 0.000003192773], + [0.000003375248, 0.000003127487], + [0.000003414596, 0.000003025013], + [0.000003436033, 0.000002973631], + [0.000003434441, 0.000002939356], + [0.000003427571, 0.000002903557], + [0.000003378042, 0.000002877601], + [0.000003291866, 0.000002864477], + [0.000003226366, 0.000002860909], + [0.000003187182, 0.000002855166], + [0.000003164739, 0.000002829347], + [0.000003146601, 0.000002769240], + [0.000003119122, 0.000002686610], + [0.000003070069, 0.000002608919], + [0.000003003054, 0.000002542865], + [0.000002933458, 0.000002496958], + [0.000002866329, 0.000002467403], + [0.000002803057, 0.000002463866], + [0.000002746278, 0.000002476544], + [0.000002700146, 0.000002498020], + [0.000002667317, 0.000002580675], + [0.000002645638, 0.000002718921], + [0.000002635911, 0.000002772990], + [0.000002636086, 0.000002810709], + [0.000002652562, 0.000002950352], + [0.000002676088, 0.000003047453], + [0.000002687318, 0.000003184295], + [0.000002686704, 0.000003389387], + [0.000002677316, 0.000003682329], + [0.000002656511, 0.000003989932], + [0.000002628736, 0.000004365187], + [0.000002613540, 0.000004988878], + [0.000002614502, 0.000005726051], + [0.000002642549, 0.000006516385], + [0.000002700294, 0.000007433276], + [0.000002766937, 0.000008651965], + [0.000002818644, 0.000010315791], + [0.000002891911, 0.000011622128], + [0.000003072559, 0.000012318730], + [0.000003356793, 0.000012658843], + [0.000003648878, 0.000012705059], + [0.000004079891, 0.000012856675], + [0.000004608142, 0.000027079093], + [0.000005221653, 0.000085583615], + [0.000005814923, 0.000127035870], + [0.000006196136, 0.000134450431], + [0.000006618686, 0.000144037194], + [0.000007400420, 0.000163445380], + [0.000008526978, 0.000202629503], + [0.000010115931, 0.000211818049], + [0.000012272307, 0.000177555987], + [0.000014876769, 0.000115131781], + [0.000018363368, 0.000054006946], + [0.000021883607, 0.000021704793], + [0.000024566684, 0.000001976523], + [0.000025401539, 0.000000546402], + [0.000025756181, 0.000022179903], + [0.000028499228, 0.000133761031], + [0.000037203017, 0.000261912528], + [0.000055203901, 0.000390658842], + [0.000081784757, 0.000574595960], + [0.000123828715, 0.000839084453], + [0.000183075770, 0.001201123103], + [0.000249289908, 0.001706981100], + [0.000318775640, 0.002241740690], + [0.000396023260, 0.002559671865], + [0.000454674230, 0.002906332479], + [0.000499020086, 0.003523359762], + [0.000536213384, 0.004072199331], + [0.000582410302, 0.004511786904], + [0.000618712837, 0.005178945000], + [0.000666628347, 0.005871067510], + [0.000731478201, 0.006091604696], + [0.000802526937, 0.005968819128], + [0.000855932699, 0.005775700079], + [0.000881681905, 0.005719194875], + [0.000906129795, 0.005831380802], + [0.000937452407, 0.006106367202], + [0.000975943349, 0.006464815877], + [0.001021219850, 0.006832434297], + [0.001073074489, 0.007147979885], + [0.001126970384, 0.007366861437], + [0.001178996168, 0.007484690748], + [0.001229773028, 0.007463465197], + [0.001280396962, 0.007331937337], + [0.001330586187, 0.007058830968], + [0.001384307860, 0.006717254637], + [0.001439832526, 0.006730340796], + [0.001489228445, 0.007336682516], + [0.001535531552, 0.007876988919], + [0.001576354058, 0.007926394494], + [0.001609809719, 0.007946021877], + [0.001636048659, 0.007972737654], + [0.001653673333, 0.008007051629], + [0.001665935462, 0.008048400825], + [0.001675108668, 0.008083323237], + [0.001684977791, 0.008104636452], + [0.001694962860, 0.008126065612], + [0.001706524904, 0.008148118074], + [0.001718799390, 0.008168975629], + [0.001731263584, 0.008198129124], + [0.001750893781, 0.008259720991], +] + +z_surf = [5281.125244140625, -829.874755859375] + +p_surf = [95178.337944004423, 102659.810195115380] + +h_geopotential_sea = [ + [81271.214258651569, 79565.223415735949], + [75860.476530385116, 74091.108300972744], + [73296.273590629382, 71461.535746643160], + [70851.789645913101, 68942.555278680607], + [68505.494452328465, 66530.398575327185], + [66209.634472868784, 64214.861793591321], + [63915.749558497060, 61979.855446700894], + [61663.373851389479, 59822.792944058157], + [59496.343483289151, 57754.169216393377], + [57388.542959267259, 55769.902745370520], + [55340.753706104217, 53864.990512644123], + [53372.192217811433, 52036.995468453650], + [51494.855070896294, 50281.004400798724], + [49715.961011807136, 48595.480579537012], + [48029.796640764056, 46983.416051164568], + [46425.296233598441, 45448.609087385288], + [44900.989040569715, 43988.642110163375], + [43464.034802863491, 42600.118820373085], + [42113.923042908646, 41282.416497395796], + [40837.805130344488, 40036.646764005920], + [39627.413788980448, 38863.209864307821], + [38482.313131267794, 37754.748129914580], + [37401.171166794025, 36702.070343726606], + [36378.120969500837, 35701.737890895296], + [35405.689592067894, 34751.380976573084], + [34479.109079667185, 33847.219086558689], + [33595.448159487343, 32985.223985640332], + [32751.196724971720, 32161.727736189496], + [31941.878740811790, 31373.831502608693], + [31163.938373598030, 30619.018698930653], + [30415.078051015575, 29895.278059312899], + [29693.679103777929, 29201.158975871258], + [28998.114069762840, 28534.888280085223], + [28326.357207242414, 27893.889319134723], + [27676.829471755169, 27275.675297661139], + [27048.782673235302, 26678.965709523509], + [26441.746562126402, 26103.045974288874], + [25854.984254626786, 25546.679010096919], + [25287.313325495626, 25008.119538274990], + [24737.567424123372, 24485.985907553000], + [24204.850285894681, 23979.555503933301], + [23688.251570906312, 23488.386988871254], + [23186.784860067844, 23012.032337499651], + [22699.508242753232, 22549.860488161787], + [22225.614861753289, 22101.316061747617], + [21764.482143294681, 21666.039242185459], + [21315.542704066080, 21243.520890610922], + [20878.286218119865, 20833.169922935995], + [20452.395938374826, 20434.688944559337], + [20037.737120464099, 20048.140159222039], + [19634.090544974973, 19673.495618338024], + [19240.623752102831, 19309.741243019187], + [18855.974064975901, 18954.921096557980], + [18478.841024955636, 18607.373848349293], + [18108.232624218861, 18266.333877500478], + [17743.274900897937, 17931.557139545868], + [17382.970460736993, 17602.232662315240], + [17026.298473445986, 17276.232022036958], + [16672.656223007190, 16951.107138209190], + [16321.988673846941, 16625.829615705861], + [15974.367207420519, 16300.847631165801], + [15629.750186582331, 15976.840174874240], + [15287.980922565081, 15654.191511762374], + [14948.964662392309, 15333.130607920111], + [14612.763100274391, 15013.889971052145], + [14279.407346527021, 14696.639498892544], + [13948.889980246706, 14381.351619991461], + [13621.208120635478, 14067.984354002308], + [13296.248619039388, 13756.777845297585], + [12973.888825736245, 13448.049000922601], + [12654.175529464965, 13141.784520398613], + [12337.178428370195, 12837.624838301501], + [12022.803954008557, 12535.131047664952], + [11710.841369998561, 12233.975367302093], + [11401.098881541699, 11933.852480053165], + [11093.479737289901, 11634.380597332914], + [10787.954348839063, 11335.206877508148], + [10484.546366669074, 11036.191481990967], + [10183.381116317518, 10737.244026898152], + [9884.742346755891, 10438.003120425037], + [9588.969323035855, 10138.159806218433], + [9296.291106427736, 9837.706384372406], + [9006.813856310799, 9536.707348391492], + [8720.526513812636, 9235.231413679701], + [8437.308150667857, 8933.335850306086], + [8156.975258971708, 8631.052692647430], + [7879.357511848897, 8328.451117945639], + [7604.314495121554, 8025.647291548432], + [7331.684726968543, 7722.787089571586], + [7061.269053376329, 7420.021764601608], + [6792.833243114885, 7117.485007462771], + [6526.150880078228, 6815.258427797420], + [6261.009018376220, 6513.329821713624], + [5997.175340799580, 6211.626353765881], + [5734.405422217289, 5910.050182290504], + [5472.791855236478, 5608.874745388526], + [5213.100665104814, 5309.189188893762], + [4956.637441786535, 5012.791212637779], + [4704.604205191818, 4721.335335395848], + [4457.891660942335, 4435.956567340425], + [4217.344608542996, 4157.629826436620], + [3983.734928370699, 3887.225673963186], + [3757.759840382695, 3625.493122696314], + [3540.040193854286, 3373.121807631094], + [3331.072392982745, 3130.740044852006], + [3131.204933807690, 2898.765141910747], + [2940.658745871215, 2677.430384893337], + [2759.563106005491, 2466.929239114841], + [2587.960489036986, 2267.373767316057], + [2425.785946732671, 2078.735357462263], + [2272.881942570194, 1900.882094764111], + [2129.019530050581, 1733.621009830222], + [1993.933497996569, 1576.707726850175], + [1867.359091366357, 1429.851932837881], + [1749.017740687474, 1292.707921599549], + [1638.610006555779, 1164.888826647178], + [1535.749140874822, 1046.005603246461], + [1440.039142334800, 935.665451965187], + [1351.224784315902, 833.469358761389], + [1269.030016719818, 739.010041260937], + [1193.056112346782, 651.860350756818], + [1122.894844863328, 571.575074128333], + [1058.168507070143, 497.698236164875], + [998.508238279333, 429.774948858374], + [943.567487301208, 367.383322730179], + [893.017707018935, 310.144000554626], + [846.545765645981, 257.692382876666], + [803.852173786508, 209.661673677558], + [764.648577070634, 165.696742337477], + [728.669384547456, 125.468747434564], + [695.663065709020, 88.673354619419], + [665.394163148830, 55.023621314736], + [637.638487671142, 24.257905722337], + [612.186126841785, -3.858257298169], + [588.844474841866, -29.541317457621], + [567.446175995904, -52.993611656466], + [547.868312285006, -74.407008284675], +] + + +h_geopotential_ground = [ + [80732.689355229857, 79649.847090065043], + [75321.951626963390, 74175.731975301838], + [72757.748687207655, 71546.159420972253], + [70313.264742491374, 69027.178953009701], + [67966.969548906738, 66615.022249656278], + [65671.109569447057, 64299.485467920415], + [63377.224655075333, 62064.479121029981], + [61124.848947967759, 59907.416618387244], + [58957.818579867431, 57838.792890722463], + [56850.018055845532, 55854.526419699614], + [54802.228802682497, 53949.614186973209], + [52833.667314389713, 52121.619142782743], + [50956.330167474567, 50365.628075127810], + [49177.436108385416, 48680.104253866106], + [47491.271737342337, 47068.039725493662], + [45886.771330176722, 45533.232761714375], + [44362.464137147988, 44073.265784492462], + [42925.509899441764, 42684.742494702172], + [41575.398139486926, 41367.040171724890], + [40299.280226922769, 40121.270438335014], + [39088.888885558728, 38947.833538636907], + [37943.788227846075, 37839.371804243674], + [36862.646263372299, 36786.694018055699], + [35839.596066079110, 35786.361565224390], + [34867.164688646175, 34836.004650902170], + [33940.584176245466, 33931.842760887783], + [33056.923256065616, 33069.847659969426], + [32212.671821550000, 32246.351410518586], + [31403.353837390066, 31458.455176937783], + [30625.413470176311, 30703.642373259743], + [29876.553147593855, 29979.901733641989], + [29155.154200356210, 29285.782650200348], + [28459.589166341117, 28619.511954414313], + [27787.832303820691, 27978.512993463813], + [27138.304568333446, 27360.298971990229], + [26510.257769813583, 26763.589383852599], + [25903.221658704679, 26187.669648617964], + [25316.459351205063, 25631.302684426009], + [24748.788422073903, 25092.743212604080], + [24199.042520701649, 24570.609581882090], + [23666.325382472962, 24064.179178262391], + [23149.726667484589, 23573.010663200344], + [22648.259956646125, 23096.656011828742], + [22160.983339331509, 22634.484162490877], + [21687.089958331570, 22185.939736076707], + [21225.957239872958, 21750.662916514550], + [20777.017800644357, 21328.144564940012], + [20339.761314698146, 20917.793597265088], + [19913.871034953103, 20519.312618888427], + [19499.212217042375, 20132.763833551129], + [19095.565641553254, 19758.119292667114], + [18702.098848681107, 19394.364917348277], + [18317.449161554181, 19039.544770887071], + [17940.316121533917, 18691.997522678386], + [17569.707720797142, 18350.957551829568], + [17204.749997476218, 18016.180813874958], + [16844.445557315270, 17686.856336644330], + [16487.773570024267, 17360.855696366049], + [16134.131319585467, 17035.730812538281], + [15783.463770425220, 16710.453290034951], + [15435.842303998797, 16385.471305494892], + [15091.225283160609, 16061.463849203332], + [14749.456019143359, 15738.815186091466], + [14410.439758970588, 15417.754282249201], + [14074.238196852670, 15098.513645381237], + [13740.882443105300, 14781.263173221634], + [13410.365076824984, 14465.975294320551], + [13082.683217213757, 14152.608028331399], + [12757.723715617667, 13841.401519626676], + [12435.363922314522, 13532.672675251693], + [12115.650626043243, 13226.408194727703], + [11798.653524948471, 12922.248512630591], + [11484.279050586834, 12619.754721994042], + [11172.316466576840, 12318.599041631185], + [10862.573978119975, 12018.476154382255], + [10554.954833868180, 11719.004271662005], + [10249.429445417341, 11419.830551837238], + [9946.021463247353, 11120.815156320057], + [9644.856212895796, 10821.867701227242], + [9346.217443334170, 10522.626794754127], + [9050.444419614134, 10222.783480547523], + [8757.766203006015, 9922.330058701496], + [8468.288952889077, 9621.331022720582], + [8182.001610390914, 9319.855088008791], + [7898.783247246136, 9017.959524635178], + [7618.450355549987, 8715.676366976521], + [7340.832608427176, 8413.074792274729], + [7065.789591699832, 8110.270965877522], + [6793.159823546822, 7807.410763900677], + [6522.744149954607, 7504.645438930698], + [6254.308339693163, 7202.108681791861], + [5987.625976656506, 6899.882102126510], + [5722.484114954499, 6597.953496042715], + [5458.650437377858, 6296.250028094971], + [5195.880518795568, 5994.673856619595], + [4934.266951814757, 5693.498419717616], + [4674.575761683092, 5393.812863222853], + [4418.112538364812, 5097.414886966869], + [4166.079301770097, 4805.959009724939], + [3919.366757520614, 4520.580241669516], + [3678.819705121275, 4242.253500765710], + [3445.210024948978, 3971.849348292277], + [3219.234936960973, 3710.116797025404], + [3001.515290432565, 3457.745481960185], + [2792.547489561023, 3215.363719181096], + [2592.680030385969, 2983.388816239838], + [2402.133842449493, 2762.054059222427], + [2221.038202583769, 2551.552913443931], + [2049.435585615265, 2351.997441645148], + [1887.261043310949, 2163.359031791353], + [1734.357039148473, 1985.505769093202], + [1590.494626628859, 1818.244684159312], + [1455.408594574847, 1661.331401179265], + [1328.834187944635, 1514.475607166971], + [1210.492837265752, 1377.331595928639], + [1100.085103134058, 1249.512500976268], + [997.224237453100, 1130.629277575551], + [901.514238913078, 1020.289126294277], + [812.699880894181, 918.093033090479], + [730.505113298097, 823.633715590027], + [654.531208925060, 736.484025085908], + [584.369941441606, 656.198748457424], + [519.643603648422, 582.321910493965], + [459.983334857612, 514.398623187465], + [405.042583879486, 452.006997059270], + [354.492803597213, 394.767674883716], + [308.020862224259, 342.316057205756], + [265.327270364787, 294.285348006648], + [226.123673648912, 250.320416666568], + [190.144481125735, 210.092421763655], + [157.138162287299, 173.297028948510], + [126.869259727108, 139.647295643826], + [99.113584249420, 108.881580051427], + [73.661223420063, 80.765417030922], + [50.319571420145, 55.082356871470], + [28.921272574183, 31.630062672625], + [9.343408863285, 10.216666044415], +] + +h_geometric_sea = [ + [82321.302429680072, 80571.415895498882], + [76774.610767000937, 74962.852325587926], + [74149.306475490841, 72272.161078490069], + [71648.563541891723, 69696.738061396711], + [69250.093634892517, 67232.461308919505], + [66904.908419369080, 64868.665381915402], + [64563.445792918043, 62588.723854522490], + [62266.009896409458, 60389.824511702573], + [60057.174412055872, 58282.490648136823], + [57910.166032481771, 56262.389832109810], + [55825.657634313648, 54324.270237315664], + [53823.071525273132, 52465.507356594098], + [51914.448686380943, 50680.972792456647], + [50106.955503803496, 48968.983286202296], + [48394.621674922018, 47332.460273836659], + [46766.066988126971, 45775.142090482477], + [45219.672930075409, 44294.462740524476], + [43762.579760164335, 42886.874475915909], + [42394.149029085012, 41551.650666970956], + [41101.252724107035, 40289.827048962274], + [39875.428750475017, 39101.722472551330], + [38716.159280049280, 37979.809596917847], + [37622.024597365649, 36914.721103992444], + [36587.023706209635, 35902.923054449602], + [35603.542782422905, 34941.969273792543], + [34666.714553039426, 34027.993147440400], + [33773.535789168774, 33156.884312985305], + [32920.423615119318, 32324.902646085568], + [32102.824853202113, 31529.090144485112], + [31317.121510602665, 30766.878969291123], + [30560.970515963520, 30036.214917304049], + [29832.717133860766, 29335.612451464393], + [29130.700005962106, 28663.262727097004], + [28452.858047286682, 28016.548621815742], + [27797.583091969318, 27392.946409988926], + [27164.106737029346, 26791.151318823308], + [26551.941897813482, 26210.430605275364], + [25960.333475830805, 25649.525760916160], + [25388.078231130439, 25106.667328309541], + [24833.990345128510, 24580.453810938026], + [24297.157288915765, 24070.148931400508], + [23776.653250348136, 23575.300484077427], + [23271.476639663266, 23095.450033366011], + [22780.671553899829, 22629.955291641592], + [22303.418876969168, 22178.250751988133], + [21839.085707364316, 21739.968307751871], + [21387.095232116179, 21314.589900345592], + [20946.928542780723, 20901.515421492524], + [20518.261977396342, 20500.440800438842], + [20100.955325095332, 20111.424126835882], + [19694.783570431373, 19734.432884741153], + [19298.904967439164, 19368.442572756387], + [18911.944921298007, 19011.481791092552], + [18532.592165340586, 18661.876443011453], + [18159.846327275896, 18318.854090645058], + [17792.826252365550, 17982.167195873411], + [17430.527115499175, 17650.998289577306], + [17071.921009163521, 17323.205646767605], + [16716.400813580032, 16996.327073197212], + [16363.910186434676, 16669.328509814422], + [16014.519872014127, 16342.660477373267], + [15668.187017051487, 16017.005288004937], + [15324.753189643581, 15692.748797180469], + [14984.122261370812, 15370.120656069221], + [14646.355319383862, 15049.353983543981], + [14311.482710850139, 14730.618907839245], + [13979.496077243180, 14413.887129076775], + [13650.391630650465, 14099.115889493918], + [13324.054862712053, 13786.545798671470], + [13000.361779136110, 13476.494463785362], + [12679.358555968684, 13168.947799939684], + [12361.114355548121, 12863.544068774285], + [12045.534434898831, 12559.842020330163], + [11732.406495244280, 12257.512129433213], + [11421.537336438227, 11956.247546127619], + [11112.829213773475, 11655.664786024850], + [10806.251815665237, 11355.409559995527], + [10501.828260842962, 11055.341455284866], + [10199.683664227616, 10755.369733795718], + [9900.102014037568, 10455.131793446279], + [9603.422880960343, 10154.317771391163], + [9309.875190430172, 9852.920110810246], + [9019.564542072592, 9551.003668122945], + [8732.478966684805, 9248.637509687742], + [8448.496347968190, 8945.879198434977], + [8167.431881985995, 8642.760971435466], + [7889.114051766960, 8339.352293830934], + [7613.401391630775, 8035.769711166048], + [7340.131375032247, 7732.159507612107], + [7069.103780987562, 7428.673300682509], + [6800.083309304530, 7125.445049129564], + [6532.842574304837, 6822.556468013485], + [6267.167766383081, 6519.995235781561], + [6002.825741768318, 6217.688285681189], + [5739.571306039476, 5915.537526040685], + [5477.496944421706, 5613.816836699418], + [5217.369648873352, 5313.617062740931], + [4960.496568969627, 5016.738310084267], + [4708.080717547937, 4724.836628180427], + [4461.012991632969, 4439.047245886609], + [4220.138069040614, 4160.344714454784], + [3986.227394628686, 3889.598802966760], + [3759.977480397923, 3627.557353041139], + [3542.008236680059, 3374.908586563715], + [3332.814890022339, 3132.279206596741], + [3132.744552804292, 2900.084614839768], + [2942.016641964526, 2678.556015013088], + [2760.758870327121, 2467.884799992007], + [2589.012132464723, 2268.180960717810], + [2426.709893863959, 2079.413806000422], + [2273.693063298474, 1901.449399929006], + [2129.731204254665, 1734.092859109780], + [1994.557712796764, 1577.098016169252], + [1867.906560669044, 1430.172896890642], + [1749.498009573750, 1292.970262334831], + [1639.031547438189, 1165.101848989617], + [1536.119413873978, 1046.177360906011], + [1440.364696720384, 935.802882031908], + [1351.511415940900, 833.578405549560], + [1269.282834192914, 739.095770277378], + [1193.279562088628, 651.927051450896], + [1123.092783926164, 571.626355816168], + [1058.344282668471, 497.737117656269], + [998.664750465698, 429.803941532628], + [943.707248637261, 367.404508327033], + [893.142893606780, 310.159098737989], + [846.658261204682, 257.702805991288], + [803.953607882711, 209.668573361121], + [764.740358048309, 165.701051729664], + [728.752731073541, 125.471218341921], + [695.739032205920, 88.674588772713], + [665.463662398778, 55.024096517409], + [637.702309507748, 24.257998082575], + [612.244955031401, -3.858254961706], + [588.898902310235, -29.541180484771], + [567.496719437890, -52.993170878161], + [547.915428077086, -74.406139325448], +] + + +h_geometric_ground = [ + [81782.732003865967, 80656.038445857834], + [76236.040341186832, 75047.474875946878], + [73610.736049676736, 72356.783628849022], + [71109.993116077618, 69781.360611755663], + [68711.523209078412, 67317.083859278457], + [66366.337993554975, 64953.287932274354], + [64024.875367103930, 62673.346404881442], + [61727.439470595345, 60474.447062061525], + [59518.603986241760, 58367.113198495776], + [57371.595606667659, 56347.012382468762], + [55287.087208499535, 54408.892787674617], + [53284.501099459019, 52550.129906953051], + [51375.878260566831, 50765.595342815599], + [49568.385077989384, 49053.605836561248], + [47856.051249107906, 47417.082824195611], + [46227.496562312859, 45859.764640841429], + [44681.102504261296, 44379.085290883428], + [43224.009334350223, 42971.497026274861], + [41855.578603270900, 41636.273217329908], + [40562.682298292922, 40374.449599321226], + [39336.858324660905, 39186.345022910282], + [38177.588854235168, 38064.432147276799], + [37083.454171551537, 36999.343654351396], + [36048.453280395523, 35987.545604808554], + [35064.972356608792, 35026.591824151496], + [34128.144127225314, 34112.615697799352], + [33234.965363354662, 33241.506863344257], + [32381.853189305206, 32409.525196444520], + [31564.254427388001, 31613.712694844064], + [30778.551084788553, 30851.501519650075], + [30022.400090149407, 30120.837467663001], + [29294.146708046654, 29420.235001823345], + [28592.129580147994, 28747.885277455956], + [27914.287621472569, 28101.171172174694], + [27259.012666155206, 27477.568960347879], + [26625.536311215234, 26875.773869182260], + [26013.371471999370, 26295.053155634316], + [25421.763050016692, 25734.148311275112], + [24849.507805316327, 25191.289878668493], + [24295.419919314398, 24665.076361296979], + [23758.586863101653, 24154.771481759461], + [23238.082824534024, 23659.923034436379], + [22732.906213849154, 23180.072583724963], + [22242.101128085716, 22714.577842000544], + [21764.848451155056, 22262.873302347085], + [21300.515281550204, 21824.590858110823], + [20848.524806302066, 21399.212450704545], + [20408.358116966610, 20986.137971851476], + [19979.691551582229, 20585.063350797795], + [19562.384899281220, 20196.046677194834], + [19156.213144617261, 19819.055435100105], + [18760.334541625052, 19453.065123115339], + [18373.374495483895, 19096.104341451504], + [17994.021739526474, 18746.498993370406], + [17621.275901461784, 18403.476641004010], + [17254.255826551438, 18066.789746232364], + [16891.956689685063, 17735.620839936259], + [16533.350583349409, 17407.828197126557], + [16177.830387765920, 17080.949623556164], + [15825.339760620564, 16753.951060173375], + [15475.949446200015, 16427.283027732217], + [15129.616591237374, 16101.627838363889], + [14786.182763829469, 15777.371347539422], + [14445.551835556700, 15454.743206428173], + [14107.784893569749, 15133.976533902933], + [13772.912285036027, 14815.241458198198], + [13440.925651429068, 14498.509679435727], + [13111.821204836353, 14183.738439852870], + [12785.484436897941, 13871.168349030422], + [12461.791353321998, 13561.117014144314], + [12140.788130154571, 13253.570350298636], + [11822.543929734009, 12948.166619133237], + [11506.964009084719, 12644.464570689115], + [11193.836069430168, 12342.134679792165], + [10882.966910624114, 12040.870096486571], + [10574.258787959363, 11740.287336383803], + [10267.681389851125, 11440.032110354479], + [9963.257835028850, 11139.964005643818], + [9661.113238413503, 10839.992284154670], + [9361.531588223455, 10539.754343805231], + [9064.852455146231, 10238.940321750115], + [8771.304764616059, 9937.542661169198], + [8480.994116258480, 9635.626218481897], + [8193.908540870692, 9333.260060046694], + [7909.925922154078, 9030.501748793929], + [7628.861456171883, 8727.383521794418], + [7350.543625952848, 8423.974844189886], + [7074.830965816663, 8120.392261525000], + [6801.560949218135, 7816.782057971059], + [6530.533355173450, 7513.295851041461], + [6261.512883490418, 7210.067599488516], + [5994.272148490725, 6907.179018372437], + [5728.597340568968, 6604.617786140513], + [5464.255315954206, 6302.310836040141], + [5201.000880225364, 6000.160076399638], + [4938.926518607594, 5698.439387058370], + [4678.799223059240, 5398.239613099883], + [4421.926143155515, 5101.360860443219], + [4169.510291733824, 4809.459178539380], + [3922.442565818857, 4523.669796245561], + [3681.567643226502, 4244.967264813736], + [3447.656968814574, 3974.221353325712], + [3221.407054583811, 3712.179903400091], + [3003.437810865948, 3459.531136922667], + [2794.244464208227, 3216.901756955693], + [2594.174126990180, 2984.707165198719], + [2403.446216150414, 2763.178565372040], + [2222.188444513009, 2552.507350350959], + [2050.441706650612, 2352.803511076762], + [1888.139468049847, 2164.036356359374], + [1735.122637484362, 1986.071950287958], + [1591.160778440554, 1818.715409468732], + [1455.987286982652, 1661.720566528204], + [1329.336134854932, 1514.795447249594], + [1210.927583759638, 1377.592812693783], + [1100.461121624077, 1249.724399348569], + [997.548988059866, 1130.799911264963], + [901.794270906273, 1020.425432390860], + [812.940990126789, 918.200955908511], + [730.712408378802, 823.718320636329], + [654.709136274516, 736.549601809847], + [584.522358112052, 656.248906175120], + [519.773856854359, 582.359668015221], + [460.094324651586, 514.426491891579], + [405.136822823149, 452.027058685985], + [354.572467792668, 394.781649096941], + [308.087835390570, 342.325356350240], + [265.383182068600, 294.291123720073], + [226.169932234197, 250.323602088616], + [190.182305259429, 210.093768700873], + [157.168606391809, 173.297139131665], + [126.893236584666, 139.646646876360], + [99.131883693636, 108.880548441527], + [73.674529217289, 80.764295397246], + [50.328476496123, 55.081369874181], + [28.926293623778, 31.629379480791], + [9.345002262974, 10.216411033503], +] diff --git a/tests/vertical/_monotonic_cases.py b/tests/vertical/_monotonic_cases.py new file mode 100644 index 0000000..511577a --- /dev/null +++ b/tests/vertical/_monotonic_cases.py @@ -0,0 +1,247 @@ +import numpy as np + +# The type of the input data per level is encoded in the test name as three letters with: +# s: scalar +# a: array +# +# So, e.g. "s_a_s" means the following input data on a level: +# - data is scalar +# - coord is array +# - target_coord is scalar + +# "data,coord,target_coord,mode,expected_data", +cases = { + "pressure_s_s_s": [ + ( + [1012.0, 1000.0, 990.0], + [1012.0, 1000.0, 990.0], + [1022.0, 1012.0, 1009.0, 995.0, 990.0, 987.0], + "linear", + [np.nan, 1012.0, 1009, 995, 990.0, np.nan], + ), + ( + [1012.0, np.nan, 990.0], + [1012.0, 1000.0, 990.0], + [1022.0, 1012.0, 1009.0, 995.0, 990.0, 987.0], + "linear", + [np.nan, 1012.0, np.nan, np.nan, np.nan, np.nan], + ), + ( + [1012.0, np.nan, 990.0], + [1012.0, 1000.0, 990.0], + [1022.0, 1012.0, 1009.0, 995.0, 990.0, 987.0], + "linear", + [np.nan, 1012.0, np.nan, np.nan, np.nan, np.nan], + ), + ( + [ + 990.0, + 1000.0, + 1012.0, + ], + [ + 990.0, + 1000.0, + 1012.0, + ], + [1022.0, 1009.0, 995.0, 987.0], + "linear", + [np.nan, 1009, 995, np.nan], + ), + ( + [1012.0, 1000.0, 990.0], + [1012.0, 1000.0, 990.0], + [1022.0, 1009.5, 1009.0, 1002.0, 1000.0, 995.0, 987.0], + "log", + [np.nan, 1009.5117769371, 1009.0133929430, 1002.0099668454, 1000.0, 995.0125628669, np.nan], + ), + ( + [1012.0, 1000.0, 990.0], + [1012.0, 1000.0, 990.0], + [1022.0, 1009.0, 995.0, 992.0, 987.0], + "nearest", + [1012.0, 1012, 1000.0, 990.0, 990.0], + ), + ], + "pressure_a_a_s": [ + ( + [[1020.0, 1010.0, 1000.0], [920.0, 910.0, 900.0], [820, 810.0, 800.0]], + [[1020.0, 1010.0, 1000.0], [920.0, 910.0, 900.0], [820, 810.0, 800.0]], + [1030.0, 1018.0, 1005.0, 950.0, 914.0, 905.0, 850.0, 814.0, 805.0, 790.0], + "linear", + [ + [np.nan, np.nan, np.nan], + [1018.0, np.nan, np.nan], + [1005.0, 1005.0, np.nan], + [950.0, 950.0, 950.0], + [914.0, 914.0, 914.0], + [905.0, 905.0, 905.0], + [850.0, 850.0, 850.0], + [np.nan, 814.0, 814.0], + [np.nan, np.nan, 805.0], + [np.nan, np.nan, np.nan], + ], + ), + ], + "pressure_a_s_s": [ + ( + [[200.0, 210.0, 220.0], [100, 110, 120], [0, 10.0, 20.0]], + [1000.0, 900.0, 800.0], + [1020.0, 1000.0, 960.0, 900.0, 860.0, 800.0, 750.0], + "linear", + [ + [np.nan, np.nan, np.nan], + [200.0, 210.0, 220.0], + [160.0, 170.0, 180.0], + [100.0, 110.0, 120.0], + [60.0, 70.0, 80.0], + [0.0, 10.0, 20.0], + [np.nan, np.nan, np.nan], + ], + ), + ( + [[0, 10.0, 20.0], [100, 110, 120], [200.0, 210.0, 220.0]], + [ + 800.0, + 900.0, + 1000.0, + ], + [1020.0, 1000.0, 960.0, 900.0, 860.0, 800.0, 750.0], + # [1000.0], + "linear", + [ + [np.nan, np.nan, np.nan], + [200.0, 210.0, 220.0], + [160.0, 170.0, 180.0], + [100.0, 110.0, 120.0], + [60.0, 70.0, 80.0], + [0.0, 10.0, 20.0], + [np.nan, np.nan, np.nan], + ], + ), + ], + "pressure_a_s_a": [ + ( + [[200.0, 210.0, 220.0], [100, 110, 120], [0, 10.0, 20.0]], + [1000.0, 900.0, 800.0], + [ + [1030.0, 1020.0, 1010.0], + [1020.0, 1000.0, 1000.0], + [960.0, 900.0, 900.0], + [860.0, 800.0, 800.0], + [750.0, 800.0, 800.0], + [749.0, 750.0, 700], + ], + "linear", + [ + [np.nan, np.nan, np.nan], + [np.nan, 210.0, 220.0], + [160.0, 110.0, 120.0], + [60.0, 10.0, 20.0], + [np.nan, 10.0, 20.0], + [np.nan, np.nan, np.nan], + ], + ), + ( + [[0, 10.0, 20.0], [100, 110, 120], [200.0, 210.0, 220.0]], + [800.0, 900.0, 1000.0], + [ + [1030.0, 1020.0, 1010.0], + [1020.0, 1000.0, 1000.0], + [960.0, 900.0, 900.0], + [860.0, 800.0, 800.0], + [750.0, 800.0, 800.0], + [749.0, 750.0, 700], + ], + "linear", + [ + [np.nan, np.nan, np.nan], + [np.nan, 210.0, 220.0], + [160.0, 110.0, 120.0], + [60.0, 10.0, 20.0], + [np.nan, 10.0, 20.0], + [np.nan, np.nan, np.nan], + ], + ), + ], + "pressure_a_a_a": [ + ( + [[200.0, 210.0, 220.0], [100, 110, 120], [0, 10.0, 20.0]], + [[1020.0, 1010.0, 1000.0], [920.0, 910.0, 900.0], [820, 810.0, 800.0]], + [ + [1030.0, 1020.0, 1010.0], + [1020.0, 1000.0, 1000.0], + [960.0, 900.0, 900.0], + [860.0, 800.0, 800.0], + [750.0, 810.0, 800.0], + [749.0, 750.0, 700], + ], + "linear", + [ + [np.nan, np.nan, np.nan], + [200.0, 200.0, 220.0], + [140.0, 100.0, 120.0], + [40.0, np.nan, 20.0], + [np.nan, 10.0, 20.0], + [np.nan, np.nan, np.nan], + ], + ), + ( + [[0, 10.0, 20.0], [100, 110, 120], [200.0, 210.0, 220.0]], + [[820, 810.0, 800.0], [920.0, 910.0, 900.0], [1020.0, 1010.0, 1000.0]], + [ + [1030.0, 1020.0, 1010.0], + [1020.0, 1000.0, 1000.0], + [960.0, 900.0, 900.0], + [860.0, 800.0, 800.0], + [750.0, 810.0, 800.0], + [749.0, 750.0, 700], + ], + "linear", + [ + [np.nan, np.nan, np.nan], + [200.0, 200.0, 220.0], + [140.0, 100.0, 120.0], + [40.0, np.nan, 20.0], + [np.nan, 10.0, 20.0], + [np.nan, np.nan, np.nan], + ], + ), + ], + "height_s_s_s": [ + ( + [-100.0, 1.0, 100], + [-100.0, 1.0, 100], + [-200.0, -100.0, 0.0, 1.0, 50.0, 100.0, 150.0], + "linear", + [np.nan, -100.0, 0.0, 1.0, 50.0, 100.0, np.nan], + ), + ], + "height_a_a_a": [ + ( + [[200.0, 210.0, 220.0], [100, 110, 120], [0, 10.0, 20.0]], + [[10.0, 20.0, 100.0], [110.0, 120.0, 200.0], [210.0, 220.0, 300.0]], + [ + [-100.0, 1.0, 10.0], + [-100.0, 30.0, 10.0], + [10.0, 20.0, 100.0], + [20.0, 30.0, 110.0], + [120.0, 130.0, 210.0], + [50.0, 130.0, 150.0], + [220.0, 130.0, 320.0], + [220.0, 230.0, 320.0], + ], + "linear", + [ + [np.nan, np.nan, np.nan], + [np.nan, 200.0, np.nan], + [200.0, 210.0, 220.0], + [190.0, 200.0, 210.0], + [90.0, 100.0, 110.0], + [160.0, 100.0, 170.0], + [np.nan, 100.0, np.nan], + [np.nan, np.nan, np.nan], + ], + ), + ], +} diff --git a/tests/vertical/_pl_data.py b/tests/vertical/_pl_data.py new file mode 100644 index 0000000..3ce39f0 --- /dev/null +++ b/tests/vertical/_pl_data.py @@ -0,0 +1,31 @@ +# Test case: +# p_surf, t, q values taken form Metview test file pl_to_hp_input.grib +# Point0: value index=253 lat=20 lon=10 +# Point1: value index=185 lat=40 lon=50 +# +t = [ + [305.56781006, 305.97210693], + [294.28321838, 294.73829651], + [284.20817566, 284.60856628], + [268.92173767, 266.76451111], + [259.13275146, 254.74603271], + [244.0201416, 239.85412598], + [222.13008118, 225.36250305], + [207.503479, 214.89312744], + [194.98274231, 205.90461731], +] + +z = [ + [1003.29638672, 1165.54638672], + [14959.85546875, 15074.60546875], + [31163.421875, 31230.921875], + [57947.11328125, 57874.86328125], + [74884.20703125, 74594.45703125], + [95689.75, 94996.0], + [122837.8125, 121992.8125], + [140559.8125, 140181.8125], + [163883.5, 164539.0], +] +p = [100000.0, 85000.0, 70000.0, 50000.0, 40000.0, 30000.0, 20000.0, 15000.0, 10000.0] +p_surf = [94984.34375, 101686.34375] +z_surf = [5283.36547852, -283.63452148] diff --git a/tests/vertical/_vertical_data.py b/tests/vertical/_vertical_data.py deleted file mode 100644 index afe392e..0000000 --- a/tests/vertical/_vertical_data.py +++ /dev/null @@ -1,1274 +0,0 @@ -A = [ - 0.00000000e00, - 2.00036502e00, - 3.10224104e00, - 4.66608381e00, - 6.82797718e00, - 9.74696636e00, - 1.36054239e01, - 1.86089306e01, - 2.49857178e01, - 3.29857101e01, - 4.28792419e01, - 5.49554634e01, - 6.95205765e01, - 8.68958817e01, - 1.07415741e02, - 1.31425507e02, - 1.59279404e02, - 1.91338562e02, - 2.27968948e02, - 2.69539581e02, - 3.16420746e02, - 3.68982361e02, - 4.27592499e02, - 4.92616028e02, - 5.64413452e02, - 6.43339905e02, - 7.29744141e02, - 8.23967834e02, - 9.26344910e02, - 1.03720117e03, - 1.15685364e03, - 1.28561035e03, - 1.42377014e03, - 1.57162292e03, - 1.72944897e03, - 1.89751929e03, - 2.07609595e03, - 2.26543164e03, - 2.46577051e03, - 2.67734814e03, - 2.90039136e03, - 3.13511938e03, - 3.38174365e03, - 3.64046826e03, - 3.91149048e03, - 4.19493066e03, - 4.49081738e03, - 4.79914941e03, - 5.11989502e03, - 5.45299072e03, - 5.79834473e03, - 6.15607422e03, - 6.52694678e03, - 6.91187061e03, - 7.31186914e03, - 7.72741211e03, - 8.15935400e03, - 8.60852539e03, - 9.07640039e03, - 9.56268262e03, - 1.00659785e04, - 1.05846318e04, - 1.11166621e04, - 1.16600674e04, - 1.22115479e04, - 1.27668730e04, - 1.33246689e04, - 1.38813311e04, - 1.44321396e04, - 1.49756152e04, - 1.55082568e04, - 1.60261152e04, - 1.65273223e04, - 1.70087891e04, - 1.74676133e04, - 1.79016211e04, - 1.83084336e04, - 1.86857188e04, - 1.90312891e04, - 1.93435117e04, - 1.96200430e04, - 1.98593906e04, - 2.00599316e04, - 2.02196641e04, - 2.03378633e04, - 2.04123086e04, - 2.04420781e04, - 2.04257188e04, - 2.03618164e04, - 2.02495117e04, - 2.00870859e04, - 1.98740254e04, - 1.96085723e04, - 1.92902266e04, - 1.89174609e04, - 1.84897070e04, - 1.80069258e04, - 1.74718398e04, - 1.68886875e04, - 1.62620469e04, - 1.55966953e04, - 1.48984531e04, - 1.41733242e04, - 1.34277695e04, - 1.26682578e04, - 1.19013398e04, - 1.11333047e04, - 1.03701758e04, - 9.61751562e03, - 8.88045312e03, - 8.16337500e03, - 7.47034375e03, - 6.80442188e03, - 6.16853125e03, - 5.56438281e03, - 4.99379688e03, - 4.45737500e03, - 3.95596094e03, - 3.48923438e03, - 3.05726562e03, - 2.65914062e03, - 2.29424219e03, - 1.96150000e03, - 1.65947656e03, - 1.38754688e03, - 1.14325000e03, - 9.26507812e02, - 7.34992188e02, - 5.68062500e02, - 4.24414062e02, - 3.02476562e02, - 2.02484375e02, - 1.22101562e02, - 6.27812500e01, - 2.28359375e01, - 3.75781298e00, - 0.00000000e00, - 0.00000000e00, -] - - -B = [ - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 0.00000000e00, - 3.81999996e-08, - 6.76070022e-06, - 2.43480008e-05, - 5.89219999e-05, - 1.11914298e-04, - 1.98577400e-04, - 3.40379687e-04, - 5.61555324e-04, - 8.89697927e-04, - 1.35280553e-03, - 1.99183798e-03, - 2.85712420e-03, - 3.97095364e-03, - 5.37781464e-03, - 7.13337678e-03, - 9.26146004e-03, - 1.18060224e-02, - 1.48156285e-02, - 1.83184519e-02, - 2.23548450e-02, - 2.69635208e-02, - 3.21760960e-02, - 3.80263999e-02, - 4.45479602e-02, - 5.17730154e-02, - 5.97284138e-02, - 6.84482530e-02, - 7.79583082e-02, - 8.82857367e-02, - 9.94616672e-02, - 1.11504652e-01, - 1.24448128e-01, - 1.38312891e-01, - 1.53125033e-01, - 1.68910414e-01, - 1.85689449e-01, - 2.03491211e-01, - 2.22332865e-01, - 2.42244005e-01, - 2.63241887e-01, - 2.85354018e-01, - 3.08598459e-01, - 3.32939088e-01, - 3.58254194e-01, - 3.84363323e-01, - 4.11124766e-01, - 4.38391209e-01, - 4.66003299e-01, - 4.93800312e-01, - 5.21619201e-01, - 5.49301147e-01, - 5.76692164e-01, - 6.03648067e-01, - 6.30035818e-01, - 6.55735970e-01, - 6.80643022e-01, - 7.04668999e-01, - 7.27738738e-01, - 7.49796569e-01, - 7.70797551e-01, - 7.90716767e-01, - 8.09536040e-01, - 8.27256083e-01, - 8.43881130e-01, - 8.59431803e-01, - 8.73929262e-01, - 8.87407541e-01, - 8.99900496e-01, - 9.11448181e-01, - 9.22095656e-01, - 9.31880772e-01, - 9.40859556e-01, - 9.49064434e-01, - 9.56549525e-01, - 9.63351727e-01, - 9.69513416e-01, - 9.75078404e-01, - 9.80071604e-01, - 9.84541893e-01, - 9.88499522e-01, - 9.91984010e-01, - 9.95002508e-01, - 9.97630119e-01, - 1.00000000e00, -] - -# Test case: -# p_surf, t, q values taken form Metview test file t_lnsp_ml137.grb -# Point0: value index=644 lat=50.0 lon=-20 -# Point1: value index=8881 lat=30.0 lon=85 -# z was generated with earthkit-meteo. Values matched equivalent -# Metview computations with mlv_geopotential_on_ml() using 0 surface -# geopotential height. - -p_surf = [101183.94696484, 53169.889084751754] - -p_full = [ - [1.00018251e00, 1.00018251e00], - [2.55130303e00, 2.55130303e00], - [3.88416242e00, 3.88416242e00], - [5.74703050e00, 5.74703050e00], - [8.28747177e00, 8.28747177e00], - [1.16761951e01, 1.16761951e01], - [1.61071772e01, 1.61071772e01], - [2.17973242e01, 2.17973242e01], - [2.89857139e01, 2.89857139e01], - [3.79324760e01, 3.79324760e01], - [4.89173526e01, 4.89173526e01], - [6.22380200e01, 6.22380200e01], - [7.82082291e01, 7.82082291e01], - [9.71558113e01, 9.71558113e01], - [1.19420624e02, 1.19420624e02], - [1.45352456e02, 1.45352456e02], - [1.75308983e02, 1.75308983e02], - [2.09653755e02, 2.09653755e02], - [2.48754265e02, 2.48754265e02], - [2.92980164e02, 2.92980164e02], - [3.42701554e02, 3.42701554e02], - [3.98287430e02, 3.98287430e02], - [4.60104264e02, 4.60104264e02], - [5.28514740e02, 5.28514740e02], - [6.03876679e02, 6.03876679e02], - [6.86542023e02, 6.86542023e02], - [7.76855988e02, 7.76855988e02], - [8.75156372e02, 8.75156372e02], - [9.81773040e02, 9.81773040e02], - [1.09702741e03, 1.09702741e03], - [1.22123200e03, 1.22123200e03], - [1.35469025e03, 1.35469025e03], - [1.49769653e03, 1.49769653e03], - [1.65053595e03, 1.65053595e03], - [1.81348413e03, 1.81348413e03], - [1.98680762e03, 1.98680762e03], - [2.17076379e03, 2.17076379e03], - [2.36560107e03, 2.36560107e03], - [2.57155933e03, 2.57155933e03], - [2.78886975e03, 2.78886975e03], - [3.01775537e03, 3.01775537e03], - [3.25843152e03, 3.25843152e03], - [3.51110595e03, 3.51110595e03], - [3.77597937e03, 3.77597937e03], - [4.05321057e03, 4.05321057e03], - [4.34287402e03, 4.34287402e03], - [4.64498340e03, 4.64498340e03], - [4.95952221e03, 4.95952221e03], - [5.28644287e03, 5.28644287e03], - [5.62566772e03, 5.62566772e03], - [5.97720947e03, 5.97720947e03], - [6.34151050e03, 6.34151050e03], - [6.71940870e03, 6.71940870e03], - [7.11187181e03, 7.11187089e03], - [7.51998459e03, 7.51982137e03], - [7.94495691e03, 7.94421008e03], - [8.38815249e03, 8.38615342e03], - [8.85110584e03, 8.84700456e03], - [9.33524989e03, 9.32779591e03], - [9.84159746e03, 9.82865870e03], - [1.03709358e04, 1.03492830e04], - [1.09240687e04, 1.08892284e04], - [1.15018174e04, 1.14479816e04], - [1.21050198e04, 1.20247248e04], - [1.27345290e04, 1.26181198e04], - [1.33912169e04, 1.32272950e04], - [1.40759726e04, 1.38515365e04], - [1.47897012e04, 1.44893447e04], - [1.55333245e04, 1.51397332e04], - [1.63077815e04, 1.58020139e04], - [1.71140279e04, 1.64749211e04], - [1.79530373e04, 1.71575864e04], - [1.88257981e04, 1.78493530e04], - [1.97333147e04, 1.85493272e04], - [2.06766071e04, 1.92568406e04], - [2.16567102e04, 1.99713568e04], - [2.26746760e04, 2.06923110e04], - [2.37315722e04, 2.14191917e04], - [2.48284777e04, 2.21516597e04], - [2.59664879e04, 2.28893469e04], - [2.71467137e04, 2.36319271e04], - [2.83702754e04, 2.43792498e04], - [2.96383095e04, 2.51310522e04], - [3.09519661e04, 2.58872916e04], - [3.23124027e04, 2.66478775e04], - [3.37207919e04, 2.74126805e04], - [3.51783182e04, 2.81817595e04], - [3.66861764e04, 2.89550621e04], - [3.82455709e04, 2.97326817e04], - [3.98577164e04, 3.05146451e04], - [4.15238360e04, 3.13010651e04], - [4.32451595e04, 3.20920492e04], - [4.50229283e04, 3.28877139e04], - [4.68583932e04, 3.36882355e04], - [4.87528119e04, 3.44937776e04], - [5.07049670e04, 3.53035565e04], - [5.27082150e04, 3.61147179e04], - [5.47507494e04, 3.69227092e04], - [5.68206795e04, 3.77233739e04], - [5.89080608e04, 3.85137062e04], - [6.10026772e04, 3.92908520e04], - [6.30942475e04, 4.00522144e04], - [6.51726239e04, 4.07954183e04], - [6.72279875e04, 4.15183717e04], - [6.92510225e04, 4.22192685e04], - [7.12330639e04, 4.28966018e04], - [7.31662426e04, 4.35491579e04], - [7.50435779e04, 4.41760174e04], - [7.68590349e04, 4.47765458e04], - [7.86075831e04, 4.53503573e04], - [8.02851936e04, 4.58973396e04], - [8.18888099e04, 4.64175770e04], - [8.34163340e04, 4.69113869e04], - [8.48665480e04, 4.73792286e04], - [8.62390374e04, 4.78217220e04], - [8.75341296e04, 4.82396138e04], - [8.87527975e04, 4.86337581e04], - [8.98965604e04, 4.90050775e04], - [9.09674070e04, 4.93545578e04], - [9.19677080e04, 4.96832443e04], - [9.29001322e04, 4.99921764e04], - [9.37675753e04, 5.02824752e04], - [9.45730895e04, 5.05551495e04], - [9.53198380e04, 5.08113722e04], - [9.60110275e04, 5.10520962e04], - [9.66498633e04, 5.12784034e04], - [9.72395209e04, 5.14913914e04], - [9.77831206e04, 5.16919957e04], - [9.82837003e04, 5.18813509e04], - [9.87441831e04, 5.20603110e04], - [9.91673778e04, 5.22300350e04], - [9.95559669e04, 5.23914338e04], - [9.99125004e04, 5.25456380e04], - [1.00239379e05, 5.26938535e04], - [1.00538866e05, 5.28372233e04], - [1.00813096e05, 5.29759068e04], - [1.01064050e05, 5.31068859e04], -] - -p_half = [ - [0.00000000e00, 0.00000000e00], - [2.00036502e00, 2.00036502e00], - [3.10224104e00, 3.10224104e00], - [4.66608381e00, 4.66608381e00], - [6.82797718e00, 6.82797718e00], - [9.74696636e00, 9.74696636e00], - [1.36054239e01, 1.36054239e01], - [1.86089306e01, 1.86089306e01], - [2.49857178e01, 2.49857178e01], - [3.29857101e01, 3.29857101e01], - [4.28792419e01, 4.28792419e01], - [5.49554634e01, 5.49554634e01], - [6.95205765e01, 6.95205765e01], - [8.68958817e01, 8.68958817e01], - [1.07415741e02, 1.07415741e02], - [1.31425507e02, 1.31425507e02], - [1.59279404e02, 1.59279404e02], - [1.91338562e02, 1.91338562e02], - [2.27968948e02, 2.27968948e02], - [2.69539581e02, 2.69539581e02], - [3.16420746e02, 3.16420746e02], - [3.68982361e02, 3.68982361e02], - [4.27592499e02, 4.27592499e02], - [4.92616028e02, 4.92616028e02], - [5.64413452e02, 5.64413452e02], - [6.43339905e02, 6.43339905e02], - [7.29744141e02, 7.29744141e02], - [8.23967834e02, 8.23967834e02], - [9.26344910e02, 9.26344910e02], - [1.03720117e03, 1.03720117e03], - [1.15685364e03, 1.15685364e03], - [1.28561035e03, 1.28561035e03], - [1.42377014e03, 1.42377014e03], - [1.57162292e03, 1.57162292e03], - [1.72944897e03, 1.72944897e03], - [1.89751929e03, 1.89751929e03], - [2.07609595e03, 2.07609595e03], - [2.26543164e03, 2.26543164e03], - [2.46577051e03, 2.46577051e03], - [2.67734814e03, 2.67734814e03], - [2.90039136e03, 2.90039136e03], - [3.13511938e03, 3.13511938e03], - [3.38174365e03, 3.38174365e03], - [3.64046826e03, 3.64046826e03], - [3.91149048e03, 3.91149048e03], - [4.19493066e03, 4.19493066e03], - [4.49081738e03, 4.49081738e03], - [4.79914941e03, 4.79914941e03], - [5.11989502e03, 5.11989502e03], - [5.45299072e03, 5.45299072e03], - [5.79834473e03, 5.79834473e03], - [6.15607422e03, 6.15607422e03], - [6.52694678e03, 6.52694678e03], - [6.91187061e03, 6.91187061e03], - [7.31187301e03, 7.31187117e03], - [7.72809618e03, 7.72777158e03], - [8.16181763e03, 8.16064858e03], - [8.61448735e03, 8.61165827e03], - [9.08772432e03, 9.08235086e03], - [9.58277547e03, 9.57324096e03], - [1.01004195e04, 1.00840765e04], - [1.06414522e04, 1.06144896e04], - [1.12066852e04, 1.11639672e04], - [1.17969496e04, 1.17319959e04], - [1.24130899e04, 1.23174537e04], - [1.30559681e04, 1.29187860e04], - [1.37264657e04, 1.35358041e04], - [1.44254796e04, 1.41672689e04], - [1.51539228e04, 1.48114205e04], - [1.59127263e04, 1.54680460e04], - [1.67028367e04, 1.61359817e04], - [1.75252190e04, 1.68138605e04], - [1.83808556e04, 1.75013124e04], - [1.92707406e04, 1.81973937e04], - [2.01958888e04, 1.89012607e04], - [2.11573255e04, 1.96124206e04], - [2.21560948e04, 2.03302931e04], - [2.31932572e04, 2.10543289e04], - [2.42698871e04, 2.17840546e04], - [2.53870684e04, 2.25192648e04], - [2.65459074e04, 2.32594290e04], - [2.77475199e04, 2.40044252e04], - [2.89930309e04, 2.47540744e04], - [3.02835882e04, 2.55080299e04], - [3.16203441e04, 2.62665533e04], - [3.30044614e04, 2.70292018e04], - [3.44371223e04, 2.77961592e04], - [3.59195140e04, 2.85673598e04], - [3.74528388e04, 2.93427644e04], - [3.90383031e04, 3.01225991e04], - [4.06771298e04, 3.09066910e04], - [4.23705422e04, 3.16954392e04], - [4.41197769e04, 3.24886592e04], - [4.59260797e04, 3.32867685e04], - [4.77907067e04, 3.40897024e04], - [4.97149171e04, 3.48978528e04], - [5.16950168e04, 3.57092602e04], - [5.37214132e04, 3.65201756e04], - [5.57800856e04, 3.73252428e04], - [5.78612734e04, 3.81215051e04], - [5.99548481e04, 3.89059073e04], - [6.20505062e04, 3.96757968e04], - [6.41379888e04, 4.04286320e04], - [6.62072591e04, 4.11622046e04], - [6.82487159e04, 4.18745389e04], - [7.02533291e04, 4.25639982e04], - [7.22127987e04, 4.32292055e04], - [7.41196866e04, 4.38691104e04], - [7.59674692e04, 4.44829244e04], - [7.77506006e04, 4.50701671e04], - [7.94645656e04, 4.56305475e04], - [8.11058216e04, 4.61641317e04], - [8.26717982e04, 4.66710223e04], - [8.41608698e04, 4.71517515e04], - [8.55722262e04, 4.76067056e04], - [8.69058486e04, 4.80367383e04], - [8.81624106e04, 4.84424892e04], - [8.93431844e04, 4.88250270e04], - [9.04499364e04, 4.91851280e04], - [9.14848777e04, 4.95239875e04], - [9.24505382e04, 4.98425011e04], - [9.33497263e04, 5.01418517e04], - [9.41854244e04, 5.04230987e04], - [9.49607545e04, 5.06872003e04], - [9.56789215e04, 5.09355442e04], - [9.63431334e04, 5.11686482e04], - [9.69565932e04, 5.13881585e04], - [9.75224486e04, 5.15946243e04], - [9.80437926e04, 5.17893670e04], - [9.85236081e04, 5.19733349e04], - [9.89647581e04, 5.21472872e04], - [9.93699976e04, 5.23127829e04], - [9.97419362e04, 5.24700848e04], - [1.00083064e05, 5.26211912e04], - [1.00395693e05, 5.27665157e04], - [1.00682039e05, 5.29079308e04], - [1.00944153e05, 5.30438828e04], - [1.01183947e05, 5.31698891e04], -] - -delta = [ - [2.99591477e00, 2.99591477e00], - [4.38795093e-01, 4.38795093e-01], - [4.08195369e-01, 4.08195369e-01], - [3.80708328e-01, 3.80708328e-01], - [3.55927631e-01, 3.55927631e-01], - [3.33512435e-01, 3.33512435e-01], - [3.13173076e-01, 3.13173076e-01], - [2.94662768e-01, 2.94662768e-01], - [2.77770067e-01, 2.77770067e-01], - [2.62313397e-01, 2.62313397e-01], - [2.48135264e-01, 2.48135264e-01], - [2.35099673e-01, 2.35099673e-01], - [2.23087866e-01, 2.23087866e-01], - [2.11996096e-01, 2.11996096e-01], - [2.01733469e-01, 2.01733469e-01], - [1.92219713e-01, 1.92219713e-01], - [1.83384517e-01, 1.83384517e-01], - [1.75164992e-01, 1.75164992e-01], - [1.67505821e-01, 1.67505821e-01], - [1.60357555e-01, 1.60357555e-01], - [1.53676038e-01, 1.53676038e-01], - [1.47421796e-01, 1.47421796e-01], - [1.41559386e-01, 1.41559386e-01], - [1.36057031e-01, 1.36057031e-01], - [1.30886154e-01, 1.30886154e-01], - [1.26020773e-01, 1.26020773e-01], - [1.21437512e-01, 1.21437512e-01], - [1.17115146e-01, 1.17115146e-01], - [1.13034543e-01, 1.13034543e-01], - [1.09178038e-01, 1.09178038e-01], - [1.05529645e-01, 1.05529645e-01], - [1.02074795e-01, 1.02074795e-01], - [9.88004110e-02, 9.88004110e-02], - [9.56940509e-02, 9.56940509e-02], - [9.27445529e-02, 9.27445529e-02], - [8.99417867e-02, 8.99417867e-02], - [8.72761274e-02, 8.72761274e-02], - [8.47390285e-02, 8.47390285e-02], - [8.23224659e-02, 8.23224659e-02], - [8.00188750e-02, 8.00188750e-02], - [7.78215735e-02, 7.78215735e-02], - [7.57241962e-02, 7.57241962e-02], - [7.37208670e-02, 7.37208670e-02], - [7.18061821e-02, 7.18061821e-02], - [6.99583123e-02, 6.99583123e-02], - [6.81579191e-02, 6.81579191e-02], - [6.64039662e-02, 6.64039662e-02], - [6.46952390e-02, 6.46952390e-02], - [6.30302792e-02, 6.30302792e-02], - [6.14082713e-02, 6.14082713e-02], - [5.98667870e-02, 5.98667870e-02], - [5.84999934e-02, 5.84999934e-02], - [5.73010459e-02, 5.73010459e-02], - [5.62591543e-02, 5.62589035e-02], - [5.53630770e-02, 5.53213233e-02], - [5.46043495e-02, 5.45031103e-02], - [5.39784690e-02, 5.37932489e-02], - [5.34791655e-02, 5.32161669e-02], - [5.30427375e-02, 5.26387418e-02], - [5.26096892e-02, 5.19857844e-02], - [5.21800044e-02, 5.12624237e-02], - [5.17535393e-02, 5.04713669e-02], - [5.13304925e-02, 4.96284221e-02], - [5.09105654e-02, 4.86974536e-02], - [5.04938000e-02, 4.76652724e-02], - [5.00804143e-02, 4.66557983e-02], - [4.96702910e-02, 4.55959699e-02], - [4.92633698e-02, 4.44642379e-02], - [4.88597538e-02, 4.33778124e-02], - [4.84593858e-02, 4.22753188e-02], - [4.80623575e-02, 4.11519108e-02], - [4.76687371e-02, 4.00722924e-02], - [4.72782474e-02, 3.90025123e-02], - [4.68911450e-02, 3.79502420e-02], - [4.65071476e-02, 3.69344433e-02], - [4.61264159e-02, 3.59489756e-02], - [4.57489800e-02, 3.49941446e-02], - [4.53747694e-02, 3.40720736e-02], - [4.50035547e-02, 3.31928967e-02], - [4.46356645e-02, 3.23394412e-02], - [4.42708751e-02, 3.15275976e-02], - [4.39090233e-02, 3.07519021e-02], - [4.35504335e-02, 3.00032023e-02], - [4.31947930e-02, 2.93030903e-02], - [4.28420319e-02, 2.86214369e-02], - [4.24923742e-02, 2.79800236e-02], - [4.21455938e-02, 2.73669492e-02], - [4.18017950e-02, 2.67811859e-02], - [4.14607851e-02, 2.62297039e-02], - [4.11227193e-02, 2.56970073e-02], - [4.07873487e-02, 2.52000975e-02], - [4.04547748e-02, 2.47182842e-02], - [4.01250049e-02, 2.42688967e-02], - [3.97980590e-02, 2.38353792e-02], - [3.94738305e-02, 2.34299481e-02], - [3.90563594e-02, 2.29847400e-02], - [3.84502870e-02, 2.24548189e-02], - [3.76052392e-02, 2.18049842e-02], - [3.66313913e-02, 2.11087143e-02], - [3.55434400e-02, 2.03675352e-02], - [3.43569215e-02, 1.95952533e-02], - [3.30881676e-02, 1.87968974e-02], - [3.17532735e-02, 1.79822230e-02], - [3.03685091e-02, 1.71575075e-02], - [2.89490773e-02, 1.63308054e-02], - [2.75096005e-02, 1.55075361e-02], - [2.60638756e-02, 1.46941167e-02], - [2.46240392e-02, 1.38949585e-02], - [2.32010631e-02, 1.31151499e-02], - [2.18049316e-02, 1.23568496e-02], - [2.04435349e-02, 1.16257332e-02], - [1.91237882e-02, 1.09203372e-02], - [1.78515537e-02, 1.02476923e-02], - [1.66306869e-02, 9.60246874e-03], - [1.54645623e-02, 8.99247472e-03], - [1.43553560e-02, 8.41120402e-03], - [1.33042706e-02, 7.86572604e-03], - [1.23115488e-02, 7.34827159e-03], - [1.13771796e-02, 6.86584732e-03], - [1.05000914e-02, 6.41090763e-03], - [9.67915830e-03, 5.98796687e-03], - [8.91250185e-03, 5.59335380e-03], - [8.19825653e-03, 5.22404202e-03], - [7.53432224e-03, 4.88757410e-03], - [6.91810713e-03, 4.56601181e-03], - [6.34726001e-03, 4.28076122e-03], - [5.81920826e-03, 4.00972095e-03], - [5.33164814e-03, 3.76736989e-03], - [4.88195424e-03, 3.54593842e-03], - [4.46761187e-03, 3.34136414e-03], - [4.08642509e-03, 3.16859527e-03], - [3.73598003e-03, 3.00243883e-03], - [3.41427290e-03, 2.87571895e-03], - [3.11882609e-03, 2.75790459e-03], - [2.84810861e-03, 2.67643065e-03], - [2.60000335e-03, 2.56629957e-03], - [2.37269361e-03, 2.37269361e-03], -] - -alpha = [ - [0.69314718, 0.69314718], - [0.20340371, 0.20340371], - [0.1902508, 0.1902508], - [0.178305, 0.178305], - [0.167429, 0.167429], - [0.15750414, 0.15750414], - [0.14842675, 0.14842675], - [0.14010632, 0.14010632], - [0.1324636, 0.1324636], - [0.12542924, 0.12542924], - [0.11894196, 0.11894196], - [0.11294809, 0.11294809], - [0.10740002, 0.10740002], - [0.10225565, 0.10225565], - [0.09747767, 0.09747767], - [0.09303272, 0.09303272], - [0.08889134, 0.08889134], - [0.0850269, 0.0850269], - [0.08141582, 0.08141582], - [0.07803682, 0.07803682], - [0.07487077, 0.07487077], - [0.07190045, 0.07190045], - [0.06911033, 0.06911033], - [0.06648636, 0.06648636], - [0.06401589, 0.06401589], - [0.0616873, 0.0616873], - [0.05949014, 0.05949014], - [0.05741484, 0.05741484], - [0.05545276, 0.05545276], - [0.0535959, 0.0535959], - [0.05183695, 0.05183695], - [0.05016928, 0.05016928], - [0.04858688, 0.04858688], - [0.04708403, 0.04708403], - [0.04565558, 0.04565558], - [0.04429686, 0.04429686], - [0.04300338, 0.04300338], - [0.04177119, 0.04177119], - [0.04059655, 0.04059655], - [0.03947591, 0.03947591], - [0.03840615, 0.03840615], - [0.0373843, 0.0373843], - [0.03640758, 0.03640758], - [0.03547345, 0.03547345], - [0.03457134, 0.03457134], - [0.03369186, 0.03369186], - [0.03283455, 0.03283455], - [0.03199885, 0.03199885], - [0.03118409, 0.03118409], - [0.03038991, 0.03038991], - [0.02963474, 0.02963474], - [0.02896483, 0.02896483], - [0.02837692, 0.02837692], - [0.02786583, 0.02786571], - [0.02742613, 0.02740564], - [0.02705372, 0.02700402], - [0.02674644, 0.02665549], - [0.02650126, 0.0263721], - [0.02628692, 0.02608848], - [0.02607421, 0.02576769], - [0.02586312, 0.02541224], - [0.02565358, 0.02502341], - [0.02544569, 0.02460897], - [0.0252393, 0.02415111], - [0.02503444, 0.02364331], - [0.02483121, 0.02314651], - [0.02462956, 0.02262474], - [0.02442945, 0.02206737], - [0.02423095, 0.02153211], - [0.02403401, 0.02098873], - [0.02383869, 0.02043484], - [0.02364502, 0.01990233], - [0.02345286, 0.01937449], - [0.02326235, 0.01885511], - [0.02307334, 0.01835354], - [0.02288591, 0.0178668], - [0.02270008, 0.01739503], - [0.02251582, 0.0169393], - [0.02233301, 0.01650464], - [0.02215181, 0.01608257], - [0.02197212, 0.01568097], - [0.02179385, 0.01529715], - [0.02161717, 0.01492659], - [0.02144192, 0.01457999], - [0.02126807, 0.01424245], - [0.02109572, 0.01392477], - [0.02092478, 0.01362106], - [0.02075529, 0.01333082], - [0.02058715, 0.01305752], - [0.02042044, 0.01279348], - [0.02025504, 0.01254713], - [0.02009101, 0.01230823], - [0.01992834, 0.01208537], - [0.01976704, 0.01187035], - [0.01960707, 0.01166923], - [0.01940107, 0.01144835], - [0.01910194, 0.01118539], - [0.01868478, 0.01086287], - [0.01820388, 0.01051723], - [0.01766644, 0.0101492], - [0.0170801, 0.00976563], - [0.01645285, 0.00936901], - [0.01579262, 0.00896416], - [0.0151074, 0.00855422], - [0.0144047, 0.00814318], - [0.01369174, 0.00773373], - [0.01297533, 0.00732907], - [0.01226149, 0.00693139], - [0.01155567, 0.00654324], - [0.01086284, 0.0061657], - [0.01018694, 0.0058016], - [0.00953142, 0.00545023], - [0.00889922, 0.00511509], - [0.0082923, 0.00479355], - [0.00771235, 0.0044895], - [0.00716051, 0.00419971], - [0.00663739, 0.00392771], - [0.00614314, 0.00366964], - [0.0056778, 0.003429], - [0.00524086, 0.00320203], - [0.00483177, 0.002991], - [0.00444963, 0.00279407], - [0.00409353, 0.00260975], - [0.00376243, 0.0024418], - [0.00345507, 0.00228127], - [0.00317027, 0.00213885], - [0.00290678, 0.00200352], - [0.00266346, 0.0018825], - [0.00243899, 0.00177192], - [0.00223214, 0.00166975], - [0.00204182, 0.00158346], - [0.00186683, 0.00150047], - [0.00170617, 0.00143717], - [0.0015586, 0.00137832], - [0.00142338, 0.00133762], - [0.00129944, 0.0012826], - [0.00118588, 0.00118588], -] - - -t = [ - [199.78929138, 203.77757263], - [209.54397583, 209.23440552], - [215.42625427, 213.40086365], - [220.50753784, 218.10128784], - [225.61582947, 223.14024353], - [232.11523438, 228.22460938], - [241.98384094, 235.28364563], - [249.85993958, 240.85603333], - [255.94047546, 247.1426239], - [261.79225159, 254.94459534], - [266.44659424, 261.02862549], - [269.80895996, 265.64880371], - [271.38972473, 266.99128723], - [270.98959351, 265.86264038], - [268.9801178, 265.0660553], - [266.35513306, 264.21060181], - [264.7883606, 264.44656372], - [262.9664917, 264.33953857], - [260.62815857, 262.35667419], - [259.09480286, 259.76863098], - [258.02911377, 256.80450439], - [256.43600464, 252.47897339], - [254.02259827, 246.3028717], - [251.17669678, 241.76361084], - [248.12364197, 239.37657166], - [245.0716095, 237.01106262], - [241.78787231, 234.81814575], - [237.96272278, 233.11897278], - [234.79064941, 232.26428223], - [232.34790039, 232.11645508], - [230.33331299, 232.07745361], - [228.74267578, 231.71142578], - [227.43002319, 230.73666382], - [226.01573181, 228.97569275], - [224.33898926, 226.95129395], - [222.99913025, 225.23057556], - [221.87628174, 223.66241455], - [220.82601929, 221.89633179], - [220.05307007, 220.30990601], - [219.6811676, 219.03077698], - [219.55952454, 217.83784485], - [219.32658386, 216.42521667], - [218.83781433, 215.17863464], - [218.3313446, 214.52177429], - [217.94612122, 213.95002747], - [217.71180725, 212.91590881], - [217.72558594, 211.1796875], - [217.85084534, 208.8752594], - [218.0191803, 206.3941803], - [218.24353027, 203.66442871], - [218.48812866, 200.94711304], - [218.70314026, 199.05274963], - [218.97155762, 198.46179199], - [219.29553223, 198.47424316], - [219.57293701, 198.26141357], - [219.67663574, 197.93640137], - [219.62417603, 197.60269165], - [219.66845703, 197.22314453], - [219.88063049, 197.10621643], - [220.22846985, 197.54878235], - [220.71125793, 198.45149231], - [221.35551453, 199.59281921], - [222.0042572, 200.85679626], - [222.4879303, 202.2769928], - [222.89889526, 203.72409058], - [223.328125, 205.27246094], - [223.87538147, 207.09120178], - [224.53361511, 209.14152527], - [225.2956543, 211.24975586], - [225.93247986, 213.34751892], - [225.98681641, 215.43310547], - [225.56298828, 217.49853516], - [225.15385437, 219.52690125], - [225.02775574, 221.53947449], - [225.11260986, 223.52374268], - [225.00570679, 225.42367554], - [224.40518188, 227.25869751], - [223.31588745, 229.04049683], - [222.05892944, 230.67514038], - [221.43502808, 232.13815308], - [221.9316864, 233.48344421], - [223.36567688, 234.74751282], - [225.34831238, 236.03581238], - [227.61721802, 237.32913208], - [230.06221008, 238.57685852], - [232.56451416, 239.76177979], - [235.03742981, 240.90950012], - [237.43273926, 242.08410645], - [239.79444885, 243.30030823], - [242.05895996, 244.57165527], - [244.19204712, 245.84341431], - [246.19921875, 247.109375], - [248.14360046, 248.33891296], - [250.22305298, 249.51211548], - [252.3525238, 250.68650818], - [254.34068298, 251.85142517], - [256.15499878, 252.99093628], - [257.79119873, 254.08514404], - [259.3658905, 255.14811707], - [260.95251465, 256.1673584], - [262.5565033, 257.07798767], - [263.99475098, 257.93225098], - [265.31828308, 258.78312683], - [266.55743408, 259.59844971], - [267.62954712, 260.38931274], - [268.55137634, 261.14122009], - [269.22834778, 261.8865509], - [269.66952515, 262.68515015], - [270.20774841, 263.51634216], - [270.96899414, 264.27954102], - [271.88400269, 264.98361206], - [272.49493408, 265.59063721], - [272.83621216, 266.21316528], - [273.56558228, 266.8644104], - [274.46348572, 267.49278259], - [275.20922852, 268.09594727], - [275.87101746, 268.60734558], - [276.49809265, 269.04301453], - [277.09143066, 269.47424316], - [277.65512085, 269.9012146], - [278.24208069, 270.27333069], - [278.84449768, 270.61988831], - [279.46356201, 270.97723389], - [280.06265259, 271.30288696], - [280.60742188, 271.609375], - [281.11647034, 271.88600159], - [281.60151672, 272.14253235], - [282.06123352, 272.3874054], - [282.47163391, 272.62397766], - [282.83157349, 272.85305786], - [283.16653442, 273.08059692], - [283.48504639, 273.29168701], - [283.77868652, 273.4954834], - [284.04649353, 273.69688416], - [284.30439758, 273.90986633], - [284.53866577, 274.1265564], - [284.78421021, 274.35256958], -] - -q = [ - [3.01709883e-06, 3.00801844e-06], - [3.84387056e-06, 3.47529965e-06], - [4.34961817e-06, 3.78843811e-06], - [4.58298211e-06, 4.10096447e-06], - [4.58293300e-06, 4.45406124e-06], - [4.58866350e-06, 4.64279663e-06], - [4.60318688e-06, 4.64550385e-06], - [4.60787214e-06, 4.64105051e-06], - [4.60761453e-06, 4.61535615e-06], - [4.59866988e-06, 4.55425743e-06], - [4.57711599e-06, 4.45467617e-06], - [4.53649000e-06, 4.32138359e-06], - [4.47918819e-06, 4.16181092e-06], - [4.41289330e-06, 3.99970622e-06], - [4.37053859e-06, 3.87450746e-06], - [4.33979017e-06, 3.79537391e-06], - [4.28324665e-06, 3.73295143e-06], - [4.21928053e-06, 3.67246321e-06], - [4.16876492e-06, 3.61666525e-06], - [4.11070073e-06, 3.55400266e-06], - [4.03302465e-06, 3.51302651e-06], - [3.95065649e-06, 3.48921526e-06], - [3.86138026e-06, 3.46655770e-06], - [3.79883841e-06, 3.42744443e-06], - [3.80090069e-06, 3.39888948e-06], - [3.89090224e-06, 3.37730694e-06], - [4.00592694e-06, 3.33389039e-06], - [4.05859964e-06, 3.28507804e-06], - [4.05837773e-06, 3.26067084e-06], - [4.02113096e-06, 3.24958842e-06], - [3.94500034e-06, 3.23908694e-06], - [3.84701934e-06, 3.23749782e-06], - [3.74084152e-06, 3.24569805e-06], - [3.60681111e-06, 3.23803647e-06], - [3.47068021e-06, 3.19538708e-06], - [3.36873427e-06, 3.14815634e-06], - [3.28857686e-06, 3.10237056e-06], - [3.23770337e-06, 3.05647382e-06], - [3.19796345e-06, 3.00596548e-06], - [3.15370562e-06, 2.95978680e-06], - [3.09788288e-06, 2.91511083e-06], - [3.02859212e-06, 2.86412637e-06], - [2.95260304e-06, 2.81302107e-06], - [2.90156686e-06, 2.79586175e-06], - [2.88648130e-06, 2.79483334e-06], - [2.88630281e-06, 2.78819380e-06], - [2.87791408e-06, 2.75902494e-06], - [2.82208839e-06, 2.70445071e-06], - [2.72617797e-06, 2.64119478e-06], - [2.64021696e-06, 2.59429112e-06], - [2.60074262e-06, 2.57514580e-06], - [2.59540116e-06, 2.58525847e-06], - [2.59828244e-06, 2.60062529e-06], - [2.60247612e-06, 2.59450167e-06], - [2.61033165e-06, 2.53349754e-06], - [2.62331196e-06, 2.52601785e-06], - [2.63964239e-06, 2.51699885e-06], - [2.65290794e-06, 2.32904051e-06], - [2.66321103e-06, 2.11617544e-06], - [2.66851043e-06, 1.93777146e-06], - [2.65594792e-06, 1.82194856e-06], - [2.64366190e-06, 1.82584427e-06], - [2.64526034e-06, 1.92744346e-06], - [2.68849180e-06, 1.98336420e-06], - [2.77688434e-06, 1.98828695e-06], - [2.75469779e-06, 1.98589100e-06], - [2.73670707e-06, 1.98838939e-06], - [2.74721538e-06, 1.99377541e-06], - [2.79482015e-06, 2.05302172e-06], - [3.53010273e-06, 2.33614719e-06], - [5.57021826e-06, 2.94668257e-06], - [7.29458009e-06, 3.76114224e-06], - [8.37367526e-06, 4.38575199e-06], - [9.58809187e-06, 4.54404881e-06], - [1.11091222e-05, 4.53771008e-06], - [1.32116986e-05, 4.52804687e-06], - [1.50918495e-05, 4.54927795e-06], - [1.69239520e-05, 4.75715387e-06], - [2.11658953e-05, 5.53457721e-06], - [2.74663107e-05, 7.26033613e-06], - [3.30927323e-05, 1.05025720e-05], - [3.94161415e-05, 1.44566965e-05], - [4.37294509e-05, 1.72649886e-05], - [4.11307184e-05, 1.77955000e-05], - [3.94712188e-05, 1.52419307e-05], - [6.06414760e-05, 1.52227367e-05], - [7.96968197e-05, 3.11190342e-05], - [8.42861052e-05, 6.19343634e-05], - [9.09653904e-05, 8.87600185e-05], - [9.04298161e-05, 1.10158954e-04], - [7.83304636e-05, 3.09775299e-04], - [1.50449098e-04, 5.85563005e-04], - [7.37198455e-04, 8.20525748e-04], - [1.02437514e-03, 9.55472175e-04], - [1.19246052e-03, 9.92904173e-04], - [1.23465780e-03, 1.00827935e-03], - [9.50902766e-04, 1.02147467e-03], - [7.25478037e-04, 1.07917200e-03], - [6.10613264e-04, 1.18174497e-03], - [5.39789663e-04, 1.30034493e-03], - [3.69320379e-04, 1.43409775e-03], - [2.71807180e-04, 1.59932183e-03], - [2.82536016e-04, 1.78314255e-03], - [3.23305593e-04, 1.93739937e-03], - [4.17901482e-04, 2.05369135e-03], - [5.53862984e-04, 2.15555900e-03], - [5.54333196e-04, 2.28262948e-03], - [7.06444250e-04, 2.44499253e-03], - [1.01638840e-03, 2.62237595e-03], - [1.34254502e-03, 2.80452775e-03], - [1.56832741e-03, 2.99550103e-03], - [2.90717693e-03, 3.19256397e-03], - [3.79513750e-03, 3.36884508e-03], - [3.98430898e-03, 3.51367071e-03], - [4.16882193e-03, 3.63142645e-03], - [4.42667022e-03, 3.72381225e-03], - [4.60835848e-03, 3.79201326e-03], - [4.59124287e-03, 3.84260853e-03], - [4.51994942e-03, 3.88575600e-03], - [5.04645684e-03, 3.93113472e-03], - [5.33124994e-03, 3.97464823e-03], - [5.37591171e-03, 4.01740265e-03], - [5.39185889e-03, 4.06625159e-03], - [5.40263100e-03, 4.12518426e-03], - [5.41078714e-03, 4.20057443e-03], - [5.41918472e-03, 4.28669646e-03], - [5.42784484e-03, 4.38118728e-03], - [5.43691765e-03, 4.47752128e-03], - [5.44691293e-03, 4.57763879e-03], - [5.45726771e-03, 4.67668528e-03], - [5.46930194e-03, 4.75738406e-03], - [5.48250892e-03, 4.80826118e-03], - [5.49916971e-03, 4.82301462e-03], - [5.52026850e-03, 4.83362299e-03], - [5.54970912e-03, 4.84303645e-03], - [5.59888977e-03, 4.85454697e-03], - [5.71770210e-03, 4.87179298e-03], -] - -z = [ - [785674.6612236819, 726683.7057534188], - [731762.3810455083, 671998.7789002417], - [706049.5171162597, 646430.7128622419], - [681472.4102107972, 622104.0352160149], - [657977.6934499493, 598866.4401671934], - [635406.4595080535, 576610.7628640280], - [613467.8436863291, 555164.9208202510], - [592071.9785725885, 534454.0102955891], - [571347.1394398381, 514458.2727056434], - [551328.1116609386, 495042.8560979214], - [532020.5604846401, 476182.7934817391], - [513462.3085516871, 457955.4074888971], - [495701.6180886799, 440475.6738432529], - [478797.8378726421, 423869.0124875852], - [462793.3688745802, 408132.1810989811], - [447682.9311579202, 393192.3318620790], - [433387.1955010486, 378963.1441496128], - [419826.2621640509, 365375.4093397735], - [406966.9009705349, 352439.8224686406], - [394753.0168531410, 340169.6216483501], - [383111.8594908047, 328541.0764334155], - [372006.8618428722, 317548.1871205007], - [361431.1091246749, 307214.6584193769], - [351375.3508619350, 297500.0413874307], - [341818.5608774458, 288290.8149790115], - [332733.0531919105, 279514.9084965917], - [324093.7040650467, 271142.2385051934], - [315886.5203255818, 263137.0923590701], - [308083.5708624303, 255455.7384218084], - [300638.8939097274, 248054.9732578196], - [293514.0230002193, 240906.7838842870], - [286678.3165232550, 234000.8923472781], - [280105.8054802197, 227337.9657990377], - [273779.9300358224, 220924.6697885553], - [267692.6368564289, 214762.0596087771], - [261830.5482186833, 208836.4969640386], - [256175.1065056970, 203129.9813223545], - [250712.3935619987, 197632.0183465342], - [245428.7389948524, 192332.4540819800], - [240307.6066399635, 187215.8986733744], - [235333.9590212574, 182269.1019430693], - [230499.4643707655, 177485.5211463044], - [225801.7442288200, 172858.1316464086], - [221237.5071217919, 168371.8701798629], - [216800.2971448229, 164014.0464646454], - [212483.3831876001, 159784.2400317785], - [208279.6630882416, 155689.9833559169], - [204182.7808252430, 151739.0430330823], - [200188.6091815423, 147933.5908378231], - [196293.6946028431, 144272.5543377650], - [192493.6655526025, 140751.9377771735], - [188780.7514877329, 137354.8475983641], - [185144.1987821921, 134051.9728381037], - [181573.0936717299, 130817.6511697433], - [178058.0278609738, 127641.2650369350], - [174591.9873122303, 124519.0405111639], - [171169.1158128520, 121445.3231811556], - [167781.7026435942, 118413.5469226445], - [164421.7848783667, 115418.2442334271], - [161085.0398541070, 112455.3383293370], - [157769.2894109111, 109521.4748096895], - [154472.2163791546, 106615.7596059051], - [151192.5259752163, 103739.4698189763], - [147931.3507743545, 100895.2676094785], - [144690.3544462335, 98088.0883697492], - [141469.8244006692, 95320.1057577650], - [138268.6697215000, 92590.6008512066], - [135085.1717895322, 89900.9918207310], - [131917.7511062246, 87251.3980451575], - [128766.5146194461, 84641.9949409411], - [125636.3228843514, 82075.3796038278], - [122534.3174903572, 79552.3268654381], - [119463.3901043265, 77072.8119880158], - [116421.2191325827, 74637.5208486200], - [113404.2256707928, 72246.1851753334], - [110412.0809072335, 69898.4315457866], - [107449.0961155454, 67594.1534636208], - [104521.4082878113, 65332.9000660542], - [101632.8833534845, 63114.1077650956], - [98780.0541612838, 60937.8853120890], - [95951.3195383330, 58804.0719928073], - [93133.4563548090, 56711.6172684407], - [90317.1509762744, 54659.2388076902], - [87497.3708822780, 52644.7958796665], - [84671.5136911559, 50666.7194181534], - [81838.4117319603, 48723.9297261154], - [78998.1788224118, 46814.9148470528], - [76151.7723418826, 44938.1911034236], - [73300.1510484022, 43091.7454961447], - [70444.3649567842, 41273.7624558410], - [67586.0290587829, 39482.4047569903], - [64726.8121090873, 37716.1430745909], - [61867.4788665567, 35973.7728383058], - [59007.6039930019, 34254.3089291265], - [56146.6784699244, 32556.7675409044], - [53289.1800337491, 30881.9693247492], - [50448.0859692376, 29234.8542970939], - [47641.9157059339, 27623.4128230908], - [44886.1032861522, 26054.2750128437], - [42190.6712639191, 24531.4086487689], - [39564.4268953333, 23058.4678998223], - [37015.9511303008, 21638.4305823104], - [34553.1041267112, 20273.4128981098], - [32182.1197195201, 18964.9038373732], - [29908.2409916448, 17713.9241321174], - [27735.7281481446, 16520.9717826059], - [25667.9889498665, 15386.0170782985], - [23707.4465506416, 14308.3974291439], - [21854.0201038766, 13287.0263310572], - [20105.3250834606, 12320.7887324085], - [18458.4187295160, 11408.4111447664], - [16910.9385814546, 10548.4504867572], - [15461.2852350823, 9739.0850400824], - [14106.2921686912, 8978.2155998765], - [12841.1999040790, 8263.6864356073], - [11662.0918737629, 7593.3449944774], - [10565.4406642674, 6965.0600518035], - [9547.3824083473, 6376.7414440159], - [8603.8479008608, 5826.1823577187], - [7730.5198370129, 5311.0944179400], - [6923.0979867238, 4829.3355648431], - [6177.5263460267, 4378.7289086158], - [5489.8206684713, 3957.2847507338], - [4856.1147153050, 3562.8247044165], - [4272.7735221519, 3193.5885465313], - [3736.3302024422, 2847.6618573126], - [3243.4487860329, 2523.1510403915], - [2790.9471670265, 2218.4352828992], - [2375.8320342832, 1931.6195956655], - [1995.3100267503, 1661.2630257632], - [1646.7330034190, 1405.4892714357], - [1327.5977290590, 1162.8269745909], - [1035.5647931545, 931.4987614010], - [768.4683004971, 709.6278157027], - [524.2887806395, 495.4402948428], - [301.1407094737, 288.6424987259], - [97.2826332998, 93.6711606543], -] diff --git a/tests/vertical/test_array_interpolate.py b/tests/vertical/test_array_interpolate.py new file mode 100644 index 0000000..12a4e0c --- /dev/null +++ b/tests/vertical/test_array_interpolate.py @@ -0,0 +1,661 @@ +# (C) Copyright 2021 ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. +# + +from importlib import import_module + +import numpy as np +import pytest +from earthkit.utils.array.namespace import _NUMPY_NAMESPACE +from earthkit.utils.array.testing import NAMESPACE_DEVICES + +from earthkit.meteo import vertical +from earthkit.meteo.utils.testing import Tolerance + +np.set_printoptions(formatter={"float_kind": "{:.10f}".format}) + +NUMPY = [x for x in NAMESPACE_DEVICES if x[0]._earthkit_array_namespace_name == "numpy"] + + +# The type of the input data per level is encoded in the test name as three letters with: +# s: scalar +# a: array +# +# So, e.g. "s_a_s" means the following input data on a level: +# - data is scalar +# - coord is array +# - target_coord is scalar + + +def _get_data_1(): + import os + import sys + + here = os.path.dirname(__file__) + sys.path.insert(0, here) + + from _monotonic_cases import cases + + return cases + + +DATA = _get_data_1() + + +def _get_data_2(name): + import os + import sys + + here = os.path.dirname(__file__) + sys.path.insert(0, here) + + return import_module(name) + + +DATA_HYBRID_CORE = _get_data_2("_hybrid_core_data") +DATA_HYBRID_H = _get_data_2("_hybrid_height_data") +DATA_PL = _get_data_2("_pl_data") + + +def _check_array_interpolate_monotonic(data, coord, target_coord, mode, expected_data, xp, device): + """Test to_pressure with scalar value, scalar pres, scalar target""" + data = xp.asarray(data, device=device) + coord = xp.asarray(coord, device=device) + target_coord = xp.asarray(target_coord, device=device) + expected_data = xp.asarray(expected_data, device=device) + + r = vertical.interpolate_monotonic(data, coord, target_coord, mode) + assert xp.allclose(r, expected_data, equal_nan=True) + + +# @pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) +@pytest.mark.parametrize("xp, device", NUMPY) +@pytest.mark.parametrize( + "data,coord,target_coord,mode,expected_data", + DATA["pressure_s_s_s"], +) +def test_array_interpolate_monotonic_s_s_s_1(data, coord, target_coord, mode, expected_data, xp, device): + """Test with scalar data, scalar coord, scalar target_coord""" + _check_array_interpolate_monotonic(data, coord, target_coord, mode, expected_data, xp, device) + + +@pytest.mark.parametrize("xp, device", NUMPY) +@pytest.mark.parametrize( + "data,coord,target_coord,mode,expected_data", + DATA["height_s_s_s"], +) +def test_array_interpolate_monotonic_s_s_s_2(data, coord, target_coord, mode, expected_data, xp, device): + """Test with scalar data, scalar coord, scalar target_coord""" + _check_array_interpolate_monotonic(data, coord, target_coord, mode, expected_data, xp, device) + + +@pytest.mark.parametrize("xp, device", NUMPY) +@pytest.mark.parametrize( + "data,coord,target_coord,mode,expected_data", + DATA["pressure_a_a_s"], +) +def test_array_interpolate_monotonic_a_a_s(data, coord, target_coord, mode, expected_data, xp, device): + """Test with array data, array coord, scalar target_coord""" + _check_array_interpolate_monotonic(data, coord, target_coord, mode, expected_data, xp, device) + + +@pytest.mark.parametrize("xp, device", NUMPY) +@pytest.mark.parametrize( + "data,coord,target_coord,mode,expected_data", + DATA["pressure_a_s_s"], +) +def test_array_interpolate_monotonic_a_s_s(data, coord, target_coord, mode, expected_data, xp, device): + """Test with array data, scalar coord, scalar target_coord""" + _check_array_interpolate_monotonic(data, coord, target_coord, mode, expected_data, xp, device) + + +@pytest.mark.parametrize("xp, device", NUMPY) +@pytest.mark.parametrize( + "data,coord,target_coord,mode,expected_data", + DATA["pressure_a_s_a"], +) +def test_array_interpolate_monotonic_a_s_a(data, coord, target_coord, mode, expected_data, xp, device): + """Test with array data, scalar coord, array target_coord""" + _check_array_interpolate_monotonic(data, coord, target_coord, mode, expected_data, xp, device) + + +@pytest.mark.parametrize("xp, device", NUMPY) +@pytest.mark.parametrize( + "data,coord,target_coord,mode,expected_data", + DATA["pressure_a_a_a"], +) +def test_array_interpolate_monotonic_a_a_a_1(data, coord, target_coord, mode, expected_data, xp, device): + """Test with array data, array coord, array target_coord""" + _check_array_interpolate_monotonic(data, coord, target_coord, mode, expected_data, xp, device) + + +@pytest.mark.parametrize("xp, device", NUMPY) +@pytest.mark.parametrize( + "data,coord,target_coord,mode,expected_data", + DATA["height_a_a_a"], +) +def test_array_interpolate_monotonic_a_a_a_2(data, coord, target_coord, mode, expected_data, xp, device): + """Test with array data, array coord, array target_coord""" + _check_array_interpolate_monotonic(data, coord, target_coord, mode, expected_data, xp, device) + + +@pytest.mark.parametrize("xp, device", NUMPY) +@pytest.mark.parametrize( + "value,pres,target,mode", + [ + ( + [100.0, 200.0], + [1000.0, 900.0], + [[1000.0, 900.0, 1000.0], [800.0, 700.0, 600.0]], + "linear", + ), + ], +) +def test_array_interpolate_monotonic_s_s_a(value, pres, target, mode, xp, device): + value = xp.asarray(value, device=device) + pres = xp.asarray(pres, device=device) + target = xp.asarray(target, device=device) + + # s_s_a is not supported + with pytest.raises(ValueError): + vertical.interpolate_monotonic(value, pres, target, mode) + + +@pytest.mark.parametrize("xp, device", NUMPY) +@pytest.mark.parametrize( + "value,pres,target,mode", + [ + ( + [100.0, 200.0], + [[1000.0, 900.0, 1000.0], [800.0, 700.0, 600.0]], + [1000.0, 900.0], + "linear", + ), + ], +) +def test_array_interpolate_monotonic_s_a_s(value, pres, target, mode, xp, device): + value = xp.asarray(value, device=device) + pres = xp.asarray(pres, device=device) + target = xp.asarray(target, device=device) + + # s_a_s is not supported + with pytest.raises(ValueError): + vertical.interpolate_monotonic(value, pres, target, mode) + + +@pytest.mark.parametrize("xp, device", NUMPY) +@pytest.mark.parametrize( + "value,pres,target,mode", + [ + ( + [100.0, 200.0], + [[1000.0, 900.0, 1000.0], [800.0, 700.0, 600.0]], + [[1000.0, 900.0, 1000.0], [800.0, 700.0, 600.0], [700.0, 600.0, 500.0]], + "linear", + ), + ], +) +def test_array_interpolate_monotonic_s_a_a(value, pres, target, mode, xp, device): + value = xp.asarray(value, device=device) + pres = xp.asarray(pres, device=device) + target = xp.asarray(target, device=device) + + # s_a_a is not supported + with pytest.raises(ValueError): + vertical.interpolate_monotonic(value, pres, target, mode) + + +@pytest.mark.parametrize("xp, device", NUMPY) +@pytest.mark.parametrize( + "data,coord,target_coord,mode,expected_data", + [ + ( + [1012.0, 1000.0, 990.0], + [1012.0, 1000.0, 990.0], + [1101.0, 1100.0, 1022.0, 1012.0, 1009.0, 995.0, 990.0, 987.0], + "linear", + [np.nan, 1100.0, 1022.0, 1012.0, 1009.0, 995.0, 990.0, np.nan], + ), + ], +) +def test_array_interpolate_monotonic_to_pressure_s_s_s_aux( + data, coord, target_coord, mode, expected_data, xp, device +): + """Test interpolation with auxiliary min/max level data""" + data = xp.asarray(data, device=device) + coord = xp.asarray(coord, device=device) + target_coord = xp.asarray(target_coord, device=device) + expected_data = xp.asarray(expected_data, device=device) + + # prescribe aux level at the bottom (max pressure in input is 1012 hPa) + r = vertical.interpolate_monotonic( + data=data, + coord=coord, + target_coord=target_coord, + interpolation=mode, + aux_max_level_data=1100, + aux_max_level_coord=1100, + ) + assert xp.allclose(r, expected_data, equal_nan=True) + + +@pytest.mark.parametrize("xp, device", NUMPY) +@pytest.mark.parametrize( + "data,coord,target_coord,mode,expected_data", + [ + ( + [10.0, 20.0, 100], + [10.0, 20.0, 100], + [-1.0, 0.0, 4.0, 10.0, 50.0, 100.0, 150.0], + "linear", + [np.nan, 0.0, 4.0, 10.0, 50.0, 100.0, np.nan], + ), + ], +) +def test_array_interpolate_monotonic_to_height_s_s_s_aux( + data, coord, target_coord, mode, expected_data, xp, device +): + """Test interpolation with auxiliary min/max level data""" + data = xp.asarray(data, device=device) + coord = xp.asarray(coord, device=device) + target_coord = xp.asarray(target_coord, device=device) + expected_data = xp.asarray(expected_data, device=device) + + # prescribe aux level at the bottom (min height in input is 10.0 m) + r = vertical.interpolate_monotonic( + data=data, + coord=coord, + target_coord=target_coord, + interpolation=mode, + aux_min_level_data=0.0, + aux_min_level_coord=0.0, + ) + assert xp.allclose(r, expected_data, equal_nan=True) + + +@pytest.mark.parametrize("xp, device", NUMPY) +@pytest.mark.parametrize( + "data,coord,target_coord,aux_data,aux_coord,mode,expected_data", + [ + ( + [[200.0, 210.0, 220.0], [100, 110, 120], [0, 10.0, 20.0]], + [[10.0, 20.0, 100.0], [110.0, 120.0, 200.0], [210.0, 220.0, 300.0]], + [ + [-100.0, 2.0, 10.0], + [-100.0, 30.0, 10.0], + [10.0, 20.0, 100.0], + [20.0, 30.0, 110.0], + [120.0, 130.0, 210.0], + [50.0, 130.0, 150.0], + [220.0, 130.0, 320.0], + [220.0, 230.0, 320.0], + ], + [300.0, 310.0, 320.0], + [0.0, 0.0, 0.0], + "linear", + [ + [np.nan, 300.0, 310.0], + [np.nan, 200.0, 310.0], + [200.0, 210.0, 220.0], + [190.0, 200.0, 210.0], + [90.0, 100.0, 110.0], + [160.0, 100.0, 170.0], + [np.nan, 100.0, np.nan], + [np.nan, np.nan, np.nan], + ], + ), + ], +) +def test_array_interpolate_monotonic_to_height_a_a_a_aux( + data, coord, target_coord, aux_data, aux_coord, mode, expected_data, xp, device +): + """Test interpolation with auxiliary min/max level data""" + data = xp.asarray(data, device=device) + coord = xp.asarray(coord, device=device) + target_coord = xp.asarray(target_coord, device=device) + expected_data = xp.asarray(expected_data, device=device) + + # prescribe aux level at the bottom + r = vertical.interpolate_monotonic( + data=data, + coord=coord, + target_coord=target_coord, + interpolation=mode, + aux_min_level_data=aux_data, + aux_min_level_coord=aux_coord, + ) + assert xp.allclose(r, expected_data, equal_nan=True) + + +@pytest.mark.parametrize("xp, device", [(_NUMPY_NAMESPACE, "cpu")]) +@pytest.mark.parametrize( + "_kwargs,expected_values", + [ + ( + {"target_p": [85000.0, 50000.0], "interpolation": "linear"}, + [[263.50982741, 287.70299692], [238.00383748, 259.50822691]], + ), + ( + {"target_p": [85000.0, 50000.0, 95100.0], "interpolation": "linear"}, + [[263.50982741, 287.70299692], [238.00383748, 259.50822691], [np.nan, 292.08454145]], + ), + ( + { + "target_p": [85000.0, 50000.0, 95100.0], + "aux_bottom_p": [95178.337944, 102659.81019512], + "aux_bottom_data": [270.0, 293.0], + "interpolation": "linear", + }, + [[263.50982741, 287.70299692], [238.00383748, 259.50822691], [269.21926951, 292.08454145]], + ), + ( + { + "target_p": [95100.0], + "interpolation": "linear", + }, + [np.nan, 292.08454145], + ), + ( + { + "target_p": [95100.0], + "aux_bottom_p": [95178.337944, 102659.81019512], + "aux_bottom_data": [270.0, 293.0], + "interpolation": "linear", + }, + [269.21926951, 292.08454145], + ), + ], +) +@pytest.mark.parametrize("part", [None, slice(-50, None)]) +def test_array_interpolate_hybrid_to_pressure_levels(_kwargs, expected_values, part, xp, device): + r_ref = expected_values + + A, B = vertical.hybrid_level_parameters(137, model="ifs") + A = A.tolist() + B = B.tolist() + + sp = DATA_HYBRID_H.p_surf + t = DATA_HYBRID_H.t + + t, r_ref, sp, A, B = (xp.asarray(x, device=device) for x in [t, r_ref, sp, A, B]) + + if part: + t = t[part] + + _kwargs = dict(_kwargs) + target_p = _kwargs.pop("target_p") + + r = vertical.interpolate_hybrid_to_pressure_levels( + t, # data to interpolate + target_p, + A, + B, + sp, + **_kwargs, + ) + + # print(repr(r)) + + tolerance = Tolerance({64: (1e-8, 1e-6), 32: (10, 1e-6)}) + atol, rtol = tolerance.get(dtype=t.dtype) + assert xp.allclose( + r, r_ref, atol=atol, rtol=rtol, equal_nan=True + ), f"max abs diff={xp.max(xp.abs(r - r_ref))}" + + +@pytest.mark.parametrize("xp, device", [(_NUMPY_NAMESPACE, "cpu")]) +@pytest.mark.parametrize( + "_kwargs,expected_values", + [ + ( + { + "target_h": [1000.0, 5000.0], + "h_type": "geometric", + "h_reference": "ground", + "interpolation": "linear", + }, + [[262.3693894784, 291.4452034379], [236.7746100208, 265.4952859218]], + ), + ( + { + "target_h": [1000.0, 5000.0], + "h_type": "geometric", + "h_reference": "sea", + "interpolation": "linear", + }, + [[265.8344752939, 291.0419484632], [239.8099274052, 264.9629089069]], + ), + ( + { + "target_h": [1000.0, 5000.0], + "h_type": "geopotential", + "h_reference": "ground", + "interpolation": "linear", + }, + [[262.3657943604, 291.4447171210], [236.7517288039, 265.4713984425]], + ), + ( + { + "target_h": [1000.0, 5000.0], + "h_type": "geopotential", + "h_reference": "sea", + "interpolation": "linear", + }, + [[265.8333668681, 291.0411459042], [239.7860545644, 264.9382000331]], + ), + ( + { + "target_h": [1000.0, 5000.0, 5.0], + "h_type": "geometric", + "h_reference": "ground", + "interpolation": "linear", + }, + [[262.3693894784, 291.4452034379], [236.7746100208, 265.4952859218], [np.nan, np.nan]], + ), + ( + { + "target_h": [1000.0, 5000.0, 5.0], + "h_type": "geometric", + "h_reference": "ground", + "aux_bottom_h": 0.0, + "aux_bottom_data": [280.0, 300.0], + "interpolation": "linear", + }, + [ + [262.3693894784, 291.4452034379], + [236.7746100208, 265.4952859218], + [274.0481585682, 296.5000734836], + ], + ), + ( + { + "target_h": [5.0], + "h_type": "geometric", + "h_reference": "ground", + "interpolation": "linear", + }, + [np.nan, np.nan], + ), + ( + { + "target_h": [5.0], + "h_type": "geometric", + "h_reference": "ground", + "aux_bottom_h": 0.0, + "aux_bottom_data": [280.0, 300.0], + "interpolation": "linear", + }, + [274.0481585682, 296.5000734836], + ), + ], +) +@pytest.mark.parametrize("part", [None, slice(-50, None)]) +def test_array_interpolate_hybrid_to_height_levels(_kwargs, expected_values, part, xp, device): + r_ref = expected_values + + A, B = vertical.hybrid_level_parameters(137, model="ifs") + A = A.tolist() + B = B.tolist() + + sp = DATA_HYBRID_H.p_surf + t = DATA_HYBRID_H.t + q = DATA_HYBRID_H.q + zs = DATA_HYBRID_H.z_surf + + r_ref, t, q, zs, sp, A, B = (xp.asarray(x, device=device) for x in [r_ref, t, q, zs, sp, A, B]) + + if part: + t = t[part] + q = q[part] + + _kwargs = dict(_kwargs) + target_h = _kwargs.pop("target_h") + + r = vertical.interpolate_hybrid_to_height_levels( + t, # data to interpolate + target_h, + t, + q, + zs, + A, + B, + sp, + **_kwargs, + ) + + # print(repr(r)) + + tolerance = Tolerance({64: (1e-8, 1e-6), 32: (10, 1e-6)}) + atol, rtol = tolerance.get(dtype=t.dtype) + assert xp.allclose( + r, r_ref, atol=atol, rtol=rtol, equal_nan=True + ), f"max abs diff={xp.max(xp.abs(r - r_ref))}" + + +@pytest.mark.parametrize("xp, device", [(_NUMPY_NAMESPACE, "cpu")]) +@pytest.mark.parametrize( + "_kwargs,expected_values", + [ + ( + { + "target_h": [1000.0, 5000.0], + "h_type": "geometric", + "h_reference": "ground", + "interpolation": "linear", + }, + [[294.20429573, 299.22387254], [271.02124509, 272.90306903]], + ), + ( + { + "target_h": [1000.0, 5000.0], + "h_type": "geometric", + "h_reference": "sea", + "interpolation": "linear", + }, + [[298.4516800756, 298.9948524649], [274.0326115423, 272.7133842002]], + ), + ( + { + "target_h": [1000.0, 5000.0], + "h_type": "geopotential", + "h_reference": "ground", + "interpolation": "linear", + }, + [[294.2022875759, 299.2221015691], [270.9937963229, 272.8758976631]], + ), + ( + { + "target_h": [1000.0, 5000.0], + "h_type": "geopotential", + "h_reference": "sea", + "interpolation": "linear", + }, + [[298.4498485083, 298.9930209085], [274.0092073632, 272.6859411558]], + ), + ( + { + "target_h": [1000.0, 5000.0, 2.0], + "h_type": "geometric", + "h_reference": "ground", + "interpolation": "linear", + }, + [ + [ + 294.20429573, + 299.22387254, + ], + [271.02124509, 272.90306903], + [302.0918370407, np.nan], + ], + ), + ( + { + "target_h": [1000.0, 5000.0, 2.0], + "h_type": "geometric", + "h_reference": "ground", + "interpolation": "linear", + "aux_bottom_h": 0.0, + "aux_bottom_data": [304.0, 306.0], + }, + [ + [ + 294.20429573, + 299.22387254, + ], + [271.02124509, 272.90306903], + [302.0918370407, 305.9996224989], + ], + ), + ( + { + "target_h": [2.0], + "h_type": "geometric", + "h_reference": "ground", + "interpolation": "linear", + }, + [302.0918370407, np.nan], + ), + ( + { + "target_h": [2.0], + "h_type": "geometric", + "h_reference": "ground", + "interpolation": "linear", + "aux_bottom_h": 0.0, + "aux_bottom_data": [304.0, 306.0], + }, + [302.0918370407, 305.9996224989], + ), + ], +) +def test_array_interpolate_pressure_to_height_levels(_kwargs, expected_values, xp, device): + r_ref = expected_values + + t = DATA_PL.t # temperature [K] + z = DATA_PL.z # geopotential [m2/s2] + sp = DATA_PL.p_surf # surface pressure [Pa] + zs = DATA_PL.z_surf # surface geopotential [m2/s2] + + t, z, r_ref, sp, zs = (xp.asarray(x, device=device) for x in [t, z, r_ref, sp, zs]) + + _kwargs = dict(_kwargs) + target_h = _kwargs.pop("target_h") + + r = vertical.interpolate_pressure_to_height_levels( + t, # data to interpolate + target_h, + z, + zs, + **_kwargs, + ) + + tolerance = Tolerance({64: (1e-8, 1e-6), 32: (10, 1e-6)}) + atol, rtol = tolerance.get(dtype=t.dtype) + assert xp.allclose( + r, r_ref, atol=atol, rtol=rtol, equal_nan=True + ), f"max abs diff={xp.max(xp.abs(r - r_ref))}" diff --git a/tests/vertical/test_array_vertical.py b/tests/vertical/test_array_vertical.py new file mode 100644 index 0000000..b2770b6 --- /dev/null +++ b/tests/vertical/test_array_vertical.py @@ -0,0 +1,523 @@ +# (C) Copyright 2021 ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. +# + +from importlib import import_module + +import numpy as np +import pytest +from earthkit.utils.array.namespace import _NUMPY_NAMESPACE +from earthkit.utils.array.testing import NAMESPACE_DEVICES + +from earthkit.meteo import vertical +from earthkit.meteo.utils.testing import Tolerance + +np.set_printoptions(formatter={"float_kind": "{:.15f}".format}) + + +def _get_data(name): + import os + import sys + + here = os.path.dirname(__file__) + sys.path.insert(0, here) + + return import_module(name) + + +DATA_HYBRID_CORE = _get_data("_hybrid_core_data") +DATA_HYBRID_H = _get_data("_hybrid_height_data") + + +@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) +@pytest.mark.parametrize( + "z,expected_value", + [(0.0, 0.0), (1000.0, 101.97162129779284), ([1000.0, 10000.0], [101.9716212978, 1019.7162129779])], +) +def test_geopotential_height_from_geopotential(z, expected_value, xp, device): + z = xp.asarray(z, device=device) + expected_value = xp.asarray(expected_value, device=device) + + r = vertical.geopotential_height_from_geopotential(z) + assert xp.allclose(r, expected_value) + + +@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) +@pytest.mark.parametrize( + "h,expected_value", + [(0.0, 0.0), (101.97162129779284, 1000.0), ([101.9716212978, 1019.7162129779], [1000.0, 10000.0])], +) +def test_geopotential_from_geopotential_height(h, expected_value, xp, device): + h = xp.asarray(h, device=device) + expected_value = xp.asarray(expected_value, device=device) + + r = vertical.geopotential_from_geopotential_height(h) + assert xp.allclose(r, expected_value) + + +@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) +@pytest.mark.parametrize( + "h,expected_value", + [ + (0.0, 0.0), + (5102.664476187331, 50000.0), + ([1019.8794448450, 5102.6644761873, 7146.0195417809], [10000.0, 50000.0, 70000.0]), + ], +) +def test_geopotential_from_geometric_height(h, expected_value, xp, device): + h = xp.asarray(h, device=device) + expected_value = xp.asarray(expected_value, device=device) + + r = vertical.geopotential_from_geometric_height(h) + assert xp.allclose(r, expected_value) + + +@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) +@pytest.mark.parametrize( + "h,expected_value", + [ + (0.0, 0.0), + (5003.9269715243, 5000.0), + ([1000.1569802279, 5003.9269715243, 7007.6992829768], [1000.0, 5000.0, 7000.0]), + ], +) +def test_geopotential_height_from_geometric_height(h, expected_value, xp, device): + h = xp.asarray(h, device=device) + expected_value = xp.asarray(expected_value, device=device) + + r = vertical.geopotential_height_from_geometric_height(h) + + assert xp.allclose(r, expected_value) + + +@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) +@pytest.mark.parametrize( + "z,expected_value", + [ + (0.0, 0.0), + (50000.0, 5102.664476187331), + ([10000.0, 50000.0, 70000.0], [1019.8794448450, 5102.6644761873, 7146.0195417809]), + ], +) +def test_geometric_height_from_geopotential(z, expected_value, xp, device): + z = xp.asarray(z, device=device) + expected_value = xp.asarray(expected_value, device=device) + + r = vertical.geometric_height_from_geopotential(z) + assert xp.allclose(r, expected_value) + + +@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) +@pytest.mark.parametrize( + "zh,expected_value", + [ + (0.0, 0.0), + (5000.0, 5003.9269715243), + ([1000.0, 5000.0, 7000.0], [1000.1569802279, 5003.9269715243, 7007.6992829768]), + ], +) +def test_geometric_height_from_geopotential_height(zh, expected_value, xp, device): + zh = xp.asarray(zh, device=device) + expected_value = xp.asarray(expected_value, device=device) + + r = vertical.geometric_height_from_geopotential_height(zh) + assert xp.allclose(r, expected_value) + + +# NOTE: this method returns numpy arrays only +@pytest.mark.parametrize("xp", [_NUMPY_NAMESPACE]) +def test_hybrid_level_parameters_1(xp): + ref_A = DATA_HYBRID_CORE.A + ref_B = DATA_HYBRID_CORE.B + ref_A, ref_B = (xp.asarray(x) for x in [ref_A, ref_B]) + + A, B = vertical.hybrid_level_parameters(137) + + # Note: A in test data has been stored with higher precision than in the conf + assert np.allclose(A, ref_A, rtol=1e-5) + assert np.allclose(B, ref_B, rtol=1e-5) + + +def test_hybrid_level_parameters_2(): + with pytest.raises(ValueError): + vertical.hybrid_level_parameters(-100) + + +def test_hybrid_level_parameters_3(): + with pytest.raises(ValueError): + vertical.hybrid_level_parameters(137, model="unknown_model") + + +@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) +# @pytest.mark.parametrize("xp, device", [(_NUMPY_NAMESPACE, "cpu")]) +@pytest.mark.parametrize("index", [(slice(None), slice(None)), (slice(None), 0), (slice(None), 1)]) +def test_pressure_on_hybrid_levels_core(index, xp, device): + sp = DATA_HYBRID_CORE.p_surf + A = DATA_HYBRID_CORE.A + B = DATA_HYBRID_CORE.B + ref_p_full = DATA_HYBRID_CORE.p_full + ref_p_half = DATA_HYBRID_CORE.p_half + ref_delta = DATA_HYBRID_CORE.delta + ref_alpha = DATA_HYBRID_CORE.alpha + + sp, ref_p_full, ref_p_half, ref_delta, ref_alpha, A, B = ( + xp.asarray(x, device=device) for x in [sp, ref_p_full, ref_p_half, ref_delta, ref_alpha, A, B] + ) + + sp = sp[index[1]] + ref_p_full = ref_p_full[index] + ref_p_half = ref_p_half[index] + ref_delta = ref_delta[index] + ref_alpha = ref_alpha[index] + + p_full, p_half, delta, alpha = vertical.pressure_on_hybrid_levels( + A, B, sp, alpha_top="ifs", output=["full", "half", "delta", "alpha"] + ) + + # print("p_full", repr(p_full)) + # print("p_half", repr(p_half)) + # print("delta", repr(delta)) + # print("alpha", repr(alpha)) + + # print("p_full diff", repr(xp.max(xp.abs(p_full - ref_p_full)))) + # print("p_half diff", repr(xp.max(xp.abs(p_half - ref_p_half)))) + # print("delta diff", repr(xp.max(xp.abs(delta - ref_delta)))) + # print("alpha diff", repr(xp.max(xp.abs(alpha - ref_alpha)))) + + tolerance = Tolerance( + { + "p_full": {64: (1e-8, 1e-6)}, + "p_half": {64: (1e-8, 1e-6)}, + "delta": {64: (1e-8, 1e-6), 32: (1e-6, 1e-5)}, + "alpha": {64: (1e-8, 1e-6), 32: (1e-4, 1e-5)}, + } + ) + atol, rtol = tolerance.get(key="p_full", dtype=sp.dtype) + assert xp.allclose(p_full, ref_p_full, atol=atol, rtol=rtol) + + atol, rtol = tolerance.get(key="p_half", dtype=sp.dtype) + assert xp.allclose(p_half, ref_p_half, atol=atol, rtol=rtol) + + # for i in range(delta.shape[0]): + # print(f"delta level {i}: computed={delta[i]}, reference={ref_delta[i]} diff={delta[i]-ref_delta[i]}") + + atol, rtol = tolerance.get(key="delta", dtype=sp.dtype) + assert xp.allclose(delta, ref_delta, atol=atol, rtol=rtol) + + atol, rtol = tolerance.get(key="alpha", dtype=sp.dtype) + assert xp.allclose(alpha, ref_alpha, atol=atol, rtol=rtol) + + +@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) +# @pytest.mark.parametrize("xp, device", [(_NUMPY_NAMESPACE, "cpu")]) +@pytest.mark.parametrize("index", [(slice(None), slice(None)), (slice(None), 0), (slice(None), 1)]) +def test_pressure_on_hybrid_levels_axis(index, xp, device): + # nondefault vertical axis for output + vertical_axis = 1 + + sp = DATA_HYBRID_CORE.p_surf + A = DATA_HYBRID_CORE.A + B = DATA_HYBRID_CORE.B + ref_p_full = DATA_HYBRID_CORE.p_full + ref_p_half = DATA_HYBRID_CORE.p_half + ref_delta = DATA_HYBRID_CORE.delta + ref_alpha = DATA_HYBRID_CORE.alpha + + nlev = len(A) - 1 + + sp, ref_p_full, ref_p_half, ref_delta, ref_alpha, A, B = ( + xp.asarray(x, device=device) for x in [sp, ref_p_full, ref_p_half, ref_delta, ref_alpha, A, B] + ) + + sp = sp[index[1]] + ref_p_full = ref_p_full[index] + ref_p_half = ref_p_half[index] + ref_delta = ref_delta[index] + ref_alpha = ref_alpha[index] + + p_full, p_half, delta, alpha = vertical.pressure_on_hybrid_levels( + A, B, sp, alpha_top="ifs", output=["full", "half", "delta", "alpha"], vertical_axis=vertical_axis + ) + + input_shape = sp.shape + + assert p_full.shape == input_shape + (nlev,) + assert p_half.shape == input_shape + (nlev + 1,) + assert delta.shape == input_shape + (nlev,) + assert alpha.shape == input_shape + (nlev,) + + if p_full.ndim > 1: + p_full = xp.moveaxis(p_full, vertical_axis, 0) + p_half = xp.moveaxis(p_half, vertical_axis, 0) + delta = xp.moveaxis(delta, vertical_axis, 0) + alpha = xp.moveaxis(alpha, vertical_axis, 0) + + tolerance = Tolerance( + { + "p_full": {64: (1e-8, 1e-6)}, + "p_half": {64: (1e-8, 1e-6)}, + "delta": {64: (1e-8, 1e-6), 32: (1e-6, 1e-5)}, + "alpha": {64: (1e-8, 1e-6), 32: (1e-4, 1e-5)}, + } + ) + atol, rtol = tolerance.get(key="p_full", dtype=sp.dtype) + assert xp.allclose(p_full, ref_p_full, atol=atol, rtol=rtol) + + atol, rtol = tolerance.get(key="p_half", dtype=sp.dtype) + assert xp.allclose(p_half, ref_p_half, atol=atol, rtol=rtol) + + atol, rtol = tolerance.get(key="delta", dtype=sp.dtype) + assert xp.allclose(delta, ref_delta, atol=atol, rtol=rtol) + + atol, rtol = tolerance.get(key="alpha", dtype=sp.dtype) + assert xp.allclose(alpha, ref_alpha, atol=atol, rtol=rtol) + + +@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) +# @pytest.mark.parametrize("xp, device", [(_NUMPY_NAMESPACE, "cpu")]) +@pytest.mark.parametrize("index", [(slice(None), slice(None)), (slice(None), 0), (slice(None), 1)]) +@pytest.mark.parametrize( + "levels", [None, list(range(90, 138)), list(range(137, 90, -1)), [1, 2], [2, 1], [1]] +) +@pytest.mark.parametrize( + "output", + [ + "full", + "half", + "delta", + "alpha", + ["full", "half", "delta", "alpha"], + ["full", "half"], + ["half", "full"], + ["delta", "alpha"], + ], +) +def test_pressure_on_hybrid_levels_output(index, levels, output, xp, device): + + sp = DATA_HYBRID_CORE.p_surf + A = DATA_HYBRID_CORE.A + B = DATA_HYBRID_CORE.B + ref_p_full = DATA_HYBRID_CORE.p_full + ref_p_half = DATA_HYBRID_CORE.p_half + ref_delta = DATA_HYBRID_CORE.delta + ref_alpha = DATA_HYBRID_CORE.alpha + + # ref_def = {"full": DATA.p_full, "half": DATA.p_half, "delta": DATA_HYBRID_CORE.delta, "alpha": DATA_HYBRID_CORE.alpha} + # ref = { + # key: val + # for key, val in ref_def.items() + # if (output == key or (isinstance(output, (list, tuple)) and key in output)) + # } + + # ref_p_full = DATA_HYBRID_CORE.p_full + # ref_p_half = DATA_HYBRID_CORE.p_half + # ref_delta = DATA_HYBRID_CORE.delta + # ref_alpha = DATA_HYBRID_CORE.alpha + + sp, ref_p_full, ref_p_half, ref_delta, ref_alpha, A, B = ( + xp.asarray(x, device=device) for x in [sp, ref_p_full, ref_p_half, ref_delta, ref_alpha, A, B] + ) + + # sp test data is 1D + sp = sp[index[1]] + + ref_def = { + "full": ref_p_full[index], + "half": ref_p_half[index], + "delta": ref_delta[index], + "alpha": ref_alpha[index], + } + ref = { + key: val + for key, val in ref_def.items() + if (output == key or (isinstance(output, (list, tuple)) and key in output)) + } + + levels = np.asarray(levels) if levels is not None else None + + if levels is not None: + levels_half_idx = levels + levels_idx = levels - 1 + for key in ref: + if key == "half": + ref[key] = ref[key][levels_half_idx] + else: + ref[key] = ref[key][levels_idx] + + res = vertical.pressure_on_hybrid_levels(A, B, sp, levels=levels, alpha_top="ifs", output=output) + + # atol and rtol for different outputs, due to different precisions in backends + tolerance = Tolerance( + { + "full": {64: (1e-8, 1e-6)}, + "half": {64: (1e-8, 1e-6)}, + "delta": {64: (1e-8, 1e-6), 32: (1e-6, 1e-5)}, + "alpha": {64: (1e-8, 1e-6), 32: (1e-4, 1e-5)}, + } + ) + + if isinstance(output, str) or len(output) == 1: + key = output if isinstance(output, str) else output[0] + # print(f"{key=}, max abs diff={xp.max(xp.abs(res - ref[key]))}") + + atol, rtol = tolerance.get(key=key, dtype=sp.dtype) + assert xp.allclose( + res, ref[key], atol=atol, rtol=rtol + ), f"{key=}, max abs diff={xp.max(xp.abs(res - ref[key]))}" + else: + assert isinstance(res, tuple) + assert len(res) == len(output) + for key, rd in zip(output, res): + atol, rtol = tolerance.get(key=key, dtype=sp.dtype) + assert xp.allclose( + rd, ref[key], atol=atol, rtol=rtol + ), f"{key=}, max abs diff={xp.max(xp.abs(rd - ref[key]))}" + + +@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) +# @pytest.mark.parametrize("xp, device", [(_NUMPY_NAMESPACE, "cpu")]) +@pytest.mark.parametrize("index", [(slice(None), slice(None)), (slice(None), 0), (slice(None), 1)]) +def test_relative_geopotential_thickness_on_hybrid_levels_alpha_delta(index, xp, device): + + alpha = DATA_HYBRID_CORE.alpha + delta = DATA_HYBRID_CORE.delta + t = DATA_HYBRID_CORE.t + q = DATA_HYBRID_CORE.q + z_ref = DATA_HYBRID_CORE.z + + z_ref, t, q, alpha, delta = (xp.asarray(x, device=device) for x in [z_ref, t, q, alpha, delta]) + + alpha = alpha[index] + delta = delta[index] + t = t[index] + q = q[index] + z_ref = z_ref[index] + + z = vertical.relative_geopotential_thickness_on_hybrid_levels_from_alpha_delta(t, q, alpha, delta) + + tolerance = Tolerance({64: (1e-8, 1e-6), 32: (1e-8, 1e-6)}) + atol, rtol = tolerance.get(dtype=t.dtype) + assert xp.allclose(z, z_ref, atol=atol, rtol=rtol), f"max abs diff={xp.max(xp.abs(z - z_ref))}" + + +@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) +# @pytest.mark.parametrize("xp, device", [(_NUMPY_NAMESPACE, "cpu")]) +@pytest.mark.parametrize("index", [(slice(None), slice(None)), (slice(None), 0), (slice(None), 1)]) +def test_relative_geopotential_thickness_on_hybrid_levels_ab(index, xp, device): + + A = DATA_HYBRID_CORE.A + B = DATA_HYBRID_CORE.B + sp = DATA_HYBRID_CORE.p_surf + t = DATA_HYBRID_CORE.t + q = DATA_HYBRID_CORE.q + z_ref = DATA_HYBRID_CORE.z + + z_ref, t, q, A, B, sp = (xp.asarray(x, device=device) for x in [z_ref, t, q, A, B, sp]) + + sp = sp[index[1]] + t = t[index] + q = q[index] + z_ref = z_ref[index] + + z = vertical.relative_geopotential_thickness_on_hybrid_levels(t, q, A, B, sp) + + tolerance = Tolerance({64: (1e-8, 1e-6), 32: (10, 1e-6)}) + atol, rtol = tolerance.get(dtype=t.dtype) + assert xp.allclose(z, z_ref, atol=atol, rtol=rtol), f"max abs diff={xp.max(xp.abs(z - z_ref))}" + + +@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) +# @pytest.mark.parametrize("xp, device", [(_NUMPY_NAMESPACE, "cpu")]) +@pytest.mark.parametrize("index", [(slice(None), slice(None)), (slice(None), 0), (slice(None), 1)]) +def test_relative_geopotential_thickness_on_hybrid_levels_part(index, xp, device): + # get only levels from 90 to 136/137 + part = slice(90, None) + + A = DATA_HYBRID_CORE.A + B = DATA_HYBRID_CORE.B + sp = DATA_HYBRID_CORE.p_surf + t = DATA_HYBRID_CORE.t + q = DATA_HYBRID_CORE.q + z_ref = DATA_HYBRID_CORE.z + + z_ref, t, q, A, B, sp = (xp.asarray(x, device=device) for x in [z_ref, t, q, A, B, sp]) + + part_index = (part, index[1]) + + sp = sp[index[1]] + t = t[part_index] + q = q[part_index] + z_ref = z_ref[part_index] + + z = vertical.relative_geopotential_thickness_on_hybrid_levels(t, q, A, B, sp) + + tolerance = Tolerance({64: (1e-8, 1e-6), 32: (10, 1e-6)}) + atol, rtol = tolerance.get(dtype=t.dtype) + assert xp.allclose(z, z_ref, atol=atol, rtol=rtol), f"max abs diff={xp.max(xp.abs(z - z_ref))}" + + +@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) +# @pytest.mark.parametrize("xp, device", [(_NUMPY_NAMESPACE, "cpu")]) +@pytest.mark.parametrize("index", [(slice(None), slice(None)), (slice(None), 0), (slice(None), 1)]) +def test_geopotential_on_hybrid_levels(index, xp, device): + + A = DATA_HYBRID_CORE.A + B = DATA_HYBRID_CORE.B + sp = DATA_HYBRID_CORE.p_surf + t = DATA_HYBRID_CORE.t + q = DATA_HYBRID_CORE.q + z_ref = DATA_HYBRID_CORE.z + zs = [0.0] * len(t[0]) # surface geopotential is zero in test data + + z_ref, t, q, zs, A, B, sp = (xp.asarray(x, device=device) for x in [z_ref, t, q, zs, A, B, sp]) + + sp = sp[index[1]] + t = t[index] + q = q[index] + z_ref = z_ref[index] + zs = zs[index[1]] + + z = vertical.geopotential_on_hybrid_levels(t, q, zs, A, B, sp) + + tolerance = Tolerance({64: (1e-8, 1e-6), 32: (10, 1e-6)}) + atol, rtol = tolerance.get(dtype=t.dtype) + assert xp.allclose(z, z_ref, atol=atol, rtol=rtol), f"max abs diff={xp.max(xp.abs(z - z_ref))}" + + +@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) +# @pytest.mark.parametrize("xp, device", [(_NUMPY_NAMESPACE, "cpu")]) +@pytest.mark.parametrize("index", [(slice(None), slice(None)), (slice(None), 0), (slice(None), 1)]) +@pytest.mark.parametrize("h_type", ["geometric", "geopotential"]) +@pytest.mark.parametrize("h_reference", ["sea", "ground"]) +def test_height_on_hybrid_levels(index, xp, device, h_type, h_reference): + + A, B = vertical.hybrid_level_parameters(137) + A = A.tolist() + B = B.tolist() + + sp = DATA_HYBRID_H.p_surf + t = DATA_HYBRID_H.t + q = DATA_HYBRID_H.q + zs = DATA_HYBRID_H.z_surf + + ref_name = f"h_{h_type}_{h_reference}" + h_ref = getattr(DATA_HYBRID_H, ref_name) + + h_ref, t, q, zs, sp, A, B = (xp.asarray(x, device=device) for x in [h_ref, t, q, zs, sp, A, B]) + + t = t[index] + q = q[index] + h_ref = h_ref[index] + zs = zs[index[1]] + sp = sp[index[1]] + + h = vertical.height_on_hybrid_levels(t, q, zs, A, B, sp, h_type=h_type, h_reference=h_reference) + + tolerance = Tolerance({64: (1e-8, 1e-6), 32: (10, 1e-6)}) + atol, rtol = tolerance.get(dtype=t.dtype) + assert xp.allclose(h, h_ref, atol=atol, rtol=rtol), f"max abs diff={xp.max(xp.abs(h - h_ref))}" diff --git a/tests/vertical/test_vertical.py b/tests/vertical/test_vertical.py deleted file mode 100644 index 5a8b17c..0000000 --- a/tests/vertical/test_vertical.py +++ /dev/null @@ -1,270 +0,0 @@ -# (C) Copyright 2021 ECMWF. -# -# This software is licensed under the terms of the Apache Licence Version 2.0 -# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. -# In applying this licence, ECMWF does not waive the privileges and immunities -# granted to it by virtue of its status as an intergovernmental organisation -# nor does it submit to any jurisdiction. -# - -import numpy as np -import pytest -from earthkit.utils.array.namespace import _NUMPY_NAMESPACE -from earthkit.utils.array.testing import NAMESPACE_DEVICES - -from earthkit.meteo import vertical - -np.set_printoptions(formatter={"float_kind": "{:.10f}".format}) - - -def _get_data(): - import os - import sys - - here = os.path.dirname(__file__) - sys.path.insert(0, here) - import _vertical_data - - return _vertical_data - - -DATA = _get_data() - - -@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) -@pytest.mark.parametrize( - "z,expected_value", - [(0.0, 0.0), (1000.0, 101.97162129779284), ([1000.0, 10000.0], [101.9716212978, 1019.7162129779])], -) -def test_geopotential_height_from_geopotential(z, expected_value, xp, device): - z = xp.asarray(z, device=device) - expected_value = xp.asarray(expected_value, device=device) - - r = vertical.geopotential_height_from_geopotential(z) - assert xp.allclose(r, expected_value) - - -@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) -@pytest.mark.parametrize( - "h,expected_value", - [(0.0, 0.0), (101.97162129779284, 1000.0), ([101.9716212978, 1019.7162129779], [1000.0, 10000.0])], -) -def test_geopotential_from_geopotential_height(h, expected_value, xp, device): - h = xp.asarray(h, device=device) - expected_value = xp.asarray(expected_value, device=device) - - r = vertical.geopotential_from_geopotential_height(h) - assert xp.allclose(r, expected_value) - - -@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) -@pytest.mark.parametrize( - "h,expected_value", - [ - (0.0, 0.0), - (5102.664476187331, 50000.0), - ([1019.8794448450, 5102.6644761873, 7146.0195417809], [10000.0, 50000.0, 70000.0]), - ], -) -def test_geopotential_from_geometric_height(h, expected_value, xp, device): - h = xp.asarray(h, device=device) - expected_value = xp.asarray(expected_value, device=device) - - r = vertical.geopotential_from_geometric_height(h) - assert xp.allclose(r, expected_value) - - -@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) -@pytest.mark.parametrize( - "h,expected_value", - [ - (0.0, 0.0), - (5003.9269715243, 5000.0), - ([1000.1569802279, 5003.9269715243, 7007.6992829768], [1000.0, 5000.0, 7000.0]), - ], -) -def test_geopotential_height_from_geometric_height(h, expected_value, xp, device): - h = xp.asarray(h, device=device) - expected_value = xp.asarray(expected_value, device=device) - - r = vertical.geopotential_height_from_geometric_height(h) - - assert xp.allclose(r, expected_value) - - -@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) -@pytest.mark.parametrize( - "z,expected_value", - [ - (0.0, 0.0), - (50000.0, 5102.664476187331), - ([10000.0, 50000.0, 70000.0], [1019.8794448450, 5102.6644761873, 7146.0195417809]), - ], -) -def test_geometric_height_from_geopotential(z, expected_value, xp, device): - z = xp.asarray(z, device=device) - expected_value = xp.asarray(expected_value, device=device) - - r = vertical.geometric_height_from_geopotential(z) - assert xp.allclose(r, expected_value) - - -@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) -@pytest.mark.parametrize( - "zh,expected_value", - [ - (0.0, 0.0), - (5000.0, 5003.9269715243), - ([1000.0, 5000.0, 7000.0], [1000.1569802279, 5003.9269715243, 7007.6992829768]), - ], -) -def test_geometric_height_from_geopotential_height(zh, expected_value, xp, device): - zh = xp.asarray(zh, device=device) - expected_value = xp.asarray(expected_value, device=device) - - r = vertical.geometric_height_from_geopotential_height(zh) - assert xp.allclose(r, expected_value) - - -@pytest.mark.parametrize("xp", [_NUMPY_NAMESPACE]) -@pytest.mark.parametrize("index", [(slice(None), slice(None)), (slice(None), 0), (slice(None), 1)]) -def test_pressure_at_model_levels(index, xp): - - sp = DATA.p_surf - A = DATA.A - B = DATA.B - ref_p_full = DATA.p_full - ref_p_half = DATA.p_half - ref_delta = DATA.delta - ref_alpha = DATA.alpha - - sp, ref_p_full, ref_p_half, ref_delta, ref_alpha, A, B = ( - xp.asarray(x) for x in [sp, ref_p_full, ref_p_half, ref_delta, ref_alpha, A, B] - ) - - sp = sp[index[1]] - ref_p_full = ref_p_full[index] - ref_p_half = ref_p_half[index] - ref_delta = ref_delta[index] - ref_alpha = ref_alpha[index] - - p_full, p_half, delta, alpha = vertical.pressure_at_model_levels(A, B, sp, alpha_top="ifs") - - # print("p_full", repr(p_full)) - # print("p_half", repr(p_half)) - # print("delta", repr(delta)) - # print("alpha", repr(alpha)) - - assert xp.allclose(p_full, ref_p_full) - assert xp.allclose(p_half, ref_p_half) - assert xp.allclose(delta, ref_delta) - assert xp.allclose(alpha, ref_alpha) - - -@pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) -@pytest.mark.parametrize("index", [(slice(None), slice(None)), (slice(None), 0), (slice(None), 1)]) -def test_relative_geopotential_thickness(index, xp, device): - - A = DATA.A - B = DATA.B - alpha = DATA.alpha - delta = DATA.delta - t = DATA.t - q = DATA.q - z_ref = DATA.z - - z_ref, t, q, alpha, delta, A, B = ( - xp.asarray(x, device=device) for x in [z_ref, t, q, alpha, delta, A, B] - ) - - alpha = alpha[index] - delta = delta[index] - t = t[index] - q = q[index] - z_ref = z_ref[index] - - z = vertical.relative_geopotential_thickness(alpha, delta, t, q) - # print("z", repr(z)) - # print("z_ref", repr(z_ref)) - - assert xp.allclose(z, z_ref) - - -@pytest.mark.parametrize("xp", [_NUMPY_NAMESPACE]) -@pytest.mark.parametrize( - "h,p_ref", - [ - (0, 101183.94696484), - (1, 101171.8606369517), - (100.0, 99979.6875841272), - (5000.0, 53738.035306025726), - (50000.0, 84.2265165561), - ], -) -def test_pressure_at_height_levels_all(h, p_ref, xp): - sp = DATA.p_surf - A = DATA.A - B = DATA.B - t = DATA.t - q = DATA.q - - sp, h, t, q, A, B = (xp.asarray(x) for x in [sp, h, t, q, A, B]) - - sp = sp[0] # use only the first surface pressure value - t = t[:, 0] - q = q[:, 0] - - p = vertical.pressure_at_height_levels(h, t, q, sp, A, B, alpha_top="ifs") - assert xp.isclose(p, p_ref) - - -@pytest.mark.parametrize("xp", [_NUMPY_NAMESPACE]) -@pytest.mark.parametrize( - "h,p_ref", - [ - (0, 101183.94696484), - (1, 101171.8606369517), - (100.0, 99979.6875841272), - (5000.0, 53738.035306025726), - ], -) -def test_pressure_at_height_levels_part(h, p_ref, xp): - # get only levels from 90 to 136/137 - part = slice(90, None) - - sp = DATA.p_surf - A = DATA.A - B = DATA.B - t = DATA.t - q = DATA.q - - assert len(A) == len(B) == len(t) + 1 == len(q) + 1 - - sp, h, t, q, A, B = (xp.asarray(x) for x in [sp, h, t, q, A, B]) - - sp = sp[0] - A = A[part] - B = B[part] - t = t[part, 0] - q = q[part, 0] - - p = vertical.pressure_at_height_levels(h, t, q, sp, A, B, alpha_top="ifs") - assert xp.isclose(p, p_ref) - - -@pytest.mark.parametrize("xp", [_NUMPY_NAMESPACE]) -def test_pressure_at_height_levels_multi_point(xp): - h = 5000.0 # height in m - sp = DATA.p_surf - A = DATA.A - B = DATA.B - t = DATA.t - q = DATA.q - - assert len(A) == len(B) == len(t) + 1 == len(q) + 1 - sp, h, t, q, A, B = (xp.asarray(x) for x in [sp, h, t, q, A, B]) - - p_ref = np.array([53738.035306025726, 27290.9128315574]) - - p = vertical.pressure_at_height_levels(h, t, q, sp, A, B, alpha_top="ifs") - assert xp.allclose(p, p_ref) diff --git a/tests/vertical/test_vertical_deprec.py b/tests/vertical/test_vertical_deprec.py new file mode 100644 index 0000000..8602388 --- /dev/null +++ b/tests/vertical/test_vertical_deprec.py @@ -0,0 +1,372 @@ +# (C) Copyright 2021 ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. +# + +from importlib import import_module + +import numpy as np +import pytest +from earthkit.utils.array.namespace import _NUMPY_NAMESPACE + +from earthkit.meteo import vertical + +np.set_printoptions(formatter={"float_kind": "{:.15f}".format}) + + +# These are tests for deprecated functions in vertical +# TODO: remove when deprecation period is over + + +def _get_data(name): + import os + import sys + + here = os.path.dirname(__file__) + sys.path.insert(0, here) + + return import_module(name) + + +DATA = _get_data("_hybrid_core_data") + + +@pytest.mark.parametrize("xp", [_NUMPY_NAMESPACE]) +@pytest.mark.parametrize("index", [(slice(None), slice(None)), (slice(None), 0), (slice(None), 1)]) +def test_pressure_at_model_levels_ori(index, xp): + + sp = DATA.p_surf + A = DATA.A + B = DATA.B + ref_p_full = DATA.p_full + ref_p_half = DATA.p_half + ref_delta = DATA.delta + ref_alpha = DATA.alpha + + sp, ref_p_full, ref_p_half, ref_delta, ref_alpha, A, B = ( + xp.asarray(x) for x in [sp, ref_p_full, ref_p_half, ref_delta, ref_alpha, A, B] + ) + + sp = sp[index[1]] + + ref_p_full = ref_p_full[index] + ref_p_half = ref_p_half[index] + ref_delta = ref_delta[index] + ref_alpha = ref_alpha[index] + + p_full, p_half, delta, alpha = vertical.pressure_at_model_levels(A, B, sp, alpha_top="ifs") + + # print("p_full", repr(p_full)) + # print("p_half", repr(p_half)) + # print("delta", repr(delta)) + # print("alpha", repr(alpha)) + + assert xp.allclose(p_full, ref_p_full, rtol=1e-8) + assert xp.allclose(p_half, ref_p_half, rtol=1e-8) + assert xp.allclose(delta, ref_delta, rtol=1e-8) + assert xp.allclose(alpha, ref_alpha, rtol=1e-8) + + +@pytest.mark.parametrize("xp", [_NUMPY_NAMESPACE]) +@pytest.mark.parametrize("index", [(slice(None), slice(None)), (slice(None), 0), (slice(None), 1)]) +def test_pressure_at_model_levels_migrated(index, xp): + + sp = DATA.p_surf + A = DATA.A + B = DATA.B + ref_p_full = DATA.p_full + ref_p_half = DATA.p_half + ref_delta = DATA.delta + ref_alpha = DATA.alpha + + sp, ref_p_full, ref_p_half, ref_delta, ref_alpha, A, B = ( + xp.asarray(x) for x in [sp, ref_p_full, ref_p_half, ref_delta, ref_alpha, A, B] + ) + + sp = sp[index[1]] + + ref_p_full = ref_p_full[index] + ref_p_half = ref_p_half[index] + ref_delta = ref_delta[index] + ref_alpha = ref_alpha[index] + + p_full, p_half, delta, alpha = vertical.pressure_on_hybrid_levels( + A, B, sp, alpha_top="ifs", output=("full", "half", "delta", "alpha") + ) + + # print("p_full", repr(p_full)) + # print("p_half", repr(p_half)) + # print("delta", repr(delta)) + # print("alpha", repr(alpha)) + + assert xp.allclose(p_full, ref_p_full, rtol=1e-8) + assert xp.allclose(p_half, ref_p_half, rtol=1e-8) + assert xp.allclose(delta, ref_delta, rtol=1e-8) + assert xp.allclose(alpha, ref_alpha, rtol=1e-8) + + +# @pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) +@pytest.mark.parametrize("xp, device", [(_NUMPY_NAMESPACE, "cpu")]) +@pytest.mark.parametrize("index", [(slice(None), slice(None)), (slice(None), 0), (slice(None), 1)]) +def test_relative_geopotential_thickness_ori(index, xp, device): + + alpha = DATA.alpha + delta = DATA.delta + t = DATA.t + q = DATA.q + z_ref = DATA.z + + z_ref, t, q, alpha, delta = (xp.asarray(x, device=device) for x in [z_ref, t, q, alpha, delta]) + + alpha = alpha[index] + delta = delta[index] + t = t[index] + q = q[index] + z_ref = z_ref[index] + + z = vertical.relative_geopotential_thickness(alpha, delta, t, q) + # print("z", repr(z)) + # print("z_ref", repr(z_ref)) + + assert xp.allclose(z, z_ref, rtol=1e-8) + + +# @pytest.mark.parametrize("xp, device", NAMESPACE_DEVICES) +@pytest.mark.parametrize("xp, device", [(_NUMPY_NAMESPACE, "cpu")]) +@pytest.mark.parametrize("index", [(slice(None), slice(None)), (slice(None), 0), (slice(None), 1)]) +def test_relative_geopotential_thickness_migrated(index, xp, device): + + alpha = DATA.alpha + delta = DATA.delta + t = DATA.t + q = DATA.q + z_ref = DATA.z + + z_ref, t, q, alpha, delta = (xp.asarray(x, device=device) for x in [z_ref, t, q, alpha, delta]) + + alpha = alpha[index] + delta = delta[index] + t = t[index] + q = q[index] + z_ref = z_ref[index] + + z = vertical.relative_geopotential_thickness_on_hybrid_levels_from_alpha_delta(t, q, alpha, delta) + # print("z", repr(z)) + # print("z_ref", repr(z_ref)) + + assert xp.allclose(z, z_ref, rtol=1e-8) + + +@pytest.mark.parametrize("xp", [_NUMPY_NAMESPACE]) +@pytest.mark.parametrize( + "h,p_ref", + [ + (0, 101183.94696484), + (1, 101171.8606369517), + (100.0, 99979.6875841272), + (5000.0, 53738.035306025726), + (50000.0, 84.2265165561), + ], +) +def test_pressure_at_height_levels_all_ori(h, p_ref, xp): + sp = DATA.p_surf + A = DATA.A + B = DATA.B + t = DATA.t + q = DATA.q + + sp, h, t, q, A, B = (xp.asarray(x) for x in [sp, h, t, q, A, B]) + + sp = sp[0] # use only the first surface pressure value + t = t[:, 0] + q = q[:, 0] + + p = vertical.pressure_at_height_levels(h, t, q, sp, A, B, alpha_top="ifs") + assert xp.isclose(p, p_ref) + + +@pytest.mark.parametrize("xp", [_NUMPY_NAMESPACE]) +@pytest.mark.parametrize( + "h,p_ref", + [ + (0, 101183.94696484), + (1, 101171.8606369517), + (100.0, 99979.6875841272), + (5000.0, 53738.035306025726), + ], +) +def test_pressure_at_height_levels_part(h, p_ref, xp): + # get only levels from 90 to 136/137 + part = slice(90, None) + + sp = DATA.p_surf + A = DATA.A + B = DATA.B + t = DATA.t + q = DATA.q + + assert len(A) == len(B) == len(t) + 1 == len(q) + 1 + + sp, h, t, q, A, B = (xp.asarray(x) for x in [sp, h, t, q, A, B]) + + sp = sp[0] + A = A[part] + B = B[part] + t = t[part, 0] + q = q[part, 0] + + p = vertical.pressure_at_height_levels(h, t, q, sp, A, B, alpha_top="ifs") + assert xp.isclose(p, p_ref) + + +@pytest.mark.parametrize("xp", [_NUMPY_NAMESPACE]) +def test_pressure_at_height_levels_multi_point(xp): + h = 5000.0 # height in m + sp = DATA.p_surf + A = DATA.A + B = DATA.B + t = DATA.t + q = DATA.q + + assert len(A) == len(B) == len(t) + 1 == len(q) + 1 + sp, h, t, q, A, B = (xp.asarray(x) for x in [sp, h, t, q, A, B]) + + p_ref = np.array([53738.035306025726, 27290.9128315574]) + + p = vertical.pressure_at_height_levels(h, t, q, sp, A, B, alpha_top="ifs") + assert xp.allclose(p, p_ref) + + +@pytest.mark.parametrize("xp", [_NUMPY_NAMESPACE]) +@pytest.mark.parametrize( + "h_target,p_ref", + [ + (0, 101183.94696484), + (1, 101171.8606369517), + (100.0, 99979.6875841272), + (5000.0, 53738.035306025726), + (50000.0, 84.2265165561), + ], +) +def test_pressure_at_height_levels_all_migrated_1(h_target, p_ref, xp): + sp = DATA.p_surf + A = DATA.A + B = DATA.B + t = DATA.t + q = DATA.q + + # p_ref = [p_ref] + + sp, h_target, p_ref, t, q, A, B = (xp.asarray(x) for x in [sp, h_target, p_ref, t, q, A, B]) + + sp = sp[0] # use only the first surface pressure value + t = t[:, 0] + q = q[:, 0] + + p_ml = vertical.pressure_on_hybrid_levels(A, B, sp, alpha_top="ifs", output="full") + h_ml = vertical.height_on_hybrid_levels( + t, q, 0, A, B, sp, alpha_top="ifs", h_type="geopotential", h_reference="ground" + ) + + p = vertical.interpolate_monotonic( + p_ml, h_ml, h_target, aux_min_level_data=sp, aux_min_level_coord=0.0, interpolation="linear" + ) + + # print("p:", repr(p), "p_ref:", repr(p_ref), p - p_ref) + # print("diff:", np.allclose(p, p_ref), np.abs(p - p_ref) < 1e-3 + 1e-8 * np.abs(p_ref)) + # print("isclose:", np.isclose(p, p_ref, rtol=1e-8, atol=1e-5)) + assert np.allclose(p, p_ref, atol=1e-3, rtol=1e-8) + + +@pytest.mark.parametrize("xp", [_NUMPY_NAMESPACE]) +@pytest.mark.parametrize( + "h_target,p_ref", + [ + (0, 101183.94696484), + (1, 101171.8606369517), + (100.0, 99979.6875841272), + (5000.0, 53738.035306025726), + (50000.0, 84.2265165561), + ], +) +def test_pressure_at_height_levels_all_migrated_2(h_target, p_ref, xp): + sp = DATA.p_surf + A = DATA.A + B = DATA.B + t = DATA.t + q = DATA.q + + # p_ref = [p_ref] + + sp, h_target, p_ref, t, q, A, B = (xp.asarray(x) for x in [sp, h_target, p_ref, t, q, A, B]) + + sp = sp[0] # use only the first surface pressure value + t = t[:, 0] + q = q[:, 0] + + p_full, alpha, delta = vertical.pressure_on_hybrid_levels( + A, B, sp, alpha_top="ifs", output=("full", "alpha", "delta") + ) + + z = vertical.relative_geopotential_thickness_on_hybrid_levels_from_alpha_delta(t, q, alpha, delta) + h = vertical.geopotential_height_from_geopotential(z) + + p = vertical.interpolate_monotonic( + p_full, h, h_target, aux_min_level_data=sp, aux_min_level_coord=0.0, interpolation="linear" + ) + + # print("p:", repr(p), "p_ref:", repr(p_ref), p - p_ref) + # print("diff:", np.allclose(p, p_ref), np.abs(p - p_ref) < 1e-3 + 1e-8 * np.abs(p_ref)) + # print("isclose:", np.isclose(p, p_ref, rtol=1e-8, atol=1e-5)) + assert np.allclose(p, p_ref, atol=1e-3, rtol=1e-8) + + +@pytest.mark.parametrize("xp", [_NUMPY_NAMESPACE]) +@pytest.mark.parametrize( + "h_target,p_ref", + [ + (0, 101183.94696484), + (1, 101171.8606369517), + (100.0, 99979.6875841272), + (5000.0, 53738.035306025726), + (50000.0, 84.2265165561), + ], +) +def test_pressure_at_height_levels_all_migrated_3(h_target, p_ref, xp): + sp = DATA.p_surf + A = DATA.A + B = DATA.B + t = DATA.t + q = DATA.q + + p_ref = [p_ref] + + sp, h_target, p_ref, t, q, A, B = (xp.asarray(x) for x in [sp, h_target, p_ref, t, q, A, B]) + + sp = sp[0] # use only the first surface pressure value + t = t[:, 0] + q = q[:, 0] + + p_ml = vertical.pressure_on_hybrid_levels(A, B, sp, output="full") + p = vertical.interpolate_hybrid_to_height_levels( + p_ml, + h_target, + t, + q, + 0, + A, + B, + sp, + alpha_top="ifs", + aux_bottom_data=sp, + aux_bottom_h=0.0, + h_type="geopotential", + h_reference="ground", + interpolation="linear", + ) + + assert np.allclose(p, p_ref, atol=1e-3, rtol=1e-8) diff --git a/tests/vertical/test_xr_monotonic.py b/tests/vertical/test_xr_monotonic.py new file mode 100644 index 0000000..edf88f9 --- /dev/null +++ b/tests/vertical/test_xr_monotonic.py @@ -0,0 +1,148 @@ +# (C) Copyright 2021 ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. +# + +import numpy as np +import pytest + +from earthkit.meteo.utils.testing import NO_XARRAY + +# TODO: implement tests once xarray interpolation is implemented + +# These tests reuse the same test cases as in test_array_monotonic.py + +# The type of the input data per level is encoded in the test name as three letters with: +# s: scalar +# a: array +# +# So, e.g. "s_a_s" means the following input data on a level: +# - data is scalar +# - coord is array +# - target_coord is scalar + + +def _make_xr(xp, data, coord): + """Make xarray Dataset from data and coord arrays""" + import xarray as xr + + data_is_scalar = xp.ndim(data[0]) == 0 + coord_is_scalar = xp.ndim(coord[0]) == 0 + + if data_is_scalar and coord_is_scalar: + data_da = xr.DataArray( + data, + dims=("level",), + coords={"level": coord}, + ) + + ds = xr.Dataset({"data": data_da}) + + elif not data_is_scalar and coord_is_scalar: + x_num = data.shape[1] + level_num = data.shape[0] + + x_dim = xp.arange(x_num) + level_dim = xp.arange(level_num) + + data_da = xr.DataArray( + data, + dims=("level", "x"), + coords={"level": level_dim, "x": x_dim}, + ) + + ds = xr.Dataset({"data": data_da}) + + elif not data_is_scalar and not coord_is_scalar: + assert data.shape == coord.shape + + x_num = data.shape[1] + level_num = data.shape[0] + + x_dim = xp.arange(x_num) + level_dim = xp.arange(level_num) + + data_da = xr.DataArray( + data, + dims=("level", "x"), + coords={"level": level_dim, "x": x_dim}, + ) + + level_da = xr.DataArray(coord, dims=("level", "x"), coords={"level": level_dim, "x": x_dim}) + + ds = xr.Dataset({"data": data_da, "level_coord": level_da}) + + return ds + + +def _get_data(): + import os + import sys + + here = os.path.dirname(__file__) + sys.path.insert(0, here) + + from _monotonic_cases import cases + + return cases + + +DATA = _get_data() + + +def make_input(conf_id): + if NO_XARRAY: + return None + + for d in DATA[conf_id]: + data, coord, target_coord, mode, expected_data = d + + yield *_make_input_xr(data, coord, target_coord, expected_data), mode + + +def _make_input_xr(data, coord, target_coord, expected_data, xp=np, device="cpu"): + data = xp.asarray(data, device=device) + coord = xp.asarray(coord, device=device) + target_coord = xp.asarray(target_coord, device=device) + expected_data = xp.asarray(expected_data, device=device) + + ds_input = _make_xr(xp, data, coord) + ds_expected = _make_xr(xp, expected_data, target_coord) + + return ds_input, ds_expected + + +@pytest.mark.skipif(NO_XARRAY, reason="Xarray tests disabled") +@pytest.mark.parametrize("ds_input,ds_expected,mode", make_input("pressure_s_s_s")) +def test_xr_interpolate_monotonic_s_s_s(ds_input, ds_expected, mode): + # print(ds_input) + # print(ds_expected) + pass + + +@pytest.mark.skipif(NO_XARRAY, reason="Xarray tests disabled") +@pytest.mark.parametrize("ds_input,ds_expected,mode", make_input("pressure_a_a_s")) +def test_xr_interpolate_monotonic_a_a_s(ds_input, ds_expected, mode): + pass + + +@pytest.mark.skipif(NO_XARRAY, reason="Xarray tests disabled") +@pytest.mark.parametrize("ds_input,ds_expected,mode", make_input("pressure_a_s_s")) +def test_xr_interpolate_monotonic_a_s_s(ds_input, ds_expected, mode): + pass + + +@pytest.mark.skipif(NO_XARRAY, reason="Xarray tests disabled") +@pytest.mark.parametrize("ds_input,ds_expected,mode", make_input("pressure_a_s_a")) +def test_xr_interpolate_monotonic_a_s_a(ds_input, ds_expected, mode): + pass + + +@pytest.mark.skipif(NO_XARRAY, reason="Xarray tests disabled") +@pytest.mark.parametrize("ds_input,ds_expected,mode", make_input("pressure_a_a_a")) +def test_xr_interpolate_monotonic_a_a_a(ds_input, ds_expected, mode): + pass