Polish method - #1032
Conversation
tturocy
left a comment
There was a problem hiding this comment.
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!
| 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) { |
There was a problem hiding this comment.
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!)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
Interesting!
I just added that check.
I will add more tests with games like this when these branches get merged.
| 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 |
There was a problem hiding this comment.
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
| p_function(x, y); | ||
| p_jacobian(x, jac_full); | ||
|
|
||
| // Square Matrix removing fixed_index column |
There was a problem hiding this comment.
This comment is slightly misleading, and actually the comment on jac_square above already covers what this is
| // 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 |
There was a problem hiding this comment.
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.
tturocy
left a comment
There was a problem hiding this comment.
A few minor adjustments noted but I think this is close once we clean them up.
Two actions:
- You'll need to resolve the merge conflicts against the new main
feature/hpbranch as this interacts with the improvements on setting direction merged a bit ago today - Remember to clear out the print tracing instrumentation for the next (final) review.
| } | ||
| } | ||
| else { | ||
| // Criterion function might take t back to being minor than t_target |
There was a problem hiding this comment.
"less than" - I can see "minor than" is a direct calque from Spanish and/or Catalan!
| 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 |
There was a problem hiding this comment.
Would be good to keep similar naming style - c_newtonTol.
| 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}; |
There was a problem hiding this comment.
Probably need something more user-interpretable here: "Path following terminated successfully at point satisfying criterion function."
| 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}; |
| } | ||
| else { | ||
| // Criterion function is not working; polish will do the job | ||
| if (t > last_t + tol) { |
There was a problem hiding this comment.
Don't need this level of nesting, can pull the if up a level
tturocy
left a comment
There was a problem hiding this comment.
Should also have added
- 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?
Removed initial tangent vector checks for path-following direction.
|
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. |
| }, | ||
| x, direction, tracking_index, termination_condition, NullCallbackFunction, | ||
| criterion_function); | ||
| if (!result.status) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
This PR introduces the new function
PolishPoint(other name suggestions are welcome) inpath.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 methodsQRDecompandNewtonStep.It returns a
TracePathResultstruct. Now,TracePathResultalso stores the number of steps necessary to finish the algorithm, useful for debugging purposes.PolishPointis used inhp.ccto 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 att=0fromcontrib/games, the tests that do not reach a regret lower than1e-14are the following:(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: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.pyin PR #1007 as a representation.