Running a Negotiation --------------------- NegMAS has several built-in negotiation ``Mechanism`` s, negotiation agents (``Negotiator`` s), and ``UtilityFunction`` s. You can use these to run negotiations as follows: .. code:: ipython3 from negmas import SAOMechanism, TimeBasedConcedingNegotiator, MappingUtilityFunction import random # for generating random ufuns random.seed(0) # for reproducibility session = SAOMechanism(outcomes=10, n_steps=100) negotiators = [TimeBasedConcedingNegotiator(name=f"a{_}") for _ in range(5)] for negotiator in negotiators: session.add( negotiator, preferences=MappingUtilityFunction( lambda x: random.random() * x[0], outcome_space=session.outcome_space ), ) print(session.run()) .. raw:: html
SAOState( running=False, waiting=False, started=True, step=95, time=0.021065915992949158, relative_time=0.9504950495049505, broken=False, timedout=False, agreement=(2,), results=None, n_negotiators=5, has_error=False, error_details='', erred_negotiator='', erred_agent='', threads={}, last_thread='', current_offer=(2,), current_proposer='a1-0c64f66e-a435-48c3-9ae7-2b1fc1f961d9', current_proposer_agent=None, n_acceptances=5, new_offers=[], new_offerer_agents=[None, None], last_negotiator='a1', current_data=None, new_data=[] )Negotations end with a status that shows you what happens. In the above example, we can see that the negotiation was not broken and did not time-out. The agreement was on outcome ``(9,)`` of the *10* possible outcomes of this negotiation. That offer was offered by negotiator ``a3`` (the rest of the agent *ID* is always a random value to ensure no name repetitions) in the ``9``\ th round of the negotiation (rounds/steps start at ``0``) and was accepted by all of the other *4* negotiators. The whole negotiation took ``4.66`` ms. Let’s look at this code example line-by-line: .. code:: python session = SAOMechanism(outcomes=10, n_steps=100) The negotiation protocol in NegMAS is handled by a ``Mechanism`` object. Here we instantiate an ``SAOMechanism`` which implements the `Stacked Alternating Offers Protocol
SAOState( running=False, waiting=False, started=True, step=18, time=0.012747249988024123, relative_time=0.9047619047619048, broken=False, timedout=False, agreement=(1, 9, 0), results=None, n_negotiators=2, has_error=False, error_details='', erred_negotiator='', erred_agent='', threads={}, last_thread='', current_offer=(1, 9, 0), current_proposer='seller-d5d50fb8-209f-4d68-a994-a59881fe4c6c', current_proposer_agent=None, n_acceptances=2, new_offers=[], new_offerer_agents=[None, None], last_negotiator='seller', current_data=None, new_data=[] )In this run, we can see that the agreement was on a high price (*9*) which is preferred by the seller but with a delivery time of *8* which is preferred by the buyer. Negotiation took *17* steps out of the allowed *20* (*90%* of the available time) We can check the negotiation history as well by printing the ``extended_trace`` which shows the step, negotiator, and offer for every s tep of the negotiation: .. code:: ipython3 session.extended_trace .. parsed-literal:: [(0, 'buyer-19434e01-7f9c-41a5-88b8-e74428e2e788', (0, 11, 9)), (0, 'seller-d5d50fb8-209f-4d68-a994-a59881fe4c6c', (9, 11, 0)), (1, 'buyer-19434e01-7f9c-41a5-88b8-e74428e2e788', (0, 11, 9)), (1, 'seller-d5d50fb8-209f-4d68-a994-a59881fe4c6c', (9, 11, 0)), (2, 'buyer-19434e01-7f9c-41a5-88b8-e74428e2e788', (0, 11, 9)), (2, 'seller-d5d50fb8-209f-4d68-a994-a59881fe4c6c', (9, 11, 0)), (3, 'buyer-19434e01-7f9c-41a5-88b8-e74428e2e788', (0, 11, 9)), (3, 'seller-d5d50fb8-209f-4d68-a994-a59881fe4c6c', (9, 11, 0)), (4, 'buyer-19434e01-7f9c-41a5-88b8-e74428e2e788', (0, 11, 9)), (4, 'seller-d5d50fb8-209f-4d68-a994-a59881fe4c6c', (9, 11, 0)), (5, 'buyer-19434e01-7f9c-41a5-88b8-e74428e2e788', (0, 11, 9)), (5, 'seller-d5d50fb8-209f-4d68-a994-a59881fe4c6c', (9, 11, 0)), (6, 'buyer-19434e01-7f9c-41a5-88b8-e74428e2e788', (0, 10, 9)), (6, 'seller-d5d50fb8-209f-4d68-a994-a59881fe4c6c', (9, 10, 0)), (7, 'buyer-19434e01-7f9c-41a5-88b8-e74428e2e788', (0, 9, 9)), (7, 'seller-d5d50fb8-209f-4d68-a994-a59881fe4c6c', (9, 9, 0)), (8, 'buyer-19434e01-7f9c-41a5-88b8-e74428e2e788', (0, 8, 9)), (8, 'seller-d5d50fb8-209f-4d68-a994-a59881fe4c6c', (9, 8, 0)), (9, 'buyer-19434e01-7f9c-41a5-88b8-e74428e2e788', (0, 11, 8)), (9, 'seller-d5d50fb8-209f-4d68-a994-a59881fe4c6c', (9, 11, 1)), (10, 'buyer-19434e01-7f9c-41a5-88b8-e74428e2e788', (0, 9, 8)), (10, 'seller-d5d50fb8-209f-4d68-a994-a59881fe4c6c', (9, 4, 0)), (11, 'buyer-19434e01-7f9c-41a5-88b8-e74428e2e788', (0, 1, 9)), (11, 'seller-d5d50fb8-209f-4d68-a994-a59881fe4c6c', (8, 6, 0)), (12, 'buyer-19434e01-7f9c-41a5-88b8-e74428e2e788', (0, 2, 8)), (12, 'seller-d5d50fb8-209f-4d68-a994-a59881fe4c6c', (9, 7, 2)), (13, 'buyer-19434e01-7f9c-41a5-88b8-e74428e2e788', (2, 2, 9)), (13, 'seller-d5d50fb8-209f-4d68-a994-a59881fe4c6c', (9, 7, 3)), (14, 'buyer-19434e01-7f9c-41a5-88b8-e74428e2e788', (0, 10, 4)), (14, 'seller-d5d50fb8-209f-4d68-a994-a59881fe4c6c', (4, 10, 0)), (15, 'buyer-19434e01-7f9c-41a5-88b8-e74428e2e788', (1, 8, 4)), (15, 'seller-d5d50fb8-209f-4d68-a994-a59881fe4c6c', (8, 3, 4)), (16, 'buyer-19434e01-7f9c-41a5-88b8-e74428e2e788', (6, 9, 7)), (16, 'seller-d5d50fb8-209f-4d68-a994-a59881fe4c6c', (1, 9, 0))] We can even plot the complete negotiation history and visually see how far were the result from the pareto frontier (it was 0.0 utility units far from it). .. code:: ipython3 session.plot(show_reserved=False) plt.show() .. image:: 01.running_simple_negotiation_files/01.running_simple_negotiation_9_0.png What happens if the seller was much more interested in delivery time. Firstly, what do you expect? Given that delivery time becomes a more important issue now, the seller will get more utility points by allowing the price to go down given that the delivery time can be made earlier. This means that we should expect the delivery time and price to go down. Let’s see what happens: .. code:: ipython3 seller_utility = LUFun( values={ "price": IdentityFun(), "quantity": LinearFun(0.2), "delivery_time": AffineFun(-1, bias=9), }, weights={"price": 1.0, "quantity": 1.0, "delivery_time": 10.0}, outcome_space=session.outcome_space, ) session = SAOMechanism(issues=issues, n_steps=50) session.add(TimeBasedConcedingNegotiator(name="buyer"), ufun=buyer_utility) session.add(TimeBasedConcedingNegotiator(name="seller"), ufun=seller_utility) print(session.run()) .. raw:: html
SAOState( running=False, waiting=False, started=True, step=41, time=0.02594933299405966, relative_time=0.8235294117647058, broken=False, timedout=False, agreement=(1, 10, 3), results=None, n_negotiators=2, has_error=False, error_details='', erred_negotiator='', erred_agent='', threads={}, last_thread='', current_offer=(1, 10, 3), current_proposer='seller-c8481a48-5e76-4f38-84b0-9cd3a041375c', current_proposer_agent=None, n_acceptances=2, new_offers=[], new_offerer_agents=[None, None], last_negotiator='seller', current_data=None, new_data=[] )We can check it visually as well: .. code:: ipython3 session.plot(show_reserved=False) plt.show() .. image:: 01.running_simple_negotiation_files/01.running_simple_negotiation_13_0.png It is clear that the new ufuns transformed the problem. Now we have many outcomes that are far from the pareto-front in this case. Nevertheless, there is money on the table as the negotiators did not agree on an outcome on the pareto front. Inspecting the utility ranges of the seller and buyer we can see that the seller can get much higher utility than the buyer (100 comapred with 20). This is a side effect of the ufun definitions and we can remove this difference by normalizing both ufuns and trying again: .. code:: ipython3 seller_utility = seller_utility.scale_max(1.0) buyer_utility = buyer_utility.scale_max(1.0) session = SAOMechanism(issues=issues, n_steps=50) session.add(TimeBasedConcedingNegotiator(name="buyer"), ufun=buyer_utility) session.add(TimeBasedConcedingNegotiator(name="seller"), ufun=seller_utility) session.run() session.plot(ylimits=(0.0, 1.01), show_reserved=False) plt.show() .. image:: 01.running_simple_negotiation_files/01.running_simple_negotiation_15_0.png What happens if we give them more time to negotiate: .. code:: ipython3 session = SAOMechanism(issues=issues, n_steps=5000) session.add(TimeBasedConcedingNegotiator(name="buyer"), ufun=buyer_utility) session.add(TimeBasedConcedingNegotiator(name="seller"), ufun=seller_utility) session.run() session.plot(ylimits=(0.0, 1.01), show_reserved=False) plt.show() .. image:: 01.running_simple_negotiation_files/01.running_simple_negotiation_17_0.png It did not help much! The two agents adjusted their concession to match the new time and they did not get to the Pareto-front. Let’s allow them to concede faster by setting their ``aspiration_type`` to *linear* instead of the default *boulware*: .. code:: ipython3 session = SAOMechanism(issues=issues, n_steps=5000) session.add( TimeBasedConcedingNegotiator(name="buyer", offering_curve="linear"), ufun=buyer_utility, ) session.add( TimeBasedConcedingNegotiator(name="seller", offering_curve="linear"), ufun=seller_utility, ) session.run() session.plot(ylimits=(0.0, 1.01), show_reserved=False) plt.show() .. image:: 01.running_simple_negotiation_files/01.running_simple_negotiation_19_0.png It is clear that longer negotiation time, and faster concession did not help the negotiators get to a point on the pareto-front. What happens if one of the negotiators (say the buyer) was tougher than the other? .. code:: ipython3 session = SAOMechanism(issues=issues, n_steps=5000) session.add( TimeBasedConcedingNegotiator(name="buyer", offering_curve="boulware"), ufun=buyer_utility, ) session.add( TimeBasedConcedingNegotiator(name="seller", offering_curve="linear"), ufun=seller_utility, ) session.run() session.plot(ylimits=(0.0, 1.01), show_reserved=False) plt.show() .. image:: 01.running_simple_negotiation_files/01.running_simple_negotiation_21_0.png Try to give an intuition for what happened: - Why did the negotiation take shorter than the previous one? - Why is the final agreement nearer to the pareto front? - Why is the buyer getting higher utility than in the case before the previous (in which it was also using a Boulware strategy)? - Why is the seller getting lower utility than in the case before the previous (in which it was also using a linear concession strategy)? - If the seller knew that the buyer will be using this strategy, what is its best response? Download :download:`Notebook