diff --git a/Code/AirSeaFluxCode_dev.py b/Code/AirSeaFluxCode_dev.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d07daaedda95cdec13b2ad36727ce9a13cdf99e
--- /dev/null
+++ b/Code/AirSeaFluxCode_dev.py
@@ -0,0 +1,900 @@
+import warnings
+import numpy as np
+import pandas as pd
+import logging
+from hum_subs import (get_hum, gamma)
+from util_subs import *
+from flux_subs import *
+from cs_wl_subs import *
+
+
+class S88:
+    def _wind_iterate(self, ind):
+        if self.gust[0] in range(1, 6):
+            self.wind[ind] = np.sqrt(np.power(np.copy(self.spd[ind]), 2) +
+                                     np.power(get_gust(
+                                         self.gust[1], self.gust[2],
+                                         self.gust[3], self.theta[ind],
+                                         self.usr[ind], self.tsrv[ind],
+                                         self.grav[ind]), 2))
+            if self.meth == "UA":
+                self.u10n[ind] = self.usr[ind]/kappa/np.log(
+                    self.ref10/self.zo[ind])
+            elif self.meth == "C35":
+                self.GustFact = self.wind/self.spd
+                self.u10n[ind] = self.usr[ind]/kappa/self.GustFact[ind]*np.log(
+                    self.ref10/self.zo[ind])
+            elif self.meth == "ecmwf":
+                self.wind[ind] = np.maximum(self.wind[ind], 0.2)
+                self.u10n[ind] = self.usr[ind]/kappa*np.log(
+                    self.ref10/self.zo[ind])
+        else:
+            # initalisation of wind
+            self.wind[ind] = np.copy(self.spd[ind])
+            self.u10n[ind] = self.wind[ind]-self.usr[ind]/kappa*(
+                np.log(self.h_in[0, ind]/self.ref10)-self.psim[ind])
+
+    def get_heights(self, hin, hout=10):
+        self.hout = hout
+        self.hin = hin
+        self.h_in = get_heights(hin, len(self.spd))
+        self.h_out = get_heights(self.hout, 1)
+
+    def get_specHumidity(self, qmeth="Buck2"):
+        self.qair, self.qsea = get_hum(self.hum, self.T, self.SST, self.P,
+                                       qmeth)
+        if (np.all(np.isnan(self.qsea)) or np.all(np.isnan(self.qair))):
+            raise ValueError("qsea and qair cannot be nan")
+        if self.meth in ["NCAR", "ecmwf"]:
+            self.qair = np.maximum(self.qair, 1e-6)
+        self.dq_in = self.qair-self.qsea
+        if self.meth in ["NCAR", "ecmwf"]:
+            self.dq_in = np.maximum(np.abs(self.dq_in), 1e-9)*np.sign(
+                self.dq_in)
+        self.dq_full = self.qair-self.qsea
+
+        # Set lapse rate and Potential Temperature (now we have humdity)
+        if self.meth == "C35":
+            self.cp  = 1004.67*np.ones(self.SST.shape)
+        elif self.meth in ["NCAR", "ecmwf"]:
+            self.cp = 1005+1860*self.qair
+        else:
+            self.cp = 1004.67*(1+0.00084*self.qsea)
+        self.tlapse = gamma("dry", self.SST, self.T, self.qair/1000, self.cp)
+        self.theta = np.copy(self.T)+self.tlapse*self.h_in[1]
+        self.dt_in = self.theta-self.SST
+        if self.meth == "ecmwf":
+            self.dt_in = np.maximum(np.abs(self.dt_in), 1e-6)*np.sign(
+                self.dt_in)
+        self.dt_full = self.theta-self.SST
+
+    def _fix_coolskin_warmlayer(self, wl, cskin, skin, Rl, Rs):
+        skin = self.skin if skin is None else skin
+        assert wl in [0, 1], "wl not valid"
+        assert cskin in [0, 1], "cskin not valid"
+        assert skin in ["C35", "ecmwf", "Beljaars"], "Skin value not valid"
+
+        if ((cskin == 1 or wl == 1) and
+            (np.all(Rl == None) or np.all(np.isnan(Rl))) and
+                ((np.all(Rs == None) or np.all(np.isnan(Rs))))):
+            print("Cool skin/warm layer is switched ON; "
+                  "Radiation input should not be empty")
+            raise
+
+        self.wl = wl
+        self.cskin = cskin
+        self.skin = skin
+        self.Rs = np.full(self.spd.shape, np.nan) if Rs is None else Rs
+        self.Rl = np.full(self.spd.shape, np.nan) if Rl is None else Rl
+
+    def set_coolskin_warmlayer(self, wl=0, cskin=0, skin=None, Rl=None,
+                               Rs=None):
+        wl = 0 if wl is None else wl
+        if hasattr(self, "skin") == False:
+            self.skin = "C35"
+        self._fix_coolskin_warmlayer(wl, cskin, skin, Rl, Rs)
+
+    def _update_coolskin_warmlayer(self, ind):
+        if self.cskin == 1:
+            # self.dter[ind], self.tkt[ind] = cs(np.copy(
+            #     self.SST[ind]), np.copy(self.tkt[ind]), self.rho[ind],
+            #     self.Rs[ind], self.Rnl[ind], self.cp[ind], self.lv[ind],
+            #     self.usr[ind], self.tsr[ind], self.qsr[ind], self.grav[ind],
+            #     self.skin)
+            if self.skin == "C35":
+                self.dter[ind], self.tkt[ind] = cs_C35(np.copy(
+                    self.SST[ind]), self.rho[ind], self.Rs[ind], self.Rnl[ind],
+                    self.cp[ind], self.lv[ind], np.copy(self.tkt[ind]),
+                    self.usr[ind], self.tsr[ind], self.qsr[ind], self.grav[ind])
+
+                # self.dter[ind] = cs_C35(np.copy(
+                #     self.skt[ind]), self.rho[ind], self.Rs[ind], self.Rnl[ind],
+                #     self.cp[ind], self.lv[ind], self.usr[ind], self.tsr[ind],
+                #     self.qsr[ind], self.grav[ind])
+            elif self.skin == "ecmwf":
+                self.dter[ind] = cs_ecmwf(
+                    self.rho[ind], self.Rs[ind], self.Rnl[ind], self.cp[ind],
+                    self.lv[ind], self.usr[ind], self.tsr[ind], self.qsr[ind],
+                    np.copy(self.SST[ind]), self.grav[ind])
+            elif self.skin == "Beljaars":
+                self.Qs[ind], self.dter[ind] = cs_Beljaars(
+                    self.rho[ind], self.Rs[ind], self.Rnl[ind], self.cp[ind],
+                    self.lv[ind], self.usr[ind], self.tsr[ind], self.qsr[ind],
+                    self.grav[ind], np.copy(self.Qs[ind]))
+
+            self.dqer[ind] = get_dqer(self.dter[ind], self.SST[ind],
+                                      self.qsea[ind], self.lv[ind])
+            self.skt[ind] = np.copy(self.SST[ind])+self.dter[ind]
+            self.skq[ind] = np.copy(self.qsea[ind])+self.dqer[ind]
+            if self.wl == 1:
+                self.dtwl[ind] = wl_ecmwf(
+                    self.rho[ind], self.Rs[ind], self.Rnl[ind], self.cp[ind],
+                    self.lv[ind], self.usr[ind], self.tsr[ind], self.qsr[ind],
+                    np.copy(self.SST[ind]), np.copy(self.skt[ind]),
+                    np.copy(self.dter[ind]), self.grav[ind])
+                self.skt[ind] = (np.copy(self.SST[ind])+self.dter[ind] +
+                                 self.dtwl[ind])
+                self.dqer[ind] = get_dqer(self.dter[ind], self.skt[ind],
+                                          self.qsea[ind], self.lv[ind])
+                self.skq[ind] = np.copy(self.qsea[ind])+self.dqer[ind]
+        else:
+            self.dter[ind] = np.zeros(self.SST[ind].shape)
+            self.dqer[ind] = np.zeros(self.SST[ind].shape)
+            self.dtwl[ind] = np.zeros(self.SST[ind].shape)
+            self.tkt[ind] = np.zeros(self.SST[ind].shape)
+
+    def _first_guess(self):
+        # reference height
+        self.ref10 = 10
+
+        #  first guesses
+        self.t10n, self.q10n = np.copy(self.theta), np.copy(self.qair)
+        self.rho = self.P*100/(287.1*self.t10n*(1+0.6077*self.q10n))
+        self.lv = (2.501-0.00237*(self.SST-CtoK))*1e6  # J/kg
+
+        #  Zeng et al. 1998
+        self.tv = self.theta*(1+0.6077*self.qair)   # virtual potential T
+        self.dtv = self.dt_in*(1+0.6077*self.qair)+0.6077*self.theta*self.dq_in
+
+        # Set the wind array
+        self.wind = np.sqrt(np.power(np.copy(self.spd), 2)+0.25)
+        self.GustFact = self.wind*0+1
+        # Rb eq. 11 Grachev & Fairall 1997, use air temp height
+        # use self.tv??  adjust wind to T-height?
+        Rb = self.grav*self.h_in[1]*self.dtv/(self.T*np.power(self.wind, 2))
+        # eq. 12 Grachev & Fairall 1997   # DO.THIS
+        self.monob = self.h_in[1]/12.0/Rb
+        # if self.meth == "UA":
+        #     self.usr = 0.06
+        #     for i in range(6):
+        #         self.zo = 0.013*self.usr*self.usr/self.grav+0.11*visc_air(self.T)/self.usr
+        #         self.usr = kappa*self.wind/np.log(self.h_in[0]/self.zo)
+        #     Rb = self.grav*self.h_in[0]*self.dtv/(self.tv*self.wind*self.wind)
+        #     zol = np.where(Rb >= 0, Rb*np.log(self.h_in[0]/self.zo) /
+        #                    (1-5*np.minimum(Rb, 0.19)),
+        #                 Rb*np.log(self.h_in[0]/self.zo))
+        #     self.monob = self.h_in[0]/zol
+        # elif self.meth == "C35":
+        #     self.usr = 0.035*self.wind*np.log(10/1e-4)/np.log(self.h_in[0]/1e-4)
+        #     self.zo = 0.011*self.usr**2/self.grav+0.11*visc_air(self.T)/self.usr
+        #     Cd10 = (kappa/np.log(10/self.zo))**2
+        #     Ch10 = 0.00115
+        #     Ct10 = Ch10/np.sqrt(Cd10)
+        #     self.zot = 10/np.exp(kappa/Ct10)
+        #     Cd = (kappa/np.log(self.h_in[0]/self.zo))**2
+        #     Ct = kappa/np.log(self.h_in[1]/self.zot)
+        #     CC = kappa*Ct/Cd
+        #     Ribcu = -self.h_in[0]/self.gust[2]/0.004/self.gust[1]**3
+        #     Rb  = -1*self.grav*self.h_in[0]/(self.T)*(
+        #         (-self.dt_in)-0.61*(self.T)*self.dq_in)/self.wind**2
+        #     zol = CC*Rb*(1+27/9*Rb/CC)
+        #     k50 = np.where(zol > 50) # stable with very thin M-O length relative to zu
+        #     zol = np.where(Rb < 0, CC*Rb/(1+Rb/Ribcu), CC*Rb*(1+27/9*Rb/CC))
+        #     self.monob = self.h_in[0]/zol
+        # ------------
+
+        # dummy_array = lambda val : np.full(self.T.shape, val)*self.msk
+        def dummy_array(val): return np.full(self.T.shape, val)*self.msk
+        if self.cskin + self.wl > 0:
+            self.dter, self.tkt, self.dtwl = [
+                dummy_array(x) for x in (-0.3, 0.001, 0.3)]
+            self.dqer = get_dqer(self.dter, self.SST, self.qsea,
+                                 self.lv)
+            self.Rnl = 0.97*(self.Rl-5.67e-8*np.power(
+                self.SST-0.3*self.cskin, 4))
+            self.Qs = 0.945*self.Rs
+        else:
+            self.dter, self.dqer, self.dtwl = [
+                dummy_array(x) for x in (0.0, 0.0, 0.0)]
+            self.Rnl, self.Qs, self.tkt = [
+                np.empty(self.arr_shp)*self.msk for _ in range(3)]
+        self.skt = np.copy(self.SST)
+        self.skq = np.copy(self.qsea)
+
+        self.u10n = np.copy(self.wind)
+        self.usr = 0.035*self.u10n
+        self.cd10n, self.zo = cdn_calc(
+            self.u10n, self.usr, self.theta, self.grav, self.meth)
+        self.psim = psim_calc(self.h_in[0]/self.monob, self.meth)
+        self.cd = cd_calc(self.cd10n, self.h_in[0], self.ref10, self.psim)
+        self.usr =np.sqrt(self.cd*np.power(self.wind, 2))
+        self.zot, self.zoq, self.tsr, self.qsr = [
+            np.empty(self.arr_shp)*self.msk for _ in range(4)]
+        self.ct10n, self.cq10n, self.ct, self.cq = [
+            np.empty(self.arr_shp)*self.msk for _ in range(4)]
+        self.tv10n = self.zot
+
+    def iterate(self, maxiter=10, tol=None):
+        if maxiter < 5:
+            warnings.warn("Iteration number <5 - resetting to 5.")
+            maxiter = 5
+
+        # Decide which variables to use in tolerances based on tolerance
+        # specification
+        tol = ['all', 0.01, 0.01, 1e-05, 1e-3,
+               0.1, 0.1] if tol is None else tol
+        assert tol[0] in ['flux', 'ref', 'all'], "unknown tolerance input"
+
+        old_vars = {"flux": ["tau", "sensible", "latent"],
+                    "ref": ["u10n", "t10n", "q10n"]}
+        old_vars["all"] = old_vars["ref"] + old_vars["flux"]
+        old_vars = old_vars[tol[0]]
+
+        new_vars = {"flux": ["tau", "sensible", "latent"],
+                    "ref": ["u10n", "t10n", "q10n"]}
+        new_vars["all"] = new_vars["ref"] + new_vars["flux"]
+        new_vars = new_vars[tol[0]]
+        # extract tolerance values by deleting flag from tol
+        tvals = np.delete(np.copy(tol), 0)
+        tol_vals = list([float(tt) for tt in tvals])
+
+        ind = np.where(self.spd > 0)
+        it = 0
+
+        # Setup empty arrays
+        self.tsrv, self.psim, self.psit, self.psiq = [
+            np.zeros(self.arr_shp)*self.msk for _ in range(4)]
+
+
+        # extreme values for first comparison
+        dummy_array = lambda val : np.full(self.T.shape, val)*self.msk
+        # you can use def instead of lambda
+        # def dummy_array(val): return np.full(self.arr_shp, val)*self.msk
+        self.itera, self.tau, self.sensible, self.latent = [
+            dummy_array(x) for x in (-1, 1e+99, 1e+99, 1e+99)]
+
+        # Generate the first guess values
+        self._first_guess()
+
+        #  iteration loop
+        ii = True
+        while ii & (it < maxiter):
+            it += 1
+
+            # Set the old variables (for comparison against "new")
+            old = np.array([np.copy(getattr(self, i)) for i in old_vars])
+
+            # Calculate cdn
+            self.cd10n[ind], self.zo[ind] = cdn_calc(
+                self.u10n[ind], self.usr[ind], self.theta[ind], self.grav[ind],
+                self.meth)
+
+            if np.all(np.isnan(self.cd10n)):
+                logging.info('break %s at iteration %s cd10n<0', meth, it)
+                break
+
+            self.psim[ind] = psim_calc(
+                self.h_in[0, ind]/self.monob[ind], self.meth)
+            self.cd[ind] = cd_calc(
+                self.cd10n[ind], self.h_in[0, ind], self.ref10, self.psim[ind])
+            # Update the wind values
+            self._wind_iterate(ind)
+
+            # temperature
+            self.ct10n[ind], self.zot[ind] = ctqn_calc(
+                "ct", self.h_in[1, ind]/self.monob[ind], self.cd10n[ind],
+                self.usr[ind], self.zo[ind], self.theta[ind], self.meth)
+            self.psit[ind] = psit_calc(
+                self.h_in[1, ind]/self.monob[ind], self.meth)
+            self.ct[ind] = ctq_calc(
+                self.cd10n[ind], self.cd[ind], self.ct10n[ind],
+                self.h_in[1, ind], self.ref10, self.psit[ind])
+
+            # humidity
+            self.cq10n[ind], self.zoq[ind] = ctqn_calc(
+                "cq", self.h_in[2, ind]/self.monob[ind], self.cd10n[ind],
+                self.usr[ind], self.zo[ind], self.theta[ind], self.meth)
+            self.psiq[ind] = psit_calc(
+                self.h_in[2, ind]/self.monob[ind], self.meth)
+            self.cq[ind] = ctq_calc(
+                self.cd10n[ind], self.cd[ind], self.cq10n[ind],
+                self.h_in[2, ind], self.ref10, self.psiq[ind])
+
+            # Some parameterizations set a minimum on parameters
+            try:
+                self._minimum_params()
+            except AttributeError:
+                pass
+
+            self.dt_full[ind] = self.dt_in[ind] - \
+                self.dter[ind]*self.cskin - self.dtwl[ind]*self.wl
+            self.dq_full[ind] = self.dq_in[ind] - self.dqer[ind]*self.cskin
+            self.usr[ind], self.tsr[ind], self.qsr[ind] = get_strs(
+                self.h_in[:, ind], self.monob[ind], self.wind[ind],
+                self.zo[ind], self.zot[ind], self.zoq[ind], self.dt_full[ind],
+                self.dq_full[ind], self.cd[ind], self.ct[ind], self.cq[ind],
+                self.meth)
+
+            # Update CS/WL parameters
+            self._update_coolskin_warmlayer(ind)
+
+            # Logging output
+            log_vars = {"dter": 2, "dqer": 7, "tkt": 2,
+                        "Rnl": 2, "usr": 3, "tsr": 4, "qsr": 7}
+            log_vars = [np.round(np.nanmedian(getattr(self, V)), R)
+                        for V, R in log_vars.items()]
+            log_vars.insert(0, self.meth)
+            logging.info(
+                'method {} | dter = {} | dqer = {} | tkt = {} | Rnl = {} |'
+                ' usr = {} | tsr = {} | qsr = {}'.format(*log_vars))
+
+            if self.cskin + self.wl > 0:
+                self.Rnl[ind] = 0.97*(self.Rl[ind]-5.67e-8 *
+                                      np.power(self.SST[ind] +
+                                               self.dter[ind]*self.cskin, 4))
+            # not sure how to handle lapse/potemp
+            # well-mixed in potential temperature ...
+            self.t10n[ind] = self.theta[ind]-self.tlapse[ind]*self.ref10 - \
+                self.tsr[ind]/kappa * \
+                (np.log(self.h_in[1, ind]/self.ref10)-self.psit[ind])
+            self.q10n[ind] = self.qair[ind]-self.qsr[ind]/kappa * \
+                (np.log(self.h_in[2, ind]/self.ref10)-self.psiq[ind])
+
+            # update stability info
+            self.tsrv[ind] = get_tsrv(
+                self.tsr[ind], self.qsr[ind], self.theta[ind], self.qair[ind])
+            self.Rb[ind] = get_Rb(
+                self.grav[ind], self.usr[ind], self.h_in[0, ind],
+                self.h_in[1, ind], self.tv[ind], self.dtv[ind], self.wind[ind],
+                self.monob[ind], self.meth)
+            if self.L == "tsrv":
+                self.monob[ind] = get_Ltsrv(
+                    self.tsrv[ind], self.grav[ind], self.tv[ind],
+                    self.usr[ind])
+            else:
+                self.monob[ind] = get_LRb(
+                    self.Rb[ind], self.h_in[1, ind], self.monob[ind],
+                    self.zo[ind], self.zot[ind], self.meth)
+
+            # Update the wind values
+            self._wind_iterate(ind)
+
+            # make sure you allow small negative values convergence
+            if it == 1:
+                self.u10n = np.where(self.u10n < 0, 0.5, self.u10n)
+
+            self.itera[ind] = np.full(1, it)
+            if self.meth in ["NCAR", "ecmwf"]:
+                self.rho = rho_air(self.theta-self.tlapse*self.h_in[0],
+                                   self.qair, self.P*100)
+                self.rho = rho_air(self.theta-self.tlapse*self.h_in[0],
+                                   self.qair,
+                                   self.P*100-self.rho*self.grav*self.h_in[0])
+                self.zUrho = self.wind*np.maximum(self.rho, 1)
+                self.tau = self.zUrho*self.cd*self.spd
+                self.sensible = self.zUrho*self.ct*(self.theta-self.SST)*self.cp
+                self.latent = self.zUrho*self.cq*(self.qair-self.qsea)*self.lv
+            else:
+                self.tau = self.rho*np.power(self.usr, 2)*self.spd/self.wind
+                self.sensible = self.rho*self.cp*self.usr*self.tsr
+                self.latent = self.rho*self.lv*self.usr*self.qsr
+
+            # Set the new variables (for comparison against "old")
+            new = np.array([np.copy(getattr(self, i)) for i in new_vars])
+
+            if it > 2:  # force at least three iterations
+                d = np.abs(new-old)  # change over this iteration
+                for ii in range(0, len(tol_vals)):
+                    d[ii, ] = d[ii, ]/tol_vals[ii]  # ratio to tolerance
+                # identifies non-convergence
+                ind = np.where(d.max(axis=0) >= 1)
+
+            self.ind = np.copy(ind)
+            ii = False if (ind[0].size == 0) else True
+            # End of iteration loop
+
+        self.itera[ind] = -1
+        self.itera = np.where(self.itera > maxiter, -1, self.itera)
+        logging.info('method %s | # of iterations:%s', self.meth, it)
+        logging.info('method %s | # of points that did not converge :%s \n',
+                     self.meth, self.ind[0].size)
+
+    def _get_humidity(self):
+        """Calculate RH used for flagging purposes & output."""
+        if self.hum[0] in ('rh', 'no'):
+            self.rh = self.hum[1]
+        elif self.hum[0] == 'Td':
+            Td = self.hum[1]  # dew point temperature (K)
+            Td = np.where(Td < 200, np.copy(Td)+CtoK, np.copy(Td))
+            T = np.where(self.T < 200, np.copy(self.T)+CtoK, np.copy(self.T))
+            # T = np.copy(self.T)
+            esd = 611.21*np.exp(17.502*((Td-CtoK)/(Td-32.19)))
+            es = 611.21*np.exp(17.502*((T-CtoK)/(T-32.19)))
+            self.rh = 100*esd/es
+        elif self.hum[0] == "q":
+            es = 611.21*np.exp(17.502*((self.T-CtoK)/(self.T-32.19)))
+            e = self.qair*self.P/(0.378*self.qair+0.622)
+            self.rh = 100*e/es
+
+    def _flag(self, out=0):
+        miss = np.copy(self.msk)  # define missing input points
+        if self.cskin == 1:
+            miss = np.where(np.isnan(self.msk+self.P+self.Rs+self.Rl),
+                            np.nan, 1)
+        else:
+            miss = np.where(np.isnan(self.msk+self.P), np.nan, 1)
+        flag = set_flag(miss, self.rh, self.u10n, self.q10n, self.t10n,
+                        self.Rb, self.hin, self.monob, self.itera, out=out)
+
+        self.flag = flag
+
+    def get_output(self, out_var=None, out=0):
+
+        assert out in [0, 1], "out must be either 0 or 1"
+
+        self._get_humidity()  # Get the Relative humidity
+        self._flag(out=out)  # Get flags
+
+        self.GFo = apply_GF(self.gust, self.spd, self.wind, "u")
+        self.u10n = self.spd-self.usr/kappa/self.GFo*(
+            np.log(self.h_in[0]/self.ref10)-self.psim) # C.4-7
+        self.uref = self.spd-self.usr/kappa/self.GFo * \
+            (np.log(self.h_in[0]/self.h_out[0])-self.psim +
+             psim_calc(self.h_out[0]/self.monob, self.meth))
+
+        self.GustFact = self.wind/self.spd
+        self.usr_gust = np.copy(self.usr)
+        self.usr_nogust = self.usr/self.GFo
+        # include lapse rate adjustment as theta is well-mixed
+        self.tref = self.theta-self.tlapse*self.h_out[1]-self.tsr/kappa * \
+            (np.log(self.h_in[1]/self.h_out[1])-self.psit +
+             psit_calc(self.h_out[1]/self.monob, self.meth))
+        self.qref = self.qair-self.qsr/kappa * \
+            (np.log(self.h_in[2]/self.h_out[2])-self.psiq +
+             psit_calc(self.h_out[2]/self.monob, self.meth))
+        self.psim_ref = psim_calc(self.h_out[0]/self.monob, self.meth)
+        self.psit_ref = psit_calc(self.h_out[1]/self.monob, self.meth)
+        self.psiq_ref = psit_calc(self.h_out[2]/self.monob, self.meth)
+
+        if self.meth == "UA":
+            self.uref = np.where(
+                self.ref10/self.monob < 0, self.spd+(self.usr/kappa)*(
+                    np.log(self.ref10/self.h_in[0])-(psim_calc(
+                        self.ref10/self.monob, self.meth) -
+                        psim_calc(self.h_in[0]/self.monob, self.meth))),
+                self.spd+(self.usr/kappa)*(np.log(self.ref10/self.h_in[0]) +
+                                           5*self.ref10/self.monob -
+                                            5*self.h_in[0]/self.monob))
+        elif self.meth == "C35":
+            self.uref = self.spd+self.usr/kappa/self.GustFact*(
+                np.log(self.h_out[0]/self.h_in[0]) -
+                psim_calc(self.h_out[0]/self.monob, self.meth) +
+                psim_calc(self.h_in[0]/self.monob, self.meth))
+            self.u10n = ((self.wind+self.usr/kappa*(
+                np.log(self.ref10/self.h_in[0]) -
+                psim_calc(self.ref10/self.monob, self.meth) +
+                psim_calc(self.h_in[0]/self.monob, self.meth)))/self.GustFact +
+                psim_calc(self.ref10/self.monob, self.meth) *
+                self.usr/kappa/self.GustFact)
+            self.tref = (self.T+self.tsr/kappa*(
+                np.log(self.h_out[1]/self.h_in[1]) -
+                psit_calc(self.h_out[1]/self.monob, self.meth) +
+                psit_calc(self.h_in[1]/self.monob, self.meth)) +
+                self.tlapse*(self.h_in[1]-self.h_out[1]))
+            self.t10n = self.tref + \
+                psit_calc(self.ref10/self.monob, self.meth)*self.tsr/kappa
+            self.qref = self.qair+self.qsr/kappa*(
+                np.log(self.h_out[2]/self.h_in[2])-psit_calc(
+                    self.h_out[2]/self.monob, self.meth)+psit_calc(
+                        self.h_in[1]/self.monob, self.meth))
+            self.q10n = self.qref + \
+                psit_calc(self.ref10/self.monob, self.meth)*self.qsr/kappa
+        elif self.meth == "ecmwf":
+            self.u10n = self.usr/kappa*np.log(self.ref10/self.zo)
+
+        if self.wl == 0:
+            self.dtwl = np.zeros(self.T.shape)*self.msk
+            # reset to zero if not used
+
+        # Do not calculate lhf if a measure of humidity is not input
+        # This gets filled into a pd dataframe and so no need to specify
+        # dimension of array
+        if self.hum[0] == 'no':
+            self.latent, self.qsr, self.q10n = np.empty(3)
+            self.qref, self.qair, self.rh = np.empty(3)
+
+        # Set the final wind speed values
+        # this seems to be gust (was wind_speed)
+        self.ug = np.sqrt(np.power(self.wind, 2)-np.power(self.spd, 2))
+
+        # Get class specific flags (will only work if self.u_hi and self.u_lo
+        # have been set in the class)
+        try:
+            self._class_flag()
+        except AttributeError:
+            pass
+
+        # Combine all output variables into a pandas array
+        res_vars = get_outvars(out_var, self.cskin, self.gust)
+
+
+        res = np.zeros((len(res_vars), len(self.spd)))
+        for i, value in enumerate(res_vars):
+            res[i][:] = getattr(self, value)
+
+        if out == 0:
+            res[:, self.ind] = np.nan
+            # set missing values where data have non acceptable values
+            if self.hum[0] != 'no':
+                res = np.asarray([
+                    np.where(self.q10n < 0, np.nan, res[i][:])
+                    for i in range(len(res_vars))])
+                # len(res_vars)-1 instead of len(res_vars) in order to keep
+                # itera= -1 for no convergence
+            res = np.asarray([
+                np.where(self.u10n < 0, np.nan, res[i][:])
+                for i in range(len(res_vars))])
+            res = np.asarray([
+                np.where(((self.t10n < 173) | (self.t10n > 373)), np.nan,
+                          res[i][:])
+                for i in range(len(res_vars))])
+        else:
+            warnings.warn("Warning: the output will contain values for points"
+                          " that have not converged and negative values "
+                          "(if any) for u10n/q10n")
+
+        resAll = pd.DataFrame(data=res.T, index=range(self.nlen),
+                              columns=res_vars)
+        if "itera" in res_vars:
+            resAll["itera"] = self.itera  # restore itera
+
+        resAll["flag"] = self.flag
+
+        return resAll
+
+    def add_variables(self, spd, T, SST, SST_fl, cskin=0, lat=None, hum=None,
+                      P=None, L=None):
+        # Add the mandatory variables
+        assert type(spd) == type(T) == type(
+            SST) == np.ndarray, "input type of spd, T and SST should be"
+        " numpy.ndarray"
+        if self.meth in ["S80", "S88", "LP82", "YT96", "UA", "NCAR"]:
+            assert SST_fl == "bulk", "input SST should be skin for method "+self.meth
+        if self.meth in ["C30", "C35", "ecmwf", "Beljaars"]:
+            if cskin == 1:
+                assert SST_fl == "bulk", "input SST should be bulk with cool skin correction switched on for method "+self.meth
+            else:
+                assert SST_fl == "skin", "input SST should be skin for method "+self.meth
+        self.L = "tsrv" if L is None else L
+        self.arr_shp = spd.shape
+        self.nlen = len(spd)
+        self.spd = spd
+        if self.meth == "NCAR":
+            self.spd = np.maximum(np.copy(self.spd), 0.5)
+        self.T = np.where(T < 200, np.copy(T)+CtoK, np.copy(T))
+        if self.meth in ["NCAR", "ecmwf"]:
+            self.T = np.maximum(self.T, 180)
+        self.hum = ['no', np.full(SST.shape, 80)] if hum is None else hum
+        self.SST = np.where(SST < 200, np.copy(SST)+CtoK, np.copy(SST))
+        self.lat = np.full(self.arr_shp, 45) if lat is None else lat
+        self.grav = gc(self.lat)
+        self.P = np.full(self.nlen, 1013) if P is None else P
+
+        # mask to preserve missing values when initialising variables
+        self.msk = np.empty(SST.shape)
+        self.msk = np.where(np.isnan(spd+T+SST), np.nan, 1)
+        self.Rb = np.empty(SST.shape)*self.msk
+
+    def add_gust(self, gust=None):
+        if np.all(gust is None):
+            try:
+                gust = self.default_gust
+            except AttributeError:
+                gust = [0, 0, 0, 0]  # gustiness OFF
+                # gust = [1, 1.2, 800]
+        elif ((np.size(gust) < 3) and (gust == 0)):
+            gust = [0, 0, 0, 0]
+
+        assert np.size(gust) == 4, "gust input must be a 4x1 array"
+        assert gust[0] in range(6), "gust at position 0 must be 0 to 5"
+        self.gust = gust
+
+    def _class_flag(self):
+        """A flag specific to this class - only used for certain classes where
+         u_lo and u_hi are defined"""
+        self.flag = np.where(((self.u10n < self.u_lo[0]) |
+                              (self.u10n > self.u_hi[0])) &
+                             (self.flag == "n"), "o",
+                             np.where(((self.u10n < self.u_lo[1]) |
+                                       (self.u10n > self.u_hi[1])) &
+                                      ((self.flag != "n") & (np.char.find(
+                                          self.flag.astype(str), 'u') == -1) &
+                                       (np.char.find(
+                                           self.flag.astype(str), 'q') == -1)),
+                                      self.flag+[","]+["o"], self.flag))
+
+    def __init__(self):
+        self.meth = "S88"
+
+
+class S80(S88):
+
+    def __init__(self):
+        self.meth = "S80"
+        self.u_lo = [6, 6]
+        self.u_hi = [22, 22]
+
+
+class YT96(S88):
+    # def _minimum_params(self):
+    #     self.u10n = np.where(self.h_in[0]/self.monob > 0,
+    #                          np.maximum(np.copy(self.u10n), 0.1), self.u10n)
+
+    def __init__(self):
+        self.meth = "YT96"
+
+        # no limits to u range as we use eq. 21 for cdn
+        # self.u_lo = [0, 3]
+        # self.u_hi = [26, 26]
+
+
+class LP82(S88):
+
+    def __init__(self):
+        self.meth = "LP82"
+        self.u_lo = [3, 3]
+        self.u_hi = [25, 25]
+
+
+class NCAR(S88):
+
+    def _minimum_params(self):
+        self.cd = np.maximum(np.copy(self.cd), 1e-4)
+        self.ct = np.maximum(np.copy(self.ct), 1e-4)
+        self.cq = np.maximum(np.copy(self.cq), 1e-4)
+        self.zo = np.minimum(np.copy(self.zo), 0.0025)
+        # self.u10n = np.maximum(np.copy(self.u10n), 0.25)
+
+    def __init__(self):
+        self.meth = "NCAR"
+        self.u_lo = [0.5, 0.5]
+        self.u_hi = [999, 999]
+
+
+class UA(S88):
+
+    def __init__(self):
+        self.meth = "UA"
+        self.default_gust = [1, 1.2, 600, 0.01]
+        self.u_lo = [-999, -999]
+        self.u_hi = [18, 18]
+
+
+class C30(S88):
+    # def set_coolskin_warmlayer(self, wl=0, cskin=1, skin="C35", Rl=None, Rs=None):
+    #     self._fix_coolskin_warmlayer(wl, cskin, skin, Rl, Rs)
+
+    def __init__(self):
+        self.meth = "C30"
+        self.default_gust = [1, 1.2, 600, 0.01]
+        self.skin = "C35"
+
+
+class C35(C30):
+    def __init__(self):
+        self.meth = "C35"
+        self.default_gust = [1, 1.2, 600, 0.01]
+        self.skin = "C35"
+
+
+class ecmwf(C30):
+    # def set_coolskin_warmlayer(self, wl=0, cskin=1, skin="ecmwf", Rl=None,
+    #                            Rs=None):
+    #     self._fix_coolskin_warmlayer(wl, cskin, skin, Rl, Rs)
+    # def _minimum_params(self):
+    #     self.cdn = np.maximum(np.copy(self.cdn), 0.1e-3)
+    #     self.ctn = np.maximum(np.copy(self.ctn), 0.1e-3)
+    #     self.cqn = np.maximum(np.copy(self.cqn), 0.1e-3)
+    #     self.wind = np.maximum(np.copy(self.wind), 0.2)
+
+    def __init__(self):
+        self.meth = "ecmwf"
+        self.default_gust = [1, 1.2, 600, 0.01]
+        self.skin = "ecmwf"
+
+
+class Beljaars(C30):
+    # def set_coolskin_warmlayer(self, wl=0, cskin=1, skin="Beljaars", Rl=None,
+    #                            Rs=None):
+    #     self._fix_coolskin_warmlayer(wl, cskin, skin, Rl, Rs)
+
+    def __init__(self):
+        self.meth = "Beljaars"
+        self.default_gust = [1, 1.2, 600, 0.01]
+        self.skin = "ecmwf"
+        # self.skin = "Beljaars"
+
+
+def AirSeaFluxCode_dev(spd, T, SST, SST_fl, meth, lat=None, hum=None, P=None,
+                       hin=18, hout=10, Rl=None, Rs=None, cskin=0, skin=None,
+                       wl=0, gust=None, qmeth="Buck2", tol=None, maxiter=10,
+                       out=0, out_var=None, L=None):
+    """
+    Calculate turbulent surface fluxes using different parameterizations.
+
+    Calculate height adjusted values for spd, T, q
+
+    Parameters
+    ----------
+        spd : float
+            relative wind speed in m/s (is assumed as magnitude difference
+            between wind and surface current vectors)
+        T : float
+            air temperature in K (will convert if < 200)
+        SST : float
+            sea surface temperature in K (will convert if < 200)
+        SST_fl : str
+            provides information on the type of the input SST; "bulk" or
+            "skin"
+        meth : str
+            "S80", "S88", "LP82", "YT96", "UA", "NCAR", "C30", "C35",
+            "ecmwf", "Beljaars"
+        lat : float
+            latitude (deg), default 45deg
+        hum : float
+            humidity input switch 2x1 [x, values] default is relative humidity
+            x='rh' : relative humidity in %
+            x='q' : specific humidity (g/kg)
+            x='Td' : dew point temperature (K)
+        P : float
+            air pressure (hPa), default 1013hPa
+        hin : float
+            sensor heights in m (array 3x1 or 3xn), default 18m
+        hout : float
+            output height, default is 10m
+        Rl : float
+            downward longwave radiation (W/m^2)
+        Rs : float
+            downward shortwave radiation (W/m^2)
+        cskin : int
+            0 switch cool skin adjustment off, else 1
+            default is 0
+        skin : str
+            cool skin method option "C35", "ecmwf" or "Beljaars"
+        wl : int
+            warm layer correction default is 0, to switch on set to 1
+        gust : int
+            4x1 [x, beta, zi, ustb] x=0 gustiness is OFF, x=1-5 gustiness is ON
+            and use gustiness factor: 1. Fairall et al. 2003, 2. GF is removed
+            from TSFs u10n, uref, 3. GF=1, 4. following Zeng et al. 1998 or
+            Brodeau et al. 2006, 5. following C35 matlab code;
+            beta gustiness parameter, default is 1.2,
+            zi PBL height (m) default is 600,
+            min is the value for gust speed in stable conditions,
+            default is 0.01ms^{-1}
+        qmeth : str
+            is the saturation evaporation method to use amongst
+            "HylandWexler","Hardy","Preining","Wexler","GoffGratch","WMO",
+            "MagnusTetens","Buck","Buck2","WMO2018","Sonntag","Bolton",
+            "IAPWS","MurphyKoop"]
+            default is Buck2
+        tol : float
+           4x1 or 7x1 [option, lim1-3 or lim1-6]
+           option : 'flux' to set tolerance limits for fluxes only lim1-3
+           option : 'ref' to set tolerance limits for height adjustment lim-1-3
+           option : 'all' to set tolerance limits for both fluxes and height
+                    adjustment lim1-6
+           default is tol=['all', 0.01, 0.01, 1e-05, 1e-3, 0.1, 0.1]
+        maxiter : int
+            number of iterations (default = 10)
+        out : int
+            set 0 to set points that have not converged, negative values of
+                  u10n, q10n or T10n out of limits to missing (default)
+            set 1 to keep points
+        out_var : str
+           optional. user can define pandas array of variables to be output.
+           the default full pandas array is :
+               out_var = ("tau", "sensible", "latent", "monob", "cd", "cd10n",
+                           "ct", "ct10n", "cq", "cq10n", "tsrv", "tsr", "qsr",
+                           "usr", "psim", "psit", "psiq", "psim_ref", "psit_ref",
+                           "psiq_ref", "u10n", "t10n", "q10n", "zo", "zot", "zoq",
+                           "uref", "tref", "qref", "dter", "dqer", "dtwl", "tkt",
+                           "qair", "qsea", "Rl", "Rs", "Rnl", "ug", "usrGF",
+                           "GustFact", "Rb", "rh", "rho", "cp", "lv", "theta",
+                           "itera")
+            the "limited" pandas array is:
+                out_var = ("tau", "sensible", "latent", "uref", "tref", "qref")
+            the user can define a custom pandas array of variables to  output.
+        L : str
+           Monin-Obukhov length definition options
+           "tsrv"  : default
+           "Rb" : following ecmwf (IFS Documentation cy46r1)
+
+    Returns
+    -------
+        res : array that contains
+                       1. momentum flux       (N/m^2)
+                       2. sensible heat       (W/m^2)
+                       3. latent heat         (W/m^2)
+                       4. Monin-Obhukov length (m)
+                       5. drag coefficient (cd)
+                       6. neutral drag coefficient (cd10n)
+                       7. heat exchange coefficient (ct)
+                       8. neutral heat exchange coefficient (ct10n)
+                       9. moisture exhange coefficient (cq)
+                       10. neutral moisture exchange coefficient (cq10n)
+                       11. star virtual temperatcure (tsrv)
+                       12. star temperature (tsr)
+                       13. star specific humidity (qsr)
+                       14. star wind speed (usr)
+                       15. momentum stability function (psim)
+                       16. heat stability function (psit)
+                       17. moisture stability function (psiq)
+                       18. momentum stability function at hout (psim_ref)
+                       19. heat stability function at hout (psit_ref)
+                       20. moisture stability function at hout (psiq_ref)
+                       21. 10m neutral wind speed (u10n)
+                       22. 10m neutral temperature (t10n)
+                       23. 10m neutral specific humidity (q10n)
+                       24. surface roughness length (zo)
+                       25. heat roughness length (zot)
+                       26. moisture roughness length (zoq)
+                       27. wind speed at reference height (uref)
+                       28. temperature at reference height (tref)
+                       29. specific humidity at reference height (qref)
+                       30. cool-skin temperature depression (dter)
+                       31. cool-skin humidity depression (dqer)
+                       32. warm layer correction (dtwl)
+                       33. thickness of the viscous layer (delta)
+                       34. specific humidity of air (qair)
+                       35. specific humidity at sea surface (qsea)
+                       36. downward longwave radiation (Rl)
+                       37. downward shortwave radiation (Rs)
+                       38. downward net longwave radiation (Rnl)
+                       39. gust wind speed (ug)
+                       40. star wind speed/GustFact (usrGF)
+                       41. Gustiness Factor (GustFact)
+                       42. Bulk Richardson number (Rb)
+                       43. relative humidity (rh)
+                       44. air density (rho)
+                       45. specific heat of moist air (cp)
+                       46. lv latent heat of vaporization (Jkg−1)
+                       47. potential temperature (theta)
+                       48. number of iterations until convergence
+                       49. flag ("n": normal, "o": out of nominal range,
+                                 "u": u10n<0, "q":q10n<0 or q>0.04
+                                 "m": missing,
+                                 "l": Rib<-0.5 or Rib>0.2 or z/L>1000,
+                                 "r" : rh>100%,
+                                 "t" : t10n<173K or t10n>373K
+                                 "i": convergence fail at n)
+
+    2021 / Author S. Biri
+    2021 / Restructured by R. Cornes
+    2021 / Simplified by E. Kent
+    """
+    logging.basicConfig(filename='flux_calc.log', filemode="w",
+                        format='%(asctime)s %(message)s', level=logging.INFO)
+    logging.captureWarnings(True)
+
+    iclass = globals()[meth]()
+    iclass.add_gust(gust=gust)
+    iclass.add_variables(spd, T, SST, SST_fl, cskin=cskin, lat=lat, hum=hum,
+                         P=P, L=L)
+    iclass.get_heights(hin, hout)
+    iclass.get_specHumidity(qmeth=qmeth)
+    iclass.set_coolskin_warmlayer(wl=wl, cskin=cskin, skin=skin, Rl=Rl, Rs=Rs)
+    iclass.iterate(tol=tol, maxiter=maxiter)
+    resAll = iclass.get_output(out_var=out_var, out=out)
+
+    return resAll