Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tree plot #35

Merged
merged 17 commits into from Feb 12, 2021
Merged

Tree plot #35

merged 17 commits into from Feb 12, 2021

Conversation

jkormu
Copy link
Contributor

@jkormu jkormu commented Jan 30, 2021

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:

  • Quickly check how new custom search feature under implementation affects the tree shape
  • Visually tune new search parameters to sane values
  • Educate your self how different settings affect the tree shape

Some potential future improvements:

  • Allow user to specify title
  • Show root's board state as fen or visually
  • support for other than 1080x1920 image sizes

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

@dje-dev
Copy link
Owner

dje-dev commented Feb 8, 2021

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.

  • It's great that you leveraged System.Drawing.Common for this functionality because Microsoft built it to be cross-platform, also working on Linux. https://www.hanselman.com/blog/how-do-you-use-systemdrawing-in-net-core
    Although it requires a sudo install on Linux of libc6-dev and libgdiplus, this should not generally be a problem (in worst case, this functionality would simply not work).

  • 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).

  • In UCI code, the output should be sent to OutStream instead of Console

  • By convention, each class is put in its own file of the same name. Also generally the public fields have the first characater capitalized and appear first, followed by the more private fields (with names that are all lower case).

  • 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.

  • It would be cool if the image viewer would automatically launch and show the generated png. This can be done using something like Process.Start("plot.png") but this is not cross-platform (I forget the right way to do this that also would work on Linux).

  • By convention comments are terminated with a period (already mostly the case).

  • The code base is currently all "2 spaces" (this option can be set in the Visual Studio preferences).

  • This code can be simplified from:

  return !(Thread is null) ? Thread : (Children.Count > 0 ? Children[0] : null);

simpler:

  return Thread is not null ? Thread : (Children.Count > 0 ? Children[0] : null);

simplest:

  return Thread ?? (Children.Count > 0 ? Children[0] : null);
  • The use of Linq is elegant indeed, for example:
 var children = (from ind in Enumerable.Range(0, node.NumChildrenExpanded) select node.ChildAtIndexRef(ind)).ToArray();

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
Copy link
Owner

dje-dev commented Feb 8, 2021

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
Copy link
Contributor Author

jkormu commented Feb 8, 2021

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
Copy link
Owner

dje-dev commented Feb 8, 2021

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
Copy link
Contributor Author

jkormu commented Feb 9, 2021

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

@dje-dev dje-dev merged commit 809509b into dje-dev:main Feb 12, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants