Source code for thermal_wind

"""Thermal wind exercise code for Mathematical Modelling of Geophysical
Fluids MPE2013 Workshop at African Institute for Mathematical Sciences.
"""
import matplotlib.pyplot as plt
import numpy as np


[docs] def thermal_wind( stn1_profile, stn2_profile, no_motion_depth=None, surface_delta=None, ): """Return the profile of horizontal velocity across the line between 2 density profile stations by integrating the density profiles and using the specified boundary condition (level of no motion, or surface height difference) to calculate the velocity. :arg stn1_profile: Name of file containing the density profile at station 1 :type stn1_profile: string :arg stn2_profile: Name of file containing the density profile at station 2 :type stn2_profile: string :arg no_motion_depth: Depth at which level of no motion boundary condition occurs in [m] :type no_motion_depth: number :arg surface_delta: Surface height difference between the 2 station in [m] :type surface_delta: number :returns: :obj:`(depth, v_vel)` Profile of horizontal velocity across the line between the 2 stations :rtype: tuple of :class:`numpy.ndarray` :raises: :exc:`IOError` if :obj:`stn1_profile` or :obj:`stn2_profile` file cannot be read :raises: :exc:`ValueError` if neither :obj:`no_motion_depth` or :obj:`surface_delta` are specified :raises: :exc:`ValueError` if both :obj:`no_motion_depth` and :obj:`surface_delta` are specified :raises: :exc:`ValueError` if depth intervals in the 2 station density profiles are unequal :raises: :exc:`ValueError` if :obj:`no_motion_depth` exceeds depth of shallowest density profile """ # Geographical constants distance = 36.89e3 # distance between stations in m avg_latitude = 26 # average latitude of stations in degrees # Physical constants acc_grav = 9.8 # m / s^2 rho0 = 1024 # kg / m^3 Omega = 2 * np.pi / (60 * 60 * 24) fo = 2 * Omega * np.sin(np.pi * avg_latitude / 180) # Read the density profiles depth1, rho1 = read_density_profile(stn1_profile) depth2, rho2 = read_density_profile(stn2_profile) # Validate the argument values, read the density profiles, and calculate # depth to which they will be integrated v_vel_depth = validate_inputs( depth1, depth2, no_motion_depth, surface_delta) # Integrate the density from the surface to the depth of the shallowest # profile del_rho = rho2[:v_vel_depth.size] - rho1[:v_vel_depth.size] delta_density = np.empty_like(v_vel_depth) delta_density[0] = del_rho[0] * v_vel_depth[:1] / 2 delta_density[1:-1] = ( del_rho[1:-1] * (v_vel_depth[2:] - v_vel_depth[:-2]) / 2) delta_density[-1] = del_rho[-1] * (v_vel_depth[-1] - v_vel_depth[-2]) sum_delta_density = delta_density.cumsum() # Apply the boundary condition if no_motion_depth is None: surface_effect = acc_grav * surface_delta / (fo * distance) no_motion_effect = 0 else: mask = v_vel_depth >= no_motion_depth i_below = -v_vel_depth[mask].size i_above = i_below + 1 proportion = ( (no_motion_depth - v_vel_depth[i_above]) / (v_vel_depth[i_below] - v_vel_depth[i_above])) sum_delta_density_no_motion = ( sum_delta_density[i_above] + proportion * (sum_delta_density[i_below] - sum_delta_density[i_above])) no_motion_effect = ( -acc_grav / (rho0 * fo) * sum_delta_density_no_motion / distance) surface_effect = 0 # Calculate the horizontal velocity values v_vel = ( surface_effect + no_motion_effect + acc_grav / (rho0 * fo) * sum_delta_density / distance) return v_vel_depth, v_vel
def validate_inputs(depth1, depth2, no_motion_depth, surface_delta): """Validate the inputs of the :func:`thermal_wind` function. * Exactly 1 of :obj:`no_motion_depth` or :obj:`surface_delta` must be specified * Depth intervals must be the same in both profiles * :obj`no_motion_depth` must not exceed the depth of the shallowest profile :arg depth1: Depths of the station 1 density profile :type depth1: :class:`numpy.ndarray` :arg depth2: Depths of the station 2 density profile :type depth2: :class:`numpy.ndarray` :arg no_motion_depth: Depth at which level of no motion boundary condition occurs :type no_motion_depth: number :arg surface_delta: Surface height difference between the 2 station boundary condition :type surface_delta: number :returns max_depth: Average of the deepest 2 levels in the shallowest depth profile :rtype: float :raises: :exc:`ValueError` if neither :obj:`no_motion_depth` or :obj:`surface_delta` are specified :raises: :exc:`ValueError` if both :obj:`no_motion_depth` and :obj:`surface_delta` are specified :raises: :exc:`ValueError` if depth intervals in the 2 station density profiles are unequal :raises: :exc:`ValueError` if :obj:`no_motion_depth` exceeds depth of shallowest density profile """ if (no_motion_depth is None and surface_delta is None or no_motion_depth is not None and surface_delta is not None): raise ValueError( 'Specify either no motion depth or surface height difference') try: if depth1.size >= depth2.size: depth = depth2 if not all(depth1[:depth2.size] == depth2): raise ValueError else: depth = depth1 if not all(depth2[:depth1.size] == depth1): raise ValueError except ValueError: raise ValueError('Depth intervals must be the same in both profiles') if no_motion_depth > depth[-1]: raise ValueError('No motion depth is deeper than data') return depth def read_density_profile(filename): """Return depth and density arrays read from filename. """ depth = [] density = [] with open(filename) as f: for line in interesting_lines(f): deep, rho = map(float, line.split()) depth.append(deep) density.append(rho) return np.array(depth), np.array(density) def interesting_lines(f): for line in f: if line and not line.startswith('#'): yield line
[docs] def plot_velocity_profile(depth, velocity): """Plot the specified velocity component profile. :arg depth: Depths :type depth: :class:`numpy.ndarray` :arg velocity: Velocity component values :type velocity: :class:`numpy.ndarray` """ fig = plt.figure() ax = fig.add_subplot(1, 1, 1) ax.plot(velocity, -depth) ax.set_xlabel('v [m/s]') ax.set_ylabel('z [m]')