UNPKG

fdjs

Version:

Finite Domain Constrain Solver

1,417 lines (1,223 loc) 71.2 kB
// Copyright (c) 2011, Srikumar K. S. (srikumarks@gmail.com) // License: New BSD (http://www.opensource.org/licenses/bsd-license.php) // // Module: FD // Status: Work (very much) in progress // // Basic finite domain constraint programming using the // "computation spaces" idea in Oz to factorize a search // problem into propagators, distributors, search strategies // and problem scripts. // // Exports: // // Class FD.space is a computation space. // FD.space has methods that add propagators to spaces. // Each space has a "brancher" object that can branch // a space if it isn't solved yet and some options can be // explored. You can enqueue different branchers into a // space's brancher object which will then control how the // search tree is created. // // Namespace FD.distribute has the following strategies - // FD.distribute.naive // FD.distribute.fail_first // // Namespace FD.search has the following search algo - // FD.search.depth_first // var FD = (function (exports, Math) { var FD_SUP = 100000000; // Fails if the given dom is empty by throwing 'fail'. // If it isn't empty, dom itself is returned. // Useful within propagators. function domain_non_empty(dom) { if (dom.length === 0) { throw 'fail'; } return dom; } // CSIS form = Canonical Sorted Interval Sequeunce form. // // Intersection of two domains given in CSIS form. // r is optional and if given it should be an array and // the domain pieces will be inserted into it, in which case // the result domain will be returned unsimplified. function domain_intersection(dom1, dom2, r) { var i, j, len1, len2, b1, b2, d1, d2, d3, d4, d, mx, mn; if (dom1.length === 0 || dom2.length === 0) { return r || []; } if (dom1.length === 1) { if (dom2.length === 1) { b1 = dom1[0]; b2 = dom2[0]; mn = b1[0] > b2[0] ? b1[0] : b2[0]; mx = b1[1] < b2[1] ? b1[1] : b2[1]; r = r || []; if (mx >= mn) { r.push([mn, mx]); } return r; } else { return domain_intersection(dom2, dom1, r); } } else { if (dom2.length === 1) { mn = dom2[0][0]; mx = dom2[0][1]; r = r || []; for (i = 0, len1 = dom1.length; i < len1; ++i) { d = dom1[i]; if (d[0] <= mx && d[1] >= mn) { r.push([(mn > d[0] ? mn : d[0]), (mx < d[1] ? mx : d[1])]); } } return r; } else { // Worst case. Both lengths are > 1. Divide and conquer. i = dom1.length >> 1; j = dom2.length >> 1; d1 = dom1.slice(0, i); d2 = dom1.slice(i); d3 = dom2.slice(0, j); d4 = dom2.slice(j); d = domain_intersection(d1, d3, r); d = domain_intersection(d1, d4, d); d = domain_intersection(d2, d3, d); d = domain_intersection(d2, d4, d); return r ? d : simplify_domain(d); } } } // Turns d into CSIS form. function simplify_domain(d) { if (d.length === 0) { return d; } if (d.length === 1) { if (d[0][1] < d[0][0]) { return []; } else { return d; } } var result = []; var i, len, prev, prevL, prevR, next, nextL, nextR; // Find the first index from which we need to do the // simplification. If at the end of this loop i >= len, // then we need to do nothing and we avoid loading the GC. // This loop checks for overlaps and ordering issues. prevL = d[0][0]; prevR = d[0][1]; i = 0; len = d.length; if (prevR >= prevL) { for (i = 1; i < len; ++i) { next = d[i]; if (next[1] < next[0] || next[0] <= prevR || next[1] <= prevR) { break; } else { prevR = next[1]; } } } if (i >= len) { // Nothing to do. return d; } d.sort(function (d1, d2) { return d1[0] - d2[0]; }); result.push(prev = d[0]); for (i = 1, len = d.length; i < len; ++i) { next = d[i]; if (prev[1] >= next[0]) { // Two consecutive domains that are at least touching. // Merge them. prev[1] = Math.max(prev[1], next[1]); } else { result.push(prev = next); } } d.splice(0, d.length); d.push.apply(d, result); return result; } function domain_bounds(d) { if (d.length > 0) { return [d[0][0], d[d.length-1][1]]; } else { throw 'fail'; } } function domain_equal(d1, d2) { if (d1.length != d2.length) { return false; } var i, len; for (i = 0, len = d1.length; i < len; ++i) { if (d1[i][0] != d2[i][0] || d1[i][1] != d2[i][1]) { return false; } } return true; } // The complement of a domain is such that domain U domain' = [[0, FD_SUP]]. function domain_complement(d) { if (d.length === 0) { return [[0, FD_SUP]]; } else { var end = 0; var result = []; var i, len; for (i = 0, len = d.length; i < len; ++i) { if (end < d[i][0]) { result.push([end, d[i][0] - 1]); } end = d[0][1] + 1; } if (end < FD_SUP) { result.push([end, FD_SUP]); } return result; } } // The union of two domains contains all the intervals in either domain. function domain_union(d1, d2) { var result = []; result.push.apply(result, d1); result.push.apply(result, d2); return simplify_domain(result); } // If a parent 'brancher' is given, then the queue is shared // with the parent, but the next_brancher is incremented. function Brancher(S, brancher) { this.space = S; this.queue = brancher ? brancher.queue : []; this.next_brancher = brancher ? brancher.next_brancher : 0; } Brancher.prototype = { branch: function () { // Note that the "next brancher" field will be // taken from the deepest space's brancher object // due to access via 'this'. However, the queue // is an array whose contents are common to all // branchers part of the same tree. var b, ch = null; do { b = this.queue[this.next_brancher]; ch = b ? b.branch.call(this, this.space) : null; } while (ch === null && this.descend()); return ch; }, enqueue: function (b) { // Note that the queue is common to all spaces // starting from the parent space, but the // 'next_brancher' index is local to the brancher // belonging to each space. this.queue.push(b); return this; }, descend: function () { if (this.next_brancher + 1 < this.queue.length) { this.next_brancher++; return true; } else { return false; } }, descend_and_branch: function () { return this.descend() ? this.branch() : null; } }; // Empty propagation step function empty_propagation_step() { return 0; } // A propagator is solved if all the depvars it affects and // depends on have domains of size = 1. function propagator_is_solved(S, p, dont_mark_solved) { var i, len, b; if (p.solved) { return true; } for (i = 0, len = p.allvars.length; i < len; ++i) { if (S.vars[p.allvars[i]].is_undetermined()) { return false; } } // return dont_mark_solved ? true : (p.solved = true); return true; } // Some common features for all propagators var Propagator = { save_vars: function (P) { var var_state_stack = P.var_state_stack || (P.var_state_stack = []); var i, N, doms = [], steps = []; for (i = 1, N = P.space.length; i < N; ++i) { doms.push(P.space[i].dom); steps.push(P.space[i].step); } var_state_stack.push([doms, steps, P.last_step]); P.old_step = P.step; }, restore_vars: function (P) { var vss = P.var_state_stack.pop(); var doms = vss[0], steps = vss[1]; var i, N; for (i = 1, N = P.space.length; i < N; ++i) { P.space[i].dom = doms[i - 1]; P.space[i].step = steps[i - 1]; } P.step = P.old_step; P.last_step = vss[2]; }, discard_saved_vars: function (P) { P.var_state_stack.pop(); delete P.old_step; }, snapshot_vars: function (P) { this.save_vars(P); return P.var_state_stack.pop()[0]; }, complementary_operator: { eq: 'neq', neq: 'eq', lt: 'gte', gt: 'lte', lte: 'gt', gte: 'lt' } }; // Concept of a space that holds fdvars, propagators and branchers. function Space(S) { if (S) { // The given space is being cloned. var i, j, v, p; // Bring the fdvars in using copy-on-write. this.vars = {}; for (i in S.vars) { p = S.vars[i]; this.vars[i] = new FDVar(p.dom, p.step); } // The propagators can simply be borrowed by reference // into this space since they all have a "set space" step // before they get to run. this._propagators = []; for (i = 0; i < S._propagators.length; ++i) { p = S._propagators[i]; if (!propagator_is_solved(S, p)) { v = {allvars: p.allvars, depvars: p.depvars, step: p.step}; this.newprop(v); } } // The brancher queue object is shared with the parent, // except for the "next brancher" state which is made // local to this space. this.brancher = new Brancher(this, S.brancher); // Keep note of the space that this one was cloned from. // // TODO: Remove this reference. It might result in less // memory being used during searches. If we keep around the // parent space like this, then we have to maintain all // spaces that we search in memory. For now, since I'm // still debugging, this is all right. this.clone_of = S; } else { // The FDVARS are all named and accessed by name. // When a space is cloned, the clone's fdvars objects // all have their __proto__ fields set to the parent's // fdvars object. This gets us copy on modify semantics. this.vars = {}; this._propagators = []; this.brancher = new Brancher(this); } this.succeeded_children = 0; this.failed_children = 0; this.stable_children = 0; return this; } // Duplicates the functionality of new Space(S) for readability. Space.prototype.clone = function () { return new Space(this); }; // When done with the space, call this to send success results // to the parent space from which it was cloned. Space.prototype.done = function () { if (this.clone_of) { this.clone_of.succeeded_children += this.succeeded_children; this.clone_of.failed_children += this.failed_children; if (this.failed) { this.clone_of.failed_children++; } if (this.succeeded_children === 0 && this.failed_children > 0) { this.failed = true; } this.clone_of.stable_children += this.stable_children; } }; // A monotonically increasing class-global counter for unique temporary variable names. Space._temp_count = 1; // Run all the propagators until stability point. Returns the number // of changes made or throws a 'fail' if any propagator failed. Space.prototype.propagate = function () { var i, ps, len, count, totalCount = 0; do { for (i = 0, ps = this._propagators, len = ps.length, count = 0; i < len; ++i) { count += ps[i].step(); } totalCount += count; } while (count > 0); // console.log(JSON.stringify(this.solution())); return totalCount; }; // Returns true if this space is solved - i.e. when // all the fdvars in the space have a singleton domain. // // This is a *very* strong condition that might not need // to be satisfied for a space to be considered to be // solved. For example, the propagators may determine // ranges for all variables under which all conditions // are met and there would be no further need to enumerate // those solutions. // // For weaker tests, use the solve_for_variables function // to create an appropriate "is_solved" tester and // set the "state.is_solved" field at search time to // that function. Space.prototype.is_solved = function () { var i, v; for (i in this.vars) { v = this.vars[i]; if (v.dom.length === 1) { if (v.dom[0][0] !== v.dom[0][1]) { return false; } else { // Singleton domain } } else { return false; } } return true; }; // Returns an object whose field names are the fdvar names // and whose values are the solved values. The space *must* // be already in a solved state for this to work. Space.prototype.solution = function () { var result = {}; var i, v, d; for (i in this.vars) { // Don't include the temporary variables in the "solution". // Temporary variables take the form of a numeric property // of the object, so we test for the key to be a number and // don't include those variables in the result. if (/^[0-9]+$/.test(i) === false) { v = this.vars[i]; d = v.dom; result[i] = (d.length === 0 ? false : ((d.length > 1 || d[0][1] > d[0][0]) ? d : d[0][0])); } } return result; }; // Utility to easily print out the state of variables in the space. Space.prototype.toString = function () { return JSON.stringify(this.solution()); }; // Injects the given proc into this space by calling // the proc with a single argument which is the current space. Space.prototype.inject = function (proc) { proc(this); // duh! return this; }; Space.prototype.initprop = function (p) { p.last_step = -1; p.space = [this]; var i, len; for (i = 0, len = p.allvars.length; i < len; ++i) { p.space.push(this.vars[p.allvars[i]]); } return p; }; // Adds the new given propagator to this space and returns the space. Space.prototype.newprop = function (p) { this._propagators.push(this.initprop(p)); return this; }; // Returns a new unique name usable for a temporary fdvar // for more complex calculations. Every call will yield // a different name that is unique across all spaces. // // You can optionally specify a domain for the temporary // if you already know something about it. Space.prototype.temp = function (dom) { var t = ++(Space._temp_count); this.decl(t, dom); return t; }; // Create N temporary FD variables and return their names // in an array. Space.prototype.temps = function (N, dom) { var i; var result = []; for (i = 0; i < N; ++i) { result.push(this.temp(dom)); } return result; }; // Create a "constant". We have no optimizing support for // constants at the moment and just treat it as a temp FDVar // whose domain is of size = 1. // // Fixing issue #1 - 'const' in the property position is not // accepted by the IE8 parser. It is likely to be rejected by // the closure compiler too. So I'm changing the name to 'konst' // instead. I'll keep the old name 'const' for compatibility. Space.prototype.konst = function (val) { if (val < 0 || val > FD_SUP) { throw "FD.space.konst: Value out of valid range"; } return this.temp([[val, val]]); }; Space.prototype['const'] = Space.prototype.konst; // Keep old name for compatibility. function FDVar(dom, step) { this.dom = dom || [[0, FD_SUP]]; this.step = step || 0; } FDVar.prototype = { is_undetermined: function () { return (this.dom.length > 1) || (this.dom[0][0] < this.dom[0][1]); }, set_dom: function (d) { if (!domain_equal(this.dom, d)) { this.dom = d; this.step++; } return d; }, constrain: function (d) { return this.set_dom(domain_non_empty(domain_intersection(this.dom, d))); }, size: function () { // TODO: Can be cached using the 'step' member which // keeps track of the number of times the domain was // changed. var i, N, s = 0; for (i = 0, N = this.dom.length; i < N; ++i) { s += this.dom[i][1] - this.dom[i][0] + 1; } return s; }, min: function () { return this.dom[0][0]; }, max: function () { return this.dom[this.dom.length - 1][1]; }, // This "mid" function is quick to calculate and is a useful // compromise if you aren't really interested in the exact // middle value, but something along the lines of "avoid the // extremes" as best as you can, as fast as you can. rough_mid: function () { var midDomIx = Math.floor(this.dom.length / 2); var midDom = this.dom[midDomIx]; return Math.round((midDom[0] + midDom[1]) / 2); }, // This is true "mid" function that returns the exact middle // value of the domain, but needs to run through the whole domain // to do it. Can be made more efficient, see TODO note below. mid: function () { var size = this.size(); var midIx = Math.floor(size / 2); var domIx = 0; var dom = this.dom[domIx]; // TODO: By right, we should do a binary search here // instead of a linear search. Yes, I'm lazy :P (Kumar) while (midIx > dom[1] - dom[0]) { midIx -= dom[1] - dom[0] + 1; domIx++; dom = this.dom[domIx]; } return dom[0] + midIx; } }; // Once you create an fdvar in a space with the given // name, it is available for accessing as a direct member // of the space. Since this can cause a name clash, it is // recommended that you start the names of fdvars with an // upper case letter. Since all the declared member names // start with a lower case letter, a clash can certainly // be avoided if you stick to that rule. // // If the domain is not specified, it is taken to be [[0, FD_SUP]]. // // Returns the space. All methods, unless otherwise noted, // will return the current space so that other methods // can be invoked in sequence. Space.prototype.decl = function (name_or_names, dom) { var i; if (name_or_names instanceof Object || name_or_names instanceof Array) { // Recursively declare all variables in the structure given. for (i in name_or_names) { this.decl(name_or_names[i], dom); } return this; } // A single variable is being declared. var name = name_or_names, fs = this.vars; var f = fs[name]; if (f) { // If it already exists, change the domain if necessary. if (dom) { f.set_dom(dom); } return this; } fs[name] = new FDVar(dom); return this; }; // Same function as var, but the domain is // that of a single number. Space.prototype.num = function(name, n) { return this.decl(name, [[n, n]]); }; // 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. // // `argv` is an array of arguments accepted by the given // comparison operator. Currently this *must* be an array of two // FD variable names. // // `boolname` 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. Space.prototype.reified = function (opname, argv, boolname) { var result, positive_propagator, negative_propagator; if (opname in Propagator.complementary_operator) { if (boolname) { this.vars[boolname].constrain([[0,1]]); result = this; } else { boolname = this.temp([[0,1]]); result = boolname; } console.log('GRAPH: ' + argv[0] + ' ' + opname + ' ' + argv[1] + ' :: ' + boolname); this[opname].apply(this, argv); positive_propagator = this._propagators.pop(); this[Propagator.complementary_operator[opname]].apply(this, argv); negative_propagator = this._propagators.pop(); var deps = argv.slice(0); deps.push(boolname); this.newprop({ allvars: deps, depvars: deps, step: function () { var S = this.space[0], v1 = this.space[1], v2 = this.space[2], b = this.space[3], bdom, snap, i, N, d, k, p, np; var nextStep = v1.step + v2.step + b.step, f; if (nextStep > this.last_step) { // We need to make sure the `last_step` related changes // are unique to this space, since p and np won't be // borrowed into cloned spaces, since they aren't in // the `S._propagators` array. if (false && this.solved) { return 0; } if (!this.p) { this.p = p = S.initprop({ allvars: positive_propagator.allvars, depvars: positive_propagator.depvars, step: positive_propagator.step }); } else { p = this.p; } if (!this.np) { this.np = np = S.initprop({ allvars: negative_propagator.allvars, depvars: negative_propagator.depvars, step: negative_propagator.step }); } else { np = this.np; } do { this.last_step = v1.step + v2.step + b.step; bdom = b.dom[0]; if (bdom[0] === 1) { p.step(); // may throw this.solved = propagator_is_solved(S, p); } if (bdom[1] === 0) { // The reified fdvar generates the negative condition. np.step(); // may throw this.solved = propagator_is_solved(S, np); } if (bdom[0] < bdom[1]) { // The reified fdvar doesn't decide the condition, so // we now need to check whether the conditions constrain // the reified fdvar. Propagator.save_vars(p); try { p.step(); Propagator.restore_vars(p); } catch (e) { Propagator.restore_vars(p); b.constrain([[0, 0]]); } bdom = b.dom[0]; } if (bdom[0] < bdom[1]) { Propagator.save_vars(np); try { np.step(); Propagator.restore_vars(np); } catch (e) { Propagator.restore_vars(np); b.constrain([[1, 1]]); } } } while (v1.step + v2.step + b.step > this.last_step); this.last_step = v1.step + v2.step + b.step; return this.last_step - nextStep; } else { return 0; } } }); return result; } else { throw "FD.space.reified: Unsupported operator '" + opname + "'"; } }; // Domain equality propagator. Creates the propagator // in this space. The specified variables need not // exist at the time the propagator is created and // added, since the fdvars are all referenced by name. // // A convention for propagators is that the return // value of a propagator is number unless // the propagator has failed, in which case it throws // an exception 'fail'. The returned number indicates // the number of domains that were changed by this propagation // step. // // The second optional argument indicates what you want the // propagator to do. Space.prototype.eq = function(v1name, v2name) { // If v2name is not specified, then we're operating in functional syntax // and the return value is expected to be v2name itself. This can happen // when, for example, scale uses a weight factor of 1. if (!v2name) { return v1name; } var p = { // Make available the list of fd variables that this // propagator affects. The space can check whether // a propagator is at its limit using this list. allvars: [v1name, v2name], depvars: [v1name, v2name], step: function () { var v1 = this.space[1], v2 = this.space[2]; var nextStep = v1.step + v2.step; if (nextStep > this.last_step) { var d = domain_non_empty(domain_intersection(v1.dom, v2.dom)); v1.set_dom(d); v2.set_dom(d); this.last_step = v1.step + v2.step; return this.last_step - nextStep; } else { return 0; } } }; return this.newprop(p); }; // Less than propagator. See general propagator nores // for fdeq which also apply to this one. Space.prototype.lt = function (v1name, v2name) { var p = { allvars: [v1name, v2name], depvars: [v1name, v2name], step: function () { var v1 = this.space[1], v2 = this.space[2]; var nextStep = v1.step + v2.step; if (nextStep > this.last_step) { var b1 = domain_bounds(v1.dom); var b2 = domain_bounds(v2.dom); this.last_step = nextStep; if (b2[0] > b1[1]) { // Condition already satisfied. No changes necessary. // Change the step function to one that does almost no work. // this.step = empty_propagation_step; this.solved = true; return 0; } var count = 0; do { this.last_step = v1.step + v2.step; b1 = domain_bounds(v1.dom); b2 = domain_bounds(v2.dom); if (b2[1] - 1 < b1[1]) { // Need to change domain of v1. v1.set_dom(domain_non_empty(domain_intersection(v1.dom, [[b1[0], b2[1] - 1]]))); } if (b1[0] + 1 > b2[0]) { // Need to change domain of v2. v2.set_dom(domain_non_empty(domain_intersection(v2.dom, [[b1[0] + 1, b2[1]]]))); } } while (v1.step + v2.step > this.last_step); return (this.last_step = v1.step + v2.step) - nextStep; } else { return 0; } } }; return this.newprop(p); }; // Greater than propagator. Space.prototype.gt = function (v1name, v2name) { return this.lt(v2name, v1name); }; // Less than or equal to propagator. Space.prototype.lte = function (v1name, v2name) { var p = { allvars: [v1name, v2name], depvars: [v1name, v2name], step: function () { var v1 = this.space[1], v2 = this.space[2]; var nextStep = v1.step + v2.step; if (nextStep > this.last_step) { var b1 = domain_bounds(v1.dom); var b2 = domain_bounds(v2.dom); this.last_step = nextStep; if (b2[0] >= b1[1]) { // Condition already satisfied. No changes necessary. // Change the step function to one that does almost no work. // this.step = empty_propagation_step; this.solved = true; return 0; } var count = 0; do { this.last_step = v1.step + v2.step; b1 = domain_bounds(v1.dom); b2 = domain_bounds(v2.dom); if (b2[1] < b1[1]) { // Need to change domain of v1. v1.set_dom(domain_non_empty(domain_intersection(v1.dom, [[b1[0], b2[1]]]))); } if (b1[0] > b2[0]) { // Need to change domain of v2. v2.set_dom(domain_non_empty(domain_intersection(v2.dom, [[b1[0], b2[1]]]))); } } while (v1.step + v2.step > this.last_step) return (this.last_step = v1.step + v2.step) - nextStep; } else { return 0; } } }; return this.newprop(p); }; // Greater than or equal to. Space.prototype.gte = function (v1name, v2name) { return this.lte(v2name, v1name); }; // Ensures that the two variables take on different values. Space.prototype.neq = function (v1name, v2name) { var p = { allvars: [v1name, v2name], depvars: [v1name, v2name], step: function () { var v1 = this.space[1], v2 = this.space[2]; var nextStep = v1.step + v2.step; if (nextStep > this.last_step) { var b1 = domain_bounds(v1.dom); var b2 = domain_bounds(v2.dom); this.last_step = nextStep; if (b2[0] > b1[1] || b1[1] < b2[0]) { // Condition already satisfied. No changes necessary. // Change the step function to one that does almost no work. // this.step = empty_propagation_step; this.solved = true; return 0; } var v12 = domain_intersection(v1.dom, v2.dom); if (v12.length === 0) { // Condition already satisfied. // Change the step function to one that does almost no work. // this.step = empty_propagation_step; this.solved = true; return 0; } do { this.last_step = v1.step + v2.step; b1 = domain_bounds(v1.dom); b2 = domain_bounds(v2.dom); if (b1[0] === b1[1]) { v2.set_dom(domain_non_empty(domain_intersection(v2.dom, domain_complement([b1])))); } if (b2[0] === b2[1]) { v1.set_dom(domain_non_empty(domain_intersection(v1.dom, domain_complement([b2])))); } } while (v1.step + v2.step > this.last_step); this.last_step = v1.step + v2.step; return this.last_step - nextStep; } else { return 0; } } }; return this.newprop(p); }; // Takes an arbitrary number of FD variables and adds propagators that // ensure that they are pairwise distinct. Space.prototype.distinct = function (vars) { var i, j, len; for (i = 0, len = vars.length; i < len; ++i) { for (j = 0; j < i; ++j) { this.neq(vars[i], vars[j]); } } return this; }; function ring(plusop, minusop, v1name, v2name, sumname) { var retval = this; // If sumname is not specified, we need to create a temp // for the result and return the name of that temp variable. if (!sumname) { sumname = this.temp(); retval = sumname; } console.log('GRAPH: ' + v1name + ' + ' + v2name + ' = ' + sumname); this.newprop({ allvars: [v1name, v2name, sumname], depvars: [v1name, v2name], step: function () { var v1 = this.space[1], v2 = this.space[2], sum = this.space[3]; var nextStep = v1.step + v2.step + sum.step; if (nextStep > this.last_step) { sum.set_dom(domain_non_empty(domain_intersection(plusop(v1.dom, v2.dom), sum.dom))); this.last_step = sum.step + v1.step + v2.step; } return this.last_step - nextStep; } }); this.newprop({ allvars: [v1name, v2name, sumname], depvars: [v2name, sumname], step: function () { var v1 = this.space[1], v2 = this.space[2], sum = this.space[3]; var nextStep = v1.step + v2.step + sum.step; if (nextStep > this.last_step) { v1.set_dom(domain_non_empty(domain_intersection(minusop(sum.dom, v2.dom), v1.dom))); this.last_step = sum.step + v1.step + v2.step; } return this.last_step - nextStep; } }); this.newprop({ allvars: [v1name, v2name, sumname], depvars: [v1name, sumname], step: function () { var v1 = this.space[1], v2 = this.space[2], sum = this.space[3]; var nextStep = v1.step + v2.step + sum.step; if (nextStep > this.last_step) { v2.set_dom(domain_non_empty(domain_intersection(minusop(sum.dom, v1.dom), v2.dom))); this.last_step = sum.step + v1.step + v2.step; } return this.last_step - nextStep; } }); return retval; }; // Bidirectional addition propagator. Space.prototype.plus = function (v1name, v2name, sumname) { return ring.call(this, dom_plus, dom_minus, v1name, v2name, sumname); }; // Bidirectional multiplication propagator. Space.prototype.times = function (v1name, v2name, prodname) { return ring.call(this, dom_times, dom_divby, v1name, v2name, prodname); }; // factor = constant number (not an fdvar) // vname is an fdvar name // prodname is an fdvar name. // // factor * v = prod Space.prototype.scale = function (factor, vname, prodname) { if (factor === 1) { return this.eq(vname, prodname); } else if (factor === 0) { return this.eq(this.temp([[0, 0]]), prodname); } else if (factor < 0) { throw "scale: negative factors not supported."; } var retval = this; if (!prodname) { prodname = this.temp(); retval = prodname; } this.newprop({ allvars: [vname, prodname], depvars: [vname], step: function () { var v = this.space[1], prod = this.space[2]; var nextStep = v.step + prod.step; if (nextStep > this.last_step) { var bd = domain_bounds(v.dom); var kd, l, r; // We multiply only the interval bounds. kd = v.dom.map(function (i) { return [Math.min(FD_SUP, i[0] * factor), Math.min(FD_SUP, i[1] * factor)]; }); prod.set_dom(domain_non_empty(domain_intersection(kd, prod.dom))); return (this.last_step = v.step + prod.step) - nextStep; } else { return 0; } } }); this.newprop({ allvars: [vname, prodname], depvars: [prodname], step: function () { var v = this.space[1], prod = this.space[2]; var nextStep = v.step + prod.step; if (nextStep > this.last_step) { var dbyk, l, r; dbyk = simplify_domain(prod.dom.map(function (i) { l = i[0] / factor; r = i[1] / factor; return [l - l % 1, r - r % 1]; })); v.set_dom(domain_non_empty(domain_intersection(dbyk, v.dom))); return (this.last_step = v.step + prod.step) - nextStep; } else { return 0; } } }); return retval; }; // TODO: Can be made more efficient. Space.prototype.times_plus = function (k1, v1name, k2, v2name, resultname) { return this.plus(this.scale(k1, v1name), this.scale(k2, v2name), resultname); }; // Sum of N fdvars = resultFDVar // Creates as many temporaries as necessary. Space.prototype.sum = function (vars, resultName) { var n, N, t1, t2; var retval = this; if (!resultName) { resultName = this.temp(); retval = resultName; } switch (vars.length) { case 0: throw "Space.sum: Nothing to sum!"; case 1: this.eq(vars[0], resultName); return retval; case 2: this.plus(vars[0], vars[1], resultName); return retval; default: n = vars.length >> 1; t2 = this.temp(); this.sum(vars.slice(n), t2); if (n > 1) { t1 = this.temp(); this.sum(vars.slice(0, n), t1); } else { t1 = vars[0]; } this.plus(t1, t2, resultName); return retval; } }; // Product of N fdvars = resultFDvar. // Create as many temporaries as necessary. Space.prototype.product = function (vars, resultName) { var n, t1, t2; var retval = this; if (!resultName) { resultName = this.temp(); retval = resultName; } switch (vars.length) { case 0: return retval; case 1: this.eq(vars[0], resultName); return retval; case 2: this.times(vars[0], vars[1], resultName); return retval; default: n = vars.length >> 1; t2 = this.temp(); this.product(vars.slice(n), t2); if (n > 1) { t1 = this.temp(); this.product(vars.slice(0, n), t1); } else { t1 = vars[0]; } this.times(t1, t2, resultName); return retval; } }; // Weighted sum of fdvars where the weights are constants. Space.prototype.wsum = function (kweights, vars, resultName) { var temps = []; var i, len, t; for (i = 0, len = vars.length; i < len; ++i) { t = this.temp(); this.scale(kweights[i], vars[i], t); temps.push(t); } return this.sum(temps, resultName); } // Closes all the gaps between the intervals according to // the given gap value. All gaps less than this gap are closed. function dom_close_gaps(d, gap) { if (d.length === 0) { return d; } var result = []; var i, len, prev, next; result.push(prev = [d[0][0], d[0][1]]); for (i = 1, len = d.length; i < len; ++i) { next = [d[i][0], d[i][1]]; if (next[0] - prev[1] < gap) { prev[1] = next[1]; } else { result.push(prev = next); } } return result; } function dom_smallest_interval_width(d) { return Math.min.apply(null, d.map(function (i) { return i[1] - i[0] + 1; })); } function dom_largest_interval_width(d) { return Math.max.apply(null, d.map(function (i) { return i[1] - i[0] + 1; })); } // The idea behind this function - which is primarily // intended for dom_plus and dom_minus and porbably applies // to nothing else - is that when adding two intervals, // both intervals expand by the other's amount. This means // that when given two segmented domains, each continuous // subdomain expands by at least the interval of the smallest // subdomain of the other segmented domain. When such an expansion // occurs, any gaps between subdomains that are <= the smallest // subdomain's interval width get filled up, which we can exploit // to reduce the number of segments in a domain. Reducing the // number of domain segments helps reduce the N^2 complexity of // the subsequent domain consistent interval addition method. function dom_close_gaps2(d1, d2) { var d, change; do { change = 0; d = dom_close_gaps(d1, dom_smallest_interval_width(d2)); change += d1.length - d.length; d1 = d; d = dom_close_gaps(d2, dom_smallest_interval_width(d1)); change += d2.length - d.length; d2 = d; } while (change > 0); return [d1, d2]; } function dom_plus(d1, d2) { var d, i, j, len1, len2, i1, i2, p = []; var change; // Simplify the domains by closing gaps since when we add // the domains, the gaps will close according to the // smallest interval width in the other domain. d = dom_close_gaps2(d1, d2); d1 = d[0]; d2 = d[1]; for (i = 0, len1 = d1.length, len2 = d2.length; i < len1; ++i) { i1 = d1[i]; for (j = 0; j < len2; ++j) { i2 = d2[j]; p.push([Math.min(FD_SUP, i1[0] + i2[0]), Math.min(FD_SUP, i1[1] + i2[1])]); } } return simplify_domain(p); } // Note that this one isn't domain consistent. function dom_times(d1, d2) { var i, j, len1, len2, i1, i2, p = []; for (i = 0, len1 = d1.length, len2 = d2.length; i < len1; ++i) { i1 = d1[i]; for (j = 0; j < len2; ++j) { i2 = d2[j]; p.push([Math.min(FD_SUP, i1[0] * i2[0]), Math.min(FD_SUP, i1[1] * i2[1])]); } } return simplify_domain(p); } function dom_minus(d1, d2) { var d, i, j, len1, len2, i1, i2, p = [], lo, hi; // Simplify the domains by closing gaps since when we add // the domains, the gaps will close according to the // smallest interval width in the other domain. d = dom_close_gaps2(d1, d2); d1 = d[0]; d2 = d[1]; for (i = 0, len1 = d1.length, len2 = d2.length; i < len1; ++i) { i1 = d1[i]; for (j = 0; j < len2; ++j) { i2 = d2[j]; lo = i1[0] - i2[1]; hi = i1[1] - i2[0]; if (hi >= 0) { p.push([(lo < 0 ? 0 : lo), hi]); } } } return simplify_domain(p); } // Note that this isn't domain consistent. function dom_divby(d1, d2) { var i, j, len1, len2, i1, i2, p = [], lo, hi; for (i = 0, len1 = d1.length, len2 = d2.length; i < len1; ++i) { i1 = d1[i]; for (j = 0; j < len2; ++j) { i2 = d2[j]; if (i2[1] > 0) { lo = i1[0] / i2[1]; hi = (i2[0] > 0 ? (i1[1] / i2[0]) : FD_SUP); if (hi >= 0) { p.push([(lo < 0 ? 0 : lo), hi]); } } } } return simplify_domain(p); } ///////////////////////////////////////////////////////////////// // Modular distribution strategies. var Distribute = {}; // The generic distributor. Distribute.generic = function (S, varnames, spec) { function select_by_order(S, vars, orderingfn) { if (vars.length > 0) { if (vars.length === 1) { return vars[0]; } var i, N, first = 0; for (i = 1, N = vars.length; i < N; ++i) { if (!orderingfn(S, vars[first], vars[i])) { first = i; } } return vars[first]; } else { return null; } } var branch_sequence = {}; var filterfn = (typeof(spec.filter) === 'string') ? Distribute.generic.filters[spec.filter] : spec.filter; var orderingfn = (typeof(spec.ordering) === 'string') ? Distribute.generic.orderings[spec.ordering] : spec.ordering; var valuefn = (typeof(spec.value) === 'string') ? Distribute.generic.values[spec.value] : spec.value; if (!filterfn || !orderingfn || !valuefn) { throw 'FD.distribute.generic: Invalid spec'; } // The role of the branch() function is to produce a function (S, n) // that will return S with the choice point n committed. The function's // 'numChoices' property will tell you how many choices are available. branch_sequence.branch = function (S) { var vars, v, doms, Sc; vars = filterfn(S, varnames); if (vars.length > 0) { v = select_by_order(S, vars, orderingfn); return v ? valuefn(v) : null;