Chan Rasjid wrote:A chess program computes the nps to give an idea how well the program is implemented and also if certain features are costly. Other things being equal, a high nps is a very important target - it means the program is implemented very efficiently and could process many nodes per second. If nps of different programs could be compared, it would be better as then we can know if we are doing things as best as is possible.
Because there could be different views on nodes or the way to count nodes, the simplest way most programs do is to increment the nodes counter at the top of a new search representing a new node visited - very likely, programs do not count wrongly in this manner! We don't have to always keep track of when and where if we edit our program with new makemove()s in our program.
Counting nodes at the start of search simplifies many things. The programmer wants to know how fast it processes a node in general and only those that require special processing. Because of this, not counting pruned nodes is trivial; it is also the more normal way most would count nodes. One of the most important thing nps tell us is how much we are doing with evaluation? Too much brings down the nps drastically and too little means we could afford to put more knowledge in our program. So the nps is like a figure to tell us what next we should do to improve our program.
Rasjid.
That's all well and good, but this isn't about simplicity, it is about accuracy. Since the classic AI definition of "node" is "chess position" I'm trying to count nodes in a way that is consistent with that definition.
I have not looked at how stockfish does pruning. If, as Volker says, they make their decision to prune a move before they make the move, then I would not count that pruned move as a node since it did not result in a new position, having never been made. In my case, making and unmaking a move has a definite cost, as does asking the question "does this move check the opponent?". I first Make() the move, which produces a new position/node, I then do additional work to determine what to do. And if I prune, I have to Unmake() the move and loop again.
In Quiesce() I do not count an entry to Quiesce() as a node. That was counted at the previous ply where I made the move to produce the position that Quiesce() receives. If Quiesce() makes any moves to search deeper, those are counted. Otherwise a call to Quiesce() that results in no captures does not get counted since it was already counted when the move was made.
If you don't want to count pruned moves as nodes, that's ok. But if you make/unmake a move, determine if it delivers check, possibly do a static evaluation to make pruning or reduction or extension decisions, and then decide to prune that move, not counting it is incorrect by the classic definition of node. Doesn't matter how many other programs use a different definition, a node is a new position, PI is 3.141592, branching factor in chess can never average less than 2 over a game, effective branching factor can, etc. Common and consistent terminology is critical in reporting results. I don't think my numbers are inflated at all. And I'm not going to try to change my numbers to match those that are not calculating them correctly in the strictest sense of the term.
If the goal is to produce the lowest possible NPS, then not counting a node whenever possible will produce that. If the goal is to produce the highest possible NPS, then counting things that are really not nodes can help. I only want to count "real" nodes... how many positions did I encounter during that search? the node count is exact for that question.