├── 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 | [![CodeFactor](https://www.codefactor.io/repository/github/aradfarahani/climate-change/badge)](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 | --------------------------------------------------------------------------------