dje-dev / Ceres

Ceres - an MCTS chess engine for research and recreation
GNU General Public License v3.0
153 stars 23 forks source link

Tree plot #35

Closed jkormu closed 3 years ago

jkormu commented 3 years ago

Adds UCI command save-tree-plot <filename> to save visual presentation of current search tree as a png-file.

Example UCI session

*** WARNING: Ceres binaries built in Debug mode and will run much more slowly than Release

|=====================================================|
| Ceres - A Monte Carlo Tree Search Chess Engine      |
|                                                     |
| (c) 2020- David Elliott and the Ceres Authors       |
|   With network backend code from Leela Chess Zero.  |
|                                                     |
|  Version 0.86. Use help to list available commands. |
|=====================================================|

Ceres user settings loaded from file Ceres.json

Network evaluation configured to use: <NNEvaluatorDef Network=LC0:66997 Device=GPU:0 >

Entering UCI command processing mode.
go nodes 8000
info depth 0 seldepth 1 time 4809 nodes 1 score cp 17 tbhits 0 nps 0 pv e2e4  string M= 134
info depth 5 seldepth 13 time 5316 nodes 1326 score cp 6 tbhits 0 nps 249 pv e2e4 e7e5 g1f3 b8c6 f1b5 g8f6 e1g1 f6e4 f1e1 e4d6 f3e5 c6e5 b5f1  string M= 137
info depth 6 seldepth 16 time 5829 nodes 3950 score cp 7 tbhits 0 nps 678 pv e2e4 e7e5 g1f3 b8c6 f1b5 g8f6 e1g1 f6e4 f1e1 e4d6 f3e5 f8e7 b5f1 c6e5 e1e5 e8g8  string M= 137
info depth 6 seldepth 19 time 6348 nodes 6914 score cp 7 tbhits 0 nps 1089 pv e2e4 e7e5 g1f3 b8c6 f1b5 g8f6 e1g1 f6e4 f1e1 e4d6 f3e5 f8e7 b5f1 c6e5 e1e5 e8g8 d2d4 e7f6 e5e1  string M= 137
info depth 6 seldepth 19 time 6540 nodes 7999 score cp 6 tbhits 0 nps 1223 pv e2e4 e7e5 g1f3 b8c6 f1b5 g8f6 e1g1 f6e4 f1e1 e4d6 f3e5 f8e7 b5f1 c6e5 e1e5 e8g8 d2d4 e7f6 e5e1  string M= 137
bestmove e2e4
save-tree-plot C:\\temp\\myplot

This saves following plot into file C:\temp\myplot.png.

myplot

Works well for larger trees, as long as memory does not run out. Drawing is fairly quick. On my machine with debug build plotting 1M node tree takes about 10 seconds, ~2 sec to calculate the x,y-coordinates and ~8 sec to draw it. Smaller trees are much faster to plot, e.g. 8000 nodes takes ~0.1 sec.

Some use cases:

Some potential future improvements:

This is my first time writing C# and implementation might be ugly at places. Happy to hear feedback how to improve.

dje-dev commented 3 years ago

Thank you very much - having this visualization directly into Ceres will be really cool!

It's impressive also for your first C# code and also that you figured out some of the relatively undocumented Ceres internals.

I don't think any substantive changes would be needed, just a number of smaller stylistic items. Comments below.

However in some cases, it can be inefficient. This is almost certainly the case because here because it ends up materializes the data structure (ToArray()). Instead, the spririt of Linq is to just keep enumerating over chains and then everything is "one at a time, just in time."

It seems you could do this here, just chain this all together in the foreach appearing below:

  foreach (MCTSNodeStruct child in children.OrderBy(c => -c.N))
  {
  }

then it there will be less objects created and speed will be much better.

dje-dev commented 3 years ago

Just a thought - perhaps you could add not only the current save-tree-plot command but also a show-tree-plot command that does the same thing but auto-launches the png in the visualizer application, to make it really easy. We could also suggest to the author of Nibbler to add a menu choice to launch that.

jkormu commented 3 years ago

Thank you for the detailed comments! I will address those.

I have only couple one questions.

Ceres consists of a stacked set of assemblies at different layers. The Ceres.MCTS layer is intended to contain only logic relating to MCTS search. However this tree plot is clearly at the presentation/feature layer, so I suggest creating a folder Visualization in the Ceres.Features assembly and putting this class in a file in this folder with namespace Ceres.Features.Visualization.TreePlot (or the like).

I initially tried this but couldn't figure out how to refer to namespace defined in Ceres.Features from Ceres.MCTS. It seems that one can only access namespaces of the Layers that are defined as project dependencies for the current Layer. However, I can't add Ceres.Features as dependency for Ceres.MCTS as it would result in circular dependency (Ceres.Features depends on Ceres.MCTS already). Any advice?

EDIT: realized that the only reason why I had this problem was that during development I was calling TreePlot.Save directly from the search code. But now that it is called from UCI, it is all fine as UCI resides in Features layer.

I'm trying to follow the convention that braces are always used blocks (e.g. if, for, while, using) even if they are a single line.

Could you give an example of bad and correct way? Tried to compared how I and rest of the code base use if and while blocks and couldn't spot the difference.

Rest seems clear for now.

I also had show-tree-plot idea in my mind but ditched it as it seemed like I would have to use Windows Forms which sounded like it will never work on Linux. I will look into the suggested way of using Process.Start("plot.png").

dje-dev commented 3 years ago

Actually the use of braces is almost perfect, just I noticed not here (missing):

 using (new SearchContextExecutionBlock(curContext))
      TreePlot.Save(curManager.Context.Root.Ref, fileName);
jkormu commented 3 years ago

I hope I got it all.

  1. Moved files to Ceres.Features.Visualization and split each of the 3 classes to separate files
  2. UCI code outputs to OutStream
  3. Public fields are defined first and are capitalized. The more private ones come after and start with lower case.
  4. Fixed the one using block in UCI missing {}
  5. added the show-tree-plot uci command as suggested
  6. Went through the comments and appended the missing periods
  7. Switched to 2 space indentation and replaced existing 4 spaces with 2 spaces
  8. Simplified the nested ternary operations by replacing the null check with ??
  9. Got rid of the ToArray() calls using the suggested approach