If I disable the line
bonus += mi->material_value();
in SF 2.0.1 sources, I get a decrease of only 1 ELO point after 20,000 hyper bullet games.
Especially with Kaufman's and Romstad's formulas regarding material imbalances in mind this was a highly unexpected result. Following the maxim to remove all code which doesn't add ELO, I wonder whether material evaluation could be a serious candidate for code removal.
Material???
Moderators: hgm, Rebel, chrisw, Ras, hgm, chrisw, Rebel, Ras
-
- Posts: 2684
- Joined: Sat Jun 14, 2008 9:17 pm
Re: Material???
I think is not, we have tried to remove independently both Kaufmann and Tord stuff we have also tried to change somehow the coefficients and always we got a weaker engine, although by not much say 5 ELO with Kaufmann's and 10-15 ELO with Tord's.Ralph Stoesser wrote:If I disable the line
bonus += mi->material_value();
in SF 2.0.1 sources, I get a decrease of only 1 ELO point after 20,000 hyper bullet games.
Especially with Kaufman's and Romstad's formulas regarding material imbalances in mind this was a highly unexpected result. Following the maxim to remove all code which doesn't add ELO, I wonder whether material evaluation could be a serious candidate for code removal.
I suspect you should validate the test at longer time controls.
-
- Posts: 408
- Joined: Sat Mar 06, 2010 9:28 am
Re: Material???
But you haven't removed the material value bonus completely like I did, right? That's understandable, because it sounds like an unsane idea to try it. The more surprising the result. If I would be able to automatically re-tune the eval params with material disabled, I would try it right now.
-
- Posts: 408
- Joined: Sat Mar 06, 2010 9:28 am
Re: Material???
60,000 games @10 sec/game: +8295, -8443, =13998
ELO difference: -2.
Not that much.
ELO difference: -2.
Not that much.
-
- Posts: 4631
- Joined: Sun Mar 12, 2006 2:40 am
- Full name: Eelco de Groot
Idea for Tord?
Hoping that the material imbalance tables do not get axed entirely, and Stockfish will still know that the Bishop pair is an important chessterm, and in case somone might try retuning all this again, here is a version with a small change in the 2.0.1 version coefficients used, I think it could be easily transferred to 2.1.1, if necessary. It is not elegant I suppose to do it this way but I did not want to try to retune everything by myself, it is impossible anyway to tune on a single computer, and changing the computation that exists would mean new coefficients in the whole table, probably. So there is just some, at present very small, adjustment added on top of the existing calculation, if there is no piece present of any of the 6 possible types.mcostalba wrote:I think is not, we have tried to remove independently both Kaufmann and Tord stuff we have also tried to change somehow the coefficients and always we got a weaker engine, although by not much say 5 ELO with Kaufmann's and 10-15 ELO with Tord's.Ralph Stoesser wrote:If I disable the line
bonus += mi->material_value();
in SF 2.0.1 sources, I get a decrease of only 1 ELO point after 20,000 hyper bullet games.
Especially with Kaufman's and Romstad's formulas regarding material imbalances in mind this was a highly unexpected result. Following the maxim to remove all code which doesn't add ELO, I wonder whether material evaluation could be a serious candidate for code removal.
I suspect you should validate the test at longer time controls.
The original table assumes there is no synergy possible in that case and the calculation of change in matValue from this piecetype is zero.
That is all fine in theory but I think there is a small problem when, in case the piecenumber is not zero, all the "synergy" calculations in Tord's second order material imbalance table, of this piecetype with the other pieces, of your own colour or of the opponent, are strictly linear with the number of pieces of either type. I think in that case you are ignoring the possible redundancies, except of course for the separate bigger terms introduced by Larry Kaufman;
Code: Select all
// Redundancy of major pieces, formula based on Kaufman's paper
// "The Evaluation of Material Imbalances in Chess"
// http://mywebpages.comcast.net/danheisman/Articles/evaluation_of_material_imbalance.htm
So at the moment I just tried to add a "hack", a penalty mostly, in case the piece number is zero, the idea is that this increases the calculated difference in general between all the cases where a piece number is zero and piece number is one. The effect should be that the synergies are no longer necessarily linear with the number of pieces of either type. (The interaction of any piece type with itself is not linear because of the second order term that describes this, but all other interactions are of the type cXY where X is one piecetype and Y is another, c is a constant.)
Modified material.cpp 2.0 version:
Code: Select all
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
////
//// Includes
////
#include <cassert>
#include <cstring>
#include <map>
#include "material.h"
using namespace std;
////
//// Local definitions
////
namespace {
// Values modified by Joona Kiiski
const Value MidgameLimit = Value(15581);
const Value EndgameLimit = Value(3998);
// Polynomial material balance parameters
const Value RedundantQueenPenalty = Value(320);
const Value RedundantRookPenalty = Value(554);
const int LinearCoefficients[6] = { 1617, -162, -1172, -190, 105, 26 };
const int QuadraticCoefficientsSameColor[][8] = {
{ 7, 7, 7, 7, 7, 7 }, { 39, 2, 7, 7, 7, 7 }, { 35, 271, -4, 7, 7, 7 },
{ 7, 25, 4, 7, 7, 7 }, { -27, -2, 46, 100, 56, 7 }, { 58, 29, 83, 148, -3, -25 } };
const int QuadraticCoefficientsOppositeColor[][8] = {
{ 41, 41, 41, 41, 41, 41 }, { 37, 41, 41, 41, 41, 41 }, { 10, 62, 41, 41, 41, 41 },
{ 57, 64, 39, 41, 41, 41 }, { 50, 40, 23, -22, 41, 41 }, { 106, 101, 3, 151, 171, 41 } };
const int MissingPiecetypeCoefficientsSameColor[][8] = {
{ 3, 1, 1, 1, 1, 1 }, { 1, 3, 1, 1, 1, 1 }, { 1, 1, 3, 1, 1, 1 },
{ 1, 1, 1, 3, 1, 1 }, { 1, 1, 1, 1, 3, 1 }, { 1, 1, 1, 1, 1, 3 } };
const int MissingPiecetypeCoefficientsOppositeColor[][8] = {
{ 3, 1, 1, 1, 1, 1 }, { 1, 3, 1, 1, 1, 1 }, { 1, 1, 3, 1, 1, 1 },
{ 1, 1, 1, 3, 1, 1 }, { 1, 1, 1, 1, 3, 1 }, { 1, 1, 1, 1, 1, 3 } };
typedef EndgameEvaluationFunctionBase EF;
typedef EndgameScalingFunctionBase SF;
typedef map<Key, EF*> EFMap;
typedef map<Key, SF*> SFMap;
// Endgame evaluation and scaling functions accessed direcly and not through
// the function maps because correspond to more then one material hash key.
EvaluationFunction<KmmKm> EvaluateKmmKm[] = { EvaluationFunction<KmmKm>(WHITE), EvaluationFunction<KmmKm>(BLACK) };
EvaluationFunction<KXK> EvaluateKXK[] = { EvaluationFunction<KXK>(WHITE), EvaluationFunction<KXK>(BLACK) };
ScalingFunction<KBPsK> ScaleKBPsK[] = { ScalingFunction<KBPsK>(WHITE), ScalingFunction<KBPsK>(BLACK) };
ScalingFunction<KQKRPs> ScaleKQKRPs[] = { ScalingFunction<KQKRPs>(WHITE), ScalingFunction<KQKRPs>(BLACK) };
ScalingFunction<KPsK> ScaleKPsK[] = { ScalingFunction<KPsK>(WHITE), ScalingFunction<KPsK>(BLACK) };
ScalingFunction<KPKP> ScaleKPKP[] = { ScalingFunction<KPKP>(WHITE), ScalingFunction<KPKP>(BLACK) };
// Helper templates used to detect a given material distribution
template<Color Us> bool is_KXK(const Position& pos) {
const Color Them = (Us == WHITE ? BLACK : WHITE);
return pos.non_pawn_material(Them) == VALUE_ZERO
&& pos.piece_count(Them, PAWN) == 0
&& pos.non_pawn_material(Us) >= RookValueMidgame;
}
template<Color Us> bool is_KBPsK(const Position& pos) {
return pos.non_pawn_material(Us) == BishopValueMidgame
&& pos.piece_count(Us, BISHOP) == 1
&& pos.piece_count(Us, PAWN) >= 1;
}
template<Color Us> bool is_KQKRPs(const Position& pos) {
const Color Them = (Us == WHITE ? BLACK : WHITE);
return pos.piece_count(Us, PAWN) == 0
&& pos.non_pawn_material(Us) == QueenValueMidgame
&& pos.piece_count(Us, QUEEN) == 1
&& pos.piece_count(Them, ROOK) == 1
&& pos.piece_count(Them, PAWN) >= 1;
}
}
////
//// Classes
////
/// EndgameFunctions class stores endgame evaluation and scaling functions
/// in two std::map. Because STL library is not guaranteed to be thread
/// safe even for read access, the maps, although with identical content,
/// are replicated for each thread. This is faster then using locks.
class EndgameFunctions {
public:
EndgameFunctions();
~EndgameFunctions();
template<class T> T* get(Key key) const;
private:
template<class T> void add(const string& keyCode);
static Key buildKey(const string& keyCode);
static const string swapColors(const string& keyCode);
// Here we store two maps, for evaluate and scaling functions...
pair<EFMap, SFMap> maps;
// ...and here is the accessing template function
template<typename T> const map<Key, T*>& get() const;
};
// Explicit specializations of a member function shall be declared in
// the namespace of which the class template is a member.
template<> const EFMap& EndgameFunctions::get<EF>() const { return maps.first; }
template<> const SFMap& EndgameFunctions::get<SF>() const { return maps.second; }
////
//// Functions
////
/// MaterialInfoTable c'tor and d'tor, called once by each thread
MaterialInfoTable::MaterialInfoTable() {
entries = new MaterialInfo[MaterialTableSize];
funcs = new EndgameFunctions();
if (!entries || !funcs)
{
cerr << "Failed to allocate " << MaterialTableSize * sizeof(MaterialInfo)
<< " bytes for material hash table." << endl;
exit(EXIT_FAILURE);
}
memset(entries, 0, MaterialTableSize * sizeof(MaterialInfo));
}
MaterialInfoTable::~MaterialInfoTable() {
delete funcs;
delete [] entries;
}
/// MaterialInfoTable::game_phase() calculates the phase given the current
/// position. Because the phase is strictly a function of the material, it
/// is stored in MaterialInfo.
Phase MaterialInfoTable::game_phase(const Position& pos) {
Value npm = pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK);
if (npm >= MidgameLimit)
return PHASE_MIDGAME;
if (npm <= EndgameLimit)
return PHASE_ENDGAME;
return Phase(((npm - EndgameLimit) * 128) / (MidgameLimit - EndgameLimit));
}
/// MaterialInfoTable::get_material_info() takes a position object as input,
/// computes or looks up a MaterialInfo object, and returns a pointer to it.
/// If the material configuration is not already present in the table, it
/// is stored there, so we don't have to recompute everything when the
/// same material configuration occurs again.
MaterialInfo* MaterialInfoTable::get_material_info(const Position& pos) {
Key key = pos.get_material_key();
unsigned index = unsigned(key & (MaterialTableSize - 1));
MaterialInfo* mi = entries + index;
// If mi->key matches the position's material hash key, it means that we
// have analysed this material configuration before, and we can simply
// return the information we found the last time instead of recomputing it.
if (mi->key == key)
return mi;
// Clear the MaterialInfo object, and set its key
memset(mi, 0, sizeof(MaterialInfo));
mi->factor[WHITE] = mi->factor[BLACK] = (uint8_t)SCALE_FACTOR_NORMAL;
mi->key = key;
// Store game phase
mi->gamePhase = MaterialInfoTable::game_phase(pos);
// Let's look if we have a specialized evaluation function for this
// particular material configuration. First we look for a fixed
// configuration one, then a generic one if previous search failed.
if ((mi->evaluationFunction = funcs->get<EF>(key)) != NULL)
return mi;
if (is_KXK<WHITE>(pos) || is_KXK<BLACK>(pos))
{
mi->evaluationFunction = is_KXK<WHITE>(pos) ? &EvaluateKXK[WHITE] : &EvaluateKXK[BLACK];
return mi;
}
if ( pos.pieces(PAWN) == EmptyBoardBB
&& pos.pieces(ROOK) == EmptyBoardBB
&& pos.pieces(QUEEN) == EmptyBoardBB)
{
// Minor piece endgame with at least one minor piece per side and
// no pawns. Note that the case KmmK is already handled by KXK.
assert((pos.pieces(KNIGHT, WHITE) | pos.pieces(BISHOP, WHITE)));
assert((pos.pieces(KNIGHT, BLACK) | pos.pieces(BISHOP, BLACK)));
if ( pos.piece_count(WHITE, BISHOP) + pos.piece_count(WHITE, KNIGHT) <= 2
&& pos.piece_count(BLACK, BISHOP) + pos.piece_count(BLACK, KNIGHT) <= 2)
{
mi->evaluationFunction = &EvaluateKmmKm[WHITE];
return mi;
}
}
// OK, we didn't find any special evaluation function for the current
// material configuration. Is there a suitable scaling function?
//
// We face problems when there are several conflicting applicable
// scaling functions and we need to decide which one to use.
SF* sf;
if ((sf = funcs->get<SF>(key)) != NULL)
{
mi->scalingFunction[sf->color()] = sf;
return mi;
}
// Generic scaling functions that refer to more then one material
// distribution. Should be probed after the specialized ones.
// Note that these ones don't return after setting the function.
if (is_KBPsK<WHITE>(pos))
mi->scalingFunction[WHITE] = &ScaleKBPsK[WHITE];
if (is_KBPsK<BLACK>(pos))
mi->scalingFunction[BLACK] = &ScaleKBPsK[BLACK];
if (is_KQKRPs<WHITE>(pos))
mi->scalingFunction[WHITE] = &ScaleKQKRPs[WHITE];
else if (is_KQKRPs<BLACK>(pos))
mi->scalingFunction[BLACK] = &ScaleKQKRPs[BLACK];
if (pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) == VALUE_ZERO)
{
if (pos.piece_count(BLACK, PAWN) == 0)
{
assert(pos.piece_count(WHITE, PAWN) >= 2);
mi->scalingFunction[WHITE] = &ScaleKPsK[WHITE];
}
else if (pos.piece_count(WHITE, PAWN) == 0)
{
assert(pos.piece_count(BLACK, PAWN) >= 2);
mi->scalingFunction[BLACK] = &ScaleKPsK[BLACK];
}
else if (pos.piece_count(WHITE, PAWN) == 1 && pos.piece_count(BLACK, PAWN) == 1)
{
// This is a special case because we set scaling functions
// for both colors instead of only one.
mi->scalingFunction[WHITE] = &ScaleKPKP[WHITE];
mi->scalingFunction[BLACK] = &ScaleKPKP[BLACK];
}
}
// Compute the space weight
if (pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) >=
2*QueenValueMidgame + 4*RookValueMidgame + 2*KnightValueMidgame)
{
int minorPieceCount = pos.piece_count(WHITE, KNIGHT)
+ pos.piece_count(BLACK, KNIGHT)
+ pos.piece_count(WHITE, BISHOP)
+ pos.piece_count(BLACK, BISHOP);
mi->spaceWeight = minorPieceCount * minorPieceCount;
}
// Evaluate the material balance
const int pieceCount[2][8] = {
{ pos.piece_count(WHITE, BISHOP) > 1, pos.piece_count(WHITE, PAWN), pos.piece_count(WHITE, KNIGHT),
pos.piece_count(WHITE, BISHOP), pos.piece_count(WHITE, ROOK), pos.piece_count(WHITE, QUEEN) },
{ pos.piece_count(BLACK, BISHOP) > 1, pos.piece_count(BLACK, PAWN), pos.piece_count(BLACK, KNIGHT),
pos.piece_count(BLACK, BISHOP), pos.piece_count(BLACK, ROOK), pos.piece_count(BLACK, QUEEN) } };
Color c, them;
int sign, pt1, pt2, pc;
int32_t v = 0, vv = 0, matValue = 0;
for (c = WHITE, sign = 1; c <= BLACK; c++, sign = -sign)
{
// No pawns makes it difficult to win, even with a material advantage
if ( pos.piece_count(c, PAWN) == 0
&& pos.non_pawn_material(c) - pos.non_pawn_material(opposite_color(c)) <= BishopValueMidgame)
{
if ( pos.non_pawn_material(c) == pos.non_pawn_material(opposite_color(c))
|| pos.non_pawn_material(c) < RookValueMidgame)
mi->factor[c] = 0;
else
{
switch (pos.piece_count(c, BISHOP)) {
case 2:
mi->factor[c] = 32;
break;
case 1:
mi->factor[c] = 12;
break;
case 0:
mi->factor[c] = 6;
break;
}
}
}
// Redundancy of major pieces, formula based on Kaufman's paper
// "The Evaluation of Material Imbalances in Chess"
// http://mywebpages.comcast.net/danheisman/Articles/evaluation_of_material_imbalance.htm
if (pieceCount[c][ROOK] >= 1)
matValue -= sign * ((pieceCount[c][ROOK] - 1) * RedundantRookPenalty + pieceCount[c][QUEEN] * RedundantQueenPenalty);
them = opposite_color(c);
v = 0;
// Second-degree polynomial material imbalance by Tord Romstad
//
// We use PIECE_TYPE_NONE as a place holder for the bishop pair "extended piece",
// this allow us to be more flexible in defining bishop pair bonuses.
for (pt1 = PIECE_TYPE_NONE; pt1 <= QUEEN; pt1++)
{
pc = pieceCount[c][pt1];
if (!pc)
{
for (pt2 = PIECE_TYPE_NONE; pt2 <= QUEEN; pt2++)
v -= (3 - pieceCount[c][pt2]) * MissingPiecetypeCoefficientsSameColor[pt1][pt2]
+ (pieceCount[them][pt2]) * MissingPiecetypeCoefficientsOppositeColor[pt1][pt2];
}
if (pc)
{
vv = LinearCoefficients[pt1];
for (pt2 = PIECE_TYPE_NONE; pt2 <= pt1; pt2++)
{
vv += pieceCount[c][pt2] * QuadraticCoefficientsSameColor[pt1][pt2]
+ pieceCount[them][pt2] * QuadraticCoefficientsOppositeColor[pt1][pt2];
}
v += pc * vv;
}
}
matValue += sign * v;
}
mi->value = (int16_t)(matValue / 16);
return mi;
}
/// EndgameFunctions member definitions
EndgameFunctions::EndgameFunctions() {
add<EvaluationFunction<KNNK> >("KNNK");
add<EvaluationFunction<KPK> >("KPK");
add<EvaluationFunction<KBNK> >("KBNK");
add<EvaluationFunction<KRKP> >("KRKP");
add<EvaluationFunction<KRKB> >("KRKB");
add<EvaluationFunction<KRKN> >("KRKN");
add<EvaluationFunction<KQKR> >("KQKR");
add<EvaluationFunction<KBBKN> >("KBBKN");
add<ScalingFunction<KNPK> >("KNPK");
add<ScalingFunction<KRPKR> >("KRPKR");
add<ScalingFunction<KBPKB> >("KBPKB");
add<ScalingFunction<KBPPKB> >("KBPPKB");
add<ScalingFunction<KBPKN> >("KBPKN");
add<ScalingFunction<KRPPKRP> >("KRPPKRP");
}
EndgameFunctions::~EndgameFunctions() {
for (EFMap::const_iterator it = maps.first.begin(); it != maps.first.end(); ++it)
delete it->second;
for (SFMap::const_iterator it = maps.second.begin(); it != maps.second.end(); ++it)
delete it->second;
}
Key EndgameFunctions::buildKey(const string& keyCode) {
assert(keyCode.length() > 0 && keyCode.length() < 8);
assert(keyCode[0] == 'K');
string fen;
bool upcase = false;
// Build up a fen string with the given pieces, note that
// the fen string could be of an illegal position.
for (size_t i = 0; i < keyCode.length(); i++)
{
if (keyCode[i] == 'K')
upcase = !upcase;
fen += char(upcase ? toupper(keyCode[i]) : tolower(keyCode[i]));
}
fen += char(8 - keyCode.length() + '0');
fen += "/8/8/8/8/8/8/8 w - -";
return Position(fen, false, 0).get_material_key();
}
const string EndgameFunctions::swapColors(const string& keyCode) {
// Build corresponding key for the opposite color: "KBPKN" -> "KNKBP"
size_t idx = keyCode.find('K', 1);
return keyCode.substr(idx) + keyCode.substr(0, idx);
}
template<class T>
void EndgameFunctions::add(const string& keyCode) {
typedef typename T::Base F;
typedef map<Key, F*> M;
const_cast<M&>(get<F>()).insert(pair<Key, F*>(buildKey(keyCode), new T(WHITE)));
const_cast<M&>(get<F>()).insert(pair<Key, F*>(buildKey(swapColors(keyCode)), new T(BLACK)));
}
template<class T>
T* EndgameFunctions::get(Key key) const {
typename map<Key, T*>::const_iterator it = get<T>().find(key);
return it != get<T>().end() ? it->second : NULL;
}
Code: Select all
const int MissingPiecetypeCoefficientsSameColor[][8] = {
{ 3, 1, 1, 1, 1, 1 }, { 1, 3, 1, 1, 1, 1 }, { 1, 1, 3, 1, 1, 1 },
{ 1, 1, 1, 3, 1, 1 }, { 1, 1, 1, 1, 3, 1 }, { 1, 1, 1, 1, 1, 3 } };
const int MissingPiecetypeCoefficientsOppositeColor[][8] = {
{ 3, 1, 1, 1, 1, 1 }, { 1, 3, 1, 1, 1, 1 }, { 1, 1, 3, 1, 1, 1 },
{ 1, 1, 1, 3, 1, 1 }, { 1, 1, 1, 1, 3, 1 }, { 1, 1, 1, 1, 1, 3 } };
Code: Select all
int32_t v = 0, vv = 0, matValue = 0;
Code: Select all
if (!pc)
{
for (pt2 = PIECE_TYPE_NONE; pt2 <= QUEEN; pt2++)
v -= (3 - pieceCount[c][pt2]) * MissingPiecetypeCoefficientsSameColor[pt1][pt2]
+ (pieceCount[them][pt2]) * MissingPiecetypeCoefficientsOppositeColor[pt1][pt2];
}
if (pc)
{
vv = LinearCoefficients[pt1];
for (pt2 = PIECE_TYPE_NONE; pt2 <= pt1; pt2++)
{
vv += pieceCount[c][pt2] * QuadraticCoefficientsSameColor[pt1][pt2]
+ pieceCount[them][pt2] * QuadraticCoefficientsOppositeColor[pt1][pt2];
}
v += pc * vv;
}
Eelco
Debugging is twice as hard as writing the code in the first
place. Therefore, if you write the code as cleverly as possible, you
are, by definition, not smart enough to debug it.
-- Brian W. Kernighan
place. Therefore, if you write the code as cleverly as possible, you
are, by definition, not smart enough to debug it.
-- Brian W. Kernighan