AirSeaFluxCode.py 38.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 18 19 20 21
                                     np.power(get_gust(self.gust[1],
                                                       self.theta[ind],
                                                       self.usr[ind],
                                            self.tsrv[ind], self.gust[2],
                                            self.grav[ind]), 2))
            # ratio of gusty to horizontal wind
            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) -
sbiri's avatar
sbiri committed
27 28 29 30 31 32 33
                                      self.psim[ind])
            # temporary as in C35
            # self.u10n[ind] = self.usr[ind]/kappa/self.GustFact[ind]*np.log(
            #     self.ref10/self.zo[ind])
            # temporary as in UA
            # self.u10n[ind] = self.usr[ind]/kappa/np.log(
            #     self.ref10/self.zo[ind])
34 35 36 37 38
            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
39 40 41
            # these lines integrate up from the surface - doesn't work when
            # gustiness is on
            # self.u10n[ind] = self.usr[ind]/kappa/np.power(
42
            #     self.GustFact[ind], 0.5)*(np.log(self.ref10/self.zo[ind]))
sbiri's avatar
sbiri committed
43 44
            # self.u10n[ind] = self.usr[ind]/kappa * \
            #     (np.log(self.ref10/self.zo[ind]))
sbiri's avatar
sbiri committed
45
        else:
sbiri's avatar
sbiri committed
46 47
            # not sure this is needed - perhaps only to remove effects of
            # initalisation of wind
sbiri's avatar
sbiri committed
48
            self.wind[ind] = np.copy(self.spd[ind])
sbiri's avatar
sbiri committed
49 50 51 52 53 54
            self.u10n[ind] = self.wind[ind]-self.usr[ind]/kappa*(
                    np.log(self.h_in[0, ind]/self.ref10)-self.psim[ind])
        # temporary to check feed into iteration loop
        # print('Mean GF: {} | Du {} | u10n {}'.format(
        #     np.nanmean(self.GustFact), np.nanmean(self.spd-self.wind),
        #     np.nanmean(self.u10n)))
sbiri's avatar
sbiri committed
55

sbiri's avatar
sbiri committed
56

sbiri's avatar
sbiri committed
57 58 59 60
    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
61
        self.h_out = get_heights(self.hout, 1)
sbiri's avatar
sbiri committed
62

sbiri's avatar
sbiri committed
63 64 65
    def get_specHumidity(self, qmeth="Buck2"):
        self.qair, self.qsea = get_hum(self.hum, self.T, self.SST, self.P,
                                       qmeth)
sbiri's avatar
sbiri committed
66 67
        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
68 69 70
        self.dq_in = self.qair-self.qsea
        self.dq_full = self.qair-self.qsea

sbiri's avatar
sbiri committed
71 72 73 74
        # 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
75 76
        self.dt_in = self.theta-self.SST
        self.dt_full = self.theta-self.SST
sbiri's avatar
sbiri committed
77 78

    def _fix_coolskin_warmlayer(self, wl, cskin, skin, Rl, Rs):
sbiri's avatar
sbiri committed
79
        skin = self.skin if skin is None else skin
sbiri's avatar
sbiri committed
80 81
        assert wl in [0, 1], "wl not valid"
        assert cskin in [0, 1], "cskin not valid"
sbiri's avatar
sbiri committed
82 83
        assert skin in ["C35", "ecmwf", "Beljaars"], "Skin value not valid"

sbiri's avatar
sbiri committed
84 85 86 87 88 89 90 91
        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
sbiri's avatar
sbiri committed
92 93
        self.cskin = cskin
        self.skin = skin
sbiri's avatar
sbiri committed
94 95
        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
96
        # print(self.meth, self.cskin, self.skin, self.wl)
sbiri's avatar
sbiri committed
97

sbiri's avatar
sbiri committed
98
    def set_coolskin_warmlayer(self, wl=0, cskin=0, skin=None, Rl=None,
sbiri's avatar
sbiri committed
99
                               Rs=None):
sbiri's avatar
sbiri committed
100
        wl = 0 if wl is None else wl
sbiri's avatar
sbiri committed
101 102
        if hasattr(self, "skin") == False:
            self.skin = "C35"
sbiri's avatar
sbiri committed
103 104
        self._fix_coolskin_warmlayer(wl, cskin, skin, Rl, Rs)

sbiri's avatar
sbiri committed
105

sbiri's avatar
sbiri committed
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
    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
125 126
            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
127 128 129 130 131 132 133 134 135 136
            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
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
                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 height1
        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
158 159
        # self.wind = np.maximum(np.copy(self.spd), 3)
        self.wind = np.sqrt(np.power(np.copy(self.spd), 2)+0.25)
sbiri's avatar
sbiri committed
160 161 162 163
        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
164
        Rb = self.grav*self.h_in[1]*self.dtv/(self.T*np.power(self.wind, 2))
sbiri's avatar
sbiri committed
165
        self.monob = self.h_in[1]/12.0/Rb  # eq. 12 Grachev & Fairall 1997   # DO.THIS
sbiri's avatar
sbiri committed
166
        # where does 12.0 come from??
sbiri's avatar
sbiri committed
167 168 169

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

170 171
        # 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
172
        if self.cskin + self.wl > 0:
sbiri's avatar
sbiri committed
173 174
            self.dter, self.tkt, self.dtwl = [
                dummy_array(x) for x in (-0.3, 0.001, 0.3)]
175 176
            self.dqer = get_dqer(self.dter, self.SST, self.qsea,
                                 self.lv)
sbiri's avatar
sbiri committed
177 178
            self.Rnl = 0.97*(self.Rl-5.67e-8*np.power(
                self.SST-0.3*self.cskin, 4))
sbiri's avatar
sbiri committed
179 180
            self.Qs = 0.945*self.Rs
        else:
sbiri's avatar
sbiri committed
181 182 183 184
            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
185 186 187 188 189
        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
190 191
        self.cd10n, self.zo = cdn_calc(
            self.u10n, self.usr, self.theta, self.grav, self.meth)
sbiri's avatar
sbiri committed
192 193 194
        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))
sbiri's avatar
sbiri committed
195 196 197 198
        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)]
sbiri's avatar
sbiri committed
199 200 201 202 203 204 205
        self.tv10n = self.zot  # remove from output

    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
206 207 208 209
        # 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
210 211
        assert tol[0] in ['flux', 'ref', 'all'], "unknown tolerance input"

sbiri's avatar
sbiri committed
212 213
        old_vars = {"flux": ["tau", "sensible", "latent"],
                    "ref": ["u10n", "t10n", "q10n"]}
sbiri's avatar
sbiri committed
214 215 216
        old_vars["all"] = old_vars["ref"] + old_vars["flux"]
        old_vars = old_vars[tol[0]]

sbiri's avatar
sbiri committed
217 218
        new_vars = {"flux": ["tau", "sensible", "latent"],
                    "ref": ["u10n", "t10n", "q10n"]}
sbiri's avatar
sbiri committed
219 220 221 222 223 224 225 226 227 228 229
        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
230 231
        self.tsrv, self.psim, self.psit, self.psiq = [
            np.zeros(self.arr_shp)*self.msk for _ in range(4)]
sbiri's avatar
sbiri committed
232 233

        # extreme values for first comparison
234 235
        dummy_array = lambda val: np.full(self.arr_shp, val)*self.msk
        # you can use def instead of lambda
sbiri's avatar
sbiri committed
236 237 238
        # 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
239 240 241 242 243 244 245 246 247 248

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

            # Calculate cdn
sbiri's avatar
sbiri committed
252 253 254 255 256
            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
257
                logging.info('break %s at iteration %s cd10n<0', meth, it)
sbiri's avatar
sbiri committed
258 259 260 261 262 263 264
                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
265 266
            # 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
267 268 269 270 271 272
            # 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
273
            # lines below with and without gustfactor
274 275 276 277 278 279 280 281 282
            # 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
283 284 285


            # temperature
sbiri's avatar
sbiri committed
286 287 288 289 290 291 292 293
            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])
sbiri's avatar
sbiri committed
294
            # wind
sbiri's avatar
sbiri committed
295 296 297 298 299 300 301 302
            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])
sbiri's avatar
sbiri committed
303 304 305 306 307 308 309


            # Some parameterizations set a minimum on parameters
            try:
                self._minimum_params()
            except AttributeError:
                pass
sbiri's avatar
sbiri committed
310 311 312

            self.dt_full[ind] = self.dt_in[ind] - \
                self.dter[ind]*self.cskin - self.dtwl[ind]*self.wl
sbiri's avatar
sbiri committed
313
            self.dq_full[ind] = self.dq_in[ind] - self.dqer[ind]*self.cskin
sbiri's avatar
sbiri committed
314 315 316 317 318
            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
319 320 321

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

sbiri's avatar
sbiri committed
323
            # Logging output
sbiri's avatar
sbiri committed
324 325 326 327 328 329 330 331
            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
332 333

            if self.cskin + self.wl > 0:
sbiri's avatar
sbiri committed
334 335 336
                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
337 338
            # not sure how to handle lapse/potemp
            # well-mixed in potential temperature ...
339
            self.t10n[ind] = self.theta[ind]-self.tlapse[ind]*self.ref10 - \
sbiri's avatar
sbiri committed
340
                self.tsr[ind]/kappa * \
341 342
                    (np.log(self.h_in[1, ind]/self.ref10)-self.psit[ind])
            self.q10n[ind] = self.qair[ind]-self.qsr[ind]/kappa * \
sbiri's avatar
sbiri committed
343
                (np.log(self.h_in[2, ind]/self.ref10)-self.psiq[ind])
sbiri's avatar
sbiri committed
344 345

            # update stability info
sbiri's avatar
sbiri committed
346 347 348 349 350 351
            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
352
            if self.L == "tsrv":
sbiri's avatar
sbiri committed
353 354 355
                self.monob[ind] = get_Ltsrv(
                    self.tsrv[ind], self.grav[ind], self.tv[ind],
                    self.usr[ind])
sbiri's avatar
sbiri committed
356
            else:
sbiri's avatar
sbiri committed
357 358 359
                self.monob[ind] = get_LRb(
                    self.Rb[ind], self.h_in[1,ind], self.monob[ind],
                    self.zo[ind], self.zot[ind], self.meth)
sbiri's avatar
sbiri committed
360 361 362

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

sbiri's avatar
sbiri committed
364
            # make sure you allow small negative values convergence
sbiri's avatar
sbiri committed
365 366
            if it == 1:
                self.u10n = np.where(self.u10n < 0, 0.5, self.u10n)
sbiri's avatar
sbiri committed
367

sbiri's avatar
sbiri committed
368
            self.itera[ind] = np.full(1, it)
sbiri's avatar
sbiri committed
369
            # remove effect of gustiness following Fairall et al. (2003)
sbiri's avatar
sbiri committed
370 371
            # usr is divided by (GustFact)^0.5 (here applied to sensible and
            # latent as well as tau)
sbiri's avatar
sbiri committed
372 373
            # GustFact should be 1 if gust is OFF
            self.tau = self.rho*np.power(self.usr, 2)/self.GustFact
sbiri's avatar
sbiri committed
374
            self.sensible = self.rho*self.cp*self.usr / \
375
                np.sqrt(self.GustFact)*self.tsr
sbiri's avatar
sbiri committed
376
            self.latent = self.rho*self.lv*self.usr / \
377
                np.sqrt(self.GustFact)*self.qsr
sbiri's avatar
sbiri committed
378
            # or leave as it is - gusty wind speed, or no gust
sbiri's avatar
sbiri committed
379 380 381
            # 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
382 383 384 385
            # temporary as in C35, UA
            # self.tau = self.rho*np.power(self.usr, 2)/self.GustFact
            # 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
386 387

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

sbiri's avatar
sbiri committed
390
            if it > 2:  # force at least three iterations
391
                d = np.abs(new-old)  # change over this iteration
sbiri's avatar
sbiri committed
392 393 394 395
                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
396 397 398 399 400 401 402 403

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


    def _get_humidity(self):
409
        """Calculate RH used for flagging purposes & output."""
sbiri's avatar
sbiri committed
410
        if self.hum[0] in ('rh', 'no'):
sbiri's avatar
sbiri committed
411
            self.rh = self.hum[1]
sbiri's avatar
sbiri committed
412
        elif self.hum[0] == 'Td':
sbiri's avatar
sbiri committed
413 414 415
            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))
416
            # T = np.copy(self.T)
sbiri's avatar
sbiri committed
417 418 419
            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
sbiri's avatar
sbiri committed
420

421 422 423
    def _flag(self, out=0):
        """Set the general flags."""
        flag = np.full(self.arr_shp, "n", dtype="object")
sbiri's avatar
sbiri committed
424

sbiri's avatar
sbiri committed
425 426 427 428 429
        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
430
            else:
sbiri's avatar
sbiri committed
431 432
                flag = np.where(
                    np.isnan(self.spd+self.T+self.SST+self.P), "m", flag)
sbiri's avatar
sbiri committed
433
        else:
sbiri's avatar
sbiri committed
434 435
            if self.cskin == 1:
                flag = np.where(np.isnan(
436 437
                    self.spd+self.T+self.SST+self.hum[1]+self.P +
                    self.Rs+self.Rl), "m", flag)
sbiri's avatar
sbiri committed
438
            else:
sbiri's avatar
sbiri committed
439 440 441
                flag = np.where(
                    np.isnan(self.spd+self.T+self.SST+self.hum[1]+self.P),
                    "m", flag)
sbiri's avatar
sbiri committed
442 443 444 445

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

        # u10n flag
sbiri's avatar
sbiri committed
446 447 448 449 450
        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
451
        # q10n flag
sbiri's avatar
sbiri committed
452 453 454 455 456
        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))
457

sbiri's avatar
sbiri committed
458
        # t10n flag
sbiri's avatar
sbiri committed
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
        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
476
        else:
sbiri's avatar
sbiri committed
477 478 479 480 481
            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
482 483
        self.flag = flag

sbiri's avatar
sbiri committed
484 485 486
    def get_output(self, out=0):

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

488
        self._get_humidity()  # Get the Relative humidity
sbiri's avatar
sbiri committed
489 490 491 492
        self._flag(out=out)  # Get flags

        # remove effect of gustiness following Fairall et al. (2003)
        # usr is divided by (GustFact)^0.5
493
        self.uref = self.spd-self.usr/kappa/np.sqrt(self.GustFact) * \
sbiri's avatar
sbiri committed
494
            (np.log(self.h_in[0]/self.h_out[0])-self.psim +
sbiri's avatar
sbiri committed
495
              psim_calc(self.h_out[0]/self.monob, self.meth))
sbiri's avatar
sbiri committed
496
        # include lapse rate adjustment as theta is well-mixed
sbiri's avatar
sbiri committed
497 498
        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 +
sbiri's avatar
sbiri committed
499
              psit_calc(self.h_out[1]/self.monob, self.meth))
sbiri's avatar
sbiri committed
500 501
        self.qref = self.qair-self.qsr/kappa * \
            (np.log(self.h_in[2]/self.h_out[2])-self.psiq +
sbiri's avatar
sbiri committed
502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
              psit_calc(self.h_out[2]/self.monob, self.meth))
        # temporary as in 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
        # temporary as in 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))
sbiri's avatar
sbiri committed
536

sbiri's avatar
sbiri committed
537 538 539
        if self.wl == 0:
            self.dtwl = np.zeros(self.T.shape)*self.msk
            # reset to zero if not used
sbiri's avatar
sbiri committed
540 541

        # Do not calculate lhf if a measure of humidity is not input
sbiri's avatar
sbiri committed
542 543 544 545 546
        # 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
547 548 549 550 551

        # 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
552 553
        # 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
554 555 556 557 558 559
        try:
            self._class_flag()
        except AttributeError:
            pass

        # Combine all output variables into a pandas array
sbiri's avatar
sbiri committed
560 561 562
        res_vars = ("tau", "sensible", "latent", "monob", "cd", "cd10n", "ct",
                    "ct10n", "cq", "cq10n", "tsrv", "tsr", "qsr", "usr",
                    "psim", "psit", "psiq", "u10n", "t10n", "q10n", "zo",
563
                    "zot", "zoq", "uref", "tref", "qref", "dter",
sbiri's avatar
sbiri committed
564
                    "dqer", "dtwl", "qair", "qsea", "Rl", "Rs", "Rnl", "ug",
565
                    "Rb", "rh", "tkt", "lv", "itera")
sbiri's avatar
sbiri committed
566 567

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

sbiri's avatar
sbiri committed
571
        if out == 0:
sbiri's avatar
sbiri committed
572 573
            res[:, self.ind] = np.nan
            # set missing values where data have non acceptable values
sbiri's avatar
sbiri committed
574
            if self.hum[0] != 'no':
575 576 577
                res[:-1] = np.asarray([
                    np.where(self.q10n < 0, np.nan, res[i][:])
                    for i in range(len(res_vars)-1)])
578 579
                # len(res_vars)-1 instead of len(res_vars) in order to keep
                # itera= -1 for no convergence
580 581 582
            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
583
        else:
sbiri's avatar
sbiri committed
584 585 586 587
            warnings.warn("Warning: the output will contain values for points"
                          " that have not converged and negative values "
                          "(if any) for u10n/q10n")

588 589
        resAll = pd.DataFrame(data=res.T, index=range(self.nlen),
                              columns=res_vars)
sbiri's avatar
sbiri committed
590 591 592 593 594 595 596 597

        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
598 599 600 601
        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
602 603 604
        self.arr_shp = spd.shape
        self.nlen = len(spd)
        self.spd = spd
sbiri's avatar
sbiri committed
605
        # self.T = T
sbiri's avatar
sbiri committed
606
        self.T = np.where(T < 200, np.copy(T)+CtoK, np.copy(T))
sbiri's avatar
sbiri committed
607
        self.hum = ['no', np.full(SST.shape, 80)] if hum is None else hum
sbiri's avatar
sbiri committed
608
        self.SST = np.where(SST < 200, np.copy(SST)+CtoK, np.copy(SST))
sbiri's avatar
sbiri committed
609
        self.lat = np.full(self.arr_shp, 45) if lat is None else lat
sbiri's avatar
sbiri committed
610
        self.grav = gc(self.lat)
611
        self.P = np.full(self.nlen, 1013) if P is None else P
sbiri's avatar
sbiri committed
612 613

        # mask to preserve missing values when initialising variables
sbiri's avatar
sbiri committed
614
        self.msk = np.empty(SST.shape)
sbiri's avatar
sbiri committed
615 616 617
        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
618 619
    def add_gust(self, gust=None):
        if np.all(gust is None):
sbiri's avatar
sbiri committed
620 621 622
            try:
                gust = self.default_gust
            except AttributeError:
sbiri's avatar
sbiri committed
623 624
                gust = [0, 0, 0]  # gustiness OFF
                # gust = [1, 1.2, 800]
sbiri's avatar
sbiri committed
625 626
        elif ((np.size(gust) < 3) and (gust == 0)):
            gust = [0, 0, 0]
sbiri's avatar
sbiri committed
627

sbiri's avatar
sbiri committed
628
        assert np.size(gust) == 3, "gust input must be a 3x1 array"
629
        assert gust[0] in [0, 1, 2], "gust at position 0 must be 0, 1 or 2"
sbiri's avatar
sbiri committed
630 631 632
        self.gust = gust

    def _class_flag(self):
sbiri's avatar
sbiri committed
633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656
        """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
657 658 659 660 661 662 663 664 665
                                      self.flag+[","]+["o"], self.flag))

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

class S80(S88):

    def __init__(self):
        self.meth = "S80"
sbiri's avatar
sbiri committed
666 667
        self.u_lo = [6, 6]
        self.u_hi = [22, 22]
sbiri's avatar
sbiri committed
668

sbiri's avatar
sbiri committed
669

sbiri's avatar
sbiri committed
670 671 672 673
class YT96(S88):

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

class LP82(S88):
sbiri's avatar
sbiri committed
679

sbiri's avatar
sbiri committed
680 681
    def __init__(self):
        self.meth = "LP82"
sbiri's avatar
sbiri committed
682 683
        self.u_lo = [3, 3]
        self.u_hi = [25, 25]
sbiri's avatar
sbiri committed
684 685 686 687 688 689 690 691 692 693


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)
sbiri's avatar
sbiri committed
694

sbiri's avatar
sbiri committed
695 696
    def __init__(self):
        self.meth = "NCAR"
sbiri's avatar
sbiri committed
697 698
        self.u_lo = [0.5, 0.5]
        self.u_hi = [999, 999]
sbiri's avatar
sbiri committed
699 700

class UA(S88):
sbiri's avatar
sbiri committed
701

sbiri's avatar
sbiri committed
702 703
    def __init__(self):
        self.meth = "UA"
sbiri's avatar
sbiri committed
704 705 706
        self.default_gust = [1, 1, 1000]
        self.u_lo = [-999, -999]
        self.u_hi = [18, 18]
sbiri's avatar
sbiri committed
707 708 709


class C30(S88):
sbiri's avatar
sbiri committed
710 711
    # 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
712 713 714

    def __init__(self):
        self.meth = "C30"
sbiri's avatar
sbiri committed
715
        self.default_gust = [1, 1.2, 600]
sbiri's avatar
sbiri committed
716
        self.skin = "C35"
sbiri's avatar
sbiri committed
717 718 719 720

class C35(C30):
    def __init__(self):
        self.meth = "C35"
sbiri's avatar
sbiri committed
721
        self.default_gust = [1, 1.2, 600]
sbiri's avatar
sbiri committed
722
        self.skin = "C35"
sbiri's avatar
sbiri committed
723 724

class ecmwf(C30):
sbiri's avatar
sbiri committed
725 726 727
    # def set_coolskin_warmlayer(self, wl=0, cskin=1, skin="ecmwf", Rl=None,
    #                            Rs=None):
    #     self._fix_coolskin_warmlayer(wl, cskin, skin, Rl, Rs)
sbiri's avatar
sbiri committed
728 729 730

    def __init__(self):
        self.meth = "ecmwf"
sbiri's avatar
sbiri committed
731
        self.default_gust = [1, 1, 1000]
sbiri's avatar
sbiri committed
732
        self.skin = "ecmwf"
sbiri's avatar
sbiri committed
733 734

class Beljaars(C30):
sbiri's avatar
sbiri committed
735 736 737
    # 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
738

sbiri's avatar
sbiri committed
739 740
    def __init__(self):
        self.meth = "Beljaars"
sbiri's avatar
sbiri committed
741
        self.default_gust = [1, 1, 1000]
sbiri's avatar
sbiri committed
742
        self.skin = "Beljaars"
sbiri's avatar
sbiri committed
743 744


745
def AirSeaFluxCode(spd, T, SST, lat=None, hum=None, P=None, hin=18, hout=10,
sbiri's avatar
sbiri committed
746
                   Rl=None, Rs=None, cskin=0, skin=None, wl=0, gust=None,
sbiri's avatar
sbiri committed
747 748
                   meth="S88", qmeth="Buck2", tol=None, maxiter=10, out=0,
                   L=None):
749
    """
sbiri's avatar
sbiri committed
750 751 752
    Calculate turbulent surface fluxes using different parameterizations.

    Calculate height adjusted values for spd, T, q
sbiri's avatar
sbiri committed
753 754 755 756 757 758 759 760 761 762 763

    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
764 765 766
            latitude (deg), default 45deg
        hum : float
            humidity input switch 2x1 [x, values] default is relative humidity
767 768 769
            x='rh' : relative humidity in %
            x='q' : specific humidity (g/kg)
            x='Td' : dew point temperature (K)
sbiri's avatar
sbiri committed
770
        P : float
771
            air pressure (hPa), default 1013hPa
sbiri's avatar
sbiri committed
772
        hin : float
773
            sensor heights in m (array 3x1 or 3xn), default 18m
sbiri's avatar
sbiri committed
774 775 776 777 778 779
        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
780
        cskin : int
781 782
            0 switch cool skin adjustment off, else 1
            default is 1
783 784 785 786
        skin : str
            cool skin method option "C35", "ecmwf" or "Beljaars"
        wl : int
            warm layer correction default is 0, to switch on set to 1
787
        gust : int
788 789
            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;
790
            beta gustiness parameter, beta=1 for UA, beta=1.2 for COARE
791
            zi PBL height (m) 600 for COARE, 1000 for UA and ecmwf
792
            default for COARE [1, 1.2, 600]
793
            default for UA, ecmwf [1, 1, 1000]
794
            else default is switched OFF
795
        meth : str
sbiri's avatar
sbiri committed
796
            "S80", "S88", "LP82", "YT96", "UA", "NCAR", "C30", "C35",
797
            "ecmwf", "Beljaars"
798 799
        qmeth : str
            is the saturation evaporation method to use amongst
800 801
            "HylandWexler","Hardy","Preining","Wexler","GoffGratch","WMO",
            "MagnusTetens","Buck","Buck2","WMO2018","Sonntag","Bolton",
802 803 804 805 806 807 808
            "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
809 810
                    adjustment lim1-6
           default is tol=['all', 0.01, 0.01, 1e-05, 1e-3, 0.1, 0.1]
sbiri's avatar
sbiri committed
811 812
        maxiter : int
            number of iterations (default = 10)
813
        out : int
814 815
            set 0 to set points that have not converged, negative values of
                  u10n and q10n to missing (default)
816
            set 1 to keep points
817
        L : str
sbiri's avatar
sbiri committed
818
           Monin-Obukhov length definition options
sbiri's avatar
sbiri committed
819
           "tsrv"  : default for "S80", "S88", "LP82", "YT96", "UA", "NCAR",
sbiri's avatar
sbiri committed
820 821 822
                     "C30", "C35"
           "Rb" : following ecmwf (IFS Documentation cy46r1), default for
                  "ecmwf", "Beljaars"
sbiri's avatar
sbiri committed
823 824 825
    Returns
    -------
        res : array that contains
sbiri's avatar
sbiri committed
826 827 828 829
                       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
830
                       5. drag coefficient (cd)
sbiri's avatar
sbiri committed
831
                       6. neutral drag coefficient (cd10n)
sbiri's avatar
sbiri committed
832
                       7. heat exchange coefficient (ct)
sbiri's avatar
sbiri committed
833
                       8. neutral heat exchange coefficient (ct10n)
sbiri's avatar
sbiri committed
834
                       9. moisture exhange coefficient (cq)
sbiri's avatar
sbiri committed
835
                       10. neutral moisture exchange coefficient (cq10n)
sbiri's avatar
sbiri committed
836
                       11. star virtual temperatcure (tsrv)
sbiri's avatar
sbiri committed
837
                       12. star temperature (tsr)
838 839
                       13. star specific humidity (qsr)
                       14. star wind speed (usr)
sbiri's avatar
sbiri committed
840
                       15. momentum stability function (psim)
841 842
                       16. heat stability function (psit)
                       17. moisture stability function (psiq)
843
                       18. 10m neutral wind speed (u10n)
844
                       19. 10m neutral temperature (t10n)
sbiri's avatar
sbiri committed
845 846 847 848 849 850 851
                       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)
852 853 854 855 856 857 858 859 860 861 862 863 864 865
                       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)
                       38. thickness of the viscous layer (delta)
                       39. lv latent heat of vaporization (Jkg−1)
                       40. number of iterations until convergence
sbiri's avatar
sbiri committed
866
                       41. flag ("n": normal, "o": out of nominal range,
867
                                 "u": u10n<0, "q":q10n<0
868
                                 "m": missing,
869
                                 "l": Rib<-0.5 or Rib>0.2 or z/L>1000,
sbiri's avatar
sbiri committed
870
                                 "r" : rh>100%,
871
                                 "t" : t10n<173K or t10n>373K
872
                                 "i": convergence fail at n)
873

874
    2021 / Author S. Biri
875 876
    2021 / Restructured by R. Cornes
    2021 / Simplified by E. Kent
sbiri's avatar
sbiri committed
877
    """
sbiri's avatar
sbiri committed
878
    logging.basicConfig(filename='flux_calc.log', filemode="w",
879
                        format='%(asctime)s %(message)s', level=logging.INFO)
880
    logging.captureWarnings(True)
sbiri's avatar
sbiri committed
881 882 883 884 885 886

    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
887 888
    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
889 890
    resAll = iclass.get_output(out=out)

891
    return resAll