fdo
Version:
A brute force Finite Domain Solver
502 lines (446 loc) • 17.4 kB
JavaScript
import {
ASSERT,
THROW,
} from '../../fdlib/src/helpers';
import {
config_addPropagator,
config_addVarAnonNothing,
} from './config';
import propagator_markovStepBare from './propagators/markov';
import propagator_reifiedStepBare from './propagators/reified';
import propagator_ringStepBare from './propagators/ring';
import propagator_minStep from './propagators/min';
import propagator_mulStep from './propagators/mul';
import propagator_divStep from './propagators/div';
import {
propagator_gtStepBare,
propagator_gtStepWouldReject,
propagator_ltStepBare,
propagator_ltStepWouldReject,
} from './propagators/lt';
import {
propagator_gteStepBare,
propagator_gteStepWouldReject,
propagator_lteStepBare,
propagator_lteStepWouldReject,
} from './propagators/lte';
import {
propagator_eqStepBare,
propagator_eqStepWouldReject,
} from './propagators/eq';
import {
propagator_neqStepBare,
propagator_neqStepWouldReject,
} from './propagators/neq';
import {
//domain__debug,
domain_invMul,
domain_minus,
domain_mul,
domain_plus,
} from '../../fdlib/src/domain';
// BODY_START
/**
* @param {string} name
* @param {Function} stepFunc
* @param {number} index1
* @param {number} [index2=-1]
* @param {number} [index3=-1]
* @param {string} [arg1='']
* @param {string} [arg2='']
* @param {string} [arg3='']
* @param {string} [arg4='']
* @param {string} [arg5='']
* @param {string} [arg6='']
* @returns {$propagator}
*/
function propagator_create(name, stepFunc, index1, index2, index3, arg1, arg2, arg3, arg4, arg5, arg6) {
return {
_class: '$propagator',
name: name,
stepper: stepFunc,
index1: index1 === undefined ? -1 : index1,
index2: index2 === undefined ? -1 : index2,
index3: index3 === undefined ? -1 : index3,
arg1: arg1 === undefined ? '' : arg1,
arg2: arg2 === undefined ? '' : arg2,
arg3: arg3 === undefined ? '' : arg3,
arg4: arg4 === undefined ? '' : arg4,
arg5: arg5 === undefined ? '' : arg5,
arg6: arg6 === undefined ? '' : arg6,
};
}
/**
* Adds propagators which reify the given operator application
* to the given boolean variable.
*
* `opname` is a string giving the name of the comparison
* operator to reify. Currently, 'eq', 'neq', 'lt', 'lte', 'gt' and 'gte'
* are supported.
*
* `leftVarIndex` and `rightVarIndex` are the arguments accepted
* by the comparison operator.
*
* `resultVarIndex` is the name of the boolean variable to which to
* reify the comparison operator. Note that this boolean
* variable must already have been declared. If this argument
* is omitted from the call, then the `reified` function can
* be used in "functional style" and will return the name of
* the reified boolean variable which you can pass to other
* propagator creator functions.
*
* @param {$config} config
* @param {string} opname
* @param {number} leftVarIndex
* @param {number} rightVarIndex
* @param {number} resultVarIndex
*/
function propagator_addReified(config, opname, leftVarIndex, rightVarIndex, resultVarIndex) {
ASSERT(config._class === '$config', 'EXPECTING_CONFIG');
ASSERT(typeof opname === 'string', 'OP_SHOULD_BE_STRING');
ASSERT(typeof leftVarIndex === 'number' && leftVarIndex >= 0, 'LEFT_VAR_SHOULD_BE_VALID_INDEX', leftVarIndex);
ASSERT(typeof rightVarIndex === 'number' && rightVarIndex >= 0, 'RIGHT_VAR_SHOULD_BE_VALID_INDEX', rightVarIndex);
ASSERT(typeof resultVarIndex === 'number' && resultVarIndex >= 0, 'RESULT_VAR_SHOULD_BE_VALID_INDEX', resultVarIndex);
let nopName;
let opFunc;
let nopFunc;
let opRejectChecker;
let nopRejectChecker;
switch (opname) {
case 'eq': {
nopName = 'neq';
opFunc = propagator_eqStepBare;
nopFunc = propagator_neqStepBare;
opRejectChecker = propagator_eqStepWouldReject;
nopRejectChecker = propagator_neqStepWouldReject;
break;
}
case 'neq': {
nopName = 'eq';
opFunc = propagator_neqStepBare;
nopFunc = propagator_eqStepBare;
opRejectChecker = propagator_neqStepWouldReject;
nopRejectChecker = propagator_eqStepWouldReject;
break;
}
case 'lt':
opFunc = propagator_ltStepBare;
opRejectChecker = propagator_ltStepWouldReject;
nopName = 'gte';
nopFunc = propagator_gteStepBare;
nopRejectChecker = propagator_gteStepWouldReject;
break;
case 'lte':
opFunc = propagator_lteStepBare;
opRejectChecker = propagator_lteStepWouldReject;
nopName = 'gt';
nopFunc = propagator_gtStepBare;
nopRejectChecker = propagator_gtStepWouldReject;
break;
case 'gt':
return propagator_addReified(config, 'lt', rightVarIndex, leftVarIndex, resultVarIndex);
case 'gte':
return propagator_addReified(config, 'lte', rightVarIndex, leftVarIndex, resultVarIndex);
default:
THROW('UNKNOWN_REIFIED_OP');
}
config_addPropagator(config, propagator_create('reified', propagator_reifiedStepBare, leftVarIndex, rightVarIndex, resultVarIndex, opFunc, nopFunc, opname, nopName, opRejectChecker, nopRejectChecker));
}
/**
* Domain equality propagator. Creates the propagator
* in given config.
* Can pass in vars or numbers that become anonymous
* vars. Must at least pass in one var because the
* propagator would be useless otherwise.
*
* @param {$config} config
* @param {number} leftVarIndex
* @param {number} rightVarIndex
*/
function propagator_addEq(config, leftVarIndex, rightVarIndex) {
ASSERT(config._class === '$config', 'EXPECTING_CONFIG');
ASSERT(typeof leftVarIndex === 'number' && leftVarIndex >= 0, 'LEFT_VAR_SHOULD_BE_VALID_INDEX', leftVarIndex);
ASSERT(typeof rightVarIndex === 'number' && rightVarIndex >= 0, 'RIGHT_VAR_SHOULD_BE_VALID_INDEX', rightVarIndex);
config_addPropagator(config, propagator_create('eq', propagator_eqStepBare, leftVarIndex, rightVarIndex));
}
/**
* Less than propagator. See general propagator nores
* for fdeq which also apply to this one.
*
* @param {$config} config
* @param {number} leftVarIndex
* @param {number} rightVarIndex
*/
function propagator_addLt(config, leftVarIndex, rightVarIndex) {
ASSERT(config._class === '$config', 'EXPECTING_CONFIG');
ASSERT(typeof leftVarIndex === 'number' && leftVarIndex >= 0, 'LEFT_VAR_SHOULD_BE_VALID_INDEX', leftVarIndex);
ASSERT(typeof rightVarIndex === 'number' && rightVarIndex >= 0, 'RIGHT_VAR_SHOULD_BE_VALID_INDEX', rightVarIndex);
config_addPropagator(config, propagator_create('lt', propagator_ltStepBare, leftVarIndex, rightVarIndex));
}
/**
* Greater than propagator.
*
* @param {$config} config
* @param {number} leftVarIndex
* @param {number} rightVarIndex
*/
function propagator_addGt(config, leftVarIndex, rightVarIndex) {
// _swap_ v1 and v2 because: a>b is b<a
propagator_addLt(config, rightVarIndex, leftVarIndex);
}
/**
* Less than or equal to propagator.
*
* @param {$config} config
* @param {number} leftVarIndex
* @param {number} rightVarIndex
*/
function propagator_addLte(config, leftVarIndex, rightVarIndex) {
ASSERT(config._class === '$config', 'EXPECTING_CONFIG');
ASSERT(typeof leftVarIndex === 'number' && leftVarIndex >= 0, 'LEFT_VAR_SHOULD_BE_VALID_INDEX', leftVarIndex);
ASSERT(typeof rightVarIndex === 'number' && rightVarIndex >= 0, 'RIGHT_VAR_SHOULD_BE_VALID_INDEX', rightVarIndex);
config_addPropagator(config, propagator_create('lte', propagator_lteStepBare, leftVarIndex, rightVarIndex));
}
/**
* @param {$config} config
* @param {number} leftVarIndex
* @param {number} rightVarIndex
* @param {number} resultVarIndex
*/
function propagator_addMul(config, leftVarIndex, rightVarIndex, resultVarIndex) {
ASSERT(config._class === '$config', 'EXPECTING_CONFIG');
ASSERT(typeof leftVarIndex === 'number' && leftVarIndex >= 0, 'LEFT_VAR_SHOULD_BE_VALID_INDEX', leftVarIndex);
ASSERT(typeof rightVarIndex === 'number' && rightVarIndex >= 0, 'RIGHT_VAR_SHOULD_BE_VALID_INDEX', rightVarIndex);
ASSERT(typeof resultVarIndex === 'number' && resultVarIndex >= 0, 'RESULT_VAR_SHOULD_BE_VALID_INDEX', resultVarIndex);
config_addPropagator(config, propagator_create('mul', propagator_mulStep, leftVarIndex, rightVarIndex, resultVarIndex));
}
/**
* @param {$config} config
* @param {number} leftVarIndex
* @param {number} rightVarIndex
* @param {number} resultVarIndex
*/
function propagator_addDiv(config, leftVarIndex, rightVarIndex, resultVarIndex) {
ASSERT(config._class === '$config', 'EXPECTING_CONFIG');
ASSERT(typeof leftVarIndex === 'number' && leftVarIndex >= 0, 'LEFT_VAR_SHOULD_BE_VALID_INDEX', leftVarIndex);
ASSERT(typeof rightVarIndex === 'number' && rightVarIndex >= 0, 'RIGHT_VAR_SHOULD_BE_VALID_INDEX', rightVarIndex);
ASSERT(typeof resultVarIndex === 'number' && resultVarIndex >= 0, 'RESULT_VAR_SHOULD_BE_VALID_INDEX', resultVarIndex);
config_addPropagator(config, propagator_create('div', propagator_divStep, leftVarIndex, rightVarIndex, resultVarIndex));
}
/**
* Greater than or equal to.
*
* @param {$config} config
* @param {number} leftVarIndex
* @param {number} rightVarIndex
*/
function propagator_addGte(config, leftVarIndex, rightVarIndex) {
// _swap_ v1 and v2 because: a>=b is b<=a
propagator_addLte(config, rightVarIndex, leftVarIndex);
}
/**
* Ensures that the two variables take on different values.
*
* @param {$config} config
* @param {number} leftVarIndex
* @param {number} rightVarIndex
*/
function propagator_addNeq(config, leftVarIndex, rightVarIndex) {
ASSERT(config._class === '$config', 'EXPECTING_CONFIG');
ASSERT(typeof leftVarIndex === 'number' && leftVarIndex >= 0, 'LEFT_VAR_SHOULD_BE_VALID_INDEX', leftVarIndex);
ASSERT(typeof rightVarIndex === 'number' && rightVarIndex >= 0, 'RIGHT_VAR_SHOULD_BE_VALID_INDEX', rightVarIndex);
config_addPropagator(config, propagator_create('neq', propagator_neqStepBare, leftVarIndex, rightVarIndex));
}
/**
* Takes an arbitrary number of FD variables and adds propagators that
* ensure that they are pairwise distinct.
*
* @param {$config} config
* @param {number[]} varIndexes
*/
function propagator_addDistinct(config, varIndexes) {
ASSERT(config._class === '$config', 'EXPECTING_CONFIG');
for (let i = 0; i < varIndexes.length; i++) {
let varIndex = varIndexes[i];
for (let j = 0; j < i; ++j) {
propagator_addNeq(config, varIndex, varIndexes[j]);
}
}
}
/**
* @param {$config} config
* @param {string} targetOpName
* @param {string} invOpName
* @param {Function} opFunc
* @param {Function} nopFunc
* @param {number} leftVarIndex
* @param {number} rightVarIndex
* @param {number} resultVarIndex
*/
function propagator_addRingPlusOrMul(config, targetOpName, invOpName, opFunc, nopFunc, leftVarIndex, rightVarIndex, resultVarIndex) {
ASSERT(config._class === '$config', 'EXPECTING_CONFIG');
ASSERT(typeof targetOpName === 'string', 'OP_SHOULD_BE_STRING');
ASSERT(typeof invOpName === 'string', 'INV_OP_SHOULD_BE_STRING');
ASSERT(typeof leftVarIndex === 'number' && leftVarIndex >= 0, 'LEFT_VAR_SHOULD_BE_VALID_INDEX', leftVarIndex);
ASSERT(typeof rightVarIndex === 'number' && rightVarIndex >= 0, 'RIGHT_VAR_SHOULD_BE_VALID_INDEX', rightVarIndex);
ASSERT(typeof resultVarIndex === 'number' && resultVarIndex >= 0, 'RESULT_VAR_SHOULD_BE_VALID_INDEX', resultVarIndex);
propagator_addRing(config, leftVarIndex, rightVarIndex, resultVarIndex, targetOpName, opFunc);
propagator_addRing(config, resultVarIndex, rightVarIndex, leftVarIndex, invOpName, nopFunc);
propagator_addRing(config, resultVarIndex, leftVarIndex, rightVarIndex, invOpName, nopFunc);
}
/**
* @param {$config} config
* @param {string} A
* @param {string} B
* @param {string} C
* @param {string} opName
* @param {Function} opFunc
*/
function propagator_addRing(config, A, B, C, opName, opFunc) {
ASSERT(config._class === '$config', 'EXPECTING_CONFIG');
ASSERT(typeof A === 'number' && A >= 0, 'LEFT_VAR_SHOULD_BE_VALID_INDEX', A);
ASSERT(typeof B === 'number' && B >= 0, 'RIGHT_VAR_SHOULD_BE_VALID_INDEX', B);
ASSERT(typeof C === 'number' && C >= 0, 'RESULT_VAR_SHOULD_BE_VALID_INDEX', C);
config_addPropagator(config, propagator_create('ring', propagator_ringStepBare, A, B, C, opName, opFunc));
}
/**
* Bidirectional addition propagator.
*
* @param {$config} config
* @param {number} leftVarIndex
* @param {number} rightVarIndex
* @param {number} resultVarIndex
*/
function propagator_addPlus(config, leftVarIndex, rightVarIndex, resultVarIndex) {
propagator_addRingPlusOrMul(config, 'plus', 'min', domain_plus, domain_minus, leftVarIndex, rightVarIndex, resultVarIndex);
}
/**
* @param {$config} config
* @param {number} leftVarIndex
* @param {number} rightVarIndex
* @param {number} resultVarIndex
*/
function propagator_addMin(config, leftVarIndex, rightVarIndex, resultVarIndex) {
ASSERT(config._class === '$config', 'EXPECTING_CONFIG');
ASSERT(typeof leftVarIndex === 'number' && leftVarIndex >= 0, 'LEFT_VAR_SHOULD_BE_VALID_INDEX', leftVarIndex);
ASSERT(typeof rightVarIndex === 'number' && rightVarIndex >= 0, 'RIGHT_VAR_SHOULD_BE_VALID_INDEX', rightVarIndex);
ASSERT(typeof resultVarIndex === 'number' && resultVarIndex >= 0, 'RESULT_VAR_SHOULD_BE_VALID_INDEX', resultVarIndex);
config_addPropagator(config, propagator_create('min', propagator_minStep, leftVarIndex, rightVarIndex, resultVarIndex));
}
/**
* Bidirectional multiplication propagator.
*
* @param {$config} config
* @param {number} leftVarIndex
* @param {number} rightVarIndex
* @param {number} resultVarIndex
*/
function propagator_addRingMul(config, leftVarIndex, rightVarIndex, resultVarIndex) {
propagator_addRingPlusOrMul(config, 'mul', 'div', domain_mul, domain_invMul, leftVarIndex, rightVarIndex, resultVarIndex);
}
/**
* Sum of N domains = resultVar
* Creates as many anonymous varIndexes as necessary.
*
* @param {$config} config
* @param {number[]} varIndexes
* @param {number} resultVarIndex
*/
function propagator_addSum(config, varIndexes, resultVarIndex) {
ASSERT(config._class === '$config', 'EXPECTING_CONFIG');
ASSERT(varIndexes instanceof Array, 'varIndexes should be an array of var names', varIndexes);
ASSERT(typeof resultVarIndex === 'number' && resultVarIndex >= 0, 'RESULT_VAR_SHOULD_BE_VALID_INDEX', typeof resultVarIndex, resultVarIndex);
let len = varIndexes.length;
switch (len) {
case 0:
THROW('SUM_REQUIRES_VARS');
return undefined;
case 1:
propagator_addEq(config, resultVarIndex, varIndexes[0]);
return undefined;
case 2:
propagator_addPlus(config, varIndexes[0], varIndexes[1], resultVarIndex);
return undefined;
}
// "divide and conquer" ugh. feels like there is a better way to do this
ASSERT(len > 2, 'expecting at least 3 elements in the list...', varIndexes);
let t1;
let n = Math.floor(varIndexes.length / 2);
if (n > 1) {
t1 = config_addVarAnonNothing(config);
propagator_addSum(config, varIndexes.slice(0, n), t1);
} else {
t1 = varIndexes[0];
}
let t2 = config_addVarAnonNothing(config);
propagator_addSum(config, varIndexes.slice(n), t2);
propagator_addPlus(config, t1, t2, resultVarIndex);
}
/**
* Product of N varIndexes = resultVar.
* Create as many anonymous varIndexes as necessary.
*
* @param {$config} config
* @param {number[]} varIndexes
* @param {number} resultVarIndex
*/
function propagator_addProduct(config, varIndexes, resultVarIndex) {
ASSERT(config._class === '$config', 'EXPECTING_CONFIG');
ASSERT(varIndexes instanceof Array, 'varIndexes should be an array of var names', varIndexes);
ASSERT(typeof resultVarIndex === 'number' && resultVarIndex >= 0, 'RESULT_VAR_SHOULD_BE_VALID_INDEX', resultVarIndex);
switch (varIndexes.length) {
case 0:
THROW('PRODUCT_REQUIRES_VARS');
return undefined;
case 1:
// note: by putting the result var first we get
// the var name back for it in case it's a number
propagator_addEq(config, resultVarIndex, varIndexes[0]);
return undefined;
case 2:
propagator_addRingMul(config, varIndexes[0], varIndexes[1], resultVarIndex);
return undefined;
}
let n = Math.floor(varIndexes.length / 2);
let t1;
if (n > 1) {
t1 = config_addVarAnonNothing(config);
propagator_addProduct(config, varIndexes.slice(0, n), t1);
} else {
t1 = varIndexes[0];
}
let t2 = config_addVarAnonNothing(config);
propagator_addProduct(config, varIndexes.slice(n), t2);
propagator_addRingMul(config, t1, t2, resultVarIndex);
}
/**
* @param {$config} config
* @param {number} varIndex
*/
function propagator_addMarkov(config, varIndex) {
ASSERT(config._class === '$config', 'EXPECTING_CONFIG');
ASSERT(typeof varIndex === 'number' && varIndex >= 0, 'VAR_SHOULD_BE_VALID_INDEX', varIndex);
config_addPropagator(config, propagator_create('markov', propagator_markovStepBare, varIndex));
}
// BODY_STOP
export {
propagator_addDistinct,
propagator_addDiv,
propagator_addEq,
propagator_addGt,
propagator_addGte,
propagator_addLt,
propagator_addLte,
propagator_addMarkov,
propagator_addMul,
propagator_addNeq,
propagator_addPlus,
propagator_addMin,
propagator_addProduct,
propagator_addReified,
propagator_addRingMul,
propagator_addSum,
// for testing
propagator_addRing,
propagator_addRingPlusOrMul,
};