forked from meteor/meteor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlogic.js
1585 lines (1424 loc) · 51.1 KB
/
logic.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
Logic._MiniSat = MiniSat; // Expose for testing and poking around
// import the private testers from types.js
var isInteger = Logic._isInteger;
var isFunction = Logic._isFunction;
var isString = Logic._isString;
var isArrayWhere = Logic._isArrayWhere;
var isFormulaOrTerm = Logic._isFormulaOrTerm;
var isFormulaOrTermOrBits = Logic._isFormulaOrTermOrBits;
Logic._assert = function (value, tester, description) {
if (! tester(value)) {
var displayValue = (typeof value === 'string' ? JSON.stringify(value) :
value);
throw new Error(displayValue + " is not " +
(tester.description || description));
}
};
// Call this as `if (assert) assertNumArgs(...)`
var assertNumArgs = function (actual, expected, funcName) {
if (actual !== expected) {
throw new Error("Expected " + expected + " args in " + funcName +
", got " + actual);
}
};
// Call `assert` as: `if (assert) assert(...)`.
// This local variable temporarily set to `null` inside
// `Logic.disablingAssertions`.
var assert = Logic._assert;
// Like `if (assert) assert(...)` but usable from other files in the package.
Logic._assertIfEnabled = function (value, tester, description) {
if (assert) assert(value, tester, description);
};
// Disabling runtime assertions speeds up clause generation. Assertions
// are disabled when the local variable `assert` is null instead of
// `Logic._assert`.
Logic.disablingAssertions = function (f) {
var oldAssert = assert;
try {
assert = null;
return f();
} finally {
assert = oldAssert;
}
};
// Back-compat.
Logic._disablingTypeChecks = Logic.disablingAssertions;
////////////////////
// Takes a Formula or Term, returns a Formula or Term.
// Unlike other operators, if you give it a Term,
// you will get a Term back (of the same type, NameTerm
// or NumTerm).
Logic.not = function (operand) {
if (assert) assert(operand, isFormulaOrTerm);
if (operand instanceof Logic.Formula) {
return new Logic.NotFormula(operand);
} else {
// Term
if (typeof operand === 'number') {
return -operand;
} else if (operand.charAt(0) === '-') {
return operand.slice(1);
} else {
return '-' + operand;
}
}
};
Logic.NAME_FALSE = "$F";
Logic.NAME_TRUE = "$T";
Logic.NUM_FALSE = 1;
Logic.NUM_TRUE = 2;
Logic.TRUE = Logic.NAME_TRUE;
Logic.FALSE = Logic.NAME_FALSE;
// Abstract base class. Subclasses are created using _defineFormula.
Logic.Formula = function () {};
Logic._defineFormula = function (constructor, typeName, methods) {
if (assert) assert(constructor, isFunction);
if (assert) assert(typeName, isString);
constructor.prototype = new Logic.Formula();
constructor.prototype.type = typeName;
if (methods) {
_.extend(constructor.prototype, methods);
}
};
// Returns a list of Clauses that together require the Formula to be
// true, or false (depending on isTrue; both cases must be
// implemented). A single Clause may also be returned. The
// implementation should call the termifier to convert terms and
// formulas to NumTerms specific to a solver instance, and use them to
// construct a Logic.Clause.
Logic.Formula.prototype.generateClauses = function (isTrue, termifier) {
throw new Error("Cannot generate this Formula; it must be expanded");
};
// All Formulas have a globally-unique id so that Solvers can track them.
// It is assigned lazily.
Logic.Formula._nextGuid = 1;
Logic.Formula.prototype._guid = null;
Logic.Formula.prototype.guid = function () {
if (this._guid === null) {
this._guid = Logic.Formula._nextGuid++;
}
return this._guid;
};
// A "clause" is a disjunction of terms, e.g. "A or B or (not C)",
// which we write "A v B v -C". Logic.Clause is mainly an internal
// Solver data structure, which is the final result of formula
// generation and mapping variable names to numbers, before passing
// the clauses to MiniSat.
Logic.Clause = function (/*formulaOrArray, ...*/) {
var terms = _.flatten(arguments);
if (assert) assert(terms, isArrayWhere(Logic.isNumTerm));
this.terms = terms; // immutable [NumTerm]
};
// Returns a new Clause with the extra term or terms appended
Logic.Clause.prototype.append = function (/*formulaOrArray, ...*/) {
return new Logic.Clause(this.terms.concat(_.flatten(arguments)));
};
var FormulaInfo = function () {
// We generate a variable when a Formula is used.
this.varName = null; // string name of variable
this.varNum = null; // number of variable (always positive)
// A formula variable that is used only in the positive or only
// in the negative doesn't need the full set of clauses that
// establish a bidirectional implication between the formula and the
// variable. For example, in the formula `Logic.or("A", "B")`, with the
// formula variable `$or1`, the full set of clauses is `A v B v
// -$or1; -A v $or1; -B v $or1`. If both `$or1` and `-$or1` appear
// elsewhere in the set of clauses, then all three of these clauses
// are required. However, somewhat surprisingly, if only `$or1` appears,
// then only the first is necessary. If only `-$or1` appears, then only
// the second and third are necessary.
//
// Suppose the formula A v B is represented by the variable $or1,
// and $or1 is only used positively. It's important that A v B being
// false forces $or1 to be false, so that when $or1 is used it has
// the appropriate effect. For example, if we have the clause $or1 v
// C, then A v B being false should force $or1 to be false, which
// forces C to be true. So we generate the clause A v B v
// -$or1. (The implications of this clause are: If A v B is false,
// $or1 must be false. If $or1 is true, A v B must be true.)
//
// However, in the case where A v B is true, we don't actually
// need to insist that the solver set $or1 to true, as long as we
// are ok with relaxing the relationship between A v B and $or1
// and getting a "wrong" value for $or1 in the solution. Suppose
// the solver goes to work and at some point determines A v B to
// be true. It could set $or1 to true, satisfying all the clauses
// where it appears, or it could set $or1 to false, which only
// constrains the solution space and doesn't open up any new
// solutions for other variables. If the solver happens to find a
// solution where A v B is true and $or1 is false, we know there
// is a similar solution that makes all the same assignments
// except it assigns $or1 to true.
//
// If a formula is used only negatively, a similar argument applies
// but with signs flipped, and if it is used both positively and
// negatively, both kinds of clauses must be generated.
//
// See the mention of "polarity" in the MiniSat+ paper
// (http://minisat.se/downloads/MiniSat+.pdf).
//
// These flags are set when generation has been done for the positive
// case or the negative case, so that we only generate each one once.
this.occursPositively = false;
this.occursNegatively = false;
// If a Formula has been directly required or forbidden, we can
// replace it by TRUE or FALSE in subsequent clauses. Track the
// information here.
this.isRequired = false;
this.isForbidden = false;
};
// The "termifier" interface is provided to a Formula's
// generateClauses method, which must use it to generate Clause
// objects.
//
// The reason for this approach is that it gives the Formula control
// over the clauses returned, but it gives the Solver control over
// Formula generation.
Logic.Termifier = function (solver) {
this.solver = solver;
};
// The main entry point, the `clause` method takes a list of
// FormulaOrTerms and converts it to a Clause containing NumTerms, *by
// replacing Formulas with their variables*, creating the variable if
// necessary. For example, if an OrFormula is represented by the
// variable `$or1`, it will be replaced by the numeric version of
// `$or1` to make the Clause. When the Clause is actually used, it
// will trigger generation of the clauses that relate `$or1` to the
// operands of the OrFormula.
Logic.Termifier.prototype.clause = function (/*args*/) {
var self = this;
var formulas = _.flatten(arguments);
if (assert) assert(formulas, isArrayWhere(isFormulaOrTerm));
return new Logic.Clause(_.map(formulas, function (f) {
return self.term(f);
}));
};
// The `term` method performs the mapping from FormulaOrTerm to
// NumTerm. It's called by `clause` and could be called directly
// from a Formula's generateClauses if it was useful for some
// reason.
Logic.Termifier.prototype.term = function (formula) {
return this.solver._formulaToTerm(formula);
};
// The `generate` method generates clauses for a Formula (or
// Term). It should be used carefully, because it works quite
// differently from passing a Formula into `clause`, which is the
// normal way for one Formula to refer to another. When you use a
// Formula in `clause`, it is replaced by the Formula's variable,
// and the Solver handles generating the Formula's clauses once.
// When you use `generate`, this system is bypassed, and the
// Formula's generateClauses method is called pretty much directly,
// returning the array of Clauses.
Logic.Termifier.prototype.generate = function (isTrue, formula) {
return this.solver._generateFormula(isTrue, formula, this);
};
Logic.Solver = function () {
var self = this;
self.clauses = []; // mutable [Clause]
self._num2name = [null]; // no 0th var
self._name2num = {}; // (' '+vname) -> vnum
// true and false
var F = self.getVarNum(Logic.NAME_FALSE, false, true); // 1
var T = self.getVarNum(Logic.NAME_TRUE, false, true); // 2
if (F !== Logic.NUM_FALSE || T !== Logic.NUM_TRUE) {
throw new Error("Assertion failure: $T and $F have wrong numeric value");
}
self._F_used = false;
self._T_used = false;
// It's important that these clauses are elements 0 and 1
// of the clauses array, so that they can optionally be stripped
// off. For example, _clauseData takes advantage of this fact.
self.clauses.push(new Logic.Clause(-Logic.NUM_FALSE));
self.clauses.push(new Logic.Clause(Logic.NUM_TRUE));
self._formulaInfo = {}; // Formula guid -> FormulaInfo
// For generating formula variables like "$or1", "$or2", "$and1", "$and2"
self._nextFormulaNumByType = {}; // Formula type -> next var id
// Map of Formulas whose info has `false` for either
// `occursPositively` or `occursNegatively`
self._ungeneratedFormulas = {}; // varNum -> Formula
self._numClausesAddedToMiniSat = 0;
self._unsat = false; // once true, no solution henceforth
self._minisat = new MiniSat(); // this takes some time
self._termifier = new Logic.Termifier(self);
};
// Get a var number for vname, assigning it a number if it is new.
// Setting "noCreate" to true causes the function to return 0 instead of
// creating a new variable.
// Setting "_createInternals" to true grants the ability to create $ variables.
Logic.Solver.prototype.getVarNum = function (vname, noCreate, _createInternals) {
var key = ' '+vname;
if (_.has(this._name2num, key)) {
return this._name2num[key];
} else if (noCreate) {
return 0;
} else {
if (vname.charAt(0) === "$" && ! _createInternals) {
throw new Error("Only generated variable names can start with $");
}
var vnum = this._num2name.length;
this._name2num[key] = vnum;
this._num2name.push(vname);
return vnum;
}
};
Logic.Solver.prototype.getVarName = function (vnum) {
if (assert) assert(vnum, isInteger);
var num2name = this._num2name;
if (vnum < 1 || vnum >= num2name.length) {
throw new Error("Bad variable num: " + vnum);
} else {
return num2name[vnum];
}
};
// Converts a Term to a NumTerm (if it isn't already). This is done
// when a Formula creates Clauses for a Solver, since Clauses require
// NumTerms. NumTerms stay the same, while a NameTerm like "-foo"
// might become (say) the number -3. If a NameTerm names a variable
// that doesn't exist, it is automatically created, unless noCreate
// is passed, in which case 0 is returned instead.
Logic.Solver.prototype.toNumTerm = function (t, noCreate) {
var self = this;
if (assert) assert(t, Logic.isTerm);
if (typeof t === 'number') {
return t;
} else { // string
var not = false;
while (t.charAt(0) === '-') {
t = t.slice(1);
not = ! not;
}
var n = self.getVarNum(t, noCreate);
if (! n) {
return 0; // must be the noCreate case
} else {
return (not ? -n : n);
}
}
};
// Converts a Term to a NameTerm (if it isn't already).
Logic.Solver.prototype.toNameTerm = function (t) {
var self = this;
if (assert) assert(t, Logic.isTerm);
if (typeof t === 'string') {
// canonicalize, removing leading "--"
while (t.slice(0, 2) === '--') {
t = t.slice(2);
}
return t;
} else { // number
var not = false;
if (t < 0) {
not = true;
t = -t;
}
t = self.getVarName(t);
if (not) {
t = '-' + t;
}
return t;
}
};
Logic.Solver.prototype._addClause = function (cls, _extraTerms,
_useTermOverride) {
var self = this;
if (assert) assert(cls, Logic.isClause);
var extraTerms = null;
if (_extraTerms) {
extraTerms = _extraTerms;
if (assert) assert(extraTerms, isArrayWhere(Logic.isNumTerm));
}
var usedF = false;
var usedT = false;
var numRealTerms = cls.terms.length;
if (extraTerms) {
// extraTerms are added to the clause as is. Formula variables in
// extraTerms do not cause Formula clause generation, which is
// necessary to implement Formula clause generation.
cls = cls.append(extraTerms);
}
for (var i = 0; i < cls.terms.length; i++) {
var t = cls.terms[i];
var v = (t < 0) ? -t : t;
if (v === Logic.NUM_FALSE) {
usedF = true;
} else if (v === Logic.NUM_TRUE) {
usedT = true;
} else if (v < 1 || v >= self._num2name.length) {
throw new Error("Bad variable number: " + v);
} else if (i < numRealTerms) {
if (_useTermOverride) {
_useTermOverride(t);
} else {
self._useFormulaTerm(t);
}
}
}
this._F_used = (this._F_used || usedF);
this._T_used = (this._T_used || usedT);
this.clauses.push(cls);
};
// When we actually use a Formula variable, generate clauses for it,
// based on whether the usage is positive or negative. For example,
// if the Formula `Logic.or("X", "Y")` is represented by `$or1`, which
// is variable number 5, then when you actually use 5 or -5 in a clause,
// the clauses "X v Y v -5" (when you use 5) or "-X v 5; -Y v 5"
// (when you use -5) will be generated. The clause "X v Y v -5"
// is equivalent to "5 => X v Y" (or -(X v Y) => -5), while the clauses
// "-X v 5; -Y v 5" are equivalent to "-5 => -X; -5 => -Y" (or
// "X => 5; Y => 5").
Logic.Solver.prototype._useFormulaTerm = function (t, _addClausesOverride) {
var self = this;
if (assert) assert(t, Logic.isNumTerm);
var v = (t < 0) ? -t : t;
if (! _.has(self._ungeneratedFormulas, v)) {
return;
}
// using a Formula's var; maybe have to generate clauses
// for the Formula
var formula = self._ungeneratedFormulas[v];
var info = self._getFormulaInfo(formula);
var positive = t > 0;
// To avoid overflowing the JS stack, defer calls to addClause.
// The way we get overflows is when Formulas are deeply nested
// (which happens naturally when you call Logic.sum or
// Logic.weightedSum on a long list of terms), which causes
// addClause to call useFormulaTerm to call addClause, and so
// on. Approach: The outermost useFormulaTerm keeps a list
// of clauses to add, and then adds them in a loop using a
// special argument to addClause that passes a special argument
// to useFormulaTerm that causes those clauses to go into the
// list too. Code outside of `_useFormulaTerm` and `_addClause(s)`
// does not have to pass these special arguments to call them.
var deferredAddClauses = null;
var addClauses;
if (! _addClausesOverride) {
deferredAddClauses = [];
addClauses = function (clauses, extraTerms) {
deferredAddClauses.push({clauses: clauses,
extraTerms: extraTerms});
};
} else {
addClauses = _addClausesOverride;
}
if (positive && ! info.occursPositively) {
// generate clauses for the formula.
// Eg, if we use variable `X` which represents the formula
// `A v B`, add the clause `A v B v -X`.
// By using the extraTerms argument to addClauses, we avoid
// treating this as a negative occurrence of X.
info.occursPositively = true;
var clauses = self._generateFormula(true, formula);
addClauses(clauses, [-v]);
} else if ((! positive) && ! info.occursNegatively) {
// Eg, if we have the term `-X` where `X` represents the
// formula `A v B`, add the clauses `-A v X` and `-B v X`.
// By using the extraTerms argument to addClauses, we avoid
// treating this as a positive occurrence of X.
info.occursNegatively = true;
var clauses = self._generateFormula(false, formula);
addClauses(clauses, [v]);
}
if (info.occursPositively && info.occursNegatively) {
delete self._ungeneratedFormulas[v];
}
if (! (deferredAddClauses && deferredAddClauses.length)) {
return;
}
var useTerm = function (t) {
self._useFormulaTerm(t, addClauses);
};
// This is the loop that turns recursion into iteration.
// When addClauses calls useTerm, which calls useFormulaTerm,
// the nested useFormulaTerm will add any clauses to our
// own deferredAddClauses list.
while (deferredAddClauses.length) {
var next = deferredAddClauses.pop();
self._addClauses(next.clauses, next.extraTerms, useTerm);
}
};
Logic.Solver.prototype._addClauses = function (array, _extraTerms,
_useTermOverride) {
if (assert) assert(array, isArrayWhere(Logic.isClause));
var self = this;
_.each(array, function (cls) {
self._addClause(cls, _extraTerms, _useTermOverride);
});
};
Logic.Solver.prototype.require = function (/*formulaOrArray, ...*/) {
this._requireForbidImpl(true, _.flatten(arguments));
};
Logic.Solver.prototype.forbid = function (/*formulaOrArray, ...*/) {
this._requireForbidImpl(false, _.flatten(arguments));
};
Logic.Solver.prototype._requireForbidImpl = function (isRequire, formulas) {
var self = this;
if (assert) assert(formulas, isArrayWhere(isFormulaOrTerm));
_.each(formulas, function (f) {
if (f instanceof Logic.NotFormula) {
self._requireForbidImpl(!isRequire, [f.operand]);
} else if (f instanceof Logic.Formula) {
var info = self._getFormulaInfo(f);
if (info.varNum !== null) {
var sign = isRequire ? 1 : -1;
self._addClause(new Logic.Clause(sign*info.varNum));
} else {
self._addClauses(self._generateFormula(isRequire, f));
}
if (isRequire) {
info.isRequired = true;
} else {
info.isForbidden = true;
}
} else {
self._addClauses(self._generateFormula(isRequire, f));
}
});
};
Logic.Solver.prototype._generateFormula = function (isTrue, formula, _termifier) {
var self = this;
if (assert) assert(formula, isFormulaOrTerm);
if (formula instanceof Logic.NotFormula) {
return self._generateFormula(!isTrue, formula.operand);
} else if (formula instanceof Logic.Formula) {
var info = self._getFormulaInfo(formula);
if ((isTrue && info.isRequired) ||
(!isTrue && info.isForbidden)) {
return [];
} else if ((isTrue && info.isForbidden) ||
(!isTrue && info.isRequired)) {
return [new Logic.Clause()]; // never satisfied clause
} else {
var ret = formula.generateClauses(isTrue,
_termifier || self._termifier);
return _.isArray(ret) ? ret : [ret];
}
} else { // Term
var t = self.toNumTerm(formula);
var sign = isTrue ? 1 : -1;
if (t === sign*Logic.NUM_TRUE || t === -sign*Logic.NUM_FALSE) {
return [];
} else if (t === sign*Logic.NUM_FALSE || t === -sign*Logic.NUM_TRUE) {
return [new Logic.Clause()]; // never satisfied clause
} else {
return [new Logic.Clause(sign*t)];
}
}
};
// Get clause data as an array of arrays of integers,
// for testing and debugging purposes.
Logic.Solver.prototype._clauseData = function () {
var clauses = _.pluck(this.clauses, 'terms');
if (! this._T_used) {
clauses.splice(1, 1);
}
if (! this._F_used) {
clauses.splice(0, 1);
}
return clauses;
};
// Get clause data as an array of human-readable strings,
// for testing and debugging purposes.
// A clause might look like "A v -B" (where "v" represents
// and OR operator).
Logic.Solver.prototype._clauseStrings = function () {
var self = this;
var clauseData = self._clauseData();
return _.map(clauseData, function (clause) {
return _.map(clause, function (nterm) {
var str = self.toNameTerm(nterm);
if (/\s/.test(str)) {
// write name in quotes for readability. we don't bother
// making this string machine-parsable in the general case.
var sign = '';
if (str.charAt(0) === '-') {
// temporarily remove '-'
sign = '-';
str = str.slice(1);
}
str = sign + '"' + str + '"';
}
return str;
}).join(' v ');
});
};
Logic.Solver.prototype._getFormulaInfo = function (formula, _noCreate) {
var self = this;
var guid = formula.guid();
if (! self._formulaInfo[guid]) {
if (_noCreate) {
return null;
}
self._formulaInfo[guid] = new FormulaInfo();
}
return self._formulaInfo[guid];
};
// Takes a Formula or an array of Formulas, returns a NumTerm or
// array of NumTerms.
Logic.Solver.prototype._formulaToTerm = function (formula) {
var self = this;
if (_.isArray(formula)) {
if (assert) assert(formula, isArrayWhere(isFormulaOrTerm));
return _.map(formula, _.bind(self._formulaToTerm, self));
} else {
if (assert) assert(formula, isFormulaOrTerm);
}
if (formula instanceof Logic.NotFormula) {
// shortcut that avoids creating a variable called
// something like "$not1" when you use Logic.not(formula).
return Logic.not(self._formulaToTerm(formula.operand));
} else if (formula instanceof Logic.Formula) {
var info = this._getFormulaInfo(formula);
if (info.isRequired) {
return Logic.NUM_TRUE;
} else if (info.isForbidden) {
return Logic.NUM_FALSE;
} else if (info.varNum === null) {
// generate a Solver-local formula variable like "$or1"
var type = formula.type;
if (! this._nextFormulaNumByType[type]) {
this._nextFormulaNumByType[type] = 1;
}
var numForVarName = this._nextFormulaNumByType[type]++;
info.varName = "$" + formula.type + numForVarName;
info.varNum = this.getVarNum(info.varName, false, true);
this._ungeneratedFormulas[info.varNum] = formula;
}
return info.varNum;
} else {
// formula is a Term
return self.toNumTerm(formula);
}
};
Logic.or = function (/*formulaOrArray, ...*/) {
var args = _.flatten(arguments);
if (args.length === 0) {
return Logic.FALSE;
} else if (args.length === 1) {
if (assert) assert(args[0], isFormulaOrTerm);
return args[0];
} else {
return new Logic.OrFormula(args);
}
};
Logic.OrFormula = function (operands) {
if (assert) assert(operands, isArrayWhere(isFormulaOrTerm));
this.operands = operands;
};
Logic._defineFormula(Logic.OrFormula, 'or', {
generateClauses: function (isTrue, t) {
if (isTrue) {
// eg A v B v C
return t.clause(this.operands);
} else {
// eg -A; -B; -C
var result = [];
_.each(this.operands, function (o) {
result.push.apply(result, t.generate(false, o));
});
return result;
}
}
});
Logic.NotFormula = function (operand) {
if (assert) assert(operand, isFormulaOrTerm);
this.operand = operand;
};
// No generation or simplification for 'not'; it is
// simplified away by the solver itself.
Logic._defineFormula(Logic.NotFormula, 'not');
Logic.and = function (/*formulaOrArray, ...*/) {
var args = _.flatten(arguments);
if (args.length === 0) {
return Logic.TRUE;
} else if (args.length === 1) {
if (assert) assert(args[0], isFormulaOrTerm);
return args[0];
} else {
return new Logic.AndFormula(args);
}
};
Logic.AndFormula = function (operands) {
if (assert) assert(operands, isArrayWhere(isFormulaOrTerm));
this.operands = operands;
};
Logic._defineFormula(Logic.AndFormula, 'and', {
generateClauses: function (isTrue, t) {
if (isTrue) {
// eg A; B; C
var result = [];
_.each(this.operands, function (o) {
result.push.apply(result, t.generate(true, o));
});
return result;
} else {
// eg -A v -B v -C
return t.clause(_.map(this.operands, Logic.not));
}
}
});
// Group `array` into groups of N, where the last group
// may be shorter than N. group([a,b,c,d,e], 3) => [[a,b,c],[d,e]]
var group = function (array, N) {
var ret = [];
for (var i = 0; i < array.length; i += N) {
ret.push(array.slice(i, i+N));
}
return ret;
};
Logic.xor = function (/*formulaOrArray, ...*/) {
var args = _.flatten(arguments);
if (args.length === 0) {
return Logic.FALSE;
} else if (args.length === 1) {
if (assert) assert(args[0], isFormulaOrTerm);
return args[0];
} else {
return new Logic.XorFormula(args);
}
};
Logic.XorFormula = function (operands) {
if (assert) assert(operands, isArrayWhere(isFormulaOrTerm));
this.operands = operands;
};
Logic._defineFormula(Logic.XorFormula, 'xor', {
generateClauses: function (isTrue, t) {
var args = this.operands;
var not = Logic.not;
if (args.length > 3) {
return t.generate(
isTrue,
Logic.xor(
_.map(group(this.operands, 3), function (group) {
return Logic.xor(group);
})));
} else if (isTrue) { // args.length <= 3
if (args.length === 0) {
return t.clause(); // always fail
} else if (args.length === 1) {
return t.clause(args[0]);
} else if (args.length === 2) {
var A = args[0], B = args[1];
return [t.clause(A, B), // A v B
t.clause(not(A), not(B))]; // -A v -B
} else if (args.length === 3) {
var A = args[0], B = args[1], C = args[2];
return [t.clause(A, B, C), // A v B v C
t.clause(A, not(B), not(C)), // A v -B v -C
t.clause(not(A), B, not(C)), // -A v B v -C
t.clause(not(A), not(B), C)]; // -A v -B v C
}
} else { // !isTrue, args.length <= 3
if (args.length === 0) {
return []; // always succeed
} else if (args.length === 1) {
return t.clause(not(args[0]));
} else if (args.length === 2) {
var A = args[0], B = args[1];
return [t.clause(A, not(B)), // A v -B
t.clause(not(A), B)]; // -A v B
} else if (args.length === 3) {
var A = args[0], B = args[1], C = args[2];
return [t.clause(not(A), not(B), not(C)), // -A v -B v -C
t.clause(not(A), B, C), // -A v B v C
t.clause(A, not(B), C), // A v -B v C
t.clause(A, B, not(C))]; // A v B v -C
}
}
}
});
Logic.atMostOne = function (/*formulaOrArray, ...*/) {
var args = _.flatten(arguments);
if (args.length <= 1) {
return Logic.TRUE;
} else {
return new Logic.AtMostOneFormula(args);
}
};
Logic.AtMostOneFormula = function (operands) {
if (assert) assert(operands, isArrayWhere(isFormulaOrTerm));
this.operands = operands;
};
Logic._defineFormula(Logic.AtMostOneFormula, 'atMostOne', {
generateClauses: function (isTrue, t) {
var args = this.operands;
var not = Logic.not;
if (args.length <= 1) {
return []; // always succeed
} else if (args.length === 2) {
return t.generate(isTrue, Logic.not(Logic.and(args)));
} else if (isTrue && args.length === 3) {
// Pick any two args; at least one is false (they aren't
// both true). This strategy would also work for
// N>3, and could provide a speed-up by having more clauses
// (N^2) but fewer propagation steps. No speed-up was
// observed on the Sudoku test from using this strategy
// up to N=10.
var clauses = [];
for (var i = 0; i < args.length; i++) {
for (var j = i+1; j < args.length; j++) {
clauses.push(t.clause(not(args[i]), not(args[j])));
}
}
return clauses;
} else if ((! isTrue) && args.length === 3) {
var A = args[0], B = args[1], C = args[2];
// Pick any two args; at least one is true (they aren't
// both false). This only works for N=3.
return [t.clause(A, B), t.clause(A, C), t.clause(B, C)];
} else {
// See the "commander variables" technique from:
// http://www.cs.cmu.edu/~wklieber/papers/2007_efficient-cnf-encoding-for-selecting-1.pdf
// But in short: At most one group has at least one "true",
// and each group has at most one "true". Formula generation
// automatically generates the right implications.
var groups = group(args, 3);
var ors = _.map(groups, function (g) { return Logic.or(g); });
if (groups[groups.length - 1].length < 2) {
// Remove final group of length 1 so we don't generate
// no-op clauses of one sort or another
groups.pop();
}
var atMostOnes = _.map(groups, function (g) {
return Logic.atMostOne(g);
});
return t.generate(isTrue, Logic.and(Logic.atMostOne(ors), atMostOnes));
}
}
});
Logic.implies = function (A, B) {
if (assert) assertNumArgs(arguments.length, 2, "Logic.implies");
return new Logic.ImpliesFormula(A, B);
};
Logic.ImpliesFormula = function (A, B) {
if (assert) assert(A, isFormulaOrTerm);
if (assert) assert(B, isFormulaOrTerm);
if (assert) assertNumArgs(arguments.length, 2, "Logic.implies");
this.A = A;
this.B = B;
};
Logic._defineFormula(Logic.ImpliesFormula, 'implies', {
generateClauses: function (isTrue, t) {
return t.generate(isTrue, Logic.or(Logic.not(this.A), this.B));
}
});
Logic.equiv = function (A, B) {
if (assert) assertNumArgs(arguments.length, 2, "Logic.equiv");
return new Logic.EquivFormula(A, B);
};
Logic.EquivFormula = function (A, B) {
if (assert) assert(A, isFormulaOrTerm);
if (assert) assert(B, isFormulaOrTerm);
if (assert) assertNumArgs(arguments.length, 2, "Logic.equiv");
this.A = A;
this.B = B;
};
Logic._defineFormula(Logic.EquivFormula, 'equiv', {
generateClauses: function (isTrue, t) {
return t.generate(!isTrue, Logic.xor(this.A, this.B));
}
});
Logic.exactlyOne = function (/*formulaOrArray, ...*/) {
var args = _.flatten(arguments);
if (args.length === 0) {
return Logic.FALSE;
} else if (args.length === 1) {
if (assert) assert(args[0], isFormulaOrTerm);
return args[0];
} else {
return new Logic.ExactlyOneFormula(args);
}
};
Logic.ExactlyOneFormula = function (operands) {
if (assert) assert(operands, isArrayWhere(isFormulaOrTerm));
this.operands = operands;
};
Logic._defineFormula(Logic.ExactlyOneFormula, 'exactlyOne', {
generateClauses: function (isTrue, t) {
var args = this.operands;
if (args.length < 3) {
return t.generate(isTrue, Logic.xor(args));
} else {
return t.generate(isTrue, Logic.and(Logic.atMostOne(args),
Logic.or(args)));
}
}
});
// List of 0 or more formulas or terms, which together represent
// a non-negative integer. Least significant bit is first. That is,
// the kth array element has a place value of 2^k.
Logic.Bits = function (formulaArray) {
if (assert) assert(formulaArray, isArrayWhere(isFormulaOrTerm));
this.bits = formulaArray; // public, immutable
};
Logic.constantBits = function (wholeNumber) {
if (assert) assert(wholeNumber, Logic.isWholeNumber);
var result = [];
while (wholeNumber) {
result.push((wholeNumber & 1) ? Logic.TRUE : Logic.FALSE);
wholeNumber >>>= 1;
}
return new Logic.Bits(result);
};
Logic.variableBits = function (baseName, nbits) {
if (assert) assert(nbits, Logic.isWholeNumber);
var result = [];
for (var i = 0; i < nbits; i++) {
result.push(baseName + '$' + i);
}
return new Logic.Bits(result);
};
// bits1 <= bits2
Logic.lessThanOrEqual = function (bits1, bits2) {
return new Logic.LessThanOrEqualFormula(bits1, bits2);
};
Logic.LessThanOrEqualFormula = function (bits1, bits2) {
if (assert) assert(bits1, Logic.isBits);
if (assert) assert(bits2, Logic.isBits);
if (assert) assertNumArgs(arguments.length, 2, "Bits comparison function");
this.bits1 = bits1;
this.bits2 = bits2;
};
var genLTE = function (bits1, bits2, t, notEqual) {
var ret = [];
// clone so we can mutate them in place
var A = bits1.bits.slice();
var B = bits2.bits.slice();
if (notEqual && ! bits2.bits.length) {
// can't be less than 0
return t.clause();
}
// if A is longer than B, the extra (high) bits
// must be 0.
while (A.length > B.length) {
var hi = A.pop();
ret.push(t.clause(Logic.not(hi)));
}
// now B.length >= A.length
// Let xors[i] be (A[i] xor B[i]), or just