UNPKG

finitedomain

Version:

A fast feature rich finite domain solver

815 lines (723 loc) 30.3 kB
import { ASSERT, THROW, } from './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_createEmpty, domain_createValue, domain_hasNoZero, domain_getValue, domain_invMul, domain_invMulValue, domain_isZero, domain_intersection, domain_isEmpty, domain_max, domain_min, domain_mul, domain_mulByValue, domain_removeGte, domain_removeLte, domain_removeValue, domain_resolveAsBooly, } from './domain'; import domain_plus from './doms/domain_plus'; import domain_minus from './doms/domain_minus'; // 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 initialDomains = config.initialDomains; let A = initialDomains[leftVarIndex]; let B = initialDomains[rightVarIndex]; let C = initialDomains[resultVarIndex]; // the reifier is solved if A or B is solved or if C is non-booly (zero ^ nonzero) let valueA = domain_getValue(A); let solvedA = valueA >= 0; let valueB = domain_getValue(B); let solvedB = valueB >= 0; let resultIsFalsy = domain_isZero(C); let resultIsTruthy = domain_hasNoZero(C); let solvedC = resultIsFalsy || resultIsTruthy; if (solvedA && solvedB && solvedC) return; let minA = domain_min(A); let maxA = domain_max(A); let minB = domain_min(B); let maxB = domain_max(B); let maxC = domain_max(C); // some of the tricks require max(C) to be 1 // the reifier can be rewritten if any two of the three vars are solved (for C that means "non-booly") // the reifier might be rewritable if one var is solved // in some cases certain constraints can be decided without being solved ([0,4]<[5,10] always holds) // the actual rewrites depends on the op, though let nopName; let opFunc; let nopFunc; let opRejectChecker; let nopRejectChecker; switch (opname) { case 'eq': { // R = A ==? B // 1 = A ==? B -> A == B // 0 = A ==? B -> A != B // R = x ==? B -> check if B contains value x, solve accordingly // if B and R are boolean we can apply even more special rules // R[0 1] = 0 ==? B[0 1] -> R != B // R[0 1] = 1 ==? B[0 1] -> R == B if (solvedC) { if (valueA >= 0) { if (resultIsTruthy) initialDomains[rightVarIndex] = domain_intersection(A, B); else initialDomains[rightVarIndex] = domain_removeValue(B, valueA); } else if (valueB >= 0) { if (resultIsTruthy) initialDomains[leftVarIndex] = domain_intersection(A, B); else initialDomains[leftVarIndex] = domain_removeValue(A, valueB); } else { // neither A nor B is solved; simplify if (resultIsTruthy) propagator_addEq(config, leftVarIndex, rightVarIndex); else propagator_addNeq(config, leftVarIndex, rightVarIndex); } return; } if (valueA >= 0 && valueB >= 0) { initialDomains[resultVarIndex] = domain_resolveAsBooly(C, valueA === valueB); return; } // C isnt solved, and A and B arent both solved // if C is a strict bool check the cases of one side solved and other side bool if (maxC <= 1) { if (maxA <= 1 && maxB === 1) { if (valueA === 0) return propagator_addNeq(config, rightVarIndex, resultVarIndex); else if (valueA === 1) return propagator_addEq(config, rightVarIndex, resultVarIndex); } if (maxB <= 1 && maxA === 1) { if (valueB === 0) return propagator_addNeq(config, leftVarIndex, resultVarIndex); else if (valueB === 1) return propagator_addEq(config, leftVarIndex, resultVarIndex); } } nopName = 'neq'; opFunc = propagator_eqStepBare; nopFunc = propagator_neqStepBare; opRejectChecker = propagator_eqStepWouldReject; nopRejectChecker = propagator_neqStepWouldReject; break; } case 'neq': { // similar optimizations to eq. just inversed. if (solvedC) { // result solved if (valueA >= 0) { if (resultIsTruthy) initialDomains[rightVarIndex] = domain_removeValue(B, valueA); else initialDomains[rightVarIndex] = A; } else if (valueB >= 0) { if (resultIsTruthy) initialDomains[leftVarIndex] = domain_removeValue(A, valueB); else initialDomains[leftVarIndex] = B; } else { // neither A nor B is solved; simplify if (resultIsTruthy) propagator_addNeq(config, leftVarIndex, rightVarIndex); else propagator_addEq(config, leftVarIndex, rightVarIndex); } return; } if (valueA >= 0 && valueB >= 0) { initialDomains[resultVarIndex] = domain_resolveAsBooly(C, valueA !== valueB); return; } // C isnt solved, and A and B arent both solved // if C is a strict bool check the cases of one side solved and other side bool if (maxC <= 1) { if (maxA <= 1 && maxB === 1) { if (valueA === 0) return propagator_addEq(config, rightVarIndex, resultVarIndex); else if (valueA === 1) return propagator_addNeq(config, rightVarIndex, resultVarIndex); } if (maxB <= 1 && maxA === 1) { if (valueB === 0) return propagator_addEq(config, leftVarIndex, resultVarIndex); else if (valueB === 1) return propagator_addNeq(config, leftVarIndex, resultVarIndex); } } nopName = 'eq'; opFunc = propagator_neqStepBare; nopFunc = propagator_eqStepBare; opRejectChecker = propagator_neqStepWouldReject; nopRejectChecker = propagator_eqStepWouldReject; break; } case 'lt': // R = A <? B // x = A <? B -> reduce A and B regardless, drop constraint // R = x <? B -> check if B >= x, solve accordingly // R[0 1] = 0 <? B[0 1] -> R == B if (solvedC) { // either this resolves the constraint or we compile a non-reifier for the remainder if (resultIsTruthy) { if (maxA < minB) return; return propagator_addLt(config, leftVarIndex, rightVarIndex); } // C=0 so solve if all A >= all B if (minA >= maxB) return; return propagator_addGte(config, leftVarIndex, rightVarIndex); } // if C is not solved, check if the domains are already solving < as is if (maxA < minB) return initialDomains[resultVarIndex] = domain_resolveAsBooly(C, true); if (minA >= maxB) return initialDomains[resultVarIndex] = domain_resolveAsBooly(C, false); // rewrite trick when all args have strict bool domains if (maxC <= 1) { if (valueA === 0 && maxB <= 1) return propagator_addEq(config, rightVarIndex, resultVarIndex); if (valueB === 0 && maxA <= 1) return propagator_addEq(config, leftVarIndex, resultVarIndex); } opFunc = propagator_neqStepBare; opRejectChecker = propagator_ltStepWouldReject; nopName = 'gte'; nopFunc = propagator_gteStepBare; nopRejectChecker = propagator_gteStepWouldReject; break; case 'lte': // R = A <=? B // x = A <=? B -> reduce A and B regardless, drop constraint // R = x <=? B -> check if B > x, solve accordingly // R[0 1] = 1 <=? B[0,1] -> eq(R, B) if (solvedC) { // result solved // either this resolves the constraint or we compile a non-reifier for the remainder if (resultIsTruthy) { if (maxA <= minB) return; return propagator_addLte(config, leftVarIndex, rightVarIndex); } // C=0 so solve if all A > all B if (minA > maxB) return; return propagator_addGt(config, leftVarIndex, rightVarIndex); } // regardless of being solved, check if the domains are already solving < as is if (maxA <= minB) return initialDomains[resultVarIndex] = domain_resolveAsBooly(C, true); if (minA > maxB) return initialDomains[resultVarIndex] = domain_resolveAsBooly(C, false); // rewrite trick when all args have strict bool domains if (maxC <= 1) { if (valueA === 1 && maxB <= 1) return propagator_addEq(config, rightVarIndex, resultVarIndex); if (valueB === 1 && maxA <= 1) return propagator_addEq(config, leftVarIndex, resultVarIndex); } 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); let initialDomains = config.initialDomains; let A = initialDomains[leftVarIndex]; if (domain_getValue(A) >= 0) return initialDomains[rightVarIndex] = A; let B = initialDomains[rightVarIndex]; if (domain_getValue(B) >= 0) return initialDomains[leftVarIndex] = B; initialDomains[leftVarIndex] = initialDomains[rightVarIndex] = domain_intersection(A, B); 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); let initialDomains = config.initialDomains; let A = initialDomains[leftVarIndex]; let B = initialDomains[rightVarIndex]; let maxA = domain_max(A); let minB = domain_min(B); // A |----| // B |--| if (maxA < minB) return; // solved let minA = domain_min(A); let maxB = domain_max(B); // A |----| // B |--| if (minA >= maxB) return initialDomains[leftVarIndex] = initialDomains[rightVarIndex] = domain_createEmpty(); // not solved nor rejected. prune invalid values // A |-------| -> |---| // B |----| |----| if (maxA >= maxB) initialDomains[leftVarIndex] = domain_removeGte(A, maxB); // A |-----| -> |----| // B |-------| |---| if (minB <= minA) initialDomains[rightVarIndex] = domain_removeLte(B, minA); 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); let initialDomains = config.initialDomains; let A = initialDomains[leftVarIndex]; let B = initialDomains[rightVarIndex]; let maxA = domain_max(A); let minB = domain_min(B); // A |----| // B |--| if (maxA < minB) return; // solved let minA = domain_min(A); let maxB = domain_max(B); // A |----| // B |--| if (minA > maxB) return initialDomains[leftVarIndex] = initialDomains[rightVarIndex] = domain_createEmpty(); // not solved nor rejected. prune invalid values // A |-------| -> |----| // B |----| |----| if (maxA >= maxB) initialDomains[leftVarIndex] = domain_removeGte(A, maxB + 1); // A |-----| -> |----| // B |-------| |----| if (minB <= minA) initialDomains[rightVarIndex] = domain_removeLte(B, minA - 1); 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); if (propagator_mulConstraintResolved(config, leftVarIndex, rightVarIndex, resultVarIndex)) return; config_addPropagator(config, propagator_create('mul', propagator_mulStep, leftVarIndex, rightVarIndex, resultVarIndex)); } /** * Would a mul for given vars need a constraint? * * @param {$config} config * @param {number} varIndexA * @param {number} varIndexB * @param {number} varIndexC * @returns {boolean} */ function propagator_mulConstraintResolved(config, varIndexA, varIndexB, varIndexC) { let initialDomains = config.initialDomains; let A = initialDomains[varIndexA]; let B = initialDomains[varIndexB]; let C = initialDomains[varIndexC]; let maxA = domain_max(A); let maxB = domain_max(B); // if A and B is unsolved just do a bounds check on result for [lo*lo, hi*hi] let vA = domain_getValue(A); let vB = domain_getValue(B); let vC = domain_getValue(C); if (vA >= 0) { if (vB >= 0) { initialDomains[varIndexC] = domain_intersection(C, domain_createValue(vA * vB)); return true; } if (vA === 0) { // C must be 0 and B can be anything initialDomains[varIndexC] = domain_intersection(C, domain_createValue(0)); return true; } if (vC >= 0) { if (vC === 0) { // if a is zero, b can be anything. otherwise, b must be zero (or the result couldn't be zero) if (vA !== 0) initialDomains[varIndexB] = domain_intersection(B, domain_createValue(0)); } else { let b = (vA % vC) ? domain_createEmpty() : domain_createValue(vA / vC); initialDomains[varIndexB] = domain_intersection(B, b); } return true; } // C can only contain values that equals b*vA for any b in B // TODO: this could be dangerous if B _and_ C are very large ranges... should we guard against that? initialDomains[varIndexC] = domain_intersection(C, domain_mulByValue(B, vA)); initialDomains[varIndexB] = (maxB === 0 || vA === 0) ? B : domain_intersection(B, domain_invMulValue(initialDomains[varIndexC], vA)); } else if (vB >= 0) { if (vB === 0) { // C must be 0 and A can be anything initialDomains[varIndexC] = domain_intersection(C, domain_createValue(0)); return true; } if (vC >= 0) { if (vC === 0) { // if b is zero, a can be anything. otherwise, a must be zero (or the result couldn't be zero) if (vB !== 0) initialDomains[varIndexA] = domain_intersection(A, domain_createValue(0)); } else { let a = (vC % vB) ? domain_createEmpty() : domain_createValue(vC / vB); initialDomains[varIndexA] = domain_intersection(A, a); } return true; } // C can only contain values that equals a*vB for any a in A // TODO: this could be dangerous if A _and_ C are very large ranges... should we guard against that? initialDomains[varIndexC] = domain_intersection(C, domain_mulByValue(A, vB)); initialDomains[varIndexA] = (maxA === 0 || vB === 0) ? A : domain_intersection(A, domain_invMulValue(initialDomains[varIndexC], vB)); } else { // simple bounds enforcement initialDomains[varIndexA] = domain_intersection(A, domain_invMul(C, B)); initialDomains[varIndexB] = domain_intersection(B, domain_invMul(C, A)); initialDomains[varIndexC] = domain_intersection(C, domain_mul(A, B)); } return false; } /** * @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); let initialDomains = config.initialDomains; let A = initialDomains[leftVarIndex]; let B = initialDomains[rightVarIndex]; let vA = domain_getValue(A); if (vA >= 0) return initialDomains[rightVarIndex] = domain_removeValue(B, vA); let vB = domain_getValue(A); if (vB >= 0) return initialDomains[leftVarIndex] = domain_removeValue(A, vB); if (domain_isEmpty(domain_intersection(A, B))) return; // no overlapping elements so constraint is redundant 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) { if (propagator_mulConstraintResolved(config, leftVarIndex, rightVarIndex, resultVarIndex)) return; 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, };