Skip to main content

No-regret learning dynamics

Project description

NoRegret is an open-source software library for no-regret learning dynamics and computational game solving, developed by the Universal, Open, Free, and Transparent Computer Poker Research Group. NoRegret implements an extensive array of regret minimizers and game solvers, and also supports GPU-acceleration. The library can be used in a variety of use cases, from solving games to conducting research in online convex optimization. NoRegret’s reliability has been established through extensive doctests and unit tests, achieving 91% code coverage.

Features

  • Extensive array of regret minimizers and game solvers.

  • High-speed implementations.

  • GPU-accleration.

Installation

The NoRegret library requires Python Version 3.12 or above and can be installed using pip:

pip install noregret

Usages

Example usages of NoRegret is shown below.

Solving Games via Regret minimization

The code snippet below demonstrates how one can solve games via regret minimization using NoRegret.

from functools import partial
from math import inf

from tqdm import tqdm
import matplotlib.pyplot as plt
import noregret as nr
import pandas as pd
import seaborn as sns

KERNEL = nr.FloatingPointKernel()
GAMES = {
    'Rock paper superscissors': nr.to_efg(nr.RockPaperSuperscissors(KERNEL)),
    'Kuhn poker': nr.from_open_spiel(KERNEL, 'kuhn_poker'),
    'Leduc poker': nr.from_open_spiel(KERNEL, 'leduc_poker'),
}
PARAMETERS = {
    'CFR': (nr.CFR, False, False),
    'CFR+': (nr.CFR_plus, True, False),
    'DCFR': (nr.DCFR, True, False),
    'PCFR+': (partial(nr.CFR_plus, gamma=2), True, True),
    'PCFR+*': (partial(nr.CFR_plus, gamma=inf), True, True),
}


def main():
    for name, game in tqdm(GAMES.items()):
        iterations = []
        exploitabilities = []
        expected_utilities = []
        variants = []

        for variant, (R_type, alt, pred) in tqdm(
                PARAMETERS.items(),
                leave=False,
        ):
            R_row = R_type(KERNEL, game.row_sequence_form_polytope)
            R_col = R_type(KERNEL, game.column_sequence_form_polytope)

            def update():
                t = R_row.iteration_count
                x_bar = R_row.average_strategy
                y_bar = R_col.average_strategy
                epsilon = game.exploitability(x_bar, y_bar)
                u = game.expected_row_utility(x_bar, y_bar)

                iterations.append(t)
                exploitabilities.append(epsilon)
                expected_utilities.append(u)
                variants.append(variant)

            nr.regret_minimization(
                game,
                R_row,
                R_col,
                alternation=alt,
                prediction=pred,
                update=update,
                progress_bar={'leave': False},
            )

        data = {
            'Iteration': iterations,
            'Exploitability': exploitabilities,
            'Expected utility': expected_utilities,
            'Variant': variants,
        }
        df = pd.DataFrame(data)

        plt.clf()
        sns.lineplot(df, x='Iteration', y='Exploitability', hue='Variant')
        plt.xscale('log')
        plt.yscale('log')
        plt.title(f'Exploitability in {name}')
        plt.show()

        plt.clf()
        sns.lineplot(df, x='Iteration', y='Expected utility', hue='Variant')
        plt.xscale('log')
        plt.title(f'Expected utility in {name}')
        plt.show()


if __name__ == '__main__':
    main()

GPU-Accelerated Game Solving

The code snippet below demonstrates how one can solve games while leveraging GPU acceleration.

from sys import stdout

from orjson import dumps, OPT_SERIALIZE_NUMPY
import noregret as nr

KERNEL = nr.CUDAKernel()
GAME = nr.from_open_spiel(KERNEL, 'liars_dice')
PARAMETERS = nr.CFR, True, False


def main():
    R_type, alt, pred = PARAMETERS
    R_row = R_type(KERNEL, GAME.row_sequence_form_polytope)
    R_col = R_type(KERNEL, GAME.column_sequence_form_polytope)
    x_bar, y_bar = nr.regret_minimization(
        GAME,
        R_row,
        R_col,
        alternation=alt,
        prediction=pred,
    )
    data = {
        'x_bar': KERNEL.numpy.asnumpy(x_bar),
        'y_bar': KERNEL.numpy.asnumpy(y_bar),
        'Exploitability': GAME.exploitability(x_bar, y_bar).item(),
        'Expected utility': GAME.expected_row_utility(x_bar, y_bar).item(),
    }

    stdout.buffer.write(dumps(data, option=OPT_SERIALIZE_NUMPY))


if __name__ == '__main__':
    main()

Solving Games via Linear Programming

The code snippet below demonstrates how one can solve games via linear programming using NoRegret.

import noregret as nr

KERNEL = nr.FloatingPointKernel()
GAMES = {
    'Rock paper superscissors': nr.RockPaperSuperscissors(KERNEL),
    'Kuhn poker': nr.from_open_spiel(KERNEL, 'kuhn_poker'),
    'Leduc poker': nr.from_open_spiel(KERNEL, 'leduc_poker'),
}


def main():
    for name, game in GAMES.items():
        x, y = nr.linear_programming(game)
        v = game.expected_row_utility(x, y)

        print(f'{name}:', v)


if __name__ == '__main__':
    main()

Conduct Research in Online Convex Optimization

The code snippet below reproduces Leme, Piliouras, and Schneider (NeurIPS, 2024) using NoRegret.

from functools import partial

import matplotlib.pyplot as plt
import noregret as nr

KERNEL = nr.FloatingPointKernel()
GAME = nr.RockPaperScissorsPlus(KERNEL)
R_type = partial(nr.MWU, learning_rate=1e-3)


def main():
    RM = R_type(KERNEL, GAME.row_dimension, is_time_symmetric=False)
    BM_RM = nr.BM(KERNEL, GAME.row_dimension, R_type, is_time_symmetric=False)

    nr.symmetric_regret_minimization(GAME, RM, iteration_count=100000)
    nr.symmetric_regret_minimization(GAME, BM_RM, iteration_count=100000)
    x, _ = nr.linear_programming(GAME)

    strategies = KERNEL.numpy.array(RM.strategies)

    plt.clf()
    plt.plot(strategies[:, 0], strategies[:, 1])
    plt.plot(strategies[-1, 0], strategies[-1, 1], 'bo')
    plt.plot(*x[:2], 'ro')
    plt.xlabel('Probability of action 1')
    plt.ylabel('Probability of action 2')
    plt.title('No-external regret dynamics')
    plt.show()

    strategies = KERNEL.numpy.array(BM_RM.strategies)

    plt.clf()
    plt.plot(strategies[:, 0], strategies[:, 1])
    plt.plot(strategies[-1, 0], strategies[-1, 1], 'bo')
    plt.plot(*x[:2], 'ro')
    plt.xlabel('Probability of action 1')
    plt.ylabel('Probability of action 2')
    plt.title('No-swap regret dynamics')
    plt.show()


if __name__ == '__main__':
    main()

Testing and Validation

Run style checks.

flake8 examples noregret

Run doctests.

shopt -s globstar
python -m doctest noregret/**/*.py

Run unit tests.

python -m unittest

Check coverage.

shopt -s globstar
coverage run -m doctest noregret/**/*.py
coverage run -a -m unittest
coverage report -m
coverage html

Contributing

Contributions are welcome! Please read our Contributing Guide for more information.

License

NoRegret is distributed under the MIT license.

Citing

If you use NoRegret in your research, please cite our library:

@misc{kim2026parallelizingcounterfactualregretminimization,
      title={Parallelizing Counterfactual Regret Minimization},
      author={Juho Kim and Tuomas Sandholm},
      year={2026},
      eprint={2605.14277},
      archivePrefix={arXiv},
      primaryClass={cs.AI},
      url={https://arxiv.org/abs/2605.14277},
}

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

noregret-0.0.0.dev4.tar.gz (25.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

noregret-0.0.0.dev4-py3-none-any.whl (33.8 kB view details)

Uploaded Python 3

File details

Details for the file noregret-0.0.0.dev4.tar.gz.

File metadata

  • Download URL: noregret-0.0.0.dev4.tar.gz
  • Upload date:
  • Size: 25.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for noregret-0.0.0.dev4.tar.gz
Algorithm Hash digest
SHA256 f3ab7a1f1bbd557f7dafa67fdb7aa0516ae92ee7a9b958622b9929c57d9ee9a0
MD5 cfacf41f07125159d3b513073c4c8369
BLAKE2b-256 8e72e772a83d39b2089504418157d3a8f57f3c8e562847d34fedfc47cfeff845

See more details on using hashes here.

File details

Details for the file noregret-0.0.0.dev4-py3-none-any.whl.

File metadata

  • Download URL: noregret-0.0.0.dev4-py3-none-any.whl
  • Upload date:
  • Size: 33.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for noregret-0.0.0.dev4-py3-none-any.whl
Algorithm Hash digest
SHA256 1ad542c102944de86cf9b92a9f92d77723edb2840f2542c602d9e6b9f9337d05
MD5 8118b7bb627317be37797d958978e3fd
BLAKE2b-256 49802173278489435789671b88221d93e770edd5506a043e5e843a01e0d07be9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page