def budget_balanced_trade_reduction(market:Market, ps_recipe:list, including_gft_0:bool = True):
    """
    Calculate the trade and prices using generalized-trade-reduction.
    :param market:   contains a list of k categories, each containing several agents.
    :param ps_recipe:  a list of integers, one integer per category.
                       Each integer i represents the number of agents of category i
                       that should be in each procurement-set.
    :return: Trade object, representing the trade and prices.

    >>> market = Market([AgentCategory("seller", [-1, -2, -3, -4, -5, -7, -8, -10, -11]),AgentCategory("buyer", [17, 14, 13, 9, 6])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [2, 1]))
    Traders: [seller: [-1, -2, -3, -4, -5, -7, -8, -10, -11], buyer: [17, 14, 13, 9, 6]]
    seller: [-1, -2, -3, -4]: all 4 agents trade and pay -5
    buyer: [17, 14, 13]: random 2 out of 3 agents trade and pay 10.0

    >>> market = Market([AgentCategory("mediator", [-3, -4, -5, -6, -7, -8, -9, -10]),AgentCategory("seller", [-1, -2, -3, -4, -5, -6, -7, -8]),AgentCategory("buyer", [17, 16, 15, 14, 13, 12, 10, 6])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [2,3,2]))
    Traders: [mediator: [-3, -4, -5, -6, -7, -8, -9, -10], seller: [-1, -2, -3, -4, -5, -6, -7, -8], buyer: [17, 16, 15, 14, 13, 12, 10, 6]]
    mediator: [-3, -4]: all 2 agents trade and pay -5
    seller: [-1, -2, -3, -4, -5]: random 3 out of 5 agents trade and pay -5.333333333333333
    buyer: [17, 16, 15, 14]: random 2 out of 4 agents trade and pay 13


    >>> market = Market([AgentCategory("seller", [-2, -4, -6, -8, -10, -12, -14]),AgentCategory("buyer", [20, 18, 16, 9, 2, 1])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [2,3]))
    Traders: [seller: [-2, -4, -6, -8, -10, -12, -14], buyer: [20, 18, 16, 9, 2, 1]]
    seller: [-2, -4]: all 2 agents trade and pay -6
    buyer: [20, 18, 16, 9]: random 3 out of 4 agents trade and pay 4.0

    >>> # Multi trade
    >>> market = Market([AgentCategory("buyer", [17, 14, 13, 9, 6]),AgentCategory("seller", [-1, -2, -3, -4, -5, -7, -8, -10, -11])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1, 2]))
    Traders: [buyer: [17, 14, 13, 9, 6], seller: [-1, -2, -3, -4, -5, -7, -8, -10, -11]]
    buyer: [17, 14]: all 2 agents trade and pay 13
    seller: [-1, -2, -3, -4, -5]: random 4 out of 5 agents trade and pay -6.5

    >>> market = Market([AgentCategory("buyer", [17, 16, 15, 14, 13, 12, 10, 6]),AgentCategory("mediator", [-3, -4, -5, -6, -7, -8, -9, -10]),AgentCategory("seller", [-1, -2, -3, -4, -5, -6, -7, -8])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [2,2,3]))
    Traders: [buyer: [17, 16, 15, 14, 13, 12, 10, 6], mediator: [-3, -4, -5, -6, -7, -8, -9, -10], seller: [-1, -2, -3, -4, -5, -6, -7, -8]]
    buyer: [17, 16]: all 2 agents trade and pay 15
    mediator: [-3, -4]: all 2 agents trade and pay -5
    seller: [-1, -2, -3, -4, -5, -6]: random 3 out of 6 agents trade and pay -6.666666666666667

    >>> market = Market([AgentCategory("buyer", [20, 18, 16, 9, 2, 1]),AgentCategory("seller", [-2, -4, -6, -8, -10, -12, -14])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [3,2]))
    Traders: [buyer: [20, 18, 16, 9, 2, 1], seller: [-2, -4, -6, -8, -10, -12, -14]]
    buyer: [20, 18, 16, 9]: random 3 out of 4 agents trade and pay 6.666666666666667
    seller: [-2, -4, -6, -8]: random 2 out of 4 agents trade and pay -10

    >>> # Multi trade
    >>> market = Market([AgentCategory("seller", [-1, -2, -2, -3]),AgentCategory("buyer", [2, 2, 2, 3])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1, 1]))
    Traders: [seller: [-1, -2, -2, -3], buyer: [3, 2, 2, 2]]
    seller: [-1, -2, -2]: all 3 agents trade and pay -2.0
    buyer: [3, 2, 2]: all 3 agents trade and pay 2

    >>> # Multi trade
    >>> market = Market([AgentCategory("buyer", [2, 2, 2, 3]),AgentCategory("seller", [-1, -2, -2, -3])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1, 1]))
    Traders: [buyer: [3, 2, 2, 2], seller: [-1, -2, -2, -3]]
    buyer: [3, 2]: all 2 agents trade and pay 2
    seller: [-1, -2, -2]: random 2 out of 3 agents trade and pay -2.0

    >>> market = Market([AgentCategory("buyer", [17, 16, 15, 14, 13, 12, 10, 6]),AgentCategory("mediator", [-3, -4, -5, -6, -7, -8, -9, -10]),AgentCategory("seller", [-1, -2, -3, -4, -5, -6, -7, -8])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [2,2,3]))
    Traders: [buyer: [17, 16, 15, 14, 13, 12, 10, 6], mediator: [-3, -4, -5, -6, -7, -8, -9, -10], seller: [-1, -2, -3, -4, -5, -6, -7, -8]]
    buyer: [17, 16]: all 2 agents trade and pay 15
    mediator: [-3, -4]: all 2 agents trade and pay -5
    seller: [-1, -2, -3, -4, -5, -6]: random 3 out of 6 agents trade and pay -6.666666666666667

    >>> # ONE BUYER, ONE SELLER
    >>> market = Market([AgentCategory("buyer", [9.]),  AgentCategory("seller", [-4.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1]))
    Traders: [buyer: [9.0], seller: [-4.0]]
    No trade

    >>> market = Market([AgentCategory("buyer", [9.,8.]),  AgentCategory("seller", [-4.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1]))
    Traders: [buyer: [9.0, 8.0], seller: [-4.0]]
    No trade

    >>> market = Market([AgentCategory("seller", [-4.]), AgentCategory("buyer", [9.,8.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1]))
    Traders: [seller: [-4.0], buyer: [9.0, 8.0]]
    seller: [-4.0]: all 1 agents trade and pay -8.0
    buyer: [9.0]: all 1 agents trade and pay 8.0

    >>> market = Market([AgentCategory("seller", [-4.,-3.]), AgentCategory("buyer", [9.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1]))
    Traders: [seller: [-3.0, -4.0], buyer: [9.0]]
    No trade

    >>> market = Market([AgentCategory("buyer", [9.]), AgentCategory("seller", [-4.,-3.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1]))
    Traders: [buyer: [9.0], seller: [-3.0, -4.0]]
    buyer: [9.0]: all 1 agents trade and pay 4.0
    seller: [-3.0]: all 1 agents trade and pay -4.0

    >>> market = Market([AgentCategory("buyer", [9.,8.]),  AgentCategory("seller", [-4.,-3.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1]))
    Traders: [buyer: [9.0, 8.0], seller: [-3.0, -4.0]]
    buyer: [9.0]: all 1 agents trade and pay 8.0
    seller: [-3.0, -4.0]: random 1 out of 2 agents trade and pay -8.0

    >>> market = Market([AgentCategory("seller", [-4.,-3.]), AgentCategory("buyer", [9.,8.])])
    >>> print(budget_balanced_trade_reduction(market, [1,1]))
    seller: [-3.0]: all 1 agents trade and pay -4.0
    buyer: [9.0, 8.0]: random 1 out of 2 agents trade and pay 4.0

    >>> # ALL POSITIVE VALUES
    >>> market = Market([AgentCategory("buyer1", [4.,3.]), AgentCategory("buyer2", [9.,8.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1]))
    Traders: [buyer1: [4.0, 3.0], buyer2: [9.0, 8.0]]
    buyer1: [4.0]: all 1 agents trade and pay 3.0
    buyer2: [9.0, 8.0]: random 1 out of 2 agents trade and pay -3.0

    >>> # ALL NEGATIVE VALUES
    >>> market = Market([AgentCategory("seller1", [-4.,-3.]), AgentCategory("seller2", [-9.,-8.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1]))
    Traders: [seller1: [-3.0, -4.0], seller2: [-8.0, -9.0]]
    No trade

    >>>
    >>> # ONE BUYER, ONE SELLER, ONE MEDIATOR
    >>> market = Market([AgentCategory("seller", [-4.,-3.]), AgentCategory("buyer", [9.,8.]), AgentCategory("mediator", [-1.,-2.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1,1]))
    Traders: [seller: [-3.0, -4.0], buyer: [9.0, 8.0], mediator: [-1.0, -2.0]]
    seller: [-3.0]: all 1 agents trade and pay -4.0
    buyer: [9.0]: all 1 agents trade and pay 8.0
    mediator: [-1.0, -2.0]: random 1 out of 2 agents trade and pay -4.0

    >>> market = Market([AgentCategory("buyer", [9.,8.]), AgentCategory("mediator", [-1.,-2.]), AgentCategory("seller", [-4.,-3.,-10.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1,1]))
    Traders: [buyer: [9.0, 8.0], mediator: [-1.0, -2.0], seller: [-3.0, -4.0, -10.0]]
    buyer: [9.0]: all 1 agents trade and pay 8.0
    mediator: [-1.0]: all 1 agents trade and pay -2.0
    seller: [-3.0, -4.0]: random 1 out of 2 agents trade and pay -6.0

    >>> market = Market([AgentCategory("buyer", [9.,8.]), AgentCategory("mediator", [-1.,-2.]), AgentCategory("seller", [-4.,-3.,-5.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1,1]))
    Traders: [buyer: [9.0, 8.0], mediator: [-1.0, -2.0], seller: [-3.0, -4.0, -5.0]]
    buyer: [9.0]: all 1 agents trade and pay 8.0
    mediator: [-1.0, -2.0]: random 1 out of 2 agents trade and pay -3.0
    seller: [-3.0, -4.0]: random 1 out of 2 agents trade and pay -5.0

    >>> market = Market([AgentCategory("buyer", [9.,8.]), AgentCategory("mediator", [-1.,-2.]), AgentCategory("seller", [-4.,-3.,-2.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1,1]))
    Traders: [buyer: [9.0, 8.0], mediator: [-1.0, -2.0], seller: [-2.0, -3.0, -4.0]]
    buyer: [9.0]: all 1 agents trade and pay 8.0
    mediator: [-1.0, -2.0]: random 1 out of 2 agents trade and pay -4.0
    seller: [-2.0, -3.0]: random 1 out of 2 agents trade and pay -4.0

    >>> market = Market([AgentCategory("buyer", [9.,8.,7.]), AgentCategory("mediator", [-1.,-2.,-3.]), AgentCategory("seller", [-4.,-3.,-2.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1,1]))
    Traders: [buyer: [9.0, 8.0, 7.0], mediator: [-1.0, -2.0, -3.0], seller: [-2.0, -3.0, -4.0]]
    buyer: [9.0, 8.0]: all 2 agents trade and pay 7.0
    mediator: [-1.0, -2.0]: all 2 agents trade and pay -3.0
    seller: [-2.0, -3.0, -4.0]: random 2 out of 3 agents trade and pay -4.0

    >>> market = Market([AgentCategory("buyer", [9.,8.,4.]), AgentCategory("mediator", [-1.,-2.,-3.]), AgentCategory("seller", [-4.,-3.,-2.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1,1]))
    Traders: [buyer: [9.0, 8.0, 4.0], mediator: [-1.0, -2.0, -3.0], seller: [-2.0, -3.0, -4.0]]
    buyer: [9.0, 8.0]: all 2 agents trade and pay 7.0
    mediator: [-1.0, -2.0]: all 2 agents trade and pay -3.0
    seller: [-2.0, -3.0]: all 2 agents trade and pay -4.0

    """
    if len(ps_recipe) != market.num_categories:
        raise ValueError(
            "There are {} categories but {} elements in the PS recipe".
                format(market.num_categories, len(ps_recipe)))

    logger.info("\n#### Budget-Balanced Trade Reduction\n")
    logger.info(market)
    (optimal_trade, remaining_market) = market.optimal_trade(ps_recipe)
    if len(optimal_trade.procurement_sets) == 0:
        return TradeWithSinglePrice(market.empty_agent_categories(), ps_recipe, [0] * len(ps_recipe))

    highest_negative_ps = remaining_market.get_highest_agents(ps_recipe)
    list_ps_to_compete = optimal_trade.procurement_sets
    if highest_negative_ps:
        list_ps_to_compete = [highest_negative_ps] + list_ps_to_compete
        remaining_market.remove_highest_agents(ps_recipe)

    for category in remaining_market.categories:
        if len(category)==0:
            category.append(-MAX_VALUE)
    logger.info("Optimal trade including one highest non-positive trade, by increasing GFT: {}".format(optimal_trade))
    logger.info("Remaining market: {}".format(remaining_market))

    actual_traders = market.empty_agent_categories()

    # Preparing the order of pivot index for trade_reduction
    pivot_indexes = []
    pivot_index_to_category_index = []
    total = 0
    index = 0
    for agent in ps_recipe:
        pivot_indexes += [total+i for i in range(agent-1, -1, -1)]
        pivot_index_to_category_index += [index for _ in range(agent)]
        total += agent
        index += 1
    found_external = False

    latest_prices = None
    for ps in list_ps_to_compete:
        ps = list(ps)
        if latest_prices is None:
            logger.info("\nCalculating prices for PS {}:".format(ps))
            for pivot_index in pivot_indexes:
                pivot_value = ps[pivot_index]
                if found_external:
                    actual_traders[pivot_index_to_category_index[pivot_index]].append(pivot_value)
                    continue
                pivot_category_index = convert_category_index(ps_recipe, pivot_index)
                pivot_category = market.categories[pivot_category_index]
                logger.info("  Looking for external competition to {} with value {}:".
                            format(pivot_category.name, pivot_value))
                best_containing_PS = remaining_market.best_containing_PS(pivot_category_index, pivot_value)
                best_containing_GFT = sum([best_containing_PS[i]*ps_recipe[i] for i in range(len(best_containing_PS))])
                if best_containing_GFT > 0 or (including_gft_0 and best_containing_GFT == 0):  # EXTERNAL COMPETITION - KEEP TRADER
                    found_external = True
                    logger.info("    best PS is {},{} with GFT {}. It is positive so it is an external competition.".
                                format(best_containing_PS, ps_recipe, best_containing_GFT))
                    prices = market.calculate_prices_by_external_competition(pivot_category_index, pivot_value, best_containing_PS, ps_recipe)
                    logger.info("    Prices are {}".format(prices))
                    latest_prices = prices
                    actual_traders[pivot_index_to_category_index[pivot_index]].append(pivot_value)
                    #for i in range(len(prices)):
                    #    agent_prices = market.categories[i].values
                    #    for value in agent_prices:
                    #        #TODO: should we check if value is greater or equal?
                    #        if value >= prices[i]:
                    #            actual_traders[i].append(value)
                    #break  # done with current PS - move to next PS
                else:  # NO EXTERNAL COMPETITION - REMOVE TRADER
                    logger.info("    Best PS is {},{} with GFT {}. It is negative so it is not an external competition.".
                                format(best_containing_PS, ps_recipe, best_containing_GFT))
                    logger.info("    Remove {} {} from trade and add to remaining market".
                                format(pivot_category.name, pivot_value))
                    ps[pivot_index] = None
                    remaining_market.append_trader(pivot_category_index, pivot_value)
                    logger.info("    Remaining market is now: {}".format(remaining_market))
        else:
            logger.info("\nPrices for PS {} are {}".format(ps, latest_prices))
            #print(pivot_index_to_category_index)
            for pivot_index in pivot_indexes:
                pivot_value = ps[pivot_index]
                actual_traders[pivot_index_to_category_index[pivot_index]].append(pivot_value)
    logger.info("\n")
    result = TradeWithSinglePrice(actual_traders, ps_recipe, latest_prices)
    logger.info(result)
    return result
示例#2
0
def budget_balanced_trade_reduction(market: Market, ps_recipe: list):
    """
    Calculate the trade and prices using generalized-trade-reduction.
    :param market:   contains a list of k categories, each containing several agents.
    :param ps_recipe:  a list of integers, one integer per category.
                       Each integer i represents the number of agents of category i
                       that should be in each procurement-set.
    :return: Trade object, representing the trade and prices.

    >>> # ONE BUYER, ONE SELLER
    >>> market = Market([AgentCategory("buyer", [9.]),  AgentCategory("seller", [-4.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1]))
    Traders: [buyer: [9.0], seller: [-4.0]]
    No trade

    >>> market = Market([AgentCategory("buyer", [9.,8.]),  AgentCategory("seller", [-4.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1]))
    Traders: [buyer: [9.0, 8.0], seller: [-4.0]]
    No trade

    >>> market = Market([AgentCategory("seller", [-4.]), AgentCategory("buyer", [9.,8.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1]))
    Traders: [seller: [-4.0], buyer: [9.0, 8.0]]
    seller: [-4.0]: all 1 agents trade and pay -8.0
    buyer: [9.0]: all 1 agents trade and pay 8.0

    >>> market = Market([AgentCategory("seller", [-4.,-3.]), AgentCategory("buyer", [9.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1]))
    Traders: [seller: [-3.0, -4.0], buyer: [9.0]]
    No trade

    >>> market = Market([AgentCategory("buyer", [9.]), AgentCategory("seller", [-4.,-3.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1]))
    Traders: [buyer: [9.0], seller: [-3.0, -4.0]]
    buyer: [9.0]: all 1 agents trade and pay 4.0
    seller: [-3.0]: all 1 agents trade and pay -4.0

    >>> market = Market([AgentCategory("buyer", [9.,8.]),  AgentCategory("seller", [-4.,-3.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1]))
    Traders: [buyer: [9.0, 8.0], seller: [-3.0, -4.0]]
    buyer: [9.0]: all 1 agents trade and pay 8.0
    seller: [-3.0, -4.0]: random 1 out of 2 agents trade and pay -8.0

    >>> market = Market([AgentCategory("seller", [-4.,-3.]), AgentCategory("buyer", [9.,8.])])
    >>> print(budget_balanced_trade_reduction(market, [1,1]))
    seller: [-3.0]: all 1 agents trade and pay -4.0
    buyer: [9.0, 8.0]: random 1 out of 2 agents trade and pay 4.0

    >>> # ALL POSITIVE VALUES
    >>> market = Market([AgentCategory("buyer1", [4.,3.]), AgentCategory("buyer2", [9.,8.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1]))
    Traders: [buyer1: [4.0, 3.0], buyer2: [9.0, 8.0]]
    buyer1: [4.0]: all 1 agents trade and pay 3.0
    buyer2: [9.0, 8.0]: random 1 out of 2 agents trade and pay -3.0

    >>> # ALL NEGATIVE VALUES
    >>> market = Market([AgentCategory("seller1", [-4.,-3.]), AgentCategory("seller2", [-9.,-8.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1]))
    Traders: [seller1: [-3.0, -4.0], seller2: [-8.0, -9.0]]
    No trade

    >>>
    >>> # ONE BUYER, ONE SELLER, ONE MEDIATOR
    >>> market = Market([AgentCategory("seller", [-4.,-3.]), AgentCategory("buyer", [9.,8.]), AgentCategory("mediator", [-1.,-2.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1,1]))
    Traders: [seller: [-3.0, -4.0], buyer: [9.0, 8.0], mediator: [-1.0, -2.0]]
    seller: [-3.0]: all 1 agents trade and pay -4.0
    buyer: [9.0]: all 1 agents trade and pay 8.0
    mediator: [-1.0, -2.0]: random 1 out of 2 agents trade and pay -4.0

    >>> market = Market([AgentCategory("buyer", [9.,8.]), AgentCategory("mediator", [-1.,-2.]), AgentCategory("seller", [-4.,-3.,-10.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1,1]))
    Traders: [buyer: [9.0, 8.0], mediator: [-1.0, -2.0], seller: [-3.0, -4.0, -10.0]]
    buyer: [9.0]: all 1 agents trade and pay 8.0
    mediator: [-1.0]: all 1 agents trade and pay -2.0
    seller: [-3.0, -4.0]: random 1 out of 2 agents trade and pay -6.0

    >>> market = Market([AgentCategory("buyer", [9.,8.]), AgentCategory("mediator", [-1.,-2.]), AgentCategory("seller", [-4.,-3.,-5.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1,1]))
    Traders: [buyer: [9.0, 8.0], mediator: [-1.0, -2.0], seller: [-3.0, -4.0, -5.0]]
    buyer: [9.0]: all 1 agents trade and pay 8.0
    mediator: [-1.0, -2.0]: random 1 out of 2 agents trade and pay -3.0
    seller: [-3.0, -4.0]: random 1 out of 2 agents trade and pay -5.0

    >>> market = Market([AgentCategory("buyer", [9.,8.]), AgentCategory("mediator", [-1.,-2.]), AgentCategory("seller", [-4.,-3.,-2.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1,1]))
    Traders: [buyer: [9.0, 8.0], mediator: [-1.0, -2.0], seller: [-2.0, -3.0, -4.0]]
    buyer: [9.0]: all 1 agents trade and pay 8.0
    mediator: [-1.0, -2.0]: random 1 out of 2 agents trade and pay -4.0
    seller: [-2.0, -3.0]: random 1 out of 2 agents trade and pay -4.0

    >>> market = Market([AgentCategory("buyer", [9.,8.,7.]), AgentCategory("mediator", [-1.,-2.,-3.]), AgentCategory("seller", [-4.,-3.,-2.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1,1]))
    Traders: [buyer: [9.0, 8.0, 7.0], mediator: [-1.0, -2.0, -3.0], seller: [-2.0, -3.0, -4.0]]
    buyer: [9.0, 8.0]: all 2 agents trade and pay 7.0
    mediator: [-1.0, -2.0]: all 2 agents trade and pay -3.0
    seller: [-2.0, -3.0]: all 2 agents trade and pay -4.0

    >>> market = Market([AgentCategory("buyer", [9.,8.,4.]), AgentCategory("mediator", [-1.,-2.,-3.]), AgentCategory("seller", [-4.,-3.,-2.])])
    >>> print(market); print(budget_balanced_trade_reduction(market, [1,1,1]))
    Traders: [buyer: [9.0, 8.0, 4.0], mediator: [-1.0, -2.0, -3.0], seller: [-2.0, -3.0, -4.0]]
    buyer: [9.0, 8.0]: all 2 agents trade and pay 7.0
    mediator: [-1.0, -2.0]: all 2 agents trade and pay -3.0
    seller: [-2.0, -3.0]: all 2 agents trade and pay -4.0

    """
    if len(ps_recipe) != market.num_categories:
        raise ValueError(
            "There are {} categories but {} elements in the PS recipe".format(
                market.num_categories, len(ps_recipe)))

    if any(r != 1 for r in ps_recipe):
        raise ValueError(
            "Currently, the trade-reduction protocol supports only recipes of ones; {} was given"
            .format(ps_recipe))

    logger.info("\n#### Budget-Balanced Trade Reduction\n")
    logger.info(market)
    (optimal_trade, remaining_market) = market.optimal_trade(ps_recipe)
    for category in remaining_market.categories:
        if len(category) == 0:
            category.append(-MAX_VALUE)
    logger.info("Optimal trade, by increasing GFT: {}".format(optimal_trade))
    logger.info("Remaining market: {}".format(remaining_market))

    actual_traders = market.empty_agent_categories()

    latest_prices = None
    for ps in optimal_trade.procurement_sets:
        ps = list(ps)
        if latest_prices is None:
            logger.info("\nCalculating prices for PS {}:".format(ps))
            for pivot_index in range(len(ps)):
                pivot_value = ps[pivot_index]
                pivot_category = market.categories[pivot_index]
                logger.info(
                    "  Looking for external competition to {} with value {}:".
                    format(pivot_category.name, pivot_value))
                best_containing_PS = remaining_market.best_containing_PS(
                    pivot_index, pivot_value)
                best_containing_GFT = sum(best_containing_PS)
                if best_containing_GFT > 0:  # EXTERNAL COMPETITION - KEEP TRADER
                    logger.info(
                        "    best PS is {} with GFT {}. It is positive so it is an external competition."
                        .format(best_containing_PS, best_containing_GFT))
                    prices = market.calculate_prices_by_external_competition(
                        pivot_index, pivot_value, best_containing_PS)
                    logger.info("    Prices are {}".format(prices))
                    latest_prices = prices
                    for i in range(market.num_categories):
                        if ps[i] is not None:
                            actual_traders[i].append(ps[i])
                    break  # done with current PS - move to next PS
                else:  # NO EXTERNAL COMPETITION - REMOVE TRADER
                    logger.info(
                        "    Best PS is {} with GFT {}. It is negative so it is not an external competition."
                        .format(best_containing_PS, best_containing_GFT))
                    logger.info(
                        "    Remove {} {} from trade and add to remaining market"
                        .format(pivot_category.name, pivot_value))
                    ps[pivot_index] = None
                    remaining_market.append_trader(pivot_index, pivot_value)
                    logger.info("    Remaining market is now: {}".format(
                        remaining_market))
        else:
            logger.info("\nPrices for PS {} are {}".format(ps, latest_prices))
            for i in range(market.num_categories):
                if ps[i] is not None:
                    actual_traders[i].append(ps[i])

    logger.info("\n")
    return TradeWithSinglePrice(actual_traders, ps_recipe, latest_prices)