UNPKG

finitedomain

Version:

A fast feature rich finite domain solver

292 lines (247 loc) 11.3 kB
import expect from '../../fixtures/mocha_proxy.fixt'; import { fixt_arrdom_range, fixt_dom_clone, fixt_dom_nums, fixt_dom_range, fixt_domainEql, stripAnonVarsFromArrays, } from '../../fixtures/domain.fixt'; import { countSolutions, } from '../../fixtures/lib'; import { LOG_FLAG_PROPSTEPS, LOG_FLAG_NONE, ASSERT_SET_LOG, } from '../../../src/helpers'; import { config_addVarDomain, config_create, } from '../../../src/config'; import { space_createRoot, space_initFromConfig, } from '../../../src/space'; import Solver from '../../../src/solver'; import propagator_reifiedStepBare from '../../../src/propagators/reified'; import { propagator_eqStepBare, propagator_eqStepWouldReject, } from '../../../src/propagators/eq'; import { propagator_neqStepBare, propagator_neqStepWouldReject, } from '../../../src/propagators/neq'; describe('propagators/reified.spec', function() { // constants (tests must copy args) let zero = fixt_dom_nums(0); let one = fixt_dom_nums(1); let bool = fixt_dom_nums(0, 1); describe('propagator_reifiedStepBare', function() { it('should exist', function() { expect(propagator_reifiedStepBare).to.be.a('function'); }); describe('enforce=false', function() { // rif -> reified ;) function riftest(A_in, B_in, bool_in, op, invop, bool_after, msg) { // test one step call with two vars and an op and check results it(`reified_step call [${msg}] with: ${[`A=[${A_in}]`, `B=[${B_in}]`, `bool=[${bool_in}]`, `op=${op}`, `inv=${invop}`, `result=[${bool_after}]`]}`, function() { let config = config_create(); config_addVarDomain(config, 'A', fixt_dom_clone(A_in, 'array')); config_addVarDomain(config, 'B', fixt_dom_clone(B_in, 'array')); config_addVarDomain(config, 'bool', fixt_dom_clone(bool_in, 'array')); let space = space_createRoot(); space_initFromConfig(space, config); expect(op === 'eq' || op === 'neq', 'if this breaks just update the test and update the new values').to.equal(true); let opFunc = op === 'eq' ? propagator_eqStepBare : propagator_neqStepBare; let nopFunc = op !== 'eq' ? propagator_eqStepBare : propagator_neqStepBare; let rejectsOp = op === 'eq' ? propagator_eqStepWouldReject : propagator_neqStepWouldReject; let rejectsNop = op !== 'eq' ? propagator_eqStepWouldReject : propagator_neqStepWouldReject; let A = config.allVarNames.indexOf('A'); let B = config.allVarNames.indexOf('B'); let bool = config.allVarNames.indexOf('bool'); propagator_reifiedStepBare(space, config, A, B, bool, opFunc, nopFunc, op, invop, rejectsOp, rejectsNop); fixt_domainEql(space.vardoms[A], A_in, 'A should be unchanged'); fixt_domainEql(space.vardoms[B], B_in, 'B should be unchanged'); fixt_domainEql(space.vardoms[bool], bool_after, 'bool should reflect expected outcome'); }); } describe('eq/neq with bools', function() { riftest(bool, bool, bool, 'eq', 'neq', bool, 'undetermined because eq/neq can only be determined when A and B are resolved'); riftest(bool, bool, bool, 'neq', 'eq', bool, 'undetermined because eq/neq can only be determined when A and B are resolved'); riftest(bool, zero, bool, 'eq', 'neq', bool, 'A is not resolved so not yet able to resolve bool'); riftest(bool, zero, bool, 'neq', 'eq', bool, 'A is not resolved so not yet able to resolve bool'); riftest(bool, one, bool, 'eq', 'neq', bool, 'A is not resolved so not yet able to resolve bool'); riftest(bool, one, bool, 'neq', 'eq', bool, 'A is not resolved so not yet able to resolve bool'); riftest(zero, bool, bool, 'eq', 'neq', bool, 'B is not resolved so not yet able to resolve bool'); riftest(zero, bool, bool, 'neq', 'eq', bool, 'B is not resolved so not yet able to resolve bool'); riftest(one, bool, bool, 'eq', 'neq', bool, 'B is not resolved so not yet able to resolve bool'); riftest(one, bool, bool, 'neq', 'eq', bool, 'B is not resolved so not yet able to resolve bool'); riftest(one, one, bool, 'eq', 'neq', one, 'A and B are resolved and eq so bool should be 1'); riftest(one, one, bool, 'neq', 'eq', zero, 'A and B are resolved and not eq so bool should be 0'); riftest(one, zero, bool, 'eq', 'neq', zero, 'A and B are resolved and not eq so bool should be 0'); riftest(one, zero, bool, 'neq', 'eq', one, 'A and B are resolved and neq so bool should be 1'); riftest(zero, one, bool, 'eq', 'neq', zero, 'A and B are resolved and not eq so bool should be 0'); riftest(zero, one, bool, 'neq', 'eq', one, 'A and B are resolved and neq so bool should be 1'); riftest(zero, zero, bool, 'eq', 'neq', one, 'A and B are resolved and eq so bool should be 1'); riftest(zero, zero, bool, 'neq', 'eq', zero, 'A and B are resolved and not eq so bool should be 0'); }); describe('eq/neq with non-bools', function() { riftest(fixt_dom_range(0, 5), fixt_dom_range(10, 15), bool, 'eq', 'neq', zero, 'undetermined but can proof eq is impossible'); riftest(fixt_dom_range(0, 5), fixt_dom_range(3, 8), bool, 'eq', 'neq', bool, 'undetermined but with overlap so cannot proof eq/neq yet'); riftest(fixt_dom_range(0, 5), one, bool, 'eq', 'neq', bool, 'A is undetermined and B is in A range so cannot proof eq/neq yet'); riftest(fixt_dom_range(110, 120), one, bool, 'eq', 'neq', zero, 'A is undetermined but B is NOT in A range must be neq'); }); }); describe('with LOG', function() { before(function() { ASSERT_SET_LOG(LOG_FLAG_PROPSTEPS); }); it('should improve test coverage by enabling logging', function() { let config = config_create(); config_addVarDomain(config, 'A', fixt_arrdom_range(0, 1)); config_addVarDomain(config, 'B', fixt_arrdom_range(0, 1)); config_addVarDomain(config, 'C', fixt_arrdom_range(0, 1)); let space = space_createRoot(); space_initFromConfig(space, config); let A = config.allVarNames.indexOf('A'); let B = config.allVarNames.indexOf('B'); let C = config.allVarNames.indexOf('C'); propagator_reifiedStepBare(space, config, A, B, C, propagator_eqStepBare, propagator_neqStepBare, 'eq', 'neq', propagator_eqStepWouldReject, propagator_neqStepWouldReject); expect(true).to.eql(true); }); after(function() { ASSERT_SET_LOG(LOG_FLAG_NONE); }); }); }); describe('solver test', function() { it('should not let reifiers influence results if they are not forced', function() { let solver = new Solver(); solver.declRange('A', 0, 1); solver.declRange('B', 0, 1); solver.declRange('C', 0, 1); solver.isEq('A', 'B', solver.decl('AnotB', [0, 1])); solver.solve({}); // a, b, c are not constrainted in any way, so 2^3=8 expect(countSolutions(solver)).to.equal(8); expect(stripAnonVarsFromArrays(solver.solutions)).to.eql([ {A: 0, B: 0, C: [0, 1], AnotB: 1}, {A: 0, B: 1, C: [0, 1], AnotB: 0}, {A: 1, B: 0, C: [0, 1], AnotB: 0}, {A: 1, B: 1, C: [0, 1], AnotB: 1}, ]); }); it('should reduce vars to a solution if they are targeted expicitly', function() { let solver = new Solver(); solver.declRange('A', 0, 1); solver.declRange('B', 0, 1); solver.declRange('C', 0, 1); solver.isEq('A', 'B', solver.decl('AnotB', [0, 1])); solver.solve({vars: ['A', 'B', 'C']}); // a, b, c are not constrainted in any way, so 2^3=8 // explicitly targeted so require a single value for all solutions expect(countSolutions(solver)).to.equal(8); expect(solver.solutions).to.eql([ {A: 0, B: 0, C: 0, AnotB: 1}, {A: 0, B: 0, C: 1, AnotB: 1}, {A: 0, B: 1, C: 0, AnotB: 0}, {A: 0, B: 1, C: 1, AnotB: 0}, {A: 1, B: 0, C: 0, AnotB: 0}, {A: 1, B: 0, C: 1, AnotB: 0}, {A: 1, B: 1, C: 0, AnotB: 1}, {A: 1, B: 1, C: 1, AnotB: 1}, ]); }); it('should be able to force a reifier to be true and affect the outcome when not targeted', function() { let solver = new Solver(); solver.declRange('A', 0, 1); solver.declRange('B', 0, 1); solver.declRange('C', 0, 1); solver.isEq('A', 'B', solver.decl('AisB')); solver.eq('AisB', 1); solver.solve({}); // all vars start with default domain, [0,1] // AisB is forced to 1 // therefor A cannot be B // C is unbound // so the only two valid outcomes are A=0,B=1 and A=1,B=0. The value // for C is irrelevant so x2, the value of AisB is always 1. expect(countSolutions(solver)).to.equal(4); expect(stripAnonVarsFromArrays(solver.solutions)).to.eql([ {A: 0, B: 0, C: [0, 1], AisB: 1}, {A: 1, B: 1, C: [0, 1], AisB: 1}, ]); }); it('should be able to force a reifier to be true and affect the outcome when targeted', function() { let solver = new Solver(); solver.declRange('A', 0, 1); solver.declRange('B', 0, 1); solver.declRange('C', 0, 1); solver.isEq('A', 'B', solver.decl('AisB')); solver.eq('AisB', 1); let solutions = solver.solve({vars: ['A', 'B', 'C']}); // all vars start with default domain, [0,1] // AisB is forced to 1 // therefor A cannot be B // C is unbound // so the only two valid outcomes are A=0,B=1 and A=1,B=0. The value // for C is irrelevant, the value of AisB is always 1. Two outcomes. expect(countSolutions(solver)).to.equal(4); // C is reduced to a single var because it is expect(stripAnonVarsFromArrays(solutions)).to.eql([ {A: 0, B: 0, C: 0, AisB: 1}, {A: 0, B: 0, C: 1, AisB: 1}, {A: 1, B: 1, C: 0, AisB: 1}, {A: 1, B: 1, C: 1, AisB: 1}, ]); }); it('should not adjust operands if result var is unconstrained', function() { let solver = new Solver(); solver.declRange('A', 0, 10); solver.isEq('A', 2); solver.solve(); expect(stripAnonVarsFromArrays(solver.solutions)).to.eql([ {A: 0}, {A: 1}, {A: 2}, {A: 3}, {A: 4}, {A: 5}, {A: 6}, {A: 7}, {A: 8}, {A: 9}, {A: 10}, ]); }); it('should adjust operands if result var is constrained to 0', function() { let solver = new Solver(); solver.declRange('A', 0, 10); solver.isEq('A', 2, 0); solver.solve(); expect(stripAnonVarsFromArrays(solver.solutions)).to.eql([ {A: 0}, {A: 1}, {A: 3}, {A: 4}, {A: 5}, {A: 6}, {A: 7}, {A: 8}, {A: 9}, {A: 10}, ]); }); it('should adjust operands if result var is constrained to 1', function() { let solver = new Solver(); solver.declRange('A', 0, 10); solver.isEq(2, 'A', 1); solver.solve(); expect(stripAnonVarsFromArrays(solver.solutions)).to.eql([ {A: 2}, ]); }); }); });