2020-06-15 15:30:56 +00:00
# toy model for use on stream
# Please give me your Twitch prime sub!
# CLimate Analysis using Digital Estimations (CLAuDE)
import numpy as np
import matplotlib . pyplot as plt
2020-06-24 20:08:16 +00:00
import time , sys , pickle
2020-06-15 15:30:56 +00:00
2020-07-01 20:18:16 +00:00
### CONTROL ###
2020-06-24 20:08:16 +00:00
day = 60 * 60 * 24 # define length of day (used for calculating Coriolis as well) (s)
dt = 60 * 9 # <----- TIMESTEP (s)
resolution = 3 # how many degrees between latitude and longitude gridpoints
2020-07-01 20:18:16 +00:00
nlevels = 4 # how many vertical layers in the atmosphere
2020-06-24 20:08:16 +00:00
planet_radius = 6.4E6 # define the planet's radius (m)
insolation = 1370 # TOA radiation from star (W m^-2)
2020-06-15 15:30:56 +00:00
2020-06-24 20:08:16 +00:00
advection = True # if you want to include advection set this to be True (currently this breaks the model!)
advection_boundary = 7 # how many gridpoints away from poles to apply advection
2020-06-15 15:30:56 +00:00
2020-06-24 20:08:16 +00:00
save = False
2020-07-01 20:18:16 +00:00
load = False
2020-06-15 15:30:56 +00:00
2020-06-24 20:08:16 +00:00
###########################
2020-06-15 15:30:56 +00:00
# define coordinate arrays
lat = np . arange ( - 90 , 91 , resolution )
lon = np . arange ( 0 , 360 , resolution )
nlat = len ( lat )
nlon = len ( lon )
lon_plot , lat_plot = np . meshgrid ( lon , lat )
2020-07-01 20:18:16 +00:00
heights = np . arange ( 0 , 100E3 , 100E3 / nlevels )
2020-06-15 15:30:56 +00:00
# initialise arrays for various physical fields
temperature_planet = np . zeros ( ( nlat , nlon ) ) + 270
2020-07-01 20:18:16 +00:00
temperature_atmosp = np . zeros ( ( nlat , nlon , nlevels ) ) + 270
air_pressure = np . zeros_like ( temperature_atmosp )
u = np . zeros_like ( temperature_atmosp )
v = np . zeros_like ( temperature_atmosp )
w = np . zeros_like ( temperature_atmosp )
air_density = np . zeros_like ( temperature_atmosp ) + 1.3
2020-06-24 20:08:16 +00:00
albedo = np . zeros_like ( temperature_planet ) + 0.5
heat_capacity_earth = np . zeros_like ( temperature_planet ) + 1E7
albedo_variance = 0.001
for i in range ( nlat ) :
for j in range ( nlon ) :
albedo [ i , j ] + = np . random . uniform ( - albedo_variance , albedo_variance )
# if including an ocean, uncomment the below
# albedo[5:55,9:20] = 0.2
# albedo[23:50,45:70] = 0.2
# albedo[2:30,85:110] = 0.2
# heat_capacity_earth[5:55,9:20] = 1E6
# heat_capacity_earth[23:50,45:70] = 1E6
# heat_capacity_earth[2:30,85:110] = 1E6
2020-06-15 15:30:56 +00:00
# define physical constants
2020-07-01 20:18:16 +00:00
epsilon = np . zeros ( nlevels )
epsilon [ 0 ] = 0.75
for i in np . arange ( 1 , nlevels ) :
epsilon [ i ] = epsilon [ i - 1 ] * 0.5
2020-06-17 16:17:11 +00:00
heat_capacity_atmos = 1E7
2020-06-24 20:08:16 +00:00
2020-06-15 15:30:56 +00:00
specific_gas = 287
thermal_diffusivity_air = 20E-6
thermal_diffusivity_roc = 1.5E-6
sigma = 5.67E-8
# define planet size and various geometric constants
circumference = 2 * np . pi * planet_radius
circle = np . pi * planet_radius * * 2
sphere = 4 * np . pi * planet_radius * * 2
# define how far apart the gridpoints are: note that we use central difference derivatives, and so these distances are actually twice the distance between gridboxes
dy = circumference / nlat
dx = np . zeros ( nlat )
coriolis = np . zeros ( nlat ) # also define the coriolis parameter here
angular_speed = 2 * np . pi / day
for i in range ( nlat ) :
dx [ i ] = dy * np . cos ( lat [ i ] * np . pi / 180 )
2020-06-15 21:10:59 +00:00
coriolis [ i ] = angular_speed * np . sin ( lat [ i ] * np . pi / 180 )
2020-07-01 20:18:16 +00:00
dz = np . zeros ( nlevels )
for k in range ( nlevels - 1 ) : dz [ k ] = heights [ k + 1 ] - heights [ k ]
dz [ - 1 ] = dz [ - 2 ]
2020-06-15 15:30:56 +00:00
2020-06-24 20:08:16 +00:00
###################### FUNCTIONS ######################
2020-06-15 15:30:56 +00:00
# define various useful differential functions:
# gradient of scalar field a in the local x direction at point i,j
2020-07-01 20:18:16 +00:00
def scalar_gradient_x ( a , i , j , k = 999 ) :
if k == 999 :
return ( a [ i , ( j + 1 ) % nlon ] - a [ i , ( j - 1 ) % nlon ] ) / dx [ i ]
else :
return ( a [ i , ( j + 1 ) % nlon , k ] - a [ i , ( j - 1 ) % nlon , k ] ) / dx [ i ]
2020-06-24 20:08:16 +00:00
2020-06-15 15:30:56 +00:00
# gradient of scalar field a in the local y direction at point i,j
2020-07-01 20:18:16 +00:00
def scalar_gradient_y ( a , i , j , k = 999 ) :
if k == 999 :
if i == 0 :
return 2 * ( a [ i + 1 , j ] - a [ i , j ] ) / dy
elif i == nlat - 1 :
return 2 * ( a [ i , j ] - a [ i - 1 , j ] ) / dy
else :
return ( a [ i + 1 , j ] - a [ i - 1 , j ] ) / dy
else :
if i == 0 :
return 2 * ( a [ i + 1 , j , k ] - a [ i , j , k ] ) / dy
elif i == nlat - 1 :
return 2 * ( a [ i , j , k ] - a [ i - 1 , j , k ] ) / dy
else :
return ( a [ i + 1 , j , k ] - a [ i - 1 , j , k ] ) / dy
def scalar_gradient_z ( a , i , j , k ) :
if k == 0 :
return ( a [ i , j , k + 1 ] - a [ i , j , k ] ) / dz [ k ]
elif k == nlevels - 1 :
return ( a [ i , j , k ] - a [ i , j , k - 1 ] ) / dz [ k ]
2020-06-15 15:30:56 +00:00
else :
2020-07-01 20:18:16 +00:00
return ( a [ i , j , k + 1 ] - a [ i , j , k - 1 ] ) / ( 2 * dz [ k ] )
2020-06-24 20:08:16 +00:00
2020-07-01 20:18:16 +00:00
# laplacian of scalar field a
2020-06-15 15:30:56 +00:00
def laplacian ( a ) :
output = np . zeros_like ( a )
2020-07-01 20:18:16 +00:00
if output . ndim == 2 :
for i in np . arange ( 1 , nlat - 1 ) :
for j in range ( nlon ) :
output [ i , j ] = ( scalar_gradient_x ( a , i , ( j + 1 ) % nlon ) - scalar_gradient_x ( a , i , ( j - 1 ) % nlon ) / dx [ i ] ) + ( scalar_gradient_y ( a , i + 1 , j ) - scalar_gradient_y ( a , i - 1 , j ) ) / dy
return output
if output . ndim == 3 :
for i in np . arange ( 1 , nlat - 1 ) :
for j in range ( nlon ) :
for k in range ( nlevels - 1 ) :
output [ i , j , k ] = ( scalar_gradient_x ( a , i , ( j + 1 ) % nlon , k ) - scalar_gradient_x ( a , i , ( j - 1 ) % nlon , k ) / dx [ i ] ) + ( scalar_gradient_y ( a , i + 1 , j , k ) - scalar_gradient_y ( a , i - 1 , j , k ) ) / dy + ( scalar_gradient_z ( a , i , j , k + 1 ) - scalar_gradient_z ( a , i , j , k - 1 ) ) / 2 * dz [ k ]
return output
2020-06-24 20:08:16 +00:00
2020-06-15 15:30:56 +00:00
# divergence of (a*u) where a is a scalar field and u is the atmospheric velocity field
def divergence_with_scalar ( a ) :
output = np . zeros_like ( a )
2020-07-01 20:18:16 +00:00
for i in range ( nlat ) :
for j in range ( nlon ) :
for k in range ( nlevels ) :
output [ i , j ] = scalar_gradient_x ( a * u , i , j , k ) + scalar_gradient_y ( a * v , i , j , k ) + scalar_gradient_z ( a * w , i , j , k )
2020-06-15 15:30:56 +00:00
return output
2020-06-24 20:08:16 +00:00
# power incident on (lat,lon) at time t
def solar ( insolation , lat , lon , t ) :
sun_longitude = - t % day
sun_longitude * = 360 / day
value = insolation * np . cos ( lat * np . pi / 180 ) * np . cos ( ( lon - sun_longitude ) * np . pi / 180 )
if value < 0 : return 0
else : return value
#################### SHOW TIME ####################
2020-06-15 15:30:56 +00:00
# set up plot
f , ax = plt . subplots ( 2 , figsize = ( 9 , 9 ) )
f . canvas . set_window_title ( ' CLAuDE ' )
ax [ 0 ] . contourf ( lon_plot , lat_plot , temperature_planet , cmap = ' seismic ' )
2020-07-01 20:18:16 +00:00
ax [ 1 ] . contourf ( lon_plot , lat_plot , temperature_atmosp [ : , : , 0 ] , cmap = ' seismic ' )
2020-06-15 15:30:56 +00:00
plt . subplots_adjust ( left = 0.1 , right = 0.75 )
ax [ 0 ] . set_title ( ' Ground temperature ' )
ax [ 1 ] . set_title ( ' Atmosphere temperature ' )
# allow for live updating as calculations take place
plt . ion ( )
plt . show ( )
2020-06-24 20:08:16 +00:00
# INITIATE TIME
t = 0
if load :
# load in previous save file
temperature_atmosp , temperature_planet , u , v , t , air_density , albedo = pickle . load ( open ( " save_file.p " , " rb " ) )
2020-06-15 15:30:56 +00:00
while True :
2020-06-24 20:08:16 +00:00
initial_time = time . time ( )
2020-06-17 16:17:11 +00:00
2020-06-24 20:08:16 +00:00
if t < 7 * day :
dt = 60 * 47
2020-06-17 16:17:11 +00:00
velocity = False
else :
2020-06-24 20:08:16 +00:00
dt = 60 * 9
2020-06-17 16:17:11 +00:00
velocity = True
2020-06-15 15:30:56 +00:00
2020-06-24 20:08:16 +00:00
# print current time in simulation to command line
print ( " t = " + str ( round ( t / day , 2 ) ) + " days " , end = ' \r ' )
print ( ' U: ' , u . max ( ) , u . min ( ) , ' V: ' , v . max ( ) , v . min ( ) )
2020-06-15 15:30:56 +00:00
# calculate change in temperature of ground and atmosphere due to radiative imbalance
for i in range ( nlat ) :
for j in range ( nlon ) :
2020-07-01 20:18:16 +00:00
temperature_planet [ i , j ] + = dt * ( ( 1 - albedo [ i , j ] ) * solar ( insolation , lat [ i ] , lon [ j ] , t ) + epsilon [ 0 ] * sigma * temperature_atmosp [ i , j , 0 ] * * 4 - sigma * temperature_planet [ i , j ] * * 4 ) / heat_capacity_earth [ i , j ]
for k in range ( nlevels ) :
if k == 0 :
temperature_atmosp [ i , j , k ] + = dt * epsilon [ k ] * sigma * ( temperature_planet [ i , j ] * * 4 + epsilon [ k + 1 ] * temperature_atmosp [ i , j , k + 1 ] * * 4 - 2 * temperature_atmosp [ i , j , k ] * * 4 ) / ( air_density [ i , j , k ] * heat_capacity_atmos )
elif k == nlevels - 1 :
temperature_atmosp [ i , j , k ] + = dt * epsilon [ k ] * sigma * ( epsilon [ k - 1 ] * temperature_atmosp [ i , j , k - 1 ] * * 4 - 2 * temperature_atmosp [ i , j , k ] * * 4 ) / ( air_density [ i , j , k ] * heat_capacity_atmos )
else :
temperature_atmosp [ i , j , k ] + = dt * epsilon [ k ] * sigma * ( epsilon [ k + 1 ] * temperature_atmosp [ i , j , k + 1 ] * * 4 + epsilon [ k - 1 ] * temperature_atmosp [ i , j , k - 1 ] * * 4 - 2 * temperature_atmosp [ i , j , k ] * * 4 ) / ( air_density [ i , j , k ] * heat_capacity_atmos )
2020-06-17 16:17:11 +00:00
2020-06-15 15:30:56 +00:00
# update air pressure
air_pressure = air_density * specific_gas * temperature_atmosp
2020-06-24 20:08:16 +00:00
2020-06-17 16:17:11 +00:00
if velocity :
2020-06-24 20:08:16 +00:00
# introduce temporary arrays to update velocity in the atmosphere
2020-06-17 16:17:11 +00:00
u_temp = np . zeros_like ( u )
v_temp = np . zeros_like ( v )
2020-07-01 20:18:16 +00:00
w_temp = np . zeros_like ( w )
2020-06-15 15:30:56 +00:00
2020-06-17 16:17:11 +00:00
# calculate acceleration of atmosphere using primitive equations on beta-plane
2020-06-24 20:08:16 +00:00
for i in np . arange ( 1 , nlat - 1 ) :
2020-06-17 16:17:11 +00:00
for j in range ( nlon ) :
2020-07-01 20:18:16 +00:00
for k in range ( nlevels ) :
u_temp [ i , j , k ] + = dt * ( - u [ i , j , k ] * scalar_gradient_x ( u , i , j , k ) - v [ i , j , k ] * scalar_gradient_y ( u , i , j , k ) + coriolis [ i ] * v [ i , j , k ] - scalar_gradient_x ( air_pressure , i , j , k ) / air_density [ i , j , k ] )
v_temp [ i , j , k ] + = dt * ( - u [ i , j , k ] * scalar_gradient_x ( v , i , j , k ) - v [ i , j , k ] * scalar_gradient_y ( v , i , j , k ) - coriolis [ i ] * u [ i , j , k ] - scalar_gradient_y ( air_pressure , i , j , k ) / air_density [ i , j , k ] )
w_temp [ i , j , k ] + = 0
2020-06-15 17:24:09 +00:00
2020-06-17 16:17:11 +00:00
u + = u_temp
v + = v_temp
2020-07-01 20:18:16 +00:00
w + = w_temp
2020-06-15 15:30:56 +00:00
2020-06-24 20:08:16 +00:00
u * = 0.99
v * = 0.99
if advection :
# allow for thermal advection in the atmosphere, and heat diffusion in the atmosphere and the ground
atmosp_addition = dt * ( thermal_diffusivity_air * laplacian ( temperature_atmosp ) + divergence_with_scalar ( temperature_atmosp ) )
2020-07-01 20:18:16 +00:00
temperature_atmosp [ advection_boundary : - advection_boundary , : , : ] - = atmosp_addition [ advection_boundary : - advection_boundary , : , : ]
temperature_atmosp [ advection_boundary - 1 , : , : ] - = 0.5 * atmosp_addition [ advection_boundary - 1 , : , : ]
temperature_atmosp [ - advection_boundary , : , : ] - = 0.5 * atmosp_addition [ - advection_boundary , : , : ]
2020-06-24 20:08:16 +00:00
# as density is now variable, allow for mass advection
density_addition = dt * divergence_with_scalar ( air_density )
2020-07-01 20:18:16 +00:00
air_density [ advection_boundary : - advection_boundary , : , : ] - = density_addition [ advection_boundary : - advection_boundary , : , : ]
air_density [ ( advection_boundary - 1 ) , : , : ] - = 0.5 * density_addition [ advection_boundary - 1 , : , : ]
air_density [ - advection_boundary , : , : ] - = 0.5 * density_addition [ - advection_boundary , : , : ]
2020-06-24 20:08:16 +00:00
temperature_planet + = dt * ( thermal_diffusivity_roc * laplacian ( temperature_planet ) )
2020-06-15 15:30:56 +00:00
# update plot
2020-06-24 20:08:16 +00:00
test = ax [ 0 ] . contourf ( lon_plot , lat_plot , temperature_planet , cmap = ' seismic ' )
2020-06-15 15:30:56 +00:00
ax [ 0 ] . set_title ( ' $ \ it {Ground} \ quad \ it {temperature} $ ' )
2020-06-24 20:08:16 +00:00
2020-07-01 20:18:16 +00:00
ax [ 1 ] . contourf ( lon_plot , lat_plot , temperature_atmosp [ : , : , 0 ] , cmap = ' seismic ' )
ax [ 1 ] . streamplot ( lon_plot , lat_plot , u [ : , : , 0 ] , v [ : , : , 0 ] , density = 0.75 , color = ' black ' )
2020-06-15 15:30:56 +00:00
ax [ 1 ] . set_title ( ' $ \ it {Atmospheric} \ quad \ it {temperature} $ ' )
for i in ax :
2020-06-24 20:08:16 +00:00
i . set_xlim ( ( lon . min ( ) , lon . max ( ) ) )
i . set_ylim ( ( lat . min ( ) , lat . max ( ) ) )
2020-06-15 15:30:56 +00:00
i . set_ylabel ( ' Latitude ' )
i . axhline ( y = 0 , color = ' black ' , alpha = 0.3 )
ax [ - 1 ] . set_xlabel ( ' Longitude ' )
cbar_ax = f . add_axes ( [ 0.85 , 0.15 , 0.05 , 0.7 ] )
f . colorbar ( test , cax = cbar_ax )
cbar_ax . set_title ( ' Temperature (K) ' )
f . suptitle ( ' Time ' + str ( round ( 24 * t / day , 2 ) ) + ' hours ' )
plt . pause ( 0.01 )
ax [ 0 ] . cla ( )
ax [ 1 ] . cla ( )
# advance time by one timestep
2020-06-24 20:08:16 +00:00
t + = dt
time_taken = float ( round ( time . time ( ) - initial_time , 3 ) )
print ( ' Time: ' , str ( time_taken ) , ' s ' )
if save :
pickle . dump ( ( temperature_atmosp , temperature_planet , u , v , t , air_density , albedo ) , open ( " save_file.p " , " wb " ) )