1use crate::NQuin;
2
3pub fn possible(graph: &[NQuin], world: u64, prop: u64, accesses: u64, holds: u64) -> bool {
14 for q in graph {
15 if q.subject == world && q.predicate == accesses {
16 let w2 = q.object;
17 if graph
18 .iter()
19 .any(|r| r.subject == w2 && r.predicate == holds && r.object == prop)
20 {
21 return true;
22 }
23 }
24 }
25 false
26}
27
28pub fn necessary(graph: &[NQuin], world: u64, prop: u64, accesses: u64, holds: u64) -> bool {
31 for q in graph {
32 if q.subject == world && q.predicate == accesses {
33 let w2 = q.object;
34 if !graph
35 .iter()
36 .any(|r| r.subject == w2 && r.predicate == holds && r.object == prop)
37 {
38 return false; }
40 }
41 }
42 true
43}
44
45#[inline]
53fn edge(graph: &[NQuin], from: u64, to: u64, accesses: u64) -> bool {
54 graph
55 .iter()
56 .any(|q| q.subject == from && q.predicate == accesses && q.object == to)
57}
58
59pub fn is_reflexive(graph: &[NQuin], accesses: u64, worlds: &[u64]) -> bool {
61 worlds.iter().all(|&w| edge(graph, w, w, accesses))
62}
63
64pub fn is_serial(graph: &[NQuin], accesses: u64, worlds: &[u64]) -> bool {
66 worlds
67 .iter()
68 .all(|&w| worlds.iter().any(|&v| edge(graph, w, v, accesses)))
69}
70
71pub fn is_symmetric(graph: &[NQuin], accesses: u64, worlds: &[u64]) -> bool {
73 for &w in worlds {
74 for &v in worlds {
75 if edge(graph, w, v, accesses) && !edge(graph, v, w, accesses) {
76 return false;
77 }
78 }
79 }
80 true
81}
82
83pub fn is_transitive(graph: &[NQuin], accesses: u64, worlds: &[u64]) -> bool {
85 for &w in worlds {
86 for &v in worlds {
87 if !edge(graph, w, v, accesses) {
88 continue;
89 }
90 for &u in worlds {
91 if edge(graph, v, u, accesses) && !edge(graph, w, u, accesses) {
92 return false;
93 }
94 }
95 }
96 }
97 true
98}
99
100pub fn is_euclidean(graph: &[NQuin], accesses: u64, worlds: &[u64]) -> bool {
102 for &w in worlds {
103 for &v in worlds {
104 if !edge(graph, w, v, accesses) {
105 continue;
106 }
107 for &u in worlds {
108 if edge(graph, w, u, accesses) && !edge(graph, v, u, accesses) {
109 return false;
110 }
111 }
112 }
113 }
114 true
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub enum ModalSystem {
120 K,
122 T,
124 D,
126 B,
128 S4,
130 S5,
132}
133
134pub fn validates(system: ModalSystem, graph: &[NQuin], accesses: u64, worlds: &[u64]) -> bool {
136 match system {
137 ModalSystem::K => true,
138 ModalSystem::T => is_reflexive(graph, accesses, worlds),
139 ModalSystem::D => is_serial(graph, accesses, worlds),
140 ModalSystem::B => is_symmetric(graph, accesses, worlds),
141 ModalSystem::S4 => {
142 is_reflexive(graph, accesses, worlds) && is_transitive(graph, accesses, worlds)
143 }
144 ModalSystem::S5 => {
145 is_reflexive(graph, accesses, worlds)
146 && is_symmetric(graph, accesses, worlds)
147 && is_transitive(graph, accesses, worlds)
148 }
149 }
150}
151
152#[inline]
161pub fn knows(graph: &[NQuin], accesses_i: u64, world: u64, prop: u64, holds: u64) -> bool {
162 necessary(graph, world, prop, accesses_i, holds)
163}
164
165pub fn everyone_knows(
167 graph: &[NQuin],
168 agent_accesses: &[u64],
169 world: u64,
170 prop: u64,
171 holds: u64,
172) -> bool {
173 agent_accesses
174 .iter()
175 .all(|&acc| knows(graph, acc, world, prop, holds))
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
186pub struct Belief {
187 pub atom: u64,
188 pub positive: bool,
189}
190
191impl Belief {
192 #[inline]
194 pub fn negate(self) -> Belief {
195 Belief {
196 atom: self.atom,
197 positive: !self.positive,
198 }
199 }
200}
201
202pub fn is_consistent(set: &[Belief]) -> bool {
204 for (i, a) in set.iter().enumerate() {
205 for b in &set[i + 1..] {
206 if a.atom == b.atom && a.positive != b.positive {
207 return false;
208 }
209 }
210 }
211 true
212}
213
214pub fn expand(set: &[Belief], belief: Belief, out: &mut [Belief]) -> usize {
217 let mut n = 0usize;
218 for &x in set {
219 if n < out.len() {
220 out[n] = x;
221 n += 1;
222 }
223 }
224 if !set.contains(&belief) && n < out.len() {
225 out[n] = belief;
226 n += 1;
227 }
228 n
229}
230
231pub fn contract(set: &[Belief], belief: Belief, out: &mut [Belief]) -> usize {
234 let mut n = 0usize;
235 for &x in set {
236 if x == belief {
237 continue;
238 }
239 if n < out.len() {
240 out[n] = x;
241 n += 1;
242 }
243 }
244 n
245}
246
247pub fn revise(set: &[Belief], belief: Belief, out: &mut [Belief]) -> usize {
251 let mut n = 0usize;
252 for &x in set {
253 if x.atom == belief.atom {
254 continue; }
256 if n < out.len() {
257 out[n] = x;
258 n += 1;
259 }
260 }
261 if n < out.len() {
262 out[n] = belief;
263 n += 1;
264 }
265 n
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271
272 fn acc(from: u64, to: u64) -> NQuin {
273 let mut q = NQuin {
274 subject: from,
275 predicate: crate::q_hash("modal:accesses"),
276 object: to,
277 context: 0,
278 metadata: 0,
279 parity: 0,
280 };
281 q.parity = q.subject ^ q.predicate ^ q.object ^ q.context;
282 q
283 }
284 fn label(world: u64, prop: u64) -> NQuin {
285 let mut q = NQuin {
286 subject: world,
287 predicate: crate::q_hash("modal:holds"),
288 object: prop,
289 context: 0,
290 metadata: 0,
291 parity: 0,
292 };
293 q.parity = q.subject ^ q.predicate ^ q.object ^ q.context;
294 q
295 }
296
297 #[test]
298 fn box_and_diamond() {
299 let accesses = crate::q_hash("modal:accesses");
300 let holds = crate::q_hash("modal:holds");
301 let p = 100u64;
302 let g = [acc(0, 1), acc(0, 2), label(1, p)];
304 assert!(
305 possible(&g, 0, p, accesses, holds),
306 "◇p: w1 (accessible) satisfies p"
307 );
308 assert!(
309 !necessary(&g, 0, p, accesses, holds),
310 "□p fails: w2 (accessible) does not satisfy p"
311 );
312 let g2 = [acc(0, 1), acc(0, 2), label(1, p), label(2, p)];
314 assert!(
315 necessary(&g2, 0, p, accesses, holds),
316 "□p: all accessible worlds satisfy p"
317 );
318 }
319
320 #[test]
321 fn frame_properties_and_axiom_systems() {
322 let accesses = crate::q_hash("modal:accesses");
323 let worlds = [0u64, 1u64, 2u64];
324
325 let mut s5 = Vec::new();
328 for &w in &worlds {
329 for &v in &worlds {
330 s5.push(acc(w, v));
331 }
332 }
333 assert!(is_reflexive(&s5, accesses, &worlds));
334 assert!(is_symmetric(&s5, accesses, &worlds));
335 assert!(is_transitive(&s5, accesses, &worlds));
336 assert!(is_euclidean(&s5, accesses, &worlds));
337 assert!(validates(ModalSystem::S5, &s5, accesses, &worlds));
338 assert!(validates(ModalSystem::S4, &s5, accesses, &worlds));
339 assert!(validates(ModalSystem::K, &s5, accesses, &worlds));
340
341 let chain = [acc(0, 1), acc(1, 2)];
345 assert!(!is_reflexive(&chain, accesses, &worlds));
346 assert!(!is_symmetric(&chain, accesses, &worlds));
347 assert!(!is_transitive(&chain, accesses, &worlds));
348 assert!(!validates(ModalSystem::T, &chain, accesses, &worlds));
349 assert!(!validates(ModalSystem::B, &chain, accesses, &worlds));
350 assert!(!validates(ModalSystem::D, &chain, accesses, &worlds));
351 assert!(validates(ModalSystem::K, &chain, accesses, &worlds)); let s4 = [
355 acc(0, 0),
356 acc(1, 1),
357 acc(2, 2),
358 acc(0, 1),
359 acc(1, 2),
360 acc(0, 2),
361 ];
362 assert!(is_reflexive(&s4, accesses, &worlds));
363 assert!(is_transitive(&s4, accesses, &worlds));
364 assert!(!is_symmetric(&s4, accesses, &worlds));
365 assert!(validates(ModalSystem::S4, &s4, accesses, &worlds));
366 assert!(!validates(ModalSystem::S5, &s4, accesses, &worlds));
367 assert!(validates(ModalSystem::T, &s4, accesses, &worlds));
368 assert!(validates(ModalSystem::D, &s4, accesses, &worlds)); }
370
371 #[test]
372 fn multi_agent_knowledge_is_per_relation() {
373 let holds = crate::q_hash("modal:holds");
374 let alice = crate::q_hash("agent:alice:accesses");
375 let bob = crate::q_hash("agent:bob:accesses");
376 let p = 100u64;
377 let mk = |from: u64, to: u64, acc: u64| {
378 let mut q = NQuin {
379 subject: from,
380 predicate: acc,
381 object: to,
382 context: 0,
383 metadata: 0,
384 parity: 0,
385 };
386 q.parity = q.subject ^ q.predicate ^ q.object ^ q.context;
387 q
388 };
389 let g = [mk(0, 1, alice), mk(0, 1, bob), mk(0, 2, bob), label(1, p)];
391 assert!(
392 knows(&g, alice, 0, p, holds),
393 "Alice knows p (all her worlds satisfy it)"
394 );
395 assert!(
396 !knows(&g, bob, 0, p, holds),
397 "Bob does not know p (w2 fails)"
398 );
399 assert!(!everyone_knows(&g, &[alice, bob], 0, p, holds));
400 }
401
402 #[test]
403 fn agm_revision_is_consistent_and_satisfies_success() {
404 let p = 1u64;
405 let bel_p = Belief {
406 atom: p,
407 positive: true,
408 };
409 let bel_not_p = Belief {
410 atom: p,
411 positive: false,
412 };
413 let q = Belief {
414 atom: 2,
415 positive: true,
416 };
417 let mut out = [Belief {
418 atom: 0,
419 positive: true,
420 }; 8];
421
422 let base = [bel_not_p, q];
424 let n = revise(&base, bel_p, &mut out);
425 let result = &out[..n];
426 assert!(result.contains(&bel_p), "success postulate: φ ∈ K*φ");
428 assert!(!result.contains(&bel_not_p), "consistency: ¬φ removed");
429 assert!(
430 result.contains(&q),
431 "minimal change: unrelated beliefs kept"
432 );
433 assert!(is_consistent(result));
434
435 let n2 = expand(&base, q, &mut out);
437 assert_eq!(n2, 2, "q already present → no growth");
438 let n3 = expand(&base, bel_p, &mut out);
439 assert_eq!(
440 n3, 3,
441 "p added by expansion (may be inconsistent — that's expansion, not revision)"
442 );
443 assert!(
444 !is_consistent(&out[..n3]),
445 "expansion does NOT guarantee consistency"
446 );
447
448 let n4 = contract(&base, bel_not_p, &mut out);
450 assert!(!out[..n4].contains(&bel_not_p));
451 assert_eq!(
452 contract(&base, bel_p, &mut out),
453 2,
454 "contracting an absent belief is vacuous"
455 );
456 }
457}