Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic certainty propagation #700

Closed

Conversation

Videodr0me
Copy link
Collaborator

@Videodr0me Videodr0me commented Jan 26, 2019

Basic certainty propagation.

Please enable by setting the option "certainty-propagation" to "true" (default is disabled = false).
Also it is highly recommended to also set "two-fold-draw-scoring" to "true" (default is normal threefold draw scoring = false). The two options are independent of each other, so you can also try leela with two-fold draw scoring without turning on certainty propagation.

This is PR#2 of the sliced PR 487 changes. In comparison to 487 it reduces code complexity by leaving out:

  • Negabound search function
  • One-ply-look ahead
  • Informative tournament stats (see PR#1)
  • certainty-depth-parameter (as there is no look-ahead)

It retains a small elo plus, does display mate, displayes correct scores in tb positions (instead of leelas eval), and features optional 2-fold draw scoring. On the plus side:

  • CPU overhead is marginal, so no need to increase threads on very fast multiple GPUs systems. Basically CP is a parameter-less on/off option.

As there are ongoing efforts to speed-up the move generator and include a transpositon/caching scheme that includes terminal/certain nodes and efficiently handles in game transpositions, its best to first get this basic version into master, and only later add the look-ahead-search. For more info check PR#487

efficient propagation of certainty, two-fold draw scoring, mate display and more.
=1 suitable for training
=2 for play
Currently negabound search depth is one.
Improves play in positions with many certain positions (nrear endgame TBs, mates).
Sees repetitions faster and scores positions more accurately.
…ersion. Increasing threads (e.g. 4 or 6) will get to masters speed now. Further speed fixes (move generator) possible....
…with lto, this yields a speed up by 30-50% in backend=random. In order to fully use CP please use 4 threads+. Changed default temporarily to 4 threads with this commit, to collect more scaling data.
…ds instant play of certain winning moves and avoidance of loosing moves regardless of visits. CP=3 now adds advanced pruning.
- exposed depth parameter (0 is no-look-ahead)
- only two modes CP=1 for training and CP=2 for play
Todo:
- change option from int to choiceoption
- use info.mate to communicate mate scores
- Certainty Propagation is a bool option now, just on or off (default = off).
- Cleanup code and comments
- Threads default = 2, but if certainty propagation is turned on please use 4 threads.
@MelleKoning
Copy link
Contributor

MelleKoning commented Jan 27, 2019

Can you test it with this position:
position fen 1B1Q4/6P1/2P5/Q1K5/8/8/8/1k6 w - - 5 110
https://lichess.org/analysis/standard/1B1Q4/6P1/2P5/Q1K5/8/8/8/1k6_w_-_-#0

For some reason it comes with the moves:

  1. Qa5-a2 Kb1-c1?? 2. Bb8-f4#

Of course, black can prolong the game two moves by just hitting the queen: 1.... Kb1xa2
in turn, white actually does have mate in 2 with either:

  1. Qd8-d1 ...
  2. Qd8-d3 ..

Assuming CP would see these as certain mates and prefer those above Qa5-a2?

Similarly, a mate in 2:
https://lichess.org/analysis/standard/5r1k/4Nppp/8/8/4R2Q/8/5PPP/6K1_w_k_-

  1. Qh4xh7+ Kh8xh7 2. Re4-h4#
    not found after 200knodes, somehow CP does yet help in these positions

@Videodr0me
Copy link
Collaborator Author

Videodr0me commented Jan 27, 2019

With certainty-propagation turned on (default is off) I get this output for the first position (Net 11248, no TBs):

info depth 4 seldepth 6 time 243 nodes 978 score cp 3092 hashfull 4 nps 4024 tbhits 0 pv g7g8r b1c2 g8f8 c2b3 b8a7
info depth 3 seldepth 7 time 280 nodes 1418 score mate 4 hashfull 5 nps 5064 tbhits 0 pv a5a3 b1c2 g7g8q c2b1 g8h7

For the second this:

info depth 3 seldepth 5 time 188 nodes 757 score cp 3025 hashfull 3 nps 4026 tbhits 0 pv h4g5 h7h6 g5f5 f7f6 e7g6
info depth 3 seldepth 5 time 227 nodes 1238 score mate 3 hashfull 4 nps 5453 tbhits 0 pv h4h7 h8h7 e4h4

So everything seems to work (except that mate count is off by one, which is the result of an oversight when I trimmed the full PR 487 - a fix will be commited shortly) . But remember that even if a mate is proven, it has no concept of shortest distance to mate. If this PR displays mate its definitely mate, but for the length of mate it displays the "average" length ot mate. So depending on the NN and its policy priors that average might (and often will) diverge from shortest length to mate. Also its no magic bullet, if the policy prior is really low for a mating line, it still takes some time to find it - but you can try the full PR487 which has a one-ply-lookahead search and singular extends single moves, that usually finds these combos much faster. We decided to slice to full PR into multiple parts to make code review and testing easier, so please refer to PR487 to see what this will ultimately do.

if (child.edge()->IsOnlyUBounded() && child.GetQ(0) >= 0.0f) Q = -0.01f;
// Penalize exploring suboptimal childs throughout the tree.
if (parent_upperbounded) {
if (child.edge()->IsOnlyUBounded()) Q -= child.GetN() * 0.1f;
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be a massive subtraction - again 0.1 seems a magic number. Should the subtraction really be able to grow unbounded?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, it can grow unbounded which is no problem at all and desired behaviour (at least its supposed to be) . Let me first explain what this does:
(1) if the parent is upperbounded, we know that the player to move is lowerbounded implying that we already have at least a draw if not more. This further implies that we must have at least one edge (move) that is lower-bounded or else the parent would not have been upper-bounded.
(2) With this knowledge we do not need to search any upper-bounded moves at all (!), as these give at maximum a draw - remember we know that we got at least one move that is at least a draw. All visits should be allocated to the non-upper bounded moves. So why not just hard prune them and be done with it. Well....
(3) There is a performance trade-off of "certain" (or terminal) nodes vs. NN evals. In my testing chosing these suboptimals (but bounded) lines often quickly runs into certain nodes or terminal nodes yielding results faster than fetching real NN evals going deeper in the optimal line.
(3) The scores of these visits will be adjusted when flowing through the bounded parent node, so the difference in scores propagated upward between taking these optimal visits or the suboptimal ones initially is minor.
(4) So the above code does not hard prune these nodes, but visits them with decreasing likelihood, so that if initially the probability is high hitting (fast) terminals/certain nodes we visit those just like "normal" leela would. If then leela stubbornly wants to continue visiting these branches, despite being suboptimal we (more or less gradually) increase the penality for doing so.

Now as for the size of the penality, this is more of a magic number than the other epsilon. I can't really say i did much testing on this. I basically eyeballed this that after 10 visits i really do not want to visit this suboptimal line again. Short version: Growing unbounded is no problem, but number can probably be optimized.

// Certainty Propagation: No need to trim the head, just resetting certainty
// state except bounds, and recomputing N should suffices even with WDL hits.
if (current_head_->GetParent()) current_head_->GetOwnEdge()->ClearCertaintyState();
current_head_->RecomputeNfromChildren();
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not seeing how this works - terminals and certains created at ExtendNode time don't get their edges generated - so when they become the head they have no edges - which breaks the concept of choosing the next move from that position.
If RecomputeNFromChildren could set it to 0 if it has no children, that might work - but since it sets n to 1 it won't be candidate for selection for extension. Generally I think if current_head_ has no child edges, its safer to call TrimTreeAtHead like we used to.

Copy link
Collaborator Author

@Videodr0me Videodr0me Feb 9, 2019

Choose a reason for hiding this comment

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

The rational is this:
(1) certain edges are not only created in ExtendNode but also when backpropagating up the tree
(2) as soon as a normal edge is made certain through backpropagation the children of the node it leads to no longer get visits, while the node itself might still get visits.
(3) Now, if such a node becomes the new head, the sum of its children+ 1 is no longer consisten with its N.

RecomputeNfromChildren ensures this consistency and only changes n if n>1, so for node with no children n is left untouched.

At least thats whats supposed to happen.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes I understand what you are trying to do - and it probably makes sense for certain edges that aren't created in ExtendNode - but those that are created in ExtendNode don't get their own edges added, but do still have GetN() returning something other than 0.. If you don't fix that they can't be used as a valid root node.

Copy link
Collaborator Author

@Videodr0me Videodr0me Feb 10, 2019

Choose a reason for hiding this comment

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

I see the following cases potentially arising. Keep in mind that RecomputeNfromChildren only recalculates when the conditional ´if (n_ > 1) ´ is met, otherwise it leaves n_ untouched:

(1) A node is extended, its child edges get created, the NN results are fetched and its score updated. After this n_ = 1 and the conditional (n_ > 1) is not met. If this node becomes root
-> behaviour from normal leela is unaltered, so n_ is correct and consistent

(2) A node is extended, its child edges get createt, but the NN results are not yet fetched. This case probably can't even happen, but to illustrate. If this node gets to be root:
-> behaviour from normal leela is unaltered (as n_ = 0 at that point) , so n_ is correct and consistent.

(3) A (non certain) node has already a visit, and child edges and now gets a second visit that leads to extending one of its childs. Here this node has n_ = 2, and one of the children has n_1. If this node gets to be root,the conditional kicks and n_ is recomputed to the same value n_= 2.
-> so, n_ is correct and consistent.
This should hold for any number of prior visits.

We only really need to Recompute if the node is certain, maybe such a condition could be added for optimization. But apart from that I must be missing something here? Please explain.

Copy link
Contributor

Choose a reason for hiding this comment

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

I added a separate comment to maybe draw your attention to the specific problem more clearly. You seem to be missing the case I was specifically calling out.

Copy link
Member

@mooskagh mooskagh left a comment

Choose a reason for hiding this comment

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

Partial comments. (half of search.cc and entire syzygy.* are still to be reviewed).

@@ -82,6 +82,7 @@ class Position {
};

enum class GameResult { UNDECIDED, WHITE_WON, DRAW, BLACK_WON };
enum class CertaintyTrigger { NONE, TB_HIT, TWO_FOLD, TERMINAL, NORMAL };
Copy link
Member

Choose a reason for hiding this comment

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

As repetition can be both THREE_FOLD (currently) and TWO_FOLD (probably in the future), how about naming that REPETITION?

Copy link
Collaborator Author

@Videodr0me Videodr0me Feb 10, 2019

Choose a reason for hiding this comment

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

This flag is currently only for two-folds to differentiate them from three-folds. Three-folds are still terminal draws . In a more refined version (not this PR) i used this distinction (all else being equal) to:
(1) Prefer terminal draws (three-folds) if root Q is <0.00 and two-folds if root Q is > 0.00, to allow the opponent make mistakes if we feel we would otherwise be better.

Also it allows to:
(2) Use the two-fold flag to display not 0.00 if best move is a two-fold but the Q from previous (first) time the position arose or root Q. This would solve the issue of displaying 0.00 score before the opponent diverges. SF fsome time ago added something similar to suppress these scores. Its a purely cosmetic issue, but here a flag to separate two-folds from three-folds would also come in handy.

I could rename it to REPETITION, SECOND_REPETITION or add another flag for a THREE_FOLD which could be set in terminals (even though the node being terminal already indicates that its a three_fold, because two_folds do not terminate the game and are just scored differently). Any name is fine by me.....

.gitignore Outdated
@@ -1,10 +0,0 @@
build/
Copy link
Member

Choose a reason for hiding this comment

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

You probably don't want to delete .gitignore.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, learning GIT makes me do some stupid things. Will try to summon all git-fu that i have to somehow fix this. I added .gitignore to the .git/info/exclude and removed it from my repo "git rm --cached .gitignore. I think i have to re-add the original .gitignore (without any changes) somehow.

src/mcts/node.h Outdated
void SetEQ(int eq);
// Clears Certainty but keeps bounds.
void ClearCertaintyState() {
(certainty_state_ & kCertainMask) ? certainty_state_ = 0
Copy link
Member

Choose a reason for hiding this comment

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

As this is not supposed to return a value, why not just simple if instead of ?:?

};
// Sets (U)pper and (L)ower bounds.
void UBound(int eq) {
certainty_state_ |= kUpperBound;
Copy link
Member

Choose a reason for hiding this comment

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

It it's expected that certainty_state_ doesn't have particular bits set (e.g. kLowerBound in this case), consider adding ASSERT().
(It will be compiled out in release builds and useful to diagnose misbehavior in debug builds)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

These bounds are additive, so a LBound node later might become UBound as well.

// kTwoFold -> edge is certain because of a two-fold.
// kGameResultWin -> edge is a proven win.
// kGameResultLoss -> edge is a proven loss.
uint8_t certainty_state_ = 0;
Copy link
Member

Choose a reason for hiding this comment

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

I think we discussed that, but can you remind why it is a property of an edge rather than node?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Mainly for speed gain and memory reduction (also it made some conceptual sense at that point in time). Iterating over edges only (more local) should be more efficient than fetching the state from the child nodes. Also if edges are certain there are cases in which the node does not need to be created, saving memory. For example PR487 look-ahead makes edges certain, and that info can be used to make the parent certain without ever visiting or creating the nodes. Especially with two-fold draw scoring this actually saves a decent amount of memory.

int multipv = 0;
for (const auto& edge : edges) {
float score = (edge.HasNode() && edge.node()->GetParent())
Copy link
Member

Choose a reason for hiding this comment

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

It's guaranteed that edge->node will have parent, and more of that, it's guaranteed that it's root_node_ (as we just called GetBestChildrenNoTemperature for it)

// adjusted by +1000; For root filtered TB moves +500.
if (params_.GetCertaintyPropagation()) {
if (edge.IsCertain() && edge.GetEQ() != 0)
uci_info.mate = edge.GetEQ() * ((uci_info.pv.size() + 1) / 2 +
Copy link
Member

Choose a reason for hiding this comment

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

If there is else then if's body should be in braces too.

int sign = (root_syzygy_rank_ - 1 > 0) - (root_syzygy_rank_ - 1 < 0);
if (sign) {
uci_info.mate = sign * (-500 + abs(root_syzygy_rank_));
} else uci_info.score = 0;
Copy link
Member

Choose a reason for hiding this comment

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

body of else in braces.

if (edge.IsTerminal()) {
v = edge.node()->GetQ();
if (edge.IsCertain()) {
v = (float)edge.edge()->GetEQ();
Copy link
Member

Choose a reason for hiding this comment

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

static_cast instead of C-style cast. By why is explicit cast needed here?

@@ -260,7 +280,8 @@ std::vector<std::string> Search::GetVerboseStats(Node* node,
}
oss << ") ";

if (edge.IsTerminal()) oss << "(T) ";
oss << " C:" << std::bitset<8>(edge.edge()->GetCertaintyState());
Copy link
Member

Choose a reason for hiding this comment

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

Instead of pritting bitset, it would be nice to build some string like ..W..T.D (with names of flags set).
Not necessary, just easier to interpret.

@borg323
Copy link
Member

borg323 commented Feb 9, 2019

This is what the diff looks like with mate score display removed (and .gitignore added back): master...borg323:basic-certainty-propagation#files_bucket

// Returns true if the population came from tablebase.
bool PopulateRootMoveLimit(MoveList* root_moves) const;
// Returns best_rank != 0 if the population came from tablebase.
// 1 for draw, > 1 for win and < 1 for loss
Copy link
Member

Choose a reason for hiding this comment

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

Hm, I don't understand that comment. If it returns 0, that's "no information", if it's -1 then it "loss in 1"?.. And 2 is "win in 1"?
Also what is best_rank?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The ranks are identical to the original ranks by the syzygy probe code, only that draw = 1 and a fail of the probe is 0. I will update the comment.

possible_moves = 1;
break;
} else if (search_->current_best_edge_ == child && possible_moves > 0)
continue;
Copy link
Member

Choose a reason for hiding this comment

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

braces around continue.

node->MakeTerminal(result);
else
node->MakeCertain(result, trigger);
return;
Copy link
Contributor

Choose a reason for hiding this comment

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

This code path here - does not call node->CreateEdges - thus these nodes have no child edges.
As an example of why this should be a problem - a TBhit will flow into this path creating a certain node with no child edges attached.
If the game advances to the point where the tbhit is now root, we need to select which move to perform (in practice hopefully using dtz root probe limiting, but ignore that for sake of argument). Since the node has visits, its not a candidate for expansion, so we will enumerate its edges trying to find a child to expand - but there are no edges to enumerate. Thus search will fail to produce a best move.

Copy link
Collaborator Author

@Videodr0me Videodr0me Feb 10, 2019

Choose a reason for hiding this comment

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

As far as i see it the HasChildren() check in picknodetoextend does not use n_ but uses if the edge list exits.

if (node->IsCertain()) {
      return NodeToProcess::TerminalHit(node, depth, 1);
    } else if (!node->HasChildren()) {
      return NodeToProcess::Extension(node, depth);
    }

So if no edges are created the node will be a candidate for expansion. The code should work in this case as n_ is not relevant. I ran into no problems in 50.000 games with this, but maybe i am missing something else here. I could just clear n_ or trim but this would lose information behind the node made certain that would have to recomputed through search. In time trouble that can be problematic. If you pinpoint the problem you see, we could fix this without losing information from previous searches.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, I can see why it may manage not to crash now.

But I do think there is an off by one error - the code to reset n_ will reset it to 1 because it sets it to 1+child_sum if greater than 1. But an unexpanded node should have n ==0. Thus its n will start from 1. Its q values will be wrong - which will effect fpu calculations.

Also the collision code will get confused because N > 0 even though its unexpanded. So multiple threads could be scheduled to expand the node simultaneously, causing serious race conditions. (I'm actually surprised that this didn't give you significant crashes.)

Copy link
Collaborator Author

@Videodr0me Videodr0me Feb 11, 2019

Choose a reason for hiding this comment

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

Hmm, i can't really remember why i had that >1 condition in the first place. I can see what is troubling you, the case arises only if:
(1) the node is a tb_hit or two-fold and,
(2) it gets at least one visit and,
(3) it then gets root.
In this case (n_=1) TryScoreUpdate is true for all threads and the node gets potentially added to eachs threads nodestoprocess for expansion. [The q value is less of an issue, as the prior certain result is just weighted higher which in case of a certain root might actually be desired]. I am a little surprised myself it never showed, especially since i generally test with more threads (PR487) and Ipmanchess's sudden death games have lots of only root expansions and playing on policy.

I think (not currently looking at the code) that i included the > 1 condition in a time prior to tablebases and always wondered what would happen if a terminal with 1 visit becomes root. I probably did not want to mess with leelas behaviour in that case. But the issue you raised would have had applied there too.
So i think one can safely drop the > 1 condition from ReComputeNfromChildren and one could also limit recomputation to certain nodes to avoid recomputation normal one-visit nodes (that should have children).

Then certain nodes without children should have n_=0, including real terminals, tb hits and two folds while fully preserving the search tree behind a certain node that was established through backpropagation.

Copy link
Contributor

@Tilps Tilps Feb 11, 2019

Choose a reason for hiding this comment

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

Your current recomputation starts with 1 then adds child visits - so regardless of the > 1 condition - its still going to end up with 1. You will need to check if the node has edges, and if not set n to 0 rather than 1.

Copy link
Collaborator Author

@Videodr0me Videodr0me Feb 11, 2019

Choose a reason for hiding this comment

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

Ok, i remember now. I wanted the function ReComputeNfromChildren to be generally applicable to all nodes. For nodes with one visit and no children as well as for nodes with children. This does not help for the abovementioned certain nodes (tb hits or two-folds) without children where we want to set n_ to 0 instead of the real number of visits. A solution might be to:

RecomputeNfromChildren for all certain nodes with children and set n_ to 0 for all certain nodes without children (simplest by trimming).

I think that should cover all cases and preserve the search tree behind a certain node that was established through backpropagation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Code changed so n_=0 for certain nodes without children.

Also changed for self-play (make move). Wouldn't this also make it possible to use TBs in self-play?

…filtered tb scores if drawn (compatible with new option kSyzygyFastPlayId), comment cleanup.
// Safe moves are added to the safe_moves output paramater.
bool root_probe(const Position& pos, bool has_repeated,
int root_probe(const Position& pos, bool has_repeated,
std::vector<Move>* safe_moves);
// Probes WDL tables to determine which moves might be on the optimal play
// path. If 50 move ply counter is non-zero some (or maybe even all) of the
// returned safe moves in a 'winning' position, may actually be draws.
// Returns false if the position is not in the tablebase.
Copy link
Member

Choose a reason for hiding this comment

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

Returns false outdated comment.

@@ -87,16 +87,17 @@ class SyzygyTablebase {
// has_repeated should be whether there are any repeats since last 50 move
// counter reset.
// Thread safe.
// Returns false if the position is not in the tablebase.
// Returns 0 if the position is not in the tablebase, and best rank
// if found (1 = draw, > 1 for wins, < 1 for losses)
Copy link
Member

Choose a reason for hiding this comment

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

Hm, I don't understand again what it returns for losses..
E.g. -1 ? Why is it < 1 and not < 0 then?..

Copy link
Member

Choose a reason for hiding this comment

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

Do you have any link explaining what "rank" means in terms of DTZ probing?

@@ -277,6 +281,8 @@ class SearchWorker {
};

NodeToProcess PickNodeToExtend(int collision_limit);
void EvalPosition(Node* node, MoveList& legal_moves, const ChessBoard& board,
Copy link
Member

Choose a reason for hiding this comment

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

Non-const references are not allowed by code style. As it's function output, use std::tuple<> return parameter instead?

@Videodr0me
Copy link
Collaborator Author

Videodr0me commented Feb 24, 2019

Here are the stats for the overhead if CP is off, some confusion about this stems from the appveyor builds generally being slower than the release builds (at least on windows). I compared latest release https://github.com/LeelaChessZero/lc0/releases/tag/v0.21.0-rc1 with my pgo compile (random backend):

threads 21.0-rc1 PR700
2 230609 nps 227940 nps
4 318255 nps 312443 nps

(https://pastebin.com/qZ9D3iVc)

I think this is completely inconsequential and reflects mostly the CP on/off checks, larger edge structure and some lines of code i did not want to shield with additional CP on/off checks.

@oscardssmith
Copy link
Contributor

Nice! Any idea on how long until merge?

@Videodr0me
Copy link
Collaborator Author

Videodr0me commented Feb 25, 2019

Nice! Any idea on how long until merge?

I actually do not know. There is still some reluctance to merge, mainly because of the edge centric approach. Maybe we will do a seperate CP release (there it would be an always-on feature - and it would also include the one-ply-look ahead). On the other hand, i will not be able to maintain that alone, and i really want to tackle the transposition issue next (would be a good combo with CP).

Crem is currently working on rewriting search infrastructure and expressed some interest into reworking CP to fit this and his vision of the codebase. Which would also be great, and i would fully support and help to make that work. Especially, since makes no sense to push for something that main devs feel uncomfortable with.

The main issue is that i store certainty info in edges and not in the node. Which to me is a minor issue that is shielded by the getters and setters. I did that with the future in mind as i believe that greatly helps for storing look-ahead results without creating nodes (saving memory), also if transpositions are handled and edges point to a certain node, we do not even have to look those up, as the edge would become certain (speed). Also a two-fold along one path might not be a two-fold if reached through another path, edge-centricity would prevent creating superflous nodes. Currently there are only a few node-centric functions left in the main search code (mainly backprop and checks for certainty) - my vision was that once CP proved its merits, that we would make those edgenode or edge centric. I fully understand that edge-centricity only makes sense with the look-ahead, transpositions and memory saving in mind.

Current developments seem to go into another direction. The node structure has been expanded greatly by storing best_child_cached_, d_, child_cache_in_flight_limit_ .... so maybe some of the paradigms have changed.

…(struct of gameresult and trigger) as return value.
@Videodr0me
Copy link
Collaborator Author

Changed last remaining reviewer issues:

  • EvalPosition no longer passes values back by reference
  • CertaintyResult struct useed to pass result (and beautify code)

This is as far as i am going to with PR700 (its a "dumbed" down PR487 anyways). I will move on. This and PR487 are here and may serve as a proof-of-concept. If anybody wants to take this further, they are welcome, and i will happily answer any questions and help out if needed.

@MelleKoning MelleKoning mentioned this pull request Mar 8, 2019
@MelleKoning
Copy link
Contributor

MelleKoning commented Mar 9, 2019

@Videodr0me @mooskagh @Mardak
Have updated the following to cleanly merge with latest code updates from master:
Videodr0me#5

uci_info.mate = edge.GetEQ() * ((uci_info.pv.size() + 1) / 2 +
(edge.IsPropagatedTBHit() ? 1000 : 0));
} else if (root_syzygy_rank_ == 1) {
uci_info.score = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

@Videodr0me It looks like @jjoshua2 is using something based on this PR for Leelenstein at CCC, and it's now reporting cp 0 for tablebase positions even when they're won positions. Fortunately the opponent knows it's a losing position, so CCC doesn't adjudicate draw. I'm guessing the return value of PopulateRootMoveLimit is not what you think it is.

Copy link
Contributor

@Mardak Mardak Apr 28, 2019

Choose a reason for hiding this comment

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

In the mean time, the simplest fix I can recommend to @jjoshua2 is to comment out this uci_info.score = 0; line as it'll just revert to the normal score it would have shown. jjoshua2#23

@Naphthalin
Copy link
Contributor

This is an old version of certainty propagation, which is probably obsolete with MLH in search.
as #800 was also closed, I recommend closing this PR.

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.