UNPKG

fdp

Version:

Finite Domain Problem reduction system

1,363 lines (1,142 loc) 298 kB
// note: you can use the tool at https://qfox.github.io/logic-table-filter/ to test some of these tricks // enter the names of the vars, the formulae (in proper JS), and the var considered a leaf and you can // quickly see whether the rewrite is valid or not. import { ASSERT, getTerm, TRACE, TRACE_MORPH, THROW, } from '../../fdlib/src/helpers'; import { EMPTY, domain__debug, domain_arrToSmallest, domain_containsValue, domain_createEmpty, domain_createRange, domain_createValue, domain_getValue, domain_hasNoZero, domain_hasZero, domain_intersection, domain_intersectionValue, domain_isBool, domain_isBooly, domain_isBoolyPair, domain_isSolved, domain_isZero, domain_max, domain_min, domain_plus, domain_removeGte, domain_removeGtUnsafe, domain_removeLte, domain_removeLtUnsafe, domain_removeValue, domain_resolveAsBooly, domain_size, } from '../../fdlib/src/domain'; import { ML_ALL, ML_NOBOOL, ML_NOLEAF, ML_DIFF, ML_DIV, ML_IMP, ML_ISALL, ML_ISDIFF, ML_ISLT, ML_ISLTE, ML_ISNALL, ML_ISNONE, ML_ISSAME, ML_ISSOME, ML_JMP, ML_JMP32, ML_LT, ML_LTE, ML_MINUS, ML_NALL, ML_NIMP, ML_NONE, ML_NOOP, ML_NOOP2, ML_NOOP3, ML_NOOP4, ML_PRODUCT, ML_SAME, ML_SOME, ML_START, ML_STOP, ML_SUM, ML_XNOR, ML_XOR, SIZEOF_V, SIZEOF_W, SIZEOF_VVV, SIZEOF_C, SIZEOF_C_2, SIZEOF_CR_2, OFFSET_C_A, OFFSET_C_B, OFFSET_C_C, OFFSET_C_R, ml__debug, ml__opName, ml_compileJumpAndConsolidate, ml_compileJumpSafe, ml_dec8, ml_dec16, ml_dec32, ml_enc8, ml_enc16, ml_eliminate, ml_getOpSizeSlow, ml_getRecycleOffsets, ml_heapSort16bitInline, ml_recycles, ml_throw, ml_validateSkeleton, ml_any2c, ml_cx2cx, ml_c2c2, ml_cr2c, ml_cr2c2, ml_cr2cr2, } from './ml'; import { BOUNTY_FLAG_NOT_BOOLY, //BOUNTY_FLAG_OTHER, BOUNTY_MAX_OFFSETS_TO_TRACK, //BOUNTY_NO_FLAGS, BOUNTY_FLAG_IMP_LHS, BOUNTY_FLAG_IMP_RHS, BOUNTY_FLAG_ISALL_ARG, BOUNTY_FLAG_ISALL_RESULT, BOUNTY_FLAG_ISSOME_RESULT, BOUNTY_FLAG_ISSAME_ARG, BOUNTY_FLAG_ISSAME_RESULT, BOUNTY_FLAG_ISLTE_ARG, BOUNTY_FLAG_LTE_LHS, BOUNTY_FLAG_LTE_RHS, BOUNTY_FLAG_NALL, BOUNTY_FLAG_DIFF, BOUNTY_FLAG_SOME, BOUNTY_FLAG_SUM_RESULT, BOUNTY_FLAG_XOR, bounty__debug, bounty__debugMeta, bounty_collect, bounty_getCounts, bounty_getMeta, bounty_getOffset, bounty_markVar, } from './bounty'; import { m2d__debug, } from './ml2dsl'; // BODY_START const ML_BOOLY_NO = 0; const ML_BOOLY_YES = 1; const ML_BOOLY_MAYBE = 2; function cutter(ml, problem, once) { TRACE('\n ## cutter', ml.length < 50 ? ml.join(' ') : ''); let term = getTerm(); let getDomain = problem.getDomain; let setDomain = problem.setDomain; let addAlias = problem.addAlias; let getAlias = problem.getAlias; let solveStack = problem.solveStack; let leafs = problem.leafs; let isConstant = problem.isConstant; let pc = 0; let bounty; let stacksBefore; let emptyDomain = false; let changes = 0; let loops = 0; let requestAnotherCycle = false; // when true this will force another cycle so the minimizer runs again do { term.time('-> cut_loop ' + loops); TRACE(' # start cutter outer loop', loops); bounty = bounty_collect(ml, problem, bounty); TRACE('\n#### Problem state between bounty and cutter: ###'); TRACE(ml__debug(ml, 0, 20, problem)); TRACE(m2d__debug(problem)); stacksBefore = solveStack.length; changes = 0; cutLoop(); term.timeEnd('-> cut_loop ' + loops); term.log(' - end cutter outer loop', loops, ', removed:', solveStack.length - stacksBefore, ' vars, total changes:', changes, ', emptyDomain =', emptyDomain, 'once=', once); ++loops; } while (!emptyDomain && changes && !once && !requestAnotherCycle); TRACE('## exit cutter', emptyDomain ? '[there was an empty domain]' : requestAnotherCycle ? '[explicitly requesting another cycle]' : loops > 1 ? '[it might not be done]' : '[it is done]'); if (emptyDomain) return -1; return loops + (requestAnotherCycle ? 1 : 0); function somethingChanged() { ++changes; } function readIndex(ml, offset) { ASSERT(ml instanceof Uint8Array, 'ml should be a buffer'); ASSERT(typeof offset === 'number' && offset >= 0 && offset <= ml.length, 'expecting valid offset'); ASSERT(arguments.length === 2, 'only two args'); return getAlias(ml_dec16(ml, offset)); } function getMeta(bounty, index, keepBoolyFlags, _debug) { ASSERT(typeof index === 'number' && index >= 0 && index <= 0xffff, 'expecting valid index'); ASSERT(arguments.length === 2 || arguments.length === 3, 'only two or three args'); if (!isConstant(index)) { let meta = bounty_getMeta(bounty, index, _debug); if (!keepBoolyFlags) return scrubBoolyFlag(meta); return meta; } return 0; } function scrubBoolyFlag(meta) { return (meta | BOUNTY_FLAG_NOT_BOOLY) ^ BOUNTY_FLAG_NOT_BOOLY; } function hasFlags(meta, flags) { return (meta & flags) === flags; } function getCounts(bounty, index) { ASSERT(typeof index === 'number' && index >= 0 && index <= 0xffff, 'expecting valid index'); ASSERT(arguments.length === 2, 'no more than two args'); if (!isConstant(index)) return bounty_getCounts(bounty, index); return 0; } // ############## function cutLoop() { TRACE('\n#### - inner cutLoop'); pc = 0; while (pc < ml.length && !emptyDomain && !requestAnotherCycle) { let pcStart = pc; let op = ml[pc]; TRACE(' -- CU pc=' + pc, ', op=', op, ml__opName(op)); TRACE(' -> op: ' + ml__debug(ml, pc, 1, problem, true)); ASSERT(ml_validateSkeleton(ml, 'cutLoop')); switch (op) { case ML_ALL: return ml_throw(ml, pc, 'all() should be solved and eliminated'); case ML_DIFF: cut_diff(ml, pc); break; case ML_DIV: pc += SIZEOF_VVV; break; case ML_IMP: cut_imp(ml, pc); break; case ML_ISALL: cut_isall(ml, pc); break; case ML_ISDIFF: cut_isdiff(ml, pc); break; case ML_ISLT: cut_islt(ml, pc); break; case ML_ISLTE: cut_islte(ml, pc); break; case ML_ISNALL: cut_isnall(ml, pc); break; case ML_ISSAME: cut_issame(ml, pc); break; case ML_ISSOME: cut_issome(ml, pc); break; case ML_ISNONE: TRACE('(skipped) issome/isnone', pc); let nlen = ml_dec16(ml, pc + 1); pc += SIZEOF_C + nlen * 2 + 2; break; case ML_LT: cut_lt(ml, pc); break; case ML_LTE: cut_lte(ml, pc); break; case ML_MINUS: pc += SIZEOF_VVV; break; case ML_NALL: cut_nall(ml, pc); break; case ML_NIMP: TRACE('(skipped) nimp', pc); pc += SIZEOF_C_2; break; case ML_NONE: return ml_throw(ml, pc, 'nors should be solved and eliminated'); case ML_PRODUCT: TRACE('(skipped) product', pc); let plen = ml_dec16(ml, pc + 1); pc += SIZEOF_C + plen * 2 + 2; break; case ML_SAME: return ml_throw(ml, pc, 'eqs should be aliased and eliminated'); case ML_SOME: cut_some(ml, pc); break; case ML_SUM: cut_sum(ml, pc); break; case ML_XOR: cut_xor(ml, pc); break; case ML_XNOR: cut_xnor(ml, pc); break; case ML_START: if (pc !== 0) return ml_throw(ml, pc, ' ! compiler problem @', pcStart); ++pc; break; case ML_STOP: return; case ML_NOBOOL: case ML_NOLEAF: pc += SIZEOF_V; break; case ML_JMP: cut_moveTo(ml, pc, SIZEOF_V + ml_dec16(ml, pc + 1)); break; case ML_JMP32: cut_moveTo(ml, pc, SIZEOF_W + ml_dec32(ml, pc + 1)); break; case ML_NOOP: cut_moveTo(ml, pc, 1); break; case ML_NOOP2: cut_moveTo(ml, pc, 2); break; case ML_NOOP3: cut_moveTo(ml, pc, 3); break; case ML_NOOP4: cut_moveTo(ml, pc, 4); break; default: getTerm().error('(cut) unknown op', pc, ' at', pc); ml_throw(ml, pc, '(cut) unknown op', pc); } } if (emptyDomain) { TRACE('Ended up with an empty domain'); return; } if (requestAnotherCycle) { TRACE('Stopped cutloop prematurely because another minimizer cycle was requested'); return; } TRACE('the implicit end; ml desynced'); THROW('ML OOB'); } function cut_diff(ml, offset) { let argCount = ml_dec16(ml, offset + 1); TRACE(' ! cut_diff;', argCount, 'args'); let indexA = readIndex(ml, offset + OFFSET_C_A); let countsA = getCounts(bounty, indexA); if (countsA > 1 && countsA < BOUNTY_MAX_OFFSETS_TO_TRACK) { // search all counts for a second SOME if (desubset_diff(ml, offset, argCount, indexA, countsA)) return; } if (argCount !== 2) { TRACE(' - did not have 2 args, bailing for now'); pc += SIZEOF_C + argCount * 2; return; } // for the remainder, these are NEQ cuts (diff[2]) let indexB = readIndex(ml, offset + OFFSET_C_B); let countsB = getCounts(bounty, indexB); TRACE(' - diff:', indexA, '!=', indexB, '::', domain__debug(getDomain(indexA, true)), '!=', domain__debug(getDomain(indexB, true))); ASSERT(!countsA || !domain_isSolved(getDomain(indexA, true)), 'if it has counts it shouldnt be solved', countsA, indexA, domain__debug(getDomain(indexA, true))); ASSERT(!countsB || !domain_isSolved(getDomain(indexB, true)), 'if it has counts it shouldnt be solved', countsB, indexB, domain__debug(getDomain(indexB, true))); TRACE(' - counts:', countsA, countsB, ', meta:', bounty__debugMeta(bounty, indexA), bounty__debugMeta(bounty, indexB)); if (indexA === indexB) { TRACE(' - index A == B, redirecting to minimizer'); requestAnotherCycle = true; return; } if (countsA === 1) { return leaf_diff_pair(ml, offset, indexA, indexB, indexA, indexB); } if (countsB === 1) { return leaf_diff_pair(ml, offset, indexB, indexA, indexA, indexB); } let TRICK_INV_DIFF_FLAGS = BOUNTY_FLAG_LTE_LHS | BOUNTY_FLAG_LTE_RHS | BOUNTY_FLAG_SOME | BOUNTY_FLAG_NALL | BOUNTY_FLAG_IMP_LHS | BOUNTY_FLAG_IMP_RHS; if (countsA > 1 && countsA <= BOUNTY_MAX_OFFSETS_TO_TRACK) { let metaA = getMeta(bounty, indexA); // check if it has any targeted ops, then check if it has no other stuff let hasGoodOps = (metaA & TRICK_INV_DIFF_FLAGS) > 0; let hasBadOps = (metaA | TRICK_INV_DIFF_FLAGS | BOUNTY_FLAG_DIFF) ^ (TRICK_INV_DIFF_FLAGS | BOUNTY_FLAG_DIFF); TRACE(' - has good:', hasGoodOps, ', hasBad:', hasBadOps); // TODO: why are we checking diff here? shouldnt that have been done above? and why not checking that below? if (hasFlags(metaA, BOUNTY_FLAG_DIFF) && hasGoodOps && !hasBadOps) { if (trick_diff_elimination(offset, indexA, countsA, indexB)) return; } if (hasFlags(metaA, BOUNTY_FLAG_DIFF | BOUNTY_FLAG_XOR)) { if (trick_diff_xor(ml, offset, indexA, countsA, indexB)) return; } if (trick_diff_alias(indexA, indexB, countsA)) return; } if (countsB > 1 && countsB <= BOUNTY_MAX_OFFSETS_TO_TRACK) { let metaB = getMeta(bounty, indexB); // first remove the booly flag, then check if it has any targeted ops, then check if it has no other stuff let hasGoodOps = (metaB & TRICK_INV_DIFF_FLAGS) > 0; let hasBadOps = (metaB | TRICK_INV_DIFF_FLAGS | BOUNTY_FLAG_DIFF) ^ (TRICK_INV_DIFF_FLAGS | BOUNTY_FLAG_DIFF); TRACE(' - has good:', hasGoodOps, ', hasBad:', hasBadOps); if (hasGoodOps && !hasBadOps) { if (trick_diff_elimination(offset, indexB, countsB, indexA)) return; } if (hasFlags(metaB, BOUNTY_FLAG_DIFF | BOUNTY_FLAG_XOR)) { if (trick_diff_xor(ml, offset, indexB, countsB, indexA)) return; } if (trick_diff_alias(indexB, indexA, countsB)) return; let A = getDomain(indexA, true); let B = getDomain(indexB, true); if (domain_isBoolyPair(A) && A === B) { TRACE(' - A and B are booly pair and equal so turn this DIFF into a XOR'); TRACE_MORPH('A:[00xx] != B:[00xx]', 'A ^ B'); ml_enc8(ml, offset, ML_XOR); bounty_markVar(bounty, indexA); bounty_markVar(bounty, indexB); somethingChanged(); return; } } TRACE(' - cut_diff changed nothing'); pc += SIZEOF_C_2; } function cut_imp(ml, offset) { let indexA = readIndex(ml, offset + OFFSET_C_A); let indexB = readIndex(ml, offset + OFFSET_C_B); let A = getDomain(indexA, true); let B = getDomain(indexB, true); let countsA = getCounts(bounty, indexA); let countsB = getCounts(bounty, indexB); TRACE(' ! cut_imp;', indexA, '->', indexB, ', ', domain__debug(A), '->', domain__debug(B)); TRACE(' - counts:', countsA, '->', countsB, ', meta:', bounty__debugMeta(bounty, indexA), '->', bounty__debugMeta(bounty, indexB)); if (indexA === indexB) { TRACE(' - index A == B, redirecting to minimizer'); requestAnotherCycle = true; return; } if (!domain_isBooly(A) || domain_hasNoZero(B)) { TRACE(' - this imp is already solved, bouncing back to minimizer'); requestAnotherCycle = true; return false; } if (countsA === 1) { return leaf_imp(ml, offset, indexA, indexB, true); } if (countsB === 1) { return leaf_imp(ml, offset, indexA, indexB, false); } if (countsA > 0) { let metaA = getMeta(bounty, indexA); ASSERT(metaA & BOUNTY_FLAG_IMP_LHS, 'should be'); if (metaA === BOUNTY_FLAG_IMP_LHS) { if (trick_only_implhs_leaf(ml, indexA, countsA)) return; } if (metaA === BOUNTY_FLAG_NALL || metaA === (BOUNTY_FLAG_NALL | BOUNTY_FLAG_IMP_LHS)) { if (trick_implhs_nall_leaf(ml, indexA, countsA)) return; } if (countsA === 2) { if (metaA === (BOUNTY_FLAG_IMP_LHS | BOUNTY_FLAG_SOME)) { if (trick_implhs_some_leaf(ml, offset, indexA, countsA)) return; } } if (hasFlags(metaA, BOUNTY_FLAG_ISALL_RESULT)) { // this trick has isall subsume the lte, so no need for R to be leaf if (trick_implhs_isall_2shared(ml, offset, indexA, countsA)) return; // this trick requires R to be leaf if (countsA === 2) { if (trick_isall_implhs_1shared(ml, offset, indexA, countsA)) return; } } if (countsA >= 3) { if (metaA === (BOUNTY_FLAG_SOME | BOUNTY_FLAG_NALL | BOUNTY_FLAG_IMP_LHS)) { if (trick_implhs_nalls_some(indexA, countsA)) return; } if (metaA === (BOUNTY_FLAG_SOME | BOUNTY_FLAG_NALL | BOUNTY_FLAG_IMP_LHS | BOUNTY_FLAG_IMP_RHS)) { if (trick_impboth_nall_some(indexA, countsA)) return; } } } if (domain_isBool(A) && domain_isBool(B)) { if (countsB === 2) { let metaB = getMeta(bounty, indexB, true); // keep booly flags if (metaB === (BOUNTY_FLAG_IMP_RHS | BOUNTY_FLAG_ISALL_RESULT)) { if (trick_imprhs_isall_entry(indexB, offset, countsB, indexA)) return; } } } TRACE(' - cut_imp did nothing'); pc += SIZEOF_C_2; } function cut_isall(ml, offset) { let argCount = ml_dec16(ml, offset + 1); let argsOffset = offset + SIZEOF_C; let opSize = SIZEOF_C + argCount * 2 + 2; let indexR = readIndex(ml, argsOffset + argCount * 2); let countsR = getCounts(bounty, indexR); TRACE(' ! cut_isall; R=', indexR, ', counts:', countsR, ', metaR:', bounty__debugMeta(bounty, indexR)); ASSERT(!countsR || !domain_isSolved(getDomain(indexR, true)), 'if it has counts it shouldnt be solved', countsR, indexR, domain__debug(getDomain(indexR, true))); if (countsR > 0 && countsR < BOUNTY_MAX_OFFSETS_TO_TRACK) { if (countsR === 1) { // when R is a leaf, the isall args are not bound by it nor the reifier so they are free return leaf_isall(ml, offset, argCount, indexR); } let metaR = getMeta(bounty, indexR); if (metaR === (BOUNTY_FLAG_ISALL_RESULT | BOUNTY_FLAG_ISALL_ARG)) { if (leaf_isall_arg_result(ml, indexR, countsR)) return; } if (countsR === 2) { if (metaR === (BOUNTY_FLAG_NALL | BOUNTY_FLAG_ISALL_RESULT)) { if (trick_isall_nall_2shared(ml, indexR, offset, countsR)) return; } } if (metaR === (BOUNTY_FLAG_NALL | BOUNTY_FLAG_ISALL_RESULT)) { if (trick_isall_nall_1shared(ml, indexR, offset, countsR)) return; } } TRACE(' cut_isall changed nothing'); pc += opSize; } function cut_isdiff(ml, offset) { let argCount = ml_dec16(ml, offset + 1); let indexR = readIndex(ml, offset + SIZEOF_C + argCount * 2); TRACE(' ! cut_isdiff; ', indexR, '::', domain__debug(getDomain(indexR, true)), ', args:', argCount); if (argCount !== 2) { TRACE(' - argCount=', argCount, ', bailing because it is not 2'); pc = offset + SIZEOF_C + argCount * 2 + 2; return; } let indexA = readIndex(ml, offset + OFFSET_C_A); let indexB = readIndex(ml, offset + OFFSET_C_B); let countsA = getCounts(bounty, indexA); let countsB = getCounts(bounty, indexB); let countsR = getCounts(bounty, indexR); TRACE(' -', indexR, '=', indexA, '!=?', indexB, '::', domain__debug(getDomain(indexR, true)), '=', domain__debug(getDomain(indexA, true)), '!=?', domain__debug(getDomain(indexB, true))); ASSERT(!countsA || !domain_isSolved(getDomain(indexA, true)), 'if it has counts it shouldnt be solved', countsA, indexA, domain__debug(getDomain(indexA, true))); ASSERT(!countsB || !domain_isSolved(getDomain(indexB, true)), 'if it has counts it shouldnt be solved', countsB, indexB, domain__debug(getDomain(indexB, true))); ASSERT(!countsR || !domain_isSolved(getDomain(indexR, true)), 'if it has counts it shouldnt be solved', countsR, indexR, domain__debug(getDomain(indexR, true))); TRACE(' - counts:', countsR, countsA, countsB, ', meta:', bounty__debugMeta(bounty, indexR), '=', bounty__debugMeta(bounty, indexA), '!=?', bounty__debugMeta(bounty, indexB)); if (countsR === 1) { return leaf_isdiff(ml, offset, indexA, indexB, indexR, indexR); } if (countsA === 1) { ASSERT(!domain_isSolved(getDomain(indexA, true)), 'A cannot be solved (bounty ignores constants so count would be 0)'); if (canCutIsdiffForArg(indexA, indexB, indexR)) { return leaf_isdiff(ml, offset, indexA, indexB, indexR, indexA); } } if (countsB === 1) { // not covered, kept here just in case the above assertion doesnt hold in prod ASSERT(!domain_isSolved(getDomain(indexB, true)), 'B cannot be solved (bounty ignores constants so count would be 0)'); if (canCutIsdiffForArg(indexB, indexA, indexR)) { return leaf_isdiff(ml, offset, indexA, indexB, indexR, indexB); } } let R = getDomain(indexR, true); let A = getDomain(indexA, true); let B = getDomain(indexB, true); if (domain_isBoolyPair(R)) { if (domain_isBoolyPair(A) && domain_isSolved(B)) { // R:[00yy] = A:[00xx] !=? 0/x if (domain_isZero(B)) { TRACE_MORPH('R = A !=? 0', '!(R ^ A)'); ml_cr2c2(ml, offset, 2, ML_XNOR, indexA, indexR); bounty_markVar(bounty, indexA); bounty_markVar(bounty, indexB); bounty_markVar(bounty, indexR); somethingChanged(); return; } else if (domain_max(A) === domain_getValue(B)) { // must confirm A contains B because it may in some edge cases not TRACE_MORPH('R = A:[00xx] !=? x', 'R ^ A'); ml_cr2c2(ml, offset, 2, ML_XOR, indexA, indexR); bounty_markVar(bounty, indexA); bounty_markVar(bounty, indexB); bounty_markVar(bounty, indexR); somethingChanged(); return; } } if (domain_isSolved(A) && domain_isBoolyPair(B)) { // R:[00yy] = 0/x !=? A:[00xx] if (domain_isZero(A)) { TRACE_MORPH('R = 0 !=? B', '!(R ^ B)'); ml_cr2c2(ml, offset, 2, ML_XNOR, indexB, indexR); bounty_markVar(bounty, indexA); bounty_markVar(bounty, indexB); bounty_markVar(bounty, indexR); somethingChanged(); return; } else if (domain_max(B) === domain_getValue(A)) { // must confirm B contains A because it may in some edge cases not TRACE_MORPH('R = x !=? B:[00xx]', 'R ^ B'); ml_cr2c2(ml, offset, 2, ML_XOR, indexB, indexR); bounty_markVar(bounty, indexA); bounty_markVar(bounty, indexB); bounty_markVar(bounty, indexR); somethingChanged(); return; } } } TRACE(' - cut_isdiff changed nothing'); pc = offset + SIZEOF_CR_2; } function canCutIsdiffForArg(indexL, indexO, indexR) { TRACE(' - canCutIsdiffForArg;', indexL, indexO, indexR, '->', domain__debug(getDomain(indexR, true)), '=', domain__debug(getDomain(indexL, true)), '!=?', domain__debug(getDomain(indexO, true))); // an isdiff with 2 args can only be leaf-cut on an arg if the leaf can represent all outcomes // so if C is solved, solve as SAME or DIFF. // otherwise make sure the leaf contains all vars of the other var and at least one var that's not in there // as long as that's impossible we can't cut it without implicitly forcing vars // first check whether R is booly-solved, this would mean fewer values to check let R = getDomain(indexR, true); if (domain_hasNoZero(R)) { TRACE(' - R=0 and size(L)>2 so cuttable'); // L contains at least two values so regardless of the state of O, L can fulfill != ASSERT(domain_size(L) >= 2, 'see?'); return true; } // R=1 or R=booly is more involved because we at least // need to know whether L contains all values in O let L = getDomain(indexL, true); let O = getDomain(indexO, true); let LO = domain_intersection(L, O); // <-- this tells us that TRACE(' - LO:', domain__debug(LO)); if (domain_isZero(R)) { // only cut if we are certain L can represent eq in any way O solves if (!LO) { TRACE(' - R>=1 and A contains no value in B so reject'); // no values in L and O match so reject setDomain(indexL, domain_createEmpty(), false, true); return false; } if (LO === O) { TRACE(' - R>=1 and A contains all values in B so cuttable'); // this means L contains all values in O (and maybe more, dont care) // which means L can uphold the eq for any value of O return true; } TRACE(' - R>=1 and A contains some but not all B so not cuttable, yet'); // there's no guarantee O solves to a value in L so we cant cut safely return true; } TRACE(' - R unresolved, cuttable if L contains all values in O and then some;', LO === O, LO !== L, 'so:', LO === O && LO !== L); // we dont know R so L should contain all values in O (LO==O) and at least // one value not in O (LO != O), to consider this a safe cut. otherwise dont. return LO === O && LO !== L; } function cut_islt(ml, offset) { let indexA = readIndex(ml, offset + 1); let indexB = readIndex(ml, offset + 3); let indexR = readIndex(ml, offset + 5); let countsA = getCounts(bounty, indexA); let countsB = getCounts(bounty, indexB); let countsR = getCounts(bounty, indexR); TRACE(' ! cut_islt; ', indexR, '=', indexA, '<?', indexB, '::', domain__debug(getDomain(indexR, true)), '=', domain__debug(getDomain(indexA, true)), '<?', domain__debug(getDomain(indexB, true))); ASSERT(!countsA || !domain_isSolved(getDomain(indexA, true)), 'if it has counts it shouldnt be solved', countsA, indexA, domain__debug(getDomain(indexA, true))); ASSERT(!countsB || !domain_isSolved(getDomain(indexB, true)), 'if it has counts it shouldnt be solved', countsB, indexB, domain__debug(getDomain(indexB, true))); ASSERT(!countsR || !domain_isSolved(getDomain(indexR, true)), 'if it has counts it shouldnt be solved', countsR, indexR, domain__debug(getDomain(indexR, true))); TRACE(' - counts:', countsR, countsA, countsB, ', meta:', bounty__debugMeta(bounty, indexR), '=', bounty__debugMeta(bounty, indexA), '<?', bounty__debugMeta(bounty, indexB)); if (countsR === 1) { return leaf_islt(ml, offset, indexA, indexB, indexR, indexR); } if (countsA === 1) { if (canCutIsltForArg(indexA, indexB, indexR, indexA, indexB)) { return leaf_islt(ml, offset, indexA, indexB, indexR, indexA); } } if (countsB === 1) { if (canCutIsltForArg(indexB, indexA, indexR, indexA, indexB)) { return leaf_islt(ml, offset, indexA, indexB, indexR, indexB); } } TRACE(' - cut_islt changed nothing'); pc = offset + SIZEOF_VVV; } function canCutIsltForArg(indexL, indexO, indexR, indexA, indexB) { TRACE(' - canCutIsltForArg;', indexL, indexO, indexR, '->', domain__debug(getDomain(indexR, true)), '=', domain__debug(getDomain(indexA, true)), '<?', domain__debug(getDomain(indexB, true))); // an islt can only be leaf-cut on an arg if the leaf can represent all outcomes // so if C is solved, solve as SAME or DIFF. // otherwise make sure the leaf contains all vars of the other var and at least one var that's not in there // as long as that's impossible we can't cut it without implicitly forcing vars // keep in mind A and B are ordered and cant be swapped // first check whether R is booly-solved, this would mean fewer values to check let A = getDomain(indexA, true); let B = getDomain(indexB, true); let R = getDomain(indexR, true); if (domain_hasNoZero(R)) { TRACE(' - R>0'); // if L is A, O must have at least one value below min(B). otherwise it must have at least one value > max(A). if (indexL === indexA) return domain_min(A) < domain_min(B); else return domain_max(B) > domain_max(A); } if (domain_isZero(R)) { TRACE(' - R=0'); // if L is A, O must have at least one value >= min(B). otherwise it must have at least one value <= max(A). if (indexL === indexA) return domain_min(A) >= domain_min(B); else return domain_max(B) <= domain_max(A); } // R unresolved. O must have at least both values to represent R=0 and R>=1 if (indexL === indexA) { TRACE(' - R unresolved, L=A', domain_min(A) < domain_min(B), domain_max(A) >= domain_max(B)); // L must contain a value < min(B) and a value >= max(B) return domain_min(A) < domain_min(B) && domain_max(A) >= domain_max(B); } TRACE(' - R unresolved, L=B', domain_max(B), '>', domain_max(A), '->', domain_max(B) > domain_max(A), domain_min(B), '<=', domain_min(A), '->', domain_min(B) <= domain_min(A)); // L is B, L must contain one value above max(A) and one value <= min(A) return domain_max(B) > domain_max(A) && domain_min(B) <= domain_min(A); } function cut_islte(ml, offset) { let indexA = readIndex(ml, offset + 1); let indexB = readIndex(ml, offset + 3); let indexR = readIndex(ml, offset + 5); let countsA = getCounts(bounty, indexA); let countsB = getCounts(bounty, indexB); let countsR = getCounts(bounty, indexR); TRACE(' ! cut_islte; ', indexR, '=', indexA, '<=?', indexB, '::', domain__debug(getDomain(indexR, true)), '=', domain__debug(getDomain(indexA, true)), '<=?', domain__debug(getDomain(indexB, true))); ASSERT(!countsA || !domain_isSolved(getDomain(indexA, true)), 'if it has counts it shouldnt be solved', countsA, indexA, domain__debug(getDomain(indexA, true))); ASSERT(!countsB || !domain_isSolved(getDomain(indexB, true)), 'if it has counts it shouldnt be solved', countsB, indexB, domain__debug(getDomain(indexB, true))); ASSERT(!countsR || !domain_isSolved(getDomain(indexR, true)), 'if it has counts it shouldnt be solved', countsR, indexR, domain__debug(getDomain(indexR, true))); TRACE(' - counts:', countsR, countsA, countsB, ', meta:', bounty__debugMeta(bounty, indexR), '=', bounty__debugMeta(bounty, indexA), '<=?', bounty__debugMeta(bounty, indexB)); let R = getDomain(indexR, true); if (!domain_isBooly(R)) { TRACE(' - R is already booly solved, requesting another minifier sweep, bailing'); requestAnotherCycle = true; return; } if (countsR === 1) { return leaf_islte(ml, offset, indexA, indexB, indexR, indexR); } let A = getDomain(indexA, true); let B = getDomain(indexB, true); if (countsA === 1) { if (canCutIslteForArg(indexA, indexB, indexA, indexB, A, B)) { return leaf_islte(ml, offset, indexA, indexB, indexR, indexA); } } if (countsB === 1) { if (canCutIslteForArg(indexB, indexA, indexA, indexB, A, B)) { return leaf_islte(ml, offset, indexA, indexB, indexR, indexB); } } if (countsR > 0 && countsR < BOUNTY_MAX_OFFSETS_TO_TRACK) { if (domain_isSolved(A)) { // R = x <=? B let metaR = getMeta(bounty, indexR); if (hasFlags(metaR, BOUNTY_FLAG_IMP_RHS)) { let metaB = getMeta(bounty, indexB); if (hasFlags(metaB, BOUNTY_FLAG_IMP_LHS)) { if (trick_imp_islte_c_v(offset, indexR, indexA, indexB, countsR)) return; } } } if (domain_isSolved(B)) { // R = A <=? x let metaR = getMeta(bounty, indexR); if (hasFlags(metaR, BOUNTY_FLAG_IMP_RHS)) { let metaA = getMeta(bounty, indexA); if (hasFlags(metaA, BOUNTY_FLAG_IMP_LHS)) { if (trick_imp_islte_v_c(offset, indexR, indexA, indexB, countsR)) return; } } } } TRACE(' - cut_islte changed nothing'); pc = offset + SIZEOF_VVV; } function canCutIslteForArg(indexL, indexO, indexA, indexB, A, B) { TRACE(' - canCutIslteForArg;', indexL, indexO, domain__debug(getDomain(indexA, true)), '<=?', domain__debug(getDomain(indexB, true))); // an islte can only be leaf-cut on an arg if the leaf can represent all outcomes // so if C is solved, solve as SAME or DIFF. // otherwise make sure the leaf contains all vars of the other var and at least one var that's not in there // as long as that's impossible we can't cut it without implicitly forcing vars // keep in mind A and B are ordered and cant be swapped // R unresolved. O must have at least both values to represent R=0 and R>=1 if (indexL === indexA) { TRACE(' - L=A', domain_min(A) <= domain_min(B), domain_max(A) > domain_max(B)); // L must contain a value <= min(B) and a value > max(B) return domain_min(A) <= domain_min(B) && domain_max(A) > domain_max(B); } TRACE(' - L=B', domain_max(B), '>=', domain_max(A), '->', domain_max(B) >= domain_max(A), domain_min(B), '<', domain_min(A), '->', domain_min(B) < domain_min(A)); // L is B, L must contain one value gte max(A) and one value below min(A) return domain_max(B) >= domain_max(A) && domain_min(B) < domain_min(A); } function cut_isnall(ml, offset) { let argCount = ml_dec16(ml, offset + 1); let argsOffset = offset + SIZEOF_C; let opSize = SIZEOF_C + argCount * 2 + 2; let indexR = readIndex(ml, argsOffset + argCount * 2); let countsR = getCounts(bounty, indexR); TRACE(' ! cut_isnall; R=', indexR); ASSERT(!countsR || !domain_isSolved(getDomain(indexR, true)), 'if it has counts it shouldnt be solved', countsR, indexR, domain__debug(getDomain(indexR, true))); if (countsR === 1) { return leaf_isnall(ml, offset, argCount, indexR, countsR); } pc += opSize; } function cut_issame(ml, offset) { let argCount = ml_dec16(ml, offset + 1); TRACE(' ! cut_issame'); if (argCount !== 2) { TRACE(' - argCount != 2 so bailing, for now'); pc = offset + SIZEOF_C + argCount * 2 + 2; return; } let indexA = readIndex(ml, offset + OFFSET_C_A); let indexB = readIndex(ml, offset + OFFSET_C_B); let indexR = readIndex(ml, offset + OFFSET_C_C); let countsA = getCounts(bounty, indexA); let countsB = getCounts(bounty, indexB); let countsR = getCounts(bounty, indexR); TRACE(' - cut_issame; ', indexR, '=', indexA, '==?', indexB, '::', domain__debug(getDomain(indexR, true)), '=', domain__debug(getDomain(indexA, true)), '==?', domain__debug(getDomain(indexB, true))); ASSERT(!countsA || !domain_isSolved(getDomain(indexA, true)), 'if it has counts it shouldnt be solved', countsA, indexA, domain__debug(getDomain(indexA, true))); ASSERT(!countsB || !domain_isSolved(getDomain(indexB, true)), 'if it has counts it shouldnt be solved', countsB, indexB, domain__debug(getDomain(indexB, true))); ASSERT(!countsR || !domain_isSolved(getDomain(indexR, true)), 'if it has counts it shouldnt be solved', countsR, indexR, domain__debug(getDomain(indexR, true))); TRACE(' - counts:', countsR, countsA, countsB, ', meta:', bounty__debugMeta(bounty, indexR), '=', bounty__debugMeta(bounty, indexA), '==?', bounty__debugMeta(bounty, indexB)); if (countsR === 1) { return leaf_issame(ml, offset, indexA, indexB, indexR, indexR); } if (countsA === 1) { ASSERT(!domain_isSolved(getDomain(indexA, true)), 'A cannot be solved (bounty ignores constants so count would be 0)'); if (canCutIssameForArg(indexA, indexB, indexR)) { return leaf_issame(ml, offset, indexA, indexB, indexR, indexA); } } if (countsB === 1) { // not covered, kept here just in case the above assertion doesnt hold in prod ASSERT(!domain_isSolved(getDomain(indexB, true)), 'B cannot be solved (bounty ignores constants so count would be 0)'); if (canCutIssameForArg(indexB, indexA, indexR)) { return leaf_issame(ml, offset, indexA, indexB, indexR, indexB); } } TRACE(' - no change from cut_issame'); ASSERT(ml_dec16(ml, offset + 1) === 2, 'should have 2 args'); pc = offset + SIZEOF_CR_2; } function canCutIssameForArg(indexL, indexO, indexR) { TRACE(' - canCutIssameForArg;', indexL, indexO, indexR, '->', domain__debug(getDomain(indexR, true)), '=', domain__debug(getDomain(indexL, true)), '==?', domain__debug(getDomain(indexO, true))); // an issame can only be leaf-cut on an arg if the leaf can represent all outcomes // so if C is solved, solve as SAME or DIFF. // otherwise make sure the leaf contains all vars of the other var and at least one var that's not in there // as long as that's impossible we can't cut it without implicitly forcing vars // first check whether R is booly-solved, this would mean fewer values to check let R = getDomain(indexR, true); if (domain_isZero(R)) { TRACE(' - R=0 and size(L)>2 so cuttable'); // L contains at least two values so regardless of the state of O, L can fulfill != ASSERT(domain_size(L) >= 2, 'see?'); return true; } // R=1 or R=booly is more involved because we at least // need to know whether L contains all values in O let L = getDomain(indexL, true); let O = getDomain(indexO, true); let LO = domain_intersection(L, O); // <-- this tells us that TRACE(' - LO:', domain__debug(LO)); if (domain_hasNoZero(R)) { // only cut if we are certain L can represent eq in any way O solves if (!LO) { TRACE(' - R>=1 and A contains no value in B so reject'); // no values in L and O match so reject setDomain(indexL, domain_createEmpty(), false, true); return false; } if (LO === O) { TRACE(' - R>=1 and A contains all values in B so cuttable'); // this means L contains all values in O (and maybe more, dont care) // which means L can uphold the eq for any value of O return true; } TRACE(' - R>=1 and A contains some but not all B so not cuttable, yet'); // there's no guarantee O solves to a value in L so we cant cut safely return true; } TRACE(' - R unresolved, cuttable if L contains all values in O and then some;', LO === O, LO !== L, 'so:', LO === O && LO !== L); // we dont know R so L should contain all values in O (LO==O) and at least // one value not in O (LO != O), to consider this a safe cut. otherwise dont. return LO === O && LO !== L; } function cut_issome(ml, offset) { let argCount = ml_dec16(ml, offset + 1); let argsOffset = offset + SIZEOF_C; let opSize = SIZEOF_C + argCount * 2 + 2; let indexR = readIndex(ml, argsOffset + argCount * 2); let countsR = getCounts(bounty, indexR); TRACE(' ! cut_issome; R=', indexR); if (countsR === 1) { return leaf_issome(ml, offset, indexR, argCount); } for (let i = 0; i < argCount; ++i) { let index = readIndex(ml, offset + SIZEOF_C + i * 2); let A = getDomain(index, true); if (domain_isZero(A)) { TRACE(' - some has zeroes, requesting minimizer to remove them'); requestAnotherCycle = true; // minimizer should eliminate these break; } } TRACE(' - cut_issome did not change anything'); pc += opSize; } function cut_lt(ml, offset) { let indexA = readIndex(ml, offset + OFFSET_C_A); let indexB = readIndex(ml, offset + OFFSET_C_B); let countsA = getCounts(bounty, indexA); let countsB = getCounts(bounty, indexB); TRACE(' ! cut_lt; ', indexA, '<', indexB, '::', domain__debug(getDomain(indexA, true)), '<', domain__debug(getDomain(indexB, true))); ASSERT(!countsA || !domain_isSolved(getDomain(indexA, true)), 'if it has counts it shouldnt be solved', countsA, indexA, domain__debug(getDomain(indexA, true))); ASSERT(!countsB || !domain_isSolved(getDomain(indexB, true)), 'if it has counts it shouldnt be solved', countsB, indexB, domain__debug(getDomain(indexB, true))); TRACE(' - counts:', countsA, countsB, ', meta:', bounty__debugMeta(bounty, indexA), '<', bounty__debugMeta(bounty, indexB)); if (indexA === indexB) { TRACE(' - index A == B, redirecting to minimizer'); requestAnotherCycle = true; return; } if (countsA === 1) { return leaf_lt(ml, offset, indexA, indexB, 'lhs'); } if (countsB === 1) { return leaf_lt(ml, offset, indexA, indexB, 'rhs'); } TRACE(' - cut_lt did not change anything'); pc += SIZEOF_C_2; } function cut_lte(ml, offset) { let indexA = readIndex(ml, offset + OFFSET_C_A); let indexB = readIndex(ml, offset + OFFSET_C_B); let countsA = getCounts(bounty, indexA); let countsB = getCounts(bounty, indexB); TRACE(' ! cut_lte; ', indexA, '<=', indexB, '::', domain__debug(getDomain(indexA, true)), '<=', domain__debug(getDomain(indexB, true))); ASSERT(!countsA || !domain_isSolved(getDomain(indexA, true)), 'if it has counts it shouldnt be solved', countsA, indexA, domain__debug(getDomain(indexA, true))); ASSERT(!countsB || !domain_isSolved(getDomain(indexB, true)), 'if it has counts it shouldnt be solved', countsB, indexB, domain__debug(getDomain(indexB, true))); TRACE(' - counts:', countsA, '<=', countsB, ', meta:', bounty__debugMeta(bounty, indexA), '<=', bounty__debugMeta(bounty, indexB)); if (indexA === indexB) { TRACE(' - index A == B, redirecting to minimizer'); requestAnotherCycle = true; return; } if (countsA === 1) { if (leaf_lte(ml, offset, indexA, indexB, true)) return; } if (countsB === 1) { if (leaf_lte(ml, offset, indexA, indexB, false)) return; } if (countsA > 0) { let metaA = getMeta(bounty, indexA); if (metaA === BOUNTY_FLAG_NALL || metaA === (BOUNTY_FLAG_NALL | BOUNTY_FLAG_LTE_LHS)) { if (trick_ltelhs_nall_leaf(ml, indexA, countsA)) return; } if (metaA === BOUNTY_FLAG_LTE_LHS) { if (trick_only_ltelhs_leaf(ml, indexA, countsA)) return; } if (countsA === 2) { if (metaA === (BOUNTY_FLAG_LTE_LHS | BOUNTY_FLAG_SOME)) { if (trick_ltelhs_some_leaf(ml, offset, indexA, countsA)) return; } } if (countsA >= 3) { if (metaA === (BOUNTY_FLAG_SOME | BOUNTY_FLAG_NALL | BOUNTY_FLAG_LTE_LHS)) { if (trick_ltelhs_nalls_some(indexA, countsA)) return; } if (metaA === (BOUNTY_FLAG_SOME | BOUNTY_FLAG_NALL | BOUNTY_FLAG_LTE_LHS | BOUNTY_FLAG_LTE_RHS)) { if (trick_lteboth_nall_some(indexA, countsA)) return; } } if (hasFlags(metaA, BOUNTY_FLAG_ISALL_RESULT)) { // in this trick one constraint subsumes the other so no need for A being a leaf if (trick_isall_ltelhs_2shared(ml, offset, indexA, countsA)) return; // in this trick A needs to be a leaf if (countsA === 2) { if (trick_isall_ltelhs_1shared(ml, offset, indexA, countsA)) return; } } } if (countsB === 2) { let metaB = getMeta(bounty, indexB); if (metaB === (BOUNTY_FLAG_LTE_RHS | BOUNTY_FLAG_ISALL_RESULT)) { if (trick_isall_lterhs_entry(indexB, offset, countsB)) return; } if (metaB === (BOUNTY_FLAG_LTE_RHS | BOUNTY_FLAG_ISSAME_RESULT)) { if (trick_issame_lterhs(indexB, offset, countsB, indexA)) return; } } TRACE(' - cut_lte changed nothing'); pc += SIZEOF_C_2; } function cut_nall(ml, offset) { let argCount = ml_dec16(ml, offset + 1); TRACE(' ! cut_nall;', argCount, 'args'); let indexA = readIndex(ml, offset + OFFSET_C_A); let countsA = getCounts(bounty, indexA); if (countsA > 1 && countsA < BOUNTY_MAX_OFFSETS_TO_TRACK) { // search all counts for a second SOME if (desubset_nall(ml, offset, argCount, indexA, countsA)) return; } if (argCount === 2) { if (readIndex(ml, offset + OFFSET_C_A) === readIndex(ml, offset + OFFSET_C_B)) { TRACE(' - argcount=2 and A==B, requesting minimzer cycle'); requestAnotherCycle = true; return; } } for (let i = 0; i < argCount; ++i) { let index = readIndex(ml, offset + SIZEOF_C + i * 2); let counts = getCounts(bounty, index); if (counts > 0) { let meta = getMeta(bounty, index); if (meta === BOUNTY_FLAG_NALL) { // var is only used in nalls. eliminate them all and defer the var if (trickNallOnly(index, counts)) return true; } } } TRACE(' - cut_nall did not change anything'); pc += SIZEOF_C + argCount * 2; } function cut_some(ml, offset) { let argCount = ml_dec16(ml, pc + 1); TRACE(' ! cut_some;', argCount, 'args'); let indexA = readIndex(ml, offset + OFFSET_C_A); let countsA = getCounts(bounty, indexA); if (countsA > 1 && countsA < BOUNTY_MAX_OFFSETS_TO_TRACK) { // search all counts for a second SOME if (desubset_some(ml, offset, argCount, indexA, countsA)) return; } if (argCount === 2) { let indexB = readIndex(ml, offset + OFFSET_C_B); if (indexA === indexB) { TRACE(' - argcount=2 and A==B, requesting minimzer cycle'); requestAnotherCycle = true; return; } if (countsA === 1) { leaf_some_2(ml, offset, indexA, indexB, indexA, indexB); return; } let countsB = getCounts(bounty, indexB); if (countsB === 1) { leaf_some_2(ml, offset, indexB, indexA, indexA, indexB); return; } } let hasZero = false; for (let i = 0; i < argCount; ++i) { let index = readIndex(ml, offset + SIZEOF_C + i * 2); let counts = getCounts(bounty, index); if (counts > 0) { let meta = getMeta(bounty, index); if (meta === BOUNTY_FLAG_SOME) { // var is only used in SOMEs. eliminate them all and defer the var if (trickSomeOnly(index, counts)) return true; } } let A = getDomain(index, true); if (domain_isZero(A)) { hasZero = true; } } if (hasZero) { TRACE(' - some has zeroes, requesting minimizer to remove them'); requestAnotherCycle = true; // minimizer should eliminate these } TRACE(' - cut_some changed nothing'); pc += SIZEOF_C + argCount * 2; } function cut_sum(ml, offset) { let argCount = ml_dec16(ml, offset + 1); let argsOffset = offset + SIZEOF_C; let opSize = SIZEOF_C + argCount * 2 + 2; let indexR = readIndex(ml, argsOffset + argCount * 2); let R = getDomain(indexR, true); let countsR = getCounts(bounty, indexR); TRACE(' ! cut_sum;'); TRACE(' - index R:', indexR, ', domain:', domain__debug(R), ', argCount:', argCount, ',counts R:', countsR, ', meta R:', bounty__debugMeta(bounty, indexR)); ASSERT(!countsR || !domain_isSolved(getDomain(indexR, true)), 'if it has counts it shouldnt be solved', countsR, indexR, domain__debug(getDomain(indexR, true))); let RisBoolyPair = domain_isBoolyPair(R); // collect meta data on the args of this sum // TODO: should we have a bounty for both constraints and vars? let allSumArgsBool = true; // all args [01]? used later let allSumArgsBoolyPairs = true; // all args have a zero and one nonzero value? let sum = domain_createValue(0); let argsMinSum = 0; let argsMaxSum = 0; let constantValue = 0; // allow up to one constant larger than 0 let constantArgIndex = -1; let multiConstants = false; for (let i = 0; i < argCount; ++i) { let index = readIndex(ml, argsOffset + i * 2); let domain = getDomain(index, true); let minValue = domain_min(domain); let maxValue = domain_max(domain); sum = domain_plus(sum, domain); argsMinSum += minValue; argsMaxSum += maxValue; //let nonBoolNonSolvedDomain = maxValue > 1; if (minValue === maxValue) { multiConstants = constantArgIndex >= 0; constantValue = minValue; constantArgIndex = i; } else { if (!domain_isBoolyPair(domain)) allSumArgsBoolyPairs = false; if (!domain_isBool(domain)) allSumArgsBool = false; } } TRACE(' - sum args; min:', argsMinSum, ', max:', argsMaxSum, ', constantValue:', constantValue, ', constant pos:', constantArgIndex, ', sum:', domain__debug(sum)); if (multiConstants) { TRACE(' - multiple constants detected, bailing so minimizer can correct this'); return; } // [0 0 23 23] = [0 1] + [0 0 2 2] + [0 0 20 20] -> R = all?(A B C) if (RisBoolyPair && allSumArgsBoolyPairs) { // this trick is irrelevant of leaf status (this could be part of minimizer) TRACE(' - R is a booly and all the args are booly too, checking whether', domain_max(R), '===', argsMaxSum); ASSERT(argsMinSum === 0, 'if all are booly argsMinSum should be zero'); if (domain_max(R) === argsMaxSum) { TRACE(' - R is', domain__debug(R), 'and all the args are booly and their argsMaxSum is equal to max(R) so this is actually an isall. morphing sum to isall'); ml_enc8(ml, offset, ML_ISALL); return; } } // note: we cant simply eliminate leaf vars because they still constrain // the allowed distance between the other variables and if you // eliminate this constraint, that limitation is not enforced anymore. // so thread carefully. if (countsR === 1) { // R can only be eliminated if all possible additions between A and B occur in it // because in that case it no longer serves as a constraint to force certain distance(s) if (sum === domain_intersection(R, sum)) { // all possible outcomes of summing any element in the sum args are part of R so // R is a leaf and the args aren't bound by it so we can safely remove the sum return leaf_sum_result(ml, offset, argCount, indexR); } // if R is [0, n-1] and all n args are [0, 1] then rewrite to a NALL if (allSumArgsBool && R === domain_createRange(0, argCount - 1)) { return trick_sum_to_nall(ml, offset, argCount, indexR); } // if R is [1, n] and all n args are [0, 1] then rewrite to a SOME if (allSumArgsBool && R === domain_createRange(1, argCount)) { return trick_some_sum(ml, offset, argCount, indexR); } } if (countsR >= 2) { let metaR = getMeta(bounty, indexR); ASSERT(hasFlags(metaR, BOUNTY_FLAG_SUM_RESULT), 'per definition because this is the R in a sum'); // TODO: cant we also do this with counts>2 when R is a bool when ignoring the sum? // TOFIX: confirm whether we need allSumArgsBool here, or whether we can lax it a little if (allSumArgsBoolyPairs && countsR === 2) { // we already confirmed that R is for a sum, so we can strictly compare the meta flags // (R = sum(A B C) & (S = R==?3) -> S = all?(A B C) // (R = sum(A B C) & (S = R==?0) -> S = none?(A B C) // (R = sum(A B C) & (S = R==?[0 1]) -> S = nall?(A B C) // (R = sum(A B C) & (S = R==?[1 2]) -> S = some?(A B C) if (metaR === (BOUNTY_FLAG_ISSAME_ARG | BOUNTY_FLAG_SUM_RESULT)) { if (trick_issame_sum(ml, offset, indexR, countsR, argCount, sum, argsMinSum, argsMaxSum, constantValue, constantArg