Razoring

Discussion of chess software programming and technical issues.

Moderator: Ras

mcostalba
Posts: 2684
Joined: Sat Jun 14, 2008 9:17 pm

Re: Razoring

Post by mcostalba »

bob wrote: when I changed from razoring to outright forward pruning there was a significant gain.
This is really unexpected to me.

I explain better, you can have 2 types of forward pruning:

1) The position is so good (according to static evaluation or similar metric) and the remaining depth so low that you simply return a fail-high. This is what in SF we call static null move pruning.

2) The position is so bad that you return a fail-low without even verify with a qsearch. This is what you'd suggest is better/comparable to razoring.

In my opinion the first technique could or could not work depending on testing, but at least is sound. Instead the second one is flawed. The two prunings seem symmetrical but there is a fundamental difference: it is your time to move, not the the opponent's one !

In the first case you are already very good so don't need to move and can simply return a fail-high (what in qsearch is also called to stand-pat). In the second case you are very bad, but you can move so if perhaps there is an hanging queen somewhere you can easily totally recover from the static evaluation bad score. That's the reason you need (you must IMHO) to qsearch() to validate the bad position evaluation netting out any hanging opponent piece before to return a fail-low score with at least a minimum level of confidence.
BubbaTough
Posts: 1154
Joined: Fri Jun 23, 2006 5:18 am

Re: Razoring

Post by BubbaTough »

Gerd Isenberg wrote:
bob wrote: Where does that "hyatt code" come from? It does not appear to look anything like the code in recent Crafty versions... I don't do "razoring" at all. I prune or reduce...
I coined Hyatt's Razoring in cpw, because you proposed it as mentioned by Lucas. Should I credit somebody else?
The problem with trying to credit someone for something like this is the basic idea (pruning based on depth + score/score estimate) is pretty obvious, and likely to be independently generated by almost anyone that hasn't seen it somewhere else already. As we can see from this thread, trying to assign credit and track all the various variants on the theme that pop up in such cases is a nightmare and likely to stir up somewhat silly arguments.

-Sam
bob
Posts: 20943
Joined: Mon Feb 27, 2006 7:30 pm
Location: Birmingham, AL

Re: Razoring

Post by bob »

mcostalba wrote:
bob wrote: when I changed from razoring to outright forward pruning there was a significant gain.
This is really unexpected to me.

I explain better, you can have 2 types of forward pruning:

1) The position is so good (according to static evaluation or similar metric) and the remaining depth so low that you simply return a fail-high. This is what in SF we call static null move pruning.

2) The position is so bad that you return a fail-low without even verify with a qsearch. This is what you'd suggest is better/comparable to razoring.

In my opinion the first technique could or could not work depending on testing, but at least is sound. Instead the second one is flawed. The two prunings seem symmetrical but there is a fundamental difference: it is your time to move, not the the opponent's one !

In the first case you are already very good so don't need to move and can simply return a fail-high (what in qsearch is also called to stand-pat). In the second case you are very bad, but you can move so if perhaps there is an hanging queen somewhere you can easily totally recover from the static evaluation bad score. That's the reason you need (you must IMHO) to qsearch() to validate the bad position evaluation netting out any hanging opponent piece before to return a fail-low score with at least a minimum level of confidence.
Wrong way of looking at it. The difference between razoring and pruning is that with pruning, you just don't search the move. With razoring, you search the move but drop into the q-search rather than doing another ply (or two or whatever your razoring limit is).

If your razoring / pruning decision is reasonable, pruning is far better. Much less effort. If your razoring/pruning decision is bad, razoring is safer because you do some sort of search, albeit a limited one.

I used to use razoring. I'd have to check to find the exact version. I used the Heinz approach of futility + extended-futility + razoring. I found the best results with pruning alone, with properly tuned margins...
User avatar
Eelco de Groot
Posts: 4702
Joined: Sun Mar 12, 2006 2:40 am
Full name:   Eelco de Groot

Re: Razoring

Post by Eelco de Groot »

bob wrote:
mcostalba wrote:
bob wrote: when I changed from razoring to outright forward pruning there was a significant gain.
This is really unexpected to me.

I explain better, you can have 2 types of forward pruning:

1) The position is so good (according to static evaluation or similar metric) and the remaining depth so low that you simply return a fail-high. This is what in SF we call static null move pruning.

2) The position is so bad that you return a fail-low without even verify with a qsearch. This is what you'd suggest is better/comparable to razoring.

In my opinion the first technique could or could not work depending on testing, but at least is sound. Instead the second one is flawed. The two prunings seem symmetrical but there is a fundamental difference: it is your time to move, not the the opponent's one !

In the first case you are already very good so don't need to move and can simply return a fail-high (what in qsearch is also called to stand-pat). In the second case you are very bad, but you can move so if perhaps there is an hanging queen somewhere you can easily totally recover from the static evaluation bad score. That's the reason you need (you must IMHO) to qsearch() to validate the bad position evaluation netting out any hanging opponent piece before to return a fail-low score with at least a minimum level of confidence.
Wrong way of looking at it. The difference between razoring and pruning is that with pruning, you just don't search the move. With razoring, you search the move but drop into the q-search rather than doing another ply (or two or whatever your razoring limit is).

If your razoring / pruning decision is reasonable, pruning is far better. Much less effort. If your razoring/pruning decision is bad, razoring is safer because you do some sort of search, albeit a limited one.

I used to use razoring. I'd have to check to find the exact version. I used the Heinz approach of futility + extended-futility + razoring. I found the best results with pruning alone, with properly tuned margins...
This is different in Stockfish, because Stockfish tries razoring before nullmove and before callng the movegenerator, by simply calling search() or qsearch() again with a reduced value of beta and if it fails low, returns that. This is very different from the way you described the implementation used in Crafty. In the case of Stockfish, I'm not sure about what to do if there is a TT entry with equal or better depth than qsearch. Just the condition && ttMove == MOVE_NONE seems not enough in that case because no move is stored in VALUE_TYPE_UPPER nodes in the case of Stockfish? In Rainbow Serpent I do store a move if depth < IIDDepth because I believe that is theoretically defendable. Maybe I missed something because I believe I have tested this before.

However if you reduce instead of prune, then again you have the question of whether to allow TT entries of VALUE_TYPE_UPPER that have a ttDepth good enough for the reduced depth. And again it may make a difference whether you have stored a "bestMove" in that case or not, even though it is of course an ALL node, theoretically.

If Marco wants to try reducing instead of pruning maybe for Stockfish too the following code is not so much worse than the default;

This is with default razoring code,

Code: Select all

     // Step 6. Razoring (is omitted in PV nodes)
    if (   !PvNode
        &&  depth < RazorDepth
        && !inCheck
        &&  refinedValue + razor_margin(depth) < beta
        &&  ttMove == MOVE_NONE
        &&  abs(beta) < VALUE_MATE_IN_PLY_MAX
        && !pos.has_pawn_on_7th(pos.side_to_move()))
    {
        Value rbeta = beta - razor_margin(depth);
        Value v = qsearch<NonPV>(pos, ss, rbeta-1, rbeta, DEPTH_ZERO);
        if (v < rbeta)
            // Logically we should return (v + razor_margin(depth)), but
            // surprisingly this did slightly weaker in tests.
            return v;
    }
I think it is still identical with Stockfish and maybe it is because the razor_margins etc. are better tuned, better than some other things I tried;

Testposion, after
[FEN "r1bq1rk1/pp1n1pp1/4pn1p/2p3B1/3P4/P1PB1N2/2P3PP/R3QRK1 w - -"]

1. Qh4 c4 2. Bxc4 hxg5 3. Nxg5 Re8 4. Bd3 Nf8 5. Rae1 Qc7
6. Rxf6 *

[d]r1b1rnk1/ppq2pp1/4pR2/6N1/3P3Q/P1PB4/2P3PP/4R1K1 b - -

Engine: Stockfish Kingdefender (Rainbow Serpent, Build 159, Athlon 2009 MHz, 256 MB, using "critical node" singular extensions combined with suppression of LMR in the child ALL node) by Tord Romstad, Marco Costalba and Joona Kiiski

1/01 0:00 +1.61 6...gxf6 (125) 0

2/02 0:00 +1.37 6...gxf6 7.Qh5 e5 8.g4 (3.746) 16

3/03 0:00 -0.88 6...gxf6 7.Ne4 Nd7 8.Nd2 (21.158) 79

4/05 0:00 -0.92 6...gxf6 7.Ne4 Nd7 8.Bb5 Kg7 9.Bxd7 Bxd7 (28.750) 102

5/06 0:00 -0.92 6...gxf6 7.Ne4 Nd7 8.Qg4+ Kf8 9.Bb5 Ke7 (35.773) 120

6/07 0:00 -0.15 6...gxf6 7.Ne4 Nd7 8.d5 f5 9.Qg5+ Kh7
10.Qh4+ Kg7 11.Ng5 Kf8 (51.253) 164

7/14 0:00 -0.08 6...gxf6 7.Ne4 Nd7 8.Ng3 Qxc3 9.Ne2 Qd2
10.Qh7+ Kf8 11.h3 (250.954) 365

8/15 0:00 -0.17 6...gxf6 7.Ne4 Nd7 8.Ng3 Qxc3 9.Nf5 Nf8
10.Nd6 Bd7 11.Qg4+ Ng6 12.Nxb7 (323.759) 391

9/22 0:02 -0.24 6...gxf6 7.Ne4 Nd7 8.Ng3 Qxc3 9.Re3 Qa1+
10.Kf2 Nf8 11.Qg4+ Ng6 12.Bxg6 fxg6
13.Qxg6+ Kf8 14.Nf5 Re7 15.Nxe7 Kxe7
16.Qg7+ (1.365.350) 460

10/22 0:04 -0.36-- 6...gxf6 7.Ne4 Nd7 8.Ng3 Qxc3 9.Re3 Qa1+
10.Kf2 Nf8 11.Qg4+ Ng6 12.Bxg6 fxg6
13.Qxg6+ Kh8 14.Qxe8+ Kg7 15.Nf5+ Kh7
16.Rg3 (2.296.346) 478

10/22 0:04 -0.48 6...gxf6 7.Ne4 Nd7 8.Ng3 Qxc3 9.Re3 Qa1+
10.Kf2 b5 11.Qh7+ Kf8 12.Nf5 e5
13.Bxb5 Bb7 14.Bxd7 Bc6 (2.377.031) 483

11/23 0:08 -0.60-- 6...gxf6 7.Ne4 Nd7 8.Ng3 Qxc3 9.Re3 Qa1+
10.Kf2 Qd1 11.Qh7+ Kf8 12.Nf5 Qxd3
13.cxd3 Nc5 (4.036.948) 496

11/23 0:08 -0.72-- 6...gxf6 7.Ne4 Nd7 8.Ng3 Qxc3 9.Re3 Qa1+
10.Kf2 Qd1 11.Qh7+ Kf8 12.Nf5 Qf3+
13.gxf3 exf5 14.Rxe8+ Kxe8 (4.095.520) 498

11/23 0:08 -0.90-- 6...gxf6 7.Ne4 Nd7 8.Ng3 Qxc3 9.Re3 Qa1+
10.Kf2 Qd1 11.Qh7+ Kf8 12.Nf5 Qf3+
13.gxf3 exf5 14.Rxe8+ Kxe8 15.Bxf5 (4.176.335) 498

11/23 0:08 -1.06 6...gxf6 7.Ne4 Nd7 8.Ng3 Qxc3 9.Re3 Qb2
10.Qh7+ Kf8 11.Nf5 Qc1+ 12.Bf1 Qxe3+
13.Nxe3 Nb6 14.Ng4 (4.311.435) 502

12/23 0:10 -1.18-- 6...gxf6 7.Ne4 Nd7 8.Ng3 Qxc3 9.Re3 Qb2
10.Qh7+ Kf8 11.Nf5 Qc1+ 12.Bf1 Qxe3+
13.Nxe3 a5 14.Ng4 Ke7 (5.308.293) 512

12/23 0:10 -1.29 6...gxf6 7.Ne4 Nd7 8.Ng3 Qxc3 9.Re3 Qa1+
10.Kf2 Qd1 11.Qh7+ Kf8 12.Nf5 Qd2+
13.Be2 Qxe3+ 14.Nxe3 Nb8 15.Qh8+ Ke7
16.Qg7 Rd8 (5.548.723) 516

13/24 0:15 -1.41-- 6...gxf6 7.Ne4 Nd7 8.Ng3 Qxc3 9.Ne2 Qxa3
10.Qh7+ Kf8 11.Qh8+ Ke7 12.Qh7 Kd8
13.Qxf7 Nf8 (8.126.542) 523

13/24 0:17 -1.53 6...gxf6 7.Ne4 Nd7 8.Ng3 Qxc3 9.Re3 Qa1+
10.Kf2 Nf8 11.Qxf6 e5 12.Nh5 Ne6
13.Qh6 Ng7 14.Nxg7 Qd1 15.Nh5 (9.053.812) 526

14/25 0:21 -1.65-- 6...gxf6 7.Ne4 Nd7 8.Ng3 Qxc3 9.Nf5 Nf8
10.Re3 Ng6 11.Qxf6 exf5 12.Rxe8+ Kh7
13.Qxf7+ Kh6 14.Kf2 Qxa3 15.g4 Nh4
16.gxf5 (11.434.029) 532

14/25 0:22 -1.77 6...gxf6 7.Ne4 Nd7 8.Ng3 Qxc3 9.Re3 Qa1+
10.Kf2 Qd1 11.Qh7+ Kf8 12.Nf5 Qd2+
13.Kg3 Qxe3+ 14.Nxe3 Ke7 15.Qf5 (12.044.249) 536

15/28 0:38 -1.88 6...gxf6 7.Ne4 Nh7 8.Re3 e5 9.Rg3+ Kf8
10.Qxh7 Ke7 11.Nxf6 Kxf6 12.Rf3+ Ke6
13.Bb5 Kd6 14.Bxe8 Be6 15.dxe5+ Kxe5
16.Qh8+ Kd6 17.Kf1 Ke7 18.Qh4+ f6 (20.562.738) 537

16/28 0:46 -2.01-- 6...gxf6 7.Ne4 Nh7 8.Re3 e5 9.Rg3+ Kf8
10.Qxh7 Ke7 11.Nxf6 Kxf6 12.Rf3+ Ke6
13.Bb5 Kd6 14.Bxe8 e4 15.Rxf7 Qxc3
16.Qxe4 (25.317.347) 539

16/28 0:50 -2.13-- 6...gxf6 7.Ne4 Nh7 8.Re3 e5 9.Rg3+ Kf8
10.Qxh7 Ke7 11.Nxf6 Be6 12.Qh4 a6
13.Nxe8+ Kxe8 14.Rg7 Ba2 15.dxe5 Bd5 (27.183.141) 541

16/28 0:55 -2.31-- 6...gxf6 7.Ne4 Nh7 8.Re3 e5 9.Rg3+ Kf8
10.Qxh7 Ke7 11.Nxf6 Kxf6 12.Rf3+ Ke6
13.Bb5 Qe7 14.dxe5 Kxe5 15.Bxe8 Qc5+
16.Kf1 Qxa3 17.Bxf7 Qa6+ 18.Kf2 Be6
19.Bxe6 (29.964.837) 542

16/28 0:58 -2.58-- 6...gxf6 7.Ne4 Nh7 8.Re3 e5 9.Rg3+ Kf8
10.Qxh7 Ke7 11.Nxf6 Kxf6 12.Rf3+ Ke6
13.Bb5 Qe7 14.dxe5 Qc5+ 15.Rf2 Qxf2+
16.Kxf2 Re7 17.Be2 Kd5 18.Qd3+ Kc5
19.h4 (31.948.663) 544

16/28 1:12 -2.98 6...gxf6 7.Ne4 Qd8 8.Nxf6+ Kg7 9.Rf1 Qe7
10.Nxe8+ Qxe8 11.Qg5+ Ng6 12.Qf6+ Kg8
13.Bxg6 fxg6 14.Rf4 e5 15.Rh4 Bg4
16.Rxg4 Rb8 (40.171.506) 555

17/28 1:21 -3.07 6...gxf6 7.Ne4 Nh7 8.Re3 e5 9.Rg3+ Kf8
10.Qxh7 Ke7 11.Nxf6 Kxf6 12.Rf3+ Kg5
13.Rxf7 Bd7 14.Rxd7 Qa5 15.Rxb7 exd4
16.h4+ Kf6 (45.630.850) 562

18/31 2:27 -3.18 6...gxf6 7.Ne4 Qe7 8.Nxf6+ Kg7
9.Nxe8+ Qxe8 10.Qg5+ Ng6 11.Bxg6 fxg6
12.Re4 Kf7 13.Rf4+ Kg8 14.Qf6 e5
15.Rh4 Bg4 16.Rxg4 e4 17.Rxg6+ Kh7
18.Rg3 e3 (82.831.195) 560

19/31 2:52 -3.28 6...gxf6 7.Ne4 Nh7 8.Re3 e5 9.Rg3+ Kf8
10.Qxh7 Ke7 11.Nf2 (97.084.226) 563

20/32 3:45 -3.39 6...gxf6 7.Ne4 Qe7 8.Nxf6+ Kg7
9.Nxe8+ Qxe8 10.Qg5+ Ng6 11.Rf1 Bd7 (128.039.296) 566

21/36 6:26 -3.27++ 6...gxf6 7.Ne4 Nh7 8.Re3 e5 9.Rg3+ Kf8
10.Qxh7 Qb6 11.Qh8+ Ke7 12.Qh4 exd4
13.Nd6 dxc3+ 14.Qd4 Qxd4+ 15.Kf1 Qxd3+
16.Rxd3 Kd8 17.Nc4+ Ke7 18.Nd6 (221.950.245) 573

21/36 7:08 -3.15++ 6...gxf6 7.Be4 fxg5 8.Bh7+ Nxh7
9.Qh3 Qxh2+ 10.Qxh2 Kh8 (247.151.910) 577

21/36 7:52 -2.97++ 6...gxf6 7.Rf1 Qe7 8.Nxe6 fxe6
9.Qg4+ Qg7 10.Bh7+ Nxh7 11.Qf3 Qxg2+
12.Kxg2 (273.274.507) 578

21/36 8:13 -3.52-- 6...gxf6 7.Ne4 Nh7 8.Re3 e5 9.Rg3+ Kf8
10.Qxh7 Qb6 11.Rf3 Be6 12.Qh6+ Ke7
13.Qxf6+ Kd7 14.Qxe5 Qb1+ 15.Rf1 Qb2
16.Nf6+ Ke7 (284.918.139) 577

best move: g7xf6 time: 8:28.687 min n/s: 577.818 nodes: 284.918.139

And this is some possible code for razoring with reductions instead of dropping in qsearch everywhere

Code: Select all

    // Step 6. Razoring (is omitted in PV nodes)
    if (   !PvNode
        &&  depth < RazorDepth
        && !inCheck
        &&  refinedValue + razor_margin(depth) < beta
        &&  !(tte && tte->type() == VALUE_TYPE_UPPER) //[condition was: ttMove == MOVE_NONE]
        &&  abs(beta) < VALUE_MATE_IN_PLY_MAX
        && !pos.has_pawn_on_7th(pos.side_to_move()))
    {
        Value rbeta = beta - razor_margin(depth);
        Depth rDepth = std::max(depth - ONE_PLY, DEPTH_ZERO);
        if (tte && can_return_tt(tte, rDepth, rbeta, ss->ply))
        {
            TT.refresh(tte);
            ss->bestMove = ttMove; // Can be MOVE_NONE
            value = value_from_tt(tte->value(), ss->ply);
        }
        else value = rDepth < ONE_PLY ? qsearch<NonPV>(pos, ss, rbeta-1, rbeta, DEPTH_ZERO) : search<NonPV>(pos, ss, rbeta-1, rbeta, rDepth);
        if (value < rbeta)
            // Logically [maybe] we should return (value + razor_margin(depth)), but
            // surprisingly this did slightly weaker in tests [with Stockfish].
            return value;
    }
Especially in the lower plies this does not seem so bad:


r1b1rnk1/ppq2pp1/4pR2/6N1/3P3Q/P1PB4/2P3PP/4R1K1 b - -

Engine: Stockfish Kingdefender (Rainbow Serpent, Build 161, Athlon 2009 MHz, 256 MB, using Razoring with reductions) by Tord Romstad, Marco Costalba and Joona Kiiski

1/01 0:00 +1.61 6...gxf6 (125) 0

2/02 0:00 +1.37 6...gxf6 7.Qh5 e5 8.g4 (4.152) 16

3/03 0:00 -0.88 6...gxf6 7.Ne4 Nd7 8.Nd2 (29.629) 99

4/05 0:00 -0.92 6...gxf6 7.Ne4 Nd7 8.Bb5 Kg7 9.Bxd7 Bxd7 (41.333) 132

5/05 0:00 -0.32 6...gxf6 7.Ne4 Nd7 8.Qh5 f5 9.Ng5 Nf6 (54.097) 164

6/06 0:00 -0.36 6...gxf6 7.Ne4 Nd7 8.Ng3 b5 9.Nf5 Qb8 (63.594) 184

7/13 0:00 -0.17 6...gxf6 7.Ne4 Nd7 8.Ng3 Qxc3 9.Nf5 Nf8
10.Nh6+ Kg7 11.Nf5+ exf5 12.Rxe8 Qxa3 (163.546) 316

8/17 0:01 -0.12 6...gxf6 7.Ne4 Nd7 8.Qh6 Rd8 9.Ng5 a5 (640.368) 512

9/17 0:01 -0.19 6...gxf6 7.Ne4 Nd7 8.Qh6 b6 9.Ng5 Nf8
10.Bh7+ Nxh7 11.Nxh7 Qe7 12.Nxf6+ Qxf6
13.Qxf6 Bb7 (1.054.640) 548

10/17 0:02 -0.31-- 6...gxf6 7.Ne4 Nd7 8.Ng3 Kg7 9.Nf5+ Kg8
10.Qg4+ Kf8 11.Qh5 a5 12.Nh6 (1.263.829) 557

10/23 0:02 -0.40 6...gxf6 7.Ne4 Nd7 8.Qh6 f5 9.Ng5 Nf8
10.Bxf5 Rd8 11.Ne4 Qc4 (1.468.463) 576

11/23 0:06 -0.52-- 6...gxf6 7.Ne4 Nd7 8.Qh6 f5 9.Re3 fxe4
10.Rh3 b6 11.Bxe4 Re7 12.Bxa8 a6 (3.970.485) 600

11/23 0:06 -0.64-- 6...gxf6 7.Ne4 Nd7 8.Qh6 f5 9.Re3 fxe4
10.Rh3 b6 11.Bxe4 Re7 12.Bxa8 Nf6 (3.987.786) 600

11/23 0:06 -0.83-- 6...gxf6 7.Ne4 Nd7 8.Qh6 f5 9.Re3 fxe4
10.Rh3 b6 11.Bxe4 Re7 12.Bxa8 f6
13.Qh8+ (4.012.172) 598

11/23 0:06 -1.11-- 6...gxf6 7.Ne4 Nd7 8.Qh6 f5 9.Re3 fxe4
10.Rh3 b6 11.Bxe4 Re7 12.Bxa8 Qb7 (4.031.040) 598

11/23 0:06 -1.51-- 6...gxf6 7.Ne4 Nd7 8.Qh6 f5 9.Re3 fxe4
10.Rh3 b6 11.Bxe4 Re7 12.Bxa8 f5
13.Rg3+ Kf7 14.Qg6+ Kf8 15.Qh6+ Ke8
16.Rg8+ (4.060.012) 598

11/23 0:08 -2.11 6...gxf6 7.Ne4 Nh7 8.Re3 f5 9.Rg3+ Kf8
10.Qxh7 fxe4 11.Rg8+ Ke7 12.Qh4+ Kd7
13.Bb5+ Kd6 14.Bxe8 e5 (4.811.646) 595

12/23 0:09 -2.23-- 6...gxf6 7.Ne4 Nh7 8.Re3 f5 9.Rg3+ Kf8
10.Qxh7 fxe4 11.Rg8+ Ke7 12.Qh4+ Kd6
13.Qg3+ e5 14.Rxe8 Kd5 15.Rxe5+ Qxe5
16.dxe5 (5.631.306) 591

12/23 0:09 -2.35-- 6...gxf6 7.Ne4 Nh7 8.Re3 f5 9.Rg3+ Kf8
10.Qxh7 fxe4 11.Rg8+ Ke7 12.Qh4+ Kd6
13.Qg3+ e5 14.Rxe8 Kd7 15.Rxe5 exd3
16.Qh4 Kc6 17.cxd3 Kd7 (5.820.239) 592

12/23 0:13 -2.34 6...gxf6 7.Ne4 Nd7 8.Qh6 f5 9.Re3 Rd8
10.Rg3+ Qxg3 11.Nxg3 e5 12.dxe5 Nxe5
13.Qg5+ (7.702.944) 590

13/23 0:14 -2.45 6...gxf6 7.Ne4 Nh7 8.Re3 f5 9.Rg3+ Kf8
10.Qxh7 fxe4 11.Rg8+ Ke7 12.Qh4+ Kd6
13.Qg3+ Ke7 14.Qxc7+ Kf6 (8.737.305) 589

14/23 0:21 -2.54 6...gxf6 7.Ne4 Nh7 8.Re3 e5 9.Rg3+ Kf8
10.Qxh7 Ke7 11.Nxf6 Kxf6 12.Rf3+ Ke6
13.Bb5 Rd8 14.Rxf7 Qxf7 15.Bc4+ Kd6
16.Qxf7 Rb8 (12.781.362) 594

15/27 1:09 -2.42++ 6...gxf6 7.Ne4 Nh7 8.Re3 e5 9.Rg3+ Kf8
10.Qxh7 Qb6 11.Rg7 Be6 12.Kf1 exd4
13.cxd4 Qxd4 14.Qh8+ Ke7 15.Rg8 Rxg8
16.Qh6 Qxd3+ 17.cxd3 Kd7 (41.441.777) 599

15/27 1:11 -2.30++ 6...gxf6 7.Ne4 Nh7 8.Re3 e5 9.Rg3+ Kf8
10.Qxh7 Ke7 11.Nxf6 Kxf6 12.Rf3+ Ke6
13.Bb5 Qe7 14.Rxf7 Qxa3 15.Bxe8 Qxc3 (42.850.086) 602

15/27 1:13 -2.12++ 6...gxf6 7.Kf1 fxg5 8.Qxg5+ Kh8
9.Qf6+ Kg8 10.Rxe6 (44.671.673) 605

15/27 1:18 -2.66 6...gxf6 7.Ne4 Qe7 8.Nxf6+ Kg7 9.Rf1 e5
10.Nh5+ Kh8 11.Rf6 Nh7 12.Bxh7 exd4
13.Be4 Rb8 14.Nf4+ Kg7 15.Qh6+ Kg8
16.Qh7+ Kf8 17.Ng6+ (47.667.946) 605

16/27 1:39 -2.54 6...gxf6 7.Ne4 Nh7 8.Re3 e5 9.Rg3+ Kf8
10.Qxh7 Qb6 11.Qh5 exd4 12.Qh6+ Ke7
13.Bb5 dxc3+ 14.Re3 Bd7 15.Nxc3+ Kd8
16.Bxd7 Rxe3 17.h4 Kxd7 18.Qxe3 (60.772.099) 611

17/30 2:30 -2.66-- 6...gxf6 7.Ne4 Nh7 8.Re3 e5 9.Qh6 Bg4
10.Rg3 f5 11.Nd6 exd4 12.Nxf5 Re1+
13.Kf2 Re2+ 14.Bxe2 Re8 15.Bxg4 Qc5 (90.911.435) 605

17/30 2:46 -2.78-- 6...gxf6 7.Ne4 Nh7 8.Re3 e5 9.Qh6 Bg4
10.Rg3 f5 11.Nd6 exd4 12.Nxf5 Re1+
13.Kf2 Re2+ 14.Bxe2 dxc3 15.Bxg4 Qb6+
16.Qxb6 axb6 17.Rxc3 Rxa3 (101.217.821) 607

17/30 2:48 -2.96-- 6...gxf6 7.Ne4 Nh7 8.Re3 e5 9.Qh6 Bg4
10.Rg3 f5 11.Nd6 exd4 12.Nxf5 Re1+
13.Kf2 Re2+ 14.Bxe2 dxc3 15.Bxg4 Qc5+
16.Re3 Qf8 17.Qxf8+ Kxf8 (102.365.448) 608

17/30 3:08 -3.21 6...gxf6 7.Ne4 Qe7 8.Nxf6+ Kg7
9.Nxe8+ Qxe8 10.Qg5+ Ng6 11.Re3 Qe7
12.Qxe7 Nxe7 13.h4 Ng6 14.Bxg6 fxg6
15.Kf2 (114.062.934) 605

18/30 3:42 -3.32 6...gxf6 7.Ne4 Nh7 8.Re3 e5 9.Qh6 Bg4
10.Rg3 Kh8 11.Rxg4 Qc8 12.Nxf6 Re6
13.Bxh7 Qg8 (135.065.443) 607

19/31 4:22 -3.44 6...gxf6 7.Ne4 Qe7 8.Nxf6+ Kg7
9.Qg5+ Ng6 10.Nxe8+ Qxe8 11.Bxg6 fxg6
12.Re4 Kh8 13.Rf4 b6 14.Rh4+ Kg8
15.Qe5 Bb7 16.Rh8+ Kf7 17.Rxe8 Rxe8
18.Qf4+ Ke7 19.c4 Kd7 (159.371.994) 606

20/31 5:30 -3.55 6...gxf6 7.Ne4 Qd8 8.Nxf6+ Kg7 9.Rf1 e5
10.Qg5+ Kh8 11.Qh6+ (201.040.730) 608

21/36 6:34 -3.67-- 6...gxf6 7.Ne4 Qd8 8.Nxf6+ Kg7 9.Rf1 Qxf6
10.Rxf6 Re7 11.Rf1 f6 12.Rxf6 (241.254.665) 612

21/36 7:49 -3.79 6...gxf6 7.Ne4 Qe7 8.Nxf6+ Kg7 9.Rf1 e5
10.Nh5+ Kh8 11.Rf6 Nh7 12.Bxh7 Qxa3
13.Rf1 Qa6 14.Ng3 Kg7 15.Qg5+ Kh8
16.Rxf7 Bh3 17.gxh3 Qa1+ 18.Kg2 exd4 (287.869.437) 612

22/36 12:09 -3.67++ 6...gxf6 7.Ne4 Nh7 8.Qxh7+ Kxh7
9.Nd6+ Kh8 10.Rxe6 fxe6 11.Nxe8 Qxh2+ (449.576.942) 616

22/36 12:22 -3.55++ 6...gxf6 7.Ne4 Nh7 8.Qxh7+ Kxh7
9.Nd2+ Kh8 10.Bb5 Qxh2+ 11.Kxh2 Rf8
12.Ne4 (458.042.151) 617

22/36 12:58 -3.36++ 6...gxf6 7.Ne4 Nh7 8.Qxh7+ Kxh7
9.Nd2+ Kh8 10.Bb5 Qxh2+ 11.Kxh2 Rf8
12.Ne4 f5 13.Nf6 Kg7 14.Kg3 Kxf6
15.Kf4 Ke7 16.Ke5 (480.996.902) 617

22/36 13:30 -3.09++ 6...gxf6 7.Nxf7 Qxf7 8.Qg4+ Qg7
9.Bh7+ Nxh7 10.Qg3 Qxg3 11.hxg3 Kf8
12.Kh2 Ke7 (501.211.630) 618

22/36 13:57 -2.68++ 6...gxf6 7.Be4 fxg5 8.Bh7+ Nxh7
9.Qe4 Qxh2+ 10.Kxh2 (518.303.518) 618

22/39 21:57 -3.91-- 6...gxf6 7.Ne4 Nh7 8.Re3 e5 9.Qh6 Bg4
10.Rg3 f5 11.Nd6 Nf6 12.Nxe8 Nxe8
13.Bxf5 Qxc3 14.Rxg4+ Ng7 15.Rxg7+ Kf8
16.Qh8+ Ke7 17.Qh4+ Kd6 18.dxe5+ Kc7 (791.153.166) 600

22/40 28:17 -4.81 6...gxf6 7.Ne4 Qe7 8.Nxf6+ Kg7 9.Rf1 a5
10.Bb5 Kg6 (1.020.400.679) 601

23/40 32:06 -4.93 6...gxf6 7.Ne4 Nh7 8.Re3 e5 9.Qh6 Bf5
10.Nxf6+ Nxf6 11.Rg3+ Bg6 12.Bxg6 Qxc3 (1.161.975.522) 603

24/40 35:46 -5.05 6...gxf6 7.Ne4 Qd8 8.Nxf6+ (1.298.721.848) 605

25/40 45:21 -4.92++ 6...gxf6 7.Ne4 Nh7 8.Qg4+ Kh8 9.Qh4 Kg7
10.Re3 Rh8 11.Rh3 Kf8 12.Qh6+ (1.650.252.445) 606

25/40 49:17 -4.80++ 6...gxf6 7.Ne4 Nh7 8.Nxf6+ Nxf6
9.Qxf6 e5 10.Qh8+ Kxh8 11.Kf2 exd4
12.Rxe8+ Kg7 (1.797.378.378) 607

25/40 52:02 -4.62++ 6...gxf6 7.Ne4 Nh7 8.Nxf6+ Nxf6
9.Qh7+ Nxh7 (1.906.415.796) 610

25/40 54:22 -4.35++ 6...gxf6 7.Ne4 Nh7 8.Nxf6+ Nxf6
9.Qxf6 e5 10.Bh7+ Kf8 11.Qg5 Qxc3
12.Qxe5 (1.994.874.850) 611

25/40 56:31 -3.94++ 6...gxf6 7.a4 fxg5 8.Rxe6 (2.075.797.802) 612

25/41 68:05 -5.17-- 6...gxf6 7.Ne4 Nh7 8.Re3 e5 9.Qh6 Bg4
10.Rg3 f5 11.Nd6 Nf8 12.Nxf5 Ne6
13.Rxg4+ Ng7 14.Nxg7 Kf8 15.Nxe8+ (2.480.355.822) 607

25/45 121:45 -6.07 6...gxf6 7.Ne4 Nd7 8.Qh6 f5 9.Re3 f6
10.Rg3+ Qxg3 11.Nxg3 e5 12.Nxf5 Nb8 (4.407.811.635) 603

best move: g7xf6 time: 128:52.625 min n/s: 603.321 nodes: 4.407.811.635

However for Stockfish it may look different and may depend on many things, storing bestMoves in some ALL nodes is just one obvious case influencing razoring in the default case as well because for Rainbow Serpent I think this decision switched off razoring, unless in the node there is no TT entry found or only a static eval one.

Eelco
Debugging is twice as hard as writing the code in the first
place. Therefore, if you write the code as cleverly as possible, you
are, by definition, not smart enough to debug it.
-- Brian W. Kernighan
User avatar
Eelco de Groot
Posts: 4702
Joined: Sun Mar 12, 2006 2:40 am
Full name:   Eelco de Groot

Re: Razoring

Post by Eelco de Groot »

Eelco de Groot wrote:

Code: Select all

    // Step 6. Razoring (is omitted in PV nodes)
    if (   !PvNode
        &&  depth < RazorDepth
        && !inCheck
        &&  refinedValue + razor_margin(depth) < beta
        &&  !(tte && tte->type() == VALUE_TYPE_UPPER) //[condition was: ttMove == MOVE_NONE]
I intended to use

Code: Select all

&&  !(tte && tte->type() == VALUE_TYPE_LOWER) //[condition was: ttMove == MOVE_NONE]
here to exclude CUT nodes, but that is not what was tested.

Eelco

Code: Select all

 
        &&  abs(beta) < VALUE_MATE_IN_PLY_MAX
        && !pos.has_pawn_on_7th(pos.side_to_move()))
    {
        Value rbeta = beta - razor_margin(depth);
        Depth rDepth = std::max(depth - ONE_PLY, DEPTH_ZERO);
        if (tte && can_return_tt(tte, rDepth, rbeta, ss->ply))
        {
            TT.refresh(tte);
            ss->bestMove = ttMove; // Can be MOVE_NONE
            value = value_from_tt(tte->value(), ss->ply);
        }
        else value = rDepth < ONE_PLY ? qsearch<NonPV>(pos, ss, rbeta-1, rbeta, DEPTH_ZERO) : search<NonPV>(pos, ss, rbeta-1, rbeta, rDepth);
        if (value < rbeta)
            // Logically [maybe] we should return (value + razor_margin(depth)), but
            // surprisingly this did slightly weaker in tests [with Stockfish].
            return value;
    }
Debugging is twice as hard as writing the code in the first
place. Therefore, if you write the code as cleverly as possible, you
are, by definition, not smart enough to debug it.
-- Brian W. Kernighan
mcostalba
Posts: 2684
Joined: Sat Jun 14, 2008 9:17 pm

Re: Razoring

Post by mcostalba »

Eelco de Groot wrote: If Marco wants to try reducing instead of pruning maybe for Stockfish too the following code is not so much worse than the default;
Hi Eelco,

actually I have tested this idea (that was suggested to me by Lucas Braesch) just few days ago.

I tested first the original idea of Lucas:

Code: Select all

// Step 6. Razoring (is omitted in PV nodes)
    if (   !PvNode
        &&  depth < RazorDepth
        && !inCheck
        &&  refinedValue + razor_margin(depth) < beta
        &&  ttMove == MOVE_NONE
        &&  abs(beta) < VALUE_MATE_IN_PLY_MAX
        && !pos.has_pawn_on_7th(pos.side_to_move()))
    {
        Value rbeta = beta - razor_margin(depth);
        Value v = qsearch<NonPV>(pos, ss, rbeta-1, rbeta, DEPTH_ZERO);

        if (v < rbeta && (depth -= ONE_PLY) < 2 * ONE_PLY)
            // Logically we should return (v + razor_margin(depth)), but
            // surprisingly this did slightly weaker in tests.
            return v;
    }
then an extension I figured out, namely keep razoring in qsearch at low depths but reduce of one ply if razor test pass at high depths (note that there is no more the condition on RazorDepth):

Code: Select all

    // Step 6. Razoring (is omitted in PV nodes)
    if (   !PvNode
        && !inCheck
        &&  refinedValue + razor_margin(depth) < beta
        &&  ttMove == MOVE_NONE
        &&  abs(beta) < VALUE_MATE_IN_PLY_MAX
        && !pos.has_pawn_on_7th(pos.side_to_move()))
    {
        Value rbeta = beta - razor_margin(depth);
        Value v = qsearch<NonPV>(pos, ss, rbeta-1, rbeta, DEPTH_ZERO);

        if (v < rbeta && (depth -= ONE_PLY) < RazorDepth)
            // Logically we should return (v + razor_margin(depth)), but
            // surprisingly this did slightly weaker in tests.
            return v;
    }
Both ideas failed to give a measurable gain, although there was no regression actually, so maybe there is something good there, but my test didn't show they were clearly better.


Regarding your idea to use condition

Code: Select all

!(tte && tte->type() == VALUE_TYPE_LOWER)
instead of testing for a ttMove, well I am not very convinced because if there is a ttMove it means that this node _was_ a cut-off node somewhere in the past, so perhaps there is still some danger in the position and the condition on ttMove seems so a bit more safer.

Finally a stylistic note on

Code: Select all

        if (tte && can_return_tt(tte, rDepth, rbeta, ss->ply))
        {
            TT.refresh(tte);
            ss->bestMove = ttMove; // Can be MOVE_NONE
            value = value_from_tt(tte->value(), ss->ply);
        }
        else value = rDepth < ONE_PLY ? qsearch<NonPV>(pos, ss, rbeta-1, rbeta, DEPTH_ZERO) : search<NonPV>(pos, ss, rbeta-1, rbeta, rDepth); 
This is redundant code IMHO because the same check on TT is done as first thing in the qsearch() call, so you just duplicate that part of code for no real gain.
mcostalba
Posts: 2684
Joined: Sat Jun 14, 2008 9:17 pm

Re: Razoring

Post by mcostalba »

bob wrote: Wrong way of looking at it.
Please try to focus on the fact that we are talking of pruning at node level, not pruning of the single move.....and no, it is not the same thing.
lucasart
Posts: 3243
Joined: Mon May 31, 2010 1:29 pm
Full name: lucasart

Re: Razoring

Post by lucasart »

mcostalba wrote:
Eelco de Groot wrote: I tested first the original idea of Lucas:

Code: Select all

// Step 6. Razoring (is omitted in PV nodes)
    if (   !PvNode
        &&  depth < RazorDepth
        && !inCheck
        &&  refinedValue + razor_margin(depth) < beta
        &&  ttMove == MOVE_NONE
        &&  abs(beta) < VALUE_MATE_IN_PLY_MAX
        && !pos.has_pawn_on_7th(pos.side_to_move()))
    {
        Value rbeta = beta - razor_margin(depth);
        Value v = qsearch<NonPV>(pos, ss, rbeta-1, rbeta, DEPTH_ZERO);

        if (v < rbeta && (depth -= ONE_PLY) < 2 * ONE_PLY)
            // Logically we should return (v + razor_margin(depth)), but
            // surprisingly this did slightly weaker in tests.
            return v;
    }
then an extension I figured out, namely keep razoring in qsearch at low depths but reduce of one ply if razor test pass at high depths (note that there is no more the condition on RazorDepth):

Code: Select all

    // Step 6. Razoring (is omitted in PV nodes)
    if (   !PvNode
        && !inCheck
        &&  refinedValue + razor_margin(depth) < beta
        &&  ttMove == MOVE_NONE
        &&  abs(beta) < VALUE_MATE_IN_PLY_MAX
        && !pos.has_pawn_on_7th(pos.side_to_move()))
    {
        Value rbeta = beta - razor_margin(depth);
        Value v = qsearch<NonPV>(pos, ss, rbeta-1, rbeta, DEPTH_ZERO);

        if (v < rbeta && (depth -= ONE_PLY) < RazorDepth)
            // Logically we should return (v + razor_margin(depth)), but
            // surprisingly this did slightly weaker in tests.
            return v;
    }
Both ideas failed to give a measurable gain, although there was no regression actually, so maybe there is something good there, but my test didn't show they were clearly better.
No improvement & no regression is not all bad. It could be a good idea to add it anyway: if the elo gain is zero but StockFish becomes seemingly better in tactical test suites.

Also if the "safe" razoring (ie reduce i/o prune and only prune at shallow depth) is equal in terms of elo, perhaps a safer razoring would allow you to become more aggressive on the razor margin ? or even slightly increase the razor depth ?
lucasart
Posts: 3243
Joined: Mon May 31, 2010 1:29 pm
Full name: lucasart

Re: Razoring

Post by lucasart »

lucasart wrote:
mcostalba wrote:
Eelco de Groot wrote: I tested first the original idea of Lucas:

Code: Select all

// Step 6. Razoring (is omitted in PV nodes)
    if (   !PvNode
        &&  depth < RazorDepth
        && !inCheck
        &&  refinedValue + razor_margin(depth) < beta
        &&  ttMove == MOVE_NONE
        &&  abs(beta) < VALUE_MATE_IN_PLY_MAX
        && !pos.has_pawn_on_7th(pos.side_to_move()))
    {
        Value rbeta = beta - razor_margin(depth);
        Value v = qsearch<NonPV>(pos, ss, rbeta-1, rbeta, DEPTH_ZERO);

        if (v < rbeta && (depth -= ONE_PLY) < 2 * ONE_PLY)
            // Logically we should return (v + razor_margin(depth)), but
            // surprisingly this did slightly weaker in tests.
            return v;
    }
then an extension I figured out, namely keep razoring in qsearch at low depths but reduce of one ply if razor test pass at high depths (note that there is no more the condition on RazorDepth):

Code: Select all

    // Step 6. Razoring (is omitted in PV nodes)
    if (   !PvNode
        && !inCheck
        &&  refinedValue + razor_margin(depth) < beta
        &&  ttMove == MOVE_NONE
        &&  abs(beta) < VALUE_MATE_IN_PLY_MAX
        && !pos.has_pawn_on_7th(pos.side_to_move()))
    {
        Value rbeta = beta - razor_margin(depth);
        Value v = qsearch<NonPV>(pos, ss, rbeta-1, rbeta, DEPTH_ZERO);

        if (v < rbeta && (depth -= ONE_PLY) < RazorDepth)
            // Logically we should return (v + razor_margin(depth)), but
            // surprisingly this did slightly weaker in tests.
            return v;
    }
Both ideas failed to give a measurable gain, although there was no regression actually, so maybe there is something good there, but my test didn't show they were clearly better.
No improvement & no regression is not all bad. It could be a good idea to add it anyway: if the elo gain is zero but StockFish becomes seemingly better in tactical test suites.

Also if the "safe" razoring (ie reduce i/o prune and only prune at shallow depth) is equal in terms of elo, perhaps a safer razoring would allow you to become more aggressive on the razor margin ? or even slightly increase the razor depth ?
I currently use the following (somewhat unorthodox) razoring in DoubleCheck 2.3.1:

Code: Select all

const bool UseRazoring = true;
static const int RazorDepth = 3;
static int RazorMargin(int depth)
{
	assert(1 <= depth && depth <= RazorDepth);
	return 2*MP + (depth-1)*MP/4;
}

...

	// Razoring
	if (UseRazoring && depth <= RazorDepth
		&& !is_pv && !is_mate_score(beta) && !in_check)
	{
		if (current_eval + RazorMargin(depth) <= alpha) {
			const int score = qsearch(B, alpha, beta, 0, ply+1, is_pv, si+1);
			if (score <= alpha		// qsearch fails low
				&& --depth <= 0)	// reduce depth
				return score;		// prune if horizon reached
		}
	}
In other words, I check whether the qsearch fails low, with no margin below alpha. This proved in testeing to be better than qsearch + RazorMargin(depth) <= alpha... oddly enough.
I wonder if the above would work in StockFish:
- compare qsearch to alpha w/o razor margin considered
- reduce i/o prune
- prune only at very shallow depth
bob
Posts: 20943
Joined: Mon Feb 27, 2006 7:30 pm
Location: Birmingham, AL

Re: Razoring

Post by bob »

For the record, this is a dangerous way to test an idea. Real games matter, positions don't. I can't count the number of times I made this mistake in the past, thinking that if my "fix" made it play better in a position where it made a mistake prior to the fix, that this was "better". Turns out it is worse just as often as it is better, when using real games rather than the problematic position.