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/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 triplesListEqual(a.triples, b.triples);
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 treated as opaque here: exact equality
1278
- return triplesListEqual(a.triples, b.triples);
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: unify their internal triple sets (order-insensitive)
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 = localIsoDateTimeString(new Date());
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.5.35",
3
+ "version": "1.5.37",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
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
-