Exemplo n.º 1
0
def test_convert_time_units():
    assert np.allclose(convert_time_units('s', 'day'), 1 / 86400)
    assert np.allclose(convert_time_units(4, 1), 86400)
    assert np.allclose(convert_time_units('days', 'seconds'), 86400)
    assert np.allclose(convert_time_units('d', 's'), 86400)
    assert np.allclose(convert_time_units(1, 4), 1 / 86400)
    assert np.allclose(convert_time_units(None, 'd'), 1.)
    assert np.allclose(convert_time_units(5, 4), 365.25)
    assert np.allclose(convert_time_units(4, 5), 1 / 365.25)
    assert np.allclose(convert_time_units('years', 'days'), 365.25)
Exemplo n.º 2
0
def plot_budget_term(df, term, title_prefix='', title_suffix='', plotted=set(),
                     model_length_units=None, model_time_units=None,
                     secondary_axis_units=None, xtick_stride=None):

    if term not in {'IN-OUT', 'PERCENT_DISCREPANCY'}:

        # get the absolute quantity for the term
        if isinstance(term, list):
            series = df[term].sum(axis=1)
            out_term = [s.replace('_IN', '_OUT') for s in term]
            out_series = df[out_term].sum(axis=1)
        else:
            term = term.replace('_IN', '').replace('_OUT', '')
            in_term = f'{term}_IN'
            out_term = f'{term}_OUT'
            series = df[in_term]
            out_series = df[out_term]

        # get the net
        net_series = series - out_series

        # get the percent relative to the total budget
        pct_series = series/df['TOTAL_IN']
        pct_out_series = out_series/df['TOTAL_OUT']
        pct_net_series = pct_series - pct_out_series

    else:
        series = df[term]
        out_term = None
        out_series = None

    if model_length_units is not None and model_time_units is not None:
        units_text = get_figure_label_unit_text(model_length_units, model_time_units,
                                                length_unit_exp=3)
    else:
        units_text = '$L^3/T$'

    if out_series is not None:
        fig, axes = plt.subplots(2, 1, sharex=True, figsize=(11, 8.5))
        axes = axes.flat
        ax = axes[0]
        series.plot(ax=ax, c='C0')
        ax.axhline(0, zorder=-1, lw=0.5, c='k')
        (-out_series).plot(ax=ax, c='C1')
        net_series.plot(ax=ax, c='0.5', zorder=-1)
        h, l = ax.get_legend_handles_labels()
        ax.legend(h, ['In', 'Out', 'Net'])
        ax.set_ylabel(f'Flow rate, in model units of {units_text}')

        # add commas to y axis values
        formatter = mpl.ticker.FuncFormatter(lambda x, p: format(int(x), ','))
        ax.get_yaxis().set_major_formatter(formatter)

        # optional 2nd y-axis with other units
        if secondary_axis_units is not None:
            length_units2, time_units2 = parse_flux_units(secondary_axis_units)
            vol_conversion = convert_volume_units(model_length_units, length_units2)
            time_conversion = convert_time_units(model_time_units, time_units2)

            def fconvert(x):
                return x * vol_conversion * time_conversion

            def rconvert(x):
                return x / (vol_conversion * time_conversion)

            secondary_axis_unit_text = get_figure_label_unit_text(length_units2, time_units2,
                                                                  length_unit_exp=3)
            secax = ax.secondary_yaxis('right', functions=(fconvert, rconvert))
            secax.set_ylabel(f'Flow rate, in {secondary_axis_unit_text}')
            secax.get_yaxis().set_major_formatter(formatter)

        # plot the percentage of total budget on second axis
        ax2 = axes[1]
        pct_series.plot(ax=axes[1], c='C0')
        ax2.axhline(0, zorder=-1, lw=0.5, c='k')
        (-pct_out_series).plot(ax=ax2, c='C1')
        pct_net_series.plot(ax=ax2, c='0.5', zorder=-1)
        ax2.set_ylabel('Fraction of model budget')

        # add stress period info
        ymin, ymax = ax.get_ylim()
        yloc = np.ones(len(df)) * (ymin - 0.02 * (ymax - ymin))
        if xtick_stride is None:
            xtick_stride = int(np.round(len(df) / 10, 0))
            xtick_stride = 1 if xtick_stride < 1 else xtick_stride
        kpers = set()
        for x, y in zip(df.index.values[::xtick_stride], yloc[::xtick_stride]):
            kper = df.loc[x, 'kper']
            # only make one line for each stress period
            if kper not in kpers:
                ax.axvline(x, lw=0.5, c='k', zorder=-2)
                ax2.axvline(x, lw=0.5, c='k', zorder=-2)
                ax2.text(x, y, kper, transform=ax.transData, ha='center', va='top')
                kpers.add(kper)

        ax2.text(0.5, -0.07, 'Model Stress Period', ha='center', va='top', transform=ax.transAxes)

    else:
        fig, ax = plt.subplots(1, 1, sharex=True, figsize=(11, 8.5))
        series.plot(ax=ax, c='C0')
        ax.axhline(0, zorder=-1, lw=0.5, c='k')
        ax2 = ax

    title_text = ' '.join((title_prefix, term.split('_')[0], title_suffix)).strip()
    ax.set_title(title_text)
    xmin, xmax = series.index.min(), series.index.max()
    ax.set_xlim(xmin, xmax)

    if not isinstance(df.index, pd.DatetimeIndex):
        ax2.set_xlabel('Time since the start of the simulation, in model units')

    plotted.update({term, out_term})
Exemplo n.º 3
0
def plot_budget_summary(df, title_prefix='', title_suffix='', date_index_fmt='%Y-%m',
                        term_nets=False,
                        model_length_units=None,
                        model_time_units=None,
                        secondary_axis_units=None, xtick_stride=6,
                        plot_start_date=None, plot_end_date=None):
    fig, ax = plt.subplots(figsize=(11, 8.5))
    df = df.copy()
    if not term_nets:
        in_cols = [c for c in df.columns if '_IN' in c and 'TOTAL' not in c]
        out_cols = [c for c in df.columns if '_OUT' in c and 'TOTAL' not in c]
        ax = df[in_cols].plot.bar(stacked=True, ax=ax, width=20)
        ax = (-df[out_cols]).plot.bar(stacked=True, ax=ax, width=20)

    if isinstance(df.index, pd.DatetimeIndex):
        ax.set_xticklabels(df.index.strftime(date_index_fmt))
    else:
        ax.set_xlabel('Time since the start of the simulation, in model units')

    ax.axhline(0, zorder=-1, lw=0.5, c='k')

    # create ylabel with model units, if provided
    if model_length_units is not None and model_time_units is not None:
        units_text = get_figure_label_unit_text(model_length_units, model_time_units,
                                                length_unit_exp=3)
    else:
        units_text = '$L^3/T$'
    ax.set_ylabel(f'Flow rate, in model units of {units_text}')

    # add commas to y axis values
    formatter = mpl.ticker.FuncFormatter(lambda x, p: format(int(x), ','))
    ax.get_yaxis().set_major_formatter(formatter)

    # optional 2nd y-axis with other units
    if secondary_axis_units is not None:
        length_units2, time_units2 = parse_flux_units(secondary_axis_units)
        vol_conversion = convert_volume_units(model_length_units, length_units2)
        time_conversion = convert_time_units(model_time_units, time_units2)

        def fconvert(x):
            return x * vol_conversion * time_conversion

        def rconvert(x):
            return x / (vol_conversion * time_conversion)

        secondary_axis_unit_text = get_figure_label_unit_text(length_units2, time_units2,
                                                              length_unit_exp=3)
        secax = ax.secondary_yaxis('right', functions=(fconvert, rconvert))
        secax.set_ylabel(f'Flow rate, in {secondary_axis_unit_text}')
        secax.get_yaxis().set_major_formatter(formatter)

    # add stress period info
    ymin, ymax = ax.get_ylim()
    xlocs = np.arange(len(df))
    yloc = np.ones(len(df)) * (ymin + 0.03 * (ymax - ymin))
    if xtick_stride is None:
        xtick_stride = int(np.round(len(df) / 10, 0))
        xtick_stride = 1 if xtick_stride < 1 else xtick_stride
    kpers = set()
    for x, y in zip(xlocs[::xtick_stride], yloc[::xtick_stride]):
        kper = int(df.iloc[x]['kper'])
        # only make one line for each stress period
        if kper not in kpers:
            ax.axvline(x, lw=0.5, c='k', zorder=-2)
            ax.text(x, y, f" {kper}", transform=ax.transData, ha='left', va='top')
            kpers.add(kper)
    ax.text(min(kpers), y + abs(0.06*y), ' model stress period:', transform=ax.transData, ha='left', va='top')
    title_text = ' '.join((title_prefix, 'budget summary', title_suffix)).strip()
    ax.set_title(title_text)
    
    ax.legend(ncol=2)

    # reduce x-tick density
    ticks = ax.xaxis.get_ticklocs()
    ticklabels = [l.get_text() for l in ax.xaxis.get_ticklabels()]
    ax.xaxis.set_ticks(ticks[::xtick_stride])
    ax.xaxis.set_ticklabels(ticklabels[::xtick_stride])
    
    # set x axis limits
    xmin, xmax = ax.get_xlim()
    if plot_start_date is not None:
        xmin = pd.Timestamp(plot_start_date)
    if plot_end_date is not None:
        xmax = pd.Timestamp(plot_end_date)
    ax.set_xlim(xmin, xmax)
    return ax
Exemplo n.º 4
0
def plot_budget_term(df,
                     term,
                     title_prefix='',
                     title_suffix='',
                     model_length_units=None,
                     model_time_units=None,
                     secondary_axis_units=None,
                     xtick_stride=None,
                     plot_start_date=None,
                     plot_end_date=None,
                     datetime_xaxis=True):
    """Make a timeseries plot of an individual MODFLOW listing file 
    budget term.

    Parameters
    ----------
    df : DataFrame
        Table of listing file budget results produced by flopy; typically the flux 
        (not volume) terms (see example below).
    title_prefix : str, optional
        Prefix to insert at the begining of the title, for example the model name.
        by default ''
    title_suffix : str, optional
        Suffix to insert at the end of the title, by default ''
    model_length_units : str, optional
        Length units of the model, for labeling and conversion 
        to secondary_axis_units, 
        by default None
    model_time_units : str, optional
        Time units of the model, for labeling and conversion 
        to secondary_axis_units, 
        by default None
    secondary_axis_units : str, optional
        Option to include a secondary y-axis on the right with
        another unit, for example 'mgal/day' for million gallons per day.
        Requires `model_length_units` and `model_time_units`.
        by default None
    xtick_stride : int, optional
        Spacing between x-ticks. May be useful for models with many stress periods.
        by default 6
    plot_start_date : str, optional
        Minimum date to plot on the x-axis, in a string format 
        recognizable by pandas (if `df` has a datetime index) or
        a numeric value (if `df` has a numeric index). May be
        useful if the model has long spinup period(s) that
        would obscure later periods of interest when datetime_xaxis=True.
        by default None (plot all dates)
    plot_end_date : str, optional
        Maximum date to plot on the x-axis, in a string format 
        recognizable by pandas (if `df` has a datetime index) or
        a numeric value (if `df` has a numeric index).
        by default None (plot all dates)
    datetime_xaxis : bool
        Plot budget values as a function of time. If False,
        plot as a function of stress period.
        by default, True

    Returns
    -------
    ax : matplotlib axes subplot instance
        
    Examples
    --------
    .. code-block:: python

        from mfexport.listfile import get_listfile_data, plot_budget_summary
        df = get_listfile_data(listfile='model.list', model_start_datetime='2000-01-01')
        plot_budget_term(df, 'WELLS')
    """
    # slice the dataframe to the specified time range (if any)
    df = df.copy()
    df = df.loc[slice(plot_start_date, plot_end_date)]

    if not datetime_xaxis and 'datetime' in df.index.dtype.name:
        df['datetime'] = df.index
        df.index = df['kper']
    if term not in {'IN-OUT', 'PERCENT_DISCREPANCY'}:

        # get the absolute quantity for the term
        if isinstance(term, list):
            series = df[term].sum(axis=1)
            out_term = [s.replace('_IN', '_OUT') for s in term]
            out_series = df[out_term].sum(axis=1)
        else:
            term = term.replace('_IN', '').replace('_OUT', '')
            in_term = f'{term}_IN'
            out_term = f'{term}_OUT'
            series = df[in_term]
            out_series = df[out_term]

        # get the net
        net_series = series - out_series

        # get the percent relative to the total budget
        pct_series = series / df['TOTAL_IN']
        pct_out_series = out_series / df['TOTAL_OUT']
        pct_net_series = pct_series - pct_out_series

    else:
        series = df[term]
        out_term = None
        out_series = None

    if model_length_units is not None and model_time_units is not None:
        units_text = get_figure_label_unit_text(model_length_units,
                                                model_time_units,
                                                length_unit_exp=3)
    else:
        units_text = '$L^3/T$'

    if out_series is not None:
        fig, axes = plt.subplots(2, 1, sharex=True, figsize=(11, 8.5))
        axes = axes.flat
        ax = axes[0]
        series.plot(ax=ax, c='C0')
        ax.axhline(0, zorder=-1, lw=0.5, c='k')
        (-out_series).plot(ax=ax, c='C1')
        net_series.plot(ax=ax, c='0.5', zorder=-1)
        h, l = ax.get_legend_handles_labels()
        ax.legend(h, ['In', 'Out', 'Net'])
        ax.set_ylabel(f'Flow rate, in model units of {units_text}')

        # add commas to y axis values
        formatter = mpl.ticker.FuncFormatter(lambda x, p: format(int(x), ','))
        ax.get_yaxis().set_major_formatter(formatter)

        # optional 2nd y-axis with other units
        if secondary_axis_units is not None:
            length_units2, time_units2 = parse_flux_units(secondary_axis_units)
            vol_conversion = convert_volume_units(model_length_units,
                                                  length_units2)
            time_conversion = convert_time_units(model_time_units, time_units2)

            def fconvert(x):
                return x * vol_conversion * time_conversion

            def rconvert(x):
                return x / (vol_conversion * time_conversion)

            secondary_axis_unit_text = get_figure_label_unit_text(
                length_units2, time_units2, length_unit_exp=3)
            secax = ax.secondary_yaxis('right', functions=(fconvert, rconvert))
            secax.set_ylabel(f'Flow rate, in {secondary_axis_unit_text}')
            secax.get_yaxis().set_major_formatter(formatter)

        # plot the percentage of total budget on second axis
        ax2 = axes[1]
        pct_series.plot(ax=axes[1], c='C0')
        ax2.axhline(0, zorder=-1, lw=0.5, c='k')
        (-pct_out_series).plot(ax=ax2, c='C1')
        pct_net_series.plot(ax=ax2, c='0.5', zorder=-1)
        ax2.set_ylabel('Fraction of budget')
        single_subplot = False
        # add stress period info
        #ymin, ymax = ax.get_ylim()
        #yloc = np.ones(len(df)) * (ymin - 0.02 * (ymax - ymin))
        #if xtick_stride is None:
        #    xtick_stride = int(np.round(len(df) / 10, 0))
        #    xtick_stride = 1 if xtick_stride < 1 else xtick_stride
        #kpers = set()
        #for x, y in zip(df.index.values[::xtick_stride], yloc[::xtick_stride]):
        #    kper = int(df.loc[x, 'kper'])
        #    # only make one line for each stress period
        #    if kper not in kpers:
        #        ax.axvline(x, lw=0.5, c='k', zorder=-2)
        #        ax2.axvline(x, lw=0.5, c='k', zorder=-2)
        #        ax2.text(x, y, kper, transform=ax.transData, ha='center', va='top')
        #        kpers.add(kper)

        #ax2.text(0.5, -0.07, 'Model Stress Period', ha='center', va='top', transform=ax.transAxes)

    else:
        fig, ax = plt.subplots(1, 1, sharex=True, figsize=(11, 8.5))
        series.plot(ax=ax, c='C0')
        ax.axhline(0, zorder=-1, lw=0.5, c='k')
        ax2 = ax
        single_subplot = True

    # add stress period info
    ymin, ymax = ax.get_ylim()
    pad = 0.02
    if single_subplot:
        pad = 0.1
    yloc = np.ones(len(df)) * (ymin - pad * (ymax - ymin))
    if xtick_stride is None:
        xtick_stride = int(np.round(len(df) / 10, 0))
        xtick_stride = 1 if xtick_stride < 1 else xtick_stride
    kpers = set()
    for x, y in zip(df.index.values[::xtick_stride], yloc[::xtick_stride]):
        kper = int(df.loc[x, 'kper'])
        # only make one line for each stress period
        if kper not in kpers:
            ax.axvline(x, lw=0.5, c='k', zorder=-2)
            ax2.axvline(x, lw=0.5, c='k', zorder=-2)
            ax2.text(x, y, kper, transform=ax.transData, ha='center', va='top')
            kpers.add(kper)

        ax2.text(0.5,
                 -0.07,
                 'Model Stress Period',
                 ha='center',
                 va='top',
                 transform=ax.transAxes)

    title_text = ' '.join(
        (title_prefix, term.split('_')[0], title_suffix)).strip()
    ax.set_title(title_text)

    # set x axis limits
    xmin, xmax = series.index.min(), series.index.max()

    if plot_start_date is not None:
        if not datetime_xaxis:
            loc = df.datetime >= plot_start_date
            xmin = df.datetime.loc[loc].index[0]
        else:
            xmin = pd.Timestamp(plot_start_date)
    if plot_end_date is not None:
        if not datetime_xaxis:
            loc = df.datetime <= plot_end_date
            xmax = df.datetime.loc[loc].index[-1]
        else:
            xmax = pd.Timestamp(plot_end_date)
    ax.set_xlim(xmin, xmax)

    if not datetime_xaxis:
        if 'datetime' in df.columns:
            xticks = ax2.get_xticks()
            datetime_labels = [
                df['datetime'].loc[int(i)].strftime('%Y-%m-%d') for i in xticks
                if i <= df.index.max()
            ]
            ax2.set_xticklabels(datetime_labels, rotation=90)

    if not isinstance(df.index, pd.DatetimeIndex):
        ax2.set_xlabel(
            'Time since the start of the simulation, in model units')

    plotted.update({term, out_term})
Exemplo n.º 5
0
def plot_budget_summary(df,
                        title_prefix='',
                        title_suffix='',
                        date_index_fmt='%Y-%m',
                        term_nets=False,
                        model_length_units=None,
                        model_time_units=None,
                        secondary_axis_units=None,
                        xtick_stride=6,
                        plot_start_date=None,
                        plot_end_date=None):
    """Plot a stacked bar chart summary of a MODFLOW listing file budget dataframe.

    Parameters
    ----------
    df : DataFrame
        Table of listing file budget results produced by flopy; typically the flux 
        (not volume) terms (see example below).
    title_prefix : str, optional
        Prefix to insert at the begining of the title, for example the model name.
        by default ''
    title_suffix : str, optional
        Suffix to insert at the end of the title, by default ''
    date_index_fmt : str, optional
        Date format for the plot x-axis, by default '%Y-%m'
    term_nets : bool, optional
        Option to only plot net quantities for each stress period.
        For example if the inflows and outflows for the WEL package 
        were +10 and -10, a bar of zero height would be plotted.
        by default False
    model_length_units : str, optional
        Length units of the model, for labeling and conversion 
        to secondary_axis_units, 
        by default None
    model_time_units : str, optional
        Time units of the model, for labeling and conversion 
        to secondary_axis_units, 
        by default None
    secondary_axis_units : str, optional
        Option to include a secondary y-axis on the right with
        another unit, for example 'mgal/day' for million gallons per day.
        Requires `model_length_units` and `model_time_units`.
        by default None
    xtick_stride : int, optional
        Spacing between x-ticks. May be useful for models with many stress periods.
        by default 6
    plot_start_date : str, optional
        Minimum date to plot on the x-axis, in a string format 
        recognizable by pandas (if `df` has a datetime index) or
        a numeric value (if `df` has a numeric index).
        by default None (plot all dates)
    plot_end_date : str, optional
        Maximum date to plot on the x-axis, in a string format 
        recognizable by pandas (if `df` has a datetime index) or
        a numeric value (if `df` has a numeric index).
        by default None (plot all dates)

    Returns
    -------
    ax : matplotlib axes subplot instance
        
    Examples
    --------
    .. code-block:: python

        from mfexport.listfile import get_listfile_data, plot_budget_summary
        df = get_listfile_data(listfile='model.list', model_start_datetime='2000-01-01')
        plot_budget_summary(df)

    """

    # slice the dataframe to the specified time range (if any)
    df = df.copy()
    df = df.loc[slice(plot_start_date, plot_end_date)]

    fig, ax = plt.subplots(figsize=(11, 8.5))
    in_cols = [c for c in df.columns if '_IN' in c and 'TOTAL' not in c]
    out_cols = [c for c in df.columns if '_OUT' in c and 'TOTAL' not in c]
    if not term_nets:
        ax = df[in_cols].plot.bar(
            stacked=True,
            ax=ax,  # width=20
        )
        ax = (-df[out_cols]).plot.bar(
            stacked=True,
            ax=ax,  # width=20
        )
    else:
        pairs = list(zip(in_cols, out_cols))
        net_cols = []
        for in_col, out_col in pairs:
            net_col = f"{in_col.split('_')[0]} (net)"
            df[net_col] = df[in_col] - df[out_col]
            net_cols.append(net_col)
        ax = df[net_cols].plot.bar(
            stacked=True,
            ax=ax,  # width=20
        )

    if isinstance(df.index, pd.DatetimeIndex):
        ax.set_xticklabels(df.index.strftime(date_index_fmt))
    else:
        ax.set_xlabel('Time since the start of the simulation, in model units')

    ax.axhline(0, zorder=-1, lw=0.5, c='k')

    # create ylabel with model units, if provided
    if model_length_units is not None and model_time_units is not None:
        units_text = get_figure_label_unit_text(model_length_units,
                                                model_time_units,
                                                length_unit_exp=3)
    else:
        units_text = '$L^3/T$'
    ax.set_ylabel(f'Flow rate, in model units of {units_text}')

    # add commas to y axis values
    formatter = mpl.ticker.FuncFormatter(lambda x, p: format(int(x), ','))
    ax.get_yaxis().set_major_formatter(formatter)

    # optional 2nd y-axis with other units
    if secondary_axis_units is not None:
        length_units2, time_units2 = parse_flux_units(secondary_axis_units)
        vol_conversion = convert_volume_units(model_length_units,
                                              length_units2)
        time_conversion = convert_time_units(model_time_units, time_units2)

        def fconvert(x):
            return x * vol_conversion * time_conversion

        def rconvert(x):
            return x / (vol_conversion * time_conversion)

        secondary_axis_unit_text = get_figure_label_unit_text(
            length_units2, time_units2, length_unit_exp=3)
        secax = ax.secondary_yaxis('right', functions=(fconvert, rconvert))
        secax.set_ylabel(f'Flow rate, in {secondary_axis_unit_text}')
        secax.get_yaxis().set_major_formatter(formatter)

    # add stress period info
    ymin, ymax = ax.get_ylim()
    xlocs = np.arange(len(df))
    yloc = np.ones(len(df)) * (ymin + 0.03 * (ymax - ymin))
    if xtick_stride is None:
        xtick_stride = int(np.round(len(df) / 10, 0))
        xtick_stride = 1 if xtick_stride < 1 else xtick_stride
    kpers = set()
    for x, y in zip(xlocs[::xtick_stride], yloc[::xtick_stride]):
        kper = int(df.iloc[x]['kper'])
        # only make one line for each stress period
        if kper not in kpers:
            ax.axvline(x, lw=0.5, c='k', zorder=-2)
            ax.text(x,
                    y,
                    f" {kper}",
                    transform=ax.transData,
                    ha='left',
                    va='top')
            kpers.add(kper)
    ax.text(min(kpers),
            y + abs(0.06 * y),
            ' model stress period:',
            transform=ax.transData,
            ha='left',
            va='top')
    title_text = 'budget summary'
    if term_nets:
        title_text += ' (net fluxes)'
    title_text = ' '.join((title_prefix, title_text, title_suffix)).strip()
    ax.set_title(title_text)

    ax.legend(ncol=2)

    # reduce x-tick density
    ticks = ax.xaxis.get_ticklocs()
    ticklabels = [l.get_text() for l in ax.xaxis.get_ticklabels()]
    ax.xaxis.set_ticks(ticks[::xtick_stride])
    ax.xaxis.set_ticklabels(ticklabels[::xtick_stride])

    return ax
Exemplo n.º 6
0
def export_sfr_results(mf2005_sfr_outputfile=None,
                       mf2005_SfrFile_instance=None,
                       mf6_sfr_stage_file=None,
                       mf6_sfr_budget_file=None,
                       mf6_package_data=None,
                       model=None,
                       model_top=None,
                       grid=None,
                       kstpkper=(0, 0),
                       sfrlinesfile=None,
                       pointsize=0.5,
                       model_length_units=None,
                       model_time_units=None,
                       output_length_units='feet',
                       output_time_units='seconds',
                       gis=True, pdfs=True,
                       output_path='postproc', suffix='',
                       verbose=False):

    pdfs_dir, rasters_dir, shps_dir = make_output_folders(output_path)
    m = model
    if not isinstance(kstpkper, list):
        kstpkper = [kstpkper]
    print('Exporting SFR results...')
    for f in [mf2005_sfr_outputfile, mf6_sfr_stage_file, mf6_sfr_budget_file]:
        if f is not None:
            print('file: {}'.format(f))

    df = read_sfr_output(mf2005_sfr_outputfile=mf2005_sfr_outputfile,
                         mf2005_SfrFile_instance=mf2005_SfrFile_instance,
                         mf6_sfr_stage_file=mf6_sfr_stage_file,
                         mf6_sfr_budget_file=mf6_sfr_budget_file,
                         mf6_package_data=mf6_package_data,
                         model=model)
    if model_length_units is None:
        if model is None:
            model_length_units = 'meters'
        else:
            model_length_units = get_length_units(m)
    if model_time_units is None:
        if model is None:
            model_time_units = 'days'
        else:
            model_time_units = get_time_units(m)
    lmult = convert_length_units(model_length_units,
                                 output_length_units)
    tmult = convert_time_units(model_time_units,
                               output_time_units)
    unit_text = get_unit_text(output_length_units,
                              output_time_units, 3)

    if 'GWF' in df.columns:
        df['Qaquifer'] = -df.GWF # for consistency with MF2005
    if 'Qmean' not in df.columns:
        df['Qmean'] = df[['Qin', 'Qout']].abs().mean(axis=1)

    # write columns in the output units
    df['Qmean_{}'.format(unit_text)] = df.Qmean * lmult**3/tmult
    df['Qaq_{}'.format(unit_text)] = df.Qaquifer * lmult**3/tmult

    # add model top comparison if available
    if isinstance(model_top, str):
        model_top = np.loadtxt(model_top)
    elif model_top is None and model is not None:
        model_top = m.dis.top.array
    
    if model_top is not None and 'i' in df.columns and 'j' in df.columns:
        df['model_top'] = model_top[df.i.values, df.j.values]
        if 'stage' in df.columns:
            df['above'] = df.stage - df.model_top
    groups = df.groupby('kstpkper')

    outfiles = []
    if gis:
        prj_file = None
        if sfrlinesfile is not None:
            sfrlines = shp2df(sfrlinesfile)
            prj_file = sfrlines[:-4] + '.prj'
            sfrlines.sort_values(by=['iseg', 'ireach'], inplace=True)
            geoms = sfrlines.geometry
        else:
            #assert sr is not None, \
            #    'need SpatialReference instance to locate model grid cells'
            #dfp = groups.get_group((0, 0)).copy()
            geoms = None
            #vertices = sr.get_vertices(dfp.i, dfp.j)
            #geoms = [Polygon(vrt) for vrt in vertices]

        for kstp, kper in kstpkper:
            print('stress period {}, timestep {}'.format(kper, kstp))
            dfp = groups.get_group((kstp, kper)).copy()
            if geoms is not None:
                dfp['geometry'] = geoms
            #dfp = gp.GeoDataFrame(dfp)
            #dfp.crs = sr.proj4_str
            # to use cell polygons instead of lines
            # verts = m.sr.get_vertices(df.i.values, df.j.values)
            #df['geometry'] = [Polygon(v) for v in verts]
            dfp['stp'] = [t[0] for t in dfp['kstpkper']]
            dfp['per'] = [t[1] for t in dfp['kstpkper']]
            dfp.drop('kstpkper', axis=1, inplace=True)  # geopandas doesn't like tuples
            outfile = '{}/sfrout_per{}_stp{}{}.shp'.format(shps_dir, kper, kstp, suffix)

            export_shapefile(outfile, dfp, modelgrid=grid, prj=prj_file)
            outfiles.append(outfile)
            #dfp.to_file(outfile)
            #print('wrote {}'.format(outfile))

    if pdfs:
        # need to add a scale that addresses units
        for kstp, kper in kstpkper:
            print('stress period {}, timestep {}'.format(kper, kstp))
            df = groups.get_group((kstp, kper)).copy()
            bf_outfile = '{}/baseflow_per{}_stp{}{}.pdf'.format(pdfs_dir, kper, kstp, suffix)
            sfr_baseflow_pdf(bf_outfile, df, pointsize=pointsize, verbose=verbose)

            qaq_outfile = '{}/qaquifer_per{}_stp{}.pdf'.format(pdfs_dir, kper, kstp, suffix)
            sfr_qaquifer_pdf(qaq_outfile, df, pointsize=pointsize, verbose=verbose)
            outfiles += [bf_outfile, qaq_outfile]
    return outfiles