Looking for magics (reducing table size)

Discussion of chess software programming and technical issues.

Moderator: Ras

gxchess1
Posts: 29
Joined: Thu Sep 15, 2022 4:51 pm
Full name: Elad Yosef Cohen

Looking for magics (reducing table size)

Post by gxchess1 »

I know it isn't really important reducing the table sizes but I am doing just for fun and learning purposes. The scheme I use is fixed-shift black magic fancy bitboards.

I created a program that simply brute forces magic numbers until it finds satisfactory one, and replaces it with the current best if it uses less entries. I do 8M iterations for each square in like 4 seconds total single threaded (so 1/8 second for a square). If I don't pack them, the table size is 115KB for rooks only. Heres some of the tricks that I use

- Do

Code: Select all

rnd64() & rnd64() & rnd64()
instead of

Code: Select all

rnd64()
to create better candidates.

- Use aging table with signature as the factor to avoid zeroing out the whole attacks every time a magic is refuted

- Use dynamic ordering to put occupancies that collide more often to the front, so I can refute incorrect candidates earlier (helps a ton)

- When testing candidates, if I can see even before refuting the candidate that the table size of that candidate is going to be bigger than the current best, I leave because it will not change the result (helps a lot too)


Before trying to somehow pack the hash tables, I think I should look for a way to find better candidates, I think I read about flipping bits randomly because it may yield some results, but what I thought about too was genetic algorithms. The mutation will be just a single bit flip or something, breeding could be some XOR operation?, crossover just regular, but the thing I was fearing is that 99.99% numbers are not magic numbers, so when creating new generations, it could be that all or most of the numbers simply aren't correct ones.

Did anyone try genetic algorithms for trying to find better magic numbers? If not, what are some of the other ways besides packing?
dangi12012
Posts: 1062
Joined: Tue Apr 28, 2020 10:03 pm
Full name: Daniel Infuehr

Re: Looking for magics (reducing table size)

Post by dangi12012 »

I did conclusevely solve that topic for my mind to be at piece which took 2 years or so. I exceeded the denseness of existing published fancy magic numbers but not by much (a few %) and much denser is not feasible.

At first I used your scheme of trying rnd64() & rnd64() & rnd64(). I had this generate around 1E8 solutions (or was it 1E9 cant remeber) for any given square so that magic * (occ | mask) >> n does not overlap - or when it does it does so with the same value. First realisation - directly hashing into ONE big power of 2 table for all squares is much worse than hashing into buckets of 2^(> number of relevant bits) which is 12.

A second realisation is that even in this simple scheme negative offsets are possible because we index into magic * (occ | mask) >> n in an offset and that is a free parameter and put those into buckets of 2^12.
This multiplication can consistently generate numbers outside of the bounds of the buckets but with negative offsets they all fall into that range.

Third realisation: We use (occ | mask) and not (occ &~ mask) because the many more '1' help with multiplication of the lower rook corners. I found that one on my own but saw it was common knowledge later.

Fourth realisation: And this on is published right now for the first time: rnd64() & rnd64() & rnd64() is not what you want. What you do here is getting a random number with on average 1/2*1/2*1/2 * 64 bits set - which is 8. This is a binomial sample around the number of 8 bits set. After a few 100 Million candidates you will get more duplicates on the binomial samplingcurve. After a few billion distinct numbers you are essentially not trying any new candidates - so you put them in a hashset OR:
You can exhaustively enumerate all 64bit numbers with 0..8 set bits which is all permutations of ncr(64, 8) + nrc(64,7) + ...
which gives you many many good candidates for almost free. 9-16bits have to be sampled but now with just rnd64() & rnd64() - but you can use c++ robin_hood hashset - which is very fast.

Getting it together:
Once you have many hundred of million known self intersecting (but self intersection on same numbers that hash into 0..2^12) with a known offset you just pick one - then the next one and now you can just iterate over all of them. Out of 100Million for square 2 you pick the best one.

At least that is what i thought - but that is not the case also. You need to order them so that the harder squares come first. That is the distribution of bits is non random and very packed. (That is the case for the rook corner squares).

With this sorted square list you do the steps above pick 1 - iterate over all 1E8 candidates - pick the best.
You will find that the table is very very dense and I remember for example 4096 self intersecting down to 1912 distinct slots and that intersecting more with other buckets in the bigger list etc.

TLDR:
You need to implement a sliding window for buckets of 4096 known taken slots and put them into the first slot they fit into. The trick is to have many many known buckets (which is candidate + initial offset which can be negative) and increment that index by one until you get above 109000 or your target total table size.
Of course even when slotting them into the first free slot you run into local optima where the table becomes dense at the bottom but later squares dont fit anymore - showing that not even the first free slot is correct. My optimal solution had index += 17 (a small prime) insted of index++ and take the first fitting slot there to circumvent early over density and missmatching upper tables.

High level background:
I got really into that topic for 1 year before even staring on movegeneration - generated a visalisation tool that showed bmp images of the seeds (as single black pixels) and showed how adding more into that table makes the image blacker and blacker until at the end its only a few white pixels.. That proved to me visually that the existing is really very dense with many many overlaps. I finally got a few % below the published numbers and was happy with that.
At one point I had 400+GB of square candidates to pick and match as dense as possible. - bishops are so small and easy to distribute into the table its not worth mentioning - and even the rooks are 50% "easy" squares. This topic is also what sparked my interest in creating faster movegenerators than fancy magic - going for faster pext and now in 2023 we have galois field, kiss, pext and fancy magic. Many movegenerators were tightly coupled to the engine code - but over the past few years we uncoupled and modernized a whole list of movegens.
Take a look: https://github.com/Gigantua/Chess_Movegen
Worlds-fastest-Bitboard-Chess-Movegenerator
Daniel Inführ - Software Developer
gxchess1
Posts: 29
Joined: Thu Sep 15, 2022 4:51 pm
Full name: Elad Yosef Cohen

Re: Looking for magics (reducing table size)

Post by gxchess1 »

Have you been able to get a less than 28 entries for the square Bishop B2 with fancy & fixed shift, which optimally should have only 6 entries? If so, whats the magic number and method (white/black)?

I have some sort of idea I wanna try but for now the best number of entries I get is 28. But the thing is that I guess finding parameters for this idea takes exponetial time, so every entry I save I reduce time by half.

So if you have any square rook/bishop with less than 28 entries I would appreciate it a lot.
gxchess1
Posts: 29
Joined: Thu Sep 15, 2022 4:51 pm
Full name: Elad Yosef Cohen

Re: Looking for magics (reducing table size)

Post by gxchess1 »

Question for everyone: Is there any square with fixed shift magics that it's hash table in magic bitboards is less than 28 entries? Currently the best I am getting is Bishop B2, with 28 entries, but I don't care about the square, I just need a square with not a lot of entries.

If so, what is the magic if that is share-able? thanks :)
gxchess1
Posts: 29
Joined: Thu Sep 15, 2022 4:51 pm
Full name: Elad Yosef Cohen

Re: Looking for magics (reducing table size)

Post by gxchess1 »

Question for everyone: Is there any square with fixed shift magics that it's hash table in magic bitboards is less than 28 entries? Currently the best I am getting is Bishop B2, with 28 entries, but I don't care about the square, I just need a square with not a lot of entries.

If so, what is the magic if that is share-able? thanks :)
User avatar
Volker Annuss
Posts: 180
Joined: Mon Sep 03, 2007 9:15 am

Re: Looking for magics (reducing table size)

Post by Volker Annuss »

It depends on your board layout what B2 is.
For me A8=0, H8=7, A1=56, A8=63 and B2=49. Here my best black magics for bishops diagonally neighbored to a corner:

Code: Select all

 9: 0xffc3bfdfbfefdbff 18  XXXXXXXXXXXXXXXXXX
14: 0xf8f5dfefe0c00081 23  X-XXXXXXXXX-X-XXXXXXX-X
49: 0xf907ffefe0c03bf0 22  XX-XXX-XXX--XX-XXX--XX
54: 0xf9ffffbfe03017ef 16  XXXXXXXXXX-XXXX
User avatar
Volker Annuss
Posts: 180
Joined: Mon Sep 03, 2007 9:15 am

Re: Looking for magics (reducing table size)

Post by Volker Annuss »

When you found a black magic number m then
  • -m is almost always also a black magic number.
  • for very small n the number m*n is more likely to be a magic number than a random number.
  • for n = 1<<i the numbers m+n, m-n, m^n are more likely to be magic numbers than random numbers. For i close to 0 or close to 63 this may not make a difference in the resulting table.
When you found two magic numbers m1 and m2 then
  • for small n the expression (n+1)*m1-n*m2 is more likely to be a magic number than a random number. This is especially good when m1 and m2 both have most bits set or most bits clear.
dangi12012
Posts: 1062
Joined: Tue Apr 28, 2020 10:03 pm
Full name: Daniel Infuehr

Re: Looking for magics (reducing table size)

Post by dangi12012 »

Volker Annuss wrote: Sun Apr 23, 2023 9:36 am When you found a black magic number m then
  • -m is almost always also a black magic number.
  • for very small n the number m*n is more likely to be a magic number than a random number.
  • for n = 1<<i the numbers m+n, m-n, m^n are more likely to be magic numbers than random numbers. For i close to 0 or close to 63 this may not make a difference in the resulting table.
When you found two magic numbers m1 and m2 then
  • for small n the expression (n+1)*m1-n*m2 is more likely to be a magic number than a random number. This is especially good when m1 and m2 both have most bits set or most bits clear.
There is a deeper level to this game. seed := magic number
There are some seeds that have 1 bit. There are seeds that have 2 bit. If there existed a solution for all squares where all bits have 1 bit we dont need to multiply at all. But its already proven there are none. If you find a magic number chaning 1 or two bits OR shifting 1 bit is very likely to be a magic also.
The minimum number of bits can easily be verified by exhaustive search via this function:

Code: Select all

void bit_twiddle_permute(uint64_t& v) {
	uint64_t t = v | (v - 1);
	v = (t + 1) | (((~t & -~t) - 1) >> (std::countr_zero(v) + 1));
}
Which is an optimized "Compute the lexicographically next bit permutation". So starting with 0b11111 will end up with 0b11111000000...... NCR(64, 5) iterations. Full function:

Code: Select all

void addNBitSet(int bits, std::vector<uint64_t>& vals)
{
	uint64_t count = ncr(64, bits);
	uint64_t value = (1ull << bits) - 1;

	for (uint64_t i = 0; i < count; i++)
	{
		bit_twiddle_permute(value);
	}
}
So now the only way to optimize magic lookup is to need to lookup for the "magic". Finding a function that can calculate the magic by shifting a constant by a square. But its more complicated than that. There definitely is a pattern there and magics can be found to have the minimum number of differint bits which will find magics that look similar to this (when you print in base2)

Code: Select all

0000000000000000000000000010000000000000000000010000000000000000
0000000000000000000000000010000000000000000000010000000000000000
0000000000000000000000000010000000100000000000010000000000000000
0000000000000000000000000010000001000000000000010000000000000000
0000000000000000000000000010000010000000000000010000000000000000
0000000000000000000000000010000100000000000000010000000000000000
0000000000000000000000000010001000000000000000010000000000000000
0000000000000000000000000010010000000000000000010000000000000000
0000000000000000000000000010100000000000000000010000000000000000
0000000000000010000000000011000000000000000000010000000000000000
0000001000000010000000000010000000000000000000010000000000000000
0000010000000010000000000110000000000000000000010000000000000000
.........................
But I found this to be a rabbit hole without end. Elusive and maybe possible to solve. Best I found that there are some constants that can be shifted left that are correct magics for the next 17 squares.
But what number you pick first will determine definitely which patterns have a minimum difference + there are always holes etc. So the chance of finding a perfect magic that simply needs to be shifted or has a 5 operation formula is something that is an open question.
You could call it a conjecture: There exists a constant and a function that can caluclate 64 valid magic numbers depending on the square without a lookup faster than a single lookup into a buffer of 64 elements.
Worlds-fastest-Bitboard-Chess-Movegenerator
Daniel Inführ - Software Developer