AirSeaFluxCode.py 35 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, 3]:
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
                                                       self.tsrv[ind],
                                                       self.gust[2],
20
                                                       self.grav[ind]), 2))
21
            # ratio of gusty to horizontal wind
sbiri's avatar
sbiri committed
22
            self.GustFact[ind] = self.wind[ind]/self.spd[ind]
sbiri's avatar
sbiri committed
23 24 25
            # 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
26
            self.u10n[ind] = self.spd[ind]-self.usr[ind]/kappa/np.sqrt(
27
                self.GustFact[ind])*(np.log(self.h_in[0, ind]/self.ref10) -
28
                                     self.psim[ind])
29 30 31
            if self.gust[0] == 2:
                self.GustFact[ind] = 1
                # option to not remove GustFact
sbiri's avatar
sbiri committed
32 33 34 35
                # self.u10n[ind] = self.wind[ind]-self.usr[ind]/kappa*(
                #     np.log(self.h_in[0, ind]/self.ref10)-self.psim[ind])
                self.u10n[ind] = self.usr[ind]/kappa/np.log(
                    self.ref10/self.zo[ind])
sbiri's avatar
sbiri committed
36
        else:
sbiri's avatar
sbiri committed
37
            # initalisation of wind
sbiri's avatar
sbiri committed
38
            self.wind[ind] = np.copy(self.spd[ind])
sbiri's avatar
sbiri committed
39
            self.u10n[ind] = self.wind[ind]-self.usr[ind]/kappa*(
40
                np.log(self.h_in[0, ind]/self.ref10)-self.psim[ind])
sbiri's avatar
sbiri committed
41

sbiri's avatar
sbiri committed
42 43 44 45
    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
46
        self.h_out = get_heights(self.hout, 1)
sbiri's avatar
sbiri committed
47

sbiri's avatar
sbiri committed
48 49 50
    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
51 52
        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
53 54 55
        self.dq_in = self.qair-self.qsea
        self.dq_full = self.qair-self.qsea

sbiri's avatar
sbiri committed
56 57 58 59
        # 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
60 61
        self.dt_in = self.theta-self.SST
        self.dt_full = self.theta-self.SST
sbiri's avatar
sbiri committed
62 63

    def _fix_coolskin_warmlayer(self, wl, cskin, skin, Rl, Rs):
sbiri's avatar
sbiri committed
64
        skin = self.skin if skin is None else skin
sbiri's avatar
sbiri committed
65 66
        assert wl in [0, 1], "wl not valid"
        assert cskin in [0, 1], "cskin not valid"
sbiri's avatar
sbiri committed
67 68
        assert skin in ["C35", "ecmwf", "Beljaars"], "Skin value not valid"

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

        self.wl = wl
sbiri's avatar
sbiri committed
77 78
        self.cskin = cskin
        self.skin = skin
sbiri's avatar
sbiri committed
79 80
        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
81

sbiri's avatar
sbiri committed
82
    def set_coolskin_warmlayer(self, wl=0, cskin=0, skin=None, Rl=None,
sbiri's avatar
sbiri committed
83
                               Rs=None):
sbiri's avatar
sbiri committed
84
        wl = 0 if wl is None else wl
sbiri's avatar
sbiri committed
85 86
        if hasattr(self, "skin") == False:
            self.skin = "C35"
sbiri's avatar
sbiri committed
87 88
        self._fix_coolskin_warmlayer(wl, cskin, skin, Rl, Rs)

sbiri's avatar
sbiri committed
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
    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
108 109
            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
110 111 112 113 114 115 116 117 118 119
            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
120 121 122 123 124 125 126 127
                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):
128
        # reference height
sbiri's avatar
sbiri committed
129 130 131 132 133 134 135 136 137 138 139 140
        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
141
        self.wind = np.sqrt(np.power(np.copy(self.spd), 2)+0.25)
sbiri's avatar
sbiri committed
142 143 144 145
        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
146
        Rb = self.grav*self.h_in[1]*self.dtv/(self.T*np.power(self.wind, 2))
147 148
        # eq. 12 Grachev & Fairall 1997   # DO.THIS
        self.monob = self.h_in[1]/12.0/Rb
sbiri's avatar
sbiri committed
149
        # where does 12.0 come from??
sbiri's avatar
sbiri committed
150 151 152

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

153 154
        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
155
        if self.cskin + self.wl > 0:
sbiri's avatar
sbiri committed
156 157
            self.dter, self.tkt, self.dtwl = [
                dummy_array(x) for x in (-0.3, 0.001, 0.3)]
158 159
            self.dqer = get_dqer(self.dter, self.SST, self.qsea,
                                 self.lv)
sbiri's avatar
sbiri committed
160 161
            self.Rnl = 0.97*(self.Rl-5.67e-8*np.power(
                self.SST-0.3*self.cskin, 4))
sbiri's avatar
sbiri committed
162 163
            self.Qs = 0.945*self.Rs
        else:
sbiri's avatar
sbiri committed
164 165 166 167
            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
168 169 170 171 172
        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
173 174
        self.cd10n, self.zo = cdn_calc(
            self.u10n, self.usr, self.theta, self.grav, self.meth)
sbiri's avatar
sbiri committed
175 176
        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)
177
        self.usr =np.sqrt(self.cd*np.power(self.wind, 2))
sbiri's avatar
sbiri committed
178 179 180 181
        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)]
182
        self.tv10n = self.zot
sbiri's avatar
sbiri committed
183 184 185 186 187 188

    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
189 190 191 192
        # 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
193 194
        assert tol[0] in ['flux', 'ref', 'all'], "unknown tolerance input"

sbiri's avatar
sbiri committed
195 196
        old_vars = {"flux": ["tau", "sensible", "latent"],
                    "ref": ["u10n", "t10n", "q10n"]}
sbiri's avatar
sbiri committed
197 198 199
        old_vars["all"] = old_vars["ref"] + old_vars["flux"]
        old_vars = old_vars[tol[0]]

sbiri's avatar
sbiri committed
200 201
        new_vars = {"flux": ["tau", "sensible", "latent"],
                    "ref": ["u10n", "t10n", "q10n"]}
sbiri's avatar
sbiri committed
202 203 204 205 206 207 208 209 210 211 212
        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
213 214
        self.tsrv, self.psim, self.psit, self.psiq = [
            np.zeros(self.arr_shp)*self.msk for _ in range(4)]
sbiri's avatar
sbiri committed
215 216

        # extreme values for first comparison
217
        dummy_array = lambda val : np.full(self.T.shape, val)*self.msk
218
        # you can use def instead of lambda
sbiri's avatar
sbiri committed
219 220 221
        # 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
222 223 224 225 226 227 228 229 230 231

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

            # Calculate cdn
sbiri's avatar
sbiri committed
235 236 237 238 239
            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
240
                logging.info('break %s at iteration %s cd10n<0', meth, it)
sbiri's avatar
sbiri committed
241 242 243 244 245 246
                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])
247 248
            # Update the wind values
            self._wind_iterate(ind)
sbiri's avatar
sbiri committed
249 250

            # temperature
sbiri's avatar
sbiri committed
251 252 253 254 255 256 257 258
            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])
259
            # humidity
sbiri's avatar
sbiri committed
260 261 262 263 264 265 266 267
            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])
268

sbiri's avatar
sbiri committed
269 270 271 272 273
            # Some parameterizations set a minimum on parameters
            try:
                self._minimum_params()
            except AttributeError:
                pass
sbiri's avatar
sbiri committed
274 275 276

            self.dt_full[ind] = self.dt_in[ind] - \
                self.dter[ind]*self.cskin - self.dtwl[ind]*self.wl
sbiri's avatar
sbiri committed
277
            self.dq_full[ind] = self.dq_in[ind] - self.dqer[ind]*self.cskin
sbiri's avatar
sbiri committed
278 279 280 281 282
            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
283 284 285

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

sbiri's avatar
sbiri committed
287
            # Logging output
sbiri's avatar
sbiri committed
288 289 290 291 292 293 294 295
            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
296 297

            if self.cskin + self.wl > 0:
sbiri's avatar
sbiri committed
298 299 300
                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
301 302
            # not sure how to handle lapse/potemp
            # well-mixed in potential temperature ...
303
            self.t10n[ind] = self.theta[ind]-self.tlapse[ind]*self.ref10 - \
sbiri's avatar
sbiri committed
304
                self.tsr[ind]/kappa * \
305
                (np.log(self.h_in[1, ind]/self.ref10)-self.psit[ind])
306
            self.q10n[ind] = self.qair[ind]-self.qsr[ind]/kappa * \
sbiri's avatar
sbiri committed
307
                (np.log(self.h_in[2, ind]/self.ref10)-self.psiq[ind])
sbiri's avatar
sbiri committed
308 309

            # update stability info
sbiri's avatar
sbiri committed
310 311 312 313 314 315
            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
316
            if self.L == "tsrv":
sbiri's avatar
sbiri committed
317 318 319
                self.monob[ind] = get_Ltsrv(
                    self.tsrv[ind], self.grav[ind], self.tv[ind],
                    self.usr[ind])
sbiri's avatar
sbiri committed
320
            else:
sbiri's avatar
sbiri committed
321
                self.monob[ind] = get_LRb(
322
                    self.Rb[ind], self.h_in[1, ind], self.monob[ind],
sbiri's avatar
sbiri committed
323
                    self.zo[ind], self.zot[ind], self.meth)
sbiri's avatar
sbiri committed
324 325 326

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

sbiri's avatar
sbiri committed
328
            # make sure you allow small negative values convergence
sbiri's avatar
sbiri committed
329 330
            if it == 1:
                self.u10n = np.where(self.u10n < 0, 0.5, self.u10n)
sbiri's avatar
sbiri committed
331

sbiri's avatar
sbiri committed
332
            self.itera[ind] = np.full(1, it)
sbiri's avatar
sbiri committed
333
            # remove effect of gustiness following Fairall et al. (2003)
sbiri's avatar
sbiri committed
334 335
            # usr is divided by (GustFact)^0.5 (here applied to sensible and
            # latent as well as tau)
336
            # GustFact should be 1 if gust is OFF or gust[0]=2
sbiri's avatar
sbiri committed
337
            self.tau = self.rho*np.power(self.usr, 2)/self.GustFact
338 339 340 341 342 343 344 345
            if self.gust[0] == 3:
                self.sensible = self.rho*self.cp*self.usr*self.tsr
                self.latent = self.rho*self.lv*self.usr*self.qsr
            else:
                self.sensible = self.rho*self.cp*self.usr / \
                    np.sqrt(self.GustFact)*self.tsr
                self.latent = self.rho*self.lv*self.usr / \
                    np.sqrt(self.GustFact)*self.qsr
sbiri's avatar
sbiri committed
346 347

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

sbiri's avatar
sbiri committed
350
            if it > 2:  # force at least three iterations
351
                d = np.abs(new-old)  # change over this iteration
sbiri's avatar
sbiri committed
352 353 354 355
                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
356 357 358 359 360 361 362 363

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

    def _get_humidity(self):
368
        """Calculate RH used for flagging purposes & output."""
sbiri's avatar
sbiri committed
369
        if self.hum[0] in ('rh', 'no'):
sbiri's avatar
sbiri committed
370
            self.rh = self.hum[1]
sbiri's avatar
sbiri committed
371
        elif self.hum[0] == 'Td':
sbiri's avatar
sbiri committed
372 373 374
            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))
375
            # T = np.copy(self.T)
sbiri's avatar
sbiri committed
376 377 378
            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
379 380 381 382
        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
383

384 385 386
    def _flag(self, out=0):
        """Set the general flags."""
        flag = np.full(self.arr_shp, "n", dtype="object")
sbiri's avatar
sbiri committed
387

sbiri's avatar
sbiri committed
388 389 390 391 392
        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
393
            else:
sbiri's avatar
sbiri committed
394 395
                flag = np.where(
                    np.isnan(self.spd+self.T+self.SST+self.P), "m", flag)
sbiri's avatar
sbiri committed
396
        else:
sbiri's avatar
sbiri committed
397 398
            if self.cskin == 1:
                flag = np.where(np.isnan(
399 400
                    self.spd+self.T+self.SST+self.hum[1]+self.P +
                    self.Rs+self.Rl), "m", flag)
sbiri's avatar
sbiri committed
401
            else:
sbiri's avatar
sbiri committed
402 403 404
                flag = np.where(
                    np.isnan(self.spd+self.T+self.SST+self.hum[1]+self.P),
                    "m", flag)
sbiri's avatar
sbiri committed
405 406 407 408

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

        # u10n flag
sbiri's avatar
sbiri committed
409 410 411 412 413
        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
414
        # q10n flag
sbiri's avatar
sbiri committed
415 416 417 418 419
        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))
420

sbiri's avatar
sbiri committed
421
        # t10n flag
sbiri's avatar
sbiri committed
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
        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
439
        else:
sbiri's avatar
sbiri committed
440 441 442 443 444
            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
445 446
        self.flag = flag

sbiri's avatar
sbiri committed
447 448 449
    def get_output(self, out=0):

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

451
        self._get_humidity()  # Get the Relative humidity
sbiri's avatar
sbiri committed
452 453 454 455
        self._flag(out=out)  # Get flags

        # remove effect of gustiness following Fairall et al. (2003)
        # usr is divided by (GustFact)^0.5
456
        self.uref = self.spd-self.usr/kappa/np.sqrt(self.GustFact) * \
sbiri's avatar
sbiri committed
457
            (np.log(self.h_in[0]/self.h_out[0])-self.psim +
458
             psim_calc(self.h_out[0]/self.monob, self.meth))
sbiri's avatar
sbiri committed
459
        # include lapse rate adjustment as theta is well-mixed
sbiri's avatar
sbiri committed
460 461
        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 +
462
             psit_calc(self.h_out[1]/self.monob, self.meth))
sbiri's avatar
sbiri committed
463 464
        self.qref = self.qair-self.qsr/kappa * \
            (np.log(self.h_in[2]/self.h_out[2])-self.psiq +
465
             psit_calc(self.h_out[2]/self.monob, self.meth))
sbiri's avatar
sbiri committed
466

sbiri's avatar
sbiri committed
467 468 469
        if self.wl == 0:
            self.dtwl = np.zeros(self.T.shape)*self.msk
            # reset to zero if not used
sbiri's avatar
sbiri committed
470 471

        # Do not calculate lhf if a measure of humidity is not input
sbiri's avatar
sbiri committed
472 473 474 475 476
        # 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
477 478 479 480 481

        # 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
482 483
        # 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
484 485 486 487 488 489
        try:
            self._class_flag()
        except AttributeError:
            pass

        # Combine all output variables into a pandas array
sbiri's avatar
sbiri committed
490 491 492
        res_vars = ("tau", "sensible", "latent", "monob", "cd", "cd10n", "ct",
                    "ct10n", "cq", "cq10n", "tsrv", "tsr", "qsr", "usr",
                    "psim", "psit", "psiq", "u10n", "t10n", "q10n", "zo",
493
                    "zot", "zoq", "uref", "tref", "qref", "dter",
sbiri's avatar
sbiri committed
494
                    "dqer", "dtwl", "qair", "qsea", "Rl", "Rs", "Rnl", "ug",
495
                    "Rb", "rh", "rho", "cp", "tkt", "lv", "itera")
sbiri's avatar
sbiri committed
496 497

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

sbiri's avatar
sbiri committed
501
        if out == 0:
sbiri's avatar
sbiri committed
502 503
            res[:, self.ind] = np.nan
            # set missing values where data have non acceptable values
sbiri's avatar
sbiri committed
504
            if self.hum[0] != 'no':
505 506 507
                res[:-1] = np.asarray([
                    np.where(self.q10n < 0, np.nan, res[i][:])
                    for i in range(len(res_vars)-1)])
508 509
                # len(res_vars)-1 instead of len(res_vars) in order to keep
                # itera= -1 for no convergence
510 511 512
            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
513
        else:
sbiri's avatar
sbiri committed
514 515 516 517
            warnings.warn("Warning: the output will contain values for points"
                          " that have not converged and negative values "
                          "(if any) for u10n/q10n")

518 519
        resAll = pd.DataFrame(data=res.T, index=range(self.nlen),
                              columns=res_vars)
sbiri's avatar
sbiri committed
520 521 522 523 524 525 526 527

        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
528 529 530 531
        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
532 533 534 535
        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
536
        self.hum = ['no', np.full(SST.shape, 80)] if hum is None else hum
sbiri's avatar
sbiri committed
537
        self.SST = np.where(SST < 200, np.copy(SST)+CtoK, np.copy(SST))
sbiri's avatar
sbiri committed
538
        self.lat = np.full(self.arr_shp, 45) if lat is None else lat
sbiri's avatar
sbiri committed
539
        self.grav = gc(self.lat)
540
        self.P = np.full(self.nlen, 1013) if P is None else P
sbiri's avatar
sbiri committed
541 542

        # mask to preserve missing values when initialising variables
sbiri's avatar
sbiri committed
543
        self.msk = np.empty(SST.shape)
sbiri's avatar
sbiri committed
544 545 546
        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
547 548
    def add_gust(self, gust=None):
        if np.all(gust is None):
sbiri's avatar
sbiri committed
549 550 551
            try:
                gust = self.default_gust
            except AttributeError:
sbiri's avatar
sbiri committed
552
                gust = [0, 0, 0]  # gustiness OFF
sbiri's avatar
sbiri committed
553 554
        elif ((np.size(gust) < 3) and (gust == 0)):
            gust = [0, 0, 0]
sbiri's avatar
sbiri committed
555

sbiri's avatar
sbiri committed
556
        assert np.size(gust) == 3, "gust input must be a 3x1 array"
557 558
        assert gust[0] in [
            0, 1, 2, 3], "gust at position 0 must be 0, 1, 2 or 3"
sbiri's avatar
sbiri committed
559 560 561
        self.gust = gust

    def _class_flag(self):
sbiri's avatar
sbiri committed
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
        """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
586 587 588 589 590
                                      self.flag+[","]+["o"], self.flag))

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

591

sbiri's avatar
sbiri committed
592 593 594 595
class S80(S88):

    def __init__(self):
        self.meth = "S80"
sbiri's avatar
sbiri committed
596 597
        self.u_lo = [6, 6]
        self.u_hi = [22, 22]
sbiri's avatar
sbiri committed
598

sbiri's avatar
sbiri committed
599

sbiri's avatar
sbiri committed
600 601 602 603
class YT96(S88):

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

608

sbiri's avatar
sbiri committed
609
class LP82(S88):
sbiri's avatar
sbiri committed
610

sbiri's avatar
sbiri committed
611 612
    def __init__(self):
        self.meth = "LP82"
sbiri's avatar
sbiri committed
613 614
        self.u_lo = [3, 3]
        self.u_hi = [25, 25]
sbiri's avatar
sbiri committed
615 616 617 618 619 620 621 622


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)
sbiri's avatar
sbiri committed
623
        # self.zo = np.minimum(np.copy(self.zo), 0.0025)
sbiri's avatar
sbiri committed
624

sbiri's avatar
sbiri committed
625 626
    def __init__(self):
        self.meth = "NCAR"
sbiri's avatar
sbiri committed
627 628
        self.u_lo = [0.5, 0.5]
        self.u_hi = [999, 999]
sbiri's avatar
sbiri committed
629

630

sbiri's avatar
sbiri committed
631
class UA(S88):
sbiri's avatar
sbiri committed
632

sbiri's avatar
sbiri committed
633 634
    def __init__(self):
        self.meth = "UA"
sbiri's avatar
sbiri committed
635
        self.default_gust = [3, 1, 1000]
sbiri's avatar
sbiri committed
636 637
        self.u_lo = [-999, -999]
        self.u_hi = [18, 18]
sbiri's avatar
sbiri committed
638 639 640


class C30(S88):
sbiri's avatar
sbiri committed
641 642
    # 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
643 644 645

    def __init__(self):
        self.meth = "C30"
sbiri's avatar
sbiri committed
646
        self.default_gust = [3, 1.2, 600]
sbiri's avatar
sbiri committed
647
        self.skin = "C35"
sbiri's avatar
sbiri committed
648

649

sbiri's avatar
sbiri committed
650 651 652
class C35(C30):
    def __init__(self):
        self.meth = "C35"
sbiri's avatar
sbiri committed
653
        self.default_gust = [3, 1.2, 600]
sbiri's avatar
sbiri committed
654
        self.skin = "C35"
sbiri's avatar
sbiri committed
655

656

sbiri's avatar
sbiri committed
657
class ecmwf(C30):
sbiri's avatar
sbiri committed
658 659 660
    # def set_coolskin_warmlayer(self, wl=0, cskin=1, skin="ecmwf", Rl=None,
    #                            Rs=None):
    #     self._fix_coolskin_warmlayer(wl, cskin, skin, Rl, Rs)
661 662 663 664 665
    # 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
666 667 668

    def __init__(self):
        self.meth = "ecmwf"
sbiri's avatar
sbiri committed
669
        self.default_gust = [3, 1, 1000]
sbiri's avatar
sbiri committed
670
        self.skin = "ecmwf"
sbiri's avatar
sbiri committed
671

672

sbiri's avatar
sbiri committed
673
class Beljaars(C30):
sbiri's avatar
sbiri committed
674 675 676
    # 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
677

sbiri's avatar
sbiri committed
678 679
    def __init__(self):
        self.meth = "Beljaars"
sbiri's avatar
sbiri committed
680
        self.default_gust = [3, 1, 1000]
sbiri's avatar
sbiri committed
681
        self.skin = "Beljaars"
sbiri's avatar
sbiri committed
682 683


684
def AirSeaFluxCode(spd, T, SST, lat=None, hum=None, P=None, hin=18, hout=10,
sbiri's avatar
sbiri committed
685
                   Rl=None, Rs=None, cskin=0, skin=None, wl=0, gust=None,
sbiri's avatar
sbiri committed
686 687
                   meth="S88", qmeth="Buck2", tol=None, maxiter=10, out=0,
                   L=None):
688
    """
sbiri's avatar
sbiri committed
689 690 691
    Calculate turbulent surface fluxes using different parameterizations.

    Calculate height adjusted values for spd, T, q
sbiri's avatar
sbiri committed
692 693 694 695 696 697 698 699 700 701 702

    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
703 704 705
            latitude (deg), default 45deg
        hum : float
            humidity input switch 2x1 [x, values] default is relative humidity
706 707 708
            x='rh' : relative humidity in %
            x='q' : specific humidity (g/kg)
            x='Td' : dew point temperature (K)
sbiri's avatar
sbiri committed
709
        P : float
710
            air pressure (hPa), default 1013hPa
sbiri's avatar
sbiri committed
711
        hin : float
712
            sensor heights in m (array 3x1 or 3xn), default 18m
sbiri's avatar
sbiri committed
713 714 715 716 717 718
        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
719
        cskin : int
720
            0 switch cool skin adjustment off, else 1
721
            default is 0
722 723 724 725
        skin : str
            cool skin method option "C35", "ecmwf" or "Beljaars"
        wl : int
            warm layer correction default is 0, to switch on set to 1
726
        gust : int
727
            3x1 [x, beta, zi] x=0 gustiness is OFF, x=1 gustiness is ON and
728 729
            use gustiness factor, x=2 gustiness is ON and gustiness factor=1,
            x=3 gustiness is ON and gustiness factor=1 for the heat fluxes;
730
            beta gustiness parameter, beta=1 for UA, beta=1.2 for COARE
731
            zi PBL height (m) 600 for COARE, 1000 for UA and ecmwf
732
            default is switched OFF
733
        meth : str
sbiri's avatar
sbiri committed
734
            "S80", "S88", "LP82", "YT96", "UA", "NCAR", "C30", "C35",
735
            "ecmwf", "Beljaars"
736 737
        qmeth : str
            is the saturation evaporation method to use amongst
738 739
            "HylandWexler","Hardy","Preining","Wexler","GoffGratch","WMO",
            "MagnusTetens","Buck","Buck2","WMO2018","Sonntag","Bolton",
740 741 742 743 744 745 746
            "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
747 748
                    adjustment lim1-6
           default is tol=['all', 0.01, 0.01, 1e-05, 1e-3, 0.1, 0.1]
sbiri's avatar
sbiri committed
749 750
        maxiter : int
            number of iterations (default = 10)
751
        out : int
752 753
            set 0 to set points that have not converged, negative values of
                  u10n and q10n to missing (default)
754
            set 1 to keep points
755
        L : str
sbiri's avatar
sbiri committed
756
           Monin-Obukhov length definition options
757 758 759
           "tsrv"  : default
           "Rb" : following ecmwf (IFS Documentation cy46r1)

sbiri's avatar
sbiri committed
760 761 762
    Returns
    -------
        res : array that contains
sbiri's avatar
sbiri committed
763 764 765 766
                       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
767
                       5. drag coefficient (cd)
sbiri's avatar
sbiri committed
768
                       6. neutral drag coefficient (cd10n)
sbiri's avatar
sbiri committed
769
                       7. heat exchange coefficient (ct)
sbiri's avatar
sbiri committed
770
                       8. neutral heat exchange coefficient (ct10n)
sbiri's avatar
sbiri committed
771
                       9. moisture exhange coefficient (cq)
sbiri's avatar
sbiri committed
772
                       10. neutral moisture exchange coefficient (cq10n)
sbiri's avatar
sbiri committed
773
                       11. star virtual temperatcure (tsrv)
sbiri's avatar
sbiri committed
774
                       12. star temperature (tsr)
775 776
                       13. star specific humidity (qsr)
                       14. star wind speed (usr)
sbiri's avatar
sbiri committed
777
                       15. momentum stability function (psim)
778 779
                       16. heat stability function (psit)
                       17. moisture stability function (psiq)
780
                       18. 10m neutral wind speed (u10n)
781
                       19. 10m neutral temperature (t10n)
sbiri's avatar
sbiri committed
782 783 784 785 786 787 788
                       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)
789 790 791 792 793 794 795 796 797 798 799
                       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)
800 801 802 803 804 805
                       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,
806
                                 "u": u10n<0, "q":q10n<0
807
                                 "m": missing,
808
                                 "l": Rib<-0.5 or Rib>0.2 or z/L>1000,
sbiri's avatar
sbiri committed
809
                                 "r" : rh>100%,
810
                                 "t" : t10n<173K or t10n>373K
811
                                 "i": convergence fail at n)
812

813
    2021 / Author S. Biri
814 815
    2021 / Restructured by R. Cornes
    2021 / Simplified by E. Kent
sbiri's avatar
sbiri committed
816
    """
sbiri's avatar
sbiri committed
817
    logging.basicConfig(filename='flux_calc.log', filemode="w",
818
                        format='%(asctime)s %(message)s', level=logging.INFO)
819
    logging.captureWarnings(True)
sbiri's avatar
sbiri committed
820 821 822 823 824 825

    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
826 827
    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
828 829
    resAll = iclass.get_output(out=out)

830
    return resAll