SEE: what you see and what you get?

Programming Topics (Computer Chess) and technical aspects as test techniques, book building, program tuning etc

Moderator: Andres Valverde

SEE: what you see and what you get?

Postby H.G.Muller » 26 Jan 2006, 12:57

I am curious how one generally handles the trade off between speed and accuracy of Static Exchange Evaluation, for which purposes the SEE value is used (e.g. move sorting, pruning/reduction/extension decisions), and how clever the SEE has to be to be suitable for a certain application. There are so many publicly available sources nowadays, that I don't know where best to look for a representative example.

In my old engine from 1983 I was using an extremely simplistic SEE: the program maintained a table counting the number of (capture) moves to each square of the board for each side separately (also counting pseudo-captures, i.e. captures of your own pieces), as well as the value of the lightest piece that covered that square. If for a certain square the number of attackers was larger than the number of defenders SEE returned a loss of the piece on that square or the lightest defender (whichever was smaller). If the number of defenders was larger or equal, it returned 0 or the value difference between the piece and its lightest attacker, whichever was larger.

Despite its coarse nature, this was enough to cover the most common cases. (Most common mistakes occur when a piece is defended by very heavy pieces only, like K+Q.) It probably would have been good enough for move sorting, where it does not hurt you very bad if you now and then make a gross mistake, as long as it does not happen too often. But unfortunately I used it as part of the evaluation in leaf nodes, and it was not nearly good enough for that. So the program played like cr*p, even worse then when I would not include this term in the evaluation at all for the same search depth...

How far can you go making a smart SEE without making the algorithm as expensive as a (quiescence-like) search? Are there smart algorithms around for this? I would be very happy with a SEE that would recognize pinnings (also on other pieces than the King!), (de)blocking threats (such as doubled Rooks, but also things like a Bishop stepping away from a Rook line) and overloaded defenders.
Last edited by H.G.Muller on 26 Jan 2006, 16:10, edited 1 time in total.
User avatar
H.G.Muller
 
Posts: 3453
Joined: 16 Nov 2005, 12:02
Location: Diemen, NL

Re: SEE: what you see and what you get?

Postby Daniel Shawul » 26 Jan 2006, 13:54

For a table based look at Rebel's page
http://members.home.nl/matador/chess840.htm
The SEE values are precalculated and used in eval in
many places. I don't know if they are accurate engouh to
be used for pruning in qsearch. I remember Ed saying
they are 99% accurate, i dont know if they have to be 99.9%
accurate to be used for pruning. I tried it once , and i didnt
see any difference.

One advantage of using bitboard is to have a fast and
complicated SEE , which can detect pinned/overloaded pieces.
You might want to look at crafty's SEE but it is not that complicated.
Rybka is "suspected" to have complicted SEE but you should be
able to read assembly code to get the idea :)

Daniel
User avatar
Daniel Shawul
 
Posts: 366
Joined: 28 Sep 2004, 09:33
Location: Ethiopia

Re: SEE: what you see and what you get?

Postby Alessandro Scotti » 26 Jan 2006, 18:15

Hi H.G.,
I'm currently using a simple see() that understands captures (including en-passant), promotions and x-rays. I hope it represents a good compromise between speed and quality. Now to really test it I need the rest of the engine though! :-)
User avatar
Alessandro Scotti
 
Posts: 306
Joined: 20 Nov 2004, 00:10
Location: Rome, Italy

Re: SEE: what you see and what you get?

Postby YvesLejeail » 26 Jan 2006, 18:49

I use a simple SEE based on attack tables, having no pinning or xray or overload detections. But i'm not a reference though :) . Its used only for sorting in fact, every capture is rewarded, only the very bad looking captures
are not considered (the captured piece < capturing piece and case_control_capturing_side < case_control_captured_side). Maybe It miss some sacrifices, and it doesn't see the effect of pinning in case of apparent bad looking captures. Also I have no idea of the real efficiency of that compared to a simple function without attack table.
:|
User avatar
YvesLejeail
 
Posts: 48
Joined: 03 Aug 2005, 17:36
Location: Pertuis, France

SEE - one super-basic implementation

Postby smcracraft » 26 Jan 2006, 19:52

Enclosed is the super-basic SEE used in TeeDee, my simplified
research program for auto-learning.

This SEE has a portion (the minimax swapoff at the end) contributed
at the ICD talkchess.com chess club to the program.

The value returned by SEE for pieces in any search's move generation
or capture generation is used to cutoff if < 0 the requirement of the
quiescence search.

This version is debugged enough to make a big difference in the
program's result but is not debugged enough nor designed correctly
for anything beyond its goal - a very basic implementation - to
speed up implementation of the program to permit the basic
research of interest.

X-rays are partially but not wholly supported.

Stuart

int mat_value[13] = {MAXNUM,
9000, 5000, 3000, 3000, 1000,
0,
1000, 3000, 3000, 5000, 9000,
MAXNUM};

#define DEFEND(sq,pc) (ABS(bd[sq])==pc && ((c == white && bd[sq]>=WP && bd[sq]<=WK)||(c == black && bd[sq]>=BK && bd[sq]<=BP)))
#define ATTACK(sq,pc) (ABS(bd[sq])==pc && ((c == black && bd[sq]>=WP && bd[sq]<=WK)||(c == white && bd[sq]>=BK && bd[sq]<=BP)))

int see(int *bd, int sq)
{
int attackcolor, defendcolor;
int stm = 1; // side
int capturedfig = bd[sq];
int gain = stm*mat_value[capturedfig + 6];
int next_fig;
int counter = 1;
int c, oppcolor, i, nsq, offset, starti, lasti;
int value;

swap_score[0] = gain;
stm = -stm;
at1=at2=at3=de1=de2=de3=0;

// look for hidden figure
// produce a move vector form source to target square, go along this vector
// from the captured piece to the border to find hidden pieces

if (ISWHITE(sq)) { c = defendcolor = white; oppcolor = attackcolor = black; }
else if (ISBLACK(sq)) { c = defendcolor = black; oppcolor = attackcolor = white; }
else {
printf("see issue at square %d, value is %d\n",sq,bd[sq]);
return(0);
}

// Do the knights
for (offset = 0; offset < 8; offset++) {
if (DEFEND(sq+knightoffsets[offset],knight)) defender[de1++]=knight;
else if (ATTACK(sq+knightoffsets[offset],knight)) attacker[at1++]=knight;
if (DEFEND(sq+sliders[offset],king)) defender[de1++]=king;
else if (ATTACK(sq+sliders[offset],king)) attacker[at1++]=king;
}

// Do the pawns
if (ISWHITE(sq)) {
if (DEFEND(sq+BCAPL,pawn))
defender[de1++]=pawn;
if (DEFEND(sq+BCAPR,pawn))
defender[de1++]=pawn;
if (ATTACK(sq+WCAPR,pawn))
attacker[at1++]=pawn;
if (ATTACK(sq+WCAPL,pawn))
attacker[at1++]=pawn;
} else {
if (DEFEND(sq+WCAPL,pawn))
defender[de1++]=pawn;
if (DEFEND(sq+WCAPR,pawn))
defender[de1++]=pawn;
if (ATTACK(sq+BCAPR,pawn))
attacker[at1++]=pawn;
if (ATTACK(sq+BCAPL,pawn))
attacker[at1++]=pawn;
}

// Sliding pieces
starti = 0; lasti = 7;
for (i = starti; i <= lasti; i++) {
nsq = sq;
while (1) {
nsq += sliders[i];
if (OFFBD(nsq))
break;
else if (EMPTY(nsq)) continue;
else if (i < 4) {
if (DEFEND(nsq,bishop))
defender[de1++]=bishop;
if (DEFEND(nsq,queen))
defender[de1++]=queen;
if (ATTACK(nsq,bishop))
attacker[at1++]=bishop;
if (ATTACK(nsq,queen))
attacker[at1++]=queen;
// break;
} else if (i >= 4) {
if (DEFEND(nsq,rook))
defender[de1++]=rook;
if (DEFEND(nsq,queen))
defender[de1++]=queen;
if (ATTACK(nsq,rook))
attacker[at1++]=rook;
if (ATTACK(nsq,queen))
attacker[at1++]=queen;
// break;
}
else
break;
}
}
qsort(attacker,at1,sizeof(int),compare0);
qsort(defender,de1,sizeof(int),compare0);

// next figure, from the back of the att/def arrays to the front
for (;;)
{
if (de1 > 0)
{
gain = gain + stm*mat_value[attacker[at1-1]+6];
swap_score[counter] = gain;
stm = -stm;
counter++;
at1--;
}
else
break; // no more defenders

if (at1 > 0)
{
gain = gain + stm*mat_value[defender[de1-1]+6];
swap_score[counter] = gain;
stm = -stm;
counter++;
de1--;
}
else
break; // no more attackers
}

// minimax through swap_scores from back to top
// back up swap_scores if there is a gain, else stand pat
stm = -stm;
counter--;
while(counter)
{
if (stm < 0)
{
if(swap_score[counter] <= swap_score[counter-1])
swap_score[counter-1] = swap_score[counter]; // else stand pat
}
else
{
if(swap_score[counter] >= swap_score[counter-1])
swap_score[counter-1] = swap_score[counter]; // else stand pat
}
counter--;
stm = -stm;
}

return (swap_score[0]);
}
smcracraft
 
Posts: 65
Joined: 15 Jan 2006, 05:38

Re: SEE: what you see and what you get?

Postby David Weller » 26 Jan 2006, 20:02

Just to chime in ...

I use sSEE [simple SEE] in my engine for ordering captures

if victim is protected by anything < attacker, relegate capture to back of list

These 'losers' can become killers along with other non-caps in the event that sSEE was wrong

I *think* sSEE is right far more often than it is wrong, but I never actually looked - it improved my MO a bit though

Not currently using attack tables so it is as good as it gets, I think

IDEA! Lets all drop our chess engines, and come up with a super fast and efficient way to test ALL THE POSSIBILITIES!!!!!
User avatar
David Weller
 
Posts: 135
Joined: 26 Sep 2004, 20:30
Location: USA

Re: SEE: what you see and what you get?

Postby L?szl? G?sp » 26 Jan 2006, 21:27

Hi H.G.,

I use an SSE very similar to yours which calculates a possible gain of a capture from the attack table information. The attack table itself contains only the least valueable attacker and defender (I'm planning to add more but as I see it would complicate the evaluation quite a lot and I'm not sure if its beneficial). It serves the move ordering and determines the quiescence moves (only winning or equal captures based on the SEE value (in millipawn)). I think it works well, though I have no other data to compare to, but I made statistics. The ratio between the capturing moves when they cause a beta cutoff, approximately: 1000:10:1 (1000 for winning captures, when SSE value > 50, 10 for equal(or uncertain) captures, when 50 > SEE value> -50, and 1 for losing captures, when SEE value < -50). So the captures which are considered winning captures generate beta cutoff 1000 times more than which are considered losing captures by the SEE (and I think the big part of these losing captures are sacrifices - causing cutoff -, but are really losing captures on their own) . And the quiscence nodes are around 15% of the all. I don't see that any, more complicated method would give much better result.

I think the most important is that the SEE routine shall not consider any good captures bad (thus eliminating from the quiescence search) while it can happen the other way which can cause only unnecessary search in the QS or slightly worse move ordering.


Regards,
L?szl?
L?szl? G?sp
 

Re: SEE: what you see and what you get?

Postby H.G.Muller » 26 Jan 2006, 22:56

Thanks for the useful reactions!

I agree with L?szl? that it is nice that (for the purpose of move ordering) there is a safe side to which one can err.

The terminology 'x-ray' is new to me. Does this mean the inclusion of pieces in the exchange that are behind other pieces that move in the same direction (such as doubled Rooks, or a Queen backed by a Bishop)?

Stuart's routine seems a bit inaccurate: if I got it correctly it searches through any piece on the ray it is tracing upto the very edge of the board, even if the piece it encounters is not moving in the direction of the ray. (This is probably a bug, I suppose the last break in the sliders loop was meant to take care of that, but it is unreacheable: either i<4 or i>=4, so the first two ifs catch everything. If the i-loop is continued should really depend on one of the if(DEFEND/ATTACK) catches, these should have formed in if() else if()... chain, so that if none of them catches, control falls through to the break that is now commented out. This is more efficient as well, since once a Bishop is found as defender, there will certainly not be a defender-Queen on the same square, neither can there be an attacker.)
....
else if (i < 4) {
if (DEFEND(nsq,bishop))
defender[de1++]=bishop;
else if (DEFEND(nsq,queen))
defender[de1++]=queen;
else if (ATTACK(nsq,bishop))
attacker[at1++]=bishop;
else if (ATTACK(nsq,queen))
attacker[at1++]=queen;
else break;
} else if (i >= 4) {
....

In addition the qsort() seems to sort irrespective of wheter a piece was only seen by X-rays, as if it were allowed to hop over the leading piece: A Pawn defended by a Bisshop behind a Queen and attacked by 2 Knights will be considered a bad capture because the recapture with the Bishop is sorted in front, while in fact it is a winning capture, gaining a Pawn.

Such forced sub-optimal capture order makes the sorting problem not obvious, although I am inclined to think that it never pays to involve a higher piece that is backed by a lower one earlier in the sequence than you would involve it when it is not backed, because if two unequal pieces move along the same ray, the higher one must be a Queen. So the leading piece must be normally sorted, but the trailing piece must not be allowed to overtake it, even if it is lower. Bubble-sort seems more easily adapted to do this than quick-sort, and is efficient enough since the attacker or defender array will hardly ever contain more than 3 pieces...

Pawns are also not backed by Bishops or Queens, but I suppose you are aware of that and that that is the reason why you say X-rays are only partially supported.
User avatar
H.G.Muller
 
Posts: 3453
Joined: 16 Nov 2005, 12:02
Location: Diemen, NL

Re: SEE: what you see and what you get?

Postby smcracraft » 27 Jan 2006, 04:26

H.G.Muller wrote:Thanks for the useful reactions!

I agree with L?szl? that it is nice that (for the purpose of move ordering) there is a safe side to which one can err.

The terminology 'x-ray' is new to me. Does this mean the inclusion of pieces in the exchange that are behind other pieces that move in the same direction (such as doubled Rooks, or a Queen backed by a Bishop)?

Stuart's routine seems a bit inaccurate: if I got it correctly it searches through any piece on the ray it is tracing upto the very edge of the board, even if the piece it encounters is not moving in the direction of the ray. (This is probably a bug, I suppose the last break in the sliders loop was meant to take care of that, but it is unreacheable: either i<4 or i>=4, so the first two ifs catch everything. If the i-loop is continued should really depend on one of the if(DEFEND/ATTACK) catches, these should have formed in if() else if()... chain, so that if none of them catches, control falls through to the break that is now commented out. This is more efficient as well, since once a Bishop is found as defender, there will certainly not be a defender-Queen on the same square, neither can there be an attacker.)
....
else if (i < 4) {
if (DEFEND(nsq,bishop))
defender[de1++]=bishop;
else if (DEFEND(nsq,queen))
defender[de1++]=queen;
else if (ATTACK(nsq,bishop))
attacker[at1++]=bishop;
else if (ATTACK(nsq,queen))
attacker[at1++]=queen;
else break;
} else if (i >= 4) {
....

In addition the qsort() seems to sort irrespective of wheter a piece was only seen by X-rays, as if it were allowed to hop over the leading piece: A Pawn defended by a Bisshop behind a Queen and attacked by 2 Knights will be considered a bad capture because the recapture with the Bishop is sorted in front, while in fact it is a winning capture, gaining a Pawn.

Such forced sub-optimal capture order makes the sorting problem not obvious, although I am inclined to think that it never pays to involve a higher piece that is backed by a lower one earlier in the sequence than you would involve it when it is not backed, because if two unequal pieces move along the same ray, the higher one must be a Queen. So the leading piece must be normally sorted, but the trailing piece must not be allowed to overtake it, even if it is lower. Bubble-sort seems more easily adapted to do this than quick-sort, and is efficient enough since the attacker or defender array will hardly ever contain more than 3 pieces...

Pawns are also not backed by Bishops or Queens, but I suppose you are aware of that and that that is the reason why you say X-rays are only partially supported.


Yes - there are many problems with the code and even given that the
result for the practical program was great. Absolutely great.

I forgot to include the one array:

int sliders[8] = {9, -9, 11, -11, 1, -1, 10, -10};

which shows the first half as bishop movements on a mailbox-type
array so >= 4 are rooks, < 4 are bishops.

My goal was to quickly get over the rigorous implementation details
of a chess program and get to what I wanted to research but I didn't
want to use an existing program so I had to put together a chess
program. At least it beats me and scores half-decently on a tactical
suite, though that says little these days.

I should spend a month on SEE() and get it working theoretically
correct, using a linked list for insert-sort, but I could say that about
almost all aspects of the program. It is a proverbial black hole and
these take a lot of time from the main research. However, when
something provably affects the research deliteriously it is fixed.

Thanks for your feedback.

Stuart
smcracraft
 
Posts: 65
Joined: 15 Jan 2006, 05:38

Re: SEE: what you see and what you get?

Postby Tony van Roon-Werten » 27 Jan 2006, 08:01

David Weller wrote:Just to chime in ...

I use sSEE [simple SEE] in my engine for ordering captures

if victim is protected by anything < attacker, relegate capture to back of list



Hi David.

So Bishop captures bishop that is defended by pawn is moved to the back ? That's wrong.

A much better one is: if (attacker<target) move move to the front.

Tony
Tony van Roon-Werten
 
Posts: 99
Joined: 02 Oct 2004, 15:31
Location: 's Hertogenbosch, Netherlands

Re: SEE: what you see and what you get?

Postby H.G.Muller » 27 Jan 2006, 09:38

smcracraft wrote:Yes - there are many problems with the code and even given that the result for the practical program was great. Absolutely great.

This illustrates nicely how forgiving move sorting is: compared to random ordering, you only have to systematically stick in one reasonable move among the first five to be searched, and the savings are already enormous (e.g. halving the branching ratio, so a hundred-fold speed improvement for a 7-ply search).

Of course the vast majority of cases to which the SEE is applied are simple, like a piece that is not defended, or only once, or where a gain is guaranteed by the fact that the lowest attacker was lower than the piece, and the number of defenders does not matter. This was why I thought I could get away with my simple implementation, based on just the number of attackers-defenders and the lowest one of each. Even Rebel seems to be pretty sloppy here, if I understood its algorithm correctly it makes no distinction between three atackers N+N+R or N+R+R.

For evaluation purposes, however, such approximations can totally wreck the playing strength. I would really love to have a SEE that can compete with a quiescence search in reliability. This is probably too much to ask for, but a next-best solution of a SEE that either gives me a reliable outcome of pending tactics, or tells me that he can not do it because the situation is too complicated (so that I can start a search in stead) would already be extremely useful.

One of the applications I would want to use it for, is extension decisions. My old 1984 engine made such decisions based on the previous moves, awarding moves which might be a logical continuation with a (fractional) extension. But I discovered last year, when I re-entered the chess arena, that this is a very bad idea at today's larger search depths. Many 'logical' continuations (e.g. capturing a piece of which you previously removed a defender) that one definitely should try early turn out to be losing captures, and by extending the tree behind them you waste valuable time by searching non-sense variations. This could be repaired afterwards, by allowing 'speculative extension', that is only carried through if during the (internal) iterative deepening continues to look good. But all these tricks are very incompatible with the idea of a hash table, the evaluation of the same position starts to become dependent on the moves leading to it in a zillion ways... So in my new engine I would like to be able to take the decision to extend or not purely on information derived from the position itself. If previous moves would have reduced the defense of a piece such that is no longer save, a SEE should see that without the help of the previous moves that were responsible for this condition, and it can also decide in advance the likely outcome of trying this move, and give the extension based on that knowledge.
User avatar
H.G.Muller
 
Posts: 3453
Joined: 16 Nov 2005, 12:02
Location: Diemen, NL

Re: SEE: what you see and what you get?

Postby Tord Romstad » 27 Jan 2006, 10:43

H.G.Muller wrote:How far can you go making a smart SEE without making the algorithm as expensive as a (quiescence-like) search? Are there smart algorithms around for this? I would be very happy with a SEE that would recognize pinnings (also on other pieces than the King!), (de)blocking threats (such as doubled Rooks, but also things like a Bishop stepping away from a Rook line) and overloaded defenders.


I've tried to make such a very accurate SEE in the past. Unless you are a much better programmer than me (which is certainly possible), I would recommend to avoid it. The code quickly gets very complicated and messy, and it is really hard to make it fast while avoiding bugs.

There is an alternative approach which is almost as accurate, but much easier to implement: Write a simple and fast SEE, and a function which takes a position as input and decides whether the simple SEE can be trusted for this position.

Tord
User avatar
Tord Romstad
 
Posts: 639
Joined: 09 Oct 2004, 12:49
Location: Oslo, Norway

Re: SEE: what you see and what you get?

Postby H.G.Muller » 27 Jan 2006, 11:50

Tord Romstad wrote:There is an alternative approach which is almost as accurate, but much easier to implement: Write a simple and fast SEE, and a function which takes a position as input and decides whether the simple SEE can be trusted for this position.

This seems indeed a sensible approach, that might not be too difficult. I might try something along the lines Stuart posted (an unbranched line of recaptures in the expected optimal order to a single square), but use it to mark all pieces that are required in the defense, so that it is immediately obvious if you try to use a defender a second time. If this is the case, you can simply ignore the possibly overloaded defender in the second exchange, and if there is still sufficient defense left, you are safe.

If not, you could immediately declare the SEE result as unreliable, or re-SEE the previous exchange without involving the overloaded piece, to check if it works better if you assign it to the defense of the new square. (You would mark the pieces used in each exchange by the number of the square that they were used to defend, so you would know what to redo.) Challenge in this approach would be to prevent eternal re-assignments, in cases where there are pieces A,B, and C that defend squares (a,b), (b,c) and (a,c), respectively.

Such truly complicated puzzles will hardly ever occur in the tree, most of the time the few squares that have more complicated influx than single attacker and 0 or 1 defenders is quite small, and the assignment of defenders only serves to prove that all exchanges were independent. By that logic it does not hurt you much if you refer the cases for which exchanges are coupled to an ordinary search. It would also not hurt performance then to invoke a slow complicated SEE in those cases, except that it is much harder on the programmer... :wink:

Pins and capture of defenders could be taken into account in a final scan, where you look for all assigned defenders if they are under attack, and if they are, look if they can be captured at low cost, or if another of your pieces to which this attack would extend would fall if you moved. In that case you should also not assign a defense role to this piece, (perhaps assign it to defense of its own square), or try a re-assignment if it was already assigned to defend some square.

If you allow only a (small) finite number of re-assignments (accompanied with recalculation of the SEE value for the square that it was defending before) the process will always terminate, and you really would have to go to very pathological cases before it reports a failure.
User avatar
H.G.Muller
 
Posts: 3453
Joined: 16 Nov 2005, 12:02
Location: Diemen, NL

Re: SEE: what you see and what you get?

Postby YvesLejeail » 28 Jan 2006, 10:04

I 've found yesterday that my programme was pruning the good moves in the SEE move ordering/pruning of quiescence. That's the reason why the search was very unstable. That was an error I probably made by accident...
Now the programme is much more stable :)
User avatar
YvesLejeail
 
Posts: 48
Joined: 03 Aug 2005, 17:36
Location: Pertuis, France

Re: SEE: what you see and what you get?

Postby David Weller » 28 Jan 2006, 12:29

Sigh ...

I get so excited when it appears someone may have found an error! ...

Tony - I failded to mention that this sSEE is only applied to captures of lesser pieces. >= captures just stick with their mvv/lva

Thank you.

average BF < 2.5
average FH1 > 92%


Tord - I like the way you think. Use simple see, but identify exceptions ...

Well, I need to consider some exceptions .....
Last edited by David Weller on 28 Jan 2006, 12:40, edited 1 time in total.
User avatar
David Weller
 
Posts: 135
Joined: 26 Sep 2004, 20:30
Location: USA

Re: SEE: what you see and what you get?

Postby H.G.Muller » 28 Jan 2006, 12:40

I am still very much in doubt if having a SEE is worth it. Mind you, I am not contesting the idea that move sorting is a crucial factor in determining the search efficiency. I just wonder if it is possible to achieve similar quality move sorting without the SEE.

Even a very basic SEE, e.g. the one posted above by Stuart, is comparatively expensive: it requires generation of a complete list of captures for both sides. In usual forward move generation this would produce all non-captures as a side effect, since the sliding pieces must scan though all normal moves to find the capture. (This might be different with bitboards?) Inverse move generation (to see what comes into a square, in stead of what goes out of it) is even more expensive, since you have to look in all 16 directions rather than just in the directions that the piece on that square moves. So the most efficient way to generate the attack/defense information that SEE needs as input might be to run your regular move generator, (perhaps telling it that it should use X-rays and report pseudo-captures), and then quick-sort the moves by target-square. At least if you want to apply SEE globally to the board.

A SEE that does only recaptures (no pins or overloads) is equivalent to a QS that only considers recaptures. In QS (and every position starts with a QS because of internal iterative deepening) I use a special move generator, that starts by selectively generating the captures of the piece that last moved, similar to Stuarts example. I do this by (after identifying the pawn captures, which can come fromonly 2 squares) running through the piece list, low to high, and testing if that pice can capture the target suare from a delta-vector table. This automatically produces the moves in the desired order. You might argue that playing out the exchange in search, you would have to redo the move generation for the same side many times (i.e. repeat it every 2 ply), while SEE does it only once and continues to use the generated list for the full depth of the exchange. Moves to the same square never interfere, so you can do that. This is where your gain must come from, SEE has no other advantages over QS.

On the other hand QS starts generating the captures to the target square as you would do for SEE, and as soon as it finds one, it can try it. The moves were selectively generated in the required order, so no need to generate them all and sort first. If that first capture tried turns out bad (below (lazy) eval, which in QS is a fail low), and the refutation move was a recapture by the opponent, it does not continue generating and trying recaptures with more valuable pieces. Just like SEE would also only compute the single capture order expected to be most favorable. (Only with other refutations, which can only occur in QS that also search other things than recapture, it tries other captures. This under the assumption that the refutation might have nothing to do with the captured piece being suffiiently defended, but could be tied to problems with using this particular capturer, i.e. it might have been pinned or overloaded.)

In many cases there might never be a second time that the same side moves. The opponent might not even get a chance, if there were no attacks to the square. (SEE might have wasted time making the defenders list anyway...) If the exchange is so deep that the same side does have to move again, it would go through the piece list again from the beginning, in stead of continuing at the piece it used two plies earlier. This is indeed a duplication of effort. But not entirely in vain, because it implements the x-rays: a lower-valued piece earlier in the list might now have access to the square under attack. Otherwise it was very easy to prevent, by telling qsearch() where it should start scanning the piece list. (E.g. pass this as an argument to the recursive call when you were already in the process of selective recapture generation. Even with x-rays, you can tell it when it does'nt have to bother with pawns again, because pawns never have x-ray vision. :wink: )

So it seems to me that a very simple QS that selectively generates recaptures can on the average even beat a SEE in speed. In addition it might be very easy to make it smarter than just recaptures, by continuing the search after the recaptures have been refuted and you don't consider it worth trying more of them. For instance, searching capture moves that go over the square abandoned on the previous ply (which can also be generated selectively) you would detect and take account of all pinnings.
User avatar
H.G.Muller
 
Posts: 3453
Joined: 16 Nov 2005, 12:02
Location: Diemen, NL

Re: SEE: what you see and what you get?

Postby smcracraft » 31 Jan 2006, 22:51

H.G.Muller wrote:I am still very much in doubt if having a SEE is worth it. Mind you, I am not contesting the idea that move sorting is a crucial factor in determining the search efficiency. I just wonder if it is possible to achieve similar quality move sorting without the SEE.

Even a very basic SEE, e.g. the one posted above by Stuart, is comparatively expensive: it requires generation of a complete list of captures for both sides. In usual forward move generation this would produce all non-captures as a side effect, since the sliding pieces must scan though all normal moves to find the capture. (This might be different with bitboards?) Inverse move generation (to see what comes into a square, in stead of what goes out of it) is even more expensive, since you have to look in all 16 directions rather than just in the directions that the piece on that square moves. So the most efficient way to generate the attack/defense information that SEE needs as input might be to run your regular move generator, (perhaps telling it that it should use X-rays and report pseudo-captures), and then quick-sort the moves by target-square. At least if you want to apply SEE globally to the board.

A SEE that does only recaptures (no pins or overloads) is equivalent to a QS that only considers recaptures. In QS (and every position starts with a QS because of internal iterative deepening) I use a special move generator, that starts by selectively generating the captures of the piece that last moved, similar to Stuarts example. I do this by (after identifying the pawn captures, which can come fromonly 2 squares) running through the piece list, low to high, and testing if that pice can capture the target suare from a delta-vector table. This automatically produces the moves in the desired order. You might argue that playing out the exchange in search, you would have to redo the move generation for the same side many times (i.e. repeat it every 2 ply), while SEE does it only once and continues to use the generated list for the full depth of the exchange. Moves to the same square never interfere, so you can do that. This is where your gain must come from, SEE has no other advantages over QS.

On the other hand QS starts generating the captures to the target square as you would do for SEE, and as soon as it finds one, it can try it. The moves were selectively generated in the required order, so no need to generate them all and sort first. If that first capture tried turns out bad (below (lazy) eval, which in QS is a fail low), and the refutation move was a recapture by the opponent, it does not continue generating and trying recaptures with more valuable pieces. Just like SEE would also only compute the single capture order expected to be most favorable. (Only with other refutations, which can only occur in QS that also search other things than recapture, it tries other captures. This under the assumption that the refutation might have nothing to do with the captured piece being suffiiently defended, but could be tied to problems with using this particular capturer, i.e. it might have been pinned or overloaded.)

In many cases there might never be a second time that the same side moves. The opponent might not even get a chance, if there were no attacks to the square. (SEE might have wasted time making the defenders list anyway...) If the exchange is so deep that the same side does have to move again, it would go through the piece list again from the beginning, in stead of continuing at the piece it used two plies earlier. This is indeed a duplication of effort. But not entirely in vain, because it implements the x-rays: a lower-valued piece earlier in the list might now have access to the square under attack. Otherwise it was very easy to prevent, by telling qsearch() where it should start scanning the piece list. (E.g. pass this as an argument to the recursive call when you were already in the process of selective recapture generation. Even with x-rays, you can tell it when it does'nt have to bother with pawns again, because pawns never have x-ray vision. :wink: )

So it seems to me that a very simple QS that selectively generates recaptures can on the average even beat a SEE in speed. In addition it might be very easy to make it smarter than just recaptures, by continuing the search after the recaptures have been refuted and you don't consider it worth trying more of them. For instance, searching capture moves that go over the square abandoned on the previous ply (which can also be generated selectively) you would detect and take account of all pinnings.


Suggest implementing it and testing in your program. That is the
final arbiter of whether it is worth it to you personally.

Stuart
smcracraft
 
Posts: 65
Joined: 15 Jan 2006, 05:38


Return to Programming and Technical Discussions

Who is online

Users browsing this forum: No registered users and 8 guests