I would like to test how Stockfish or other Engines change their moves and evaluations as the number of nodes increases. Is there a way to run Stockfish (or another engine) and force it to output its move preference every 1000 nodes?
1 Answer
One approach is by sending the command go nodes 1000
, go nodes 2000 ...
to the engine and record the moves and score.
Here is a sample code to do like that using python chess lib along with other data and plotting libs.
Code
"""
This script will only work for those uci engines that supports
"go nodes <value>" command.
Requirements:
pip install chess
pip install pandas
pip install matplotlib
"""
import chess
import chess.engine
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as plticker
def engine_analysis(efn, fen, nodelimit):
engine = chess.engine.SimpleEngine.popen_uci(efn)
try:
engine.configure({"Hash": 128})
engine.configure({"Threads": 1})
except Exception as exception:
pass
board = chess.Board(fen)
limit = chess.engine.Limit(nodes=nodelimit)
value, sanmove, nodes, timesec = None, None, 0, 0
with engine.analysis(board, limit=limit) as analysis:
for info in analysis:
pv = info.get('pv')
score = info.get('score')
nodes = info.get('nodes')
timesec = info.get('time')
if pv is not None:
move = pv[0]
sanmove = board.san(move)
if score is not None:
value = score.relative.score(mate_score=32000)
engine.quit()
return sanmove, value, nodes, timesec
def run_engine():
# fen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1' # startpos
fen = 'rnbqkb1r/1p2pppp/p2p1n2/8/3NP3/2N5/PPP2PPP/R1BQKB1R w KQkq - 0 6' # sicilian
nodes_per_interval = 1000
num_interval = 30
e1 = {'name': 'Stockfish 14', 'file': 'E:/Chess/Engines/stockfish/sf14/sf14.exe'}
e2 = {'name': 'Lc0 0.28', 'file': 'E:/Chess/Engines/Lc0/lc0-v0.28.0-windows-gpu-nvidia-cudnn-nodll/lc0.exe'}
engines = [e1, e2]
dfs = {}
for eng in engines:
d = []
for i in range(num_interval):
nodelimit = (i+1) * nodes_per_interval
move, score, actualnodes, timesec = engine_analysis(eng['file'], fen, nodelimit)
d.append([nodelimit, actualnodes, move, score, timesec])
df = pd.DataFrame(d)
df.columns = ['nodes', 'actualnodes', 'move', 'scorecp', 'timesec']
dfs.update({eng['name']: df})
print(f'fen: {fen}')
for k, v in dfs.items():
print(f'{k}:')
print(f'{v.to_string(index=False)}\n')
# Plot
x = dfs[e1['name']]['nodes']
y1 = dfs[e1['name']]['scorecp']
y2 = dfs[e2['name']]['scorecp']
_, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(12, 6))
ax1.plot(x, y1)
ax1.set_title(f"{e1['name']} score at different node levels")
ax1.set_ylabel('score cp')
ax2.plot(x, y2)
ax2.set_title(f"{e2['name']} score at different node levels")
ax2.set_xlabel('nodes')
ax2.set_ylabel('score cp')
plt.setp(ax2.get_xticklabels(), ha="right", rotation=45)
loc = plticker.MultipleLocator(base=nodes_per_interval) # add ticks in every point
ax2.xaxis.set_major_locator(loc)
plt.savefig('engine_nodes.png')
plt.show()
# start
run_engine()
Output
fen: rnbqkb1r/1p2pppp/p2p1n2/8/3NP3/2N5/PPP2PPP/R1BQKB1R w KQkq - 0 6
Stockfish 14:
nodes actualnodes move scorecp timesec
1000 1002 Be3 64 0.002
2000 2001 Bg5 -24 0.003
3000 3005 Nb3 46 0.003
4000 4010 Bg5 32 0.004
5000 5004 Bg5 4 0.004
6000 6003 Nb3 13 0.005
7000 7040 Bg5 19 0.006
8000 8013 Be3 41 0.009
9000 9003 Be2 40 0.007
10000 10019 Be2 33 0.008
11000 11003 f3 50 0.009
12000 12014 Qf3 40 0.010
13000 13004 a4 27 0.010
14000 14014 a4 47 0.011
15000 15006 Be2 32 0.011
16000 16040 Nb3 6 0.012
17000 17022 Nb3 37 0.013
18000 18007 Nb3 52 0.014
19000 19019 a4 17 0.014
20000 20040 h3 21 0.020
21000 21032 Be3 38 0.016
22000 22011 f3 18 0.016
23000 23039 f3 38 0.023
24000 24036 f3 50 0.028
25000 25019 Bg5 69 0.021
26000 26021 h3 30 0.019
27000 27071 Be3 27 0.019
28000 28072 Nb3 44 0.021
29000 29032 Be3 64 0.020
30000 30039 Be2 34 0.022
Lc0 0.28:
nodes actualnodes move scorecp timesec
1000 1022 f3 20 0.156
2000 2012 f3 19 0.180
3000 2997 f3 20 0.214
4000 3411 f3 19 0.224
5000 4211 f3 19 0.249
6000 5428 f3 19 0.279
7000 6018 f3 19 0.319
8000 6449 f3 19 0.339
9000 6993 f3 20 0.349
10000 7552 f3 20 0.369
11000 8039 f3 20 0.392
12000 8173 f3 20 0.379
13000 8942 f3 20 0.404
14000 9583 f3 20 0.417
15000 10075 f3 20 0.475
16000 10653 f3 20 0.466
17000 11045 f3 20 0.474
18000 11732 f3 20 0.506
19000 12561 f3 20 0.535
20000 12763 f3 20 0.543
21000 13268 f3 20 0.570
22000 13829 f3 20 0.584
23000 14323 f3 20 0.602
24000 14883 f3 20 0.641
25000 15823 f3 19 0.663
26000 16810 f3 19 0.693
27000 25923 f3 18 0.938
28000 27064 f3 18 1.001
29000 26266 f3 18 0.976
30000 29043 Be3 18 1.052