UNPKG

finitedomain

Version:

A fast feature rich finite domain solver

360 lines (306 loc) 10.5 kB
import expect from '../fixtures/mocha_proxy.fixt'; import { stripAnonVars, stripAnonVarsFromArrays, } from '../fixtures/domain.fixt'; import { countSolutions, } from '../fixtures/lib'; import Solver from '../../src/solver'; // These Solver specs focus on using Markov describe('solver.markov.spec', function() { this.timeout(60000); // takes long under istanbul / even longer under travis it('should exist', function() { expect(Solver).to.be.a('function'); }); it('should enforce markov which should only allow values from the legend', function() { // if this test fails, the default markov propagator is probably not added // any markov var should only allow solutions with values of the legend. // this markov config has only zeroes, your test probably failed with two // solutions where should only be one; that where V=0. // See solver_collectDistributionOverrides for the markov propagator. let solver = new Solver({}); solver.declRange('V', 0, 1, { valtype: 'markov', legend: [0, 0], // this means only 0 can be picked, regardless. bust otherwise. matrix: [ {vector: [1, 1]}, ], }); let solutions = solver.solve(); // will only try (and pass) V=0 because V=1 is not in the legend expect(countSolutions(solver)).to.equal(1); expect(solutions).to.eql([{V: 0}]); }); describe.skip('random functions', function() { const FUNCS = { default: undefined, 'Math.random': Math.random, // Multiverse.Random should be tested on its own in its own package... //'seeded': new Multiverse.Random "sjf20ru" // redundant. custom() { return Math.random(); }, MIN_FIRST() { return 0; }, MAX_FIRST() { return 1 - 1e-5; }, }; function setupSolverForRandomTest(random_func) { let solver = new Solver({}); solver.declRange('STATE', 0, 10); solver.declRange('V1', 0, 1, { valtype: 'markov', legend: [0, 1], random: random_func, matrix: [ { vector: [0.1, 1], boolVarName() { return solver.isEq('STATE', 5); }, }, { vector: [1, 1], }, ], }); solver.declRange('V2', 0, 1, { valtype: 'markov', legend: [0, 1], random: random_func, matrix: [ { vector: [0.1, 1], boolVarName() { return solver.isEq('STATE', 100); }, }, { vector: [1, 1], }, ], }); solver.eq('STATE', 5); return solver; } function testRandom(random_name) { let random_func = FUNCS[random_name]; describe(`random function: ${random_name}`, function() { it(`should have a fixed outcome for ${random_name}`, function() { if (random_name === 'MIN_FIRST') { let solver = setupSolverForRandomTest(random_func); let solutions = solver.solve({max: 1}); expect(stripAnonVarsFromArrays(solutions)).to.eql([{STATE: 5, V1: 0, V2: 0}]); } if (random_name === 'MAX_FIRST') { let solver = setupSolverForRandomTest(random_func); let solutions = solver.solve({max: 1}); expect(stripAnonVarsFromArrays(solutions)).to.eql([{STATE: 5, V1: 1, V2: 1}]); } }); it('should have a certain probability distribution of the random function when getting _one_ solution 1000x', function() { let v1_count = [0, 0]; let v2_count = [0, 0]; for (let i = 0; i < 1000; ++i) { let solver = setupSolverForRandomTest(random_func); let solutions = solver.solve({max: 1}); expect(countSolutions(solver)).to.equal(1); for (let k = 0; k < solutions.length; k++) { let solution = solutions[k]; v1_count[solution['V1']]++; v2_count[solution['V2']]++; } } switch (random_name) { case 'MIN_FIRST': expect(v1_count, 'minfirst v1 count').to.eql([1000, 0]); expect(v2_count, 'minfirst v2 count').to.eql([1000, 0]); break; case 'MAX_FIRST': expect(v1_count, 'maxfirst v1 count').to.eql([0, 1000]); expect(v2_count, 'maxfirst v2 count').to.eql([0, 1000]); break; default: // random functions are within 15% error assuming a normal distribution let v1_c = (v1_count[0] / v1_count[1]) - (1 / 10); //let v2_c = (v2_count[0] / v2_count[1]) - (1 / 10); expect(v1_c < 0.15, `${v1_c} < .15`).to.equal(true); //expect(v2_c > .85, `${v2_c} > .85`).to.equal(true); // TOFIX: confirm and fix this line.... } }); it('should express equal probabilities when getting _all_ solutions x500 times', function() { let v1_count = [0, 0]; let v2_count = [0, 0]; for (let i = 0; i < 500; ++i) { let solver = setupSolverForRandomTest(random_func); let solutions = solver.solve(); // no max! for (let k = 0; k < solutions.length; k++) { let solution = solutions[k]; v1_count[solution['V1']]++; v2_count[solution['V2']]++; } } expect(v1_count[0] === v1_count[1]).to.equal(true); expect(v2_count[0] === v2_count[1]).to.equal(true); }); }); } Object.keys(FUNCS).forEach(testRandom); }); it('should interpret large domain w/ sparse legend & 0 probability as a constraint', function() { let solver = new Solver({}); solver.declRange('A', 0, 10); solver.declRange('B', 0, 100, { valtype: 'markov', legend: [10, 100], matrix: [ { vector: [1, 0], boolVarName() { return solver.isEq('A', 5); }, }, { vector: [0, 1], }, ], }); solver.declRange('C', 0, 100, { valtype: 'markov', legend: [10, 100], matrix: [ { vector: [1, 0], boolVarName() { return solver.isEq('A', 100); }, }, { vector: [0, 1], }, ], }); solver.eq('A', 5); let solutions = solver.solve({vars: ['A', 'B', 'C']}); // there is only one solution for A (5), in which case B will // use vector [1,0] on legend [10,100], meaning 10. C will use // vector [0,1] on [10,100], meaning 100. so the only valid // solution can be A=5,B=10,C=100 expect(countSolutions(solver)).to.equal(1); expect(stripAnonVars(solutions[0])).to.eql({ A: 5, B: 10, C: 100, }); }); it('should expand vectors in markov with the expandVectorsWith option', function() { // same as previous markov test, but with incomplete, to-be-padded, vectors let solver = new Solver({}); solver.declRange('V1', 1, 4, { valtype: 'markov', legend: [1, 2], // 3,4] expandVectorsWith: 1, random() { return 0; }, // always pick first element matrix: [ {vector: [1, 1]}, // 1,1] padded by expandVectorsWith ], }); solver.declRange('V2', 1, 4, { valtype: 'markov', legend: [1, 2], // 3,4] expandVectorsWith: 1, random() { return 1 - 1e-5; }, // always pick last element matrix: [ {vector: [1, 1]}, // 1,1] padded by expandVectorsWith ], }); solver.gt('V1', 0); solver.gt('V2', 0); let solutions = solver.solve(); expect(countSolutions(solver)).to.equal(16); expect(stripAnonVarsFromArrays(solutions)).to.eql([ {V1: 1, V2: 4}, {V1: 1, V2: 3}, {V1: 1, V2: 2}, {V1: 1, V2: 1}, {V1: 2, V2: 4}, {V1: 2, V2: 3}, {V1: 2, V2: 2}, {V1: 2, V2: 1}, {V1: 3, V2: 4}, {V1: 3, V2: 3}, {V1: 3, V2: 2}, {V1: 3, V2: 1}, {V1: 4, V2: 4}, {V1: 4, V2: 3}, {V1: 4, V2: 2}, {V1: 4, V2: 1}, ]); }); it('Markov expandVectorsWith w/o any legend or vector', function() { let solver = new Solver({}); solver.declRange('V1', 1, 4, { valtype: 'markov', // legend: [1,2,3,4] expandVectorsWith: 1, random() { return 0; }, // pick first eligible legend value }); // matrix is added by expandVectorsWith, set to [1, 1, 1, 1] solver.declRange('V2', 1, 4, { valtype: 'markov', // legend: [1,2,3,4] expandVectorsWith: 1, random() { return 1 - 1e-5; }, // pick last eligible legend value }); // matrix is added by expandVectorsWith, set to [1, 1, 1, 1] solver.gt('V1', 0); solver.gt('V2', 0); let solutions = solver.solve(); expect(countSolutions(solver)).to.equal(16); expect(stripAnonVarsFromArrays(solutions)).to.eql([ {V1: 1, V2: 4}, {V1: 1, V2: 3}, {V1: 1, V2: 2}, {V1: 1, V2: 1}, {V1: 2, V2: 4}, {V1: 2, V2: 3}, {V1: 2, V2: 2}, {V1: 2, V2: 1}, {V1: 3, V2: 4}, {V1: 3, V2: 3}, {V1: 3, V2: 2}, {V1: 3, V2: 1}, {V1: 4, V2: 4}, {V1: 4, V2: 3}, {V1: 4, V2: 2}, {V1: 4, V2: 1}, ]); }); describe('markov legend should govern valid domain', () => it('should reject if legend contains no values in the domain without vector expansion', function() { // this examples the case where a solution automatically solves a markov // var before the markov distributor gets a chance to do anything let solver = new Solver(); solver.declRange('A_NORM', 0, 1); solver.declRange('B_MARK', 0, 1, { valtype: 'markov', legend: [2], matrix: [ {vector: [1]}, ], }); solver.gt('B_MARK', 'A_NORM'); // B can only become 2 as goverened by the legend, but the domain // never contained 2 so it will never be considered. No solution // should be possible as no valid value for B can be picked. solver.solve({ max: 1, distribute: { // distribute should be ignored as it should reject immediately varStrategy: {type: 'throw'}, valueStrategy: 'throw', }, }); expect(countSolutions(solver)).to.eql(0); }) ); });