Skip to content

Commit

Permalink
Merge with remote branch 'origin/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
eduherminio committed May 11, 2024
2 parents 163f18d + e2a6f15 commit ed91435
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 153 deletions.
3 changes: 3 additions & 0 deletions src/Lynx.Cli/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"GeneralSettings": {
"EnableTuning": true
},
"EngineSettings": {
"DefaultMaxDepth": 3,
"TranspositionTableEnabled": true,
Expand Down
3 changes: 2 additions & 1 deletion src/Lynx.Cli/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
// Settings that affect the executable behavior
"GeneralSettings": {
"EnableLogging": true // logging can be completely disablesd, both console and file, setting this to false. Alternatively, one or more "NLog.rules" can be removed/tweaked
"EnableLogging": true, // logging can be completely disablesd, both console and file, setting this to false. Alternatively, one or more "NLog.rules" can be removed/tweaked
"EnableTuning": false // Exposes search tunable values via UCI. Intended to be used for developer only purposes
},

// Settings that affect the engine behavior
Expand Down
2 changes: 2 additions & 0 deletions src/Lynx/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ public static int Hash
public sealed class GeneralSettings
{
public bool EnableLogging { get; set; } = false;

public bool EnableTuning { get; set; } = false;
}

public sealed class EngineSettings
Expand Down
150 changes: 148 additions & 2 deletions src/Lynx/SPSAAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Numerics;
using NLog;
using System.Numerics;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
Expand Down Expand Up @@ -47,7 +48,7 @@ public string ToOBString(PropertyInfo property)
return $"{property.Name}, int, {val}, {MinValue}, {MaxValue}, {Step}, {Configuration.EngineSettings.SPSA_OB_R_end}";
}

private static T GetPropertyValue(PropertyInfo property)
internal static T GetPropertyValue(PropertyInfo property)
{
T val = (T)property.GetValue(Configuration.EngineSettings)!;

Expand Down Expand Up @@ -83,3 +84,148 @@ public string ToOBPrettyString(PropertyInfo property)
#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
}
}

public static class SPSAAttributeHelpers
{
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();

public static IEnumerable<string> GenerateOpenBenchStrings()
{
foreach (var property in typeof(EngineSettings).GetProperties())
{
var genericType = typeof(SPSAAttribute<>);
var spsaArray = property.GetCustomAttributes(genericType);
var count = spsaArray.Count();

if (count > 1)
{
_logger.Warn("Property {0} has more than one [{1}]", property.Name, genericType.Name);
}

if (count == 0)
{
continue;
}

var genericSpsa = spsaArray.First();
if (genericSpsa is SPSAAttribute<int> intSpsa)
{
yield return intSpsa.ToOBString(property);
}
else if (genericSpsa is SPSAAttribute<double> doubleSpsa)
{
yield return doubleSpsa.ToOBString(property);
}
else
{
_logger.Error("Property {0} has a [{1}] defined with unsupported type <{2}>", property.Name, genericSpsa);
}
}
}

public static IEnumerable<string> GenerateOpenBenchPrettyStrings()
{
foreach (var property in typeof(EngineSettings).GetProperties())
{
var genericType = typeof(SPSAAttribute<>);
var spsaArray = property.GetCustomAttributes(genericType);
var count = spsaArray.Count();

if (count > 1)
{
_logger.Warn("Property {0} has more than one [{1}]", property.Name, genericType.Name);
}

if (count == 0)
{
continue;
}

var genericSpsa = spsaArray.First();
if (genericSpsa is SPSAAttribute<int> intSpsa)
{
yield return intSpsa.ToOBPrettyString(property);
}
else if (genericSpsa is SPSAAttribute<double> doubleSpsa)
{
yield return doubleSpsa.ToOBPrettyString(property);
}
else
{
_logger.Error("Property {0} has a [{1}] defined with unsupported type <{2}>", property.Name, genericSpsa);
}
}
}

public static IEnumerable<KeyValuePair<string, JsonNode?>> GenerateWeatherFactoryStrings()
{
foreach (var property in typeof(EngineSettings).GetProperties())
{
var genericType = typeof(SPSAAttribute<>);
var spsaArray = property.GetCustomAttributes(genericType);
var count = spsaArray.Count();

if (count > 1)
{
_logger.Warn("Property {0} has more than one [{1}]", property.Name, genericType.Name);
}

if (count == 0)
{
continue;
}

var genericSpsa = spsaArray.First();
if (genericSpsa is SPSAAttribute<int> intSpsa)
{
yield return intSpsa.ToWeatherFactoryString(property);
}
else if (genericSpsa is SPSAAttribute<double> doubleSpsa)
{
yield return doubleSpsa.ToWeatherFactoryString(property);
}
else
{
_logger.Error("Property {0} has a [{1}] defined with unsupported type <{2}>", property.Name, genericSpsa);
}
}
}

public static IEnumerable<string> GenerateOptionStrings()
{
foreach (var property in typeof(EngineSettings).GetProperties())
{
var genericType = typeof(SPSAAttribute<>);
var spsaArray = property.GetCustomAttributes(genericType);
var count = spsaArray.Count();

if (count > 1)
{
_logger.Warn("Property {0} has more than one [{1}]", property.Name, genericType.Name);
}

if (count == 0)
{
continue;
}

var genericSpsa = spsaArray.First();
if (genericSpsa is SPSAAttribute<int> intSpsa)
{
var val = SPSAAttribute<int>.GetPropertyValue(property);

yield return $"option name {property.Name} type spin default {val} min {intSpsa.MinValue} max {intSpsa.MaxValue}";
}
else if (genericSpsa is SPSAAttribute<double> doubleSpsa)
{
var val = SPSAAttribute<double>.GetPropertyValue(property);

yield return $"option name {property.Name} type spin default {val} min {doubleSpsa.MinValue} max {doubleSpsa.MaxValue}";
}
else
{
_logger.Error("Property {0} has a [{1}] defined with unsupported type <{2}>", property.Name, genericSpsa);
}
}
}
}
48 changes: 24 additions & 24 deletions src/Lynx/Search/NegaMax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,30 +97,6 @@ private int NegaMax(int depth, int ply, int alpha, int beta, bool parentWasNullM
staticEval = ttScore;
}

// 🔍 Null Move Pruning (NMP) - our position is so good that we can potentially afford giving our opponent a double move and still remain ahead of beta
if (depth >= Configuration.EngineSettings.NMP_MinDepth
&& staticEval >= beta
&& !parentWasNullMove
&& phase > 2 // Zugzwang risk reduction: pieces other than pawn presents
&& (ttElementType != NodeType.Alpha || ttEvaluation >= beta)) // TT suggests NMP will fail: entry must not be a fail-low entry with a score below beta - Stormphrax and Ethereal
{
var nmpReduction = Configuration.EngineSettings.NMP_BaseDepthReduction + ((depth + Configuration.EngineSettings.NMP_DepthIncrement) / Configuration.EngineSettings.NMP_DepthDivisor); // Clarity

// TODO more advanced adaptative reduction, similar to what Ethereal and Stormphrax are doing
//var nmpReduction = Math.Min(
// depth,
// 3 + (depth / 3) + Math.Min((staticEval - beta) / 200, 3));

var gameState = position.MakeNullMove();
var evaluation = -NegaMax(depth - 1 - nmpReduction, ply + 1, -beta, -beta + 1, parentWasNullMove: true);
position.UnMakeNullMove(gameState);

if (evaluation >= beta)
{
return evaluation;
}
}

if (depth <= Configuration.EngineSettings.RFP_MaxDepth)
{
// 🔍 Reverse Futility Pruning (RFP) - https://www.chessprogramming.org/Reverse_Futility_Pruning
Expand Down Expand Up @@ -163,6 +139,30 @@ private int NegaMax(int depth, int ply, int alpha, int beta, bool parentWasNullM
}
}
}

// 🔍 Null Move Pruning (NMP) - our position is so good that we can potentially afford giving our opponent a double move and still remain ahead of beta
if (depth >= Configuration.EngineSettings.NMP_MinDepth
&& staticEval >= beta
&& !parentWasNullMove
&& phase > 2 // Zugzwang risk reduction: pieces other than pawn presents
&& (ttElementType != NodeType.Alpha || ttEvaluation >= beta)) // TT suggests NMP will fail: entry must not be a fail-low entry with a score below beta - Stormphrax and Ethereal
{
var nmpReduction = Configuration.EngineSettings.NMP_BaseDepthReduction + ((depth + Configuration.EngineSettings.NMP_DepthIncrement) / Configuration.EngineSettings.NMP_DepthDivisor); // Clarity

// TODO more advanced adaptative reduction, similar to what Ethereal and Stormphrax are doing
//var nmpReduction = Math.Min(
// depth,
// 3 + (depth / 3) + Math.Min((staticEval - beta) / 200, 3));

var gameState = position.MakeNullMove();
var evaluation = -NegaMax(depth - 1 - nmpReduction, ply + 1, -beta, -beta + 1, parentWasNullMove: true);
position.UnMakeNullMove(gameState);

if (evaluation >= beta)
{
return evaluation;
}
}
}

Span<Move> moves = stackalloc Move[Constants.MaxNumberOfPossibleMovesInAPosition];
Expand Down
45 changes: 9 additions & 36 deletions src/Lynx/UCI/Commands/Engine/OptionCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,42 +124,15 @@ public sealed class OptionCommand : EngineBaseCommand
public const string Id = "option";

public static readonly ImmutableArray<string> AvailableOptions =
[
"option name UCI_Opponent type string",
$"option name UCI_EngineAbout type string default {IdCommand.EngineName} by {IdCommand.EngineAuthor}, see https://github.com/lynx-chess/Lynx",
$"option name UCI_ShowWDL type check default {Configuration.EngineSettings.ShowWDL}",
$"option name Hash type spin default {Configuration.EngineSettings.TranspositionTableSize} min {Constants.AbsoluteMinTTSize} max {Constants.AbsoluteMaxTTSize}",
$"option name OnlineTablebaseInRootPositions type check default {Configuration.EngineSettings.UseOnlineTablebaseInRootPositions}",
"option name Threads type spin default 1 min 1 max 1",

#region Search tuning

$"option name {nameof(Configuration.EngineSettings.LMR_MinDepth)} type spin default {Configuration.EngineSettings.LMR_MinDepth} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.LMR_MinFullDepthSearchedMoves)} type spin default {Configuration.EngineSettings.LMR_MinFullDepthSearchedMoves} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.LMR_Base)} type spin default {100 * Configuration.EngineSettings.LMR_Base} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.LMR_Divisor)} type spin default {100 * Configuration.EngineSettings.LMR_Divisor} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.NMP_MinDepth)} type spin default {Configuration.EngineSettings.NMP_MinDepth} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.NMP_BaseDepthReduction)} type spin default {Configuration.EngineSettings.NMP_BaseDepthReduction} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.NMP_DepthIncrement)} type spin default {Configuration.EngineSettings.NMP_DepthIncrement} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.NMP_DepthDivisor)} type spin default {Configuration.EngineSettings.NMP_DepthDivisor} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.AspirationWindow_Delta)} type spin default {Configuration.EngineSettings.AspirationWindow_Delta} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.AspirationWindow_MinDepth)} type spin default {Configuration.EngineSettings.AspirationWindow_MinDepth} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.RFP_MaxDepth)} type spin default {Configuration.EngineSettings.RFP_MaxDepth} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.RFP_DepthScalingFactor)} type spin default {Configuration.EngineSettings.RFP_DepthScalingFactor} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.Razoring_MaxDepth)} type spin default {Configuration.EngineSettings.Razoring_MaxDepth} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.Razoring_Depth1Bonus)} type spin default {Configuration.EngineSettings.Razoring_Depth1Bonus} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.Razoring_NotDepth1Bonus)} type spin default {Configuration.EngineSettings.Razoring_NotDepth1Bonus} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.IIR_MinDepth)} type spin default {Configuration.EngineSettings.IIR_MinDepth} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.LMP_MaxDepth)} type spin default {Configuration.EngineSettings.LMP_MaxDepth} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.LMP_BaseMovesToTry)} type spin default {Configuration.EngineSettings.LMP_BaseMovesToTry} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.LMP_MovesDepthMultiplier)} type spin default {Configuration.EngineSettings.LMP_MovesDepthMultiplier} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.SEE_BadCaptureReduction)} type spin default {Configuration.EngineSettings.SEE_BadCaptureReduction} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.FP_MaxDepth)} type spin default {Configuration.EngineSettings.FP_MaxDepth} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.FP_DepthScalingFactor)} type spin default {Configuration.EngineSettings.FP_DepthScalingFactor} min 0 max 1024",
$"option name {nameof(Configuration.EngineSettings.FP_Margin)} type spin default {Configuration.EngineSettings.FP_Margin} min 0 max 1024",

#endregion
];
[
"option name UCI_Opponent type string",
$"option name UCI_EngineAbout type string default {IdCommand.EngineName} by {IdCommand.EngineAuthor}, see https://github.com/lynx-chess/Lynx",
$"option name UCI_ShowWDL type check default {Configuration.EngineSettings.ShowWDL}",
$"option name Hash type spin default {Configuration.EngineSettings.TranspositionTableSize} min {Constants.AbsoluteMinTTSize} max {Constants.AbsoluteMaxTTSize}",
$"option name OnlineTablebaseInRootPositions type check default {Configuration.EngineSettings.UseOnlineTablebaseInRootPositions}",
"option name Threads type spin default 1 min 1 max 1",
.. Configuration.GeneralSettings.EnableTuning ? SPSAAttributeHelpers.GenerateOptionStrings() : []
];

//"option name UCI_AnalyseMode type check",
//"option name NalimovPath type string default C:/...",
Expand Down
Loading

0 comments on commit ed91435

Please sign in to comment.