Source code for unicode_fol_kit.semantics.dynamic_epistemic

"""Public announcement logic (PAL) — dynamic epistemic model update.

Static epistemic logic (``Knows`` over a Kripke model) describes *what agents know*;
**public announcement logic** adds the dynamics: a truthful public announcement of
``φ`` removes every world where ``φ`` is false, so the agents' knowledge changes. The
announcement operator ``[φ!]ψ`` ("after announcing φ, ψ holds") has the truth
condition::

    M, w ⊨ [φ!] ψ   iff   M, w ⊨ φ  implies  M|φ, w ⊨ ψ

where ``M|φ`` is :func:`announce` — ``M`` restricted to its ``φ``-worlds (relations and
valuation cut down to the survivors). The dual ``⟨φ!⟩ψ`` ("φ is true and after
announcing it ψ holds") is ``φ ∧ M|φ,w ⊨ ψ``. Announcements compose, so iterated
updates and the Moore-sentence phenomenon (announcing ``p ∧ ¬K_a p`` makes it false)
fall straight out.

Built on :func:`unicode_fol_kit.semantics.kripke.satisfies_modal`; the announced and
post formulas are ordinary (propositional, epistemic, …) modal formulas.

Public API: :func:`announce`, :func:`box_announce`, :func:`diamond_announce`.
"""

from typing import Any

from ..fol.nodes import Node
from .kripke import KripkeModel, satisfies_modal


[docs] def announce(model: KripkeModel, formula: Node) -> KripkeModel: """Return ``model`` updated by a truthful public announcement of ``formula``. The result keeps exactly the worlds where ``formula`` is true (under :func:`satisfies_modal`); every relation is restricted to those survivors and the valuation (and per-world object domains, if any) likewise. Inputs are not mutated. """ survivors = frozenset(w for w in model.worlds if satisfies_modal(formula, model, w)) relations = { name: {(a, b) for (a, b) in edges if a in survivors and b in survivors} for name, edges in model.relations.items() } valuation = {w: set(model.atoms_true_at(w)) for w in survivors} domains = None if model.domains is not None: domains = {w: set(model.domains.get(w, frozenset())) for w in survivors} return KripkeModel(survivors, relations, valuation, domains=domains)
[docs] def box_announce(model: KripkeModel, world: Any, announcement: Node, post: Node) -> bool: """Return whether ``model, world ⊨ [announcement!] post`` (PAL box). Vacuously true when the announcement is false at ``world`` (an untruthful announcement is not made); otherwise ``post`` must hold at ``world`` in the updated model :func:`announce`. """ if not satisfies_modal(announcement, model, world): return True updated = announce(model, announcement) return satisfies_modal(post, updated, world)
[docs] def diamond_announce(model: KripkeModel, world: Any, announcement: Node, post: Node) -> bool: """Return whether ``model, world ⊨ ⟨announcement!⟩ post`` (PAL diamond). True iff the announcement is truthful at ``world`` **and** ``post`` then holds at ``world`` in the updated model — the dual of :func:`box_announce`. """ if not satisfies_modal(announcement, model, world): return False updated = announce(model, announcement) return satisfies_modal(post, updated, world)