{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "7302e776", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:06.630424Z", "iopub.status.busy": "2022-02-19T02:24:06.629547Z", "iopub.status.idle": "2022-02-19T02:24:06.631692Z", "shell.execute_reply": "2022-02-19T02:24:06.632144Z" }, "papermill": { "duration": 0.111793, "end_time": "2022-02-19T02:24:06.632385", "exception": false, "start_time": "2022-02-19T02:24:06.520592", "status": "completed" }, "tags": [ "remove_cell" ] }, "outputs": [], "source": [ "# This is to make the results reproducible if you are using the Jupyter notebook version.\n", "from rich import print\n", "from random import seed\n", "\n", "seed(0)" ] }, { "cell_type": "markdown", "id": "47cec5df", "metadata": { "collapsed": true, "papermill": { "duration": 0.092942, "end_time": "2022-02-19T02:24:06.817500", "exception": false, "start_time": "2022-02-19T02:24:06.724558", "status": "completed" }, "tags": [] }, "source": [ "# Overview\n", "\n", "negmas was designed mainly as a research and educational tool with special emphasis on supporting multi-strand multilateral multi-issue negotiations with complex utility\n", "functions. This section gives an introduction to the main concepts of the public interface.\n", "\n", "In order to use the library you will need to import it as follows (assuming that you followed the instructions in the installation section of this document):" ] }, { "cell_type": "code", "execution_count": 2, "id": "9ebe4585", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:07.066546Z", "iopub.status.busy": "2022-02-19T02:24:07.063935Z", "iopub.status.idle": "2022-02-19T02:24:07.768861Z", "shell.execute_reply": "2022-02-19T02:24:07.769718Z" }, "papermill": { "duration": 0.856672, "end_time": "2022-02-19T02:24:07.770014", "exception": false, "start_time": "2022-02-19T02:24:06.913342", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "import negmas" ] }, { "cell_type": "markdown", "id": "849a5a9a", "metadata": { "papermill": { "duration": 0.104638, "end_time": "2022-02-19T02:24:07.988645", "exception": false, "start_time": "2022-02-19T02:24:07.884007", "status": "completed" }, "tags": [] }, "source": [ "To simplify the use of this platform, all classes and functions from all base modules are aliased in the root package (except generics and helpers). This is an example of importing just `Outcome` which is defined in the `outcomes` package" ] }, { "cell_type": "code", "execution_count": 3, "id": "6eae0d84", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:08.214625Z", "iopub.status.busy": "2022-02-19T02:24:08.213138Z", "iopub.status.idle": "2022-02-19T02:24:08.215817Z", "shell.execute_reply": "2022-02-19T02:24:08.216800Z" }, "papermill": { "duration": 0.11697, "end_time": "2022-02-19T02:24:08.217093", "exception": false, "start_time": "2022-02-19T02:24:08.100123", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "from negmas import Outcome" ] }, { "cell_type": "markdown", "id": "be7f911d", "metadata": { "papermill": { "duration": 0.114915, "end_time": "2022-02-19T02:24:08.439301", "exception": false, "start_time": "2022-02-19T02:24:08.324386", "status": "completed" }, "tags": [] }, "source": [ "It is possible *but not recommended* to just import everything in the package using:" ] }, { "cell_type": "code", "execution_count": 4, "id": "f563d8e8", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:08.675737Z", "iopub.status.busy": "2022-02-19T02:24:08.674638Z", "iopub.status.idle": "2022-02-19T02:24:09.110929Z", "shell.execute_reply": "2022-02-19T02:24:09.111853Z" }, "papermill": { "duration": 0.553419, "end_time": "2022-02-19T02:24:09.112773", "exception": false, "start_time": "2022-02-19T02:24:08.559354", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "from negmas import *\n", "from negmas.helpers import *\n", "from negmas.helpers.prob import *" ] }, { "cell_type": "markdown", "id": "d3549d24", "metadata": { "papermill": { "duration": 0.118809, "end_time": "2022-02-19T02:24:09.355108", "exception": false, "start_time": "2022-02-19T02:24:09.236299", "status": "completed" }, "tags": [] }, "source": [ "## Organization\n", "\n", "The package is organized into a set of modules/packages that combine together related functionality. There are base modules, protocol specific modules, advanced and helper modules.\n", "\n", "* **Base Modules** Implements basic automated negotiation functionality:\n", " 1. **outcomes** This module represents issues, outcome and responses and provides basic functions and methods to operator with and on them.\n", " 1. **preferences** This modules represents the base type of all preferences and different widely used utility function types including linear and nonlinear utilities and constraint-based utilities. This module also implements basic analysis tools like finding the pareto-frontier, sampling outcomes with given utilities from the outcome space, etc.\n", " 1. **negotiators** This module represents basic negotiation agent implementation and provides basic interfaces to be overriden (implemented) by higher specialized modules\n", " 1. **mechanisms** This module represents the most basic conceptual view of a negotiation protocol supporting both mediate and unmediated mechanisms. The term `mechanism` was used instead of the more common `protocol` to stress the fact that this mechanism need not be a standard negotiation protocol. For example auction mechanisms (like second-price auctions) can easily be implemented as a `Mechanism` in negmas.\n", " 1. **common** Provides data structures that are used by all modules including mechanism-state, and the agent-mechanism-interface.\n", " 1. **genius** Implements a specific type negotiator for the stacked alternating offers protocol called `GeniusNegotiator` which can run `NegotiationParty` based agents from the Java [Genius](http://ii.tudelft.nl/genius/) platform.\n", "\n", "* **Mechanism Specific Modules** These modules implement the base mechanism, negotiator type(s), state, and related computational resources specific to a single (or a set of related) negotiation/auction protocols\n", " 1. **sao** Implements that stacked alternating offers protocol for unmediated multiparty multi-issue negotiations. Other than providing the `SAOMechanism` class representing the protocol, this package provides a set of simple negotiators including the time-based `AspirationNegotiator`, a `SimpleTitForTatNegotiator`, among others.\n", " 1. **st** Implements two basic single-text mediated negotiation protocols (veto and hill-climbing) and the basic negotiator types to support them.\n", " 1. **mt** Implements and extension of single text mediated protocols to handle multiple *proposed agreements* in parallel.\n", " 1. **ga** Implements a Genetic Algorithm based single text mediated negotiation protocol\n", " \n", "* **Advanced Negotiation Modules** These modules model advanced negotiation problems and techniques\n", " 1. **situated** Implements world simulations within which agents with intrinsic utility functions can engage in simultaneous interconnected situated negotiations. It is the most important module for the goals of this library. The `Agent` and `World` classes described in details later belong to this module\n", " 1. **modeling** This is a set of submodules implementing modeling of opponent utility, opponent strategy, opponent's future offers and opponent's probability of accepting offers.\n", " 1. **elicitation** Implements several preference elicitation during negotiation methods.\n", " 1. **concurrent** Implements mechanism types, and other computational resources to support concurrent negotiation.\n", "\n", "* **Helper Modules** These modules provide basic activities that is not directly related to the negotiation but that are relied upon by different base modules. The end user is not expected to interact directly with these modules.\n", " * **common** Provides common interfaces that are used by all other modules. \n", " * **helpers** Various helper functions and classes used throughout the library including mixins for logging.\n", " * **inout** Provides functions to load and store XML Genius domains and utility functions.\n", " * **java** \\[Depricated\\] Provides an interface to JNegMAS allowing agents and negotiators to be developed in Java.\n", " * **tournaments** Supports creating and running tournaments to compare agents and negotiators.\n", " * **checkpoints** Supports saving and reloading world simulations to/from secondary storage.\n", " * **visualizers** \\[Under development\\] Supports visualization of world simulation, negotiation sessions, negotiators, and agents.\n", " * **generics** Provides a set of types and interfaces to increase the representation flexibility of different base modules. \n", " " ] }, { "cell_type": "markdown", "id": "f1e3a90f", "metadata": { "papermill": { "duration": 0.123348, "end_time": "2022-02-19T02:24:09.575077", "exception": false, "start_time": "2022-02-19T02:24:09.451729", "status": "completed" }, "tags": [] }, "source": [ "## A (not very) brief introduction to NegMAS\n", "\n", "This figure shows the main active components of a simulation in a NegMAS world:\n", "![NegMAS world](figs/world.png)\n", "\n", "The simulation is run using a **World** object which defines what happens in\n", "every simulation **step**, provides a **BulletinBoard** object containing all\n", "public information about the game, calls various callbacks defined in the\n", "**Agent** object representing each agent in the environment, takes care of\n", "running negotiations and keeps track of agreement signing and the resulting\n", "**Contract**s. The **World** object also controls logging, event management,\n", "serialization, visualization, etc. Refer to the\n", "[World](https://negmas.readthedocs.io/en/latest/api/negmas.situated.World.html)\n", "documentation for more details (*you need to do that only if you are\n", "implementing new world simulations*).\n", "\n", "The designer of the game implements a **World** class by overriding few\n", "abstract methods in the base **World** class. \n", "\n", "The logic of an agent is NegMAS is implemented in an **Agent** object. The\n", "designer of the simulation, should provide a base class for its specific world\n", "inherited from NegMAS's **Agent** class. Refer to the\n", "[Agent](https://negmas.readthedocs.io/en/latest/api/negmas.situated.Agent.html)\n", "documentation for more details about general NegMAS agents. \n", "\n", "So now we have the **World** and the **Agent** objects, and we already said\n", "that the agent does not directly interact with the world. How does these two\n", "types of entities interact then?\n", "\n", "- When the **World** wants to interact with the **Agent**, it calls some method\n", " in it. For example, to instruct the agent to *initialize* itself, the world\n", " calls the **init()** method defined by the **Agent**. To inform the agent\n", " that a negotiation it is involved in is concluded with success, the **World**\n", " calls the method **on_negotiation_success()** defined by the agent.\n", "- When the **Agent** wants to interact with the **World**, it accesses an\n", " interface object called an **AgentWorldInterface** or **AWI** for short which\n", " provides all the services available to the **Agent**. For example, to request\n", " a negotiation with another agent, the **Agent** object needs to call\n", " **request_negotiation()** defined in the **AWI**.\n", "\n", "The world designer usually defines an AWI for its world that inherits NegMAS's\n", "**AgentWorldInterface** class and provides any special services for agents\n", "interacting in this world. You can find all the services available to your agent\n", "through the AgentWorldInterface\n", "[here](https://negmas.readthedocs.io/en/latest/api/negmas.situated.AgentWorldInterface.html).\n", "These methods and properties are still available for your agent in SCML. Nevertheless,\n", "in many cases, more convenient ways to access some of the information (e.g. the\n", "bulletin board) is provided in the specific AWIs implemented in the SCML package\n", "to be described now.\n", "\n", "Now that we know how worlds and agents work and interact, we can look at how\n", "negotiation is managed in NegMAS. **Note that you can create negotiations that do\n", "not belong to any world**\n", "\n", "A negotiation is controlled by a **Mechanism** object which implements the\n", "negotiation protocol (e.g. the alternating offers protocol). NegMAS provides\n", "several mediated and unmediated negotiation protocols (as well as auction\n", "mechanisms). The specific **Mechanism** that is used in SCML is the\n", "**SAOMechanism** which implements the bargaining protocol.\n", "\n", "Negotiation strategies are implemented in a **Negotiator** object which usually\n", "inherits some base negotiator-class corresponding to the mechanism(s) it supports.\n", "\n", "The interaction between **Mechanism** and **Negotiator** objects mirrors the\n", "interaction between **World** and **Agent** objects. **Mechanism** objects call\n", "methods in **Negotiator** objects directly but **Negotiator** objects can only\n", "access services provided by the **Mechanism** object through a\n", "**NegotiatorMechanismInterface** (AMI). You can find more details about the general NegMAS NMI\n", "[here](https://negmas.readthedocs.io/en/latest/api/negmas.common.NegotiatorMechanismInterface.html).\n", "\n", "Each specific **Mechanism** defines a corresponding specific\n", "**AgentMechanismInterface** class (in the same way that **World** classes\n", "define their own AWI). \n", "\n", "To negotiate effectively, negotiators employ a **UtilityFunction** \n", "(or any other form of **Preferences** objects) to represent\n", "their preferences over different possible **Outcome**s of the negotiation\n", "(where an outcome is a full assignment of values to all negotiated **Issue**s).\n", "NegMAS provides an extensive set of preferences types, utility functions, and issue types. Please\n", "refer to this [overview](https://negmas.readthedocs.io/en/latest/overview.html) and\n", "[tutorials](https://negmas.readthedocs.io/en/latest/tutorials.html) for more details.\n", "NegMAS also provides some basic **SAONegotiator**s for the **SAOMechanism**\n", "(Check the class diagram\n", "[here](https://negmas.readthedocs.io/en/latest/modules/sao.html)). Moreover, you can\n", "access almost all [Genius](http://ii.tudelft.nl/genius/) agents using NegMAS's\n", "[GeniusNegotiator](https://negmas.readthedocs.io/en/latest/api/negmas.genius.GeniusNegotiator.html)\n", "including all finalists and winners of all past ANAC competitions.\n", "\n", "Now we understand how agents interact with worlds through AWIs and negotiators\n", "interact with mechanisms through AMIs. We know that the general simulation is\n", "controlled by the world while each negotiation is controlled by a mechanism\n", "within that world. **We need now to connect these two triplets of objects**\n", "\n", "As the figure above shows: **Negotiator** objects can be created and controlled\n", "by **Agent** objects for the purpose of negotiating with other **Agent**\n", "objects. The standard flow of operations is something like this:\n", "\n", "1. **Agent** A uses its AWI to *request_negotiation()* with Agent B passing a\n", " **Negotiator** to be used in this negotiation. Usually Agent A will also\n", " create a **UtilityFunction** and attach it to the **Negotiator** it just\n", " created (by setting its *ufun* attribute).\n", "2. The **World** calls Agent B's *respond_to_negotiation_request()* asking it\n", " to provide its own **Negotiator** to negotiate with Agent A's Negotiator. It\n", " can also just reject the negotiation request by returning no negotiators.\n", "3. The **World** will then create a **Mechanism** and ask both **Negotiator**s\n", " to *join* it. If all goes well, the negotiation starts (at a time defined\n", " by the simulation rules) and runs until either an agreement or disagreement\n", " is reached.\n", "4. The **World** class will then inform **Agent**s A and B about the results of\n", " the negotiation using their *on_negotiation_success* and\n", " *on_negotiation_failure* callbacks. \n", "5. Successful negotiations lead to **Agreement**s but are still not binding in\n", " general until signed by all agents involved (A and B in this case).\n", " **Agent**'s '*sign_all_contracts* is used for this.\n", "6. Signed agreements become *Contract*s and are executed (as specified in the\n", " simulation rules) by the **World**.\n", "\n", "When negotiations are independent, these are all the objects needed.\n", "Nevertheless, in many cases, negotiations are\n", "inter-dependent. This means that what is *good* in one negotiation depends on\n", "other concurrently running negotiations (or on expectations of future\n", "negotiations). NegMAS provides two ways to support this case shown in the\n", "following figure:\n", "\n", "![controllers](figs/controllers.png)\n", "\n", "1. Let **Negotiator**s use **UtilityFunction**s that depend on some common\n", " state. That is what is happening in the left two negotiations. \n", "2. Have multiple **Negotiator**s be controlled by a single **Controller**\n", " object with its own utility function that depends on what is happening on\n", " all the negotiations controlled. \n", "\n", "The **Negotiator**s connected to a controller lost their autonomy and just pass \n", "control to their *owning* **Controller**. \n", "\n", "This concludes our introduction to NegMAS and different objects you need to know \n", "about to develop your agent." ] }, { "cell_type": "markdown", "id": "a91b5c24", "metadata": { "papermill": { "duration": 0.098144, "end_time": "2022-02-19T02:24:09.753549", "exception": false, "start_time": "2022-02-19T02:24:09.655405", "status": "completed" }, "tags": [] }, "source": [ "## Outcomes, Issues and Outcome Spaces\n", "\n", "Negotiations are conducted between multiple agents with the goal of achieving an *agreement* (usually called a contract) on one of several possible outcomes. Each *outcome* is in general an assignment of some value to a set of issues. Each *issue* is a variable that can take one of a -- probably infinite -- set of values from some predefined *domain*.\n", "\n", "The classes and functions supporting management of issues, outcome-spaces and outcomes are implemented in the `outcomes` module.\n", "\n", "Issues are represented in ``negmas`` using the `Issue` class. An issue is defined by a set of ``values`` and a ``name``.\n", "\n", "NegMAS supports a variety of `Issue` types." ] }, { "cell_type": "markdown", "id": "4398bb12", "metadata": { "papermill": { "duration": 0.123018, "end_time": "2022-02-19T02:24:09.990972", "exception": false, "start_time": "2022-02-19T02:24:09.867954", "status": "completed" }, "tags": [] }, "source": [ "* Using a set of strings:" ] }, { "cell_type": "code", "execution_count": 5, "id": "bf7faa88", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:10.201895Z", "iopub.status.busy": "2022-02-19T02:24:10.200820Z", "iopub.status.idle": "2022-02-19T02:24:10.204479Z", "shell.execute_reply": "2022-02-19T02:24:10.205039Z" }, "papermill": { "duration": 0.096243, "end_time": "2022-02-19T02:24:10.205230", "exception": false, "start_time": "2022-02-19T02:24:10.108987", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
issueQKqgvpMi: ['to be', 'not to be']\n",
                            "
\n" ], "text/plain": [ "issueQKqgvpMi: \u001b[1m[\u001b[0m\u001b[32m'to be'\u001b[0m, \u001b[32m'not to be'\u001b[0m\u001b[1m]\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
The Problem: ['to be', 'not to be']\n",
                            "
\n" ], "text/plain": [ "The Problem: \u001b[1m[\u001b[0m\u001b[32m'to be'\u001b[0m, \u001b[32m'not to be'\u001b[0m\u001b[1m]\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# an issue with randomly assigned name\n", "issue1 = make_issue(values=[\"to be\", \"not to be\"])\n", "print(issue1)\n", "# an issue with given name:\n", "issue2 = make_issue(values=[\"to be\", \"not to be\"], name=\"The Problem\")\n", "print(issue2)" ] }, { "cell_type": "markdown", "id": "cff42e13", "metadata": { "papermill": { "duration": 0.106143, "end_time": "2022-02-19T02:24:10.419022", "exception": false, "start_time": "2022-02-19T02:24:10.312879", "status": "completed" }, "tags": [] }, "source": [ "* Using a single integer to give an issue which takes any value from `0` to the given integer minus 1:" ] }, { "cell_type": "code", "execution_count": 6, "id": "c9815035", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:10.640667Z", "iopub.status.busy": "2022-02-19T02:24:10.639697Z", "iopub.status.idle": "2022-02-19T02:24:10.643063Z", "shell.execute_reply": "2022-02-19T02:24:10.643515Z" }, "papermill": { "duration": 0.117487, "end_time": "2022-02-19T02:24:10.643700", "exception": false, "start_time": "2022-02-19T02:24:10.526213", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
number of items: (0, 9)\n",
                            "
\n" ], "text/plain": [ "number of items: \u001b[1m(\u001b[0m\u001b[1;36m0\u001b[0m, \u001b[1;36m9\u001b[0m\u001b[1m)\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "issue3 = make_issue(values=10, name=\"number of items\")\n", "print(issue3)" ] }, { "cell_type": "markdown", "id": "b035bb53", "metadata": { "papermill": { "duration": 0.125342, "end_time": "2022-02-19T02:24:10.887347", "exception": false, "start_time": "2022-02-19T02:24:10.762005", "status": "completed" }, "tags": [] }, "source": [ "* Using a `tuple` with a lower and upper real-valued boundaries to give an issue with an infinite number of possibilities (all real numbers in between)" ] }, { "cell_type": "code", "execution_count": 7, "id": "53eff112", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:11.204985Z", "iopub.status.busy": "2022-02-19T02:24:11.202068Z", "iopub.status.idle": "2022-02-19T02:24:11.212672Z", "shell.execute_reply": "2022-02-19T02:24:11.211204Z" }, "papermill": { "duration": 0.178508, "end_time": "2022-02-19T02:24:11.213169", "exception": false, "start_time": "2022-02-19T02:24:11.034661", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
cost: (0.0, 1.0)\n",
                            "
\n" ], "text/plain": [ "cost: \u001b[1m(\u001b[0m\u001b[1;36m0.0\u001b[0m, \u001b[1;36m1.0\u001b[0m\u001b[1m)\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "issue4 = make_issue(values=(0.0, 1.0), name=\"cost\")\n", "print(issue4)" ] }, { "cell_type": "markdown", "id": "02df997f", "metadata": { "papermill": { "duration": 0.099019, "end_time": "2022-02-19T02:24:11.419710", "exception": false, "start_time": "2022-02-19T02:24:11.320691", "status": "completed" }, "tags": [] }, "source": [ "The `Issue` class provides some useful functions. For example you can find the ``cardinality`` of any issue using:" ] }, { "cell_type": "code", "execution_count": 8, "id": "2f4988a8", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:11.664633Z", "iopub.status.busy": "2022-02-19T02:24:11.663516Z", "iopub.status.idle": "2022-02-19T02:24:11.667092Z", "shell.execute_reply": "2022-02-19T02:24:11.667537Z" }, "papermill": { "duration": 0.127306, "end_time": "2022-02-19T02:24:11.667757", "exception": false, "start_time": "2022-02-19T02:24:11.540451", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/plain": [ "[2, 10, inf]" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[issue2.cardinality, issue3.cardinality, issue4.cardinality]" ] }, { "cell_type": "markdown", "id": "3cdd44ca", "metadata": { "papermill": { "duration": 0.114613, "end_time": "2022-02-19T02:24:11.887735", "exception": false, "start_time": "2022-02-19T02:24:11.773122", "status": "completed" }, "tags": [] }, "source": [ "It is also possible to check the `type` of the issue and whether it is discrete or continuous:" ] }, { "cell_type": "code", "execution_count": 9, "id": "73cbb5e3", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:12.096351Z", "iopub.status.busy": "2022-02-19T02:24:12.094950Z", "iopub.status.idle": "2022-02-19T02:24:12.099274Z", "shell.execute_reply": "2022-02-19T02:24:12.099965Z" }, "papermill": { "duration": 0.108964, "end_time": "2022-02-19T02:24:12.100322", "exception": false, "start_time": "2022-02-19T02:24:11.991358", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/plain": [ "['categorical', True, False]" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[issue2.type, issue2.is_discrete(), issue2.is_continuous()]" ] }, { "cell_type": "markdown", "id": "c8cdf8c6", "metadata": { "papermill": { "duration": 0.111642, "end_time": "2022-02-19T02:24:12.295436", "exception": false, "start_time": "2022-02-19T02:24:12.183794", "status": "completed" }, "tags": [] }, "source": [ "It is possible to check the total cardinality for a set of issues:" ] }, { "cell_type": "code", "execution_count": 10, "id": "1902bee0", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:12.514704Z", "iopub.status.busy": "2022-02-19T02:24:12.513770Z", "iopub.status.idle": "2022-02-19T02:24:12.517140Z", "shell.execute_reply": "2022-02-19T02:24:12.517592Z" }, "papermill": { "duration": 0.124063, "end_time": "2022-02-19T02:24:12.517779", "exception": false, "start_time": "2022-02-19T02:24:12.393716", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/plain": [ "[inf, 40]" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[\n", " num_outcomes([issue1, issue2, issue3, issue4]), # expected inf\n", " num_outcomes([issue1, issue2, issue3]),\n", "] # expected 40 = 2 * 2 * 10" ] }, { "cell_type": "markdown", "id": "90130e6f", "metadata": { "papermill": { "duration": 0.096522, "end_time": "2022-02-19T02:24:12.689926", "exception": false, "start_time": "2022-02-19T02:24:12.593404", "status": "completed" }, "tags": [] }, "source": [ "You can pick random valid or invalid values for the issue:" ] }, { "cell_type": "code", "execution_count": 11, "id": "f7032e82", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:12.909447Z", "iopub.status.busy": "2022-02-19T02:24:12.907993Z", "iopub.status.idle": "2022-02-19T02:24:12.913740Z", "shell.execute_reply": "2022-02-19T02:24:12.914677Z" }, "papermill": { "duration": 0.134133, "end_time": "2022-02-19T02:24:12.915010", "exception": false, "start_time": "2022-02-19T02:24:12.780877", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/plain": [ "[['to be', '20231127H095259847823jJTGt6qBto be20231127H095259847844XtRgNy0I'],\n", " [6, 10],\n", " [0.6118970848141451, 1.928063278403899]]" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[\n", " [issue1.rand_valid(), issue1.rand_invalid()],\n", " [issue3.rand_valid(), issue3.rand_invalid()],\n", " [issue4.rand_valid(), issue4.rand_invalid()],\n", "]" ] }, { "cell_type": "markdown", "id": "a1aadd8c", "metadata": { "papermill": { "duration": 0.174, "end_time": "2022-02-19T02:24:13.240512", "exception": false, "start_time": "2022-02-19T02:24:13.066512", "status": "completed" }, "tags": [] }, "source": [ "You can also list all valid values for an issue using `all` or sample from them using `value_generator`. Notice that `all` and `value_generator` return generators so both are memory efficient." ] }, { "cell_type": "code", "execution_count": 12, "id": "48a600fc", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:13.487206Z", "iopub.status.busy": "2022-02-19T02:24:13.485691Z", "iopub.status.idle": "2022-02-19T02:24:13.490690Z", "shell.execute_reply": "2022-02-19T02:24:13.492513Z" }, "papermill": { "duration": 0.116924, "end_time": "2022-02-19T02:24:13.493043", "exception": false, "start_time": "2022-02-19T02:24:13.376119", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
('to be', 'not to be')\n",
                            "
\n" ], "text/plain": [ "\u001b[1m(\u001b[0m\u001b[32m'to be'\u001b[0m, \u001b[32m'not to be'\u001b[0m\u001b[1m)\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
('to be', 'not to be')\n",
                            "
\n" ], "text/plain": [ "\u001b[1m(\u001b[0m\u001b[32m'to be'\u001b[0m, \u001b[32m'not to be'\u001b[0m\u001b[1m)\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n",
                            "
\n" ], "text/plain": [ "\u001b[1m(\u001b[0m\u001b[1;36m0\u001b[0m, \u001b[1;36m1\u001b[0m, \u001b[1;36m2\u001b[0m, \u001b[1;36m3\u001b[0m, \u001b[1;36m4\u001b[0m, \u001b[1;36m5\u001b[0m, \u001b[1;36m6\u001b[0m, \u001b[1;36m7\u001b[0m, \u001b[1;36m8\u001b[0m, \u001b[1;36m9\u001b[0m\u001b[1m)\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
Cannot enumerate all values of a continuous issue\n",
                            "
\n" ], "text/plain": [ "Cannot enumerate all values of a continuous issue\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "print(tuple(issue1.all))\n", "print(tuple(issue2.all))\n", "print(tuple(issue3.all))\n", "try:\n", " print(tuple(issue4.all))\n", "except ValueError as e:\n", " print(e)" ] }, { "cell_type": "markdown", "id": "aba39581", "metadata": { "papermill": { "duration": 0.114029, "end_time": "2022-02-19T02:24:13.713974", "exception": false, "start_time": "2022-02-19T02:24:13.599945", "status": "completed" }, "tags": [] }, "source": [ "### Outcomes\n", "Now that we know how to define issues, defining outcomes from a negotiation is even simpler. An outcome can be any python `mapping` or `iterable` with a known length. That includes dictionaries, lists, tuples among many other. \n", "\n", "Here is how to define an outcome for the last three issues mentioned above:" ] }, { "cell_type": "code", "execution_count": 13, "id": "26c67c8f", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:13.968578Z", "iopub.status.busy": "2022-02-19T02:24:13.967371Z", "iopub.status.idle": "2022-02-19T02:24:13.970047Z", "shell.execute_reply": "2022-02-19T02:24:13.970877Z" }, "papermill": { "duration": 0.135555, "end_time": "2022-02-19T02:24:13.971241", "exception": false, "start_time": "2022-02-19T02:24:13.835686", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "valid_outcome = {\"The Problem\": \"to be\", \"number of items\": 5, \"cost\": 0.15}\n", "invalid_outcome = {\"The Problem\": \"to be\", \"number of items\": 10, \"cost\": 0.15}" ] }, { "cell_type": "markdown", "id": "5158303a", "metadata": { "papermill": { "duration": 0.102238, "end_time": "2022-02-19T02:24:14.184475", "exception": false, "start_time": "2022-02-19T02:24:14.082237", "status": "completed" }, "tags": [] }, "source": [ "Notice that the ``invalid_outcome`` is assigning a value of ``10`` to the ``number of items`` issue which is not an acceptable value (``cost`` ranges between ``0`` and ``9``).\n", "\n", "Because `outcomes` can be represented with many built-in collection classes, the only common ancestor of all outcome objects is the `object` class. Nevertheless, the `outcomes` module provide a type-alias `Outcome` that can be used for static type checking if needed. The `outcomes` module also provides some functions for dealing with `outcome` objects in relation to `Issue`s. These are some examples:" ] }, { "cell_type": "code", "execution_count": 14, "id": "355c1bd9", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:14.416818Z", "iopub.status.busy": "2022-02-19T02:24:14.413607Z", "iopub.status.idle": "2022-02-19T02:24:14.419130Z", "shell.execute_reply": "2022-02-19T02:24:14.417528Z" }, "papermill": { "duration": 0.121175, "end_time": "2022-02-19T02:24:14.419319", "exception": false, "start_time": "2022-02-19T02:24:14.298144", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/plain": [ "[True, False]" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[\n", " outcome_is_valid(valid_outcome, [issue2, issue3, issue4]), # valid giving True\n", " outcome_is_valid(invalid_outcome, [issue2, issue3, issue4]), # invalid giving False\n", "]" ] }, { "cell_type": "markdown", "id": "3b742dc2", "metadata": { "papermill": { "duration": 0.096127, "end_time": "2022-02-19T02:24:14.605369", "exception": false, "start_time": "2022-02-19T02:24:14.509242", "status": "completed" }, "tags": [] }, "source": [ "It is not necessary for an outcome to assign a value for *all* issues to be considered *valid*. For example the following outcomes are all valid for the last three issues given above:\n" ] }, { "cell_type": "code", "execution_count": 15, "id": "b945f72d", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:14.794507Z", "iopub.status.busy": "2022-02-19T02:24:14.793159Z", "iopub.status.idle": "2022-02-19T02:24:14.797930Z", "shell.execute_reply": "2022-02-19T02:24:14.799085Z" }, "papermill": { "duration": 0.111422, "end_time": "2022-02-19T02:24:14.799314", "exception": false, "start_time": "2022-02-19T02:24:14.687892", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/plain": [ "[False, False]" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[\n", " outcome_is_valid({\"The Problem\": \"to be\"}, [issue2, issue3, issue4]),\n", " outcome_is_valid(\n", " {\"The Problem\": \"to be\", \"number of items\": 5}, [issue2, issue3, issue4]\n", " ),\n", "]" ] }, { "cell_type": "markdown", "id": "e9cacff0", "metadata": { "papermill": { "duration": 0.140652, "end_time": "2022-02-19T02:24:15.034586", "exception": false, "start_time": "2022-02-19T02:24:14.893934", "status": "completed" }, "tags": [] }, "source": [ "You can check the validity of outcomes defined as tuples or lists the same way." ] }, { "cell_type": "code", "execution_count": 16, "id": "548e9a20", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:15.333355Z", "iopub.status.busy": "2022-02-19T02:24:15.332187Z", "iopub.status.idle": "2022-02-19T02:24:15.339410Z", "shell.execute_reply": "2022-02-19T02:24:15.340547Z" }, "papermill": { "duration": 0.133268, "end_time": "2022-02-19T02:24:15.341378", "exception": false, "start_time": "2022-02-19T02:24:15.208110", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/plain": [ "[True, False]" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[\n", " outcome_is_valid([\"to be\", 4, 0.5], [issue2, issue3, issue4]),\n", " outcome_is_valid((\"to be\", 4, 1.5), [issue2, issue3, issue4]),\n", "]" ] }, { "cell_type": "markdown", "id": "1a06a7b8", "metadata": { "papermill": { "duration": 0.12133, "end_time": "2022-02-19T02:24:15.585421", "exception": false, "start_time": "2022-02-19T02:24:15.464091", "status": "completed" }, "tags": [] }, "source": [ "It is also important for some applications to check if an outcome is `complete` in the sense that it assigns a *valid* value to every issue in the given set of issues. This can be done using the `outcome_is_complete` function:" ] }, { "cell_type": "code", "execution_count": 17, "id": "bf62b3b9", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:15.822701Z", "iopub.status.busy": "2022-02-19T02:24:15.821726Z", "iopub.status.idle": "2022-02-19T02:24:15.825060Z", "shell.execute_reply": "2022-02-19T02:24:15.825654Z" }, "papermill": { "duration": 0.104307, "end_time": "2022-02-19T02:24:15.826075", "exception": false, "start_time": "2022-02-19T02:24:15.721768", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/plain": [ "[True, False, False]" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[\n", " outcome_is_complete(valid_outcome, [issue2, issue3, issue4]), # complete -> True\n", " outcome_is_complete(\n", " invalid_outcome, [issue2, issue3, issue4]\n", " ), # invalid -> incomplete -> False\n", " outcome_is_complete(\n", " {\"The Problem\": \"to be\"}, [issue2, issue3, issue4]\n", " ), # incomplete -> False\n", "]" ] }, { "cell_type": "markdown", "id": "64eb13eb", "metadata": { "papermill": { "duration": 0.12572, "end_time": "2022-02-19T02:24:16.070195", "exception": false, "start_time": "2022-02-19T02:24:15.944475", "status": "completed" }, "tags": [] }, "source": [ "#### Outcome Ranges and constraints\n", "Sometimes, it is important to represent not only a single outcome but a range of outcomes. This can be represented using an `OutcomeRange`. Again, an outcome range can be almost any `mapping` or `iterable` in python including dictionaries, lists, tuples, etc with the only exception that the values stored in it can be not only be `int`, `str`, `float` but also `tuple`s of two of any of them representing a range or a `list` of values. This is easier shown:" ] }, { "cell_type": "code", "execution_count": 18, "id": "fd7a9025", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:16.275433Z", "iopub.status.busy": "2022-02-19T02:24:16.274341Z", "iopub.status.idle": "2022-02-19T02:24:16.276286Z", "shell.execute_reply": "2022-02-19T02:24:16.276736Z" }, "papermill": { "duration": 0.116338, "end_time": "2022-02-19T02:24:16.277021", "exception": false, "start_time": "2022-02-19T02:24:16.160683", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "range1 = {\n", " \"The Problem\": [\"to be\", \"not to be\"],\n", " \"number of items\": 5,\n", " \"cost\": (0.1, 0.2),\n", "}" ] }, { "cell_type": "markdown", "id": "78d5e1b1", "metadata": { "papermill": { "duration": 0.125255, "end_time": "2022-02-19T02:24:16.512886", "exception": false, "start_time": "2022-02-19T02:24:16.387631", "status": "completed" }, "tags": [] }, "source": [ "``range1`` represents the following range of outcomes:\n", "\n", "* **The Problem**: accepts both ``to be`` and ``not to be``\n", "\n", "* **number of items**: accepts only the value ``5``\n", "\n", "* **cost**: accepts any real number between ``0.1`` and ``0.2`` up to representation error\n", "\n", "It is easy to check whether a specific outcome is within a given range:" ] }, { "cell_type": "code", "execution_count": 19, "id": "2bd2ed91", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:16.738361Z", "iopub.status.busy": "2022-02-19T02:24:16.736943Z", "iopub.status.idle": "2022-02-19T02:24:16.743079Z", "shell.execute_reply": "2022-02-19T02:24:16.743629Z" }, "papermill": { "duration": 0.121633, "end_time": "2022-02-19T02:24:16.743880", "exception": false, "start_time": "2022-02-19T02:24:16.622247", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/plain": [ "[True, False]" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "outcome1 = {\"The Problem\": \"to be\", \"number of items\": 5, \"cost\": 0.15}\n", "outcome2 = {\"The Problem\": \"to be\", \"number of items\": 10, \"cost\": 0.15}\n", "[\n", " outcome_in_range(outcome1, range1), # True\n", " outcome_in_range(outcome2, range1), # False\n", "]" ] }, { "cell_type": "markdown", "id": "bbdc0981", "metadata": { "papermill": { "duration": 0.144809, "end_time": "2022-02-19T02:24:16.973039", "exception": false, "start_time": "2022-02-19T02:24:16.828230", "status": "completed" }, "tags": [] }, "source": [ "In general outcome ranges constraint outcomes depending on the type of the constraint:\n", "\n", "* **tuple** The outcome must fall within the range specified by the first and second elements. Only valid for values that can be compared using `__lt__` (e.g. int, float, str).\n", "* **single value** The outcome must equal this given value.\n", "* **list of values** The outcome must be within the list.\n", "* **list of tuples** The outcome must fall within one of the ranges specified by the tuples.\n", "\n" ] }, { "cell_type": "markdown", "id": "22728881", "metadata": { "papermill": { "duration": 0.115281, "end_time": "2022-02-19T02:24:17.244738", "exception": false, "start_time": "2022-02-19T02:24:17.129457", "status": "completed" }, "tags": [] }, "source": [ "### Outcome Spaces\n", "\n", "An outcome-space is a *set of outcomes* which can be enumerated, sampled, etc.\n", "\n", "NegMAS supports a special kind of outcome-spaces called `CartesianOutcomeSpace` which represents the Cartesian product of a set of issues and can be created using `make_os` function:" ] }, { "cell_type": "code", "execution_count": 20, "id": "be9e6fb4", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:17.541105Z", "iopub.status.busy": "2022-02-19T02:24:17.538749Z", "iopub.status.idle": "2022-02-19T02:24:17.544869Z", "shell.execute_reply": "2022-02-19T02:24:17.546024Z" }, "papermill": { "duration": 0.163291, "end_time": "2022-02-19T02:24:17.546581", "exception": false, "start_time": "2022-02-19T02:24:17.383290", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
<class 'negmas.outcomes.outcome_space.CartesianOutcomeSpace'>\n",
                            "
\n" ], "text/plain": [ "\u001b[1m<\u001b[0m\u001b[1;95mclass\u001b[0m\u001b[39m \u001b[0m\u001b[32m'negmas.outcomes.outcome_space.CartesianOutcomeSpace'\u001b[0m\u001b[1m>\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "myos = make_os([issue1, issue2, issue3, issue4])\n", "print(type(myos))" ] }, { "cell_type": "markdown", "id": "eebd4185", "metadata": { "papermill": { "duration": 0.128915, "end_time": "2022-02-19T02:24:17.789556", "exception": false, "start_time": "2022-02-19T02:24:17.660641", "status": "completed" }, "tags": [] }, "source": [ "A special case of `CartesianOutcomeSpace` is a `DiscreteCartesianOutcomeSpace` (see the examle above) which represent a Cartesian outcome-space with discrete issues (i.e. no issues are continuous).\n", "\n", "`OutcomeSpace` provide convenient methods for gettin information about the outcome-space or manipulating it. Some of the most important examples are:\n", "\n", "- **is_numeric, is_integer, is_float** Checks if all components of all outcomes are numeric, integer or float.\n", "- **is_discrete, is_finite, is_continuous** Check if the outcome space itself is discrete, finite or continuous.\n", "- **cardinality** returns the number of outcomes in the outcome-space.\n", "- **cardinality_if_discretized** returns the number of outcomes in the outcome-space if we discretize it.\n", "- **to_discrete, to_largest_discrete** create an discrete outcome-space that ranges over the input outcome-space.\n", "- **sample** returns outcomes from the outcome-space.\n", "- **enumerate_or_sample** sample from continuous outcome-spaces and enumerate all outcomes of discrete outcome-spaces.\n", "\n", "\n", "`DiscreteOutcomeSpace` is a special case of `OutcomeSpace` representing a finite outcome space and adds some operations including:\n", "\n", "- **to_single_issue** generates a single-issue outcome-space with the same number of outcomes as the given outcome-space\n", "- **limit_cardinality** generates a discrete outcome-space that *approximates* the input outcome-space using at most some predefined number of outcomes.\n" ] }, { "cell_type": "markdown", "id": "b4d5f374", "metadata": { "papermill": { "duration": 0.121508, "end_time": "2022-02-19T02:24:18.008781", "exception": false, "start_time": "2022-02-19T02:24:17.887273", "status": "completed" }, "tags": [] }, "source": [ "## Utilities and Preferences\n", "Agents engage in negotiations to maximize their utility. That is the central dogma in negotiation research. `negmas` allows the user to define their own utility functions based on a set of predefined base classes that can be found in the `utilities` module.\n" ] }, { "cell_type": "markdown", "id": "2adc0d2a", "metadata": { "papermill": { "duration": 0.08783, "end_time": "2022-02-19T02:24:18.179317", "exception": false, "start_time": "2022-02-19T02:24:18.091487", "status": "completed" }, "tags": [] }, "source": [ "### Utility Values\n", "In most applications, utility values can be represented by real numbers. Nevertheless, some applications need a more complicated representation. For example, during utility elicitation (the process of learning about the utility function of the human being represented by the agent) or opponent modeling (the process of learning about the utility function of an opponent), the need may arise to represent a probability distribution over utilities. \n", "\n", "`negmas` allows all functions that receive a utility value to receive a utility distribution. This is achieved through the use of two basic type definitions:\n", "\n", "* `Distribution` That is a probability distribution class capable of representing probabilistic variables having both continuous and discrete distributions and applying basic operations on them (addition, subtraction and multiplication). Currently we use `scipy.stats` for modeling these distributions but this is an implementation detail that should not be relied upon as it is likely that the probabilistic framework will be changed in the future to enhance the flexibility of the package and its integration with other probabilistic modeling packages (e.g. PyMC3). A concrete implementation of `Distribution` provided by NegMAS is `ScipyDistribution`. A special case if the `Real` distribution which represents a delta distribution $\\delta(v)$ at a given real value $v$ (i.e. $p(x)=1$ for $x=v$ and $0$ otherwise) which acts both as a `Distribution` and a `float`. \n", "\n", "* `Value` This is the input and output type used whenever a utility value is to be represented in the whole package. It is defined as a union of a real value and a `Distribution` (`float | Distribution`). This way, it is possible to pass utility distributions to most functions expecting (or returning) a utility value including utility functions. \n", "\n", "This means that both of the following are valid utility values" ] }, { "cell_type": "code", "execution_count": 21, "id": "ac6da032", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:18.392281Z", "iopub.status.busy": "2022-02-19T02:24:18.390998Z", "iopub.status.idle": "2022-02-19T02:24:18.394528Z", "shell.execute_reply": "2022-02-19T02:24:18.394970Z" }, "papermill": { "duration": 0.102182, "end_time": "2022-02-19T02:24:18.395153", "exception": false, "start_time": "2022-02-19T02:24:18.292971", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
1.0\n",
                            "
\n" ], "text/plain": [ "\u001b[1;36m1.0\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
U(0.0, 1.0)\n",
                            "
\n" ], "text/plain": [ "\u001b[1;35mU\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m0.0\u001b[0m, \u001b[1;36m1.0\u001b[0m\u001b[1m)\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "u1 = Real(1.0)\n", "u2 = UniformDistribution() # standard normal distribution\n", "print(u1)\n", "print(u2)" ] }, { "cell_type": "markdown", "id": "ab00dab1", "metadata": { "papermill": { "duration": 0.11622, "end_time": "2022-02-19T02:24:18.629125", "exception": false, "start_time": "2022-02-19T02:24:18.512905", "status": "completed" }, "tags": [] }, "source": [ "### Preferences\n", "\n", "`Rational` entities in NegMAS (including `Agent`s, `Negotiator`s, and `Controller`s) can have `Preferences` which define how much they prefer an `Outcome` over another. Several types of preferences are supported in NegMAS and they all must implement the `BasePref` protocol.\n", "\n", "### Ordinal and Cardinal Preferences\n", "\n", "The most general `Preferences` type in NegMAS is `Ordinal` `Preferences` which can only represent partial ordering of outcomes in the outcome-space throgh the `is_not_worse()` method. An entity with this kind of preferences can compare two outcomes but it gets one bit of information out of this comparison (which is better for the entity) and has no way to know *how much* is the difference\n", "\n", "`CarindalProb` `Preferences`, on the other hand, implement `difference_prob()` which return a `Distribution` indicating *how much* is the difference between two outcomes. A crisp version (`CardinalCrisp`) moreover implements `difference()` which returns a `float` indicating *exactly* the difference in value for the entity between two outcomes.\n", "\n", "Every `CadrinalCrisp` object is a `CardinalProb` which is also an `Ordinal` object.\n", "\n", "### Crisp and Prob Preferences\n", "\n", "NegMAS usually implements two versions of each `Preferences` type (other than `Ordinal`) that represent a probabilistic version (ending with `Prob`) returing `Distribution`s when queried, and a crisp version (ending with `Crisp`) returning a `float`. This simplifies the development of agents and negotiators working with probability distributions.\n", "\n", "### Stationary and Non-Stationary Preferences\n", "\n", "Stationary `Preferences` are those that *do not change during the lifetime of their owner*, while non-stationary `Preferences` are allowed to change. The entity having non-stationary preferences usually faces a harder problem achieving its goals as it needs to take into account this possible change. Entities interacting with other entities with non-stationary `Preferences` are also in reatively harder situation comapred with those dealing with entities with stationary `Preferences`. \n", "\n", "Stationary Preference type names start with `Stationary` (e.g. `StationaryCardinalProb`) while non-stationary types start with `NonStationary` (e.g. `NonStationaryCardinalProb`).\n", "\n" ] }, { "cell_type": "markdown", "id": "576cb4aa", "metadata": { "papermill": { "duration": 0.117832, "end_time": "2022-02-19T02:24:18.843652", "exception": false, "start_time": "2022-02-19T02:24:18.725820", "status": "completed" }, "tags": [] }, "source": [ "### Utility Functions\n" ] }, { "cell_type": "markdown", "id": "25a02e88", "metadata": { "papermill": { "duration": 0.167007, "end_time": "2022-02-19T02:24:19.209653", "exception": false, "start_time": "2022-02-19T02:24:19.042646", "status": "completed" }, "tags": [] }, "source": [ "Utility functions are entities that take an `Outcome` and return its `Value`. There are many types of utility functions defined in the literature. In this package, the base of all utiliy functions is the `BaseUtilityFunction` class which is defined in the `preferences.ufun` module. It behaves like a standard python `Callable` which can be called with a single `Outcome` object (i.e. a dictionary, list, tuple etc representing an outcome) and returns a `Value`. This allows utility functions to return a distribution instead of a single utility value. Special cases are `UtilityFunction` which is the base class of all crisp ufuns (returning a `float` when called) and `ProbUtilityFunction` which is the base class of all probabilistic ufuns (returning a `Distribution` when called).\n", "\n", "Utility functions in `negmas` have a helper `property` called `type` which returns the type of the utility function and a helper function `eu` for returning the expected utility of a given outcome which is guaranteed to return a real number (`float`) even if the utiliy function itself is returning a utility distribution.\n", "\n", "To implement a specific utility function, you need to override the single `eval` function provided in the `UtilityFunction`/`ProbUtilityFunction` abstract base class. This is a simple example:\n" ] }, { "cell_type": "code", "execution_count": 22, "id": "6fc08cfe", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:19.539637Z", "iopub.status.busy": "2022-02-19T02:24:19.538554Z", "iopub.status.idle": "2022-02-19T02:24:19.541995Z", "shell.execute_reply": "2022-02-19T02:24:19.542692Z" }, "papermill": { "duration": 0.213181, "end_time": "2022-02-19T02:24:19.543067", "exception": false, "start_time": "2022-02-19T02:24:19.329886", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/plain": [ "30.0" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "COST = 0\n", "\n", "\n", "class ConstUtilityFunction(UtilityFunction):\n", " def eval(self, offer):\n", " try:\n", " return 3.0 * offer[COST]\n", " except KeyError: # No value was given to the cost\n", " return None\n", "\n", " def xml(self):\n", " return \"\"\n", "\n", "\n", "f = ConstUtilityFunction()\n", "f((10,))" ] }, { "cell_type": "markdown", "id": "439d16de", "metadata": { "papermill": { "duration": 0.142935, "end_time": "2022-02-19T02:24:19.796766", "exception": false, "start_time": "2022-02-19T02:24:19.653831", "status": "completed" }, "tags": [] }, "source": [ "Note that we used `StationaryUtilityFunction` as the base class to inform users of the `ConstUtilityFunction` class that it represents a stationary ufun which means that it is OK to cache results of calls to the ufun for example.\n", "\n", "General Utility functions can store internal state and use it to return different values for the same outcome over time allowing for dynamic change or evolution of them during negotiations. For example this *silly* utility function responds to the mood of the user:" ] }, { "cell_type": "code", "execution_count": 23, "id": "e0597c46", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:20.045010Z", "iopub.status.busy": "2022-02-19T02:24:20.044121Z", "iopub.status.idle": "2022-02-19T02:24:20.047229Z", "shell.execute_reply": "2022-02-19T02:24:20.047688Z" }, "papermill": { "duration": 0.130778, "end_time": "2022-02-19T02:24:20.047873", "exception": false, "start_time": "2022-02-19T02:24:19.917095", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
Utility in good mood of (10,) is 10.0\n",
                            "
\n" ], "text/plain": [ "Utility in good mood of \u001b[1m(\u001b[0m\u001b[1;36m10\u001b[0m,\u001b[1m)\u001b[0m is \u001b[1;36m10.0\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
Utility in bad mood of (10,) is 1.0\n",
                            "
\n" ], "text/plain": [ "Utility in bad mood of \u001b[1m(\u001b[0m\u001b[1;36m10\u001b[0m,\u001b[1m)\u001b[0m is \u001b[1;36m1.0\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
Utility in good mood of (10,) is undecidable: Cannot calculate utility for (10,)\n",
                            "
\n" ], "text/plain": [ "Utility in good mood of \u001b[1m(\u001b[0m\u001b[1;36m10\u001b[0m,\u001b[1m)\u001b[0m is undecidable: Cannot calculate utility for \u001b[1m(\u001b[0m\u001b[1;36m10\u001b[0m,\u001b[1m)\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "class MoodyUtilityFunction(UtilityFunction):\n", " def __init__(self, mood=\"good\", stationary=False):\n", " super().__init__()\n", " self.mood = mood\n", " self._stationary = stationary\n", "\n", " def to_stationary(self):\n", " return MoodyUtilityFunction(mood=self.mood, stationary=True)\n", "\n", " def eval(self, offer):\n", " if self.mood not in (\"good\", \"bad\"):\n", " raise ValueError(f\"Cannot calculate utility for {offer}\")\n", " return float(offer[COST]) if self.mood == \"good\" else 0.1 * offer[COST]\n", "\n", " def set_mood(self, mood):\n", " if self._stationary:\n", " return\n", " self.mood = mood\n", "\n", " def xml(self):\n", " pass\n", "\n", "\n", "offer = (10,)\n", "\n", "f = MoodyUtilityFunction()\n", "# I am in a good mode now\n", "print(f\"Utility in good mood of {offer} is {f(offer)}\")\n", "f.set_mood(\"bad\")\n", "print(f\"Utility in bad mood of {offer} is {f(offer)}\")\n", "f.set_mood(\"undecided\")\n", "try:\n", " y = f(offer)\n", "except ValueError as e:\n", " print(f\"Utility in good mood of {offer} is undecidable: {e}\")" ] }, { "cell_type": "markdown", "id": "1fd5fdbb", "metadata": { "papermill": { "duration": 0.103863, "end_time": "2022-02-19T02:24:20.245107", "exception": false, "start_time": "2022-02-19T02:24:20.141244", "status": "completed" }, "tags": [] }, "source": [ "Notice that (as the last example shows) utility functions can return `None` to indicate that the utility value cannot be inferred for this outcome/offer.\n", "\n", "### Preferences Protcols\n", "\n", "The `preferences` module provide a set of other python protocols that guarantee that a given `Preferences` object has some predefined properties. This can be used by developers to adjust the behavior of any entity based on the specific features of its preferences or to limit the applicability of some strategy to a given `Preferences` type.\n", "\n", "Here are some examples of these protocols all applying to utility functions (see next section) (note that *protocol* here is used in the Pythonic sense of a duck-typed interface):\n", "\n", "| Protoocol | Meaning |\n", "| :--- | :--- |\n", "| Scalable | The utility function can be scaled by some factor |\n", "| PartiallyScalable | The utility function can be scaled in some part of the outcome-space |\n", "| Shiftable | The utility function can be shifted by some constant value |\n", "| PartiallyShiftable | The utility function can be by some constant value in some part of the outcome-space |\n", "| Normalizable | The utility function can be normalized to fall in some given range |\n", "| HasReservedOutcome | The utility function defines some outcome as the default outcome in case of disagreement | \n", "| HasReservedDistribution | The utility function defines some distribution as the distribution from which a value is chosen in case of disagreement | \n", "| HasReservedValue | The utility function defines some value as the default value for the agent in case of agreement in case of disagreement | \n", "| HasRange | The utility function defines some value as the default value for the agent in case of agreement in case of disagreement | \n", "| IndIssues | The utility function is a mathematical function (linear or otherwise) of a set of single-issue functions. | \n", "\n", "The package provides a set of predefined utility functions representing most widely used types. The following subsections describe them briefly.\n" ] }, { "cell_type": "markdown", "id": "155612af", "metadata": { "papermill": { "duration": 0.16655, "end_time": "2022-02-19T02:24:20.527126", "exception": false, "start_time": "2022-02-19T02:24:20.360576", "status": "completed" }, "tags": [] }, "source": [ "### Linear Additive Utility Functions\n", "The `LinearAdditiveUtilityFunction` class represents a function that linearly aggregate utilities assigned to issues in the given outcome which can be defined mathematically as follows:\n", "\n", "$$U(o) = \\sum_{i=0}^{\\left|o\\right|}{w_i\\times g_i(o_i)}$$\n", "\n", "where $o$ is an outcome, $w$ is a real-valued weight vector, $\\left|o\\right|$ is the number of issues, $o_i$ if the value assigned in outcome $o$ to issue $i$, and $g$ is a vector of functions each mapping one issue of the outcome to some real-valued number (utility of this issue).\n", "\n", "Notice that despite the name, this type of utiliy functions can represent nonlinear relation between issue values and utility values. The linearity is in how these possibly nonlinear mappings are being combind to generate a utility value for the outcome.\n" ] }, { "cell_type": "markdown", "id": "42bb7905", "metadata": { "papermill": { "duration": 0.126506, "end_time": "2022-02-19T02:24:20.749407", "exception": false, "start_time": "2022-02-19T02:24:20.622901", "status": "completed" }, "tags": [] }, "source": [ "Note that a utility function needs to know the outcome-space over which is it defined. There are three ways to pass this to the `UtilityFunction` constructor:\n", "\n", "1. **issues=...** pass a list of issues (usually made using `make_issue`)\n", "2. **outcome_space=...** pass an `OutcomeSpace` type (usualy made using `make_os`)\n", "3. **outcomes=...** pass a list of outcomes.\n", "\n", "The following three ufuns are exactly equivalent:" ] }, { "cell_type": "code", "execution_count": 24, "id": "244cb288", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:21.029496Z", "iopub.status.busy": "2022-02-19T02:24:21.027151Z", "iopub.status.idle": "2022-02-19T02:24:21.030415Z", "shell.execute_reply": "2022-02-19T02:24:21.028133Z" }, "papermill": { "duration": 0.165989, "end_time": "2022-02-19T02:24:21.030639", "exception": false, "start_time": "2022-02-19T02:24:20.864650", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "issues = [make_issue(2, \"i1\"), make_issue(2, \"i2\")]\n", "u1 = LinearAdditiveUtilityFunction(\n", " issues=issues, values=[lambda x: x, lambda x: x, lambda x: x]\n", ")" ] }, { "cell_type": "code", "execution_count": 25, "id": "88fd6aa0", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:21.378015Z", "iopub.status.busy": "2022-02-19T02:24:21.377008Z", "iopub.status.idle": "2022-02-19T02:24:21.379551Z", "shell.execute_reply": "2022-02-19T02:24:21.380342Z" }, "papermill": { "duration": 0.144777, "end_time": "2022-02-19T02:24:21.380591", "exception": false, "start_time": "2022-02-19T02:24:21.235814", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "u2 = LinearAdditiveUtilityFunction(\n", " outcome_space=make_os(issues=issues), values=[lambda x: x, lambda x: x, lambda x: x]\n", ")" ] }, { "cell_type": "code", "execution_count": 26, "id": "88b231e4", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:21.613672Z", "iopub.status.busy": "2022-02-19T02:24:21.612590Z", "iopub.status.idle": "2022-02-19T02:24:21.615144Z", "shell.execute_reply": "2022-02-19T02:24:21.616073Z" }, "papermill": { "duration": 0.123033, "end_time": "2022-02-19T02:24:21.616400", "exception": false, "start_time": "2022-02-19T02:24:21.493367", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "u3 = LinearAdditiveUtilityFunction(\n", " outcomes=[(0, 0), (0, 1), (1, 0), (1, 1)],\n", " values=[lambda x: x, lambda x: x, lambda x: x],\n", ")" ] }, { "cell_type": "markdown", "id": "089d6e2f", "metadata": { "papermill": { "duration": 0.099713, "end_time": "2022-02-19T02:24:21.834853", "exception": false, "start_time": "2022-02-19T02:24:21.735140", "status": "completed" }, "tags": [] }, "source": [ "For example, the following utility function represents the utility of `buyer` who wants low cost, many items, and prefers delivery:" ] }, { "cell_type": "code", "execution_count": 27, "id": "6faa5ba0", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:22.088536Z", "iopub.status.busy": "2022-02-19T02:24:22.087200Z", "iopub.status.idle": "2022-02-19T02:24:22.090908Z", "shell.execute_reply": "2022-02-19T02:24:22.090027Z" }, "papermill": { "duration": 0.134135, "end_time": "2022-02-19T02:24:22.091197", "exception": false, "start_time": "2022-02-19T02:24:21.957062", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "issues = [\n", " make_issue((0, 10), \"price\"),\n", " make_issue((1, 10), \"number of items\"),\n", " make_issue([\"delivered\", \"not delivered\"], \"delivery\"),\n", "]\n", "buyer_utility = LinearAdditiveUtilityFunction(\n", " {\n", " \"price\": lambda x: -x,\n", " \"number of items\": lambda x: 0.5 * x,\n", " \"delivery\": {\"delivered\": 1.0, \"not delivered\": 0.0},\n", " },\n", " issues=issues,\n", ")" ] }, { "cell_type": "markdown", "id": "ce6759de", "metadata": { "papermill": { "duration": 0.126008, "end_time": "2022-02-19T02:24:22.350255", "exception": false, "start_time": "2022-02-19T02:24:22.224247", "status": "completed" }, "tags": [] }, "source": [ "Given this definition of utility, we can easily calculate the utility of different options:" ] }, { "cell_type": "code", "execution_count": 28, "id": "557e6089", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:22.569566Z", "iopub.status.busy": "2022-02-19T02:24:22.568689Z", "iopub.status.idle": "2022-02-19T02:24:22.571784Z", "shell.execute_reply": "2022-02-19T02:24:22.572280Z" }, "papermill": { "duration": 0.113568, "end_time": "2022-02-19T02:24:22.572499", "exception": false, "start_time": "2022-02-19T02:24:22.458931", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
0.5\n",
                            "
\n" ], "text/plain": [ "\u001b[1;36m0.5\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "print(buyer_utility((1.0, 3, \"not delivered\")))" ] }, { "cell_type": "markdown", "id": "23425b2f", "metadata": { "papermill": { "duration": 0.126712, "end_time": "2022-02-19T02:24:22.800525", "exception": false, "start_time": "2022-02-19T02:24:22.673813", "status": "completed" }, "tags": [] }, "source": [ "Now what happens if we offer to deliver the items:" ] }, { "cell_type": "code", "execution_count": 29, "id": "3e6b963b", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:23.120334Z", "iopub.status.busy": "2022-02-19T02:24:23.119228Z", "iopub.status.idle": "2022-02-19T02:24:23.123198Z", "shell.execute_reply": "2022-02-19T02:24:23.124078Z" }, "papermill": { "duration": 0.188143, "end_time": "2022-02-19T02:24:23.125378", "exception": false, "start_time": "2022-02-19T02:24:22.937235", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
1.5\n",
                            "
\n" ], "text/plain": [ "\u001b[1;36m1.5\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "print(buyer_utility((1.0, 3, \"delivered\")))" ] }, { "cell_type": "markdown", "id": "14d2e633", "metadata": { "papermill": { "duration": 0.112885, "end_time": "2022-02-19T02:24:23.383171", "exception": false, "start_time": "2022-02-19T02:24:23.270286", "status": "completed" }, "tags": [] }, "source": [ "And if delivery was accompanied with an increase in price" ] }, { "cell_type": "code", "execution_count": 30, "id": "0c2ca8d9", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:23.615093Z", "iopub.status.busy": "2022-02-19T02:24:23.613893Z", "iopub.status.idle": "2022-02-19T02:24:23.619423Z", "shell.execute_reply": "2022-02-19T02:24:23.620477Z" }, "papermill": { "duration": 0.123735, "end_time": "2022-02-19T02:24:23.620731", "exception": false, "start_time": "2022-02-19T02:24:23.496996", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
0.7\n",
                            "
\n" ], "text/plain": [ "\u001b[1;36m0.7\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "print(buyer_utility((1.8, 3, \"delivered\")))" ] }, { "cell_type": "markdown", "id": "f05377b7", "metadata": { "papermill": { "duration": 0.107036, "end_time": "2022-02-19T02:24:23.838411", "exception": false, "start_time": "2022-02-19T02:24:23.731375", "status": "completed" }, "tags": [] }, "source": [ "It is clear that this buyer will still accept that increase of price from ``'1.0'`` to ``'1.8``' if it is accompanied with the delivery option.\n", "\n", "As explained before, you can use `dict2outcome` to make ufun calls more readable:" ] }, { "cell_type": "code", "execution_count": 31, "id": "875b7601", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:24.071447Z", "iopub.status.busy": "2022-02-19T02:24:24.070273Z", "iopub.status.idle": "2022-02-19T02:24:24.074741Z", "shell.execute_reply": "2022-02-19T02:24:24.075541Z" }, "papermill": { "duration": 0.117145, "end_time": "2022-02-19T02:24:24.075812", "exception": false, "start_time": "2022-02-19T02:24:23.958667", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/plain": [ "0.7" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "buyer_utility(\n", " dict2outcome(\n", " {\"price\": 1.8, \"number of items\": 3, \"delivery\": \"delivered\"},\n", " issues=buyer_utility.issues,\n", " )\n", ")" ] }, { "cell_type": "markdown", "id": "7af40cee", "metadata": { "papermill": { "duration": 0.121062, "end_time": "2022-02-19T02:24:24.300497", "exception": false, "start_time": "2022-02-19T02:24:24.179435", "status": "completed" }, "tags": [] }, "source": [ "### Nonlinear Aggregation Utility Functions\n", "A direct generalization of the linear agggregation utility functions is provided by the `NonLinearAggregationUtilityFunction` which represents the following function:\n", "\n", "$$U(o) = f\\left(\\left\\{{g_i(o_i)}\\right\\}\\right)$$\n", "\n", "where $g$ is a vector of functions defined as before and $f$ is a mapping from a vector of real-values to a single real value.\n", "\n", "For example, a seller's utility can be defined as:" ] }, { "cell_type": "code", "execution_count": 32, "id": "7a20b040", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:24.552669Z", "iopub.status.busy": "2022-02-19T02:24:24.551577Z", "iopub.status.idle": "2022-02-19T02:24:24.556400Z", "shell.execute_reply": "2022-02-19T02:24:24.555104Z" }, "papermill": { "duration": 0.132835, "end_time": "2022-02-19T02:24:24.557316", "exception": false, "start_time": "2022-02-19T02:24:24.424481", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "seller_utility = NonLinearAggregationUtilityFunction(\n", " (lambda x: x, lambda x: 0.5 * x, {\"delivered\": 1.0, \"not delivered\": 0.0}),\n", " f=lambda x: x[0] / x[1] - 0.5 * x[2],\n", ")" ] }, { "cell_type": "markdown", "id": "0f8c0349", "metadata": { "papermill": { "duration": 0.127918, "end_time": "2022-02-19T02:24:24.804216", "exception": false, "start_time": "2022-02-19T02:24:24.676298", "status": "completed" }, "tags": [] }, "source": [ "This utility will go up with the ``price`` and down with the ``number of items`` as expected but not linearly.\n", "\n", "We can now evaluate different options similar to the case for the buyer:" ] }, { "cell_type": "code", "execution_count": 33, "id": "beff8416", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:25.111659Z", "iopub.status.busy": "2022-02-19T02:24:25.110200Z", "iopub.status.idle": "2022-02-19T02:24:25.116153Z", "shell.execute_reply": "2022-02-19T02:24:25.116980Z" }, "papermill": { "duration": 0.186108, "end_time": "2022-02-19T02:24:25.117723", "exception": false, "start_time": "2022-02-19T02:24:24.931615", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
0.6666666666666666\n",
                            "
\n" ], "text/plain": [ "\u001b[1;36m0.6666666666666666\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "print(seller_utility((1.0, 3, \"not delivered\")))" ] }, { "cell_type": "code", "execution_count": 34, "id": "3bcc19cf", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:25.444990Z", "iopub.status.busy": "2022-02-19T02:24:25.443275Z", "iopub.status.idle": "2022-02-19T02:24:25.448483Z", "shell.execute_reply": "2022-02-19T02:24:25.449316Z" }, "papermill": { "duration": 0.15144, "end_time": "2022-02-19T02:24:25.449650", "exception": false, "start_time": "2022-02-19T02:24:25.298210", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
0.16666666666666663\n",
                            "
\n" ], "text/plain": [ "\u001b[1;36m0.16666666666666663\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "print(seller_utility((1.0, 3, \"delivered\")))" ] }, { "cell_type": "code", "execution_count": 35, "id": "5b96d7da", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:25.744673Z", "iopub.status.busy": "2022-02-19T02:24:25.742129Z", "iopub.status.idle": "2022-02-19T02:24:25.748202Z", "shell.execute_reply": "2022-02-19T02:24:25.746972Z" }, "papermill": { "duration": 0.171505, "end_time": "2022-02-19T02:24:25.748522", "exception": false, "start_time": "2022-02-19T02:24:25.577017", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
0.7\n",
                            "
\n" ], "text/plain": [ "\u001b[1;36m0.7\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "print(seller_utility((1.8, 3, \"delivered\")))" ] }, { "cell_type": "markdown", "id": "1689794c", "metadata": { "papermill": { "duration": 0.124595, "end_time": "2022-02-19T02:24:25.980056", "exception": false, "start_time": "2022-02-19T02:24:25.855461", "status": "completed" }, "tags": [] }, "source": [ "### Hyper Rectangle Utility Functions\n", "In many cases, it is not possible to define a utility mapping for every issue independently. We provide the utility function `HyperVolumeUtilityFunction` to handle this situation by allowing for representation of a set of nonlinear functions defined on arbitrary hyper-volumes of the space of outcomes.\n", "\n", "The simplest example is a nonlinear-function that is defined over the whole space but that nonlinearly combines several issues to calculate the utility. \n", "\n", "For example the previous `NonLinearUtilityFunction` for the ``seller`` can be represented as follows:\n" ] }, { "cell_type": "code", "execution_count": 36, "id": "9cd6f0ab", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:26.194479Z", "iopub.status.busy": "2022-02-19T02:24:26.193648Z", "iopub.status.idle": "2022-02-19T02:24:26.196964Z", "shell.execute_reply": "2022-02-19T02:24:26.197439Z" }, "papermill": { "duration": 0.110029, "end_time": "2022-02-19T02:24:26.197662", "exception": false, "start_time": "2022-02-19T02:24:26.087633", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
0.6666666666666666\n",
                            "
\n" ], "text/plain": [ "\u001b[1;36m0.6666666666666666\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
0.16666666666666663\n",
                            "
\n" ], "text/plain": [ "\u001b[1;36m0.16666666666666663\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
0.7\n",
                            "
\n" ], "text/plain": [ "\u001b[1;36m0.7\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "seller_utility = HyperRectangleUtilityFunction(\n", " outcome_ranges=[None],\n", " utilities=[\n", " lambda x: 2.0 * x[\"price\"] / x[\"number of items\"]\n", " - 0.5 * int(x[\"delivery\"] == \"delivered\")\n", " ],\n", ")\n", "print(seller_utility({\"price\": 1.0, \"number of items\": 3, \"delivery\": \"not delivered\"}))\n", "print(seller_utility({\"price\": 1.0, \"number of items\": 3, \"delivery\": \"delivered\"}))\n", "print(seller_utility({\"price\": 1.8, \"number of items\": 3, \"delivery\": \"delivered\"}))" ] }, { "cell_type": "markdown", "id": "596d5849", "metadata": { "papermill": { "duration": 0.100068, "end_time": "2022-02-19T02:24:26.412121", "exception": false, "start_time": "2022-02-19T02:24:26.312053", "status": "completed" }, "tags": [] }, "source": [ "This function recovered exactly the same values as the `NonlinearUtilityFuction` defined earlier by defining a single hyper-volume with the special value of `None` which applies the function to the whole space and then defining a single nonlinear function over the whole space to implement the required utiltiy mapping.\n", "\n", "`HyperVolumeUtilityFunction` was designed to a more complex situation in which you can have multiple nonlinear functions defined over different parts of the space of possible outcomes.\n", "\n", "Here is an example in which we combine one global utility function and two different local ones:\n" ] }, { "cell_type": "code", "execution_count": 37, "id": "b0e08678", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:26.642764Z", "iopub.status.busy": "2022-02-19T02:24:26.641873Z", "iopub.status.idle": "2022-02-19T02:24:26.644239Z", "shell.execute_reply": "2022-02-19T02:24:26.644691Z" }, "papermill": { "duration": 0.136629, "end_time": "2022-02-19T02:24:26.644876", "exception": false, "start_time": "2022-02-19T02:24:26.508247", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "f = HyperRectangleUtilityFunction(\n", " outcome_ranges=[\n", " None,\n", " {0: (1.0, 2.0), 1: (1.0, 2.0)},\n", " {0: (1.4, 2.0), 2: (2.0, 3.0)},\n", " ],\n", " utilities=[5.0, 2.0, lambda x: 2 * x[2] + x[0]],\n", " weights=[1, 0.5, 2.5],\n", ")" ] }, { "cell_type": "markdown", "id": "64b2dc0c", "metadata": { "papermill": { "duration": 0.119047, "end_time": "2022-02-19T02:24:26.884756", "exception": false, "start_time": "2022-02-19T02:24:26.765709", "status": "completed" }, "tags": [] }, "source": [ "There are three nonlinear functions in this example:\n", "\n", "* A global function which gives a utility of ``5.0`` everywhere\n", "* A local function which gives a utility of ``2.0`` to any outcome for which the first issue (issue ``0``) has a value between ``1.0 and ``2.0`` and the second issue (issue ``1``) has a value between ``1.0`` and ``2.0`` which is represented as: ``{0: (1.0, 2.0), 1: (1.0, 2.0)}``\n", "* A second local function which gives a utility that depends on both the third and first issues ``(lambda x: 2 * x[2] + x[0]``) on the range ``{0: (1.4, 2.0), 2: (2.0, 3.0)}``.\n", "\n", "You can also have weights for combining these functions linearly. The default is just to sum all values from these functions to calculate the final utility.\n", "\n", "Here are some examples:\n", "* An outcome that falls in the range of all constraints:" ] }, { "cell_type": "code", "execution_count": 38, "id": "e6849a01", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:27.213832Z", "iopub.status.busy": "2022-02-19T02:24:27.211479Z", "iopub.status.idle": "2022-02-19T02:24:27.218794Z", "shell.execute_reply": "2022-02-19T02:24:27.217889Z" }, "papermill": { "duration": 0.16554, "end_time": "2022-02-19T02:24:27.219085", "exception": false, "start_time": "2022-02-19T02:24:27.053545", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/plain": [ "22.25" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f([1.5, 1.5, 2.5])" ] }, { "cell_type": "markdown", "id": "afdc9223", "metadata": { "papermill": { "duration": 0.116036, "end_time": "2022-02-19T02:24:27.467929", "exception": false, "start_time": "2022-02-19T02:24:27.351893", "status": "completed" }, "tags": [] }, "source": [ "* An outcome that falls in the range of the global and first local constraints only:" ] }, { "cell_type": "code", "execution_count": 39, "id": "b7d55261", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:27.666359Z", "iopub.status.busy": "2022-02-19T02:24:27.665435Z", "iopub.status.idle": "2022-02-19T02:24:27.668951Z", "shell.execute_reply": "2022-02-19T02:24:27.669398Z" }, "papermill": { "duration": 0.109475, "end_time": "2022-02-19T02:24:27.669583", "exception": false, "start_time": "2022-02-19T02:24:27.560108", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/plain": [ "6.0" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f([1.5, 1.5, 1.0])" ] }, { "cell_type": "markdown", "id": "19db401d", "metadata": { "papermill": { "duration": 0.121049, "end_time": "2022-02-19T02:24:27.897254", "exception": false, "start_time": "2022-02-19T02:24:27.776205", "status": "completed" }, "tags": [] }, "source": [ "* An outcome that misses a value for some of the issues:" ] }, { "cell_type": "code", "execution_count": 40, "id": "109699e1", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:28.129197Z", "iopub.status.busy": "2022-02-19T02:24:28.128263Z", "iopub.status.idle": "2022-02-19T02:24:28.131509Z", "shell.execute_reply": "2022-02-19T02:24:28.131977Z" }, "papermill": { "duration": 0.127127, "end_time": "2022-02-19T02:24:28.132164", "exception": false, "start_time": "2022-02-19T02:24:28.005037", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
None\n",
                            "
\n" ], "text/plain": [ "\u001b[3;35mNone\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "print(f([1.5, 1.5]))" ] }, { "cell_type": "markdown", "id": "d79a5864", "metadata": { "papermill": { "duration": 0.124581, "end_time": "2022-02-19T02:24:28.348513", "exception": false, "start_time": "2022-02-19T02:24:28.223932", "status": "completed" }, "tags": [] }, "source": [ "Notice that in this case, no utility is calculated because we do not know if the outcome falls within the range of the second local function or not. To allow such cases, the initializer of `HyperVolumeUtilityFunction` allows you to ignore such cases:" ] }, { "cell_type": "code", "execution_count": 41, "id": "f2296ac4", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:28.587094Z", "iopub.status.busy": "2022-02-19T02:24:28.586208Z", "iopub.status.idle": "2022-02-19T02:24:28.589400Z", "shell.execute_reply": "2022-02-19T02:24:28.590107Z" }, "papermill": { "duration": 0.144828, "end_time": "2022-02-19T02:24:28.590680", "exception": false, "start_time": "2022-02-19T02:24:28.445852", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
7.0\n",
                            "
\n" ], "text/plain": [ "\u001b[1;36m7.0\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "g = HyperRectangleUtilityFunction(\n", " outcome_ranges=[\n", " None,\n", " {0: (1.0, 2.0), 1: (1.0, 2.0)},\n", " {0: (1.4, 2.0), 2: (2.0, 3.0)},\n", " ],\n", " utilities=[5.0, 2.0, lambda x: 2 * x[2] + x[0]],\n", " ignore_failing_range_utilities=True,\n", " ignore_issues_not_in_input=True,\n", ")\n", "print(g([1.5, 1.5]))" ] }, { "cell_type": "markdown", "id": "4baa358f", "metadata": { "papermill": { "duration": 0.133389, "end_time": "2022-02-19T02:24:28.871488", "exception": false, "start_time": "2022-02-19T02:24:28.738099", "status": "completed" }, "tags": [] }, "source": [ "### Nonlinear Hyper Rectangle Utility Functions\n", "`HyperVolumeUtilityFunction` should be able to handle most complex multi-issue utility evaluations but we provide a more general class called `NoneLinearHyperVolumeUtilityFunction` which replaces the simple weighted summation of local/global functions implemented in `HyperVolumeUtilityFunction` with a more general nonlinar mapping.\n", "\n", "The relation between `NoneLinearHyperVolumeUtilityFunction` and `HyperVolumeUtilityFunction` is exactly the same as that between `NonLinearAdditiveUtilityFunction` and `LinearAdditiveUtilityFunction`" ] }, { "cell_type": "markdown", "id": "5c5eb7b8", "metadata": { "papermill": { "duration": 0.200787, "end_time": "2022-02-19T02:24:29.214785", "exception": false, "start_time": "2022-02-19T02:24:29.013998", "status": "completed" }, "tags": [] }, "source": [ "## Other utility function types\n", "\n", "There are several other built-in utility function types in the utilities module. Operations for utility function serialization to and from xml as sell as normalization, finding pareto-frontier, generation of ufuns, etc are also available. Please check the documentation of the utilities module for more details" ] }, { "cell_type": "code", "execution_count": 42, "id": "28a2b297", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:29.448141Z", "iopub.status.busy": "2022-02-19T02:24:29.447009Z", "iopub.status.idle": "2022-02-19T02:24:29.451044Z", "shell.execute_reply": "2022-02-19T02:24:29.452210Z" }, "papermill": { "duration": 0.123372, "end_time": "2022-02-19T02:24:29.452628", "exception": false, "start_time": "2022-02-19T02:24:29.329256", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
[\n",
                            "    'BaseUtilityFunction',\n",
                            "    'UtilityFunction',\n",
                            "    'ProbUtilityFunction',\n",
                            "    'PresortingInverseUtilityFunction',\n",
                            "    'SamplingInverseUtilityFunction',\n",
                            "    'DiscountedUtilityFunction',\n",
                            "    'ConstUtilityFunction',\n",
                            "    'LinearUtilityAggregationFunction',\n",
                            "    'LinearAdditiveUtilityFunction',\n",
                            "    'LinearUtilityFunction',\n",
                            "    'AffineUtilityFunction',\n",
                            "    'MappingUtilityFunction',\n",
                            "    'NonLinearAggregationUtilityFunction',\n",
                            "    'HyperRectangleUtilityFunction',\n",
                            "    'NonlinearHyperRectangleUtilityFunction',\n",
                            "    'RandomUtilityFunction',\n",
                            "    'RankOnlyUtilityFunction',\n",
                            "    'ProbMappingUtilityFunction',\n",
                            "    'IPUtilityFunction',\n",
                            "    'ILSUtilityFunction',\n",
                            "    'UniformUtilityFunction',\n",
                            "    'ProbRandomUtilityFunction',\n",
                            "    'WeightedUtilityFunction',\n",
                            "    'ComplexNonlinearUtilityFunction'\n",
                            "]\n",
                            "
\n" ], "text/plain": [ "\u001b[1m[\u001b[0m\n", " \u001b[32m'BaseUtilityFunction'\u001b[0m,\n", " \u001b[32m'UtilityFunction'\u001b[0m,\n", " \u001b[32m'ProbUtilityFunction'\u001b[0m,\n", " \u001b[32m'PresortingInverseUtilityFunction'\u001b[0m,\n", " \u001b[32m'SamplingInverseUtilityFunction'\u001b[0m,\n", " \u001b[32m'DiscountedUtilityFunction'\u001b[0m,\n", " \u001b[32m'ConstUtilityFunction'\u001b[0m,\n", " \u001b[32m'LinearUtilityAggregationFunction'\u001b[0m,\n", " \u001b[32m'LinearAdditiveUtilityFunction'\u001b[0m,\n", " \u001b[32m'LinearUtilityFunction'\u001b[0m,\n", " \u001b[32m'AffineUtilityFunction'\u001b[0m,\n", " \u001b[32m'MappingUtilityFunction'\u001b[0m,\n", " \u001b[32m'NonLinearAggregationUtilityFunction'\u001b[0m,\n", " \u001b[32m'HyperRectangleUtilityFunction'\u001b[0m,\n", " \u001b[32m'NonlinearHyperRectangleUtilityFunction'\u001b[0m,\n", " \u001b[32m'RandomUtilityFunction'\u001b[0m,\n", " \u001b[32m'RankOnlyUtilityFunction'\u001b[0m,\n", " \u001b[32m'ProbMappingUtilityFunction'\u001b[0m,\n", " \u001b[32m'IPUtilityFunction'\u001b[0m,\n", " \u001b[32m'ILSUtilityFunction'\u001b[0m,\n", " \u001b[32m'UniformUtilityFunction'\u001b[0m,\n", " \u001b[32m'ProbRandomUtilityFunction'\u001b[0m,\n", " \u001b[32m'WeightedUtilityFunction'\u001b[0m,\n", " \u001b[32m'ComplexNonlinearUtilityFunction'\u001b[0m\n", "\u001b[1m]\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "print(list(_ for _ in negmas.preferences.__all__ if _.endswith(\"Function\")))" ] }, { "cell_type": "markdown", "id": "120b91af", "metadata": { "papermill": { "duration": 0.1112, "end_time": "2022-02-19T02:24:29.682787", "exception": false, "start_time": "2022-02-19T02:24:29.571587", "status": "completed" }, "tags": [] }, "source": [ "## Utility Helpers and Analysis Tools\n", "\n", "NegMAS provides a set of functions that help with common tasks required while developing negotiation agents. These are some examples:\n", "\n", "* **pareto_frontier** Finds the pareto-frontier of a set of utility functions.\n", "* **make_discounted_ufun** Takes a utility function and returns one that is discounted (linearly and/or \n", " exponentially).\n", "* **normalize** Normalizes a utility function within a given range.\n", "* **outcome_with_utility** Finds an outcome with a utility within some range.\n", "* **minmax** Finds the range of values of a utility function and outcomes with highest and lowest utilities." ] }, { "cell_type": "markdown", "id": "b1b00116", "metadata": { "papermill": { "duration": 0.14679, "end_time": "2022-02-19T02:24:29.935757", "exception": false, "start_time": "2022-02-19T02:24:29.788967", "status": "completed" }, "tags": [] }, "source": [ "## Responses\n", "\n", "When negotiations are run, agents are allowed to respond to given offers for the final contract. An offer is simply an outcome (either complete or incomplete depending on the protocol but it is always valid). Negotiators can then respond with one of the values defined by the `Response` enumeration in the `outcomes` module. Currently these are:\n", "\n", "* **ACCEPT_OFFER** Accepts the offer.\n", "* **REJECT_OFFER** Rejects the offer.\n", "* **END_NEGOTIATION** This implies rejection of the offer and further more indicates that the agent is not willing to continue with the negotiation. The protocol is free to handle this situation. It may just end the negotiation with no agreement, may just remove the agent from the negotiation and keep it running with the remaining agents (if that makes sense) or just gives the agent a second chance by treating it as just a ``REJECT_OFFER`` case. In most case the first response (just end the negotiation) is expected.\n", "* **NO_RESPONSE** Making no response at all. This is usually not allowed by negotiation protocols and will be considered a protocol violation in most cases. Nevertheless, negotiation protocols are free to handle this response when it arise in any way.\n", "* **WAIT** Used to make the negotiation wait for a slow running process in one of the negotiators. This should never be returned from user code. It is used by some builtin controllers in the system to synchronize responses (e.g. ``SAOSyncController`` )" ] }, { "cell_type": "markdown", "id": "cc90eb23", "metadata": { "papermill": { "duration": 0.116182, "end_time": "2022-02-19T02:24:30.172679", "exception": false, "start_time": "2022-02-19T02:24:30.056497", "status": "completed" }, "tags": [] }, "source": [ "## Rational Entities\n", "\n", "A `Rational` entity in NegMAS is an object that has an associated `UtilityFunction`. There are three types of `Rational` entities defined in the library:\n", "\n", "* **Negotiator** represents a negotiation agent that can interact with `Mechanism` objects (representing negotiation protocols) using a dedicated `AgentMechanismInterface` the defines public information of the mechanism. A negotiator is tied to a single negotiation. \n", "* **Agent** represents a more complex entity than a negotiation agent. It does not interact directly with negotiation protocols (i.e. it does not have an `AgentMechanismInterface`) and is needed when there is a need to adjust behavior in multiple negotiations and/or when there is a need to interact with a simulation or the real world (represented in negmas by a `World` object) through an `AgentWorldInterface`.\n", "* **Controller** A mid-level entity between `Negotiator` and `Agent`. It can *control* multiple negotiator objects at the same time but it cannot interact with mechanisms or worlds directly. Usually controllers are created by agents to manage a set of interrelated negotiations through dedicated negotiators in each of them.\n", "\n" ] }, { "cell_type": "markdown", "id": "c155e128", "metadata": { "papermill": { "duration": 0.113655, "end_time": "2022-02-19T02:24:30.392341", "exception": false, "start_time": "2022-02-19T02:24:30.278686", "status": "completed" }, "tags": [] }, "source": [ "### Negotiators\n", "Negotiations are conducted by negotiators. We reserve the term ``Agent`` to more complex entities that can interact with a simulation or the real world and spawn ``Negotiator`` objects as needed (see the situated module documentation). The base ``Negotiator`` is implemented in the `negotiators` module. The design of this module tried to achieve maximum flexibility by relying mostly on Mixins instead of inheritance for adding functionality as will be described later.\n", "\n", "To build your negotiator, you need to inherit from a ``Negotiator`` suitable for the negotiation mechanism your negotiator is compatible with, implement its abstract functions.\n", "\n", "Negotiators related to a specific negotiation mechanism are implemented in that mechanism's module. For example, negotiators designed for the Stacked Alternating Offers Mechanism are found in the ``sao`` module." ] }, { "cell_type": "markdown", "id": "e003afc6", "metadata": { "papermill": { "duration": 0.116151, "end_time": "2022-02-19T02:24:30.649640", "exception": false, "start_time": "2022-02-19T02:24:30.533489", "status": "completed" }, "tags": [] }, "source": [ "#### The Base Negotiator\n", "The base class of all negotiators is `Negotiator`. Negotiators define callbacks that are called by `Mechanism`s to implement the *negotiation protocol*.\n", "\n", "The base `Negotiator` class defines basic functionality including the ability to access the `Mechanism` settings in the form of an `AgentMechanismInterface` accessible through the `ami` attribute of the `Negotiator`.\n", "\n", "#### Genius Negotiator\n", "There is a special type of negotiators called ``GeniusNegotiator`` implemented in the ``genius`` module that is capable of interacting with negotiation sessions running in the genius platform (JVM). Please refer to the documentation of ``genius`` module for more information." ] }, { "cell_type": "markdown", "id": "0770029f", "metadata": { "papermill": { "duration": 0.11775, "end_time": "2022-02-19T02:24:30.875980", "exception": false, "start_time": "2022-02-19T02:24:30.758230", "status": "completed" }, "tags": [] }, "source": [ "### Controller\n", "A `Controller` is an object that can control multiple negotiators either by taking full or partial control from the `Negotiator`s. By default, controllers will just resend all requests received to the corresponding negotiator. This means that if you do not override any methods in the controller, all negotiation related actions will still be handled by the `Negotiator`. To allow controllers to actually manage negotiations, a subclass of `Controller` needs to implement these actions without calling the base class's implementation.\n", "\n", "A special kind of negotiator called `ControlledNegotiator` is designed to work with controllers that take full responsibility of the negotiation. These negotiators act just as a relay station passing all requests from the mechanism object to the controller and all responses back. \n" ] }, { "cell_type": "markdown", "id": "9874c783", "metadata": { "papermill": { "duration": 0.17673, "end_time": "2022-02-19T02:24:31.206361", "exception": false, "start_time": "2022-02-19T02:24:31.029631", "status": "completed" }, "tags": [] }, "source": [ "### Agents\n", "\n", "Self interested entities in NegMAS can be represented by either `Negotiator`s or `Agent`s. Use negotiators when a single negotiation session is involved, otherwise use an agent. Agents can own both negotiators and controllers (that manage negotiators) and can act in the `World` (simulated or real)." ] }, { "cell_type": "markdown", "id": "01a09a8d", "metadata": { "papermill": { "duration": 0.1172, "end_time": "2022-02-19T02:24:31.437454", "exception": false, "start_time": "2022-02-19T02:24:31.320254", "status": "completed" }, "tags": [] }, "source": [ "## Putting Everything together\n", "\n", "Other than `Rational` objects, NegMAS defines two types of entities that orchestrate the interactions between `Rational` objects:\n", "\n", "* **Mechanisms** represent interaction protocols which can be negotiation protocols or auctions. A `Mechanism` object connects a set of `Negotiator`s and implements the interaction protocol. \n", "* **Worlds** represent either the real world or (usually) a simulation that connects `Agent`s together. `Agent`s can find each other using the world's `BulletinBoard` (or other mechanisms defined by the world simulation), they can act in the world, receive state from it and -- most importantly for our current purposes -- request/run negotiations involving other agents (through dedicated `Controller` and/or `Negotiator` objects). \n", "\n", "A picture is worth a thousand words. The following figure shows how all the classes we mentioned so far fit together\n", "\n", "\n", "The most important points to notice about this figure are the following:\n", "\n", "* Almost all entities are `NamedObject`s which means they have a *user assigned* name used for debugging, printing, and logging, and a *system assigned* id used when programatically accessing the object. For example, agents request negotiations with other agents from the world using the partner's *id* not *name*.\n", "* `Controller` objects can access neither worlds nor mechanisms directly and they depend on agents to create them and on negotiators to negotiate for them.\n", "* A `UtilityFunction` in negmas is an active entity, it is not just a mathematical function but it can have state, access the mechanism state or settings (through its own `AgentMechanismInterface`) and can change its returned value for the same output during the negotiation. Ufuns need not be dyanmic in this sense but they can be.\n" ] }, { "cell_type": "markdown", "id": "51fdb08f", "metadata": { "papermill": { "duration": 0.13418, "end_time": "2022-02-19T02:24:31.692329", "exception": false, "start_time": "2022-02-19T02:24:31.558149", "status": "completed" }, "tags": [] }, "source": [ "## Mechanisms (Negotiations)\n", "The base ``Mechanism`` class is implemented in the `mechanisms` module.\n", "\n", "All protocols in the package inherit from the `Mechanism` class and provide the following basic functionalities:\n", "\n", "* checking `capabilities` of agents against `requirements` of the protocol\n", "* allowing agents to be join and leave the negotiation under the control of the underlying protocol. For example the protocol may allow or disallow agents from entering the negotiation once it started, it may allow or disallow modifying the issues being negotiated, may allow only a predefined maximum and minimum number of agents to engage in the negotiation. All of this is controlled through parameters to the protocol initializer.\n", "* provide the basic flow of protocols so that new protocols can be implemented by just overriding a single `__call__()` function.\n", "* provide basic callbacks that can be extended by new protocols.\n", "
\n", "Protocols must extend any callback (i.e. call the `super()` version) instead of overriding them as they may do some actions to ensure correct processing.\n", "
\n", " \n", "\n", "The simplest way to use a protocol is to just run one of the already provided protocols. This is an example of a full negotiation session:" ] }, { "cell_type": "code", "execution_count": 43, "id": "2f04d11f", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:31.934386Z", "iopub.status.busy": "2022-02-19T02:24:31.932462Z", "iopub.status.idle": "2022-02-19T02:24:31.937224Z", "shell.execute_reply": "2022-02-19T02:24:31.937892Z" }, "papermill": { "duration": 0.132418, "end_time": "2022-02-19T02:24:31.938171", "exception": false, "start_time": "2022-02-19T02:24:31.805753", "status": "completed" }, "scrolled": true, "tags": [] }, "outputs": [ { "data": { "text/plain": [ "(3,)" ] }, "execution_count": 43, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p = SAOMechanism(outcomes=6, n_steps=10)\n", "p.add(LimitedOutcomesNegotiator(name=\"seller\", acceptable_outcomes=[(2,), (3,), (5,)]))\n", "p.add(LimitedOutcomesNegotiator(name=\"buyer\", acceptable_outcomes=[(1,), (4,), (3,)]))\n", "state = p.run()\n", "p.state.agreement" ] }, { "cell_type": "markdown", "id": "f1e185c8", "metadata": { "papermill": { "duration": 0.112289, "end_time": "2022-02-19T02:24:32.176490", "exception": false, "start_time": "2022-02-19T02:24:32.064201", "status": "completed" }, "tags": [] }, "source": [ "You can create a new protocol by overriding a single function in the `Mechanism` class. \n", "\n", "The built-in `SAOMechanism` calls negotiators sequentially. Let's implement a simplified similar protocol that asks *all* negotiators to respond to every offer in parallel. " ] }, { "cell_type": "code", "execution_count": 44, "id": "6cc1a2af", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:32.391768Z", "iopub.status.busy": "2022-02-19T02:24:32.390446Z", "iopub.status.idle": "2022-02-19T02:24:32.393386Z", "shell.execute_reply": "2022-02-19T02:24:32.394089Z" }, "papermill": { "duration": 0.116971, "end_time": "2022-02-19T02:24:32.394306", "exception": false, "start_time": "2022-02-19T02:24:32.277335", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "from concurrent.futures import ThreadPoolExecutor\n", "from attr import define\n", "\n", "\n", "class ParallelResponseMechanism(Mechanism):\n", " def __init__(self, *args, initial_state=None, **kwargs):\n", " super().__init__(\n", " *args,\n", " initial_state=SAOState() if not initial_state else initial_state,\n", " **kwargs,\n", " )\n", " self.state.current_offer = None\n", " self.current_offerer = -1\n", "\n", " def __call__(self, state):\n", " n_agents = len(self.negotiators)\n", " nxt = (self.current_offerer + 1) % n_agents\n", " current = self.negotiators[nxt]\n", " offer = None\n", " self.state.current_offer = (\n", " current.propose(self.state) if offer is None else offer\n", " )\n", "\n", " def get_response(negotiator, state=self.state):\n", " return negotiator.respond(state, self.current_offerer)\n", "\n", " with ThreadPoolExecutor(4) as executor:\n", " responses = executor.map(\n", " get_response, [_ for _ in self.negotiators if _.id != current.id]\n", " )\n", " self.current_offerer = nxt\n", " if all(_ == ResponseType.ACCEPT_OFFER for _ in responses):\n", " state.agreement = self.state.current_offer\n", " if any(_ == ResponseType.END_NEGOTIATION for _ in responses):\n", " state.broken = True\n", " return MechanismStepResult(state=state)" ] }, { "cell_type": "markdown", "id": "5697cf16", "metadata": { "papermill": { "duration": 0.11736, "end_time": "2022-02-19T02:24:32.617318", "exception": false, "start_time": "2022-02-19T02:24:32.499958", "status": "completed" }, "tags": [] }, "source": [ "We needed only to override the `__call__` method which defines one round of the negotiation.\n", "The protocol goes as follows:\n", "\n", "1. Ask the next negotiator to propose.\n", "2. Get the response of all negotiators (using the thread-pool)\n", "3. If all negotiators accept the current offer, return it as the agreement\n", "4. Otherwise, if any negotiators responded with END_NEGOTIATION, break the negotiation\n", "5. Otherwise, change the next negotiator and return.\n", "\n", "Note that we did not need to take care of timeouts because they are handled by the base `Mechanism` class. Nor did we need to handle adding agents to the negotiation, removing them (for dynamic protocols), checking for errors, etc. \n", "\n", "The `__call__` method receives the current mechanism state and an optional action. If the action is passed, then it is expected that the corresponding negotiator will not be called and the action will be just used instead of calling the corresponding negotiator." ] }, { "cell_type": "markdown", "id": "96ddb3a7", "metadata": { "papermill": { "duration": 0.11736, "end_time": "2022-02-19T02:24:32.617318", "exception": false, "start_time": "2022-02-19T02:24:32.499958", "status": "completed" }, "tags": [] }, "source": [ "Agents can now engage in interactions with this protocol as easily as any built-in protocol:" ] }, { "cell_type": "code", "execution_count": 45, "id": "9a46c5cc", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:32.837231Z", "iopub.status.busy": "2022-02-19T02:24:32.836173Z", "iopub.status.idle": "2022-02-19T02:24:32.842618Z", "shell.execute_reply": "2022-02-19T02:24:32.843198Z" }, "papermill": { "duration": 0.118673, "end_time": "2022-02-19T02:24:32.843470", "exception": false, "start_time": "2022-02-19T02:24:32.724797", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/plain": [ "(3,)" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p = ParallelResponseMechanism(outcomes=6, n_steps=10)\n", "p.add(LimitedOutcomesNegotiator(name=\"seller\", acceptable_outcomes=[(2,), (3,), (5,)]))\n", "p.add(LimitedOutcomesNegotiator(name=\"buyer\", acceptable_outcomes=[(1,), (4,), (3,)]))\n", "state = p.run()\n", "p.state.agreement" ] }, { "cell_type": "markdown", "id": "5638e119", "metadata": { "papermill": { "duration": 0.163958, "end_time": "2022-02-19T02:24:33.144073", "exception": false, "start_time": "2022-02-19T02:24:32.980115", "status": "completed" }, "tags": [] }, "source": [ "The negotiation ran with the expected results\n" ] }, { "cell_type": "markdown", "id": "eaf789db", "metadata": { "papermill": { "duration": 0.101136, "end_time": "2022-02-19T02:24:33.359873", "exception": false, "start_time": "2022-02-19T02:24:33.258737", "status": "completed" }, "tags": [] }, "source": [ "Our mechanism keeps a history in the form of a list of `MechanismState` objects (on per round). Let's check it:" ] }, { "cell_type": "code", "execution_count": 46, "id": "1a82160c", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:33.595859Z", "iopub.status.busy": "2022-02-19T02:24:33.594963Z", "iopub.status.idle": "2022-02-19T02:24:33.632533Z", "shell.execute_reply": "2022-02-19T02:24:33.633698Z" }, "papermill": { "duration": 0.16296, "end_time": "2022-02-19T02:24:33.633989", "exception": false, "start_time": "2022-02-19T02:24:33.471029", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
runningwaitingstartedsteptimerelative_timebrokentimedoutagreementresults...error_detailsthreadslast_threadcurrent_offercurrent_proposercurrent_proposer_agentn_acceptancesnew_offersnew_offerer_agentslast_negotiator
0FalseFalseTrue00.00.0FalseFalse(3,)None...{}(3,)NoneNone0<class 'list'><class 'list'>None
\n", "

1 rows × 22 columns

\n", "
" ], "text/plain": [ " running waiting started step time relative_time broken timedout \\\n", "0 False False True 0 0.0 0.0 False False \n", "\n", " agreement results ... error_details threads last_thread current_offer \\\n", "0 (3,) None ... {} (3,) \n", "\n", " current_proposer current_proposer_agent n_acceptances new_offers \\\n", "0 None None 0 \n", "\n", " new_offerer_agents last_negotiator \n", "0 None \n", "\n", "[1 rows x 22 columns]" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import pandas as pd\n", "\n", "pd.DataFrame([_.asdict() for _ in p.history])" ] }, { "cell_type": "markdown", "id": "1f1a40cd", "metadata": { "papermill": { "duration": 0.086161, "end_time": "2022-02-19T02:24:33.832111", "exception": false, "start_time": "2022-02-19T02:24:33.745950", "status": "completed" }, "tags": [] }, "source": [ "We can see that the negotiation did not time-out, and that the final agreement was `(3,)` but that is hardly useful. It will be much better if we can also see the offers exchanged and who offered them. \n", "\n", "To do that we need to *augment* the mechanism state. NegMAS defines an easy way to do that by defining a new `MechanismState` type and filling it in the mechanism:" ] }, { "cell_type": "code", "execution_count": 47, "id": "66bc4283", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:34.046253Z", "iopub.status.busy": "2022-02-19T02:24:34.045189Z", "iopub.status.idle": "2022-02-19T02:24:34.048757Z", "shell.execute_reply": "2022-02-19T02:24:34.047912Z" }, "papermill": { "duration": 0.124141, "end_time": "2022-02-19T02:24:34.049006", "exception": false, "start_time": "2022-02-19T02:24:33.924865", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "from attrs import define\n", "\n", "\n", "@define\n", "class MyState(MechanismState):\n", " current_offer: Outcome | None = None\n", " current_offerer: str = \"none\"\n", "\n", "\n", "class NewParallelResponseMechanism(ParallelResponseMechanism):\n", " def __init__(self, *args, **kwargs):\n", " kwargs[\"initial_state\"] = MyState()\n", " super().__init__(*args, **kwargs)" ] }, { "cell_type": "markdown", "id": "24a31274", "metadata": { "papermill": { "duration": 0.099258, "end_time": "2022-02-19T02:24:34.242182", "exception": false, "start_time": "2022-02-19T02:24:34.142924", "status": "completed" }, "tags": [] }, "source": [ "That is all. We just needed to define our new state type, set the state_factory of the mechanism to it and define how to fill it in the `extra_state` method. Now it is possible to use this mechanism as we did previously" ] }, { "cell_type": "code", "execution_count": 48, "id": "9c106038", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:34.448775Z", "iopub.status.busy": "2022-02-19T02:24:34.447917Z", "iopub.status.idle": "2022-02-19T02:24:34.452406Z", "shell.execute_reply": "2022-02-19T02:24:34.453247Z" }, "papermill": { "duration": 0.122018, "end_time": "2022-02-19T02:24:34.453771", "exception": false, "start_time": "2022-02-19T02:24:34.331753", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
Agreement: (3,)\n",
                            "
\n" ], "text/plain": [ "Agreement: \u001b[1m(\u001b[0m\u001b[1;36m3\u001b[0m,\u001b[1m)\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "p = NewParallelResponseMechanism(outcomes=6, n_steps=10)\n", "p.add(LimitedOutcomesNegotiator(name=\"seller\", acceptable_outcomes=[(2,), (3,), (5,)]))\n", "p.add(LimitedOutcomesNegotiator(name=\"buyer\", acceptable_outcomes=[(1,), (4,), (3,)]))\n", "p.run()\n", "print(f\"Agreement: {p.state.agreement}\")" ] }, { "cell_type": "markdown", "id": "5fff21d4", "metadata": { "papermill": { "duration": 0.098976, "end_time": "2022-02-19T02:24:34.643434", "exception": false, "start_time": "2022-02-19T02:24:34.544458", "status": "completed" }, "tags": [] }, "source": [ "We can now check the history again (showing few of the attributes only) to confirm that the current offer and its source are stored." ] }, { "cell_type": "code", "execution_count": 49, "id": "49e61167", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:34.868978Z", "iopub.status.busy": "2022-02-19T02:24:34.867794Z", "iopub.status.idle": "2022-02-19T02:24:34.872114Z", "shell.execute_reply": "2022-02-19T02:24:34.872611Z" }, "papermill": { "duration": 0.130648, "end_time": "2022-02-19T02:24:34.872809", "exception": false, "start_time": "2022-02-19T02:24:34.742161", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
stepagreementrelative_timetimedoutbrokencurrent_offercurrent_offerer
00(3,)0.0FalseFalse(3,)none
\n", "
" ], "text/plain": [ " step agreement relative_time timedout broken current_offer \\\n", "0 0 (3,) 0.0 False False (3,) \n", "\n", " current_offerer \n", "0 none " ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def show_history(p):\n", " \"\"\"Returns a Pandas Dataframe with the negotiation history\"\"\"\n", " return pd.DataFrame(\n", " [\n", " dict(\n", " step=_.step,\n", " agreement=_.agreement,\n", " relative_time=_.relative_time,\n", " timedout=_.timedout,\n", " broken=_.broken,\n", " current_offer=_.current_offer,\n", " current_offerer=_.current_offerer,\n", " )\n", " for _ in p.history\n", " ]\n", " )\n", "\n", "\n", "show_history(p)" ] }, { "cell_type": "markdown", "id": "42774303", "metadata": { "papermill": { "duration": 0.178912, "end_time": "2022-02-19T02:24:35.200609", "exception": false, "start_time": "2022-02-19T02:24:35.021697", "status": "completed" }, "tags": [] }, "source": [ "Let's see what happens if agreement is impossible (no intersection of acceptable outcomes in our case):" ] }, { "cell_type": "code", "execution_count": 50, "id": "b3ac7d4c", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:35.415290Z", "iopub.status.busy": "2022-02-19T02:24:35.414118Z", "iopub.status.idle": "2022-02-19T02:24:35.433706Z", "shell.execute_reply": "2022-02-19T02:24:35.434755Z" }, "papermill": { "duration": 0.130702, "end_time": "2022-02-19T02:24:35.435213", "exception": false, "start_time": "2022-02-19T02:24:35.304511", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
Agreement: None\n",
                            "
\n" ], "text/plain": [ "Agreement: \u001b[3;35mNone\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
stepagreementrelative_timetimedoutbrokencurrent_offercurrent_offerer
00None0.000000FalseFalse(5,)none
11None0.285714FalseFalse(4,)none
22None0.428571FalseFalse(2,)none
33None0.571429FalseFalse(4,)none
44None0.714286FalseFalse(0,)none
55None0.857143FalseFalse(1,)none
\n", "
" ], "text/plain": [ " step agreement relative_time timedout broken current_offer \\\n", "0 0 None 0.000000 False False (5,) \n", "1 1 None 0.285714 False False (4,) \n", "2 2 None 0.428571 False False (2,) \n", "3 3 None 0.571429 False False (4,) \n", "4 4 None 0.714286 False False (0,) \n", "5 5 None 0.857143 False False (1,) \n", "\n", " current_offerer \n", "0 none \n", "1 none \n", "2 none \n", "3 none \n", "4 none \n", "5 none " ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p = NewParallelResponseMechanism(outcomes=6, n_steps=6)\n", "p.add(LimitedOutcomesNegotiator(name=\"seller\", acceptable_outcomes=[(2,), (0,), (5,)]))\n", "p.add(LimitedOutcomesNegotiator(name=\"buyer\", acceptable_outcomes=[(1,), (4,), (3,)]))\n", "p.run()\n", "print(f\"Agreement: {p.state.agreement}\")\n", "show_history(p)" ] }, { "cell_type": "markdown", "id": "38c5b569", "metadata": { "papermill": { "duration": 0.103735, "end_time": "2022-02-19T02:24:35.646594", "exception": false, "start_time": "2022-02-19T02:24:35.542859", "status": "completed" }, "tags": [] }, "source": [ "As expected, the negotiation timed out. Let's try to make it possible for the agents to agree by providing a common outcome that they may agree upon:" ] }, { "cell_type": "code", "execution_count": 51, "id": "a0946c1b", "metadata": { "execution": { "iopub.execute_input": "2022-02-19T02:24:36.012842Z", "iopub.status.busy": "2022-02-19T02:24:36.009953Z", "iopub.status.idle": "2022-02-19T02:24:36.052330Z", "shell.execute_reply": "2022-02-19T02:24:36.020889Z" }, "papermill": { "duration": 0.310174, "end_time": "2022-02-19T02:24:36.052772", "exception": false, "start_time": "2022-02-19T02:24:35.742598", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
Agreement: (3,)\n",
                            "
\n" ], "text/plain": [ "Agreement: \u001b[1m(\u001b[0m\u001b[1;36m3\u001b[0m,\u001b[1m)\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
stepagreementrelative_timetimedoutbrokencurrent_offercurrent_offerer
00None0.000000FalseFalse(0,)none
11None0.285714FalseFalse(1,)none
22None0.428571FalseFalse(0,)none
33None0.571429FalseFalse(4,)none
44(3,)0.714286FalseFalse(3,)none
\n", "
" ], "text/plain": [ " step agreement relative_time timedout broken current_offer \\\n", "0 0 None 0.000000 False False (0,) \n", "1 1 None 0.285714 False False (1,) \n", "2 2 None 0.428571 False False (0,) \n", "3 3 None 0.571429 False False (4,) \n", "4 4 (3,) 0.714286 False False (3,) \n", "\n", " current_offerer \n", "0 none \n", "1 none \n", "2 none \n", "3 none \n", "4 none " ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p = NewParallelResponseMechanism(outcomes=6, n_steps=6)\n", "p.add(LimitedOutcomesNegotiator(name=\"seller\", acceptable_outcomes=[(3,), (0,), (5,)]))\n", "p.add(LimitedOutcomesNegotiator(name=\"buyer\", acceptable_outcomes=[(1,), (4,), (3,)]))\n", "p.run()\n", "print(f\"Agreement: {p.state.agreement}\")\n", "show_history(p)" ] }, { "cell_type": "markdown", "id": "efe4f5ca", "metadata": { "papermill": { "duration": 0.176666, "end_time": "2022-02-19T02:24:36.458198", "exception": false, "start_time": "2022-02-19T02:24:36.281532", "status": "completed" }, "tags": [] }, "source": [ "We got an agreement again as expected.\n", "\n", "## Worlds (Simulations)\n", "\n", "A world in NegMAS is what connects all agents together. It has a `simulation_step` that is used to run a simulation (or update the state from the real world) and manages creation and destruction of `AgentWorldInterface`s (AWI) and connecting them to `Agent`s. \n", "\n", "`Agent`s can join and leave worlds using the `join` and `leave` methods and can interact with it through their AWIs.\n", "\n", "To create a new world type, you need to override a single method (`simulation_step`) in the base `World` class to define your simulation. Most likely you will also need to define a base `Agent` inherited class that is capable of interacting with this world and a corresponding `AgentWorldInterface`. \n", "\n", "You can see an example of a world simulation in the tutorials." ] }, { "cell_type": "code", "execution_count": null, "id": "6bca82c3", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "celltoolbar": "Tags", "kernelspec": { "display_name": "negmas", "language": "python", "name": "negmas" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.4" }, "papermill": { "default_parameters": {}, "duration": 38.470849, "end_time": "2022-02-19T02:24:37.243798", "environment_variables": {}, "exception": null, "input_path": "/Users/yasser/code/projects/negmas/notebooks/overview.ipynb", "output_path": "/Users/yasser/code/projects/negmas/notebooks/overview.ipynb", "parameters": {}, "start_time": "2022-02-19T02:23:58.772949", "version": "2.3.4" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": false } }, "nbformat": 4, "nbformat_minor": 5 }