The castling move is from d=1. The score returned from QS. The static score is from d=1.hgm wrote: ↑Sun Jul 12, 2020 7:41 pmThe problem is that it doesn't help to know that the move is winning due to what it does at larger depth (where it could very well have become killer, because the sibbling, searched earlier, suffered less LMR). At d=1 threats that gain Queens will not be able to fail high unless they actually capture that Queen. Any gain that comes later, even if it was verified by a deeper search, will remain invisible. It would only make sense to search a futile killer when you extend it. The maximum it could raise eval by itself (as opposed to what followed from the threat it made) should have been taken care of by the margin for the in-loop FP. An extra large margin on the killers seems useless. The same would hold for any other move that for some reason you know to be very good. At d=1 they just don't stand a chance.Desperado wrote: ↑Sun Jul 12, 2020 1:59 pmYou do not need to know that! The reason why a move is strong and why you decide to search the subtree might be different.
If you exclude a killer move because you expect a killer to be strong for any reason it might be because of a double attack or anything else.
Of course killer move is only one ad hoc example.
Well, it was said that Stockfish only does this at d=1.Ok, i see the discussion is now mainly for d=1.
I don't get this. The move is castling, is that searched in QS? Or is this from a d=1 node, where castling was searched with a QS reply? There doesn't seem to be any good capture for black after white castles in the given position, so the score must have been obtained from stand-pat after the castling. It is 101 larger than the eval before the castling. So castling increases eval by 101. Is that centi-Pawn? That seems a gross over-estimate for the value of castling. It would for instance sacrifice a Pawn to push it over the horizon. If castling gets you more than gaining a Pawn, then castling should be searched in QS...The stand pat situation is a good point ( for discussion ), but you can print easily situations where our imagination fails and you find moves where some evaluation criterias sum together and will exactly do what you described (avoiding stand pat). Just an example...
Code: Select all
[static_eval: -70 static_eval-alpha: -88 score_from_qs: 31 : alpha:18] move: e1g1 CTM : WHITE woo : 1 wooo: 1 boo : 1 booo: 1 R50 : 0 EPT : -- br -- bb bq bk bb -- br bp bp bp -- bp bp bp bp -- -- -- -- -- bn -- -- -- -- -- -- -- -- -- -- -- -- -- bn -- -- -- -- -- -- wn wb -- wn -- -- wp wp wp wp -- wp wp wp wr -- wb wq wk -- -- wr
You argue from the assumption that the in-loop futility pruning is done on the basis of the full evaluation after the move. Because that is where these positional criteria are summed up. But this is not how it is usually done: one just adds a cheap estimate (such as the victim value, and possibly the change in PST score) of what the move gains to the full evaluation before the move. Subtle effects due to relation with other pieces, such as mobility or pawn-structure changes are supposed to be taken care of by the margin.Especially in advanced engines, positional criterias sum easiliy up to a value of a pawn and more.
Looks like getting the king out of the center and some shield bonuses are enough in that case.
If most moves gain or lose you more than a Pawn, in-loop FP only makes sense if you use a margin larger than a Pawn, because you don't want to habitually prune a lot of stuff that scores above alpha. If castling is the only non-capture that earns such an exceptionally high score, it is silly to test all moves individually for raising the score that much (in fact testing whether they are castlings). If you see before the loop that alpha - eval is large enough to prune all non-captures except castlings, you should just try the castlings. It wouldn't be that hard to have a dedicated move geerator that only generates castlings. In fact it is likely that this is already part of your regular move generator.
Yes, at d > 1 the situation is quite different. But you would use large margins in the first place, for that reason. And the 'dropping into QS' stuff seems to be done only at d=1 (in Stockfish). At d > 1 it would even make less sense; by reducing the depth you get yourself back in the d <= 1 situation. Reducing depth because of futility is totally pointless in general: if you don't believe there is a reasonable chance to pump up the score to alpha in a number of moves, allowing fewer moves for it will certainly not get you there. You should just fail low in that case.I think you would agree that for d > 1 all this stuff becomes more complicated, but the frontier nodes (whatever frontier means today ) include quiets.(Guess:) In most positions quiet moves turn out to be better than captures, otherwise we would never examine quiets at all.
That there are only bad captures for black is just one more reason to check the position.
I did not want to focus on a castle move or any other special group of moves.
I just want to point out that quiet moves are able avoid stand pat when entering QS, even when there is mentionable delta.
The list can be very long and situations can be very different for moves. That works...and there are some other reasons.
One is,a QS node might able to return a hash value instead of a stand pat score. (Perhaps that happend in my example, i didn't check it)
As you can see, the real word situation brings a lot more situations than we can think of and the interaction of the components are very complex.
My pawn values are: mg 80 eg 100
Code: Select all
if(prunable && is_quiet && !pv_node) {
if(depth <= 8 && moves_searched > lmp[progress > 0][depth]) continue;
// INNER LOOP EXAMPLE
if(depth <= 3 && dynamic_eval + 40 + depth * 40 <= alpha) continue;
}
Code: Select all
// OUTSIDE THE LOOP
if(!pv_node && !evasion && !eMove && !IsMate(beta)) {
static const int RM[4] = {0, 120, 160, 200};
if(depth <= 3 && dynamic_eval + RM[depth] <= alpha) {
score = qs(pt, pos, alpha, beta, 0, ply);
if(score <= alpha)
return score;
}
You won't be able to think about all interactions as i already said and you don't need to.
The simple point is:
1. both types of futility checks have nothing in common unless all branches are useless, then the position is also useless
2. using different constraints in the loop than outside is ok because i test for something different (the branch not the position).
3. Moves that are in the category "not prunable" get a chance to be searched even after capture moves and even when they drop into QS directly.
All i do is talking about (my) ideas and my conclusion is still, that we are talking of two pruning techniques which complement.
You are right, that both techniques at the end can give the same information, that the position is futile.
That does not mean looking at different groups of moves (including quiets) is useless. I think i was able to provide some good reasons.
To be honest, i implemented the inner loop pruning before one or two weeks and was faced with the same questions.
I checked some real world situations an understood that there are a lot of situations i never thought of before and there are more than i am able to examine. But that is enough, at least for me, to abstract that there can be futile moves beside the not prunable moves.
So, i found an implementation for my engine that also gained some elo (2 or 3 elo i think).