centipawn score taking armageddon into account

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

User avatar
xr_a_y
Posts: 1871
Joined: Sat Nov 25, 2017 2:28 pm
Location: France

centipawn score taking armageddon into account

Post by xr_a_y »

Hi all,

I'm trying to make Minic compatible with Armageddon (draw is a win for Black).
To do so I first convert centipawn to WDL values (despite the "polemic" I'm using the same script as what Vondele did for SF, using Minic game to fit a 3rd order polynomial on the current ply for "a" and "b" coefficient of a shifted sigmoid). And I added a fonction to convert back a win probability to a score.

I'm using that if armageddon is enabled. If it is black turn I add draw probability to win probability and convert this to a new corrected cp score.
If it is White turn, I add draw probability to lose probability and convert this to a new corrected opposite cp score.

Maybe just the code speaks better

Code: Select all

// idea from Stockfish
namespace WDL{
    // Coefficients of a 3rd order polynomial fit based on Minic test data (from CCRL, CEGT, FGRL)
    const double as[] = {-13.65744616,  94.04894005,  -95.05180396,  84.853482690};
    const double bs[] = {-10.78187987,  77.22626799, -132.72201029,  122.54185402};
}

inline double toWDLModel(ScoreType v, DepthType ply) {
    // limit input ply and rescale
    const double m = std::min(DepthType(256), ply) / 32.0;
    const double a = (((WDL::as[0] * m + WDL::as[1]) * m + WDL::as[2]) * m) + WDL::as[3];
    const double b = (((WDL::bs[0] * m + WDL::bs[1]) * m + WDL::bs[2]) * m) + WDL::bs[3];    
    // clamp score
    const double x = std::clamp(double((a - v) / b), -600.0, 600.0);
    // win probability from 0 to 1000
    return 0.5 + 1000. / (1. + std::exp(x));
}

inline ScoreType fromWDLModel(double w, DepthType ply) {
    // limit input ply and rescale
    const double m = std::min(DepthType(256), ply) / 32.0;
    const double a = (((WDL::as[0] * m + WDL::as[1]) * m + WDL::as[2]) * m) + WDL::as[3];
    const double b = (((WDL::bs[0] * m + WDL::bs[1]) * m + WDL::bs[2]) * m) + WDL::bs[3];    
    const double s = a - b * std::log(1000./(std::max(w,0.5+std::numeric_limits<double>::epsilon())-0.5) - 1.);
    return ScoreType(std::clamp(s, double(-MATE + ply) , double(MATE - ply + 1)) );
}

ScoreType armageddonScore(ScoreType score, DepthType ply, Color c){
   if ( ! DynamicConfig::armageddon) return score;
   double wdlW = toWDLModel(score,ply);
   double wdlL = toWDLModel(-score,ply);
   const double wdlD = 1000 - wdlW - wdlL;
   if (c == Co_Black ){
      wdlW += wdlD;
      score = fromWDLModel(wdlW,ply);   
   }
   else{
      wdlL += wdlD;
      score = -fromWDLModel(wdlL,ply);   
   }
   return score;
}
But using this Minic is dramatically loosing versus Seer "Armageddon" that is using a real WDL model as output layer of its net.

I'm tracking some bugs.
For instance, it seems to me that the clamping in toWDLModel (you can use 600 or 800 as bound) is necessary so that std::exp don't return inf.
Next thing is that std::log(1000./(std::max(w,0.5+std::numeric_limits<double>::epsilon())-0.5) - 1.); that we shall manipulate carefully.

Of course draw score is handle separately also taking armageddon onto account.


Any idea of better formulas to take into account without struggling to much ?
User avatar
xr_a_y
Posts: 1871
Joined: Sat Nov 25, 2017 2:28 pm
Location: France

Re: centipawn score taking armageddon into account

Post by xr_a_y »

Ok, I'm feeling a bit dumb here ...:oops: :oops: :oops:

All of this is just in fact a shift of the score based on the "a" constant above ...

https://ibb.co/NK9Bbc0

This will do the job probably

Code: Select all

inline ScoreType shiftArmageddon(ScoreType v, DepthType ply, Color c){
    // limit input ply and rescale
    const double m = std::min(DepthType(256), ply) / 32.0;
    const double a = (((WDL::as[0] * m + WDL::as[1]) * m + WDL::as[2]) * m) + WDL::as[3];
    if ( c == Co_White ) return ScoreType(v - 2*a);
    else                 return ScoreType(v + 2*a);
}
So quick and dirty Armageddon support is just a shift of score depending on ply ... but this doesn't work well of course ...