def horadv(self, vdx_in, b_in, dt): r""" Carry out horizon buoyancy advection into the column model from an adjoining model, for the timestepping solution. This function implements an upwind advection scheme. Parameters ---------- vdx_in : float or ndarray Total advective transport per unit height into the column for the timestepping solution. Positive values indicate transport into the column. Units: m\ :sup:`2`/s b_in : float or ndarray Buoyancy vales from the adjoining module for the timestepping solution. Units: m/s\ :sup:`2` dt : int Numerical timestep over which solution are iterated. Units: s """ vdx_in = make_array(vdx_in, self.z, 'vdx_in') b_in = make_array(b_in, self.z, 'b_in') adv_idx = vdx_in > 0.0 db = b_in - self.b self.b[adv_idx] = self.b[adv_idx] + dt * vdx_in[adv_idx] * db[ adv_idx] / self.Area(self.z[adv_idx])
def __init__( self, y=None, # grid (input) Ks=0., # hor. diffusivity (input) h=50., # ML depth (input) L=4e6, # zonal width (input) surflux=0., # prescribed surface buoyancy flux (in m^2/s^3; input) # Notice that the first and last gridpoints are BCs and should have no flux rest_mask=0., # mask for surface restoring (1 where restoring is applied 0 elsewhere) b_rest=0., # surface buoyancy towards which we are restoring v_pist=1.5 / 86400., # Piston velocity for restoring in SL (input) bs=0.0, # Surface buoyancy (input, output) Psi_s=None # Overturning in the ML (output) ): r""" Parameters ---------- y : ndarray Uniform meridional mixed layer grid. Units: m Ks : float Horizontal diffusivity in the mixed layer. Units: h : float Mixed layer depth. Units: m L : float Zonal length of the mixed layer. Units: m surflux : float, ndarray, or function; optional Prescribed surface buoyancy flux. Units: m\ :sup:`2`/s\ :sup:`3` rest_mask : float, ndarray, or function; optional Surface restoring mask, prescribed as 1 where restoring is desired, and 0 elsewhere. b_rest : float, ndarray, or function; optional Prescribed surface buoyancy profile towards which mixed layer buoyancy is restored. Units: v_pist : float; optional Prescribed piston velocity for buoyancy restoration. Units: m/s bs : float, ndarray, or function; optional Initial meridional surface buoyancy profile in the mixed layer. Units: Psi_s : ndarray; optional Initial overturning transport in the mixed layer. Units: Sv """ # initialize grid: if isinstance(y, np.ndarray): self.y = y else: raise TypeError('y needs to be numpy array providing (regular) grid') self.Ks = Ks self.h = h self.L = L self.surflux = make_array(surflux, self.y, 'surflux') self.rest_mask = make_array(rest_mask, self.y, 'rest_mask') self.b_rest = make_array(b_rest, self.y, 'b_rest') self.v_pist = v_pist self.Psi_s = Psi_s self.bs = make_array(bs, self.y, 'bs')
def Psib(self, nb=500): r""" Remap the overturning streamfunction from physical depth space, into isopycnal space .. math:: \Psi^b\left(b\right) = \int_{-H}^0 \partial_z\Psi\left(z\right)\mathcal{H}\left[b - b_{up}\left(z\right)\right] by computing upwind density classes .. math:: \begin{aligned} b_{up}\left(z\right) = \begin{cases} b_N\left(z\right), & \partial_z\Psi\left(z\right) > 0 \\ b_B\left(z\right), & \partial_z\Psi\left(z\right) < 0 \end{cases} \end{aligned} where :math:`b_N\left(z\right)` is the density profiles in the northern region, :math:`b_B\left(z\right)` is the density profile in the southern basin, and :math:`\mathcal{H}` is the Heaviside step function. Parameters ---------- nb : int; optional Number of upstream density classes into which the streamfunction is to be remapped. Returns ------- psib : ndarray An array representing the values of the overturning streamfunction in each upwind density class. """ # map overturning into isopycnal space: b1 = make_array(self.b1, self.z, 'b1') b2 = make_array(self.b2, self.z, 'b2') bmin = min(np.min(b1), np.min(b2)) bmax = max(np.max(b1), np.max(b2)) self.bgrid = np.linspace(bmin, bmax, nb) udydz = -(self.Psi[1:] - self.Psi[:-1]) psib = 0. * self.bgrid bup_bot = b1[:-1].copy() bup_top = b1[1:].copy() idx = udydz < 0 bup_bot[idx] = b2[:-1][idx] bup_top[idx] = b2[1:][idx] for i in range(0, len(self.bgrid)): mask = np.clip((bup_top - self.bgrid[i]) / (bup_top - bup_bot), 0., 1.) psib[i] = np.sum(mask * udydz) return psib
def test_so_ml_init(self, so_ml_config): if not isinstance(so_ml_config['y'], np.ndarray) or not len( so_ml_config['y']): with pytest.raises(TypeError) as yinfo: SO_ML(**so_ml_config) assert (str(yinfo.value) == "y needs to be numpy array providing (regular) grid") return so_ml = SO_ML(**so_ml_config) for k in [ 'y', 'Ks', 'h', 'L', 'surflux', 'rest_mask', 'b_rest', 'v_pist', 'Psi_s', 'bs' ]: assert hasattr(so_ml, k) so_ml_signature = funcsigs.signature(SO_ML) for k in ['Ks', 'h', 'L', 'v_pist', 'Psi_s']: assert getattr( so_ml, k) == (so_ml_config[k] if k in so_ml_config and so_ml_config[k] else so_ml_signature.parameters[k].default) for k in ['surflux', 'rest_mask', 'b_rest', 'bs']: assert all( getattr(so_ml, k) == make_array(( so_ml_config[k] if k in so_ml_config and so_ml_config[k] else so_ml_signature.parameters[k].default), so_ml.y, k))
def __init__( self, z=None, # grid (input) kappa=None, # diffusivity profile (input) bs=0.025, # surface buoyancy bound. cond (input) bbot=0.0, # bottom buoyancy boundary condition (input) bzbot=None, # bottom strat. as alternative boundary condition (input) b=0.0, # Buoyancy profile (input, output) Area=None, # Horizontal area (can be function of depth) N2min=1e-7 # Minimum strat. for conv adjustment ): r""" Parameters ---------- z : ndarray Vertical depth levels of column grid. Units: m kappa : float, function, or ndarray Vertical diffusivity profile. Units: m\ :sup:`2`/s bs : float Surface level buoyancy boundary condition. Units: m/s\ :sup:`2` bbot : float; optional Bottom level buoyancy boundary condition. Units: m/s\ :sup:`2` bzbot : float; optional Bottom level buoyancy stratification. Can be used as an alternative to **bbot**. Units: s\ :sup:`-2` b : float, function, or ndarray Initial vertical buoyancy profile. Recalculated on model run. Units: m/s Area : float, function, or ndarray Horizontal area of basin. Units: m\ :sup:`2` N2min : float; optional Minimum stratification for convective adjustment. Units: s\ :sup:`-1` """ # initialize grid: if isinstance(z, np.ndarray) and len(z) > 0: self.z = z else: raise TypeError('z needs to be numpy array providing grid levels') self.kappa = make_func(kappa, self.z, 'kappa') self.Area = make_func(Area, self.z, 'Area') self.bs = bs self.bbot = bbot self.bzbot = bzbot self.N2min = N2min self.b = make_array(b, self.z, 'b') if check_numpy_version(): self.bz = np.gradient(self.b, z) else: self.bz = 0. * z # notice that this is just for initialization of ode solver
def vertadvdiff(self, wA, dt, do_conv=False): r""" Calculate and apply the forcing from advection and diffusion on the vertical buoyancy profile, for the timestepping solution. This function implements an upwind advection scheme. Parameters ---------- wA : float or ndarray Area integrated velocity profile for the timestepping solution. Units: m\ :sup:`3`/s dt : int Numerical timestep over which solution are iterated. Units: s """ wA = make_array(wA, self.z, 'wA') dz = self.z[1:] - self.z[:-1] # apply boundary conditions: if not do_conv: # if we use convection, upper BC is already applied there self.b[-1] = self.bs self.b[0] = (self.bbot if self.bzbot is None else self.b[1] - self.bzbot * dz[0]) bz = (self.b[1:] - self.b[:-1]) / dz bz_up = bz[1:] bz_down = bz[:-1] bzz = (bz_up - bz_down) / (0.5 * (dz[1:] + dz[:-1])) #upwind advection: weff = wA - self.dAkappa_dz(self.z) bz = bz_down bz[weff[1:-1] < 0] = bz_up[weff[1:-1] < 0] db_dt = (-weff[1:-1] * bz / self.Area(self.z[1:-1]) + self.kappa(self.z[1:-1]) * bzz) self.b[1:-1] = self.b[1:-1] + dt * db_dt