def era_dummy_year (bin_dir, last_year, option='era5', nlon=None, nlat=None, out_dir=None, prec=32): bin_dir = real_dir(bin_dir) if out_dir is None: out_dir = bin_dir if nlon is None: if option == 'era5': nlon = 1440 elif option == 'eraint': nlon = 480 else: print 'Error (era_dummy_year): invalid option ' + option sys.exit() if nlat is None: # The same for both cases, assuming ERA5 was cut off at 30S nlat = 241 # Figure out the file paths if option == 'era5': var_names = ['apressure', 'atemp', 'aqh', 'uwind', 'vwind', 'precip', 'swdown', 'lwdown', 'evap'] file_head = 'ERA5_' elif option == 'eraint': var_names = ['msl', 'tmp2m_degC', 'spfh2m', 'u10m', 'v10m', 'rain', 'dsw', 'dlw'] file_head = 'ERAinterim_' for var in var_names: file_in = bin_dir + file_head + var + '_' + str(last_year) # Select the last time index data = read_binary(file_in, [nlon, nlat], 'xyt', prec=prec)[-1,:] file_out = out_dir + file_head + var + '_' + str(last_year+1) write_binary(data, file_out, prec=prec)
def monthly_era5_files (file_head_in, start_year, end_year, file_head_out): grid = ERA5Grid() per_day = 24/6 for year in range(start_year, end_year+1): print 'Processing year ' + str(year) data = read_binary(file_head_in+'_'+str(year), [grid.nx, grid.ny], 'xyt') data_monthly = np.empty([12, grid.ny, grid.nx]) t = 0 for month in range(12): nt = days_per_month(month+1, year)*per_day print 'Indices ' + str(t) + ' to ' + str(t+nt-1) data_monthly[month,:] = np.mean(data[t:t+nt,:], axis=0) t += nt write_binary(data_monthly, file_head_out+'_'+str(year))
def balance_obcs (grid_path, option='balance', obcs_file_w_u=None, obcs_file_e_u=None, obcs_file_s_v=None, obcs_file_n_v=None, d_eta=None, d_t=None, max_deta_dt=0.5, prec=32): if option == 'correct' and (d_eta is None or d_t is None): print 'Error (balance_obcs): must set d_eta and d_t for option="correct"' sys.exit() print 'Building grid' grid = Grid(grid_path) # Calculate integrands of area, scaled by hFacC # Note that dx and dy are only available on western and southern edges of cells respectively; for the eastern and northern boundary, will just have to use 1 cell in. Not perfect, but this correction wouldn't perfectly conserve anyway. # Area of western face = dy*dz*hfac dA_w = xy_to_xyz(grid.dy_w, grid)*z_to_xyz(grid.dz, grid)*grid.hfac # Area of southern face = dx*dz*hfac dA_s = xy_to_xyz(grid.dx_s, grid)*z_to_xyz(grid.dz, grid)*grid.hfac # Now extract the area array at each boundary, and wrap up into a list for easy iteration later dA_bdry = [dA_w[:,:,0], dA_w[:,:,-1], dA_s[:,0,:], dA_s[:,-1,:]] # Some more lists: bdry_key = ['W', 'E', 'S', 'N'] files = [obcs_file_w_u, obcs_file_e_u, obcs_file_s_v, obcs_file_n_v] dimensions = ['yzt', 'yzt', 'xzt', 'xzt'] sign = [1, -1, 1, -1] # Multiply velocity variable by this to get incoming transport # Initialise number of timesteps num_time = None # Integrate the total area of ocean cells on boundaries total_area = 0 for i in range(len(files)): if files[i] is not None: print 'Calculating area of ' + bdry_key[i] + ' boundary' total_area += np.sum(dA_bdry[i]) # Calculate the net transport into the domain if option in ['balance', 'dampen']: # Transport based on OBCS normal velocities if option == 'balance': net_transport = 0 elif option == 'dampen': net_transport = None for i in range(len(files)): if files[i] is not None: print 'Processing ' + bdry_key[i] + ' boundary from ' + files[i] # Read data vel = read_binary(files[i], [grid.nx, grid.ny, grid.nz], dimensions[i], prec=prec) if num_time is None: # Find number of time indices num_time = vel.shape[0] elif num_time != vel.shape[0]: print 'Error (balance_obcs): inconsistent number of time indices between OBCS files' sys.exit() if option == 'dampen' and net_transport is None: # Initialise transport per month net_transport = np.zeros(num_time) if option == 'balance': # Time-average velocity (this is equivalent to calculating the transport at each month and then time-averaging at the end - it's all just sums) vel = np.mean(vel, axis=0) # Integrate net transport through this boundary into the domain, and add to global sum net_transport += np.sum(sign[i]*vel*dA_bdry[i]) elif option == 'dampen': # Integrate net transport at each month for t in range(num_time): net_transport[t] += np.sum(sign[i]*vel[t,:]*dA_bdry[i]) elif option == 'correct': # Transport based on simulated changes in sea surface height # Need area of sea surface dA_sfc = np.sum(grid.dA*np.invert(grid.land_mask).astype(float)) # Calculate transport in m^3/s net_transport = d_eta*dA_sfc/(d_t*sec_per_year) # Inner function to nicely print the net transport to the user def print_net_transport (transport): if transport < 0: direction = 'out of the domain' else: direction = 'into the domain' print 'Net transport is ' + str(abs(transport*1e-6)) + ' Sv ' + direction if option == 'dampen': for t in range(num_time): print 'Month ' + str(t+1) print_net_transport(net_transport[t]) else: print_net_transport(net_transport) if option == 'dampen': # Calculate the acceptable maximum absolute transport # First need total area of sea surface (including cavities) in domain surface_area = np.sum(mask_land(grid.dA, grid)) max_transport = max_deta_dt*surface_area/(sec_per_day*30) print 'Maximum allowable transport is ' + str(max_transport*1e-6) + ' Sv' if np.max(np.abs(net_transport)) <= max_transport: print 'OBCS satisfy this; nothing to do' return # Work out by what factor to dampen the transports scale_factor = max_transport/np.max(np.abs(net_transport)) print 'Will scale transports by ' + str(scale_factor) # Calculate corresponding velocity correction at each month correction = np.zeros(num_time) for t in range(num_time): correction[t] = (scale_factor-1)*net_transport[t]/total_area print 'Month ' + str(t+1) + ': will apply correction of ' + str(correction[t]) + ' m/s to normal velocity at each boundary' else: # Calculate single correction in m/s correction = -1*net_transport/total_area print 'Will apply correction of ' + str(correction) + ' m/s to normal velocity at each boundary' # Now apply the correction for i in range(len(files)): if files[i] is not None: print 'Correcting ' + files[i] # Read all the data again vel = read_binary(files[i], [grid.nx, grid.ny, grid.nz], dimensions[i], prec=prec) # Apply the correction if option == 'dampen': for t in range(num_time): vel[t,:] += sign[i]*correction[t] else: vel += sign[i]*correction # Overwrite the file write_binary(vel, files[i], prec=prec) if option in ['balance', 'dampen']: # Recalculate the transport to make sure it worked if option == 'balance': net_transport_new = 0 elif option == 'dampen': net_transport_new = np.zeros(num_time) for i in range(len(files)): if files[i] is not None: vel = read_binary(files[i], [grid.nx, grid.ny, grid.nz], dimensions[i], prec=prec) if option == 'balance': vel = np.mean(vel, axis=0) net_transport_new += np.sum(sign[i]*vel*dA_bdry[i]) elif option == 'dampen': for t in range(num_time): net_transport_new[t] += np.sum(sign[i]*vel[t,:]*dA_bdry[i]) if option == 'balance': print_net_transport(net_transport_new) elif option == 'dampen': for t in range(num_time): print 'Month ' + str(t+1) print_net_transport(net_transport_new[t])
def calc_load_anomaly (grid, out_file, option='constant', ini_temp_file=None, ini_salt_file=None, ini_temp=None, ini_salt=None, constant_t=-1.9, constant_s=34.4, eosType='MDJWF', rhoConst=1035, tAlpha=None, sBeta=None, Tref=None, Sref=None, hfac=None, prec=64, check_grid=True): errorTol = 1e-13 # convergence criteria # Build the grid if needed if check_grid: grid = choose_grid(grid, None) # Decide which hfac to use if hfac is None: hfac = grid.hfac # Set temperature and salinity if ini_temp is not None and ini_salt is not None: # Deep copy of the arrays temp = np.copy(ini_temp) salt = np.copy(ini_salt) elif ini_temp_file is not None and ini_salt_file is not None: # Read from file temp = read_binary(ini_temp_file, [grid.nx, grid.ny, grid.nz], 'xyz', prec=prec) salt = read_binary(ini_salt_file, [grid.nx, grid.ny, grid.nz], 'xyz', prec=prec) else: print 'Error (calc_load_anomaly): Must either specify ini_temp and ini_salt OR ini_temp_file and ini_salt_file' sys.exit() # Fill in the ice shelves # The bathymetry will get filled too, but that doesn't matter because pressure is integrated from the top down closed = hfac==0 if option == 'constant': # Fill with constant values temp[closed] = constant_t salt[closed] = constant_s elif option == 'nearest': # Select the layer immediately below the ice shelves and tile to make it 3D temp_top = xy_to_xyz(select_top(np.ma.masked_where(closed, temp), return_masked=False), grid) salt_top = xy_to_xyz(select_top(np.ma.masked_where(closed, salt), return_masked=False), grid) # Fill the mask with these values temp[closed] = temp_top[closed] salt[closed] = salt_top[closed] elif option == 'precomputed': for data in [temp, salt]: # Make sure there are no missing values if (data[~closed]==0).any(): print 'Error (calc_load_anomaly): you selected the precomputed option, but there are appear to be missing values in the land mask.' sys.exit() # Make sure it's not a masked array as this will break the rms if isinstance(data, np.ma.MaskedArray): # Fill the mask with zeros data[data.mask] = 0 data = data.data else: print 'Error (calc_load_anomaly): invalid option ' + option sys.exit() # Get vertical integrands considering z at both centres and edges of layers dz_merged = np.zeros(2*grid.nz) dz_merged[::2] = abs(grid.z - grid.z_edges[:-1]) # dz of top half of each cell dz_merged[1::2] = abs(grid.z_edges[1:] - grid.z) # dz of bottom half of each cell # Tile to make 3D z = z_to_xyz(grid.z, grid) dz_merged = z_to_xyz(dz_merged, grid) # Initial guess for pressure (dbar) at centres of cells press = abs(z)*gravity*rhoConst*1e-4 # Iteratively calculate pressure load anomaly until it converges press_old = np.zeros(press.shape) # Dummy initial value for pressure from last iteration rms_error = 0 while True: rms_old = rms_error rms_error = rms(press, press_old) print 'RMS error = ' + str(rms_error) if rms_error < errorTol or np.abs(rms_error-rms_old) < 0.1*errorTol: print 'Converged' break # Save old pressure press_old = np.copy(press) # Calculate density anomaly at centres of cells drho_c = density(eosType, salt, temp, press, rhoConst=rhoConst, Tref=Tref, Sref=Sref, tAlpha=tAlpha, sBeta=sBeta) - rhoConst # Use this for both centres and edges of cells drho = np.zeros(dz_merged.shape) drho[::2,...] = drho_c drho[1::2,...] = drho_c # Integrate pressure load anomaly (Pa) pload_full = np.cumsum(drho*gravity*dz_merged, axis=0) # Update estimate of pressure press = (abs(z)*gravity*rhoConst + pload_full[1::2,...])*1e-4 # Extract pload at each level edge (don't care about centres anymore) pload_edges = pload_full[::2,...] # Now find pload at the ice shelf base # For each xy point, calculate three variables: # (1) pload at the base of the last fully dry ice shelf cell # (2) pload at the base of the cell beneath that # (3) hFacC for that cell # To calculate (1) we have to shift pload_3d_edges upward by 1 cell pload_edges_above = neighbours_z(pload_edges)[0] pload_above = select_top(np.ma.masked_where(closed, pload_edges_above), return_masked=False) pload_below = select_top(np.ma.masked_where(closed, pload_edges), return_masked=False) hfac_below = select_top(np.ma.masked_where(closed, hfac), return_masked=False) # Now we can interpolate to the ice base pload = pload_above + (1-hfac_below)*(pload_below - pload_above) # Write to file write_binary(pload, out_file, prec=prec)
def crash_to_netcdf(crash_dir, grid_path): # Make sure crash_dir is a proper directory if not crash_dir.endswith('/'): crash_dir += '/' # Read the grid grid = Grid(grid_path) # Initialise the NetCDF file ncfile = NCfile(crash_dir + 'crash.nc', grid, 'xyz') # Find all the crash files for file in os.listdir(crash_dir): if file.startswith('stateThetacrash') and file.endswith('.data'): # Found temperature # Read it from binary temp = read_binary(crash_dir + file, grid, 'xyz') # Write it to NetCDF ncfile.add_variable('THETA', temp, 'xyz', units='C') if file.startswith('stateSaltcrash') and file.endswith('.data'): salt = read_binary(crash_dir + file, grid, 'xyz') ncfile.add_variable('SALT', salt, 'xyz', units='psu') if file.startswith('stateUvelcrash') and file.endswith('.data'): u = read_binary(crash_dir + file, grid, 'xyz') ncfile.add_variable('UVEL', u, 'xyz', gtype='u', units='m/s') if file.startswith('stateVvelcrash') and file.endswith('.data'): v = read_binary(crash_dir + file, grid, 'xyz') ncfile.add_variable('VVEL', v, 'xyz', gtype='v', units='m/s') if file.startswith('stateWvelcrash') and file.endswith('.data'): w = read_binary(crash_dir + file, grid, 'xyz') ncfile.add_variable('WVEL', w, 'xyz', gtype='w', units='m/s') if file.startswith('stateEtacrash') and file.endswith('.data'): eta = read_binary(crash_dir + file, grid, 'xy') ncfile.add_variable('ETAN', eta, 'xy', units='m') if file.startswith('stateAreacrash') and file.endswith('.data'): area = read_binary(crash_dir + file, grid, 'xy') ncfile.add_variable('SIarea', area, 'xy', units='fraction') if file.startswith('stateHeffcrash') and file.endswith('.data'): heff = read_binary(crash_dir + file, grid, 'xy') ncfile.add_variable('SIheff', heff, 'xy', units='m') if file.startswith('stateUicecrash') and file.endswith('.data'): uice = read_binary(crash_dir + file, grid, 'xy') ncfile.add_variable('SIuice', uice, 'xy', gtype='u', units='m/s') if file.startswith('stateVicecrash') and file.endswith('.data'): vice = read_binary(crash_dir + file, grid, 'xy') ncfile.add_variable('SIvice', vice, 'xy', gtype='v', units='m/s') if file.startswith('stateQnetcrash') and file.endswith('.data'): qnet = read_binary(crash_dir + file, grid, 'xy') ncfile.add_variable('Qnet', qnet, 'xy', units='W/m^2') if file.startswith('stateMxlcrash') and file.endswith('.data'): mld = read_binary(crash_dir + file, grid, 'xy') ncfile.add_variable('MXLDEPTH', mld, 'xy', units='m') if file.startswith('stateEmpmrcrash') and file.endswith('.data'): empmr = read_binary(crash_dir + file, grid, 'xy') ncfile.add_variable('Empmr', empmr, 'xy', units='kg/m^2/s') ncfile.finished()
def process_forcing_for_correction(source, var, mit_grid_dir, out_file, in_dir=None, start_year=1979, end_year=None): # Set parameters based on source dataset if source == 'ERA5': if in_dir is None: # Path on BAS servers in_dir = '/data/oceans_input/processed_input_data/ERA5/' file_head = 'ERA5_' gtype = ['t', 't', 't', 't', 't'] elif source == 'UKESM': if in_dir is None: # Path on JASMIN in_dir = '/badc/cmip6/data/CMIP6/CMIP/MOHC/UKESM1-0-LL/' expt = 'historical' ensemble_member = 'r1i1p1f2' if var == 'wind': var_names_in = ['uas', 'vas'] gtype = ['u', 'v'] elif var == 'thermo': var_names_in = ['tas', 'huss', 'pr', 'ssrd', 'strd'] gtype = ['t', 't', 't', 't', 't'] days_per_year = 12 * 30 elif source == 'PACE': if in_dir is None: # Path on BAS servers in_dir = '/data/oceans_input/processed_input_data/CESM/PACE_new/' file_head = 'PACE_ens' num_ens = 20 missing_ens = 13 if var == 'wind': var_names_in = ['UBOT', 'VBOT'] monthly = [False, False] elif var == 'thermo': var_names_in = ['TREFHT', 'QBOT', 'PRECT', 'FSDS', 'FLDS'] monthly = [False, False, False, True, True] gtype = ['t', 't', 't', 't', 't'] else: print 'Error (process_forcing_for_correction): invalid source ' + source sys.exit() # Set parameters based on variable type if var == 'wind': var_names = ['uwind', 'vwind'] units = ['m/s', 'm/s'] elif var == 'thermo': var_names = ['atemp', 'aqh', 'precip', 'swdown', 'lwdown'] units = ['degC', '1', 'm/s', 'W/m^2', 'W/m^2'] else: print 'Error (process_forcing_for_correction): invalid var ' + var sys.exit() # Check end_year is defined if end_year is None: print 'Error (process_forcing_for_correction): must set end_year. Typically use 2014 for WSFRIS and 2013 for PACE.' sys.exit() mit_grid_dir = real_dir(mit_grid_dir) in_dir = real_dir(in_dir) print 'Building grids' if source == 'ERA5': forcing_grid = ERA5Grid() elif source == 'UKESM': forcing_grid = UKESMGrid() elif source == 'PACE': forcing_grid = PACEGrid() mit_grid = Grid(mit_grid_dir) ncfile = NCfile(out_file, mit_grid, 'xy') # Loop over variables for n in range(len(var_names)): print 'Processing variable ' + var_names[n] # Read the data, time-integrating as we go data = None num_time = 0 if source == 'ERA5': # Loop over years for year in range(start_year, end_year + 1): file_path = in_dir + file_head + var_names[n] + '_' + str(year) data_tmp = read_binary(file_path, [forcing_grid.nx, forcing_grid.ny], 'xyt') if data is None: data = np.sum(data_tmp, axis=0) else: data += np.sum(data_tmp, axis=0) num_time += data_tmp.shape[0] elif source == ' UKESM': in_files, start_years, end_years = find_cmip6_files( in_dir, expt, ensemble_member, var_names_in[n], 'day') # Loop over each file for t in range(len(in_files)): file_path = in_files[t] print 'Processing ' + file_path print 'Covers years ' + str(start_years[t]) + ' to ' + str( end_years[t]) # Loop over years t_start = 0 # Time index in file t_end = t_start + days_per_year for year in range(start_years[t], end_years[t] + 1): if year >= start_year and year <= end_year: print 'Processing ' + str(year) # Read data print 'Reading ' + str(year) + ' from indices ' + str( t_start) + '-' + str(t_end) data_tmp = read_netcdf(file_path, var_names_in[n], t_start=t_start, t_end=t_end) if data is None: data = np.sum(data_tmp, axis=0) else: data += np.sum(data_tmp, axis=0) num_time += days_per_year # Update time range for next time t_start = t_end t_end = t_start + days_per_year if var_names[n] == 'atemp': # Convert from K to C data -= temp_C2K elif var_names[n] == 'precip': # Convert from kg/m^2/s to m/s data /= rho_fw elif var_names[n] in ['swdown', 'lwdown']: # Swap sign on radiation fluxes data *= -1 elif source == 'PACE': # Loop over years for year in range(start_year, end_year + 1): # Loop over ensemble members data_tmp = None num_ens_tmp = 0 for ens in range(1, num_ens + 1): if ens == missing_ens: continue file_path = in_dir + file_head + str(ens).zfill( 2) + '_' + var_names_in[n] + '_' + str(year) data_tmp_ens = read_binary( file_path, [forcing_grid.nx, forcing_grid.ny], 'xyt') if data_tmp is None: data_tmp = data_tmp_ens else: data_tmp += data_tmp_ens num_ens_tmp += 1 # Ensemble mean for this year data_tmp /= num_ens_tmp # Now accumulate time integral if monthly[n]: # Weighting for different number of days per month for month in range(data_tmp.shape[0]): # Get number of days per month with no leap years ndays = days_per_month(month + 1, 1979) data_tmp[month, :] *= ndays num_time += ndays else: num_time += data_tmp.shape[0] if data is None: data = np.sum(data_tmp, axis=0) else: data += np.sum(data_tmp, axis=0) # Now convert from time-integral to time-average data /= num_time forcing_lon, forcing_lat = forcing_grid.get_lon_lat(gtype=gtype[n], dim=1) # Get longitude in the range -180 to 180, then split and rearrange so it's monotonically increasing forcing_lon = fix_lon_range(forcing_lon) i_split = np.nonzero(forcing_lon < 0)[0][0] forcing_lon = split_longitude(forcing_lon, i_split) data = split_longitude(data, i_split) # Now interpolate to MITgcm tracer grid mit_lon, mit_lat = mit_grid.get_lon_lat(gtype='t', dim=1) print 'Interpolating' data_interp = interp_reg_xy(forcing_lon, forcing_lat, data, mit_lon, mit_lat) print 'Saving to ' + out_file ncfile.add_variable(var_names[n], data_interp, 'xy', units=units[n]) ncfile.close()