예제 #1
0
def test_get_inset_boundary_heads(tmr, parent_heads):
    """Verify that inset model specified head boundary accurately
    reflects parent model head solution, including when cells
    are dry or missing (e.g. pinched out cells in MF6).
    """
    bheads_df = tmr.get_inset_boundary_heads()
    groups = bheads_df.groupby('per')
    all_kstpkper = parent_heads.get_kstpkper()
    kstpkper_list = [all_kstpkper[0], all_kstpkper[-1]]
    for kstp, kper in kstpkper_list:
        hds = parent_heads.get_data(kstpkper=(kstp, kper))
        df = groups.get_group(kper)
        df['cellid'] = list(zip(df.k, df.i, df.j))
        # check for duplicate locations (esp. corners)
        # in mf2005, duplicate chd heads will be summed
        assert not df.cellid.duplicated().any()

        # x, y, z locations of inset model boundary cells
        ix = tmr.inset.modelgrid.xcellcenters[df.i, df.j]
        iy = tmr.inset.modelgrid.ycellcenters[df.i, df.j]
        iz = tmr.inset.modelgrid.zcellcenters[df.k, df.i, df.j]

        # parent model grid cells associated with inset boundary cells
        i, j = get_ij(tmr.parent.modelgrid, ix, iy)
        k = get_layer(tmr.parent.dis.botm.array, i, j, iz)
def test_upw_setup(pfl_nwt_with_dis, case):

    m = pfl_nwt_with_dis  #deepcopy(pfl_nwt_with_dis)

    if case == 0:
        # test intermediate array creation
        m.cfg['upw']['remake_arrays'] = True
        upw = m.setup_upw()
        arrayfiles = m.cfg['intermediate_data']['hk'] + \
                     m.cfg['intermediate_data']['vka']
        for f in arrayfiles:
            assert os.path.exists(f)
        # check that lakes were set up properly
        hiKlakes_value = {}
        hiKlakes_value['hk'] = float(m.cfg['parent']['hiKlakes_value'])
        hiKlakes_value['sy'] = 1.0
        hiKlakes_value['ss'] = 1.0
        for var in ['hk', 'sy', 'ss']:
            arr = upw.__dict__[var].array
            for k, kvar in enumerate(arr):
                if not np.any(m.isbc[k] == 2):
                    assert kvar.max(axis=(0, 1)) < hiKlakes_value[var]
                else:
                    assert np.diff(kvar[m.isbc[k] == 2]).sum() == 0
                    assert kvar[m.isbc[k] == 2][0] == hiKlakes_value[var]

        # compare values to parent model
        for var in ['hk', 'vka']:
            ix, iy = m.modelgrid.xcellcenters.ravel(
            ), m.modelgrid.ycellcenters.ravel()
            pi, pj = get_ij(m.parent.modelgrid, ix, iy)
            parent_layer = {0: 0, 1: 0, 2: 1, 3: 2, 4: 3}
            for k, pk in parent_layer.items():
                parent_vals = m.parent.upw.__dict__[var].array[pk, pi, pj]
                inset_vals = upw.__dict__[var].array
                parent_max_val = m.parent.upw.__dict__[var].array.max()
                valid_parent = parent_vals != hiKlakes_value.get(var, -9999)
                valid_inset = inset_vals[k].ravel() != hiKlakes_value.get(
                    var, -9999)
                parent_vals = parent_vals[valid_parent & valid_inset]
                inset_vals = inset_vals[k].ravel()[valid_parent & valid_inset]
                assert np.allclose(parent_vals, inset_vals, rtol=0.01)

    elif case == 1:
        # test changing vka to anisotropy
        m.cfg['upw']['layvka'] = [1, 1, 1, 1, 1]
        m.cfg['upw']['vka'] = [10, 10, 10, 10, 10]
        upw = m.setup_upw()
        assert np.array_equal(m.upw.layvka.array, np.array([1, 1, 1, 1, 1]))
        assert np.allclose(m.upw.vka.array.max(axis=(1, 2)),
                           np.array([10, 10, 10, 10, 10]))
예제 #3
0
def compare_inset_parent_values(inset_array,
                                parent_array,
                                inset_modelgrid,
                                parent_modelgrid,
                                inset_parent_layer_mapping=None,
                                nodata=-9999,
                                **kwargs):
    """Compare values on different model grids (for example, parent and inset models that overlap),
    by getting the closes parent cell at each inset cell location.

    todo: compare_inset_parent_values: add interpolation for more precise comparison

    Parameters
    ----------
    inset_array : inset model values (ndarray)
    parent_array : parent model values (ndarray)
    inset_modelgrid : flopy modelgrid for inset model
    parent_modelgrid : flopy modelgrid for parent model
    inset_parent_layer_mapping : dict
        Mapping between inset and parent model layers
        {inset model layer: parent model layer}
    nodata : float
        Exclude these values from comparison
    kwargs :
        kwargs to np.allclose

    Returns
    -------
    AssertionError if np.allclose evaluates to False for any layer

    """
    if len(inset_array.shape) < 3:
        inset_array = np.array([inset_array])
    if inset_parent_layer_mapping is None:
        nlay = inset_array.shape[0]
        inset_parent_layer_mapping = dict(
            zip(list(range(nlay)), list(range(nlay))))
    ix, iy = inset_modelgrid.xcellcenters.ravel(
    ), inset_modelgrid.ycellcenters.ravel()
    pi, pj = get_ij(parent_modelgrid, ix, iy)
    for k, pk in inset_parent_layer_mapping.items():
        parent_vals = parent_array[pk, pi, pj]
        valid = (parent_vals != nodata) & (inset_array[k].ravel() != nodata)
        parent_vals = parent_vals[valid]
        inset_vals = inset_array[k].ravel()[valid]
        assert np.allclose(parent_vals, inset_vals, **kwargs)
예제 #4
0
 def _source_grid_mask(self):
     """Boolean array indicating window in parent model grid (subset of cells)
     that encompass the pfl_nwt model domain. Used to speed up interpolation
     of parent grid values onto pfl_nwt grid."""
     if self._source_mask is None:
         mask = np.zeros(
             (self.parent.modelgrid.nrow, self.parent.modelgrid.ncol),
             dtype=bool)
         if self.inset.parent_mask.shape == self.parent.modelgrid.xcellcenters.shape:
             mask = self.inset.parent_mask
         else:
             x, y = np.squeeze(self.inset.bbox.exterior.coords.xy)
             pi, pj = get_ij(self.parent.modelgrid, x, y)
             pad = 3
             i0, i1 = pi.min() - pad, pi.max() + pad
             j0, j1 = pj.min() - pad, pj.max() + pad
             mask[i0:i1, j0:j1] = True
         self._source_mask = mask
     return self._source_mask
def test_wel_setup(shellmound_model_with_dis):
    m = shellmound_model_with_dis  # deepcopy(model)
    m.cfg['wel']['external_files'] = False
    wel = m.setup_wel()
    wel.write()
    assert os.path.exists(os.path.join(m.model_ws, wel.filename))
    assert isinstance(wel, mf6.ModflowGwfwel)
    assert wel.stress_period_data is not None

    # verify that periodata blocks were written
    output = read_mf6_block(wel.filename, 'period')
    for per, ra in wel.stress_period_data.data.items():
        assert len(output[per + 1]) == len(ra)

    # check the stress_period_data against source data
    sums = [
        ra['q'].sum() if ra is not None else 0
        for ra in wel.stress_period_data.array
    ]
    cellids = set()
    cellids2d = set()
    for per, ra in wel.stress_period_data.data.items():
        cellids.update(set(ra['cellid']))
        cellids2d.update(set([c[1:] for c in ra['cellid']]))

    # sum the rates from the source files
    min_thickness = m.cfg['wel']['source_data']['csvfiles'][
        'vertical_flux_distribution']['minimum_layer_thickness']
    dfs = []
    for f in m.cfg['wel']['source_data']['csvfiles']['filenames']:
        dfs.append(pd.read_csv(f))
    df = pd.concat(dfs)

    # cull wells to within model area
    l, b, r, t = m.modelgrid.bounds
    outside = (df.x.values > r) | (df.x.values < l) | (df.y.values <
                                                       b) | (df.y.values > t)
    df['outside'] = outside
    df = df.loc[~outside]
    df['start_datetime'] = pd.to_datetime(df.start_datetime)
    df['end_datetime'] = pd.to_datetime(df.end_datetime)
    from mfsetup.grid import get_ij
    i, j = get_ij(m.modelgrid, df.x.values, df.y.values)
    df['i'] = i
    df['j'] = j
    thicknesses = get_layer_thicknesses(m.dis.top.array, m.dis.botm.array,
                                        m.idomain)
    b = thicknesses[:, i, j]
    b[np.isnan(b)] = 0
    df['k'] = np.argmax(b, axis=0)
    df['laythick'] = b[df['k'].values, range(b.shape[1])]
    df['idomain'] = m.idomain[df['k'], i, j]
    valid_ij = (df['idomain'] == 1) & (
        df['laythick'] > min_thickness
    )  # nwell array of valid i, j locations (with at least one valid layer)
    culled = df.loc[~valid_ij].copy()  # wells in invalid i, j locations
    df = df.loc[valid_ij].copy()  # remaining wells
    cellids_2d_2 = set(list(zip(df['i'], df['j'])))
    df.index = df.start_datetime
    sums2 = []
    for i, r in m.perioddata.iterrows():
        end_datetime = r.end_datetime - pd.Timedelta(1, unit='d')
        welldata_overlaps_period = (df.start_datetime < end_datetime) & \
                                   (df.end_datetime > r.start_datetime)
        q = df.loc[welldata_overlaps_period, 'flux_m3'].sum()
        sums2.append(q)
    sums = np.array(sums)
    sums2 = np.array(sums2)
    # if this doesn't match
    # may be due to wells with invalid open intervals getting removed
    assert np.allclose(sums, sums2, rtol=0.01)
예제 #6
0
def get_open_interval_thickness(m,
                                heads=None,
                                i=None,
                                j=None,
                                x=None,
                                y=None,
                                screen_top=None,
                                screen_botm=None,
                                nodata=-999):
    """
    Gets the thicknesses of each model layer at specified locations and
    open intervals. If heads are supplied, a saturated thickness is determined
    for each row, column or x, y location; otherwise, total layer thickness is used.
    Returned thicknesses are limited to open intervals (screen_top, screen_botm)
    if included, otherwise the layer tops and bottoms and (optionally) the water table
    are used.

    Parameters
    ----------
    m : mfsetup.MF6model or mfsetup.MFnwtModel instance
        Must have dis, and optionally, attached MFsetupGrid instance
    heads : 2D array OR 3D array (optional)
        numpy array of shape nlay by n locations (2D) OR complete heads array
        of the model for one time (3D).
    i : 1D array-like of ints, of length n locations
        zero-based row indices (optional; alternately specify x, y)
    j : 1D array-like of ints, of length n locations
        zero-based column indices (optional; alternately specify x, y)
    x : 1D array-like of floats, of length n locations
        x locations in real world coordinates (optional)
    y : 1D array-like of floats, of length n locations
        y locations in real world coordinates (optional)
    screen_top : 1D array-like of floats, of length n locations
        open interval tops (optional; default is model top)
    screen_botm : 1D array-like of floats, of length n locations
        open interval bottoms (optional; default is model bottom)
    nodata : numeric
        optional; locations where heads=nodata will be assigned T=0

    Returns
    -------
    T : 2D array of same shape as heads (nlay x n locations)
        Transmissivities in each layer at each location

    """
    if i is not None and j is not None:
        pass
    elif x is not None and y is not None:
        # get row, col for observation locations
        i, j = get_ij(m.modelgrid, x, y)
    else:
        raise ValueError('Must specify row, column or x, y locations.')

    botm = m.dis.botm.array[:, i, j]

    if heads is None:
        # use model top elevations; expand to nlay x n locations
        heads = np.repeat(m.dis.top.array[np.newaxis, i, j], m.nlay, axis=0)
    if heads.shape == (m.nlay, m.nrow, m.ncol):
        heads = heads[:, i, j]

    msg = 'Shape of heads array must be nlay x n locations'
    assert heads.shape == botm.shape, msg

    # set open interval tops/bottoms to model top/bottom if None
    if screen_top is None:
        screen_top = m.dis.top.array[i, j]
    if screen_botm is None:
        screen_botm = m.dis.botm.array[-1, i, j]

    # make an array of layer tops
    tops = np.empty_like(botm, dtype=float)
    tops[0, :] = m.dis.top.array[i, j]
    tops[1:, :] = botm[:-1]

    # expand top and bottom arrays to be same shape as botm, thickness, etc.
    # (so we have an open interval value for each layer)
    sctoparr = np.zeros(botm.shape)
    sctoparr[:] = screen_top
    scbotarr = np.zeros(botm.shape)
    scbotarr[:] = screen_botm

    # start with layer tops
    # set tops above heads to heads
    # set tops above screen top to screen top
    # (we only care about the saturated open interval)
    openinvtop = tops.copy()
    openinvtop[openinvtop > heads] = heads[openinvtop > heads]
    openinvtop[openinvtop > sctoparr] = sctoparr[openinvtop > screen_top]

    # start with layer bottoms
    # set bottoms below screened interval to screened interval bottom
    # set screen bottoms below bottoms to layer bottoms
    openinvbotm = botm.copy()
    openinvbotm[openinvbotm < scbotarr] = scbotarr[openinvbotm < screen_botm]
    openinvbotm[scbotarr < botm] = botm[scbotarr < botm]

    # compute thickness of open interval in each layer
    thick = openinvtop - openinvbotm

    # assign open intervals above or below model to closest cell in column
    not_in_layer = np.sum(thick < 0, axis=0)
    not_in_any_layer = not_in_layer == thick.shape[0]
    for i, n in enumerate(not_in_any_layer):
        if n:
            closest = np.argmax(thick[:, i])
            thick[closest, i] = 1.
    thick[thick < 0] = 0
    thick[heads == nodata] = 0  # exclude nodata cells
    return thick
예제 #7
0
def setup_wel_data(model, for_external_files=True):
    """Performs the part of well package setup that is independent of
    MODFLOW version. Returns a DataFrame with the information
    needed to set up stress_period_data.
    """
    # default options for distributing fluxes vertically
    vfd_defaults = {
        'across_layers':
        False,
        'distribute_by':
        'thickness',
        'screen_top_col':
        'screen_top',
        'screen_botm_col':
        'screen_botm',
        'minimum_layer_thickness':
        model.cfg['wel'].get('minimum_layer_thickness', 2.)
    }

    # master dataframe for stress period data
    columns = ['per', 'k', 'i', 'j', 'q', 'boundname']
    df = pd.DataFrame(columns=columns)

    # check for source data
    datasets = model.cfg['wel'].get('source_data')

    # delete the dropped wells file if it exists, to avoid confusion
    dropped_wells_file = model.cfg['wel']['output_files'][
        'dropped_wells_file'].format(model.name)
    if os.path.exists(dropped_wells_file):
        os.remove(dropped_wells_file)

    # get well package input from source (parent) model in lieu of source data
    # todo: fetching correct well package from mf6 parent model
    if datasets is None and model.cfg['parent'].get('default_source_data') \
        and hasattr(model.parent, 'wel'):

        # get well stress period data from mfnwt or mf6 model
        parent = model.parent
        spd = get_package_stress_period_data(parent, package_name='wel')
        # map the parent stress period data to inset stress periods
        periods = spd.groupby('per')
        dfs = []
        for inset_per, parent_per in model.parent_stress_periods.items():
            if parent_per in periods.groups:
                period = periods.get_group(parent_per)
                if len(dfs) > 0 and period.drop('per', axis=1).equals(
                        dfs[-1].drop('per', axis=1)):
                    continue
                else:
                    dfs.append(period)
        spd = pd.concat(dfs)

        parent_well_i = spd.i.copy()
        parent_well_j = spd.j.copy()
        parent_well_k = spd.k.copy()

        # set boundnames based on well locations in parent model
        parent_name = parent.name
        spd['boundname'] = [
            '{}_({},{},{})'.format(parent_name, pk, pi, pj)
            for pk, pi, pj in zip(parent_well_k, parent_well_i, parent_well_j)
        ]

        parent_well_x = parent.modelgrid.xcellcenters[parent_well_i,
                                                      parent_well_j]
        parent_well_y = parent.modelgrid.ycellcenters[parent_well_i,
                                                      parent_well_j]
        coords = project((parent_well_x, parent_well_y),
                         model.modelgrid.proj_str, parent.modelgrid.proj_str)
        geoms = [Point(x, y) for x, y in zip(*coords)]
        bounds = model.modelgrid.bbox
        within = [g.within(bounds) for g in geoms]
        i, j = get_ij(model.modelgrid, parent_well_x[within],
                      parent_well_y[within])
        spd = spd.loc[within].copy()
        spd['i'] = i
        spd['j'] = j
        df = df.append(spd)

    # read source data and map onto model space and time discretization
    # multiple types of source data can be submitted
    elif datasets is not None:
        for k, v in datasets.items():

            # determine the format
            if 'csvfile' in k.lower():  # generic csv
                #  read csv file and aggregate flow rates to model stress periods
                #  sum well fluxes co-located in a cell
                sd = TransientTabularSourceData.from_config(
                    v, resolve_duplicates_with='sum', dest_model=model)
                csvdata = sd.get_data()
                csvdata.rename(columns={
                    v['data_column']: 'q',
                    v['id_column']: 'boundname'
                },
                               inplace=True)
                if 'k' not in csvdata.columns:
                    if model.nlay > 1:
                        vfd = vfd_defaults.copy()
                        vfd.update(v.get('vertical_flux_distribution', {}))
                        csvdata = assign_layers_from_screen_top_botm(
                            csvdata, model, **vfd)
                    else:
                        csvdata['k'] = 0
                df = df.append(csvdata[columns])

            elif k.lower() == 'wells':  # generic dict
                added_wells = {k: v for k, v in v.items() if v is not None}
                if len(added_wells) > 0:
                    aw = pd.DataFrame(added_wells).T
                    aw['boundname'] = aw.index
                else:
                    aw = None
                if aw is not None:
                    if 'x' in aw.columns and 'y' in aw.columns:
                        aw['i'], aw['j'] = get_ij(model.modelgrid,
                                                  aw['x'].values,
                                                  aw['y'].values)
                    aw['per'] = aw['per'].astype(int)
                    aw['k'] = aw['k'].astype(int)
                    df = df.append(aw)

            elif k.lower() == 'wdnr_dataset':  # custom input format for WI DNR
                # Get steady-state pumping rates
                check_source_files([v['water_use'], v['water_use_points']])

                # fill out period stats
                period_stats = v['period_stats']
                if isinstance(period_stats, str):
                    period_stats = {
                        kper: period_stats
                        for kper in range(model.nper)
                    }

                # separate out stress periods with period mean statistics vs.
                # those to be resampled based on start/end dates
                resampled_periods = {
                    k: v
                    for k, v in period_stats.items() if v == 'resample'
                }
                periods_with_dataset_means = {
                    k: v
                    for k, v in period_stats.items()
                    if k not in resampled_periods
                }

                if len(periods_with_dataset_means) > 0:
                    wu_means = get_mean_pumping_rates(
                        v['water_use'],
                        v['water_use_points'],
                        period_stats=periods_with_dataset_means,
                        drop_ids=v.get('drop_ids'),
                        model=model)
                    df = df.append(wu_means)
                if len(resampled_periods) > 0:
                    wu_resampled = resample_pumping_rates(
                        v['water_use'],
                        v['water_use_points'],
                        drop_ids=v.get('drop_ids'),
                        exclude_steady_state=True,
                        model=model)
                    df = df.append(wu_resampled)

    # boundary fluxes from parent model
    if model.perimeter_bc_type == 'flux':
        assert model.parent is not None, "need parent model for TMR cut"

        # boundary fluxes
        kstpkper = [(0, 0)]
        tmr = Tmr(model.parent, model)

        # parent periods to copy over
        kstpkper = [(0, per)
                    for per in model.cfg['model']['parent_stress_periods']]
        bfluxes = tmr.get_inset_boundary_fluxes(kstpkper=kstpkper)
        bfluxes['boundname'] = 'boundary_flux'
        df = df.append(bfluxes)

    for col in ['per', 'k', 'i', 'j']:
        df[col] = df[col].astype(int)

    # drop any k, i, j locations that are inactive
    if model.version == 'mf6':
        inactive = model.dis.idomain.array[df.k.values, df.i.values,
                                           df.j.values] != 1
    else:
        inactive = model.bas6.ibound.array[df.k.values, df.i.values,
                                           df.j.values] != 1

    # record dropped wells in csv file
    # (which might contain wells dropped by other routines)
    if np.any(inactive):
        #inactive_i, inactive_j = df.loc[inactive, 'i'].values, df.loc[inactive, 'j'].values
        dropped = df.loc[inactive].copy()
        dropped = dropped.groupby(['k', 'i', 'j']).first().reset_index()
        dropped['reason'] = 'in inactive cell'
        dropped['routine'] = __name__ + '.setup_wel_data'
        append_csv(dropped_wells_file, dropped, index=False,
                   float_format='%g')  # append to existing file if it exists
    df = df.loc[~inactive].copy()

    copy_fluxes_to_subsequent_periods = False
    if copy_fluxes_to_subsequent_periods and len(df) > 0:
        df = copy_fluxes_to_subsequent_periods(df)

    wel_lookup_file = model.cfg['wel']['output_files']['lookup_file'].format(
        model.name)
    wel_lookup_file = os.path.join(model._tables_path,
                                   os.path.split(wel_lookup_file)[1])
    model.cfg['wel']['output_files']['lookup_file'] = wel_lookup_file

    # verify that all wells have a boundname
    if df.boundname.isna().any():
        no_name = df.boundname.isna()
        k, i, j = df.loc[no_name, ['k', 'i', 'j']].T.values
        names = ['({},{},{})'.format(k, i, j) for k, i, j in zip(k, i, j)]
        df.loc[no_name, 'boundname'] = names
    assert not df.boundname.isna().any()

    # save a lookup file with well site numbers/categories
    df.sort_values(by=['boundname', 'per'], inplace=True)
    df[['per', 'k', 'i', 'j', 'q', 'boundname']].to_csv(wel_lookup_file,
                                                        index=False)

    # convert to one-based and comment out header if df will be written straight to external file
    if for_external_files:
        df.rename(columns={'k': '#k'}, inplace=True)
        df['#k'] += 1
        df['i'] += 1
        df['j'] += 1
    return df
예제 #8
0
def read_wdnr_monthly_water_use(wu_file, wu_points, model,
                                active_area=None,
                                drop_ids=None,
                                minimum_layer_thickness=2
                                ):
    """Read water use data from a master file generated from
    WDNR_wu_data.ipynb. Cull data to area of model. Reshape
    to one month-year-site value per row.

    Parameters
    ----------
    wu_file : csv file
        Water use data ouput from the WDNR_wu_data.ipynb.
    wu_points : point shapefile
        Water use locations, generated in the WDNR_wu_data.ipynb
        Must be in same CRS as sr.
    model : flopy.modflow.Modflow instance
        Must have a valid attached .sr attribute defining the model grid.
        Only wells within the bounds of the sr will be retained.
        Sr is also used for row/column lookup.
        Must be in same CRS as wu_points.
    active_area : str (shapefile path) or shapely.geometry.Polygon
        Polygon denoting active area of the model. If specified,
        wells are culled to this area instead of the model bounding box.
        (default None)
    minimum_layer_thickness : scalar
        Minimum layer thickness to have pumping.

    Returns
    -------
    monthly_data : DataFrame

    """
    col_fmt = '{}_wdrl_gpm_amt'
    data_renames = {'site_seq_no': 'site_no',
                    'wdrl_year': 'year'}
    df = pd.read_csv(wu_file)
    drop_cols = [c for c in df.columns if 'unnamed' in c.lower()]
    drop_cols += ['objectid']
    df.drop(drop_cols, axis=1, inplace=True, errors='ignore')
    df.rename(columns=data_renames, inplace=True)
    if drop_ids is not None:
        df = df.loc[~df.site_no.isin(drop_ids)].copy()

    # implement automatic reprojection in gis-utils
    # maintaining backwards compatibility
    kwargs = {'dest_crs': model.modelgrid.crs}
    kwargs = get_input_arguments(kwargs, shp2df)
    locs = shp2df(wu_points, **kwargs)
    site_seq_col = [c for c in locs if 'site_se' in c.lower()]
    locs_renames = {c: 'site_no' for c in site_seq_col}
    locs.rename(columns=locs_renames, inplace=True)
    if drop_ids is not None:
        locs = locs.loc[~locs.site_no.isin(drop_ids)].copy()

    if active_area is None:
        # cull the data to the model bounds
        features = model.modelgrid.bbox
        txt = "No wells are inside the model bounds of {}"\
            .format(model.modelgrid.extent)
    elif isinstance(active_area, str):
        # implement automatic reprojection in gis-utils
        # maintaining backwards compatibility
        kwargs = {'dest_crs': model.modelgrid.crs}
        kwargs = get_input_arguments(kwargs, shp2df)
        features = shp2df(active_area, **kwargs).geometry.tolist()
        if len(features) > 1:
            features = MultiPolygon(features)
        else:
            features = Polygon(features[0])
        txt = "No wells are inside the area of {}"\
            .format(active_area)
    elif isinstance(active_area, Polygon):
        features = active_area

    within = [g.within(features) for g in locs.geometry]
    assert len(within) > 0, txt
    locs = locs.loc[within].copy()
    if len(locs) == 0:
        print('No wells within model area:\n{}\n{}'.format(wu_file, wu_points))
        return None, None
    df = df.loc[df.site_no.isin(locs.site_no)]
    df.sort_values(by=['site_no', 'year'], inplace=True)

    # create seperate dataframe with well info
    well_info = df[['site_no',
                    'well_radius_mm',
                    'borehole_radius_mm',
                    'well_depth_m',
                    'elev_open_int_top_m',
                    'elev_open_int_bot_m',
                    'screen_length_m',
                    'screen_midpoint_elev_m']].copy()
    # groupby site number to cull duplicate information
    well_info = well_info.groupby('site_no').first()
    well_info['site_no'] = well_info.index

    # add top elevation, screen midpoint elev, row, column and layer
    points = dict(zip(locs['site_no'], locs.geometry))
    well_info['x'] = [points[sn].x for sn in well_info.site_no]
    well_info['y'] = [points[sn].y for sn in well_info.site_no]

    # have to do a loop because modelgrid.rasterize currently only works with scalars
    print('intersecting wells with model grid...')
    t0 = time.time()
    #i, j = [], []
    #for x, y in zip(well_info.x.values, well_info.y.values):
    #    iy, jx = model.modelgrid.rasterize(x, y)
    #    i.append(iy)
    #    j.append(jx)
    i, j = get_ij(model.modelgrid, well_info.x.values, well_info.y.values)
    print("took {:.2f}s\n".format(time.time() - t0))

    top = model.dis.top.array
    botm = model.dis.botm.array
    thickness = get_layer_thicknesses(top, botm)
    well_info['i'] = i
    well_info['j'] = j
    well_info['elv_m'] = top[i, j]
    well_info['elv_top_m'] = well_info.elev_open_int_top_m
    well_info['elv_botm_m'] = well_info.elev_open_int_bot_m
    well_info['elv_mdpt_m'] = well_info.screen_midpoint_elev_m
    well_info['k'] = get_layer(botm, i, j, elev=well_info['elv_mdpt_m'].values)
    well_info['laythick'] = thickness[well_info.k.values, i, j]
    well_info['ktop'] = get_layer(botm, i, j, elev=well_info['elv_top_m'].values)
    well_info['kbotm'] = get_layer(botm, i, j, elev=well_info['elv_botm_m'].values)

    # for wells in a layer below minimum thickness
    # move to layer with screen top, then screen botm,
    # put remainder in layer 1 and hope for the best
    well_info = wells.assign_layers_from_screen_top_botm(well_info, model,
                                       flux_col='q',
                                       screen_top_col='elv_top_m',
                                       screen_botm_col='elv_botm_m',
                                       across_layers=False,
                                       distribute_by='transmissivity',
                                       minimum_layer_thickness=2.)
    #isthin = well_info.laythick < minimum_layer_thickness
    #well_info.loc[isthin, 'k'] = well_info.loc[isthin, 'ktop'].values
    #well_info.loc[isthin, 'laythick'] = model.dis.thickness.array[well_info.k[isthin].values,
    #                                                              well_info.i[isthin].values,
    #                                                              well_info.j[isthin].values]
    #isthin = well_info.laythick < minimum_layer_thickness
    #well_info.loc[isthin, 'k'] = well_info.loc[isthin, 'kbotm'].values
    #well_info.loc[isthin, 'laythick'] = model.dis.thickness.array[well_info.k[isthin].values,
    #                                                              well_info.i[isthin].values,
    #                                                              well_info.j[isthin].values]
    #isthin = well_info.laythick < minimum_layer_thickness
    #well_info.loc[isthin, 'k'] = 1
    #well_info.loc[isthin, 'laythick'] = model.dis.thickness.array[well_info.k[isthin].values,
    #                                                              well_info.i[isthin].values,
    #                                                              well_info.j[isthin].values]
    isthin = well_info.laythick < minimum_layer_thickness
    assert not np.any(isthin)

    # make a datetime column
    monthlyQ_cols = [col_fmt.format(calendar.month_abbr[i]).lower()
                     for i in range(1, 13)]
    monthly_data = df[['site_no', 'year'] + monthlyQ_cols]
    monthly_data.columns = ['site_no', 'year'] + np.arange(1, 13).tolist()

    # stack the data
    # so that each row is a site number, year, month
    # reset the index to move multi-index levels back out to columns
    stacked = monthly_data.set_index(['site_no', 'year']).stack().reset_index()
    stacked.columns = ['site_no', 'year', 'month', 'gallons']
    stacked['datetime'] = pd.to_datetime(['{}-{:02d}'.format(y, m)
                                          for y, m in zip(stacked.year, stacked.month)])
    monthly_data = stacked
    return well_info, monthly_data
예제 #9
0
    def __init__(
        self,
        parent_model,
        inset_model,
        parent_head_file=None,
        parent_cell_budget_file=None,
        parent_length_units=None,
        inset_length_units=None,
        inset_parent_layer_mapping=None,
        inset_parent_period_mapping=None,
    ):

        self.inset = inset_model
        self.parent = parent_model
        self.inset._set_parent_modelgrid()
        self.cbc = None
        self._inset_parent_layer_mapping = inset_parent_layer_mapping
        self._source_mask = None
        self._inset_parent_period_mapping = inset_parent_period_mapping
        self.hpth = None  # path to parent heads output file
        self.cpth = None  # path to parent cell budget output file

        self.pi0 = None
        self.pj0 = None
        self.pi1 = None
        self.pj1 = None
        self.pi_list = None
        self.pj_list = None

        if parent_length_units is None:
            parent_length_units = self.inset.cfg['parent']['length_units']
        if inset_length_units is None:
            inset_length_units = self.inset.length_units
        self.length_unit_conversion = convert_length_units(
            parent_length_units, inset_length_units)

        if parent_head_file is None:
            parent_head_file = os.path.join(self.parent.model_ws,
                                            '{}.hds'.format(self.parent.name))
            if os.path.exists(parent_head_file):
                self.hpth = parent_cell_budget_file
        else:
            self.hpth = parent_head_file
        if parent_cell_budget_file is None:
            for extension in 'cbc', 'cbb':
                parent_cell_budget_file = os.path.join(
                    self.parent.model_ws,
                    '{}.{}'.format(self.parent.name, extension))
                if os.path.exists(parent_cell_budget_file):
                    self.cpth = parent_cell_budget_file
                    break
        else:
            self.cpth = parent_cell_budget_file

        if self.hpth is None and self.cpth is None:
            raise ValueError(
                "No head or cell budget output files found for parent model {}"
                .format(self.parent.name))

        # get bounding cells in parent model for pfl_nwt model
        irregular_domain = False

        # see if irregular domain
        irregbound_cfg = self.inset.cfg['perimeter_boundary'].get(
            'source_data', {}).get('irregular_boundary')
        if irregbound_cfg is not None:
            irregular_domain = True
            irregbound_cfg['variable'] = 'perimeter_boundary'
            irregbound_cfg['dest_model'] = self.inset

            sd = ArraySourceData.from_config(irregbound_cfg)
            data = sd.get_data()
            idm_outline = data[0]
            connections = get_horizontal_connections(idm_outline,
                                                     connection_info=False,
                                                     layer_elevations=1,
                                                     delr=1,
                                                     delc=1,
                                                     inside=True)
            self.pi_list, self.pj_list = connections.i.to_list(
            ), connections.j.to_list()
        # otherwise just get the corners of the inset if rectangular domain
        else:
            self.pi0, self.pj0 = get_ij(
                self.parent.modelgrid, self.inset.modelgrid.xcellcenters[0, 0],
                self.inset.modelgrid.ycellcenters[0, 0])
            self.pi1, self.pj1 = get_ij(
                self.parent.modelgrid, self.inset.modelgrid.xcellcenters[-1,
                                                                         -1],
                self.inset.modelgrid.ycellcenters[-1, -1])
            self.parent_nrow_in_inset = self.pi1 - self.pi0 + 1
            self.parent_ncol_in_inset = self.pj1 - self.pj0 + 1

        # check for an even number of pfl_nwt cells per parent cell in x and y directions
        x_refinment = self.parent.modelgrid.delr[
            0] / self.inset.modelgrid.delr[0]
        y_refinment = self.parent.modelgrid.delc[
            0] / self.inset.modelgrid.delc[0]
        assert int(
            x_refinment
        ) == x_refinment, "pfl_nwt delr must be factor of parent delr"
        assert int(
            y_refinment
        ) == y_refinment, "pfl_nwt delc must be factor of parent delc"
        assert x_refinment == y_refinment, "grid must have same x and y discretization"
        self.refinement = int(x_refinment)
예제 #10
0
        # check that lakes were set up properly
        if not simulate_high_k_lakes:
            assert not np.any(m._isbc2d == 2)
            assert upw.hk.array.max() < m.cfg['high_k_lakes']['high_k_value']
            assert upw.sy.array.min() < m.cfg['high_k_lakes']['sy']
            assert upw.ss.array.min() > m.cfg['high_k_lakes']['ss']
        else:
            assert np.any(m._isbc2d == 2)
            assert upw.hk.array.max() == m.cfg['high_k_lakes']['high_k_value']
            assert upw.sy.array.max() == m.cfg['high_k_lakes']['sy']
            assert np.allclose(upw.ss.array.min(), m.cfg['high_k_lakes']['ss'])

        # compare values to parent model
        for var in ['hk', 'vka']:
            ix, iy = m.modelgrid.xcellcenters.ravel(), m.modelgrid.ycellcenters.ravel()
            pi, pj = get_ij(m.parent.modelgrid, ix, iy)
            parent_layer = {0: 0, 1: 0, 2: 1, 3: 2, 4: 3}
            for k, pk in parent_layer.items():
                parent_vals = m.parent.upw.__dict__[var].array[pk, pi, pj]
                inset_vals = upw.__dict__[var].array
                valid_parent = parent_vals != m.cfg['high_k_lakes'].get('high_k_value', -9999)
                valid_inset = inset_vals[k].ravel() != m.cfg['high_k_lakes'].get('high_k_value', -9999)
                parent_vals = parent_vals[valid_parent & valid_inset]
                inset_vals = inset_vals[k].ravel()[valid_parent & valid_inset]
                assert np.allclose(parent_vals, inset_vals, rtol=0.01)

    elif case == 1:
        # test changing vka to anisotropy
        m.cfg['upw']['layvka'] = [1, 1, 1, 1, 1]
        m.cfg['upw']['vka'] = [10, 10, 10, 10, 10]
        upw = m.setup_upw()
예제 #11
0
def setup_head_observations(model,
                            obs_info_files=None,
                            format='hyd',
                            obsname_column='obsname'):

    self = model
    package = format
    source_data_config = self.cfg[package]['source_data']

    # set a 14 character obsname limit for the hydmod package
    # https://water.usgs.gov/ogw/modflow-nwt/MODFLOW-NWT-Guide/index.html?hyd.htm
    # 40 character limit for MODFLOW-6 (see IO doc)
    obsname_character_limit = 40
    if format == 'hyd':
        obsname_character_limit = 14

    # TODO: read head observation data using TabularSourceData instead
    if obs_info_files is None:
        for key in 'filename', 'filenames':
            if key in source_data_config:
                obs_info_files = source_data_config[key]
        if obs_info_files is None:
            print("No data for the Observation (OBS) utility.")
            return

    # get obs_info_files into dictionary format
    # filename: dict of column names mappings
    if isinstance(obs_info_files, str):
        obs_info_files = [obs_info_files]
    if isinstance(obs_info_files, list):
        obs_info_files = {
            f: self.cfg[package]['default_columns']
            for f in obs_info_files
        }
    elif isinstance(obs_info_files, dict):
        for k, v in obs_info_files.items():
            if v is None:
                obs_info_files[k] = self.cfg[package]['default_columns']

    check_source_files(obs_info_files.keys())
    # dictionaries mapping from obstypes to hydmod input
    pckg = {
        'LK':
        'BAS',  # head package for high-K lakes; lake package lakes get dropped
        'GW': 'BAS',
        'head': 'BAS',
        'lake': 'BAS',
        'ST': 'SFR',
        'flux': 'SFR'
    }
    arr = {
        'LK':
        'HD',  # head package for high-K lakes; lake package lakes get dropped
        'GW': 'HD',
        'ST': 'SO',
        'flux': 'SO'
    }
    print('Reading observation files...')
    dfs = []
    for f, column_info in obs_info_files.items():
        print(f)
        column_mappings = self.cfg[package]['source_data'].get(
            'column_mappings')
        df = read_observation_data(f,
                                   column_info,
                                   column_mappings=column_mappings)
        if 'obs_type' in df.columns and 'pckg' not in df.columns:
            df['pckg'] = [pckg.get(s, 'BAS') for s in df['obs_type']]
        elif 'pckg' not in df.columns:
            df['pckg'] = 'BAS'  # default to getting heads
        if 'obs_type' in df.columns and 'intyp' not in df.columns:
            df['arr'] = [arr.get(s, 'HD') for s in df['obs_type']]
        elif 'arr' not in df.columns:
            df['arr'] = 'HD'
        df['intyp'] = ['I' if p == 'BAS' else 'C' for p in df['pckg']]
        df[obsname_column] = df[obsname_column].astype(str).str.lower()

        dfs.append(
            df[['pckg', 'arr', 'intyp', 'x', 'y', obsname_column, 'file']])
    df = pd.concat(dfs, axis=0)

    print('\nCulling observations to model area...')
    df['geometry'] = [Point(x, y) for x, y in zip(df.x, df.y)]
    within = [g.within(self.bbox) for g in df.geometry]
    df = df.loc[within].copy()

    print(
        'Dropping head observations that coincide with Lake Package Lakes...')
    i, j = get_ij(self.modelgrid, df.x.values, df.y.values)
    islak = self.lakarr[0, i, j] != 0

    df['i'], df['j'] = i, j
    df = df.loc[~islak].copy()

    drop_obs = self.cfg[package].get('drop_observations', [])
    if len(drop_obs) > 0:
        print('Dropping head observations specified in {}...'.format(
            self.cfg.get('filename', 'config file')))
        df = df.loc[~df[obsname_column].astype(str).isin(drop_obs)]

    # make unique observation names for each model layer; applying the character limit
    # preserve end of obsname, truncating initial characters as needed
    # (for observations based on lat-lon coordinates such as usgs, or other naming schemes
    #  where names share leading characters)
    prefix_character_limit = obsname_character_limit  # - 2
    df[obsname_column] = [
        obsname[-prefix_character_limit:] for obsname in df[obsname_column]
    ]
    duplicated = df[obsname_column].duplicated(keep=False)
    # check for duplicate names after truncation
    if duplicated.sum() > 0:
        print(
            'Warning- {} duplicate observation names encountered. First instance of each name will be used.'
            .format(duplicated.sum()))
        print(df.loc[duplicated, [obsname_column, 'file']])

    # make sure every head observation is in each layer
    non_heads = df.loc[df.arr != 'HD'].copy()
    heads = df.loc[df.arr == 'HD'].copy()
    heads0 = heads.groupby(obsname_column).first().reset_index()
    heads0[obsname_column] = heads0[obsname_column].astype(str)
    heads_all_layers = pd.concat([heads0] *
                                 self.nlay).sort_values(by=obsname_column)
    heads_all_layers['klay'] = list(range(self.nlay)) * len(heads0)
    heads_all_layers[obsname_column] = [
        '{}'.format(obsname)  # _{:.0f}'.format(obsname, k)
        for obsname, k in zip(heads_all_layers[obsname_column],
                              heads_all_layers['klay'])
    ]
    df = pd.concat([heads_all_layers, non_heads], axis=0)

    # dtypes
    assert df[obsname_column].dtype == np.object
    df['klay'] = df.klay.astype(int)

    if format == 'hyd':
        # get model locations
        xl, yl = self.modelgrid.get_local_coords(df.x.values, df.y.values)
        df['xl'] = xl
        df['yl'] = yl
        # drop observations located in inactive cels
        ibdn = model.bas6.ibound.array[df.klay.values, df.i.values,
                                       df.j.values]
        active = ibdn == 1
        df.drop(['i', 'j'], axis=1, inplace=True)
    elif format == 'obs':  # mf6 observation utility
        obstype = {'BAS': 'HEAD'}
        renames = {'pckg': 'obstype'}
        df.pckg.replace(obstype, inplace=True)
        df.rename(columns=renames, inplace=True)
        df['id'] = list(zip(df.klay, df.i, df.j))
        # drop observations located in inactive cels
        idm = model.idomain[df.klay.values, df.i.values, df.j.values]
        active = idm == 1
        df.drop(['arr', 'intyp', 'i', 'j'], axis=1, inplace=True)
    df = df.loc[active].copy()
    return df