if __name__ == '__main__':
    from tools import *
    import dask.dataframe as dd
    import pandas as pd
    import matplotlib.pyplot as plt
    import time
    import re
    from datetime import datetime


    plt.rcParams['text.usetex'] = True
    ax1, ax1_, ax2, ax2_, ax3 = None, None, None, None, None
    markers = ["s", "h", "D", "v", "o"]
    marker_id = -1
    added_baseline_legend = False

    PLOT_RC = False

    def plot_add(alpha, consistency, robustness, name, quality=None, baseline=False, trivial=False, trivial_legend=False):
        if PLOT_RC:
            return plot_add_rc(alpha, consistency, robustness, name, quality, baseline, trivial, trivial_legend)
        global added_baseline_legend, marker_id

        if trivial:
            color = plt.gca().lines[-1].get_color()
            marker = None # markers[marker_id]
            plotargs = {"linestyle": "dashed", "linewidth": None, "color": color, "marker": marker}
            mean = consistency.groupby(alpha).mean()
            if trivial_legend:
                ax1.plot(mean.index, mean, **{**plotargs, "color": "black"}, label=name)
            ax1s = [ax1] if ax1_ is None else [ax1, ax1_]
            ax2s = [ax2] if ax1_ is None else [ax2, ax2_]
            for ax1__, ax2__ in zip(ax1s, ax2s):
                mean = consistency.groupby(alpha).mean()
                ax1__.plot(mean.index, mean, **plotargs)
                mean = robustness.groupby(alpha).mean()
                ax2__.plot(mean.index, mean, **plotargs)
            return

        if not baseline:
            color = next(ax1._get_lines.prop_cycler)['color']
            marker_id += 1
            marker = markers[marker_id]
            grp = consistency.groupby(alpha)
            med = grp.mean()

            ax1s = [ax1] if ax1_ is None else [ax1, ax1_]
            for i, ax in enumerate(ax1s):
                ax.plot(med.index, med.to_numpy(), # marker=marker,
                        label=None if i>0 else name + ("" if quality is None else f" $({quality.mean():.2f})$"),
                        color=color)
                std = grp.head(5).std()
                ax.fill_between(med.index.astype(float), med-std, med+std, alpha=0.2, color=color)

        if baseline:
            grp = robustness.groupby(alpha)
            med = grp.mean()

            ax2.plot(med.index, grp.mean()[med.index[0]] * np.ones_like(med.index), linewidth=0.7, color="black",
                    label=None if added_baseline_legend else name)
            added_baseline_legend = True

        else:
            grp = robustness.groupby(alpha)
            med = grp.mean()

            ax2s = [ax2] if ax2_ is None else [ax2, ax2_]
            for ax in ax2s:
                ax.plot(med.index, med.to_numpy(), # marker=marker,
                        color=color)
                std = grp.head(5).std()
                ax.fill_between(med.index.astype(float), med-std, med+std, alpha=0.2, color=color)

    def plot_add_rc(alpha, consistency, robustness, name, quality=None, baseline=False, trivial=False, trivial_legend=False):
        global added_baseline_legend

        con = consistency.groupby(alpha)
        rob = robustness.groupby(alpha)
        con_mean = con.mean().to_numpy()
        rob_mean = rob.mean().to_numpy()
        rob_std = rob.std().to_numpy()

        ixs = np.argsort(con_mean)
        con_mean_ = con_mean[ixs]
        rob_mean_ = rob_mean[ixs]
        rob_std_ = rob_std[ixs]

        if trivial:
            color = plt.gca().lines[-1].get_color()
            if trivial_legend:
                ax1.plot(con_mean_, rob_mean_, color="black", linestyle="dashed", label=name)
            ax1.plot(con_mean_, rob_mean_, color=color, linestyle="dashed")

        elif baseline:
            label = None if added_baseline_legend else name
            added_baseline_legend = True
            ax1.axhline(y=rob_mean[0], linewidth=0.7, color="black", label=label)

        else:
            color = next(ax1._get_lines.prop_cycler)['color']
            label = name + ("" if quality is None else f" $({quality.mean():.2f})$")
            ax1.plot(con_mean_, rob_mean_, label=label, color=color) # , marker=markers.pop())
            ax1.fill_between(con_mean_, rob_mean_-rob_std_, rob_mean_+rob_std_, alpha=0.2, color=color)
            ax1.scatter(con_mean[:1], rob_mean[:1], color="black", marker="x")

    def plot_add_from_df(df, alg_val="alg_val", pred_val="pred_val", opt_val="opt_val", label=None, baseline=False, trivial=False, trivial_legend=False):
        label = label or name(df)
        plot_add(df["alpha"],
                 df[alg_val] / df[pred_val],
                 df[alg_val] / df[opt_val],
                 label,
                 df[pred_val] / df[opt_val],
                 baseline=baseline,
                 trivial=trivial,
                 trivial_legend=trivial_legend)

    def plot_add2(quality, consistency, robustness, name):
        color = next(ax1._get_lines.prop_cycler)['color']
        marker = markers.pop()

        ax1.scatter(quality, consistency, alpha=0.5, s=10, label=name, color=color, marker=marker)
        ax2.scatter(quality, robustness, alpha=0.5, s=10, color=color, marker=marker)

    def plot_dfs(dfs, title_constants=None, legend_constants=None, broken=False,
            plot_title=False, alter_fn=None,
            ylim=(None, None), ylim2=(None, None), labels=None, rows=1, ylabel_offset=-0.078,
            spacing=None):
        global ax1, ax1_, ax2, ax2_, ax3
        plt.rcParams.update({'font.size': 13})
        if PLOT_RC:
            fig, ax1 = plt.subplots(1, 1, figsize=(4.8, 3.2))
            ax1.set_xlabel("Consistency")
            ax1.set_ylabel("Robustness")
        elif not broken:
            fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9.6, 3.0))
        else:
            fig = plt.figure(figsize=(14, 2.0))
            ax1  = fig.add_axes([0,    0,    0.25, 1])
            ax1_ = fig.add_axes([0.25, 0.30, 0.15, 0.7])
            ax1_.yaxis.tick_right()
            ax2  = fig.add_axes([0.50, 0,    0.25, 1])
            ax2_ = fig.add_axes([0.75, 0.30, 0.15, 0.7])
            ax2_.yaxis.tick_right()

        title, constants = name(pd.concat(dfs), return_constants=True,
                filter_constants=title_constants)
        if labels is None: labels = [None] * len(dfs)
        for i, (df, l) in enumerate(zip(dfs, labels)):
            label = l or name(df.drop(constants, axis=1),
                    filter_constants=legend_constants, alter=True, alter_fn=alter_fn)
            for alg, df_alg in df.groupby("alg"):
                is_trivial = alg == "trivial"
                plot_add_from_df(df_alg, label="Random Mixture" if is_trivial else label, trivial=is_trivial, trivial_legend=i+1>=len(dfs))
        for df in dfs:
            for _, df_alg in df.groupby("alg"):
                plot_add_from_df(df_alg, label="Worst-Case Baseline", baseline=True)
                break

        if not PLOT_RC:
            ax1.set_xlabel("$\\alpha$")
            ax1.set_ylabel("Consistency")
            ax2.set_xlabel("$\\alpha$")
            ax2.set_ylabel("Robustness")
            if ylim[0] is not None: ax1_.set_ylim(*ylim[0])
            if ylim[1] is not None: ax2_.set_ylim(*ylim[1])

        fig.legend(loc="upper center", ncol=int(np.ceil((len(dfs) + 2) / rows)),
                bbox_to_anchor=(0.45, 1.24 + 0.05 * rows), framealpha=0, edgecolor="black", # 1.05 + 0.4 * 
                shadow=False, columnspacing=spacing,
                handletextpad=None if spacing is None else spacing/2)
        if plot_title: fig.suptitle(title)
        if not broken: fig.tight_layout()

        now = re.sub("\D+", "-", str(datetime.now()))
        plt.savefig(f"figs/plot-{now}.pdf", bbox_inches="tight")

    def plot_dfs2(dfs, plot_title=False, rows=1):
        global ax1, ax2
        plt.rcParams.update({'font.size': 11})
        fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(6.4, 2.5))

        title, constants = name(pd.concat(dfs), return_constants=True)
        for df in dfs:
            label = name(df.drop(constants, axis=1), alter=True)
            plot_add2(
                    df["pred_val"] / df["opt_val"],
                    df["alg_val"] / df["pred_val"],
                    df["alg_val"] / df["opt_val"],
                    label)

        ax1.set_xlabel("Prediction Competitiveness")
        ax1.set_ylabel("Consistency")
        ax2.set_xlabel("Prediction Competitiveness")
        ax2.set_ylabel("Robustness")
        fig.legend(loc="upper center", ncol=int(np.ceil(len(dfs) / rows)),
                bbox_to_anchor=(0.51, 1.07),
                framealpha=0, edgecolor="black",
                shadow=False, handletextpad=0.3, columnspacing=1.0)
        if plot_title: fig.suptitle(title)
        fig.tight_layout()
        now = re.sub("\D+", "-", str(datetime.now()))
        plt.savefig(f"figs/plot-{now}.pdf", bbox_inches="tight")



    @lru_cache(maxsize=128, typed=False)
    def create_predictions3(day, n_trial, advs_imps_fn, quota_fn):
        print(" 3", end="", flush=True)
        advs_, imps_ = advs_imps_fn(day)
        advs = quota_fn(advs_, imps_)
        opt_pred, opt_val = opt_assignment(advs, imps_)
        return advs, imps_, opt_pred, opt_val

    @lru_cache(maxsize=128, typed=False)
    def create_predictions2(day, n_trial, advs_imps_fn, quota_fn, order_fn):
        print(" 2", end="", flush=True)
        advs, imps_, opt_pred, opt_val = create_predictions3(day, n_trial, advs_imps_fn, quota_fn)
        imps = order_fn(imps_)
        return advs, imps_, opt_pred, opt_val

    @lru_cache(maxsize=128, typed=False)
    def create_predictions(day, n_trial, advs_imps_fn, quota_fn, order_fn, pred_fn, corruption_fn, pred_day_offset):
        print("creating predictions...", end="", flush=True)
        advs, imps, opt_pred, opt_val = create_predictions2(day, n_trial, advs_imps_fn, quota_fn, order_fn)
        x, *xs = str(day).split("-")
        offset_day = "-".join(map(str, [int(x) - pred_day_offset] + xs))
        if type(day) == int: offset_day = int(offset_day)
        # day if pred_day_offset == 0 else day - pred_day_offset
        p_advs, p_imps, p_opt_pred, _ = create_predictions2(offset_day, n_trial, advs_imps_fn, quota_fn, order_fn)
        pred = pred_fn(p_advs, p_imps, p_opt_pred, on_advs=advs, on_imps=imps)
        pred = corruption_fn(pred)
        pred_val = pred_value(advs, imps, pred)
        print(" done")
        return {"advs": advs, "imps": imps, "opt_val": opt_val, "pred": pred, "pred_val": pred_val}

    def simulate(row):
        day_, n_trial_, advs_imps_fn_, quota_fn_, order_fn_, pred_fn_, corruption_fn_, pred_day_offset_, alg, alpha, *other_ = row
        args = [day_, n_trial_, advs_imps_fn_, quota_fn_, order_fn_, pred_fn_, corruption_fn_, pred_day_offset_]
        ret = create_predictions(*args).copy()
        advs, imps, pred = ret["advs"], ret["imps"], ret["pred"]
        alg_val = alg(advs, imps, pred, alpha=alpha, verbose=True)
        ret["alg_val"] = alg_val
        print(f"c={ret['alg_val'] / ret['pred_val']:.3f} r={ret['alg_val'] / ret['opt_val']:.3f}")
        return ret

    def simulateall(dfs, ref=None):
        return list(map(lambda x: apply(x[1], simulate,
            ref=None if ref is None else f"{ref}-{x[0]}"), enumerate(dfs)))

    def name(df, return_constants=False, filter_constants=None, alter=False, alter_fn=None):
        def human_readable(c, v):
            if callable(v):
                v = v.__name__
            if isinstance(v, float):
                v = f"{v:.3f}"
            if c == "alpha":
                v = f"$\\alpha={v}$"
            return str(v) # names[v] if v in names else str(v)
        constants = [k for k in df.columns if ((df[k].dtype != object or df[k].apply(type).eq(str).all()) and df[k].nunique() == 1)
                or df[k].apply(lambda x, y: x is y, args=[df.iloc[0][k]]).all()]
        if filter_constants is not None:
            constants = list(filter(lambda c: c in filter_constants, constants))
        values = {c: df.iloc[0][c] for c in constants}
        legend_vals = {c: human_readable(c, v) for c, v in values.items()}
        legend = legend_vals["label"] if "label" in constants else ", ".join([legend_vals[c] for c in ["alpha", "advs_imps_fn", "quota_fn", "order_fn", "pred_fn", "corruption_fn"] if c in legend_vals])
        if alter:
            print(legend)
            legend = legend.replace(",", " ")
            legend = re.sub("\s{2,}", " ", legend)
            if alter_fn is not None: legend = alter_fn(legend)
        if return_constants:
            return legend, constants
        else:
            return legend



    @named("data quota")
    def original_quota(advs, _):
        return advs

    @named("data order")
    def original_order(imps):
        return imps

    @named("OPT")
    def opt_pred(_1, _2, opt_pred, on_advs=None, on_imps=None):
        return opt_pred

    @named("")
    def no_corruption(pred):
        return pred

    @named("constant quota")
    def constant_quota(advs, imps, quota=4):
        return (quota * np.ones(imps.n_adv)).astype(int)

    @named("iPinYou")
    def ipinyou_nrows(day, nrows=None):
        advs, imps = ipinyou_advs_imps(day)
        # imps.stats()
        return advs, imps[:nrows]

    @named("Yahoo")
    def yahoo_nrows(day, nrows=None, supply_factor=0.05):
        advs, imps = yahoo_advs_imps(day)
        imps = Impressions(imps.imp_types, imp_supply=(supply_factor * imps.imp_supply).astype(int), shrink=False)
        return advs, imps[:nrows]

    def custom_adv(advs, imps):
        return create_advertisers(imps.n_adv, min_budget=12)

    @named("Synthetic Data")
    def synthetic_advs_imps(_, n_adv=5, n_imp=1000, spread=0.01, n_types=5):
        print("CREATING NEW RANDOM IMPRESSIONS")
        imps = create_impressions_synthetic3(n_adv, n_imp, spread=spread, n_types=5)
        advs = create_advertisers(n_adv, min_budget=85, equal_budget=True)
        return advs, imps

    @named("Synthetic AdWords Data")
    def adwords_advs_imps(*args, **kwargs):
        advs, imps = synthetic_advs_imps(*args, **kwargs)
        imps.is_adwords = True
        return advs, imps

    def worst_case_advs_imps(_):
        n_adv = 10
        n_imp = 1000
        advs = create_advertisers_worst_case(n_adv, n_imp)
        imps = create_impressions_worst_case(n_adv, n_imp)
        return advs, imps


    ipinyou_days = [20130606, 20130607, 20130608, 20130609, 20130610, 20130611, 20130612]
    ipinyou_days_15 = [f"{day}-{15}" for day in ipinyou_days]
    n_trials = 1
    alphas = np.linspace(1, 5, 10)

    def data(
            advs_imps,
            days,
            quota=original_quota,
            order=original_order,
            pred=opt_pred,
            corruption=no_corruption,
            pred_day_offset=0,
            alg=[exp_avg_pred],
            alphas=alphas,
            **kwargs
            ):
        return pd.DataFrame(product(days, range(n_trials), [advs_imps],
            quota if isinstance(quota, list) else [quota],
            [order], pred if isinstance(pred, list) else [pred],
            corruption if isinstance(corruption, list) else [corruption],
            [pred_day_offset], alg, alphas, *kwargs.values()),
            columns=["day", "n_trial", "advs_imps_fn", "quota_fn", "order_fn",
                "pred_fn", "corruption_fn", "pred_day_offset", "alg", "alpha",
                *kwargs.keys()])

    ipinyou_data = partial(data, bind(ipinyou_nrows, nrows=2000), ipinyou_days)
    ipinyou_data2 = partial(data, bind(ipinyou_nrows, nrows=2000), ipinyou_days[1:])

    yahoo_days = range(1, 124)[2:6]
    yahoo_data = partial(data, yahoo_nrows, yahoo_days)



    # Plots:

    n_trials = 5

    def plot_synthetic1():
        synthetic_data = partial(data,
                bind(synthetic_advs_imps, n_adv=12, n_imp=2000, spread=1.5, n_types=10),
                [0],
                alg=[exp_avg_pred, trivial])

        df1 = synthetic_data()

        df2 = synthetic_data(pred=bind(dual_base, epsilon=0.1))

        df3 = synthetic_data(corruption=bind(corrupt_prediction_rnd, corr_prob=0.5))

        df4 = synthetic_data(corruption=bind(corrupt_prediction_biased, corr_prob=0.5))

        dfs = [df1, df2, df3, df4]
        dfs = simulateall(dfs, ref="plot1a-triv")
        plot_dfs(dfs, plot_title=False, # alter_fn=lambda x: re.sub("(iased|andom) corruption", "-corr.", x),
                # spacing=1.0,
                rows=2)

    def plot_synthetic2():
        synthetic_data1 = partial(data,
                bind(synthetic_advs_imps, n_adv=12, n_imp=2000, spread=0.2, n_types=10),
                [0],
                alg=[exp_avg_pred, trivial])
        synthetic_data2 = partial(data,
                bind(synthetic_advs_imps, n_adv=12, n_imp=2000, spread=1.0, n_types=10),
                [0],
                alg=[exp_avg_pred, trivial])

        df1 = synthetic_data1(pred=bind(dual_base, epsilon=0.02))
        df2 = synthetic_data1(pred=bind(dual_base, epsilon=0.2))
        df3 = synthetic_data2(pred=bind(dual_base, epsilon=0.02))
        df4 = synthetic_data2(pred=bind(dual_base, epsilon=0.2))

        dfs = [df1, df2, df3, df4]
        dfs = simulateall(dfs, ref="plot1b-triv")
        plot_dfs(dfs, plot_title=False, rows=2, # spacing=1.0,
                alter_fn=lambda x: "$" + re.search('.sigma=[\d\.]+', x).group(0) + ",\\ " + re.search('.epsilon=[\d\.]+', x).group(0) + "$")

    def plot_synthetic3():
        alpha = 5

        fixed_synthetic = bind(synthetic_advs_imps, n_adv=12, n_imp=2000, spread=0.2, n_types=10)
        df1 = data(
                fixed_synthetic,
                [0],
                alphas=[alpha],
                pred=[bind(dual_base, epsilon=epsilon) for epsilon in np.linspace(0.01, 1, 50)],
                label=["DualBase"])

        df2 = data(
                fixed_synthetic,
                [0],
                alphas=[alpha],
                corruption=[bind(corrupt_prediction_rnd, corr_prob=p) for p in np.linspace(0, 1, 50)],
                label=["OPT random corruption"])

        df3 = data(
                fixed_synthetic,
                [0],
                alphas=[alpha],
                corruption=[bind(corrupt_prediction_biased, corr_prob=p) for p in np.linspace(0, 1, 50)],
                label=["OPT biased corruption"])

        dfs = [df1, df2, df3]
        dfs = simulateall(dfs, ref=f"plot1c-{alpha}")
        plot_dfs2(dfs) # , rows=1)

    def plot_worst_case():
        worst_case_data = partial(data, worst_case_advs_imps, [0], alg=[exp_avg_pred, trivial])

        df1 = worst_case_data()

        df2 = worst_case_data(pred=bind(dual_base, epsilon=0.1))

        df4 = worst_case_data(corruption=bind(corrupt_prediction_biased, corr_prob=0.75))

        df3 = worst_case_data(corruption=bind(corrupt_prediction_biased, corr_prob=0.5))

        dfs = [df1, df2, df3, df4]
        dfs = simulateall(dfs, ref=f"plot2-triv")
        plot_dfs(dfs, rows=2) # alter_fn=lambda x: re.sub("biased corruption", "b-corr.", x), spacing=1.0)
        n_trials = 1

    def plot_real_world1():
        ipinyou_data3 = partial(data, ipinyou_nrows, ipinyou_days_15[1:], alg=[exp_avg_pred, trivial])
        ipinyou_budgeted = bind(ipinyou_data3, quota=bind(constant_quota, quota=10))

        df1 = ipinyou_budgeted()
        df2 = ipinyou_budgeted(pred=dual_base)
        df3 = ipinyou_budgeted(corruption=bind(corrupt_prediction_biased, corr_prob=0.5))
        df4 = ipinyou_budgeted(corruption=bind(corrupt_prediction_biased, corr_prob=0.9))
        df5 = ipinyou_budgeted(pred=bind(dual_base, epsilon=1), pred_day_offset=1)

        dfs = [df1, df2, df3, df4, df5]
        dfs = simulateall(dfs, ref="plot3a-triv")
        plot_dfs(dfs, broken=True, labels=(None, None, None, None, "PreviousDay"), rows=2,
                ylim=((0.89, 2.1), (0.92, 1.02)),
                ylabel_offset=-2.9)

    def plot_real_world2():
        df1 = yahoo_data(quota=create_advertisers_rnd_quota, alg=[exp_avg_pred, trivial])

        df2 = yahoo_data(
                quota=create_advertisers_rnd_quota,
                pred=dual_base,
                alg=[exp_avg_pred, trivial])

        df3 = yahoo_data(
                quota=create_advertisers_rnd_quota,
                corruption=bind(corrupt_prediction_rnd, corr_prob=0.9),
                alg=[exp_avg_pred, trivial])

        df4 = yahoo_data(
                quota=create_advertisers_rnd_quota,
                corruption=bind(corrupt_prediction_biased, corr_prob=0.9),
                alg=[exp_avg_pred, trivial])

        df5 = yahoo_data(
                quota=create_advertisers_rnd_quota,
                pred=bind(dual_base, epsilon=1),
                pred_day_offset=1,
                alg=[exp_avg_pred, trivial])

        dfs_ = [df1, df2, df3, df4, df5]
        dfs = simulateall(dfs_, ref="plot3b-triv__")

        plot_dfs(dfs, broken=True, ylim=((0.75, 1.5), (0.85, 1.025)),
                labels=(None, None, None, None, "PreviousDay"),
                ylabel_offset=-0.2, rows=2)
                # alter_fn=lambda x: re.sub("(iased|andom) corruption", "-corr.", x))

    def plot_real_world3():
        ipinyou_data3 = partial(data, ipinyou_nrows, ipinyou_days_15[1:])
        ipinyou_budgeted = bind(ipinyou_data3, quota=bind(constant_quota, quota=10))

        alpha = 5
        df1 = ipinyou_budgeted(
                alphas=[alpha],
                pred=[bind(dual_base, epsilon=epsilon) for epsilon in np.linspace(0.01, 0.5, 20)],
                label=["DualBase"])

        df2 = ipinyou_budgeted(
                alphas=[alpha],
                corruption=[bind(corrupt_prediction_rnd, corr_prob=p) for p in np.linspace(0, 0.5, 20)],
                label=["OPT random corruption"])

        df3 = ipinyou_budgeted(
                alphas=[alpha],
                corruption=[bind(corrupt_prediction_biased, corr_prob=p) for p in np.linspace(0, 0.5, 20)],
                label=["OPT biased corruption"])

        dfs = [df1, df2, df3]
        dfs = simulateall(dfs, ref="plot3c")
        plot_dfs2(dfs)

    def plot_real_world4():
        alpha=5
        df1 = yahoo_data(
                quota=create_advertisers_rnd_quota,
                alphas=[alpha],
                pred=[bind(dual_base, epsilon=epsilon) for epsilon in np.linspace(0.01, 0.5, 10)],
                label=["DualBase"])

        df2 = yahoo_data(
                quota=create_advertisers_rnd_quota,
                alphas=[alpha],
                corruption=[bind(corrupt_prediction_rnd, corr_prob=p) for p in np.linspace(0, 0.5, 10)],
                label=["OPT random corruption"])

        df3 = yahoo_data(
                quota=create_advertisers_rnd_quota,
                alphas=[alpha],
                corruption=[bind(corrupt_prediction_biased, corr_prob=p) for p in np.linspace(0, 0.5, 10)],
                label=["OPT biased corruption"])

        dfs = [df1, df2, df3]
        dfs = simulateall(dfs, ref="plot3d")
        plot_dfs2(dfs)

    def plot_gap():
        adwords_data = partial(
                data,
                bind(adwords_advs_imps, n_adv=10, n_imp=1000, spread=0.5, n_types=5),
                [0],
                # alg=[gap_pred],
                corruption=bind(corrupt_prediction_rnd, corr_prob=0.5))
        df1 = adwords_data(alg=[gap_pred, trivial])
        df2 = adwords_data(alg=[mahdian])
        dfs = [df1, df2]
        dfs = simulateall(dfs, ref="plot5a")
        plot_dfs(dfs, labels=("GAP ExpAvg", "Mahdian"))



    plot_synthetic1()
