Chase Derivation #
It is time to define the chase.
We are going to introduce slightly different representations and the ChaseDerivation is arguably the most basic but also most versatile one.
As briefly discussed for the ChaseNode already,
the chase starts on an initial fact set and then applies triggers until all triggers are obsolete, which might only be the case after infinitely many steps.
This whole process needs to be "fair", i.e. a trigger that is active needs to be not active after finitely many steps. This can happen by applying the trigger itself or another trigger that will make the former obsolete.
We should note that chase derivations usually start on a Database, i.e. a finite fact set where all terms are constants.
This is what we demand for a ChaseBranch later but for now it is more convenient for us to relax this condition since it is not relevant for
most basic definitions and results.
What we gain by relaxing the condition is that we can drop an abitrary number of ChaseNodes from the beginning of a derivation and still be left with a derivation. This gives the ChaseDerivation a more coinductive behavior that conveniently alignes with the PossiblyInfiniteList that is used to provide the basic structure of the derivation.
The goal of the chase is to eventually build a most general (i.e. universal) model of a given KnowledgeBase by iteratively satisfying all triggers and thereby all rules.
For the more generic ChaseDerivation, we cannot quite expect this as a result but we can already show that the ChaseDerivation.result models all of the rules in ChaseDerivation.result_models_rules.
Chase Step #
To define how one chase step follows from the previous, we introduce two auxiliary definitions that capture two cases that can occur for a given ChaseNode in a ChaseDerivation.
Case: There still exists an
activetrigger, in which case the nextChaseNodeshall be defined accordingly. That is, we add the facts introduced by the trigger to the facts from the previous step and we set the origin of theChaseNodeto the trigger.Case: There is no active trigger. Then the next chase step must be undefined (
none), i.e. theChaseDerivationstops at this point.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
Instances For
The ChaseDerivation Structure #
The backbone of the ChaseDerivation is a PossiblyInfiniteList of ChaseNodes with a couple of conditions.
- We enforce that there is at least an initial
ChaseNode. - At each step in the derivation, either there exists a trigger that yields the next node or there is no trigger and consequently the derivation stops at this point. This is expressed by the two auxiliary definitions above.
- The derivation needs to be fair. That is, for each trigger, there exists a step in the derivation from which on the trigger is not active.
Expressing the conditions in terms of the machinery available form the PossiblyInfiniteList is admittedly quite convoluted.
As part of the framework built around the ChaseDerivation, we will also restate these conditions in a more accessible way. See e.g. ChaseDerivation.fairness'.
- branch : PossiblyInfiniteList (ChaseNode obs rules)
Instances For
Basic Definitions #
Here we introduce some auxiliary definitions and theorems and we lift some of the machinery of the underlying PossiblyInfiniteList to ChaseDerivation.
Membership of ChaseNodes in the ChaseDerivation directly corresponds to membership in the PossiblyInfiniteList.
Equations
- ChaseDerivation.instMembershipChaseNode = { mem := fun (cd : ChaseDerivation obs rules) (node : ChaseNode obs rules) => node ∈ cd.branch }
Each suffix of the underlying PossiblyInfiniteList is itself a ChaseDerivation as long as its head is not none.
Equations
- cd.derivation_for_branch_suffix l2 suffix l2_head_some = { branch := l2, isSome_head := l2_head_some, triggers_exist := ⋯, fairness := ⋯ }
Instances For
The head of the ChaseDerivation is the initial ChaseNode. We know that this is never none.
Instances For
The head is a member.
The "next" ChaseNode #
For a ChaseDerivation derivation, its next node is the ChaseNode immediately following the head.
We mainly introduce a couple of theorems here that abstract away the triggers_exist condition from the ChaseDerivation definition.
Instances For
The next node is a member.
The next node is not none if and only if some trigger is active on head.
The fact set of the next ChaseNode consists exactly of the facts from head and the result of the trigger that introduces next.
ChaseDerivation Suffixes #
We define a suffix relation on ChaseDerivation simply as the suffix relation of the underlying PossiblyInfiniteList.
We also use this to state the fairness condition more nicely.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Members of our suffix are also our members.
Each ChaseNode in the ChaseDerivation induces a subderivation.
Fairness can be stated as: For each trigger, there exists a subderivation such that the trigger is not active for all nodes in the subderivation.
Tail #
If next exists, we can drop the first element from the ChaseDerivation and obtain a new ChaseDerivation, which, inspired by the PossiblyInfiniteList, we call the tail.
Equations
- cd.tail next_some = cd.derivation_for_branch_suffix cd.branch.tail ⋯ next_some
Instances For
A node is a member if and only if it is either the head or it is a member of the tail.
A derivation is a suffix of another if and only if both are the same or the first is a suffix of the second's tail.
Induction Principle for Members #
Similar to PossiblyInfiniteList.mem_rec, we define an induction principle to show
properties of ChaseNodes in a ChaseDerivation.
For this, we introduce ChaseDerivatio.Node as the subtype of ChaseNode that features a membership proof.
Instances For
A Node of our suffix can be cast into our Node type.
Equations
- ChaseDerivation.Node.cast_suffix suffix node = ⟨node.val, ⋯⟩
Instances For
If we want to show a motive for all nodes in a derivation, it is enough to show the motive for the head and for the next node in each abitrary subderivation where the motive already holds for the head. This can be used with the induction tactic.
A node is a member of the tail if and only if there is a subderivation where the node is in the next position. Part of this proof uses the induction principle defined above.
Subset Monotonicity of Facts in ChaseNodes #
Since ChaseNodes always extend the previous facts, the fact sets can only be growing along the ChaseDerivation.
This has a couple of convenient implications. For example, we can never see the same ChaseNode twice as otherwise the trigger in second occurrence would already be obsolete.
Each member's facts contain the head facts. Note that this extends to arbitrary pairs of members since each member always induces a subderivation where it acts as the head.
A first implication of facts_node_subset_every_mem is that, considering one of our subderivations, each of our members either has all of its facts contained in the head of the subderivation or it is itself a member of the subderivation.
The head cannot occur in the tail. Otherwise, it would be introduced using a trigger but then this trigger is already obsolete since all the facts from head already occur in the very beginning. We use ObsoletenessCondition.contains_trg_result_implies_cond here.
By head_not_mem_tail, if we have a suffix but our head occurs in the suffix, then our suffix is equal to us.
And now by eq_of_suffix_of_head_mem, if we have two suffixes $C$ and $D$, and the head of $D$ occurs in $C$, then $D$ is a suffix of $C$. This may seem obvious even without the above results but it is really not even though it aligns well with one's intuition. Imagine that a ChaseNode could occur multiple times in a ChaseDerivation. Then we may have a case where $C$ is a subderivation of $D$, $D$ is not a subderivation of $C$ but $D$ occurs in $C$.
Predecessor Relation #
Since we learned that ChaseNodes cannot occur multiple times in the same ChaseDerivation,
we can define predecessor relation (≼), which is a total order on ChaseDerivation.Node.
This is particularly nice, since we can do this while completely ignoring indices of the nodes in the underlying PossiblyInfiniteList.
A node $n$ is a predecessor of a node $m$ if there is a subderivation there $n$ is the head and $m$ is an arbitrary member.
Equations
Instances For
Equations
- ChaseDerivation.«term_≼_» = Lean.ParserDescr.trailingNode `ChaseDerivation.«term_≼_» 50 50 (Lean.ParserDescr.binary `andthen (Lean.ParserDescr.symbol " ≼ ") (Lean.ParserDescr.cat `term 51))
Instances For
The predecessor relation is stable across suffixes. That is, predecessor in our suffix are also predecessor for us. We only need to cast the nodes.
The predecessor relation is reflexive.
The predecessor relation is antisymmetric.
The predecessor relation is transitive.
The predecessor relation is total.
The facts of our predecessor are a subset of our facts.
We can express fairness in terms of the predecessor relation: For each trigger, there is a node such that the trigger is not active for each of the node's successors.
We also define a strict version of the predecessor relation (≺) in the obvious way.
By that, we obtain a strict total order on ChaseDerivation.Node.
A node is a strict predecessor of another if it is a predecessor but not equal.
Instances For
Equations
- ChaseDerivation.«term_≺_» = Lean.ParserDescr.trailingNode `ChaseDerivation.«term_≺_» 50 50 (Lean.ParserDescr.binary `andthen (Lean.ParserDescr.symbol " ≺ ") (Lean.ParserDescr.cat `term 51))
Instances For
As for the predecessor relation, we can show that the relation is stable across suffixes given that we cast the nodes.
The strict predecessor relation is irreflexive.
The strict predecessor relation is asymmetric.
The strict predecessor relation is transitive.
The strict predecessor relation is total.
A predecessor is either equal or a strict predecessor.
The facts of a strict successor cannot be a subset of our facts. This is because strict successor nodes can only be introduced by active triggers. But if a trigger only produces facts that already exist, then it cannot be active.
Chase Result #
Here, we define the result of a ChaseDerivation, which is simply the FactSet that is the union of all facts of all ChaseNodes.
Instances For
Every node's facts occur in the result.
The result of our suffix is the same our result.
For each (finite) list of facts in the result, there is a node that that contains all of them.
If a trigger is loaded for the result, then it is loaded for some node in the derivation.
The result is a model of all rules. This is true because otherwise, there would be an active trigger on the result. But then, this trigger needs to be active for some node and the hence it needs to become inactive at some point due to fairness. Hence it cannot be active on the result. Contradiction!
Terms in the Chase #
We make some general observations about certain terms that might occur in the chase.
- Constants can only originate directly from rules or from the initial fact set. No other constants can be introduced.
- Functional terms can either also originate from the initial fact set or they are introduced as fresh terms by a trigger.
The second observation entails that the precense of a functional term that does not occur in the initial fact set implies
that the trigger that introduces this term must have been applied in some ChaseNode.
Constants in the chase can only come from the initial fact set or from a constant in a rule.
Each functional term in the chase originates as a fresh term from a trigger if it was not already part of the initial fact set.
If a functional term occurs in the chase, then the trigger that introduces this term must have been used in the chase, unless the term already occurs in the initial fact set.
If a functional term occurs in the chase, then the result of the trigger that introduces this term is contained in the current node, unless the functional term already occurs in the initial fact set.
Minimal Nodes with given Properties #
If a property hold for a given node in the chase, then there must be a "first" node for which this property holds. That means that this node is minimal with respect to the ≺ relation.
The result itself is prop_for_node_has_minimal_such_node and is shown via Nat.prop_for_nat_has_minimal_such_nat.
This forces us to take into account specific node indices and therefore requires auxiliary theorems, mainly get?_branch_injective, which is shown using node_has_unique_position.