Skip to content

Polish method - #1032

Open
AndresFerCervell wants to merge 8 commits into
gambitproject:feature/hpfrom
AndresFerCervell:feature/hp_polish
Open

Polish method#1032
AndresFerCervell wants to merge 8 commits into
gambitproject:feature/hpfrom
AndresFerCervell:feature/hp_polish

Conversation

@AndresFerCervell

Copy link
Copy Markdown
Contributor

This PR introduces the new function PolishPoint (other name suggestions are welcome) in path.cc. Its goal is to reduce the regret of a point that is already close to an equilibrium found by the path-following algorithm. Fixing the value of a component of the point (t in HP), it uses a Newton-type method to find a nearby point with lower regret. The resolution of the system uses the already implemented methods QRDecomp and NewtonStep.

It returns a TracePathResult struct. Now, TracePathResult also stores the number of steps necessary to finish the algorithm, useful for debugging purposes.
PolishPoint is used in hp.cc to refine the equilibrium computed by the tracer.

The termination function has been updated: now it returns true if t >= 1.

The results are very positive. When adding the orientation fix from PR #1030, we see that now we can accurately control the regret of the outputs.

For example, if we set tol=1e-1 (the tolerance is extremely high, so the polish function does nothing), and test the games with a unique best response at t=0 from contrib/games, the tests that do not reach a regret lower than 1e-14 are the following:

  - todd1.nfg -> Eq: 1, Regret: 8.71e-02, Max T: 1.2008
  - 5x4x3.nfg -> Eq: 1, Regret: 1.60e-02, Max T: 1.08448
  - cent2.nfg -> Eq: 1, Regret: 3.00e-02, Max T: 1.01178
  - g3.nfg -> Eq: 1, Regret: 9.42e-03, Max T: 1.01912
  - g1.nfg -> Eq: 1, Regret: 7.63e-03, Max T: 1.32698
  - mixdom2.nfg -> Eq: 1, Regret: 7.23e-03, Max T: 1.03742
  - 2x2const.nfg -> Eq: 1, Regret: 1.12e-02, Max T: 1.03507
  - g2.nfg -> Eq: 1, Regret: 5.73e-04, Max T: 1.35734
  - 2x2x2x2x2.nfg -> Eq: 1, Regret: 4.24e-03, Max T: 1.01367

(Eq means the number of equilibria found, but that is not of interest for this PR, nor is Max T).
If we set now tol=1e-8, the results are the following:

  - 5x4x3.nfg -> Eq: 1, Regret: 3.95e-10, Max T: 1.08448
  - g3.nfg -> Eq: 1, Regret: 4.00e-10, Max T: 1.01912
  - g1.nfg -> Eq: 1, Regret: 5.15e-09, Max T: 1.32698
  - mixdom2.nfg -> Eq: 1, Regret: 3.91e-13, Max T: 1.03742
  - 2x2const.nfg -> Eq: 1, Regret: 5.17e-10, Max T: 1.03507
  - g2.nfg -> Eq: 1, Regret: 8.79e-14, Max T: 1.35734
  - 2x2x2x2x2.nfg -> Eq: 1, Regret: 5.39e-09, Max T: 1.01367

We can see that every failing game has a regret higher than the tol.
If we decide to equal the tolerance of the tests with the one in hp.cc, they all pass!
Some of these games are in test_hp.py in PR #1007 as a representation.

@tturocy tturocy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The results on this polishing idea are indeed very promising. A few adjustments to make, but I don't see anything fundamentally wrong with the approach or implementation!

Comment thread src/solvers/hp/hp.cc Outdated
auto termination_condition = [](const Vector<double> &point) { return point[1] >= 1.5; };
auto criterion_function = [](const Vector<double> &point,
const Vector<double> &tangent) -> double { return point[1] - 1.0; };
auto termination_condition = [t_target](const Vector<double> &point) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to specify a termination condition at all now. Actually by having both termination condition and criterion, the criterion never gets used. The result is we stop once we cross t=1. But that could be a ways past t=1, and possibly leave us with a profile that's far enough from Nash that our polishing step doesn't work.

Instead if we have no termination condition and let criterion do the work, we wind up somewhere really close to t=1. Then the polish step will work (almost for sure all the time!)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand, but I see two problems with removing it completely:

-The tracer will return a False status even when it is successful, because the only way that it has now of finishing is by reducing the step size to the minimum.

-vd.nfg fails because the criterion calculations become unstable near t=1, so eventually it keeps growing to infinity.

Therefore, I suggest an implementation of the termination function that makes use of the criterion, but also has a safeguard in case it fails, leaving the problem to the polishing function.

It works by comparing the current t with the last one when last_t was bigger than 1. If t > last_t, criterion is not working properly, and we have to finish the tracer before t gets too far.
It also ends if we have reached the expected equilibrium.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good points both.

For the first point, this is a case where the step size getting small means something different in the two "modes". When we're in Newton mode, it's actually potentially a sign of success, not failure. What we should do is add a check that when in Newton mode, when the step size is tiny, check the value of the criterion function against a constant tolerance - if within that tolerance return success rather than failure.

For the second point, that game's got continua of equilibria which makes it a very good example. Here's the output from enummixed:

convex-1,1,0,0,0,0,1,0,0
convex-1,1,0,0,0,2/3,1/3,0,0
convex-2,0,0,1,0,0,0,1,0
convex-2,0,0,1,0,1,0,0,0
convex-3,0,0,3/4,1/4,0,0,1/4,3/4
convex-3,0,0,3/4,1/4,1/4,1/3,0,5/12
convex-3,0,0,3/4,1/4,1/4,0,0,3/4
convex-3,0,0,3/4,1/4,0,7/12,1/4,1/6
convex-4,0,0,0,1,0,0,0,1
convex-4,0,0,0,1,0,2/3,0,1/3

That is, the set of equilibria is the union of four convex sets (with the indicated extreme points). Seeing where the method is converging to in relation to these sets would be useful.

This will therefore also be a good practical example to include in the writeup, because it's a concrete example of where actually running this on "real" games - as opposed simply to doing the math theoretically, or running on games with randomly-generated payoffs, which will never have such positive-dimension equilibrium sets!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting!
I just added that check.
I will add more tests with games like this when these branches get merged.

Comment thread src/solvers/logit/path.h Outdated
Vector<double> final_point;
bool status; // true if path tracing terminated successfully, false if it terminated due to error
std::string message; // error message if status is false
int step; // Step at which the tracing terminated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling this steps feels better - yes it's the step number where it finished but also is the number of steps taken, and we use steps everywhere else

Comment thread src/solvers/logit/path.cc Outdated
p_function(x, y);
p_jacobian(x, jac_full);

// Square Matrix removing fixed_index column

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is slightly misleading, and actually the comment on jac_square above already covers what this is

Comment thread src/solvers/logit/path.h Outdated
// This function reduces the regret of a point that is close to an equilibrium that has been found
// by the path-following algorithm. Fixing the value of a component of the point, it uses a
// Newton-type method to find a nearby point with lower regret.
TracePathResult

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like we're abusing the return type. Even if the fields are the same, the idea behind these result return types is that they're semantically related to the method they're coming from. So I'd suggest defining its own result type for this method.

Comment thread src/solvers/hp/hp.cc
@tturocy tturocy mentioned this pull request Aug 11, 2026

@tturocy tturocy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor adjustments noted but I think this is close once we clean them up.

Two actions:

  1. You'll need to resolve the merge conflicts against the new main feature/hp branch as this interacts with the improvements on setting direction merged a bit ago today
  2. Remember to clear out the print tracing instrumentation for the next (final) review.

Comment thread src/solvers/hp/hp.cc Outdated
}
}
else {
// Criterion function might take t back to being minor than t_target

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"less than" - I can see "minor than" is a direct calque from Spanish and/or Catalan!

Comment thread src/solvers/hp/hp.cc
Comment thread src/solvers/logit/path.cc Outdated
double h = m_hStart; // initial stepsize
const double c_hmin = 1.0e-8; // minimal stepsize
const int c_maxIter = 100; // maximum iterations in corrector
const double newton_tol = 1.0e-8; // tolerance for Newton convergence

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to keep similar naming style - c_newtonTol.

Comment thread src/solvers/logit/path.cc Outdated
if (fabs(h) <= c_hmin) {
return {x, false, "Stepsize fell below minimum threshold.", steps};
if (newton && std::abs(p_criterion(x, t)) < newton_tol) {
return {x, true, "Path tracing terminated successfully (Newton convergence)", steps};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably need something more user-interpretable here: "Path following terminated successfully at point satisfying criterion function."

Comment thread src/solvers/logit/path.cc Outdated
if (fabs(h) <= c_hmin) {
return {x, false, "Stepsize fell below minimum threshold.", steps};
if (newton && std::abs(p_criterion(x, t)) < newton_tol) {
return {x, true, "Path tracing terminated successfully (Newton convergence)", steps};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above

Comment thread src/solvers/hp/hp.cc Outdated
}
else {
// Criterion function is not working; polish will do the job
if (t > last_t + tol) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need this level of nesting, can pull the if up a level

@tturocy tturocy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should also have added

  1. Would it make sense to roll in the test games from that other PR into this now, assuming they are working well with this methodology?

@AndresFerCervell

Copy link
Copy Markdown
Contributor Author

I decided to add the tests in this PR to avoid conflicts merging the other one. They all pass, although I had to add the catalog games that were not available at the start of the project.

Comment thread src/solvers/hp/hp.cc
},
x, direction, tracking_index, termination_condition, NullCallbackFunction,
criterion_function);
if (!result.status) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes some of the complex tests fail because the Tracer is not able to reach such a strict tolerance. The error message is "Maximum iterations exceeded.". I think we should let the polish function finish the work even though the tracer failed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noted some comments in a separate email but to keep things in one place:

The first example (fig6_8) actually the tracing is successful but incorrectly reports failure, basically because it doesn't have to Newton step at all to achieve the tolerance. That's a simple adjustment.

The second example really needs some scrutiny to understand why it's failing so we can be precise about what this implementation computes!

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