MinimalChess - how to move forward?

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

User avatar
lithander
Posts: 880
Joined: Sun Dec 27, 2020 2:40 am
Location: Bremen, Germany
Full name: Thomas Jahn

Re: MinimalChess - how to move forward?

Post by lithander »

My SEE implementation solved the 25 positions from Arasan but still it doesn't help as much as I hoped for so maybe there are still bugs. So I looked for more testcases and found Minic's test positions: https://github.com/tryingsomestuff/Mini ... ce/cli.cpp

And my SEE failed right the very first test in that list:

Code: Select all

posList.push_back( SEETest{"3r3k/3r4/2n1n3/8/3p4/2PR4/1B1Q4/3R3K w - - 0 1", ToMove(Sq_d3,Sq_d4, T_capture), ScoreType(P - R + N - P )});
[d]3r3k/3r4/2n1n3/8/3p4/2PR4/1B1Q4/3R3K w - - 0

The expected result is P - R + N - P which would be -200 cp with standard material values.

My see doesn't stop after blacks NxP which would return the expected -200 cp. Instead it suggests playing BxN. Now all pieces get captured except for the last white rook. This means white has lost 1800 cp and black has lost 1700 cp so it's still losing for white (-100) but better than the shorter sequence given by the test. I looked 2 hours for the bug in my code and finally even computed it on paper and now I think the test gives the wrong result, maybe? To both my SEE implementation and me it this seems like the correct sequence is:

[pgn][Variant "From Position"]
[FEN "3r3k/3r4/2n1n3/8/3p4/2PR4/1B1Q4/3R3K w - - 0 1"]

1. Rxd4 Nexd4 2. cxd4 Nxd4 3. Bxd4+ Rxd4 4. Qxd4+ Rxd4 5. Rxd4[/pgn]
Minimal Chess (simple, open source, C#) - Youtube & Github
Leorik (competitive, in active development, C#) - Github & Lichess
Jakob Progsch
Posts: 40
Joined: Fri Apr 16, 2021 4:44 pm
Full name: Jakob Progsch

Re: MinimalChess - how to move forward?

Post by Jakob Progsch »

Do you value the queen at 900 or 1000cp?

I use 1000cp and get -200 both as the final see value as well as for the full exchange.
User avatar
lithander
Posts: 880
Joined: Sun Dec 27, 2020 2:40 am
Location: Bremen, Germany
Full name: Thomas Jahn

Re: MinimalChess - how to move forward?

Post by lithander »

Jakob Progsch wrote: Fri Aug 06, 2021 3:27 am Do you value the queen at 900 or 1000cp?

I use 1000cp and get -200 both as the final see value as well as for the full exchange.
I use Pawn = 100, Knight and Bishop = 300, Rook = 500, Queen = 900. But that could explain the difference... I can't find where exactly minic defines the values it uses, though. :/
Minimal Chess (simple, open source, C#) - Youtube & Github
Leorik (competitive, in active development, C#) - Github & Lichess
User avatar
xr_a_y
Posts: 1871
Joined: Sat Nov 25, 2017 2:28 pm
Location: France

Re: MinimalChess - how to move forward?

Post by xr_a_y »

Hi, those positions are from Vajolet (https://github.com/elcabesa/vajolet/blo ... e-test.cpp) but I think I had to fix some of the results, not quite sure.

My current SEE is just a swaplist, I don't use a cutoff version anymore (see https://github.com/tryingsomestuff/Mini ... herSee.cpp)

Values in Minic are https://github.com/tryingsomestuff/Mini ... i.cpp#L274 and thus there https://github.com/tryingsomestuff/Mini ... nition.cpp

BTW, taking always using a lower piece value is

[pgn]
[FEN "3r3k/3r4/2n1n3/8/3p4/2PR4/1B1Q4/3R3K w - - 0"]

1. cxd4 Nexd4 2. Bxd4+ Nxd4 3. Rxd4 Rxd4 4. Qxd4+ Rxd4 5. Rxd4
[/pgn]
amanjpro
Posts: 883
Joined: Sat Mar 13, 2021 1:47 am
Full name: Amanj Sherwany

Re: MinimalChess - how to move forward?

Post by amanjpro »

xr_a_y wrote: Fri Aug 06, 2021 8:54 am Hi, those positions are from Vajolet (https://github.com/elcabesa/vajolet/blo ... e-test.cpp) but I think I had to fix some of the results, not quite sure.

My current SEE is just a swaplist, I don't use a cutoff version anymore (see https://github.com/tryingsomestuff/Mini ... herSee.cpp)

Values in Minic are https://github.com/tryingsomestuff/Mini ... i.cpp#L274 and thus there https://github.com/tryingsomestuff/Mini ... nition.cpp

BTW, taking always using a lower piece value is

[pgn]
[FEN "3r3k/3r4/2n1n3/8/3p4/2PR4/1B1Q4/3R3K w - - 0"]

1. cxd4 Nexd4 2. Bxd4+ Nxd4 3. Rxd4 Rxd4 4. Qxd4+ Rxd4 5. Rxd4
[/pgn]
Your test case, tests SEE if you start taking by R not P

posList.push_back( SEETest{"3r3k/3r4/2n1n3/8/3p4/2PR4/1B1Q4/3R3K w - - 0 1", ToMove(Sq_d3,Sq_d4, T_capture), ScoreType(P - R + N - P )});

Unless I am reading it wrong
User avatar
xr_a_y
Posts: 1871
Joined: Sat Nov 25, 2017 2:28 pm
Location: France

Re: MinimalChess - how to move forward?

Post by xr_a_y »

amanjpro wrote: Fri Aug 06, 2021 3:19 pm
xr_a_y wrote: Fri Aug 06, 2021 8:54 am Hi, those positions are from Vajolet (https://github.com/elcabesa/vajolet/blo ... e-test.cpp) but I think I had to fix some of the results, not quite sure.

My current SEE is just a swaplist, I don't use a cutoff version anymore (see https://github.com/tryingsomestuff/Mini ... herSee.cpp)

Values in Minic are https://github.com/tryingsomestuff/Mini ... i.cpp#L274 and thus there https://github.com/tryingsomestuff/Mini ... nition.cpp

BTW, taking always using a lower piece value is

[pgn]
[FEN "3r3k/3r4/2n1n3/8/3p4/2PR4/1B1Q4/3R3K w - - 0"]

1. cxd4 Nexd4 2. Bxd4+ Nxd4 3. Rxd4 Rxd4 4. Qxd4+ Rxd4 5. Rxd4
[/pgn]
Your test case, tests SEE if you start taking by R not P

posList.push_back( SEETest{"3r3k/3r4/2n1n3/8/3p4/2PR4/1B1Q4/3R3K w - - 0 1", ToMove(Sq_d3,Sq_d4, T_capture), ScoreType(P - R + N - P )});

Unless I am reading it wrong
You are right ! I missed that.
User avatar
lithander
Posts: 880
Joined: Sun Dec 27, 2020 2:40 am
Location: Bremen, Germany
Full name: Thomas Jahn

Re: MinimalChess - how to move forward?

Post by lithander »

mvanthoor wrote: Wed Aug 04, 2021 5:09 pm I'm convinced that, at some point, increasing speed is not going to be enough, because if the engine doesn't have an extensive evaluation, it will just find bad moves faster, or deeper.
[...]
Also, some features seem to only be useful when you can reach deep enough.
That seems to be a contradiction. Isn't raw speed the best way to reach the depths were features that have a fixed cost and a compound benefit over the following plies become benefitial? (e.g. SEE in move ordering)

Also, I really preferred the time when I was implementing "basic" features that could be validated by something as simple as a perft test! ;) To validate the benefit of pruning techniques statistically with a slow engine means you need to play a lot of games with rather high time control settings. It takes forever...
j.t. wrote: Wed Aug 04, 2021 12:16 am if standPat + position.see(move) + 150 < alpha:
skipThisMove()
if standPat + position.see(move) - 50 >= beta:
return standPat + position.see(move) - 50

I'm still exploring SEE and how to make use of it. Previously I was just skipping moves with negative SEE values in QSearch. But in the implementation you suggest you also skip "good" captures if they seem to have no hope to raise alpha. Or if they seem too good and fail high. But couldn't "two" of these captures together raise alpha and you miss these chances? Anyway, I implemented it and tested it:

Code: Select all

NO SEE (vanilla MMC)
Searched 200 positions to depth 8. 76863K nodes visited. Took 70.284 seconds!
Best move found in 175 / 200 positions!

SKIP BAD CAPTURES (SEE(move) < 0) IN QSEARCH
Searched 200 positions to depth 8. 38530K nodes visited. Took 51.888 seconds!
Best move found in 175 / 200 positions!

SKIP IF standPat + position.see(move) + 150 < alpha:
Searched 200 positions to depth 8. 29697K nodes visited. Took 49.159 seconds!
Best move found in 169 / 200 positions!
So less nodes had to be visited but MMC failed a few more tests now that both previous implementations solved.
Consequently, while it improved ELO over the vanilla version by 0-15 ELO depending on the opponent in the gauntlet at 5s + 0.5s inc, 4000 games each, it didn't do as good as the "skip bad captures" variant that scored 15-30 ELO over vanilla (no-SEE).

An additional advantage of the version that skips all bad captures independent of alpha and beta and stand pat is that I don't really need to know the specific SEE value. If the capturing piece less valuable than the victim I don't have to look any further: it's a good capture, regardless of the specific SEE value. And even if I have to look further I can break from exploring the exchange as soon as alpha and beta have the same sign.

amanjpro wrote: Wed Aug 04, 2021 12:17 am I meant to say eval - pawnvalue*depth >= beta.

And the reasoning is that, this position is so good for you, that your opponent won't play it
Isn't that what the nullMove pruning (which I have) already covers, though? Or is this in QSearch where MMC doesn't do any nullMove pruning?

Btw, I also tried a simple futility pruning scheme where I skip the QSearch if the static-eval of the non quiet position is signficantly below alpha.
On paper this looks really promising:

Code: Select all

[if eval - 200 < alpha skip QSearch]
Searched 200 positions to depth 11. 435155K nodes visited. Took 509.511 seconds!
Best move found in 195 / 200 positions!

[Vanilla MMC]
Searched 200 positions to depth 11. 744695K nodes visited. Took 1001.341 seconds!
Best move found in 194 / 200 positions!
For some reason it even solves one more position at depth 11 and it takes only half the time! I was quite excited hoping for a nice ELO boost for something so simple but in actual play this version did much worse... lost about 50 ELO instead of gaining anything. It really makes "trying stuff" difficult that there's no failsafe way to verify or falsify an improvement without actually playing thousands of games. :/
Minimal Chess (simple, open source, C#) - Youtube & Github
Leorik (competitive, in active development, C#) - Github & Lichess
amanjpro
Posts: 883
Joined: Sat Mar 13, 2021 1:47 am
Full name: Amanj Sherwany

Re: MinimalChess - how to move forward?

Post by amanjpro »

lithander wrote: Sat Aug 07, 2021 12:08 pm Isn't that what the nullMove pruning (which I have) already covers, though? Or is this in QSearch where MMC doesn't do any nullMove pruning?
You are right, it covers a susbest of the cases that null move pruning covers (and that is why some people call it static null move pruning). In fact, even futlity pruning (the one you tried in QS) is another usecase of the null move observation.

But, using reverse futility pruning (or static null move pruning) has the benefit, that you don't even need to search the position, you prune right away. Actually, I always try reverse futility pruning first, and only if it didn't prune, I try null-move pruning. The idea is simple, the position is too good to be reached in real play, so I just do not search it

https://github.com/amanjpro/zahak/blob/ ... #L214-L246
lithander wrote: Sat Aug 07, 2021 12:08 pm For some reason it even solves one more position at depth 11 and it takes only half the time! I was quite excited hoping for a nice ELO boost for something so simple but in actual play this version did much worse... lost about 50 ELO instead of gaining anything. It really makes "trying stuff" difficult that there's no failsafe way to verify or falsify an improvement without actually playing thousands of games. :/
Funny enough, I used to have a set of EPDs to test changes (before running a real match), and somehow Zahak 2.0.0 is doing better in some of them than Zahak 5.0 despite the 600 elo difference! I stopped caring about them as soon as I realized that my texel tuning made the engine perform worse, but it also gave the engine 200 elo boost :D
User avatar
lithander
Posts: 880
Joined: Sun Dec 27, 2020 2:40 am
Location: Bremen, Germany
Full name: Thomas Jahn

Re: MinimalChess - how to move forward?

Post by lithander »

I thought I might give an update here on what I tried and what stuck.

So I tried the idea first of not searching nodes further that are already hopeless below alpha. Close to the leafs it worked well and with increasing margins (e.g. futility_margin = 70 * depth) it also worked for higher depths. I wrote a version that would keep track of false-positives (my pruning would have skipped the move but actually it would have raised alpha) and realized that if I don't use this pruning when either side's king is in check I could reduce the rate of false-positives way below one percent. At that point it provided a nice speed increase that started to over-compensate for the loss of ELO through accuracy. I gained about 40 ELO.

Code: Select all

bool tactical = position.IsChecked(position.SideToMove) || child.IsChecked(child.SideToMove);

//some moves are hopeless and can be skipped without deeper evaluation
if (depth <= 4 && !tactical)
{
	int futilityMargin = (int)color * depth * MAX_GAIN_PER_PLY;
	if (window.FailLow(child.Score + futilityMargin, color))
		continue;
}
This was a first win. But at this time the cost of evaluating a position was becoming a hot path in the overall performance of the program. I was already updating the zobrist hash (used for the transposition table) in an iterative fashion and so it didn't involve much extra work to also update the evaluation iteratively. After all it's just based on PSTs. Implementing that didn't hurt the simplicity of the program but made it a litte faster. (+10 ELO)

Encouraged by the fact that pruning a few moves that had the potential to raise alpha didn't apparently hurt playing strength too much I looked into the portion of my code that does the PVS search. The idea was that for every node after the first I would assume that the move would probably fail low and search it with a tiny window around alpha.

Code: Select all

int score = EvalPositionTT(child, depth - 1, window.GetLowerBound(color));
if (window.FailLow(score, color))
	continue;
This was the original version which is really a lossless optimization but now I tried evaluating the child positions at reduced depth. And again I looked into the nature of false positives (positive = you can safely prune) and it turns out that it makes sense to also include moves involving check here. Then I realized that I was in the process of reinventing late move reductions because I was already skipping the PV move I was experimenting with skipping a few more moves before starting to reduce. If the reduced search around alpha doesn't fail low it's searched at full depth just like before.
Currently the code looks like this:

Code: Select all

if (depth >= 2)
{
	//non-tactical late moves are searched at a reduced depth to make this test even faster!
	int R = (tactical || expandedNodes < 4) ? 0 : 2;
	int score = EvalPositionTT(child, depth - R - 1, window.GetLowerBound(color));
	if (window.FailLow(score, color))
		continue;
}
This reduced the branching factor massively but when I searched a test-suite of positions to the same depth I solved quite a few less puzzles. So the increased depth is paid for with a significant loss of accuracy. Still it was worth about 30 ELO. If I limit the search by time not depth it also solves more puzzles.
And now that I basically had LMR implemented I remembered reading on this forum that there was a synergy between good move ordering and late move reductions. Up to this point I wasn't sorting quiet moves at all. And so I implemented a simple history heuristic.

When a node fails to raise alpha I record it as 'bad' when it raises 'beta' I record it as 'good'. And I use the ratio of the two for sorting the quiet moves in a naive fashion. (One line of code, making use of the build in Sort functionality of C# lists) Even though the NPS of my engine suffered the branching factor improved enough to give me another +50 ELO. Without late move reductions such a brute force approach to sorting the quiet moves didn't improve the search more than it cost in terms of performance. That features can't be measured in isolation and are only effective in combination with each other makes engine development just a little more tricky! (and drives up the time required for proper testing)

Before I made the above described changes I had no search reductions or extensions besides null move pruning. Now the engine is pruning and reducing quite aggressively and I wonder how that changes it's playstyle (personally, I can't judge it. If there's another Zatour, let me know Amanj! I always very much appreciated your qualitative reviews of my engines play!) but it definitely made it stronger in engine vs engine matches. I think the current dev version of Minimal Chess is probably playing at around 2500 CCRL Elo.
Minimal Chess (simple, open source, C#) - Youtube & Github
Leorik (competitive, in active development, C#) - Github & Lichess
amanjpro
Posts: 883
Joined: Sat Mar 13, 2021 1:47 am
Full name: Amanj Sherwany

Re: MinimalChess - how to move forward?

Post by amanjpro »

Congratulations Thomas, I'm happy for you :)

Did you also try check extension? This has always been sure 15 elos and makes the engine more aware tactically


As for ZaTour, I'm a bit conflicted about continuing it as is. If you remember, my qualification requirement was an engine lower than 2800 rating, but I believe Zahak will soon hit this mark. So, I'm not sure what to do, but I for sure dont want the tournament without Zahak in it, and adapting the rules for Zahak is a hut unfair. I'm in a conflict really