Ejemplo n.º 1
0
 def __init__(
     self,
     folder: Union[str, Path],
     id: str = None,
     callback: Callable[[NamedObject, int], None] = None,
     watch: bool = False,
     object_type: Type[NamedObject] = NamedObject,
 ):
     self.__folder = Path(folder).absolute()
     if id is None:
         pattern = "*.json"
     else:
         pattern = "*id*.json"
     self.__infos = [load(_) for _ in self.__folder.glob(pattern)]
     self.__files = dict(
         zip(
             (_["step"] for _ in self.__infos), (_["filename"] for _ in self.__infos)
         )
     )
     self.__sorted_steps = sorted(list(self.__files.keys()))
     self._step_index = -1
     self.__object: NamedObject = None
     self.__object_type = object_type
     self.__callbacks = []
     if callback is not None:
         self.register_callback(callback)
     self.__watch = watch
     if watch:
         raise NotImplementedError("File watching is not implemented yet")
Ejemplo n.º 2
0
def create(
    ctx,
    name,
    steps,
    ttype,
    timeout,
    log,
    verbosity,
    reveal_names,
    runs,
    configs,
    max_runs,
    competitors,
    world_config,
    jcompetitors,
    non_competitors,
    compact,
    factories,
    agents,
    log_ufuns,
    log_negs,
    raise_exceptions,
    steps_min,
    steps_max,
    path,
    cw,
):
    if len(path) > 0:
        sys.path.append(path)
    kwargs = {}
    if world_config is not None and len(world_config) > 0:
        for wc in world_config:
            kwargs.update(load(wc))
    warning_n_runs = 2000
    if timeout <= 0:
        timeout = None
    if name == "random":
        name = unique_name(base="", rand_digits=0)
    ctx.obj["tournament_name"] = name
    if max_runs <= 0:
        max_runs = None
    if compact:
        log_ufuns = False

    if not compact:
        if not reveal_names:
            print(
                "You are running the tournament with --debug. Will reveal agent types in their names"
            )
        reveal_names = True
        verbosity = max(1, verbosity)

    worlds_per_config = (None if max_runs is None else int(
        round(max_runs / (configs * runs))))

    all_competitors = competitors.split(";")
    for i, cp in enumerate(all_competitors):
        if "." not in cp:
            all_competitors[i] = "negmas.apps.scml.factory_managers." + cp
    all_competitors_params = [dict() for _ in range(len(all_competitors))]
    if jcompetitors is not None and len(jcompetitors) > 0:
        jcompetitor_params = [{
            "java_class_name": _
        } for _ in jcompetitors.split(";")]
        for jp in jcompetitor_params:
            if "." not in jp["java_class_name"]:
                jp["java_class_name"] = (
                    "jnegmas.apps.scml.factory_managers." +
                    jp["java_class_name"])
        jcompetitors = ["negmas.apps.scml.JavaFactoryManager"
                        ] * len(jcompetitor_params)
        all_competitors += jcompetitors
        all_competitors_params += jcompetitor_params
        print(
            "You are using some Java agents. The tournament MUST run serially")
        if not jnegmas_bridge_is_running():
            print(
                "Error: You are using java competitors but jnegmas bridge is not running\n\nTo correct this issue"
                " run the following command IN A DIFFERENT TERMINAL because it will block:\n\n"
                "$ negmas jnegmas")
            exit(1)

    # if ttype.lower() == "anac2019std":
    #     if (
    #         "negmas.apps.scml.factory_managers.GreedyFactoryManager"
    #         not in all_competitors
    #     ):
    #         all_competitors.append(
    #             "negmas.apps.scml.factory_managers.GreedyFactoryManager"
    #         )
    #         all_competitors_params.append({})

    permutation_size = len(all_competitors) if "sabotage" not in ttype else 1
    recommended = runs * configs * permutation_size
    if worlds_per_config is not None and worlds_per_config < 1:
        print(
            f"You need at least {(configs * runs)} runs even with a single permutation of managers."
            f".\n\nSet --max-runs to at least {(configs * runs)} (Recommended {recommended})"
        )
        return

    if max_runs is not None and max_runs < recommended:
        print(
            f"You are running {max_runs} worlds only but it is recommended to set {max_runs} to at least "
            f"{recommended}. Will continue")

    if ttype == "anac2019std":
        agents = 1

    if steps is None:
        steps = (steps_min, steps_max)

    if worlds_per_config is None:
        n_comp = len(all_competitors) if ttype != "anac2019sabotage" else 2
        n_worlds = permutation_size * runs * configs
        if n_worlds > warning_n_runs:
            print(
                f"You are running the maximum possible number of permutations for each configuration. This is roughly"
                f" {n_worlds} simulations (each for {steps} steps). That will take a VERY long time."
                f"\n\nYou can reduce the number of simulations by setting --configs>=1 (currently {configs}) or "
                f"--runs>= 1 (currently {runs}) to a lower value. "
                f"\nFinally, you can limit the maximum number of worlds to run by setting --max-runs=integer."
            )
            # if (
            #     not input(f"Are you sure you want to run {n_worlds} simulations?")
            #     .lower()
            #     .startswith("y")
            # ):
            #     exit(0)
            max_runs = int(
                input(
                    f"Input the maximum number of simulations to run. Zero to run all of the {n_worlds} "
                    f"simulations. ^C or a negative number to exit [0 : {n_worlds}]:"
                ))
            if max_runs == 0:
                max_runs = None
            if max_runs is not None and max_runs < 0:
                exit(0)
            worlds_per_config = (None if max_runs is None else int(
                round(max_runs / (configs * runs))))

    if len(jcompetitors) > 0:
        print(
            "You are using java-competitors. The tournament will be run serially"
        )
        parallelism = "serial"

    non_competitor_params = None
    if len(non_competitors) < 1:
        non_competitors = None
    else:
        non_competitors = non_competitors.split(";")
        for i, cp in enumerate(non_competitors):
            if "." not in cp:
                non_competitors[i] = "negmas.apps.scml.factory_managers." + cp

    if ttype.lower() == "anac2019std":
        if non_competitors is None:
            non_competitors = (DefaultGreedyManager, )
            non_competitor_params = ({}, )
        print(
            f"Tournament will be run between {len(all_competitors)} agents: ")
        pprint(all_competitors)
        print("Non-competitors are: ")
        pprint(non_competitors)
        results = create_tournament(
            competitors=all_competitors,
            competitor_params=all_competitors_params,
            non_competitors=non_competitors,
            non_competitor_params=non_competitor_params,
            agent_names_reveal_type=reveal_names,
            n_competitors_per_world=cw,
            n_configs=configs,
            n_runs_per_world=runs,
            max_worlds_per_config=worlds_per_config,
            base_tournament_path=log,
            total_timeout=timeout,
            name=name,
            verbose=verbosity > 0,
            n_agents_per_competitor=1,
            world_generator=anac2019_world_generator,
            config_generator=anac2019_config_generator,
            config_assigner=anac2019_assigner,
            score_calculator=balance_calculator,
            min_factories_per_level=factories,
            compact=compact,
            n_steps=steps,
            log_ufuns=log_ufuns,
            log_negotiations=log_negs,
            ignore_agent_exceptions=not raise_exceptions,
            ignore_contract_execution_exceptions=not raise_exceptions,
            **kwargs,
        )
    elif ttype.lower() in ("anac2019collusion", "anac2019"):
        print(
            f"Tournament will be run between {len(all_competitors)} agents: ")
        pprint(all_competitors)
        print("Non-competitors are: ")
        pprint(non_competitors)
        results = create_tournament(
            competitors=all_competitors,
            competitor_params=all_competitors_params,
            non_competitors=non_competitors,
            non_competitor_params=non_competitor_params,
            agent_names_reveal_type=reveal_names,
            n_competitors_per_world=cw,
            n_configs=configs,
            n_runs_per_world=runs,
            max_worlds_per_config=worlds_per_config,
            base_tournament_path=log,
            total_timeout=timeout,
            name=name,
            verbose=verbosity > 0,
            n_agents_per_competitor=agents,
            world_generator=anac2019_world_generator,
            config_generator=anac2019_config_generator,
            config_assigner=anac2019_assigner,
            score_calculator=balance_calculator,
            min_factories_per_level=factories,
            compact=compact,
            n_steps=steps,
            log_ufuns=log_ufuns,
            log_negotiations=log_negs,
            ignore_agent_exceptions=not raise_exceptions,
            ignore_contract_execution_exceptions=not raise_exceptions,
            **kwargs,
        )
    elif ttype.lower() == "anac2019sabotage":
        print(
            f"Tournament will be run between {len(all_competitors)} agents: ")
        pprint(all_competitors)
        print("Non-competitors are: ")
        pprint(non_competitors)
        results = create_tournament(
            competitors=all_competitors,
            competitor_params=all_competitors_params,
            agent_names_reveal_type=reveal_names,
            n_agents_per_competitor=agents,
            base_tournament_path=log,
            total_timeout=timeout,
            name=name,
            verbose=verbosity > 0,
            n_runs_per_world=runs,
            n_configs=configs,
            max_worlds_per_config=worlds_per_config,
            non_competitors=non_competitors,
            min_factories_per_level=factories,
            n_steps=steps,
            compact=compact,
            log_ufuns=log_ufuns,
            log_negotiations=log_negs,
            ignore_agent_exceptions=not raise_exceptions,
            ignore_contract_execution_exceptions=not raise_exceptions,
            non_competitor_params=non_competitor_params,
            world_generator=anac2019_world_generator,
            config_generator=anac2019_sabotage_config_generator,
            config_assigner=anac2019_sabotage_assigner,
            score_calculator=sabotage_effectiveness,
            **kwargs,
        )
    else:
        raise ValueError(f"Unknown tournament type {ttype}")
    ctx.obj["tournament_name"] = results.name
    ctx.obj["tournament_log_folder"] = log
    ctx.obj["compact"] = compact
    print(
        f"Saved all configs to {str(results)}\nTournament name is {results.name}"
    )
Ejemplo n.º 3
0
def create(
    ctx,
    name,
    timeout,
    log,
    verbosity,
    reveal_names,
    runs,
    configs,
    max_runs,
    competitors,
    world_config,
    jcompetitors,
    non_competitors,
    compact,
    agents,
    log_ufuns,
    log_negs,
    raise_exceptions,
    steps_min,
    steps_max,
    path,
    cw,
    world_generator,
    config_generator,
    assigner,
    scorer,
    java_interop_class,
):
    if len(config_generator is None or config_generator.strip()) == 0:
        print(
            "ERROR: You did not specify a config generator. Use --config-generator to specify one and see the "
            "documentation of the create_tournament method in negmas.situated for details about it."
            "\nThe following must be explicitly specified to create a tournament: a world-generator, "
            "an assigner, a scorer, and a config-generator.")

        return -4

    if len(world_generator is None or world_generator.strip()) == 0:
        print(
            "ERROR: You did not specify a world generator. Use --world-generator to specify one and see the "
            "documentation of the create_tournament method in negmas.situated for details about it."
            "\nThe following must be explicitly specified to create a tournament: a world-generator, "
            "an assigner, a scorer, and a config-generator.")

        return -3

    if len(assigner is None or assigner.strip()) == 0:
        print(
            "ERROR: You did not specify an assigner. Use --assigner to specify one and see the documentation"
            " of the create_tournament method in negmas.situated for details about it."
            "\nThe following must be explicitly specified to create a tournament: a world-generator, "
            "an assigner, a scorer, and a config-generator.")

        return -2

    if len(scorer is None or scorer.strip()) == 0:
        print(
            "ERROR: You did not specify a scorer. Use --scorer to specify one and see the documentation of the "
            "create_tournament method in negmas.situated for details about it."
            "\nThe following must be explicitly specified to create a tournament: a world-generator, "
            "an assigner, a scorer, and a config-generator.")

        return -1

    if (jcompetitors is not None and len(jcompetitors) > 0
            and len(java_interop_class.strip()) == 0):
        print(
            f"ERROR: You are passing java competitors but not java interop "
            f"class (use --java-interop-class "
            f" to pass the name of that class or do not pass java competitors"
            f" [--jcompetitors])")

        return -5

    if len(path) > 0:
        sys.path.append(path)
    kwargs = {}

    if world_config is not None and len(world_config) > 0:
        for wc in world_config:
            kwargs.update(load(wc))
    warning_n_runs = 2000

    if timeout <= 0:
        timeout = None

    if name == "random":
        name = unique_name(base="", rand_digits=0)
    ctx.obj["tournament_name"] = name

    if max_runs <= 0:
        max_runs = None

    if compact:
        log_ufuns = False

    if not compact:
        if not reveal_names:
            print("You are running the tournament with --debug. Will reveal "
                  "agent types in their names")
        reveal_names = True
        verbosity = max(1, verbosity)

    worlds_per_config = (None if max_runs is None else int(
        round(max_runs / (configs * runs))))

    all_competitors = competitors.split(";")
    all_competitors_params = [dict() for _ in range(len(all_competitors))]

    if jcompetitors is not None and len(jcompetitors) > 0:
        jcompetitor_params = [{
            "java_class_name": _
        } for _ in jcompetitors.split(";")]
        jcompetitors = [java_interop_class] * len(jcompetitor_params)
        all_competitors += jcompetitors
        all_competitors_params += jcompetitor_params
        print(
            "You are using some Java agents. The tournament MUST run serially")

        if not jnegmas_bridge_is_running():
            print(
                "Error: You are using java competitors but jnegmas bridge is not running\n\nTo correct this issue"
                " run the following command IN A DIFFERENT TERMINAL because it will block:\n\n"
                "$ negmas jnegmas")
            exit(1)

    permutation_size = 1
    recommended = runs * configs * permutation_size

    if worlds_per_config is not None and worlds_per_config < 1:
        print(
            f"You need at least {(configs * runs)} runs even with a single permutation of managers."
            f".\n\nSet --max-runs to at least {(configs * runs)} (Recommended {recommended})"
        )

        return

    if max_runs is not None and max_runs < recommended:
        print(
            f"You are running {max_runs} worlds only but it is recommended to set {max_runs} to at least "
            f"{recommended}. Will continue")

    steps = (steps_min, steps_max)

    if worlds_per_config is None:
        n_comp = len(all_competitors)
        n_worlds = permutation_size * runs * configs

        if n_worlds > warning_n_runs:
            print(
                f"You are running the maximum possible number of permutations for each configuration. This is roughly"
                f" {n_worlds} simulations (each for {steps} steps). That will take a VERY long time."
                f"\n\nYou can reduce the number of simulations by setting --configs>=1 (currently {configs}) or "
                f"--runs>= 1 (currently {runs}) to a lower value. "
                f"\nFinally, you can limit the maximum number of worlds to run by setting --max-runs=integer."
            )
            # if (
            #     not input(f"Are you sure you want to run {n_worlds} simulations?")
            #     .lower()
            #     .startswith("y")
            # ):
            #     exit(0)
            max_runs = int(
                input(
                    f"Input the maximum number of simulations to run. Zero to run all of the {n_worlds} "
                    f"simulations. ^C or a negative number to exit [0 : {n_worlds}]:"
                ))

            if max_runs == 0:
                max_runs = None

            if max_runs is not None and max_runs < 0:
                exit(0)
            worlds_per_config = (None if max_runs is None else int(
                round(max_runs / (configs * runs))))

    if len(jcompetitors) > 0:
        print(
            "You are using java-competitors. The tournament will be run serially"
        )
        parallelism = "serial"

    non_competitor_params = None

    if len(non_competitors) < 1:
        non_competitors = None
    else:
        non_competitors = non_competitors.split(";")

    print(f"Tournament will be run between {len(all_competitors)} agents: ")
    pprint(all_competitors)
    print("Non-competitors are: ")
    pprint(non_competitors)
    results = create_tournament(
        competitors=all_competitors,
        competitor_params=all_competitors_params,
        non_competitors=non_competitors,
        non_competitor_params=non_competitor_params,
        agent_names_reveal_type=reveal_names,
        n_competitors_per_world=cw,
        n_configs=configs,
        n_runs_per_world=runs,
        max_worlds_per_config=worlds_per_config,
        base_tournament_path=log,
        total_timeout=timeout,
        name=name,
        verbose=verbosity > 0,
        n_agents_per_competitor=agents,
        world_generator=world_generator,
        config_generator=config_generator,
        config_assigner=assigner,
        score_calculator=scorer,
        compact=compact,
        n_steps=steps,
        log_ufuns=log_ufuns,
        log_negotiations=log_negs,
        ignore_agent_exceptions=not raise_exceptions,
        ignore_contract_execution_exceptions=not raise_exceptions,
        parallelism=parallelism,
        **kwargs,
    )

    ctx.obj["tournament_name"] = results.name
    ctx.obj["tournament_log_folder"] = log
    ctx.obj["compact"] = compact
    print(
        f"Saved all configs to {str(results)}\nTournament name is {results.name}"
    )
Ejemplo n.º 4
0
def scml(
    steps,
    levels,
    neg_speedup,
    negotiator,
    agents,
    horizon,
    min_consumption,
    max_consumption,
    transport,
    time,
    neg_time,
    neg_steps,
    sign,
    guaranteed,
    lines,
    retrials,
    use_consumer,
    max_insurance,
    riskiness,
    competitors,
    jcompetitors,
    log,
    compact,
    log_ufuns,
    log_negs,
    reserved_value,
    balance,
    shared_profile,
    raise_exceptions,
    path,
    world_config,
):
    kwargs = dict(
        no_bank=True,
        no_insurance=False,
        prevent_cfp_tampering=True,
        ignore_negotiated_penalties=False,
        neg_step_time_limit=10,
        breach_penalty_society=0.02,
        premium=0.03,
        premium_time_increment=0.1,
        premium_breach_increment=0.001,
        max_allowed_breach_level=None,
        breach_penalty_society_min=0.0,
        breach_penalty_victim=0.0,
        breach_move_max_product=True,
        transfer_delay=0,
        start_negotiations_immediately=False,
        catalog_profit=0.15,
        financial_reports_period=10,
        default_price_for_products_without_one=1,
        compensation_fraction=0.5,
    )
    if world_config is not None and len(world_config) > 0:
        for wc in world_config:
            kwargs.update(load(wc))
    if len(path) > 0:
        sys.path.append(path)
    if max_insurance < 0:
        warnings.warn(
            f"Negative max insurance ({max_insurance}) is deprecated. Set --max-insurance=inf for always "
            f"buying and --max-insurance=0.0 for never buying. Will continue assuming --max-insurance=inf"
        )
        max_insurance = float("inf")

    if "." not in negotiator:
        negotiator = "negmas.sao." + negotiator

    params = {
        "steps": steps,
        "levels": levels,
        "neg_speedup": neg_speedup,
        "negotiator": negotiator,
        "agents": agents,
        "horizon": horizon,
        "min_consumption": min_consumption,
        "max_consumption": max_consumption,
        "transport": transport,
        "time": time,
        "neg_time": neg_time,
        "neg_steps": neg_steps,
        "sign": sign,
        "guaranteed": guaranteed,
        "lines": lines,
        "retrials": retrials,
        "use_consumer": use_consumer,
        "max_insurance": max_insurance,
        "riskiness": riskiness,
    }
    if compact:
        log_ufuns = False
        log_negs = False
    neg_speedup = neg_speedup if neg_speedup is not None and neg_speedup > 0 else None
    if min_consumption == max_consumption:
        consumption = min_consumption
    else:
        consumption = (min_consumption, max_consumption)
    customer_kwargs = {
        "negotiator_type": negotiator,
        "consumption_horizon": horizon
    }
    miner_kwargs = {"negotiator_type": negotiator, "n_retrials": retrials}
    factory_kwargs = {
        "negotiator_type": negotiator,
        "n_retrials": retrials,
        "sign_only_guaranteed_contracts": guaranteed,
        "use_consumer": use_consumer,
        "riskiness": riskiness,
        "max_insurance_premium": max_insurance,
        "reserved_value": reserved_value,
    }
    if log.startswith("~/"):
        log_dir = Path.home() / log[2:]
    else:
        log_dir = Path(log)
    world_name = unique_name(base="scml", add_time=True, rand_digits=0)
    log_dir = log_dir / world_name
    log_dir = log_dir.absolute()
    os.makedirs(log_dir, exist_ok=True)

    exception = None

    def _no_default(s):
        return not (s.startswith("negmas.apps.scml")
                    and s.endswith("GreedyFactoryManager"))

    all_competitors = competitors.split(";")
    for i, cp in enumerate(all_competitors):
        if "." not in cp:
            all_competitors[i] = "negmas.apps.scml.factory_managers." + cp
    all_competitors_params = [
        dict() if _no_default(_) else factory_kwargs for _ in all_competitors
    ]
    if jcompetitors is not None and len(jcompetitors) > 0:
        jcompetitor_params = [{
            "java_class_name": _
        } for _ in jcompetitors.split(";")]
        for jp in jcompetitor_params:
            if "." not in jp["java_class_name"]:
                jp["java_class_name"] = (
                    "jnegmas.apps.scml.factory_managers." +
                    jp["java_class_name"])
        jcompetitors = ["negmas.apps.scml.JavaFactoryManager"
                        ] * len(jcompetitor_params)
        all_competitors += jcompetitors
        all_competitors_params += jcompetitor_params
        print(
            "You are using some Java agents. The tournament MUST run serially")
        parallelism = "serial"
        if not jnegmas_bridge_is_running():
            print(
                "Error: You are using java competitors but jnegmas bridge is not running\n\nTo correct this issue"
                " run the following command IN A DIFFERENT TERMINAL because it will block:\n\n"
                "$ negmas jnegmas")
            exit(1)

    world = SCMLWorld.chain_world(
        n_steps=steps,
        negotiation_speed=neg_speedup,
        n_intermediate_levels=levels,
        n_miners=agents,
        n_consumers=agents,
        n_factories_per_level=agents,
        consumption=consumption,
        consumer_kwargs=customer_kwargs,
        miner_kwargs=miner_kwargs,
        default_manager_params=factory_kwargs,
        transportation_delay=transport,
        time_limit=time,
        neg_time_limit=neg_time,
        neg_n_steps=neg_steps,
        default_signing_delay=sign,
        n_lines_per_factory=lines,
        compact=compact,
        agent_names_reveal_type=True,
        log_ufuns=log_ufuns,
        manager_types=all_competitors,
        manager_params=all_competitors_params,
        log_negotiations=log_negs,
        log_folder=log_dir,
        name=world_name,
        shared_profile_per_factory=shared_profile,
        initial_wallet_balances=balance,
        ignore_agent_exceptions=not raise_exceptions,
        ignore_contract_execution_exceptions=not raise_exceptions,
        **kwargs,
    )
    failed = False
    strt = perf_counter()
    try:
        for i in progressbar.progressbar(range(world.n_steps),
                                         max_value=world.n_steps):
            elapsed = perf_counter() - strt
            if world.time_limit is not None and elapsed >= world.time_limit:
                break
            if not world.step():
                break
    except Exception:
        exception = traceback.format_exc()
        failed = True
    elapsed = perf_counter() - strt

    def print_and_log(s):
        world.logdebug(s)
        print(s)

    world.logdebug(f"{pformat(world.stats, compact=True)}")
    world.logdebug(
        f"=================================================\n"
        f"steps: {steps}, horizon: {horizon}, time: {time}, levels: {levels}, agents_per_level: "
        f"{agents}, lines: {lines}, guaranteed: {guaranteed}, negotiator: {negotiator}\n"
        f"consumption: {consumption}"
        f", transport_to: {transport}, sign: {sign}, speedup: {neg_speedup}, neg_steps: {neg_steps}"
        f", retrials: {retrials}"
        f", neg_time: {neg_time}\n"
        f"==================================================")

    save_stats(world=world, log_dir=log_dir, params=params)

    if len(world.saved_contracts) > 0:
        data = pd.DataFrame(world.saved_contracts)
        data = data.sort_values(["delivery_time"])
        data = data.loc[data.signed_at >= 0, [
            "seller_type",
            "buyer_type",
            "seller_name",
            "buyer_name",
            "delivery_time",
            "unit_price",
            "quantity",
            "product_name",
            "n_neg_steps",
            "signed_at",
        ], ]
        data.columns = [
            "seller_type",
            "buyer_type",
            "seller",
            "buyer",
            "t",
            "price",
            "q",
            "product",
            "steps",
            "signed",
        ]
        print_and_log(tabulate(data, headers="keys", tablefmt="psql"))

        data["product_id"] = np.array([_.id for _ in data["product"].values])
        d2 = (data.loc[(~(data["signed"].isnull())) &
                       (data["signed"] > -1), :].groupby(
                           ["product_id"]).apply(lambda x: pd.DataFrame([{
                               "uprice":
                               np.sum(x["price"] * x["q"]) / np.sum(x["q"]),
                               "quantity":
                               np.sum(x["q"]),
                           }])))
        d2 = d2.reset_index().sort_values(["product_id"])
        products = dict(zip([_.id for _ in world.products], world.products))
        d2["Product"] = np.array(
            [products[_] for _ in d2["product_id"].values])
        d2 = d2.loc[:, ["Product", "uprice", "quantity"]]
        d2.columns = ["Product", "Avg. Unit Price", "Total Quantity"]
        print_and_log(tabulate(d2, headers="keys", tablefmt="psql"))

        n_executed = sum(world.stats["n_contracts_executed"])
        n_negs = sum(world.stats["n_negotiations"])
        n_contracts = len(world.saved_contracts)
        try:
            agent_scores = sorted(
                [[_.name, world.a2f[_.id].total_balance]
                 for _ in world.agents.values()
                 if isinstance(_, FactoryManager)],
                key=lambda x: x[1],
                reverse=True,
            )
            agent_scores = pd.DataFrame(data=np.array(agent_scores),
                                        columns=["Agent", "Final Balance"])
            print_and_log(
                tabulate(agent_scores, headers="keys", tablefmt="psql"))
        except:
            pass
        winners = [
            f"{_.name} gaining {world.a2f[_.id].total_balance / world.a2f[_.id].initial_balance - 1.0:0.0%}"
            for _ in world.winners
        ]
        print_and_log(
            f"{n_contracts} contracts :-) [N. Negotiations: {n_negs}, Agreement Rate: "
            f"{world.agreement_rate:0.0%}]"
            f" (rounds/successful negotiation: {world.n_negotiation_rounds_successful:5.2f}, "
            f"rounds/broken negotiation: {world.n_negotiation_rounds_failed:5.2f})"
        )
        print_and_log(
            f"Cancelled: {world.cancellation_rate:0.0%}, Executed: {world.contract_execution_fraction:0.0%}"
            f", Breached: {world.breach_rate:0.0%}, N. Executed: {n_executed}, Business size: "
            f"{world.business_size}\n"
            f"Winners: {winners}\n"
            f"Running Time {humanize_time(elapsed)}")
    else:
        print_and_log("No contracts! :-(")
        print_and_log(f"Running Time {humanize_time(elapsed)}")

    if failed:
        print(exception)
        world.logdebug(exception)
        print(f"FAILED at step {world.current_step} of {world.n_steps}\n")