From 2d325f351178dde4a54f2fd6659353b12cf3c788 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Tue, 15 Sep 2026 12:57:45 +0100 Subject: [PATCH 1/5] Set up outline of general concepts --- doc/concepts.rst | 55 ++++++++++++++++++++++++++++++++++++++++++++++++ doc/contents.rst | 1 + doc/index.rst | 1 + doc/pygambit.rst | 4 ++++ 4 files changed, 61 insertions(+) create mode 100644 doc/concepts.rst diff --git a/doc/concepts.rst b/doc/concepts.rst new file mode 100644 index 0000000000..d875df09bb --- /dev/null +++ b/doc/concepts.rst @@ -0,0 +1,55 @@ +.. _concepts: + +Concepts and conventions +========================= + + + +.. _concepts-representations: + +Game representations +-------------------- + + +.. _concepts-players-actions-strategies: + +Players, actions, and strategies +---------------------------------- + + + +.. _concepts-outcomes: + +Outcomes +--------- + + +.. _concepts-numerical-data: + +Numerical data conventions +----------------------------- + + + +.. _concepts-information-sets: + +Information sets and imperfect information +--------------------------------------------- + + +.. _concepts-strategy-types: + +Pure, mixed, and totally mixed strategies +--------------------------------------------- + + +.. _concepts-profile-representations: + +Mixed strategy profiles and mixed behavior profiles +-------------------------------------------------------- + + +.. _concepts-regret: + +Regret and the Liapunov value +-------------------------------- diff --git a/doc/contents.rst b/doc/contents.rst index bc97c5f8e6..b7401b0f6f 100644 --- a/doc/contents.rst +++ b/doc/contents.rst @@ -7,6 +7,7 @@ Detailed table of contents :maxdepth: 3 intro + concepts pygambit tools gui diff --git a/doc/index.rst b/doc/index.rst index 9329f1b8a2..6ba64f04ca 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -106,6 +106,7 @@ construction and analysis of finite extensive and strategic games. :maxdepth: 1 install + concepts algorithms pygambit tools diff --git a/doc/pygambit.rst b/doc/pygambit.rst index 1c8f0f7723..b6aa5082b0 100644 --- a/doc/pygambit.rst +++ b/doc/pygambit.rst @@ -14,6 +14,10 @@ You can run the tutorials interactively as Jupyter notebooks, see :ref:`local_tu Installing PyGambit also installs Gambit's :ref:`command-line tools ` for computing Nash equilibria from the shell or from scripts. +Before working through the tutorials, it may help to read :doc:`concepts`, which sets +out the terminology and modeling conventions used throughout this documentation and the +PyGambit API. + New user tutorials ------------------ From 1d67957b31be2d21c416a61fddd5e54c9282e423 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Tue, 15 Sep 2026 13:33:05 +0100 Subject: [PATCH 2/5] Move section documenting mixed strategy profiles and mixed behavior profiles. --- doc/concepts.rst | 26 +++++++++++++++---- .../advanced_tutorials/starting_points.ipynb | 14 +++------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/doc/concepts.rst b/doc/concepts.rst index d875df09bb..1d1b242f68 100644 --- a/doc/concepts.rst +++ b/doc/concepts.rst @@ -39,14 +39,30 @@ Information sets and imperfect information .. _concepts-strategy-types: -Pure, mixed, and totally mixed strategies ---------------------------------------------- +Mixed strategy profiles and mixed behavior profiles +----------------------------------------------------- +A probability distribution over a player's strategies is a ``MixedStrategy``. +An assignment of a ``MixedStrategy`` to each player forms a ``MixedStrategyProfile``, where following the +standard convention in game theory it is assumed players randomize independently. -.. _concepts-profile-representations: +In an extensive game, +a probability distribution over a player's actions at an information set is a ``MixedAction``. +An assignment of a ``MixedAction`` to each information set of a player is a ``MixedBehavior``. +An assignment of a ``MixedBehavior`` to each player forms a ``MixedBehaviorProfile``. +Following the standard convention in game theory, it is assumed the randomization of each +``MixedAction`` in a profile is realized independently. + +``MixedStrategyProfile`` and ``MixedBehaviorProfile`` objects express their probabilities either +as floating-point or rational numbers. Each profile is one type or the other; the precision of +all probabilities is the same in any profile. + +``MixedStrategy`` and ``MixedAction`` objects enforce that the probability assigned to each strategy +or action is nonnegative. By convention, these values are not enforced to sum to one; if they +do not sum to one, they are interpreted as weights. It is not valid to set all the probabilities/weights +of a ``MixedStrategy`` or ``MixedAction`` to zero. ``MixedStrategy`` and ``MixedAction`` objects can be +normalized to sum to one via an explicit operation. -Mixed strategy profiles and mixed behavior profiles --------------------------------------------------------- .. _concepts-regret: diff --git a/doc/tutorials/advanced_tutorials/starting_points.ipynb b/doc/tutorials/advanced_tutorials/starting_points.ipynb index be74ab7c79..2585500e7a 100644 --- a/doc/tutorials/advanced_tutorials/starting_points.ipynb +++ b/doc/tutorials/advanced_tutorials/starting_points.ipynb @@ -7,20 +7,12 @@ "source": [ "# Generating starting points for algorithms\n", "\n", - "In the previous tutorial, we demonstrated how to calculate the Nash equilibria of a game set up using Gambit and interpret the `MixedStrategyProfile` or `MixedBehaviorProfile` objects returned by the solver.\n", - "In this tutorial, we will demonstrate how to use a `MixedStrategyProfile` or `MixedBehaviorProfile` as an initial condition, a starting point, for some methods of computing Nash equilibria.\n", - "The equilibria found will depend on which starting point is selected.\n", + "Some methods for computing Nash equilibria are **multistart** algorithms. A single run of these methods takes a starting point, usually expressed as a `MixedStrategyProfile` or `MixedBehaviorProfile`, and uses that to find one (or in some cases more than one) equilibrium, generally by following some (deterministic) path. The equilibrium or equilibria found by such a method may, and in general will, depend on the starting point provided.\n", "\n", - "To facilitate generating starting points, Gambit's `Game` class provides the methods `random_strategy_profile` and `random_behavior_profile`, to generate profiles which are drawn from the uniform distribution on the product of simplices. In other words, the profiles are sampled from a uniform distribution so that each possible mixed strategy profile (or mixed behaviour profile) is equally likely to be selected.\n", + "To facilitate generating starting points, the `Game` class provides the methods `random_strategy_profile` and `random_behavior_profile`. These sample a `MixedStrategyProfile` or `MixedBehaviorProfile`, respectively, from the uniform distribution over the product of probability distributions over strategies or actions.\n", "\n", "As an example, we consider a three-player game from McKelvey and McLennan (1997), in which each player has two strategies.\n", - "This game has nine equilibria in total, and in particular has two totally mixed Nash equilibria, which is the maximum possible number of regular totally mixed equilbria in games of this size.\n", - "\n", - "Pure and mixed strategies:\n", - "\n", - "- **Pure strategy**: A player chooses the action with probability 1 (always picks the same move)\n", - "- **Mixed strategy**: A player assigns probabilities to their available actions (some actions may have probability 0)\n", - "- **Totally mixed strategy**: Mixed strategy where every available action is chosen with strictly positive probability (no action has probability 0)" + "This game has nine equilibria in total, and in particular has two totally mixed Nash equilibria, which is the maximum possible number of regular totally mixed equilbria in games of this size.\n" ] }, { From 554468f07e1f627f90a9e7c3e54ec81c63f969e8 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Mon, 21 Sep 2026 15:25:37 +0100 Subject: [PATCH 3/5] Don't need section on regret in concepts, this is handled in algorithms --- doc/concepts.rst | 7 ------- 1 file changed, 7 deletions(-) diff --git a/doc/concepts.rst b/doc/concepts.rst index 1d1b242f68..7994fdb111 100644 --- a/doc/concepts.rst +++ b/doc/concepts.rst @@ -62,10 +62,3 @@ or action is nonnegative. By convention, these values are not enforced to sum t do not sum to one, they are interpreted as weights. It is not valid to set all the probabilities/weights of a ``MixedStrategy`` or ``MixedAction`` to zero. ``MixedStrategy`` and ``MixedAction`` objects can be normalized to sum to one via an explicit operation. - - - -.. _concepts-regret: - -Regret and the Liapunov value --------------------------------- From 60c867c52bc5ab9d6aa87e22b3b24e32e8b050f7 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Mon, 21 Sep 2026 15:36:41 +0100 Subject: [PATCH 4/5] Move discussion of numerical data of a game back to concepts guide --- doc/concepts.rst | 107 +++++++++++ doc/tutorials/03_stripped_down_poker.ipynb | 206 --------------------- 2 files changed, 107 insertions(+), 206 deletions(-) diff --git a/doc/concepts.rst b/doc/concepts.rst index 7994fdb111..c937a5236b 100644 --- a/doc/concepts.rst +++ b/doc/concepts.rst @@ -29,6 +29,113 @@ Outcomes Numerical data conventions ----------------------------- +Payoffs to players and probabilities of actions at events are specified as numbers. +Gambit represents the numerical values in a game in exact precision, using either decimal +or rational representations. + +To illustrate, consider a trivial game which just has one move for the chance player: + +.. code-block:: python + + small_game = gbt.ExtensiveGame() + small_game.append_event(gbt.H.path(), dict.fromkeys(["a", "b", "c"], gbt.Rational(1, 3))) + list(small_game.get_action_probs(gbt.H.path()).values()) + +Here we specified an equal-probability distribution over the three actions, using +``pygambit``'s :py:class:`.Rational` class, which is derived from Python's +``fractions.Fraction``, to represent the probabilities exactly. + +Numerical data can be set as rational numbers. +:py:meth:`.ExtensiveGame.make_event` forms a collection of nodes into an event with a given +distribution; applied to a single node which is already an event, it resets the probabilities. +We can use it to change the distribution, again with ``Rational`` numbers: + +.. code-block:: python + + small_game.make_event( + gbt.H.path(), + {"a": gbt.Rational(1, 4), "b": gbt.Rational(1, 2), "c": gbt.Rational(1, 4)} + ) + list(small_game.get_action_probs(gbt.H.path()).values()) + +Numerical data can also be explicitly specified as decimal numbers: + +.. code-block:: python + + small_game.make_event( + gbt.H.path(), + {"a": gbt.Decimal(".25"), "b": gbt.Decimal(".50"), "c": gbt.Decimal(".25")} + ) + list(small_game.get_action_probs(gbt.H.path()).values()) + +Although the two representations above are mathematically equivalent, ``pygambit`` +remembers the format in which the values were specified. + +Expressing rational or decimal numbers as above is verbose and tedious. +``pygambit`` offers a more concise way to express numerical data in games: when setting +numerical game data, ``pygambit`` will attempt to convert text strings to their rational or +decimal representation. The above can therefore be written more compactly using string +representations: + +.. code-block:: python + + small_game.make_event(gbt.H.path(), {"a": "1/4", "b": "1/2", "c": "1/4"}) + list(small_game.get_action_probs(gbt.H.path()).values()) + + small_game.make_event(gbt.H.path(), {"a": ".25", "b": ".50", "c": ".25"}) + list(small_game.get_action_probs(gbt.H.path()).values()) + +As a further convenience, ``pygambit`` will accept Python ``int`` and ``float`` values. +``int`` values are always interpreted as :py:class:`.Rational` values. + +``pygambit`` attempts to render ``float`` values in an appropriate :py:class:`.Decimal` +equivalent. In the majority of cases, this creates no problems. For example: + +.. code-block:: python + + small_game.make_event(gbt.H.path(), {"a": .25, "b": .50, "c": .25}) + list(small_game.get_action_probs(gbt.H.path()).values()) + +However, rounding can cause difficulties when attempting to use ``float`` values to +represent values which do not have an exact decimal representation: + +.. code-block:: python + + small_game.make_event(gbt.H.path(), {"a": 1/3, "b": 1/3, "c": 1/3}) + # ValueError: ... + +This behavior can be slightly surprising, especially in light of the fact that in Python, + +.. code-block:: python + + 1/3 + 1/3 + 1/3 + # 1.0 + +In checking whether these probabilities sum to one, ``pygambit`` first converts each of the +probabilities to a :py:class:`.Decimal` representation, via the following method: + +.. code-block:: python + + gbt.Decimal(str(1/3)) + +and the sum-to-one check then fails because: + +.. code-block:: python + + gbt.Decimal(str(1/3)) + gbt.Decimal(str(1/3)) + gbt.Decimal(str(1/3)) + # Decimal('0.9999999999999999166...') + +Setting payoffs for players also follows the same rules. Representing probabilities and +payoffs exactly is essential, because ``pygambit`` offers (in particular for two-player +games) the possibility of computation of equilibria exactly, because the Nash equilibria of +any two-player game with rational payoffs and chance probabilities can be expressed exactly +in terms of rational numbers. + +It is therefore advisable always to specify the numerical data of games either in terms of +:py:class:`.Decimal` or :py:class:`.Rational` values, or their string equivalents. It is +safe to use ``int`` values, but ``float`` values should be used with some care to ensure the +values are recorded as intended. + .. _concepts-information-sets: diff --git a/doc/tutorials/03_stripped_down_poker.ipynb b/doc/tutorials/03_stripped_down_poker.ipynb index 8ebd449aba..f642eeb9f5 100644 --- a/doc/tutorials/03_stripped_down_poker.ipynb +++ b/doc/tutorials/03_stripped_down_poker.ipynb @@ -775,212 +775,6 @@ "outputs": [], "source": "for label in g.get_outcomes():\n payoffs = g.get_outcome_payoffs(label)\n g.set_outcome_payoffs(label, {player: value * 2 for player, value in payoffs})\n\n(\n gbt.nash.liap_solve(g.mixed_strategy_profile(), maxregret=1e-1)\n .equilibrium.max_regret() / (g.max_payoff - g.min_payoff)\n)" }, - { - "cell_type": "markdown", - "id": "5f1f66e0", - "metadata": {}, - "source": "## Representation of numerical data of a game\n\nPayoffs to players and probabilities of actions at events are specified as numbers.\nGambit represents the numerical values in a game in exact precision, using either decimal or rational representations.\n\nTo illustrate, consider a trivial game which just has one move for the chance player:" - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2f79695a", - "metadata": {}, - "outputs": [], - "source": [ - "small_game = gbt.ExtensiveGame()\n", - "small_game.append_event(gbt.H.path(), dict.fromkeys([\"a\", \"b\", \"c\"], gbt.Rational(1, 3)))\n", - "list(small_game.get_action_probs(gbt.H.path()).values())" - ] - }, - { - "cell_type": "markdown", - "id": "dc4522b5", - "metadata": {}, - "source": "Here we specified an equal-probability distribution over the three actions, using `pygambit`'s `Rational` class, which is derived from Python's `fractions.Fraction`, to represent the probabilities exactly.\n\nNumerical data can be set as rational numbers.\n`make_event` forms a collection of nodes into an event with a given distribution; applied to a single node which is already an event, it resets the probabilities.\nLet's use it to change the distribution, again with `Rational` numbers:" - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5de6acb2", - "metadata": {}, - "outputs": [], - "source": [ - "small_game.make_event(\n", - " gbt.H.path(),\n", - " {\"a\": gbt.Rational(1, 4), \"b\": gbt.Rational(1, 2), \"c\": gbt.Rational(1, 4)}\n", - ")\n", - "list(small_game.get_action_probs(gbt.H.path()).values())" - ] - }, - { - "cell_type": "markdown", - "id": "23263b21", - "metadata": {}, - "source": [ - "Numerical data can also be explicitly specified as decimal numbers:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c47d2ab6", - "metadata": {}, - "outputs": [], - "source": [ - "small_game.make_event(\n", - " gbt.H.path(),\n", - " {\"a\": gbt.Decimal(\".25\"), \"b\": gbt.Decimal(\".50\"), \"c\": gbt.Decimal(\".25\")}\n", - ")\n", - "list(small_game.get_action_probs(gbt.H.path()).values())" - ] - }, - { - "cell_type": "markdown", - "id": "bffda303", - "metadata": {}, - "source": [ - "Although the two representations above are mathematically equivalent, `pygambit` remembers the format in which the values were specified.\n", - "\n", - "Expressing rational or decimal numbers as above is verbose and tedious.\n", - "`pygambit` offers a more concise way to express numerical data in games: when setting numerical game data, `pygambit` will attempt to convert text strings to their rational or decimal representation.\n", - "The above can therefore be written more compactly using string representations:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "04329084", - "metadata": {}, - "outputs": [], - "source": [ - "small_game.make_event(gbt.H.path(), {\"a\": \"1/4\", \"b\": \"1/2\", \"c\": \"1/4\"})\n", - "list(small_game.get_action_probs(gbt.H.path()).values())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9015e129", - "metadata": {}, - "outputs": [], - "source": [ - "small_game.make_event(gbt.H.path(), {\"a\": \".25\", \"b\": \".50\", \"c\": \".25\"})\n", - "list(small_game.get_action_probs(gbt.H.path()).values())" - ] - }, - { - "cell_type": "markdown", - "id": "9f22d40d", - "metadata": {}, - "source": [ - "As a further convenience, `pygambit` will accept Python `int` and `float` values.\n", - "`int` values are always interpreted as `Rational` values.\n", - "\n", - "`pygambit` attempts to render `float` values in an appropriate `Decimal` equivalent.\n", - "In the majority of cases, this creates no problems.\n", - "For example:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0a019aa5", - "metadata": {}, - "outputs": [], - "source": [ - "small_game.make_event(gbt.H.path(), {\"a\": .25, \"b\": .50, \"c\": .25})\n", - "list(small_game.get_action_probs(gbt.H.path()).values())" - ] - }, - { - "cell_type": "markdown", - "id": "d53adcd4", - "metadata": {}, - "source": [ - "However, rounding can cause difficulties when attempting to use `float` values to represent values which do not have an exact decimal representation:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1991d288", - "metadata": {}, - "outputs": [], - "source": [ - "try:\n", - " small_game.make_event(gbt.H.path(), {\"a\": 1/3, \"b\": 1/3, \"c\": 1/3})\n", - "except ValueError as e:\n", - " print(\"ValueError:\", e)" - ] - }, - { - "cell_type": "markdown", - "id": "89fefd34", - "metadata": {}, - "source": [ - "This behavior can be slightly surprising, especially in light of the fact that\n", - "in Python:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b1dc37fd", - "metadata": {}, - "outputs": [], - "source": [ - "1/3 + 1/3 + 1/3" - ] - }, - { - "cell_type": "markdown", - "id": "a06699af", - "metadata": {}, - "source": [ - "In checking whether these probabilities sum to one, `pygambit` first converts each of the probabilities to a `Decimal` representation, via the following method:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dc1edea2", - "metadata": {}, - "outputs": [], - "source": [ - "gbt.Decimal(str(1/3))" - ] - }, - { - "cell_type": "markdown", - "id": "4bfff415", - "metadata": {}, - "source": [ - "...and the sum-to-one check then fails because:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1edd90d6", - "metadata": {}, - "outputs": [], - "source": [ - "gbt.Decimal(str(1/3)) + gbt.Decimal(str(1/3)) + gbt.Decimal(str(1/3))" - ] - }, - { - "cell_type": "markdown", - "id": "5208b7a4", - "metadata": {}, - "source": [ - "Setting payoffs for players also follows the same rules.\n", - "Representing probabilities and payoffs exactly is essential, because `pygambit` offers (in particular for two-player games) the possibility of computation of equilibria exactly, because the Nash equilibria of any two-player game with rational payoffs and chance probabilities can be expressed exactly in terms of rational numbers.\n", - "\n", - "It is therefore advisable always to specify the numerical data of games either in terms of `Decimal` or `Rational` values, or their string equivalents.\n", - "It is safe to use `int` values, but `float` values should be used with some care to ensure the values are recorded as intended." - ] - }, { "cell_type": "markdown", "id": "65def67e", From 16b76d02d8de2c022be61f4829987c348a89c219 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 23 Sep 2026 14:28:14 +0100 Subject: [PATCH 5/5] Move section on external programs --- doc/algorithms.rst | 30 +++++++++++++++++++ doc/pygambit.rst | 1 - .../pygambit.external_programs.rst | 29 ------------------ 3 files changed, 30 insertions(+), 30 deletions(-) delete mode 100644 doc/tutorials/advanced_tutorials/pygambit.external_programs.rst diff --git a/doc/algorithms.rst b/doc/algorithms.rst index db71bc87d2..638a30becf 100644 --- a/doc/algorithms.rst +++ b/doc/algorithms.rst @@ -129,6 +129,36 @@ support is complete, that support may be reported with incomplete equilibria, and (in verbose mode) is identified with the label "budget-exceeded" -- see :ref:`gambit-enumpoly ` for details. +Using external programs to compute Nash equilibria +---------------------------------------------------- + +.. TODO: this needs to be updated, see issue #561 + +Because the problem of finding Nash equilibria can be expressed in various +mathematical formulations (see :cite:p:`McKMcL96`), it is helpful to make use +of other software packages designed specifically for solving those problems. + +There are currently two integrations offered for using external programs to solve +for equilibria: + +- :py:func:`.enummixed_solve` supports enumeration of equilibria in + two-player games via `lrslib`. [#lrslib]_ +- :py:func:`.enumpoly_solve` supports computation of totally-mixed equilibria + on supports in strategic games via `PHCpack`. [#phcpack]_ + +For both calls, using the external program requires passing the path to the +executable (via the `lrsnash_path` and `phcpack_path` arguments, respectively). + +The user must download and compile or install these programs on their own; these are +not packaged with Gambit. The solver calls do take care of producing the required +input files, and reading the output to convert into Gambit objects for further +processing. + + +.. [#lrslib] http://cgm.cs.mcgill.ca/~avis/C/lrs.html + +.. [#phcpack] https://homepages.math.uic.edu/~jan/PHCpack/phcpack.html + .. _lp: lp diff --git a/doc/pygambit.rst b/doc/pygambit.rst index b6aa5082b0..8b4f378aed 100644 --- a/doc/pygambit.rst +++ b/doc/pygambit.rst @@ -50,7 +50,6 @@ Advanced tutorials: tutorials/advanced_tutorials/starting_points tutorials/advanced_tutorials/quantal_response tutorials/advanced_tutorials/agent_versus_non_agent_regret - .. pygambit.external_programs Interoperability tutorials -------------------------- diff --git a/doc/tutorials/advanced_tutorials/pygambit.external_programs.rst b/doc/tutorials/advanced_tutorials/pygambit.external_programs.rst deleted file mode 100644 index 14614d945a..0000000000 --- a/doc/tutorials/advanced_tutorials/pygambit.external_programs.rst +++ /dev/null @@ -1,29 +0,0 @@ -Using external programs to compute Nash equilibria -================================================== - -.. TODO: this needs to be updated, see issue #561 - -Because the problem of finding Nash equilibria can be expressed in various -mathematical formulations (see :cite:p:`McKMcL96`), it is helpful to make use -of other software packages designed specifically for solving those problems. - -There are currently two integrations offered for using external programs to solve -for equilibria: - -- :py:func:`.enummixed_solve` supports enumeration of equilibria in - two-player games via `lrslib`. [#lrslib]_ -- :py:func:`.enumpoly_solve` supports computation of totally-mixed equilibria - on supports in strategic games via `PHCpack`. [#phcpack]_ - -For both calls, using the external program requires passing the path to the -executable (via the `lrsnash_path` and `phcpack_path` arguments, respectively). - -The user must download and compile or install these programs on their own; these are -not packaged with Gambit. The solver calls do take care of producing the required -input files, and reading the output to convert into Gambit objects for further -processing. - - -.. [#lrslib] http://cgm.cs.mcgill.ca/~avis/C/lrs.html - -.. [#phcpack] https://homepages.math.uic.edu/~jan/PHCpack/phcpack.html