Korrektheit und Vollständigkeit der Kalküle Satz: Die Programmkalküle • Hoare-Kalkül für partielle Korrektheit • Hoare-Kalkül für totale Korrektheit • Dynamische Logik sind korrekt. Z.B. SP ⊢HC ϕ {α} ψ ⇒ SP |= ϕ {α} ψ. Sie sind alle unvollständig, d.h. SP |= ϕ {α} ψ impliziert nicht, dass SP ⊢HC ϕ {α} ψ gilt. 204 Gegenbeispiel zur Vollständigkeit Gegenbeispiel zur Vollständigkeit des Hoare-Kalküls: Nat = data specification nat = 0 | +1 (-1 : nat) end data specification x = 0 ∧ y = y0 { while y 6= 0 do y := y -1; x := x +1 od } x = y0 Schleifeninvariante? x + y = y0 ? Eigentlich schon, aber keine Formel in Nat. 205 Unvollständigkeit der Programmkalküle • Das Problem ist, dass man die Invariante mit den vorhandenen Symbolen nicht ausdrücken kann. • Die Invariante INV ist eine spezielle Induktionshypothese Die Behauptung für n lautet: “Nach n Schleifendurchläufen gilt INV”. • Das Problem ist also dasselbe wie bei den Generiertheitsklauseln. Satz: Die Programmkalküle sind relativ vollständig: Wenn man alle wahren Aussagen über den nat. Zahlen als Axiome hätte, dann könnte jede wahre Aussage über Programmen bewiesen werden. Intuition: • Der Programmkalkül ist nicht „schlimmer“ unvollständig, als die Prädikatenlogik es war. Es fehlen keine Programm-Regeln. • Wenn etwas nicht bewiesen werden kann, dann liegt es nur an fehlenden Hilfsfunktionen/Axiomen für Datenstrukturen. 206 Prozeduren und Heuristiken für Programme 207 Prozeduren: Syntax • Neues Programmkonstrukt : Prozeduraufruf p#(t;y) • p# ist Prozedurname (das # ist übliche KIV-Konvention) • Terme t der Sorten s sind Eingabe-Parameter • Paarweise verschiedene Variablen y der Sorten s′ sind Ein-Ausgabe-Parameter • s : s′ heisst auch der (Aufrufs-)Modus der Prozedur • Prozeduren p# ∈ Ps:s′ sind neuer Bestandteil der Signatur einer Spezifikation • KIV: Deklaration zwischen predicates und variables per: procedures p# s1 × ...× sn : s’1 × ...× s’m; 208 Prozeduren: Semantik • Semantik: Prozeduren sind eine Relation über den Trägern der Parametersorten: [[p#]] ⊆ As × As′ × As′ • (a, b, c) ∈ [[p]] bedeutet: Die Prozedur p#, aufgerufen mit ⋆ Eingaben a für die Eingabe-Variablen ⋆ Eingaben b für die Ein/Ausgabe-Variablen terminiert mit Ausgabe c in den Ein/Ausgabe-Variablen • Damit das stimmt: Kein Zugriff auf globalen Variablen! Ersatz: Zusätzliche Ein/Ausgabe-Parameter • Normalfall in KIV: Funktionale Prozeduren: Ein/Ausgabe-Variablen dienen nur zur Ausgabe: c (und Terminierung) hängen nicht von b ab. • Wenn nicht, Schlüsselwort nonfunctional am Ende der Prozedurdefinition 209 Prozedurdeklarationen • Möglich: Axiome für Prozeduren (Vor- und Nachbedingung) • Normalerweise (hinter den axioms) Prozedurdeklarationen declarations f#(x; y) { if x = 0 then y := 1 else { f#(x -1;y); y := y * x } } • Erlaubt: (gegenseitige) Rekursion • Semantik: Prozeduraufruf erhält “die übliche” Semantik. Formal: Vereinigung aller tiefenbeschränkten Rekursionen (analog zu: Vereinigung über beschränkte Zahl von Schl.durchläufen) 210 Regeln für Prozeduraufrufe Falls Prozedurdeklaration p#(y; z).α gegeben: x y = σ, Γ ⊢ hαz iϕ, ∆ Γ ⊢ hp#(σ; x)iϕ, ∆ x y = σ, hαz iϕ, Γ ⊢ ∆ hp#(σ; x)iϕ, Γ ⊢ ∆ call right call left Dabei: y sind die lokalen Variablen auf denen p# rechnet. Sie dürfen in der Sequenz nicht frei vorkommen (evtl. umbenennen) Die Regel gilt auch für Boxen statt Diamonds. 211 Ein Beispiel procedures MAXL# natlist : nat; MAX# nat, nat : nat; declaration MAX#(m,n; n0) { if m < n then n0 := n else n0 := m }; MAXL#(x; n) { if x = [] then n := 0 else { MAXL#(x.rest; n); MAX#(n,x.first;n) } } 212 Programme als Voraussetzungen: execute call Nützlich bei Induktion, um den Call aus der Induktionsvoraussetzung gegen den gerade aktuellen zu „kürzen“. execute call Γ ⊢ σ = τ, ∆ y ϕx , Γ hp#(σ; x)i(x = y), ⊢ y ψz , ∆ hp#(σ; x)iϕ, Γ ⊢ hp#(τ ; z)iψ, ∆ Gilt (so) nur für funktionale (und damit auch deterministische) Prozeduren (y neu): contract call Γ⊢σ=τ hp#(σ; z)i(z = x x′ ), ϕx ′ hp#(σ; x)iϕ, hp#(τ ; y)iψ, Γ ⊢ ∆ 213 x′ , ψy , Γ ⊢∆ Zwischenzustände einführen: split left Die folgende Regeln wird meist für α = Prozeduraufruf angewandt (x = modifizierte Variablen von α, x′ neu): split left x x ′ , ϕx ′ ,Γ ⊢ ∆ hαix = hαiϕ, Γ ⊢ ∆ • Führt einen Zustand x′ am Ende von α ein, über den man “reden” kann. • Dieser wird bei der Anwendung von Lemmata der Form hαi x = x0 ⊢ ϕ als Instanz für x0 gebraucht 214 Einfache Heuristiken für Programme • symbolic execution: Wendet alle Regeln für Programme an, die keine Fallunterscheidung ergeben: assign, if positive/negative, skip, abort, let • split left: Wendet die Regel split left an • contract and execute: Wendet execute call, contract call an Im Heuristik-Satz „DL heuristics“ enthalten (zusammen mit simplifier, quantifier closing, module specific). Kann immer verwendet werden. 215 Fallunterscheidungs-Heuristiken • conditional right split: wendet if right an • conditional left split: wendet if left an • dl case distinction: Fallunterscheidung (conjunction right etc.), aber nur für Programmformeln Im Heuristik-Satz „DL Heuristics + Case Splitting“ enthalten. Sollte man verwenden, wenn Beweisstruktur der Kontrollstruktur der Programme folgt (meist der Fall). Heuristik-Satz „DL heuristics + Induction“ enthält zusätzlich Heuristiken für (noethersche) Induktion (induction, apply ind once). 216 Heuristiken für Prozeduraufrufe • calls nonrecursive: Führt alle nichtrekursiven Aufrufe aus • calls concrete: Führt alle Aufrufe aus, die konkrete Parameter haben, i. e. Terme die höchstens Parametervariablen enthalten • weak unfold: ⋆ Führt rekursive Prozeduren einmal aus, wenn sie in der Induktionshypothese vorkommen. Höher in der Aufrufshierarchie liegende Aufrufe bevorzugt. ⋆ Weitere Aufrufe werden ausgeführt, wenn festgestellt wird, dass deren Tests so ausgehen, dass kein weiterer rekursiver Aufruf auftritt. • unfold: Führt zusätzlich rekursive Prozeduren (einmal) aus, bei denen der rekursive Aufruf schon in der Sequenz vorkommt „DL Heuristics“ enthält weak unfold, „DL Heuristics + Induction“ enthält zusätzlich unfold. 217 Nichtdeterministische Programme 218 Nichtdet. Programme: Syntax KIV kennt noch zwei Programmkonstrukte für nichtdeterministische Programme: • α or β : Wählt nichtdeterministisch eines der beiden Programme • choose x with ϕ in α ifnone β ⋆ Bindet lokale Variablen x (wie let) an irgendwelche Werte, die ϕ erfüllen ⋆ ϕ darf von anderen Programmvariablen als nur x abhängen ⋆ Führt mit den lokalen Variablen α aus. ⋆ Falls überhaupt keine passenden Werte fur x existieren, die ϕ erfüllen, wird β (ohne lokale Variablen) ausgeführt. ⋆ ifnone abort kann weggelassen werden (default). 219 Beispiele für choose Beispiele: • choose n with true in α: Rät beliebige natürliche Zahl • choose n with n < m in α ifnone β : Wählt natürliche Zahl n, die kleiner m ist, und führt α aus. Wenn m = 0 gilt, wird stattdessen β ausgeführt. • choose boolvar with true in if boolvar then α else β Ist äquivalent zu α or β 220 Nichtdet. Programme: Semantik Semantik von or: [[α or β ]] = [[α]] ∪ [[β ]] Semantik von choose: [[choose x with ϕ in α ifnone β ]] a a v(x) = {(v, wx ) | es gibt a mit A, vx |= ϕ und (vx , w) ∈ [[α]]} a ∪ {(v, w) | (v, w) ∈ [[β ]] und es gibt kein a mit A, vx |= ϕ} 221 Ein Zusatzproblem für die Semantik Was ist die Semantik von skip? Was ist die Semantik von skip or abort? 222 Ein Zusatzproblem für die Semantik Was ist die Semantik von skip? Was ist die Semantik von skip or abort? Antwort: Beide sind gleich: Identität auf allen Zuständen Verhalten sich die Programme unterschiedlich? 222 Ein Zusatzproblem für die Semantik Was ist die Semantik von skip? Was ist die Semantik von skip or abort? Antwort: Beide sind gleich: Identität auf allen Zuständen Verhalten sich die Programme unterschiedlich? Antwort: Ja, skip terminiert garantiert, skip or abort nicht. Also: Die relationale Semantik kann nicht ausdrücken, dass ein nichtdeterministisches Programm garantiert terminiert. Damit kann es auch die dynamische Logik nicht: hskip or aborti true besagt, dass es einen terminierenden Ablauf gibt. 222 Garantierte Terminierung Definieren eine zusätzliche zweite Semantik für Programme: α ↓ ⊆ ST gibt die Menge der Zustände (ST = Menge der Variablenbelegungen), für die α garantiert terminiert. Einige Fälle (while und Rekursion sind schwierig) sind: • abort ↓ = ∅ • skip ↓ = ST • x := e ↓ = ST • (α ∨ β) ↓ = α ↓ ∩ β ↓ • (α; β) ↓ = {v | v ∈ α ↓ und für alle w mit (v, w) ∈ [[α]] gilt: w ∈ β ↓} • choose x with ϕ in α ifnone β ↓ = a a {v | es gibt a mit A, vx |= ϕ und für jedes solche a ist vx ∈ α ↓} a ∪ { v | es gibt kein a mit A, vx |= ϕ und v ∈ β ↓} Beachte: Die Definition der garantierten Terminierung von compounds benutzt die relationale Semantik. 223 Neuer Operator: strong diamond Wir addieren einen neuen Operator („strong diamond“) zur Logik. h|α|i ϕ besagt: α terminiert garantiert, und in allen Endzuständen gilt ϕ Formal: A, v |= h|α|iψ :⇔ v ∈ α ↓ und für alle w : (v, w) ∈ [[α]] gilt: A, w |= ψ . Der Operator wurde von E.W. Dijkstra 1976 erfunden, und schreibt sich in der Literatur meist wp(α,ϕ) (von „weakest precondition“). Der Kalkül heisst deshalb auch wp-Kalkül. Bemerkung: Die strong diamond-Klammern bekommt man mit F12 (KIV-Symbol) und dann { bzw. }. Bemerkung: 224 Kalkülregeln für or Das Gute an strong diamonds: Für deterministische Programme sind die Regeln für strong diamonds genau dieselben wie für diamonds. or right Γ ⊢ hαi ψ, hβi ψ, ∆ Γ ⊢ hα ∨ βi ψ, ∆ Γ ⊢ [α] ψ, ∆ Γ ⊢ [β] ψ, ∆ Γ ⊢ [α ∨ β] ψ, ∆ Γ ⊢ h|α|i ψ, ∆ Γ ⊢ h|β|i ψ, ∆ Γ ⊢ h|α ∨ β|i ψ, ∆ 225 Kalkülregeln für choose choose right Γ⊢∃ y y.ϕx ∧ y hαx i ψ, (∀ x.¬ ϕ) ∧ hβi ψ, ∆ Γ ⊢ hchoose x with ϕ in α ifnone βi ψ, ∆ y ϕx , Γ y [αx ] y ϕx , Γ y h|αx |i ⊢ ψ, ∆ ∀ x.¬ ϕ, Γ ⊢ [β] ψ, ∆ Γ ⊢ [choose x with ϕ in α ifnone β] ψ, ∆ ⊢ ψ, ∆ ∀ x.¬ ϕ, Γ ⊢ h|β|i ψ, ∆ Γ ⊢ h|choose x with ϕ in α ifnone β|i ψ, ∆ Die Variablen y sind neue Variablen (für die lokalen Versionen von x). 226