UNPKG

finitedomain

Version:

A fast feature rich finite domain solver

534 lines (415 loc) 19.5 kB
import expect from '../fixtures/mocha_proxy.fixt'; import { fixt_arrdom_nums, fixt_arrdom_range, fixt_arrdom_ranges, fixt_arrdom_solved, fixt_dom_empty, fixt_dom_nums, fixt_dom_range, fixt_domainEql, stripAnonVars, } from '../fixtures/domain.fixt'; import { SUB, SUP, } from '../../src/helpers'; import { config_addConstraint, config_addVarAnonConstant, config_addVarAnonNothing, config_addVarAnonRange, config_addVarConstant, config_addVarDomain, config_addVarNothing, config_addVarRange, config_create, config_setOption, } from '../../src/config'; import { space_createClone, space_createRoot, space_generateVars, space_getUnsolvedVarCount, _space_getUnsolvedVarNamesFresh, space_initFromConfig, space_updateUnsolvedVarList, space_propagate, space_solution, space_toConfig, } from '../../src/space'; describe('src/space.spec', function() { describe('Space class', function() { describe('space_createRoot()', function() { it('should exist', function() { expect(space_createRoot).to.be.a('function'); }); it('should create a new instance', function() { // I dont want to test for instanceof... but i dont think we can change that due to ext. api. expect(space_createRoot()).to.be.an('object'); }); it('should init vars and var_names', function() { expect(space_createRoot().vardoms).to.be.an('array'); }); }); describe('space_createClone()', function() { let config; let space; let clone; beforeEach(function() { config = config_create(); space = space_createRoot(); space_initFromConfig(space, config); clone = space_createClone(space); }); it('should return a new space', function() { expect(clone).to.not.equal(space); }); it('should clone vardoms', function() { expect(space.vardoms).to.not.equal(clone.vardoms); }); it('should clone solved var list', function() { expect(space._unsolved).to.not.equal(clone._unsolved); }); it('should deep clone the vars', function() { //for var_name in config.allVarNames for (let i = 0; i < config.allVarNames.length; ++i) { let varName = config.allVarNames[i]; if (typeof clone.vardoms[varName] !== 'number') expect(clone.vardoms[varName]).to.not.equal(space.vardoms[varName]); expect(clone.vardoms[varName]).to.eql(space.vardoms[varName]); } }); }); describe('targeted vars', function() { it('should not add unconstrained vars when targeting all', function() { let config = config_create(); config_addVarRange(config, 'A', 32, 55); config_addVarRange(config, 'B', 0, 1); config_addVarRange(config, 'C', 0, 1); config.targetedVars = 'all'; let space = space_createRoot(); space_initFromConfig(space, config); expect(space_getUnsolvedVarCount(space, config)).to.eql(0); }); it('should use explicitly targeted vars regardless of being constrained', function() { let config = config_create(); config_addVarRange(config, 'A', 32, 55); config_addVarRange(config, 'B', 0, 1); config.targetedVars = ['A', 'B']; let space = space_createRoot(); space_initFromConfig(space, config); expect(_space_getUnsolvedVarNamesFresh(space, config).sort()).to.eql(['A', 'B']); }); it('should not care about the order of the var names', function() { let targets = ['B', 'A']; let config = config_create(); config_addVarRange(config, 'A', 32, 55); config_addVarRange(config, 'B', 0, 1); config.targetedVars = targets.slice(0); let space = space_createRoot(); space_initFromConfig(space, config); expect(_space_getUnsolvedVarNamesFresh(space, config).sort()).to.eql(targets.sort()); }); it('should throw if var names dont exist', function() { let config = config_create(); config_addVarRange(config, 'A', 32, 55); config_addVarRange(config, 'B', 0, 1); config_addVarRange(config, 'C', 0, 1); config.targetedVars = ['FAIL']; let space = space_createRoot(); expect(_ => space_initFromConfig(space, config)).to.throw('E_TARGETED_VARS_SHOULD_EXIST_NOW'); }); }); describe('space_isSolved()', function() { it('should return true if there are no vars', function() { let config = config_create(); let space = space_createRoot(); space_initFromConfig(space, config); expect(space_updateUnsolvedVarList(space, config)).to.equal(true); }); it('should return true if all 1 vars are solved', function() { let config = config_create(); let space = space_createRoot(); config_addVarAnonConstant(config, 1); space_initFromConfig(space, config); expect(space_updateUnsolvedVarList(space, config), 'only one solved var').to.equal(true); }); it('should return true if all 2 vars are solved', function() { let config = config_create(); let space = space_createRoot(); config_addVarAnonConstant(config, 1); config_addVarAnonConstant(config, 1); space_initFromConfig(space, config); expect(space_updateUnsolvedVarList(space, config), 'two solved vars').to.equal(true); }); it('should return false if one var is not solved and is targeted', function() { let config = config_create(); let space = space_createRoot(); config_addVarAnonRange(config, 0, 1); config.targetedVars = config.allVarNames.slice(0); space_initFromConfig(space, config); expect(space_updateUnsolvedVarList(space, config), 'only one unsolved var').to.equal(false); }); it('should have no unsolved var indexes if explicitly targeting no vars', function() { let config = config_create(); let space = space_createRoot(); config_addVarAnonRange(config, 0, 1); config.targetedVars = []; space_initFromConfig(space, config); expect(space_getUnsolvedVarCount(space, config), 'unsolved vars to solve').to.equal(0); }); it('should return false if at least one var of two is not solved and targeted', function() { let config = config_create(); let space = space_createRoot(); config_addVarAnonRange(config, 0, 1); config_addVarAnonRange(config, 0, 1); config.targetedVars = config.allVarNames.slice(0); space_initFromConfig(space, config); expect(space_updateUnsolvedVarList(space, config), 'two unsolved vars').to.equal(false); }); it('should return false if at least one var of two is not solved and not targeted', function() { let config = config_create(); let space = space_createRoot(); config_addVarAnonRange(config, 0, 1); config_addVarAnonRange(config, 0, 1); space_initFromConfig(space, config); expect(space_updateUnsolvedVarList(space, config), 'two unsolved vars').to.equal(true); }); it('should return false if at least one var of three is not solved and all targeted', function() { let config = config_create(); let space = space_createRoot(); config_addVarAnonRange(config, 0, 1); config_addVarAnonRange(config, 0, 1); config_addVarAnonConstant(config, 1); config.targetedVars = config.allVarNames.slice(0); space_initFromConfig(space, config); expect(space_updateUnsolvedVarList(space, config), 'two unsolved vars and a solved var').to.equal(false); }); it('should return false if at least one var of three is not solved and not targeted', function() { let config = config_create(); let space = space_createRoot(); config_addVarAnonRange(config, 0, 1); config_addVarAnonRange(config, 0, 1); config_addVarAnonConstant(config, 1); space_initFromConfig(space, config); expect(space_updateUnsolvedVarList(space, config), 'two unsolved vars and a solved var').to.equal(true); }); it('should return false if at least one var of three is not solved and only that one not is targeted', function() { let config = config_create(); let space = space_createRoot(); config_addVarAnonRange(config, 0, 1); config_addVarAnonRange(config, 0, 1); let A = config_addVarAnonConstant(config, 1); config.targetedVars = [config.allVarNames[A]]; space_initFromConfig(space, config); expect(space_updateUnsolvedVarList(space, config), 'two unsolved vars and a solved var').to.equal(true); }); it('should return false if at least one var of three is not solved and that one is targeted', function() { let config = config_create(); let space = space_createRoot(); let A = config_addVarAnonRange(config, 0, 1); let B = config_addVarAnonRange(config, 0, 1); config_addVarAnonConstant(config, 1); config.targetedVars = [config.allVarNames[A], config.allVarNames[B]]; space_initFromConfig(space, config); expect(space_updateUnsolvedVarList(space, config), 'two unsolved vars and a solved var').to.equal(false); }); }); describe('space_solution()', function() { it('should return an object, not array', function() { let config = config_create(); expect(space_solution(space_createRoot(), config)).to.be.an('object'); expect(space_solution(space_createRoot(), config)).not.to.be.an('array'); }); it('should return an empty object if there are no vars', function() { let config = config_create(); let space = space_createRoot(); expect(space_solution(space, config)).to.eql({}); }); it('should return false if a var covers no (more) elements', function() { let config = config_create(); let space = space_createRoot(); config_addVarDomain(config, 'test', fixt_arrdom_nums(100)); space_initFromConfig(space, config); space.vardoms[config.allVarNames.indexOf('test')] = fixt_dom_empty(); expect(space_solution(space, config)).to.eql({test: false}); }); it('should return the value of a var is solved', function() { let config = config_create(); let space = space_createRoot(); config_addVarDomain(config, 'test', fixt_arrdom_solved(5)); space_initFromConfig(space, config); expect(space_solution(space, config)).to.eql({test: 5}); }); it('should return the domain of a var if not yet determined', function() { let config = config_create(); let space = space_createRoot(); config_addVarRange(config, 'single_range', 10, 120); config_addVarDomain(config, 'multi_range', fixt_arrdom_ranges([10, 20], [30, 40])); config_addVarDomain(config, 'multi_range_with_solved', fixt_arrdom_ranges([18, 20], [25, 25], [30, 40])); space_initFromConfig(space, config); expect(space_solution(space, config)).to.eql({ single_range: fixt_arrdom_range(10, 120), multi_range: fixt_arrdom_ranges([10, 20], [30, 40]), multi_range_with_solved: fixt_arrdom_ranges([18, 20], [25, 25], [30, 40]), }); }); it('should not add anonymous vars to the result', function() { let config = config_create(); let space = space_createRoot(); config_addVarAnonConstant(config, 15); config_addVarConstant(config, 'addme', 20); space_initFromConfig(space, config); expect(stripAnonVars(space_solution(space, config))).to.eql({addme: 20}); }); }); describe('space_toConfig', function() { it('should convert a space to its config', function() { let config = config_create(); let space = space_createRoot(); space_initFromConfig(space, config); let config2 = config_create(); // fresh config object // if a space has no special things, it should produce a // fresh config... (but it's a fickle test at best) expect(space_toConfig(space, config)).to.eql(config2); }); it('should convert a space with a var without domain', function() { let config = config_create(); let space = space_createRoot(); // fresh space object config_addVarNothing(config, 'A'); // becomes [SUB SUP] space_initFromConfig(space, config); let config2 = space_toConfig(space, config); expect(config2.allVarNames).to.eql(['A']); expect(config2.initialDomains, 'empty property should exist').to.eql([fixt_dom_range(SUB, SUP)]); }); }); describe('space_propagate', function() { describe('simple cases', function() { it('should not reject this multiply case', function() { let config = config_create(); let space = space_createRoot(); config_addVarRange(config, 'A', 0, 10); config_addVarRange(config, 'B', 0, 10); config_addVarRange(config, 'MAX', 25, 25); config_addVarRange(config, 'MUL', 0, 100); config_addConstraint(config, 'ring-mul', ['A', 'B', 'MUL'], 'mul'); config_addConstraint(config, 'lt', ['MUL', 'MAX']); space_initFromConfig(space, config); expect(space_propagate(space, config)).to.eql(false); }); }); describe('vars tied to only one propagator', function() { it('step 0; two bools at start of search', function() { let config = config_create(); let space = space_createRoot(); config_addVarRange(config, 'A', 0, 1); config_addVarRange(config, 'B', 0, 1); config_addConstraint(config, 'neq', ['A', 'B']); space_initFromConfig(space, config); // A and B only connect to one propagator // at the start of a search nothing should change // so after propagate() the vars should remain the same space_propagate(space, config); expect(space.vardoms[config.allVarNames.indexOf('B')]).to.eql(fixt_dom_nums(0, 1)); expect(space.vardoms[config.allVarNames.indexOf('A')]).to.eql(fixt_dom_nums(0, 1)); }); it('step 1; first bool updated', function() { let config = config_create(); let space = space_createRoot(); config_addVarRange(config, 'A', 0, 0); config_addVarRange(config, 'B', 0, 1); config_addConstraint(config, 'neq', ['A', 'B']); space_initFromConfig(space, config); space.updatedVarIndex = config.allVarNames.indexOf('A'); // mark A as having been updated externally // A "was updated" by a distributor // since it ties to neq it should step that propagator which should // affect B and solve the space. if it doesn't that probably means // the propagator is incorrectly skipped (or hey, some other bug) space_propagate(space, config); fixt_domainEql(space.vardoms[config.allVarNames.indexOf('A')], fixt_dom_nums(0)); // we set it fixt_domainEql(space.vardoms[config.allVarNames.indexOf('B')], fixt_dom_nums(1)); // by neq }); it('step 1; second bool updated', function() { let config = config_create(); let space = space_createRoot(); config_addVarRange(config, 'A', 0, 1); config_addVarRange(config, 'B', 0, 0); config_addConstraint(config, 'neq', ['A', 'B']); space_initFromConfig(space, config); space.updatedVarIndex = config.allVarNames.indexOf('B'); // mark A as having been updated externally // B "was updated" by a distributor // since it ties to neq it should step that propagator which should // affect A and solve the space. if it doesn't that probably means // the propagator is incorrectly skipped (or hey, some other bug) space_propagate(space, config); fixt_domainEql(space.vardoms[config.allVarNames.indexOf('A')], fixt_dom_nums(1)); // by neq fixt_domainEql(space.vardoms[config.allVarNames.indexOf('B')], fixt_dom_nums(0)); // we set it }); }); describe('timeout callback', function() { it('should ignore timeout callback if not set at all', function() { // (base timeout callback test) let config = config_create(); let space = space_createRoot(); config_addVarRange(config, 'A', 0, 10); config_addVarRange(config, 'B', 0, 10); config_addConstraint(config, 'lt', ['A', 'B']); space_initFromConfig(space, config); expect(space_propagate(space, config)).to.eql(false); }); it('should not break early if callback doesnt return true', function() { let config = config_create(); let space = space_createRoot(); config_addVarRange(config, 'A', 0, 10); config_addVarRange(config, 'B', 0, 10); config_addConstraint(config, 'lt', ['A', 'B']); config_setOption(config, 'timeoutCallback', _ => false); space_initFromConfig(space, config); expect(space_propagate(space, config)).to.eql(false); }); it('should break early if callback returns true', function() { let config = config_create(); let space = space_createRoot(); config_addVarRange(config, 'A', 0, 10); config_addVarRange(config, 'B', 0, 10); config_addConstraint(config, 'lt', ['A', 'B']); config_setOption(config, 'timeoutCallback', _ => true); space_initFromConfig(space, config); expect(space_propagate(space, config)).to.eql(true); }); }); }); describe('space_generateVars', function() { it('should exist', function() { expect(space_generateVars).to.be.a('function'); }); it('should require config and space', function() { let config = config_create(); let space = space_createRoot(); expect(_ => space_generateVars(space, {})).to.throw('EXPECTING_CONFIG'); expect(_ => space_generateVars({}, config)).to.throw('SPACE_SHOULD_BE_SPACE'); }); it('should create a constant', function() { let config = config_create(); let name = config_addVarAnonConstant(config, 10); let space = space_createRoot(); space_generateVars(space, config); fixt_domainEql(space.vardoms[name], fixt_dom_nums(10)); }); it('should create a full width var', function() { let config = config_create(); let name = config_addVarAnonNothing(config); let space = space_createRoot(); space_generateVars(space, config); expect(space.vardoms[name]).to.eql(fixt_dom_range(SUB, SUP)); }); it('should clone a domained var', function() { let config = config_create(); let name = config_addVarAnonRange(config, 32, 55); let space = space_createRoot(); space_generateVars(space, config); expect(space.vardoms[name]).to.eql(fixt_dom_range(32, 55)); }); }); }); });