eyeprolog 1.1.17 → 1.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/examples/clpz-global-constraints.pl +35 -0
- package/examples/output/clpz-global-constraints.pl +7 -0
- package/package.json +1 -1
- package/playground.html +2 -1
- package/src/clpz.js +335 -5
- package/src/lib/clpz.pl +21 -0
- package/src/playground-worker.js +1 -1
- package/src/standard-library.js +6 -4
- package/test/run-regression.mjs +18 -4
- package/the-art-of-eyeprolog.md +15 -11
package/README.md
CHANGED
|
@@ -91,7 +91,7 @@ noun --> [world] | [prolog].
|
|
|
91
91
|
%% goal: phrase(sentence, Words)
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
EyeProlog also adds
|
|
94
|
+
EyeProlog also adds 99 public library predicate indicators to its 129-entry ISO
|
|
95
95
|
profile. **60 are implemented entirely as ordinary Prolog clauses** in focused
|
|
96
96
|
modules under `src/lib/`; the control predicates and finite-domain
|
|
97
97
|
`library(clpz)` kernel use backtrackable host support.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
:- use_module(library(clpz)).
|
|
2
|
+
|
|
3
|
+
% Finite global constraints cover compatibility tables, lexicographic and
|
|
4
|
+
% non-overlapping schedules, value cardinalities with costs, Hamiltonian
|
|
5
|
+
% circuits, distinct-value counting, and three-way integer comparison.
|
|
6
|
+
|
|
7
|
+
%% goal: advanced_clpz(X0, X1)
|
|
8
|
+
|
|
9
|
+
advanced_clpz(table, compatible(4, Y, Distinct)) :-
|
|
10
|
+
tuples_in([[X, Y]], [[1, 2], [1, 5], [4, 0], [4, 3]]),
|
|
11
|
+
X = 4,
|
|
12
|
+
nvalue(Distinct, [X, Y]),
|
|
13
|
+
labeling([], [Y]).
|
|
14
|
+
|
|
15
|
+
advanced_clpz(schedule, starts([A, B, C])) :-
|
|
16
|
+
[A, B, C] ins 0..3,
|
|
17
|
+
lex_chain([[A, B], [B, C]]),
|
|
18
|
+
serialized([A, B, C], [1, 2, 1]),
|
|
19
|
+
A #< B,
|
|
20
|
+
B #< C,
|
|
21
|
+
labeling([ff], [A, B, C]).
|
|
22
|
+
|
|
23
|
+
advanced_clpz(cardinality, assignment(Values, Cost, Order)) :-
|
|
24
|
+
Values = [_, _, _],
|
|
25
|
+
global_cardinality(Values, [1-2, 3-1],
|
|
26
|
+
[cost(Cost, [[5, 1], [2, 4], [3, 6]])]),
|
|
27
|
+
labeling([ff], Values),
|
|
28
|
+
zcompare(Order, Cost, 10).
|
|
29
|
+
|
|
30
|
+
advanced_clpz(circuit, successors(Values)) :-
|
|
31
|
+
Values = [A, B, _, _],
|
|
32
|
+
circuit(Values),
|
|
33
|
+
A #= 2,
|
|
34
|
+
B #= 3,
|
|
35
|
+
labeling([ff], Values).
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
advanced_clpz(table, compatible(4, 0, 2)).
|
|
2
|
+
advanced_clpz(table, compatible(4, 3, 2)).
|
|
3
|
+
advanced_clpz(schedule, starts([0, 1, 3])).
|
|
4
|
+
advanced_clpz(cardinality, assignment([1, 1, 3], 13, >)).
|
|
5
|
+
advanced_clpz(cardinality, assignment([1, 3, 1], 12, >)).
|
|
6
|
+
advanced_clpz(cardinality, assignment([3, 1, 1], 6, <)).
|
|
7
|
+
advanced_clpz(circuit, successors([2, 3, 4, 1])).
|
package/package.json
CHANGED
package/playground.html
CHANGED
|
@@ -465,6 +465,7 @@
|
|
|
465
465
|
"chart-parser",
|
|
466
466
|
"clinical-trial-screening",
|
|
467
467
|
"clpz-factorial",
|
|
468
|
+
"clpz-global-constraints",
|
|
468
469
|
"clpz-n-queens",
|
|
469
470
|
"clpz-resource-allocation",
|
|
470
471
|
"collatz-1000",
|
|
@@ -961,7 +962,7 @@
|
|
|
961
962
|
runButton.disabled = true;
|
|
962
963
|
stopButton.disabled = false;
|
|
963
964
|
|
|
964
|
-
const workerUrl = new URL('./src/playground-worker.js?playground=
|
|
965
|
+
const workerUrl = new URL('./src/playground-worker.js?playground=20260811c', location.href);
|
|
965
966
|
activeWorker = new Worker(workerUrl, { type: 'module' });
|
|
966
967
|
const worker = activeWorker;
|
|
967
968
|
const request = {
|
package/src/clpz.js
CHANGED
|
@@ -19,10 +19,17 @@ export const clpzBuiltins = {
|
|
|
19
19
|
registry.add('eyeprolog__clpz_in', 2, inBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
20
20
|
registry.add('eyeprolog__clpz_ins', 2, insBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
21
21
|
registry.add('eyeprolog__clpz_all_distinct', 1, allDistinctBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
22
|
+
registry.add('eyeprolog__clpz_nvalue', 2, nvalueBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
22
23
|
registry.add('eyeprolog__clpz_sum', 3, sumBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
23
24
|
registry.add('eyeprolog__clpz_scalar_product', 4, scalarProductBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
25
|
+
registry.add('eyeprolog__clpz_tuples_in', 2, tuplesInBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
26
|
+
registry.add('eyeprolog__clpz_lex_chain', 1, lexChainBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
27
|
+
registry.add('eyeprolog__clpz_serialized', 2, serializedBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
28
|
+
registry.add('eyeprolog__clpz_global_cardinality', 3, globalCardinalityBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
29
|
+
registry.add('eyeprolog__clpz_circuit', 1, circuitBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
24
30
|
registry.add('eyeprolog__clpz_chain', 2, chainBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
25
31
|
registry.add('eyeprolog__clpz_element', 3, elementBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
32
|
+
registry.add('eyeprolog__clpz_zcompare', 3, zcompareBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
26
33
|
registry.add('eyeprolog__clpz_labeling', 2, labelingBuiltin, { eyePrologLibrary: true });
|
|
27
34
|
registry.add('eyeprolog__clpz_fd_var', 1, fdVarBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
28
35
|
registry.add('eyeprolog__clpz_fd_inf', 2, fdBoundBuiltin('inf'), { deterministic: true, eyePrologLibrary: true });
|
|
@@ -206,20 +213,30 @@ function domainForRoot(root, env) {
|
|
|
206
213
|
}
|
|
207
214
|
|
|
208
215
|
function constrainTermDomain(next, term, values) {
|
|
216
|
+
const result = narrowTermDomain(next, term, values);
|
|
217
|
+
return result.ok && clpzStateConsistent(next);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function narrowTermDomain(next, term, values) {
|
|
209
221
|
const resolved = deref(term, next);
|
|
210
222
|
if (resolved.type === NUMBER) {
|
|
211
223
|
const value = integerValue(resolved, next);
|
|
212
|
-
return values.some((candidate) => candidate === value);
|
|
224
|
+
return { ok: values.some((candidate) => candidate === value), changed: false };
|
|
213
225
|
}
|
|
214
226
|
if (resolved.type !== VAR) throw new PrologError('type_error(integer)', resolved);
|
|
215
227
|
const existing = domainForRoot(resolved.name, next);
|
|
216
228
|
const narrowed = existing == null ? values : intersectValues(existing, values);
|
|
217
|
-
if (narrowed.length === 0) return false;
|
|
229
|
+
if (narrowed.length === 0) return { ok: false, changed: false };
|
|
230
|
+
const changed = existing == null || existing.length !== narrowed.length ||
|
|
231
|
+
existing.some((value, index) => value !== narrowed[index]);
|
|
232
|
+
if (!changed) return { ok: true, changed: false };
|
|
218
233
|
const domains = new Map(storeOf(next).domains);
|
|
219
234
|
domains.set(resolved.name, narrowed);
|
|
220
235
|
updateStore(next, { domains });
|
|
221
|
-
if (narrowed.length === 1 && !unify(resolved, numberTerm(narrowed[0].toString()), next))
|
|
222
|
-
|
|
236
|
+
if (narrowed.length === 1 && !unify(resolved, numberTerm(narrowed[0].toString()), next)) {
|
|
237
|
+
return { ok: false, changed: false };
|
|
238
|
+
}
|
|
239
|
+
return { ok: true, changed: true };
|
|
223
240
|
}
|
|
224
241
|
|
|
225
242
|
function* inBuiltin({ goal, env }) {
|
|
@@ -248,11 +265,36 @@ function listArgument(term, env) {
|
|
|
248
265
|
return items;
|
|
249
266
|
}
|
|
250
267
|
|
|
268
|
+
function nestedListArgument(term, env) {
|
|
269
|
+
return listArgument(term, env).map((item) => listArgument(item, env));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function integerListArgument(term, env) {
|
|
273
|
+
return listArgument(term, env).map((item) => {
|
|
274
|
+
const value = integerValue(item, env);
|
|
275
|
+
if (value == null) throw new PrologError('instantiation_error');
|
|
276
|
+
return value;
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function integerRange(lower, upper) {
|
|
281
|
+
const values = [];
|
|
282
|
+
for (let value = BigInt(lower); value <= BigInt(upper); value++) values.push(value);
|
|
283
|
+
return values;
|
|
284
|
+
}
|
|
285
|
+
|
|
251
286
|
function* allDistinctBuiltin({ goal, env }) {
|
|
252
287
|
const next = env.clone();
|
|
253
288
|
if (addGlobal(next, { kind: 'allDistinct', terms: listArgument(goal.args[0], env) })) yield next;
|
|
254
289
|
}
|
|
255
290
|
|
|
291
|
+
function* nvalueBuiltin({ goal, env }) {
|
|
292
|
+
const terms = listArgument(goal.args[1], env);
|
|
293
|
+
const next = env.clone();
|
|
294
|
+
if (!constrainTermDomain(next, goal.args[0], integerRange(terms.length === 0 ? 0 : 1, terms.length))) return;
|
|
295
|
+
if (addGlobal(next, { kind: 'nvalue', count: goal.args[0], terms })) yield next;
|
|
296
|
+
}
|
|
297
|
+
|
|
256
298
|
function* sumBuiltin({ goal, env }) {
|
|
257
299
|
const next = env.clone();
|
|
258
300
|
const relation = relationName(goal.args[1], env);
|
|
@@ -269,6 +311,93 @@ function* scalarProductBuiltin({ goal, env }) {
|
|
|
269
311
|
if (addGlobal(next, { kind: 'scalarProduct', coefficients, terms, relation, value: goal.args[3] })) yield next;
|
|
270
312
|
}
|
|
271
313
|
|
|
314
|
+
function* tuplesInBuiltin({ goal, env }) {
|
|
315
|
+
const tuples = nestedListArgument(goal.args[0], env);
|
|
316
|
+
const relation = nestedListArgument(goal.args[1], env).map((row) => row.map((item) => {
|
|
317
|
+
const value = integerValue(item, env);
|
|
318
|
+
if (value == null) throw new PrologError('instantiation_error');
|
|
319
|
+
return value;
|
|
320
|
+
}));
|
|
321
|
+
const next = env.clone();
|
|
322
|
+
if (addGlobal(next, { kind: 'tuplesIn', tuples, relation })) yield next;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function* lexChainBuiltin({ goal, env }) {
|
|
326
|
+
const lists = nestedListArgument(goal.args[0], env);
|
|
327
|
+
if (lists.some((list) => list.length !== (lists[0]?.length ?? 0))) return;
|
|
328
|
+
const next = env.clone();
|
|
329
|
+
if (addGlobal(next, { kind: 'lexChain', lists })) yield next;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function* serializedBuiltin({ goal, env }) {
|
|
333
|
+
const starts = listArgument(goal.args[0], env);
|
|
334
|
+
const durations = integerListArgument(goal.args[1], env);
|
|
335
|
+
if (starts.length !== durations.length) throw new PrologError('domain_error(same_length)');
|
|
336
|
+
if (durations.some((duration) => duration < 0n)) throw new PrologError('domain_error(not_less_than_zero)');
|
|
337
|
+
const next = env.clone();
|
|
338
|
+
if (addGlobal(next, { kind: 'serialized', starts, durations })) yield next;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function gccPairs(term, env) {
|
|
342
|
+
const pairs = listArgument(term, env).map((pair) => {
|
|
343
|
+
const resolved = deref(pair, env);
|
|
344
|
+
if (resolved.type !== COMPOUND || resolved.name !== '-' || resolved.arity !== 2) {
|
|
345
|
+
throw new PrologError('domain_error(gcc_pair)', resolved);
|
|
346
|
+
}
|
|
347
|
+
const key = integerValue(resolved.args[0], env);
|
|
348
|
+
if (key == null) throw new PrologError('instantiation_error');
|
|
349
|
+
return { key, count: resolved.args[1] };
|
|
350
|
+
});
|
|
351
|
+
const keys = new Set(pairs.map(({ key }) => key.toString()));
|
|
352
|
+
if (keys.size !== pairs.length) throw new PrologError('domain_error(gcc_unique_key_pairs)', deref(term, env));
|
|
353
|
+
return pairs;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function gccOptions(term, env, rowCount, columnCount) {
|
|
357
|
+
let cost = null;
|
|
358
|
+
let matrix = null;
|
|
359
|
+
for (const option of listArgument(term, env)) {
|
|
360
|
+
const resolved = deref(option, env);
|
|
361
|
+
if (resolved.type === COMPOUND && resolved.name === 'consistency' && resolved.arity === 1 &&
|
|
362
|
+
deref(resolved.args[0], env).type === ATOM && deref(resolved.args[0], env).name === 'value') continue;
|
|
363
|
+
if (resolved.type === COMPOUND && resolved.name === 'cost' && resolved.arity === 2 && cost == null) {
|
|
364
|
+
cost = resolved.args[0];
|
|
365
|
+
matrix = nestedListArgument(resolved.args[1], env).map((row) => row.map((item) => {
|
|
366
|
+
const value = integerValue(item, env);
|
|
367
|
+
if (value == null) throw new PrologError('instantiation_error');
|
|
368
|
+
return value;
|
|
369
|
+
}));
|
|
370
|
+
if (matrix.length !== rowCount || matrix.some((row) => row.length !== columnCount)) {
|
|
371
|
+
throw new PrologError('domain_error(gcc_cost_matrix)', resolved.args[1]);
|
|
372
|
+
}
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
throw new PrologError('domain_error(global_cardinality_option)', resolved);
|
|
376
|
+
}
|
|
377
|
+
return { cost, matrix };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function* globalCardinalityBuiltin({ goal, env }) {
|
|
381
|
+
const terms = listArgument(goal.args[0], env);
|
|
382
|
+
const pairs = gccPairs(goal.args[1], env);
|
|
383
|
+
const options = gccOptions(goal.args[2], env, terms.length, pairs.length);
|
|
384
|
+
const keys = pairs.map(({ key }) => key).sort(compareBigInt);
|
|
385
|
+
const next = env.clone();
|
|
386
|
+
for (const term of terms) if (!constrainTermDomain(next, term, keys)) return;
|
|
387
|
+
for (const { count } of pairs) if (!constrainTermDomain(next, count, integerRange(0, terms.length))) return;
|
|
388
|
+
if (addGlobal(next, { kind: 'globalCardinality', terms, pairs, ...options })) yield next;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function* circuitBuiltin({ goal, env }) {
|
|
392
|
+
const terms = listArgument(goal.args[0], env);
|
|
393
|
+
const next = env.clone();
|
|
394
|
+
if (terms.length > 0) {
|
|
395
|
+
const values = integerRange(1, terms.length);
|
|
396
|
+
for (const term of terms) if (!constrainTermDomain(next, term, values)) return;
|
|
397
|
+
}
|
|
398
|
+
if (addGlobal(next, { kind: 'circuit', terms })) yield next;
|
|
399
|
+
}
|
|
400
|
+
|
|
272
401
|
function* chainBuiltin({ goal, env }) {
|
|
273
402
|
const terms = listArgument(goal.args[1], env);
|
|
274
403
|
const relation = relationName(goal.args[0], env);
|
|
@@ -282,6 +411,70 @@ function* elementBuiltin({ goal, env }) {
|
|
|
282
411
|
if (addGlobal(next, { kind: 'element', index: goal.args[0], terms: listArgument(goal.args[1], env), value: goal.args[2] })) yield next;
|
|
283
412
|
}
|
|
284
413
|
|
|
414
|
+
function* zcompareBuiltin({ goal, env }) {
|
|
415
|
+
const next = env.clone();
|
|
416
|
+
if (addGlobal(next, { kind: 'zcompare', order: goal.args[0], left: goal.args[1], right: goal.args[2] })) yield next;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function tupleCandidates(tuple, relation, env) {
|
|
420
|
+
return relation.filter((row) => row.length === tuple.length && row.every((candidate, index) => {
|
|
421
|
+
const resolved = deref(tuple[index], env);
|
|
422
|
+
if (resolved.type === NUMBER) return integerValue(resolved, env) === candidate;
|
|
423
|
+
if (resolved.type !== VAR) throw new PrologError('type_error(integer)', resolved);
|
|
424
|
+
const domain = domainForRoot(resolved.name, env);
|
|
425
|
+
return domain == null || domain.some((value) => value === candidate);
|
|
426
|
+
}));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function lexPairTruth(left, right, env) {
|
|
430
|
+
if (left.length !== right.length) return false;
|
|
431
|
+
for (let index = 0; index < left.length; index++) {
|
|
432
|
+
const a = expressionValue(left[index], env);
|
|
433
|
+
const b = expressionValue(right[index], env);
|
|
434
|
+
if (a == null || b == null) return true;
|
|
435
|
+
if (a < b) return true;
|
|
436
|
+
if (a > b) return false;
|
|
437
|
+
}
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function circuitTruth(terms, env) {
|
|
442
|
+
if (terms.length === 0) return true;
|
|
443
|
+
const values = terms.map((term) => expressionValue(term, env));
|
|
444
|
+
const seenValues = new Set();
|
|
445
|
+
for (let index = 0; index < values.length; index++) {
|
|
446
|
+
const value = values[index];
|
|
447
|
+
if (value == null) continue;
|
|
448
|
+
if (value < 1n || value > BigInt(values.length)) return false;
|
|
449
|
+
if (values.length > 1 && value === BigInt(index + 1)) return false;
|
|
450
|
+
const key = value.toString();
|
|
451
|
+
if (seenValues.has(key)) return false;
|
|
452
|
+
seenValues.add(key);
|
|
453
|
+
}
|
|
454
|
+
if (values.some((value) => value == null)) return true;
|
|
455
|
+
const visited = new Set();
|
|
456
|
+
let node = 1;
|
|
457
|
+
for (let step = 0; step < values.length; step++) {
|
|
458
|
+
if (visited.has(node)) return false;
|
|
459
|
+
visited.add(node);
|
|
460
|
+
node = Number(values[node - 1]);
|
|
461
|
+
}
|
|
462
|
+
return node === 1 && visited.size === values.length;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function orderAtom(term, env) {
|
|
466
|
+
const resolved = deref(term, env);
|
|
467
|
+
if (resolved.type === VAR) return null;
|
|
468
|
+
if (resolved.type !== ATOM || !['<', '=', '>'].includes(resolved.name)) {
|
|
469
|
+
throw new PrologError('domain_error(order)', resolved);
|
|
470
|
+
}
|
|
471
|
+
return resolved.name;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function comparedOrder(left, right) {
|
|
475
|
+
return left < right ? '<' : left > right ? '>' : '=';
|
|
476
|
+
}
|
|
477
|
+
|
|
285
478
|
function globalTruth(global, env) {
|
|
286
479
|
if (global.kind === 'allDistinct') {
|
|
287
480
|
const seen = new Set();
|
|
@@ -294,6 +487,14 @@ function globalTruth(global, env) {
|
|
|
294
487
|
}
|
|
295
488
|
return true;
|
|
296
489
|
}
|
|
490
|
+
if (global.kind === 'nvalue') {
|
|
491
|
+
const values = global.terms.map((term) => expressionValue(term, env));
|
|
492
|
+
const known = new Set(values.filter((value) => value != null).map(String));
|
|
493
|
+
const count = expressionValue(global.count, env);
|
|
494
|
+
if (count == null) return true;
|
|
495
|
+
if (count < BigInt(known.size) || count > BigInt(known.size + values.filter((value) => value == null).length)) return false;
|
|
496
|
+
return values.some((value) => value == null) || count === BigInt(known.size);
|
|
497
|
+
}
|
|
297
498
|
if (global.kind === 'sum') {
|
|
298
499
|
const values = global.terms.map((term) => expressionValue(term, env));
|
|
299
500
|
const right = expressionValue(global.value, env);
|
|
@@ -309,6 +510,54 @@ function globalTruth(global, env) {
|
|
|
309
510
|
for (let i = 0; i < values.length; i++) total += integerValue(global.coefficients[i], env) * values[i];
|
|
310
511
|
return relationTruth(relationTerm(global.relation, numberTerm(total.toString()), global.value), env) !== false;
|
|
311
512
|
}
|
|
513
|
+
if (global.kind === 'tuplesIn') {
|
|
514
|
+
return global.tuples.every((tuple) => tupleCandidates(tuple, global.relation, env).length > 0);
|
|
515
|
+
}
|
|
516
|
+
if (global.kind === 'lexChain') {
|
|
517
|
+
for (let index = 1; index < global.lists.length; index++) {
|
|
518
|
+
if (!lexPairTruth(global.lists[index - 1], global.lists[index], env)) return false;
|
|
519
|
+
}
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
if (global.kind === 'serialized') {
|
|
523
|
+
for (let left = 0; left < global.starts.length; left++) {
|
|
524
|
+
const a = expressionValue(global.starts[left], env);
|
|
525
|
+
if (a == null) continue;
|
|
526
|
+
for (let right = left + 1; right < global.starts.length; right++) {
|
|
527
|
+
const b = expressionValue(global.starts[right], env);
|
|
528
|
+
if (b == null) continue;
|
|
529
|
+
if (a + global.durations[left] > b && b + global.durations[right] > a) return false;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return true;
|
|
533
|
+
}
|
|
534
|
+
if (global.kind === 'globalCardinality') {
|
|
535
|
+
const values = global.terms.map((term) => expressionValue(term, env));
|
|
536
|
+
for (const { key, count: countTerm } of global.pairs) {
|
|
537
|
+
const known = values.filter((value) => value === key).length;
|
|
538
|
+
const possible = global.terms.filter((term, index) => {
|
|
539
|
+
if (values[index] != null) return false;
|
|
540
|
+
const resolved = deref(term, env);
|
|
541
|
+
const domain = resolved.type === VAR ? domainForRoot(resolved.name, env) : null;
|
|
542
|
+
return domain == null || domain.some((value) => value === key);
|
|
543
|
+
}).length;
|
|
544
|
+
const count = expressionValue(countTerm, env);
|
|
545
|
+
if (count != null && (count < BigInt(known) || count > BigInt(known + possible))) return false;
|
|
546
|
+
if (possible === 0 && count != null && count !== BigInt(known)) return false;
|
|
547
|
+
}
|
|
548
|
+
if (global.cost != null && values.every((value) => value != null)) {
|
|
549
|
+
let total = 0n;
|
|
550
|
+
for (let row = 0; row < values.length; row++) {
|
|
551
|
+
const column = global.pairs.findIndex(({ key }) => key === values[row]);
|
|
552
|
+
if (column < 0) return false;
|
|
553
|
+
total += global.matrix[row][column];
|
|
554
|
+
}
|
|
555
|
+
const cost = expressionValue(global.cost, env);
|
|
556
|
+
if (cost != null && cost !== total) return false;
|
|
557
|
+
}
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
if (global.kind === 'circuit') return circuitTruth(global.terms, env);
|
|
312
561
|
if (global.kind === 'chain') {
|
|
313
562
|
for (let i = 1; i < global.terms.length; i++) {
|
|
314
563
|
const truth = relationTruth(relationTerm(global.relation, global.terms[i - 1], global.terms[i]), env);
|
|
@@ -325,6 +574,12 @@ function globalTruth(global, env) {
|
|
|
325
574
|
const right = expressionValue(global.value, env);
|
|
326
575
|
return left == null || right == null || left === right;
|
|
327
576
|
}
|
|
577
|
+
if (global.kind === 'zcompare') {
|
|
578
|
+
const order = orderAtom(global.order, env);
|
|
579
|
+
const left = expressionValue(global.left, env);
|
|
580
|
+
const right = expressionValue(global.right, env);
|
|
581
|
+
return order == null || left == null || right == null || order === comparedOrder(left, right);
|
|
582
|
+
}
|
|
328
583
|
return true;
|
|
329
584
|
}
|
|
330
585
|
|
|
@@ -410,7 +665,82 @@ function propagate(env) {
|
|
|
410
665
|
}
|
|
411
666
|
}
|
|
412
667
|
for (const global of store.globals) {
|
|
413
|
-
if (global.kind === '
|
|
668
|
+
if (global.kind === 'nvalue') {
|
|
669
|
+
const values = global.terms.map((term) => expressionValue(term, env));
|
|
670
|
+
const known = new Set(values.filter((value) => value != null).map(String));
|
|
671
|
+
const unresolved = values.filter((value) => value == null).length;
|
|
672
|
+
const result = narrowTermDomain(env, global.count, integerRange(known.size, known.size + unresolved));
|
|
673
|
+
if (!result.ok) return false;
|
|
674
|
+
changed ||= result.changed;
|
|
675
|
+
} else if (global.kind === 'tuplesIn') {
|
|
676
|
+
for (const tuple of global.tuples) {
|
|
677
|
+
const candidates = tupleCandidates(tuple, global.relation, env);
|
|
678
|
+
if (candidates.length === 0) return false;
|
|
679
|
+
for (let index = 0; index < tuple.length; index++) {
|
|
680
|
+
const values = [...new Set(candidates.map((row) => row[index].toString()))].map(BigInt).sort(compareBigInt);
|
|
681
|
+
const result = narrowTermDomain(env, tuple[index], values);
|
|
682
|
+
if (!result.ok) return false;
|
|
683
|
+
changed ||= result.changed;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
} else if (global.kind === 'globalCardinality') {
|
|
687
|
+
const values = global.terms.map((term) => expressionValue(term, env));
|
|
688
|
+
for (const { key, count: countTerm } of global.pairs) {
|
|
689
|
+
const known = values.filter((value) => value === key).length;
|
|
690
|
+
const possibleTerms = global.terms.filter((term, index) => {
|
|
691
|
+
if (values[index] != null) return false;
|
|
692
|
+
const resolved = deref(term, env);
|
|
693
|
+
const domain = resolved.type === VAR ? domainForRoot(resolved.name, env) : null;
|
|
694
|
+
return domain == null || domain.some((value) => value === key);
|
|
695
|
+
});
|
|
696
|
+
const countResult = narrowTermDomain(env, countTerm, integerRange(known, known + possibleTerms.length));
|
|
697
|
+
if (!countResult.ok) return false;
|
|
698
|
+
changed ||= countResult.changed;
|
|
699
|
+
const count = expressionValue(countTerm, env);
|
|
700
|
+
if (count === BigInt(known)) {
|
|
701
|
+
for (const term of possibleTerms) {
|
|
702
|
+
const resolved = deref(term, env);
|
|
703
|
+
if (resolved.type !== VAR) continue;
|
|
704
|
+
const domain = domainForRoot(resolved.name, env);
|
|
705
|
+
const result = narrowTermDomain(env, term, domain.filter((value) => value !== key));
|
|
706
|
+
if (!result.ok) return false;
|
|
707
|
+
changed ||= result.changed;
|
|
708
|
+
}
|
|
709
|
+
} else if (count === BigInt(known + possibleTerms.length)) {
|
|
710
|
+
for (const term of possibleTerms) {
|
|
711
|
+
const result = narrowTermDomain(env, term, [key]);
|
|
712
|
+
if (!result.ok) return false;
|
|
713
|
+
changed ||= result.changed;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
if (global.cost != null && values.every((value) => value != null)) {
|
|
718
|
+
let total = 0n;
|
|
719
|
+
for (let row = 0; row < values.length; row++) {
|
|
720
|
+
const column = global.pairs.findIndex(({ key }) => key === values[row]);
|
|
721
|
+
if (column < 0) return false;
|
|
722
|
+
total += global.matrix[row][column];
|
|
723
|
+
}
|
|
724
|
+
const result = bindExpressionEquality(global.cost, numberTerm(total.toString()), env);
|
|
725
|
+
if (!result.ok) return false;
|
|
726
|
+
changed ||= result.changed;
|
|
727
|
+
}
|
|
728
|
+
} else if (global.kind === 'zcompare') {
|
|
729
|
+
const order = orderAtom(global.order, env);
|
|
730
|
+
const left = expressionValue(global.left, env);
|
|
731
|
+
const right = expressionValue(global.right, env);
|
|
732
|
+
if (left != null && right != null) {
|
|
733
|
+
const resolvedOrder = atom(comparedOrder(left, right));
|
|
734
|
+
if (order == null) {
|
|
735
|
+
if (!unify(global.order, resolvedOrder, env)) return false;
|
|
736
|
+
changed = true;
|
|
737
|
+
} else if (order !== resolvedOrder.name) return false;
|
|
738
|
+
} else if (order === '=') {
|
|
739
|
+
const result = bindLinearEquality(global.left, global.right, env);
|
|
740
|
+
if (!result.ok) return false;
|
|
741
|
+
changed ||= result.changed;
|
|
742
|
+
}
|
|
743
|
+
} else if (global.kind === 'element') {
|
|
414
744
|
const index = expressionValue(global.index, env);
|
|
415
745
|
if (index != null) {
|
|
416
746
|
if (index < 1n || index > BigInt(global.terms.length)) return false;
|
package/src/lib/clpz.pl
CHANGED
|
@@ -25,13 +25,21 @@
|
|
|
25
25
|
ins/2,
|
|
26
26
|
all_different/1,
|
|
27
27
|
all_distinct/1,
|
|
28
|
+
nvalue/2,
|
|
28
29
|
sum/3,
|
|
29
30
|
scalar_product/4,
|
|
31
|
+
tuples_in/2,
|
|
30
32
|
labeling/2,
|
|
31
33
|
label/1,
|
|
32
34
|
indomain/1,
|
|
35
|
+
lex_chain/1,
|
|
36
|
+
serialized/2,
|
|
37
|
+
global_cardinality/2,
|
|
38
|
+
global_cardinality/3,
|
|
39
|
+
circuit/1,
|
|
33
40
|
chain/2,
|
|
34
41
|
element/3,
|
|
42
|
+
zcompare/3,
|
|
35
43
|
fd_var/1,
|
|
36
44
|
fd_inf/2,
|
|
37
45
|
fd_sup/2,
|
|
@@ -75,6 +83,7 @@ ins(Integers, Domain) :- eyeprolog__clpz_ins(Integers, Domain).
|
|
|
75
83
|
|
|
76
84
|
all_different(Integers) :- all_distinct(Integers).
|
|
77
85
|
all_distinct(Integers) :- eyeprolog__clpz_all_distinct(Integers).
|
|
86
|
+
nvalue(Count, Integers) :- eyeprolog__clpz_nvalue(Count, Integers).
|
|
78
87
|
|
|
79
88
|
sum(Integers, Relation, Value) :-
|
|
80
89
|
eyeprolog__clpz_sum(Integers, Relation, Value).
|
|
@@ -82,15 +91,27 @@ sum(Integers, Relation, Value) :-
|
|
|
82
91
|
scalar_product(Coefficients, Integers, Relation, Value) :-
|
|
83
92
|
eyeprolog__clpz_scalar_product(Coefficients, Integers, Relation, Value).
|
|
84
93
|
|
|
94
|
+
tuples_in(Tuples, Relation) :- eyeprolog__clpz_tuples_in(Tuples, Relation).
|
|
95
|
+
|
|
85
96
|
labeling(Options, Integers) :-
|
|
86
97
|
eyeprolog__clpz_labeling(Options, Integers).
|
|
87
98
|
|
|
88
99
|
label(Integers) :- labeling([], Integers).
|
|
89
100
|
indomain(Integer) :- labeling([], [Integer]).
|
|
90
101
|
|
|
102
|
+
lex_chain(Lists) :- eyeprolog__clpz_lex_chain(Lists).
|
|
103
|
+
serialized(Starts, Durations) :- eyeprolog__clpz_serialized(Starts, Durations).
|
|
104
|
+
|
|
105
|
+
global_cardinality(Integers, Pairs) :-
|
|
106
|
+
global_cardinality(Integers, Pairs, []).
|
|
107
|
+
global_cardinality(Integers, Pairs, Options) :-
|
|
108
|
+
eyeprolog__clpz_global_cardinality(Integers, Pairs, Options).
|
|
109
|
+
|
|
110
|
+
circuit(Integers) :- eyeprolog__clpz_circuit(Integers).
|
|
91
111
|
chain(Relation, Integers) :- eyeprolog__clpz_chain(Relation, Integers).
|
|
92
112
|
element(Index, Integers, Value) :-
|
|
93
113
|
eyeprolog__clpz_element(Index, Integers, Value).
|
|
114
|
+
zcompare(Order, Left, Right) :- eyeprolog__clpz_zcompare(Order, Left, Right).
|
|
94
115
|
|
|
95
116
|
fd_var(Integer) :- eyeprolog__clpz_fd_var(Integer).
|
|
96
117
|
fd_inf(Integer, Infimum) :- eyeprolog__clpz_fd_inf(Integer, Infimum).
|
package/src/playground-worker.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Browser worker entry used by playground.html.
|
|
2
2
|
// Keep this module free of Node-only imports: it is fetched directly by the
|
|
3
3
|
// browser and is also exercised by test/run-playground.mjs.
|
|
4
|
-
import { createEyePrologRegistry, run } from './index.js?playground=
|
|
4
|
+
import { createEyePrologRegistry, run } from './index.js?playground=20260811c';
|
|
5
5
|
|
|
6
6
|
const registry = createEyePrologRegistry();
|
|
7
7
|
|
package/src/standard-library.js
CHANGED
|
@@ -22,7 +22,7 @@ const moduleFiles = Object.freeze({
|
|
|
22
22
|
|
|
23
23
|
const cacheKey = isNode
|
|
24
24
|
? null
|
|
25
|
-
: (new URL(import.meta.url).searchParams.get('playground') ?? '
|
|
25
|
+
: (new URL(import.meta.url).searchParams.get('playground') ?? '20260811c');
|
|
26
26
|
|
|
27
27
|
export const standardLibrarySources = new Map(await Promise.all(
|
|
28
28
|
Object.entries(moduleFiles).map(async ([name, filename]) => [name, {
|
|
@@ -48,9 +48,11 @@ export const eyePrologNativeLibraryIndicators = Object.freeze([
|
|
|
48
48
|
'call_nth/2', 'freeze/2',
|
|
49
49
|
'#>/2', '#</2', '#>=/2', '#=</2', '#=/2', '#\\=/2', '#\\/1',
|
|
50
50
|
'#<==>/2', '#==>/2', '#<==/2', '#\\//2', '#\\/2', '#/\\/2',
|
|
51
|
-
'in/2', 'ins/2', 'all_different/1', 'all_distinct/1', 'sum/3',
|
|
52
|
-
'scalar_product/4', '
|
|
53
|
-
'
|
|
51
|
+
'in/2', 'ins/2', 'all_different/1', 'all_distinct/1', 'nvalue/2', 'sum/3',
|
|
52
|
+
'scalar_product/4', 'tuples_in/2', 'labeling/2', 'label/1',
|
|
53
|
+
'indomain/1', 'lex_chain/1', 'serialized/2', 'global_cardinality/2',
|
|
54
|
+
'global_cardinality/3', 'circuit/1', 'chain/2', 'element/3', 'zcompare/3',
|
|
55
|
+
'fd_var/1', 'fd_inf/2', 'fd_sup/2', 'fd_size/2', 'fd_dom/2',
|
|
54
56
|
]);
|
|
55
57
|
export const eyePrologPortableLibraryIndicators = Object.freeze([
|
|
56
58
|
'sumall/3', 'aggregate_min/5', 'aggregate_max/5',
|
package/test/run-regression.mjs
CHANGED
|
@@ -1438,20 +1438,21 @@ open(X) :- candidate(X), \\+ closed(X).
|
|
|
1438
1438
|
assertEqual(Boolean(registry.get('is', 2)), true, 'ISO is/2 exists');
|
|
1439
1439
|
assertEqual(Boolean(registry.get('append', 3)), false, 'append/3 is not ISO core');
|
|
1440
1440
|
assertEqual(library.eyePrologLibrary, true, 'complete registry marker');
|
|
1441
|
-
assertEqual(library.defs.size,
|
|
1441
|
+
assertEqual(library.defs.size, 152, 'EyeProlog registry contains ISO definitions and private library adapters');
|
|
1442
1442
|
assertEqual(Boolean(registry.get('phrase', 2)), true, 'Part 3 phrase/2 exists');
|
|
1443
1443
|
assertEqual(Boolean(registry.get('phrase', 3)), true, 'Part 3 phrase/3 exists');
|
|
1444
|
-
assertEqual(registeredNativeEyePrologLibraryNames().length,
|
|
1444
|
+
assertEqual(registeredNativeEyePrologLibraryNames().length, 39, 'public native EyeProlog builtin count');
|
|
1445
1445
|
assertEqual(eyePrologPortableLibraryIndicators.length, 60, 'portable Prolog library count');
|
|
1446
|
-
assertEqual(eyePrologNativeLibraryIndicators.length,
|
|
1446
|
+
assertEqual(eyePrologNativeLibraryIndicators.length, 39, 'native host library count');
|
|
1447
1447
|
assertEqual(eyePrologNativeLibraryIndicators.slice(0, 2).join(','), 'call_nth/2,freeze/2', 'control predicates requiring host support');
|
|
1448
|
-
assertEqual(eyePrologLibraryIndicators.length,
|
|
1448
|
+
assertEqual(eyePrologLibraryIndicators.length, 99, 'complete EyeProlog library surface');
|
|
1449
1449
|
assertEqual(registry.get('eyeprolog__call_nth', 2), null, 'private call_nth adapter is absent from ISO registry');
|
|
1450
1450
|
assertEqual(Boolean(library.get('eyeprolog__call_nth', 2)), true, 'private call_nth adapter is registered for EyeProlog');
|
|
1451
1451
|
assertEqual(library.get('eyeprolog__call_nth', 2)?.eyePrologLibrary, true, 'private adapter is marked as library support');
|
|
1452
1452
|
assertEqual(registry.get('eyeprolog__freeze', 2), null, 'private freeze adapter is absent from ISO registry');
|
|
1453
1453
|
assertEqual(Boolean(library.get('eyeprolog__freeze', 2)), true, 'private freeze adapter is registered for EyeProlog');
|
|
1454
1454
|
assertEqual(Boolean(library.get('eyeprolog__clpz_labeling', 2)), true, 'private CLP(Z) labeling adapter is registered');
|
|
1455
|
+
assertEqual(Boolean(library.get('eyeprolog__clpz_global_cardinality', 3)), true, 'private CLP(Z) cardinality adapter is registered');
|
|
1455
1456
|
assertEqual(library.get('between', 3), null, 'between/3 remains portable Prolog');
|
|
1456
1457
|
assertEqual(library.get('smallest_divisor_from', 3), null, 'smallest_divisor_from/3 remains portable Prolog');
|
|
1457
1458
|
assertEqual(library.get('random', 3), null, 'random/3 remains portable Prolog');
|
|
@@ -1545,6 +1546,19 @@ contradiction :- Z in 1..3, Z = 4.
|
|
|
1545
1546
|
'answer(1, 4, 1).\nanswer(2, 3, 0).\n', 'CLP(Z) constrained answers');
|
|
1546
1547
|
},
|
|
1547
1548
|
},
|
|
1549
|
+
{
|
|
1550
|
+
name: 'CLP(Z) finite global constraints match the portable example',
|
|
1551
|
+
run: () => {
|
|
1552
|
+
const filename = path.join(packageRoot, 'examples', 'clpz-global-constraints.pl');
|
|
1553
|
+
const source = fs.readFileSync(filename, 'utf8');
|
|
1554
|
+
const program = Program.parseSources([{ text: source, filename }]);
|
|
1555
|
+
assertEqual(program.findGroup('tuples_in', 2)?.module, 'clpz', 'tuples_in/2 module');
|
|
1556
|
+
assertEqual(program.findGroup('global_cardinality', 3)?.module, 'clpz', 'global_cardinality/3 module');
|
|
1557
|
+
assertEqual(program.findGroup('circuit', 1)?.module, 'clpz', 'circuit/1 module');
|
|
1558
|
+
const expected = fs.readFileSync(path.join(packageRoot, 'examples', 'output', 'clpz-global-constraints.pl'), 'utf8');
|
|
1559
|
+
assertEqual(run(program, { goal: 'advanced_clpz(X0, X1)' }).stdout, expected, 'global constraint answers');
|
|
1560
|
+
},
|
|
1561
|
+
},
|
|
1548
1562
|
{
|
|
1549
1563
|
name: 'Part 2 modules isolate predicates and support selective imports',
|
|
1550
1564
|
run: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -5762,14 +5762,14 @@ so side effects occur in Prolog execution order.
|
|
|
5762
5762
|
|
|
5763
5763
|
### The EyeProlog library
|
|
5764
5764
|
|
|
5765
|
-
EyeProlog exposes **
|
|
5765
|
+
EyeProlog exposes **99 library predicate indicators** in addition to the 129
|
|
5766
5766
|
indicators in its isolated ISO profile. **60 are implemented entirely as
|
|
5767
|
-
ordinary Prolog clauses** in focused modules under `src/lib/`. The remaining
|
|
5767
|
+
ordinary Prolog clauses** in focused modules under `src/lib/`. The remaining 39
|
|
5768
5768
|
are public wrappers around backtrackable host support: Prologue `call_nth/2` and
|
|
5769
|
-
`freeze/2`, plus the
|
|
5770
|
-
resulting normal EyeProlog language surface is therefore **
|
|
5769
|
+
`freeze/2`, plus the 37-predicate finite-domain `library(clpz)` kernel. The
|
|
5770
|
+
resulting normal EyeProlog language surface is therefore **228 public predicate
|
|
5771
5771
|
indicators**. Internally, the runtime registry contains the 129 ISO definitions
|
|
5772
|
-
plus
|
|
5772
|
+
plus 23 private library adapters; public relations remain module source clauses.
|
|
5773
5773
|
|
|
5774
5774
|
The sources are `src/lib/aggregate.pl`, `src/lib/clpz.pl`, `src/lib/comparison.pl`,
|
|
5775
5775
|
`src/lib/dates.pl`, `src/lib/iso_ext.pl`, `src/lib/lists.pl`,
|
|
@@ -5799,7 +5799,7 @@ between solution branches.
|
|
|
5799
5799
|
| Module | Exported predicate indicators |
|
|
5800
5800
|
| --- | --- |
|
|
5801
5801
|
| `library(aggregate)` | `sumall/3`, `aggregate_min/5`, `aggregate_max/5` |
|
|
5802
|
-
| `library(clpz)` | `#>/2`, `#</2`, `#>=/2`, `#=</2`, `#=/2`, `#\=/2`, `#\/1`, `#<==>/2`, `#==>/2`, `#<==/2`, `#\//2`, `#\/2`, `#/\/2`, `in/2`, `ins/2`, `all_different/1`, `all_distinct/1`, `sum/3`, `scalar_product/4`, `labeling/2`, `label/1`, `indomain/1`, `chain/2`, `element/3`, `fd_var/1`, `fd_inf/2`, `fd_sup/2`, `fd_size/2`, `fd_dom/2` |
|
|
5802
|
+
| `library(clpz)` | `#>/2`, `#</2`, `#>=/2`, `#=</2`, `#=/2`, `#\=/2`, `#\/1`, `#<==>/2`, `#==>/2`, `#<==/2`, `#\//2`, `#\/2`, `#/\/2`, `in/2`, `ins/2`, `all_different/1`, `all_distinct/1`, `nvalue/2`, `sum/3`, `scalar_product/4`, `tuples_in/2`, `labeling/2`, `label/1`, `indomain/1`, `lex_chain/1`, `serialized/2`, `global_cardinality/2`, `global_cardinality/3`, `circuit/1`, `chain/2`, `element/3`, `zcompare/3`, `fd_var/1`, `fd_inf/2`, `fd_sup/2`, `fd_size/2`, `fd_dom/2` |
|
|
5803
5803
|
| `library(comparison)` | `lt/2`, `gt/2`, `le/2`, `ge/2` |
|
|
5804
5804
|
| `library(dates)` | `difference/3` |
|
|
5805
5805
|
| `library(iso_ext)` | `countall/2`, `forall/2`, `succ/2`, `cfor/3`, `findall/4`, `variant/2` |
|
|
@@ -5816,10 +5816,13 @@ between solution branches.
|
|
|
5816
5816
|
programming over integers. Its first implementation step provides finite
|
|
5817
5817
|
interval and union domains, arithmetic and reified constraints, backtrackable
|
|
5818
5818
|
labeling with `ff`, `up`, and `down`, global distinctness, linear sums and
|
|
5819
|
-
scalar products, chains, elements,
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5819
|
+
scalar products, chains, elements, value counting, extensional tuple tables,
|
|
5820
|
+
lexicographic chains, serialized schedules, global cardinality with costs,
|
|
5821
|
+
Hamiltonian circuits, three-way comparison, and domain reflection. Constraints
|
|
5822
|
+
are kept in the logical environment, so failed alternatives cannot leak domains
|
|
5823
|
+
into later branches. The full Trealla library also contains automata,
|
|
5824
|
+
cumulative and two-dimensional scheduling constraints, and unbounded-domain
|
|
5825
|
+
propagation that are not yet exported here.
|
|
5823
5826
|
|
|
5824
5827
|
`library(iso_ext)` collects extensions commonly found in mature Prolog
|
|
5825
5828
|
systems. `forall/2` checks an action for every solution of a condition;
|
|
@@ -6362,7 +6365,7 @@ Review questions:
|
|
|
6362
6365
|
</figure>
|
|
6363
6366
|
|
|
6364
6367
|
The [examples directory](https://github.com/eyereasoner/eyeprolog/tree/main/examples/) is the book's executable companion. The
|
|
6365
|
-
top-level directory contains **
|
|
6368
|
+
top-level directory contains **210 self-contained runnable programs**. Every
|
|
6366
6369
|
source program has an exact answer file under
|
|
6367
6370
|
[examples/output](https://github.com/eyereasoner/eyeprolog/tree/main/examples/output/), and **61 selected programs** have a checked
|
|
6368
6371
|
explanation under [examples/proof](https://github.com/eyereasoner/eyeprolog/tree/main/examples/proof/). The thematic tables below link every top-level program and open the program
|
|
@@ -6411,6 +6414,7 @@ mode at a time.
|
|
|
6411
6414
|
| Program | Standard facility | Checked answer |
|
|
6412
6415
|
| --- | --- | --- |
|
|
6413
6416
|
| [CLP(Z) factorial](https://github.com/eyereasoner/eyeprolog/blob/main/examples/clpz-factorial.pl) | Declarative predecessor and product constraints propagate a factorial without mode-sensitive `is/2`. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/clpz-factorial.pl) |
|
|
6417
|
+
| [CLP(Z) global constraints](https://github.com/eyereasoner/eyeprolog/blob/main/examples/clpz-global-constraints.pl) | Compatibility tables, lexicographic and serialized schedules, global cardinality with costs, circuits, value counting, and integer comparison. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/clpz-global-constraints.pl) |
|
|
6414
6418
|
| [CLP(Z) N-queens](https://github.com/eyereasoner/eyeprolog/blob/main/examples/clpz-n-queens.pl) | Six-queens using finite domains, delayed diagonal constraints, `all_distinct/1`, and first-fail labeling. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/clpz-n-queens.pl) |
|
|
6415
6419
|
| [CLP(Z) resource allocation](https://github.com/eyereasoner/eyeprolog/blob/main/examples/clpz-resource-allocation.pl) | Resource assignment using `element/3`, `sum/3`, `scalar_product/4`, reification, labeling options, and domain reflection. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/clpz-resource-allocation.pl) |
|
|
6416
6420
|
| [Combinatorics Findall Sort](https://github.com/eyereasoner/eyeprolog/blob/main/examples/combinatorics-findall-sort.pl) | Eyelet-inspired combinations example using `findall/3` and ISO `sort/2`. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/combinatorics-findall-sort.pl) |
|