fdp
Version:
Finite Domain Problem reduction system
1,363 lines (1,142 loc) • 298 kB
JavaScript
// 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