Skip to content

Structure of hp method, ComputeInitialPoint and ExtractEquilibrium methods - #973

Merged
tturocy merged 6 commits into
gambitproject:feature/hpfrom
AndresFerCervell:feature/hp_initial_point
Jul 20, 2026
Merged

Structure of hp method, ComputeInitialPoint and ExtractEquilibrium methods#973
tturocy merged 6 commits into
gambitproject:feature/hpfrom
AndresFerCervell:feature/hp_initial_point

Conversation

@AndresFerCervell

@AndresFerCervell AndresFerCervell commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Issues closed by this PR

None

Description of the changes in this PR

  1. Solver Architecture: Created the new src/solvers/hp/hpsystem.h and src/solvers/hp/hpsystem.cc files that will have the mathematical implementation of the algorithm. src/solvers/hp/hpsystem.h includes a draft of the declaration of the methods that will be used.
  2. Variable Transformation: Implemented the $\sigma(\alpha)$ and $\lambda(\alpha)$ transformations.
  3. Start Point Calculation (t=0): Implemented HPEquationSystem::ComputeInitialPoint() to compute the starting vector $(t, \alpha, \mu)$ by finding the best response to the prior belief.
  4. Equilibrium Extraction: Implemented HPEquationSystem::ExtractEquilibrium() to map the final $\alpha$ variables back into a normalized MixedStrategyProfile<double>.
  5. PyGambit Update: ModifiedMakefile.am to inform about the new files, gambit.pxd, nash.pxi, and nash.py. Now, pygambit.nash.hp_solve() receives two parameters: the game and the prior.
  6. hp.cc Update: Given a game and a prior, it computes the initial point (t=0), writes the vector in "alpha form" (this is provisional) and returns the MixedStrategyProfile<double> associated.

Important Notes:

  • tol being defined as 1e-9 is provisional. It should be scaled according to the proportins of the game. Maybe using a variable scale as it is done in src/solvers/logit/efglogit.cc would be a nice solution.
  • A big problem we may face is that the paper assumes a generic prior generating a strictly unique best response. This will not always be the case, so for now, I have assigned 1 to the first best response found, and 0 to the other tied strategies. However, ths will lead to a crash, since the entire algorithm works assuming that there cannot be two variables alpha being zero at the same time, as especified in page 183. Therefore, we can either warn the user and stop the execution if multiple best responses are found, which is probably not a fine solution, or implement a perturbation method that changes the prior when a degenerate tie is detected,

How to review this PR

  • Mathematical Logic: Review src/solvers/hp/hpsystem.cc and compare ComputeInitialPoint() against the theoretical setup in Section 4 of Herings & Peeters (2001).
  • Testing: The next Python script uses the example proposed in the paper. The expected result is [(1,0), (0,1)]
import numpy as np
import pygambit 

p1_payoffs = np.array([[2, 0], [0, 1]])
p2_payoffs = np.array([[1, 0], [0, 4]])
game = pygambit.Game.from_arrays(p1_payoffs, p2_payoffs, title="Harsanyi-Selten")

prior = game.mixed_strategy_profile()


p1 = list(game.players)[0]
p2 = list(game.players)[1]


s1_p1 = list(p1.strategies)[0]
s2_p1 = list(p1.strategies)[1]
s1_p2 = list(p2.strategies)[0]
s2_p2 = list(p2.strategies)[1]

prior[s1_p1] = 0.5
prior[s2_p1] = 0.5
prior[s1_p2] = 2.0 / 3.0
prior[s2_p2] = 1.0 / 3.0

print("Prior:")
print(prior)

resultado = pygambit.nash.hp_solve(game, prior=prior)
print("Final result (probabilities form):")
print(resultado)

@tturocy

tturocy commented Jul 7, 2026

Copy link
Copy Markdown
Member

To capture notes/suggestions from the meeting:

  • We don't need to specify a game if we specify initial beliefs as a profile, because the profile already is inherently associated with the game.
  • For the moment we will require a unique best response from the initial beliefs; if this fails then an exception can be thrown (with a suitably informative message!)
  • The example case (and any other tests) can be set up a test fixtures. These can just be put in tests/test_hp.py for now until we decide how best to standardise them. Tests of correct output (of the expected profile) of course will fail right now - these can be marked using xfail (https://docs.pytest.org/en/stable/reference/reference.html#pytest-mark-xfail) at the start.
  • While we're still on the fence as to naming, we'll stick with "HP" as the method name for now.

@AndresFerCervell
AndresFerCervell marked this pull request as ready for review July 11, 2026 20:29
@AndresFerCervell

AndresFerCervell commented Jul 12, 2026

Copy link
Copy Markdown
Contributor Author

As discussed in the meeting, I have createdtests/test_hp.py.
It contains the first two examples of Harsanyi & Selten (1988) section 4.11 (the second one is the same that appeared in the H&P paper.) marked as expected to fail. Note: the first example already passes because the final output is the expected in t=0.
The third example is not added since the linear tracing procedure is not well defined. It will be interesting to check its behavior once we have the tracer implemented, so we will study it with more depth in the future.

Furthermore, I have added a test with multiple best responses for player 1, which already works (throwing the exception).

More will be added in the future.

AndresFerCervell and others added 2 commits July 14, 2026 15:02
Updated expected results for HPSolver test case.
@AndresFerCervell

Copy link
Copy Markdown
Contributor Author

I have implemented GetValue, CalculateDynamicPayoff and the Jacobian. I decided to try directly using the derivatives so it would be faster.
The tracer is already hooked up. It passes the three tests that we have so far in the test section!

Next steps, notes and questions:
-Adding more tests as you told me and see how robust the implementation is.
-Take path.cc and path.h and leave them in a folder in core, or in any other suggested region of the repository.
-Although the solver is always going to return a single MixedStrategyProfile, I have decided to keep returning a list with a single element to ensure consistency with the other algorithms, I think it is the best solution.
-The Callback function I have used is provisional and will be deleted, but for now I found it convenient to leave it there to do some checks and see how the tracer works.
-An important adjustment that must be made: the tracer must end exactly when t=1. I will work on that to ensure that it does not go too far.

Tell me what you think!

@tturocy

tturocy commented Jul 14, 2026

Copy link
Copy Markdown
Member

This is definitely exciting progress!

I've had a quick look at this on some of our other very standard games and I think the necessity of terminating on t=1 very closely is essential. For example you can load this game from the catalog:

pygambit.catalog.load("journals/other/reiley2008/fig1")

(see https://gambitproject.readthedocs.io/en/latest/catalog.html#catalog). This is returning a point that is not Nash.

I think what is going on is the test cases so far always select a pure strategy equilibrium so we get away with not stopping exactly at 1 because the path is no longer moving very much in the transformed space. But when the limiting equilibrium involves randomisation it looks like this does matter.

I believe the mechanism for homing in on t=1 is already present in the tracer. See the implementation in logit that allows us to compute a quantal response equilibrium for a given value of $\lambda$. This is exactly the same as wanting to get to exactly this value of $t$. So it should be a simple criterion function that will let us do this!

@tturocy

tturocy commented Jul 14, 2026

Copy link
Copy Markdown
Member

I should say in addition: I am looking at the logging output for the Reiley "stripped down poker" example above. Just interpolating between the point at $t=.865855$ and $t=1.21879$ gives something in the vicinity of the equilibrium. This supports my hunch that the path-following seems to be working just about correctly it's just that getting exactly $t=1$ is critical.

@tturocy

tturocy commented Jul 20, 2026

Copy link
Copy Markdown
Member

As from our catchup meeting, I think this is a clear "finished" step so am going to squash and merge it. Subsequent tasks (test examples, cleanup, refactoring) we can isolate as separate commits/PRs.

@tturocy
tturocy merged commit 76fd809 into gambitproject:feature/hp Jul 20, 2026
14 checks passed
@AndresFerCervell
AndresFerCervell deleted the feature/hp_initial_point branch July 27, 2026 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants