예제 #1
0
def create_outputs(model, realization_dir, runname, formats):
    """
    This functions creates output files based on the model output in the
    current run directory.

    This calls mainly the functions defined below for the different datatypes.

    Parameters
    ----------
    model : Model instance (see model.py)
        The model object holding all the data arrays
    realization_dir : str
        Directory where the current realization results should be stored.
    runname : str
        Name of the model run
    formats : list of str
        List of output formats
    """

    # This dictionary links the output format names from the ini-file with output
    # functions and file endings.
    # If you want to add an output format, add a function and add the description here.
    output_desc = {
        'mat':to_mat,
        'py':to_pickle,
        'npz':to_npz,
        'h5':to_hdf5,
        'vtr':to_vtr,
        'mf':to_modflow,
        'mf6':to_mf6,
        'hgs':to_hgs,
    }

    for fmt in formats:
        if fmt not in output_desc:
            raise ValueError('No such output format: ' + fmt)
        fname = pathlib.Path(realization_dir) / runname
        try:
            output_desc[fmt](model, str(fname.resolve()))
        except ImportError as e:
            # in case of an import error, only warn the user instead of raising
            # an error
            warnings.warn(str(e))
        print_to_stdout('Saved', fmt, 'output to', realization_dir)
예제 #2
0
def parameters(inifile):
    """
    Parses the inifile and returns all sections as dictionaries. Furthermore it
    sets to correct directory names for the run settings.

    Parameters
    ----------
    inifile : path
        Path to inifile. If ``inifile`` is set to 0, the ini-file for the MADE
        test case is used.
    
    Returns
    -------
    run : dict
        HyVR run settings (number of simulations, where to store results)
    model_dict : dict
        model setup parameters
    strata_dict : dict
        strata setup parameters
    hydraulics : dict
        Parsed hydraulics section of the ini-file
    flowtrans : dict
        Flow and transport settings for output
    elements : list of dicts
        List of dictionaries of settings for :class:`hyvr.model.ae_types.AEType`.
    """

    print_to_stdout("Reading parameter file")

    # test case
    if inifile == 0:
        from pkg_resources import resource_filename
        inifile = resource_filename(__name__, str(pathlib.Path('../made.ini')))

    # read file
    p = cp.ConfigParser()
    try:
        p.read(inifile, encoding='utf-8')
    except cp.MissingSectionHeaderError:
        # this is probably caused by a wrong encoding
        p.read(inifile, encoding='utf-8-sig')
    if len(p.sections()) == 0:
        raise FileNotFoundError(
            "Parameter file {:s} not found!".format(inifile))

    old_format = 'dataoutputs' in dict(p['run'])
    if old_format:
        run, model, strata, hydraulics, flowtrans, elements = get_new_parameters_from_deprecated(
            *parse_deprecated_inifile(p))

    else:
        run, model, strata, hydraulics, flowtrans, elements = parse_inifile(p)

    # Runname and Modeldir
    # ====================
    # The following code sets
    # - runname: based on this, the output directory is named
    # - modeldir: the directory where to create the output directory
    # - rundir: the output directory

    # if runname is not given, it's the part of the ini-file before
    # "_parameters.ini" or ".ini"
    # in the test case runname is given
    ini_path = pathlib.Path(inifile).parent.resolve()
    ini_name = pathlib.Path(inifile).name
    if run['runname'] is None:
        if ini_name.endswith("autogenerated_backup.ini"):
            run['runname'] = ini_name[0:-25]
        else:
            run['runname'] = ".".join(ini_name.split('.')[0:-1])

    # separate task: find modeldir

    # modeldir is either the given option or the path of the .ini file
    # rundir is modeldir/runname
    # But:
    # If the .ini-filename ends with "autogenerated_backup.ini", the directory of the
    # ini-file is the rundir and modeldir is the directory above.
    # This should overwrite all other settings (except overwrite_old_output)
    if ini_name.endswith("_autogenerated_backup.ini"):
        run['modeldir'] = ini_path.parent.resolve()
        run['rundir'] = ini_path
    else:
        # either inipath or the chosen modeldir
        if run['modeldir'] is None:
            run['modeldir'] = ini_path
        if run['modeldir'] == 'select':
            directory = input(
                'Please input the model directory save path, or press <enter> to'
                'save in default directory:\n')
            if len(run['modeldir']) == 0:
                run['modeldir'] = inipath
            else:
                run['modeldir'] = pathlib.Path(directory).resolve()
        run['rundir'] = run['modeldir'] / run['runname']

    return run, model, strata, hydraulics, flowtrans, elements
예제 #3
0
파일: model.py 프로젝트: driftingtides/hyvr
    def generate_model(self):
        """
        Generates the model.

        This function generates the full model by creating all strata, the AEs
        within the strata and the geometrical objects within these.

        The method starts from the domain top and calls the strata generation
        function for each stratum, which handles the generation of
        architectural elements.
        """
        hu.print_to_stdout('Generating model...')
        strata_dict = self.strata_dict
        self.strata = []
        self.n_strata = len(strata_dict['strata'])

        # These are the additional parameters that a stratum needs
        param_names = [
            'bg_facies',
            'bg_azim',
            'bg_dip',
            'ae_prob',
            'ae_z_mean',
            'avul',
            'avul_prob',
            'strata',
        ]

        top_surface = ContactSurface(self.grid, mode='flat', z=self.grid.lz)
        for si in range(self.n_strata):
            # create top surface
            if si == self.n_strata - 1:
                bottom_surface = ContactSurface(self.grid, mode='flat', z=self.grid.z0)
            else:
                bottom_surface = ContactSurface(self.grid, **strata_dict['contact_models'][-1-si])
            # if the bottom surface is above the top surface, we will assign
            # the top surface values instead
            bottom_surface.use_lower_surface_value(top_surface)

            stratum_params = {name:strata_dict[name][-1-si] for name in param_names}
            ae_types = [self.ae_types[ae_type] for ae_type in strata_dict['ae_in_strata'][-1-si]]


            stratum = Stratum(bottom_surface,
                              top_surface,
                              stratum_params,
                              ae_types,
                              self.grid,
            )

            self.strata.append(stratum)

            # use old top as bottom
            top_surface = bottom_surface

        # stratum boundaries as arrays
        self.strata_zmins = np.array([stratum.zmin for stratum in self.strata])
        self.strata_zmaxs = np.array([stratum.zmax for stratum in self.strata])

        # enumerate all objects and AEs
        num_ae = 0
        num_ha = 0
        for stratum in self.strata:
            for ae in stratum.aes:
                ae.num = num_ae
                num_ae += 1
                for obj in ae.object_list:
                    obj.num_ha = num_ha
                    num_ha += 1
예제 #4
0
파일: model.py 프로젝트: driftingtides/hyvr
    def generate_hydraulic_parameters(self, hydraulics):
        """
        Generates (heterogeneous) hydraulic parameters

        Parameters
        ----------
        hydraulics : dictionary
            parsed hydraulics section of the ini-file
        """

        hu.print_to_stdout('Generating hydraulic parameters')

        het = self.generate_heterogeneity
        ani = self.generate_anisotropy

        ha_arr = self.data['ha']
        fac = self.data['facies']
        ae_arr = self.data['ae']
        azim = self.data['azim']
        dip = self.data['dip']

        # Initialise storage arrays:
        # hydraulic conductivity
        self.data['k_iso'] = np.zeros((self.grid.nx, self.grid.ny, self.grid.nz),
                                      dtype=np.float)
        # porosity
        self.data['poros' ]= np.zeros((self.grid.nx, self.grid.ny, self.grid.nz),
                                      dtype=np.float)
        # anisotropy ratio
        self.data['anirat' ]= np.ones((self.grid.nx, self.grid.ny, self.grid.nz),
                                      dtype=np.float)

        k_iso = self.data['k_iso']
        poros = self.data['poros']
        anirat = self.data['anirat']

        if het:
            # Heterogeneous case
            for mi in np.unique(ha_arr):
                for fi in np.unique(fac[ha_arr == mi]):
                    mifi = (ha_arr == mi) & (fac == fi)    # Get mask for relevant values

                    if self.heterogeneity_level == 'internal':

                        # Generate internal heterogeneity - hydraulic conductivity
                        k_flat = hu.specsim(self.grid,
                                            hydraulics['sig_y'][fi],
                                            hydraulics['ycorlengths'][fi],
                                            covmod='exp',
                                            selection_mask=mifi)

                        k_flat = np.exp(k_flat) * hydraulics['k_h'][fi]
                        k_iso[mifi] = k_flat

                        # Generate internal heterogeneity - porosity
                        n_flat = hu.specsim(self.grid, 
                                            hydraulics['sig_n'][fi],
                                            hydraulics['ncorlengths'][fi],
                                            covmod='exp',
                                            selection_mask=mifi) + hydraulics['n'][fi]
                        poros[mifi] = n_flat

                    elif self.heterogeneity_level == 'facies':
                        # Assign heterogeneity at facies level only
                        k_iso[mifi] = hydraulics['k_h'][fi]
                        poros[mifi] = hydraulics['n'][fi]

                    # Assign anisotropy ratio
                    anirat[mifi] = hydraulics['k_ratio'][fi]


            """ Assign background heterogeneity per architectural element """
            if self.heterogeneity_level == 'internal':
                # for si, aei in enumerate(ae_lu):
                for stratum in self.strata:
                    for ae in stratum.aes:
                        m0 = ha_arr == 0
                        ms = ae_arr == ae.num
                        ae_mask = m0 & ms     # Get material that equals zero within in architectural element
                        if np.all(ae_mask == False):
                            continue

                        ae_bg_facies = ae.bg_facies

                        # Assign background material
                        k_ae_flat = hu.specsim(self.grid,
                                            hydraulics['sig_y'][ae_bg_facies],
                                            hydraulics['ycorlengths'][ae_bg_facies], 
                                            covmod='exp',
                                            selection_mask=ae_mask)
                        k_ae_flat = np.exp(k_ae_flat) * hydraulics['k_h'][ae_bg_facies]          # back-transform from log space
                        k_iso[ae_mask] = k_ae_flat

                        # Generate internal heterogeneity - porosity
                        n_flat = hu.specsim(self.grid,
                                            hydraulics['sig_n'][ae_bg_facies],
                                            hydraulics['ncorlengths'][ae_bg_facies],
                                            covmod='exp',
                                            selection_mask=ae_mask)
                        n_flat = n_flat + hydraulics['n'][ae_bg_facies]
                        poros[ae_mask] = n_flat

                        # Assign background anisotropy ratio
                        anirat[ae_mask] = hydraulics['k_ratio'][ae_bg_facies]

                        # Assign architectural element trends
                        if ae.type_params['k_ztrend'] is not None:
                            # Vertical trend
                            zf_vec = np.linspace(*ae.type_params['k_ztrend'], self.grid.nz)  # Z factor at each elevation
                            k_iso *= zf_vec
                        if ae.type_params['k_xtrend'] is not None:
                            xf_vec = np.linspace(*ae.type_params['k_xtrend'], self.grid.nx)
                            k_iso = np.transpose(k_iso.transpose() * xf_vec)

                """ Assign trends to hydraulic parameters globally """
                if hydraulics['k_ztrend'] is not None:
                    zf_vec = np.linspace(*hydraulics['k_ztrend'], self.grid.nz)  # Z factor at each elevation
                    k_iso *= zf_vec
                if hydraulics['k_xtrend'] is not None:
                    xf_vec = np.linspace(*hydraulics['k_xtrend'], self.grid.nx)
                    k_iso = np.transpose(k_iso.transpose() * xf_vec)

                if hydraulics['n_ztrend'] is not None:
                    zf_vec = np.linspace(*hydraulics['n_ztrend'], self.grid.nz)  # Z factor at each elevation
                    poros *= zf_vec
                if hydraulics['n_xtrend'] is not None:
                   xf_vec = np.linspace(*hydraulics['n_xtrend'], self.grid.nx)
                   poros = np.transpose(poros.transpose() * xf_vec)

        else:
            # Homogeneous case
            for hy_idx, hyi in enumerate(hydraulics['hydro']):
                hyi = hy_idx
                k_iso[fac == hyi] = hydraulics['k_h'][hy_idx]
                poros[fac == hyi] = hydraulics['n'][hy_idx]
                anirat[fac == hyi] = hydraulics['k_ratio'][hy_idx]

        # Assignment of anisotropy
        self.data['ktensors'] = np.zeros((self.grid.nx, self.grid.ny, self.grid.nz, 3, 3), dtype=np.float)
        ho.set_anisotropic_ktensor(self.data['ktensors'], k_iso, azim*np.pi/180, dip*np.pi/180, anirat)
예제 #5
0
파일: stratum.py 프로젝트: mcl1996/hyvr
    def __init__(self, bottom_surface, top_surface, params, ae_types, grid):
        """
        Stratum constructor. This generates the stratum by generating the AEs
        within.

        Parameters
        ----------
        bottom_surface : ContactSurface object
            The bottom surface of the stratum
        top_surface : ContactSurface object
            The top surface of the stratum
        params : dict
            Dictionary with other necessary parameters
        ae_types : list of AEType objects
            The possible AE types within this stratum
        grid : Grid object
        """
        self.bottom_surface = bottom_surface
        self.top_surface = top_surface
        self.params = params
        self.name = params['strata']
        self.ae_types = ae_types
        self.ae_type_names = [ae_type.name for ae_type in ae_types]
        self.bg_facies = params['bg_facies']
        self.bg_azim = params['bg_azim']
        self.bg_dip = params['bg_dip']

        # generate AEs
        self.zmin = self.bottom_surface.zmin
        self.zmax = self.top_surface.zmax
        self.ae_zmins = []
        self.ae_zmaxs = []
        self.aes = []

        # first top is top of stratum
        ae_top_surface = self.top_surface
        # choose type of first AE
        curr_ae_type = prob_choose(self.ae_types, self.params['ae_prob'])
        znow = self.top_surface.zmean
        while znow > self.bottom_surface.zmean:

            num_type = self.ae_type_names.index(curr_ae_type.name)

            hu.print_to_stdout('Generating', curr_ae_type.name,
                               'from {:2.3f} m'.format(znow))

            # find height of AE
            mean_height = self.params['ae_z_mean'][num_type]
            # TODO: It might be nice to specify the height variance in the inifile
            height = np.random.normal(mean_height, mean_height * 0.1)
            # potential avulsion
            if np.random.uniform() <= self.params['avul_prob'][0]:
                dz = -np.random.uniform(*self.params['avul'])
            else:
                dz = 0
            znow -= height + dz

            # create bottom contact surface
            # -----------------------------
            # the type of the bottom surface is determined by the type of the
            # AE below, if it is not the lowest AE
            next_ae_type = prob_choose(self.ae_types, self.params['ae_prob'])
            if znow <= self.bottom_surface.zmean:
                # limit top surface of uppermost AE to top surface of stratum
                znow = self.bottom_surface.zmean
                ae_bottom_surface = self.bottom_surface
            else:
                bottom_surface_params = next_ae_type.params['contact_model']
                bottom_surface_params['z'] = znow
                ae_bottom_surface = ContactSurface(grid,
                                                   **bottom_surface_params)

            # if the bottom surface is above the top surface, we will assign
            # the (lower) top surface values instead
            use_lower_surface_value(ae_bottom_surface, ae_top_surface)

            # close to the bottom it might also be possible that the ae_bottom
            # is below the stratum bottom if the stratum bottom is not flat
            # in this case we will assign the (higher) bottom surface value instead
            use_higher_surface_value(ae_bottom_surface, self.bottom_surface)

            # generate element: this will place all the objects within an AE
            element = curr_ae_type.generate_realization(
                ae_bottom_surface, ae_top_surface, self, grid)
            self.aes.append(element)

            # if the minimum/maximum depth changed, update it
            ae_zmin = element.zmin
            if ae_zmin < self.zmin:
                self.zmin = ae_zmin
            self.ae_zmins.append(ae_zmin)
            ae_zmax = element.zmax
            if ae_zmax > self.zmax:
                self.zmax = ae_zmax
            self.ae_zmaxs.append(ae_zmax)

            # current bottom is top of next, next ae is new current ae
            ae_top_surface = ae_bottom_surface
            curr_ae_type = next_ae_type

        self.n_ae = len(self.aes)
예제 #6
0
파일: parameters.py 프로젝트: mcl1996/hyvr
def parameters(inifile):
    """
    Get parameters for hierarchical facies modelling from a .ini-file

    Parameters:
        inifile (str):  		Parameter file path

    Returns:
        - run *(dict)* - Model run parameters
        - model *(dict)* - Model domain parameters
        - sequences *(dict)* - Sequence parameters
        - hydraulics *(dict)* - Hydraulic properties parameters
        - flowtrans *(dict)* - Flow & transport simulation parameters
        - elements *(dict)* - Architectural elements and parameters
    """

    print_to_stdout("Reading parameter file")

    # test case
    if inifile == 0:
        from pkg_resources import resource_filename
        inifile = resource_filename(__name__,
                                    os.path.join(os.pardir, 'made.ini'))

    # read file
    p = cp.ConfigParser()
    try:
        p.read(inifile, encoding='utf-8')
    except cp.MissingSectionHeaderError:
        # this is probably caused by a wrong encoding
        p.read(inifile, encoding='utf-8-sig')
    if len(p.sections()) == 0:
        raise FileNotFoundError(
            "Parameter file {:s} not found!".format(inifile))

    old_format = 'dataoutputs' in dict(p['run'])
    if old_format:
        run, model, strata, hydraulics, flowtrans, elements = get_new_parameters_from_deprecated(
            *parse_deprecated_inifile(p))

    else:
        run, model, strata, hydraulics, flowtrans, elements = parse_inifile(p)

    # Runname and Modeldir
    # ====================

    # if runname is not given, it's the part of the ini-file before
    # "_parameters.ini" or ".ini"
    # in the test case runname is given
    ini_path = os.path.dirname(os.path.abspath(inifile))
    ini_name = os.path.basename(os.path.abspath(inifile))
    if run['runname'] is None:
        if ini_name[-25:] == "autogenerated_backup.ini":
            run['runname'] = ini_name[0:-25]
        else:
            run['runname'] = ini_name[0:-4]

    # separate task: find modeldir

    # modeldir is either the given option or the path of the .ini file
    # rundir is modeldir/runname
    # But:
    # If the .ini-filename ends with "autogenerated_backup.ini", the directory of the
    # ini-file is the rundir and modeldir is the directory above.
    # This should overwrite all other settings (except overwrite_old_output)
    if ini_name[-25:] == "_autogenerated_backup.ini":
        run['modeldir'] = os.path.abspath(os.path.join(ini_path, os.pardir))
        run['rundir'] = os.path.abspath(ini_path)
    else:
        # either inipath or the chosen modeldir
        if run['modeldir'] is None:
            run['modeldir'] = ini_path
        if run['modeldir'] == 'select':
            run['modeldir'] = input(
                'Please input the model directory save path, or press <enter> to'
                'save in default directory:\n')
            if len(run['modeldir']) == 0:
                run['modeldir'] = inipath
        run['modeldir'] = os.path.abspath(run['modeldir'])
        run['rundir'] = os.path.join(run['modeldir'], run['runname'])

    return run, model, strata, hydraulics, flowtrans, elements
예제 #7
0
파일: extpar.py 프로젝트: mcl1996/hyvr
def gen_extpar(channel_type, model, ae, count, ani=True):
    """
    Generate extruded parabola geometries:

    * Flow regime is assumed to be reasonably constant so the major geometry of the extruded
      parabolas doesn't change so much
    * 'Migration' of the extruded parabolas according to a shift vector


    Parameters
    ----------
    extpar_type : ExtrudedParabolaType
    model : Model object
    ae : AERealization object
    ae : list
        AE parameters
        TODO: use AERealization instead
    count : int
        Material number and/or identifier
    ani : bool, optional (default: True)
        Whether anisotropy should be generated
        
    Returns
    -------
    count : int
        Material number and/or identifier
    """
    # This is just for convenience
    ae_arr = model.data['ae']
    ha_arr = model.data['ha']
    hat_arr = model.data['hat']
    fac = model.data['fac']
    azim = model.data['azim']
    dip = model.data['dip']

    x2 = model.X[:, :, 0]
    y2 = model.Y[:, :, 0]
    # TODO: replace z3 with model.Z
    _, _, z3 = np.meshgrid(range(0, model.nx),
                           range(0, model.ny),
                           range(0, model.nz),
                           indexing='ij')  # 2-D grid

    # start location
    total_extpar = int(channel_type.channel_no)
    if model.display:
        # Place troughs in domain centre for display features
        xstart = (model.x0 + model.lx) / 2 * np.ones(total_extpar)
        ystart = np.random.uniform(model.y0, model.y0 + model.ly, total_extpar)
    else:
        # Randomly place curve starting points
        # TODO: this was the original setting for xstart, why was this hard-coded?
        # xstart = np.random.uniform(-10, 0, total_extpar)
        xstart = np.random.uniform(model.x0, model.xmax, total_extpar)
        ystart = np.random.uniform(model.y0, model.ymax, total_extpar)

    # loop over extruded parabola top depths
    ch_bot = ae.z_bottom
    ch_bot += channel_type.depth * channel_type.buffer

    ch_top = ae.z_top
    ch_dz = channel_type.agg
    ch_top += ch_dz

    for znow in np.arange(ch_bot, ch_top, ch_dz):
        hu.print_to_stdout('z = {:2.4f}m'.format(znow))
        # Assign linear trend to extruded parabola sizes
        if channel_type.geo_ztrend is not None:
            zfactor = np.interp(
                znow, [model.z0, model.z0 + model.lz],
                [channel_type.geo_ztrend[0], channel_type.geo_ztrend[1]])
        else:
            zfactor = 1
        z_ch_width = channel_type.width * zfactor
        z_ch_depth = channel_type.depth * zfactor

        if channel_type.channel_start is not None:
            cstart = channel_type.channel_start
        else:
            cstart = []
        # Loop over total extruded parabolas per system
        for chan in range(0, total_extpar):
            """ Loop over multiple extruded parabolas at 'timestep' """
            aha = ferguson_curve(model,
                                 channel_type.h,
                                 channel_type.k,
                                 channel_type.ds,
                                 channel_type.eps_factor,
                                 disp=model.display,
                                 ch_start=cstart)
            """ Get flow direction in extruded parabolas for azimuth """
            # For periodicity shift trajectories into model unit cell
            if model.periodic:
                aha[aha[:, 1] < yvec[0], 1] += model.ly
                aha[aha[:, 1] > yvec[-1], 1] -= model.ly

            # initialize 2-D distance matrix
            D = 1e20 * np.ones_like(x2)

            # initialize sum of inverse-distance weights
            sumW = np.zeros(np.shape(x2), dtype=float)
            W = np.zeros(np.shape(x2), dtype=float)

            # initialize velocity orientation at this level
            vx_znow = np.zeros(np.shape(x2), dtype=float)
            vy_znow = np.zeros(np.shape(x2), dtype=float)

            # loop over all points of trajectory
            for ii in range(0, len(aha)):
                # distance to current point
                R = np.sqrt((x2 - aha[ii][0])**2 + (y2 - aha[ii][1])**2)

                # smallest distance of entire grid to all points so far
                D[R < D] = R[R < D]

                # inverse-distance weight for velocity interpolation
                W[:] = 1e-20
                W[R < z_ch_width / 2] = 1 / (R[R < z_ch_width / 2] + 1e-20)

                # velocity interpolation in 2-D
                vx_znow += aha[ii][2] * W
                vy_znow += aha[ii][3] * W
                sumW += W

            vx_znow /= sumW
            vy_znow /= sumW

            # Assign facies sets with dip values
            if sum(channel_type.dip) > 0:
                do, fd, dv, av = hu.dip_sets(
                    model,
                    channel_type,
                    znow,
                    curve=[aha[:, 0], aha[:, 1], vx_znow, vy_znow])
            else:
                do = np.ones((model.nx, model.ny, model.nz)) + count
                fd = np.ones((model.nx, model.ny, model.nz), dtype=int) * int(
                    np.random.choice(channel_type.facies))
                dv = 0.0
                av = np.zeros((model.nx, model.ny, model.nz))
            """ Copy results into 3-D field """
            # Iterate over all nodes below current top elevation
            znow_index = int(np.around(
                (znow - model.z0) / model.dz))  # TODO: fix around stuff
            zbelow_index = int(
                np.around((znow - z_ch_depth - model.z0) / model.dz))
            d_range = np.arange(max(0, zbelow_index),
                                min(model.nz, znow_index))  # Depth range
            if len(
                    d_range
            ) > 0:  # Only compute if extruded parabola depth range is finite

                # Get mask arrays for each condition
                in_extpar = D[:, :, None]**2 <= z_ch_width**2 / 4 - (
                    (znow_index - z3) * model.dz * z_ch_width /
                    (z_ch_depth * 2))**2  # is grid cell in extruded parabola
                finite_v = ~np.isnan(
                    vx_znow)  # Only assign if velocity is finite
                below_top = ae_arr <= ae.num  # Don't assign values to locations higher than top contact surface
                chan_mask = in_extpar * finite_v[:, :, None] * below_top
                if znow_index <= z3[:, :, -1].max():
                    # Set mask above top of extruded parabola to False
                    chan_mask[:, :, znow_index:-1] = False

                # Assign properties
                fac[chan_mask] = fd[chan_mask]
                ha_arr[chan_mask] = count
                hat_arr[chan_mask] = channel_type.ae_id
                ae_arr[chan_mask] = ae.num

                if channel_type.lag is not None:
                    in_lag = (
                        znow - z_ch_depth + float(channel_type.lag[0])
                    ) > z3 * model.dz  # Is grid cell in extruded parabola
                    fac[np.logical_and(in_extpar,
                                       in_lag)] = int(channel_type.lag[1])

                if ani:
                    # calcuate azimuth, to 1 degree
                    azim2d = np.round(
                        (np.arctan2(vx_znow, vy_znow) - np.pi / 2) * 180 /
                        np.pi)
                    azim3d = azim2d[:, :, None] * np.ones(
                        (model.nx, model.ny, model.nz))
                    azim[chan_mask] = azim3d[chan_mask]
                    dip[chan_mask] = dv

                count += 1

        # Shift starting values with migration vector from parameter file
        if channel_type.mig:  # TODO: fix this
            xstart += np.random.uniform(-channel_type.mig[0],
                                        channel_type.mig[0])
            ystart += np.random.uniform(-channel_type.mig[1],
                                        channel_type.mig[1])

    return count