Skip to content

Commit

Permalink
v2.0 (#46)
Browse files Browse the repository at this point in the history
* Add more test to bring coverage up

* Update documentation

* Drop 3.9 support

* Bump major version

* Drop 3.9 from pyproject.toml

* Make rate return Rating objects

* Update lock file

* Add fragments for breaking changes

* Update doctest

* Update doctest

* Update doctest

* Update doctest
  • Loading branch information
vivekjoshy committed Feb 20, 2022
1 parent 7472e91 commit 932e502
Show file tree
Hide file tree
Showing 23 changed files with 244 additions and 195 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.0.2
current_version = 2.0.0
commit = False
tag = False
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\-(?P<release>[a-z]+)\.(?P<build>\d+))?
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
fail-fast: false
matrix:
os: [Ubuntu, MacOS, Windows]
python-version: [3.9, "3.10"]
python-version: ["3.10"]
experimental: [false]
defaults:
run:
Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@ If `a1` and `a2` are on a team, and wins against a team of `b1` and `b2`, send t
```python
>>> [[x1, x2], [y1, y2]] = rate([[a1, a2], [b1, b2]])
>>> x1, x2, y1, y2
([28.669648436582808, 8.071520788025197], [33.83086971107981, 5.062772998705765], [43.071274808241974, 2.4166900452721256], [23.149503312339064, 6.1378606973362135])
(Rating(mu=28.669648436582808, sigma=8.071520788025197), Rating(mu=33.83086971107981, sigma=5.062772998705765), Rating(mu=43.071274808241974, sigma=2.4166900452721256), Rating(mu=23.149503312339064, sigma=6.1378606973362135))
```

You can also create `Rating` objects by importing `create_rating`:

```python
>>> from openskill import create_rating
>>> x1 = [28.669648436582808, 8.071520788025197]
>>> x1 = create_rating(x1)
>>> x1
Rating(mu=28.669648436582808, sigma=8.071520788025197)
Expand All @@ -52,7 +53,7 @@ When displaying a rating, or sorting a list of ratings, you can use `ordinal`:

```python
>>> from openskill import ordinal
>>> ordinal(mu=43.07, sigma=2.42)
>>> ordinal([43.07, 2.42])
35.81
```

Expand All @@ -65,7 +66,7 @@ If your teams are listed in one order but your ranking is in a different order,
>>> a1 = b1 = c1 = d1 = Rating()
>>> result = [[a2], [b2], [c2], [d2]] = rate([[a1], [b1], [c1], [d1]], rank=[4, 1, 3, 2])
>>> result
[[[20.96265504062538, 8.083731307186588]], [[27.795084971874736, 8.263160757613477]], [[24.68943500312503, 8.083731307186588]], [[26.552824984374855, 8.179213704945203]]]
[[Rating(mu=20.96265504062538, sigma=8.083731307186588)], [Rating(mu=27.795084971874736, sigma=8.263160757613477)], [Rating(mu=24.68943500312503, sigma=8.083731307186588)], [Rating(mu=26.552824984374855, sigma=8.179213704945203)]]
```

It's assumed that the lower ranks are better (wins), while higher ranks are worse (losses). You can provide a score instead, where lower is worse and higher is better. These can just be raw scores from the game, if you want.
Expand All @@ -76,7 +77,7 @@ Ties should have either equivalent rank or score.
>>> a1 = b1 = c1 = d1 = Rating()
>>> result = [[a2], [b2], [c2], [d2]] = rate([[a1], [b1], [c1], [d1]], score=[37, 19, 37, 42])
>>> result
[[[24.68943500312503, 8.179213704945203]], [[22.826045021875203, 8.179213704945203]], [[24.68943500312503, 8.179213704945203]], [[27.795084971874736, 8.263160757613477]]]
[[Rating(mu=24.68943500312503, sigma=8.179213704945203)], [Rating(mu=22.826045021875203, sigma=8.179213704945203)], [Rating(mu=24.68943500312503, sigma=8.179213704945203)], [Rating(mu=27.795084971874736, sigma=8.263160757613477)]]
```

## Predicting Winners
Expand Down Expand Up @@ -115,7 +116,7 @@ The default model is `PlackettLuce`. You can import alternate models from `opens
>>> from openskill.models import BradleyTerryFull
>>> a1 = b1 = c1 = d1 = Rating()
>>> rate([[a1], [b1], [c1], [d1]], rank=[4, 1, 3, 2], model=BradleyTerryFull)
[[[17.09430584957905, 7.5012190693964005]], [[32.90569415042095, 7.5012190693964005]], [[22.36476861652635, 7.5012190693964005]], [[27.63523138347365, 7.5012190693964005]]]
[[Rating(mu=17.09430584957905, sigma=7.5012190693964005)], [Rating(mu=32.90569415042095, sigma=7.5012190693964005)], [Rating(mu=22.36476861652635, sigma=7.5012190693964005)], [Rating(mu=27.63523138347365, sigma=7.5012190693964005)]]
```

### Available Models
Expand Down
2 changes: 2 additions & 0 deletions changes/46.breaking.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- The ``rate`` function now returns ``Rating`` objects.
- Changes ``ordinal`` to accept both ``Rating`` objects and lists or tuples of 2 floats.
1 change: 1 addition & 0 deletions changes/46.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
``create_rating`` now checks if the argument is the correct type.
8 changes: 0 additions & 8 deletions docs/api/openskill.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,6 @@ openskill.constants module
:undoc-members:
:show-inheritance:

openskill.ordinal module
------------------------

.. automodule:: openskill.ordinal
:members:
:undoc-members:
:show-inheritance:

openskill.rate module
---------------------

Expand Down
11 changes: 6 additions & 5 deletions docs/manual.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,14 @@ and ``b2``, send this into rate:
>>> [[x1, x2], [y1, y2]] = rate([[a1, a2], [b1, b2]])
>>> x1, x2, y1, y2
([28.669648436582808, 8.071520788025197], [33.83086971107981, 5.062772998705765], [43.071274808241974, 2.4166900452721256], [23.149503312339064, 6.1378606973362135])
(Rating(mu=28.669648436582808, sigma=8.071520788025197), Rating(mu=33.83086971107981, sigma=5.062772998705765), Rating(mu=43.071274808241974, sigma=2.4166900452721256), Rating(mu=23.149503312339064, sigma=6.1378606973362135))
You can also create ``Rating`` objects by importing ``create_rating``:

.. code:: python
>>> from openskill import create_rating
>>> x1 = [28.669648436582808, 8.071520788025197]
>>> x1 = create_rating(x1)
>>> x1
Rating(mu=28.669648436582808, sigma=8.071520788025197)
Expand All @@ -51,7 +52,7 @@ When displaying a rating, or sorting a list of ratings, you can use
.. code:: python
>>> from openskill import ordinal
>>> ordinal(mu=43.07, sigma=2.42)
>>> ordinal([43.07, 2.42])
35.81
By default, this returns ``mu - 3 * sigma``, showing a rating for which
Expand All @@ -70,7 +71,7 @@ order, for convenience you can specify a ranks option, such as:
>>> a1 = b1 = c1 = d1 = Rating()
>>> result = [[a2], [b2], [c2], [d2]] = rate([[a1], [b1], [c1], [d1]], rank=[4, 1, 3, 2])
>>> result
[[[20.96265504062538, 8.083731307186588]], [[27.795084971874736, 8.263160757613477]], [[24.68943500312503, 8.083731307186588]], [[26.552824984374855, 8.179213704945203]]]
[[Rating(mu=20.96265504062538, sigma=8.083731307186588)], [Rating(mu=27.795084971874736, sigma=8.263160757613477)], [Rating(mu=24.68943500312503, sigma=8.083731307186588)], [Rating(mu=26.552824984374855, sigma=8.179213704945203)]]
It's assumed that the lower ranks are better (wins), while higher ranks
are worse (losses). You can provide a score instead, where lower is
Expand All @@ -84,7 +85,7 @@ Ties should have either equivalent rank or score.
>>> a1 = b1 = c1 = d1 = Rating()
>>> result = [[a2], [b2], [c2], [d2]] = rate([[a1], [b1], [c1], [d1]], score=[37, 19, 37, 42])
>>> result
[[[24.68943500312503, 8.179213704945203]], [[22.826045021875203, 8.179213704945203]], [[24.68943500312503, 8.179213704945203]], [[27.795084971874736, 8.263160757613477]]]
[[Rating(mu=24.68943500312503, sigma=8.179213704945203)], [Rating(mu=22.826045021875203, sigma=8.179213704945203)], [Rating(mu=24.68943500312503, sigma=8.179213704945203)], [Rating(mu=27.795084971874736, sigma=8.263160757613477)]]
Predicting Winners
------------------
Expand Down Expand Up @@ -129,7 +130,7 @@ from ``openskill.models`` like so:
>>> from openskill.models import BradleyTerryFull
>>> a1 = b1 = c1 = d1 = Rating()
>>> rate([[a1], [b1], [c1], [d1]], rank=[4, 1, 3, 2], model=BradleyTerryFull)
[[[17.09430584957905, 7.5012190693964005]], [[32.90569415042095, 7.5012190693964005]], [[22.36476861652635, 7.5012190693964005]], [[27.63523138347365, 7.5012190693964005]]]
[[Rating(mu=17.09430584957905, sigma=7.5012190693964005)], [Rating(mu=32.90569415042095, sigma=7.5012190693964005)], [Rating(mu=22.36476861652635, sigma=7.5012190693964005)], [Rating(mu=27.63523138347365, sigma=7.5012190693964005)]]
Available Models
~~~~~~~~~~~~~~~~
Expand Down
4 changes: 2 additions & 2 deletions openskill/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from openskill.ordinal import ordinal
from openskill.rate import ordinal
from openskill.rate import (
rate,
Rating,
Expand All @@ -9,4 +9,4 @@
)


__version__ = "1.0.2"
__version__ = "2.0.0"
7 changes: 0 additions & 7 deletions openskill/ordinal.py

This file was deleted.

95 changes: 85 additions & 10 deletions openskill/rate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
import math
from collections import deque
from functools import reduce
from operator import sub
from typing import Optional, Union, List

from openskill.constants import mu as default_mu, beta
from openskill.constants import mu as default_mu, beta, Constants
from openskill.constants import sigma as default_sigma
from openskill.models.plackett_luce import PlackettLuce
from openskill.statistics import phi_major, phi_major_inverse
Expand Down Expand Up @@ -33,11 +32,64 @@ def __repr__(self):
return f"Rating(mu={self.mu}, sigma={self.sigma})"

def __eq__(self, other):
if len(other) == 2:
if self.mu == other[0] and self.sigma == other[1]:
if isinstance(other, Rating):
if self.mu == other.mu and self.sigma == other.sigma:
return True
else:
return False
elif isinstance(other, Union[list, tuple]):
if len(other) == 2:
for value in other:
if not isinstance(value, Union[int, float]):
raise ValueError(
f"The {other.__class__.__name__} contains an "
f"element '{value}' of type '{value.__class__.__name__}'"
)
if self.mu == other[0] and self.sigma == other[1]:
return True
else:
return False
else:
raise ValueError(
f"The '{other.__class__.__name__}' object has more than two floats."
)
else:
raise ValueError(
"You can only compare Rating objects with each other or a list of two floats."
)


def ordinal(agent: Union[Rating, list, tuple], **options) -> float:
"""
Convert `mu` and `sigma` into a single value for sorting purposes.
:param agent: A :class:`~openskill.rate.Rating` object or a :class:`~list` or :class:`~tuple` of :class:`~float`
objects.
:param options: Pass in a set of custom values for constants defined in the Weng-Lin paper.
:return: A :class:`~float` object that represents a 1 dimensional value for a rating.
"""
if isinstance(agent, Union[list, tuple]):
if len(agent) == 2:
for value in agent:
if not isinstance(value, Union[int, float]):
raise ValueError(
f"The {agent.__class__.__name__} contains an "
f"element '{value}' of type '{value.__class__.__name__}'"
)
z = Constants(**options).Z
return agent[0] - z * agent[1]
else:
raise ValueError(
f"The '{agent.__class__.__name__}' object has more than two floats."
)
elif isinstance(agent, Rating):
# Calculate Z
z = Constants(**options).Z
return agent.mu - z * agent.sigma
else:
raise ValueError(
"You can only pass 'Rating' objects, two-tuples or lists to 'agent'."
)


def create_rating(rating_list: List[Union[int, float]]) -> Rating:
Expand All @@ -47,7 +99,18 @@ def create_rating(rating_list: List[Union[int, float]]) -> Rating:
:param rating_list: A list of two values where the first value is the `mu` and the second value is the `sigma`.
:return: A :class:`~openskill.rate.Rating` object created from the list passed in.
"""
return Rating(mu=rating_list[0], sigma=rating_list[1])
if isinstance(rating_list, Rating):
raise TypeError("Argument is already a 'Rating' object.")
elif len(rating_list) == 2:
for value in rating_list:
if not isinstance(value, Union[int, float]):
raise ValueError(
f"The {rating_list.__class__.__name__} contains an "
f"element '{value}' of type '{value.__class__.__name__}'"
)
return Rating(mu=rating_list[0], sigma=rating_list[1])
else:
raise TypeError(f"Cannot accept '{rating_list.__class__.__name__}' type.")


def team_rating(game: List[List[Rating]], **options) -> List[List[Union[int, float]]]:
Expand All @@ -73,16 +136,15 @@ def team_rating(game: List[List[Rating]], **options) -> List[List[Union[int, flo
return result


def rate(teams: List[List[Rating]], **options) -> List[List[Union[int, float]]]:
def rate(teams: List[List[Rating]], **options) -> List[List[Rating]]:
"""
Rate multiple teams consisting of one of more agents. Order of teams determines rank.
:param teams: A list of teams, where teams are lists of :class:`~openskill.rate.Rating` objects.
:param rank: A list of :class:`~int` where the lower values represent the winners.
:param score: A list of :class:`~int` where higher values represent the winners.
:param options: Pass in a set of custom values for constants defined in the Weng-Lin paper.
:return: Returns a list of lists containing `mu` and
`sigma` values that can be passed into :func:`~openskill.rate.create_rating`
:return: Returns a list of :class:`~openskill.rate.Rating` objects.
"""
if "rank" in options:
rank = options["rank"]
Expand All @@ -108,9 +170,22 @@ def rate(teams: List[List[Rating]], **options) -> List[List[Union[int, float]]]:
if rank and tenet:
result = model.calculate()
result, old_tenet = unwind(tenet, result)
return result
final_result = []
for item in result:
team = []
for player in item:
team.append(create_rating(player))
final_result.append(team)
return final_result
else:
return model.calculate()
result = model.calculate()
final_result = []
for item in result:
team = []
for player in item:
team.append(create_rating(player))
final_result.append(team)
return final_result


def predict_win(teams: List[List[Rating]], **options) -> List[Union[int, float]]:
Expand Down
4 changes: 2 additions & 2 deletions openskill/util.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import math
from itertools import zip_longest
from typing import List, Optional
from typing import List, Optional, Tuple

from openskill.constants import Constants

Expand Down Expand Up @@ -99,7 +99,7 @@ def transpose(xs):
return [list(map(lambda r: r[i], xs)) for i, _ in enumerate(xs[0])]


def unwind(ranks, teams):
def unwind(ranks, teams) -> Tuple[List, List[int]]:
if not ranks or not teams:
return None, None

Expand Down

0 comments on commit 932e502

Please sign in to comment.