├── AI
    └── GTC
    │   ├── Global Temperature Change.ipynb
    │   ├── __pycache__
    │       ├── utils.cpython-37.pyc
    │       └── utils.cpython-39.pyc
    │   ├── data
    │       ├── datasheet.md
    │       └── global_temperature.csv
    │   ├── req.txt
    │   └── utils.py
├── ClimateDataProject.ipynb
├── DataDownload.ipynb
├── H5py
    ├── Africa 2.ipynb
    └── Africa.ipynb
├── IranClimateChange.ipynb
├── LICENSE
├── NetCDF4
    └── NetCDF4.ipynb
├── README.md
├── experiments
    ├── AtmosGCM
    │   ├── GCMDriver
    │   │   ├── GCMDriver.jl
    │   │   ├── baroclinicwave_problem.jl
    │   │   ├── gcm_base_states.jl
    │   │   ├── gcm_bcs.jl
    │   │   ├── gcm_moisture_profiles.jl
    │   │   ├── gcm_perturbations.jl
    │   │   ├── gcm_sources.jl
    │   │   └── heldsuarez_problem.jl
    │   ├── heldsuarez.jl
    │   ├── moist_baroclinic_wave_bulksfcflux.jl
    │   └── nonhydrostatic_gravity_wave.jl
    ├── AtmosLES
    │   ├── Artifacts.toml
    │   ├── bomex_les.jl
    │   ├── bomex_model.jl
    │   ├── bomex_single_stack.jl
    │   ├── cfsite_hadgem2-a_07_amip.jl
    │   ├── convective_bl_les.jl
    │   ├── convective_bl_model.jl
    │   ├── dycoms.jl
    │   ├── ekman_layer_model.jl
    │   ├── rising_bubble_bryan.jl
    │   ├── rising_bubble_theta_formulation.jl
    │   ├── schar_scalar_advection.jl
    │   ├── squall_line.jl
    │   ├── stable_bl_les.jl
    │   ├── stable_bl_model.jl
    │   ├── surfacebubble.jl
    │   └── taylor_green.jl
    ├── OceanBoxGCM
    │   ├── homogeneous_box.jl
    │   ├── ocean_gyre.jl
    │   └── simple_box.jl
    ├── OceanSplitExplicit
    │   └── simple_box.jl
    └── TestCase
    │   ├── baroclinic_wave.jl
    │   ├── baroclinic_wave_fvm.jl
    │   ├── isothermal_zonal_flow.jl
    │   ├── risingbubble.jl
    │   ├── risingbubble_fvm.jl
    │   ├── solid_body_rotation.jl
    │   ├── solid_body_rotation_fvm.jl
    │   └── solid_body_rotation_mountain.jl
└── xarray
    ├── ClimateDataProject.ipynb
    ├── DataDownload.ipynb
    └── IranClimateChange.ipynb
/AI/GTC/Global Temperature Change.ipynb:
--------------------------------------------------------------------------------
  1 | {
  2 |  "cells": [
  3 |   {
  4 |    "cell_type": "code",
  5 |    "execution_count": null,
  6 |    "id": "2aa357de-1033-420e-87c9-5fcdb66018bb",
  7 |    "metadata": {},
  8 |    "outputs": [],
  9 |    "source": [
 10 |     "import utils \n",
 11 |     "import pandas as pd\n",
 12 |     "import numpy as np\n",
 13 |     "import IPython\n",
 14 |     "from itables import init_notebook_mode\n",
 15 |     "init_notebook_mode(all_interactive = False)\n",
 16 |     "print('All packages imported successfully!')"
 17 |    ]
 18 |   },
 19 |   {
 20 |    "cell_type": "code",
 21 |    "execution_count": null,
 22 |    "id": "af7fe38b-6621-48aa-b611-b79c7dbcaa92",
 23 |    "metadata": {},
 24 |    "outputs": [],
 25 |    "source": [
 26 |     "temperature_data = pd.read_csv('E:/openai/data/global_temperature.csv') #import the data\n",
 27 |     "temperature_data.columns.values[4:] = temperature_data.columns[4:].map(int) #column names to int for easy manipulation\n",
 28 |     "print('the dataset contains', len(temperature_data), 'rows')\n",
 29 |     "temperature_data.tail()"
 30 |    ]
 31 |   },
 32 |   {
 33 |    "cell_type": "code",
 34 |    "execution_count": null,
 35 |    "id": "d045956b-d506-49a4-81cd-4187eb6bf919",
 36 |    "metadata": {},
 37 |    "outputs": [],
 38 |    "source": [
 39 |     "plot_data = temperature_data[np.array(range(1880,2022,1))]"
 40 |    ]
 41 |   },
 42 |   {
 43 |    "cell_type": "code",
 44 |    "execution_count": null,
 45 |    "id": "89b81b1e-b057-4192-9be8-f71e06b60f39",
 46 |    "metadata": {},
 47 |    "outputs": [],
 48 |    "source": [
 49 |     "diff = plot_data.sub(plot_data.iloc[:, 101:132].dropna().mean(axis=1) - 0.69, axis=0)  "
 50 |    ]
 51 |   },
 52 |   {
 53 |    "cell_type": "code",
 54 |    "execution_count": null,
 55 |    "id": "e054213f-e82b-4ca2-ac72-dde759c1855a",
 56 |    "metadata": {},
 57 |    "outputs": [],
 58 |    "source": [
 59 |     "utils.bar_global(diff)"
 60 |    ]
 61 |   },
 62 |   {
 63 |    "cell_type": "code",
 64 |    "execution_count": null,
 65 |    "id": "a90cec72-3418-4f72-aa62-4d81d778ae48",
 66 |    "metadata": {},
 67 |    "outputs": [],
 68 |    "source": [
 69 |     "utils.local_temp_map(temperature_data)"
 70 |    ]
 71 |   },
 72 |   {
 73 |    "cell_type": "code",
 74 |    "execution_count": null,
 75 |    "id": "6014b99d-c899-4b32-8497-e2fb219b8cc0",
 76 |    "metadata": {},
 77 |    "outputs": [],
 78 |    "source": [
 79 |     "utils.slider_global_temp()"
 80 |    ]
 81 |   },
 82 |   {
 83 |    "cell_type": "code",
 84 |    "execution_count": null,
 85 |    "id": "bb74cbaf-d7f0-4abb-a38b-c187f83ab284",
 86 |    "metadata": {},
 87 |    "outputs": [],
 88 |    "source": [
 89 |     "#https://climate.nasa.gov/images-of-change/?id=796#796-collapsing-ice-shelf-reveals-a-possible-new-island-eastern-antarctica\n",
 90 |     "src = 'https://cdn.knightlab.com/libs/juxtapose/latest/embed/index.html?uid=dab72496-6d1f-11ed-b5bd-6595d9b17862'\n",
 91 |     "IPython.display.IFrame(src, width = 700, height = 720)"
 92 |    ]
 93 |   },
 94 |   {
 95 |    "cell_type": "code",
 96 |    "execution_count": null,
 97 |    "id": "8f8a44f7-4c5f-4ba8-a5a1-02f8c9ca481f",
 98 |    "metadata": {},
 99 |    "outputs": [],
100 |    "source": [
101 |     "#https://climate.nasa.gov/images-of-change/?id=778#778-melting-glaciers-enlarge-lakes-on-tibetan-plateau\n",
102 |     "src = 'https://cdn.knightlab.com/libs/juxtapose/latest/embed/index.html?uid=269f7416-6d21-11ed-b5bd-6595d9b17862'\n",
103 |     "IPython.display.IFrame(src, width = 750, height = 520)"
104 |    ]
105 |   },
106 |   {
107 |    "cell_type": "code",
108 |    "execution_count": null,
109 |    "id": "80fb8a04-8d2f-41a3-80ac-efdc6706375c",
110 |    "metadata": {},
111 |    "outputs": [],
112 |    "source": [
113 |     "#https://climate.nasa.gov/images-of-change?id=777#777-grand-plateau-glacier-retreats\n",
114 |     "src = 'https://cdn.knightlab.com/libs/juxtapose/latest/embed/index.html?uid=0c37b40e-7027-11ed-b5bd-6595d9b17862'\n",
115 |     "IPython.display.IFrame(src, width = 700, height = 550)"
116 |    ]
117 |   },
118 |   {
119 |    "cell_type": "code",
120 |    "execution_count": null,
121 |    "id": "b7ee8beb-1eb4-4d27-8793-80116dfbf0da",
122 |    "metadata": {},
123 |    "outputs": [],
124 |    "source": []
125 |   }
126 |  ],
127 |  "metadata": {
128 |   "kernelspec": {
129 |    "display_name": "Python 3 (ipykernel)",
130 |    "language": "python",
131 |    "name": "python3"
132 |   },
133 |   "language_info": {
134 |    "codemirror_mode": {
135 |     "name": "ipython",
136 |     "version": 3
137 |    },
138 |    "file_extension": ".py",
139 |    "mimetype": "text/x-python",
140 |    "name": "python",
141 |    "nbconvert_exporter": "python",
142 |    "pygments_lexer": "ipython3",
143 |    "version": "3.9.7"
144 |   }
145 |  },
146 |  "nbformat": 4,
147 |  "nbformat_minor": 5
148 | }
149 | 
--------------------------------------------------------------------------------
/AI/GTC/__pycache__/utils.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aradfarahani/Climate-Change/0fcf3947422b5f8404b74b7d7873e43ae015f969/AI/GTC/__pycache__/utils.cpython-37.pyc
--------------------------------------------------------------------------------
/AI/GTC/__pycache__/utils.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aradfarahani/Climate-Change/0fcf3947422b5f8404b74b7d7873e43ae015f969/AI/GTC/__pycache__/utils.cpython-39.pyc
--------------------------------------------------------------------------------
/AI/GTC/data/datasheet.md:
--------------------------------------------------------------------------------
 1 | # Datasheet: *Global temperature data* Lab 3
 2 | 
 3 | Author: DeepLearning.AI (DLAI)
 4 | 
 5 | Files:
 6 | 	global_temperature.csv
 7 | 	.png files in NASA subfolder
 8 | 
 9 | ## Motivation
10 | 
11 | The data is a collection of global temperature mesurements. The data comes from two sources: National Oceanic and Atmospheric Administration (NOAA) and National Aeronautics and Space Administration (NASA).
12 | 
13 | The dataframe global_temperature.csv was downloaded from the National Oceanic and Atmospheric Administration (NOAA) (various datasets available here: https://www.ncei.noaa.gov/access/search/index)
14 | The .png files are from National Aeronautics and Space Administration (NASA) (available here: https://climate.nasa.gov/climate_resources/139/video-global-warming-from-1880-to-2022/)
15 | 
16 | The data was downloaded to be used in the DLAI course "AI for Climate Change". 
17 | 
18 | ## Composition
19 | 
20 | global_temperature.csv
21 | 
22 | The dataset contains the temperature measurements for various (794) stations around the world between 1880 and 2021. The dataset includes the following columns: STATION, NAME, LATITUDE, LONGITUDE. The rest of the column names are years from 1880 to 2021. Each row represents one measuring station and the yearly data is in the corresponding columns.
23 | The metadata columns are all fully populated, but there is some missing temperature data for various years at some stations.
24 | 
25 | .png files in NASA subfolder
26 | 
27 | The files are global world maps with a superimposed heatmap of the temperature change with respect to the NASA's baseline period (1951-1980). The images show the time range between years 1884 and 2020. 
28 | 
--------------------------------------------------------------------------------
/AI/GTC/req.txt:
--------------------------------------------------------------------------------
1 | folium==0.12.1.post1
2 | mplcursors==0.5.2
3 | numpy==1.21.2
4 | pandas==1.3.4
5 | ipywidgets==8.0.2
6 | matplotlib==3.5.3
--------------------------------------------------------------------------------
/AI/GTC/utils.py:
--------------------------------------------------------------------------------
  1 | import numpy as np
  2 | import pandas as pd
  3 | import matplotlib.pyplot as plt
  4 | import mplcursors
  5 | import base64
  6 | import folium
  7 | import IPython
  8 | import ipywidgets as widgets
  9 | from IPython.display import clear_output
 10 | 
 11 | 
 12 | def bar_global(data: pd.core.frame.DataFrame) -> None:
 13 |     '''
 14 |     Plotting function that gets the temperature timeseries dataframe as input
 15 |     and creates and interactive bar plot with the average differences per year from 1880 to 2021.
 16 | 
 17 |     data: Wide timeseries dataframe with temperature for multiple weather stations in Celsius. Stations are in the rows,
 18 |     years are in the columns.
 19 |     '''
 20 | 
 21 |     values = np.array(data.mean())  # get global average difference per year
 22 |     clrs = ["blue" if (x < 0) else "red" for x in values]  # colors for positive and negative differences
 23 |     fig, ax = plt.subplots(figsize=(9, 4))  # set plot size
 24 |     line = ax.bar(np.array(range(1880, 2022, 1)), values, color=clrs)  # bar plot
 25 |     ax.set_xlim(1879, 2022)  # set axis limits
 26 |     plt.title("Station network temperature difference \nwith respect to 1850-1900 average", fontsize=12)
 27 |     plt.xlabel("Year", fontsize=12)
 28 |     plt.ylabel("Temperature ($ ^{\circ}$C)", fontsize=12)
 29 | 
 30 |     # cursor interaction for creating labels
 31 |     cursor = mplcursors.cursor()
 32 | 
 33 |     @cursor.connect("add")
 34 |     def on_add(sel):
 35 |         x, y, width, height = sel.artist[sel.index].get_bbox().bounds
 36 |         sel.annotation.get_bbox_patch().set(fc="white", alpha=1)
 37 |         sel.annotation.set(
 38 |             text=f"Year {int(x) + 1} \n {values[int(x) - 1880 + 1]:.2f}" + "C" + "$^{\circ}$",
 39 |             position=(0, 20),
 40 |             anncoords="offset points",
 41 |         )
 42 |         sel.annotation.xy = (x + width / 2, height)
 43 |         sel.axvline(x=x, color="k")
 44 | 
 45 |     # show plot
 46 |     plt.show()
 47 | 
 48 | 
 49 | def local_temp_map(df: pd.core.frame.DataFrame) -> folium.Map:
 50 |     '''
 51 |     Plotting function that gets the temperature timeseries dataframe with additional information and returns an interactive map
 52 |     with bar plots for each station.
 53 | 
 54 |     df: Wide timeseries dataframe with temperature for multiple weather stations in Celsius. Stations are in the rows,
 55 |     years are in the columns. Contains geographical coordinates and station names.
 56 |     '''
 57 | 
 58 |     folium_map = folium.Map(
 59 |         location=[35, 0],
 60 |         zoom_start=1.5,
 61 |         tiles="cartodb positron",
 62 |         max_bounds=False,
 63 |     )
 64 |     loc = df[["LATITUDE", "LONGITUDE"]]
 65 |     width, height = 500, 230
 66 | 
 67 |     for index, location_info in loc.iterrows():
 68 |         png = f"plots/{df['STATION'].loc[index]}.png"
 69 |         encoded = base64.b64encode(open(png, "rb").read())
 70 |         html = '
'.format
 71 |         iframe = folium.IFrame(
 72 |             html(encoded.decode("UTF-8")), width=width, height=height
 73 |         )
 74 |         popup = folium.Popup(iframe, max_width=2650)
 75 |         folium.Marker(
 76 |             [location_info['LATITUDE'], location_info['LONGITUDE']],
 77 |             popup=popup,
 78 |             icon=folium.Icon(color='black', icon_color='white'),
 79 |         ).add_to(folium_map)
 80 | 
 81 |     return folium_map
 82 | 
 83 | 
 84 | def slider_global_temp() -> None:
 85 |     '''
 86 |     Creates an interactive slider that shows the global temperature around the globe from 1884 to 2020
 87 |     '''
 88 | 
 89 |     # set up plot
 90 |     out = widgets.Output()
 91 | 
 92 |     def update(Year=1884):
 93 |         with out:
 94 |             clear_output(wait=True)
 95 |             display(IPython.display.Image(f'data/NASA/{Year}.png'))
 96 | 
 97 |     slider = widgets.IntSlider(
 98 |         min=1884, max=2020, layout=widgets.Layout(width='95%')
 99 |     )
100 |     widgets.interactive(update, Year=slider)
101 | 
102 |     layout = widgets.Layout(
103 |         flex_flow='column', align_items='center', width='700px'
104 |     )
105 | 
106 |     wid = widgets.HBox(children=(slider, out), layout=layout)
107 |     display(wid)
108 | 
--------------------------------------------------------------------------------
/DataDownload.ipynb:
--------------------------------------------------------------------------------
  1 | {
  2 |  "cells": [
  3 |   {
  4 |    "cell_type": "code",
  5 |    "execution_count": 1,
  6 |    "id": "1d33bfd4-71bd-4fe9-ac29-9f1fa1f5afb3",
  7 |    "metadata": {},
  8 |    "outputs": [
  9 |     {
 10 |      "name": "stderr",
 11 |      "output_type": "stream",
 12 |      "text": [
 13 |       "2023-08-12 00:53:06,445 INFO Welcome to the CDS\n",
 14 |       "2023-08-12 00:53:06,448 INFO Sending request to https://cds.climate.copernicus.eu/api/v2/resources/reanalysis-era5-land-monthly-means\n",
 15 |       "2023-08-12 00:53:06,649 INFO Request is queued\n",
 16 |       "2023-08-12 01:05:35,257 INFO Request is completed\n",
 17 |       "2023-08-12 01:05:35,259 INFO Downloading https://download-0011-clone.copernicus-climate.eu/cache-compute-0011/cache/data5/adaptor.mars.internal-1691789677.5640478-20790-2-5a4356b1-f303-4f78-a822-8156ec9b5c60.zip to download.netcdf.zip (73.5M)\n",
 18 |       "2023-08-12 01:19:02,575 ERROR Download interupted: HTTPSConnectionPool(host='download-0011-clone.copernicus-climate.eu', port=443): Read timed out.\n",
 19 |       "2023-08-12 01:19:02,579 ERROR Download incomplete, downloaded 41329664 byte(s) out of 77073404\n",
 20 |       "2023-08-12 01:19:02,580 WARNING Sleeping 10 seconds\n",
 21 |       "2023-08-12 01:19:12,588 WARNING Resuming download at byte 41329664\n",
 22 |       "2023-08-12 01:21:12,237 INFO Download rate 80.3K/s                                                                     \n"
 23 |      ]
 24 |     },
 25 |     {
 26 |      "data": {
 27 |       "text/plain": [
 28 |        "Result(content_length=77073404,content_type=application/zip,location=https://download-0011-clone.copernicus-climate.eu/cache-compute-0011/cache/data5/adaptor.mars.internal-1691789677.5640478-20790-2-5a4356b1-f303-4f78-a822-8156ec9b5c60.zip)"
 29 |       ]
 30 |      },
 31 |      "execution_count": 1,
 32 |      "metadata": {},
 33 |      "output_type": "execute_result"
 34 |     }
 35 |    ],
 36 |    "source": [
 37 |     "import cdsapi\n",
 38 |     "\n",
 39 |     "c = cdsapi.Client()\n",
 40 |     "\n",
 41 |     "c.retrieve(\n",
 42 |     "    'reanalysis-era5-land-monthly-means',\n",
 43 |     "    {\n",
 44 |     "        'product_type': 'monthly_averaged_reanalysis',\n",
 45 |     "        'variable': [\n",
 46 |     "            'leaf_area_index_low_vegetation', 'runoff', 'skin_reservoir_content',\n",
 47 |     "            'skin_temperature', 'snow_cover', 'snow_depth',\n",
 48 |     "            'snow_evaporation', 'snowfall', 'snowmelt',\n",
 49 |     "            'total_evaporation', 'total_precipitation',\n",
 50 |     "        ],\n",
 51 |     "        'year': [\n",
 52 |     "            '2000', '2001', '2002',\n",
 53 |     "            '2003', '2004', '2005',\n",
 54 |     "            '2006', '2007', '2008',\n",
 55 |     "            '2009', '2010', '2011',\n",
 56 |     "            '2012', '2013', '2014',\n",
 57 |     "            '2015', '2016', '2017',\n",
 58 |     "            '2018', '2019', '2020',\n",
 59 |     "            '2021',\n",
 60 |     "        ],\n",
 61 |     "        'month': [\n",
 62 |     "            '01', '02', '03',\n",
 63 |     "            '04', '05', '06',\n",
 64 |     "            '07', '08', '09',\n",
 65 |     "            '10', '11', '12',\n",
 66 |     "        ],\n",
 67 |     "        'time': '00:00',\n",
 68 |     "        'area': [\n",
 69 |     "            41.29, 44.69, 22.51,\n",
 70 |     "            62.13,\n",
 71 |     "        ],\n",
 72 |     "        'format': 'netcdf.zip',\n",
 73 |     "    },\n",
 74 |     "    'download.netcdf.zip')"
 75 |    ]
 76 |   },
 77 |   {
 78 |    "cell_type": "code",
 79 |    "execution_count": null,
 80 |    "id": "927ecb08-c78e-4d15-ad4a-bcb14e4be59d",
 81 |    "metadata": {},
 82 |    "outputs": [],
 83 |    "source": []
 84 |   }
 85 |  ],
 86 |  "metadata": {
 87 |   "kernelspec": {
 88 |    "display_name": "Python 3 (ipykernel)",
 89 |    "language": "python",
 90 |    "name": "python3"
 91 |   },
 92 |   "language_info": {
 93 |    "codemirror_mode": {
 94 |     "name": "ipython",
 95 |     "version": 3
 96 |    },
 97 |    "file_extension": ".py",
 98 |    "mimetype": "text/x-python",
 99 |    "name": "python",
100 |    "nbconvert_exporter": "python",
101 |    "pygments_lexer": "ipython3",
102 |    "version": "3.9.7"
103 |   }
104 |  },
105 |  "nbformat": 4,
106 |  "nbformat_minor": 5
107 | }
108 | 
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2023 Mahdi Farmahini Farahani
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.codefactor.io/repository/github/aradfarahani/climate-change)
2 | # Climate Change
3 | Some scripts for Climate Change Monitoring in python and julia
4 | 
--------------------------------------------------------------------------------
/experiments/AtmosGCM/GCMDriver/baroclinicwave_problem.jl:
--------------------------------------------------------------------------------
 1 | # This file establishes the default initial conditions, boundary conditions and sources
 2 | # for the baroclinicwave_problem experiment, following [Ullrich2014](@cite)
 3 | 
 4 | # Override default CLIMAParameters for consistency with literature on this case
 5 | CLIMAParameters.Planet.press_triple(::EarthParameterSet) = 610.78
 6 | 
 7 | struct BaroclinicWaveProblem{BCS, ISP, ISA, WP, BS, MP} <: AbstractAtmosProblem
 8 |     boundaryconditions::BCS
 9 |     init_state_prognostic::ISP
10 |     init_state_auxiliary::ISA
11 |     perturbation::WP
12 |     base_state::BS
13 |     moisture_profile::MP
14 | end
15 | function BaroclinicWaveProblem(
16 |     physics::AtmosPhysics;
17 |     boundaryconditions = (AtmosBC(physics;), AtmosBC(physics;)),
18 |     perturbation = nothing,
19 |     base_state = nothing,
20 |     moisture_profile = nothing,
21 | )
22 |     # Set up defaults
23 |     if isnothing(perturbation)
24 |         perturbation = DeterministicPerturbation()
25 |     end
26 |     if isnothing(base_state)
27 |         base_state = BCWaveBaseState()
28 |     end
29 |     if isnothing(moisture_profile)
30 |         moisture_profile = MoistLowTropicsMoistureProfile()
31 |     end
32 | 
33 |     problem = (
34 |         boundaryconditions,
35 |         init_gcm_experiment!,
36 |         (_...) -> nothing,
37 |         perturbation,
38 |         base_state,
39 |         moisture_profile,
40 |     )
41 |     return BaroclinicWaveProblem{typeof.(problem)...}(problem...)
42 | end
43 | 
44 | problem_name(::BaroclinicWaveProblem) = "BaroclinicWave"
45 | 
46 | setup_source(::BaroclinicWaveProblem) = (Gravity(), Coriolis())
47 | 
--------------------------------------------------------------------------------
/experiments/AtmosGCM/GCMDriver/gcm_base_states.jl:
--------------------------------------------------------------------------------
  1 | # GCM Initial Base State
  2 | # This file contains helpers and lists currely avaiable options
  3 | 
  4 | abstract type AbstractBaseState end
  5 | struct ZeroBaseState <: AbstractBaseState end
  6 | struct BCWaveBaseState <: AbstractBaseState end
  7 | struct HeldSuarezBaseState <: AbstractBaseState end
  8 | 
  9 | # Helper for parsing `--init-base-state`` command line argument
 10 | function parse_base_state_arg(arg)
 11 |     if arg === nothing
 12 |         base_state = nothing
 13 |     elseif arg == "bc_wave"
 14 |         base_state = BCWaveBaseState()
 15 |     elseif arg == "heldsuarez"
 16 |         base_state = HeldSuarezBaseState()
 17 |     elseif arg == "zero"
 18 |         base_state = ZeroBaseState()
 19 |     else
 20 |         error("unknown base state: " * arg)
 21 |     end
 22 | 
 23 |     return base_state
 24 | end
 25 | 
 26 | # Initial base state from rest, independent of the model reference state
 27 | function init_base_state(::ZeroBaseState, bl, state, aux, coords, t)
 28 |     FT = eltype(state)
 29 |     param_set = parameter_set(bl)
 30 |     _R_d::FT = R_d(param_set)
 31 |     _grav::FT = grav(param_set)
 32 |     _a::FT = planet_radius(param_set)
 33 |     _p_0::FT = MSLP(param_set)
 34 |     T_initial = FT(255)
 35 |     r = norm(coords, 2)
 36 |     h = r - _a
 37 | 
 38 |     scale_height = _R_d * FT(T_initial) / _grav
 39 |     p = FT(_p_0) * exp(-h / scale_height)
 40 |     u_ref, v_ref, w_ref = (FT(0), FT(0), FT(0))
 41 |     return T_initial, p, u_ref, v_ref, w_ref
 42 | end
 43 | 
 44 | # Initial base state from rest, consistent with the model reference state
 45 | function init_base_state(::HeldSuarezBaseState, bl, state, aux, coords, t)
 46 |     FT = eltype(state)
 47 | 
 48 |     # T_v is dry ref state
 49 |     T_v = aux.ref_state.T
 50 |     p = aux.ref_state.p
 51 |     u_ref, v_ref, w_ref = (FT(0), FT(0), FT(0))
 52 | 
 53 |     return T_v, p, u_ref, v_ref, w_ref
 54 | end
 55 | 
 56 | # Initial base state following
 57 | # Ullrich et al. (2016) Dynamical Core Model Intercomparison Project (DCMIP2016) Test Case Document
 58 | function init_base_state(::BCWaveBaseState, bl, state, aux, coords, t)
 59 |     FT = eltype(state)
 60 | 
 61 |     # general parameters
 62 |     param_set = parameter_set(bl)
 63 |     _grav::FT = grav(param_set)
 64 |     _R_d::FT = R_d(param_set)
 65 |     _Ω::FT = Omega(param_set)
 66 |     _a::FT = planet_radius(param_set)
 67 |     _p_0::FT = MSLP(param_set)
 68 | 
 69 |     # grid
 70 |     φ = latitude(bl, aux)
 71 |     z = altitude(bl, aux)
 72 |     γ::FT = 1 # set to 0 for shallow-atmosphere case and to 1 for deep atmosphere case
 73 | 
 74 |     # base state parameters
 75 |     k::FT = 3
 76 |     T_E::FT = 310
 77 |     T_P::FT = 240
 78 |     T_0::FT = 0.5 * (T_E + T_P)
 79 |     Γ::FT = 0.005
 80 |     A::FT = 1 / Γ
 81 |     B::FT = (T_0 - T_P) / T_0 / T_P
 82 |     C::FT = 0.5 * (k + 2) * (T_E - T_P) / T_E / T_P
 83 |     b::FT = 2
 84 |     H::FT = _R_d * T_0 / _grav
 85 |     z_t::FT = 15e3
 86 |     λ_c::FT = π / 9
 87 |     φ_c::FT = 2 * π / 9
 88 |     d_0::FT = _a / 6
 89 |     V_p::FT = 1
 90 | 
 91 |     # convenience functions for temperature and pressure
 92 |     τ_z_1::FT = exp(Γ * z / T_0)
 93 |     τ_z_2::FT = 1 - 2 * (z / b / H)^2
 94 |     τ_z_3::FT = exp(-(z / b / H)^2)
 95 |     τ_1::FT = 1 / T_0 * τ_z_1 + B * τ_z_2 * τ_z_3
 96 |     τ_2::FT = C * τ_z_2 * τ_z_3
 97 |     τ_int_1::FT = A * (τ_z_1 - 1) + B * z * τ_z_3
 98 |     τ_int_2::FT = C * z * τ_z_3
 99 |     I_T::FT =
100 |         (cos(φ) * (1 + γ * z / _a))^k -
101 |         k / (k + 2) * (cos(φ) * (1 + γ * z / _a))^(k + 2)
102 | 
103 |     # base state virtual temperature and pressure
104 |     T_v::FT = (τ_1 - τ_2 * I_T)^(-1)
105 |     p::FT = _p_0 * exp(-_grav / _R_d * (τ_int_1 - τ_int_2 * I_T))
106 | 
107 |     # base state velocity
108 |     U::FT =
109 |         _grav * k / _a *
110 |         τ_int_2 *
111 |         T_v *
112 |         (
113 |             (cos(φ) * (1 + γ * z / _a))^(k - 1) -
114 |             (cos(φ) * (1 + γ * z / _a))^(k + 1)
115 |         )
116 |     u_ref::FT =
117 |         -_Ω * (_a + γ * z) * cos(φ) +
118 |         sqrt((_Ω * (_a + γ * z) * cos(φ))^2 + (_a + γ * z) * cos(φ) * U)
119 |     v_ref::FT = 0
120 |     w_ref::FT = 0
121 | 
122 |     return T_v, p, u_ref, v_ref, w_ref
123 | end
124 | 
--------------------------------------------------------------------------------
/experiments/AtmosGCM/GCMDriver/gcm_bcs.jl:
--------------------------------------------------------------------------------
 1 | # GCM Boundary Conditions
 2 | # This file contains helpers and lists currely avaiable options
 3 | 
 4 | # Helper for parsing `--surface-flux` command line argument
 5 | function parse_surface_flux_arg(
 6 |     physics::AtmosPhysics,
 7 |     arg,
 8 |     ::Type{FT},
 9 |     param_set,
10 |     orientation,
11 |     moisture,
12 | ) where {FT}
13 |     if arg === nothing || arg == "default"
14 |         boundaryconditions = (AtmosBC(physics;), AtmosBC(physics;))
15 |     elseif arg == "bulk"
16 |         if !isa(moisture, EquilMoist)
17 |             error("need a moisture model for surface-flux: bulk")
18 |         end
19 |         _C_drag = C_drag(param_set)::FT
20 |         bulk_flux = Varying_SST_TJ16(param_set, orientation, moisture) # GCM-specific function for T_sfc, q_sfc = f(latitude, height)
21 |         #bulk_flux = (T_sfc, q_sfc) # prescribed constant T_sfc, q_sfc
22 |         boundaryconditions = (
23 |             AtmosBC(
24 |                 physics;
25 |                 energy = BulkFormulaEnergy(
26 |                     (bl, state, aux, t, normPu_int) -> _C_drag,
27 |                     (bl, state, aux, t) -> bulk_flux(state, aux, t),
28 |                 ),
29 |                 moisture = BulkFormulaMoisture(
30 |                     (state, aux, t, normPu_int) -> _C_drag,
31 |                     (state, aux, t) -> begin
32 |                         _, q_tot = bulk_flux(state, aux, t)
33 |                         q_tot
34 |                     end,
35 |                 ),
36 |             ),
37 |             AtmosBC(physics;),
38 |         )
39 |     else
40 |         error("unknown surface flux: " * arg)
41 |     end
42 | 
43 |     return boundaryconditions
44 | end
45 | 
46 | # Current options for GCM boundary conditions:
47 | 
48 | """
49 | struct Varying_SST_TJ16{PS, O, MM}
50 |     param_set::PS
51 |     orientation::O
52 |     moisture::MM
53 | 
54 | Defines analytical function for prescribed T_sfc and q_sfc, following
55 | Thatcher and Jablonowski (2016), used to calculate bulk surface fluxes.
56 | T_sfc_pole = SST at the poles (default: 271 K), specified above
57 | """
58 | struct Varying_SST_TJ16{PS, O, MM}
59 |     param_set::PS
60 |     orientation::O
61 |     moisture::MM
62 | end
63 | function (st::Varying_SST_TJ16)(state, aux, t)
64 |     FT = eltype(state)
65 |     φ = latitude(st.orientation, aux)
66 | 
67 |     T_sfc_pole = FT(271.0)              # surface polar temperature
68 |     Δφ = FT(26) * FT(π) / FT(180)       # latitudinal width of Gaussian function
69 |     ΔSST = FT(29)                       # Eq-pole SST difference in K
70 |     T_sfc = ΔSST * exp(-φ^2 / (2 * Δφ^2)) + T_sfc_pole
71 | 
72 |     eps = FT(0.622)
73 |     ρ = state.ρ
74 | 
75 |     q_tot = state.moisture.ρq_tot / ρ
76 |     q = PhasePartition(q_tot)
77 |     param_set = st.param_set
78 |     e_int = internal_energy(st.orientation, state, aux)
79 |     T = air_temperature(param_set, e_int, q)
80 |     p = air_pressure(param_set, T, ρ, q)
81 | 
82 |     _T_triple::FT = T_triple(param_set)          # triple point of water
83 |     _press_triple::FT = press_triple(param_set)  # sat water pressure at T_triple
84 |     _LH_v0::FT = LH_v0(param_set)                # latent heat of vaporization at T_triple
85 |     _R_v::FT = R_v(param_set)                    # gas constant for water vapor
86 | 
87 |     q_sfc =
88 |         eps / p *
89 |         _press_triple *
90 |         exp(-_LH_v0 / _R_v * (FT(1) / T_sfc - FT(1) / _T_triple))
91 | 
92 |     return T_sfc, q_sfc
93 | end
94 | 
--------------------------------------------------------------------------------
/experiments/AtmosGCM/GCMDriver/gcm_moisture_profiles.jl:
--------------------------------------------------------------------------------
 1 | # GCM Initial Moisture Profiles
 2 | # This file contains helpers and lists currely avaiable options
 3 | 
 4 | abstract type AbstractMoistureProfile end
 5 | struct NoMoistureProfile <: AbstractMoistureProfile end
 6 | struct ZeroMoistureProfile <: AbstractMoistureProfile end
 7 | struct MoistLowTropicsMoistureProfile <: AbstractMoistureProfile end
 8 | 
 9 | # Helper for parsing `--init-moisture-profile` command line argument
10 | function parse_moisture_profile_arg(arg)
11 |     if arg === nothing
12 |         moisture_profile = nothing
13 |     elseif arg == "moist_low_tropics"
14 |         moisture_profile = MoistLowTropicsMoistureProfile()
15 |     elseif arg == "zero"
16 |         moisture_profile = ZeroMoistureProfile()
17 |     elseif arg == "dry"
18 |         moisture_profile = NoMoistureProfile()
19 |     else
20 |         error("unknown moisture profile: " * arg)
21 |     end
22 | 
23 |     return moisture_profile
24 | end
25 | 
26 | # Initial moisture profile for a dry model setup
27 | function init_moisture_profile(
28 |     ::NoMoistureProfile,
29 |     bl,
30 |     state,
31 |     aux,
32 |     coords,
33 |     t,
34 |     p,
35 | )
36 |     FT = eltype(state)
37 |     return FT(0)
38 | end
39 | 
40 | # Initial moisture profile for a moist model setup with 0 initial moisture
41 | function init_moisture_profile(
42 |     ::ZeroMoistureProfile,
43 |     bl,
44 |     state,
45 |     aux,
46 |     coords,
47 |     t,
48 |     p,
49 | )
50 |     FT = eltype(state)
51 |     return FT(0)
52 | end
53 | 
54 | # Initial moisture profile following
55 | # Ullrich et al. (2016) Dynamical Core Model Intercomparison Project (DCMIP2016) Test Case Document
56 | function init_moisture_profile(
57 |     ::MoistLowTropicsMoistureProfile,
58 |     bl,
59 |     state,
60 |     aux,
61 |     coords,
62 |     t,
63 |     p,
64 | )
65 |     FT = eltype(state)
66 | 
67 |     param_set = parameter_set(bl)
68 |     _p_0::FT = MSLP(param_set)
69 | 
70 |     φ = latitude(bl, aux)
71 | 
72 |     # Humidity parameters
73 |     p_w::FT = 34e3              # Pressure width parameter for specific humidity
74 |     η_crit::FT = p_w / _p_0     # Critical pressure coordinate
75 |     q_0::FT = 0.018             # Maximum specific humidity (default: 0.018)
76 |     q_t::FT = 1e-12             # Specific humidity above artificial tropopause
77 |     φ_w::FT = 2π / 9            # Specific humidity latitude wind parameter
78 | 
79 |     # get q_tot profile if needed
80 |     η = p / _p_0                # Pressure coordinate η
81 |     if η > η_crit
82 |         q_tot = q_0 * exp(-(φ / φ_w)^4) * exp(-((η - 1) * _p_0 / p_w)^2)
83 |     else
84 |         q_tot = q_t
85 |     end
86 | 
87 |     return q_tot
88 | end
89 | 
--------------------------------------------------------------------------------
/experiments/AtmosGCM/GCMDriver/gcm_perturbations.jl:
--------------------------------------------------------------------------------
  1 | # GCM Initial Perturbation
  2 | # This file contains helpers and lists currely avaiable options
  3 | 
  4 | using Distributions
  5 | using Random
  6 | using Distributions: Uniform
  7 | using Random: rand
  8 | 
  9 | abstract type AbstractPerturbation end
 10 | struct NoPerturbation <: AbstractPerturbation end
 11 | struct DeterministicPerturbation <: AbstractPerturbation end
 12 | struct RandomPerturbation <: AbstractPerturbation end
 13 | 
 14 | # Helper for parsing `--init-perturbation` command line argument
 15 | function parse_perturbation_arg(arg)
 16 |     if arg === nothing
 17 |         perturbation = nothing
 18 |     elseif arg == "deterministic"
 19 |         perturbation = DeterministicPerturbation()
 20 |     elseif arg == "zero"
 21 |         perturbation = NoPerturbation()
 22 |     elseif arg == "random"
 23 |         perturbation = RandomPerturbation()
 24 |     else
 25 |         error("unknown perturbation: " * arg)
 26 |     end
 27 | 
 28 |     return perturbation
 29 | end
 30 | 
 31 | function init_perturbation(::NoPerturbation, bl, state, aux, coords, t)
 32 |     FT = eltype(state)
 33 | 
 34 |     u′, v′, w′ = (FT(0), FT(0), FT(0))
 35 |     rand_pert = FT(1)
 36 | 
 37 |     return u′, v′, w′, rand_pert
 38 | end
 39 | 
 40 | # Velocity perturbation following
 41 | # Ullrich et al. (2016) Dynamical Core Model Intercomparison Project (DCMIP2016) Test Case Document
 42 | function init_perturbation(
 43 |     ::DeterministicPerturbation,
 44 |     bl,
 45 |     state,
 46 |     aux,
 47 |     coords,
 48 |     t,
 49 | )
 50 |     FT = eltype(state)
 51 | 
 52 |     # get parameters
 53 |     param_set = parameter_set(bl)
 54 |     _a::FT = planet_radius(param_set)
 55 | 
 56 |     φ = latitude(bl, aux)
 57 |     λ = longitude(bl, aux)
 58 |     z = altitude(bl, aux)
 59 | 
 60 |     # perturbation specific parameters
 61 |     z_t::FT = 15e3
 62 |     λ_c::FT = π / 9
 63 |     φ_c::FT = 2 * π / 9
 64 |     d_0::FT = _a / 6
 65 |     V_p::FT = 10
 66 | 
 67 |     F_z::FT = 1 - 3 * (z / z_t)^2 + 2 * (z / z_t)^3
 68 |     if z > z_t
 69 |         F_z = FT(0)
 70 |     end
 71 |     d::FT = _a * acos(sin(φ) * sin(φ_c) + cos(φ) * cos(φ_c) * cos(λ - λ_c))
 72 |     c3::FT = cos(π * d / 2 / d_0)^3
 73 |     s1::FT = sin(π * d / 2 / d_0)
 74 |     if 0 < d < d_0 && d != FT(_a * π)
 75 |         u′::FT =
 76 |             -16 * V_p / 3 / sqrt(3) *
 77 |             F_z *
 78 |             c3 *
 79 |             s1 *
 80 |             (-sin(φ_c) * cos(φ) + cos(φ_c) * sin(φ) * cos(λ - λ_c)) /
 81 |             sin(d / _a)
 82 |         v′::FT =
 83 |             16 * V_p / 3 / sqrt(3) * F_z * c3 * s1 * cos(φ_c) * sin(λ - λ_c) /
 84 |             sin(d / _a)
 85 |     else
 86 |         u′ = FT(0)
 87 |         v′ = FT(0)
 88 |     end
 89 |     w′ = FT(0)
 90 |     rand_pert = FT(1)
 91 | 
 92 |     return u′, v′, w′, rand_pert
 93 | end
 94 | 
 95 | function init_perturbation(::RandomPerturbation, bl, state, aux, coords, t)
 96 |     FT = eltype(state)
 97 |     u′, v′, w′ = (FT(0), FT(0), FT(0))
 98 |     rand_pert = FT(1.0 + rand(Uniform(-1e-3, 1e-3)))
 99 | 
100 |     return u′, v′, w′, rand_pert
101 | end
102 | 
--------------------------------------------------------------------------------
/experiments/AtmosGCM/GCMDriver/gcm_sources.jl:
--------------------------------------------------------------------------------
 1 | # GCM-specific Sources
 2 | # This file contains helpers and lists currently available options
 3 | 
 4 | # Current options for GCM-specific sources:
 5 | 
 6 | """
 7 |     HeldSuarezForcing <: TendencyDef{Source}
 8 | 
 9 | Defines a forcing that parametrises radiative and frictional effects using
10 | Newtonian relaxation and Rayleigh friction, following Held and Suarez (1994)
11 | """
12 | struct HeldSuarezForcing <: TendencyDef{Source} end
13 | 
14 | prognostic_vars(::HeldSuarezForcing) = (Momentum(), Energy())
15 | 
16 | function held_suarez_forcing_coefficients(bl, args)
17 |     @unpack state, aux = args
18 |     @unpack ts = args.precomputed
19 |     FT = eltype(state)
20 | 
21 |     # Parameters
22 |     T_ref = FT(255)
23 | 
24 |     param_set = parameter_set(bl)
25 |     _R_d = FT(R_d(param_set))
26 |     _day = FT(day(param_set))
27 |     _grav = FT(grav(param_set))
28 |     _cp_d = FT(cp_d(param_set))
29 |     _p0 = FT(MSLP(param_set))
30 | 
31 |     # Held-Suarez parameters
32 |     k_a = FT(1 / (40 * _day))
33 |     k_f = FT(1 / _day)
34 |     k_s = FT(1 / (4 * _day))
35 |     ΔT_y = FT(60)
36 |     Δθ_z = FT(10)
37 |     T_equator = FT(315)
38 |     T_min = FT(200)
39 |     σ_b = FT(7 / 10)
40 | 
41 |     # Held-Suarez forcing
42 |     φ = latitude(bl, aux)
43 |     p = air_pressure(ts)
44 | 
45 |     #TODO: replace _p0 with dynamic surface pressure in Δσ calculations to account
46 |     #for topography, but leave unchanged for calculations of σ involved in T_equil
47 |     σ = p / _p0
48 |     exner_p = σ^(_R_d / _cp_d)
49 |     Δσ = (σ - σ_b) / (1 - σ_b)
50 |     height_factor = max(0, Δσ)
51 |     T_equil = (T_equator - ΔT_y * sin(φ)^2 - Δθ_z * log(σ) * cos(φ)^2) * exner_p
52 |     T_equil = max(T_min, T_equil)
53 |     k_T = k_a + (k_s - k_a) * height_factor * cos(φ)^4
54 |     k_v = k_f * height_factor
55 |     return (k_v = k_v, k_T = k_T, T_equil = T_equil)
56 | end
57 | 
58 | function source(::Energy, s::HeldSuarezForcing, m, args)
59 |     @unpack state = args
60 |     @unpack ts = args.precomputed
61 |     nt = held_suarez_forcing_coefficients(m, args)
62 |     FT = eltype(state)
63 |     param_set = parameter_set(bl)
64 |     _cv_d = FT(cv_d(param_set))
65 |     @unpack k_T, T_equil = nt
66 |     T = air_temperature(ts)
67 |     return -k_T * state.ρ * _cv_d * (T - T_equil)
68 | end
69 | 
70 | function source(::Momentum, s::HeldSuarezForcing, m, args)
71 |     nt = held_suarez_forcing_coefficients(m, args)
72 |     return -nt.k_v * projection_tangential(m, args.aux, args.state.ρu)
73 | end
74 | 
--------------------------------------------------------------------------------
/experiments/AtmosGCM/GCMDriver/heldsuarez_problem.jl:
--------------------------------------------------------------------------------
 1 | # This file establishes the default initial conditions, boundary conditions and sources
 2 | # for the heldsuarez_problem experiment, following:
 3 | #
 4 | # - Held, I. M. and Suarez, M. J.: A proposal for the intercomparison
 5 | # of the dynamical cores of atmospheric general circulation models,
 6 | # B. Am. Meteorol. Soc., 75, 1825–1830, 1994.
 7 | #
 8 | # - Thatcher, D. R. and Jablonowski, C.: A moist aquaplanet variant of the
 9 | # Held–Suarez test for atmospheric model dynamical cores, Geosci. Model Dev.,
10 | # 9, 1263–1292, 2016.
11 | 
12 | # Override default CLIMAParameters for consistency with literature on this case
13 | using Thermodynamics
14 | 
15 | using CLIMAParameters.Planet
16 | 
17 | struct HeldSuarezProblem{BC, ISP, ISA, WP, BS, MP} <: AbstractAtmosProblem
18 |     boundaryconditions::BC
19 |     init_state_prognostic::ISP
20 |     init_state_auxiliary::ISA
21 |     perturbation::WP
22 |     base_state::BS
23 |     moisture_profile::MP
24 | end
25 | function HeldSuarezProblem(
26 |     physics::AtmosPhysics;
27 |     boundaryconditions = (AtmosBC(physics;), AtmosBC(physics;)),
28 |     perturbation = nothing,
29 |     base_state = nothing,
30 |     moisture_profile = nothing,
31 | )
32 |     # Set up defaults
33 |     if isnothing(perturbation)
34 |         perturbation = DeterministicPerturbation()
35 |     end
36 |     if isnothing(base_state)
37 |         base_state = HeldSuarezBaseState()
38 |     end
39 |     if isnothing(moisture_profile)
40 |         moisture_profile = MoistLowTropicsMoistureProfile()
41 |     end
42 | 
43 |     problem = (
44 |         boundaryconditions,
45 |         init_gcm_experiment!,
46 |         (_...) -> nothing,
47 |         perturbation,
48 |         base_state,
49 |         moisture_profile,
50 |     )
51 |     return HeldSuarezProblem{typeof.(problem)...}(problem...)
52 | end
53 | 
54 | problem_name(::HeldSuarezProblem) = "HeldSuarez"
55 | 
56 | setup_source(::HeldSuarezProblem) = (Gravity(), Coriolis(), HeldSuarezForcing())
57 | 
--------------------------------------------------------------------------------
/experiments/AtmosGCM/heldsuarez.jl:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env julia --project
  2 | using ClimateMachine
  3 | using ArgParse
  4 | using UnPack
  5 | 
  6 | s = ArgParseSettings()
  7 | @add_arg_table! s begin
  8 |     "--number-of-tracers"
  9 |     help = "Number of dummy tracers"
 10 |     metavar = ""
 11 |     arg_type = Int
 12 |     default = 0
 13 | end
 14 | 
 15 | parsed_args = ClimateMachine.init(parse_clargs = true, custom_clargs = s)
 16 | const number_of_tracers = parsed_args["number-of-tracers"]
 17 | 
 18 | using ClimateMachine.Atmos
 19 | using ClimateMachine.Orientations
 20 | using ClimateMachine.ConfigTypes
 21 | using ClimateMachine.Diagnostics
 22 | using ClimateMachine.GenericCallbacks
 23 | using ClimateMachine.ODESolvers
 24 | using ClimateMachine.StdDiagnostics
 25 | using ClimateMachine.SystemSolvers: ManyColumnLU
 26 | using ClimateMachine.TurbulenceClosures
 27 | using ClimateMachine.Mesh.Filters
 28 | using ClimateMachine.Mesh.Grids
 29 | using Thermodynamics.TemperatureProfiles
 30 | using Thermodynamics:
 31 |     air_pressure, air_density, air_temperature, total_energy, internal_energy
 32 | using ClimateMachine.VariableTemplates
 33 | 
 34 | using ClimateMachine.BalanceLaws
 35 | import ClimateMachine.BalanceLaws: source, prognostic_vars
 36 | 
 37 | using LinearAlgebra
 38 | using StaticArrays
 39 | using Test
 40 | 
 41 | using CLIMAParameters
 42 | using CLIMAParameters.Planet:
 43 |     MSLP, R_d, day, cp_d, cv_d, grav, Omega, planet_radius
 44 | struct EarthParameterSet <: AbstractEarthParameterSet end
 45 | const param_set = EarthParameterSet()
 46 | 
 47 | function init_heldsuarez!(problem, bl, state, aux, localgeo, t)
 48 |     FT = eltype(state)
 49 | 
 50 |     param_set = parameter_set(bl)
 51 |     # parameters
 52 |     _a::FT = planet_radius(param_set)
 53 | 
 54 |     z_t::FT = 15e3
 55 |     λ_c::FT = π / 9
 56 |     φ_c::FT = 2 * π / 9
 57 |     d_0::FT = _a / 6
 58 |     V_p::FT = 10
 59 | 
 60 |     # grid
 61 |     φ = latitude(bl.orientation, aux)
 62 |     λ = longitude(bl.orientation, aux)
 63 |     z = altitude(bl.orientation, param_set, aux)
 64 | 
 65 |     # deterministic velocity perturbation
 66 |     F_z::FT = 1 - 3 * (z / z_t)^2 + 2 * (z / z_t)^3
 67 |     if z > z_t
 68 |         F_z = FT(0)
 69 |     end
 70 |     d::FT = _a * acos(sin(φ) * sin(φ_c) + cos(φ) * cos(φ_c) * cos(λ - λ_c))
 71 |     c3::FT = cos(π * d / 2 / d_0)^3
 72 |     s1::FT = sin(π * d / 2 / d_0)
 73 |     if 0 < d < d_0 && d != FT(_a * π)
 74 |         u′::FT =
 75 |             -16 * V_p / 3 / sqrt(3) *
 76 |             F_z *
 77 |             c3 *
 78 |             s1 *
 79 |             (-sin(φ_c) * cos(φ) + cos(φ_c) * sin(φ) * cos(λ - λ_c)) /
 80 |             sin(d / _a)
 81 |         v′::FT =
 82 |             16 * V_p / 3 / sqrt(3) * F_z * c3 * s1 * cos(φ_c) * sin(λ - λ_c) /
 83 |             sin(d / _a)
 84 |     else
 85 |         u′ = FT(0)
 86 |         v′ = FT(0)
 87 |     end
 88 |     w′::FT = 0
 89 |     u_sphere = SVector{3, FT}(u′, v′, w′)
 90 |     u_cart = sphr_to_cart_vec(bl.orientation, u_sphere, aux)
 91 | 
 92 |     ## potential & kinetic energy
 93 |     e_kin::FT = 0.5 * u_cart' * u_cart
 94 | 
 95 |     ## Assign state variables
 96 |     state.ρ = aux.ref_state.ρ
 97 |     state.ρu = state.ρ * u_cart
 98 |     state.energy.ρe = aux.ref_state.ρe + state.ρ * e_kin
 99 |     if number_of_tracers > 0
100 |         state.tracers.ρχ = @SVector [FT(ii) for ii in 1:number_of_tracers]
101 |     end
102 | 
103 |     nothing
104 | end
105 | 
106 | """
107 |     HeldSuarezForcing <: TendencyDef{Source}
108 | 
109 | Defines a forcing that parametrises radiative and frictional effects using
110 | Newtonian relaxation and Rayleigh friction, following Held and Suarez (1994)
111 | """
112 | struct HeldSuarezForcing <: TendencyDef{Source} end
113 | 
114 | prognostic_vars(::HeldSuarezForcing) = (Momentum(), Energy())
115 | 
116 | function held_suarez_forcing_coefficients(bl, args)
117 |     @unpack state, aux = args
118 |     @unpack ts = args.precomputed
119 |     FT = eltype(state)
120 | 
121 |     # Parameters
122 |     T_ref = FT(255)
123 |     param_set = parameter_set(bl)
124 |     _R_d = FT(R_d(param_set))
125 |     _day = FT(day(param_set))
126 |     _grav = FT(grav(param_set))
127 |     _cp_d = FT(cp_d(param_set))
128 |     _p0 = FT(MSLP(param_set))
129 | 
130 |     # Held-Suarez parameters
131 |     k_a = FT(1 / (40 * _day))
132 |     k_f = FT(1 / _day)
133 |     k_s = FT(1 / (4 * _day))
134 |     ΔT_y = FT(60)
135 |     Δθ_z = FT(10)
136 |     T_equator = FT(315)
137 |     T_min = FT(200)
138 |     σ_b = FT(7 / 10)
139 | 
140 |     # Held-Suarez forcing
141 |     φ = latitude(bl, aux)
142 |     p = air_pressure(ts)
143 | 
144 |     #TODO: replace _p0 with dynamic surface pressure in Δσ calculations to account
145 |     #for topography, but leave unchanged for calculations of σ involved in T_equil
146 |     σ = p / _p0
147 |     exner_p = σ^(_R_d / _cp_d)
148 |     Δσ = (σ - σ_b) / (1 - σ_b)
149 |     height_factor = max(0, Δσ)
150 |     T_equil = (T_equator - ΔT_y * sin(φ)^2 - Δθ_z * log(σ) * cos(φ)^2) * exner_p
151 |     T_equil = max(T_min, T_equil)
152 |     k_T = k_a + (k_s - k_a) * height_factor * cos(φ)^4
153 |     k_v = k_f * height_factor
154 |     return (k_v = k_v, k_T = k_T, T_equil = T_equil)
155 | end
156 | 
157 | function source(::Energy, s::HeldSuarezForcing, m, args)
158 |     @unpack state = args
159 |     @unpack ts = args.precomputed
160 |     nt = held_suarez_forcing_coefficients(m, args)
161 |     FT = eltype(state)
162 |     param_set = parameter_set(m)
163 |     _cv_d = FT(cv_d(param_set))
164 |     @unpack k_T, T_equil = nt
165 |     T = air_temperature(ts)
166 |     return -k_T * state.ρ * _cv_d * (T - T_equil)
167 | end
168 | 
169 | function source(::Momentum, s::HeldSuarezForcing, m, args)
170 |     nt = held_suarez_forcing_coefficients(m, args)
171 |     return -nt.k_v * projection_tangential(m, args.aux, args.state.ρu)
172 | end
173 | 
174 | function config_heldsuarez(FT, poly_order, resolution)
175 |     # Set up a reference state for linearization of equations
176 |     temp_profile_ref =
177 |         DecayingTemperatureProfile{FT}(param_set, FT(290), FT(220), FT(8e3))
178 |     ref_state = HydrostaticState(temp_profile_ref)
179 | 
180 |     # Set up the atmosphere model
181 |     exp_name = "HeldSuarez"
182 |     domain_height::FT = 30e3 # distance between surface and top of atmosphere (m)
183 | 
184 |     if number_of_tracers > 0
185 |         δ_χ = @SVector [FT(ii) for ii in 1:number_of_tracers]
186 |         tracers = NTracers{number_of_tracers, FT}(δ_χ)
187 |     else
188 |         tracers = NoTracers()
189 |     end
190 | 
191 |     physics = AtmosPhysics{FT}(
192 |         param_set;
193 |         ref_state = ref_state,
194 |         turbulence = ConstantKinematicViscosity(FT(0)),
195 |         hyperdiffusion = DryBiharmonic(FT(8 * 3600)),
196 |         moisture = DryModel(),
197 |         tracers = tracers,
198 |     )
199 | 
200 |     model = AtmosModel{FT}(
201 |         AtmosGCMConfigType,
202 |         physics;
203 |         init_state_prognostic = init_heldsuarez!,
204 |         source = (Gravity(), Coriolis(), HeldSuarezForcing()),
205 |     )
206 | 
207 |     config = ClimateMachine.AtmosGCMConfiguration(
208 |         exp_name,
209 |         poly_order,
210 |         resolution,
211 |         domain_height,
212 |         param_set,
213 |         init_heldsuarez!;
214 |         model = model,
215 |     )
216 | 
217 |     return config
218 | end
219 | 
220 | function main()
221 |     # Driver configuration parameters
222 |     FT = Float64                             # floating type precision
223 |     poly_order = 5                           # discontinuous Galerkin polynomial order
224 |     n_horz = 8                              # horizontal element number
225 |     n_vert = 4                               # vertical element number
226 |     n_days::FT = 1
227 |     timestart::FT = 0                        # start time (s)
228 |     timeend::FT = n_days * day(param_set)    # end time (s)
229 | 
230 |     # Set up driver configuration
231 |     driver_config = config_heldsuarez(FT, poly_order, (n_horz, n_vert))
232 | 
233 |     # Set up experiment
234 |     ode_solver_type = ClimateMachine.IMEXSolverType(
235 |         implicit_model = AtmosAcousticGravityLinearModel,
236 |         implicit_solver = ManyColumnLU,
237 |         solver_method = ARK2GiraldoKellyConstantinescu,
238 |         split_explicit_implicit = true,
239 |         discrete_splitting = false,
240 |     )
241 | 
242 |     CFL = FT(0.1) # target acoustic CFL number
243 | 
244 |     # time step is computed such that the horizontal acoustic Courant number is CFL
245 |     solver_config = ClimateMachine.SolverConfiguration(
246 |         timestart,
247 |         timeend,
248 |         driver_config,
249 |         Courant_number = CFL,
250 |         ode_solver_type = ode_solver_type,
251 |         CFL_direction = HorizontalDirection(),
252 |         diffdir = HorizontalDirection(),
253 |     )
254 | 
255 |     # Set up diagnostics
256 |     dgn_ssecs = cld(timeend, 2) + 30
257 |     dgn_interval = "$(dgn_ssecs)ssecs"
258 |     dgn_config = config_diagnostics(FT, driver_config, dgn_interval)
259 | 
260 |     # Set up user-defined callbacks
261 |     filterorder = 20
262 |     filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)
263 |     cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do
264 |         Filters.apply!(
265 |             solver_config.Q,
266 |             AtmosFilterPerturbations(driver_config.bl),
267 |             solver_config.dg.grid,
268 |             filter,
269 |             state_auxiliary = solver_config.dg.state_auxiliary,
270 |         )
271 |         nothing
272 |     end
273 | 
274 |     # Run the model
275 |     result = ClimateMachine.invoke!(
276 |         solver_config;
277 |         diagnostics_config = dgn_config,
278 |         user_callbacks = (cbfilter,),
279 |         check_euclidean_distance = true,
280 |     )
281 | end
282 | 
283 | function config_diagnostics(FT, driver_config, interval)
284 |     _planet_radius = FT(planet_radius(param_set))
285 | 
286 |     info = driver_config.config_info
287 |     boundaries = [
288 |         FT(-90.0) FT(-180.0) _planet_radius
289 |         FT(90.0) FT(180.0) FT(_planet_radius + info.domain_height)
290 |     ]
291 |     resolution = (FT(1), FT(1), FT(1000)) # in (deg, deg, m)
292 |     interpol = ClimateMachine.InterpolationConfiguration(
293 |         driver_config,
294 |         boundaries,
295 |         resolution,
296 |     )
297 | 
298 |     dgngrp = StdDiagnostics.AtmosGCMDefault(
299 |         interval,
300 |         driver_config.name,
301 |         interpol = interpol,
302 |     )
303 | 
304 |     return ClimateMachine.DiagnosticsConfiguration([dgngrp])
305 | end
306 | 
307 | main()
308 | 
--------------------------------------------------------------------------------
/experiments/AtmosGCM/nonhydrostatic_gravity_wave.jl:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env julia --project
  2 | using ClimateMachine
  3 | ClimateMachine.init(parse_clargs = true)
  4 | 
  5 | using ClimateMachine.Atmos
  6 | using ClimateMachine.Orientations
  7 | using ClimateMachine.ConfigTypes
  8 | using ClimateMachine.Diagnostics
  9 | using ClimateMachine.GenericCallbacks
 10 | using ClimateMachine.ODESolvers
 11 | using ClimateMachine.TurbulenceClosures
 12 | using ClimateMachine.SystemSolvers: ManyColumnLU
 13 | using ClimateMachine.Mesh.Filters
 14 | using ClimateMachine.Mesh.Grids
 15 | using ClimateMachine.Mesh.Interpolation
 16 | using Thermodynamics.TemperatureProfiles
 17 | using Thermodynamics: total_energy, air_density
 18 | using ClimateMachine.VariableTemplates
 19 | 
 20 | using Distributions: Uniform
 21 | using LinearAlgebra
 22 | using StaticArrays
 23 | 
 24 | using CLIMAParameters
 25 | using CLIMAParameters.Planet:
 26 |     R_d, day, grav, cp_d, planet_radius, Omega, kappa_d, MSLP
 27 | struct EarthParameterSet <: AbstractEarthParameterSet end
 28 | const param_set = EarthParameterSet()
 29 | 
 30 | import CLIMAParameters
 31 | CLIMAParameters.Planet.Omega(::EarthParameterSet) = 0.0
 32 | CLIMAParameters.Planet.planet_radius(::EarthParameterSet) = 6.371e6 / 125.0
 33 | CLIMAParameters.Planet.MSLP(::EarthParameterSet) = 1e5
 34 | 
 35 | 
 36 | function init_nonhydrostatic_gravity_wave!(problem, bl, state, aux, localgeo, t)
 37 |     FT = eltype(state)
 38 | 
 39 |     # grid
 40 |     φ = latitude(bl, aux)
 41 |     λ = longitude(bl, aux)
 42 |     z = altitude(bl, aux)
 43 | 
 44 |     # parameters
 45 |     param_set = parameter_set(bl)
 46 |     _grav::FT = grav(param_set)
 47 |     _cp::FT = cp_d(param_set)
 48 |     _Ω::FT = Omega(param_set)
 49 |     _a::FT = planet_radius(param_set)
 50 |     _R_d::FT = R_d(param_set)
 51 |     _kappa::FT = kappa_d(param_set)
 52 |     _p_eq::FT = MSLP(param_set)
 53 | 
 54 |     N::FT = 0.01
 55 |     u_0::FT = 0.0
 56 |     G::FT = _grav^2 / N^2 / _cp
 57 |     T_eq::FT = 300
 58 |     Δθ::FT = 0.0
 59 |     d::FT = 5e3
 60 |     λ_c::FT = 2 * π / 3
 61 |     φ_c::FT = 0
 62 |     L_z::FT = 20e3
 63 | 
 64 |     # initial velocity profile (we need to transform the vector into the Cartesian
 65 |     # coordinate system)
 66 |     u_sphere = SVector{3, FT}(u_0 * cos(φ), 0, 0)
 67 |     u_init = sphr_to_cart_vec(bl.orientation, u_sphere, aux)
 68 | 
 69 |     # background temperature
 70 |     T_s::FT =
 71 |         G +
 72 |         (T_eq - G) *
 73 |         exp(-u_0 * N^2 / 4 / _grav^2 * (u_0 + 2 * _Ω * _a) * (cos(2 * φ) - 1))
 74 |     T_b::FT = G * (1 - exp(N^2 / _grav * z)) + T_s * exp(N^2 / _grav * z)
 75 | 
 76 |     # pressure
 77 |     p_s::FT =
 78 |         _p_eq *
 79 |         exp(u_0 / 4 / G / _R_d * (u_0 + 2 * _Ω * _a) * (cos(2 * φ) - 1)) *
 80 |         (T_s / T_eq)^(1 / _kappa)
 81 |     p::FT = p_s * (G / T_s * exp(-N^2 / _grav * z) + 1 - G / T_s)^(1 / _kappa)
 82 | 
 83 |     # background potential temperature
 84 |     θ_b::FT = T_b * (_p_eq / p)^_kappa
 85 | 
 86 |     # potential temperature perturbation
 87 |     r::FT = _a * acos(sin(φ_c) * sin(φ) + cos(φ_c) * cos(φ) * cos(λ - λ_c))
 88 |     s::FT = d^2 / (d^2 + r^2)
 89 |     θ′::FT = Δθ * s * sin(2 * π * z / L_z)
 90 | 
 91 |     # temperature perturbation
 92 |     T′::FT = θ′ * (p / _p_eq)^_kappa
 93 | 
 94 |     # temperature
 95 |     T::FT = T_b + T′
 96 | 
 97 |     # density
 98 |     ρ = air_density(param_set, T_b, p)
 99 | 
100 |     # potential & kinetic energy
101 |     e_pot = gravitational_potential(bl.orientation, aux)
102 |     e_kin::FT = 0.5 * sum(abs2.(u_init))
103 | 
104 |     state.ρ = ρ
105 |     state.ρu = ρ * u_init
106 |     state.energy.ρe = ρ * total_energy(param_set, e_kin, e_pot, T)
107 | 
108 |     nothing
109 | end
110 | 
111 | function config_nonhydrostatic_gravity_wave(FT, poly_order, resolution)
112 |     # Set up a reference state for linearization of equations
113 |     temp_profile_ref =
114 |         DecayingTemperatureProfile{FT}(param_set, FT(300), FT(100), FT(27.5e3))
115 |     ref_state = HydrostaticState(temp_profile_ref)
116 | 
117 |     domain_height::FT = 10e3               # distance between surface and top of atmosphere (m)
118 | 
119 |     # Set up the atmosphere model
120 |     exp_name = "NonhydrostaticGravityWave"
121 | 
122 |     physics = AtmosPhysics{FT}(
123 |         param_set;
124 |         ref_state = ref_state,
125 |         turbulence = ConstantKinematicViscosity(FT(0)),
126 |         moisture = DryModel(),
127 |     )
128 | 
129 |     model = AtmosModel{FT}(
130 |         AtmosGCMConfigType,
131 |         physics;
132 |         init_state_prognostic = init_nonhydrostatic_gravity_wave!,
133 |         source = (Gravity(),),
134 |     )
135 | 
136 |     config = ClimateMachine.AtmosGCMConfiguration(
137 |         exp_name,
138 |         poly_order,
139 |         resolution,
140 |         domain_height,
141 |         param_set,
142 |         init_nonhydrostatic_gravity_wave!;
143 |         model = model,
144 |     )
145 | 
146 |     return config
147 | end
148 | 
149 | function config_diagnostics(FT, driver_config)
150 |     interval = "40000steps" # chosen to allow a single diagnostics collection
151 | 
152 |     _planet_radius = FT(planet_radius(param_set))
153 | 
154 |     info = driver_config.config_info
155 |     boundaries = [
156 |         FT(-90.0) FT(-180.0) _planet_radius
157 |         FT(90.0) FT(180.0) FT(_planet_radius + info.domain_height)
158 |     ]
159 |     resolution = (FT(10), FT(10), FT(100)) # in (deg, deg, m)
160 |     interpol = ClimateMachine.InterpolationConfiguration(
161 |         driver_config,
162 |         boundaries,
163 |         resolution,
164 |     )
165 | 
166 |     dgngrp = setup_atmos_default_diagnostics(
167 |         AtmosGCMConfigType(),
168 |         interval,
169 |         driver_config.name,
170 |         interpol = interpol,
171 |     )
172 | 
173 |     return ClimateMachine.DiagnosticsConfiguration([dgngrp])
174 | end
175 | 
176 | function main()
177 |     # Driver configuration parameters
178 |     FT = Float64                             # floating type precision
179 |     poly_order = 5                           # discontinuous Galerkin polynomial order
180 |     n_horz = 8                               # horizontal element number
181 |     n_vert = 4                               # vertical element number
182 |     timestart = FT(0)                        # start time (s)
183 |     timeend = FT(3600)                       # end time (s)
184 | 
185 |     # Set up driver configuration
186 |     driver_config =
187 |         config_nonhydrostatic_gravity_wave(FT, poly_order, (n_horz, n_vert))
188 | 
189 |     # Set up experiment
190 |     CFL = FT(0.4)
191 |     solver_config = ClimateMachine.SolverConfiguration(
192 |         timestart,
193 |         timeend,
194 |         driver_config,
195 |         Courant_number = CFL,
196 |         CFL_direction = HorizontalDirection(),
197 |     )
198 | 
199 |     # Set up diagnostics
200 |     dgn_config = config_diagnostics(FT, driver_config)
201 | 
202 |     # Set up user-defined callbacks
203 |     filterorder = 64
204 |     filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)
205 |     cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do
206 |         Filters.apply!(
207 |             solver_config.Q,
208 |             AtmosFilterPerturbations(driver_config.bl),
209 |             solver_config.dg.grid,
210 |             filter,
211 |             state_auxiliary = solver_config.dg.state_auxiliary,
212 |         )
213 |         nothing
214 |     end
215 | 
216 |     # Run the model
217 |     result = ClimateMachine.invoke!(
218 |         solver_config;
219 |         diagnostics_config = dgn_config,
220 |         user_callbacks = (cbfilter,),
221 |         check_euclidean_distance = true,
222 |     )
223 | end
224 | 
225 | main()
226 | 
--------------------------------------------------------------------------------
/experiments/AtmosLES/Artifacts.toml:
--------------------------------------------------------------------------------
1 | [lsforcing]
2 | git-tree-sha1 = "6ac99861047fc1b2c16caa81506bde25e197dc0a"
3 | 
4 | [soundings]
5 | git-tree-sha1 = "54fd0eef5f47c4f32662a9f3fa56accc00a822fc"
6 | 
--------------------------------------------------------------------------------
/experiments/AtmosLES/bomex_les.jl:
--------------------------------------------------------------------------------
  1 | using Random
  2 | include("bomex_model.jl")
  3 | 
  4 | function add_perturbations!(state, localgeo)
  5 |     FT = eltype(state)
  6 |     z = localgeo.coord[3]
  7 |     if z <= FT(400) # Add random perturbations to bottom 400m of model
  8 |         state.energy.ρe += (rand() - 0.5) * state.energy.ρe / 100
  9 |         state.moisture.ρq_tot += (rand() - 0.5) * state.moisture.ρq_tot / 100
 10 |     end
 11 | end
 12 | 
 13 | function main()
 14 |     # add a command line argument to specify the kind of surface flux
 15 |     # TODO: this will move to the future namelist functionality
 16 |     bomex_args = ArgParseSettings(autofix_names = true)
 17 |     add_arg_group!(bomex_args, "BOMEX")
 18 |     @add_arg_table! bomex_args begin
 19 |         "--surface-flux"
 20 |         help = "specify surface flux for energy and moisture"
 21 |         metavar = "prescribed|bulk"
 22 |         arg_type = String
 23 |         default = "prescribed"
 24 |         "--moisture-model"
 25 |         help = "specify cloud condensate model"
 26 |         metavar = "equilibrium|nonequilibrium"
 27 |         arg_type = String
 28 |         default = "equilibrium"
 29 |     end
 30 | 
 31 |     cl_args =
 32 |         ClimateMachine.init(parse_clargs = true, custom_clargs = bomex_args)
 33 | 
 34 |     surface_flux = cl_args["surface_flux"]
 35 |     moisture_model = cl_args["moisture_model"]
 36 | 
 37 |     FT = Float64
 38 |     config_type = AtmosLESConfigType
 39 | 
 40 |     # DG polynomial order
 41 |     N = 4
 42 |     # Domain resolution and size
 43 |     Δh = FT(100)
 44 |     Δv = FT(40)
 45 | 
 46 |     resolution = (Δh, Δh, Δv)
 47 | 
 48 |     # Prescribe domain parameters
 49 |     xmax = FT(6400)
 50 |     ymax = FT(6400)
 51 |     zmax = FT(3000)
 52 | 
 53 |     t0 = FT(0)
 54 | 
 55 |     # For a full-run, please set the timeend to 3600*6 seconds
 56 |     # and change the values in ConservationCheck
 57 |     # For the test we set this to == 20 minutes
 58 |     timeend = FT(1200)
 59 |     #timeend = FT(3600 * 6)
 60 |     CFLmax = FT(0.35)
 61 | 
 62 |     # Choose default IMEX solver
 63 |     ode_solver_type = ClimateMachine.IMEXSolverType()
 64 | 
 65 |     model = bomex_model(
 66 |         FT,
 67 |         config_type,
 68 |         zmax,
 69 |         surface_flux,
 70 |         moisture_model = moisture_model,
 71 |     )
 72 |     ics = model.problem.init_state_prognostic
 73 |     # Assemble configuration
 74 |     driver_config = ClimateMachine.AtmosLESConfiguration(
 75 |         "BOMEX",
 76 |         N,
 77 |         resolution,
 78 |         xmax,
 79 |         ymax,
 80 |         zmax,
 81 |         param_set,
 82 |         ics;
 83 |         model = model,
 84 |     )
 85 | 
 86 |     solver_config = ClimateMachine.SolverConfiguration(
 87 |         t0,
 88 |         timeend,
 89 |         driver_config,
 90 |         init_on_cpu = true,
 91 |         Courant_number = CFLmax,
 92 |         CFL_direction = HorizontalDirection(),
 93 |     )
 94 |     dgn_config =
 95 |         config_diagnostics(driver_config, timeend, xmax, ymax, zmax, resolution)
 96 | 
 97 |     if moisture_model == "equilibrium"
 98 |         filter_vars = ("moisture.ρq_tot",)
 99 |     elseif moisture_model == "nonequilibrium"
100 |         filter_vars = ("moisture.ρq_tot", "moisture.ρq_liq", "moisture.ρq_ice")
101 |     end
102 | 
103 |     cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do
104 |         Filters.apply!(
105 |             solver_config.Q,
106 |             filter_vars,
107 |             solver_config.dg.grid,
108 |             TMARFilter(),
109 |         )
110 |         nothing
111 |     end
112 | 
113 |     check_cons = (
114 |         ClimateMachine.ConservationCheck("ρ", "3000steps", FT(0.0001)),
115 |         ClimateMachine.ConservationCheck("energy.ρe", "3000steps", FT(0.0025)),
116 |     )
117 | 
118 |     result = ClimateMachine.invoke!(
119 |         solver_config;
120 |         user_callbacks = (cbtmarfilter,),
121 |         diagnostics_config = dgn_config,
122 |         check_cons = check_cons,
123 |         check_euclidean_distance = true,
124 |     )
125 | end
126 | 
127 | function config_diagnostics(
128 |     driver_config,
129 |     timeend,
130 |     xmax::FT,
131 |     ymax::FT,
132 |     zmax::FT,
133 |     resolution,
134 | ) where {FT}
135 |     default_interval = "$(cld(timeend, 2) + 10)ssecs"
136 |     default_dgngrp = setup_atmos_default_diagnostics(
137 |         AtmosLESConfigType(),
138 |         default_interval,
139 |         driver_config.name,
140 |     )
141 |     core_interval = "$(cld(timeend, 4) + 10)ssecs"
142 |     core_dgngrp = setup_atmos_core_diagnostics(
143 |         AtmosLESConfigType(),
144 |         core_interval,
145 |         driver_config.name,
146 |     )
147 |     boundaries = [
148 |         FT(0) FT(0) FT(0)
149 |         xmax ymax zmax
150 |     ]
151 |     interpol = ClimateMachine.InterpolationConfiguration(
152 |         driver_config,
153 |         boundaries,
154 |         resolution,
155 |     )
156 |     dt_dgngrp = setup_dump_tendencies_diagnostics(
157 |         AtmosLESConfigType(),
158 |         default_interval,
159 |         driver_config.name,
160 |         interpol = interpol,
161 |     )
162 |     return ClimateMachine.DiagnosticsConfiguration([
163 |         default_dgngrp,
164 |         core_dgngrp,
165 |         dt_dgngrp,
166 |     ])
167 | end
168 | 
169 | main()
170 | 
--------------------------------------------------------------------------------
/experiments/AtmosLES/bomex_single_stack.jl:
--------------------------------------------------------------------------------
  1 | include("bomex_model.jl")
  2 | 
  3 | function main()
  4 |     # add a command line argument to specify the kind of surface flux
  5 |     # TODO: this will move to the future namelist functionality
  6 |     bomex_args = ArgParseSettings(autofix_names = true)
  7 |     add_arg_group!(bomex_args, "BOMEX")
  8 |     @add_arg_table! bomex_args begin
  9 |         "--moisture-model"
 10 |         help = "specify cloud condensate model"
 11 |         metavar = "equilibrium|nonequilibrium"
 12 |         arg_type = String
 13 |         default = "equilibrium"
 14 |         "--surface-flux"
 15 |         help = "specify surface flux for energy and moisture"
 16 |         metavar = "prescribed|bulk"
 17 |         arg_type = String
 18 |         default = "prescribed"
 19 |     end
 20 | 
 21 |     cl_args =
 22 |         ClimateMachine.init(parse_clargs = true, custom_clargs = bomex_args)
 23 | 
 24 |     surface_flux = cl_args["surface_flux"]
 25 |     moisture_model = cl_args["moisture_model"]
 26 | 
 27 |     FT = Float64
 28 |     config_type = SingleStackConfigType
 29 | 
 30 |     # DG polynomial order
 31 |     N = 1
 32 | 
 33 |     # Prescribe domain parameters
 34 |     nelem_vert = 50
 35 |     zmax = FT(3000)
 36 | 
 37 |     t0 = FT(0)
 38 | 
 39 |     # For a full-run, please set the timeend to 3600*6 seconds
 40 |     # For the test we set this to == 30 minutes
 41 |     timeend = FT(1800)
 42 |     #timeend = FT(3600 * 6)
 43 |     CFLmax = FT(0.90)
 44 | 
 45 |     # Choose default IMEX solver
 46 |     ode_solver_type = ClimateMachine.IMEXSolverType()
 47 | 
 48 |     model = bomex_model(
 49 |         FT,
 50 |         config_type,
 51 |         zmax,
 52 |         surface_flux;
 53 |         moisture_model = moisture_model,
 54 |     )
 55 |     ics = model.problem.init_state_prognostic
 56 | 
 57 |     # Assemble configuration
 58 |     driver_config = ClimateMachine.SingleStackConfiguration(
 59 |         "BOMEX_SINGLE_STACK",
 60 |         N,
 61 |         nelem_vert,
 62 |         zmax,
 63 |         param_set,
 64 |         model,
 65 |     )
 66 | 
 67 |     solver_config = ClimateMachine.SolverConfiguration(
 68 |         t0,
 69 |         timeend,
 70 |         driver_config,
 71 |         ode_solver_type = ode_solver_type,
 72 |         init_on_cpu = true,
 73 |         Courant_number = CFLmax,
 74 |     )
 75 |     dgn_config = config_diagnostics(driver_config, timeend)
 76 | 
 77 |     cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do
 78 |         Filters.apply!(
 79 |             solver_config.Q,
 80 |             ("moisture.ρq_tot",),
 81 |             solver_config.dg.grid,
 82 |             TMARFilter(),
 83 |         )
 84 |         nothing
 85 |     end
 86 | 
 87 |     check_cons = (
 88 |         ClimateMachine.ConservationCheck("ρ", "3000steps", FT(0.0001)),
 89 |         ClimateMachine.ConservationCheck("energy.ρe", "3000steps", FT(0.0025)),
 90 |     )
 91 | 
 92 |     result = ClimateMachine.invoke!(
 93 |         solver_config;
 94 |         user_callbacks = (cbtmarfilter,),
 95 |         diagnostics_config = dgn_config,
 96 |         check_cons = check_cons,
 97 |         check_euclidean_distance = true,
 98 |     )
 99 | end
100 | 
101 | function config_diagnostics(driver_config, timeend)
102 |     FT = eltype(driver_config.grid)
103 |     info = driver_config.config_info
104 |     interval = "$(cld(timeend, 2) + 10)ssecs"
105 | 
106 |     boundaries = [
107 |         FT(0) FT(0) FT(0)
108 |         FT(info.hmax) FT(info.hmax) FT(info.zmax)
109 |     ]
110 |     axes = (
111 |         [FT(1)],
112 |         [FT(1)],
113 |         collect(range(boundaries[1, 3], boundaries[2, 3], step = FT(50)),),
114 |     )
115 |     interpol = ClimateMachine.InterpolationConfiguration(
116 |         driver_config,
117 |         boundaries;
118 |         axes = axes,
119 |     )
120 |     dgngrp = setup_dump_state_diagnostics(
121 |         SingleStackConfigType(),
122 |         interval,
123 |         driver_config.name,
124 |         interpol = interpol,
125 |     )
126 |     return ClimateMachine.DiagnosticsConfiguration([dgngrp])
127 | end
128 | 
129 | main()
130 | 
--------------------------------------------------------------------------------
/experiments/AtmosLES/convective_bl_les.jl:
--------------------------------------------------------------------------------
 1 | include("convective_bl_model.jl")
 2 | function main()
 3 | 
 4 |     # TODO: this will move to the future namelist functionality
 5 |     cbl_args = ArgParseSettings(autofix_names = true)
 6 |     add_arg_group!(cbl_args, "ConvectiveBoundaryLayer")
 7 |     @add_arg_table! cbl_args begin
 8 |         "--surface-flux"
 9 |         help = "specify surface flux for energy and moisture"
10 |         metavar = "prescribed|bulk"
11 |         arg_type = String
12 |         default = "bulk"
13 |     end
14 | 
15 |     cl_args = ClimateMachine.init(parse_clargs = true, custom_clargs = cbl_args)
16 | 
17 |     surface_flux = cl_args["surface_flux"]
18 | 
19 |     FT = Float64
20 |     config_type = AtmosLESConfigType
21 | 
22 |     # DG polynomial order
23 |     N = 4
24 |     # Domain resolution and size
25 |     Δh = FT(80)
26 |     Δv = FT(80)
27 | 
28 |     resolution = (Δh, Δh, Δv)
29 | 
30 |     # Prescribe domain parameters
31 |     xmax = FT(4800)
32 |     ymax = FT(4800)
33 |     zmax = FT(3200)
34 | 
35 |     t0 = FT(0)
36 | 
37 |     # Full simulation requires 16+ hours of simulated time
38 |     timeend = FT(3600 * 0.1)
39 |     CFLmax = FT(0.4)
40 | 
41 |     # Choose default Explicit solver
42 |     ode_solver_type = ClimateMachine.ExplicitSolverType()
43 | 
44 |     model = convective_bl_model(FT, config_type, zmax, surface_flux)
45 |     ics = model.problem.init_state_prognostic
46 | 
47 |     # Assemble configuration
48 |     driver_config = ClimateMachine.AtmosLESConfiguration(
49 |         "ConvectiveBoundaryLayer",
50 |         N,
51 |         resolution,
52 |         xmax,
53 |         ymax,
54 |         zmax,
55 |         param_set,
56 |         ics,
57 |         model = model,
58 |     )
59 |     solver_config = ClimateMachine.SolverConfiguration(
60 |         t0,
61 |         timeend,
62 |         driver_config,
63 |         ode_solver_type = ode_solver_type,
64 |         init_on_cpu = true,
65 |         Courant_number = CFLmax,
66 |     )
67 |     dgn_config = config_diagnostics(driver_config)
68 | 
69 |     check_cons = (
70 |         ClimateMachine.ConservationCheck("ρ", "1mins", FT(0.0001)),
71 |         ClimateMachine.ConservationCheck("energy.ρe", "1mins", FT(0.0025)),
72 |     )
73 | 
74 |     result = ClimateMachine.invoke!(
75 |         solver_config;
76 |         diagnostics_config = dgn_config,
77 |         check_cons = check_cons,
78 |         check_euclidean_distance = true,
79 |     )
80 | end
81 | 
82 | main()
83 | 
--------------------------------------------------------------------------------
/experiments/AtmosLES/convective_bl_model.jl:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env julia --project
  2 | # This experiment file establishes the initial conditions, boundary conditions,
  3 | # source terms and simulation parameters (domain size + resolution) for the
  4 | 
  5 | # Convective Boundary Layer LES case (Kitamura et al, 2016).
  6 | 
  7 | ## ### Convective Boundary Layer LES
  8 | ## [Nishizawa2018](@cite)
  9 | #
 10 | # To simulate the experiment, type in
 11 | #
 12 | # julia --project experiments/AtmosLES/convective_bl_les.jl
 13 | 
 14 | using ArgParse
 15 | using Distributions
 16 | using DocStringExtensions
 17 | using LinearAlgebra
 18 | using Printf
 19 | using Random
 20 | using StaticArrays
 21 | using UnPack
 22 | using Test
 23 | 
 24 | using ClimateMachine
 25 | using ClimateMachine.Atmos
 26 | using ClimateMachine.ConfigTypes
 27 | using ClimateMachine.DGMethods.NumericalFluxes
 28 | using ClimateMachine.Diagnostics
 29 | using ClimateMachine.GenericCallbacks
 30 | using ClimateMachine.Mesh.Filters
 31 | using ClimateMachine.Mesh.Grids
 32 | using ClimateMachine.ODESolvers
 33 | using ClimateMachine.Orientations
 34 | using Thermodynamics
 35 | using ClimateMachine.TurbulenceClosures
 36 | using ClimateMachine.TurbulenceConvection
 37 | using ClimateMachine.VariableTemplates
 38 | 
 39 | using ClimateMachine.BalanceLaws
 40 | import ClimateMachine.BalanceLaws: source, prognostic_vars
 41 | 
 42 | using CLIMAParameters
 43 | using CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav
 44 | struct EarthParameterSet <: AbstractEarthParameterSet end
 45 | const param_set = EarthParameterSet()
 46 | 
 47 | using ClimateMachine.Atmos: altitude, recover_thermo_state
 48 | 
 49 | """
 50 |     ConvectiveBL Geostrophic Forcing (Source)
 51 | """
 52 | struct ConvectiveBLGeostrophic{FT} <: TendencyDef{Source}
 53 |     "Coriolis parameter [s⁻¹]"
 54 |     f_coriolis::FT
 55 |     "Eastward geostrophic velocity `[m/s]` (Base)"
 56 |     u_geostrophic::FT
 57 |     "Eastward geostrophic velocity `[m/s]` (Slope)"
 58 |     u_slope::FT
 59 |     "Northward geostrophic velocity `[m/s]`"
 60 |     v_geostrophic::FT
 61 | end
 62 | prognostic_vars(::ConvectiveBLGeostrophic) = (Momentum(),)
 63 | 
 64 | function source(::Momentum, s::ConvectiveBLGeostrophic, m, args)
 65 |     @unpack state, aux = args
 66 |     @unpack f_coriolis, u_geostrophic, u_slope, v_geostrophic = s
 67 | 
 68 |     z = altitude(m, aux)
 69 |     # Note z dependence of eastward geostrophic velocity
 70 |     u_geo = SVector(u_geostrophic + u_slope * z, v_geostrophic, 0)
 71 |     ẑ = vertical_unit_vector(m, aux)
 72 |     fkvector = f_coriolis * ẑ
 73 |     # Accumulate sources
 74 |     return -fkvector × (state.ρu .- state.ρ * u_geo)
 75 | end
 76 | 
 77 | """
 78 |   ConvectiveBL Sponge (Source)
 79 | """
 80 | struct ConvectiveBLSponge{FT} <: TendencyDef{Source}
 81 |     "Maximum domain altitude (m)"
 82 |     z_max::FT
 83 |     "Altitude at with sponge starts (m)"
 84 |     z_sponge::FT
 85 |     "Sponge Strength 0 ⩽ α_max ⩽ 1"
 86 |     α_max::FT
 87 |     "Sponge exponent"
 88 |     γ::FT
 89 |     "Eastward geostrophic velocity `[m/s]` (Base)"
 90 |     u_geostrophic::FT
 91 |     "Eastward geostrophic velocity `[m/s]` (Slope)"
 92 |     u_slope::FT
 93 |     "Northward geostrophic velocity `[m/s]`"
 94 |     v_geostrophic::FT
 95 | end
 96 | prognostic_vars(::ConvectiveBLSponge) = (Momentum(),)
 97 | 
 98 | function source(::Momentum, s::ConvectiveBLSponge, m, args)
 99 |     @unpack state, aux = args
100 | 
101 |     @unpack z_max, z_sponge, α_max, γ = s
102 |     @unpack u_geostrophic, u_slope, v_geostrophic = s
103 | 
104 |     z = altitude(m, aux)
105 |     u_geo = SVector(u_geostrophic + u_slope * z, v_geostrophic, 0)
106 |     ẑ = vertical_unit_vector(m, aux)
107 |     # Accumulate sources
108 |     if z_sponge <= z
109 |         r = (z - z_sponge) / (z_max - z_sponge)
110 |         β_sponge = α_max * sinpi(r / 2)^s.γ
111 |         return -β_sponge * (state.ρu .- state.ρ * u_geo)
112 |     else
113 |         FT = eltype(state)
114 |         return SVector{3, FT}(0, 0, 0)
115 |     end
116 | end
117 | 
118 | """
119 |   Initial Condition for ConvectiveBoundaryLayer LES
120 | """
121 | function init_problem!(problem, bl, state, aux, localgeo, t)
122 |     (x, y, z) = localgeo.coord
123 | 
124 |     # Problem floating point precision
125 |     param_set = parameter_set(bl)
126 |     FT = eltype(state)
127 |     R_gas::FT = R_d(param_set)
128 |     c_p::FT = cp_d(param_set)
129 |     c_v::FT = cv_d(param_set)
130 |     p0::FT = MSLP(param_set)
131 |     _grav::FT = grav(param_set)
132 |     γ::FT = c_p / c_v
133 |     # Initialise speeds [u = Eastward, v = Northward, w = Vertical]
134 |     u::FT = 4
135 |     v::FT = 0
136 |     w::FT = 0
137 |     # Assign piecewise quantities to θ_liq and q_tot
138 |     θ_liq::FT = 0
139 |     q_tot::FT = 0
140 |     # functions for potential temperature
141 | 
142 |     θ_liq = FT(288) + FT(4 / 1000) * z
143 |     θ = θ_liq
144 |     π_exner = FT(1) - _grav / (c_p * θ) * z # exner pressure
145 |     ρ = p0 / (R_gas * θ) * (π_exner)^(c_v / R_gas) # density
146 |     # Establish thermodynamic state and moist phase partitioning
147 |     TS = PhaseEquil_ρθq(param_set, ρ, θ_liq, q_tot)
148 | 
149 |     # Compute momentum contributions
150 |     ρu = ρ * u
151 |     ρv = ρ * v
152 |     ρw = ρ * w
153 | 
154 |     # Compute energy contributions
155 |     e_kin = FT(1 // 2) * (u^2 + v^2 + w^2)
156 |     e_pot = _grav * z
157 |     ρe_tot = ρ * total_energy(e_kin, e_pot, TS)
158 | 
159 |     # Assign initial conditions for prognostic state variables
160 |     state.ρ = ρ
161 |     state.ρu = SVector(ρu, ρv, ρw)
162 |     state.energy.ρe = ρe_tot
163 |     if !(moisture_model(bl) isa DryModel)
164 |         state.moisture.ρq_tot = ρ * q_tot
165 |     end
166 | 
167 |     if z <= FT(400) # Add random perturbations to bottom 400m of model
168 |         state.energy.ρe += rand() * ρe_tot / 100
169 |     end
170 |     init_state_prognostic!(turbconv_model(bl), bl, state, aux, localgeo, t)
171 | end
172 | 
173 | function surface_temperature_variation(bl, state, t)
174 |     FT = eltype(state)
175 |     ρ = state.ρ
176 |     θ_liq_sfc = FT(291.15) + FT(20) * sinpi(FT(t / 12 / 3600))
177 |     param_set = parameter_set(bl)
178 |     if moisture_model(bl) isa DryModel
179 |         TS = PhaseDry_ρθ(param_set, ρ, θ_liq_sfc)
180 |     else
181 |         q_tot = state.moisture.ρq_tot / ρ
182 |         TS = PhaseEquil_ρθq(param_set, ρ, θ_liq_sfc, q_tot)
183 |     end
184 |     return air_temperature(TS)
185 | end
186 | 
187 | function convective_bl_model(
188 |     ::Type{FT},
189 |     config_type,
190 |     zmax,
191 |     surface_flux;
192 |     turbconv = NoTurbConv(),
193 |     moisture_model = "dry",
194 | ) where {FT}
195 | 
196 |     ics = init_problem!     # Initial conditions
197 | 
198 |     C_smag = FT(0.23)     # Smagorinsky coefficient
199 |     C_drag = FT(0.001)    # Momentum exchange coefficient
200 |     z_sponge = FT(2560)     # Start of sponge layer
201 | 
202 |     α_max = FT(0.75)       # Strength of sponge layer (timescale)
203 | 
204 |     γ = 2                  # Strength of sponge layer (exponent)
205 |     u_geostrophic = FT(4)        # Eastward relaxation speed
206 |     u_slope = FT(0)              # Slope of altitude-dependent relaxation speed
207 |     v_geostrophic = FT(0)        # Northward relaxation speed
208 |     f_coriolis = FT(1.031e-4) # Coriolis parameter
209 |     u_star = FT(0.3)
210 |     q_sfc = FT(0)
211 |     moisture_flux = FT(0)
212 | 
213 |     # Assemble source components
214 |     source_default = (
215 |         Gravity(),
216 |         ConvectiveBLSponge{FT}(
217 |             zmax,
218 |             z_sponge,
219 |             α_max,
220 |             γ,
221 |             u_geostrophic,
222 |             u_slope,
223 |             v_geostrophic,
224 |         ),
225 |         ConvectiveBLGeostrophic{FT}(
226 |             f_coriolis,
227 |             u_geostrophic,
228 |             u_slope,
229 |             v_geostrophic,
230 |         ),
231 |         turbconv_sources(turbconv)...,
232 |     )
233 | 
234 |     if moisture_model == "dry"
235 |         source = source_default
236 |         moisture = DryModel()
237 |     elseif moisture_model == "equilibrium"
238 |         source = source_default
239 |         moisture = EquilMoist(; maxiter = 5, tolerance = FT(0.1))
240 |     elseif moisture_model == "nonequilibrium"
241 |         source = (source_default..., CreateClouds())
242 |         moisture = NonEquilMoist()
243 |     else
244 |         @warn @sprintf(
245 |             """
246 | %s: unrecognized moisture_model in source terms, using the defaults""",
247 |             moisture_model,
248 |         )
249 |         source = source_default
250 |     end
251 |     # Set up problem initial and boundary conditions
252 |     if surface_flux == "prescribed"
253 |         energy_bc = PrescribedEnergyFlux((state, aux, t) -> LHF + SHF)
254 |         moisture_bc = PrescribedMoistureFlux((state, aux, t) -> moisture_flux)
255 |     elseif surface_flux == "bulk"
256 |         energy_bc = BulkFormulaEnergy(
257 |             (bl, state, aux, t, normPu_int) -> C_drag,
258 |             (bl, state, aux, t) ->
259 |                 (surface_temperature_variation(bl, state, t), q_sfc),
260 |         )
261 |         moisture_bc = BulkFormulaMoisture(
262 |             (state, aux, t, normPu_int) -> C_drag,
263 |             (state, aux, t) -> q_sfc,
264 |         )
265 |     else
266 |         @warn @sprintf(
267 |             """
268 | %s: unrecognized surface flux; using 'prescribed'""",
269 |             surface_flux,
270 |         )
271 |     end
272 | 
273 |     # Define the physics
274 |     physics = AtmosPhysics{FT}(
275 |         param_set;
276 |         turbulence = SmagorinskyLilly{FT}(C_smag),
277 |         moisture = moisture,
278 |         turbconv = turbconv,
279 |     )
280 | 
281 |     moisture_bcs = moisture_model == "dry" ? () : (; moisture = moisture_bc)
282 |     boundary_conditions = (
283 |         AtmosBC(
284 |             physics;
285 |             momentum = Impenetrable(DragLaw(
286 |                 # normPu_int is the internal horizontal speed
287 |                 # P represents the projection onto the horizontal
288 |                 (state, aux, t, normPu_int) -> (u_star / normPu_int)^2,
289 |             )),
290 |             energy = energy_bc,
291 |             moisture_bcs...,
292 |             turbconv = turbconv_bcs(turbconv)[1],
293 |         ),
294 |         AtmosBC(physics; turbconv = turbconv_bcs(turbconv)[2]),
295 |     )
296 | 
297 |     problem = AtmosProblem(
298 |         init_state_prognostic = ics,
299 |         boundaryconditions = boundary_conditions,
300 |     )
301 | 
302 |     # Assemble model components
303 |     model =
304 |         AtmosModel{FT}(config_type, physics; problem = problem, source = source)
305 | 
306 |     return model
307 | end
308 | 
309 | function config_diagnostics(driver_config)
310 |     default_dgngrp = setup_atmos_default_diagnostics(
311 |         AtmosLESConfigType(),
312 |         "2500steps",
313 |         driver_config.name,
314 |     )
315 |     core_dgngrp = setup_atmos_core_diagnostics(
316 |         AtmosLESConfigType(),
317 |         "2500steps",
318 |         driver_config.name,
319 |     )
320 |     return ClimateMachine.DiagnosticsConfiguration([
321 |         default_dgngrp,
322 |         core_dgngrp,
323 |     ])
324 | end
325 | 
--------------------------------------------------------------------------------
/experiments/AtmosLES/ekman_layer_model.jl:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env julia --project
  2 | #=
  3 | # This experiment file establishes the initial conditions, boundary conditions,
  4 | # source terms and simulation parameters (domain size + resolution) for
  5 | # a dry neutrally stratified Ekman layer.
  6 | # 
  7 | # The initial conditions are given by constant horizontal velocity of 1 m/s,
  8 | # and a constant potential temperature profile. The bottom boundary condition
  9 | # results in momentum drag, and there is no exchange of heat from the surface
 10 | # since the fluxes are zero and the temperature is homogeneous.
 11 | #
 12 | =#
 13 | 
 14 | using ArgParse
 15 | using Distributions
 16 | using StaticArrays
 17 | using Test
 18 | using DocStringExtensions
 19 | using LinearAlgebra
 20 | using Printf
 21 | using UnPack
 22 | 
 23 | using ClimateMachine
 24 | using ClimateMachine.Atmos
 25 | using ClimateMachine.Orientations
 26 | using ClimateMachine.ConfigTypes
 27 | using ClimateMachine.DGMethods.NumericalFluxes
 28 | using Thermodynamics.TemperatureProfiles
 29 | using ClimateMachine.Diagnostics
 30 | using ClimateMachine.GenericCallbacks
 31 | using ClimateMachine.Mesh.Filters
 32 | using ClimateMachine.Mesh.Grids
 33 | using ClimateMachine.ODESolvers
 34 | using Thermodynamics
 35 | using ClimateMachine.TurbulenceClosures
 36 | using ClimateMachine.TurbulenceConvection
 37 | using ClimateMachine.VariableTemplates
 38 | using ClimateMachine.BalanceLaws
 39 | import ClimateMachine.BalanceLaws: source, prognostic_vars
 40 | 
 41 | using CLIMAParameters
 42 | using CLIMAParameters.Planet: cp_d, cv_d, grav, T_surf_ref
 43 | using CLIMAParameters.Atmos.SubgridScale: C_smag, C_drag
 44 | struct EarthParameterSet <: AbstractEarthParameterSet end
 45 | const param_set = EarthParameterSet()
 46 | import CLIMAParameters
 47 | 
 48 | using ClimateMachine.Atmos: altitude, recover_thermo_state, density
 49 | 
 50 | """
 51 |   EkmanLayer Geostrophic Forcing (Source)
 52 | """
 53 | struct EkmanLayerGeostrophic{FT} <: TendencyDef{Source}
 54 |     "Coriolis parameter [s⁻¹]"
 55 |     f_coriolis::FT
 56 |     "Eastward geostrophic velocity `[m/s]` (Base)"
 57 |     u_geostrophic::FT
 58 |     "Eastward geostrophic velocity `[m/s]` (Slope)"
 59 |     u_slope::FT
 60 |     "Northward geostrophic velocity `[m/s]`"
 61 |     v_geostrophic::FT
 62 | end
 63 | prognostic_vars(::EkmanLayerGeostrophic) = (Momentum(),)
 64 | 
 65 | function source(::Momentum, s::EkmanLayerGeostrophic, m, args)
 66 |     @unpack state, aux = args
 67 |     @unpack f_coriolis, u_geostrophic, u_slope, v_geostrophic = s
 68 | 
 69 |     z = altitude(m, aux)
 70 |     # Note z dependence of eastward geostrophic velocity
 71 |     u_geo = SVector(u_geostrophic + u_slope * z, v_geostrophic, 0)
 72 |     ẑ = vertical_unit_vector(m, aux)
 73 |     fkvector = f_coriolis * ẑ
 74 |     # Accumulate sources
 75 |     return -fkvector × (state.ρu .- state.ρ * u_geo)
 76 | end
 77 | 
 78 | """
 79 |   EkmanLayer Sponge (Source)
 80 | """
 81 | struct EkmanLayerSponge{FT} <: TendencyDef{Source}
 82 |     "Maximum domain altitude (m)"
 83 |     z_max::FT
 84 |     "Altitude at with sponge starts (m)"
 85 |     z_sponge::FT
 86 |     "Sponge Strength 0 ⩽ α_max ⩽ 1"
 87 |     α_max::FT
 88 |     "Sponge exponent"
 89 |     γ::FT
 90 |     "Eastward geostrophic velocity `[m/s]` (Base)"
 91 |     u_geostrophic::FT
 92 |     "Eastward geostrophic velocity `[m/s]` (Slope)"
 93 |     u_slope::FT
 94 |     "Northward geostrophic velocity `[m/s]`"
 95 |     v_geostrophic::FT
 96 | end
 97 | prognostic_vars(::EkmanLayerSponge) = (Momentum(),)
 98 | 
 99 | function source(::Momentum, s::EkmanLayerSponge, m, args)
100 |     @unpack state, aux = args
101 | 
102 |     @unpack z_max, z_sponge, α_max, γ = s
103 |     @unpack u_geostrophic, u_slope, v_geostrophic = s
104 | 
105 |     z = altitude(m, aux)
106 |     u_geo = SVector(u_geostrophic + u_slope * z, v_geostrophic, 0)
107 |     ẑ = vertical_unit_vector(m, aux)
108 |     # Accumulate sources
109 |     if z_sponge <= z
110 |         r = (z - z_sponge) / (z_max - z_sponge)
111 |         β_sponge = α_max * sinpi(r / 2)^s.γ
112 |         return -β_sponge * (state.ρu .- state.ρ * u_geo)
113 |     else
114 |         FT = eltype(state)
115 |         return SVector{3, FT}(0, 0, 0)
116 |     end
117 | end
118 | 
119 | add_perturbations!(state, localgeo) = nothing
120 | 
121 | """
122 |   Initial Condition for EkmanLayer simulation
123 | """
124 | function init_problem!(problem, bl, state, aux, localgeo, t)
125 |     (x, y, z) = localgeo.coord
126 |     # Problem floating point precision
127 |     param_set = parameter_set(bl)
128 |     FT = eltype(state)
129 |     c_p::FT = cp_d(param_set)
130 |     c_v::FT = cv_d(param_set)
131 |     _grav::FT = grav(param_set)
132 |     γ::FT = c_p / c_v
133 |     # Initialise speeds [u = Eastward, v = Northward, w = Vertical]
134 |     u::FT = 1
135 |     v::FT = 0
136 |     w::FT = 0
137 |     # Assign constant θ profile and equal to surface temperature
138 |     θ::FT = T_surf_ref(param_set)
139 | 
140 |     p = aux.ref_state.p
141 |     TS = PhaseDry_pθ(param_set, p, θ)
142 | 
143 |     compress = compressibility_model(bl) isa Compressible
144 |     ρ = compress ? air_density(TS) : aux.ref_state.ρ
145 |     # Compute momentum contributions
146 |     ρu = ρ * u
147 |     ρv = ρ * v
148 |     ρw = ρ * w
149 | 
150 |     # Compute energy contributions
151 |     e_kin = FT(1 // 2) * (u^2 + v^2 + w^2)
152 |     e_pot = _grav * z
153 |     ρe_tot = ρ * total_energy(e_kin, e_pot, TS)
154 | 
155 |     # Assign initial conditions for prognostic state variables
156 |     state.ρ = ρ
157 |     state.ρu = SVector(ρu, ρv, ρw)
158 |     state.energy.ρe = ρe_tot
159 |     add_perturbations!(state, localgeo)
160 |     init_state_prognostic!(turbconv_model(bl), bl, state, aux, localgeo, t)
161 | end
162 | 
163 | function ekman_layer_model(
164 |     ::Type{FT},
165 |     config_type,
166 |     zmax,
167 |     surface_flux;
168 |     turbulence = ConstantKinematicViscosity(FT(0.1)),
169 |     turbconv = NoTurbConv(),
170 |     compressibility = Compressible(),
171 |     ref_state = HydrostaticState(DryAdiabaticProfile{FT}(param_set),),
172 | ) where {FT}
173 | 
174 |     ics = init_problem!     # Initial conditions
175 | 
176 |     C_drag_::FT = C_drag(param_set) # FT(0.001)    # Momentum exchange coefficient
177 |     u_star = FT(0.30)
178 |     z_0 = FT(0.1)          # Roughness height
179 | 
180 |     z_sponge = FT(300)     # Start of sponge layer
181 |     α_max = FT(0.75)       # Strength of sponge layer (timescale)
182 |     γ = 2                  # Strength of sponge layer (exponent)
183 | 
184 |     u_geostrophic = FT(1)        # Eastward relaxation speed
185 |     u_slope = FT(0)              # Slope of altitude-dependent relaxation speed
186 |     v_geostrophic = FT(0)        # Northward relaxation speed
187 |     f_coriolis = FT(1.39e-4) # Coriolis parameter at 73N
188 | 
189 |     q_sfc = FT(0)
190 |     θ_sfc = T_surf_ref(param_set)
191 |     g = compressibility isa Compressible ? (Gravity(),) : ()
192 | 
193 |     # Assemble source components
194 |     source_default = (
195 |         g...,
196 |         EkmanLayerSponge{FT}(
197 |             zmax,
198 |             z_sponge,
199 |             α_max,
200 |             γ,
201 |             u_geostrophic,
202 |             u_slope,
203 |             v_geostrophic,
204 |         ),
205 |         EkmanLayerGeostrophic(
206 |             f_coriolis,
207 |             u_geostrophic,
208 |             u_slope,
209 |             v_geostrophic,
210 |         ),
211 |         turbconv_sources(turbconv)...,
212 |     )
213 |     source = source_default
214 | 
215 |     # Set up problem initial and boundary conditions
216 |     if surface_flux == "prescribed"
217 |         energy_bc = PrescribedEnergyFlux((state, aux, t) -> FT(0))
218 |     elseif surface_flux == "bulk"
219 |         energy_bc = BulkFormulaEnergy(
220 |             (bl, state, aux, t, normPu_int) -> C_drag_,
221 |             (bl, state, aux, t) -> (θ_sfc, q_sfc),
222 |         )
223 |     elseif surface_flux == "custom_sbl"
224 |         energy_bc = PrescribedTemperature((state, aux, t) -> θ_sfc)
225 |     elseif surface_flux == "Nishizawa2018"
226 |         energy_bc = NishizawaEnergyFlux(
227 |             (bl, state, aux, t, normPu_int) -> z_0,
228 |             (bl, state, aux, t) -> (θ_sfc, q_sfc),
229 |         )
230 |     else
231 |         @warn @sprintf(
232 |             """
233 | %s: unrecognized surface flux; using 'prescribed'""",
234 |             surface_flux,
235 |         )
236 |     end
237 | 
238 |     physics = AtmosPhysics{FT}(
239 |         param_set;
240 |         ref_state = ref_state,
241 |         turbulence = turbulence,
242 |         moisture = DryModel(),
243 |         turbconv = turbconv,
244 |         compressibility = compressibility,
245 |     )
246 | 
247 |     moisture_bcs = ()
248 |     boundary_conditions = (
249 |         AtmosBC(
250 |             physics;
251 |             momentum = Impenetrable(DragLaw(
252 |                 # normPu_int is the internal horizontal speed
253 |                 # P represents the projection onto the horizontal
254 |                 (state, aux, t, normPu_int) -> (u_star / normPu_int)^2,
255 |             )),
256 |             energy = energy_bc,
257 |             moisture_bcs...,
258 |             turbconv = turbconv_bcs(turbconv)[1],
259 |         ),
260 |         AtmosBC(physics; turbconv = turbconv_bcs(turbconv)[2]),
261 |     )
262 | 
263 |     problem = AtmosProblem(
264 |         init_state_prognostic = ics,
265 |         boundaryconditions = boundary_conditions,
266 |     )
267 | 
268 |     # Assemble model components
269 |     model =
270 |         AtmosModel{FT}(config_type, physics; problem = problem, source = source)
271 | 
272 |     return model
273 | end
274 | 
275 | function config_diagnostics(driver_config)
276 |     default_dgngrp = setup_atmos_default_diagnostics(
277 |         AtmosLESConfigType(),
278 |         "60ssecs",
279 |         driver_config.name,
280 |     )
281 |     core_dgngrp = setup_atmos_core_diagnostics(
282 |         AtmosLESConfigType(),
283 |         "60ssecs",
284 |         driver_config.name,
285 |     )
286 |     return ClimateMachine.DiagnosticsConfiguration([
287 |         default_dgngrp,
288 |         core_dgngrp,
289 |     ])
290 | end
291 | 
--------------------------------------------------------------------------------
/experiments/AtmosLES/rising_bubble_bryan.jl:
--------------------------------------------------------------------------------
  1 | using ClimateMachine
  2 | 
  3 | using ClimateMachine.Atmos
  4 | using ClimateMachine.Orientations
  5 | using ClimateMachine.ConfigTypes
  6 | using ClimateMachine.Diagnostics
  7 | using ClimateMachine.GenericCallbacks
  8 | using ClimateMachine.ODESolvers
  9 | using ClimateMachine.SystemSolvers
 10 | using ClimateMachine.Mesh.Filters
 11 | using Thermodynamics
 12 | using Thermodynamics.TemperatureProfiles
 13 | using ClimateMachine.TurbulenceClosures
 14 | using ClimateMachine.VariableTemplates
 15 | using ClimateMachine.NumericalFluxes
 16 | using ClimateMachine.VTK
 17 | 
 18 | using StaticArrays
 19 | using Test
 20 | using Printf
 21 | using MPI
 22 | using ArgParse
 23 | 
 24 | using CLIMAParameters
 25 | using CLIMAParameters.Atmos.SubgridScale: C_smag
 26 | using CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav
 27 | struct EarthParameterSet <: AbstractEarthParameterSet end
 28 | const param_set = EarthParameterSet()
 29 | 
 30 | # ------------------------ Description ------------------------- #
 31 | # 1) Dry Rising Bubble (circular potential temperature perturbation)
 32 | # 2) Boundaries - `All Walls` : Impenetrable(FreeSlip())
 33 | #                               Laterally periodic
 34 | # 3) Domain - 20000m[horizontal] x 10000m[vertical] (2-dimensional)
 35 | # 4) Timeend - 1000s
 36 | # 5) Mesh Aspect Ratio (Effective resolution) 2:1
 37 | # 7) Overrides defaults for
 38 | #               `init_on_cpu`
 39 | #               `solver_type`
 40 | #               `sources`
 41 | #               `C_smag`
 42 | # 8) Default settings can be found in `src/Driver/Configurations.jl`
 43 | # ------------------------ Description ------------------------- #
 44 | function init_risingbubble!(problem, bl, state, aux, localgeo, t)
 45 |     (x, y, z) = localgeo.coord
 46 | 
 47 |     FT = eltype(state)
 48 |     param_set = parameter_set(bl)
 49 |     R_gas::FT = R_d(param_set)
 50 |     c_p::FT = cp_d(param_set)
 51 |     c_v::FT = cv_d(param_set)
 52 |     γ::FT = c_p / c_v
 53 |     p0::FT = MSLP(param_set)
 54 |     _grav::FT = grav(param_set)
 55 | 
 56 |     xc::FT = 10000
 57 |     zc::FT = 2000
 58 |     r = sqrt((x - xc)^2 + (z - zc)^2)
 59 |     rc::FT = 2000
 60 |     θ_ref::FT = 300
 61 |     Δθ::FT = 0
 62 | 
 63 |     if r <= rc
 64 |         Δθ = FT(2) * cospi(0.5 * r / rc)^2
 65 |     end
 66 | 
 67 |     # Perturbed state:
 68 |     θ = θ_ref + Δθ # potential temperature
 69 |     π_exner = FT(1) - _grav / (c_p * θ) * z # exner pressure
 70 |     ρ = p0 / (R_gas * θ) * (π_exner)^(c_v / R_gas) # density
 71 |     q_tot = FT(0)
 72 |     ts = PhaseEquil_ρθq(param_set, ρ, θ, q_tot)
 73 |     q_pt = PhasePartition(ts)
 74 | 
 75 |     ρu = SVector(FT(0), FT(0), FT(0))
 76 | 
 77 |     # State (prognostic) variable assignment
 78 |     e_kin = FT(0)
 79 |     e_pot = gravitational_potential(bl.orientation, aux)
 80 |     ρe_tot = ρ * total_energy(e_kin, e_pot, ts)
 81 |     state.ρ = ρ
 82 |     state.ρu = ρu
 83 |     state.energy.ρe = ρe_tot
 84 |     state.moisture.ρq_tot = ρ * q_pt.tot
 85 | end
 86 | 
 87 | function config_risingbubble(FT, N, resolution, xmax, ymax, zmax, fast_method)
 88 | 
 89 |     # Choose fast solver
 90 |     if fast_method == "LowStorageRungeKutta2N"
 91 |         ode_solver = ClimateMachine.MISSolverType(
 92 |             splitting_type = ClimateMachine.SlowFastSplitting(),
 93 |             fast_model = AtmosAcousticGravityLinearModel,
 94 |             mis_method = MIS2,
 95 |             fast_method = LSRK54CarpenterKennedy,
 96 |             nsubsteps = (50,),
 97 |         )
 98 |     elseif fast_method == "StrongStabilityPreservingRungeKutta"
 99 |         ode_solver = ClimateMachine.MISSolverType(
100 |             splitting_type = ClimateMachine.SlowFastSplitting(),
101 |             fast_model = AtmosAcousticGravityLinearModel,
102 |             mis_method = MIS2,
103 |             fast_method = SSPRK33ShuOsher,
104 |             nsubsteps = (12,),
105 |         )
106 |     elseif fast_method == "MultirateInfinitesimalStep"
107 |         ode_solver = ClimateMachine.MISSolverType(
108 |             splitting_type = ClimateMachine.HEVISplitting(),
109 |             fast_model = AtmosAcousticGravityLinearModel,
110 |             mis_method = MIS2,
111 |             fast_method = (dg, Q, nsubsteps) -> MultirateInfinitesimalStep(
112 |                 MISKWRK43,
113 |                 dg,
114 |                 (dgi, Qi) -> LSRK54CarpenterKennedy(dgi, Qi),
115 |                 Q,
116 |                 nsubsteps = nsubsteps,
117 |             ),
118 |             nsubsteps = (12, 2),
119 |         )
120 |     elseif fast_method == "MultirateRungeKutta"
121 |         ode_solver = ClimateMachine.MISSolverType(
122 |             splitting_type = ClimateMachine.HEVISplitting(),
123 |             fast_model = AtmosAcousticGravityLinearModel,
124 |             mis_method = MIS2,
125 |             fast_method = (dg, Q, nsubsteps) -> MultirateRungeKutta(
126 |                 LSRK144NiegemannDiehlBusch,
127 |                 dg,
128 |                 Q,
129 |                 steps = nsubsteps,
130 |             ),
131 |             nsubsteps = (12, 4),
132 |         )
133 |     elseif fast_method == "AdditiveRungeKutta"
134 |         ode_solver = ClimateMachine.MISSolverType(
135 |             splitting_type = ClimateMachine.HEVISplitting(),
136 |             fast_model = AtmosAcousticGravityLinearModel,
137 |             mis_method = MISRK3,
138 |             fast_method = (dg, Q, dt, nsubsteps) -> AdditiveRungeKutta(
139 |                 ARK548L2SA2KennedyCarpenter,
140 |                 dg,
141 |                 LinearBackwardEulerSolver(ManyColumnLU(), isadjustable = true),
142 |                 Q,
143 |                 dt = dt,
144 |                 nsubsteps = nsubsteps,
145 |             ),
146 |             nsubsteps = (12,),
147 |         )
148 |     else
149 |         error("Invalid --fast_method=$fast_method")
150 |     end
151 | 
152 |     # Set up the model
153 |     C_smag = FT(0.23)
154 |     ref_state =
155 |         HydrostaticState(DryAdiabaticProfile{FT}(param_set, FT(300), FT(0)))
156 | 
157 |     physics = AtmosPhysics{FT}(
158 |         param_set;
159 |         turbulence = SmagorinskyLilly{FT}(C_smag),
160 |         ref_state = ref_state,
161 |     )
162 | 
163 |     model = AtmosModel{FT}(
164 |         AtmosLESConfigType,
165 |         physics;
166 |         source = (Gravity(),),
167 |         init_state_prognostic = init_risingbubble!,
168 |     )
169 | 
170 |     # Problem configuration
171 |     config = ClimateMachine.AtmosLESConfiguration(
172 |         "DryRisingBubbleMIS",
173 |         N,
174 |         resolution,
175 |         xmax,
176 |         ymax,
177 |         zmax,
178 |         param_set,
179 |         init_risingbubble!,
180 |         model = model,
181 |     )
182 |     return config, ode_solver
183 | end
184 | 
185 | function config_diagnostics(driver_config)
186 |     interval = "10000steps"
187 |     dgngrp = setup_atmos_default_diagnostics(
188 |         AtmosLESConfigType(),
189 |         interval,
190 |         driver_config.name,
191 |     )
192 |     return ClimateMachine.DiagnosticsConfiguration([dgngrp])
193 | end
194 | 
195 | function main()
196 | 
197 |     rbb_args = ArgParseSettings(autofix_names = true)
198 |     add_arg_group!(rbb_args, "RisingBubbleBryan")
199 |     @add_arg_table! rbb_args begin
200 |         "--fast_method"
201 |         help = "Choice of fast solver for the MIS method"
202 |         metavar = ""
203 |         arg_type = String
204 |         default = "AdditiveRungeKutta"
205 |     end
206 | 
207 |     cl_args = ClimateMachine.init(parse_clargs = true, custom_clargs = rbb_args)
208 |     fast_method = cl_args["fast_method"]
209 | 
210 |     # Working precision
211 |     FT = Float64
212 |     # DG polynomial order
213 |     N = 3
214 |     # Domain resolution and size
215 |     Δx = FT(125)
216 |     Δy = FT(125)
217 |     Δz = FT(125)
218 |     resolution = (Δx, Δy, Δz)
219 |     # Domain extents
220 |     xmax = FT(20000)
221 |     ymax = FT(1000)
222 |     zmax = FT(10000)
223 |     # Simulation time
224 |     t0 = FT(0)
225 |     timeend = FT(20)
226 | 
227 |     # Time-step size (s)
228 |     Δt = FT(0.4)
229 | 
230 |     driver_config, ode_solver_type =
231 |         config_risingbubble(FT, N, resolution, xmax, ymax, zmax, fast_method)
232 |     solver_config = ClimateMachine.SolverConfiguration(
233 |         t0,
234 |         timeend,
235 |         driver_config,
236 |         ode_solver_type = ode_solver_type,
237 |         init_on_cpu = true,
238 |         ode_dt = Δt,
239 |     )
240 |     dgn_config = config_diagnostics(driver_config)
241 | 
242 |     # Invoke solver (calls solve! function for time-integrator)
243 |     result = ClimateMachine.invoke!(
244 |         solver_config;
245 |         diagnostics_config = dgn_config,
246 |         check_euclidean_distance = true,
247 |     )
248 | 
249 |     @test isapprox(result, FT(1); atol = 1.5e-3)
250 | end
251 | 
252 | main()
253 | 
--------------------------------------------------------------------------------
/experiments/AtmosLES/schar_scalar_advection.jl:
--------------------------------------------------------------------------------
  1 | using ClimateMachine
  2 | ClimateMachine.init(parse_clargs = true)
  3 | 
  4 | using ClimateMachine.Atmos
  5 | using ClimateMachine.Orientations
  6 | using ClimateMachine.ConfigTypes
  7 | using ClimateMachine.Diagnostics
  8 | using ClimateMachine.GenericCallbacks
  9 | using ClimateMachine.ODESolvers
 10 | using ClimateMachine.Mesh.Filters
 11 | using ClimateMachine.Mesh.Topologies
 12 | using ClimateMachine.Mesh.Grids
 13 | using Thermodynamics.TemperatureProfiles
 14 | using Thermodynamics
 15 | using ClimateMachine.TurbulenceClosures
 16 | using ClimateMachine.VariableTemplates
 17 | using StaticArrays
 18 | using Test
 19 | 
 20 | using CLIMAParameters
 21 | using CLIMAParameters.Atmos.SubgridScale: C_smag
 22 | using CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav
 23 | struct EarthParameterSet <: AbstractEarthParameterSet end
 24 | const param_set = EarthParameterSet()
 25 | 
 26 | ### Citation
 27 | # [Schar2002](@cite)
 28 | 
 29 | # ## [Initial Conditions]
 30 | function init_schar!(problem, bl, state, aux, localgeo, t)
 31 |     ## Problem float-type
 32 |     FT = eltype(state)
 33 | 
 34 |     (x, y, z) = localgeo.coord
 35 | 
 36 |     ## Unpack constant parameters
 37 |     param_set = parameter_set(bl)
 38 |     R_gas::FT = R_d(param_set)
 39 |     c_p::FT = cp_d(param_set)
 40 |     c_v::FT = cv_d(param_set)
 41 |     p0::FT = MSLP(param_set)
 42 |     _grav::FT = grav(param_set)
 43 |     γ::FT = c_p / c_v
 44 | 
 45 |     c::FT = c_v / R_gas
 46 |     c2::FT = R_gas / c_p
 47 | 
 48 |     Tiso::FT = 250.0
 49 |     θ0::FT = Tiso
 50 | 
 51 |     ## Calculate the Brunt-Vaisaila frequency for an isothermal field
 52 |     ## Hydrostatic background state
 53 |     Brunt::FT = _grav / sqrt(c_p * Tiso)
 54 |     Brunt2::FT = Brunt * Brunt
 55 |     g2::FT = _grav * _grav
 56 | 
 57 |     π_exner::FT = exp(-_grav * z / (c_p * Tiso))
 58 |     θ::FT = θ0 * exp(Brunt2 * z / _grav)
 59 |     ρ::FT = p0 / (R_gas * θ) * (π_exner)^c
 60 | 
 61 |     ## Compute perturbed thermodynamic state:
 62 |     T = θ * π_exner
 63 |     e_int = internal_energy(param_set, T)
 64 |     ts = PhaseDry(param_set, e_int, ρ)
 65 | 
 66 |     ## Initial velocity
 67 |     z₁::FT = 4000
 68 |     z₂::FT = 5000
 69 |     u₀::FT = 10
 70 |     zscale = (z - z₁) / (z₂ - z₁)
 71 |     if z₂ <= z
 72 |         u = FT(1)
 73 |     elseif z₁ <= z < z₂
 74 |         u = (sinpi(zscale / 2))^2
 75 |     elseif z <= z₁
 76 |         u = FT(0)
 77 |     end
 78 |     u *= u₀
 79 | 
 80 |     ## Initial scalar anomaly profile
 81 |     ## Equivalent to a nondiffusive tracer
 82 |     Ax::FT = 25000
 83 |     Az::FT = 3000
 84 |     x₀::FT = 25000
 85 |     z₀::FT = 9000
 86 |     r = ((x - x₀) / Ax)^2 + ((z - z₀) / Az)^2
 87 |     if r <= 1
 88 |         χ = (cospi(r / 2))^2
 89 |     else
 90 |         χ = 0
 91 |     end
 92 | 
 93 |     ## State (prognostic) variable assignment
 94 |     e_kin = FT(1 / 2) * u^2                               # kinetic energy
 95 |     e_pot = gravitational_potential(bl.orientation, aux)# potential energy
 96 |     ρe_tot = ρ * total_energy(e_kin, e_pot, ts)         # total energy
 97 | 
 98 |     state.ρ = ρ
 99 |     state.ρu = SVector{3, FT}(ρ * u, 0, 0)
100 |     state.energy.ρe = ρe_tot
101 |     state.tracers.ρχ = ρ * SVector{1, FT}(χ)
102 | end
103 | 
104 | # Define a `setmax` method
105 | function setmax(f, xmax, ymax, zmax)
106 |     function setmaxima(xin, yin, zin)
107 |         return f(xin, yin, zin; xmax = xmax, ymax = ymax, zmax = zmax)
108 |     end
109 |     return setmaxima
110 | end
111 | 
112 | function warp_schar(
113 |     xin,
114 |     yin,
115 |     zin;
116 |     xmax = 150000.0,
117 |     ymax = 5000.0,
118 |     zmax = 25000.0,
119 | )
120 |     FT = eltype(xin)
121 |     a::FT = 25000 ## Half-width parameter [m]
122 |     r = sqrt(xin^2 + yin^2)
123 |     h₀::FT = 3000 ## Peak height [m]
124 |     λ::FT = 8000 ## Wavelength
125 |     h_star =
126 |         abs(xin - xmax / 2) <= a ? h₀ * (cospi((xin - xmax / 2) / 2a))^2 : FT(0)
127 |     h = h_star * (cospi((xin - xmax / 2) / λ))^2
128 |     x, y, z = xin, yin, zin + h * (zmax - zin) / zmax
129 |     return x, y, z
130 | end
131 | 
132 | function config_schar(FT, N, resolution, xmax, ymax, zmax)
133 |     u_relaxation = SVector(FT(10), FT(0), FT(0))
134 | 
135 |     ## Wave damping coefficient (1/s)
136 |     sponge_ampz = FT(0.5)
137 | 
138 |     ## Vertical level where the absorbing layer starts
139 |     z_s = FT(20000.0)
140 | 
141 |     ## Pass the sponge parameters to the sponge calculator
142 |     rayleigh_sponge =
143 |         RayleighSponge{FT}(zmax, z_s, sponge_ampz, u_relaxation, 2)
144 | 
145 |     ## Setup the source terms for this problem:
146 |     source = (Gravity(), rayleigh_sponge)
147 | 
148 |     ## Define the reference state:
149 |     T_virt = FT(250)
150 |     temp_profile_ref = IsothermalProfile(param_set, T_virt)
151 |     ref_state = HydrostaticState(temp_profile_ref)
152 |     nothing # hide
153 | 
154 |     # Define a warping function to build an analytic topography:
155 | 
156 |     _C_smag = FT(0.21)
157 |     _δχ = SVector{1, FT}(0)
158 | 
159 |     physics = AtmosPhysics{FT}(
160 |         param_set;
161 |         ref_state = ref_state,
162 |         turbulence = Vreman(_C_smag),
163 |         moisture = DryModel(),
164 |         tracers = NTracers{1, FT}(_δχ),
165 |     )
166 | 
167 |     model = AtmosModel{FT}(
168 |         AtmosLESConfigType,
169 |         physics;
170 |         init_state_prognostic = init_schar!,
171 |         source = source,
172 |     )
173 | 
174 |     config = ClimateMachine.AtmosLESConfiguration(
175 |         "ScharScalarAdvection",  # Problem title [String]
176 |         N,                       # Polynomial order [Int]
177 |         resolution,              # (Δx, Δy, Δz) effective resolution [m]
178 |         xmax,                    # Domain maximum size [m]
179 |         ymax,                    # Domain maximum size [m]
180 |         zmax,                    # Domain maximum size [m]
181 |         param_set,               # Parameter set.
182 |         init_schar!,             # Function specifying initial condition
183 |         model = model,           # Model type
184 |         meshwarp = setmax(warp_schar, xmax, ymax, zmax),
185 |     )
186 | 
187 |     return config
188 | end
189 | 
190 | # Define a `main` method (entry point)
191 | function main()
192 | 
193 |     FT = Float64
194 | 
195 |     ## Define the polynomial order and effective grid spacings:
196 |     N = 4
197 | 
198 |     ## Define the domain size and spatial resolution
199 |     xmax = FT(150000)
200 |     ymax = FT(2500)
201 |     zmax = FT(25000)
202 |     Δx = FT(500)
203 |     Δy = FT(500)
204 |     Δz = FT(500)
205 |     resolution = (Δx, Δy, Δz)
206 | 
207 |     t0 = FT(0)
208 |     timeend = FT(10000)
209 | 
210 |     ## Define the max Courant for the time time integrator (ode_solver).
211 |     ## The default value is 1.7 for LSRK144:
212 |     CFL = FT(1.5)
213 | 
214 |     ## Assign configurations so they can be passed to the `invoke!` function
215 |     driver_config = config_schar(FT, N, resolution, xmax, ymax, zmax)
216 | 
217 |     ## Define the time integrator:
218 |     ## We chose an explicit single-rate LSRK144 for this problem
219 |     ode_solver_type = ClimateMachine.ExplicitSolverType(
220 |         solver_method = LSRK144NiegemannDiehlBusch,
221 |     )
222 | 
223 |     solver_config = ClimateMachine.SolverConfiguration(
224 |         t0,
225 |         timeend,
226 |         driver_config,
227 |         ode_solver_type = ode_solver_type,
228 |         init_on_cpu = true,
229 |         Courant_number = CFL,
230 |     )
231 | 
232 |     ## State Conservation Callback
233 |     # State variable
234 |     Q = solver_config.Q
235 |     # Volume geometry information
236 |     vgeo = driver_config.grid.vgeo
237 |     M = vgeo[:, Grids._M, :]
238 |     # Unpack prognostic vars
239 |     ρχ₀ = Q[:, 6, :]
240 |     # DG variable sums
241 |     Σρχ₀ = sum(ρχ₀ .* M)
242 |     cb_check_tracer = GenericCallbacks.EveryXSimulationSteps(1000) do
243 |         Q = solver_config.Q
244 |         δρχ = (sum(Q[:, 6, :] .* M) .- Σρχ₀) ./ Σρχ₀
245 |         @show (abs(δρχ))
246 |         nothing
247 |     end
248 | 
249 |     ## Set up the spectral filter to remove the solutions spurious modes
250 |     ## Define the order of the exponential filter: use 32 or 64 for this problem.
251 |     ## The larger the value, the less dissipation you get:
252 |     filterorder = 64
253 |     filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)
254 |     cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do
255 |         Filters.apply!(
256 |             solver_config.Q,
257 |             AtmosFilterPerturbations(driver_config.bl),
258 |             solver_config.dg.grid,
259 |             filter,
260 |             state_auxiliary = solver_config.dg.state_auxiliary,
261 |         )
262 |         nothing
263 |     end
264 |     ## End exponential filter
265 | 
266 |     ## Invoke solver (calls `solve!` function for time-integrator),
267 |     ## pass the driver, solver and diagnostic config information.
268 |     result = ClimateMachine.invoke!(
269 |         solver_config;
270 |         user_callbacks = (cbfilter, cb_check_tracer),
271 |         check_euclidean_distance = true,
272 |     )
273 | 
274 |     ## Check that the solution norm is reasonable.
275 |     @test isapprox(result, FT(1); atol = 1.5e-4)
276 | end
277 | 
278 | # Call `main`
279 | main()
280 | 
--------------------------------------------------------------------------------
/experiments/AtmosLES/stable_bl_les.jl:
--------------------------------------------------------------------------------
  1 | using Random
  2 | 
  3 | include("stable_bl_model.jl")
  4 | 
  5 | function add_perturbations!(state, localgeo)
  6 |     FT = eltype(state)
  7 |     z = localgeo.coord[3]
  8 |     if z <= FT(50) # Add random perturbations to bottom 50m of model
  9 |         state.energy.ρe += (rand() - 0.5) * state.energy.ρe / 100
 10 |     end
 11 | end
 12 | 
 13 | function set_clima_parameters(filename)
 14 |     eval(:(include($filename)))
 15 | end
 16 | 
 17 | function main(cl_args)
 18 | 
 19 |     surface_flux = cl_args["surface_flux"]
 20 | 
 21 |     FT = Float64
 22 |     config_type = AtmosLESConfigType
 23 |     # DG polynomial order
 24 |     N = 4
 25 |     # Domain resolution and size
 26 |     Δh = FT(20)
 27 |     Δv = FT(20)
 28 | 
 29 |     resolution = (Δh, Δh, Δv)
 30 | 
 31 |     # Prescribe domain parameters
 32 |     xmax = FT(100)
 33 |     ymax = FT(100)
 34 |     zmax = FT(400)
 35 | 
 36 |     t0 = FT(0)
 37 | 
 38 |     # Required simulation time == 9hours
 39 |     timeend = FT(3600 * 0.1)
 40 |     CFLmax = FT(0.4)
 41 | 
 42 |     C_smag_ = C_smag(param_set) #FT(0.23)
 43 | 
 44 |     model = stable_bl_model(
 45 |         FT,
 46 |         config_type,
 47 |         zmax,
 48 |         surface_flux;
 49 |         turbulence = SmagorinskyLilly{FT}(C_smag_),
 50 |     )
 51 | 
 52 |     ics = model.problem.init_state_prognostic
 53 | 
 54 |     # Assemble configuration
 55 |     driver_config = ClimateMachine.AtmosLESConfiguration(
 56 |         "StableBoundaryLayer",
 57 |         N,
 58 |         resolution,
 59 |         xmax,
 60 |         ymax,
 61 |         zmax,
 62 |         param_set,
 63 |         init_problem!,
 64 |         model = model,
 65 |     )
 66 | 
 67 |     # Choose default IMEX solver
 68 |     ode_solver_type = ClimateMachine.ExplicitSolverType()
 69 | 
 70 |     solver_config = ClimateMachine.SolverConfiguration(
 71 |         t0,
 72 |         timeend,
 73 |         driver_config,
 74 |         ode_solver_type = ode_solver_type,
 75 |         init_on_cpu = true,
 76 |         Courant_number = CFLmax,
 77 |     )
 78 |     dgn_config = config_diagnostics(driver_config)
 79 | 
 80 |     check_cons = (ClimateMachine.ConservationCheck("ρ", "1mins", FT(0.0001)),)
 81 | 
 82 |     result = ClimateMachine.invoke!(
 83 |         solver_config;
 84 |         diagnostics_config = dgn_config,
 85 |         check_cons = check_cons,
 86 |         check_euclidean_distance = true,
 87 |     )
 88 | end
 89 | 
 90 | # ArgParse in global scope to modify Clima Parameters
 91 | sbl_args = ArgParseSettings(autofix_names = true)
 92 | add_arg_group!(sbl_args, "StableBoundaryLayer")
 93 | @add_arg_table! sbl_args begin
 94 |     "--cparam-file"
 95 |     help = "specify CLIMAParameters file"
 96 |     arg_type = Union{String, Nothing}
 97 |     default = nothing
 98 | 
 99 |     "--surface-flux"
100 |     help = "specify surface flux for energy and moisture"
101 |     metavar = "prescribed|bulk|custom_sbl"
102 |     arg_type = String
103 |     default = "custom_sbl"
104 | end
105 | 
106 | cl_args = ClimateMachine.init(parse_clargs = true, custom_clargs = sbl_args)
107 | if !isnothing(cl_args["cparam_file"])
108 |     filename = cl_args["cparam_file"]
109 |     set_clima_parameters(filename)
110 | end
111 | 
112 | main(cl_args)
113 | 
--------------------------------------------------------------------------------
/experiments/AtmosLES/stable_bl_model.jl:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env julia --project
  2 | #=
  3 | # This experiment file establishes the initial conditions, boundary conditions,
  4 | # source terms and simulation parameters (domain size + resolution) for the
  5 | # GABLS LES case ([Beare2006](@cite); [Kosovic2000](@cite)).
  6 | #
  7 | ## [Kosovic2000](@cite)
  8 | #
  9 | # To simulate the experiment, type in
 10 | #
 11 | # julia --project experiments/AtmosLES/stable_bl_les.jl
 12 | =#
 13 | 
 14 | using ArgParse
 15 | using Distributions
 16 | using StaticArrays
 17 | using Test
 18 | using DocStringExtensions
 19 | using LinearAlgebra
 20 | using Printf
 21 | using UnPack
 22 | 
 23 | using ClimateMachine
 24 | using ClimateMachine.Atmos
 25 | using ClimateMachine.Orientations
 26 | using ClimateMachine.ConfigTypes
 27 | using ClimateMachine.DGMethods.NumericalFluxes
 28 | using Thermodynamics.TemperatureProfiles
 29 | using ClimateMachine.Diagnostics
 30 | using ClimateMachine.GenericCallbacks
 31 | using ClimateMachine.Mesh.Filters
 32 | using ClimateMachine.Mesh.Grids
 33 | using ClimateMachine.ODESolvers
 34 | using Thermodynamics
 35 | using ClimateMachine.TurbulenceClosures
 36 | using ClimateMachine.TurbulenceConvection
 37 | using ClimateMachine.VariableTemplates
 38 | using ClimateMachine.BalanceLaws
 39 | import ClimateMachine.BalanceLaws: source, prognostic_vars
 40 | 
 41 | using CLIMAParameters
 42 | using CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav, day
 43 | using CLIMAParameters.Atmos.SubgridScale: C_smag, C_drag
 44 | struct EarthParameterSet <: AbstractEarthParameterSet end
 45 | const param_set = EarthParameterSet()
 46 | import CLIMAParameters
 47 | 
 48 | using ClimateMachine.Atmos: altitude, recover_thermo_state, density
 49 | 
 50 | """
 51 |   StableBL Geostrophic Forcing (Source)
 52 | """
 53 | struct StableBLGeostrophic{FT} <: TendencyDef{Source}
 54 |     "Coriolis parameter [s⁻¹]"
 55 |     f_coriolis::FT
 56 |     "Eastward geostrophic velocity `[m/s]` (Base)"
 57 |     u_geostrophic::FT
 58 |     "Eastward geostrophic velocity `[m/s]` (Slope)"
 59 |     u_slope::FT
 60 |     "Northward geostrophic velocity `[m/s]`"
 61 |     v_geostrophic::FT
 62 | end
 63 | prognostic_vars(::StableBLGeostrophic) = (Momentum(),)
 64 | 
 65 | function source(::Momentum, s::StableBLGeostrophic, m, args)
 66 |     @unpack state, aux = args
 67 |     @unpack f_coriolis, u_geostrophic, u_slope, v_geostrophic = s
 68 | 
 69 |     z = altitude(m, aux)
 70 |     # Note z dependence of eastward geostrophic velocity
 71 |     u_geo = SVector(u_geostrophic + u_slope * z, v_geostrophic, 0)
 72 |     ẑ = vertical_unit_vector(m, aux)
 73 |     fkvector = f_coriolis * ẑ
 74 |     # Accumulate sources
 75 |     return -fkvector × (state.ρu .- state.ρ * u_geo)
 76 | end
 77 | 
 78 | """
 79 |   StableBL Sponge (Source)
 80 | """
 81 | struct StableBLSponge{FT} <: TendencyDef{Source}
 82 |     "Maximum domain altitude (m)"
 83 |     z_max::FT
 84 |     "Altitude at with sponge starts (m)"
 85 |     z_sponge::FT
 86 |     "Sponge Strength 0 ⩽ α_max ⩽ 1"
 87 |     α_max::FT
 88 |     "Sponge exponent"
 89 |     γ::FT
 90 |     "Eastward geostrophic velocity `[m/s]` (Base)"
 91 |     u_geostrophic::FT
 92 |     "Eastward geostrophic velocity `[m/s]` (Slope)"
 93 |     u_slope::FT
 94 |     "Northward geostrophic velocity `[m/s]`"
 95 |     v_geostrophic::FT
 96 | end
 97 | 
 98 | prognostic_vars(::StableBLSponge) = (Momentum(),)
 99 | 
100 | function source(::Momentum, s::StableBLSponge, m, args)
101 |     @unpack state, aux = args
102 | 
103 |     @unpack z_max, z_sponge, α_max, γ = s
104 |     @unpack u_geostrophic, u_slope, v_geostrophic = s
105 | 
106 |     z = altitude(m, aux)
107 |     u_geo = SVector(u_geostrophic + u_slope * z, v_geostrophic, 0)
108 |     ẑ = vertical_unit_vector(m, aux)
109 |     # Accumulate sources
110 |     if z_sponge <= z
111 |         r = (z - z_sponge) / (z_max - z_sponge)
112 |         β_sponge = α_max * sinpi(r / 2)^s.γ
113 |         return -β_sponge * (state.ρu .- state.ρ * u_geo)
114 |     else
115 |         FT = eltype(state)
116 |         return SVector{3, FT}(0, 0, 0)
117 |     end
118 | end
119 | 
120 | add_perturbations!(state, localgeo) = nothing
121 | 
122 | """
123 |   Initial Condition for StableBoundaryLayer LES
124 | """
125 | function init_problem!(problem, bl, state, aux, localgeo, t)
126 |     (x, y, z) = localgeo.coord
127 |     # Problem floating point precision
128 |     param_set = parameter_set(bl)
129 |     FT = eltype(state)
130 |     R_gas::FT = R_d(param_set)
131 |     c_p::FT = cp_d(param_set)
132 |     c_v::FT = cv_d(param_set)
133 |     p0::FT = MSLP(param_set)
134 |     _grav::FT = grav(param_set)
135 |     γ::FT = c_p / c_v
136 |     # Initialise speeds [u = Eastward, v = Northward, w = Vertical]
137 |     u::FT = 8
138 |     v::FT = 0
139 |     w::FT = 0
140 |     # Assign piecewise quantities to θ_liq and q_tot
141 |     θ_liq::FT = 0
142 |     q_tot::FT = 0
143 |     # Piecewise functions for potential temperature and total moisture
144 |     z1 = FT(100)
145 |     if z <= z1
146 |         θ_liq = FT(265)
147 |     else
148 |         θ_liq = FT(265) + FT(0.01) * (z - z1)
149 |     end
150 |     θ = θ_liq
151 |     p = aux.ref_state.p
152 |     # Establish thermodynamic state and moist phase partitioning
153 |     if moisture_model(bl) isa DryModel
154 |         TS = PhaseDry_pθ(param_set, p, θ)
155 |     else
156 |         TS = PhaseEquil_pθq(param_set, p, θ_liq, q_tot)
157 |     end
158 | 
159 |     compress = compressibility_model(bl) isa Compressible
160 |     ρ = compress ? air_density(TS) : aux.ref_state.ρ
161 |     # Compute momentum contributions
162 |     ρu = ρ * u
163 |     ρv = ρ * v
164 |     ρw = ρ * w
165 | 
166 |     # Compute energy contributions
167 |     e_kin = FT(1 // 2) * (u^2 + v^2 + w^2)
168 |     e_pot = _grav * z
169 |     ρe_tot = ρ * total_energy(e_kin, e_pot, TS)
170 | 
171 |     # Assign initial conditions for prognostic state variables
172 |     state.ρ = ρ
173 |     state.ρu = SVector(ρu, ρv, ρw)
174 |     state.energy.ρe = ρe_tot
175 |     if !(moisture_model(bl) isa DryModel)
176 |         state.moisture.ρq_tot = ρ * q_tot
177 |     end
178 |     add_perturbations!(state, localgeo)
179 |     init_state_prognostic!(turbconv_model(bl), bl, state, aux, localgeo, t)
180 | end
181 | 
182 | function surface_temperature_variation(state, t)
183 |     FT = eltype(state)
184 |     return FT(265) - FT(1 / 4) * (t / 3600)
185 | end
186 | 
187 | function stable_bl_model(
188 |     ::Type{FT},
189 |     config_type,
190 |     zmax,
191 |     surface_flux;
192 |     turbulence = ConstantKinematicViscosity(FT(0)),
193 |     turbconv = NoTurbConv(),
194 |     compressibility = Compressible(),
195 |     moisture_model = "dry",
196 |     ref_state = HydrostaticState(DecayingTemperatureProfile{FT}(param_set),),
197 | ) where {FT}
198 | 
199 |     ics = init_problem!     # Initial conditions
200 | 
201 |     C_drag_::FT = C_drag(param_set) # FT(0.001)    # Momentum exchange coefficient
202 |     u_star = FT(0.30)
203 |     z_0 = FT(0.1)          # Roughness height
204 | 
205 |     z_sponge = FT(300)     # Start of sponge layer
206 |     α_max = FT(0.75)       # Strength of sponge layer (timescale)
207 |     γ = 2                  # Strength of sponge layer (exponent)
208 | 
209 |     u_geostrophic = FT(8)        # Eastward relaxation speed
210 |     u_slope = FT(0)              # Slope of altitude-dependent relaxation speed
211 |     v_geostrophic = FT(0)        # Northward relaxation speed
212 |     f_coriolis = FT(1.39e-4) # Coriolis parameter at 73N
213 | 
214 |     q_sfc = FT(0)
215 | 
216 |     g = compressibility isa Compressible ? (Gravity(),) : ()
217 | 
218 |     # Assemble source components
219 |     source_default = (
220 |         g...,
221 |         StableBLSponge{FT}(
222 |             zmax,
223 |             z_sponge,
224 |             α_max,
225 |             γ,
226 |             u_geostrophic,
227 |             u_slope,
228 |             v_geostrophic,
229 |         ),
230 |         StableBLGeostrophic{FT}(
231 |             f_coriolis,
232 |             u_geostrophic,
233 |             u_slope,
234 |             v_geostrophic,
235 |         ),
236 |         turbconv_sources(turbconv)...,
237 |     )
238 |     if moisture_model == "dry"
239 |         source = source_default
240 |         moisture = DryModel()
241 |     elseif moisture_model == "equilibrium"
242 |         source = source_default
243 |         moisture = EquilMoist(; maxiter = 5, tolerance = FT(0.1))
244 |     elseif moisture_model == "nonequilibrium"
245 |         source = (source_default..., CreateClouds())
246 |         moisture = NonEquilMoist()
247 |     else
248 |         @warn @sprintf(
249 |             """
250 | %s: unrecognized moisture_model in source terms, using the defaults""",
251 |             moisture_model,
252 |         )
253 |         source = source_default
254 |     end
255 |     # Set up problem initial and boundary conditions
256 |     if surface_flux == "prescribed"
257 |         energy_bc = PrescribedEnergyFlux((state, aux, t) -> LHF + SHF)
258 |         moisture_bc = PrescribedMoistureFlux((state, aux, t) -> moisture_flux)
259 |     elseif surface_flux == "bulk"
260 |         energy_bc = BulkFormulaEnergy(
261 |             (bl, state, aux, t, normPu_int) -> C_drag_,
262 |             (bl, state, aux, t) ->
263 |                 (surface_temperature_variation(state, t), q_sfc),
264 |         )
265 |         moisture_bc = BulkFormulaMoisture(
266 |             (state, aux, t, normPu_int) -> C_drag_,
267 |             (state, aux, t) -> q_sfc,
268 |         )
269 |     elseif surface_flux == "custom_sbl"
270 |         energy_bc = PrescribedTemperature(
271 |             (state, aux, t) -> surface_temperature_variation(state, t),
272 |         )
273 |         moisture_bc = BulkFormulaMoisture(
274 |             (state, aux, t, normPu_int) -> C_drag_,
275 |             (state, aux, t) -> q_sfc,
276 |         )
277 |     elseif surface_flux == "Nishizawa2018"
278 |         energy_bc = NishizawaEnergyFlux(
279 |             (bl, state, aux, t, normPu_int) -> z_0,
280 |             (bl, state, aux, t) ->
281 |                 (surface_temperature_variation(state, t), q_sfc),
282 |         )
283 |         moisture_bc = PrescribedMoistureFlux((state, aux, t) -> moisture_flux)
284 |     else
285 |         @warn @sprintf(
286 |             """
287 | %s: unrecognized surface flux; using 'prescribed'""",
288 |             surface_flux,
289 |         )
290 |     end
291 | 
292 |     # Define the physics
293 |     physics = AtmosPhysics{FT}(
294 |         param_set;
295 |         ref_state = ref_state,
296 |         turbulence = turbulence,
297 |         moisture = moisture,
298 |         turbconv = turbconv,
299 |         compressibility = compressibility,
300 |     )
301 | 
302 |     moisture_bcs = moisture_model == "dry" ? () : (; moisture = moisture_bc)
303 |     boundary_conditions = (
304 |         AtmosBC(
305 |             physics;
306 |             momentum = Impenetrable(DragLaw(
307 |                 # normPu_int is the internal horizontal speed
308 |                 # P represents the projection onto the horizontal
309 |                 (state, aux, t, normPu_int) -> (u_star / normPu_int)^2,
310 |             )),
311 |             energy = energy_bc,
312 |             moisture_bcs...,
313 |             turbconv = turbconv_bcs(turbconv)[1],
314 |         ),
315 |         AtmosBC(physics; turbconv = turbconv_bcs(turbconv)[2]),
316 |     )
317 | 
318 |     moisture_flux = FT(0)
319 |     problem = AtmosProblem(
320 |         init_state_prognostic = ics,
321 |         boundaryconditions = boundary_conditions,
322 |     )
323 | 
324 |     # Assemble model components
325 | 
326 |     model =
327 |         AtmosModel{FT}(config_type, physics; problem = problem, source = source)
328 | 
329 |     return model
330 | end
331 | 
332 | function config_diagnostics(driver_config)
333 |     default_dgngrp = setup_atmos_default_diagnostics(
334 |         AtmosLESConfigType(),
335 |         "2500steps",
336 |         driver_config.name,
337 |     )
338 |     core_dgngrp = setup_atmos_core_diagnostics(
339 |         AtmosLESConfigType(),
340 |         "2500steps",
341 |         driver_config.name,
342 |     )
343 |     return ClimateMachine.DiagnosticsConfiguration([
344 |         default_dgngrp,
345 |         core_dgngrp,
346 |     ])
347 | end
348 | 
--------------------------------------------------------------------------------
/experiments/AtmosLES/surfacebubble.jl:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env julia --project
  2 | using ClimateMachine
  3 | ClimateMachine.init(parse_clargs = true)
  4 | 
  5 | using ClimateMachine.Atmos
  6 | using ClimateMachine.Orientations
  7 | using ClimateMachine.ConfigTypes
  8 | using ClimateMachine.GenericCallbacks
  9 | using ClimateMachine.DGMethods.NumericalFluxes
 10 | using ClimateMachine.Diagnostics
 11 | using ClimateMachine.Mesh.Filters
 12 | using ClimateMachine.ODESolvers
 13 | using ClimateMachine.StdDiagnostics
 14 | using Thermodynamics
 15 | using ClimateMachine.TurbulenceClosures
 16 | using ClimateMachine.VariableTemplates
 17 | 
 18 | using Distributions
 19 | using StaticArrays
 20 | using Test
 21 | using DocStringExtensions
 22 | using LinearAlgebra
 23 | 
 24 | using CLIMAParameters
 25 | using CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav
 26 | struct EarthParameterSet <: AbstractEarthParameterSet end
 27 | const param_set = EarthParameterSet()
 28 | 
 29 | # -------------------- Surface Driven Bubble ----------------- #
 30 | # Rising thermals driven by a prescribed surface heat flux.
 31 | # 1) Boundary Conditions:
 32 | #       Laterally periodic with no flow penetration through top
 33 | #       and bottom wall boundaries.
 34 | #       Momentum: Impenetrable(FreeSlip())
 35 | #       Energy:   Spatially varying non-zero heat flux up to time t₁
 36 | # 2) Domain: 1250m × 1250m × 1000m
 37 | # Configuration defaults are in `src/Driver/Configurations.jl`
 38 | 
 39 | 
 40 | """
 41 |   Surface Driven Thermal Bubble
 42 | """
 43 | function init_surfacebubble!(problem, bl, state, aux, localgeo, t)
 44 |     (x, y, z) = localgeo.coord
 45 |     param_set = parameter_set(bl)
 46 |     FT = eltype(state)
 47 |     R_gas::FT = R_d(param_set)
 48 |     c_p::FT = cp_d(param_set)
 49 |     c_v::FT = cv_d(param_set)
 50 |     p0::FT = MSLP(param_set)
 51 |     _grav::FT = grav(param_set)
 52 |     γ::FT = c_p / c_v
 53 | 
 54 |     xc::FT = 1250
 55 |     yc::FT = 1250
 56 |     zc::FT = 1250
 57 |     θ_ref::FT = 300
 58 |     Δθ::FT = 0
 59 | 
 60 |     #Perturbed state:
 61 |     θ = θ_ref + Δθ # potential temperature
 62 |     π_exner = FT(1) - _grav / (c_p * θ) * z # exner pressure
 63 |     ρ = p0 / (R_gas * θ) * (π_exner)^(c_v / R_gas) # density
 64 | 
 65 |     q_tot = FT(0)
 66 |     ts = PhaseEquil_ρθq(param_set, ρ, θ, q_tot)
 67 |     q_pt = PhasePartition(ts)
 68 | 
 69 |     ρu = SVector(FT(0), FT(0), FT(0))
 70 |     # energy definitions
 71 |     e_kin = FT(0)
 72 |     e_pot = gravitational_potential(bl.orientation, aux)
 73 |     ρe_tot = ρ * total_energy(e_kin, e_pot, ts)
 74 |     state.ρ = ρ
 75 |     state.ρu = ρu
 76 |     state.energy.ρe = ρe_tot
 77 |     state.moisture.ρq_tot = ρ * q_pt.tot
 78 | end
 79 | 
 80 | function config_surfacebubble(FT, N, resolution, xmax, ymax, zmax)
 81 |     # Boundary conditions
 82 |     # Heat Flux Peak Magnitude
 83 |     F₀ = FT(100)
 84 |     # Time [s] at which `heater` turns off
 85 |     t₁ = FT(500)
 86 |     # Plume wavelength scaling
 87 |     x₀ = xmax
 88 |     function energyflux(state, aux, t)
 89 |         x = aux.coord[1]
 90 |         y = aux.coord[2]
 91 |         MSEF = F₀ * (cospi(2 * x / x₀))^2 * (cospi(2 * y / x₀))^2
 92 |         t < t₁ ? MSEF : zero(MSEF)
 93 |     end
 94 | 
 95 |     C_smag = FT(0.23)
 96 | 
 97 |     physics = AtmosPhysics{FT}(
 98 |         param_set;
 99 |         turbulence = SmagorinskyLilly{FT}(C_smag),
100 |         moisture = EquilMoist(),
101 |     )
102 | 
103 |     problem = AtmosProblem(
104 |         boundaryconditions = (
105 |             AtmosBC(physics; energy = PrescribedEnergyFlux(energyflux)),
106 |             AtmosBC(physics;),
107 |         ),
108 |         init_state_prognostic = init_surfacebubble!,
109 |     )
110 |     model = AtmosModel{FT}(
111 |         AtmosLESConfigType,
112 |         physics;
113 |         problem = problem,
114 |         source = (Gravity(),),
115 |     )
116 |     config = ClimateMachine.AtmosLESConfiguration(
117 |         "SurfaceDrivenBubble",
118 |         N,
119 |         resolution,
120 |         xmax,
121 |         ymax,
122 |         zmax,
123 |         param_set,
124 |         init_surfacebubble!,
125 |         model = model,
126 |     )
127 | 
128 |     return config
129 | end
130 | 
131 | function config_diagnostics(
132 |     driver_config,
133 |     xmax::FT,
134 |     ymax::FT,
135 |     zmax::FT,
136 |     resolution,
137 |     interval,
138 | ) where {FT}
139 |     dgngrp = StdDiagnostics.AtmosLESDefault(interval, driver_config.name)
140 |     boundaries = [
141 |         FT(0) FT(0) FT(0)
142 |         xmax ymax zmax
143 |     ]
144 |     interpol = ClimateMachine.InterpolationConfiguration(
145 |         driver_config,
146 |         boundaries,
147 |         resolution,
148 |     )
149 |     pdgngrp = setup_atmos_default_perturbations(
150 |         AtmosLESConfigType(),
151 |         interval,
152 |         driver_config.name,
153 |         interpol = interpol,
154 |     )
155 |     return ClimateMachine.DiagnosticsConfiguration([dgngrp, pdgngrp])
156 | end
157 | 
158 | function main()
159 |     FT = Float64
160 |     # DG polynomial order
161 |     N = 4
162 |     # Domain resolution and size
163 |     Δh = FT(50)
164 |     Δv = FT(50)
165 |     resolution = (Δh, Δh, Δv)
166 |     xmax = FT(2000)
167 |     ymax = FT(2000)
168 |     zmax = FT(2000)
169 |     t0 = FT(0)
170 |     timeend = FT(2000)
171 | 
172 |     driver_config = config_surfacebubble(FT, N, resolution, xmax, ymax, zmax)
173 | 
174 |     ode_solver_type = ClimateMachine.ExplicitSolverType(
175 |         solver_method = LSRK144NiegemannDiehlBusch,
176 |     )
177 | 
178 |     solver_config = ClimateMachine.SolverConfiguration(
179 |         t0,
180 |         timeend,
181 |         driver_config,
182 |         init_on_cpu = true,
183 |         ode_solver_type = ode_solver_type,
184 |     )
185 | 
186 |     dgn_ssecs = (timeend / 2) + 10
187 |     dgn_interval = "$(dgn_ssecs)ssecs"
188 |     dgn_config = config_diagnostics(
189 |         driver_config,
190 |         xmax,
191 |         ymax,
192 |         zmax,
193 |         resolution,
194 |         dgn_interval,
195 |     )
196 | 
197 |     cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do
198 |         Filters.apply!(
199 |             solver_config.Q,
200 |             ("moisture.ρq_tot",),
201 |             solver_config.dg.grid,
202 |             TMARFilter(),
203 |         )
204 |         nothing
205 |     end
206 | 
207 |     result = ClimateMachine.invoke!(
208 |         solver_config;
209 |         diagnostics_config = dgn_config,
210 |         user_callbacks = (cbtmarfilter,),
211 |         check_euclidean_distance = true,
212 |     )
213 | 
214 |     @test isapprox(result, FT(1); atol = 1.5e-3)
215 | end
216 | 
217 | main()
218 | 
--------------------------------------------------------------------------------
/experiments/AtmosLES/taylor_green.jl:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env julia --project
  2 | 
  3 | using ClimateMachine
  4 | ClimateMachine.init(parse_clargs = true)
  5 | 
  6 | using ClimateMachine.Atmos
  7 | using ClimateMachine.Orientations
  8 | using ClimateMachine.ConfigTypes
  9 | using ClimateMachine.Diagnostics
 10 | using ClimateMachine.DGMethods.NumericalFluxes
 11 | using ClimateMachine.GenericCallbacks
 12 | using ClimateMachine.ODESolvers
 13 | using ClimateMachine.Mesh.Filters
 14 | using Thermodynamics
 15 | using ClimateMachine.TurbulenceClosures
 16 | using ClimateMachine.VariableTemplates
 17 | 
 18 | using Distributions
 19 | using StaticArrays
 20 | using Test
 21 | using DocStringExtensions
 22 | using LinearAlgebra
 23 | 
 24 | using CLIMAParameters
 25 | using CLIMAParameters.Planet: R_d, cv_d, cp_d, MSLP, grav, LH_v0
 26 | using CLIMAParameters.Atmos.SubgridScale: C_smag
 27 | struct EarthParameterSet <: AbstractEarthParameterSet end
 28 | const param_set = EarthParameterSet()
 29 | 
 30 | import ClimateMachine.BalanceLaws:
 31 |     vars_state,
 32 |     indefinite_stack_integral!,
 33 |     reverse_indefinite_stack_integral!,
 34 |     integral_load_auxiliary_state!,
 35 |     integral_set_auxiliary_state!,
 36 |     reverse_integral_load_auxiliary_state!,
 37 |     reverse_integral_set_auxiliary_state!
 38 | 
 39 | import ClimateMachine.BalanceLaws: boundary_state!
 40 | import ClimateMachine.Atmos: flux_second_order!
 41 | 
 42 | """
 43 |     Initial Condition for Taylor-Green vortex (LES)
 44 | 
 45 | ## References:
 46 |  - [Taylor1937](@cite)
 47 |  - [Rafei2018](@cite)
 48 |  - [Bull2014](@cite)
 49 | """
 50 | function init_greenvortex!(problem, bl, state, aux, localgeo, t)
 51 |     (x, y, z) = localgeo.coord
 52 | 
 53 |     # Problem float-type
 54 |     FT = eltype(state)
 55 |     param_set = parameter_set(bl)
 56 | 
 57 |     # Unpack constant parameters
 58 |     R_gas::FT = R_d(param_set)
 59 |     c_p::FT = cp_d(param_set)
 60 |     c_v::FT = cv_d(param_set)
 61 |     p0::FT = MSLP(param_set)
 62 |     _grav::FT = grav(param_set)
 63 |     γ::FT = c_p / c_v
 64 | 
 65 |     # Compute perturbed thermodynamic state:
 66 |     ρ = FT(1.178)      # density
 67 |     ρu = SVector(FT(0), FT(0), FT(0))  # momentum
 68 |     #State (prognostic) variable assignment
 69 |     e_pot = FT(0)# potential energy
 70 |     Pinf = 101325
 71 |     Uzero = FT(100)
 72 |     p = Pinf + (ρ * Uzero^2 / 16) * (2 + cos(2 * z)) * (cos(2 * x) + cos(2 * y))
 73 |     u = Uzero * sin(x) * cos(y) * cos(z)
 74 |     v = -Uzero * cos(x) * sin(y) * cos(z)
 75 |     e_kin = 0.5 * (u^2 + v^2)
 76 |     T = p / (ρ * R_gas)
 77 |     e_int = internal_energy(param_set, T, PhasePartition(FT(0)))
 78 |     ρe_tot = ρ * (e_kin + e_pot + e_int)
 79 |     # Assign State Variables
 80 |     state.ρ = ρ
 81 |     state.ρu = SVector(FT(ρ * u), FT(ρ * v), FT(0))
 82 |     state.energy.ρe = ρe_tot
 83 | end
 84 | 
 85 | # Set up AtmosLESConfiguration for the experiment
 86 | function config_greenvortex(
 87 |     ::Type{FT},
 88 |     N,
 89 |     (xmin, xmax, ymin, ymax, zmin, zmax),
 90 |     resolution,
 91 | ) where {FT}
 92 | 
 93 |     _C_smag = FT(C_smag(param_set))
 94 |     physics = AtmosPhysics{FT}(
 95 |         param_set;                          # Parameter set corresponding to earth parameters
 96 |         ref_state = NoReferenceState(),
 97 |         turbulence = Vreman(_C_smag),       # Turbulence closure model
 98 |         moisture = DryModel(),
 99 |     )
100 | 
101 |     model = AtmosModel{FT}(
102 |         AtmosLESConfigType,                 # Flow in a box, requires the AtmosLESConfigType
103 |         physics;                            # Atmos physics
104 |         init_state_prognostic = init_greenvortex!,
105 |         orientation = NoOrientation(),
106 |         source = (),
107 |     )
108 | 
109 |     config = ClimateMachine.AtmosLESConfiguration(
110 |         "GreenVortex",          # Problem title [String]
111 |         N,                      # Polynomial order [Int]
112 |         resolution,             # (Δx, Δy, Δz) effective resolution [m]
113 |         xmax,                   # Domain maximum size [m]
114 |         ymax,                   # Domain maximum size [m]
115 |         zmax,                   # Domain maximum size [m]
116 |         param_set,              # Parameter set.
117 |         init_greenvortex!,      # Function specifying initial condition
118 |         boundary = ((0, 0), (0, 0), (0, 0)),
119 |         periodicity = (true, true, true),
120 |         xmin = xmin,
121 |         ymin = ymin,
122 |         zmin = zmin,
123 |         model = model,                  # Model type
124 |     )
125 |     return config
126 | end
127 | 
128 | # Define the diagnostics configuration for this experiment
129 | function config_diagnostics(
130 |     driver_config,
131 |     (xmin, xmax, ymin, ymax, zmin, zmax),
132 |     resolution,
133 |     tnor,
134 |     titer,
135 |     snor,
136 | )
137 |     ts_dgngrp = setup_atmos_turbulence_stats(
138 |         AtmosLESConfigType(),
139 |         "360steps",
140 |         driver_config.name,
141 |         tnor,
142 |         titer,
143 |     )
144 | 
145 |     boundaries = [
146 |         xmin ymin zmin
147 |         xmax ymax zmax
148 |     ]
149 |     interpol = ClimateMachine.InterpolationConfiguration(
150 |         driver_config,
151 |         boundaries,
152 |         resolution,
153 |     )
154 |     ds_dgngrp = setup_atmos_spectra_diagnostics(
155 |         AtmosLESConfigType(),
156 |         "0.06ssecs",
157 |         driver_config.name,
158 |         interpol = interpol,
159 |         snor,
160 |     )
161 |     me_dgngrp = setup_atmos_mass_energy_loss(
162 |         AtmosLESConfigType(),
163 |         "0.02ssecs",
164 |         driver_config.name,
165 |     )
166 |     return ClimateMachine.DiagnosticsConfiguration([
167 |         ts_dgngrp,
168 |         ds_dgngrp,
169 |         me_dgngrp,
170 |     ],)
171 | end
172 | 
173 | # Entry point
174 | function main()
175 |     FT = Float64
176 |     # DG polynomial order
177 |     N = 4
178 |     # Domain resolution and size
179 |     Ncellsx = 64
180 |     Ncellsy = 64
181 |     Ncellsz = 64
182 |     Δx = FT(2 * pi / Ncellsx)
183 |     Δy = Δx
184 |     Δz = Δx
185 |     resolution = (Δx, Δy, Δz)
186 |     xmin = FT(-pi)
187 |     xmax = FT(pi)
188 |     ymin = FT(-pi)
189 |     ymax = FT(pi)
190 |     zmin = FT(-pi)
191 |     zmax = FT(pi)
192 |     # Simulation time
193 |     t0 = FT(0)
194 |     timeend = FT(0.1)
195 |     CFL = FT(1.8)
196 | 
197 |     driver_config = config_greenvortex(
198 |         FT,
199 |         N,
200 |         (xmin, xmax, ymin, ymax, zmin, zmax),
201 |         resolution,
202 |     )
203 | 
204 |     ode_solver_type = ClimateMachine.ExplicitSolverType(
205 |         solver_method = LSRK144NiegemannDiehlBusch,
206 |     )
207 | 
208 |     solver_config = ClimateMachine.SolverConfiguration(
209 |         t0,
210 |         timeend,
211 |         driver_config,
212 |         ode_solver_type = ode_solver_type,
213 |         init_on_cpu = true,
214 |         Courant_number = CFL,
215 |     )
216 | 
217 |     tnor = FT(100)
218 |     titer = FT(0.01)
219 |     snor = FT(10000.0)
220 |     dgn_config = config_diagnostics(
221 |         driver_config,
222 |         (xmin, xmax, ymin, ymax, zmin, zmax),
223 |         resolution,
224 |         tnor,
225 |         titer,
226 |         snor,
227 |     )
228 | 
229 |     check_cons = (
230 |         ClimateMachine.ConservationCheck("ρ", "100steps", FT(0.0001)),
231 |         ClimateMachine.ConservationCheck("energy.ρe", "100steps", FT(0.0025)),
232 |     )
233 | 
234 |     result = ClimateMachine.invoke!(
235 |         solver_config;
236 |         diagnostics_config = dgn_config,
237 |         check_cons = check_cons,
238 |         check_euclidean_distance = true,
239 |     )
240 | 
241 | end
242 | 
243 | main()
244 | 
--------------------------------------------------------------------------------
/experiments/OceanBoxGCM/homogeneous_box.jl:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env julia --project
 2 | 
 3 | include("simple_box.jl")
 4 | ClimateMachine.init(parse_clargs = true)
 5 | 
 6 | # Float type
 7 | const FT = Float64
 8 | 
 9 | # simulation time
10 | const timestart = FT(0)      # s
11 | const timestep = FT(55)     # s
12 | const timeend = FT(6 * 3600) # s
13 | timespan = (timestart, timeend)
14 | 
15 | # DG polynomial order
16 | const N = Int(4)
17 | 
18 | # Domain resolution
19 | const Nˣ = Int(20)
20 | const Nʸ = Int(20)
21 | const Nᶻ = Int(50)
22 | resolution = (N, Nˣ, Nʸ, Nᶻ)
23 | 
24 | # Domain size
25 | const Lˣ = 4e6    # m
26 | const Lʸ = 4e6    # m
27 | const H = 400   # m
28 | dimensions = (Lˣ, Lʸ, H)
29 | 
30 | BC = (
31 |     OceanBC(Impenetrable(NoSlip()), Insulating()),
32 |     OceanBC(Impenetrable(NoSlip()), Insulating()),
33 |     OceanBC(Penetrable(KinematicStress()), Insulating()),
34 | )
35 | 
36 | run_simple_box(
37 |     "homogeneous_box",
38 |     resolution,
39 |     dimensions,
40 |     timespan,
41 |     HomogeneousBox,
42 |     imex = true,
43 |     Δt = timestep,
44 |     BC = BC,
45 | )
46 | 
--------------------------------------------------------------------------------
/experiments/OceanBoxGCM/ocean_gyre.jl:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env julia --project
 2 | 
 3 | include("simple_box.jl")
 4 | ClimateMachine.init(parse_clargs = true)
 5 | 
 6 | # Float type
 7 | const FT = Float64
 8 | 
 9 | # simulation time
10 | const timestart = FT(0)      # s
11 | const timestep = FT(240)     # s
12 | const timeend = FT(86400) # s
13 | timespan = (timestart, timeend)
14 | 
15 | # DG polynomial order
16 | const N = Int(4)
17 | 
18 | # Domain resolution
19 | const Nˣ = Int(20)
20 | const Nʸ = Int(20)
21 | const Nᶻ = Int(20)
22 | resolution = (N, Nˣ, Nʸ, Nᶻ)
23 | 
24 | # Domain size
25 | const Lˣ = 4e6    # m
26 | const Lʸ = 4e6    # m
27 | const H = 1000   # m
28 | dimensions = (Lˣ, Lʸ, H)
29 | 
30 | BC = (
31 |     OceanBC(Impenetrable(NoSlip()), Insulating()),
32 |     OceanBC(Impenetrable(NoSlip()), Insulating()),
33 |     OceanBC(Penetrable(KinematicStress()), TemperatureFlux()),
34 | )
35 | 
36 | run_simple_box(
37 |     "ocean_gyre",
38 |     resolution,
39 |     dimensions,
40 |     timespan,
41 |     OceanGyre,
42 |     imex = false,
43 |     Δt = timestep,
44 |     BC = BC,
45 | )
46 | 
--------------------------------------------------------------------------------
/experiments/OceanBoxGCM/simple_box.jl:
--------------------------------------------------------------------------------
  1 | using Test
  2 | using ClimateMachine
  3 | using ClimateMachine.GenericCallbacks
  4 | using ClimateMachine.ODESolvers
  5 | using ClimateMachine.Mesh.Filters
  6 | using ClimateMachine.VariableTemplates
  7 | using ClimateMachine.Mesh.Grids: polynomialorders
  8 | using ClimateMachine.BalanceLaws
  9 | using ClimateMachine.Ocean
 10 | using ClimateMachine.Ocean.HydrostaticBoussinesq
 11 | using ClimateMachine.Ocean.OceanProblems
 12 | 
 13 | using CLIMAParameters
 14 | using CLIMAParameters.Planet: grav
 15 | struct EarthParameterSet <: AbstractEarthParameterSet end
 16 | const param_set = EarthParameterSet()
 17 | 
 18 | function config_simple_box(name, resolution, dimensions, problem; BC = nothing)
 19 |     if BC == nothing
 20 |         problem = problem{FT}(dimensions..., τₒ = 0.2)
 21 |     else
 22 |         problem = problem{FT}(dimensions...; BC = BC)
 23 |     end
 24 | 
 25 |     _grav::FT = grav(param_set)
 26 |     cʰ = sqrt(_grav * problem.H) # m/s
 27 |     model = HydrostaticBoussinesqModel{FT}(param_set, problem, cʰ = cʰ)
 28 | 
 29 |     N, Nˣ, Nʸ, Nᶻ = resolution
 30 |     resolution = (Nˣ, Nʸ, Nᶻ)
 31 | 
 32 |     solver_type = ExplicitSolverType(solver_method = LSRK144NiegemannDiehlBusch)
 33 |     config = ClimateMachine.OceanBoxGCMConfiguration(
 34 |         name,
 35 |         N,
 36 |         resolution,
 37 |         param_set,
 38 |         model,
 39 |     )
 40 | 
 41 |     return config, solver_type
 42 | end
 43 | 
 44 | function run_simple_box(
 45 |     name,
 46 |     resolution,
 47 |     dimensions,
 48 |     timespan,
 49 |     problem;
 50 |     imex::Bool = false,
 51 |     BC = nothing,
 52 |     Δt = nothing,
 53 |     refDat = (),
 54 | )
 55 |     if imex
 56 |         ode_solver_type =
 57 |             ClimateMachine.IMEXSolverType(implicit_model = LinearHBModel)
 58 |         Courant_number = 0.1
 59 |     else
 60 |         ode_solver_type = ClimateMachine.ExplicitSolverType(
 61 |             solver_method = LSRK144NiegemannDiehlBusch,
 62 |         )
 63 |         Courant_number = 0.4
 64 |     end
 65 | 
 66 |     driver_config, solver_type_driver_config =
 67 |         config_simple_box(name, resolution, dimensions, problem; BC = BC)
 68 | 
 69 |     grid = driver_config.grid
 70 |     # XXX: Needs updating for multiple polynomial orders
 71 |     N = polynomialorders(grid)
 72 |     # Currently only support single polynomial order
 73 |     @assert all(N[1] .== N)
 74 |     N = N[1]
 75 |     vert_filter = CutoffFilter(grid, N - 1)
 76 |     exp_filter = ExponentialFilter(grid, 1, 8)
 77 |     modeldata = (vert_filter = vert_filter, exp_filter = exp_filter)
 78 | 
 79 |     timestart, timeend = timespan
 80 |     solver_config = ClimateMachine.SolverConfiguration(
 81 |         timestart,
 82 |         timeend,
 83 |         driver_config,
 84 |         init_on_cpu = true,
 85 |         ode_solver_type = ode_solver_type,
 86 |         ode_dt = Δt,
 87 |         modeldata = modeldata,
 88 |         Courant_number = Courant_number,
 89 |     )
 90 | 
 91 |     ## Create a callback to report state statistics for main MPIStateArrays
 92 |     ## every ntFreq timesteps.
 93 |     nt_freq = floor(Int, 1 // 10 * solver_config.timeend / solver_config.dt)
 94 |     cb = ClimateMachine.StateCheck.sccreate(
 95 |         [(solver_config.Q, "Q"), (solver_config.dg.state_auxiliary, "s_aux")],
 96 |         nt_freq;
 97 |         prec = 12,
 98 |     )
 99 | 
100 |     result = ClimateMachine.invoke!(solver_config; user_callbacks = [cb])
101 | 
102 |     ## Check results against reference if present
103 |     ClimateMachine.StateCheck.scprintref(cb)
104 |     if length(refDat) > 0
105 |         @test ClimateMachine.StateCheck.scdocheck(cb, refDat)
106 |     end
107 | end
108 | 
--------------------------------------------------------------------------------
/experiments/OceanSplitExplicit/simple_box.jl:
--------------------------------------------------------------------------------
  1 | using Test
  2 | using MPI
  3 | 
  4 | using ClimateMachine
  5 | using ClimateMachine.GenericCallbacks
  6 | using ClimateMachine.ODESolvers
  7 | 
  8 | using ClimateMachine.Mesh.Filters
  9 | using ClimateMachine.Mesh.Grids
 10 | using ClimateMachine.Mesh.Topologies
 11 | 
 12 | using ClimateMachine.DGMethods
 13 | using ClimateMachine.DGMethods.NumericalFluxes
 14 | using ClimateMachine.SystemSolvers
 15 | 
 16 | using ClimateMachine.VariableTemplates
 17 | using ClimateMachine.BalanceLaws
 18 | 
 19 | using ClimateMachine.Ocean
 20 | using ClimateMachine.Ocean.SplitExplicit01
 21 | using ClimateMachine.Ocean.OceanProblems
 22 | 
 23 | using ClimateMachine:
 24 |     ConfigSpecificInfo, DriverConfiguration, OceanSplitExplicitConfigType
 25 | 
 26 | using CLIMAParameters
 27 | using CLIMAParameters.Planet: grav
 28 | 
 29 | struct EarthParameterSet <: AbstractEarthParameterSet end
 30 | const param_set = EarthParameterSet()
 31 | 
 32 | struct OceanSplitExplicitSpecificInfo <: ConfigSpecificInfo
 33 |     model_2D::BalanceLaw
 34 |     grid_2D::DiscontinuousSpectralElementGrid
 35 |     dg::DGModel
 36 | end
 37 | 
 38 | function OceanSplitExplicitConfiguration(
 39 |     name::String,
 40 |     N::Union{Int, NTuple{2, Int}},
 41 |     (Nˣ, Nʸ, Nᶻ)::NTuple{3, Int},
 42 |     param_set::AbstractParameterSet,
 43 |     model_3D;
 44 |     FT = Float64,
 45 |     array_type = ClimateMachine.array_type(),
 46 |     solver_type = SplitExplicitSolverType{FT}(90.0 * 60.0, 240.0),
 47 |     mpicomm = MPI.COMM_WORLD,
 48 |     numerical_flux_first_order = RusanovNumericalFlux(),
 49 |     numerical_flux_second_order = CentralNumericalFluxSecondOrder(),
 50 |     numerical_flux_gradient = CentralNumericalFluxGradient(),
 51 |     fv_reconstruction = nothing,
 52 |     periodicity = (false, false, false),
 53 |     boundary = ((1, 1), (1, 1), (2, 3)),
 54 | )
 55 | 
 56 |     (polyorder_horz, polyorder_vert) = isa(N, Int) ? (N, N) : N
 57 | 
 58 |     xrange = range(FT(0); length = Nˣ + 1, stop = model_3D.problem.Lˣ)
 59 |     yrange = range(FT(0); length = Nʸ + 1, stop = model_3D.problem.Lʸ)
 60 |     zrange = range(FT(-model_3D.problem.H); length = Nᶻ + 1, stop = 0)
 61 | 
 62 |     brickrange_2D = (xrange, yrange)
 63 |     brickrange_3D = (xrange, yrange, zrange)
 64 | 
 65 |     topology_2D = BrickTopology(
 66 |         mpicomm,
 67 |         brickrange_2D;
 68 |         periodicity = (periodicity[1], periodicity[2]),
 69 |         boundary = (boundary[1], boundary[2]),
 70 |     )
 71 |     topology_3D = StackedBrickTopology(
 72 |         mpicomm,
 73 |         brickrange_3D;
 74 |         periodicity = periodicity,
 75 |         boundary = boundary,
 76 |     )
 77 | 
 78 |     grid_2D = DiscontinuousSpectralElementGrid(
 79 |         topology_2D,
 80 |         FloatType = FT,
 81 |         DeviceArray = array_type,
 82 |         polynomialorder = polyorder_horz,
 83 |     )
 84 |     grid_3D = DiscontinuousSpectralElementGrid(
 85 |         topology_3D,
 86 |         FloatType = FT,
 87 |         DeviceArray = array_type,
 88 |         polynomialorder = (polyorder_horz, polyorder_vert),
 89 |     )
 90 | 
 91 |     model_2D = BarotropicModel(model_3D)
 92 | 
 93 |     dg_2D = DGModel(
 94 |         model_2D,
 95 |         grid_2D,
 96 |         numerical_flux_first_order,
 97 |         numerical_flux_second_order,
 98 |         numerical_flux_gradient,
 99 |     )
100 | 
101 |     Q_2D = init_ode_state(dg_2D, FT(0); init_on_cpu = true)
102 | 
103 |     vert_filter = CutoffFilter(grid_3D, polyorder_vert - 1)
104 |     exp_filter = ExponentialFilter(grid_3D, 1, 8)
105 | 
106 |     flowintegral_dg = DGModel(
107 |         ClimateMachine.Ocean.SplitExplicit01.FlowIntegralModel(model_3D),
108 |         grid_3D,
109 |         numerical_flux_first_order,
110 |         numerical_flux_second_order,
111 |         numerical_flux_gradient,
112 |     )
113 | 
114 |     tendency_dg = DGModel(
115 |         ClimateMachine.Ocean.SplitExplicit01.TendencyIntegralModel(model_3D),
116 |         grid_3D,
117 |         numerical_flux_first_order,
118 |         numerical_flux_second_order,
119 |         numerical_flux_gradient,
120 |     )
121 | 
122 |     conti3d_dg = DGModel(
123 |         ClimateMachine.Ocean.SplitExplicit01.Continuity3dModel(model_3D),
124 |         grid_3D,
125 |         numerical_flux_first_order,
126 |         numerical_flux_second_order,
127 |         numerical_flux_gradient,
128 |     )
129 |     conti3d_Q = init_ode_state(conti3d_dg, FT(0); init_on_cpu = true)
130 | 
131 |     ivdc_dg = DGModel(
132 |         ClimateMachine.Ocean.SplitExplicit01.IVDCModel(model_3D),
133 |         grid_3D,
134 |         numerical_flux_first_order,
135 |         numerical_flux_second_order,
136 |         numerical_flux_gradient;
137 |         direction = VerticalDirection(),
138 |     )
139 |     # Not sure this is needed since we set values later,
140 |     # but we'll do it just in case!
141 |     ivdc_Q = init_ode_state(ivdc_dg, FT(0); init_on_cpu = true)
142 |     ivdc_RHS = init_ode_state(ivdc_dg, FT(0); init_on_cpu = true)
143 | 
144 |     ivdc_bgm_solver = BatchedGeneralizedMinimalResidual(
145 |         ivdc_dg,
146 |         ivdc_Q;
147 |         max_subspace_size = 10,
148 |     )
149 | 
150 |     modeldata = (
151 |         dg_2D = dg_2D,
152 |         Q_2D = Q_2D,
153 |         vert_filter = vert_filter,
154 |         exp_filter = exp_filter,
155 |         flowintegral_dg = flowintegral_dg,
156 |         tendency_dg = tendency_dg,
157 |         conti3d_dg = conti3d_dg,
158 |         conti3d_Q = conti3d_Q,
159 |         ivdc_dg = ivdc_dg,
160 |         ivdc_Q = ivdc_Q,
161 |         ivdc_RHS = ivdc_RHS,
162 |         ivdc_bgm_solver = ivdc_bgm_solver,
163 |     )
164 | 
165 |     dg_3D = DGModel(
166 |         model_3D,
167 |         grid_3D,
168 |         numerical_flux_first_order,
169 |         numerical_flux_second_order,
170 |         numerical_flux_gradient;
171 |         modeldata = modeldata,
172 |     )
173 | 
174 | 
175 |     return DriverConfiguration(
176 |         OceanSplitExplicitConfigType(),
177 |         name,
178 |         (polyorder_horz, polyorder_vert),
179 |         FT,
180 |         array_type,
181 |         param_set,
182 |         model_3D,
183 |         mpicomm,
184 |         grid_3D,
185 |         numerical_flux_first_order,
186 |         numerical_flux_second_order,
187 |         numerical_flux_gradient,
188 |         fv_reconstruction,
189 |         nothing, # filter
190 |         OceanSplitExplicitSpecificInfo(model_2D, grid_2D, dg_3D),
191 |     )
192 | end
193 | 
194 | 
195 | function config_simple_box(
196 |     name,
197 |     resolution,
198 |     dimensions,
199 |     boundary_conditions;
200 |     dt_slow = 90.0 * 60.0,
201 |     dt_fast = 240.0,
202 | )
203 | 
204 |     problem = OceanGyre{FT}(
205 |         dimensions...;
206 |         τₒ = 0.1,
207 |         λʳ = 10 // 86400,
208 |         θᴱ = 10,
209 |         BC = boundary_conditions,
210 |     )
211 | 
212 |     add_fast_substeps = 2
213 |     numImplSteps = 5
214 |     numImplSteps > 0 ? ivdc_dt = dt_slow / FT(numImplSteps) : ivdc_dt = dt_slow
215 |     model_3D = OceanModel{FT}(
216 |         param_set,
217 |         problem;
218 |         cʰ = 1,
219 |         κᶜ = FT(0.1),
220 |         add_fast_substeps = add_fast_substeps,
221 |         numImplSteps = numImplSteps,
222 |         ivdc_dt = ivdc_dt,
223 |     )
224 | 
225 |     N, Nˣ, Nʸ, Nᶻ = resolution
226 |     resolution = (Nˣ, Nʸ, Nᶻ)
227 | 
228 |     config = OceanSplitExplicitConfiguration(
229 |         name,
230 |         N,
231 |         resolution,
232 |         param_set,
233 |         model_3D,
234 |     )
235 |     solver_type = SplitExplicitSolverType{FT}(dt_slow, dt_fast)
236 | 
237 |     return config, solver_type
238 | end
239 | 
240 | function run_simple_box(driver_config, timespan, dt_slow; refDat = ())
241 | 
242 |     timestart, timeend = timespan
243 |     solver_config = ClimateMachine.SolverConfiguration(
244 |         timestart,
245 |         timeend,
246 |         driver_config,
247 |         init_on_cpu = true,
248 |         ode_dt = dt_slow,
249 |     )
250 | 
251 |     ## Create a callback to report state statistics for main MPIStateArrays
252 |     ## every ntFreq timesteps.
253 |     nt_freq = 1 # floor(Int, 1 // 10 * solver_config.timeend / solver_config.dt)
254 |     cb = ClimateMachine.StateCheck.sccreate(
255 |         [
256 |             (solver_config.Q, "oce Q_3D"),
257 |             (solver_config.dg.state_auxiliary, "oce aux"),
258 |             (solver_config.dg.modeldata.Q_2D, "baro Q_2D"),
259 |             (solver_config.dg.modeldata.dg_2D.state_auxiliary, "baro aux"),
260 |         ],
261 |         nt_freq;
262 |         prec = 12,
263 |     )
264 | 
265 |     result = ClimateMachine.invoke!(solver_config; user_callbacks = [cb])
266 | 
267 |     ## Check results against reference if present
268 |     ClimateMachine.StateCheck.scprintref(cb)
269 |     if length(refDat) > 0
270 |         @test ClimateMachine.StateCheck.scdocheck(cb, refDat)
271 |     end
272 | end
273 | 
--------------------------------------------------------------------------------
/experiments/TestCase/baroclinic_wave.jl:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env julia --project
  2 | 
  3 | using ArgParse
  4 | using LinearAlgebra
  5 | using StaticArrays
  6 | using Test
  7 | 
  8 | using ClimateMachine
  9 | using ClimateMachine.Atmos
 10 | using ClimateMachine.Orientations
 11 | using ClimateMachine.ConfigTypes
 12 | using ClimateMachine.Diagnostics
 13 | using ClimateMachine.GenericCallbacks
 14 | using ClimateMachine.ODESolvers
 15 | using ClimateMachine.TurbulenceClosures
 16 | using ClimateMachine.SystemSolvers: ManyColumnLU
 17 | using ClimateMachine.Mesh.Filters
 18 | using ClimateMachine.Mesh.Grids
 19 | using Thermodynamics.TemperatureProfiles
 20 | using Thermodynamics:
 21 |     air_density, air_temperature, total_energy, internal_energy, PhasePartition
 22 | using ClimateMachine.TurbulenceClosures
 23 | using ClimateMachine.VariableTemplates
 24 | using ClimateMachine.Spectra: compute_gaussian!
 25 | 
 26 | using CLIMAParameters
 27 | using CLIMAParameters.Planet: MSLP, R_d, day, grav, Omega, planet_radius
 28 | struct EarthParameterSet <: AbstractEarthParameterSet end
 29 | const param_set = EarthParameterSet()
 30 | 
 31 | function init_baroclinic_wave!(problem, bl, state, aux, localgeo, t)
 32 |     FT = eltype(state)
 33 | 
 34 |     # parameters
 35 |     param_set = parameter_set(bl)
 36 |     _grav::FT = grav(param_set)
 37 |     _R_d::FT = R_d(param_set)
 38 |     _Ω::FT = Omega(param_set)
 39 |     _a::FT = planet_radius(param_set)
 40 |     _p_0::FT = MSLP(param_set)
 41 | 
 42 |     k::FT = 3
 43 |     T_E::FT = 310
 44 |     T_P::FT = 240
 45 |     T_0::FT = 0.5 * (T_E + T_P)
 46 |     Γ::FT = 0.005
 47 |     A::FT = 1 / Γ
 48 |     B::FT = (T_0 - T_P) / T_0 / T_P
 49 |     C::FT = 0.5 * (k + 2) * (T_E - T_P) / T_E / T_P
 50 |     b::FT = 2
 51 |     H::FT = _R_d * T_0 / _grav
 52 |     z_t::FT = 15e3
 53 |     λ_c::FT = π / 9
 54 |     φ_c::FT = 2 * π / 9
 55 |     d_0::FT = _a / 6
 56 |     V_p::FT = 1
 57 |     M_v::FT = 0.608
 58 |     p_w::FT = 34e3                 # Pressure width parameter for specific humidity
 59 |     η_crit::FT = p_w / _p_0        # Critical pressure coordinate
 60 |     q_0::FT = 0.018                # Maximum specific humidity (default: 0.018)
 61 |     q_t::FT = 1e-12                # Specific humidity above artificial tropopause
 62 |     φ_w::FT = 2π / 9               # Specific humidity latitude wind parameter
 63 | 
 64 |     # grid
 65 |     φ = latitude(bl.orientation, aux)
 66 |     λ = longitude(bl.orientation, aux)
 67 |     z = altitude(bl.orientation, param_set, aux)
 68 |     r::FT = z + _a
 69 |     γ::FT = 1 # set to 0 for shallow-atmosphere case and to 1 for deep atmosphere case
 70 | 
 71 |     # convenience functions for temperature and pressure
 72 |     τ_z_1::FT = exp(Γ * z / T_0)
 73 |     τ_z_2::FT = 1 - 2 * (z / b / H)^2
 74 |     τ_z_3::FT = exp(-(z / b / H)^2)
 75 |     τ_1::FT = 1 / T_0 * τ_z_1 + B * τ_z_2 * τ_z_3
 76 |     τ_2::FT = C * τ_z_2 * τ_z_3
 77 |     τ_int_1::FT = A * (τ_z_1 - 1) + B * z * τ_z_3
 78 |     τ_int_2::FT = C * z * τ_z_3
 79 |     I_T::FT =
 80 |         (cos(φ) * (1 + γ * z / _a))^k -
 81 |         k / (k + 2) * (cos(φ) * (1 + γ * z / _a))^(k + 2)
 82 | 
 83 |     # base state virtual temperature, pressure, specific humidity, density
 84 |     T_v::FT = (τ_1 - τ_2 * I_T)^(-1)
 85 |     p::FT = _p_0 * exp(-_grav / _R_d * (τ_int_1 - τ_int_2 * I_T))
 86 | 
 87 |     # base state velocity
 88 |     U::FT =
 89 |         _grav * k / _a *
 90 |         τ_int_2 *
 91 |         T_v *
 92 |         (
 93 |             (cos(φ) * (1 + γ * z / _a))^(k - 1) -
 94 |             (cos(φ) * (1 + γ * z / _a))^(k + 1)
 95 |         )
 96 |     u_ref::FT =
 97 |         -_Ω * (_a + γ * z) * cos(φ) +
 98 |         sqrt((_Ω * (_a + γ * z) * cos(φ))^2 + (_a + γ * z) * cos(φ) * U)
 99 |     v_ref::FT = 0
100 |     w_ref::FT = 0
101 | 
102 |     # velocity perturbations
103 |     F_z::FT = 1 - 3 * (z / z_t)^2 + 2 * (z / z_t)^3
104 |     if z > z_t
105 |         F_z = FT(0)
106 |     end
107 |     d::FT = _a * acos(sin(φ) * sin(φ_c) + cos(φ) * cos(φ_c) * cos(λ - λ_c))
108 |     c3::FT = cos(π * d / 2 / d_0)^3
109 |     s1::FT = sin(π * d / 2 / d_0)
110 |     if 0 < d < d_0 && d != FT(_a * π)
111 |         u′::FT =
112 |             -16 * V_p / 3 / sqrt(3) *
113 |             F_z *
114 |             c3 *
115 |             s1 *
116 |             (-sin(φ_c) * cos(φ) + cos(φ_c) * sin(φ) * cos(λ - λ_c)) /
117 |             sin(d / _a)
118 |         v′::FT =
119 |             16 * V_p / 3 / sqrt(3) * F_z * c3 * s1 * cos(φ_c) * sin(λ - λ_c) /
120 |             sin(d / _a)
121 |     else
122 |         u′ = FT(0)
123 |         v′ = FT(0)
124 |     end
125 |     w′::FT = 0
126 |     u_sphere = SVector{3, FT}(u_ref + u′, v_ref + v′, w_ref + w′)
127 |     u_cart = sphr_to_cart_vec(bl.orientation, u_sphere, aux)
128 | 
129 |     if moisture_model(bl) isa DryModel
130 |         q_tot = FT(0)
131 |     else
132 |         ## Compute moisture profile
133 |         ## Pressure coordinate η
134 |         ## η_crit = p_t / p_w ; p_t = 10000 hPa, p_w = 340 hPa
135 |         η = p / _p_0
136 |         if η > η_crit
137 |             q_tot = q_0 * exp(-(φ / φ_w)^4) * exp(-((η - 1) * _p_0 / p_w)^2)
138 |         else
139 |             q_tot = q_t
140 |         end
141 |     end
142 |     phase_partition = PhasePartition(q_tot)
143 | 
144 |     ## temperature & density
145 |     T::FT = T_v / (1 + M_v * q_tot)
146 |     ρ::FT = air_density(param_set, T, p, phase_partition)
147 | 
148 |     ## potential & kinetic energy
149 |     e_pot::FT = gravitational_potential(bl.orientation, aux)
150 |     e_kin::FT = 0.5 * u_cart' * u_cart
151 |     e_tot::FT = total_energy(param_set, e_kin, e_pot, T, phase_partition)
152 | 
153 |     ## Assign state variables
154 |     state.ρ = ρ
155 |     state.ρu = ρ * u_cart
156 |     state.energy.ρe = ρ * e_tot
157 | 
158 |     if !(moisture_model(bl) isa DryModel)
159 |         state.moisture.ρq_tot = ρ * q_tot
160 |     end
161 | 
162 |     nothing
163 | end
164 | 
165 | function config_baroclinic_wave(FT, poly_order, resolution, with_moisture)
166 |     # Set up a reference state for linearization of equations
167 |     temp_profile_ref =
168 |         DecayingTemperatureProfile{FT}(param_set, FT(290), FT(220), FT(8e3))
169 |     ref_state = HydrostaticState(temp_profile_ref)
170 | 
171 |     # Set up the atmosphere model
172 |     exp_name = "BaroclinicWave"
173 |     domain_height::FT = 30e3 # distance between surface and top of atmosphere (m)
174 |     if with_moisture
175 |         hyperdiffusion = EquilMoistBiharmonic(FT(8 * 3600))
176 |         moisture = EquilMoist()
177 |         source = (Gravity(), Coriolis(), RemovePrecipitation(true)) # precipitation is default to NoPrecipitation() as 0M microphysics
178 |     else
179 |         hyperdiffusion = DryBiharmonic(FT(8 * 3600))
180 |         moisture = DryModel()
181 |         source = (Gravity(), Coriolis())
182 |     end
183 |     physics = AtmosPhysics{FT}(
184 |         param_set;
185 |         ref_state = ref_state,
186 |         turbulence = ConstantKinematicViscosity(FT(0)),
187 |         hyperdiffusion = hyperdiffusion,
188 |         moisture = moisture,
189 |     )
190 |     model = AtmosModel{FT}(
191 |         AtmosGCMConfigType,
192 |         physics;
193 |         init_state_prognostic = init_baroclinic_wave!,
194 |         source = source,
195 |     )
196 | 
197 |     config = ClimateMachine.AtmosGCMConfiguration(
198 |         exp_name,
199 |         poly_order,
200 |         resolution,
201 |         domain_height,
202 |         param_set,
203 |         init_baroclinic_wave!;
204 |         model = model,
205 |     )
206 | 
207 |     return config
208 | end
209 | 
210 | function main()
211 |     # add a command line argument to specify whether to use a moist setup
212 |     # TODO: this will move to the future namelist functionality
213 |     bw_args = ArgParseSettings(autofix_names = true)
214 |     add_arg_group!(bw_args, "BaroclinicWave")
215 |     @add_arg_table! bw_args begin
216 |         "--with-moisture"
217 |         help = "use a moist setup"
218 |         action = :store_const
219 |         constant = true
220 |         default = false
221 |     end
222 | 
223 |     cl_args = ClimateMachine.init(parse_clargs = true, custom_clargs = bw_args)
224 |     with_moisture = cl_args["with_moisture"]
225 | 
226 |     # Driver configuration parameters
227 |     FT = Float64                             # floating type precision
228 |     poly_order = (5, 6)                      # discontinuous Galerkin polynomial order
229 |     n_horz = 8                               # horizontal element number
230 |     n_vert = 3                               # vertical element number
231 |     n_days::FT = 1
232 |     timestart::FT = 0                        # start time (s)
233 |     timeend::FT = n_days * day(param_set)    # end time (s)
234 | 
235 |     # Set up driver configuration
236 |     driver_config =
237 |         config_baroclinic_wave(FT, poly_order, (n_horz, n_vert), with_moisture)
238 | 
239 |     # Set up experiment
240 |     ode_solver_type = ClimateMachine.IMEXSolverType(
241 |         implicit_model = AtmosAcousticGravityLinearModel,
242 |         implicit_solver = ManyColumnLU,
243 |         solver_method = ARK2GiraldoKellyConstantinescu,
244 |         split_explicit_implicit = true,
245 |         discrete_splitting = false,
246 |     )
247 | 
248 |     CFL = FT(0.1) # target acoustic CFL number
249 | 
250 |     # time step is computed such that the horizontal acoustic Courant number is CFL
251 |     solver_config = ClimateMachine.SolverConfiguration(
252 |         timestart,
253 |         timeend,
254 |         driver_config,
255 |         Courant_number = CFL,
256 |         ode_solver_type = ode_solver_type,
257 |         CFL_direction = HorizontalDirection(),
258 |         diffdir = HorizontalDirection(),
259 |     )
260 | 
261 |     # Set up diagnostics
262 |     dgn_config = config_diagnostics(FT, driver_config)
263 | 
264 |     # Set up user-defined callbacks
265 |     filterorder = 20
266 |     filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)
267 |     cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do
268 |         Filters.apply!(
269 |             solver_config.Q,
270 |             AtmosFilterPerturbations(driver_config.bl),
271 |             solver_config.dg.grid,
272 |             filter,
273 |             state_auxiliary = solver_config.dg.state_auxiliary,
274 |         )
275 |         nothing
276 |     end
277 | 
278 |     cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do
279 |         Filters.apply!(
280 |             solver_config.Q,
281 |             ("moisture.ρq_tot",),
282 |             solver_config.dg.grid,
283 |             TMARFilter(),
284 |         )
285 |         nothing
286 |     end
287 | 
288 |     # Run the model
289 |     result = ClimateMachine.invoke!(
290 |         solver_config;
291 |         diagnostics_config = dgn_config,
292 |         user_callbacks = (cbfilter,),
293 |         #user_callbacks = (cbtmarfilter, cbfilter),
294 |         check_euclidean_distance = true,
295 |     )
296 | end
297 | 
298 | function config_diagnostics(FT, driver_config)
299 |     interval = "12shours" # chosen to allow diagnostics every 12 simulated hours
300 | 
301 |     _planet_radius = FT(planet_radius(param_set))
302 | 
303 |     info = driver_config.config_info
304 | 
305 |     # Setup diagnostic grid(s)
306 |     nlats = 128
307 | 
308 |     sinθ, wts = compute_gaussian!(nlats)
309 |     lats = asin.(sinθ) .* 180 / π
310 |     lons = 180.0 ./ nlats * collect(FT, 1:1:(2nlats))[:] .- 180.0
311 | 
312 |     boundaries = [
313 |         FT(lats[1]) FT(lons[1]) _planet_radius
314 |         FT(lats[end]) FT(lons[end]) FT(_planet_radius + info.domain_height)
315 |     ]
316 | 
317 |     lvls = collect(range(
318 |         boundaries[1, 3],
319 |         boundaries[2, 3],
320 |         step = FT(1000), # in m
321 |     ))
322 | 
323 |     #boundaries = [
324 |     #    FT(-90.0) FT(-180.0) _planet_radius
325 |     #    FT(90.0) FT(180.0) FT(_planet_radius + info.domain_height)
326 |     #]
327 | 
328 |     #resolution = (FT(2), FT(2), FT(1000)) # in (deg, deg, m)
329 | 
330 |     interpol = ClimateMachine.InterpolationConfiguration(
331 |         driver_config,
332 |         boundaries;
333 |         axes = [lats, lons, lvls],
334 |     )
335 | 
336 |     dgngrp = setup_atmos_default_diagnostics(
337 |         AtmosGCMConfigType(),
338 |         interval,
339 |         driver_config.name,
340 |         interpol = interpol,
341 |     )
342 | 
343 |     ds_dgngrp = setup_atmos_spectra_diagnostics(
344 |         AtmosGCMConfigType(),
345 |         interval,
346 |         driver_config.name,
347 |         interpol = interpol,
348 |     )
349 | 
350 |     return ClimateMachine.DiagnosticsConfiguration([dgngrp, ds_dgngrp])
351 | end
352 | 
353 | main()
354 | 
--------------------------------------------------------------------------------
/experiments/TestCase/baroclinic_wave_fvm.jl:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env julia --project
  2 | 
  3 | using ArgParse
  4 | using LinearAlgebra
  5 | using StaticArrays
  6 | using Test
  7 | 
  8 | using ClimateMachine
  9 | using ClimateMachine.Atmos
 10 | using ClimateMachine.Orientations
 11 | using ClimateMachine.ConfigTypes
 12 | using ClimateMachine.Diagnostics
 13 | using ClimateMachine.GenericCallbacks
 14 | using ClimateMachine.ODESolvers
 15 | using ClimateMachine.TurbulenceClosures
 16 | using ClimateMachine.SystemSolvers: ManyColumnLU
 17 | using ClimateMachine.Mesh.Filters
 18 | using ClimateMachine.Mesh.Grids
 19 | using Thermodynamics.TemperatureProfiles
 20 | using Thermodynamics:
 21 |     air_density, air_temperature, total_energy, internal_energy, PhasePartition
 22 | using ClimateMachine.TurbulenceClosures
 23 | using ClimateMachine.VariableTemplates
 24 | import ClimateMachine.DGMethods.FVReconstructions: FVLinear
 25 | 
 26 | using CLIMAParameters
 27 | using CLIMAParameters.Planet: MSLP, R_d, day, grav, Omega, planet_radius
 28 | struct EarthParameterSet <: AbstractEarthParameterSet end
 29 | const param_set = EarthParameterSet()
 30 | 
 31 | function init_baroclinic_wave!(problem, bl, state, aux, localgeo, t)
 32 |     FT = eltype(state)
 33 | 
 34 |     # parameters
 35 |     param_set = parameter_set(bl)
 36 |     _grav::FT = grav(param_set)
 37 |     _R_d::FT = R_d(param_set)
 38 |     _Ω::FT = Omega(param_set)
 39 |     _a::FT = planet_radius(param_set)
 40 |     _p_0::FT = MSLP(param_set)
 41 | 
 42 |     k::FT = 3
 43 |     T_E::FT = 310
 44 |     T_P::FT = 240
 45 |     T_0::FT = 0.5 * (T_E + T_P)
 46 |     Γ::FT = 0.005
 47 |     A::FT = 1 / Γ
 48 |     B::FT = (T_0 - T_P) / T_0 / T_P
 49 |     C::FT = 0.5 * (k + 2) * (T_E - T_P) / T_E / T_P
 50 |     b::FT = 2
 51 |     H::FT = _R_d * T_0 / _grav
 52 |     z_t::FT = 15e3
 53 |     λ_c::FT = π / 9
 54 |     φ_c::FT = 2 * π / 9
 55 |     d_0::FT = _a / 6
 56 |     V_p::FT = 1
 57 |     M_v::FT = 0.608
 58 |     p_w::FT = 34e3                 # Pressure width parameter for specific humidity
 59 |     η_crit::FT = p_w / _p_0        # Critical pressure coordinate
 60 |     q_0::FT = 0.018                # Maximum specific humidity (default: 0.018)
 61 |     q_t::FT = 1e-12                # Specific humidity above artificial tropopause
 62 |     φ_w::FT = 2π / 9               # Specific humidity latitude wind parameter
 63 | 
 64 |     # grid
 65 |     φ = latitude(bl.orientation, aux)
 66 |     λ = longitude(bl.orientation, aux)
 67 |     z = altitude(bl.orientation, param_set, aux)
 68 |     r::FT = z + _a
 69 |     γ::FT = 1 # set to 0 for shallow-atmosphere case and to 1 for deep atmosphere case
 70 | 
 71 |     # convenience functions for temperature and pressure
 72 |     τ_z_1::FT = exp(Γ * z / T_0)
 73 |     τ_z_2::FT = 1 - 2 * (z / b / H)^2
 74 |     τ_z_3::FT = exp(-(z / b / H)^2)
 75 |     τ_1::FT = 1 / T_0 * τ_z_1 + B * τ_z_2 * τ_z_3
 76 |     τ_2::FT = C * τ_z_2 * τ_z_3
 77 |     τ_int_1::FT = A * (τ_z_1 - 1) + B * z * τ_z_3
 78 |     τ_int_2::FT = C * z * τ_z_3
 79 |     I_T::FT =
 80 |         (cos(φ) * (1 + γ * z / _a))^k -
 81 |         k / (k + 2) * (cos(φ) * (1 + γ * z / _a))^(k + 2)
 82 | 
 83 |     # base state virtual temperature, pressure, specific humidity, density
 84 |     T_v::FT = (τ_1 - τ_2 * I_T)^(-1)
 85 |     p::FT = _p_0 * exp(-_grav / _R_d * (τ_int_1 - τ_int_2 * I_T))
 86 | 
 87 |     # base state velocity
 88 |     U::FT =
 89 |         _grav * k / _a *
 90 |         τ_int_2 *
 91 |         T_v *
 92 |         (
 93 |             (cos(φ) * (1 + γ * z / _a))^(k - 1) -
 94 |             (cos(φ) * (1 + γ * z / _a))^(k + 1)
 95 |         )
 96 |     u_ref::FT =
 97 |         -_Ω * (_a + γ * z) * cos(φ) +
 98 |         sqrt((_Ω * (_a + γ * z) * cos(φ))^2 + (_a + γ * z) * cos(φ) * U)
 99 |     v_ref::FT = 0
100 |     w_ref::FT = 0
101 | 
102 |     # velocity perturbations
103 |     F_z::FT = 1 - 3 * (z / z_t)^2 + 2 * (z / z_t)^3
104 |     if z > z_t
105 |         F_z = FT(0)
106 |     end
107 |     d::FT = _a * acos(sin(φ) * sin(φ_c) + cos(φ) * cos(φ_c) * cos(λ - λ_c))
108 |     c3::FT = cos(π * d / 2 / d_0)^3
109 |     s1::FT = sin(π * d / 2 / d_0)
110 |     if 0 < d < d_0 && d != FT(_a * π)
111 |         u′::FT =
112 |             -16 * V_p / 3 / sqrt(3) *
113 |             F_z *
114 |             c3 *
115 |             s1 *
116 |             (-sin(φ_c) * cos(φ) + cos(φ_c) * sin(φ) * cos(λ - λ_c)) /
117 |             sin(d / _a)
118 |         v′::FT =
119 |             16 * V_p / 3 / sqrt(3) * F_z * c3 * s1 * cos(φ_c) * sin(λ - λ_c) /
120 |             sin(d / _a)
121 |     else
122 |         u′ = FT(0)
123 |         v′ = FT(0)
124 |     end
125 |     w′::FT = 0
126 |     u_sphere = SVector{3, FT}(u_ref + u′, v_ref + v′, w_ref + w′)
127 |     u_cart = sphr_to_cart_vec(bl.orientation, u_sphere, aux)
128 | 
129 |     if moisture_model(bl) isa DryModel
130 |         q_tot = FT(0)
131 |     else
132 |         ## Compute moisture profile
133 |         ## Pressure coordinate η
134 |         ## η_crit = p_t / p_w ; p_t = 10000 hPa, p_w = 340 hPa
135 |         η = p / _p_0
136 |         if η > η_crit
137 |             q_tot = q_0 * exp(-(φ / φ_w)^4) * exp(-((η - 1) * _p_0 / p_w)^2)
138 |         else
139 |             q_tot = q_t
140 |         end
141 |     end
142 |     phase_partition = PhasePartition(q_tot)
143 | 
144 |     ## temperature & density
145 |     T::FT = T_v / (1 + M_v * q_tot)
146 |     ρ::FT = air_density(param_set, T, p, phase_partition)
147 | 
148 |     ## potential & kinetic energy
149 |     e_pot::FT = gravitational_potential(bl.orientation, aux)
150 |     e_kin::FT = 0.5 * u_cart' * u_cart
151 |     e_tot::FT = total_energy(param_set, e_kin, e_pot, T, phase_partition)
152 | 
153 |     ## Assign state variables
154 |     state.ρ = ρ
155 |     state.ρu = ρ * u_cart
156 |     state.energy.ρe = ρ * e_tot
157 | 
158 |     if !(moisture_model(bl) isa DryModel)
159 |         state.moisture.ρq_tot = ρ * q_tot
160 |     end
161 | 
162 |     nothing
163 | end
164 | 
165 | function config_baroclinic_wave(
166 |     FT,
167 |     poly_order,
168 |     fv_reconstruction,
169 |     resolution,
170 |     with_moisture,
171 | )
172 |     # Set up a reference state for linearization of equations
173 |     temp_profile_ref =
174 |         DecayingTemperatureProfile{FT}(param_set, FT(290), FT(220), FT(8e3))
175 |     ref_state = HydrostaticState(temp_profile_ref; subtract_off = false)
176 | 
177 |     # Set up the atmosphere model
178 |     exp_name = "BaroclinicWave"
179 |     domain_height::FT = 30e3 # distance between surface and top of atmosphere (m)
180 |     if with_moisture
181 |         # hyperdiffusion = EquilMoistBiharmonic(FT(8 * 3600))
182 |         moisture = EquilMoist()
183 |         source = (Gravity(), Coriolis())
184 |     else
185 |         # hyperdiffusion = DryBiharmonic(FT(8 * 3600))
186 |         moisture = DryModel()
187 |         source = (Gravity(), Coriolis())
188 |     end
189 |     physics = AtmosPhysics{FT}(
190 |         param_set;
191 |         ref_state = ref_state,
192 |         turbulence = ConstantKinematicViscosity(FT(0)),
193 |         # hyperdiffusion = hyperdiffusion,
194 |         moisture = moisture,
195 |     )
196 |     model = AtmosModel{FT}(
197 |         AtmosGCMConfigType,
198 |         physics;
199 |         init_state_prognostic = init_baroclinic_wave!,
200 |         source = source,
201 |     )
202 | 
203 |     config = ClimateMachine.AtmosGCMConfiguration(
204 |         exp_name,
205 |         poly_order,
206 |         resolution,
207 |         domain_height,
208 |         param_set,
209 |         init_baroclinic_wave!;
210 |         model = model,
211 |         numerical_flux_first_order = RoeNumericalFlux(),
212 |         fv_reconstruction = HBFVReconstruction(model, fv_reconstruction),
213 |     )
214 | 
215 |     return config
216 | end
217 | 
218 | function main()
219 |     # add a command line argument to specify whether to use a moist setup
220 |     # TODO: this will move to the future namelist functionality
221 |     bw_args = ArgParseSettings(autofix_names = true)
222 |     add_arg_group!(bw_args, "BaroclinicWave")
223 |     @add_arg_table! bw_args begin
224 |         "--with-moisture"
225 |         help = "use a moist setup"
226 |         action = :store_const
227 |         constant = true
228 |         default = false
229 |     end
230 | 
231 |     cl_args = ClimateMachine.init(parse_clargs = true, custom_clargs = bw_args)
232 |     with_moisture = cl_args["with_moisture"]
233 | 
234 |     # Driver configuration parameters
235 |     FT = Float64                             # floating type precision
236 |     poly_order = (5, 0)                      # discontinuous Galerkin polynomial order
237 |     fv_reconstruction = FVLinear()           # finite volume reconstruction scheme
238 |     n_horz = 8                               # horizontal element number
239 |     n_vert = 20                              # vertical element number
240 |     timestart::FT = 0                        # start time (s)
241 |     timeend::FT = 3600                       # end time (s)
242 | 
243 |     # Set up driver configuration
244 |     driver_config = config_baroclinic_wave(
245 |         FT,
246 |         poly_order,
247 |         fv_reconstruction,
248 |         (n_horz, n_vert),
249 |         with_moisture,
250 |     )
251 | 
252 |     # Set up experiment
253 | 
254 |     ode_solver_type = ClimateMachine.ExplicitSolverType(
255 |         solver_method = LSRK54CarpenterKennedy,
256 |     )
257 | 
258 |     CFL = FT(0.2) # target acoustic CFL number
259 | 
260 |     # time step is computed such that the horizontal acoustic Courant number is CFL
261 |     solver_config = ClimateMachine.SolverConfiguration(
262 |         timestart,
263 |         timeend,
264 |         driver_config,
265 |         Courant_number = CFL,
266 |         ode_solver_type = ode_solver_type,
267 |         CFL_direction = EveryDirection(),
268 |         diffdir = HorizontalDirection(),
269 |     )
270 | 
271 |     # Set up diagnostics
272 |     dgn_config = config_diagnostics(FT, driver_config)
273 | 
274 |     #TODO enable filter
275 |     # # Set up user-defined callbacks
276 |     filterorder = 20
277 |     filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)
278 |     cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do
279 |         Filters.apply!(
280 |             solver_config.Q,
281 |             AtmosFilterPerturbations(driver_config.bl),
282 |             solver_config.dg.grid,
283 |             filter,
284 |             state_auxiliary = solver_config.dg.state_auxiliary,
285 |             direction = HorizontalDirection(),
286 |         )
287 |         nothing
288 |     end
289 | 
290 |     # cbtmarfilter = GenericCallbacks.EveryXSimulationSteps(1) do
291 |     #     Filters.apply!(
292 |     #         solver_config.Q,
293 |     #         ("moisture.ρq_tot",),
294 |     #         solver_config.dg.grid,
295 |     #         TMARFilter(),
296 |     #     )
297 |     #     nothing
298 |     # end
299 | 
300 |     # Run the model
301 |     result = ClimateMachine.invoke!(
302 |         solver_config;
303 |         diagnostics_config = dgn_config,
304 |         user_callbacks = (cbfilter,),
305 |         #user_callbacks = (cbtmarfilter, cbfilter),
306 |         check_euclidean_distance = true,
307 |     )
308 | end
309 | 
310 | function config_diagnostics(FT, driver_config)
311 |     interval = "12shours" # chosen to allow diagnostics every 12 simulated hours
312 | 
313 |     _planet_radius = FT(planet_radius(param_set))
314 | 
315 |     info = driver_config.config_info
316 |     boundaries = [
317 |         FT(-90.0) FT(-180.0) _planet_radius
318 |         FT(90.0) FT(180.0) FT(_planet_radius + info.domain_height)
319 |     ]
320 |     resolution = (FT(2), FT(2), FT(1000)) # in (deg, deg, m)
321 |     interpol = ClimateMachine.InterpolationConfiguration(
322 |         driver_config,
323 |         boundaries,
324 |         resolution,
325 |     )
326 | 
327 |     dgngrp = setup_atmos_default_diagnostics(
328 |         AtmosGCMConfigType(),
329 |         interval,
330 |         driver_config.name,
331 |         interpol = interpol,
332 |     )
333 | 
334 |     return ClimateMachine.DiagnosticsConfiguration([dgngrp])
335 | end
336 | 
337 | main()
338 | 
--------------------------------------------------------------------------------
/experiments/TestCase/isothermal_zonal_flow.jl:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env julia --project
  2 | using ClimateMachine
  3 | ClimateMachine.init(parse_clargs = true)
  4 | 
  5 | using ClimateMachine.Atmos
  6 | using ClimateMachine.ConfigTypes
  7 | using ClimateMachine.NumericalFluxes
  8 | using ClimateMachine.Diagnostics
  9 | using ClimateMachine.Orientations
 10 | using ClimateMachine.GenericCallbacks
 11 | using ClimateMachine.ODESolvers
 12 | using ClimateMachine.SystemSolvers: ManyColumnLU
 13 | using ClimateMachine.Mesh.Filters
 14 | using ClimateMachine.Mesh.Grids
 15 | using ClimateMachine.Mesh.Interpolation
 16 | using Thermodynamics.TemperatureProfiles
 17 | using Thermodynamics: total_energy, air_density
 18 | using ClimateMachine.TurbulenceClosures
 19 | using ClimateMachine.VariableTemplates
 20 | 
 21 | using Distributions: Uniform
 22 | using LinearAlgebra
 23 | using StaticArrays
 24 | using Test
 25 | 
 26 | using CLIMAParameters
 27 | using CLIMAParameters.Planet:
 28 |     R_d, day, grav, cp_d, planet_radius, Omega, kappa_d, MSLP
 29 | struct EarthParameterSet <: AbstractEarthParameterSet end
 30 | const param_set = EarthParameterSet()
 31 | 
 32 | import CLIMAParameters
 33 | CLIMAParameters.Planet.Omega(::EarthParameterSet) = 0.0
 34 | CLIMAParameters.Planet.planet_radius(::EarthParameterSet) = 6.371e6 / 125.0
 35 | CLIMAParameters.Planet.MSLP(::EarthParameterSet) = 1e5
 36 | 
 37 | function init_isothermal_zonal_flow!(problem, bl, state, aux, localgeo, t)
 38 |     FT = eltype(state)
 39 |     param_set = parameter_set(bl)
 40 |     φ = latitude(bl.orientation, aux)
 41 |     z = altitude(bl.orientation, param_set, aux)
 42 | 
 43 |     _grav::FT = grav(param_set)
 44 |     _a::FT = planet_radius(param_set)
 45 |     _R_d::FT = R_d(param_set)
 46 |     _MSLP::FT = MSLP(param_set)
 47 | 
 48 |     u₀ = FT(20)
 49 |     T₀ = FT(300)
 50 | 
 51 |     shallow_atmos = false
 52 |     if shallow_atmos
 53 |         f1 = z
 54 |         f2 = FT(0)
 55 |         shear = FT(1)
 56 |     else
 57 |         f1 = z
 58 |         f2 = z / _a + z^2 / (2 * _a^2)
 59 |         shear = 1 + z / _a
 60 |     end
 61 | 
 62 |     u_sphere = SVector{3, FT}(u₀ * shear * cos(φ), 0, 0)
 63 |     u_init = sphr_to_cart_vec(bl.orientation, u_sphere, aux)
 64 | 
 65 |     prefac = u₀^2 / (_R_d * T₀)
 66 |     fac1 = prefac * f2 * cos(φ)^2
 67 |     fac2 = prefac * sin(φ)^2 / 2
 68 |     fac3 = _grav * f1 / (_R_d * T₀)
 69 |     exparg = fac1 - fac2 - fac3
 70 |     p = _MSLP * exp(exparg)
 71 | 
 72 |     ρ = air_density(param_set, T₀, p)
 73 | 
 74 |     e_pot = gravitational_potential(bl.orientation, aux)
 75 |     e_kin = u_init' * u_init / 2
 76 | 
 77 |     state.ρ = ρ
 78 |     state.ρu = ρ * u_init
 79 |     state.energy.ρe = ρ * total_energy(param_set, e_kin, e_pot, T₀)
 80 | end
 81 | 
 82 | function config_isothermal_zonal_flow(
 83 |     FT,
 84 |     poly_order,
 85 |     cutoff_order,
 86 |     resolution,
 87 |     ref_state,
 88 | )
 89 |     # Set up a reference state for linearization of equations
 90 | 
 91 |     domain_height = FT(10e3)
 92 | 
 93 |     # Set up the atmosphere model
 94 |     exp_name = "IsothermalZonalFlow"
 95 | 
 96 |     physics = AtmosPhysics{FT}(
 97 |         param_set;
 98 |         ref_state = ref_state,
 99 |         turbulence = ConstantKinematicViscosity(FT(0)),
100 |         moisture = DryModel(),
101 |     )
102 |     model = AtmosModel{FT}(
103 |         AtmosGCMConfigType,
104 |         physics;
105 |         init_state_prognostic = init_isothermal_zonal_flow!,
106 |         source = (Gravity(),),
107 |     )
108 | 
109 |     config = ClimateMachine.AtmosGCMConfiguration(
110 |         exp_name,
111 |         poly_order,
112 |         resolution,
113 |         domain_height,
114 |         param_set,
115 |         init_isothermal_zonal_flow!;
116 |         model = model,
117 |         numerical_flux_first_order = RoeNumericalFlux(),
118 |         Ncutoff = cutoff_order,
119 |     )
120 | 
121 |     return config
122 | end
123 | 
124 | function config_diagnostics(FT, driver_config)
125 |     interval = "40000steps" # chosen to allow a single diagnostics collection
126 | 
127 |     _planet_radius = FT(planet_radius(param_set))
128 | 
129 |     info = driver_config.config_info
130 |     boundaries = [
131 |         FT(-90.0) FT(-180.0) _planet_radius
132 |         FT(90.0) FT(180.0) FT(_planet_radius + info.domain_height)
133 |     ]
134 |     resolution = (FT(10), FT(10), FT(100)) # in (deg, deg, m)
135 |     interpol = ClimateMachine.InterpolationConfiguration(
136 |         driver_config,
137 |         boundaries,
138 |         resolution,
139 |     )
140 | 
141 |     dgngrp = setup_atmos_default_diagnostics(
142 |         AtmosGCMConfigType(),
143 |         interval,
144 |         driver_config.name,
145 |         interpol = interpol,
146 |     )
147 | 
148 |     return ClimateMachine.DiagnosticsConfiguration([dgngrp])
149 | end
150 | 
151 | function main()
152 |     # Driver configuration parameters
153 |     FT = Float64                             # floating type precision
154 |     poly_order = 5                           # discontinuous Galerkin polynomial order
155 |     cutoff_order = 4
156 |     n_horz = 10                              # horizontal element number
157 |     n_vert = 5                               # vertical element number
158 |     timestart = FT(0)                        # start time (s)
159 |     timeend = FT(3600)                       # end time (s)
160 | 
161 |     # set up the reference state for linearization of equations
162 |     temp_profile_ref = IsothermalProfile(param_set, FT(300))
163 |     ref_state = HydrostaticState(temp_profile_ref)
164 | 
165 |     # Set up driver configuration
166 |     driver_config = config_isothermal_zonal_flow(
167 |         FT,
168 |         poly_order,
169 |         cutoff_order,
170 |         (n_horz, n_vert),
171 |         ref_state,
172 |     )
173 | 
174 |     # Set up experiment
175 |     ode_solver_type = ClimateMachine.IMEXSolverType(
176 |         implicit_model = AtmosAcousticGravityLinearModel,
177 |         implicit_solver = ManyColumnLU,
178 |         solver_method = ARK2GiraldoKellyConstantinescu,
179 |         split_explicit_implicit = false,
180 |         discrete_splitting = true,
181 |     )
182 |     CFL = FT(0.4)
183 |     solver_config = ClimateMachine.SolverConfiguration(
184 |         timestart,
185 |         timeend,
186 |         driver_config,
187 |         Courant_number = CFL,
188 |         init_on_cpu = true,
189 |         ode_solver_type = ode_solver_type,
190 |         CFL_direction = HorizontalDirection(),
191 |     )
192 | 
193 |     # save the initial condition for testing
194 |     Q0 = copy(solver_config.Q)
195 | 
196 |     # Set up diagnostics
197 |     dgn_config = config_diagnostics(FT, driver_config)
198 | 
199 |     # Set up user-defined callbacks
200 |     filterorder = 64
201 |     filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)
202 |     cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do
203 |         Filters.apply!(
204 |             solver_config.Q,
205 |             AtmosFilterPerturbations(driver_config.bl),
206 |             solver_config.dg.grid,
207 |             filter,
208 |             state_auxiliary = solver_config.dg.state_auxiliary,
209 |         )
210 |         nothing
211 |     end
212 | 
213 |     # Run the model
214 |     result = ClimateMachine.invoke!(
215 |         solver_config;
216 |         diagnostics_config = dgn_config,
217 |         user_callbacks = (cbfilter,),
218 |         check_euclidean_distance = true,
219 |     )
220 | 
221 |     relative_error = norm(solver_config.Q .- Q0) / norm(Q0)
222 |     @info "Relative error = $relative_error"
223 |     @test relative_error < 1e-7
224 | 
225 | end
226 | 
227 | main()
228 | 
--------------------------------------------------------------------------------
/experiments/TestCase/risingbubble.jl:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env julia --project
  2 | using ArgParse
  3 | 
  4 | using ClimateMachine
  5 | using ClimateMachine.Atmos
  6 | using ClimateMachine.Orientations
  7 | using ClimateMachine.ConfigTypes
  8 | using ClimateMachine.Diagnostics
  9 | using ClimateMachine.GenericCallbacks
 10 | using ClimateMachine.ODESolvers
 11 | using Thermodynamics.TemperatureProfiles
 12 | using Thermodynamics
 13 | using ClimateMachine.TurbulenceClosures
 14 | using ClimateMachine.VariableTemplates
 15 | using StaticArrays
 16 | using Test
 17 | using CLIMAParameters
 18 | using CLIMAParameters.Atmos.SubgridScale: C_smag
 19 | using CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav
 20 | struct EarthParameterSet <: AbstractEarthParameterSet end
 21 | const param_set = EarthParameterSet();
 22 | 
 23 | function init_risingbubble!(problem, bl, state, aux, localgeo, t)
 24 |     ## Problem float-type
 25 |     FT = eltype(state)
 26 | 
 27 |     (x, y, z) = localgeo.coord
 28 |     param_set = parameter_set(bl)
 29 | 
 30 |     ## Unpack constant parameters
 31 |     R_gas::FT = R_d(param_set)
 32 |     c_p::FT = cp_d(param_set)
 33 |     c_v::FT = cv_d(param_set)
 34 |     p0::FT = MSLP(param_set)
 35 |     _grav::FT = grav(param_set)
 36 |     γ::FT = c_p / c_v
 37 | 
 38 |     ## Define bubble center and background potential temperature
 39 |     xc::FT = 5000
 40 |     yc::FT = 1000
 41 |     zc::FT = 2000
 42 |     r = sqrt((x - xc)^2 + (z - zc)^2)
 43 |     rc::FT = 2000
 44 |     θamplitude::FT = 2
 45 |     q_tot_amplitude::FT = 1e-3
 46 | 
 47 |     ## TODO: clean this up, or add convenience function:
 48 |     ## This is configured in the reference hydrostatic state
 49 |     ref_state = reference_state(bl)
 50 |     θ_ref::FT = ref_state.virtual_temperature_profile.T_surface
 51 | 
 52 |     ## Add the thermal perturbation:
 53 |     Δθ::FT = 0
 54 |     Δq_tot::FT = 0
 55 |     if r <= rc
 56 |         Δθ = θamplitude * (1.0 - r / rc)
 57 |         Δq_tot = q_tot_amplitude * (1.0 - r / rc)
 58 |     end
 59 | 
 60 |     ## Compute perturbed thermodynamic state:
 61 |     θ = θ_ref + Δθ                                      # potential temperature
 62 |     q_pt = PhasePartition(Δq_tot)
 63 |     R_m = gas_constant_air(param_set, q_pt)
 64 |     _cp_m = cp_m(param_set, q_pt)
 65 |     _cv_m = cv_m(param_set, q_pt)
 66 |     π_exner = FT(1) - _grav / (_cp_m * θ) * z           # exner pressure
 67 |     ρ = p0 / (R_gas * θ) * (π_exner)^(_cv_m / R_m)      # density
 68 |     T = θ * π_exner
 69 | 
 70 |     if moisture_model(bl) isa EquilMoist
 71 |         e_int = internal_energy(param_set, T, q_pt)
 72 |         ts = PhaseEquil_ρeq(param_set, ρ, e_int, Δq_tot)
 73 |     else
 74 |         e_int = internal_energy(param_set, T)
 75 |         ts = PhaseDry(param_set, e_int, ρ)
 76 |     end
 77 |     ρu = SVector(FT(0), FT(0), FT(0))                   # momentum
 78 |     ## State (prognostic) variable assignment
 79 |     e_kin = FT(0)                                       # kinetic energy
 80 |     e_pot = gravitational_potential(bl, aux)            # potential energy
 81 |     ρe_tot = ρ * total_energy(e_kin, e_pot, ts)         # total energy
 82 |     ρq_tot = ρ * Δq_tot                                 # total water specific humidity
 83 | 
 84 |     ## Assign State Variables
 85 |     state.ρ = ρ
 86 |     state.ρu = ρu
 87 |     state.energy.ρe = ρe_tot
 88 |     if !(moisture_model(bl) isa DryModel)
 89 |         state.moisture.ρq_tot = ρq_tot
 90 |     end
 91 | end
 92 | 
 93 | function config_risingbubble(FT, N, resolution, xmax, ymax, zmax, with_moisture)
 94 | 
 95 |     T_surface = FT(300)
 96 |     T_min_ref = FT(0)
 97 |     T_profile = DryAdiabaticProfile{FT}(param_set, T_surface, T_min_ref)
 98 |     ref_state = HydrostaticState(T_profile)
 99 | 
100 |     _C_smag = FT(C_smag(param_set))
101 | 
102 |     if with_moisture
103 |         moisture = EquilMoist()
104 |     else
105 |         moisture = DryModel()
106 |     end
107 |     physics = AtmosPhysics{FT}(
108 |         param_set;
109 |         ref_state = ref_state,
110 |         turbulence = SmagorinskyLilly(_C_smag),
111 |         moisture = moisture,
112 |         tracers = NoTracers(),
113 |     )
114 |     model = AtmosModel{FT}(
115 |         AtmosLESConfigType,
116 |         physics;
117 |         init_state_prognostic = init_risingbubble!,
118 |         source = (Gravity(),),
119 |     )
120 | 
121 |     config = ClimateMachine.AtmosLESConfiguration(
122 |         "RisingBubble",
123 |         N,
124 |         resolution,
125 |         xmax,
126 |         ymax,
127 |         zmax,
128 |         param_set,
129 |         init_risingbubble!,
130 |         model = model,
131 |     )
132 |     return config
133 | end
134 | 
135 | function config_diagnostics(driver_config)
136 |     FT = Float64
137 |     interval = "50ssecs"
138 |     boundaries = [
139 |         FT(0.0) FT(0.0) FT(0.0)
140 |         FT(10000) FT(500) FT(10000)
141 |     ]
142 |     resolution = (FT(100), FT(100), FT(100))
143 |     interpol = ClimateMachine.InterpolationConfiguration(
144 |         driver_config,
145 |         boundaries,
146 |         resolution,
147 |     )
148 |     dgngrp = setup_atmos_default_diagnostics(
149 |         AtmosLESConfigType(),
150 |         interval,
151 |         driver_config.name,
152 |     )
153 |     state_dgngrp = setup_dump_state_diagnostics(
154 |         AtmosLESConfigType(),
155 |         interval,
156 |         driver_config.name,
157 |         interpol = interpol,
158 |     )
159 |     aux_dgngrp = setup_dump_aux_diagnostics(
160 |         AtmosLESConfigType(),
161 |         interval,
162 |         driver_config.name,
163 |         interpol = interpol,
164 |     )
165 |     return ClimateMachine.DiagnosticsConfiguration([
166 |         dgngrp,
167 |         state_dgngrp,
168 |         aux_dgngrp,
169 |     ])
170 | end
171 | 
172 | function main()
173 |     # add a command line argument to specify whether to use a moist setup
174 |     rb_args = ArgParseSettings(autofix_names = true)
175 |     add_arg_group!(rb_args, "RisingBubble")
176 |     @add_arg_table! rb_args begin
177 |         "--with-moisture"
178 |         help = "use a moist setup"
179 |         action = :store_const
180 |         constant = true
181 |         default = false
182 |     end
183 |     cl_args = ClimateMachine.init(parse_clargs = true, custom_clargs = rb_args)
184 |     with_moisture = cl_args["with_moisture"]
185 | 
186 |     FT = Float64
187 |     N = 4
188 |     Δh = FT(125)
189 |     Δv = FT(125)
190 |     resolution = (Δh, Δh, Δv)
191 |     xmax = FT(10000)
192 |     ymax = FT(500)
193 |     zmax = FT(10000)
194 |     t0 = FT(0)
195 |     timeend = FT(1000)
196 | 
197 |     CFL = FT(1.7)
198 | 
199 |     driver_config =
200 |         config_risingbubble(FT, N, resolution, xmax, ymax, zmax, with_moisture)
201 | 
202 |     ode_solver_type = ClimateMachine.ExplicitSolverType(
203 |         solver_method = LSRK144NiegemannDiehlBusch,
204 |     )
205 | 
206 |     solver_config = ClimateMachine.SolverConfiguration(
207 |         t0,
208 |         timeend,
209 |         driver_config,
210 |         ode_solver_type = ode_solver_type,
211 |         init_on_cpu = true,
212 |         Courant_number = CFL,
213 |     )
214 |     dgn_config = config_diagnostics(driver_config)
215 | 
216 |     result = ClimateMachine.invoke!(
217 |         solver_config;
218 |         diagnostics_config = dgn_config,
219 |         user_callbacks = (),
220 |         check_euclidean_distance = true,
221 |     )
222 | 
223 |     @test isapprox(result, FT(1); atol = 1.5e-3)
224 | end
225 | 
226 | main()
227 | 
--------------------------------------------------------------------------------
/experiments/TestCase/risingbubble_fvm.jl:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env julia --project
  2 | using ArgParse
  3 | 
  4 | using ClimateMachine
  5 | using ClimateMachine.Atmos
  6 | using ClimateMachine.Orientations
  7 | using ClimateMachine.ConfigTypes
  8 | using ClimateMachine.Diagnostics
  9 | using ClimateMachine.GenericCallbacks
 10 | using ClimateMachine.ODESolvers
 11 | using Thermodynamics.TemperatureProfiles
 12 | using Thermodynamics
 13 | using ClimateMachine.TurbulenceClosures
 14 | using ClimateMachine.VariableTemplates
 15 | import ClimateMachine.DGMethods.FVReconstructions: FVLinear
 16 | 
 17 | using StaticArrays
 18 | using Test
 19 | using CLIMAParameters
 20 | using CLIMAParameters.Atmos.SubgridScale: C_smag
 21 | using CLIMAParameters.Planet: R_d, cp_d, cv_d, MSLP, grav
 22 | struct EarthParameterSet <: AbstractEarthParameterSet end
 23 | const param_set = EarthParameterSet();
 24 | 
 25 | function init_risingbubble!(problem, bl, state, aux, localgeo, t)
 26 |     ## Problem float-type
 27 |     FT = eltype(state)
 28 | 
 29 |     (x, y, z) = localgeo.coord
 30 |     param_set = parameter_set(bl)
 31 | 
 32 |     ## Unpack constant parameters
 33 |     R_gas::FT = R_d(param_set)
 34 |     c_p::FT = cp_d(param_set)
 35 |     c_v::FT = cv_d(param_set)
 36 |     p0::FT = MSLP(param_set)
 37 |     _grav::FT = grav(param_set)
 38 |     γ::FT = c_p / c_v
 39 | 
 40 |     ## Define bubble center and background potential temperature
 41 |     xc::FT = 5000
 42 |     yc::FT = 1000
 43 |     zc::FT = 2000
 44 |     r = sqrt((x - xc)^2 + (z - zc)^2)
 45 |     rc::FT = 2000
 46 |     θamplitude::FT = 2
 47 |     q_tot_amplitude::FT = 1e-3
 48 | 
 49 |     ## TODO: clean this up, or add convenience function:
 50 |     ## This is configured in the reference hydrostatic state
 51 |     ref_state = reference_state(bl)
 52 |     θ_ref::FT = ref_state.virtual_temperature_profile.T_surface
 53 | 
 54 |     ## Add the thermal perturbation:
 55 |     Δθ::FT = 0
 56 |     Δq_tot::FT = 0
 57 |     if r <= rc
 58 |         Δθ = θamplitude * (1.0 - r / rc)
 59 |         Δq_tot = q_tot_amplitude * (1.0 - r / rc)
 60 |     end
 61 | 
 62 |     ## Compute perturbed thermodynamic state:
 63 |     θ = θ_ref + Δθ                                      # potential temperature
 64 |     q_pt = PhasePartition(Δq_tot)
 65 |     R_m = gas_constant_air(param_set, q_pt)
 66 |     _cp_m = cp_m(param_set, q_pt)
 67 |     _cv_m = cv_m(param_set, q_pt)
 68 |     π_exner = FT(1) - _grav / (_cp_m * θ) * z           # exner pressure
 69 |     ρ = p0 / (R_gas * θ) * (π_exner)^(_cv_m / R_m)      # density
 70 |     T = θ * π_exner
 71 | 
 72 |     if moisture_model(bl) isa EquilMoist
 73 |         e_int = internal_energy(param_set, T, q_pt)
 74 |         ts = PhaseEquil_ρeq(param_set, ρ, e_int, Δq_tot)
 75 |     else
 76 |         e_int = internal_energy(param_set, T)
 77 |         ts = PhaseDry(param_set, e_int, ρ)
 78 |     end
 79 |     ρu = SVector(FT(0), FT(0), FT(0))                   # momentum
 80 |     ## State (prognostic) variable assignment
 81 |     e_kin = FT(0)                                       # kinetic energy
 82 |     e_pot = gravitational_potential(bl, aux)            # potential energy
 83 |     ρe_tot = ρ * total_energy(e_kin, e_pot, ts)         # total energy
 84 |     ρq_tot = ρ * Δq_tot                                 # total water specific humidity
 85 | 
 86 |     ## Assign State Variables
 87 |     state.ρ = ρ
 88 |     state.ρu = ρu
 89 |     state.energy.ρe = ρe_tot
 90 |     if !(moisture_model(bl) isa DryModel)
 91 |         state.moisture.ρq_tot = ρq_tot
 92 |     end
 93 | end
 94 | 
 95 | function config_risingbubble(
 96 |     FT,
 97 |     N,
 98 |     fv_reconstruction,
 99 |     resolution,
100 |     xmax,
101 |     ymax,
102 |     zmax,
103 |     with_moisture,
104 | )
105 | 
106 |     T_surface = FT(300)
107 |     T_min_ref = FT(0)
108 |     T_profile = DryAdiabaticProfile{FT}(param_set, T_surface, T_min_ref)
109 |     ref_state = HydrostaticState(T_profile)
110 | 
111 |     _C_smag = FT(C_smag(param_set))
112 | 
113 |     if with_moisture
114 |         moisture = EquilMoist()
115 |     else
116 |         moisture = DryModel()
117 |     end
118 |     physics = AtmosPhysics{FT}(
119 |         param_set;
120 |         ref_state = ref_state,
121 |         turbulence = ConstantKinematicViscosity(FT(0)),
122 |         moisture = moisture,
123 |         tracers = NoTracers(),
124 |     )
125 |     model = AtmosModel{FT}(
126 |         AtmosLESConfigType,
127 |         physics;
128 |         init_state_prognostic = init_risingbubble!,
129 |         source = (Gravity(),),
130 |     )
131 | 
132 |     config = ClimateMachine.AtmosLESConfiguration(
133 |         "RisingBubble",
134 |         (N, 0),
135 |         resolution,
136 |         xmax,
137 |         ymax,
138 |         zmax,
139 |         param_set,
140 |         init_risingbubble!,
141 |         model = model,
142 |         numerical_flux_first_order = RoeNumericalFlux(),
143 |         fv_reconstruction = HBFVReconstruction(model, fv_reconstruction),
144 |     )
145 |     return config
146 | end
147 | 
148 | function config_diagnostics(driver_config)
149 |     FT = Float64
150 |     interval = "50ssecs"
151 |     boundaries = [
152 |         FT(0.0) FT(0.0) FT(0.0)
153 |         FT(10000) FT(500) FT(10000)
154 |     ]
155 |     resolution = (FT(100), FT(100), FT(100))
156 |     interpol = ClimateMachine.InterpolationConfiguration(
157 |         driver_config,
158 |         boundaries,
159 |         resolution,
160 |     )
161 |     dgngrp = setup_atmos_default_diagnostics(
162 |         AtmosLESConfigType(),
163 |         interval,
164 |         driver_config.name,
165 |     )
166 |     state_dgngrp = setup_dump_state_diagnostics(
167 |         AtmosLESConfigType(),
168 |         interval,
169 |         driver_config.name,
170 |         interpol = interpol,
171 |     )
172 |     aux_dgngrp = setup_dump_aux_diagnostics(
173 |         AtmosLESConfigType(),
174 |         interval,
175 |         driver_config.name,
176 |         interpol = interpol,
177 |     )
178 |     return ClimateMachine.DiagnosticsConfiguration([
179 |         dgngrp,
180 |         state_dgngrp,
181 |         aux_dgngrp,
182 |     ])
183 | end
184 | 
185 | function main()
186 |     # add a command line argument to specify whether to use a moist setup
187 |     rb_args = ArgParseSettings(autofix_names = true)
188 |     add_arg_group!(rb_args, "RisingBubble")
189 |     @add_arg_table! rb_args begin
190 |         "--with-moisture"
191 |         help = "use a moist setup"
192 |         action = :store_const
193 |         constant = true
194 |         default = false
195 |     end
196 |     cl_args = ClimateMachine.init(parse_clargs = true, custom_clargs = rb_args)
197 |     with_moisture = cl_args["with_moisture"]
198 | 
199 |     FT = Float64
200 |     N = 4
201 |     # effective mesh size
202 |     Δh = FT(125)
203 |     Δv = FT(125)
204 |     resolution = (Δh, Δh, Δv)
205 |     xmax = FT(10000)
206 |     ymax = FT(500)
207 |     zmax = FT(10000)
208 |     t0 = FT(0)
209 |     timeend = FT(1000)
210 |     fv_reconstruction = FVLinear()         # finite volume reconstruction scheme
211 | 
212 |     CFL = FT(0.2)
213 | 
214 |     driver_config = config_risingbubble(
215 |         FT,
216 |         N,
217 |         fv_reconstruction,
218 |         resolution,
219 |         xmax,
220 |         ymax,
221 |         zmax,
222 |         with_moisture,
223 |     )
224 | 
225 |     ode_solver_type = ClimateMachine.ExplicitSolverType(
226 |         solver_method = LSRK54CarpenterKennedy,
227 |     )
228 | 
229 |     solver_config = ClimateMachine.SolverConfiguration(
230 |         t0,
231 |         timeend,
232 |         driver_config,
233 |         ode_solver_type = ode_solver_type,
234 |         init_on_cpu = true,
235 |         Courant_number = CFL,
236 |     )
237 |     dgn_config = config_diagnostics(driver_config)
238 | 
239 |     result = ClimateMachine.invoke!(
240 |         solver_config;
241 |         diagnostics_config = dgn_config,
242 |         user_callbacks = (),
243 |         check_euclidean_distance = true,
244 |     )
245 | 
246 |     @test isapprox(result, FT(1); atol = 1.5e-3)
247 | end
248 | 
249 | main()
250 | 
--------------------------------------------------------------------------------
/experiments/TestCase/solid_body_rotation.jl:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env julia --project
  2 | using ClimateMachine
  3 | ClimateMachine.init(parse_clargs = true)
  4 | 
  5 | using ClimateMachine.Atmos
  6 | using ClimateMachine.Orientations
  7 | using ClimateMachine.ConfigTypes
  8 | using ClimateMachine.NumericalFluxes
  9 | using ClimateMachine.Diagnostics
 10 | using ClimateMachine.GenericCallbacks
 11 | using ClimateMachine.ODESolvers
 12 | using ClimateMachine.TurbulenceClosures
 13 | using ClimateMachine.SystemSolvers: ManyColumnLU
 14 | using ClimateMachine.Mesh.Filters
 15 | using ClimateMachine.Mesh.Grids
 16 | using ClimateMachine.Mesh.Interpolation
 17 | using Thermodynamics.TemperatureProfiles
 18 | using ClimateMachine.VariableTemplates
 19 | using Thermodynamics: air_density, total_energy
 20 | 
 21 | using LinearAlgebra
 22 | using StaticArrays
 23 | using Test
 24 | 
 25 | using CLIMAParameters
 26 | using CLIMAParameters.Planet: day, planet_radius
 27 | struct EarthParameterSet <: AbstractEarthParameterSet end
 28 | const param_set = EarthParameterSet()
 29 | 
 30 | function init_solid_body_rotation!(problem, bl, state, aux, localgeo, t)
 31 |     FT = eltype(state)
 32 | 
 33 |     # initial velocity profile (we need to transform the vector into the Cartesian
 34 |     # coordinate system)
 35 |     u_0::FT = 0
 36 |     u_sphere = SVector{3, FT}(u_0, 0, 0)
 37 |     u_init = sphr_to_cart_vec(bl.orientation, u_sphere, aux)
 38 |     e_kin::FT = 0.5 * sum(abs2.(u_init))
 39 | 
 40 |     # Assign state variables
 41 |     state.ρ = aux.ref_state.ρ
 42 |     state.ρu = u_init
 43 |     state.energy.ρe = aux.ref_state.ρe + state.ρ * e_kin
 44 | 
 45 |     nothing
 46 | end
 47 | 
 48 | function config_solid_body_rotation(FT, poly_order, resolution, ref_state)
 49 | 
 50 |     # Set up the atmosphere model
 51 |     exp_name = "SolidBodyRotation"
 52 |     domain_height::FT = 30e3 # distance between surface and top of atmosphere (m)
 53 | 
 54 |     physics = AtmosPhysics{FT}(
 55 |         param_set;
 56 |         ref_state = ref_state,
 57 |         turbulence = ConstantKinematicViscosity(FT(0)),
 58 |         #hyperdiffusion = DryBiharmonic(FT(8 * 3600)),
 59 |         moisture = DryModel(),
 60 |     )
 61 |     model = AtmosModel{FT}(
 62 |         AtmosGCMConfigType,
 63 |         physics;
 64 |         init_state_prognostic = init_solid_body_rotation!,
 65 |         source = (Gravity(), Coriolis()),
 66 |     )
 67 | 
 68 |     config = ClimateMachine.AtmosGCMConfiguration(
 69 |         exp_name,
 70 |         poly_order,
 71 |         resolution,
 72 |         domain_height,
 73 |         param_set,
 74 |         init_solid_body_rotation!;
 75 |         model = model,
 76 |         numerical_flux_first_order = RoeNumericalFlux(),
 77 |     )
 78 | 
 79 |     return config
 80 | end
 81 | 
 82 | function main()
 83 |     # Driver configuration parameters
 84 |     FT = Float64                             # floating type precision
 85 |     poly_order = (5, 4)                     # discontinuous Galerkin polynomial order
 86 |     n_horz = 8                              # horizontal element number
 87 |     n_vert = 4                               # vertical element number
 88 |     timestart::FT = 0                        # start time (s)
 89 |     timeend::FT = 7200
 90 | 
 91 |     # Set up a reference state for linearization of equations
 92 |     temp_profile_ref =
 93 |         DecayingTemperatureProfile{FT}(param_set, FT(290), FT(220), FT(8e3))
 94 |     ref_state = HydrostaticState(temp_profile_ref)
 95 | 
 96 |     # Set up driver configuration
 97 |     driver_config =
 98 |         config_solid_body_rotation(FT, poly_order, (n_horz, n_vert), ref_state)
 99 | 
100 |     # Set up experiment
101 |     ode_solver_type = ClimateMachine.IMEXSolverType(
102 |         implicit_model = AtmosAcousticGravityLinearModel,
103 |         implicit_solver = ManyColumnLU,
104 |         solver_method = ARK2GiraldoKellyConstantinescu,
105 |         split_explicit_implicit = false,
106 |         discrete_splitting = true,
107 |     )
108 | 
109 |     CFL = FT(0.2) # target acoustic CFL number
110 | 
111 |     # time step is computed such that the horizontal acoustic Courant number is CFL
112 |     solver_config = ClimateMachine.SolverConfiguration(
113 |         timestart,
114 |         timeend,
115 |         driver_config,
116 |         Courant_number = CFL,
117 |         ode_solver_type = ode_solver_type,
118 |         CFL_direction = HorizontalDirection(),
119 |         diffdir = HorizontalDirection(),
120 |     )
121 | 
122 |     # initialize using a different ref state (mega-hack)
123 |     temp_profile_init =
124 |         DecayingTemperatureProfile{FT}(param_set, FT(280), FT(230), FT(9e3))
125 |     init_ref_state = HydrostaticState(temp_profile_init)
126 | 
127 |     init_driver_config = config_solid_body_rotation(
128 |         FT,
129 |         poly_order,
130 |         (n_horz, n_vert),
131 |         init_ref_state,
132 |     )
133 |     init_solver_config = ClimateMachine.SolverConfiguration(
134 |         timestart,
135 |         timeend,
136 |         init_driver_config,
137 |         Courant_number = CFL,
138 |         ode_solver_type = ode_solver_type,
139 |         CFL_direction = HorizontalDirection(),
140 |         diffdir = HorizontalDirection(),
141 |     )
142 | 
143 |     # initialization
144 |     solver_config.Q .= init_solver_config.Q
145 | 
146 |     # Set up diagnostics
147 |     dgn_config = config_diagnostics(FT, driver_config)
148 | 
149 |     # Set up user-defined callbacks
150 |     filterorder = 20
151 |     filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)
152 |     cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do
153 |         Filters.apply!(
154 |             solver_config.Q,
155 |             AtmosFilterPerturbations(driver_config.bl),
156 |             solver_config.dg.grid,
157 |             filter,
158 |             # filter perturbations from the initial state
159 |             state_auxiliary = init_solver_config.dg.state_auxiliary,
160 |         )
161 |         nothing
162 |     end
163 | 
164 |     # Run the model
165 |     result = ClimateMachine.invoke!(
166 |         solver_config;
167 |         diagnostics_config = dgn_config,
168 |         user_callbacks = (cbfilter,),
169 |         check_euclidean_distance = false,
170 |     )
171 | 
172 |     relative_error =
173 |         norm(solver_config.Q .- init_solver_config.Q) /
174 |         norm(init_solver_config.Q)
175 |     @info "Relative error = $relative_error"
176 |     @test relative_error < 1e-9
177 | end
178 | 
179 | function config_diagnostics(FT, driver_config)
180 |     interval = "0.5shours" # chosen to allow diagnostics every 30 simulated minutes
181 | 
182 |     _planet_radius = FT(planet_radius(param_set))
183 | 
184 |     info = driver_config.config_info
185 |     boundaries = [
186 |         FT(-90.0) FT(-180.0) _planet_radius
187 |         FT(90.0) FT(180.0) FT(_planet_radius + info.domain_height)
188 |     ]
189 |     resolution = (FT(2), FT(2), FT(1000)) # in (deg, deg, m)
190 |     interpol = ClimateMachine.InterpolationConfiguration(
191 |         driver_config,
192 |         boundaries,
193 |         resolution,
194 |     )
195 | 
196 |     dgngrp = setup_atmos_default_diagnostics(
197 |         AtmosGCMConfigType(),
198 |         interval,
199 |         driver_config.name,
200 |         interpol = interpol,
201 |     )
202 | 
203 |     return ClimateMachine.DiagnosticsConfiguration([dgngrp])
204 | end
205 | 
206 | main()
207 | 
--------------------------------------------------------------------------------
/experiments/TestCase/solid_body_rotation_fvm.jl:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env julia --project
  2 | using ClimateMachine
  3 | ClimateMachine.init(parse_clargs = true)
  4 | 
  5 | using ClimateMachine.Atmos
  6 | using ClimateMachine.Orientations
  7 | using ClimateMachine.ConfigTypes
  8 | using ClimateMachine.NumericalFluxes
  9 | using ClimateMachine.Diagnostics
 10 | using ClimateMachine.GenericCallbacks
 11 | using ClimateMachine.ODESolvers
 12 | using ClimateMachine.TurbulenceClosures
 13 | using ClimateMachine.SystemSolvers: ManyColumnLU
 14 | using ClimateMachine.Mesh.Filters
 15 | using ClimateMachine.Mesh.Grids
 16 | using ClimateMachine.Mesh.Interpolation
 17 | using ClimateMachine.Mesh.Topologies
 18 | using Thermodynamics.TemperatureProfiles
 19 | using ClimateMachine.VariableTemplates
 20 | using Thermodynamics: air_density, total_energy
 21 | import ClimateMachine.DGMethods.FVReconstructions: FVLinear
 22 | 
 23 | using LinearAlgebra
 24 | using StaticArrays
 25 | using Test
 26 | 
 27 | using CLIMAParameters
 28 | using CLIMAParameters.Planet: day, planet_radius
 29 | struct EarthParameterSet <: AbstractEarthParameterSet end
 30 | const param_set = EarthParameterSet()
 31 | 
 32 | function init_solid_body_rotation!(problem, bl, state, aux, localgeo, t)
 33 |     FT = eltype(state)
 34 | 
 35 |     # initial velocity profile (we need to transform the vector into the Cartesian
 36 |     # coordinate system)
 37 |     u_0::FT = 0
 38 |     u_sphere = SVector{3, FT}(u_0, 0, 0)
 39 |     u_init = sphr_to_cart_vec(bl.orientation, u_sphere, aux)
 40 |     e_kin::FT = 0.5 * sum(abs2.(u_init))
 41 | 
 42 |     # Assign state variables
 43 |     state.ρ = aux.ref_state.ρ
 44 |     state.ρu = u_init
 45 |     state.energy.ρe = aux.ref_state.ρe + state.ρ * e_kin
 46 | 
 47 |     nothing
 48 | end
 49 | 
 50 | function config_solid_body_rotation(
 51 |     FT,
 52 |     poly_order,
 53 |     fv_reconstruction,
 54 |     resolution,
 55 |     ref_state,
 56 | )
 57 | 
 58 |     # Set up the atmosphere model
 59 |     exp_name = "SolidBodyRotation"
 60 |     domain_height::FT = 30e3 # distance between surface and top of atmosphere (m)
 61 | 
 62 |     physics = AtmosPhysics{FT}(
 63 |         param_set;
 64 |         ref_state = ref_state,
 65 |         turbulence = ConstantKinematicViscosity(FT(0)),
 66 |         #hyperdiffusion = DryBiharmonic(FT(8 * 3600)),
 67 |         moisture = DryModel(),
 68 |     )
 69 |     model = AtmosModel{FT}(
 70 |         AtmosGCMConfigType,
 71 |         physics;
 72 |         init_state_prognostic = init_solid_body_rotation!,
 73 |         source = (Gravity(), Coriolis()),
 74 |     )
 75 | 
 76 |     config = ClimateMachine.AtmosGCMConfiguration(
 77 |         exp_name,
 78 |         poly_order,
 79 |         resolution,
 80 |         domain_height,
 81 |         param_set,
 82 |         init_solid_body_rotation!;
 83 |         model = model,
 84 |         numerical_flux_first_order = RoeNumericalFlux(),
 85 |         fv_reconstruction = HBFVReconstruction(model, fv_reconstruction),
 86 |         #grid_stretching = (SingleExponentialStretching(FT(2.0)),),
 87 |     )
 88 | 
 89 |     return config
 90 | end
 91 | 
 92 | function main()
 93 |     # Driver configuration parameters
 94 |     FT = Float64                             # floating type precision
 95 |     poly_order = (5, 0)                     # discontinuous Galerkin polynomial order
 96 |     n_horz = 8                              # horizontal element number
 97 |     n_vert = 20                               # vertical element number
 98 |     timestart::FT = 0                        # start time (s)
 99 |     timeend::FT = 3600    # end time (s)
100 |     fv_reconstruction = FVLinear()
101 | 
102 |     # Set up a reference state for linearization of equations
103 |     temp_profile_ref =
104 |         DecayingTemperatureProfile{FT}(param_set, FT(290), FT(220), FT(8e3))
105 |     ref_state = HydrostaticState(temp_profile_ref; subtract_off = false)
106 | 
107 |     # Set up driver configuration
108 |     driver_config = config_solid_body_rotation(
109 |         FT,
110 |         poly_order,
111 |         fv_reconstruction,
112 |         (n_horz, n_vert),
113 |         ref_state,
114 |     )
115 | 
116 |     ode_solver_type = ClimateMachine.ExplicitSolverType(
117 |         solver_method = LSRK54CarpenterKennedy,
118 |     )
119 | 
120 |     CFL = FT(0.5) # target acoustic CFL number
121 | 
122 |     # time step is computed such that the horizontal acoustic Courant number is CFL
123 |     solver_config = ClimateMachine.SolverConfiguration(
124 |         timestart,
125 |         timeend,
126 |         driver_config,
127 |         Courant_number = CFL,
128 |         ode_solver_type = ode_solver_type,
129 |         CFL_direction = EveryDirection(),
130 |         diffdir = HorizontalDirection(),
131 |     )
132 | 
133 |     # initialize using a different ref state (mega-hack)
134 |     temp_profile_init =
135 |         DecayingTemperatureProfile{FT}(param_set, FT(280), FT(230), FT(9e3))
136 |     init_ref_state = HydrostaticState(temp_profile_init; subtract_off = false)
137 | 
138 |     init_driver_config = config_solid_body_rotation(
139 |         FT,
140 |         poly_order,
141 |         fv_reconstruction,
142 |         (n_horz, n_vert),
143 |         init_ref_state,
144 |     )
145 |     init_solver_config = ClimateMachine.SolverConfiguration(
146 |         timestart,
147 |         timeend,
148 |         init_driver_config,
149 |         Courant_number = CFL,
150 |         ode_solver_type = ode_solver_type,
151 |         CFL_direction = EveryDirection(),
152 |         diffdir = HorizontalDirection(),
153 |     )
154 | 
155 |     # initialization
156 |     solver_config.Q .= init_solver_config.Q
157 | 
158 |     # Set up diagnostics
159 |     dgn_config = config_diagnostics(FT, driver_config)
160 | 
161 |     # Set up user-defined callbacks
162 |     filterorder = 20
163 |     filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)
164 |     cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do
165 |         Filters.apply!(
166 |             solver_config.Q,
167 |             AtmosFilterPerturbations(driver_config.bl),
168 |             # driver_config.bl,
169 |             solver_config.dg.grid,
170 |             filter,
171 |             # filter perturbations from the initial state
172 |             state_auxiliary = init_solver_config.dg.state_auxiliary,
173 |             direction = HorizontalDirection(),
174 |         )
175 |         nothing
176 |     end
177 | 
178 |     # Run the model
179 |     result = ClimateMachine.invoke!(
180 |         solver_config;
181 |         diagnostics_config = dgn_config,
182 |         user_callbacks = (cbfilter,),
183 |         check_euclidean_distance = false,
184 |     )
185 | 
186 |     relative_error =
187 |         norm(solver_config.Q .- init_solver_config.Q) /
188 |         norm(init_solver_config.Q)
189 |     @info "Relative error = $relative_error"
190 |     @test relative_error < 1e-9
191 | end
192 | 
193 | function config_diagnostics(FT, driver_config)
194 |     interval = "0.5shours" # chosen to allow diagnostics every 30 simulated minutes
195 | 
196 |     _planet_radius = FT(planet_radius(param_set))
197 | 
198 |     info = driver_config.config_info
199 | 
200 |     # Setup diagnostic grid(s)
201 | 
202 |     boundaries = [
203 |         FT(-90.0) FT(-180.0) _planet_radius
204 |         FT(90.0) FT(180.0) FT(_planet_radius + info.domain_height)
205 |     ]
206 | 
207 |     lats = collect(range(boundaries[1, 1], boundaries[2, 1], step = FT(2)))
208 | 
209 |     lons = collect(range(boundaries[1, 2], boundaries[2, 2], step = FT(2)))
210 | 
211 |     lvls = collect(range(
212 |         boundaries[1, 3],
213 |         boundaries[2, 3],
214 |         step = FT(1000), # in m
215 |     ))
216 | 
217 |     interpol = ClimateMachine.InterpolationConfiguration(
218 |         driver_config.grid.topology,
219 |         driver_config,
220 |         boundaries,
221 |         [lats, lons, lvls];
222 |         nr_toler = FT(1e-7),
223 |     )
224 | 
225 |     dgngrp = setup_atmos_default_diagnostics(
226 |         AtmosGCMConfigType(),
227 |         interval,
228 |         driver_config.name,
229 |         interpol = interpol,
230 |     )
231 | 
232 |     return ClimateMachine.DiagnosticsConfiguration([dgngrp])
233 | end
234 | 
235 | main()
236 | 
--------------------------------------------------------------------------------
/experiments/TestCase/solid_body_rotation_mountain.jl:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env julia --project
  2 | using ClimateMachine
  3 | ClimateMachine.init(parse_clargs = true)
  4 | 
  5 | using ClimateMachine.Atmos
  6 | using ClimateMachine.Orientations
  7 | using ClimateMachine.ConfigTypes
  8 | using ClimateMachine.NumericalFluxes
  9 | using ClimateMachine.Diagnostics
 10 | using ClimateMachine.GenericCallbacks
 11 | using ClimateMachine.ODESolvers
 12 | using ClimateMachine.TurbulenceClosures
 13 | using ClimateMachine.SystemSolvers: ManyColumnLU
 14 | using ClimateMachine.Mesh.Filters
 15 | using ClimateMachine.Mesh.Grids
 16 | using ClimateMachine.Mesh.Topologies
 17 | using ClimateMachine.Mesh.Interpolation
 18 | using Thermodynamics.TemperatureProfiles
 19 | using ClimateMachine.VariableTemplates
 20 | using Thermodynamics: air_density, total_energy
 21 | 
 22 | using LinearAlgebra
 23 | using StaticArrays
 24 | 
 25 | using CLIMAParameters
 26 | using CLIMAParameters.Planet: day, planet_radius
 27 | struct EarthParameterSet <: AbstractEarthParameterSet end
 28 | const param_set = EarthParameterSet()
 29 | 
 30 | function set_topofun(f, r_inner, r_outer, topography)
 31 |     function wrapper_topo(a, b, c)
 32 |         return f(
 33 |             a,
 34 |             b,
 35 |             c,
 36 |             max(abs(a), abs(b), abs(c));
 37 |             r_inner = r_inner,
 38 |             r_outer = r_outer,
 39 |             topography = topography,
 40 |         )
 41 |     end
 42 |     return wrapper_topo
 43 | end
 44 | 
 45 | function init_solid_body_rotation!(problem, bl, state, aux, localgeo, t)
 46 |     FT = eltype(state)
 47 | 
 48 |     # initial velocity profile (we need to transform the vector into the Cartesian
 49 |     # coordinate system)
 50 |     u_0::FT = 0
 51 |     u_sphere = SVector{3, FT}(u_0, 0, 0)
 52 |     u_init = sphr_to_cart_vec(bl.orientation, u_sphere, aux)
 53 |     e_kin::FT = 0.5 * sum(abs2.(u_init))
 54 | 
 55 |     # Assign state variables
 56 |     state.ρ = aux.ref_state.ρ
 57 |     state.ρu = u_init
 58 |     state.energy.ρe = aux.ref_state.ρe + state.ρ * e_kin
 59 | 
 60 |     nothing
 61 | end
 62 | 
 63 | function config_solid_body_rotation(FT, poly_order, resolution, ref_state)
 64 | 
 65 |     # Set up the atmosphere model
 66 |     exp_name = "SolidBodyRotation"
 67 |     domain_height::FT = 30e3 # distance between surface and top of atmosphere (m)
 68 | 
 69 |     _planet_radius = FT(planet_radius(param_set))
 70 | 
 71 |     physics = AtmosPhysics{FT}(
 72 |         param_set;
 73 |         ref_state = ref_state,
 74 |         turbulence = ConstantKinematicViscosity(FT(0)),
 75 |         #hyperdiffusion = DryBiharmonic(FT(8 * 3600)),
 76 |         moisture = DryModel(),
 77 |     )
 78 |     model = AtmosModel{FT}(
 79 |         AtmosGCMConfigType,
 80 |         physics;
 81 |         init_state_prognostic = init_solid_body_rotation!,
 82 |         source = (Gravity(), Coriolis()),
 83 |     )
 84 | 
 85 |     config = ClimateMachine.AtmosGCMConfiguration(
 86 |         exp_name,
 87 |         poly_order,
 88 |         resolution,
 89 |         domain_height,
 90 |         param_set,
 91 |         init_solid_body_rotation!;
 92 |         model = model,
 93 |         numerical_flux_first_order = RoeNumericalFlux(),
 94 |         meshwarp = set_topofun(
 95 |             cubed_sphere_topo_warp,                   ## Topography warp function
 96 |             _planet_radius,                       ## Domain inner radius
 97 |             _planet_radius + domain_height,       ## Domain outer radius
 98 |             DCMIPMountain(),                      ## Problem specific dispatch
 99 |         ),
100 |     )
101 | 
102 |     return config
103 | end
104 | 
105 | function main()
106 |     # Driver configuration parameters
107 |     FT = Float64                             # floating type precision
108 |     poly_order = 5                           # discontinuous Galerkin polynomial order
109 |     n_horz = 8                               # horizontal element number
110 |     n_vert = 4                               # vertical element number
111 |     timestart::FT = 0                        # start time (s)
112 |     timeend::FT = 7200
113 | 
114 |     # Set up a reference state for linearization of equations
115 |     temp_profile_ref =
116 |         DecayingTemperatureProfile{FT}(param_set, FT(290), FT(220), FT(8e3))
117 |     ref_state = HydrostaticState(temp_profile_ref)
118 | 
119 |     # Set up driver configuration
120 |     driver_config =
121 |         config_solid_body_rotation(FT, poly_order, (n_horz, n_vert), ref_state)
122 | 
123 |     # Set up experiment
124 |     ode_solver_type = ClimateMachine.IMEXSolverType(
125 |         implicit_model = AtmosAcousticGravityLinearModel,
126 |         implicit_solver = ManyColumnLU,
127 |         solver_method = ARK2GiraldoKellyConstantinescu,
128 |         split_explicit_implicit = false,
129 |         discrete_splitting = true,
130 |     )
131 | 
132 |     CFL = FT(0.2) # target acoustic CFL number
133 | 
134 |     # time step is computed such that the horizontal acoustic Courant number is CFL
135 |     solver_config = ClimateMachine.SolverConfiguration(
136 |         timestart,
137 |         timeend,
138 |         driver_config,
139 |         Courant_number = CFL,
140 |         ode_solver_type = ode_solver_type,
141 |         CFL_direction = HorizontalDirection(),
142 |         diffdir = HorizontalDirection(),
143 |     )
144 | 
145 |     # initialize using a different ref state (mega-hack)
146 |     temp_profile_init =
147 |         DecayingTemperatureProfile{FT}(param_set, FT(280), FT(230), FT(9e3))
148 |     init_ref_state = HydrostaticState(temp_profile_init)
149 | 
150 |     init_driver_config = config_solid_body_rotation(
151 |         FT,
152 |         poly_order,
153 |         (n_horz, n_vert),
154 |         init_ref_state,
155 |     )
156 |     init_solver_config = ClimateMachine.SolverConfiguration(
157 |         timestart,
158 |         timeend,
159 |         init_driver_config,
160 |         Courant_number = CFL,
161 |         ode_solver_type = ode_solver_type,
162 |         CFL_direction = HorizontalDirection(),
163 |         diffdir = HorizontalDirection(),
164 |     )
165 | 
166 |     # initialization
167 |     solver_config.Q .= init_solver_config.Q
168 | 
169 |     # Set up diagnostics
170 |     dgn_config = config_diagnostics(FT, driver_config)
171 | 
172 |     # Set up user-defined callbacks
173 |     filterorder = 20
174 |     filter = ExponentialFilter(solver_config.dg.grid, 0, filterorder)
175 |     cbfilter = GenericCallbacks.EveryXSimulationSteps(1) do
176 |         Filters.apply!(
177 |             solver_config.Q,
178 |             AtmosFilterPerturbations(driver_config.bl),
179 |             solver_config.dg.grid,
180 |             filter,
181 |             # filter perturbations from the initial state
182 |             state_auxiliary = init_solver_config.dg.state_auxiliary,
183 |         )
184 |         nothing
185 |     end
186 | 
187 |     # Run the model
188 |     result = ClimateMachine.invoke!(
189 |         solver_config;
190 |         diagnostics_config = dgn_config,
191 |         user_callbacks = (cbfilter,),
192 |         check_euclidean_distance = false,
193 |     )
194 | 
195 |     relative_error =
196 |         norm(solver_config.Q .- init_solver_config.Q) /
197 |         norm(init_solver_config.Q)
198 |     @info "Relative error = $relative_error"
199 | end
200 | 
201 | function config_diagnostics(FT, driver_config)
202 |     interval = "0.5shours" # chosen to allow diagnostics every 30 simulated minutes
203 | 
204 |     _planet_radius = FT(planet_radius(param_set))
205 | 
206 |     info = driver_config.config_info
207 |     boundaries = [
208 |         FT(-90.0) FT(-180.0) _planet_radius
209 |         FT(90.0) FT(180.0) FT(_planet_radius + info.domain_height)
210 |     ]
211 |     resolution = (FT(2), FT(2), FT(1000)) # in (deg, deg, m)
212 |     interpol = ClimateMachine.InterpolationConfiguration(
213 |         driver_config,
214 |         boundaries,
215 |         resolution,
216 |     )
217 | 
218 |     dgngrp = setup_atmos_default_diagnostics(
219 |         AtmosGCMConfigType(),
220 |         interval,
221 |         driver_config.name,
222 |         interpol = interpol,
223 |     )
224 | 
225 |     return ClimateMachine.DiagnosticsConfiguration([dgngrp])
226 | end
227 | 
228 | main()
229 | 
--------------------------------------------------------------------------------
/xarray/DataDownload.ipynb:
--------------------------------------------------------------------------------
  1 | {
  2 |  "cells": [
  3 |   {
  4 |    "cell_type": "code",
  5 |    "execution_count": 1,
  6 |    "id": "1d33bfd4-71bd-4fe9-ac29-9f1fa1f5afb3",
  7 |    "metadata": {},
  8 |    "outputs": [
  9 |     {
 10 |      "name": "stderr",
 11 |      "output_type": "stream",
 12 |      "text": [
 13 |       "2023-08-12 00:53:06,445 INFO Welcome to the CDS\n",
 14 |       "2023-08-12 00:53:06,448 INFO Sending request to https://cds.climate.copernicus.eu/api/v2/resources/reanalysis-era5-land-monthly-means\n",
 15 |       "2023-08-12 00:53:06,649 INFO Request is queued\n",
 16 |       "2023-08-12 01:05:35,257 INFO Request is completed\n",
 17 |       "2023-08-12 01:05:35,259 INFO Downloading https://download-0011-clone.copernicus-climate.eu/cache-compute-0011/cache/data5/adaptor.mars.internal-1691789677.5640478-20790-2-5a4356b1-f303-4f78-a822-8156ec9b5c60.zip to download.netcdf.zip (73.5M)\n",
 18 |       "2023-08-12 01:19:02,575 ERROR Download interupted: HTTPSConnectionPool(host='download-0011-clone.copernicus-climate.eu', port=443): Read timed out.\n",
 19 |       "2023-08-12 01:19:02,579 ERROR Download incomplete, downloaded 41329664 byte(s) out of 77073404\n",
 20 |       "2023-08-12 01:19:02,580 WARNING Sleeping 10 seconds\n",
 21 |       "2023-08-12 01:19:12,588 WARNING Resuming download at byte 41329664\n",
 22 |       "2023-08-12 01:21:12,237 INFO Download rate 80.3K/s                                                                     \n"
 23 |      ]
 24 |     },
 25 |     {
 26 |      "data": {
 27 |       "text/plain": [
 28 |        "Result(content_length=77073404,content_type=application/zip,location=https://download-0011-clone.copernicus-climate.eu/cache-compute-0011/cache/data5/adaptor.mars.internal-1691789677.5640478-20790-2-5a4356b1-f303-4f78-a822-8156ec9b5c60.zip)"
 29 |       ]
 30 |      },
 31 |      "execution_count": 1,
 32 |      "metadata": {},
 33 |      "output_type": "execute_result"
 34 |     }
 35 |    ],
 36 |    "source": [
 37 |     "import cdsapi\n",
 38 |     "\n",
 39 |     "c = cdsapi.Client()\n",
 40 |     "\n",
 41 |     "c.retrieve(\n",
 42 |     "    'reanalysis-era5-land-monthly-means',\n",
 43 |     "    {\n",
 44 |     "        'product_type': 'monthly_averaged_reanalysis',\n",
 45 |     "        'variable': [\n",
 46 |     "            'leaf_area_index_low_vegetation', 'runoff', 'skin_reservoir_content',\n",
 47 |     "            'skin_temperature', 'snow_cover', 'snow_depth',\n",
 48 |     "            'snow_evaporation', 'snowfall', 'snowmelt',\n",
 49 |     "            'total_evaporation', 'total_precipitation',\n",
 50 |     "        ],\n",
 51 |     "        'year': [\n",
 52 |     "            '2000', '2001', '2002',\n",
 53 |     "            '2003', '2004', '2005',\n",
 54 |     "            '2006', '2007', '2008',\n",
 55 |     "            '2009', '2010', '2011',\n",
 56 |     "            '2012', '2013', '2014',\n",
 57 |     "            '2015', '2016', '2017',\n",
 58 |     "            '2018', '2019', '2020',\n",
 59 |     "            '2021',\n",
 60 |     "        ],\n",
 61 |     "        'month': [\n",
 62 |     "            '01', '02', '03',\n",
 63 |     "            '04', '05', '06',\n",
 64 |     "            '07', '08', '09',\n",
 65 |     "            '10', '11', '12',\n",
 66 |     "        ],\n",
 67 |     "        'time': '00:00',\n",
 68 |     "        'area': [\n",
 69 |     "            41.29, 44.69, 22.51,\n",
 70 |     "            62.13,\n",
 71 |     "        ],\n",
 72 |     "        'format': 'netcdf.zip',\n",
 73 |     "    },\n",
 74 |     "    'download.netcdf.zip')"
 75 |    ]
 76 |   },
 77 |   {
 78 |    "cell_type": "code",
 79 |    "execution_count": null,
 80 |    "id": "927ecb08-c78e-4d15-ad4a-bcb14e4be59d",
 81 |    "metadata": {},
 82 |    "outputs": [],
 83 |    "source": []
 84 |   }
 85 |  ],
 86 |  "metadata": {
 87 |   "kernelspec": {
 88 |    "display_name": "Python 3 (ipykernel)",
 89 |    "language": "python",
 90 |    "name": "python3"
 91 |   },
 92 |   "language_info": {
 93 |    "codemirror_mode": {
 94 |     "name": "ipython",
 95 |     "version": 3
 96 |    },
 97 |    "file_extension": ".py",
 98 |    "mimetype": "text/x-python",
 99 |    "name": "python",
100 |    "nbconvert_exporter": "python",
101 |    "pygments_lexer": "ipython3",
102 |    "version": "3.9.7"
103 |   }
104 |  },
105 |  "nbformat": 4,
106 |  "nbformat_minor": 5
107 | }
108 | 
--------------------------------------------------------------------------------