Refactor (2)
This commit is contained in:
156
packages/kripke/src/semantics.ts
Normal file
156
packages/kripke/src/semantics.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import { BitSet, permutate } from "@cannorin/utils";
|
||||
import type { Formula, PropVar } from "./syntax";
|
||||
|
||||
export const worlds = ["a", "b", "c", "d"] as const;
|
||||
|
||||
export type World = (typeof worlds)[number];
|
||||
|
||||
export type Relation = `${World}${World}`;
|
||||
export const left = (rel: Relation) => rel[0] as World;
|
||||
export const right = (rel: Relation) => rel[1] as World;
|
||||
export const reverse = (rel: Relation) =>
|
||||
`${right(rel)}${left(rel)}` as Relation;
|
||||
|
||||
export const relation: Relation[] = worlds.flatMap((w) =>
|
||||
worlds.map((x) => `${w}${x}` as const),
|
||||
);
|
||||
|
||||
const relationSet = new BitSet(relation);
|
||||
|
||||
export interface Frame {
|
||||
relations: Set<Relation>;
|
||||
}
|
||||
|
||||
export interface Model extends Frame {
|
||||
valuations: Set<`${World}${PropVar}`>;
|
||||
}
|
||||
|
||||
export function satisfy(m: Model, w: World, fml: Formula): boolean {
|
||||
switch (fml.type) {
|
||||
case "top":
|
||||
return true;
|
||||
case "bot":
|
||||
return false;
|
||||
case "propvar":
|
||||
return m.valuations.has(`${w}${fml.name}`);
|
||||
case "not":
|
||||
return !satisfy(m, w, fml.fml);
|
||||
case "box": {
|
||||
for (const rel of m.relations.values()) {
|
||||
if (left(rel) !== w) continue;
|
||||
if (!satisfy(m, right(rel), fml.fml)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case "diamond": {
|
||||
for (const rel of m.relations.values()) {
|
||||
if (left(rel) !== w) continue;
|
||||
if (satisfy(m, right(rel), fml.fml)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case "to": {
|
||||
if (!satisfy(m, w, fml.left)) return true;
|
||||
return satisfy(m, w, fml.right);
|
||||
}
|
||||
case "or": {
|
||||
if (satisfy(m, w, fml.left)) return true;
|
||||
return satisfy(m, w, fml.right);
|
||||
}
|
||||
case "and": {
|
||||
if (!satisfy(m, w, fml.left)) return false;
|
||||
return satisfy(m, w, fml.right);
|
||||
}
|
||||
case "eq": {
|
||||
return satisfy(m, w, fml.left) === satisfy(m, w, fml.right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const validInModel = (m: Model, fml: Formula) =>
|
||||
worlds.every((w) => satisfy(m, w, fml));
|
||||
|
||||
export function getFrame(id: number): Frame {
|
||||
return { relations: relationSet.decode(id) };
|
||||
}
|
||||
|
||||
export function getId(frame: Frame) {
|
||||
return relationSet.encode(frame.relations);
|
||||
}
|
||||
|
||||
const worldPermutations = permutate(worlds).map(
|
||||
(perm) => new Map(worlds.map((k, i) => [k, perm[i] as World])),
|
||||
);
|
||||
|
||||
function applyPermutation<T extends Frame>(
|
||||
frame: T,
|
||||
permutation: Map<World, World>,
|
||||
) {
|
||||
const relations = new Set<Relation>();
|
||||
for (const rel of frame.relations) {
|
||||
const l = left(rel);
|
||||
const r = right(rel);
|
||||
relations.add(
|
||||
`${permutation.get(l) ?? l}${permutation.get(r) ?? r}` as Relation,
|
||||
);
|
||||
}
|
||||
return { ...frame, relations } as T;
|
||||
}
|
||||
|
||||
export function generateAllFrames() {
|
||||
const canonicals: number[] = [];
|
||||
const nontrivials: number[] = [];
|
||||
const map = new Map<number, number>();
|
||||
|
||||
const total = 2 ** relation.length;
|
||||
for (let id = 0; id < total; id++) {
|
||||
if (map.has(id)) continue;
|
||||
|
||||
const relations = relationSet.decode(id);
|
||||
|
||||
const frame = { relations };
|
||||
const equivalentIds: number[] = [];
|
||||
|
||||
let canonicalId = id;
|
||||
for (const perm of worldPermutations) {
|
||||
const permuted = applyPermutation(frame, perm);
|
||||
const permutedId = relationSet.encode(permuted.relations);
|
||||
equivalentIds.push(permutedId);
|
||||
if (canonicalId === null || permutedId < canonicalId) {
|
||||
canonicalId = permutedId;
|
||||
}
|
||||
}
|
||||
|
||||
canonicals.push(canonicalId);
|
||||
for (const equivalentId of equivalentIds) {
|
||||
map.set(equivalentId, canonicalId);
|
||||
}
|
||||
|
||||
// Exclude the sizes with less than 10 frames (= can be bruteforced)
|
||||
if (relations.size >= 3 && relations.size <= 13) {
|
||||
nontrivials.push(canonicalId);
|
||||
}
|
||||
}
|
||||
|
||||
const isomorphic: Uint16Array = new Uint16Array(total);
|
||||
for (let id = 0; id < total; id++) {
|
||||
const value = map.get(id);
|
||||
if (value === undefined) throw Error(`impossible (${id})`);
|
||||
isomorphic[id] = value;
|
||||
}
|
||||
|
||||
return { canonicals, isomorphic, nontrivials };
|
||||
}
|
||||
|
||||
const allFrames = generateAllFrames();
|
||||
|
||||
/** canonical frames among all the isomorphic ones */
|
||||
const canonicals = allFrames.canonicals;
|
||||
|
||||
/** mapping to get the canonical frame */
|
||||
const isomorphic = allFrames.isomorphic;
|
||||
|
||||
/** frames that are suitable for the puzzle */
|
||||
const nontrivials = allFrames.nontrivials;
|
||||
|
||||
export { canonicals, isomorphic, nontrivials };
|
||||
Reference in New Issue
Block a user