Example #1
0
def read_sat(t0, dtRange, satdir, pre=False):
    """ Generator reading the satellite data.
    The loop is infinite; sat data are called when required until the end of
    the parcel loop. """
    # get dt from satmap
    dt = dtRange
    # initial time
    current_time = t0
    while True:
        fname = os.path.join(satdir, current_time.strftime('%Y%m%d%H_TB230'))
        dat = readidx107(fname, quiet=True)
        """ Generate the sequence of time ranges.
        This procedure works with empty time slots """
        # get the list of discontinuities, note that it is turned to a list
        id = list(np.where(dat['ir_start'][1:] - dat['ir_start'][:-1])[0])
        #test print(dat['ir_start'][id+1],dat['ir_start'][0])
        # append and prepend last and pre-first positions
        id.append(len(dat['ir_start']) - 1)
        id[:0] = [-1]
        #test  print(id)
        dat['dtRange'] = dt
        dat['nt'] = int(cloudtop_step / dtRange)
        #test print('nt ',dat['nt'])
        tf = current_time + cloudtop_step / 2
        # Generate list of time intervals
        dat['time'] = [[tf - dt, tf]]
        if pre:
            off = timedelta()
            while tf >= current_time - cloudtop_step / 2:
                dat['time'].append([tf, tf + dt])
                if debug: print('times', [tf, tf + dt])
                tf -= dt
        else:
            off = -dt
            while tf > current_time - cloudtop_step / 2:
                tf -= dt
                dat['time'].append([tf - dt, tf])

        dat['time'].reverse()
        #test print(len(dat['time']))
        dat['numRange'] = np.zeros(dat['nt'], dtype='int')
        dat['indexRange'] = np.empty(shape=(dat['nt'], 2), dtype='int')
        dat['indexRange'].fill(-999)
        # index in the list of time segments
        nc = dat['nt'] - 1
        # process the list of crossing from the last one, backward in time
        while len(id) > 1:
            idc = id.pop()
            # find the corresponding segment, skipping empty ones
            while off+current_time+timedelta(seconds=int(dat['ir_start'][idc])) \
                        != dat['time'][nc][0]:
                nc -= 1
            dat['indexRange'][nc, :] = [id[-1] + 1, idc + 1]
            dat['numRange'][nc] = idc - id[-1]
            nc -= 1
        # check that all parcels are sorted
        print('check sorting ', np.sum(dat['numRange']), dat['numpart'])
        # iterate time
        current_time -= cloudtop_step
        yield dat
Example #2
0
def main():
    global IDX_ORGN
    global lowpcut, highpcut
    parser = argparse.ArgumentParser()
    parser.add_argument("-y", "--year", type=int, help="year")
    parser.add_argument("-m",
                        "--month",
                        type=int,
                        choices=1 + np.arange(12),
                        help="month")
    parser.add_argument("-a",
                        "--advect",
                        type=str,
                        choices=["EID", "EIZ"],
                        help="source of advecting winds")
    parser.add_argument("-s", "--suffix", type=str, help="suffix")
    #parser.add_argument("-l","--level",type=int,help="P level")
    parser.add_argument("-q",
                        "--quiet",
                        type=str,
                        choices=["y", "n"],
                        help="quiet (y) or not (n)")
    parser.add_argument("-ct",
                        "--cloud_type",
                        type=str,
                        choices=["meanhigh", "veryhigh"],
                        help="cloud type filter")
    parser.add_argument("-t",
                        "--step",
                        type=int,
                        help="step in hours between two part files")
    parser.add_argument("-d",
                        "--duration",
                        type=int,
                        help="duration of integration in hours")
    parser.add_argument("-gs",
                        "--granule_size",
                        type=int,
                        help="size of the granule")
    parser.add_argument("-gn",
                        "--granule_step",
                        type=int,
                        help="number of granules in a step")
    parser.add_argument("-ab", "--age_bound", type=int, help="age_bound")
    parser.add_argument("-binx",
                        "--binx",
                        type=int,
                        help="number of grid points in longitude direction")
    parser.add_argument("-biny",
                        "--biny",
                        type=int,
                        help="number of grid points in latitude direction")
    parser.add_argument("-hmax",
                        "--hmax",
                        type=int,
                        help='maximum number of hours in traczilla simulation')
    parser.add_argument("-username", "--username", type=str, help='username')
    parser.add_argument("-userout", "--userout", type=str, help='userout')
    """ Parsed parameters"""
    # Parsed parameters
    # Interval between two part files (in hours)
    step = 6
    # Largest time to be processed (in hours)
    hmax = 1464
    # Age limit in days
    age_bound = 30
    # start date of the backward run, corresponding to itime=0
    year = 2017
    # 8 +1 means we start on September 1 at 0h and cover the whole month of August
    month = 6 + 1
    advect = 'EIZ'
    suffix = '_150_150hPa_500'
    quiet = False
    cloud_type = 'veryhigh'
    # Bound on the age of the parcel (in days)
    age_bound = 30
    # Number of parcels launched per time slot (grid size)
    binx = 320
    biny = 224
    # Number of granules in a step betwwen two part files
    granule_step = 6
    """ Non parsed parameters"""
    # Time width of the parcel slice
    slice_width = timedelta(minutes=5)
    # dtRange (now defined in satmap definition)
    #dtRange={'MSG1':timedelta(minutes=15),'Hima':timedelta(minutes=20)}
    # day=1 should not be changed
    day = 1
    # low p cut applied in exiter
    lowpcut = 3000
    # high p cut applied in exiter
    highpcut = 50000

    args = parser.parse_args()
    if args.year is not None: year = args.year
    if args.month is not None: month = args.month + 1
    if args.advect is not None: advect = args.advect
    if args.suffix is not None: suffix = args.suffix
    #if args.level is not None: level=args.level
    if args.quiet is not None:
        if args.quiet == 'y': quiet = True
        else: quiet = False
    if args.cloud_type is not None: cloud_type = args.cloud_type
    if args.step is not None: step = args.step
    if args.hmax is not None: hmax = args.hmax
    if args.binx is not None: binx = args.binx
    if args.biny is not None: biny = args.biny
    granule_size = binx * biny
    if args.granule_size is not None: granule_size = args.granule_size
    if args.duration is not None: hmax = args.duration
    if args.age_bound is not None: age_bound = args.age_bound
    if args.granule_step is not None: granule_step = args.granule_step
    if args.username is not None: username = args.username
    if args.userout is not None: userout = args.userout

    # Define main directories
    main_sat_dir = '/bdd/STRATOCLIM/flexpart_in'
    if 'ciclad' in socket.gethostname():
        traj_dir = os.path.join('/data/', username, 'flexout', 'COCHIN',
                                'BACK')
        out_dir = os.path.join('/data', userout, 'STC')
    elif ('climserv' in socket.gethostname()) | ('polytechnique'
                                                 in socket.gethostname()):
        traj_dir = os.path.join('/homedata/', username, 'flexout', 'COCHIN',
                                'BACK')
        out_dir = os.path.join('/homedata', userout, 'STC')
    else:
        print('CANNOT RECOGNIZE HOST - DO NOT RUN ON NON DEFINED HOSTS')
        exit()

    # Output diretories
    # Update the out_dir with the cloud type
    out_dir = os.path.join(out_dir, 'STC-BACK-OUT-Cochin-SAF-' + cloud_type)

    sdate = datetime(year, month, day)
    # fdate defined to make output under the name of the month where parcels are released
    fdate = sdate - timedelta(days=1)

    # Number of slices between two outputs
    dstep = timedelta(hours=step)
    nb_slices = int(dstep / slice_width)

    # size of granules launched during a step
    granule_quanta = granule_size * granule_step

    # Manage the file that receives the print output
    if quiet:
        # Output file
        print_file = os.path.join(
            out_dir, 'out',
            'BACK-' + advect + fdate.strftime('-%b-%Y') + suffix + '.out')
        fsock = open(print_file, 'w')
        sys.stdout = fsock

    print('year', year, 'month', month, 'day', day)
    print('advect', advect)
    print('suffix', suffix)

    # Directory of the backward trajectories
    ftraj = os.path.join(traj_dir,
                         'BACK-' + advect + fdate.strftime('-%b-%Y') + suffix)

    # Output file
    out_file = os.path.join(
        out_dir,
        'BACK-' + advect + fdate.strftime('-%b-%Y') + suffix + '.hdf5z')
    #out_file1 = os.path.join(out_dir,'BACK-'+advect+fdate.strftime('-%b-%Y-')+str(level)+'K'+suffix+'.hdf5b')
    #out_file2 = os.path.join(out_dir,'BACK-'+advect+fdate.strftime('-%b-%Y-')+str(level)+'K'+suffix+'.pkl')

    # Directories for the satellite cloud top files
    satdir ={'MSG1':os.path.join(main_sat_dir,'msg1','S_NWC'),\
             'Hima':os.path.join(main_sat_dir,'himawari','S_NWC')}
    """ Initialization of the calculation """
    # Initialize the grid
    gg = geosat.GeoGrid('FullAMA_SAFBox')
    # Initialize the dictionary of the parcel dictionaries
    partStep = {}
    satmap = pixmap(gg)

    # Build the satellite field generator
    get_sat = {'MSG1': read_sat(sdate,'MSG1',satmap.zone['MSG1']['dtRange'],satdir['MSG1'],pre=True),\
               'Hima': read_sat(sdate,'Hima',satmap.zone['Hima']['dtRange'],satdir['Hima'],pre=True)}

    # Open the part_000 file that contains the initial positions
    part0 = readidx107(os.path.join(ftraj, 'part_000'), quiet=True)
    print('numpart', part0['numpart'])
    numpart = part0['numpart']
    numpart_s = granule_size

    # stamp_date not set in these runs
    # current_date actually shifted by one day / sdate
    current_date = sdate
    # check flag is clean
    print('check flag is clean ',((part0['flag']&I_HIT)!=0).sum(),((part0['flag']&I_DEAD)!=0).sum(),\
                                 ((part0['flag']&I_CROSSED)!=0).sum())
    # check idx_orgn
    if part0['idx_orgn'] != 0:
        print('MINCHIA, IDX_ORGN NOT 0 AS ASSUMED, CORRECTED WITH READ VALUE')
        print('VALUE ', part0['idx_orgn'])
        IDX_ORGN = part0['idx_orgn']
    idx1 = IDX_ORGN

    # Build a dictionary to host the results
    prod0 = defaultdict(dict)
    prod0['src']['x'] = np.full(part0['numpart'],
                                fill_value=np.nan,
                                dtype='float')
    prod0['src']['y'] = np.full(part0['numpart'],
                                fill_value=np.nan,
                                dtype='float')
    prod0['src']['p'] = np.full(part0['numpart'],
                                fill_value=np.nan,
                                dtype='float')
    prod0['src']['t'] = np.full(part0['numpart'],
                                fill_value=np.nan,
                                dtype='float')
    prod0['src']['age'] = np.full(part0['numpart'],
                                  fill_value=np.nan,
                                  dtype='int')
    prod0['flag_source'] = part0['flag']
    prod0['rvs'] = np.full(part0['numpart'], 0.01, dtype='float')

    # truncate eventually to 32 bits at the output stage

    # read the part_000 file for the first granule
    partStep[0] = {}
    partStep[0]['x'] = part0['x'][:granule_size]
    partStep[0]['y'] = part0['y'][:granule_size]
    partStep[0]['t'] = part0['t'][:granule_size]
    partStep[0]['p'] = part0['p'][:granule_size]
    partStep[0]['t'] = part0['t'][:granule_size]
    partStep[0]['idx_back'] = part0['idx_back'][:granule_size]
    partStep[0]['ir_start'] = part0['ir_start'][:granule_size]
    partStep[0]['itime'] = 0

    # number of hists and exits
    nhits = 0
    nexits = 0
    ndborne = 0
    nnew = granule_size
    nold = 0

    # used to get non borne parcels
    new = np.empty(part0['numpart'], dtype='bool')
    new.fill(False)

    print('Initialization completed')
    """ Main loop on the output time steps """
    for hour in range(step, hmax + 1, step):
        pid = os.getpid()
        py = psutil.Process(pid)
        memoryUse = py.memory_info()[0] / 2**30
        print('memory use: {:4.2f} gb'.format(memoryUse))
        # Get rid of dictionary no longer used
        if hour >= 2 * step: del partStep[hour - 2 * step]

        # Read the new data
        partStep[hour] = readpart107(hour, ftraj, quiet=True)
        # Link the names as views
        partante = partStep[hour - step]
        partpost = partStep[hour]
        if partpost['nact'] > 0:
            print('hour ', hour, '  numact ', partpost['nact'], '  max p ',
                  partpost['p'].max())
        else:
            print('hour ', hour, '  numact ', partpost['nact'])
        # New date valid for partpost
        current_date -= dstep

        # Processing of water mixing ratio
        # Select non stopped parcels in partante
        selec = (prod0['flag_source'][partante['idx_back'] - IDX_ORGN]
                 & I_STOP) == 0
        idx = partante['idx_back'][selec]
        prod0['rvs'][idx-IDX_ORGN] = np.minimum(prod0['rvs'][idx-IDX_ORGN],\
                satratio(partante['p'][selec],partante['t'][selec]))
        """ Select the parcels that are common to the two steps
        ketp_a is a logical field with same length as partante
        kept_p is a logical field with same length as partpost
        After the launch of the earliest parcel along the flight track, there
        should not be any member in new.
        """
        kept_a = np.in1d(partante['idx_back'],
                         partpost['idx_back'],
                         assume_unique=True)
        kept_p = np.in1d(partpost['idx_back'],
                         partante['idx_back'],
                         assume_unique=True)
        #new_p = ~np.in1d(partpost['idx_back'],partpost['idx_back'],assume_unique=True)
        print('kept a, p ', len(kept_a), len(kept_p), kept_a.sum(),
              kept_p.sum(), '  new ',
              len(partpost['x']) - kept_p.sum())
        nnew += len(partpost['x']) - kept_p.sum()
        """ PROCESSING OF DEADBORNE PARCELS
        Manage the parcels launched during the last 6-hour which have already
        exited and do not appear in posold or posact (borne dead parcels).
        These parcels are stored in the last part of posact, at most
        the last granule_quanta parcels. """
        if numpart_s < numpart:
            print("manage deadborne", flush=True)
            # First index of the current quanta """
            numpart_s += granule_quanta
            print("idx1", idx1, " numpart_s", numpart_s)
            # Extract the last granule_size indexes from posacti, this is where the new parcels should be
            if hour == step:
                idx_act = partpost['idx_back']
            else:
                idx_act = partpost['idx_back'][-granule_quanta:]
            # Generate the list of indexes that should be found in this range
            # ACHTUNG ACHTUNG : this works because IDX_ORGN=1, FIX THAT
            idx_theor = np.arange(idx1, numpart_s + IDX_ORGN)
            # Find the missing indexes in idx_act (make a single line after validation)
            kept_borne = np.in1d(idx_theor, idx_act, assume_unique=True)
            idx_deadborne = idx_theor[~kept_borne]
            # Process these parcels by assigning exit at initial location
            prod0['flag_source'][
                idx_deadborne -
                IDX_ORGN] = prod0['flag_source'][idx_deadborne -
                                                 IDX_ORGN] | I_DEAD + I_DBORNE
            prod0['src']['x'][idx_deadborne -
                              IDX_ORGN] = part0['x'][idx_deadborne - IDX_ORGN]
            prod0['src']['y'][idx_deadborne -
                              IDX_ORGN] = part0['y'][idx_deadborne - IDX_ORGN]
            prod0['src']['p'][idx_deadborne -
                              IDX_ORGN] = part0['p'][idx_deadborne - IDX_ORGN]
            prod0['src']['t'][idx_deadborne -
                              IDX_ORGN] = part0['t'][idx_deadborne - IDX_ORGN]
            prod0['src']['age'][idx_deadborne - IDX_ORGN] = 0.
            print("number of deadborne ", len(idx_deadborne))
            ndborne += len(idx_deadborne)
            idx1 = numpart_s + IDX_ORGN
        """ PROCESSING OF CROSSED PARCELS """
        if len(kept_a) > 0:
            exits = exiter(int((partante['itime']+partpost['itime'])/2), \
                partante['x'][~kept_a],partante['y'][~kept_a],partante['p'][~kept_a],\
                partante['t'][~kept_a],partante['idx_back'][~kept_a],\
                prod0['flag_source'],prod0['src']['x'],prod0['src']['y'],\
                prod0['src']['p'],prod0['src']['t'],prod0['src']['age'],\
                part0['ir_start'], satmap.range)
            nexits += exits
            print('exit ', nexits, exits, np.sum(~kept_a),
                  len(kept_a) - len(kept_p))
        """ PROCESSING OF PARCELS WHICH ARE COMMON TO THE TWO OUTPUTS  """
        # Select the kept parcels which have not been hit yet
        # !!! Never use and between two lists, the result is wrong

        if kept_p.sum() == 0:
            live_a = live_p = kept_p
        else:
            live_a = np.logical_and(
                kept_a, (prod0['flag_source'][partante['idx_back'] - IDX_ORGN]
                         & I_DEAD) == 0)
            live_p = np.logical_and(
                kept_p, (prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                         & I_DEAD) == 0)
        print('live a, b ', live_a.sum(), live_p.sum())
        del kept_a
        del kept_p
        """ Correction step that moves partante['x'] to avoid big jumps at the periodicity frontier on the x-axis """
        diffx = partpost['x'][live_p] - partante['x'][live_a]
        bb = np.zeros(len(diffx))
        bb[diffx > 180] = 360
        bb[diffx < -180] = -360
        partante['x'][live_a] += bb
        del bb
        del diffx
        # DOES not work in the following WAY
        #partante['x'][live_a][diffx>180] += 360
        #partante['x'][live_a][diffx<-180] -= 360

        # Build generator for parcel locations of the 5' slices
        gsp = get_slice_part(partante, partpost, live_a, live_p, current_date,
                             dstep, slice_width)
        if verbose: print('built parcel generator for ', current_date)
        """  MAIN LOOP ON THE PARCEL TIME SLICES  """

        for i in range(nb_slices):
            # get the slice for the particles
            datpart = next(gsp)
            if verbose: print('part slice ', i, datpart['time'])
            # Check whether the present satellite image is valid
            # The while should ensure that the run synchronizes
            # when it starts.

            while satmap.check('MSG1', datpart['time']) is False:
                # if not get next satellite image
                datsat1 = next(get_sat['MSG1'])
                # Check that the image is available
                if datsat1 is not None:
                    pm1 = geosat.SatGrid(datsat1, gg)
                    pm1._sat_togrid('CTTH_PRESS')
                    #print('pm1 diag',len(datsat1.var['CTTH_PRESS'][:].compressed()),
                    #                 len(pm1.var['CTTH_PRESS'][:].compressed()))
                    pm1._sat_togrid('CT')
                    pm1.attr = datsat1.attr.copy()
                    satmap.fill('MSG1', pm1, cloud_type)
                    del pm1
                    del datsat1
                else:
                    # if the image is missing, extend the lease
                    try:
                        satmap.extend('MSG1')
                    except:
                        # This handle the unlikely case where the first image is missing
                        continue
            while satmap.check('Hima', datpart['time']) is False:
                # if not get next satellite image
                datsath = next(get_sat['Hima'])
                # Check that the image is available
                if datsath is not None:
                    pmh = geosat.SatGrid(datsath, gg)
                    pmh._sat_togrid('CTTH_PRESS')
                    #print('pmh diag',len(datsath.var['CTTH_PRESS'][:].compressed()),
                    #                 len(pmh.var['CTTH_PRESS'][:].compressed()))
                    pmh._sat_togrid('CT')
                    pmh.attr = datsath.attr.copy()
                    satmap.fill('Hima', pmh, cloud_type)
                    del datsath
                    del pmh
                else:
                    # if the image is missing, extend the lease
                    try:
                        satmap.extend('Hima')
                    except:
                        # This handle the unlikely case where the first image is missing
                        continue
            """ Select the parcels located within the domain """
            # TODO TODO the values used here should be derived from parameters defined above
            indomain = np.all((datpart['x'] > -10, datpart['x'] < 160,
                               datpart['y'] > 0, datpart['y'] < 50),
                              axis=0)
            """ PROCESS THE COMPARISON OF PARCEL PRESSURES TO CLOUDS """
            if indomain.sum() > 0:
                nhits += convbirth(datpart['itime'],
                    datpart['x'][indomain],datpart['y'][indomain],datpart['p'][indomain],\
                    datpart['t'][indomain],datpart['idx_back'][indomain],\
                    prod0['flag_source'],prod0['src']['x'],prod0['src']['y'],\
                    prod0['src']['p'],prod0['src']['t'],prod0['src']['age'],\
                    satmap.ptop, part0['ir_start'],\
                    satmap.range[0,0],satmap.range[1,0],satmap.stepx,satmap.stepy,satmap.binx,satmap.biny)

            sys.stdout.flush()
        """ End of of loop on slices """

        # Check the age limit (easier to do it here)
        print("Manage age limit", flush=True)
        age_sec = part0['ir_start'][partante['idx_back'] -
                                    IDX_ORGN] - partante['itime']
        IIold_o = age_sec > (age_bound - 0.25) * 86400
        IIold_o = IIold_o & (
            (prod0['flag_source'][partante['idx_back'] - IDX_ORGN] & I_STOP)
            == 0)
        idx_IIold = partante['idx_back'][IIold_o]
        j_IIold_o = np.where(IIold_o)
        prod0['flag_source'][idx_IIold - IDX_ORGN] = prod0['flag_source'][
            idx_IIold - IDX_ORGN] | I_DEAD + I_OLD
        prod0['src']['x'][idx_IIold - IDX_ORGN] = partante['x'][j_IIold_o]
        prod0['src']['y'][idx_IIold - IDX_ORGN] = partante['y'][j_IIold_o]
        prod0['src']['p'][idx_IIold - IDX_ORGN] = partante['p'][j_IIold_o]
        prod0['src']['t'][idx_IIold - IDX_ORGN] = partante['t'][j_IIold_o]
        prod0['src']['age'][idx_IIold - IDX_ORGN] = (
            (part0['ir_start'][idx_IIold - IDX_ORGN] - partante['itime']) /
            86400)
        print("number of IIold ", len(idx_IIold))
        nold += len(idx_IIold)

        # find parcels still alive       if kept_p.sum()==0:
        try:
            nlive = ((prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                      & I_DEAD) == 0).sum()
            n_nohit = ((prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                        & I_HIT) == 0).sum()
        except:
            nlive = 0
            n_nohit = 0
        print('end hour ', hour, '  numact', partpost['nact'], ' nexits',
              nexits, ' nhits', nhits, ' nlive', nlive, ' nohit', n_nohit)
        # check that nlive + nhits + nexits = numpart, should be true after the first day
        if part0['numpart'] != nexits + nhits + nlive + ndborne:
            print('@@@ ACHTUNG numpart not equal to sum ', part0['numpart'],
                  nexits + nhits + nlive + ndborne)
    """ End of the procedure and storage of the result """
    pid = os.getpid()
    py = psutil.Process(pid)
    memoryUse = py.memory_info()[0] / 2**30
    print('memory use before clean: {:4.2f} gb'.format(memoryUse))
    del partante
    del partpost
    del live_a
    del live_p
    del datpart
    prod0['rvs'] = prod0['rvs'].astype(np.float32)
    for var in ['age', 'p', 't', 'x', 'y']:
        prod0['src'][var] = prod0['src'][var].astype(np.float32)
    pid = os.getpid()
    py = psutil.Process(pid)
    memoryUse = py.memory_info()[0] / 2**30
    print('memory use after clean: {:4.2f} gb'.format(memoryUse))
    #output file
    #pickle.dump(prod0,gzip.open(out_file,'wb'))
    #try:
    #    dd.io.save(out_file1,prod0,compression='blosc')
    #except:
    #    print('error with dd blosc')
    try:
        dd.io.save(out_file, prod0, compression='zlib')
    except:
        print('error with dd zlib')
    """ End of the procedure and storage of the result """
    #output file
    #dd.io.save(out_file2,prod0,compression='zlib')
    #pickle.dump(prod0,gzip.open(out_file,'wb'))
    # close the print file
    if quiet: fsock.close()
Example #3
0
def main():
    global IDX_ORGN
    parser = argparse.ArgumentParser()
    parser.add_argument("-y", "--year", type=int, help="year")
    parser.add_argument("-m",
                        "--month",
                        type=int,
                        choices=1 + np.arange(12),
                        help="month")
    parser.add_argument("-d1",
                        "--day1",
                        type=int,
                        choices=1 + np.arange(31),
                        help="day1")
    parser.add_argument("-d2",
                        "--day2",
                        type=int,
                        choices=1 + np.arange(31),
                        help="day2")
    parser.add_argument(
        "-a",
        "--advect",
        type=str,
        choices=["OPZ", "EAD", "EAZ", "EID", "EIZ", "EID-FULL", "EIZ-FULL"],
        help="source of advecting winds")
    parser.add_argument("-s",
                        "--suffix",
                        type=str,
                        help="suffix for special cases")
    parser.add_argument("-q",
                        "--quiet",
                        type=str,
                        choices=["y", "n"],
                        help="quiet (y) or not (n)")
    #parser.add_argument("-c","--clean0",type=bool,help="clean part_000")
    parser.add_argument("-t",
                        "--step",
                        type=int,
                        help="step in hour between two part files")
    parser.add_argument("-ct",
                        "--cloud_type",
                        type=str,
                        choices=["meanhigh", "veryhigh", "silviahigh"],
                        help="cloud type filter")
    parser.add_argument("-k",
                        "--diffus",
                        type=str,
                        choices=['01', '1', '001'],
                        help='diffusivity parameter')
    parser.add_argument("-v",
                        "--vshift",
                        type=int,
                        choices=[0, 1, 2],
                        help='vertical shift')
    parser.add_argument("-hm",
                        "--hmax",
                        type=int,
                        help='maximum considered integration time')

    # to be updated
    # Define main directories
    if 'ciclad' in socket.gethostname():
        main_sat_dir = '/data/legras/flexpart_in/SAFNWC'
        #SVC_Dir = '/bdd/CFMIP/SEL2'
        traj_dir = '/data/akottayil/flexout/STC/BACK'
        out_dir = '/data/legras/STC'
    elif ('climserv' in socket.gethostname()) | ('polytechnique'
                                                 in socket.gethostname()):
        main_sat_dir = '/data/legras/flexpart_in/SAFNWC'
        #SVC_Dir = '/bdd/CFMIP/SEL2'
        traj_dir = '/data/akottayil/flexout/STC/BACK'
        out_dir = '/homedata/legras/STC'
    else:
        print('CANNOT RECOGNIZE HOST - DO NOT RUN ON NON DEFINED HOSTS')
        exit()
    """ Parameters """
    step = 3
    hmax = 732
    dstep = timedelta(hours=step)
    # time width of the parcel slice
    slice_width = timedelta(minutes=5)
    # number of slices between two outputs
    nb_slices = int(dstep / slice_width)
    # default values of parameters
    # date of the flight
    year = 2017
    month = 7 + 1
    day1 = 1
    day2 = 10
    advect = 'EAD'
    suffix = ''
    quiet = False
    clean0 = True
    cloud_type = 'silviahigh'
    diffus = '01'
    vshift = 0
    super = ''
    args = parser.parse_args()
    if args.year is not None: year = args.year
    if args.month is not None: month = args.month + 1
    if args.advect is not None: advect = args.advect
    if args.day1 is not None: day1 = args.day1
    if args.day2 is not None: day2 = args.day2
    if args.suffix is not None: suffix = '-' + args.suffix
    if args.hmax is not None: hmax = args.hmax
    if args.quiet is not None:
        if args.quiet == 'y': quiet = True
        else: quiet = False
    #if args.clean0 is not None: clean0 = args.clean0
    if args.cloud_type is not None: cloud_type = args.cloud_type
    if args.step is not None: step = args.step
    if args.diffus is not None: diffus = args.diffus
    diffus = '-D' + diffus
    if args.vshift is not None:
        vshift = args.vshift
        if vshift > 0:
            super = 'super' + str(vshift)

    # Update the out_dir with the cloud type and the super paramater
    out_dir = os.path.join(out_dir, 'SVC-BACK-OUT-SAF-' + super + cloud_type)
    try:
        os.mkdir(out_dir)
        os.mkdir(out_dir + '/out')
    except:
        print('out_dir directory already created')

    # Dates beginning and end
    date_beg = datetime(year=year, month=month, day=day1, hour=0)
    date_end = datetime(year=year, month=month, day=day2, hour=0)

    # Manage the file that receives the print output
    if quiet:
        # Output file
        print_file = os.path.join(
            out_dir, 'out',
            'BACK-SVC-EID-FULL-' + date_beg.strftime('%b-%Y-day%d-') +
            date_end.strftime('%d-D01') + '.out')
        fsock = open(print_file, 'w')
        sys.stdout = fsock

    # initial time to read the sat files
    # should be after the end of the flight
    sdate = date_end + timedelta(days=1)
    print('year', year, 'month', month, 'day', day2)
    print('advect', advect)
    print('suffix', suffix)

    # patch to fix the problem of the data hole on the evening of 2 August 2017
    if sdate == datetime(2017, 8, 2): sdate = datetime(2017, 8, 3, 6)

    # Directories of the backward trajectories and name of the output file
    ftraj = os.path.join(
        traj_dir, 'BACK-SVC-EID-FULL-' + date_beg.strftime('%b-%Y-day%d-') +
        date_end.strftime('%d-D01'))
    out_file2 = os.path.join(
        out_dir, 'BACK-SVC-EID-FULL-' + date_beg.strftime('%b-%Y-day%d-') +
        date_end.strftime('%d-D01') + '.hdf5')

    # Directories for the satellite cloud top files
    satdir ={'MSG1':os.path.join(main_sat_dir,'msg1','S_NWC'),\
             'Hima':os.path.join(main_sat_dir,'himawari','S_NWC')}
    """ Initialization of the calculation """
    # Initialize the grid
    gg = geosat.GeoGrid('FullAMA_SAFBox')
    # Initialize the dictionary of the parcel dictionaries
    partStep = {}
    satmap = pixmap(gg)

    # Build the satellite field generator
    get_sat = {'MSG1': read_sat(sdate,'MSG1',satmap.zone['MSG1']['dtRange'],satdir['MSG1'],pre=True,vshift=vshift),\
               'Hima': read_sat(sdate,'Hima',satmap.zone['Hima']['dtRange'],satdir['Hima'],pre=True,vshift=vshift)}

    # Read the index file that contains the initial positions
    part0 = readidx107(os.path.join(ftraj, 'part_000'), quiet=True)
    print('numpart', part0['numpart'])
    # stamp_date not set in these runs
    # current_date actually shifted by one day / sdate
    # We assume here that part time is defined from this day at 0h
    # sdate defined above should be equal or posterior to current_date
    current_date = sdate
    # check flag is clean
    print('check flag is clean ',((part0['flag']&I_HIT)!=0).sum(),((part0['flag']&I_DEAD)!=0).sum(),\
                                 ((part0['flag']&I_CROSSED)!=0).sum())
    # check idx_orgn
    if part0['idx_orgn'] != 0:
        print('MINCHIA, IDX_ORGN NOT 0 AS ASSUMED, CORRECTED WITH READ VALUE')
        print('VALUE ', part0['idx_orgn'])
        IDX_ORGN = part0['idx_orgn']

    # Build a dictionary to host the results
    prod0 = defaultdict(dict)
    prod0['src']['x'] = np.empty(part0['numpart'], dtype='float')
    prod0['src']['y'] = np.empty(part0['numpart'], dtype='float')
    prod0['src']['p'] = np.empty(part0['numpart'], dtype='float')
    prod0['src']['t'] = np.empty(part0['numpart'], dtype='float')
    prod0['src']['age'] = np.empty(part0['numpart'], dtype='int')
    prod0['flag_source'] = part0['flag']
    # truncate eventually to 32 bits at the output stage

    # read the part_000 file
    partStep[0] = readpart107(0, ftraj, quiet=True)
    # cleaning is necessary for runs starting in the fake restart mode
    # otherwise all parcels are thought to exit at the first step
    if clean0:
        partStep[0]['idx_back'] = []

    # number of hists and exits
    nhits = 0
    nexits = 0
    ndborne = 0
    nnew = 0

    # used to get non borne parcels
    new = np.empty(part0['numpart'], dtype='bool')
    new.fill(False)

    print('Initialization completed')
    """ Main loop on the output time steps """
    for hour in range(step, hmax + 1, step):
        pid = os.getpid()
        py = psutil.Process(pid)
        memoryUse = py.memory_info()[0] / 2**30
        print('memory use: {:4.2f} gb'.format(memoryUse))
        # Get rid of dictionary no longer used
        if hour >= 2 * step: del partStep[hour - 2 * step]
        # Read the new data
        partStep[hour] = readpart107(hour, ftraj, quiet=True)
        # Link the names
        partante = partStep[hour - step]
        partpost = partStep[hour]
        if partpost['nact'] > 0:
            print('hour ', hour, '  numact ', partpost['nact'], '  max p ',
                  partpost['p'].max())
        else:
            print('hour ', hour, '  numact ', partpost['nact'])
        # New date valid for partpost
        current_date -= dstep
        """ Select the parcels that are common to the two steps
        ketp_a is a logical field with same length as partante
        kept_p is a logical field with same length as partpost
        After the launch of the earliest parcel along the flight track, there
        should not be any member in new
        The parcels
        """
        kept_a = np.in1d(partante['idx_back'],
                         partpost['idx_back'],
                         assume_unique=True)
        kept_p = np.in1d(partpost['idx_back'],
                         partante['idx_back'],
                         assume_unique=True)
        #new_p = ~np.in1d(partpost['idx_back'],partpost['idx_back'],assume_unique=True)
        print('kept a, p ', len(kept_a), len(kept_p), kept_a.sum(),
              kept_p.sum(), '  new ',
              len(partpost['x']) - kept_p.sum())
        """ IDENTIFY AND TAKE CARE OF DEADBORNE AS NON BORNE PARCELS """
        if (hour < ((day2 - day1 + 5) * 24)) & (partpost['nact'] > 0):
            new[partpost['idx_back'][~kept_p] - IDX_ORGN] = True
            nnew += len(partpost['x']) - kept_p.sum()
        if hour == ((day2 - day1 + 5) * 24):
            ndborne = np.sum(~new)
            prod0['flag_source'][~new] |= I_DBORNE + I_DEAD
            prod0['src']['x'][~new] = part0['x'][~new]
            prod0['src']['y'][~new] = part0['y'][~new]
            prod0['src']['p'][~new] = part0['p'][~new]
            prod0['src']['t'][~new] = part0['t'][~new]
            prod0['src']['age'][~new] = 0
            print('number of dead borne', ndborne, part0['numpart'] - nnew)
            del new
        """ INSERT HERE CODE FOR NEW PARCELS """
        # nothing to be done for new parcels, just wait and see
        """ PROCESSING OF CROSSED PARCELS """
        if len(kept_a) > 0:
            exits = exiter(int((partante['itime']+partpost['itime'])/2), \
                partante['x'][~kept_a],partante['y'][~kept_a],partante['p'][~kept_a],\
                partante['t'][~kept_a],partante['idx_back'][~kept_a],\
                prod0['flag_source'],prod0['src']['x'],prod0['src']['y'],\
                prod0['src']['p'],prod0['src']['t'],prod0['src']['age'],\
                part0['ir_start'], satmap.range)
            nexits += exits
            print('exit ', nexits, exits, np.sum(~kept_a),
                  len(kept_a) - len(kept_p))
        """ PROCESSING OF PARCELS WHICH ARE COMMON TO THE TWO OUTPUTS  """
        # Select the kept parcels which have not been hit yet
        # !!! Never use and between two lists, the result is wrong

        if kept_p.sum() == 0:
            live_a = live_p = kept_p
        else:
            live_a = np.logical_and(
                kept_a, (prod0['flag_source'][partante['idx_back'] - IDX_ORGN]
                         & I_DEAD) == 0)
            live_p = np.logical_and(
                kept_p, (prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                         & I_DEAD) == 0)
        print('live a, b ', live_a.sum(), live_p.sum())

        # Build generator for parcel locations of the 5' slices
        gsp = get_slice_part(partante, partpost, live_a, live_p, current_date,
                             dstep, slice_width)
        if verbose: print('built parcel generator for ', current_date)
        """  MAIN LOOP ON THE PARCEL TIME SLICES  """

        for i in range(nb_slices):
            # get the slice for the particles
            datpart = next(gsp)
            if verbose: print('part slice ', i, datpart['time'])
            # Check whether the present satellite image is valid
            # The while should ensure that the run synchronizes
            # when it starts.
            while satmap.check('MSG1', datpart['time']) is False:
                # if not get next satellite image
                datsat1 = next(get_sat['MSG1'])
                # Check that the image is available
                if datsat1 is not None:
                    pm1 = geosat.SatGrid(datsat1, gg)
                    pm1._sat_togrid('CTTH_PRESS')
                    #print('pm1 diag',len(datsat1.var['CTTH_PRESS'][:].compressed()),
                    #                 len(pm1.var['CTTH_PRESS'][:].compressed()))
                    pm1._sat_togrid('CT')
                    if vshift > 0: pm1._sat_togrid('CTTH_TEMPER')
                    pm1.attr = datsat1.attr.copy()
                    satmap.fill('MSG1', pm1, cloud_type, vshift=vshift)
                    del pm1
                    del datsat1
                else:
                    # if the image is missing, extend the lease
                    try:
                        satmap.extend('MSG1')
                    except:
                        # This handle the unlikely case where the first image is missing
                        continue
            while satmap.check('Hima', datpart['time']) is False:
                # if not get next satellite image
                datsath = next(get_sat['Hima'])
                # Check that the image is available
                if datsath is not None:
                    pmh = geosat.SatGrid(datsath, gg)
                    pmh._sat_togrid('CTTH_PRESS')
                    #print('pmh diag',len(datsath.var['CTTH_PRESS'][:].compressed()),
                    #                 len(pmh.var['CTTH_PRESS'][:].compressed()))
                    pmh._sat_togrid('CT')
                    if vshift > 0: pmh._sat_togrid('CTTH_TEMPER')
                    pmh.attr = datsath.attr.copy()
                    satmap.fill('Hima', pmh, cloud_type, vshift=vshift)
                    del datsath
                    del pmh
                else:
                    # if the image is missing, extend the lease
                    try:
                        satmap.extend('Hima')
                    except:
                        # This handle the unlikely case where the first image is missing
                        continue
            """ PROCESS THE COMPARISON OF PARCEL PRESSURES TO CLOUDS """
            if len(datpart['x']) > 0:
                nhits += convbirth(datpart['itime'],
                    datpart['x'],datpart['y'],datpart['p'],datpart['t'],datpart['idx_back'],\
                    prod0['flag_source'],prod0['src']['x'],prod0['src']['y'],\
                    prod0['src']['p'],prod0['src']['t'],prod0['src']['age'],\
                    satmap.ptop, part0['ir_start'],\
                    satmap.range[0,0],satmap.range[1,0],satmap.stepx,satmap.stepy,satmap.binx,satmap.biny)

            sys.stdout.flush()
        """ End of of loop on slices """
        # find parcels still alive       if kept_p.sum()==0:
        try:
            nlive = ((prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                      & I_DEAD) == 0).sum()
            n_nohit = ((prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                        & I_HIT) == 0).sum()
        except:
            nlive = 0
            n_nohit = 0
        print('end hour ', hour, '  numact', partpost['nact'], ' nexits',
              nexits, ' nhits', nhits, ' nlive', nlive, ' nohit', n_nohit)
        # check that nlive + nhits + nexits = numpart, should be true after the first day
        if part0['numpart'] != nexits + nhits + nlive + ndborne:
            print('@@@ ACHTUNG numpart not equal to sum ', part0['numpart'],
                  nexits + nhits + nlive + ndborne)
    """ End of the procedure and storage of the result """
    #output file
    dd.io.save(out_file2, prod0, compression='zlib')
    #pickle.dump(prod0,gzip.open(out_file,'wb'))
    # close the print file
    if quiet: fsock.close()
Example #4
0
def main():
    global IDX_ORGN
    parser = argparse.ArgumentParser()
    parser.add_argument("-y","--year",type=int,help="year")
    parser.add_argument("-m","--month",type=int,choices=1+np.arange(12),help="month")
    parser.add_argument("-a","--advect",type=str,choices=["EID-FULL","EIZ-FULL"],help="source of advecting winds")
    parser.add_argument("-l","--level",type=int,help="PT level")
    parser.add_argument("-s","--suffix",type=str,help="suffix for special cases")
    parser.add_argument("-q","--quiet",type=str,choices=["y","n"],help="quiet (y) or not (n)")

    # to be updated
    if socket.gethostname() == 'graphium':
        pass
    elif 'ciclad' in socket.gethostname():
        traj_dir = '/data/legras/flexout/STC/BACK'
        out_dir = '/data/legras/STC'
    elif socket.gethostname() == 'grapelli':
        pass
    elif socket.gethostname() == 'coltrane':
        pass
    else:
         print ('CANNOT RECOGNIZE HOST - DO NOT RUN ON NON DEFINED HOSTS')
         exit()

    """ Parameters """
    # to do (perhaps) : some parameters might be parsed from command line
    # step and max output time
    step = 6
    hmax = 1824
    dstep = timedelta (hours=step)
    # age limit in days
    age_bound = 44.
    # time width of the parcel slice
    slice_width = timedelta(hours=1)
    # number of slices between two outputs
    nb_slices = int(dstep/slice_width)
    # defines here the offset for the detrainment (100h)
    detr_offset = 1/(100*3600.)
    # defines the domain
    domain = np.array([[-10.,160.],[0.,50.]])
    
    # default values of parameters
    # start date of the backward run, corresponding to itime=0 
    year=2017
    # 8 +1 means we start on September 1 at 0h and cover the whole month of August
    month=8+1
    # Should not be changed
    day=1
    advect = 'EAD'
    suffix =''
    quiet = False
    level = 380
    args = parser.parse_args()
    if args.year is not None:
        year=args.year
    if args.month is not None:
        month=args.month+1
    if args.advect is not None:
        advect=args.advect
    if args.level is not None:
        level=args.level
    if args.suffix is not None:
        suffix='-'+args.suffix
    if args.quiet is not None:
        if args.quiet=='y':
            quiet=True
        else:
            quiet=False

    # Update the out_dir with the platform
    out_dir = os.path.join(out_dir,'STC-BACK-DETR-OUT')
    sdate = datetime(year,month,day)
    # fdate defined to make output under the name of the month where parcels are released 
    fdate = sdate - timedelta(days=1)
   
    """ Define granule_size and granule_quanta
    granule_size:  Number of parcels launched per time slot (1 per degree on a 170x50 grid)
    granule_step: number of granules in 6 hours
    granula_quanta:  size of granules launched during 6 hours """
    if 'FULL' in advect:
        granule_size = 28800
        granule_step = 6
        granule_quanta = granule_size * granule_step
    else:
        granule_size = 8500
        granule_step = 6*4
        granule_quanta = granule_size * granule_step

    # Manage the file that receives the print output
    if quiet:
        # Output file
        print_file = os.path.join(out_dir,'out','BACK-'+advect+fdate.strftime('-%b-%Y-')+str(level)+'K'+suffix+'.out')
        fsock = open(print_file,'w')
        sys.stdout=fsock

    # initial time to read the sat files
    # should be after the end of the flight
    # and a 12h or 0h boundary
    print('year',year,'month',month,'day',day)
    print('advect',advect)
    print('suffix',suffix)

    # Directory of the backward trajectories
    ftraj = os.path.join(traj_dir,'BACK-'+advect+fdate.strftime('-%b-%Y-')+str(level)+'K'+suffix)    

    # Output file
    out_file = os.path.join(out_dir,'BACK-'+advect+fdate.strftime('-%b-%Y-')+str(level)+'K'+suffix+'.hdf5b')    
    out_file1 = os.path.join(out_dir,'BACK-'+advect+fdate.strftime('-%b-%Y-')+str(level)+'K'+suffix+'.hdf5z')    
    out_file2 = os.path.join(out_dir,'BACK-'+advect+fdate.strftime('-%b-%Y-')+str(level)+'K'+suffix+'.pkl')    

    """ Initialization of the calculation """
    # Initialize the dictionary of the parcel dictionaries
    partStep={}   

    # Open the part_000 file that contains the initial positions
    part0 = readidx107(os.path.join(ftraj,'part_000'),quiet=False)
    print('numpart',part0['numpart'])
    numpart = part0['numpart']
    numpart_s = granule_size
    
    # stamp_date not set in these runs
    # current_date actually shifted by one day / sdate
    current_date = sdate
    # check flag is clean
    print('check flag is clean ',((part0['flag']&I_HIT)!=0).sum(),((part0['flag']&I_DEAD)!=0).sum(),\
                                 ((part0['flag']&I_CROSSED)!=0).sum())

    # check idx_orgn
    if part0['idx_orgn'] != 0:
        print('MINCHIA, IDX_ORGN NOT 0 AS ASSUMED, CORRECTED WITH READ VALUE')
        print('VALUE ',part0['idx_orgn'])
        IDX_ORGN = part0['idx_orgn']
    idx1 = IDX_ORGN

    # Build a dictionary to host the results
    prod0 = defaultdict(dict)
    # Locations of the crossing and detrainement
    nsrc = 6
    prod0['src']['x'] = np.full(shape=(nsrc,part0['numpart']),fill_value=np.nan,dtype='float')
    prod0['src']['y'] = np.full(shape=(nsrc,part0['numpart']),fill_value=np.nan,dtype='float')
    prod0['src']['p'] = np.full(shape=(nsrc,part0['numpart']),fill_value=np.nan,dtype='float')
    prod0['src']['t'] = np.full(shape=(nsrc,part0['numpart']),fill_value=np.nan,dtype='float')
    prod0['src']['age'] = np.full(shape=(nsrc,part0['numpart']),fill_value=np.nan,dtype='float')
    # Flag is copied from index
    prod0['flag_source'] = part0['flag']
    # Make a source array to accumulate the chi 
    # Dimension is that of the ERA5 field (201,681)
    prod0['source'] = np.zeros(shape=(201,681),dtype='float')
    # truncate eventually to 32 bits at the output stage

    # Inintialize the erosion 
    prod0['chi'] = np.full(part0['numpart'],1.,dtype='float')
    prod0['passed'] = np.full(part0['numpart'],10,dtype='int')
   
    # Build the interpolator to the hybrid level
    fhyb, void = tohyb()
    #vfhyb = np.vectorize(fhyb)

    # Read the part_000 file for the first granule
    partStep[0] = {}
    partStep[0]['x']=part0['x'][:granule_size]
    partStep[0]['y']=part0['y'][:granule_size]
    partStep[0]['t']=part0['t'][:granule_size]
    partStep[0]['p']=part0['p'][:granule_size]
    partStep[0]['t']=part0['t'][:granule_size]
    partStep[0]['idx_back']=part0['idx_back'][:granule_size]
    partStep[0]['ir_start']=part0['ir_start'][:granule_size]
    partStep[0]['itime'] = 0

    # number of hists and exits
    nhits = np.array([0,0,0,0,0,0])
    nexits = 0
    ndborne = 0
    nnew = granule_size
    nold = 0
    nradada = 0

    # used to get non borne parcels
    new = np.empty(part0['numpart'],dtype='bool')
    new.fill(False)

    print('Initialization completed')

    """ Main loop on the output time steps """
    for hour in range(step,hmax+1,step):
        pid = os.getpid()
        py = psutil.Process(pid)
        memoryUse = py.memory_info()[0]/2**30
        print('memory use: {:4.2f} gb'.format(memoryUse))
        # Get rid of dictionary no longer used
        if hour >= 2*step: del partStep[hour-2*step]
        # Read the new data
        partStep[hour] = readpart107(hour,ftraj,quiet=True)
        # Link the names
        partante = partStep[hour-step]
        partpost = partStep[hour]
        if partpost['nact']>0:
            print('hour ',hour,'  numact ', partpost['nact'], '  max p ',partpost['p'].max())
        else:
            print('hour ',hour,'  numact ', partpost['nact'])
        # New date valid for partpost
        current_date -= dstep
        """ Select the parcels that are common to the two steps
        ketp_a is a logical field with same length as partante
        kept_p is a logical field with same length as partpost
        After the launch of the earliest parcel along the flight track, there
        should not be any member in new.
        """
        kept_a = np.in1d(partante['idx_back'],partpost['idx_back'],assume_unique=True)
        kept_p = np.in1d(partpost['idx_back'],partante['idx_back'],assume_unique=True)
        #new_p = ~np.in1d(partpost['idx_back'],partpost['idx_back'],assume_unique=True)
        print('kept a, p ',len(kept_a),len(kept_p),kept_a.sum(),kept_p.sum(),'  new ',len(partpost['x'])-kept_p.sum())
        nnew += len(partpost['x'])-kept_p.sum()
        
        """ PROCESSING OF DEADBORNE PARCELS
        Manage the parcels launched during the last 6-hour which have already
        exited and do not appear in posold or posact (borne dead parcels).
        These parcels are stored in the last part of posact, at most
        the last granule_quanta parcels. 
        PB: this does not process the first parcels launched at time 0 since initially
        numpart_s = granule_size"""
        if numpart_s < numpart :
            print("manage deadborne",flush=True)
            # First index of the current quanta """
            numpart_s += granule_quanta
            print("numpart_s ",numpart_s) 
            # Extract the last granule_size indexes from posact
            if hour==step:
                idx_act = partpost['idx_back']
            else:    
                idx_act = partpost['idx_back'][-granule_quanta:]
            # Generate the list of indexes that should be found in this range
            idx_theor = np.arange(idx1,numpart_s+IDX_ORGN)
            # Find the missing indexes in idx_act (make a single line after validation)
            kept_borne = np.in1d(idx_theor,idx_act,assume_unique=True)
            idx_deadborne = idx_theor[~kept_borne]
            # Process these parcels by assigning exit at initial location
            prod0['flag_source'][idx_deadborne-IDX_ORGN] = prod0['flag_source'][idx_deadborne-IDX_ORGN] | I_DEAD+I_DBORNE
            prod0['src']['x'][0,idx_deadborne-IDX_ORGN] = part0['x'][idx_deadborne-IDX_ORGN]
            prod0['src']['y'][0,idx_deadborne-IDX_ORGN] = part0['y'][idx_deadborne-IDX_ORGN]
            prod0['src']['p'][0,idx_deadborne-IDX_ORGN] = part0['p'][idx_deadborne-IDX_ORGN]
            prod0['src']['t'][0,idx_deadborne-IDX_ORGN] = part0['t'][idx_deadborne-IDX_ORGN]
            prod0['src']['age'][0,idx_deadborne-IDX_ORGN] = 0.
            print("number of deadborne ",len(idx_deadborne))
            ndborne += len(idx_deadborne)
            idx1 = numpart_s + IDX_ORGN

        """ PROCESSING OF CROSSED PARCELS """
        # last known location before crossing stored in the index 0 of src fields
        if len(kept_a)>0:
            exits = exiter(int((partante['itime']+partpost['itime'])/2), \
                partante['x'][~kept_a],partante['y'][~kept_a],partante['p'][~kept_a],\
                partante['t'][~kept_a],partante['idx_back'][~kept_a],\
                prod0['flag_source'],prod0['src']['x'],prod0['src']['y'],\
                prod0['src']['p'],prod0['src']['t'],prod0['src']['age'],\
                part0['ir_start'], domain)
            nexits += exits
            #nhits[0] += exits
            print('exit ',nexits, exits, np.sum(~kept_a), len(kept_a) - len(kept_p))

        """ PROCESSING OF PARCELS WHICH ARE COMMON TO THE TWO OUTPUTS  """
        # Select the kept parcels which have not been hit yet
        # !!! Never use and between two lists, the result is wrong

        if kept_p.sum()==0:
            live_a = live_p = kept_p
        else:
            live_a = np.logical_and(kept_a,(prod0['flag_source'][partante['idx_back']-IDX_ORGN] & I_DEAD) == 0)
            live_p = np.logical_and(kept_p,(prod0['flag_source'][partpost['idx_back']-IDX_ORGN] & I_DEAD) == 0)
        print('live a, p ',live_a.sum(),live_p.sum())
        del kept_a
        del kept_p
        
        """ Correction step that moves partante['x'] to avoid big jumps at the periodicity frontier on the x-axis """
        diffx = partpost['x'][live_p] - partante['x'][live_a]
        bb = np.zeros(len(diffx))
        bb[diffx>180] = 360
        bb[diffx<-180] = -360
        partante['x'][live_a] += bb
        del bb
        del diffx
        # DOES not work in the following WAY
        #partante['x'][live_a][diffx>180] += 360
        #partante['x'][live_a][diffx<-180] -= 360 

        # Build generator for live parcel locations of the 1h slices
        gsp = get_slice_part(partante,partpost,live_a,live_p,current_date,dstep,slice_width)
        if verbose: print('built parcel generator for ',current_date)

        """  MAIN LOOP ON THE 1H PARCEL TIME SLICES  """

        for i in range(nb_slices):
            # get the 1h slice for the particles
            datpart = next(gsp)         
            # skip if no particles
            if datpart['ti'] == None:
                continue
            print('current_date ',datpart['ti'])
            #@@ test
#            print('ti in main ',datpart['ti'])
#            print('pi in main ',np.min(datpart['pi']),np.max(datpart['pi']))
#            print('idx_back   ',np.min(datpart['idx_back']),np.max(datpart['idx_back']))
#            #@@ end test
            # as the ECMWF files are also available every hour
            datrean = read_ECMWF(datpart['ti'])
            """ 
             Calculate the -log surface pressure at parcel location at time ti  
             create a 2D linear interpolar from the surface pressure field 
             This interpolator is meant to generate the surface pressure which is in turn used to determine
             sigma and the hybrid level. 
             The interpolator is only defined in the area where ECMWF ERA5 data are available."""                            
            lsp = RegularGridInterpolator((datrean.attr['lats'],datrean.attr['lons']),\
                                          -np.log(datrean.var['SP']),method='linear')
            
            """ Select the pairs (i,f) entirely located within the domain """
            indomain = np.all((datpart['xi']>-10,datpart['xi']<160,datpart['yi']>0,datpart['yi']<50,
                           datpart['xf']>-10,datpart['xf']<160,datpart['yf']>0,datpart['yf']<50),axis=0)
            
            # perform the interpolation for the location of live parcels at time ti
            # ACHTUNG: this interpolation is only meaningful for parcels laying within
            # the domain of the ERA5 data
            lspi = lsp(np.transpose([datpart['yi'][indomain],datpart['xi'][indomain]]))
            #@@ test
#            print('surface pressure ',np.exp(-np.min(datpart['lspi'])),np.exp(-np.max(datpart['lspi'])))
#            print('particle pressure ',np.min(datpart['pi']),np.max(datpart['pi'])) 
            #@@ end test
            # get the closest hybrid level at time ti
            # define first -log sigma = -log(p) - -log(ps)
            lsig = - np.log(datpart['pi'][indomain]) - lspi
            #@@ test
#            print('sigma ',np.exp(-np.max(lsig)),np.exp(-np.min(lsig)))
            #@@ end test
            # get the hybrid level, the rank of the first retained level is substracted to have hyb starting from 0 
            hyb = np.floor(fhyb(np.transpose([lsig,lspi]))+0.5).astype(np.int64)-datrean.attr['levs'][0]
            #@@ test the extreme values of sigma end ps
            if np.min(lsig) < - np.log(0.95):
                print('large sigma detected ',np.exp(-np.min(lsig)))
            if np.max(lspi) > -np.log(45000):
                print('small ps detected ',np.exp(-np.max(lspi)))
                
            """ PROCESS THE PARCELS WHICH ARE TOO CLOSE TO GROUND
             These parcels are flagged as crossed and dead, their last location is stored in the
             index 0 of src fields.
             This test handles also the cases outside the interpolation domain as NaN produced by fhyb
             generates very large value of hyb. 
             The trajectories which are stopped here have exited the domain where winds are available to flexpart
             and therefore are wrong from this point. For this reason we label them from their last valid position.
             This last sentence is only valid in simulations using ERA5 as ERA-Interim contains data down to the ground.
            """
            if np.max(hyb)> 100 :
                selec = hyb>100
                nr = radada(datpart['itime'],
                     datpart['xf'][indomain][selec],datpart['yf'][indomain][selec],datpart['pf'][indomain][selec],
                     datpart['tempf'][indomain][selec],datpart['idx_back'][indomain][selec],
                     prod0['flag_source'],prod0['src']['x'],prod0['src']['y'],
                     prod0['src']['p'],prod0['src']['t'],prod0['src']['age'] ,prod0['source'],
                     part0['ir_start'])
                nradada += nr
                nhits[0] += nr
       
            """ PROCESS THE (ADJOINT) DETRAINMENT """
            n1 = detrainer(datpart['itime'], 
                datpart['xi'][indomain],datpart['yi'][indomain],datpart['pi'][indomain],datpart['tempi'][indomain],hyb,
                datpart['xf'][indomain],datpart['yf'][indomain], datrean.var['UDR'], datpart['idx_back'][indomain],\
                prod0['flag_source'],part0['ir_start'], prod0['chi'],prod0['passed'],\
                prod0['src']['x'],prod0['src']['y'],prod0['src']['p'],prod0['src']['t'],prod0['src']['age'],prod0['source'],\
                datrean.attr['Lo1'],datrean.attr['La1'],datrean.attr['dlo'],datrean.attr['dla'],detr_offset)
            nhits += n1
            #@@ test
            # print('return from detrainer',nhits)
            #@@ end test 
            del datpart
            sys.stdout.flush()

        """ End of of loop on slices """
        
        # Check the age limit (easier to do it here)
        print("Manage age limit",flush=True)
        age_sec = part0['ir_start'][partante['idx_back']-IDX_ORGN]-partante['itime']
        IIold_o = age_sec > (age_bound-0.25) * 86400
        IIold_o = IIold_o & ((prod0['flag_source'][partante['idx_back']-IDX_ORGN] & I_STOP)==0)
        idx_IIold = partante['idx_back'][IIold_o]
        j_IIold_o = np.where(IIold_o)
        prod0['flag_source'][idx_IIold-IDX_ORGN] = prod0['flag_source'][idx_IIold-IDX_ORGN] | I_DEAD+I_OLD
        prod0['src']['x'][0,idx_IIold-IDX_ORGN] = partante['x'][j_IIold_o]
        prod0['src']['y'][0,idx_IIold-IDX_ORGN] = partante['y'][j_IIold_o]
        prod0['src']['p'][0,idx_IIold-IDX_ORGN] = partante['p'][j_IIold_o]
        prod0['src']['t'][0,idx_IIold-IDX_ORGN] = partante['t'][j_IIold_o]
        prod0['src']['age'][0,idx_IIold-IDX_ORGN] = ((part0['ir_start'][idx_IIold-IDX_ORGN]- partante['itime'])/86400)
        print("number of IIold ",len(idx_IIold)) 
        nold += len(idx_IIold)
 
        
        # find parcels still alive       if kept_p.sum()==0:
        try:
            # number of parcels still alive
            nlive = ((prod0['flag_source'][partpost['idx_back']-IDX_ORGN] & I_DEAD) == 0).sum()
            # number of parcels still alive and not hit
            nprist = ((prod0['flag_source'][partpost['idx_back']-IDX_ORGN] & (I_DEAD+I_HIT)) == 0).sum()
            # number of parcels which have hit and crossed
            nouthit = ((prod0['flag_source'][partpost['idx_back']-IDX_ORGN] & I_HIT+I_CROSSED) == I_HIT+I_CROSSED).sum()
            # number of parcels which heve crossed without hit
            noutprist = ((prod0['flag_source'][partpost['idx_back']-IDX_ORGN] & I_HIT+I_CROSSED) == I_CROSSED).sum()
            # number of parcels which have hit without crossing
            nhitpure = ((prod0['flag_source'][partpost['idx_back']-IDX_ORGN] & I_HIT+I_CROSSED) == I_HIT).sum()                  
        except:
            nlive = 0
            nprist =0
            nouthit = 0
            noutprist = 0
            nhitpure = 0
            nprist = part0['numpart']
            
        print('end hour ',hour,'  numact', partpost['nact'], ' nnew',nnew,' nexits',nexits,' nold',nold,' ndborne',ndborne)
        print('nhits',nhits)
        print('nlive', nlive,' nprist',nprist,' nouthit',nouthit,' noutprist',noutprist,' nhitpure',nhitpure)
        # check that nprist + nhits + nexits + nold = nnew
        #if partpost['nact'] != nprist + nouthit + noutprist + nhitpure + ndborne:
        #    print('@@@ ACHTUNG numact not equal to sum ',partpost['nact'],nprist + nouthit + noutprist + nhitpure + ndborne)
      

    """ End of the procedure and storage of the result """
    pid = os.getpid()
    py = psutil.Process(pid)
    memoryUse = py.memory_info()[0]/2**30
    print('memory use before clean: {:4.2f} gb'.format(memoryUse))
    del partante
    del partpost
    del live_a
    del live_p
    # reduction of the size of prod0 by converting float64 into float32
    prod0['chi'] = prod0['chi'].astype(np.float32)
    prod0['passed'] = prod0['passed'].astype(np.int32)
    prod0['source'] = prod0['source'].astype(np.float32)
    for var in ['age','p','t','x','y']:
        prod0['src'][var] = prod0['src'][var].astype(np.float32)
    pid = os.getpid()
    py = psutil.Process(pid)
    memoryUse = py.memory_info()[0]/2**30
    print('memory use after clean: {:4.2f} gb'.format(memoryUse))

    #output file
    try:
        dd.io.save(out_file1,prod0,compression='blosc')
    except:
        print('error with dd blosc')
    try:
        dd.io.save(out_file,prod0,compression='zlib')
    except:
        print('error with dd zlib')
    try:
        pickle.dump(prod0,open(out_file2,'wb'))
    except:
        print('error with pickle')

    # close the print file
    if quiet: fsock.close()
Example #5
0
def main():
    global IDX_ORGN
    parser = argparse.ArgumentParser()
    parser.add_argument("-y", "--year", type=int, help="year")
    parser.add_argument("-m",
                        "--month",
                        type=int,
                        choices=1 + np.arange(12),
                        help="month")
    parser.add_argument("-d",
                        "--day",
                        type=int,
                        choices=1 + np.arange(31),
                        help="day")
    parser.add_argument("-a",
                        "--advect",
                        type=str,
                        choices=["OPZ", "EAD", "EAZ", "EID", "EIZ"],
                        help="source of advecting winds")
    parser.add_argument("-p",
                        "--platform",
                        type=str,
                        choices=["M55", "GLO", "BAL"],
                        help="measurement platform")
    parser.add_argument("-n",
                        "--launch_number",
                        type=int,
                        help="balloon launch number within a day")
    parser.add_argument("-s",
                        "--suffix",
                        type=str,
                        help="suffix for special cases")
    parser.add_argument("-q",
                        "--quiet",
                        type=str,
                        choices=["y", "n"],
                        help="quiet (y) or not (n)")
    parser.add_argument("-c", "--clean0", type=bool, help="clean part_000")
    parser.add_argument("-r",
                        "--reanalysis",
                        type=str,
                        choices=["ERA5", "ERAI"],
                        help="reanalysis for detrainement")

    # to be updated
    if 'ciclad' in socket.gethostname():
        #root_dir = '/home/legras/STC/STC-M55'
        traj_dir = '/data/legras/flexout/STC/M55'
        out_dir = '/data/legras/STC'
        mask_dir = '/home/legras/STC/mkSTCmask'
    elif ('climserv' in socket.gethostname()) | ('polytechnique'
                                                 in socket.gethostname()):
        #root_dir = '/home/legras/STC/STC-M55'
        traj_dir = '/bdd/STRATOCLIM/flexout/M55'
        out_dir = '/homedata/legras/STC'
        mask_dir = '/home/legras/STC/mkSTCmask'
    else:
        print('CANNOT RECOGNIZE HOST - DO NOT RUN ON NON DEFINED HOSTS')
        exit()
    """ Parameters """
    # fixed parameters
    # to do (perhaps) : some parameters might be parsed from command line
    # step and max output time
    step = 6
    hmax = 732
    dstep = timedelta(hours=step)
    # time width of the parcel slice
    # slice_width cannot be chosen independently of the step
    # see below the main loop
    # ACHTUNG ACHTUNG!!!! If you change this value, change also the chi erosion in detrainer which is hard coded
    # to occur by 1-hour steps
    slice_width = timedelta(hours=1)
    # number of slices between two outputs
    nb_slices = int(dstep / slice_width)
    # defines here the offset for the detrainment (100h)
    detr_offset = 1 / (100 * 3600.)
    # defines the domain where M55 parcels are living (no FULL trajectories for this setup)
    domain = np.array([[-10., 160.], [0., 50.]])
    # Size of each packet of parcels
    segl = 1000

    # default values of changeable parameters
    # date of the flight
    year = 2017
    month = 7
    day = 29
    platform = 'M55'
    advect = 'EAZ'
    # choice of the reanalysis from which detrainement rates are extracted
    rea = 'ERA5'
    suffix = ''
    launch_number = ''
    quiet = False
    clean0 = False
    args = parser.parse_args()
    if args.year is not None: year = args.year
    if args.month is not None: month = args.month
    if args.day is not None: day = args.day
    if args.advect is not None: advect = args.advect
    if args.platform is not None: platform = args.platform
    if args.launch_number is not None:
        launch_number = '-' + str(args.launch_number)
    if args.suffix is not None: suffix = '-' + args.suffix
    if args.quiet is not None:
        if args.quiet == 'y':
            quiet = True
        else:
            quiet = False
    if args.clean0 is not None: clean0 = args.clean0
    if args.reanalysis is not None: rea = args.reanalysis

    # Update the out_dir with the platform
    out_dir = os.path.join(out_dir, 'STC-' + platform + '-DETR-OUT')

    fdate = datetime(year, month, day)

    # Manage the file that receives the print output
    if quiet:
        # Output file
        print_file = os.path.join(
            out_dir, 'out',
            platform + fdate.strftime('-%Y%m%d') + launch_number + '-' +
            advect + '-D01' + suffix + '-' + rea + '.out')
        fsock = open(print_file, 'w')
        sys.stdout = fsock

    # initial time to read the detrainment files
    # should be after the end of the flight
    # and a 12h or 0h boundary
    print('year', year, 'month', month, 'day', day)
    print('advect', advect)
    print('platform', platform)
    print('launch_number', launch_number)
    print('suffix', suffix)

    # Read the region mask
    # ACHTUNG: the mask should fit the domain and dimensions of the prodO['source']
    # defined below
    if rea == 'ERA5':
        mm = pickle.load(
            gzip.open(os.path.join(mask_dir, 'MaskCartopy2-ERA5-STC.pkl')))
    elif rea == 'ERAI':
        mm = pickle.load(
            gzip.open(os.path.join(mask_dir, 'MaskCartopy2-ERA-I.pkl')))
    mask = mm['mask']

    # Directory of the backward trajectories
    ftraj = os.path.join(
        traj_dir, platform + fdate.strftime('-%Y%m%d') + launch_number + '-' +
        advect + '-D01' + suffix)

    # Output file
    #out_file = os.path.join(out_dir,platform+fdate.strftime('-%Y%m%d')+launch_number+'-'+advect+'-D01'+suffix+'.pkl')
    out_file2 = os.path.join(
        out_dir, platform + fdate.strftime('-%Y%m%d') + launch_number + '-' +
        advect + '-D01' + suffix + '-' + rea + '.hdf5')
    """ Initialization of the calculation """
    # Initialize the dictionary of the parcel dictionaries
    partStep = {}

    # Read the index file that contains the initial positions
    part0 = readidx107(os.path.join(ftraj, 'index_old'), quiet=False)
    print('numpart', part0['numpart'])

    # stamp_date not set in these runs
    # current_date actually shifted by one day / sdate
    current_date = fdate + timedelta(days=1)
    # check flag is clean
    print('check flag is clean ',((part0['flag']&I_HIT)!=0).sum(),((part0['flag']&I_DEAD)!=0).sum(),\
                                 ((part0['flag']&I_CROSSED)!=0).sum())

    # check idx_orgn
    if part0['idx_orgn'] != 0:
        print('MINCHIA, IDX_ORGN NOT 0 AS ASSUMED, CORRECTED WITH READ VALUE')
        print('VALUE ', part0['idx_orgn'])
        IDX_ORGN = part0['idx_orgn']

    # Build a dictionary to host the results
    prod0 = defaultdict(dict)
    # Locations of the crossing and detrainement
    nsrc = 6
    prod0['src']['x'] = np.full(shape=(nsrc, part0['numpart']),
                                fill_value=np.nan,
                                dtype='float')
    prod0['src']['y'] = np.full(shape=(nsrc, part0['numpart']),
                                fill_value=np.nan,
                                dtype='float')
    prod0['src']['p'] = np.full(shape=(nsrc, part0['numpart']),
                                fill_value=np.nan,
                                dtype='float')
    prod0['src']['t'] = np.full(shape=(nsrc, part0['numpart']),
                                fill_value=np.nan,
                                dtype='float')
    prod0['src']['age'] = np.full(shape=(nsrc, part0['numpart']),
                                  fill_value=np.nan,
                                  dtype='float')
    # Flag is copied from index
    prod0['flag_source'] = part0['flag']
    # Make a source array to accumulate the chi
    # For ERA5: Dimension is that of the STC ERA5 fields (201,681) at 0.25° resolution
    # For ERAI: The domain is the reduced domain (10-50N,10W,160E) at 1° resolution, that is (51,171) size
    # Both latitudes and longitudes are growing
    if rea == 'ERA5':
        prod0['source'] = np.zeros(shape=(201, 681), dtype='float')
        xshift = 0
        yshift = 0
    elif rea == 'ERAI':
        prod0['source'] = np.zeros(shape=(51, 171), dtype='float')
        # shift of the source grid in the original ERAI grid with origins at (90S, 179W)
        xshift = -169
        yshift = -90
    # truncate eventually to 32 bits at the output stage
    # Source array that cumulates within regions as a function of time
    prod0['pl'] = np.zeros(shape=(len(mm['regcode']) + 1,
                                  int(part0['numpart'] / segl)),
                           dtype='float')

    # Initialize the erosion
    prod0['chi'] = np.full(part0['numpart'], 1., dtype='float')
    prod0['passed'] = np.full(part0['numpart'], 10, dtype='int')

    # Build the interpolator to the hybrid level
    fhyb, void = tohyb(rea)
    #vfhyb = np.vectorize(fhyb)

    # Read the part_000 file
    partStep[0] = readpart107(0, ftraj, quiet=True)
    # cleaning is necessary for runs starting in the fake restart mode
    # otherwise all parcels are thought to exit at the first step
    if clean0:
        partStep[0]['idx_back'] = []

    # number of hists and exits
    nhits = np.array([0, 0, 0, 0, 0, 0])
    nexits = 0
    ndborne = 0
    nnew = 0
    nradada = 0

    # used to get non borne parcels
    new = np.empty(part0['numpart'], dtype='bool')
    new.fill(False)

    print('Initialization completed')
    """ Main loop on the output time steps """
    for hour in range(step, hmax + 1, step):
        pid = os.getpid()
        py = psutil.Process(pid)
        memoryUse = py.memory_info()[0] / 2**30
        print('memory use: {:4.2f} gb'.format(memoryUse))
        # Get rid of dictionary no longer used
        if hour >= 2 * step: del partStep[hour - 2 * step]
        # Read the new data
        partStep[hour] = readpart107(hour, ftraj, quiet=True)
        # Link the names
        partante = partStep[hour - step]
        partpost = partStep[hour]
        if partpost['nact'] > 0:
            print('hour ', hour, '  numact ', partpost['nact'], '  max p ',
                  partpost['p'].max())
        else:
            print('hour ', hour, '  numact ', partpost['nact'])
        # New date valid for partpost
        current_date -= dstep
        """ Select the parcels that are common to the two steps
        ketp_a is a logical field with same length as partante
        kept_p is a logical field with same length as partpost
        After the launch of the earliest parcel along the flight track, there
        should not be any member in new.
        """
        kept_a = np.in1d(partante['idx_back'],
                         partpost['idx_back'],
                         assume_unique=True)
        kept_p = np.in1d(partpost['idx_back'],
                         partante['idx_back'],
                         assume_unique=True)
        #new_p = ~np.in1d(partpost['idx_back'],partpost['idx_back'],assume_unique=True)
        print('kept a, p ', len(kept_a), len(kept_p), kept_a.sum(),
              kept_p.sum(), '  new ',
              len(partpost['x']) - kept_p.sum())
        """ IDENTIFY AND TAKE CARE OF DEADBORNE AS NON BORNE PARCELS """
        # Find and count the parcels in partpost which where not in partante, flag them as new
        if (hour <= 30) & (partpost['nact'] > 0):
            new[partpost['idx_back'][~kept_p] - IDX_ORGN] = True
            nnew += len(partpost['x']) - kept_p.sum()
        # When the release of parcels has ended, flag the ones that did not show up as dead
        # source their last location as the launching location
        if hour == 30:
            ndborne = np.sum(~new)
            prod0['flag_source'][~new] |= I_DBORNE + I_DEAD
            prod0['src']['x'][0, ~new] = part0['x'][~new]
            prod0['src']['y'][0, ~new] = part0['y'][~new]
            prod0['src']['p'][0, ~new] = part0['p'][~new]
            prod0['src']['t'][0, ~new] = part0['t'][~new]
            prod0['src']['age'][0, ~new] = 0
            print('number of dead borne', ndborne, part0['numpart'] - nnew)
            # get rid of new
            del new
        """ PROCESSING OF CROSSED PARCELS """
        # last known location before crossing stored in the index 0 of src fields
        if len(kept_a) > 0:
            exits = exiter(int((partante['itime']+partpost['itime'])/2), \
                partante['x'][~kept_a],partante['y'][~kept_a],partante['p'][~kept_a],\
                partante['t'][~kept_a],partante['idx_back'][~kept_a],\
                prod0['flag_source'],prod0['src']['x'],prod0['src']['y'],\
                prod0['src']['p'],prod0['src']['t'],prod0['src']['age'],\
                part0['ir_start'], domain)
            nexits += exits
            nhits[0] += exits
            print('exit ', nexits, exits, np.sum(~kept_a),
                  len(kept_a) - len(kept_p))
        """ PROCESSING OF PARCELS WHICH ARE COMMON TO THE TWO OUTPUTS  """
        # Select the kept parcels which have not been hit yet
        # !!! Never use and between two lists, the result is wrong

        if kept_p.sum() == 0:
            live_a = live_p = kept_p
        else:
            live_a = np.logical_and(
                kept_a, (prod0['flag_source'][partante['idx_back'] - IDX_ORGN]
                         & I_DEAD) == 0)
            live_p = np.logical_and(
                kept_p, (prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                         & I_DEAD) == 0)
        print('live a, p ', live_a.sum(), live_p.sum())
        del kept_a
        del kept_p

        # Build generator for live parcel locations of the 1h slices
        gsp = get_slice_part(partante, partpost, live_a, live_p, current_date,
                             dstep, slice_width)
        if verbose: print('built parcel generator for ', current_date)
        """  MAIN LOOP ON THE PARCEL TIME SLICES  """

        for i in range(nb_slices):
            # get the next slice for the particles
            datpart = next(gsp)
            # skip if no particles
            if datpart['ti'] == None:
                continue
            print('current_date ', datpart['ti'])
            #@@ test
            #            print('ti in main ',datpart['ti'])
            #            print('pi in main ',np.min(datpart['pi']),np.max(datpart['pi']))
            #            print('idx_back   ',np.min(datpart['idx_back']),np.max(datpart['idx_back']))
            #            #@@ end test
            # What follows is not independent of the choices of step and slice_width
            # TO DO : a more general version that does not have this dependency
            # This should not be difficult using a generator with validity times
            # We just need to make sure the entrainment data and SP data are valid
            # during the interval [ti tf]
            # Read ERA5 data as the files are also available every hour
            # this might not work if output step is changed without changing slice width
            # the detrainement is actually defined as an average over the next hour that is between ti and tf
            if rea == 'ERA5':
                datrean = read_ECMWF(datpart['ti'], rea)
                # calculate the -log surface pressure at parcel location at time ti
                # create a 2D linear interpolar from the surface pressure field
                lsp = RegularGridInterpolator((datrean.attr['lats'],datrean.attr['lons']),\
                        -np.log(datrean.var['SP']))
            # In the case of ERA-Interim, we read the detrainment which is averaged over a 3h-interval
            # if ti corresponds to the first or the fourth interval of a 6-hour step
            # This might not work if the output step is cahnged without changing slice width
            elif rea == 'ERAI' and ((datpart['ti'].hour % 3) == 2):
                datrean = read_ECMWF(datpart['ti'] - timedelta(hours=2), rea)
                lsp = RegularGridInterpolator((datrean.attr['lats'],np.append(datrean.attr['lons'],181)),\
                        -np.log(np.append(datrean.var['SP'].T,[datrean.var['SP'][:,0]],axis=0).T))
            # perform the interpolation for the location of live parcels at time 0.5*(ti+tf)
            datpart['lsp'] = lsp(
                np.transpose([
                    0.5 * (datpart['yi'] + datpart['yf']),
                    0.5 * (datpart['xi'] + datpart['xf'])
                ]))
            #@@ test
            #            print('surface pressure ',np.exp(-np.min(datpart['lsp'])),np.exp(-np.max(datpart['lsp'])))
            #            print('particle pressure ',np.min(datpart['pi']),np.max(datpart['pi']))
            #@@ end test
            # get the closest hybrid level at time ti
            # define first -log sigma = -log(p) - -log(ps) = - log(p/ps)
            lsig = -np.log(0.5 *
                           (datpart['pi'] + datpart['pf'])) - datpart['lsp']
            #@@ test
            #            print('sigma ',np.exp(-np.max(lsig)),np.exp(-np.min(lsig)))
            #@@ end test
            # get the hybrid level at time ti, the rank of the first retained level is substracted to have hyb starting from 0
            # +1 because the levels are counted from 1, not 0 and +0.5 because we get the closest neighbour
            hyb = np.floor(fhyb(np.transpose([lsig, datpart['lsp']])) +
                           1.5).astype(np.int64) - datrean.attr['levs'][0]
            #@@ test the extreme values of sigma end ps
            if np.min(lsig) < -np.log(0.95):
                print('large sigma detected ', np.exp(-np.min(lsig)))
            if np.max(datpart['lsp']) > -np.log(45000):
                print('small ps detected ', np.exp(-np.max(datpart['lsp'])))
            """ PROCESS THE PARCELS WHICH ARE TOO CLOSE TO GROUND
             These parcels are flagged as crossed and dead, their last location is stored in the
             index 0 of src fields.
             This test handles also the cases outside the interpolation domain as NaN produced by fhyb
             generates very large value of hyb. 
             The trajectories which are stopped here have exited the domain where winds are available to flexpart
             and therefore are wrong from this point. For this reason we label them from their last valid position.
             The threshold 100 is valid for the particular STC ERA5 archive only.
             With ERA-I, this section should not operate."""
            if np.max(hyb) > 100:
                selec = hyb > 100
                nr = radada(datpart['itime'], datpart['xf'][selec],
                            datpart['yf'][selec], datpart['pf'][selec],
                            datpart['tempf'][selec],
                            datpart['idx_back'][selec], prod0['flag_source'],
                            prod0['src']['x'], prod0['src']['y'],
                            prod0['src']['p'], prod0['src']['t'],
                            prod0['src']['age'], part0['ir_start'])
                nradada += nr
                nhits[0] += nr
            """ PROCESS THE (ADJOINT) DETRAINMENT """
            n1 = detrainer(datpart['itime'],
                datpart['xi'],datpart['yi'],datpart['pi'],datpart['tempi'],hyb,
                datpart['xf'],datpart['yf'], datrean.var['UDR'], datpart['idx_back'],\
                prod0['flag_source'],part0['ir_start'], prod0['chi'],prod0['passed'],\
                prod0['src']['x'],prod0['src']['y'],prod0['src']['p'],prod0['src']['t'],\
                prod0['src']['age'],prod0['source'],prod0['pl'],\
                datrean.attr['Lo1'],datrean.attr['La1'],datrean.attr['dlo'],datrean.attr['dla'],\
                xshift,yshift,detr_offset,segl,mask)
            nhits += n1
            #@@ test
            print('return from detrainer', nhits)
            #@@ end test
            sys.stdout.flush()
        """ End of of loop on slices """
        # find parcels still alive       if kept_p.sum()==0:
        try:
            # number of parcels still alive
            nlive = ((prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                      & I_DEAD) == 0).sum()
            # number of parcels still alive and not hit
            nprist = ((prod0['flag_source'][partpost['idx_back'] - IDX_ORGN] &
                       (I_DEAD + I_HIT)) == 0).sum()
            # number of parcels which have hit and crossed
            nouthit = ((prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                        & I_HIT + I_CROSSED) == I_HIT + I_CROSSED).sum()
            # number of parcels which heve crossed without hit
            noutprist = ((prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                          & I_HIT + I_CROSSED) == I_CROSSED).sum()
            # number of parcels which have hit without crossing
            nhitpure = ((prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                         & I_HIT + I_CROSSED) == I_HIT).sum()

        except:
            nlive = 0
            nprist = 0
            nouthit = 0
            noutprist = 0
            nhitpure = 0
            nprist = part0['numpart']

        print('end hour ', hour, '  numact', partpost['nact'], ' nexits',
              nexits, ' nhits', nhits)
        print('nlive', nlive, ' nprist', nprist, ' nouthit', nouthit,
              ' noutprist', noutprist, ' nhitpure', nhitpure)
        # check that nlive + nhits + nexits = numpart, should be true after the first day
        if partpost[
                'nact'] != nprist + nouthit + noutprist + nhitpure + ndborne:
            print('@@@ ACHTUNG numact not equal to sum ', partpost['nact'],
                  nprist + nouthit + noutprist + nhitpure + ndborne)
    """ End of the procedure and storage of the result """
    # clear other fields before the output as this step requires memory
    pid = os.getpid()
    py = psutil.Process(pid)
    memoryUse = py.memory_info()[0] / 2**30
    print('memory use before clean: {:4.2f} gb'.format(memoryUse))
    del partante
    del partpost
    del partStep
    del datpart
    del datrean
    del hyb
    del live_a
    del live_p
    pid = os.getpid()
    py = psutil.Process(pid)
    memoryUse = py.memory_info()[0] / 2**30
    print('memory use after clean: {:4.2f} gb'.format(memoryUse))
    sys.stdout.flush()
    # reduction of the size of prod0 by converting float64 into float32
    prod0['chi'] = prod0['chi'].astype(np.float32)
    prod0['passed'] = prod0['passed'].astype(np.int32)
    for var in ['age', 'p', 't', 'x', 'y']:
        prod0['src'][var] = prod0['src'][var].astype(np.float32)
    #output file
    dd.io.save(out_file2, prod0, compression='zlib')
    #pickle.dump(prod0,gzip.open(out_file,'wb'))
    # close the print file
    print('completed run')
    if quiet: fsock.close()
    return
Example #6
0
def main():
    global IDX_ORGN
    parser = argparse.ArgumentParser()
    parser.add_argument("-y", "--year", type=int, help="year")
    parser.add_argument("-m",
                        "--month",
                        type=int,
                        choices=1 + np.arange(12),
                        help="month")
    parser.add_argument(
        "-a",
        "--advect",
        type=str,
        choices=["OPZ", "EAD", "EAZ", "EID", "EIZ", "EID-FULL", "EIZ-FULL"],
        help="source of advecting winds")
    parser.add_argument("-l", "--level", type=int, help="PT level")
    parser.add_argument("-s",
                        "--suffix",
                        type=str,
                        help="suffix for special cases")
    parser.add_argument("-q",
                        "--quiet",
                        type=str,
                        choices=["y", "n"],
                        help="quiet (y) or not (n)")

    # to be updated
    if socket.gethostname() == 'graphium':
        pass
    elif 'ciclad' in socket.gethostname():
        #root_dir = '/home/legras/STC/STC-M55'
        main_sat_dir = '/bdd/STRATOCLIM/flexpart_in'
        traj_dir = '/data/legras/flexout/STC/BACK'
        out_dir = '/data/legras/STC'
    elif socket.gethostname() == 'grapelli':
        pass
    elif socket.gethostname() == 'coltrane':
        pass
    else:
        print('CANNOT RECOGNIZE HOST - DO NOT RUN ON NON DEFINED HOSTS')
        exit()
    """ Parameters """
    # to do (perhaps) : some parameters might be parsed from command line
    # step and max output time
    step = 6
    hmax = 1824
    dstep = timedelta(hours=step)
    # age limit in days
    age_bound = 44.
    # time width of the parcel slice
    slice_width = timedelta(minutes=5)
    # dtRange
    dtRange = {'MSG1': timedelta(minutes=30), 'Hima': timedelta(minutes=20)}
    # number of slices between two outputs
    nb_slices = int(dstep / slice_width)
    # default values of parameters
    # start date of the backward run, corresponding to itime=0
    year = 2017
    # 8 +1 means we start on September 1 at 0h and cover the whole month of August
    month = 8 + 1
    # day=1 should not be changed
    day = 1
    advect = 'EAD'
    suffix = ''
    quiet = False
    level = 380
    args = parser.parse_args()
    if args.year is not None:
        year = args.year
    if args.month is not None:
        month = args.month + 1
    if args.advect is not None:
        advect = args.advect
    if args.level is not None:
        level = args.level
    if args.suffix is not None:
        suffix = '-' + args.suffix
    if args.quiet is not None:
        if args.quiet == 'y':
            quiet = True
        else:
            quiet = False

    # Update the out_dir with the platform
    out_dir = os.path.join(out_dir, 'STC-BACK-OUT')

    sdate = datetime(year, month, day)
    # fdate defined to make output under the name of the month where parcels are released
    fdate = sdate - timedelta(days=1)

    # Number of parcels launched per time slot (1 per degree on a 170x50 grid)
    granule_size = 8500
    # number of granules in 6 hours
    granule_step = 4 * 6
    # size of granules launched during 6 hours
    granule_quanta = granule_size * granule_step
    # Modification of granule_size and granule_quanta
    if 'FULL' in advect:
        granule_size = 28800
        granule_step = 6
        granule_quanta = granule_size * granule_step

    # Manage the file that receives the print output
    if quiet:
        # Output file
        print_file = os.path.join(
            out_dir, 'out', 'BACK-' + advect + fdate.strftime('-%b-%Y-') +
            str(level) + 'K' + suffix + '.out')
        fsock = open(print_file, 'w')
        sys.stdout = fsock

    print('year', year, 'month', month, 'day', day)
    print('advect', advect)
    print('suffix', suffix)

    # Directory of the backward trajectories
    ftraj = os.path.join(
        traj_dir, 'BACK-' + advect + fdate.strftime('-%b-%Y-') + str(level) +
        'K' + suffix)

    # Output file
    out_file = os.path.join(
        out_dir, 'BACK-' + advect + fdate.strftime('-%b-%Y-') + str(level) +
        'K' + suffix + '.hdf5z')
    out_file1 = os.path.join(
        out_dir, 'BACK-' + advect + fdate.strftime('-%b-%Y-') + str(level) +
        'K' + suffix + '.hdf5b')
    out_file2 = os.path.join(
        out_dir, 'BACK-' + advect + fdate.strftime('-%b-%Y-') + str(level) +
        'K' + suffix + '.pkl')

    # Directories for the satellite cloud top files
    satdir ={'MSG1':os.path.join(main_sat_dir,'StratoClim+1kmD_msg1-c'),\
             'Hima':os.path.join(main_sat_dir,'StratoClim+1kmD_himawari-d')}
    """ Initialization of the calculation """
    # Initialize the slice map to be used as a buffer for the cloudtops
    satmap = pixmap()
    satfill = {}
    datsat = {}
    # Initialize the dictionary of the parcel dictionaries
    partStep = {}

    # Build the satellite field generator
    get_sat = {'MSG1': read_sat(sdate,dtRange['MSG1'],satdir['MSG1']),\
               'Hima': read_sat(sdate,dtRange['Hima'],satdir['Hima'])}

    # Open the part_000 file that contains the initial positions
    part0 = readidx107(os.path.join(ftraj, 'part_000'), quiet=True)
    print('numpart', part0['numpart'])
    numpart = part0['numpart']
    numpart_s = granule_size

    # stamp_date not set in these runs
    # current_date actually shifted by one day / sdate
    current_date = sdate
    # check flag is clean
    print('check flag is clean ',((part0['flag']&I_HIT)!=0).sum(),((part0['flag']&I_DEAD)!=0).sum(),\
                                 ((part0['flag']&I_CROSSED)!=0).sum())
    # check idx_orgn
    if part0['idx_orgn'] != 0:
        print('MINCHIA, IDX_ORGN NOT 0 AS ASSUMED, CORRECTED WITH READ VALUE')
        print('VALUE ', part0['idx_orgn'])
        IDX_ORGN = part0['idx_orgn']
    idx1 = IDX_ORGN

    # Build a dictionary to host the results
    prod0 = defaultdict(dict)
    prod0['src']['x'] = np.full(part0['numpart'],
                                fill_value=np.nan,
                                dtype='float')
    prod0['src']['y'] = np.full(part0['numpart'],
                                fill_value=np.nan,
                                dtype='float')
    prod0['src']['p'] = np.full(part0['numpart'],
                                fill_value=np.nan,
                                dtype='float')
    prod0['src']['t'] = np.full(part0['numpart'],
                                fill_value=np.nan,
                                dtype='float')
    prod0['src']['age'] = np.full(part0['numpart'],
                                  fill_value=np.nan,
                                  dtype='int')
    prod0['flag_source'] = part0['flag']
    prod0['rvs'] = np.full(part0['numpart'], 0.01, dtype='float')

    # truncate eventually to 32 bits at the output stage

    # read the part_000 file for the first granule
    partStep[0] = {}
    partStep[0]['x'] = part0['x'][:granule_size]
    partStep[0]['y'] = part0['y'][:granule_size]
    partStep[0]['t'] = part0['t'][:granule_size]
    partStep[0]['p'] = part0['p'][:granule_size]
    partStep[0]['t'] = part0['t'][:granule_size]
    partStep[0]['idx_back'] = part0['idx_back'][:granule_size]
    partStep[0]['ir_start'] = part0['ir_start'][:granule_size]
    partStep[0]['itime'] = 0

    # number of hists and exits
    nhits = 0
    nexits = 0
    ndborne = 0
    nnew = granule_size
    nold = 0

    # used to get non borne parcels
    new = np.empty(part0['numpart'], dtype='bool')
    new.fill(False)

    print('Initialization completed')
    """ Main loop on the output time steps """
    for hour in range(step, hmax + 1, step):
        pid = os.getpid()
        py = psutil.Process(pid)
        memoryUse = py.memory_info()[0] / 2**30
        print('memory use: {:4.2f} gb'.format(memoryUse))
        # Get rid of dictionary no longer used
        if hour >= 2 * step: del partStep[hour - 2 * step]

        # Read the new data
        partStep[hour] = readpart107(hour, ftraj, quiet=True)
        # Link the names
        partante = partStep[hour - step]
        partpost = partStep[hour]

        if partpost['nact'] > 0:
            print('hour ', hour, '  numact ', partpost['nact'], '  max p ',
                  partpost['p'].max())
        else:
            print('hour ', hour, '  numact ', partpost['nact'])
        # New date valid for partpost
        current_date -= dstep

        # Processing of water mixing ratio
        # Select non stopped parcels in partante
        selec = (prod0['flag_source'][partante['idx_back'] - IDX_ORGN]
                 & I_STOP) == 0
        idx = partante['idx_back'][selec]
        prod0['rvs'][idx-IDX_ORGN] = np.minimum(prod0['rvs'][idx-IDX_ORGN],\
                satratio(partante['p'][selec],partante['t'][selec]))
        """ Select the parcels that are common to the two steps
        ketp_a is a logical field with same length as partante
        kept_p is a logical field with same length as partpost
        """
        kept_a = np.in1d(partante['idx_back'],
                         partpost['idx_back'],
                         assume_unique=True)
        kept_p = np.in1d(partpost['idx_back'],
                         partante['idx_back'],
                         assume_unique=True)
        #new_p = ~np.in1d(partpost['idx_back'],partpost['idx_back'],assume_unique=True)
        print('kept a, p ', len(kept_a), len(kept_p), kept_a.sum(),
              kept_p.sum(), '  new ',
              len(partpost['x']) - kept_p.sum())
        nnew += len(partpost['x']) - kept_p.sum()
        """ PROCESSING OF DEADBORNE PARCELS
        Manage the parcels launched during the last 6-hour which have already
        exited and do not appear in posold or posact (borne dead parcels).
        These parcels are stored in the last part of posact, at most
        the last granule_quanta parcels. """
        if numpart_s < numpart:
            print("manage deadborne", flush=True)
            # First index of the current quanta """
            numpart_s += granule_quanta
            print("idx1", idx1, " numpart_s", numpart_s)
            # Extract the last granule_size indexes from posacti, this is where the new parcels should be
            if hour == step:
                idx_act = partpost['idx_back']
            else:
                idx_act = partpost['idx_back'][-granule_quanta:]
            # Generate the list of indexes that should be found in this range
            # ACHTUNG ACHTUNG : this works because IDX_ORGN=1, FIX THAT
            idx_theor = np.arange(idx1, numpart_s + IDX_ORGN)
            # Find the missing indexes in idx_act (make a single line after validation)
            kept_borne = np.in1d(idx_theor, idx_act, assume_unique=True)
            idx_deadborne = idx_theor[~kept_borne]
            # Process these parcels by assigning exit at initial location
            prod0['flag_source'][
                idx_deadborne -
                IDX_ORGN] = prod0['flag_source'][idx_deadborne -
                                                 IDX_ORGN] | I_DEAD + I_DBORNE
            prod0['src']['x'][idx_deadborne -
                              IDX_ORGN] = part0['x'][idx_deadborne - IDX_ORGN]
            prod0['src']['y'][idx_deadborne -
                              IDX_ORGN] = part0['y'][idx_deadborne - IDX_ORGN]
            prod0['src']['p'][idx_deadborne -
                              IDX_ORGN] = part0['p'][idx_deadborne - IDX_ORGN]
            prod0['src']['t'][idx_deadborne -
                              IDX_ORGN] = part0['t'][idx_deadborne - IDX_ORGN]
            prod0['src']['age'][idx_deadborne - IDX_ORGN] = 0.
            print("number of deadborne ", len(idx_deadborne))
            ndborne += len(idx_deadborne)
            idx1 = numpart_s + IDX_ORGN
        """ PROCESSING OF CROSSED PARCELS """
        if len(kept_a) > 0:
            exits = exiter(int((partante['itime']+partpost['itime'])/2), \
                partante['x'][~kept_a],partante['y'][~kept_a],partante['p'][~kept_a],\
                partante['t'][~kept_a],partante['idx_back'][~kept_a],\
                prod0['flag_source'],prod0['src']['x'],prod0['src']['y'],\
                prod0['src']['p'],prod0['src']['t'],prod0['src']['age'],\
                part0['ir_start'], satmap.range)
            nexits += exits
            print('exit ', nexits, exits, np.sum(~kept_a),
                  len(kept_a) - len(kept_p))
        """ PROCESSING OF PARCELS WHICH ARE COMMON TO THE TWO OUTPUTS  """
        # Select the kept parcels which have not been hit yet
        # !!! Never use and between two lists, the result is wrong

        if kept_p.sum() == 0:
            live_a = live_p = kept_p
        else:
            live_a = np.logical_and(
                kept_a, (prod0['flag_source'][partante['idx_back'] - IDX_ORGN]
                         & I_DEAD) == 0)
            live_p = np.logical_and(
                kept_p, (prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                         & I_DEAD) == 0)
        print('live a, p ', live_a.sum(), live_p.sum())
        del kept_a
        del kept_p

        # Build generator for parcel locations of the 5' slices
        gsp = get_slice_part(partante, partpost, live_a, live_p, current_date,
                             dstep, slice_width)
        if verbose: print('built parcel generator for ', current_date)
        """  MAIN LOOP ON THE PARCEL TIME SLICES  """

        for i in range(nb_slices):
            # get the slice for the particles
            datpart = next(gsp)
            if verbose: print('part slice ', i, datpart['time'])
            # Make sure the present satellite slice is OK
            # The while should ensure that the run synchronizes
            # when it starts.
            while satmap.check('MSG1', datpart['time']) is False:
                # if not get next satellite slice
                try:
                    void = next(satfill['MSG1'])
                # read new satellite file if the slice generator is over
                # make a new slice generator and get first slice
                except:
                    datsat['MSG1'] = next(get_sat['MSG1'])
                    satfill['MSG1'] = satmap.fill('MSG1', datsat)
                    void = next(satfill['MSG1'])
                finally:
                    if verbose:
                        print('check MSG1 ',
                              satmap.check('MSG1', datpart['time']), '##',
                              datpart['time'], '##', satmap.zone['MSG1']['ti'],
                              '##', satmap.zone['MSG1']['tf'])
            while satmap.check('Hima', datpart['time']) is False:
                try:
                    void = next(satfill['Hima'])
                except:
                    datsat['Hima'] = next(get_sat['Hima'])
                    satfill['Hima'] = satmap.fill('Hima', datsat)
                    void = next(satfill['Hima'])
                finally:
                    if verbose:
                        print('check Hima ',
                              satmap.check('Hima', datpart['time']), '##',
                              datpart['time'], '##', satmap.zone['Hima']['ti'],
                              '##', satmap.zone['Hima']['tf'])
            """ PROCESS THE COMPARISON OF PARCEL PRESSURES TO CLOUDS """
            if len(datpart['x']) > 0:
                nhits += convbirth(datpart['itime'],
                    datpart['x'],datpart['y'],datpart['p'],datpart['t'],datpart['idx_back'],\
                    prod0['flag_source'],prod0['src']['x'],prod0['src']['y'],\
                    prod0['src']['p'],prod0['src']['t'],prod0['src']['age'],\
                    satmap.ptop, part0['ir_start'],\
                    satmap.range[0,0],satmap.range[1,0],satmap.stepx,satmap.stepy,satmap.binx,satmap.biny)

            sys.stdout.flush()
            """ End of of loop on slices """

        # Check the age limit (easier to do it here)
        print("Manage age limit", flush=True)
        age_sec = part0['ir_start'][partante['idx_back'] -
                                    IDX_ORGN] - partante['itime']
        IIold_o = age_sec > (age_bound - 0.25) * 86400
        IIold_o = IIold_o & (
            (prod0['flag_source'][partante['idx_back'] - IDX_ORGN] & I_STOP)
            == 0)
        idx_IIold = partante['idx_back'][IIold_o]
        j_IIold_o = np.where(IIold_o)
        prod0['flag_source'][idx_IIold - IDX_ORGN] = prod0['flag_source'][
            idx_IIold - IDX_ORGN] | I_DEAD + I_OLD
        prod0['src']['x'][idx_IIold - IDX_ORGN] = partante['x'][j_IIold_o]
        prod0['src']['y'][idx_IIold - IDX_ORGN] = partante['y'][j_IIold_o]
        prod0['src']['p'][idx_IIold - IDX_ORGN] = partante['p'][j_IIold_o]
        prod0['src']['t'][idx_IIold - IDX_ORGN] = partante['t'][j_IIold_o]
        prod0['src']['age'][idx_IIold - IDX_ORGN] = (
            (part0['ir_start'][idx_IIold - IDX_ORGN] - partante['itime']) /
            86400)
        print("number of IIold ", len(idx_IIold))
        nold += len(idx_IIold)

        # find parcels still alive       if kept_p.sum()==0:
        try:
            nlive = ((prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                      & I_DEAD) == 0).sum()
            #n_nohit = ((prod0['flag_source'][partpost['idx_back']-IDX_ORGN] & I_HIT) == 0).sum()
        except:
            nlive = 0
            #n_nohit =0
        print('end hour ',hour,'  numact', partpost['nact'],' nnew',nnew, ' nexits',nexits,' nhits',nhits, ' nlive',nlive,\
              ' nold',nold,' ndborne',ndborne)
        # check that nold + nlive + nhits + nexits = nnew
        if nnew != nexits + nhits + nlive + nold:
            print('@@@ ACHTUNG nnew not equal to sum ', nnew,
                  nexits + nhits + nlive + nold)
    """ End of the procedure and storage of the result """
    pid = os.getpid()
    py = psutil.Process(pid)
    memoryUse = py.memory_info()[0] / 2**30
    print('memory use before clean: {:4.2f} gb'.format(memoryUse))
    del partante
    del partpost
    del live_a
    del live_p
    del datsat
    del datpart
    del satfill
    prod0['rvs'] = prod0['rvs'].astype(np.float32)
    for var in ['age', 'p', 't', 'x', 'y']:
        prod0['src'][var] = prod0['src'][var].astype(np.float32)
    pid = os.getpid()
    py = psutil.Process(pid)
    memoryUse = py.memory_info()[0] / 2**30
    print('memory use after clean: {:4.2f} gb'.format(memoryUse))
    #output file
    #pickle.dump(prod0,gzip.open(out_file,'wb'))
    try:
        dd.io.save(out_file1, prod0, compression='blosc')
    except:
        print('error with dd blosc')
    try:
        dd.io.save(out_file, prod0, compression='zlib')
    except:
        print('error with dd zlib')
    try:
        pickle.dump(prod0, open(out_file2, 'wb'))
    except:
        print('error with pickle')
    # close the print file
    if quiet: fsock.close()
def main():
    global IDX_ORGN
    parser = argparse.ArgumentParser()
    parser.add_argument("-y", "--year", type=int, help="year")
    parser.add_argument("-m1",
                        "--month1",
                        type=int,
                        choices=1 + np.arange(12),
                        help="month1")
    parser.add_argument("-m2",
                        "--month2",
                        type=int,
                        choices=1 + np.arange(12),
                        help="month2")
    parser.add_argument("-d1",
                        "--day1",
                        type=int,
                        choices=1 + np.arange(31),
                        help="day1")
    parser.add_argument("-d2",
                        "--day2",
                        type=int,
                        choices=1 + np.arange(31),
                        help="day2")
    parser.add_argument("-a",
                        "--advect",
                        type=str,
                        choices=["OPZ", "EAD", "EAZ", "EID", "EIZ"],
                        help="source of advecting winds")
    parser.add_argument("-s",
                        "--suffix",
                        type=str,
                        help="suffix for special cases")
    parser.add_argument("-q",
                        "--quiet",
                        type=str,
                        choices=["y", "n"],
                        help="quiet (y) or not (n)")
    parser.add_argument("-l", "--level", type=int, help="PT level")
    #parser.add_argument("-c","--clean0",type=bool,help="clean part_000")
    parser.add_argument("-t",
                        "--step",
                        type=int,
                        help="step in hour between two part files")
    #parser.add_argument("-ct","--cloud_type",type=str,choices=["meanhigh","veryhigh","silviahigh"],help="cloud type filter")
    #parser.add_argument("-k","--diffus",type=str,choices=['01','1','001'],help='diffusivity parameter')
    parser.add_argument("-v",
                        "--vshift",
                        type=int,
                        choices=[0, 10],
                        help='vertical shift')
    parser.add_argument("-hm",
                        "--hmax",
                        type=int,
                        help='maximum considered integration time (hour)')
    parser.add_argument("-b",
                        "--bak",
                        type=str,
                        choices=["y", "n"],
                        help="backup (y) or not (n)")
    parser.add_argument("-c",
                        "--cont",
                        type=str,
                        choices=["y", "n"],
                        help="continuation (y) or not (n)")
    parser.add_argument("-bs",
                        "--bakstep",
                        type=int,
                        help='backup step (hour)')

    # to be updated
    # Define main directories
    if 'ciclad' in socket.gethostname():
        #main_sat_dir = '/data/legras/flexpart_in/SAFNWC'
        #SVC_Dir = '/bdd/CFMIP/SEL2'
        traj_dir = '/data/legras/flexout/STC/Sivan'
        out_dir = '/data/legras/STC'
        #out_dir = '/data/legras/STC'
    elif ('climserv' in socket.gethostname()) | ('polytechnique'
                                                 in socket.gethostname()):
        traj_dir = '/data/legras/flexout/STC/Sivan'
        out_dir = '/homedata/legras/STC'
    else:
        print('CANNOT RECOGNIZE HOST - DO NOT RUN ON NON DEFINED HOSTS')
        exit()
    """ Parameters """
    # Output step of the trajectories in hour
    step = 6
    hmax = 3360
    #hmax = 18
    dstep = timedelta(hours=step)
    # time width of the parcel slice
    # might be better passed as a parameter
    age_bound = 44.5
    # time width of the parcel slice
    slice_width = timedelta(minutes=5)
    # number of slices between two outputs
    nb_slices = int(dstep / slice_width)
    # Number of parcels launched per time slot (1 per degree on a 170x50 grid)
    granule_size = 8500
    # number of granules in step hours
    granule_step = 6
    # size of granules launched during step hours
    granule_quanta = granule_size * granule_step
    # default values of parameters
    # date of the flight
    year = 2017
    month1 = 7
    month2 = 9
    day1 = 1
    day2 = 30
    advect = 'EAD'
    suffix = ''
    quiet = False
    clean0 = True
    backup = False
    backup_step = 40 * step
    restart = False
    #cloud_type = 'silviahigh'
    #diffus = '01'
    vshift = 0
    level = 380
    super = ''
    # Step of GridSat
    dtRange = timedelta(hours=3)
    args = parser.parse_args()
    if args.year is not None: year = args.year
    if args.month1 is not None: month1 = args.month1
    if args.month2 is not None: month2 = args.month2
    if args.advect is not None: advect = args.advect
    if args.day1 is not None: day1 = args.day1
    if args.day2 is not None: day2 = args.day2
    if args.level is not None: level = args.level
    if args.hmax is not None: hmax = args.hmax
    if args.suffix is not None: suffix = '-' + args.suffix
    if args.quiet is not None:
        if args.quiet == 'y': quiet = True
        else: quiet = False
    #if args.clean0 is not None: clean0 = args.clean0
    #if args.cloud_type is not None: cloud_type = args.cloud_type
    if args.step is not None: step = args.step
    #if args.diffus is not None: diffus = args.diffus
    #diffus = '-D' + diffus
    if args.vshift is not None:
        vshift = args.vshift
        if vshift > 0:
            super = '-super' + str(vshift)
    if args.bak is not None:
        if args.bak == 'y': backup = True
        else: backup = False
    if args.bakstep is not None: backup_step = args.bakstep
    if args.cont is not None:
        if args.cont == 'y': restart = True
        else: restart = False

    # Update the out_dir with the cloud type and the super paramater
    out_dir = os.path.join(out_dir, 'STC-Sivan-OUT-GridSat-WMO' + super)
    try:
        os.mkdir(out_dir)
        os.mkdir(out_dir + '/out')
    except:
        print('out_dir directory already created')

    # Dates beginning and end
    date_beg = datetime(year=year, month=month1, day=day1, hour=0)
    date_end = datetime(year=year, month=month2, day=day2, hour=0)

    # Manage the file that receives the print output
    if quiet:
        # Output file
        #print_file = os.path.join(out_dir,'out','BACK-SVC-EAD-'+date_beg.strftime('%b-%Y-day%d-')+date_end.strftime('%d-D01')+'.out')
        print_file = os.path.join(
            out_dir, 'out', 'Sivan-T2-' + advect + '-JAS-' +
            date_beg.strftime('%Y-') + str(level) + 'K.out')
        fsock = open(print_file, 'w')
        sys.stdout = fsock

    # initial time to read the sat files
    # should be after the latest launch date
    sdate = date_end + timedelta(days=1)
    print('year', year, 'month1', month1, 'day1', day1, 'month2', month2,
          'day2', day2)
    print('advect', advect)
    print('suffix', suffix)

    # patch to fix the problem of the data hole on the evening of 2 August 2017
    # should not happen
    #if sdate == datetime(2017,8,2): sdate = datetime(2017,8,3,6)

    # Directory of the backward trajectories (input) and name of the output file
    ftraj = os.path.join(
        traj_dir, 'Sivan-' + advect + '-JAS-' + date_beg.strftime('%Y-') +
        str(level) + 'K')
    out_file2 = os.path.join(
        out_dir, 'Sivan-T2-' + advect + '-JAS-' + date_beg.strftime('%Y-') +
        str(level) + 'K.hdf5')
    # backup file
    if backup:
        bak_file_prod0 = os.path.join(
            out_dir, 'Sivan-T2-' + advect + '-JAS-' +
            date_beg.strftime('%Y-') + str(level) + 'K-backup-prod0.hdf5')
        bak_file_params = os.path.join(
            out_dir, 'Sivan-T2-' + advect + '-JAS-' +
            date_beg.strftime('%Y-') + str(level) + 'K-backup-params.hdf5')
    """ Initialization of the calculation """
    # Initialize the dictionary of the parcel dictionaries
    partStep = {}
    # initialize a grid that will be used to before actually doing any read
    gg = geosat.GeoGrid('GridSat')

    # Build the satellite field generator
    get_sat = read_sat(sdate, dtRange, pre=True, vshift=vshift)
    # Build the ECMWF field generator (both available at 3 hour interval)
    get_ERA5 = read_ERA5(sdate, dtRange, pre=True)
    # Read the index file that contains the initial positions
    part0 = readidx107(os.path.join(ftraj, 'part_000'), quiet=False)
    print('numpart', part0['numpart'])
    numpart = part0['numpart']
    numpart_s = granule_size  # because of parcels launched at time 0
    # stamp_date not set in these runs
    # current_date actually shifted by one day / sdate
    # We assume here that part time is defined from this day at 0h
    # sdate defined above should be equal or posterior to current_date
    current_date = sdate
    # check flag is clean
    print('check flag is clean ',((part0['flag']&I_HIT)!=0).sum(),((part0['flag']&I_DEAD)!=0).sum(),\
                                 ((part0['flag']&I_CROSSED)!=0).sum())
    # check idx_orgn
    if part0['idx_orgn'] != 0:
        print(
            'MINCHIA, IDX_ORGN NOT 0 ASSTC-M55-OUT-SAF-super1silviahigh ASSUMED, CORRECTED WITH READ VALUE'
        )
        print('VALUE ', part0['idx_orgn'])
        IDX_ORGN = part0['idx_orgn']
    idx1 = IDX_ORGN

    # Build a dictionary to host the results
    prod0 = defaultdict(dict)
    prod0['src']['x'] = np.empty(part0['numpart'], dtype='float')
    prod0['src']['y'] = np.empty(part0['numpart'], dtype='float')
    prod0['src']['p'] = np.empty(part0['numpart'], dtype='float')
    prod0['src']['t'] = np.empty(part0['numpart'], dtype='float')
    prod0['src']['age'] = np.empty(part0['numpart'], dtype='int')
    prod0['flag_source'] = part0['flag']
    # Set rvs at arbitrary large value
    prod0['rvs'] = np.full(part0['numpart'], 0.01, dtype='float')
    # truncate eventually to 32 bits at the output stage

    # read the part_000 file
    if not restart:
        partStep[0] = readpart107(0, ftraj, quiet=False)
        # cleaning is necessary for runs starting in the fake restart mode
        # otherwise all parcels are thought to exit at the first step
        if clean0:
            partStep[0]['idx_back'] = []
        # parcels with longitude east of zero degree are set to negative values
        partStep[0]['x'][partStep[0]['x'] > 180] -= 360

    # number of hists and exits
    nhits = 0
    nexits = 0
    ndborne = 0
    nnew = granule_size  # because of parcels launched at time 0
    nold = 0
    offset = 0

    # initialize datsat to None to force first read
    datsat = None

    # used to get non borne parcels (presently unused)
    #new = np.empty(part0['numpart'],dtype='bool')
    #new.fill(False)

    print('Initialization completed')

    if restart:
        print('restart run')
        try:
            params = fl.load(bak_file_params)
            prod0 = fl.load(bak_file_prod0)
        except:
            print('cannot load backup')
            return -1
        [
            offset, nhits, nexits, nold, ndborne, nnew, idx1, numpart_s,
            current_date
        ] = params['params']
        partStep[offset] = readpart107(offset, ftraj, quiet=True)
        partStep[offset]['x'][partStep[offset]['x'] > 180] -= 360
        # Initialize sat and ERA5 yield one step ahead as a precaution
        get_sat = read_sat(current_date + dstep,
                           dtRange,
                           pre=True,
                           vshift=vshift)
        get_ERA5 = read_ERA5(current_date + dstep, dtRange, pre=True)
    """ Main loop on the output time steps """
    for hour in range(step + offset, hmax + 1, step):
        pid = os.getpid()
        py = psutil.Process(pid)
        memoryUse = py.memory_info()[0] / 2**30
        print('memory use: {:4.2f} gb'.format(memoryUse))
        # Get rid of dictionary no longer used
        if hour >= 2 * step:
            try:
                del partStep[hour - 2 * step]
            except:
                print('nothing to delete')

        # Read the new data
        partStep[hour] = readpart107(hour, ftraj, quiet=True)
        # Link the names as views
        partante = partStep[hour - step]
        partpost = partStep[hour]
        # parcels with longitude east of zero degree are set to negative values
        # done with try to prevent errors when the file is empty
        try:
            partpost['x'][partpost['x'] > 180] -= 360
            #print("partpost x ",partpost['x'].min(),partpost['x'].max())
        except:
            pass

        if partpost['nact'] > 0:
            print('hour ', hour, '  numact ', partpost['nact'], '  max p ',
                  partpost['p'].max())
        else:
            print('hour ', hour, '  numact ', partpost['nact'])
        # New date valid for partpost
        current_date -= dstep

        # Processing of water mixing ratio
        # Select non stopped parcels in partante
        if len(partante['idx_back']) > 0:
            selec = (prod0['flag_source'][partante['idx_back'] - IDX_ORGN]
                     & I_STOP) == 0
            idx = partante['idx_back'][selec]
            prod0['rvs'][idx-IDX_ORGN] = np.minimum(prod0['rvs'][idx-IDX_ORGN],\
                satratio(partante['p'][selec],partante['t'][selec]))
        """ Select the parcels that are common to the two steps
        ketp_a is a logical field with same length as partante
        kept_p is a logical field with same length as partpost
        After the launch of the earliest parcel along the flight track, there
        should not be any member in new
        The parcels
        """
        kept_a = np.in1d(partante['idx_back'],
                         partpost['idx_back'],
                         assume_unique=True)
        kept_p = np.in1d(partpost['idx_back'],
                         partante['idx_back'],
                         assume_unique=True)
        #new_p = ~np.in1d(partpost['idx_back'],partpost['idx_back'],assume_unique=True)
        print('kept a, p ', len(kept_a), len(kept_p), kept_a.sum(),
              kept_p.sum(), '  new ',
              len(partpost['x']) - kept_p.sum())
        nnew += len(partpost['x']) - kept_p.sum()
        """ PROCESSING OF DEADBORNE PARCELS
        Manage the parcels launched during the last 6-hour which have already
        exited and do not appear in posold or posact (borne dead parcels).
        These parcels are stored in the last part of posact, at most
        the last granule_quanta parcels. """
        if numpart_s < numpart:
            # First index of the current quanta """
            numpart_s += granule_quanta
            print("manage deadborne idx1", idx1, " numpart_s", numpart_s)
            # Extract the last granule_size indexes from posacti, this is where the new parcels should be
            if hour == step:
                idx_act = partpost['idx_back']
            else:
                idx_act = partpost['idx_back'][-granule_quanta:]
            # Generate the list of indexes that should be found in this range
            # This should works for both values of IDX_orgn
            idx_theor = np.arange(idx1, numpart_s + IDX_ORGN)
            # Find the missing indexes in idx_act (make a single line after validation)
            kept_borne = np.in1d(idx_theor, idx_act, assume_unique=True)
            idx_deadborne = idx_theor[~kept_borne]
            # Process these parcels by assigning exit at initial location
            prod0['flag_source'][
                idx_deadborne -
                IDX_ORGN] = prod0['flag_source'][idx_deadborne -
                                                 IDX_ORGN] | I_DEAD + I_DBORNE
            prod0['src']['x'][idx_deadborne -
                              IDX_ORGN] = part0['x'][idx_deadborne - IDX_ORGN]
            prod0['src']['y'][idx_deadborne -
                              IDX_ORGN] = part0['y'][idx_deadborne - IDX_ORGN]
            prod0['src']['p'][idx_deadborne -
                              IDX_ORGN] = part0['p'][idx_deadborne - IDX_ORGN]
            prod0['src']['t'][idx_deadborne -
                              IDX_ORGN] = part0['t'][idx_deadborne - IDX_ORGN]
            prod0['src']['age'][idx_deadborne - IDX_ORGN] = 0.
            print("number of deadborne ", len(idx_deadborne))
            ndborne += len(idx_deadborne)
            idx1 = numpart_s + IDX_ORGN
        """ PROCESSING OF CROSSED PARCELS """
        if len(kept_a) > 0:
            exits = exiter(int((partante['itime']+partpost['itime'])/2), \
                partante['x'][~kept_a],partante['y'][~kept_a],partante['p'][~kept_a],\
                partante['t'][~kept_a],partante['idx_back'][~kept_a],\
                prod0['flag_source'],prod0['src']['x'],prod0['src']['y'],\
                prod0['src']['p'],prod0['src']['t'],prod0['src']['age'],\
                part0['ir_start'], gg.box_range)
            nexits += exits
            print('exit ', nexits, exits, np.sum(~kept_a),
                  len(kept_a) - len(kept_p))
        """ PROCESSING OF PARCELS WHICH ARE COMMON TO THE TWO OUTPUTS  """
        # Select the kept parcels which have not been hit yet
        # !!! Never use and between two lists, the result is wrong

        if kept_p.sum() == 0:
            live_a = live_p = kept_p
        else:
            live_a = np.logical_and(
                kept_a, (prod0['flag_source'][partante['idx_back'] - IDX_ORGN]
                         & I_DEAD) == 0)
            live_p = np.logical_and(
                kept_p, (prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                         & I_DEAD) == 0)
        print('live a, b ', live_a.sum(), live_p.sum())
        """ Correction step that moves partante['x'] to avoid big jumps at the periodicity frontier on the x-axis
        To be activated in the FULL version"""
        # if kept_p.sum()>0:
        #     diffx = partpost['x'][live_p] - partante['x'][live_a]
        #     bb = np.zeros(len(diffx))
        #     bb[diffx>180] = 360
        #     bb[diffx<-180] = -360
        #     partante['x'][live_a] += bb
        #     del bb
        #     del diffx
        # del kept_p
        # del kept_a
        # DOES not work in the following WAY
        #partante['x'][live_a][diffx>180] += 360
        #partante['x'][live_a][diffx<-180] -= 360

        # Build generator for parcel locations of the 5' slices
        gsp = get_slice_part(partante, partpost, live_a, live_p, current_date,
                             dstep, slice_width)
        if verbose: print('built parcel generator for ', current_date)
        """  MAIN LOOP ON THE PARCEL TIME SLICES  """

        for i in range(nb_slices):
            # get the slice for the particles
            datpart = next(gsp)
            # Check x within (-180,180), necessary when GridSat is used
            #if datpart['x'] != []:
            if len(datpart['x']) > 0:
                datpart['x'] = (datpart['x'] + 180) % 360 - 180
            if verbose: print('part slice ', i, datpart['time'])
            # Check whether the present satellite image is valid
            # The while should ensure that the run synchronizes
            # when it starts.
            while check(datsat, datpart['time']) is False:
                # if not get next satellite image
                datsat = next(get_sat)
                datera = next(get_ERA5)
            """ PROCESS THE COMPARISON OF PARCEL PRESSURES TO CLOUDS """
            if len(datpart['x']) > 0:
                # tropopause pressure at the location of the parcels
                #print('ptropo x ',datpart['x'].min(),datpart['x'].max())
                ptrop = datera.fP(np.transpose([datpart['y'], datpart['x']]))
                nhits += convbirth(datpart['itime'],
                    datpart['x'],datpart['y'],datpart['p'],datpart['t'],datpart['idx_back'],\
                    prod0['flag_source'],ptrop,prod0['src']['x'],prod0['src']['y'],\
                    prod0['src']['p'],prod0['src']['t'],prod0['src']['age'],\
                    datsat.var['IR0'], part0['ir_start'],\
                    datsat.geogrid.box_range[0,0],datsat.geogrid.box_range[1,0],datsat.geogrid.stepx,\
                    datsat.geogrid.stepy,datsat.geogrid.box_binx,datsat.geogrid.box_biny)
        """ End of of loop on slices """
        """ INSERT HERE CODE FOR PARCELS ENDING BY AGE
        Important: if the age limit is set in the run, the age_bound needs to
        be decremented by one output step. Otherwise, the parcel disappeared
        and is wrongly processed as crossed."""
        # Check the age limit (easier to do it here)
        if len(partante['idx_back']) > 0:
            age_sec = part0['ir_start'][partante['idx_back'] -
                                        IDX_ORGN] - partante['itime']
            IIold_o = age_sec > (age_bound - (step / 24)) * 86400
            IIold_o = IIold_o & (
                (prod0['flag_source'][partante['idx_back'] - IDX_ORGN]
                 & I_STOP) == 0)
            idx_IIold = partante['idx_back'][IIold_o]
            j_IIold_o = np.where(IIold_o)
            prod0['flag_source'][idx_IIold - IDX_ORGN] = prod0['flag_source'][
                idx_IIold - IDX_ORGN] | I_DEAD + I_OLD
            prod0['src']['x'][idx_IIold - IDX_ORGN] = partante['x'][j_IIold_o]
            prod0['src']['y'][idx_IIold - IDX_ORGN] = partante['y'][j_IIold_o]
            prod0['src']['p'][idx_IIold - IDX_ORGN] = partante['p'][j_IIold_o]
            prod0['src']['t'][idx_IIold - IDX_ORGN] = partante['t'][j_IIold_o]
            prod0['src']['age'][idx_IIold - IDX_ORGN] = (
                (part0['ir_start'][idx_IIold - IDX_ORGN] - partante['itime']) /
                86400)
            print("manage age limit: number of IIold ", len(idx_IIold))
            nold += len(idx_IIold)

        # find parcels still alive after processing this step  if kept_p.sum()==0:
        try:
            nlive = ((prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                      & I_DEAD) == 0).sum()
            n_nohit = ((prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                        & I_HIT) == 0).sum()
        except:
            nlive = 0
            n_nohit = 0

        # check that nlive + nhits + nexits = numpart, should be true after the first day
        if numpart_s != nexits + nhits + nlive + ndborne + nold:
            print('@@@ ACHTUNG numpart_s not equal to sum ', numpart_s,
                  nexits + nhits + nlive + ndborne + nold)

        print('end hour ',
              hour,
              '  numact',
              partpost['nact'],
              ' nexits',
              nexits,
              ' nhits',
              nhits,
              ' nlive',
              nlive,
              ' nohit',
              n_nohit,
              ' nold',
              nold,
              flush=True)

        sys.stdout.flush()

        if backup & (hour % backup_step == 0):
            fl.save(bak_file_prod0, prod0)
            fl.save(
                bak_file_params, {
                    'params': [
                        hour, nhits, nexits, nold, ndborne, nnew, idx1,
                        numpart_s, current_date
                    ]
                })
    """ End of the procedure and storage of the result """
    pid = os.getpid()
    py = psutil.Process(pid)
    memoryUse = py.memory_info()[0] / 2**30
    print('memory use before clean: {:4.2f} gb'.format(memoryUse))
    del partante
    del partpost
    del live_a
    del live_p
    del datpart
    prod0['rvs'] = prod0['rvs'].astype(np.float32)
    for var in ['age', 'p', 't', 'x', 'y']:
        prod0['src'][var] = prod0['src'][var].astype(np.float32)
    pid = os.getpid()
    py = psutil.Process(pid)
    memoryUse = py.memory_info()[0] / 2**30
    print('memory use after clean: {:4.2f} gb'.format(memoryUse))
    #output file
    fl.save(out_file2, prod0, compression='zlib')
    #pickle.dump(prod0,gzip.open(out_file,'wb'))
    # close the print file
    if quiet: fsock.close()
Example #8
0
def main():
    global IDX_ORGN
    parser = argparse.ArgumentParser()
    parser.add_argument("-y", "--year", type=int, help="year")
    parser.add_argument("-m",
                        "--month",
                        type=int,
                        choices=1 + np.arange(12),
                        help="month")
    parser.add_argument("-d",
                        "--day",
                        type=int,
                        choices=1 + np.arange(31),
                        help="day")
    parser.add_argument("-a",
                        "--advect",
                        type=str,
                        choices=["OPZ", "EAD", "EAZ", "EID", "EIZ"],
                        help="source of advecting winds")
    parser.add_argument("-p",
                        "--platform",
                        type=str,
                        choices=["M55", "GLO", "BAL"],
                        help="measurement platform")
    parser.add_argument("-n",
                        "--launch_number",
                        type=int,
                        help="balloon launch number within a day")
    parser.add_argument("-s",
                        "--suffix",
                        type=str,
                        help="suffix for special cases")
    parser.add_argument("-q",
                        "--quiet",
                        type=str,
                        choices=["y", "n"],
                        help="quiet (y) or not (n)")
    parser.add_argument("-t",
                        "--tau",
                        type=int,
                        help="cloud cover time scale (h)")

    # to be updated
    if socket.gethostname() == 'graphium':
        pass
    elif 'ciclad' in socket.gethostname():
        #root_dir = '/home/legras/STC/STC-M55'
        traj_dir = '/data/legras/flexout/STC/M55'
        out_dir = '/data/legras/STC'
    elif ('climserv' in socket.gethostname()) | ('polytechnique'
                                                 in socket.gethostname()):
        #root_dir = '/home/legras/STC/STC-M55'
        traj_dir = '/bdd/STRATOCLIM/flexout/M55'
        out_dir = '/homedata/legras/STC'
    elif socket.gethostname() == 'grapelli':
        pass
    elif socket.gethostname() == 'gort':
        pass
    else:
        print('CANNOT RECOGNIZE HOST - DO NOT RUN ON NON DEFINED HOSTS')
        exit()
    """ Parameters """
    # to do (perhaps) : some parameters might be parsed from command line
    # step and max output time
    step = 6
    hmax = 732
    dstep = timedelta(hours=step)
    # time width of the parcel slice
    slice_width = timedelta(hours=1)
    # number of slices between two outputs
    nb_slices = int(dstep / slice_width)
    # defines here the offset for the cloud-cover
    cc_offset = 0.001
    # defines the domain
    domain = np.array([[-10., 160.], [0., 50.]])

    # default values of parameters
    # date of the flight
    year = 2017
    month = 7
    day = 29
    platform = 'GLO'
    advect = 'EAZ'
    suffix = ''
    launch_number = ''
    quiet = False
    tau = 3
    args = parser.parse_args()
    if args.year is not None:
        year = args.year
    if args.month is not None:
        month = args.month
    if args.day is not None:
        day = args.day
    if args.advect is not None:
        advect = args.advect
    if args.platform is not None:
        platform = args.platform
    if args.launch_number is not None:
        launch_number = '-' + str(args.launch_number)
    if args.suffix is not None:
        suffix = '-' + args.suffix
    if args.quiet is not None:
        if args.quiet == 'y':
            quiet = True
        else:
            quiet = False
    if args.tau is not None:
        tau = args.tau

    # Change tau into its inverse in s**-1
    tauf = '-{:d}H'.format(tau)
    tau = 1 / (3600 * tau)

    # Update the out_dir with the platform
    out_dir = os.path.join(out_dir, 'STC-' + platform + '-CC-OUT')

    fdate = datetime(year, month, day)

    # Manage the file that receives the print output
    if quiet:
        # Output file
        print_file = os.path.join(
            out_dir, 'out', platform + fdate.strftime('-%Y%m%d') +
            launch_number + '-' + advect + '-D01' + suffix + tauf + '.out')
        fsock = open(print_file, 'w')
        sys.stdout = fsock

    # initial time to read the sat files
    # should be after the end of the flight
    # and a 12h or 0h boundary
    print('year', year, 'month', month, 'day', day)
    print('advect', advect)
    print('platform', platform)
    print('launch_number', launch_number)
    print('suffix', suffix)
    print('tau', tau)

    # Directory of the backward trajectories
    ftraj = os.path.join(
        traj_dir, platform + fdate.strftime('-%Y%m%d') + launch_number + '-' +
        advect + '-D01' + suffix)

    # Output file
    out_file = os.path.join(
        out_dir, platform + fdate.strftime('-%Y%m%d') + launch_number + '-' +
        advect + '-D01' + suffix + tauf + '.hdf5')
    """ Initialization of the calculation """
    # Initialize the dictionary of the parcel dictionaries
    partStep = {}

    # Read the index file that contains the initial positions
    part0 = readidx107(os.path.join(ftraj, 'index_old'), quiet=False)
    print('numpart', part0['numpart'])

    # stamp_date not set in these runs
    # current_date actually shifted by one day / sdate
    current_date = fdate + timedelta(days=1)
    # check flag is clean
    print('check flag is clean ',((part0['flag']&I_HIT)!=0).sum(),((part0['flag']&I_DEAD)!=0).sum(),\
                                 ((part0['flag']&I_CROSSED)!=0).sum())

    # check idx_orgn
    if part0['idx_orgn'] != 0:
        print('MINCHIA, IDX_ORGN NOT 0 AS ASSUMED, CORRECTED WITH READ VALUE')
        print('VALUE ', part0['idx_orgn'])
        IDX_ORGN = part0['idx_orgn']

    # Build a dictionary to host the results
    prod0 = defaultdict(dict)
    # Locations of the crossing and detrainement
    nsrc = 6
    prod0['src']['x'] = np.empty(shape=(nsrc, part0['numpart']), dtype='float')
    prod0['src']['y'] = np.empty(shape=(nsrc, part0['numpart']), dtype='float')
    prod0['src']['p'] = np.empty(shape=(nsrc, part0['numpart']), dtype='float')
    prod0['src']['t'] = np.empty(shape=(nsrc, part0['numpart']), dtype='float')
    prod0['src']['age'] = np.empty(shape=(nsrc, part0['numpart']),
                                   dtype='float')
    prod0['src']['x'].fill(np.nan)
    prod0['src']['y'].fill(np.nan)
    prod0['src']['p'].fill(np.nan)
    prod0['src']['t'].fill(np.nan)
    prod0['src']['age'].fill(np.nan)
    # Flag is copied from index
    prod0['flag_source'] = part0['flag']
    # Ininitalize the erosion
    prod0['chi'] = np.empty(part0['numpart'], dtype='float')
    prod0['passed'] = np.empty(part0['numpart'], dtype='int')
    prod0['chi'].fill(1.)
    prod0['passed'].fill(10)

    # Build the interpolator to the hybrid level
    fhyb, void = tohyb()
    #vfhyb = np.vectorize(fhyb)

    # Read the part_000 file
    partStep[0] = readpart107(0, ftraj, quiet=True)

    # number of hists and exits
    nhits = np.array([0, 0, 0, 0, 0, 0])
    nexits = 0
    ndborne = 0
    nnew = 0
    nradada = 0

    # used to get non borne parcels
    new = np.empty(part0['numpart'], dtype='bool')
    new.fill(False)

    print('Initialization completed')
    """ Main loop on the output time steps """
    for hour in range(step, hmax + 1, step):
        pid = os.getpid()
        py = psutil.Process(pid)
        memoryUse = py.memory_info()[0] / 2**30
        print('memory use: {:4.2f} gb'.format(memoryUse))
        # Get rid of dictionary no longer used
        if hour >= 2 * step: del partStep[hour - 2 * step]
        # Read the new data
        partStep[hour] = readpart107(hour, ftraj, quiet=True)
        # Link the names
        partante = partStep[hour - step]
        partpost = partStep[hour]
        if partpost['nact'] > 0:
            print('hour ', hour, '  numact ', partpost['nact'], '  max p ',
                  partpost['p'].max())
        else:
            print('hour ', hour, '  numact ', partpost['nact'])
        # New date valid for partpost
        current_date -= dstep
        """ Select the parcels that are common to the two steps
        ketp_a is a logical field with same length as partante
        kept_p is a logical field with same length as partpost
        After the launch of the earliest parcel along the flight track, there
        should not be any member in new.
        """
        kept_a = np.in1d(partante['idx_back'],
                         partpost['idx_back'],
                         assume_unique=True)
        kept_p = np.in1d(partpost['idx_back'],
                         partante['idx_back'],
                         assume_unique=True)
        #new_p = ~np.in1d(partpost['idx_back'],partpost['idx_back'],assume_unique=True)
        print('kept a, p ', len(kept_a), len(kept_p), kept_a.sum(),
              kept_p.sum(), '  new ',
              len(partpost['x']) - kept_p.sum())
        """ IDENTIFY AND TAKE CARE OF DEADBORNE AS NON BORNE PARCELS """
        # Find and count the parcels in partpost which where not in partante, flag them as new
        if (hour <= 30) & (partpost['nact'] > 0):
            new[partpost['idx_back'][~kept_p] - IDX_ORGN] = True
            nnew += len(partpost['x']) - kept_p.sum()
        # When the release of parcels has ended, flag the ones that did not show up as dead
        # source their last location as the launching location
        if hour == 30:
            ndborne = np.sum(~new)
            prod0['flag_source'][~new] |= I_DBORNE + I_DEAD
            prod0['src']['x'][0, ~new] = part0['x'][~new]
            prod0['src']['y'][0, ~new] = part0['y'][~new]
            prod0['src']['p'][0, ~new] = part0['p'][~new]
            prod0['src']['t'][0, ~new] = part0['t'][~new]
            prod0['src']['age'][0, ~new] = 0
            print('number of dead borne', ndborne, part0['numpart'] - nnew)
            # get rid of new
            del new
        """ INSERT HERE CODE FOR NEW PARCELS """
        # nothing to be done for new parcels, just wait and see
        """ PROCESSING OF CROSSED PARCELS """
        # last known location before crossing stored in the index 0 of src fields
        if len(kept_a) > 0:
            exits = exiter(int((partante['itime']+partpost['itime'])/2), \
                partante['x'][~kept_a],partante['y'][~kept_a],partante['p'][~kept_a],\
                partante['t'][~kept_a],partante['idx_back'][~kept_a],\
                prod0['flag_source'],prod0['src']['x'],prod0['src']['y'],\
                prod0['src']['p'],prod0['src']['t'],prod0['src']['age'],\
                part0['ir_start'], domain)
            nexits += exits
            nhits[0] += exits
            print('exit ', nexits, exits, np.sum(~kept_a),
                  len(kept_a) - len(kept_p))
        """ PROCESSING OF PARCELS WHICH ARE COMMON TO THE TWO OUTPUTS  """
        # Select the kept parcels which have not been hit yet
        # !!! Never use and between two lists, the result is wrong

        if kept_p.sum() == 0:
            live_a = live_p = kept_p
        else:
            live_a = np.logical_and(
                kept_a, (prod0['flag_source'][partante['idx_back'] - IDX_ORGN]
                         & I_DEAD) == 0)
            live_p = np.logical_and(
                kept_p, (prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                         & I_DEAD) == 0)
        print('live a, b ', live_a.sum(), live_p.sum())
        del kept_a
        del kept_p

        # Build generator for live parcel locations of the 1h slices
        gsp = get_slice_part(partante, partpost, live_a, live_p, current_date,
                             dstep, slice_width)
        if verbose: print('built parcel generator for ', current_date)
        """  MAIN LOOP ON THE PARCEL TIME SLICES  """

        for i in range(nb_slices):
            # get the 1h slice for the particles
            datpart = next(gsp)
            # skip if no particles
            if datpart['ti'] == None:
                continue
            print('current_date ', datpart['ti'])
            #@@ test
            #            print('ti in main ',datpart['ti'])
            #            print('pi in main ',np.min(datpart['pi']),np.max(datpart['pi']))
            #            print('idx_back   ',np.min(datpart['idx_back']),np.max(datpart['idx_back']))
            #            #@@ end test
            # as the ECMWF files are alaos available every hour
            datrean = read_ECMWF(datpart['ti'])
            # calculate the -log surface pressure at parcel location at time ti
            # create a 2D linear interpolar from the surface pressure field
            lsp = RegularGridInterpolator((datrean.attr['lats'],datrean.attr['lons']),\
                                          -np.log(datrean.var['SP']))
            # perform the interpolation for the location of live parcels at time ti
            datpart['lspi'] = lsp(np.transpose([datpart['yi'], datpart['xi']]))
            #@@ test
            #            print('surface pressure ',np.exp(-np.min(datpart['lspi'])),np.exp(-np.max(datpart['lspi'])))
            #            print('particle pressure ',np.min(datpart['pi']),np.max(datpart['pi']))
            #@@ end test
            # get the closest hybrid level at time ti
            # define first -log sigma = -log(p) - -log(ps)
            lsig = -np.log(datpart['pi']) - datpart['lspi']
            #@@ test
            #            print('sigma ',np.exp(-np.max(lsig)),np.exp(-np.min(lsig)))
            #@@ end test
            # get the hybrid level, the rank of the first retained level is substracted to have hyb starting from 0
            hyb = np.floor(fhyb(np.transpose([lsig, datpart['lspi']])) +
                           0.5).astype(np.int64) - datrean.attr['levs'][0]
            #@@ test the extreme values of sigma end ps
            if np.min(lsig) < -np.log(0.95):
                print('large sigma detected ', np.exp(-np.min(lsig)))
            if np.max(datpart['lspi']) > -np.log(45000):
                print('small ps detected ', np.exp(-np.max(datpart['lspi'])))
            del lsig
            """ PROCESS THE PARCELS WHICH ARE TOO CLOSE TO GROUND
             These parcels are flagged as crossed and dead, their last location is stored in the
             index 0 of src fields.
             This test handles also the cases outside the interpolation domain as NaN produced by fhyb
             generates very large value of hyb. 
             The trajectories which are stopped here have exited the domain where winds are avilable to flexpart
             and therefore are wrong from this point. For this reason we label them from their last valid position."""
            if np.max(hyb) > 100:
                selec = hyb > 100
                nr = radada(datpart['itime'], datpart['xf'][selec],
                            datpart['yf'][selec], datpart['pf'][selec],
                            datpart['tempf'][selec],
                            datpart['idx_back'][selec], prod0['flag_source'],
                            prod0['src']['x'], prod0['src']['y'],
                            prod0['src']['p'], prod0['src']['t'],
                            prod0['src']['age'], part0['ir_start'])
                nradada += nr
                nhits[0] += nr
            """ PROCESS THE (ADJOINT) DETRAINMENT """
            n1 = detrainer(datpart['itime'],
                datpart['xi'],datpart['yi'],datpart['pi'],datpart['tempi'],hyb,
                datpart['xf'],datpart['yf'], datrean.var['CC'], datpart['idx_back'],\
                prod0['flag_source'],part0['ir_start'], prod0['chi'],prod0['passed'],\
                prod0['src']['x'],prod0['src']['y'],prod0['src']['p'],prod0['src']['t'],prod0['src']['age'],\
                datrean.attr['Lo1'],datrean.attr['La1'],datrean.attr['dlo'],datrean.attr['dla'],cc_offset,tau)
            nhits += n1
            #@@ test
            print('return from detrainer', nhits)
            #@@ end test
            sys.stdout.flush()
        """ End of of loop on slices """
        # find parcels still alive       if kept_p.sum()==0:
        try:
            # number of parcels still alive
            nlive = ((prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                      & I_DEAD) == 0).sum()
            # number of parcels still alive and not hit
            nprist = ((prod0['flag_source'][partpost['idx_back'] - IDX_ORGN] &
                       (I_DEAD + I_HIT)) == 0).sum()
            # number of parcels which have hit and crossed
            nouthit = ((prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                        & I_HIT + I_CROSSED) == I_HIT + I_CROSSED).sum()
            # number of parcels which heve crossed without hit
            noutprist = ((prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                          & I_HIT + I_CROSSED) == I_CROSSED).sum()
            # number of parcels which have hit without crossing
            nhitpure = ((prod0['flag_source'][partpost['idx_back'] - IDX_ORGN]
                         & I_HIT + I_CROSSED) == I_HIT).sum()

        except:
            nlive = 0
            nprist = 0
            nouthit = 0
            noutprist = 0
            nhitpure = 0
            nprist = part0['numpart']

        print('end hour ', hour, '  numact', partpost['nact'], ' nexits',
              nexits, ' nhits', nhits)
        print('nlive', nlive, ' nprist', nprist, ' nouthit', nouthit,
              ' noutprist', noutprist, ' nhitpure', nhitpure)
        # check that nlive + nhits + nexits = numpart, should be true after the first day
        if partpost[
                'nact'] != nprist + nouthit + noutprist + nhitpure + ndborne:
            print('@@@ ACHTUNG numact not equal to sum ', partpost['nact'],
                  nprist + nouthit + noutprist + nhitpure + ndborne)
    """ End of the procedure and storage of the result """
    # clear memory as pickle dump is memory consuming
    del partante
    del partpost
    del partStep
    del datrean
    del datpart
    del hyb
    del live_p
    del live_a
    prod0['chi'] = prod0['chi'].astype(np.float32)
    prod0['passed'] = prod0['passed'].astype(np.int32)
    for var in ['age', 'p', 't', 'x', 'y']:
        prod0['src'][var] = prod0['src'][var].astype(np.float32)
    #output file
    #pickle.dump(prod0,gzip.open(out_file,'wb'))
    dd.io.save(out_file, prod0, compression='zlib')
    # close the print file
    if quiet: fsock.close()
Example #9
0
def main():
    global IDX_ORGN
    parser = argparse.ArgumentParser()
    parser.add_argument("-y","--year",type=int,help="year")
    parser.add_argument("-m","--month",type=int,choices=1+np.arange(12),help="month")
    parser.add_argument("-d","--day",type=int,choices=1+np.arange(31),help="day")
    parser.add_argument("-a","--advect",type=str,choices=["OPZ","EAD","EAZ","EID","EIZ"],help="source of advecting winds")
    parser.add_argument("-p","--platform",type=str,choices=["M55","GLO","BAL"],help="measurement platform")
    parser.add_argument("-n","--launch_number",type=int,help="balloon launch number within a day")
    parser.add_argument("-s","--suffix",type=str,help="suffix for special cases")
    parser.add_argument("-q","--quiet",type=str,choices=["y","n"],help="quiet (y) or not (n)")
    parser.add_argument("-c","--clean0",type=bool,help="clean part_000")

    # to be updated
    if socket.gethostname() == 'graphium':
        pass
    elif 'ciclad' in socket.gethostname():
        #root_dir = '/home/legras/STC/STC-M55'
        main_sat_dir = '/bdd/STRATOCLIM/flexpart_in'
        traj_dir = '/data/legras/flexout/STC/M55'
        out_dir = '/data/legras/STC'
    elif ('climserv' in socket.gethostname()) | ('polytechnique' in socket.gethostname()):
        #root_dir = '/home/legras/STC/STC-M55'
        main_sat_dir = '/bdd/STRATOCLIM/flexpart_in'
        traj_dir = '/bdd/STRATOCLIM/flexout/M55'
        out_dir = '/homedata/legras/STC'
    elif socket.gethostname() == 'grapelli':
        pass
    elif socket.gethostname() == 'gort':
        pass
    else:
         print ('CANNOT RECOGNIZE HOST - DO NOT RUN ON NON DEFINED HOSTS')
         exit()

    """ Parameters """
    # to do (perhaps) : some parameters might be parsed from command line
    # step and max output time
    step = 1
    hmax = 732
    dstep = timedelta (hours=step)
    # time width of the parcel slice
    slice_width = timedelta(minutes=5)
    # dtRange
    dtRange={'MSG1':timedelta(minutes=30),'Hima':timedelta(minutes=20)}
    # number of slices between two outputs
    nb_slices = int(dstep/slice_width)
    # default values of parameters
    # date of the flight
    year=2017
    month=7
    day=27
    platform = 'M55'
    advect = 'OPZ'
    suffix =''
    launch_number=''
    quiet = False
    clean0 = False
    args = parser.parse_args()
    if args.year is not None:
        year=args.year
    if args.month is not None:
        month=args.month
    if args.day is not None:
        day=args.day
    if args.advect is not None:
        advect=args.advect
    if args.platform is not None:
        platform=args.platform
    if args.launch_number is not None:
        launch_number='-'+str(args.launch_number)
    if args.suffix is not None:
        suffix='-'+args.suffix
    if args.quiet is not None:
        if args.quiet=='y':
            quiet=True
        else:
            quiet=False
    if args.clean0 is not None:
        clean0 = args.clean0

    # Update the out_dir with the platform
    out_dir = os.path.join(out_dir,'STC-'+platform+'-OUT')

    fdate = datetime(year,month,day)

    # Manage the file that receives the print output
    if quiet:
        # Output file
        print_file = os.path.join(out_dir,'out',platform+fdate.strftime('-%Y%m%d')+launch_number+'-'+advect+'-D01'+suffix+'.out')
        saveout = sys.stdout
        fsock = open(print_file,'w')
        sys.stdout=fsock

    # initial time to read the sat files
    # should be after the end of the flight
    # and a 12h or 0h boundary
    sdate = fdate + timedelta(days=1)
    print('year',year,'month',month,'day',day)
    print('advect',advect)
    print('platform',platform)
    print('launch_number',launch_number)
    print('suffix',suffix)

    # Directory of the backward trajectories
    ftraj = os.path.join(traj_dir,platform+fdate.strftime('-%Y%m%d')+launch_number+'-'+advect+'-D01'+suffix)

    # Output file
    out_file = os.path.join(out_dir,platform+fdate.strftime('-%Y%m%d')+launch_number+'-'+advect+'-D01'+suffix+'.pkl')
    out_file2 = os.path.join(out_dir,platform+fdate.strftime('-%Y%m%d')+launch_number+'-'+advect+'-D01'+suffix+'.hdf5')

    # Directories for the satellite cloud top files
    satdir ={'MSG1':os.path.join(main_sat_dir,'StratoClim+1kmD_msg1-c'),\
             'Hima':os.path.join(main_sat_dir,'StratoClim+1kmD_himawari-d')}

    """ Initialization of the calculation """
    # Initialize the slice map to be used as a buffer for the cloudtops
    satmap = pixmap()
    satfill = {}
    datsat = {}
    # Initialize the dictionary of the parcel dictionaries
    partStep={}

    # Build the satellite field generator
    get_sat = {'MSG1': read_sat(sdate,dtRange['MSG1'],satdir['MSG1']),\
               'Hima': read_sat(sdate,dtRange['Hima'],satdir['Hima'])}

    # Read the index file that contains the initial positions
    part0 = readidx107(os.path.join(ftraj,'index_old'),quiet=True)
    print('numpart',part0['numpart'])
    # stamp_date not set in these runs
    # current_date actually shifted by one day / sdate
    current_date = fdate + timedelta(days=1)
    # check flag is clean
    print('check flag is clean ',((part0['flag']&I_HIT)!=0).sum(),((part0['flag']&I_DEAD)!=0).sum(),\
                                 ((part0['flag']&I_CROSSED)!=0).sum())
    # check idx_orgn
    if part0['idx_orgn'] != 0:
        print('MINCHIA, IDX_ORGN NOT 0 AS ASSUMED, CORRECTED WITH READ VALUE')
        print('VALUE ',part0['idx_orgn'])
        IDX_ORGN = part0['idx_orgn']

    # Build a dictionary to host the results
    prod0 = defaultdict(dict)
    prod0['src']['x'] = np.empty(part0['numpart'],dtype='float')
    prod0['src']['y'] = np.empty(part0['numpart'],dtype='float')
    prod0['src']['p'] = np.empty(part0['numpart'],dtype='float')
    prod0['src']['t'] = np.empty(part0['numpart'],dtype='float')
    prod0['src']['age'] = np.empty(part0['numpart'],dtype='int')
    prod0['flag_source'] = part0['flag']
    # truncate eventually to 32 bits at the output stage

    # read the part_000 file
    partStep[0] = readpart107(0,ftraj,quiet=True)
    # cleaning is necessary for runs starting in the fake restart mode
    # otherwise all parcels are thought to exit at the first step
    if clean0:
        partStep[0]['idx_back']=[]

    # number of hists and exits
    nhits = 0
    nexits = 0
    ndborne = 0
    nnew = 0

    # used to get non borne parcels
    new = np.empty(part0['numpart'],dtype='bool')
    new.fill(False)

    print('Initialization completed')


    """ Main loop on the output time steps """
    for hour in range(step,hmax+1,step):
        pid = os.getpid()
        py = psutil.Process(pid)
        memoryUse = py.memory_info()[0]/2**30
        print('memory use: {:4.2f} gb'.format(memoryUse))
        # Get rid of dictionary no longer used
        if hour >= 2*step: del partStep[hour-2*step]
        # Read the new data
        partStep[hour] = readpart107(hour,ftraj,quiet=True)
        # Link the names
        partante = partStep[hour-step]
        partpost = partStep[hour]
        if partpost['nact']>0:
            print('hour ',hour,'  numact ', partpost['nact'], '  max p ',partpost['p'].max())
        else:
            print('hour ',hour,'  numact ', partpost['nact'])
        # New date valid for partpost
        current_date -= dstep
        """ Select the parcels that are common to the two steps
        ketp_a is a logical field with same length as partante
        kept_p is a logical field with same length as partpost
        After the launch of the earliest parcel along the flight track, there
        should not be any member in new
        The parcels
        """
        kept_a = np.in1d(partante['idx_back'],partpost['idx_back'],assume_unique=True)
        kept_p = np.in1d(partpost['idx_back'],partante['idx_back'],assume_unique=True)
        #new_p = ~np.in1d(partpost['idx_back'],partpost['idx_back'],assume_unique=True)
        print('kept a, p ',len(kept_a),len(kept_p),kept_a.sum(),kept_p.sum(),'  new ',len(partpost['x'])-kept_p.sum())

        """ IDENTIFY AND TAKE CARE OF DEADBORNE AS NON BORNE PARCELS """
        if (hour <= 30) & (partpost['nact']>0):
            new[partpost['idx_back'][~kept_p]-IDX_ORGN] = True
            nnew += len(partpost['x'])-kept_p.sum()
        if hour == 30:
            ndborne = np.sum(~new)
            prod0['flag_source'][~new] |= I_DBORNE + I_DEAD
            prod0['src']['x'][~new] = part0['x'][~new]
            prod0['src']['y'][~new] = part0['y'][~new]
            prod0['src']['p'][~new] = part0['p'][~new]
            prod0['src']['t'][~new] = part0['t'][~new]
            prod0['src']['age'][~new] = 0
            print('number of dead borne',ndborne,part0['numpart']-nnew)
            del new

        """ INSERT HERE CODE FOR NEW PARCELS """
        # nothing to be done for new parcels, just wait and see

        """ PROCESSING OF CROSSED PARCELS """
        if len(kept_a)>0:
            exits = exiter(int((partante['itime']+partpost['itime'])/2), \
                partante['x'][~kept_a],partante['y'][~kept_a],partante['p'][~kept_a],\
                partante['t'][~kept_a],partante['idx_back'][~kept_a],\
                prod0['flag_source'],prod0['src']['x'],prod0['src']['y'],\
                prod0['src']['p'],prod0['src']['t'],prod0['src']['age'],\
                part0['ir_start'], satmap.range)
            nexits += exits
            print('exit ',nexits, exits, np.sum(~kept_a), len(kept_a) - len(kept_p))

        """ PROCESSING OF PARCELS WHICH ARE COMMON TO THE TWO OUTPUTS  """
        # Select the kept parcels which have not been hit yet
        # !!! Never use and between two lists, the result is wrong

        if kept_p.sum()==0:
            live_a = live_p = kept_p
        else:
            live_a = np.logical_and(kept_a,(prod0['flag_source'][partante['idx_back']-IDX_ORGN] & I_DEAD) == 0)
            live_p = np.logical_and(kept_p,(prod0['flag_source'][partpost['idx_back']-IDX_ORGN] & I_DEAD) == 0)
        print('live a, b ',live_a.sum(),live_p.sum())

        # Build generator for parcel locations of the 5' slices
        gsp = get_slice_part(partante,partpost,live_a,live_p,current_date,dstep,slice_width)
        if verbose: print('built parcel generator for ',current_date)

        """  MAIN LOOP ON THE PARCEL TIME SLICES  """

        for i in range(nb_slices):
            # get the slice for the particles
            datpart = next(gsp)
            if verbose: print('part slice ',i, datpart['time'])
            # Make sure the present satellite slice is OK
            # The while should ensure that the run synchronizes
            # when it starts.
            while satmap.check('MSG1',datpart['time']) is False:
                # if not get next satellite slice
                try:
                    void = next(satfill['MSG1'])
                # read new satellite file if the slice generator is over
                # make a new slice generator and get first slice
                except:
                    datsat['MSG1'] = next(get_sat['MSG1'])
                    satfill['MSG1'] = satmap.fill('MSG1',datsat)
                    void = next(satfill['MSG1'])
                finally:
                    if verbose: print('check MSG1 ',satmap.check('MSG1',datpart['time']),'##',datpart['time'],
                          '##',satmap.zone['MSG1']['ti'],'##',satmap.zone['MSG1']['tf'])
            while satmap.check('Hima',datpart['time']) is False:
                try:
                    void = next(satfill['Hima'])
                except:
                    datsat['Hima'] = next(get_sat['Hima'])
                    satfill['Hima'] = satmap.fill('Hima',datsat)
                    void = next(satfill['Hima'])
                finally:
                    if verbose: print('check Hima ',satmap.check('Hima',datpart['time']),'##',datpart['time'],
                          '##',satmap.zone['Hima']['ti'],'##',satmap.zone['Hima']['tf'])

            """ PROCESS THE COMPARISON OF PARCEL PRESSURES TO CLOUDS """
            if len(datpart['x'])>0:
                nhits += convbirth(datpart['itime'],
                    datpart['x'],datpart['y'],datpart['p'],datpart['t'],datpart['idx_back'],\
                    prod0['flag_source'],prod0['src']['x'],prod0['src']['y'],\
                    prod0['src']['p'],prod0['src']['t'],prod0['src']['age'],\
                    satmap.ptop, part0['ir_start'],\
                    satmap.range[0,0],satmap.range[1,0],satmap.stepx,satmap.stepy,satmap.binx,satmap.biny)

            sys.stdout.flush()

        """ End of of loop on slices """
        # find parcels still alive       if kept_p.sum()==0:
        try:
            nlive = ((prod0['flag_source'][partpost['idx_back']-IDX_ORGN] & I_DEAD) == 0).sum()
            n_nohit = ((prod0['flag_source'][partpost['idx_back']-IDX_ORGN] & I_HIT) == 0).sum()
        except:
            nlive = 0
            n_nohit =0
        print('end hour ',hour,'  numact', partpost['nact'], ' nexits',nexits,' nhits',nhits, ' nlive',nlive,' nohit',n_nohit)
        # check that nlive + nhits + nexits = numpart, should be true after the first day
        if part0['numpart'] != nexits + nhits + nlive + ndborne:
            print('@@@ ACHTUNG numpart not equal to sum ',part0['numpart'],nexits+nhits+nlive+ndborne)

    """ End of the procedure and storage of the result """
    #output file
    dd.io.save(out_file2,prod0,compression='zlib')
    #pickle.dump(prod0,gzip.open(out_file,'wb'))
    # close the print file
    if quiet: fsock.close()