Author: Ratko V Tomic
Date: 00:37:05 11/15/99
Go up one level in this thread
------------------------------------------- CODING ANY LEGAL CHESS POSITION IN 155 BITS ------------------------------------------- > 2) How do you decode such a monster? Although I didn't originally plan to work out a practical and detailed encoding/decoding algorithms (I only knew, from the general programming experience, that the framework described could be implemented efficiently in time & space), now that you wish to be convinced beyond any doubt, I'll describe the algorithm in enough detail for anyone to try it out (I have no idea why would anyone wish to do so, but here it is anyway). The full position code FPC was a fixed length (155 bit without castle or EP; around 157 with these included) code, consisting of 2 variable length components, the Material Content (MC, i.e. material counts) part and the Material Placement part (MP), or symbolically: FPC=(MC,MP(MC)), where we use MP(MC) to denote that the placement encoding is dependent on the given MC state. The MC part was a Huffman coded index into an array of all MC states (there are 58,084,310 distinct MC states). The MP part, for a given MC, was an index into an array of all distinct permutations (placements) of the given (by this MC) material. These (conceptual only, of course) arrays, one for each of the 58 million MC states, would have as many as 3*10^42 entries, or in total 2.46*10^46 entries for all such arrays. This conceptual "algorithm" is obviously impractical due to the huge memory demands. For clarity, the description of the practical algorithm is divided into 3 parts (phases) and the encoding + decoding are covered in parallel in each phase. Part 1 covers encoding/decoding of the MC part to and from a fixed length (26 bit) binary coded MC index (MC26 index). Part 2 decribes how, for a given MC and any one of its placements, to produce an MP index and how, given an MC and any of its index values to reconstruct the placement. Part 3 describes Huffman (de)coding of the MC26 index, in conjuction with the MP part of the code, resulting in the fixed length FPC-155 code. The algorithm is "practical" in the sense that it will complete on any PC and encode/decode hundreds of positions in a "blink of an eye," although it certainly could be optimized further (if one were to convert it into a C code). Notation: BINOMIAL COEFFICIENTS: {n;k} = n*(n-1)*...*(n-k+1)/k! = n!/(k!*(n-k)!) = {n,n-k} {n;n} = {n;0} = 1 if n<k --> {n;k} = 0 MULTINOMIAL COEFFICIENTS: {n;k1,k2,...,km} = n!/(k1!*k2!...*km!*(n-k)!) = = n*(n-1)*...*(n-k+1)/(k1!*k2!*...*km!) where k=k1+k2+...+km a % b is a modulo b a \ b is integer division (or a\b = floor(a/b) = (a-a%b)/b) 1. INDEXING MATERIAL CONTENT ---------------------------- The MC state is specified via a record of 10 counts for the 10 pieces MC2 = (QRBNP,qrbnp) which have changeable counts. The construction in the original post would first build an array of MC states for one side only, MC1 = (QRBNP), under the constraint that the total excess in the counts of Q,R,B,N plus the number of pawns cannot exceed 8 (since each excess piece could only come from the promotion). The resulting array MA1[] had 8694 distinct states (13.1 bits index). The two side MC state array MA2[] was constructed by pairing elements of MA1[] among themselves as MC2 = (MA1[w], MA1[b]), where w index would run 0..8693 and b index from 0..w (the case b>w is identical regarding legality & # of placements, so it wasn't explicitly recomputed, just the results of the sums were doubled for all w>b cases), and for each pair the promotions/capture costraint was checked: wx <= 2*bpd + bnd + wd (1w) bx <= 2*wpd + wnd + bd (1b) where wx (or bx) is excess in white (black) pieces, wc (bc) is the total white (black) count defect, bpd (wnd) is black (white) pawn defect, and bnd (wnd) is black (white) nonpawn defect. The terms excess and defect were covered in detail in another post in this thread (which also deduced eqs. 1w,b), briefly the definitions are: D-1. The EXCESS of a piece is the piece count excess above its initial state (e.g. 5 W.Queens have WQ excess 4), and it is 0 if piece count is not above the initial count. Only nonpawns can have excess. The sum of excesses for all white (black) pieces is wx (bx). D-2. The DEFECT for a nonpawn is the defect in the piece count below its initial count (e.g. 0 W.Rooks have W.Rook defect 2; 1 W.Rook has defect 1; 3 W.Rooks have defect 0). The sum of white (black) nonpawn defects is wnd (bnd). D-3. The DEFECT for white (black) pawns is the number of missing pawns below the number 8-wx (or 8-bx) implied by the excess for white (black), e.g. wpd = 8-wx-wnp (where wnp is number of white pawns). D-4. The total white (black) DEFECT is wd=wnd+wpd (or bd=bnd+bpd). Important point is that all these excesses and defects are computed directly from the counts in a given MC state, without speculating on number of actual captures and promotions (which are not uniquely determined from the MC counts). The number of all (w,b) pairs is 8694^2 = 75,585,636 but the constraint 1 was satisfied by only NMC = 58,084,310 pairs i.e. 26 whole bits (25.7916 bits) is needed for indexing. If we were to fill out entire MCA2[NMC] array, we could easily decode any MC26 Index i (i=0..NMC-1) by simple lookup in MCA2[i] to retrieve the MC2 state for this i. The encoding (i.e. producing an index i from a given MC) would involve searching for a given MC in MCA2[NMC] (using log2(NMC) comparisons) and the index where the matching MC was found is our MC26 index i. Had we not eliminated the elements from the (w,b) pairs, which left scattered holes in the square array (w,b), we could have used for both encoding and decoding only the MCA1[8694] array, constructing the needed (w,b) pairs on the fly. Namely, to encode (MCW,MCB) state, we would do 2 binary searches in the MCA1[], obtaining w and b one side indices, then compute: i(w,b)=8694*w+b (2) The decoding of i would be done as: b=i%8694, w=i\8694 (3) We can still do this, but this index would require 26.1716 bits or 27 whole bits (instead of 26 whole bits) and would require checking of the pair for legality (under the pair constraint (1w,b)) before computing the placement index. If the holes left by the constraint (1) in the square (w,b) array were more regular e.g. if for each w index in the pairing loop, the accepted pairs had a contiguous range of the b index, say 0..bmax, then we could still apply the method (2) for encoding by replacing in (2) the term 8694*w with an array lookup OFFS[w], where the OFFS[0..8693] would contain code offsets for each w index. In fact, we can sort our original MCA1[] array so that exactly this type regularity does hold. Namely, if we rewrite constraint (1w,b) as: wx - wd <= 2*bpd + bnd (4w) bx - bd <= 2*wpd + wnd (4b) we have the white and the black parameters on different sides of the inequality. If we now sort the array MA1[] in the decreasing order of the value 2*bpd+bnd (which is the right hand side of (4w)), and for a given w, run the b index from 0 and up, as soon as the first failure of (4w) occurs, say at some index B1, all the remaining values of index b (from B1 to 8693) will violate (4w) automatically. Namely, the right hand side of (4w) can only stay the same or get lower as b index increases (due to the sorting in decreasing order), hence if for b=B1 (4w) is violated it will stay violated for b>B1. If no violation occured for any b, we will set B1=8694, so that each w will have its own B1. The total number of pairs produced this way, i.e. under the (4w) constraint but without the (4b) constraint, is 62,096,507 = 25.888009 bits, which is 0.096 bits more than 25.791645 bits for the 58,084,310 pairs optimal array (obtained when both (4a) and (4b) are applied). But in both cases the count fits in a 26 whole bits binary index. Note also that we still maintain the full constraint (4w) and (4b) at our encoder main entry, i.e. we return an error when we get an MC state which violates either inequality. We also maintain the full constraint when enumerating all possible placements, hence all the figures for placement counts for all fully valid MCs, and the resulting Huffman code weights (hence the MC Huffman code lengths) remain the same for all fully valid MC states. Since the MC26 code is only an intermediate code (being turned into a Huffman code, which remained same as for the full constraint), the only effect of this relaxation is that some numbers (MC26 binary index values) in the intermediate calculations gain 0.096 bits, which given the use of whole bits (which remain under the 26 whole bits) here doesn't produce any practical difference (other than slightly enlarging the Huffman code tables, as we will see in part 3). We now continue with the encoder/decoder construction. The value of B1 varies as w index varies (the outer loop index). As we check all pairs this way, we compute and store B1 values (one obtained for each w index) in an array B1[0..8693]. Then we compute cummulative values of B1's into an array CB1[0..8693], where CB1[0]=0, ..., CB1[w+1] = CB1[w]+B1[w], i.e. the CB1[] array will have 0, B1, B1+B2, B1+B2+B3, ..., B1+B2+...+B8692 as its elements. The encoding of MC into MC26 code then works as follows: E1) We obtain input MC state as (MCw,MCb), we check it against the (4w,b) constraints and return an error if it violates either. E2) Then we search in the one side MC array (the MA1[0..8693] array containing all valid one side MC records, which are the piece counts) the MCw and Mcb states, and say we find them at positions w and b. (If either is not found, the one side state is illegal and we return an error). E3) We compute the MC26 code as MC26 = CB1[w]+b. (The followup Huffman encoding of MC26 is in part 3) The decoding of MC26 works as follows: D1) We obtain MC26 (from Huffman decoder, given in part3) and do a binary search in the CB1[0..8693] array to find the nearest lower (or equal) value to MC26 (this will take up to 14 comparisons). The index at which this was found is, say, w (it will always be found since it doesn't have to match excatly MC26). D2) The b index is then: b = MC26-CB1[w]. Since CB1[w] is the nearest lower/equal CB1[] element to MC26, and since the difference between adjacent CB1[] elements is at most 8694, the value of b will always be in range 0..8693. D3) Using these w and b we retrieve MCw=MA1[w] and MCb=MA1[b] to obtain the full MC state (MCw,MCb). So we have a quick way to get from the MC state to the MC26 index and back, using the 2 arrays of 8694 elements each, MA1[] (using at most 17 bits per entry) and CB1[] (using at most 26 bits per entry). These arrays are precomputed and are part of the program's constants (either loaded as data files or included into the code as preinitialized arrays of constants), hence their construction time isn't a part of the encoder/decoder execution time. 2. INDEXING MATERIAL PLACEMENTS -------------------------------- For a given MC state there will be NP different piece placements. The task here is to compute index IP (IP range: 0..NP-1) for a given specific placement MP. Alternatively, given IP, decoder must reconstruct (uniquely, of course) the specific placement MP. There is a more generic subtask here, which is: given some piece type with count M and with NS available squares for placing the M copies of the piece, find the indexing for all the unique placements of the M copies. Once we have solved this problem, we can use variable radix coding to encode the combined index for all piece types present, hence produce the the MP(MC) part of the full code. The variable radix scheme will use the maximum arrangements for each piece type (in given order of types) as the radix for a given position, while the index value for a specific placement of that piece type will be a digit in that place. We'll tackle the generic sub-problem first. 2.1 INDEXING ARRANGEMENTS OF A SINGLE PIECE TYPE ------------------------------------------------- We have M copies of a single piece type and NS squares (NS>=M) to place this piece. The number of distinct placements is: NP = NS*(NS-1)*..*(NS-M+1)/M! = {NS;M} (5) We will order our M pieces by placing piece P1 on square S1, P2 on S2,... PM on SM, where no 2 pieces can go on one square. We will use labeling convention where the following is satisfied: 1 <= SM < ... < S2 < S1 <= NS i.e. if we imagine NS squares in an array 1,2,...NS then P1 is placed rightmost (the front piece), P2 to the left of it, and the PM last to the left of the rest (the tail piece). The encoder's objective is, given a position list for the M pieces, SP=(SM,..,S2,S1), to obtain a number I = f(SP) such that 0<=I<NP and that any two different SP1 and SP2 always give a different I1 and I2. Decoder has to reconstruct for a given I the unique SP(I) list. This problem has an obvious recursive nature. Namely, if we look at the fixed position of the front piece P1, the remaining pieces replicate the same problem, but on smaller scale, with M1=M-1 pieces and on NS1=S1-1 squares. Conceptually, the solution to the indexing problem could work as follows: if we have some array of index base values IB[] for each valid position of the P1 (which is M,M+1,...,NS), then the Index for SP(NS,M) would be IB[S1] + Index(SP(S1-1,M-1)). The base values IB[S1] would be constructed by adding up numbers of used up index values for P1 positions lower than S1. Before giving the general formula for the index, let me illustrate the method on 3 rooks placements on an empty board (NS=64, M=3). The placement I=0 is 123, i.e. the squares for the rooks are: S1=3, S2=2, S3=1. Here are several more in a table, arranged by the front rook: S1 IB NA Arrangements ------------------------------------- 3 0 1 123 ------------------------------------- 4 1 3 124 134 234 ------------------------------------- 5 4 6 125 135 145 235 245 345 ------------------------------------- 6 10 10 126 136 146 156 236 246 256 346 356 456 ------------------------------------- 7 20 15 127 137 147 157 167 237 247 257 267 347 357 367 457 467 567 ------------------------------------- 8 35 21 128 ..... 178 ............. 678 ------------------------------------- k {k-1;3} {k-1;2} 12k ... 1(k-1)k ............... (k-2)(k-1)k ------------------------------------- 64 {63;3} {63;2} The NA coulumn gives number of arrangements for the given front rook position S1. The IB column is the cummulative value for NA up to (but not including) a given S1. Our index for, say the entry 257 (S1=7) is 20+1+2+3+1=27, i.e. we take IB which is 20, then going column by column (using thus rook 2 as the front rook) within the S1=7 group we count how many arrangements came before the 257. To decode 27 into 257 arrangement, we first search in the IB array for the nearest IB[x]<=27 and IB[x+1]>27. This returns IB[7]=20, thus our front rook is at S1=7. The sub-table for S1=7 is the same kind of arrangement table, except that it is for 2 rooks on 6 squares, and flipped horizontally. The NA, IB and S2 values here are: S2 2 3 4 5 6 NA 1 2 3 4 5 IB 0 1 3 6 10 for the S2 values 2 3 4 5 6. The 2-rook index, which was offset by 20 for the 3 rook index, is 27-20=7. So we look in the 2 rook table for the nearest IB<7, which is 6, for S2=5. The remaining 1-rook code is 7-6=1, and we have for the 1 rook table: S3 1 2 3 4 NA 1 1 1 1 IB 0 1 2 3 We look for the nearest IB<=1, and find it for S3=2, giving the full placement S3,S2,S1 as 257. Now we'll look how to create these types of tables in the general case, (NS,M). For M pieces on NS squares, the NA coulumn gives for each S1 (the front piece position, which goes from M to NS) the number of arrangements of the remaining M-1 pieces on S1-1 squares, hence using eq.(5): NA(S1) = {S1-1;M-1} (6) The IB column for S1 is a cummulative NA() value up to the preceding S1 value, i.e. IB(S1) = IB(S1-1)+NA(S1-1), for S1=M+1...NS and IB(M)=0, for 0 based index. Expanding the recursion into a sum: IB(S1) = Sum(j=M..S1-1; NA(j)) (7) for S1>M and IB(M)=0 (by definition). Using the binomial coefficient formula for summation on the upper binomial coefficient index: {n;n}+{n+1;n}+{n+2;n}+...+(m;n} = {m+1;n+1} (where m>=n) we get, IB(S1) = Sum(j=M..S1-1; {j-1;M-1}) = {M-1;M-1} + {M;M-1} + .. .. + {S1-2;M-1} = {S1-1;M}, i.e. IB(S1) = {S1-1;M} (7) Our cummulative index base is simply a binomial coefficient. The full index is obtained recursing down to M=1 and adding all (lower M) IB(Sk) contributions, yielding: I(S1,S2,..SM) = Sum(j=1..M; {Sj-1;M-j+1}) (8) = {S1-1;M} + {S2-1;M-1} + ... + {SM-1;1} (Note that (8) assumes that by definition the terms of type {n;k} with n<k are 0, e.g. if S3=1 or S2=2 or S1=3 in our M=3 rook example, then the three terms are {2;3}=0 {1;2}=0 and {0;1}=0, giving I(3,2,1)=0.) Decoding of I(S1,..,SM) works by finding x for the largest {x;M} < I, the obtained {x;M} gives us the leading term in (8), with x=S1-1 i.e. S1=x+1. Then we reduce I by this {x;M} i.e. assign I1=I-{S1-1;M} and repeat this for the next term in (8), obtain S2, etc. The uniqueness of decoding follows from observing how we obtained (7) & (8), i.e. by noting that any term in (8) is greater than the sum of all the terms to the right of it (the closests the two ever come together is if the S1-S2=1, S2-S3=1, S3-S4=1,.. in which case {S1-1;M} is greater from the sum to the right of it in (8) by exactly 1}, which makes this a sort of number base with S1, S2,.. being its digits (S1 the most significant, SM the least). The sum (8) achieves maximum when all S values are at maximum, i.e. S1=NS, S2=NS-1, ... Sk=NS-k+1. In that case the sum (8) is exactly equal {NS;M}-1, meaning that index values cover the required range 0..NP-1, where NP was given by (5). Searching for {x;M}<I we use in decoding involves a short binary search in the table of {x;M} with x<=64 (which should complete in at most 6 comparisons). A precomputed 64x64 table could serve encoding & decoding needs (since M is always given, we could make array of 64 pointers indexed by M, each entry giving us a pointer into an array of 64 {x;M} values). If one were doing other than chess enumerations with this combination indexing method, with large x values, and a machine has a fast floating point, one can use estimate x ~~ M/2+(I*M!)^1/M (the m-th root finds roughly the middle of the range of M multipliers in the numerator of the {x;M}) to get us very near the correct x. We can illustrate (8) by encoding our earlier example 257 (S3=2, S2=5; S1=7, M=3): I(7,5,2)={7-1;3} + {5-1;2} + {2-1;1} = 20+6+1 = 27 Decoding od 27 starts with looking for {x;3} nearest to 27, not exceeding 27. Value {6;3}=20 and {7;3}=35, hence x=6 or S1=x+1=7. Then I1=27-20=7. We look for {x;2}<=7 which from {4;2}=6 {5;2}=10 gives x=4, hence S2=x+1=5. Then I2=6-5=1 and we look for {x;1}=x<=1, hence x=1 and S3=x+1=2, i.e. the placement for the index 27 is 257. 2.2 INDEXING MULTIPLE PIECE TYPES --------------------------------- Now we have k piece types, P1, P2 to Pk, and for each we have a count M1, M2,.., Mk and the squares available, NS1 squares for the P1, NS2=NS1-M1 for P2, NS3=NS2-M2 for P3, etc. For this particular convention of placing the pieces, the number of arrangements for P1 is NP1={NS1;M1}, for P2 NP2={NS2;M2},... NPk={NSk;Mk}. The total number of arrangements for the k types is NP=NP1*NP2*...*NPk. Some particular arrangement of these k piece types will produce k independent index values I1, I2, ... Ik (using the indexing method from part (2.1)), and these index values have property: 0 <= Ij < NPj for all j=1,2..,k. To represent the full index I for all k piece type, we will use I1,..Ik as k digits in a mixed radix representation, where the 1st digit is in radix B1=NP1, second digit in radix B2=NP2,.. , k-th digit in radix Bk=NPk. Hence we form the full index for all k piece types as: I = I1+B1*(I2+B2*(I3+...+B[k-2]*(I[k-1]+B[k-1]*Ik)..) (9) Where the range of the MP index I is 0..NP-1. This is the same type of representation as for, say, a 4 digit decimal number d with digits (d4 d3 d2 d1) as: d = d1+10*(d2+10*(d3+10*d4)) = d1 + d2*10 + d3*10^2 + d4*10^3 Decoding of index I works in exact analogy with conversion of binary to decimal radix, i.e. from given I we obtain "digits" I1, I2, ... using the steps: I1=I%NP1, I=I\NP1, (10) I2=I%NP2, I=I\NP2, .... I[k-1]=I%NP[k-1], I=I\NP[k-1], Ik=I The obtained values I1, I2,..Ik would be decoded as the placement index I in section (2.1). Note also that each subsequent piece uses the left-over square array from the previous piece, hence the S1,S2,..,SM in the section (2.1) refer to the empty square holes, 1 denoting the 1st hole, 2 the second hole, etc. In using the mixed radix method for a given MC state, the pawns, which can occupy only the 48 squares instead of 64 should be encoded in the 1st 2 "digits" if both color pawns are present, or as 1st digit if only one color pawns are present. If we have wp and bp white and black pawns, and n3,n4,n5,..,nk of other pieces, and we denote p=wp+bp, then in the scheme above we will have: NS1=48, M1=wp, NS2=48-M1 M2=bp, NS3=64-p M3=n3, NS4=NS3-n3 M4=n4, ... NSk=NS[k-1]-n[k-1] Mk=nk. To minimize the size of the NP numbers (the radix values), it is best for a given MC, after dealing with pawns (which always come 1st if present) to sort the counts n3,n4,.. in increasing order, so that the largest n's come with the least NS number. Thus n3=1 and n4=1 would be set for kings, then the rest of pieces. The worst case (for a size of an individual NP) occurs when we have only the 2 kings and 10 copies of a single piece (R,B or N). In that case the NP for this piece type is {62;10}=1.1*10^11 = 36.6 bits, i.e. we cannot generally use 32-bit integer for the NPs, but the 64-bit integers will work safely. The full index value in (9) is much larger, the worst case being a 30 piece MC={QRBNP=3,3,2,4; qrbnp=3,3,2,4} with number of placements and index 0<= I< NP=3.0678*10^42 = 141.14 bits i.e. at least 18 byte integers are needed for to handle the mixed radix arithmetic in (9) and its decoder in (10). -- continued in 2/2
This page took 0 seconds to execute
Last modified: Thu, 15 Apr 21 08:11:13 -0700
Current Computer Chess Club Forums at Talkchess. This site by Sean Mintz.