Documentation

ExistentialRules.Triggers.Basic

PreTriggers #

Triggers are one of the most essential definitions for the chase. They are our primary way for modelling specific applications of rules. Quite simply, a trigger is just a pair of a rule and a substitution that tells us how variables should be replaced. For actual triggers, we will require a way to tell if they should still be applied or not. We refer to this with the notion of "obsolescence" later. However, most of the machinery around triggers can be introduced agnostic of any kind of obsolescence. Consequently, we call the "almost trigger" a PreTrigger.

A trigger is self contained in the sense that it "knows" what its result will be independant of the chase context. This would not be so simple if we considered nulls instead of Skolem terms.

structure PreTrigger (sig : Signature) [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] :
Type (max (max u_1 u_2) u_3)

A PreTrigger is nothing more than a pair of a Rule and a GroundSubstitution.

Instances For

    The mapped_frontier results from applying the triggers substitution to all frontier variables.

    Equations
    Instances For
      @[simp]

      The lenth of the mapped_frontier is exactly the length of the frontier.

      theorem PreTrigger.apply_mapping_after_mapped_frontier {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg : PreTrigger sig} {mapping : TermMapping (GroundTerm sig) (GroundTerm sig)} :
      List.map mapping trg.mapped_frontier = { rule := trg.rule, subs := mapping trg.subs }.mapped_frontier

      Applying a term mapping after the trigger can be combined with the substitution without affecting the mapped_frontier.

      def PreTrigger.skolemize_var_or_const {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] (trg : PreTrigger sig) (i : Nat) (lt : i < trg.rule.head.length) (var_or_const : VarOrConst sig) :

      In the context of a trigger, we Skolemize a VarOrConst by passing the rule in the trigger.

      Equations
      Instances For

        We apply a trigger to a VarOrConst by Skolemizing and then applying the GroundSubstitution from the trigger. We need this to define the trigger result later.

        Equations
        Instances For
          @[simp]

          Applying a trigger to a constant changes nothing.

          @[simp]

          Applying a trigger to an non-existential variable does not skolemize but merely applied the substitution.

          @[simp]
          theorem PreTrigger.apply_to_var_or_const_frontier_var {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] (trg : PreTrigger sig) (i : Nat) (lt : i < trg.rule.head.length) (v : sig.V) :

          Applying a trigger to a frontier variable does not skolemize but merely applied the substitution.

          def PreTrigger.functional_term_for_var {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] (trg : PreTrigger sig) (i : Nat) (lt : i < trg.rule.head.length) (v : sig.V) (v_mem : v trg.rule.existential_vars_for_head_disjunct i lt) :

          A shortcut definition for how the Skolem term resulting from applying the trigger to an existential variable will look.

          Equations
          Instances For
            theorem PreTrigger.functional_term_for_var.inj {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg : PreTrigger sig} {i1 i2 : Nat} {lt1 : i1 < trg.rule.head.length} {lt2 : i2 < trg.rule.head.length} {v1 : sig.V} {v1_mem : v1 trg.rule.existential_vars_for_head_disjunct i1 lt1} {v2 : sig.V} {v2_mem : v2 trg.rule.existential_vars_for_head_disjunct i2 lt2} :
            trg.functional_term_for_var i1 lt1 v1 v1_mem = trg.functional_term_for_var i2 lt2 v2 v2_memi1 = i2 v1 = v2

            The functional_term_for_var function is injective for a fixed trigger.

            @[simp]
            theorem PreTrigger.functional_term_for_var.injEq {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg : PreTrigger sig} {i1 i2 : Nat} {lt1 : i1 < trg.rule.head.length} {lt2 : i2 < trg.rule.head.length} {v1 : sig.V} {v1_mem : v1 trg.rule.existential_vars_for_head_disjunct i1 lt1} {v2 : sig.V} {v2_mem : v2 trg.rule.existential_vars_for_head_disjunct i2 lt2} :
            trg.functional_term_for_var i1 lt1 v1 v1_mem = trg.functional_term_for_var i2 lt2 v2 v2_mem i1 = i2 v1 = v2
            @[simp]

            Applying a trigger to an existential variable, yields exactly the Skolem function term from the shortcup definition functional_term_for_var.

            For existential variables, applying the trigger is injective.

            Applying the trigger to non-frontier variables yields a term that cannot possibly be in the mapped frontier. In other words, terms for existential variables are fresh (although freshness entails more than that).

            @[reducible, inline]
            abbrev PreTrigger.apply_to_function_free_atom {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] (trg : PreTrigger sig) (i : Nat) (lt : i < trg.rule.head.length) (atom : FunctionFreeAtom sig) :
            Fact sig

            We lift the trigger application from VarOrConst to FunctionFreeAtom.

            Equations
            Instances For
              @[implicit_reducible]
              def PreTrigger.mapped_body {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] (trg : PreTrigger sig) :
              List (Fact sig)

              The body does not feature any existential variables. Therefore, we mapped body merely results from applying the trigger's substitution to the body of its rule.

              Equations
              Instances For
                @[simp]

                The length of the mapped_body is the same as the length of the rule body.

                A term occurs in mapped_body if and only if it is a constant in the rule body of if there exists a variable in the rule body that is mapped to the term by the substitution.

                @[implicit_reducible]
                def PreTrigger.mapped_head {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] (trg : PreTrigger sig) :
                List (List (Fact sig))

                The mapped head is the result of the trigger and is simply the application to all head atoms. This result has a list of result facts for each of the head disjuncts. Note again that existential variables are Skolemized before the trigger's substitution is applied but this is hidden within the previously defined functions.

                Equations
                • One or more equations did not get rendered due to their size.
                Instances For
                  @[simp]

                  The length of the mapped_head is the same as the length of the rule head. That is, the trigger result indeed has one list of facts for each of the head disjuncts.

                  Also for each head disjunct, the number of result facts is equal to the number of atoms in the conjunction.

                  For a fixed head index, we can view the trigger merely as a substitution that internally captures the Skolemization of existential variables. In other words, applying this substitution to the specified head disjunct yields exactly the trigger result for the same disjunct. Viewing the trigger as a substitution can be convenient for theorems and proofs.

                  Equations
                  Instances For
                    @[simp]
                    theorem PreTrigger.apply_subs_for_var_or_const_eq {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] (trg : PreTrigger sig) (i : Nat) (lt : i < trg.rule.head.length) (voc : VarOrConst sig) :

                    Applying the subs_for_mapped_head is the same as applying the trigger on VarOrConst.

                    @[simp]

                    Applying the subs_for_mapped_head is the same as applying the trigger on FunctionFreeAtom.

                    @[simp]

                    Applying the subs_for_mapped_head on the head is exactly the trigger result (mapped_head).

                    The list of fresh terms are the function terms introduced for the existential variables.

                    Equations
                    • One or more equations did not get rendered due to their size.
                    Instances For

                      The functional term produces by an existential variable is one of the fresh terms.

                      theorem PreTrigger.mem_fresh_terms {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg : PreTrigger sig} {i : Nat} {lt : i < trg.rule.head.length} (t : GroundTerm sig) :
                      t trg.fresh_terms_for_head_disjunct i lt (v : sig.V), (v_mem : v trg.rule.existential_vars_for_head_disjunct i lt), t = GroundTerm.func { rule := trg.rule, headIdx := i, headIdx_lt := lt, v := v, v_mem := v_mem } trg.mapped_frontier

                      This theorem unfolds some of the internal definitions of fresh_terms_for_head_disjunct.

                      theorem PreTrigger.term_functional_of_mem_fresh_terms {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg : PreTrigger sig} {i : Nat} {lt : i < trg.rule.head.length} (t : GroundTerm sig) :
                      t trg.fresh_terms_for_head_disjunct i lt (func : SkolemFS sig), (ts : List (GroundTerm sig)), (arity_ok : ts.length = func.arity), t = GroundTerm.func func ts arity_ok

                      Fresh terms are always functional.

                      Constants can never be fresh.

                      Mappings of frontier variables can never be fresh.

                      def PreTrigger.existential_var_for_fresh_term {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] (trg : PreTrigger sig) (i : Nat) (lt : i < trg.rule.head.length) (t : GroundTerm sig) (t_mem : t trg.fresh_terms_for_head_disjunct i lt) :
                      sig.V

                      For a given fresh term, we can obtain the existential variable that introduced it.

                      Equations
                      Instances For
                        @[simp]

                        Indeed getting the existential variable for a functional term introduced for a variable yields exactly this variable (as expected).

                        def PreTrigger.atom_for_result_fact {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] (trg : PreTrigger sig) {f : Fact sig} (i : Nat) (lt : i < trg.rule.head.length) (f_mem : f trg.mapped_head[i]) :

                        For a fact in the trigger result, we can obtain the head atom that yields the fact.

                        Equations
                        Instances For
                          @[simp]
                          theorem PreTrigger.apply_on_atom_for_result_fact_is_fact {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] (trg : PreTrigger sig) {f : Fact sig} (i : Nat) (lt : i < trg.rule.head.length) (f_mem : f trg.mapped_head[i]) :

                          Applying the trigger on the atom from atom_for_result_fact indeed yields the correct fact.

                          theorem PreTrigger.atom_for_result_fact_mem_head {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg : PreTrigger sig} {f : Fact sig} {i : Nat} {lt : i < trg.rule.head.length} {f_mem : f trg.mapped_head[i]} :
                          trg.atom_for_result_fact i lt f_mem trg.rule.head[i]

                          The atom from atom_for_result_fact occurs in the correct rule head disjunct.

                          def PreTrigger.var_or_const_for_result_term {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] (trg : PreTrigger sig) {f : Fact sig} {t : GroundTerm sig} (i : Nat) (lt : i < trg.rule.head.length) (f_mem : f trg.mapped_head[i]) (t_mem : t f.terms) :

                          For any term in the result (not just fresh ones), we can obtain the corresponding VarOrConst from the rule.

                          Equations
                          Instances For
                            @[simp]
                            theorem PreTrigger.apply_on_var_or_const_for_result_term_is_term {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] (trg : PreTrigger sig) {f : Fact sig} {t : GroundTerm sig} (i : Nat) (lt : i < trg.rule.head.length) (f_mem : f trg.mapped_head[i]) (t_mem : t f.terms) :
                            trg.apply_to_var_or_const i lt (trg.var_or_const_for_result_term i lt f_mem t_mem) = t

                            Applying the trigger on the VarOrConst from var_or_const_for_result_term indeed yields the correct term.

                            theorem PreTrigger.var_or_const_for_result_term_mem_atom_for_result_fact {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg : PreTrigger sig} {f : Fact sig} {t : GroundTerm sig} {i : Nat} {lt : i < trg.rule.head.length} {f_mem : f trg.mapped_head[i]} {t_mem : t f.terms} :
                            trg.var_or_const_for_result_term i lt f_mem t_mem (trg.atom_for_result_fact i lt f_mem).terms

                            For a term in a result fact, var_or_const_for_result_term returns a VarOrConst that is in atom_for_result_fact.

                            theorem PreTrigger.var_or_const_for_result_term_mem_terms_head {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg : PreTrigger sig} {f : Fact sig} {t : GroundTerm sig} {i : Nat} {lt : i < trg.rule.head.length} {f_mem : f trg.mapped_head[i]} {t_mem : t f.terms} :

                            For a term in a result fact, var_or_const_for_result_term occurs in the correct head disjunct.

                            A term occurs in the trigger result for a given head index if and only if one of the following three cases holds. 1. The term is a constant in the head. 2. The term results from mapping a frontier variable in the head. 3. The term is a fresh term of the head.

                            The constants in the trigger result are a subset of the constants from the mapped frontier terms and the constants that occur directly in the rule head.

                            def PreTrigger.loaded {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] (trg : PreTrigger sig) (fs : FactSet sig) :

                            The trigger is loaded for a FactSet if its mapped body occurs in the fact set.

                            Equations
                            Instances For
                              theorem PreTrigger.term_mapping_preserves_loadedness {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] (trg : PreTrigger sig) (fs : FactSet sig) (h : GroundTermMapping sig) (h_id : h.isIdOnConstants) :
                              trg.loaded fs{ rule := trg.rule, subs := h trg.subs }.loaded (h.applyFactSet fs)

                              Applying a GroundTermMapping that is the id on constants after the trigger substitution and on the fact set preserves loadedness.

                              def PreTrigger.satisfied_for_disj {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] (trg : PreTrigger sig) (fs : FactSet sig) (i : Nat) (lt : i < trg.rule.head.length) :

                              A trigger head is satisfied for a FactSet if there exists a substitution that agrees with the trigger substitution on all frontier variable such that the mapping of the head occurs in the fact set. This corresponds to FOL semantics. It is important to note here that a trigger being satisfied in this sense does not necessarily mean that it is obsolete! Obsolescence might be defined almost arbitrarily and for example in the Skolem chase, a satisfied trigger is often not obsolete. However, for the restricted (aka. standard) chase, obsolescence is defined via satisfaction.

                              Equations
                              Instances For
                                theorem PreTrigger.satisfied_for_disj_of_mapped_head_contained {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] (trg : PreTrigger sig) (fs : FactSet sig) (i : Nat) (lt : i < trg.rule.head.length) :
                                trg.mapped_head[i].toSet fstrg.satisfied_for_disj fs i lt

                                If the exact trigger result is contained in the fact set, then the trigger is also satisfied.

                                def PreTrigger.satisfied {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] (trg : PreTrigger sig) (fs : FactSet sig) :

                                The trigger is satisfied if it is satisfied for some head. Note that this checks out with FOL semantics since the heads are part of a big disjunction.

                                Equations
                                Instances For
                                  theorem PreTrigger.satisfied_of_satisfied_subset {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg : PreTrigger sig} {fs fs2 : FactSet sig} (sub : fs fs2) :
                                  trg.satisfied fstrg.satisfied fs2

                                  If a trigger is satisfied, then it is also satisfied on all supersets.

                                  def PreTrigger.equiv {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] (trg1 trg2 : PreTrigger sig) :

                                  We consider two trigger to be equivalent if they share the same rule and their substitutions agree on the frontier variables. This entails that they have the same result.

                                  Equations
                                  Instances For
                                    theorem PreTrigger.equiv_refl {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg : PreTrigger sig} :
                                    trg.equiv trg

                                    Trigger equivalence is reflexive.

                                    theorem PreTrigger.equiv_symm {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg1 trg2 : PreTrigger sig} :
                                    trg1.equiv trg2trg2.equiv trg1

                                    Trigger equivalence is symmetric.

                                    theorem PreTrigger.equiv_trans {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg1 trg2 trg3 : PreTrigger sig} :
                                    trg1.equiv trg2trg2.equiv trg3trg1.equiv trg3

                                    Trigger equivalence is transitive.

                                    theorem PreTrigger.mapped_frontier_eq_of_equiv {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg1 trg2 : PreTrigger sig} (equiv : trg1.equiv trg2) :

                                    Equivalent triggers have the same mapped_frontier.

                                    theorem PreTrigger.equiv_of_rule_eq_of_mapped_frontier_equiv {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg1 trg2 : PreTrigger sig} (rule_eq : trg1.rule = trg2.rule) (mapped_front_eq : trg1.mapped_frontier = trg2.mapped_frontier) :
                                    trg1.equiv trg2

                                    Two triggers with same rule and same mapped_frontier are equivalent.

                                    def PreTrigger.strong_equiv {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] (trg1 trg2 : PreTrigger sig) :

                                    We consider two trigger to be strongly equivalent if they share the same rule and their substitutions agree not only on the frontier variables but on all body variables.

                                    Equations
                                    Instances For
                                      theorem PreTrigger.strong_equiv_refl {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg : PreTrigger sig} :
                                      trg.strong_equiv trg

                                      Strong equivalence is reflexive.

                                      theorem PreTrigger.strong_equiv_symm {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg1 trg2 : PreTrigger sig} :
                                      trg1.strong_equiv trg2trg2.strong_equiv trg1

                                      Strong equivalence is symmetric.

                                      theorem PreTrigger.strong_equiv_trans {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg1 trg2 trg3 : PreTrigger sig} :
                                      trg1.strong_equiv trg2trg2.strong_equiv trg3trg1.strong_equiv trg3

                                      strong equivalence is transitive.

                                      theorem PreTrigger.equiv_of_strong_equiv {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg1 trg2 : PreTrigger sig} :
                                      trg1.strong_equiv trg2trg1.equiv trg2

                                      Strong equivalence implies equivalence.

                                      Applying the substitutions of strongly equivalent triggers to a body atom yields the same result. (This is not necessarily true if the triggers are only equivalent.)

                                      theorem PreTrigger.mapped_body_eq_of_strong_equiv {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg1 trg2 : PreTrigger sig} :
                                      trg1.strong_equiv trg2trg1.mapped_body = trg2.mapped_body

                                      Strongly equivalent triggers have the same mapped_body. Again, this is not necessarily true for triggers that are only equivalent.

                                      theorem PreTrigger.apply_to_function_free_atom_eq_of_equiv {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg1 trg2 : PreTrigger sig} (equiv : trg1.equiv trg2) (i : Nat) (lt : i < trg1.rule.head.length) (a : FunctionFreeAtom sig) :

                                      Applying two equivalent triggers to the same (head) atom yields the same result.

                                      theorem PreTrigger.result_eq_of_equiv {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg1 trg2 : PreTrigger sig} :
                                      trg1.equiv trg2trg1.mapped_head = trg2.mapped_head

                                      As intended, equivalent triggers have the same result.

                                      theorem PreTrigger.satisfied_preserved_of_equiv {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg1 trg2 : PreTrigger sig} :
                                      trg1.equiv trg2∀ {fs : FactSet sig}, trg1.satisfied fs trg2.satisfied fs

                                      Equivalent triggers are satisfied on the same fact sets.

                                      theorem PreTrigger.equiv_of_term_mem_fresh_terms_for_head_disjunct {sig : Signature} [DecidableEq sig.P] [DecidableEq sig.C] [DecidableEq sig.V] {trg1 trg2 : PreTrigger sig} {i1 i2 : Nat} {lt1 : i1 < trg1.rule.head.length} {lt2 : i2 < trg2.rule.head.length} {t : GroundTerm sig} :
                                      t trg1.fresh_terms_for_head_disjunct i1 lt1t trg2.fresh_terms_for_head_disjunct i2 lt2trg1.equiv trg2 i1 = i2

                                      If a ground term is fresh in two PreTriggers for two head indices, then actually these two PreTriggers (and indices) need to be equivalent (the same)! Why is this the case? Fresh terms are always Skolem function terms. Therefore they contain a rule, which needs to be the same for both triggers. The head indices are also part of the functional term so a similar argument can be made to show that these need to be equal. To see why the triggers also need to agree on their frontier mapping, we only need to remind ourselves that the Skolem term contains all the mapped frontier terms as arguments.