rubykon 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +11 -0
- data/CHANGELOG.md +32 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +7 -0
- data/Guardfile +12 -0
- data/LICENSE +22 -0
- data/POSSIBLE_IMPROVEMENTS.md +25 -0
- data/README.md +36 -0
- data/Rakefile +6 -0
- data/benchmark/benchmark.sh +22 -0
- data/benchmark/full_playout.rb +17 -0
- data/benchmark/mcts_avg.rb +23 -0
- data/benchmark/playout.rb +15 -0
- data/benchmark/playout_micros.rb +188 -0
- data/benchmark/profiling/full_playout.rb +7 -0
- data/benchmark/profiling/mcts.rb +6 -0
- data/benchmark/results/HISTORY.md +541 -0
- data/benchmark/scoring.rb +20 -0
- data/benchmark/scoring_micros.rb +60 -0
- data/benchmark/support/benchmark-ips.rb +11 -0
- data/benchmark/support/benchmark-ips_shim.rb +143 -0
- data/benchmark/support/playout_help.rb +13 -0
- data/examples/mcts_laziness.rb +22 -0
- data/exe/rubykon +5 -0
- data/lib/benchmark/avg.rb +14 -0
- data/lib/benchmark/avg/benchmark_suite.rb +59 -0
- data/lib/benchmark/avg/job.rb +92 -0
- data/lib/mcts.rb +11 -0
- data/lib/mcts/examples/double_step.rb +68 -0
- data/lib/mcts/mcts.rb +13 -0
- data/lib/mcts/node.rb +88 -0
- data/lib/mcts/playout.rb +22 -0
- data/lib/mcts/root.rb +49 -0
- data/lib/rubykon.rb +13 -0
- data/lib/rubykon/board.rb +188 -0
- data/lib/rubykon/cli.rb +122 -0
- data/lib/rubykon/exceptions/exceptions.rb +1 -0
- data/lib/rubykon/exceptions/illegal_move_exception.rb +4 -0
- data/lib/rubykon/eye_detector.rb +27 -0
- data/lib/rubykon/game.rb +115 -0
- data/lib/rubykon/game_scorer.rb +62 -0
- data/lib/rubykon/game_state.rb +93 -0
- data/lib/rubykon/group.rb +99 -0
- data/lib/rubykon/group_tracker.rb +144 -0
- data/lib/rubykon/gtp_coordinate_converter.rb +25 -0
- data/lib/rubykon/move_validator.rb +55 -0
- data/lib/rubykon/version.rb +3 -0
- data/rubykon.gemspec +21 -0
- metadata +97 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative '../lib/rubykon'
|
2
|
+
require_relative 'support/benchmark-ips'
|
3
|
+
require_relative 'support/playout_help'
|
4
|
+
|
5
|
+
Benchmark.ips do |benchmark|
|
6
|
+
game_9 = playout_for(9).game_state.game
|
7
|
+
game_13 = playout_for(13).game_state.game
|
8
|
+
game_19 = playout_for(19).game_state.game
|
9
|
+
scorer = Rubykon::GameScorer.new
|
10
|
+
|
11
|
+
benchmark.report '9x9 scoring' do
|
12
|
+
scorer.score game_9
|
13
|
+
end
|
14
|
+
benchmark.report '13x13 scoring' do
|
15
|
+
scorer.score game_13
|
16
|
+
end
|
17
|
+
benchmark.report '19x19 scoring' do
|
18
|
+
scorer.score game_19
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require_relative '../lib/rubykon'
|
2
|
+
require_relative 'support/playout_help'
|
3
|
+
require_relative 'support/benchmark-ips'
|
4
|
+
|
5
|
+
class Rubykon::GameScorer
|
6
|
+
public :score_empty_cutting_point
|
7
|
+
public :find_candidate_color
|
8
|
+
public :only_one_color_adjacent?
|
9
|
+
end
|
10
|
+
|
11
|
+
Benchmark.ips do |benchmark|
|
12
|
+
|
13
|
+
board = Rubykon::Board.from <<-BOARD
|
14
|
+
OOOOO-O-OOOOOOXXXXX
|
15
|
+
O-OOOOOO-O-OOOX-X-X
|
16
|
+
OO-OOXOXOOOO-OOXXXX
|
17
|
+
-OOOXXXXXXOOOOOX-X-
|
18
|
+
OOOOOXOXXXOOOOOOXXX
|
19
|
+
OXXOOOOOXOOOO-OOOOX
|
20
|
+
X-XOOOXXXOOOOOO-OOO
|
21
|
+
XXXOXXXOXXOOOOOOO-O
|
22
|
+
XX-XXXOOOOO-OOO-OOO
|
23
|
+
X-XXXXOXXO-OOOOOOOO
|
24
|
+
-XXXXXXXXXO-OOO-OOO
|
25
|
+
XXXX-X-XXXXOOXOO-O-
|
26
|
+
XX-XX-XX-XOOXXOOOOO
|
27
|
+
-XXXXX-XXXXOX-XOOOO
|
28
|
+
XXX-XXXXXXXXXXXXOXO
|
29
|
+
XXXX-XXXXX-X-XXXOXO
|
30
|
+
-XXXX-X-XXXXXXXXXXO
|
31
|
+
X-XX-XXXX-X-XX-XOOO
|
32
|
+
-XXXXX-XXXXXXXXXO-O
|
33
|
+
BOARD
|
34
|
+
scorer = Rubykon::GameScorer.new
|
35
|
+
identifier = board.identifier_for(3, 3)
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
benchmark.report 'score_empty_cp' do
|
40
|
+
game_score = {Rubykon::Board::BLACK => 0,
|
41
|
+
Rubykon::Board::WHITE => Rubykon::Game::DEFAULT_KOMI}
|
42
|
+
scorer.score_empty_cutting_point(identifier, board, game_score)
|
43
|
+
end
|
44
|
+
|
45
|
+
benchmark.report 'Board#neighbour_colors_of' do
|
46
|
+
board.neighbour_colors_of(identifier)
|
47
|
+
end
|
48
|
+
|
49
|
+
neighbour_colors = board.neighbour_colors_of(identifier)
|
50
|
+
|
51
|
+
benchmark.report 'find_candidate_color' do
|
52
|
+
scorer.find_candidate_color(neighbour_colors)
|
53
|
+
end
|
54
|
+
|
55
|
+
candidate_color = scorer.find_candidate_color(neighbour_colors)
|
56
|
+
|
57
|
+
benchmark.report 'only_one_c_adj?' do
|
58
|
+
scorer.only_one_color_adjacent?(neighbour_colors, candidate_color)
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
def truffle? # truffle can't do gem install
|
2
|
+
defined?(RUBY_DESCRIPTION) && RUBY_DESCRIPTION.match(/graal/)
|
3
|
+
end
|
4
|
+
|
5
|
+
require 'benchmark/ips'
|
6
|
+
|
7
|
+
# only loaded for truffle normally, as it has little to no effect on other
|
8
|
+
# implementations I tested and only results in them running WAY longer.
|
9
|
+
if truffle?
|
10
|
+
require_relative 'benchmark-ips_shim'
|
11
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# Shim modifying benchmark-ips to work better with truffle,
|
2
|
+
# written by Chris Seaton and taken from:
|
3
|
+
# https://gist.github.com/chrisseaton/1c4cb91f3c95ddcf2d1e
|
4
|
+
# Note that this has very little effect on the performance of CRuby/JRuby.
|
5
|
+
# I tried it out and results were ~2 to 4 % better which is well within
|
6
|
+
# the tolerance. On a pre 0.3 version this moved truffle fromm 33 ips of
|
7
|
+
# 19x19 gameplay up to 169.
|
8
|
+
|
9
|
+
# This file modifies benchmark-ips to better accommodate the optimisation
|
10
|
+
# characteristics of sophisticated implementations of Ruby that have a very
|
11
|
+
# large difference between cold and warmed up performance, and that apply
|
12
|
+
# optimisations such as value profiling or other speculation on runtime values.
|
13
|
+
# Recommended to be used with a large (60s) warmup and (30s) measure time. This
|
14
|
+
# has been modified to be the default. Note that on top of that, it now runs
|
15
|
+
# warmup five times, so generating the report will be a lot slower than
|
16
|
+
# before.
|
17
|
+
|
18
|
+
# Code is modified from benchmark-ips
|
19
|
+
|
20
|
+
# Copyright (c) 2015 Evan Phoenix
|
21
|
+
#
|
22
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
23
|
+
# of this software and associated documentation files (the 'Software'), to deal
|
24
|
+
# in the Software without restriction, including without limitation the rights
|
25
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
26
|
+
# copies of the Software, and to permit persons to whom the Software is
|
27
|
+
# furnished to do so, subject to the following conditions:
|
28
|
+
#
|
29
|
+
# The above copyright notice and this permission notice shall be included in
|
30
|
+
# all copies or substantial portions of the Software.
|
31
|
+
#
|
32
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
33
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
34
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
35
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
36
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
37
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
38
|
+
# SOFTWARE.
|
39
|
+
|
40
|
+
module Benchmark
|
41
|
+
module IPS
|
42
|
+
class Job
|
43
|
+
|
44
|
+
def run_warmup
|
45
|
+
@list.each do |item|
|
46
|
+
@suite.warming item.label, @warmup if @suite
|
47
|
+
|
48
|
+
unless @quiet
|
49
|
+
$stdout.print item.label_rjust
|
50
|
+
end
|
51
|
+
|
52
|
+
Timing.clean_env
|
53
|
+
|
54
|
+
# Modification - run with different iteration parameters to defeat
|
55
|
+
# value profiling and other speculation on runtime values.
|
56
|
+
|
57
|
+
item.call_times 1
|
58
|
+
item.call_times 2
|
59
|
+
item.call_times 3
|
60
|
+
|
61
|
+
# Modification - actual time to warm up - not measured
|
62
|
+
|
63
|
+
target = Time.now + @warmup
|
64
|
+
while Time.now < target
|
65
|
+
item.call_times 1
|
66
|
+
end
|
67
|
+
|
68
|
+
before = Time.now
|
69
|
+
target = Time.now + @warmup
|
70
|
+
|
71
|
+
warmup_iter = 0
|
72
|
+
|
73
|
+
while Time.now < target
|
74
|
+
item.call_times(1)
|
75
|
+
warmup_iter += 1
|
76
|
+
end
|
77
|
+
|
78
|
+
after = Time.now
|
79
|
+
|
80
|
+
warmup_time_us = time_us before, after
|
81
|
+
|
82
|
+
@timing[item] = cycles_per_100ms warmup_time_us, warmup_iter
|
83
|
+
|
84
|
+
# Modification - warm up again with this new iteration value that we
|
85
|
+
# haven't run before.
|
86
|
+
|
87
|
+
cycles = @timing[item]
|
88
|
+
target = Time.now + @warmup
|
89
|
+
|
90
|
+
while Time.now < target
|
91
|
+
item.call_times cycles
|
92
|
+
end
|
93
|
+
|
94
|
+
# Modification repeat the scaling again
|
95
|
+
|
96
|
+
before = Time.now
|
97
|
+
target = Time.now + @warmup
|
98
|
+
|
99
|
+
warmup_iter = 0
|
100
|
+
|
101
|
+
while Time.now < target
|
102
|
+
item.call_times cycles
|
103
|
+
warmup_iter += cycles
|
104
|
+
end
|
105
|
+
|
106
|
+
after = Time.now
|
107
|
+
|
108
|
+
warmup_time_us = time_us before, after
|
109
|
+
|
110
|
+
@timing[item] = cycles_per_100ms warmup_time_us, warmup_iter
|
111
|
+
|
112
|
+
case Benchmark::IPS.options[:format]
|
113
|
+
when :human
|
114
|
+
$stdout.printf "%s i/100ms\n", Helpers.scale(@timing[item]) unless @quiet
|
115
|
+
else
|
116
|
+
$stdout.printf "%10d i/100ms\n", @timing[item] unless @quiet
|
117
|
+
end
|
118
|
+
|
119
|
+
@suite.warmup_stats warmup_time_us, @timing[item] if @suite
|
120
|
+
|
121
|
+
# Modification - warm up again with this new iteration value that we
|
122
|
+
# haven't run before.
|
123
|
+
|
124
|
+
cycles = @timing[item]
|
125
|
+
target = Time.now + @warmup
|
126
|
+
|
127
|
+
while Time.now < target
|
128
|
+
item.call_times cycles
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
alias_method :old_initialize, :initialize
|
134
|
+
|
135
|
+
def initialize opts={}
|
136
|
+
old_initialize opts
|
137
|
+
@warmup = 60
|
138
|
+
@time = 30
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
def full_playout_for(size)
|
2
|
+
playout_object_for(size).play
|
3
|
+
end
|
4
|
+
|
5
|
+
def playout_for(size)
|
6
|
+
playout_object = playout_object_for(size)
|
7
|
+
playout_object.playout
|
8
|
+
playout_object
|
9
|
+
end
|
10
|
+
|
11
|
+
def playout_object_for(size)
|
12
|
+
MCTS::Playout.new(Rubykon::GameState.new Rubykon::Game.new(size))
|
13
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative '../lib/mcts'
|
2
|
+
require_relative '../lib/mcts/examples/double_step'
|
3
|
+
|
4
|
+
mcts = MCTS::MCTS.new
|
5
|
+
|
6
|
+
|
7
|
+
[
|
8
|
+
{black: -4, white: 0}, {black: -3, white: 0}, {black: -2, white: 0},
|
9
|
+
{black: -1, white: 0}, {black: 0, white: 0}, {black: 0, white: -1},
|
10
|
+
{black: 0, white: -2}, {black: 0, white: -3},
|
11
|
+
{black: 0, white: -4}].each do |position|
|
12
|
+
[2, 4, 8, 10, 16, 32, 64, 100].each do |playouts|
|
13
|
+
results = Hash.new {0}
|
14
|
+
double_step_game = MCTS::Examples::DoubleStep.new position
|
15
|
+
10_000.times do |i|
|
16
|
+
root = mcts.start double_step_game, playouts
|
17
|
+
best_move = root.best_move
|
18
|
+
results[best_move] += 1
|
19
|
+
end
|
20
|
+
puts "Distribution for #{playouts} playouts with a handicap of #{position[:black] - position[:white]}: #{results}"
|
21
|
+
end
|
22
|
+
end
|
data/exe/rubykon
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
module Benchmark
|
3
|
+
module Avg
|
4
|
+
|
5
|
+
OUTPUT_WIDTH = 80
|
6
|
+
LABEL_WIDTH = 30
|
7
|
+
PADDING = 2
|
8
|
+
METRICS_WIDTH = OUTPUT_WIDTH - LABEL_WIDTH
|
9
|
+
|
10
|
+
class BenchmarkSuite
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@options = default_options
|
14
|
+
@jobs = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def config(options)
|
18
|
+
@options.merge! options
|
19
|
+
end
|
20
|
+
|
21
|
+
def report(label = "", &block)
|
22
|
+
@jobs << Job.new(label, block)
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def run
|
27
|
+
puts 'Running your benchmark...'
|
28
|
+
divider
|
29
|
+
each_job { |job| job.run @options[:warmup], @options[:time] }
|
30
|
+
puts 'Benchmarking finished, here are your reports...'
|
31
|
+
puts
|
32
|
+
puts 'Warm up results:'
|
33
|
+
divider
|
34
|
+
each_job { |job| puts job.warmup_report }
|
35
|
+
puts
|
36
|
+
puts 'Runtime results:'
|
37
|
+
divider
|
38
|
+
each_job { |job| puts job.runtime_report }
|
39
|
+
divider
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def default_options
|
44
|
+
{
|
45
|
+
warmup: 30,
|
46
|
+
time: 60,
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def divider
|
51
|
+
puts '-' * OUTPUT_WIDTH
|
52
|
+
end
|
53
|
+
|
54
|
+
def each_job(&proc)
|
55
|
+
@jobs.each &proc
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
module Benchmark
|
3
|
+
module Avg
|
4
|
+
class Job
|
5
|
+
PRECISION = 2
|
6
|
+
|
7
|
+
def initialize(label, block)
|
8
|
+
@label = label
|
9
|
+
@block = block
|
10
|
+
@warmup_samples = []
|
11
|
+
@run_samples = []
|
12
|
+
@warming_up = true
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(warmup_time, run_time)
|
16
|
+
warmup_finish = Time.now + warmup_time
|
17
|
+
measure_until(@warmup_samples, warmup_finish)
|
18
|
+
finish_warmup
|
19
|
+
|
20
|
+
suite_finish = Time.now + run_time
|
21
|
+
measure_until(@run_samples, suite_finish)
|
22
|
+
finish_measure
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def warmup_report
|
27
|
+
report @warmup_samples
|
28
|
+
end
|
29
|
+
|
30
|
+
def runtime_report
|
31
|
+
report @run_samples
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def measure_until(samples, finish_time)
|
36
|
+
while Time.now < finish_time do
|
37
|
+
measure_block(samples)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def measure_block(samples)
|
42
|
+
start = Time.now
|
43
|
+
@block.call
|
44
|
+
finish = Time.now
|
45
|
+
samples << (finish - start)
|
46
|
+
end
|
47
|
+
|
48
|
+
def finish_warmup
|
49
|
+
@warming_up = false
|
50
|
+
puts "Finished warm up for #{@label}, running the real bechmarks now"
|
51
|
+
end
|
52
|
+
|
53
|
+
def finish_measure
|
54
|
+
puts "Finished measuring the run time for #{@label}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def report(samples)
|
58
|
+
times = extract_times(samples)
|
59
|
+
label = @label.ljust(LABEL_WIDTH - PADDING) + padding_space
|
60
|
+
metrics = "#{round(times[:ipm])} i/min" << padding_space
|
61
|
+
metrics << "#{round(times[:avg])} s (avg)" << padding_space
|
62
|
+
metrics << "(± #{round(times[:standard_deviation_percent])}%)"
|
63
|
+
label + metrics
|
64
|
+
end
|
65
|
+
|
66
|
+
def round(number)
|
67
|
+
# not Float#round to also get numbers like 3.20 (static number after ,)
|
68
|
+
sprintf("%.#{PRECISION}f", number)
|
69
|
+
end
|
70
|
+
|
71
|
+
def padding_space
|
72
|
+
' ' * PADDING
|
73
|
+
end
|
74
|
+
|
75
|
+
def extract_times(samples)
|
76
|
+
times = {}
|
77
|
+
times[:total] = samples.inject(:+)
|
78
|
+
iterations = samples.size
|
79
|
+
times[:avg] = times[:total] / iterations
|
80
|
+
times[:ipm] = iterations / (times[:total] / 60)
|
81
|
+
total_variane = samples.inject(0) do |total, time|
|
82
|
+
total + ((time - times[:avg]) ** 2)
|
83
|
+
end
|
84
|
+
times[:variance] = total_variane / iterations
|
85
|
+
times[:standard_deviation] = Math.sqrt times[:variance]
|
86
|
+
times[:standard_deviation_percent] =
|
87
|
+
100.0 * (times[:standard_deviation] / times[:avg])
|
88
|
+
times
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|