require File.dirname(__FILE__) + '/../spec_helper' require File.dirname(__FILE__) + '/constraint_helper' class ChannelSampleProblem < Gecode::Model attr :elements attr :positions attr :sets def initialize @elements = int_var_array(4, 0..3) @elements.must_be.distinct @positions = int_var_array(4, 0..3) @positions.must_be.distinct @sets = set_var_array(4, [], 0..3) branch_on @positions end end class BoolChannelSampleProblem < Gecode::Model attr :bool_enum attr :bool attr :int def initialize @bool_enum = bool_var_array(4) @int = int_var(0..3) @bool = bool_var branch_on @int end end class SetChannelSampleProblem < Gecode::Model attr :bool_enum attr :set def initialize @bool_enum = bool_var_array(4) @set = set_var([], 0..3) branch_on @bool_enum end end describe Gecode::Constraints::IntEnum::Channel, ' (two int enums)' do before do @model = ChannelSampleProblem.new @positions = @model.positions @elements = @model.elements @invoke_options = lambda do |hash| @positions.must.channel @elements, hash @model.solve! end @expect_options = option_expectation do |strength, kind, reif_var| Gecode::Raw.should_receive(:channel).once.with( an_instance_of(Gecode::Raw::Space), an_instance_of(Gecode::Raw::IntVarArray), an_instance_of(Gecode::Raw::IntVarArray), strength, kind) end end it 'should translate into a channel constraint' do Gecode::Raw.should_receive(:channel).once.with( an_instance_of(Gecode::Raw::Space), anything, anything, Gecode::Raw::ICL_DEF, Gecode::Raw::PK_DEF) @invoke_options.call({}) end it 'should constrain variables to be channelled' do @elements.must.channel @positions @model.solve! elements = @model.elements.values positions = @model.elements.values elements.each_with_index do |element, i| element.should equal(positions.index(i)) end end it 'should not allow negation' do lambda{ @elements.must_not.channel @positions }.should raise_error( Gecode::MissingConstraintError) end it 'should raise error for unsupported right hand sides' do lambda{ @elements.must.channel 'hello' }.should raise_error(TypeError) end it_should_behave_like 'non-reifiable constraint' end describe Gecode::Constraints::SetEnum::Channel::IntChannelConstraint, ' (channel with set as right hand side)' do before do @model = ChannelSampleProblem.new @positions = @model.positions @sets = @model.sets @invoke_options = lambda do |hash| @positions.must.channel @sets, hash @model.solve! end @expect_options = option_expectation do |strength, kind, reif_var| Gecode::Raw.should_receive(:channel).once.with( an_instance_of(Gecode::Raw::Space), an_instance_of(Gecode::Raw::IntVarArray), an_instance_of(Gecode::Raw::SetVarArray)) end end it 'should translate into a channel constraint' do @expect_options.call({}) @positions.must.channel @sets @model.solve! end it 'should constrain variables to be channelled' do @positions.must.channel @sets @model.solve! sets = @model.sets positions = @model.positions.values positions.each_with_index do |position, i| sets[position].value.should include(i) end end it_should_behave_like 'non-reifiable set constraint' end describe Gecode::Constraints::SetEnum::Channel::IntChannelConstraint, ' (channel with set as left hand side)' do before do @model = ChannelSampleProblem.new @positions = @model.positions @sets = @model.sets @invoke_options = lambda do |hash| @sets.must.channel @positions, hash @model.solve! end @expect_options = option_expectation do |strength, kind, reif_var| Gecode::Raw.should_receive(:channel).once.with( an_instance_of(Gecode::Raw::Space), an_instance_of(Gecode::Raw::IntVarArray), an_instance_of(Gecode::Raw::SetVarArray)) end end it 'should translate into a channel constraint' do @expect_options.call({}) @sets.must.channel @positions @model.solve! end it 'should not allow negation' do lambda{ @sets.must_not.channel @positions }.should raise_error( Gecode::MissingConstraintError) end it 'should raise error for unsupported right hand sides' do lambda{ @sets.must.channel 'hello' }.should raise_error(TypeError) end it_should_behave_like 'non-reifiable set constraint' end # Requires @model, @bool and @int. Also requires @place_constraint which is a # method that takes five variables: a boolean variable, an integer variable, # the name of the equality method to use, whether or not the constraint should # be negated and a hash of options, and places the channel constraint on them. describe 'channel constraint between one int and one bool variable', :shared => true do before do @invoke_options = lambda do |hash| @place_constraint.call(@bool, @int, :==, false, hash) @model.solve! end @expect_options = option_expectation do |strength, kind, reif_var| Gecode::Raw.should_receive(:channel).once.with( an_instance_of(Gecode::Raw::Space), an_instance_of(Gecode::Raw::IntVar), an_instance_of(Gecode::Raw::BoolVar), strength, kind) end end ([:==] + Gecode::Constraints::Util::COMPARISON_ALIASES[:==]).each do |ali| it "should translate #{ali} into a channel constraint" do @expect_options.call({}) @place_constraint.call(@bool, @int, ali, false, {}) @model.solve! end end it 'should constrain the int variable to be 1 when the boolean variable is true' do @bool.must_be.true @place_constraint.call(@bool, @int, :==, false, {}) @model.solve! @int.value.should == 1 end it 'should constrain the int variable to be 0 when the boolean variable is false' do @bool.must_be.false @place_constraint.call(@bool, @int, :==, false, {}) @model.solve! @int.value.should == 0 end it 'should not allow negation' do lambda do @place_constraint.call(@bool, @int, :==, true, {}) end.should raise_error(Gecode::MissingConstraintError) end it_should_behave_like 'non-reifiable constraint' end describe Gecode::Constraints::Int::Channel, ' (one int and one bool variable)' do before do @model = BoolChannelSampleProblem.new @bool = @model.bool_var @int = @model.int_var @place_constraint = lambda do |bool, int, equals_method_name, negate, options| if negate int.must_not.method(equals_method_name).call(bool, options) else int.must.method(equals_method_name).call(bool, options) end end end it 'should not shadow linear boolean constraints' do lambda do (@bool + @bool).must == @bool @model.solve! end.should_not raise_error end it 'should raise error for unsupported right hand sides' do lambda{ @int.must == 'hello' }.should raise_error(TypeError) end it_should_behave_like 'channel constraint between one int and one bool variable' end describe Gecode::Constraints::Int::Channel, ' (one bool and one int variable)' do before do @model = BoolChannelSampleProblem.new @bool = @model.bool_var @int = @model.int_var @place_constraint = lambda do |bool, int, equals_method_name, negate, options| if negate bool.must_not.method(equals_method_name).call(int, options) else bool.must.method(equals_method_name).call(int, options) end end end it 'should not shadow linear boolean constraints' do lambda do @bool.must == @bool + @bool @model.solve! end.should_not raise_error end it 'should raise error for unsupported right hand sides' do lambda{ @bool.must == 'hello' }.should raise_error(TypeError) end it_should_behave_like 'channel constraint between one int and one bool variable' end # Requires @model, @bool_enum and @int. Also requires @place_constraint which # is a method that takes four variables: a boolean enum, an integer variable, # whether or not the constraint should be negated and a hash of options, and # places the channel constraint on them. describe 'channel constraint between bool enum and int variable', :shared => true do before do @invoke_options = lambda do |hash| @place_constraint.call(@bools, @int, false, hash) @model.solve! end @expect_options = option_expectation do |strength, kind, reif_var| Gecode::Raw.should_receive(:channel).once.with( an_instance_of(Gecode::Raw::Space), an_instance_of(Gecode::Raw::BoolVarArray), an_instance_of(Gecode::Raw::IntVar), 0, strength, kind) end end it 'should channel the bool enum with the integer variable' do @int.must > 2 @place_constraint.call(@bools, @int, false, {}) @model.solve!.should_not be_nil int_val = @int.value @bools.values.each_with_index do |bool, index| bool.should == (index == int_val) end end it 'should take the offset into account when channeling' do @int.must > 2 offset = 1 @place_constraint.call(@bools, @int, false, :offset => offset) @model.solve!.should_not be_nil int_val = @int.value @bools.values.each_with_index do |bool, index| bool.should == (index + offset == int_val) end end it 'should not allow negation' do lambda do @place_constraint.call(@bools, @int, true, {}) end.should raise_error(Gecode::MissingConstraintError) end it_should_behave_like 'non-reifiable constraint' end describe Gecode::Constraints::BoolEnum::Channel, ' (bool enum as lhs with int variable)' do before do @model = BoolChannelSampleProblem.new @bools = @model.bool_enum @int = @model.int @place_constraint = lambda do |bools, int, negate, options| unless negate bools.must.channel(int, options) else bools.must_not.channel(int, options) end end end it 'should raise error if an integer variable is not given as right hand side' do lambda do @bools.must.channel 'hello' end.should raise_error(TypeError) end it_should_behave_like 'channel constraint between bool enum and int variable' end describe Gecode::Constraints::BoolEnum::Channel, ' (int variable as lhs with bool enum)' do before do @model = BoolChannelSampleProblem.new @bools = @model.bool_enum @int = @model.int @place_constraint = lambda do |bools, int, negate, options| unless negate int.must.channel(bools, options) else int.must_not.channel(bools, options) end end end it 'should raise error if a boolean enum is not given as right hand side' do lambda do @int.must.channel 'hello' end.should raise_error(TypeError) end it_should_behave_like 'channel constraint between bool enum and int variable' end # Requires @model, @bool_enum and @set. Also requires @place_constraint which # is a method that takes four variables: a boolean enum, a set variable, # whether or not the constraint should be negated and a hash of options, and # places the channel constraint on them. describe 'channel constraint between set variable and bool enum', :shared => true do before do @invoke_options = lambda do |hash| @place_constraint.call(@bools, @set, false, hash) @model.solve! end @expect_options = option_expectation do |strength, kind, reif_var| Gecode::Raw.should_receive(:channel).once.with( an_instance_of(Gecode::Raw::Space), an_instance_of(Gecode::Raw::BoolVarArray), an_instance_of(Gecode::Raw::SetVar)) end end it 'should channel the bool enum with the set variable' do @set.must_be.superset_of [0, 2] @place_constraint.call(@bools, @set, false, {}) @model.solve!.should_not be_nil set_values = @set.value @bools.values.each_with_index do |bool, index| bool.should == set_values.include?(index) end end it 'should not allow negation' do lambda do @place_constraint.call(@bools, @set, true, {}) end.should raise_error(Gecode::MissingConstraintError) end it_should_behave_like 'non-reifiable set constraint' end describe Gecode::Constraints::Set::Channel, ' (set variable as lhs with bool enum)' do before do @model = SetChannelSampleProblem.new @bools = @model.bool_enum @set = @model.set @place_constraint = lambda do |bools, set, negate, options| unless negate set.must.channel(bools, options) else set.must_not.channel(bools, options) end end end it 'should raise error if a boolean enum is not given as right hand side' do lambda do @set.must.channel 'hello' end.should raise_error(TypeError) end it_should_behave_like 'channel constraint between set variable and bool enum' end describe Gecode::Constraints::Set::Channel, ' (bool enum as lhs with set variable)' do before do @model = SetChannelSampleProblem.new @bools = @model.bool_enum @set = @model.set @place_constraint = lambda do |bools, set, negate, options| unless negate bools.must.channel(set, options) else bools.must_not.channel(set, options) end end end it 'should raise error if an integer variable is not given as right hand side' do lambda do @bools.must.channel 'hello' end.should raise_error(TypeError) end it_should_behave_like 'channel constraint between set variable and bool enum' end