diff --git a/README.md b/README.md index 9874d2d..8d6773c 100644 --- a/README.md +++ b/README.md @@ -3,5 +3,85 @@ ![PyTest result](https://github.com/ariel-research/fairpyx/workflows/pytest/badge.svg) [![PyPI version](https://badge.fury.io/py/fairpyx.svg)](https://badge.fury.io/py/fairpyx) -`fairpyx` is a Python library containing various algorithms for fair allocation, with an emphasis on [Course-seat allocation](https://en.wikipedia.org/wiki/Course_allocation). +`fairpyx` is a Python library containing various algorithms for fair allocation, with an emphasis on [Course allocation](https://en.wikipedia.org/wiki/Course_allocation). It is designed for three target audiences: + +* Laypeople, who want to use existing fair division algorithms for real-life problems. +* Researchers, who develop new fair division algorithms and want to quickly implement them and compare to existing algorithms. +* Students, who want to trace the execution of algorithms to understand how they work. + +## Installation + +For the stable version: + + pip install fairpyx + +For the latest version: + + pip install git+https://github.com/ariel-research/fairpyx.git + +To verify that everything was installed correctly, run one of the example programs, e.g. + + python examples/items.py + python examples/cake.py + +or run the tests: + + pytest + +## Usage + +The function `fairpyx.divide` can be used to activate all fair division algorithms. For example: + + import fairpyx + + valuations = {"Alice": {"w":11,"x":22,"y":44,"z":0}, "George": {"w":22,"x":11,"y":66,"z":33}} + + ### Allocating indivisible items using the Iterated Maximum Matching algorithm: + fairpy.divide(algorithm=fairpy.items.iterated_maximum_matching, input=valuations) + + ### Allocating divisible goods using the leximin algorithm: + fairpy.divide(fairpy.items.leximin, valuations) + + ### Dividing a cake using cut-and-choose: + from fairpy import PiecewiseConstantAgent + Alice = PiecewiseConstantAgent([33,33], "Alice") + George = PiecewiseConstantAgent([11,55], "George") + fairpy.divide(algorithm=fairpy.cake.cut_and_choose.asymmetric_protocol, input=[George, Alice]) + + +## Features and Examples + +1. [Item allocation algorithms](examples/items.md), for both divisible and indivisible items; + +1. [Cake-cutting algorithms](examples/cake.md); + +1. [Various input formats](examples/input_formats.md), to easily use by both researchers and end-users; + +1. [Various output formats](examples/output_formats.md); + +1. [Optional logging](examples/loggers.md), to learn and understand how the algorithms work. + + +## Adding new algorithms + +To add a new algorithm for item allocation, write a function that accepts one of the following parameters: + +* [AgentList](fairpy/agentlist.py) - a list of [Agent](fairpy/agents.py) objects. See e.g. [the implementation of Round Robin](fairpy/items/round_robin.py) for usage example. +* [ValuationMatrix](fairpy/valuations.py) - a matrix v where v[i,j] is the value of agent i to item j. See e.g. [the implementation of Leximin](fairpy/items/leximin.py) for usage example. + +Your function may accept any other custom parameters. + + +## See also + +* [other open-source projects related to fairness](related.md). + + +## Installation for development + + clone https://github.com/ariel-research/fairpyx.git + cd fairpyx + pip install -r requirements.txt + pip install -e . + diff --git a/examples/_pweave.py b/examples/_pweave.py new file mode 100644 index 0000000..8b388cd --- /dev/null +++ b/examples/_pweave.py @@ -0,0 +1,26 @@ +""" +Run all the example files and convert them to markdown files containing the output. +Uses `pweave`. +""" + +import pweave, datetime, glob, os + + +def publish_to_markdown(python_file: str, output_file: str): + doc = pweave.Pweb(python_file, kernel="python3", doctype="markdown", output=output_file) + + doc.theme = "skeleton" # The default option is skeleton , other options are pweave (the old theme), bootstrap , cerulean and journal. All look the same to me. + + doc.read() + doc.run() + doc.format() + doc.formatted += f"\n---\nMarkdown generated automatically from [{python_file}]({python_file}) using [Pweave](http://mpastell.com/pweave) {pweave.__version__} on {datetime.date.today()}.\n" + doc.write() + + +if __name__ == "__main__": + for python_file in glob.glob("*.py"): + print(python_file) + if python_file != os.path.basename(__file__): + output_file = python_file.replace(".py", ".md") + publish_to_markdown(python_file, output_file) diff --git a/examples/input_formats.md b/examples/input_formats.md new file mode 100644 index 0000000..8736a44 --- /dev/null +++ b/examples/input_formats.md @@ -0,0 +1,67 @@ +# Input formats + + +```python +import fairpyx +divide = fairpyx.divide +``` + + + +`fairpyx` allows various input formats, so that you can easily use it on your own data, +whether for applications or for research. +For example, suppose you want to divide candies among your children. +It is convenient to collect their preferences in a dict of dicts: + + +```python +valuations = { + "Ami": {"green": 8, "red":7, "blue": 6, "yellow": 5}, + "Tami": {"green": 12, "red":8, "blue": 4, "yellow": 2} } +allocation = divide(fairpyx.algorithms.round_robin, valuations=valuations) +``` + + + +You can then see the resulting allocation with the agents' real names: + + +```python +print(allocation) +``` + +``` +{'Ami': ['blue', 'green'], 'Tami': ['red', 'yellow']} +``` + + + +For research, passing a dict of dicts as a parameter may be too verbose. +You can call the same algorithm with only the values, or only the value matrix: + + +```python +print(divide(fairpyx.algorithms.round_robin, valuations={"Ami": [8,7,6,5], "Tami": [12,8,4,2]})) +print(divide(fairpyx.algorithms.round_robin, valuations=[[8,7,6,5], [12,8,4,2]])) + + +# #' For experiments, you can use a numpy random matrix: + +import numpy as np +valuations = np.random.randint(1,100,[2,4]) +print(valuations) +allocation = divide(fairpyx.algorithms.round_robin, valuations=valuations) +print(allocation) +``` + +``` +{'Ami': [0, 2], 'Tami': [1, 3]} +{0: [0, 2], 1: [1, 3]} +[[70 26 35 78] + [47 8 51 38]] +{0: [0, 3], 1: [1, 2]} +``` + + +--- +Markdown generated automatically from [input_formats.py](input_formats.py) using [Pweave](http://mpastell.com/pweave) 0.30.3 on 2023-10-18. diff --git a/examples/input_formats.py b/examples/input_formats.py new file mode 100644 index 0000000..3ffab02 --- /dev/null +++ b/examples/input_formats.py @@ -0,0 +1,33 @@ +#' # Input formats + +import fairpyx +divide = fairpyx.divide + +#' `fairpyx` allows various input formats, so that you can easily use it on your own data, +#' whether for applications or for research. +#' For example, suppose you want to divide candies among your children. +#' It is convenient to collect their preferences in a dict of dicts: + +valuations = { + "Ami": {"green": 8, "red":7, "blue": 6, "yellow": 5}, + "Tami": {"green": 12, "red":8, "blue": 4, "yellow": 2} } +allocation = divide(fairpyx.algorithms.round_robin, valuations=valuations) + +#' You can then see the resulting allocation with the agents' real names: + +print(allocation) + +#' For research, passing a dict of dicts as a parameter may be too verbose. +#' You can call the same algorithm with only the values, or only the value matrix: + +print(divide(fairpyx.algorithms.round_robin, valuations={"Ami": [8,7,6,5], "Tami": [12,8,4,2]})) +print(divide(fairpyx.algorithms.round_robin, valuations=[[8,7,6,5], [12,8,4,2]])) + + +# #' For experiments, you can use a numpy random matrix: + +import numpy as np +valuations = np.random.randint(1,100,[2,4]) +print(valuations) +allocation = divide(fairpyx.algorithms.round_robin, valuations=valuations) +print(allocation) diff --git a/fairpyx/__init__.py b/fairpyx/__init__.py index ef02deb..b55d732 100644 --- a/fairpyx/__init__.py +++ b/fairpyx/__init__.py @@ -5,8 +5,11 @@ from fairpyx.explanations import ExplanationLogger, ConsoleExplanationLogger, StringsExplanationLogger, FilesExplanationLogger from fairpyx.adaptors import divide +import fairpyx.algorithms as algorithms + # # Algorithms: # from fairpyx.iterated_maximum_matching import iterated_maximum_matching, iterated_maximum_matching_adjusted, iterated_maximum_matching_unadjusted # from fairpyx.utilitarian_matching import utilitarian_matching -# from fairpyx.picking_sequence import picking_sequence, serial_dictatorship, round_robin, bidirectional_round_robin +# from fairpyx.algorithms.picking_sequence import picking_sequence, serial_dictatorship, round_robin, bidirectional_round_robin # from fairpyx.almost_egalitarian import almost_egalitarian_allocation, almost_egalitarian_with_donation, almost_egalitarian_without_donation + diff --git a/fairpyx/algorithms/__init__.py b/fairpyx/algorithms/__init__.py new file mode 100644 index 0000000..b489acf --- /dev/null +++ b/fairpyx/algorithms/__init__.py @@ -0,0 +1 @@ +from fairpyx.algorithms.picking_sequence import round_robin, bidirectional_round_robin, serial_dictatorship