class: center, middle # Artificial Intelligence ### Logic --- class: center, middle # Data Structures for Logic --- # Data Structures * If we actually want to use logic for something, we need an implementation * We will start with how to store formulas and interpretations in data structures * This will provide us with a way to query the truth-values of logical formulas under an interpretation * Later we will see how we can manipulate these interpretations --- # Data Structures * First we need to represent atoms, for example just as tuples: `\(human(h)\)` becomes `("human", "h")` or `("human", ("h",))` * Then we can store the atoms in a suitable data structure, e.g. a python set, to get our interpretation * Next we need a way to store logical formulas * What is the *structure* of a logical formula? --- # Syntax Trees .left-column[ $$ \forall x: (P(x) \vee Q(x)) \rightarrow \neg R(x) $$ ] .right-column[
] --- # Formula Syntax * We represent our syntax tree using a data structure, e.g. ``` class LogicalFormula: def isModeledBy(self, world): return False ``` * We subclass this for each operator, overriding `isModeledBy` accordingly * For example, the subclass `Atom`, will do `return self.atom in world.atoms` (and store `self.atom` appropriately) * The subclass `And` needs to call `isModeledBy` for all its children, and return `True` iff all of them return `True` --- # Data Structure: And ``` class And(LogicalFormula): def __init__(self, children): self.children = children def isModeledBy(self, world): result = True for c in self.children: result = result and c.isModeledBy(world) return result ``` Note: This "and" can have more than two children, which is a useful optimization! --- class: small # Data Structures * For the quantifiers `\(\forall\)` and `\(\exists\)` we need to be able to substitute variables in formulas, so we need to add another method to our base class: ``` def substitute(self, variable, value): return self ``` * This method should replace the given variable with the given value in *all* children, or parameters (in the case of atoms), and returns a **copy** of itself with the substitution performed * `Forall` can then use this in its `isModeledBy` method ``` def isModeledBy(self, world): values = world.getValues(self.set) for value in values: if not isModeledBy(self.child.substitute(self.variable, value): return False return True ``` --- # Syntax Tree Interpretation
--- # Syntax Tree Interpretation
--- # Syntax Tree Interpretation
--- # Data Structures * Now we can represent logical interpretations and formulas * We can use this to determine which formulas hold under our interpretation * An agent could, for example, use this to collect some true data and then reason about which rules hold (e.g. collect information about all animals and items in a zoo, and then determine if every animal has something to eat) * The other direction is even more interesting, though: Collect *some* information and have the rules, and "generate" more information --- class: center, middle # Inference --- # Inference * Just having a static interpretation of the world is not very interesting * One form of intelligence is to generate/derive "new" knowledge * Or answer questions * This process is called *inference* --- # Inference * Facts: What we know about the world, which includes literals and formulas * Rules: Logical instructions that tell us how we can generate more facts, using properties of logic * You can also imagine this as a logical proof: We know some things about the world, and we prove that some other things logically follow --- class: mediumt # Modus Ponens Maybe the most "famous" inference rule is the Modus Ponens: $$ \{a, a \rightarrow b\} \models b $$ If we know `a` and we know that `a` implies `b`, then we can conclude `b` Note how the notation is a generalization of what we used for interpretations: Any set can be on the left side! --- class: medium # Queries * With this, an agent can start generating facts that exist and fill a database * A user can then ask a "question" in the form of a query: Is x true? * If the agent has the asked fact in their database they answer with yes * By keeping track of the used rules, the agent can also provide a proof! --- # Inference Algorithms * In practice, we may have "many" possible facts * Imagine a logical reasoning system for natural numbers: We can say a number is prime if it has some properties (number of proper divisors is equal to two), but we don't want the agent to generate **all** prime numbers * Instead we can answer queries: Does a particular formula hold, e.g. "Is 9 a prime number"? * There are several different algorithms to answer such queries --- # Resolution * Resolution is one such algorithm to perform inference * The idea is to take disjunctions ("or"s) of logical literals as rules * If we know that one of this literals is false, we can infer that at least one of the others must be true * Also remember that an implication is also an or! $$ a \rightarrow b \Leftrightarrow \neg a \vee b\\\\ \{\neg a \vee b, a\} \models b $$ How to do queries: We negate the query and try to "resolve" it with what we know $$ \{\neg a \vee b, a, \neg b\} \models \bot $$ --- class: mediumt # Prolog Prolog is a logical programming language that allows us to write such disjunctions and then does the inference by resolution for us! ```Prolog a. b :- a. c :- not(b). ?- b true ?- c false ``` --- # Prolog ```Prolog loves(vincent, mia). loves(marcellus, mia). loves(pumpkin, honey_bunny). loves(honey_bunny, pumpkin). jealous(X, Y) :- loves(X, Z), loves(Y, Z). ?- jealous(vincent, marcellus). true ?- loves(vincent, Q). Q = mia ?- jealous(Q, vincent). Q = vincent Q = marcellus false ``` (From [SWISH Prolog Examples](https://swish.swi-prolog.org/example/kb.pl)) --- # Prolog ### 1972 - Alain Colmerauer designs the logic language Prolog. His goal is to create a language with the intelligence of a two year old. He proves he has reached his goal by showing a Prolog session that says "No." to every query. From: [A Brief, Incomplete, and Mostly Wrong History of Programming Languages](http://james-iry.blogspot.com/2009/05/brief-incomplete-and-mostly-wrong.html) --- class: center, middle # Actions --- # Actions? * We often want to represent a world that is changing, rather than just static facts * Simple solution: Let's use constants representing (discrete) times, and increase the arity of each predicate by one to account for time * For example, `\(\mathit{human}(\mathit{socrates})\)` becomes `\(\mathit{human}(\mathit{socrates}, 0)\)`, or `\(\forall t \in T: \mathit{human}(\mathit{socrates}, t)\)` * An *action* is something that changes some truth values over time, often represented as an implication, like `\(\mathit{alive}(\mathit{socrates}, 400 \text{BCE}) \rightarrow \neg \mathit{alive}(\mathit{socrates}, 399 \text{BCE})\)` * This can be generalized with quantifiers and special predicates that indicate when actions occur, if needed --- # The Yale Shooting Problem * Consider Fred, a turkey, and a gun * At time 0, Fred is alive and the gun is unloaded * We load the gun, so that at time 1 the gun is loaded * Then we wait * At time 2 we shoot, such that at time 3 Fred is dead --- # The Yale Shooting Problem $$ \mathit{alive}(\mathit{Fred}, 0)\\\\ \neg \mathit{loaded}(\mathit{Gun}, 0)\\\\ \neg \mathit{loaded}(\mathit{Gun}, 0) \rightarrow \mathit{loaded}(\mathit{Gun}, 1)\\\\ \mathit{loaded}(\mathit{Gun}, 2) \rightarrow \neg \mathit{alive}(\mathit{Fred}, 3) $$ -- What about `\(\mathit{alive}(\mathit{Fred}, 1)\)`? What about `\(\mathit{loaded}(\mathit{Gun}, 2)\)`? Idea: Let's say "the least" number of things change each time step! -- The turkey could still die at time step 1 and stay dead ... --- # The Frame Problem - The Yale Shooting Problem is an illustration of the Frame Problem: Everything that is not changed by an action (the "frame") should stay the same - But how would we even write this in logical formulas? - There are several solutions! - One approach: Frame axioms. For each action state what the action leaves unchanged. This may require a lot of extra formulas. - Another approach: Distinguish states and actions, and represent changes as a transition system --- class: center, middle # Transition Systems --- # Transition System Approach - We start with an initial state `\(s_0 = \{\mathit{alive}(\mathit{Fred}), \neg \mathit{loaded}(\mathit{Gun}) \}\)` (or, using the closed-world assumption simply `\(s_0 = \{\mathit{alive}(\mathit{Fred})\}\)`), which we can use for logical queries such as `\(s_0 \models \mathit{alive}(\mathit{Fred}) \)` - We have an action `load(Gun)` which turns a state into another state: - If the Gun is not loaded, it becomes loaded - If the Gun is already loaded, nothing happens - `\(s_1 = \{\mathit{alive}(\mathit{Fred}), \mathit{loaded}(\mathit{Gun}) \}\)` - Now we can query `\(s_1 \models \mathit{alive}(\mathit{Fred})\)` --- # Transition System Approach - This forms the basis for the planning problem, which we will discuss next time - We can express many interesting problems this way, such as how to distribute packages using trucks and airplanes, determine a robot's actions to perform a task, play games, etc. - However, it also has drawbacks: If our states only represent individual time steps, we can't (easily) query things like "how long was Fred alive" --- # What is an action? * We consider our actions to consist of two parts: a precondition and an effect * The precondition tells us when we can use an action (e.g. we can only shoot if we even have the gun) * The effect tells us what happens (if the gun is loaded, we kill the turkey, otherwise nothing happens) * We can determine if a precondition holds in a world with our `models` function * We also need to be able to apply an effect, which is where `apply` comes in --- class: medium # What is an effect * For simplicity, we will assume our effects are also represented as a logical formula, which tells us what is true after the action * However, we will only support a subset of formulas as possible effects * For example, what would a formula like `\(a \vee b\)` mean as an effect? -- * We'll call the subset of formulas we allow the *effect formulas* * An effect formula can be a conjunction of: - Literals (atoms and negated atoms) - Forall-quantifiers where the inner formula is also an effect formula - `when` expressions --- # Effect Application * Since an effect is just a formula, we could add a method to our base class: ``` def apply(self, world): return changed_world ``` * Depending on the formula, we can then override this method and apply the changes of the subformulas * This may be tricky to get right (particularly negated literals), we'll discuss an alternative in a bit * For `And` you can do something like: ``` def apply(self, world): result = world for c in self.children: result = c.apply(result) return result ``` --- # When-Expressions * Some actions have different effects, depending on what is already true in the world, like firing the gun which as a different effect when the gun is loaded * We could make two actions: `fire-gun-if-loaded` and `fire-gun-if-not-loaded`, but this becomes cumbersome to write * Instead we allow conditional effects that are only applied when a condition holds: $$ \operatorname{when}\:(\phi)\:(\psi) $$ * Note that the condition can be *any* logical formula, but the conditional effect has to be an effect formula --- # When-Expressions Example Say we have the worlds $$ s_0 = \\{\mathit{alive}(\mathit{Fred})\\}\\\\ s_1 = \\{ \mathit{alive}(\mathit{Fred}), \mathit{loaded}(\mathit{Gun}) \\} $$ And the effect of the shoot action: $$ e = \operatorname{when}\:(\mathit{loaded}(\mathit{Gun}))\:(\neg \mathit{alive}(\mathit{Fred}) \wedge \neg \mathit{loaded}(\mathit{Gun}) $$ What happens when we apply e to `\(s_0\)`?
What happens when we apply it to `\(s_1\)`? --- # When-Expressions, Implementation * We can represent a when-expression as a tuple in the Abstract Syntax Tree `("when", x, y)`, where x is the condition and y is the conditional effect * Just like with the logical connectives, we introduce a subclass of `LogicalFormula` that represents a when-expression * When we apply the when-expression to a world, we apply the y-part iff the x-part holds, e.g. ``` class When(LogicalFormula): def apply(self, world): if self.condition.isModeledBy(world): return self.effect.apply(world) return world ``` --- class: medium # Implementing Action Application * There are several ways to implement action application * One is with the `apply` method in all `LogicalFormula` subclasses * Another is to determine the additions and deletions a formula causes and use set operations on the set of atoms ``` class LogicalFormula: def getChanges(self, world): return (self.getAdditions(world),self.getDeletions(world)) ``` * The latter is probably (depending on the exact implementation, and the rest of the code) slightly faster, because it makes fewer copies of the atom set, and you can precompute a lot (minus conditional expressions) --- # References - [Logic and AI](https://plato.stanford.edu/entries/logic-ai/) - [Online Prolog Interpreter](https://swish.swi-prolog.org/) - [The Frame Problem](https://plato.stanford.edu/entries/frame-problem/)