Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add common evolution test and fix a lot of system bugs

  • Loading branch information...
commit 5a6b8722912e1bc78f3095f349453e7bbd34cf46 1 parent 254fe92
@ai authored
View
3  Rakefile
@@ -21,6 +21,5 @@ Rake::RDocTask.new do |rdoc|
rdoc.rdoc_files.include('**/*.rdoc', '*/lib/**/*.rb')
rdoc.title = 'D2NA'
rdoc.rdoc_dir = 'doc'
- rdoc.options << '-c utf-8'
- rdoc.options << '--all'
+ rdoc.options << '--charset utf-8' << '--all' << '--inline-source'
end
View
88 evolution/lib/d2na-evolution/mutable_code.rb
@@ -18,7 +18,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
=end
-require 'faker'
require 'set'
module D2NA
@@ -42,6 +41,9 @@ def initialize(&block)
@unused_conditions = []
@conditions_permutations = [Set[]]
@commands = []
+ @modify_depth = 0
+ @generated_states = 0
+
@mutation_params = {
min_actions: 1, max_actions: 3,
min_state_actions: 3, max_state_actions: 9,
@@ -127,11 +129,20 @@ def on(*conditions, &block)
# send :Output
# end
def modify(&block)
- @modified_rules = Set[]
- self.instance_eval(&block)
- @modified_rules.each do |rule|
- rule.compile
+ @modify_depth += 1
+
+ if 1 == @modify_depth
+ @modified_rules = Set[]
+ self.instance_eval(&block)
+
+ @modified_rules.each do |rule|
+ rule.compile
+ end
+ else
+ self.instance_eval(&block)
end
+
+ @modify_depth -= 1
self
end
@@ -151,7 +162,6 @@ def mutate!(params = {})
modify do
count.times do
choice = sum * rand
-
if choice < p[:add]
# Add command
add_command rand(conditions_count),
@@ -159,13 +169,11 @@ def mutate!(params = {})
elsif choice < p[:add] + p[:remove]
# Remove command
- remove_command rand(length)
+ remove_command(rand(length))
elsif choice < sum - p[:remove_state]
# Add state
- begin
- state = Faker::Name.first_name.downcase.to_sym
- end while @states.keys.include? state
+ state = new_state_name
before_permutation = @conditions_permutations.length
before_conditions = conditions_count
before_commands = @commands.length
@@ -180,7 +188,7 @@ def mutate!(params = {})
*@commands[rand(@commands.length)]
end
- else
+ elsif not @states.empty?
# Remove state
remove_state @states.keys[rand(@states.length)]
end
@@ -206,6 +214,7 @@ def delete_rule(rule)
@unused_conditions << set
@exists_conditions.delete(set)
@rules.delete(rule)
+ @required.delete(rule)
rule.conditions.each do |condition|
@conditions_cache[condition].delete(rule)
end
@@ -214,17 +223,23 @@ def delete_rule(rule)
# Delete state, all it’s commands and rules with it in conditions.
def remove_state(state)
- @conditions_cache[state].each do |rule|
+ @conditions_cache[state].clone.each do |rule|
delete_rule(rule)
end
modify do
- @rules.each_with_index do |rule, rule_number|
- rule.commands.each_with_index do |command, command_number|
- if state == command[1]
- remove_command(command_number, rule_number)
- break
+ rule_number = 0
+ while @rules.length - 1 >= rule_number
+ rule = @rules[rule_number]
+ command_number = 0
+ while rule.commands.length - 1 >= command_number
+ if state == rule.commands[command_number][1]
+ rule = remove_command(command_number, rule_number)
+ break unless rule
+ else
+ command_number += 1
end
end
+ rule_number += 1 if rule
end
end
@conditions_cache.delete(state)
@@ -249,6 +264,9 @@ def clone
another.instance_variable_set(name, value.dup)
end
end
+ another.instance_variable_get('@conditions_cache').each_pair do |i, value|
+ another.instance_variable_get('@conditions_cache')[i] = value.clone
+ end
another.instance_variable_set('@rules', @rules.clone)
another.instance_variable_set('@parent', self)
another
@@ -256,6 +274,18 @@ def clone
protected
+ # Get next new unused state name.
+ def new_state_name
+ @generated_states += 1
+ num = @generated_states - 1
+ name = ''
+ begin
+ name << ('a'.ord + num % 26).chr
+ num /= 26
+ end while 0 < num
+ name.to_sym
+ end
+
# Add conditions as unused if it isn’t used in rules.
def add_unused_conditions(conditions)
unless @exists_conditions.include? conditions
@@ -274,8 +304,9 @@ def add_unused_conditions(conditions)
def add_command(rule_number, command, param)
output param if :send == command
if @rules.length > rule_number
- rule = @rules[rule_number].dup
- @rules[rule_number] = rule
+ old = @rules[rule_number]
+ @modified_rules.delete(old)
+ @rules[rule_number] = rule = clone_rule(old)
else
rule = on(*@unused_conditions[rule_number - @rules.length].to_a)
end
@@ -284,6 +315,18 @@ def add_command(rule_number, command, param)
@length += 1
end
+ # Clone rule and change all necessary caches. Used in copy on write.
+ def clone_rule(rule)
+ clone = rule.dup
+ clone.commands = clone.commands.dup
+ @required[clone] = @required.delete(rule)
+ rule.conditions.each do |condition|
+ @conditions_cache[condition].delete(rule)
+ @conditions_cache[condition] << clone
+ end
+ clone
+ end
+
# Delete command in special +position+ of all code if +rule+ is +nil+ or
# in +position+ in rule with special number.
#
@@ -291,17 +334,18 @@ def add_command(rule_number, command, param)
def remove_command(position, rule_number = nil)
if rule_number
rule = @rules[rule_number]
+ @modified_rules.delete(rule)
if 1 == rule.commands.length
delete_rule(rule)
- @modified_rules.delete(rule)
+ nil
else
- rule = rule.dup
+ rule = clone_rule(rule)
@rules[rule_number] = rule
rule.commands.delete_at(position)
@modified_rules << rule
@length -= 1
+ rule
end
- rule
else
before = 0
@rules.each_with_index do |rule, i|
View
3  evolution/lib/d2na-evolution/population.rb
@@ -92,6 +92,9 @@ def result_index(result)
end
end
+ #TODO fix binary search to remove this hack
+ answer -= 1 if @results.length == answer
+
case result <=> @results[answer]
when 0
return answer
View
1  evolution/lib/d2na-evolution/tests.rb
@@ -57,6 +57,7 @@ def run(code)
@result = TestResult.new
@tests.each do |test, description, priority|
@current_priority = priority
+ @code.start
instance_eval &test
@code.reset!
end
View
21 evolution/spec/evolution_spec.rb
@@ -186,4 +186,25 @@ def mutate!; end
}.should raise_error D2NA::Timeout, /5 steps/
end
+ it "should generate summator" do
+ evolution = D2NA::Evolution.new do
+ protocode do
+ input :A, :B
+ output :C
+ end
+ first_population 5
+ stagnation_limit 10
+
+ selection do
+ out_should_be_empty
+ send :A, :A
+ out_should C: 2
+ send :B, :B, :B
+ out_should C: 5
+ end
+ end
+
+ evolution.step! while evolution.next_step?
+ end
+
end
View
32 evolution/spec/mutable_code_spec.rb
@@ -2,6 +2,8 @@
require File.join(File.dirname(__FILE__), 'spec_helper')
describe D2NA::MutableCode do
+ include ConditionsCacheHelper
+
before do
@code = D2NA::MutableCode.new do
on :Init do
@@ -76,7 +78,7 @@
@code.should have(3).rules
@code.rules[2].conditions.to_set.should == [:waiting, :Input].to_set
@code.rules[2].commands.should == [[:send, :Output]]
- @code.conditions_cache[:waiting].should == [@code.rules[2]]
+ @code.should have_actual_conditions_cache
end
it "should delete empty rule" do
@@ -153,8 +155,9 @@
@code.delete_rule(@code.rules[1])
@code.should have(1).rules
@code.length.should == 1
- @code.conditions_cache[:Input].should be_empty
+ @code.should have_actual_conditions_cache
@code.unused_conditions.length.should == unused + 1
+ @code.should have(1).required
end
it "should remove state" do
@@ -162,16 +165,26 @@
on :Init do
send :Output
up :waiting
+ down :waiting
+ end
+ on :A do
+ up :waiting
end
- on :Input, :waiting do
+ on :B do
+ up :waiting
+ end
+ on :A, :waiting do
send :Output
end
+ on :waiting do
+ up :waiting
+ end
end
code.remove_state :waiting
code.states.keys.should_not include(:waiting)
- code.unused_conditions.length.should == 1
- code.conditions_cache.keys.should_not include(:waiting)
+ code.should have(2).unused_conditions
+ code.should have_actual_conditions_cache
code.should have(1).rules
code.rules[0].commands.should == [[:send, :Output]]
end
@@ -182,24 +195,29 @@
end
it "should mutate commands" do
- @code.mutation_params.merge!(add_state: 0, remove_state: 0)
+ @code.mutation_params.merge!(add_state: 0, remove_state: 0, max_actions: 3)
@code.mutate!(add: 1, remove: 0, min_actions: 3)
@code.length.should == 6
+ @code.should have_actual_conditions_cache
@code.mutate!(add: 0, remove: 1, max_actions: 1)
@code.length.should == 5
+ @code.should have_actual_conditions_cache
end
it "should mutate states" do
- @code.mutation_params.merge!(add: 0, remove: 0, max_actions: 1)
+ @code.mutation_params.merge!(add: 0, remove: 0, max_actions: 1,
+ max_state_actions: 3)
@code.mutate!(add_state: 1, remove_state: 0, min_state_actions: 3)
@code.should have(2).states
@code.should have_at_least(1).rules
@code.should have(5).commands
+ @code.should have_actual_conditions_cache
@code.mutate!(add_state: 0, remove_state: 1)
@code.should have(1).states
+ @code.should have_actual_conditions_cache
end
end
View
52 evolution/spec/spec_helper.rb
@@ -7,3 +7,55 @@ def fake_code
code.stub!(:mutate!)
code
end
+
+module ConditionsCacheHelper
+ class ConditionsCacheMatcher
+ def matches?(code)
+ @code = code
+ @recache = {}
+ @code.input_signals.each do |signal|
+ @recache[signal] = []
+ end
+ @code.states.keys.each do |state|
+ @recache[state] = []
+ end
+ @code.rules.each do |rule|
+ rule.conditions.each do |condition|
+ @recache[condition] << rule
+ end
+ end
+
+ if @recache.keys.sort == @code.conditions_cache.keys.sort
+ @recache.each_pair do |condition, rules|
+ if sort(rules) != sort(@code.conditions_cache[condition])
+ return false
+ end
+ end
+ true
+ else
+ false
+ end
+ end
+
+ def sort(rules)
+ rules.sort do |a, b|
+ a.object_id <=> b.object_id
+ end
+ end
+
+ def failure_message
+ require 'pp'
+ "expected in conditions_cache:\n#{@recache.pretty_inspect}" +
+ "got:\n#{@code.conditions_cache.pretty_inspect}"
+ end
+
+ def negative_failure_message
+ require 'pp'
+ "doesn't expected in conditions_cache:\n#{@recache.pretty_inspect}"
+ end
+ end
+
+ def have_actual_conditions_cache
+ ConditionsCacheMatcher.new
+ end
+end
View
1  vm/lib/d2na-vm/code.rb
@@ -195,6 +195,7 @@ def send_in(signal)
return self unless @input_signals.include? signal
check_signal_name(signal)
+ @diff = {}
@conditions_cache[signal].each do |rule|
rule.call(self) if 1 == @required[rule]
end
View
7 vm/lib/d2na-vm/rule.rb
@@ -34,7 +34,7 @@ class Rule
# Array of block commands (<tt>[:type, :name]</tt>). Type can be
# <tt>:up</tt>, <tt>:down</tt> (increment/decrement state) or <tt>:send</tt>
# (send output signal). Name should be state or output signal name.
- attr_reader :commands
+ attr_accessor :commands
# Create D²NA rule for +creator+ Code with special +conditions+. In block
# you can call +up+, +down+ and +send+ methods to add commands.
@@ -43,7 +43,7 @@ def initialize(conditions, creator = nil, &block)
@commands = []
@output = []
@states = []
-
+
if block_given?
instance_eval(&block)
compile
@@ -58,7 +58,8 @@ def initialize(conditions, creator = nil, &block)
creator.output(*@output)
creator.add_states(*@states)
end
- @output = @states = nil
+ remove_instance_variable :@output
+ remove_instance_variable :@states
end
# Run commands on +owner+.
Please sign in to comment.
Something went wrong with that request. Please try again.