To run this simulation run these following shell commands:
$ git clone https://gist.github.com/929241297c138964bc90.git cube-stats
$ cd cube-stats
$ gem install rubiks_cube
$ ruby cube-stats.rb 100
| .*.sw[op] | |
| *.json | |
| *.csv |
| #!/usr/bin/env ruby | |
| require "json" | |
| require "rubiks_cube" | |
| require "./scramble.rb" | |
| # usage help | |
| if ARGV.length != 1 && ARGV.length != 2 then | |
| puts "Usage: cube-stats <trials> [scramble_moves]" | |
| puts | |
| puts " <trials> : number of times to run the simulation" | |
| puts "[scramble_moves] : number of preliminary random moves prior to each simulation" | |
| exit 1 | |
| end | |
| # number of trials to do | |
| trials = ARGV[0].to_i | |
| abort "error: trials must be >= 1" if trials < 1 | |
| # moves to scramble within each trial (default 100) | |
| if not ARGV[1] then | |
| scramble_moves = 100 | |
| else | |
| scramble_moves = ARGV[1].to_i | |
| end | |
| # create a new cube and a data hash | |
| data = { | |
| :trials => trials, | |
| :scramble_moves => scramble_moves, | |
| :moves => Array.new | |
| } | |
| puts "Running #{trials} trials with #{scramble_moves} scramble moves per trial." | |
| # run the trials | |
| trials.times do |i| | |
| # Scramble cube | |
| cube = RubiksCube::Cube.new | |
| seq = Scramble.get_random_moves(scramble_moves) | |
| cube.perform! seq | |
| # How many moves does it take? | |
| solution = RubiksCube::TwoCycleSolution.new(cube) | |
| puts "Trial ##{(i+1).to_s.rjust(3, "0")} - #{solution.length} moves" | |
| data[:moves] << solution.length | |
| end | |
| # dump data to JSON | |
| File.open("data.json", "w") do |f| | |
| f.write JSON.pretty_generate(data) + "\n" | |
| end | |
| ### | |
| puts "## scramble_moves vs. std dev correlation test" | |
| sd_test = [] | |
| module Enumerable | |
| def sum | |
| self.inject(0){|accum, i| accum + i } | |
| end | |
| def mean | |
| self.sum/self.length.to_f | |
| end | |
| def sample_variance | |
| m = self.mean | |
| sum = self.inject(0){|accum, i| accum +(i-m)**2 } | |
| sum/(self.length - 1).to_f | |
| end | |
| def standard_deviation | |
| return Math.sqrt(self.sample_variance) | |
| end | |
| end | |
| # test scramble moves from 0 to 100 | |
| (0..100).step(1) do |scramble_moves| | |
| 100.times do |i| | |
| # Scramble cube | |
| cube = RubiksCube::Cube.new | |
| seq = Scramble.get_random_moves(scramble_moves) | |
| cube.perform! seq | |
| # How many moves does it take? | |
| solution = RubiksCube::TwoCycleSolution.new(cube) | |
| puts "sm=#{scramble_moves} : Trial ##{(i+1).to_s.rjust(3, "0")} - #{solution.length} moves" | |
| # indice = scramble moves tested | |
| sd_test[scramble_moves] = [] if not sd_test[scramble_moves] | |
| sd_test[scramble_moves] << solution.length | |
| end | |
| end | |
| # calculate and write standard deviations to a CSV | |
| File.open("sd_data.csv", "w") do |f| | |
| f.write("scramble_moves,std_dev\n") | |
| sd_test.each_with_index do |val, index| | |
| next if not val | |
| sd = val.standard_deviation | |
| puts "val = #{val}, sd = #{sd}, i = #{index}" | |
| f.write("#{index},#{sd}\n") | |
| end | |
| end |
| #!/usr/bin/env Rscript | |
| library("rjson") | |
| pdf(file="Rplots.pdf", width=7.5, height=7) | |
| data <- fromJSON(file="data.json") | |
| print("Summary of Data:") | |
| summary(data$moves) | |
| hist(data$moves, | |
| main="Distrubution of Moves to Solve Rubiks Cube", | |
| sub="(using the two-cycle solution method)", | |
| xlab="Number of Moves to Solve Cube", ylab="Frequency", | |
| col="gray" | |
| ) | |
| sd_data <- read.csv("sd_data.csv") | |
| plot(sd_data, | |
| main="Preliminary Scramble Moves VS. Standard Deviation", | |
| xlab="Number of Preliminary Scramble Moves", | |
| ylab="Standard Deviation (of moves to solve)", | |
| pch=19 | |
| ) |
| module Scramble | |
| extend self | |
| @move_types = [ | |
| "F", "F'", "B", "B'", "L", "L'", | |
| "R", "R'", "U", "U'", "D", "D'" | |
| ] | |
| # To prevent unnecessary moves, e.g. sequential F and F', | |
| # each move has only some possible successors. The easy way | |
| # is to use only movements that will permute the previous. | |
| @successors = { | |
| "F" => ["L", "L'", "R", "R'", "U", "U'", "D", "D'"], | |
| "F'" => ["L", "L'", "R", "R'", "U", "U'", "D", "D'"], | |
| "B" => ["L", "L'", "R", "R'", "U", "U'", "D", "D'"], | |
| "B'" => ["L", "L'", "R", "R'", "U", "U'", "D", "D'"], | |
| "L" => ["F", "F'", "B", "B'", "U", "U'", "D", "D'"], | |
| "L'" => ["F", "F'", "B", "B'", "U", "U'", "D", "D'"], | |
| "R" => ["F", "F'", "B", "B'", "U", "U'", "D", "D'"], | |
| "R'" => ["F", "F'", "B", "B'", "U", "U'", "D", "D'"], | |
| "U" => ["F", "F'", "B", "B'", "L", "L'", "R", "R'"], | |
| "U'" => ["F", "F'", "B", "B'", "L", "L'", "R", "R'"], | |
| "D" => ["F", "F'", "B", "B'", "L", "L'", "R", "R'"], | |
| "D'" => ["F", "F'", "B", "B'", "L", "L'", "R", "R'"] | |
| } | |
| def random_move | |
| mi = rand(0..11) | |
| return @move_types[mi] | |
| end | |
| def random_successor(move_type) | |
| mi = rand(0..7) | |
| return @successors[move_type][mi] | |
| end | |
| def get_random_moves(count) | |
| move = random_move | |
| moves = [] | |
| # Generate the moves | |
| count.times do | |
| moves << move | |
| move = random_successor(move) | |
| end | |
| # Convert to string | |
| str = "" | |
| moves.each do |move| | |
| str << "#{move} " | |
| end | |
| return str | |
| end | |
| end |