AirSeaFluxCode.py 36.6 KB
Newer Older
sbiri's avatar
sbiri committed
1
import warnings
sbiri's avatar
sbiri committed
2
import numpy as np
3
import pandas as pd
sbiri's avatar
sbiri committed
4
import logging
5
from hum_subs import (get_hum, gamma)
sbiri's avatar
sbiri committed
6 7 8
from util_subs import *
from flux_subs import *
from cs_wl_subs import *
sbiri's avatar
sbiri committed
9 10


sbiri's avatar
sbiri committed
11 12
class S88:
    def _wind_iterate(self, ind):
13
        if self.gust[0] in [1, 2]:
sbiri's avatar
sbiri committed
14
            self.wind[ind] = np.sqrt(np.power(np.copy(self.spd[ind]), 2) +
sbiri's avatar
sbiri committed
15 16 17
                                     np.power(get_gust(self.gust[1],
                                                       self.theta[ind],
                                                       self.usr[ind],
18 19 20
                                                       self.tsrv[ind], self.gust[2],
                                                       self.grav[ind]), 2))
           # ratio of gusty to horizontal wind
sbiri's avatar
sbiri committed
21
            self.GustFact[ind] = self.wind[ind]/self.spd[ind]
sbiri's avatar
sbiri committed
22 23 24
            # if we update wind, also need to update u10n
            # remove effect of gustiness following Fairall et al. (2003)
            # usr is divided by (GustFact)^0.5
25 26
            self.u10n[ind] = self.wind[ind]-self.usr[ind]/kappa/np.sqrt(
                self.GustFact[ind])*(np.log(self.h_in[0, ind]/self.ref10) -
27
                                     self.psim[ind])
28 29 30 31 32
            if self.gust[0] == 2:
                self.GustFact[ind] = 1
                # option to not remove GustFact
                self.u10n[ind] = self.wind[ind]-self.usr[ind]/kappa*(
                    np.log(self.h_in[0, ind]/self.ref10)-self.psim[ind])
sbiri's avatar
sbiri committed
33 34 35
            # these lines integrate up from the surface - doesn't work when
            # gustiness is on
            # self.u10n[ind] = self.usr[ind]/kappa/np.power(
36
            #     self.GustFact[ind], 0.5)*(np.log(self.ref10/self.zo[ind]))
sbiri's avatar
sbiri committed
37 38
            # self.u10n[ind] = self.usr[ind]/kappa * \
            #     (np.log(self.ref10/self.zo[ind]))
sbiri's avatar
sbiri committed
39
        else:
sbiri's avatar
sbiri committed
40 41
            # not sure this is needed - perhaps only to remove effects of
            # initalisation of wind
sbiri's avatar
sbiri committed
42
            self.wind[ind] = np.copy(self.spd[ind])
sbiri's avatar
sbiri committed
43
            self.u10n[ind] = self.wind[ind]-self.usr[ind]/kappa*(
44
                np.log(self.h_in[0, ind]/self.ref10)-self.psim[ind])
sbiri's avatar
sbiri committed
45

sbiri's avatar
sbiri committed
46 47 48 49
    def get_heights(self, hin, hout=10):
        self.hout = hout
        self.hin = hin
        self.h_in = get_heights(hin, len(self.spd))
sbiri's avatar
sbiri committed
50
        self.h_out = get_heights(self.hout, 1)
sbiri's avatar
sbiri committed
51

sbiri's avatar
sbiri committed
52
    def get_specHumidity(self, qmeth="Buck2"):
53
        # input qair, qsea directly
sbiri's avatar
sbiri committed
54 55
        self.qair, self.qsea = get_hum(self.hum, self.T, self.SST, self.P,
                                       qmeth)
sbiri's avatar
sbiri committed
56 57
        if (np.all(np.isnan(self.qsea)) or np.all(np.isnan(self.qair))):
            raise ValueError("qsea and qair cannot be nan")
sbiri's avatar
sbiri committed
58 59 60
        self.dq_in = self.qair-self.qsea
        self.dq_full = self.qair-self.qsea

sbiri's avatar
sbiri committed
61 62 63 64
        # Set lapse rate and Potential Temperature (now we have humdity)
        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]
sbiri's avatar
sbiri committed
65 66
        self.dt_in = self.theta-self.SST
        self.dt_full = self.theta-self.SST
sbiri's avatar
sbiri committed
67 68

    def _fix_coolskin_warmlayer(self, wl, cskin, skin, Rl, Rs):
sbiri's avatar
sbiri committed
69
        skin = self.skin if skin is None else skin
sbiri's avatar
sbiri committed
70 71
        assert wl in [0, 1], "wl not valid"
        assert cskin in [0, 1], "cskin not valid"
sbiri's avatar
sbiri committed
72 73
        assert skin in ["C35", "ecmwf", "Beljaars"], "Skin value not valid"

sbiri's avatar
sbiri committed
74 75
        if ((cskin == 1 or wl == 1) and
            (np.all(Rl == None) or np.all(np.isnan(Rl))) and
76
                ((np.all(Rs == None) or np.all(np.isnan(Rs))))):
sbiri's avatar
sbiri committed
77 78 79 80 81
            print("Cool skin/warm layer is switched ON; "
                  "Radiation input should not be empty")
            raise

        self.wl = wl
sbiri's avatar
sbiri committed
82 83
        self.cskin = cskin
        self.skin = skin
sbiri's avatar
sbiri committed
84 85
        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
sbiri's avatar
sbiri committed
86

sbiri's avatar
sbiri committed
87
    def set_coolskin_warmlayer(self, wl=0, cskin=0, skin=None, Rl=None,
sbiri's avatar
sbiri committed
88
                               Rs=None):
sbiri's avatar
sbiri committed
89
        wl = 0 if wl is None else wl
sbiri's avatar
sbiri committed
90 91
        if hasattr(self, "skin") == False:
            self.skin = "C35"
sbiri's avatar
sbiri committed
92 93
        self._fix_coolskin_warmlayer(wl, cskin, skin, Rl, Rs)

sbiri's avatar
sbiri committed
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
    def _update_coolskin_warmlayer(self, ind):
        if self.cskin == 1:
            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])
            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])
sbiri's avatar
sbiri committed
113 114
            self.skt[ind] = np.copy(self.SST[ind])+self.dter[ind]
            self.skq[ind] = np.copy(self.qsea[ind])+self.dqer[ind]
sbiri's avatar
sbiri committed
115 116 117 118 119 120 121 122 123 124
            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])
sbiri's avatar
sbiri committed
125 126 127 128 129 130 131 132
                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):
133
        # reference height
sbiri's avatar
sbiri committed
134 135 136 137 138 139 140 141 142 143 144 145
        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
sbiri's avatar
sbiri committed
146
        self.wind = np.sqrt(np.power(np.copy(self.spd), 2)+0.25)
sbiri's avatar
sbiri committed
147 148 149 150
        self.GustFact = self.wind*0+1

        # Rb eq. 11 Grachev & Fairall 1997, use air temp height
        # use self.tv??  adjust wind to T-height?
sbiri's avatar
sbiri committed
151
        Rb = self.grav*self.h_in[1]*self.dtv/(self.T*np.power(self.wind, 2))
152 153
        # eq. 12 Grachev & Fairall 1997   # DO.THIS
        self.monob = self.h_in[1]/12.0/Rb
sbiri's avatar
sbiri committed
154
        # where does 12.0 come from??
sbiri's avatar
sbiri committed
155 156 157

        # ------------

158 159
        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
sbiri's avatar
sbiri committed
160
        if self.cskin + self.wl > 0:
sbiri's avatar
sbiri committed
161 162
            self.dter, self.tkt, self.dtwl = [
                dummy_array(x) for x in (-0.3, 0.001, 0.3)]
163 164
            self.dqer = get_dqer(self.dter, self.SST, self.qsea,
                                 self.lv)
sbiri's avatar
sbiri committed
165 166
            self.Rnl = 0.97*(self.Rl-5.67e-8*np.power(
                self.SST-0.3*self.cskin, 4))
sbiri's avatar
sbiri committed
167 168
            self.Qs = 0.945*self.Rs
        else:
sbiri's avatar
sbiri committed
169 170 171 172
            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)]
sbiri's avatar
sbiri committed
173 174 175 176 177
        self.skt = np.copy(self.SST)
        self.skq = np.copy(self.qsea)

        self.u10n = np.copy(self.wind)
        self.usr = 0.035*self.u10n
sbiri's avatar
sbiri committed
178 179
        self.cd10n, self.zo = cdn_calc(
            self.u10n, self.usr, self.theta, self.grav, self.meth)
sbiri's avatar
sbiri committed
180 181
        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)
182
        self.usr =np.sqrt(self.cd*np.power(self.wind, 2))
sbiri's avatar
sbiri committed
183 184 185 186
        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)]
187
        self.tv10n = self.zot
sbiri's avatar
sbiri committed
188 189 190 191 192 193

    def iterate(self, maxiter=10, tol=None):
        if maxiter < 5:
            warnings.warn("Iteration number <5 - resetting to 5.")
            maxiter = 5

sbiri's avatar
sbiri committed
194 195 196 197
        # 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
sbiri's avatar
sbiri committed
198 199
        assert tol[0] in ['flux', 'ref', 'all'], "unknown tolerance input"

sbiri's avatar
sbiri committed
200 201
        old_vars = {"flux": ["tau", "sensible", "latent"],
                    "ref": ["u10n", "t10n", "q10n"]}
sbiri's avatar
sbiri committed
202 203 204
        old_vars["all"] = old_vars["ref"] + old_vars["flux"]
        old_vars = old_vars[tol[0]]

sbiri's avatar
sbiri committed
205 206
        new_vars = {"flux": ["tau", "sensible", "latent"],
                    "ref": ["u10n", "t10n", "q10n"]}
sbiri's avatar
sbiri committed
207 208 209 210 211 212 213 214 215 216 217
        new_vars["all"] = new_vars["ref"] + new_vars["flux"]
        new_vars = new_vars[tol[0]]
        # I'm sure there are better ways of doing this
        # 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
sbiri's avatar
sbiri committed
218 219
        self.tsrv, self.psim, self.psit, self.psiq = [
            np.zeros(self.arr_shp)*self.msk for _ in range(4)]
sbiri's avatar
sbiri committed
220 221

        # extreme values for first comparison
222
        def dummy_array(val): return np.full(self.arr_shp, val)*self.msk
223
        # you can use def instead of lambda
sbiri's avatar
sbiri committed
224 225 226
        # 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)]
sbiri's avatar
sbiri committed
227 228 229 230 231 232 233 234 235 236

        # 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")
sbiri's avatar
sbiri committed
237
            old = np.array([np.copy(getattr(self, i)) for i in old_vars])
sbiri's avatar
sbiri committed
238 239

            # Calculate cdn
sbiri's avatar
sbiri committed
240 241 242 243 244
            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)):
sbiri's avatar
sbiri committed
245
                logging.info('break %s at iteration %s cd10n<0', meth, it)
sbiri's avatar
sbiri committed
246 247 248 249 250 251
                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])
sbiri's avatar
sbiri committed
252 253
            # remove effect of gustiness following Fairall et al. (2003)
            # usr is divided by (GustFact)^0.5 (updated in wind_iterate)
sbiri's avatar
sbiri committed
254 255 256 257 258 259
            # these 2 equations integrating from surface should be equivalent
            # - but they are not due to gustiness
            # self.u10n[ind] = self.usr[ind]/kappa/np.power(
            #     self.GustFact[ind],0.5)*(np.log(self.ref10/self.zo[ind]))
            # self.u10n[ind] = self.usr[ind]/kappa*(
            #     np.log(self.ref10/self.zo[ind]))
sbiri's avatar
sbiri committed
260
            # lines below with and without gustfactor
261 262 263 264 265 266 267 268 269
            # if self.gust[0] == 1:
            #     self.u10n[ind] = self.wind[ind]-self.usr[ind]/kappa/np.sqrt(
            #         self.GustFact[ind], 0.5)*(np.log(
            #             self.h_in[0, ind]/self.ref10)-self.psim[ind])
            # elif self.gust[0] == 2:
            #     self.u10n[ind] = self.wind[ind]-self.usr[ind]/kappa*(
            #         np.log(self.h_in[0, ind]/self.ref10)-self.psim[ind])
            # Update the wind values
            self._wind_iterate(ind)
sbiri's avatar
sbiri committed
270 271

            # temperature
sbiri's avatar
sbiri committed
272 273 274 275 276 277 278 279
            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])
280
            # humidity
sbiri's avatar
sbiri committed
281 282 283 284 285 286 287 288
            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])
289
            
sbiri's avatar
sbiri committed
290 291 292 293 294
            # Some parameterizations set a minimum on parameters
            try:
                self._minimum_params()
            except AttributeError:
                pass
sbiri's avatar
sbiri committed
295 296 297

            self.dt_full[ind] = self.dt_in[ind] - \
                self.dter[ind]*self.cskin - self.dtwl[ind]*self.wl
sbiri's avatar
sbiri committed
298
            self.dq_full[ind] = self.dq_in[ind] - self.dqer[ind]*self.cskin
sbiri's avatar
sbiri committed
299 300 301 302 303
            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)
sbiri's avatar
sbiri committed
304 305 306

            # Update CS/WL parameters
            self._update_coolskin_warmlayer(ind)
sbiri's avatar
sbiri committed
307

sbiri's avatar
sbiri committed
308
            # Logging output
sbiri's avatar
sbiri committed
309 310 311 312 313 314 315 316
            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))
sbiri's avatar
sbiri committed
317 318

            if self.cskin + self.wl > 0:
sbiri's avatar
sbiri committed
319 320 321
                self.Rnl[ind] = 0.97*(self.Rl[ind]-5.67e-8 *
                                      np.power(self.SST[ind] +
                                               self.dter[ind]*self.cskin, 4))
sbiri's avatar
sbiri committed
322 323
            # not sure how to handle lapse/potemp
            # well-mixed in potential temperature ...
324
            self.t10n[ind] = self.theta[ind]-self.tlapse[ind]*self.ref10 - \
sbiri's avatar
sbiri committed
325
                self.tsr[ind]/kappa * \
326
                (np.log(self.h_in[1, ind]/self.ref10)-self.psit[ind])
327
            self.q10n[ind] = self.qair[ind]-self.qsr[ind]/kappa * \
sbiri's avatar
sbiri committed
328
                (np.log(self.h_in[2, ind]/self.ref10)-self.psiq[ind])
sbiri's avatar
sbiri committed
329 330

            # update stability info
sbiri's avatar
sbiri committed
331 332 333 334 335 336
            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)
sbiri's avatar
sbiri committed
337
            if self.L == "tsrv":
sbiri's avatar
sbiri committed
338 339 340
                self.monob[ind] = get_Ltsrv(
                    self.tsrv[ind], self.grav[ind], self.tv[ind],
                    self.usr[ind])
sbiri's avatar
sbiri committed
341
            else:
sbiri's avatar
sbiri committed
342
                self.monob[ind] = get_LRb(
343
                    self.Rb[ind], self.h_in[1, ind], self.monob[ind],
sbiri's avatar
sbiri committed
344
                    self.zo[ind], self.zot[ind], self.meth)
sbiri's avatar
sbiri committed
345 346 347

            # Update the wind values
            self._wind_iterate(ind)
sbiri's avatar
sbiri committed
348

sbiri's avatar
sbiri committed
349
            # make sure you allow small negative values convergence
sbiri's avatar
sbiri committed
350 351
            if it == 1:
                self.u10n = np.where(self.u10n < 0, 0.5, self.u10n)
sbiri's avatar
sbiri committed
352

sbiri's avatar
sbiri committed
353
            self.itera[ind] = np.full(1, it)
sbiri's avatar
sbiri committed
354
            # remove effect of gustiness following Fairall et al. (2003)
sbiri's avatar
sbiri committed
355 356
            # usr is divided by (GustFact)^0.5 (here applied to sensible and
            # latent as well as tau)
sbiri's avatar
sbiri committed
357 358
            # GustFact should be 1 if gust is OFF
            self.tau = self.rho*np.power(self.usr, 2)/self.GustFact
sbiri's avatar
sbiri committed
359
            self.sensible = self.rho*self.cp*self.usr / \
360
                np.sqrt(self.GustFact)*self.tsr
sbiri's avatar
sbiri committed
361
            self.latent = self.rho*self.lv*self.usr / \
362
                np.sqrt(self.GustFact)*self.qsr
sbiri's avatar
sbiri committed
363
            # or leave as it is - gusty wind speed, or no gust
sbiri's avatar
sbiri committed
364 365 366
            # self.tau = self.rho*np.power(self.usr, 2)
            # self.sensible = self.rho*self.cp*self.usr*self.tsr
            # self.latent = self.rho*self.lv*self.usr*self.qsr
sbiri's avatar
sbiri committed
367 368

            # Set the new variables (for comparison against "old")
sbiri's avatar
sbiri committed
369
            new = np.array([np.copy(getattr(self, i)) for i in new_vars])
sbiri's avatar
sbiri committed
370

sbiri's avatar
sbiri committed
371
            if it > 2:  # force at least three iterations
372
                d = np.abs(new-old)  # change over this iteration
sbiri's avatar
sbiri committed
373 374 375 376
                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)
sbiri's avatar
sbiri committed
377 378 379 380 381 382 383 384

            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)
sbiri's avatar
sbiri committed
385 386
        logging.info('method %s | # of points that did not converge :%s \n',
                     self.meth, self.ind[0].size)
sbiri's avatar
sbiri committed
387 388

    def _get_humidity(self):
389
        """Calculate RH used for flagging purposes & output."""
sbiri's avatar
sbiri committed
390
        if self.hum[0] in ('rh', 'no'):
sbiri's avatar
sbiri committed
391
            self.rh = self.hum[1]
sbiri's avatar
sbiri committed
392
        elif self.hum[0] == 'Td':
sbiri's avatar
sbiri committed
393 394 395
            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))
396
            # T = np.copy(self.T)
sbiri's avatar
sbiri committed
397 398 399
            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
400 401 402 403
        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
sbiri's avatar
sbiri committed
404

405 406 407
    def _flag(self, out=0):
        """Set the general flags."""
        flag = np.full(self.arr_shp, "n", dtype="object")
sbiri's avatar
sbiri committed
408

sbiri's avatar
sbiri committed
409 410 411 412 413
        if self.hum[0] == 'no':
            if self.cskin == 1:
                flag = np.where(
                    np.isnan(self.spd+self.T+self.SST+self.P+self.Rs+self.Rl),
                    "m", flag)
sbiri's avatar
sbiri committed
414
            else:
sbiri's avatar
sbiri committed
415 416
                flag = np.where(
                    np.isnan(self.spd+self.T+self.SST+self.P), "m", flag)
sbiri's avatar
sbiri committed
417
        else:
sbiri's avatar
sbiri committed
418 419
            if self.cskin == 1:
                flag = np.where(np.isnan(
420 421
                    self.spd+self.T+self.SST+self.hum[1]+self.P +
                    self.Rs+self.Rl), "m", flag)
sbiri's avatar
sbiri committed
422
            else:
sbiri's avatar
sbiri committed
423 424 425
                flag = np.where(
                    np.isnan(self.spd+self.T+self.SST+self.hum[1]+self.P),
                    "m", flag)
sbiri's avatar
sbiri committed
426 427 428 429

            flag = np.where(self.rh > 100, "r", flag)

        # u10n flag
sbiri's avatar
sbiri committed
430 431 432 433 434
        flag = np.where(((self.u10n < 0) | (self.u10n > 200)) & (flag == "n"),
                        "u",
                        np.where(((self.u10n < 0) | (self.u10n > 200)) &
                                 (np.char.find(flag.astype(str), 'u') == -1),
                                 flag+[","]+["u"], flag))
sbiri's avatar
sbiri committed
435
        # q10n flag
sbiri's avatar
sbiri committed
436 437 438 439 440
        flag = np.where(
            ((self.q10n < 0) | (self.q10n > 40*0.001)) & (flag == "n"), "q",
            np.where(
                ((self.q10n < 0) | (self.q10n > 40*0.001)) & (flag != "n"),
                flag+[","]+["q"], flag))
441

sbiri's avatar
sbiri committed
442
        # t10n flag
sbiri's avatar
sbiri committed
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
        flag = np.where(
            ((self.t10n < 173) | (self.t10n > 373)) & (flag == "n"), "t",
            np.where(((self.t10n < 173) | (self.t10n > 373)) & (flag != "n"),
                     flag+[","]+["t"], flag))

        flag = np.where(
            ((self.Rb < -0.5) | (self.Rb > 0.2) |
             ((self.hin[0]/self.monob) > 1000)) & (flag == "n"), "l",
            np.where(((self.Rb < -0.5) | (self.Rb > 0.2) |
                      ((self.hin[0]/self.monob) > 1000)) &
                     (flag != "n"), flag+[","]+["l"], flag))

        if out == 1:
            flag = np.where((self.itera == -1) & (flag == "n"), "i", np.where(
                (self.itera == -1) &
                ((flag != "n") & (np.char.find(flag.astype(str), 'm') == -1)),
                flag+[","]+["i"], flag))
sbiri's avatar
sbiri committed
460
        else:
sbiri's avatar
sbiri committed
461 462 463 464 465
            flag = np.where((self.itera == -1) & (flag == "n"), "i", np.where(
                (self.itera == -1) &
                ((flag != "n") & (np.char.find(flag.astype(str), 'm') == -1) &
                 (np.char.find(flag.astype(str), 'u') == -1)),
                flag+[","]+["i"], flag))
sbiri's avatar
sbiri committed
466 467
        self.flag = flag

sbiri's avatar
sbiri committed
468 469 470
    def get_output(self, out=0):

        assert out in [0, 1], "out must be either 0 or 1"
sbiri's avatar
sbiri committed
471

472
        self._get_humidity()  # Get the Relative humidity
sbiri's avatar
sbiri committed
473 474 475 476
        self._flag(out=out)  # Get flags

        # remove effect of gustiness following Fairall et al. (2003)
        # usr is divided by (GustFact)^0.5
477
        self.uref = self.spd-self.usr/kappa/np.sqrt(self.GustFact) * \
sbiri's avatar
sbiri committed
478
            (np.log(self.h_in[0]/self.h_out[0])-self.psim +
479
             psim_calc(self.h_out[0]/self.monob, self.meth))
sbiri's avatar
sbiri committed
480
        # include lapse rate adjustment as theta is well-mixed
sbiri's avatar
sbiri committed
481 482
        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 +
483
             psit_calc(self.h_out[1]/self.monob, self.meth))
sbiri's avatar
sbiri committed
484 485
        self.qref = self.qair-self.qsr/kappa * \
            (np.log(self.h_in[2]/self.h_out[2])-self.psiq +
486
             psit_calc(self.h_out[2]/self.monob, self.meth))
sbiri's avatar
sbiri committed
487

sbiri's avatar
sbiri committed
488 489 490
        if self.wl == 0:
            self.dtwl = np.zeros(self.T.shape)*self.msk
            # reset to zero if not used
sbiri's avatar
sbiri committed
491 492

        # Do not calculate lhf if a measure of humidity is not input
sbiri's avatar
sbiri committed
493 494 495 496 497
        # This gets filled into a pd dataframe and so no need to specify y
        # 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)
sbiri's avatar
sbiri committed
498 499 500 501 502

        # 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))

sbiri's avatar
sbiri committed
503 504
        # Get class specific flags (will only work if self.u_hi and self.u_lo
        # have been set in the class)
sbiri's avatar
sbiri committed
505 506 507 508 509 510
        try:
            self._class_flag()
        except AttributeError:
            pass

        # Combine all output variables into a pandas array
sbiri's avatar
sbiri committed
511 512 513
        res_vars = ("tau", "sensible", "latent", "monob", "cd", "cd10n", "ct",
                    "ct10n", "cq", "cq10n", "tsrv", "tsr", "qsr", "usr",
                    "psim", "psit", "psiq", "u10n", "t10n", "q10n", "zo",
514
                    "zot", "zoq", "uref", "tref", "qref", "dter",
sbiri's avatar
sbiri committed
515
                    "dqer", "dtwl", "qair", "qsea", "Rl", "Rs", "Rnl", "ug",
516
                    "Rb", "rh", "rho", "cp", "tkt", "lv", "itera")
sbiri's avatar
sbiri committed
517 518

        res = np.zeros((len(res_vars), len(self.spd)))
sbiri's avatar
sbiri committed
519 520
        for i, value in enumerate(res_vars):
            res[i][:] = getattr(self, value)
sbiri's avatar
sbiri committed
521

sbiri's avatar
sbiri committed
522
        if out == 0:
sbiri's avatar
sbiri committed
523 524
            res[:, self.ind] = np.nan
            # set missing values where data have non acceptable values
sbiri's avatar
sbiri committed
525
            if self.hum[0] != 'no':
526 527 528
                res[:-1] = np.asarray([
                    np.where(self.q10n < 0, np.nan, res[i][:])
                    for i in range(len(res_vars)-1)])
529 530
                # len(res_vars)-1 instead of len(res_vars) in order to keep
                # itera= -1 for no convergence
531 532 533
            res[:-1] = np.asarray([
                np.where(self.u10n < 0, np.nan, res[i][:])
                for i in range(len(res_vars)-1)])
sbiri's avatar
sbiri committed
534
        else:
sbiri's avatar
sbiri committed
535 536 537 538
            warnings.warn("Warning: the output will contain values for points"
                          " that have not converged and negative values "
                          "(if any) for u10n/q10n")

539 540
        resAll = pd.DataFrame(data=res.T, index=range(self.nlen),
                              columns=res_vars)
sbiri's avatar
sbiri committed
541 542 543 544 545 546 547 548

        resAll["flag"] = self.flag

        return resAll

    def add_variables(self, spd, T, SST, lat=None, hum=None, P=None, L=None):

        # Add the mandatory variables
sbiri's avatar
sbiri committed
549 550 551 552
        assert type(spd) == type(T) == type(
            SST) == np.ndarray, "input type of spd, T and SST should be"
        " numpy.ndarray"
        self.L = "tsrv" if L is None else L
sbiri's avatar
sbiri committed
553 554 555 556
        self.arr_shp = spd.shape
        self.nlen = len(spd)
        self.spd = spd
        self.T = np.where(T < 200, np.copy(T)+CtoK, np.copy(T))
sbiri's avatar
sbiri committed
557
        self.hum = ['no', np.full(SST.shape, 80)] if hum is None else hum
sbiri's avatar
sbiri committed
558
        self.SST = np.where(SST < 200, np.copy(SST)+CtoK, np.copy(SST))
sbiri's avatar
sbiri committed
559
        self.lat = np.full(self.arr_shp, 45) if lat is None else lat
sbiri's avatar
sbiri committed
560
        self.grav = gc(self.lat)
561
        self.P = np.full(self.nlen, 1013) if P is None else P
sbiri's avatar
sbiri committed
562 563

        # mask to preserve missing values when initialising variables
sbiri's avatar
sbiri committed
564
        self.msk = np.empty(SST.shape)
sbiri's avatar
sbiri committed
565 566 567
        self.msk = np.where(np.isnan(spd+T+SST), np.nan, 1)
        self.Rb = np.empty(SST.shape)*self.msk

sbiri's avatar
sbiri committed
568 569
    def add_gust(self, gust=None):
        if np.all(gust is None):
sbiri's avatar
sbiri committed
570 571 572
            try:
                gust = self.default_gust
            except AttributeError:
sbiri's avatar
sbiri committed
573 574
                gust = [0, 0, 0]  # gustiness OFF
                # gust = [1, 1.2, 800]
sbiri's avatar
sbiri committed
575 576
        elif ((np.size(gust) < 3) and (gust == 0)):
            gust = [0, 0, 0]
sbiri's avatar
sbiri committed
577

sbiri's avatar
sbiri committed
578
        assert np.size(gust) == 3, "gust input must be a 3x1 array"
579
        assert gust[0] in [0, 1, 2], "gust at position 0 must be 0, 1 or 2"
sbiri's avatar
sbiri committed
580 581 582
        self.gust = gust

    def _class_flag(self):
sbiri's avatar
sbiri committed
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606
        """A flag specific to this class - only used for certain classes where
         u_lo and u_hi are defined"""
        # flag.tmp = np.where(
        #     ((self.u10n < self.u_lo[0]) | (self.u10n > self.u_hi[0])),"o", "")
        # self.flag = np.where(
        #     self.flag == "n" & flag.tmp == "o", "o", self.flag)
        # self.flag = np.where(
        #     flag.tmp == "o" & self.flag != "n" & self.flag != "o" &
        #     self.flag != "m", self.flag+[","]+["o"], self.flag)
        # flag.add = ["u", "q", "t"]
        # self.flag = np.where(
        #     flag.tmp == "o" & (np.char.find(self.flag.astype(str), 'u') == -1 |
        #                        np.char.find(self.flag.astype(str), 'q') == -1 |
        #                        np.char.find(self.flag.astype(str), 't') == -1),
        #     self.flag+[","]+["o"], self.flag))
        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)),
sbiri's avatar
sbiri committed
607 608 609 610 611
                                      self.flag+[","]+["o"], self.flag))

    def __init__(self):
        self.meth = "S88"

612

sbiri's avatar
sbiri committed
613 614 615 616
class S80(S88):

    def __init__(self):
        self.meth = "S80"
sbiri's avatar
sbiri committed
617 618
        self.u_lo = [6, 6]
        self.u_hi = [22, 22]
sbiri's avatar
sbiri committed
619

sbiri's avatar
sbiri committed
620

sbiri's avatar
sbiri committed
621 622 623 624
class YT96(S88):

    def __init__(self):
        self.meth = "YT96"
625
        # no limits to u range as we use eq. 21 for cdn
sbiri's avatar
sbiri committed
626 627
        # self.u_lo = [0, 3]
        # self.u_hi = [26, 26]
sbiri's avatar
sbiri committed
628

629

sbiri's avatar
sbiri committed
630
class LP82(S88):
sbiri's avatar
sbiri committed
631

sbiri's avatar
sbiri committed
632 633
    def __init__(self):
        self.meth = "LP82"
sbiri's avatar
sbiri committed
634 635
        self.u_lo = [3, 3]
        self.u_hi = [25, 25]
sbiri's avatar
sbiri committed
636 637 638 639 640 641 642 643 644


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)
645
        # self.u10n = np.maximum(np.copy(self.u10n), 0.25)
sbiri's avatar
sbiri committed
646

sbiri's avatar
sbiri committed
647 648
    def __init__(self):
        self.meth = "NCAR"
sbiri's avatar
sbiri committed
649 650
        self.u_lo = [0.5, 0.5]
        self.u_hi = [999, 999]
sbiri's avatar
sbiri committed
651

652

sbiri's avatar
sbiri committed
653
class UA(S88):
sbiri's avatar
sbiri committed
654

sbiri's avatar
sbiri committed
655 656
    def __init__(self):
        self.meth = "UA"
sbiri's avatar
sbiri committed
657 658 659
        self.default_gust = [1, 1, 1000]
        self.u_lo = [-999, -999]
        self.u_hi = [18, 18]
sbiri's avatar
sbiri committed
660 661 662


class C30(S88):
sbiri's avatar
sbiri committed
663 664
    # def set_coolskin_warmlayer(self, wl=0, cskin=1, skin="C35", Rl=None, Rs=None):
    #     self._fix_coolskin_warmlayer(wl, cskin, skin, Rl, Rs)
sbiri's avatar
sbiri committed
665 666 667

    def __init__(self):
        self.meth = "C30"
sbiri's avatar
sbiri committed
668
        self.default_gust = [1, 1.2, 600]
sbiri's avatar
sbiri committed
669
        self.skin = "C35"
sbiri's avatar
sbiri committed
670

671

sbiri's avatar
sbiri committed
672 673 674
class C35(C30):
    def __init__(self):
        self.meth = "C35"
sbiri's avatar
sbiri committed
675
        self.default_gust = [1, 1.2, 600]
sbiri's avatar
sbiri committed
676
        self.skin = "C35"
sbiri's avatar
sbiri committed
677

678

sbiri's avatar
sbiri committed
679
class ecmwf(C30):
sbiri's avatar
sbiri committed
680 681 682
    # def set_coolskin_warmlayer(self, wl=0, cskin=1, skin="ecmwf", Rl=None,
    #                            Rs=None):
    #     self._fix_coolskin_warmlayer(wl, cskin, skin, Rl, Rs)
683 684 685 686 687
    # 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)
sbiri's avatar
sbiri committed
688 689 690

    def __init__(self):
        self.meth = "ecmwf"
sbiri's avatar
sbiri committed
691
        self.default_gust = [1, 1, 1000]
sbiri's avatar
sbiri committed
692
        self.skin = "ecmwf"
sbiri's avatar
sbiri committed
693

694

sbiri's avatar
sbiri committed
695
class Beljaars(C30):
sbiri's avatar
sbiri committed
696 697 698
    # def set_coolskin_warmlayer(self, wl=0, cskin=1, skin="Beljaars", Rl=None,
    #                            Rs=None):
    #     self._fix_coolskin_warmlayer(wl, cskin, skin, Rl, Rs)
sbiri's avatar
sbiri committed
699

sbiri's avatar
sbiri committed
700 701
    def __init__(self):
        self.meth = "Beljaars"
sbiri's avatar
sbiri committed
702
        self.default_gust = [1, 1, 1000]
sbiri's avatar
sbiri committed
703
        self.skin = "Beljaars"
sbiri's avatar
sbiri committed
704 705


706
def AirSeaFluxCode(spd, T, SST, lat=None, hum=None, P=None, hin=18, hout=10,
sbiri's avatar
sbiri committed
707
                   Rl=None, Rs=None, cskin=0, skin=None, wl=0, gust=None,
sbiri's avatar
sbiri committed
708 709
                   meth="S88", qmeth="Buck2", tol=None, maxiter=10, out=0,
                   L=None):
710
    """
sbiri's avatar
sbiri committed
711 712 713
    Calculate turbulent surface fluxes using different parameterizations.

    Calculate height adjusted values for spd, T, q
sbiri's avatar
sbiri committed
714 715 716 717 718 719 720 721 722 723 724

    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)
        lat : float
725 726 727
            latitude (deg), default 45deg
        hum : float
            humidity input switch 2x1 [x, values] default is relative humidity
728 729 730
            x='rh' : relative humidity in %
            x='q' : specific humidity (g/kg)
            x='Td' : dew point temperature (K)
sbiri's avatar
sbiri committed
731
        P : float
732
            air pressure (hPa), default 1013hPa
sbiri's avatar
sbiri committed
733
        hin : float
734
            sensor heights in m (array 3x1 or 3xn), default 18m
sbiri's avatar
sbiri committed
735 736 737 738 739 740
        hout : float
            output height, default is 10m
        Rl : float
            downward longwave radiation (W/m^2)
        Rs : float
            downward shortwave radiation (W/m^2)
sbiri's avatar
sbiri committed
741
        cskin : int
742 743
            0 switch cool skin adjustment off, else 1
            default is 1
744 745 746 747
        skin : str
            cool skin method option "C35", "ecmwf" or "Beljaars"
        wl : int
            warm layer correction default is 0, to switch on set to 1
748
        gust : int
749 750
            3x1 [x, beta, zi] x=0 gustiness is OFF, x=1 gustiness is ON and
            use gustiness factor, x=2 gustiness is ON and gustiness factor=1;
751
            beta gustiness parameter, beta=1 for UA, beta=1.2 for COARE
752
            zi PBL height (m) 600 for COARE, 1000 for UA and ecmwf
753
            default for COARE [1, 1.2, 600]
754
            default for UA, ecmwf [1, 1, 1000]
755
            else default is switched OFF
756
        meth : str
sbiri's avatar
sbiri committed
757
            "S80", "S88", "LP82", "YT96", "UA", "NCAR", "C30", "C35",
758
            "ecmwf", "Beljaars"
759 760
        qmeth : str
            is the saturation evaporation method to use amongst
761 762
            "HylandWexler","Hardy","Preining","Wexler","GoffGratch","WMO",
            "MagnusTetens","Buck","Buck2","WMO2018","Sonntag","Bolton",
763 764 765 766 767 768 769
            "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
sbiri's avatar
sbiri committed
770 771
                    adjustment lim1-6
           default is tol=['all', 0.01, 0.01, 1e-05, 1e-3, 0.1, 0.1]
sbiri's avatar
sbiri committed
772 773
        maxiter : int
            number of iterations (default = 10)
774
        out : int
775 776
            set 0 to set points that have not converged, negative values of
                  u10n and q10n to missing (default)
777
            set 1 to keep points
778
        L : str
sbiri's avatar
sbiri committed
779
           Monin-Obukhov length definition options
sbiri's avatar
sbiri committed
780
           "tsrv"  : default for "S80", "S88", "LP82", "YT96", "UA", "NCAR",
sbiri's avatar
sbiri committed
781 782 783
                     "C30", "C35"
           "Rb" : following ecmwf (IFS Documentation cy46r1), default for
                  "ecmwf", "Beljaars"
sbiri's avatar
sbiri committed
784 785 786
    Returns
    -------
        res : array that contains
sbiri's avatar
sbiri committed
787 788 789 790
                       1. momentum flux       (N/m^2)
                       2. sensible heat       (W/m^2)
                       3. latent heat         (W/m^2)
                       4. Monin-Obhukov length (m)
sbiri's avatar
sbiri committed
791
                       5. drag coefficient (cd)
sbiri's avatar
sbiri committed
792
                       6. neutral drag coefficient (cd10n)
sbiri's avatar
sbiri committed
793
                       7. heat exchange coefficient (ct)
sbiri's avatar
sbiri committed
794
                       8. neutral heat exchange coefficient (ct10n)
sbiri's avatar
sbiri committed
795
                       9. moisture exhange coefficient (cq)
sbiri's avatar
sbiri committed
796
                       10. neutral moisture exchange coefficient (cq10n)
sbiri's avatar
sbiri committed
797
                       11. star virtual temperatcure (tsrv)
sbiri's avatar
sbiri committed
798
                       12. star temperature (tsr)
799 800
                       13. star specific humidity (qsr)
                       14. star wind speed (usr)
sbiri's avatar
sbiri committed
801
                       15. momentum stability function (psim)
802 803
                       16. heat stability function (psit)
                       17. moisture stability function (psiq)
804
                       18. 10m neutral wind speed (u10n)
805
                       19. 10m neutral temperature (t10n)
sbiri's avatar
sbiri committed
806 807 808 809 810 811 812
                       20. 10m neutral specific humidity (q10n)
                       21. surface roughness length (zo)
                       22. heat roughness length (zot)
                       23. moisture roughness length (zoq)
                       24. wind speed at reference height (uref)
                       25. temperature at reference height (tref)
                       26. specific humidity at reference height (qref)
813 814 815 816 817 818 819 820 821 822 823
                       27. cool-skin temperature depression (dter)
                       28. cool-skin humidity depression (dqer)
                       29. warm layer correction (dtwl)
                       30. specific humidity of air (qair)
                       31. specific humidity at sea surface (qsea)
                       32. downward longwave radiation (Rl)
                       33. downward shortwave radiation (Rs)
                       34. downward net longwave radiation (Rnl)
                       35. gust wind speed (ug)
                       36. Bulk Richardson number (Rib)
                       37. relative humidity (rh)
824 825 826 827 828 829
                       38. air density (rho)
                       39  specific heat of moist air (cp)
                       40. thickness of the viscous layer (delta)
                       41. lv latent heat of vaporization (Jkg−1)
                       42. number of iterations until convergence
                       43. flag ("n": normal, "o": out of nominal range,
830
                                 "u": u10n<0, "q":q10n<0
831
                                 "m": missing,
832
                                 "l": Rib<-0.5 or Rib>0.2 or z/L>1000,
sbiri's avatar
sbiri committed
833
                                 "r" : rh>100%,
834
                                 "t" : t10n<173K or t10n>373K
835
                                 "i": convergence fail at n)
836

837
    2021 / Author S. Biri
838 839
    2021 / Restructured by R. Cornes
    2021 / Simplified by E. Kent
sbiri's avatar
sbiri committed
840
    """
sbiri's avatar
sbiri committed
841
    logging.basicConfig(filename='flux_calc.log', filemode="w",
842
                        format='%(asctime)s %(message)s', level=logging.INFO)
843
    logging.captureWarnings(True)
sbiri's avatar
sbiri committed
844 845 846 847 848 849

    iclass = globals()[meth]()
    iclass.add_gust(gust=gust)
    iclass.add_variables(spd, T, SST, lat=lat, hum=hum, P=P, L=L)
    iclass.get_heights(hin, hout)
    iclass.get_specHumidity(qmeth=qmeth)
sbiri's avatar
sbiri committed
850 851
    iclass.set_coolskin_warmlayer(wl=wl, cskin=cskin, skin=skin, Rl=Rl, Rs=Rs)
    iclass.iterate(tol=tol, maxiter=maxiter)
sbiri's avatar
sbiri committed
852 853
    resAll = iclass.get_output(out=out)

854
    return resAll