eyeling 1.5.35 → 1.5.37
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/examples/brussels-brew-club.n3 +119 -0
- package/examples/drone-corridor-planner.n3 +146 -40
- package/examples/ev-roundtrip-planner.n3 +189 -0
- package/examples/json-pointer.n3 +5 -0
- package/examples/json-reconcile-vat.n3 +5 -0
- package/examples/oslo-steps-workflow-composition.n3 +305 -0
- package/examples/output/brussels-brew-club.n3 +498 -0
- package/examples/output/drone-corridor-planner.n3 +717 -51
- package/examples/output/ev-roundtrip-planner.n3 +403 -0
- package/examples/output/json-reconcile-vat.n3 +49 -48
- package/examples/output/oslo-steps-workflow-composition.n3 +148 -0
- package/examples/output/skolem.n3 +5 -4
- package/examples/rdf-list.n3 +7 -3
- package/eyeling.js +183 -7
- package/package.json +1 -1
- package/test/api.test.js +30 -0
- package/examples/drone-corridor-planner-v2.n3 +0 -237
- package/examples/output/drone-corridor-planner-v2.n3 +0 -819
package/eyeling.js
CHANGED
|
@@ -67,6 +67,52 @@ const jsonPointerCache = new Map();
|
|
|
67
67
|
// Controls whether human-readable proof comments are printed.
|
|
68
68
|
let proofCommentsEnabled = true;
|
|
69
69
|
|
|
70
|
+
// ----------------------------------------------------------------------------
|
|
71
|
+
// Deterministic time support
|
|
72
|
+
// ----------------------------------------------------------------------------
|
|
73
|
+
// If set, overrides time:localTime across the whole run (and across runs if you
|
|
74
|
+
// pass the same value). Store as xsd:dateTime *lexical* string (no quotes).
|
|
75
|
+
let fixedNowLex = null;
|
|
76
|
+
|
|
77
|
+
// If not fixed, we still memoize one value per run to avoid re-firing rules.
|
|
78
|
+
let runNowLex = null;
|
|
79
|
+
|
|
80
|
+
function normalizeDateTimeLex(s) {
|
|
81
|
+
// Accept: 2025-... , "2025-..." , "2025-..."^^xsd:dateTime , "..."^^<...>
|
|
82
|
+
if (s == null) return null;
|
|
83
|
+
let t = String(s).trim();
|
|
84
|
+
const caret = t.indexOf("^^");
|
|
85
|
+
if (caret >= 0) t = t.slice(0, caret).trim();
|
|
86
|
+
if (t.startsWith('"') && t.endsWith('"') && t.length >= 2) t = t.slice(1, -1);
|
|
87
|
+
return t.trim();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function utcIsoDateTimeStringFromEpochSeconds(sec) {
|
|
91
|
+
const ms = sec * 1000;
|
|
92
|
+
const d = new Date(ms);
|
|
93
|
+
function pad(n, w = 2) { return String(n).padStart(w, "0"); }
|
|
94
|
+
const year = d.getUTCFullYear();
|
|
95
|
+
const month = d.getUTCMonth() + 1;
|
|
96
|
+
const day = d.getUTCDate();
|
|
97
|
+
const hour = d.getUTCHours();
|
|
98
|
+
const min = d.getUTCMinutes();
|
|
99
|
+
const s2 = d.getUTCSeconds();
|
|
100
|
+
const ms2 = d.getUTCMilliseconds();
|
|
101
|
+
const msPart = ms2 ? "." + String(ms2).padStart(3, "0") : "";
|
|
102
|
+
return (
|
|
103
|
+
pad(year, 4) + "-" + pad(month) + "-" + pad(day) + "T" +
|
|
104
|
+
pad(hour) + ":" + pad(min) + ":" + pad(s2) + msPart +
|
|
105
|
+
"+00:00"
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getNowLex() {
|
|
110
|
+
if (fixedNowLex) return fixedNowLex;
|
|
111
|
+
if (runNowLex) return runNowLex;
|
|
112
|
+
runNowLex = localIsoDateTimeString(new Date());
|
|
113
|
+
return runNowLex;
|
|
114
|
+
}
|
|
115
|
+
|
|
70
116
|
// Deterministic pseudo-UUID from a string key (for log:skolem).
|
|
71
117
|
// Not cryptographically strong, but stable and platform-independent.
|
|
72
118
|
function deterministicSkolemIdFromKey(key) {
|
|
@@ -106,6 +152,8 @@ function deterministicSkolemIdFromKey(key) {
|
|
|
106
152
|
);
|
|
107
153
|
}
|
|
108
154
|
|
|
155
|
+
let runLocalTimeCache = null;
|
|
156
|
+
|
|
109
157
|
// ============================================================================
|
|
110
158
|
// AST (Abstract Syntax Tree)
|
|
111
159
|
// ============================================================================
|
|
@@ -511,6 +559,7 @@ class PrefixEnv {
|
|
|
511
559
|
m["string"] = STRING_NS;
|
|
512
560
|
m["list"] = LIST_NS;
|
|
513
561
|
m["time"] = TIME_NS;
|
|
562
|
+
m["genid"] = SKOLEM_NS;
|
|
514
563
|
m[""] = "";
|
|
515
564
|
return new PrefixEnv(m);
|
|
516
565
|
}
|
|
@@ -1225,7 +1274,7 @@ function termsEqual(a, b) {
|
|
|
1225
1274
|
return true;
|
|
1226
1275
|
}
|
|
1227
1276
|
if (a instanceof FormulaTerm) {
|
|
1228
|
-
return
|
|
1277
|
+
return alphaEqFormulaTriples(a.triples, b.triples);
|
|
1229
1278
|
}
|
|
1230
1279
|
return false;
|
|
1231
1280
|
}
|
|
@@ -1244,6 +1293,96 @@ function triplesListEqual(xs, ys) {
|
|
|
1244
1293
|
return true;
|
|
1245
1294
|
}
|
|
1246
1295
|
|
|
1296
|
+
// Alpha-equivalence for quoted formulas, up to *variable* and blank-node renaming.
|
|
1297
|
+
// Treats a formula as an unordered set of triples (order-insensitive match).
|
|
1298
|
+
function alphaEqVarName(x, y, vmap) {
|
|
1299
|
+
if (vmap.hasOwnProperty(x)) return vmap[x] === y;
|
|
1300
|
+
vmap[x] = y;
|
|
1301
|
+
return true;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
function alphaEqTermInFormula(a, b, vmap, bmap) {
|
|
1305
|
+
// Blank nodes: renamable
|
|
1306
|
+
if (a instanceof Blank && b instanceof Blank) {
|
|
1307
|
+
const x = a.label;
|
|
1308
|
+
const y = b.label;
|
|
1309
|
+
if (bmap.hasOwnProperty(x)) return bmap[x] === y;
|
|
1310
|
+
bmap[x] = y;
|
|
1311
|
+
return true;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// Variables: renamable (ONLY inside quoted formulas)
|
|
1315
|
+
if (a instanceof Var && b instanceof Var) {
|
|
1316
|
+
return alphaEqVarName(a.name, b.name, vmap);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
if (a instanceof Iri && b instanceof Iri) return a.value === b.value;
|
|
1320
|
+
if (a instanceof Literal && b instanceof Literal) return a.value === b.value;
|
|
1321
|
+
|
|
1322
|
+
if (a instanceof ListTerm && b instanceof ListTerm) {
|
|
1323
|
+
if (a.elems.length !== b.elems.length) return false;
|
|
1324
|
+
for (let i = 0; i < a.elems.length; i++) {
|
|
1325
|
+
if (!alphaEqTermInFormula(a.elems[i], b.elems[i], vmap, bmap)) return false;
|
|
1326
|
+
}
|
|
1327
|
+
return true;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
if (a instanceof OpenListTerm && b instanceof OpenListTerm) {
|
|
1331
|
+
if (a.prefix.length !== b.prefix.length) return false;
|
|
1332
|
+
for (let i = 0; i < a.prefix.length; i++) {
|
|
1333
|
+
if (!alphaEqTermInFormula(a.prefix[i], b.prefix[i], vmap, bmap)) return false;
|
|
1334
|
+
}
|
|
1335
|
+
// tailVar is a var-name string, so treat it as renamable too
|
|
1336
|
+
return alphaEqVarName(a.tailVar, b.tailVar, vmap);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
// Nested formulas: compare with fresh maps (separate scope)
|
|
1340
|
+
if (a instanceof FormulaTerm && b instanceof FormulaTerm) {
|
|
1341
|
+
return alphaEqFormulaTriples(a.triples, b.triples);
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
return false;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
function alphaEqTripleInFormula(a, b, vmap, bmap) {
|
|
1348
|
+
return (
|
|
1349
|
+
alphaEqTermInFormula(a.s, b.s, vmap, bmap) &&
|
|
1350
|
+
alphaEqTermInFormula(a.p, b.p, vmap, bmap) &&
|
|
1351
|
+
alphaEqTermInFormula(a.o, b.o, vmap, bmap)
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
function alphaEqFormulaTriples(xs, ys) {
|
|
1356
|
+
if (xs.length !== ys.length) return false;
|
|
1357
|
+
// Fast path: exact same sequence.
|
|
1358
|
+
if (triplesListEqual(xs, ys)) return true;
|
|
1359
|
+
|
|
1360
|
+
// Order-insensitive backtracking match, threading var/blank mappings.
|
|
1361
|
+
const used = new Array(ys.length).fill(false);
|
|
1362
|
+
|
|
1363
|
+
function step(i, vmap, bmap) {
|
|
1364
|
+
if (i >= xs.length) return true;
|
|
1365
|
+
const x = xs[i];
|
|
1366
|
+
for (let j = 0; j < ys.length; j++) {
|
|
1367
|
+
if (used[j]) continue;
|
|
1368
|
+
const y = ys[j];
|
|
1369
|
+
// Cheap pruning when both predicates are IRIs.
|
|
1370
|
+
if (x.p instanceof Iri && y.p instanceof Iri && x.p.value !== y.p.value) continue;
|
|
1371
|
+
|
|
1372
|
+
const v2 = { ...vmap };
|
|
1373
|
+
const b2 = { ...bmap };
|
|
1374
|
+
if (!alphaEqTripleInFormula(x, y, v2, b2)) continue;
|
|
1375
|
+
|
|
1376
|
+
used[j] = true;
|
|
1377
|
+
if (step(i + 1, v2, b2)) return true;
|
|
1378
|
+
used[j] = false;
|
|
1379
|
+
}
|
|
1380
|
+
return false;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
return step(0, {}, {});
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1247
1386
|
function alphaEqTerm(a, b, bmap) {
|
|
1248
1387
|
if (a instanceof Blank && b instanceof Blank) {
|
|
1249
1388
|
const x = a.label;
|
|
@@ -1274,8 +1413,8 @@ function alphaEqTerm(a, b, bmap) {
|
|
|
1274
1413
|
return true;
|
|
1275
1414
|
}
|
|
1276
1415
|
if (a instanceof FormulaTerm && b instanceof FormulaTerm) {
|
|
1277
|
-
// formulas are
|
|
1278
|
-
return
|
|
1416
|
+
// formulas are alpha-equivalent up to var/blank renaming
|
|
1417
|
+
return alphaEqFormulaTriples(a.triples, b.triples);
|
|
1279
1418
|
}
|
|
1280
1419
|
return false;
|
|
1281
1420
|
}
|
|
@@ -1541,12 +1680,29 @@ function containsVarTerm(t, v) {
|
|
|
1541
1680
|
return false;
|
|
1542
1681
|
}
|
|
1543
1682
|
|
|
1683
|
+
function isGroundTermInFormula(t) {
|
|
1684
|
+
// EYE-style: variables inside formula terms are treated as local placeholders,
|
|
1685
|
+
// so they don't make the *surrounding triple* non-ground.
|
|
1686
|
+
if (t instanceof OpenListTerm) return false;
|
|
1687
|
+
if (t instanceof ListTerm) return t.elems.every(e => isGroundTermInFormula(e));
|
|
1688
|
+
if (t instanceof FormulaTerm) return t.triples.every(tr => isGroundTripleInFormula(tr));
|
|
1689
|
+
// Iri/Literal/Blank/Var are all OK inside formulas
|
|
1690
|
+
return true;
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
function isGroundTripleInFormula(tr) {
|
|
1694
|
+
return (
|
|
1695
|
+
isGroundTermInFormula(tr.s) &&
|
|
1696
|
+
isGroundTermInFormula(tr.p) &&
|
|
1697
|
+
isGroundTermInFormula(tr.o)
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1544
1701
|
function isGroundTerm(t) {
|
|
1545
1702
|
if (t instanceof Var) return false;
|
|
1546
1703
|
if (t instanceof ListTerm) return t.elems.every(e => isGroundTerm(e));
|
|
1547
1704
|
if (t instanceof OpenListTerm) return false;
|
|
1548
|
-
if (t instanceof FormulaTerm)
|
|
1549
|
-
return t.triples.every(tr => isGroundTriple(tr));
|
|
1705
|
+
if (t instanceof FormulaTerm) return t.triples.every(tr => isGroundTripleInFormula(tr));
|
|
1550
1706
|
return true;
|
|
1551
1707
|
}
|
|
1552
1708
|
|
|
@@ -1755,8 +1911,11 @@ function unifyTerm(a, b, subst) {
|
|
|
1755
1911
|
return s2;
|
|
1756
1912
|
}
|
|
1757
1913
|
|
|
1758
|
-
// Formulas:
|
|
1914
|
+
// Formulas:
|
|
1915
|
+
// 1) If they are alpha-equivalent, succeed without leaking internal bindings.
|
|
1916
|
+
// 2) Otherwise fall back to full unification (may bind vars).
|
|
1759
1917
|
if (a instanceof FormulaTerm && b instanceof FormulaTerm) {
|
|
1918
|
+
if (alphaEqFormulaTriples(a.triples, b.triples)) return { ...subst };
|
|
1760
1919
|
return unifyFormulaTriples(a.triples, b.triples, subst);
|
|
1761
1920
|
}
|
|
1762
1921
|
return null;
|
|
@@ -2869,7 +3028,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
2869
3028
|
// time:localTime
|
|
2870
3029
|
// "" time:localTime ?D. binds ?D to “now” as xsd:dateTime.
|
|
2871
3030
|
if (g.p instanceof Iri && g.p.value === TIME_NS + "localTime") {
|
|
2872
|
-
const now =
|
|
3031
|
+
const now = getNowLex();
|
|
3032
|
+
|
|
2873
3033
|
if (g.o instanceof Var) {
|
|
2874
3034
|
const s2 = { ...subst };
|
|
2875
3035
|
s2[g.o.name] = new Literal(`"${now}"^^<${XSD_NS}dateTime>`);
|
|
@@ -3692,6 +3852,12 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3692
3852
|
function isBuiltinPred(p) {
|
|
3693
3853
|
if (!(p instanceof Iri)) return false;
|
|
3694
3854
|
const v = p.value;
|
|
3855
|
+
|
|
3856
|
+
// Treat RDF Collections as list-term builtins too.
|
|
3857
|
+
if (v === RDF_NS + "first" || v === RDF_NS + "rest") {
|
|
3858
|
+
return true;
|
|
3859
|
+
}
|
|
3860
|
+
|
|
3695
3861
|
return (
|
|
3696
3862
|
v.startsWith(CRYPTO_NS) ||
|
|
3697
3863
|
v.startsWith(MATH_NS) ||
|
|
@@ -4148,6 +4314,16 @@ function termToN3(t, pref) {
|
|
|
4148
4314
|
}
|
|
4149
4315
|
if (t instanceof Literal) {
|
|
4150
4316
|
const [lex, dt] = literalParts(t.value);
|
|
4317
|
+
|
|
4318
|
+
// Pretty-print xsd:boolean as bare true/false
|
|
4319
|
+
if (dt === XSD_NS + "boolean") {
|
|
4320
|
+
const v = stripQuotes(lex);
|
|
4321
|
+
if (v === "true" || v === "false") return v;
|
|
4322
|
+
// optional: normalize 1/0 too
|
|
4323
|
+
if (v === "1") return "true";
|
|
4324
|
+
if (v === "0") return "false";
|
|
4325
|
+
}
|
|
4326
|
+
|
|
4151
4327
|
if (!dt) return t.value; // keep numbers, booleans, lang-tagged strings, etc.
|
|
4152
4328
|
const qdt = pref.shrinkIri(dt);
|
|
4153
4329
|
if (qdt !== null) return `${lex}^^${qdt}`; // e.g. ^^rdf:JSON
|
package/package.json
CHANGED
package/test/api.test.js
CHANGED
|
@@ -672,6 +672,36 @@ ${U('a')} <-${U('p')} ${U('b')}.`,
|
|
|
672
672
|
new RegExp(`_:b2\\s+<${EX}q>\\s+<${EX}joe>\\s*\\.`),
|
|
673
673
|
],
|
|
674
674
|
},
|
|
675
|
+
|
|
676
|
+
{ name: '48 rdf:first: works on list terms (alias of list:first)',
|
|
677
|
+
opt: { proofComments: false }, input: ` { ( ${U('a')} ${U('b')} ${U('c')} ) rdf:first ?x. } => { ${U('s')} ${U('first')} ?x. }.
|
|
678
|
+
`,
|
|
679
|
+
expect: [new RegExp(`${EX}s>\\s+<${EX}first>\\s+<${EX}a>\\s*\\.`)],
|
|
680
|
+
},
|
|
681
|
+
|
|
682
|
+
{ name: '49 rdf:rest: works on list terms (alias of list:rest)',
|
|
683
|
+
opt: { proofComments: false }, input: ` { ( ${U('a')} ${U('b')} ${U('c')} ) rdf:rest ?r. ?r rdf:first ?y. } => { ${U('s')} ${U('second')} ?y. }.
|
|
684
|
+
`,
|
|
685
|
+
expect: [new RegExp(`${EX}s>\\s+<${EX}second>\\s+<${EX}b>\\s*\\.`)],
|
|
686
|
+
},
|
|
687
|
+
|
|
688
|
+
{ name: '50 rdf collection materialization: rdf:first/rdf:rest triples become list terms',
|
|
689
|
+
opt: { proofComments: false }, input: ` ${U('s')} ${U('p')} _:l1.
|
|
690
|
+
_:l1 rdf:first ${U('a')}.
|
|
691
|
+
_:l1 rdf:rest _:l2.
|
|
692
|
+
_:l2 rdf:first ${U('b')}.
|
|
693
|
+
_:l2 rdf:rest rdf:nil.
|
|
694
|
+
|
|
695
|
+
{ ${U('s')} ${U('p')} ?lst. ?lst rdf:first ?x. } => { ${U('s')} ${U('q')} ?x. }.
|
|
696
|
+
{ ${U('s')} ${U('p')} ?lst. ?lst rdf:rest ?r. ?r rdf:first ?y. } => { ${U('s')} ${U('q2')} ?y. }.
|
|
697
|
+
{ ${U('s')} ${U('p')} ?lst. ?lst list:rest ?r. ?r list:first ?y. } => { ${U('s')} ${U('q3')} ?y. }.
|
|
698
|
+
`,
|
|
699
|
+
expect: [
|
|
700
|
+
new RegExp(`${EX}s>\\s+<${EX}q>\\s+<${EX}a>\\s*\\.`),
|
|
701
|
+
new RegExp(`${EX}s>\\s+<${EX}q2>\\s+<${EX}b>\\s*\\.`),
|
|
702
|
+
new RegExp(`${EX}s>\\s+<${EX}q3>\\s+<${EX}b>\\s*\\.`),
|
|
703
|
+
],
|
|
704
|
+
},
|
|
675
705
|
];
|
|
676
706
|
|
|
677
707
|
let passed = 0;
|
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
# ========================================================================================
|
|
2
|
-
# Drone Corridor Planner v2 (example N3 program)
|
|
3
|
-
#
|
|
4
|
-
# What this file is
|
|
5
|
-
# - A tiny “planner” written in Notation3 (N3): it searches for a sequence of
|
|
6
|
-
# actions that moves a drone from a start state to a goal state.
|
|
7
|
-
# - Intended to run in an N3 reasoner that supports forward rules (`=>`),
|
|
8
|
-
# backward rules (`<=`), and a few built-ins (e.g., `math:*`, `list:*`).
|
|
9
|
-
#
|
|
10
|
-
# How to run (one simple option)
|
|
11
|
-
# - With the JavaScript `eyeling` CLI:
|
|
12
|
-
# npx eyeling examples/drone-corridor-planner-v2.n3
|
|
13
|
-
# The output will be newly derived facts, including one or more `gps:plan`
|
|
14
|
-
# results (see “Output” below).
|
|
15
|
-
#
|
|
16
|
-
# Big idea (in plain terms)
|
|
17
|
-
# - We describe a “world” as a small set of facts: location, battery level,
|
|
18
|
-
# and whether the drone has a permit.
|
|
19
|
-
# - We describe each possible action (fly, train, charge, buy/get permit) as a
|
|
20
|
-
# state transition: FROM-state -> TO-state plus some numeric “weights”.
|
|
21
|
-
# - The rules then *compose* these transitions into multi-step plans while
|
|
22
|
-
# aggregating the weights (e.g., add durations, multiply beliefs).
|
|
23
|
-
#
|
|
24
|
-
# Reading the action descriptions
|
|
25
|
-
# - Each action is encoded as a `gps:description` with this shape:
|
|
26
|
-
# ( FROM true TO :actionName duration cost belief comfort )
|
|
27
|
-
# where:
|
|
28
|
-
# - FROM / TO are little graphs in `{ ... }` describing the state.
|
|
29
|
-
# - `true` is a placeholder “precondition” (kept for symmetry with richer
|
|
30
|
-
# variants where extra conditions may appear).
|
|
31
|
-
# - duration/cost/belief/comfort are example numbers used for scoring/pruning.
|
|
32
|
-
#
|
|
33
|
-
# Bounded search (why it doesn’t loop forever)
|
|
34
|
-
# - The planner is *fuel-bounded*: it carries a list of “fuel tokens” and
|
|
35
|
-
# consumes one token per step. When fuel runs out, expansion stops.
|
|
36
|
-
# - This is important because the map can contain cycles (e.g., going back to a
|
|
37
|
-
# previous city). Fuel-bounding keeps the search safe and finite.
|
|
38
|
-
#
|
|
39
|
-
# How scores are combined across a multi-step plan
|
|
40
|
-
# - Duration and cost are summed.
|
|
41
|
-
# - Belief and comfort are multiplied (so long plans tend to reduce them).
|
|
42
|
-
# - The list of actions is built by appending each step’s action name.
|
|
43
|
-
#
|
|
44
|
-
# Output (what you should expect to see)
|
|
45
|
-
# - The “Query” section asks for plans that take the drone from the initial
|
|
46
|
-
# state to the goal (Oostende), then prunes bad plans using thresholds like:
|
|
47
|
-
# belief > ... and cost < ...
|
|
48
|
-
# - Each surviving plan is reported as a derived fact like:
|
|
49
|
-
# :d1 gps:plan ( ...actions... duration cost belief comfort battery permit fuelLeft ).
|
|
50
|
-
#
|
|
51
|
-
# Customizing this example
|
|
52
|
-
# - Add/edit `gps:description` lines to introduce new routes/actions.
|
|
53
|
-
# - Change the initial state (`:d1 :location ...`, `:battery ...`, `:permit ...`).
|
|
54
|
-
# - Adjust `:fuel7` (more/less steps) and the pruning thresholds in the Query.
|
|
55
|
-
#
|
|
56
|
-
# Notes
|
|
57
|
-
# - This is a *toy* corridor-planning model meant to illustrate rule-based
|
|
58
|
-
# planning patterns. The numeric values and “units” are illustrative.
|
|
59
|
-
# ========================================================================================
|
|
60
|
-
|
|
61
|
-
@prefix math: <http://www.w3.org/2000/10/swap/math#>.
|
|
62
|
-
@prefix list: <http://www.w3.org/2000/10/swap/list#>.
|
|
63
|
-
@prefix gps: <https://eyereasoner.github.io/eye/reasoning/gps/gps-schema#>.
|
|
64
|
-
@prefix : <https://eyereasoner.github.io/eye/reasoning#>.
|
|
65
|
-
|
|
66
|
-
# ----------------
|
|
67
|
-
# current state
|
|
68
|
-
# ----------------
|
|
69
|
-
:d1 :location :Gent.
|
|
70
|
-
:d1 :battery :full.
|
|
71
|
-
:d1 :permit :none.
|
|
72
|
-
|
|
73
|
-
# bounded search horizon (7 steps max)
|
|
74
|
-
:fuel7 :value (:t :t :t :t :t :t :t).
|
|
75
|
-
|
|
76
|
-
# -----------------------------------------------------------------
|
|
77
|
-
# "map" / action descriptions (as backward rules)
|
|
78
|
-
# State = { ?S :location ... . ?S :battery ... . ?S :permit ... . }
|
|
79
|
-
# -----------------------------------------------------------------
|
|
80
|
-
# two ways to reach Brugge (tradeoff: cost/comfort)
|
|
81
|
-
{:map-DRONE gps:description (
|
|
82
|
-
{?S :location :Gent. ?S :battery :full. ?S :permit ?P.} true
|
|
83
|
-
{?S :location :Brugge. ?S :battery :mid. ?S :permit ?P.}
|
|
84
|
-
:fly_gent_brugge
|
|
85
|
-
1500.0 0.006 0.99 0.99
|
|
86
|
-
)} <= true.
|
|
87
|
-
|
|
88
|
-
{:map-DRONE gps:description (
|
|
89
|
-
{?S :location :Gent. ?S :battery ?B. ?S :permit ?P.} true
|
|
90
|
-
{?S :location :Brugge. ?S :battery ?B. ?S :permit ?P.}
|
|
91
|
-
:train_gent_brugge
|
|
92
|
-
1700.0 0.012 0.999 0.995
|
|
93
|
-
)} <= true.
|
|
94
|
-
|
|
95
|
-
# via Kortrijk (permit + charging opportunities)
|
|
96
|
-
{:map-DRONE gps:description (
|
|
97
|
-
{?S :location :Gent. ?S :battery :full. ?S :permit ?P.} true
|
|
98
|
-
{?S :location :Kortrijk. ?S :battery :mid. ?S :permit ?P.}
|
|
99
|
-
:fly_gent_kortrijk
|
|
100
|
-
1600.0 0.007 0.99 0.99
|
|
101
|
-
)} <= true.
|
|
102
|
-
|
|
103
|
-
{:map-DRONE gps:description (
|
|
104
|
-
{?S :location :Kortrijk. ?S :battery :mid. ?S :permit ?P.} true
|
|
105
|
-
{?S :location :Brugge. ?S :battery :low. ?S :permit ?P.}
|
|
106
|
-
:fly_kortrijk_brugge
|
|
107
|
-
1600.0 0.007 0.99 0.99
|
|
108
|
-
)} <= true.
|
|
109
|
-
|
|
110
|
-
# cycle edge (safe due to fuel bound; typically pruned by belief threshold)
|
|
111
|
-
{:map-DRONE gps:description (
|
|
112
|
-
{?S :location :Brugge. ?S :battery :mid. ?S :permit ?P.} true
|
|
113
|
-
{?S :location :Kortrijk. ?S :battery :low. ?S :permit ?P.}
|
|
114
|
-
:fly_brugge_kortrijk
|
|
115
|
-
1600.0 0.007 0.985 0.98
|
|
116
|
-
)} <= true.
|
|
117
|
-
|
|
118
|
-
# get permit in Kortrijk (best belief)
|
|
119
|
-
{:map-DRONE gps:description (
|
|
120
|
-
{?S :location :Kortrijk. ?S :battery ?B. ?S :permit :none.} true
|
|
121
|
-
{?S :location :Kortrijk. ?S :battery ?B. ?S :permit :yes.}
|
|
122
|
-
:get_zone_permit_kortrijk
|
|
123
|
-
300.0 0.001 0.999 1.0
|
|
124
|
-
)} <= true.
|
|
125
|
-
|
|
126
|
-
# get permit in Brugge (faster, but lower belief)
|
|
127
|
-
{:map-DRONE gps:description (
|
|
128
|
-
{?S :location :Brugge. ?S :battery ?B. ?S :permit :none.} true
|
|
129
|
-
{?S :location :Brugge. ?S :battery ?B. ?S :permit :yes.}
|
|
130
|
-
:buy_permit_brugge
|
|
131
|
-
450.0 0.002 0.98 1.0
|
|
132
|
-
)} <= true.
|
|
133
|
-
|
|
134
|
-
# charging options
|
|
135
|
-
{:map-DRONE gps:description (
|
|
136
|
-
{?S :location :Brugge. ?S :battery :low. ?S :permit ?P.} true
|
|
137
|
-
{?S :location :Brugge. ?S :battery :full. ?S :permit ?P.}
|
|
138
|
-
:quick_charge_brugge
|
|
139
|
-
600.0 0.004 0.999 0.97
|
|
140
|
-
)} <= true.
|
|
141
|
-
|
|
142
|
-
{:map-DRONE gps:description (
|
|
143
|
-
{?S :location :Brugge. ?S :battery :mid. ?S :permit ?P.} true
|
|
144
|
-
{?S :location :Brugge. ?S :battery :full. ?S :permit ?P.}
|
|
145
|
-
:topup_brugge
|
|
146
|
-
400.0 0.003 0.999 0.98
|
|
147
|
-
)} <= true.
|
|
148
|
-
|
|
149
|
-
{:map-DRONE gps:description (
|
|
150
|
-
{?S :location :Kortrijk. ?S :battery :mid. ?S :permit ?P.} true
|
|
151
|
-
{?S :location :Kortrijk. ?S :battery :full. ?S :permit ?P.}
|
|
152
|
-
:emergency_charge_kortrijk
|
|
153
|
-
500.0 0.003 0.999 0.95
|
|
154
|
-
)} <= true.
|
|
155
|
-
|
|
156
|
-
# to Oostende: three alternatives
|
|
157
|
-
# A) restricted corridor: fastest+comfortable, but requires permit=yes and full battery
|
|
158
|
-
{:map-DRONE gps:description (
|
|
159
|
-
{?S :location :Brugge. ?S :battery :full. ?S :permit :yes.} true
|
|
160
|
-
{?S :location :Oostende. ?S :battery :mid. ?S :permit :yes.}
|
|
161
|
-
:cross_corridor_brugge_oostende
|
|
162
|
-
900.0 0.004 0.98 1.0
|
|
163
|
-
)} <= true.
|
|
164
|
-
|
|
165
|
-
# B) public coastline: no permit required, slower
|
|
166
|
-
{:map-DRONE gps:description (
|
|
167
|
-
{?S :location :Brugge. ?S :battery :mid. ?S :permit ?P.} true
|
|
168
|
-
{?S :location :Oostende. ?S :battery :low. ?S :permit ?P.}
|
|
169
|
-
:public_coastline_brugge_oostende
|
|
170
|
-
1300.0 0.006 0.97 0.96
|
|
171
|
-
)} <= true.
|
|
172
|
-
|
|
173
|
-
{:map-DRONE gps:description (
|
|
174
|
-
{?S :location :Brugge. ?S :battery :full. ?S :permit ?P.} true
|
|
175
|
-
{?S :location :Oostende. ?S :battery :mid. ?S :permit ?P.}
|
|
176
|
-
:public_coastline_brugge_oostende
|
|
177
|
-
1200.0 0.006 0.975 0.96
|
|
178
|
-
)} <= true.
|
|
179
|
-
|
|
180
|
-
# C) Kortrijk shortcut: requires permit and full battery; quicker but lower comfort
|
|
181
|
-
{:map-DRONE gps:description (
|
|
182
|
-
{?S :location :Kortrijk. ?S :battery :full. ?S :permit :yes.} true
|
|
183
|
-
{?S :location :Oostende. ?S :battery :mid. ?S :permit :yes.}
|
|
184
|
-
:direct_corridor_kortrijk_oostende
|
|
185
|
-
1100.0 0.009 0.955 0.92
|
|
186
|
-
)} <= true.
|
|
187
|
-
|
|
188
|
-
# -------------------------------------------------
|
|
189
|
-
# planner: compute all bounded paths
|
|
190
|
-
# -------------------------------------------------
|
|
191
|
-
# Base: one description is a path (consume 1 fuel token)
|
|
192
|
-
{
|
|
193
|
-
(?From ?To (?Act) ?Dur ?Cost ?Bel ?Comf ?FuelIn ?FuelOut) :path true.
|
|
194
|
-
}
|
|
195
|
-
<=
|
|
196
|
-
{
|
|
197
|
-
:map-DRONE gps:description (?From true ?To ?Act ?Dur ?Cost ?Bel ?Comf).
|
|
198
|
-
?FuelIn list:rest ?FuelOut.
|
|
199
|
-
}.
|
|
200
|
-
|
|
201
|
-
# Recursive: chain one step + rest path, aggregate weights, consume 1 fuel token
|
|
202
|
-
{
|
|
203
|
-
(?From ?To ?Actions ?Dur ?Cost ?Bel ?Comf ?FuelIn ?FuelOut) :path true.
|
|
204
|
-
}
|
|
205
|
-
<=
|
|
206
|
-
{
|
|
207
|
-
:map-DRONE gps:description (?From true ?Mid ?Act ?Dur1 ?Cost1 ?Bel1 ?Comf1).
|
|
208
|
-
|
|
209
|
-
?FuelIn list:rest ?FuelMid.
|
|
210
|
-
(?Mid ?To ?RestActs ?Dur2 ?Cost2 ?Bel2 ?Comf2 ?FuelMid ?FuelOut) :path true.
|
|
211
|
-
|
|
212
|
-
((?Act) ?RestActs) list:append ?Actions.
|
|
213
|
-
|
|
214
|
-
(?Dur1 ?Dur2) math:sum ?Dur.
|
|
215
|
-
(?Cost1 ?Cost2) math:sum ?Cost.
|
|
216
|
-
(?Bel1 ?Bel2) math:product ?Bel.
|
|
217
|
-
(?Comf1 ?Comf2) math:product ?Comf.
|
|
218
|
-
}.
|
|
219
|
-
|
|
220
|
-
# -------------------------------------------------
|
|
221
|
-
# Query: plans for d1 to Oostende with pruning thresholds
|
|
222
|
-
# -------------------------------------------------
|
|
223
|
-
{
|
|
224
|
-
:fuel7 :value ?Fuel.
|
|
225
|
-
|
|
226
|
-
({:d1 :location :Gent. :d1 :battery :full. :d1 :permit :none.}
|
|
227
|
-
{:d1 :location :Oostende. :d1 :battery ?B. :d1 :permit ?P.}
|
|
228
|
-
?Acts ?Dur ?Cost ?Bel ?Comf ?Fuel ?FuelLeft) :path true.
|
|
229
|
-
|
|
230
|
-
?Bel math:greaterThan 0.94.
|
|
231
|
-
?Cost math:lessThan 0.03.
|
|
232
|
-
}
|
|
233
|
-
=>
|
|
234
|
-
{
|
|
235
|
-
:d1 gps:plan (?Acts ?Dur ?Cost ?Bel ?Comf ?B ?P ?FuelLeft).
|
|
236
|
-
}.
|
|
237
|
-
|